diff --git a/.editorconfig b/.editorconfig index 8e3c490a8..8740487fd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,14 +10,8 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.rs] -max_line_length = 100 - [*.yml] ident_size = 2 -[*.hir] -indent_size = 4 - [*.toml] -indent_size = 2 +indent_size = 4 diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml deleted file mode 100644 index 2e774868d..000000000 --- a/.github/workflows/book.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: book - -on: - push: - branches: - - main - - next - -jobs: - publish_docs: - name: publish docs - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install Rust - run: | - rustup update --no-self-update - rustc --version - - name: Install cargo-make - run: | - cargo install cargo-make --force - - name: Build - run: | - cargo make docs - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/docs/site diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 000000000..72d4c28d3 --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,46 @@ +name: build-docs + +# Limits workflow concurrency to only the latest commit in the PR. +concurrency: + group: "${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +on: + push: + branches: [main, next] + paths: + - "docs/external/**" + - ".github/workflows/build-docs.yml" + pull_request: + types: [opened, reopened, synchronize] + paths: + - "docs/external/**" + - ".github/workflows/build-docs.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-docs: + name: Build Documentation + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + cache-dependency-path: docs/external/package-lock.json + + - name: Install dependencies + working-directory: ./docs/external + run: npm ci + + - name: Build documentation + working-directory: ./docs/external + run: npm run build:dev diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d3ef949d..21673a9c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,26 +1,34 @@ name: CI +permissions: + contents: read + on: push: branches: - main - next paths-ignore: - - "*.md" - - "*.txt" - - "docs" + - "**.md" + - "**.txt" + - "docs/**" pull_request: paths-ignore: - - "*.md" - - "*.txt" - - "docs" + - "**.md" + - "**.txt" + - "docs/**" + +env: + # Set the new value when the cache grows too much and we hit the runner's disk space limit + # via https://github.com/rust-lang/docs.rs/pull/2433 + RUST_CACHE_KEY: rust-cache-20250528 jobs: lint: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust run: | rustup update --no-self-update @@ -30,6 +38,7 @@ jobs: with: # Use a common cache for the basic compilation/formatter/clippy checks shared-key: ${{ github.workflow }}-shared + prefix-key: ${{ env.RUST_CACHE_KEY }} save-if: ${{ github.ref == 'refs/heads/next' }} - name: Install cargo-make run: | @@ -48,7 +57,7 @@ jobs: # But run this check even if the lint check failed if: ${{ always() }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust run: | rustup update --no-self-update @@ -58,6 +67,7 @@ jobs: with: # Use a common cache for the basic compilation/formatter/clippy checks shared-key: ${{ github.workflow }}-shared + prefix-key: ${{ env.RUST_CACHE_KEY }} # But do not save this cache, just use it save-if: false - name: Install cargo-make @@ -73,7 +83,7 @@ jobs: name: midenc unit tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust run: | rustup update --no-self-update @@ -84,6 +94,7 @@ jobs: # NOTE: We use a different cache for the tests, so they can be run in parallel, but we # also share the cache for the tests for efficiency shared-key: ${{ github.workflow }}-shared-tests + prefix-key: ${{ env.RUST_CACHE_KEY }} save-if: ${{ github.ref == 'refs/heads/next' }} - name: Install cargo-make run: | @@ -98,7 +109,39 @@ jobs: cargo make check --tests - name: Test run: | - cargo make test -E 'not (package(miden-integration-tests) or package(cargo-miden))' + cargo make test -E 'not (package(miden-integration-tests) or package(miden-integration-node-tests) or package(cargo-miden))' + + check_release: + name: release checks + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Install Rust + run: | + rustup update --no-self-update + rustc --version + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + with: + # NOTE: We use a different cache for the these release checks + shared-key: ${{ github.workflow }}-shared-release-checks + prefix-key: ${{ env.RUST_CACHE_KEY }} + save-if: ${{ github.ref == 'refs/heads/next' }} + - name: Install cargo-make + run: | + if ! cargo make --version 2>/dev/null; then + cargo install cargo-make --force + fi + - name: Check(release) + # We run `check` with `--release` to check the release build (without + # `debug_assertions`, etc.) + run: | + cargo make check --release --tests + - name: Check each member (release) + # To uncover any compilation errors hidden by the workspace feature + # unification and avoid surprises on publishing the crates. + run: | + cargo make check-each --release --all-features midenc_integration_tests: name: midenc integration tests @@ -107,7 +150,7 @@ jobs: # benefit that we can re-use the cache from the unit test job for all integration tests needs: [unit_tests] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust run: | rustup update --no-self-update @@ -116,6 +159,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: shared-key: ${{ github.workflow }}-shared-tests + prefix-key: ${{ env.RUST_CACHE_KEY }} # Do not persist the cache, leave that to the unit tests, we just use the cache here save-if: false - name: Install cargo-make @@ -134,7 +178,7 @@ jobs: # benefit that we can re-use the cache from the unit test job for all integration tests needs: [unit_tests] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Rust run: | rustup update --no-self-update @@ -143,6 +187,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: shared-key: ${{ github.workflow }}-shared-tests + prefix-key: ${{ env.RUST_CACHE_KEY }} # Do not persist the cache, leave that to the unit tests, we just use the cache here save-if: false - name: Install cargo-make @@ -153,3 +198,58 @@ jobs: - name: Test run: | cargo make test -E 'package(cargo-miden)' + + miden_integration_node_tests: + name: miden integration node tests + runs-on: ubuntu-latest + # We only want to run the integration tests if the unit tests pass, and that has the added + # benefit that we can re-use the cache from the unit test job for all integration tests + needs: [unit_tests] + steps: + - uses: actions/checkout@v5 + - name: Install Rust + run: | + rustup update --no-self-update + rustc --version + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + with: + shared-key: ${{ github.workflow }}-shared-tests + prefix-key: ${{ env.RUST_CACHE_KEY }} + # Do not persist the cache, leave that to the unit tests, we just use the cache here + save-if: false + - name: Install cargo-make + run: | + if ! cargo make --version 2>/dev/null; then + cargo install cargo-make --force + fi + - name: Test + run: | + cargo make test -E 'package(miden-integration-node-tests)' + + cargo_publish_dry_run: + name: cargo publish dry run + runs-on: ubuntu-latest + needs: [unit_tests] + steps: + - uses: actions/checkout@v5 + - name: Install Rust + run: | + rustup update --no-self-update + rustc --version + - name: Cache Cargo + uses: Swatinem/rust-cache@v2 + with: + # Use a common cache for the basic compilation/formatter/clippy checks + shared-key: ${{ github.workflow }}-shared + prefix-key: ${{ env.RUST_CACHE_KEY }} + save-if: ${{ github.ref == 'refs/heads/next' }} + - name: Install cargo-make + run: | + if ! cargo make --version 2>/dev/null; then + cargo install cargo-make --force + fi + - name: cargo publish dry run + # Simulate publishing to uncover any build errors when each crate folder is built in isolation + run: | + cargo make publish-dry-run diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 478f30866..12f33972d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,41 +1,123 @@ -# Runs `release-plz release` only after the release PR (starts with `release-plz-`) -# is merged to the next branch. See `release_always = false` in `release-plz.toml` -# Publishes any unpublished crates when. -# Does nothing if all crates are already published (i.e. have their versions on crates.io). -# Does not create/update release PRs. -# The crate version bumping and changelog generation is done via the `release-plz update` CLI command. -# Then manually create a release PR(starts with `release-plz-`) with the proposed changes and -# when the PR is merged this action will publish the crates. -# See CONTRIBUTING.md for more details. - -name: release-plz +# Our release workflow is as follows: +# +# 1. Merging to `main` will create a new release PR containing any unreleased changes +# 2. The release PR gets merged to `main` when we are ready to publish the release +# 3. The crates are published to crates.io, a new git tag is created, as well as a GitHub release +# 4. A job is run to pre-build the executable for our supported targets and upload them to the +# release. +name: release on: push: branches: - - next + - main jobs: - release-plz: - name: release-plz + publish: + name: publish any unpublished packages runs-on: ubuntu-latest + if: ${{ github.repository_owner == '0xMiden' }} + permissions: + contents: read + outputs: + releases: ${{ steps.publish.outputs.releases }} + releases_created: ${{ steps.publish.outputs.releases_created }} steps: - - uses: actions/checkout@v4 - - name: Install Rust + - &checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + - &install-rust + name: Install Rust run: | rustup update --no-self-update rustc --version - - name: Cache Cargo - uses: Swatinem/rust-cache@v2 - with: - save-if: ${{ github.ref == 'refs/heads/next' }} - name: Publish - uses: MarcoIeni/release-plz-action@v0.5 + id: publish + uses: release-plz/action@v0.5 with: - # Only run the `release` command that publishes any unpublished crates. command: release - # `manifest_path` is omitted because it defaults to the root directory - # manifest_path: "..." + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + + upload-artifacts: + name: upload pre-built midenc executable artifacts + runs-on: ubuntu-latest + needs: publish + if: ${{ github.repository_owner == '0xMiden' && needs.publish.outputs.releases_created == 'true' }} + permissions: + contents: write + strategy: + matrix: + target: [aarch64-apple-darwin, x86_64-unknown-linux-gnu] + steps: + - *checkout + - *install-rust + - name: Determine midenc release tag + id: midenc-release + env: + RELEASES: ${{ needs.publish.outputs.releases }} + run: | + set -eo pipefail + echo "RELEASES:" + echo "==================" + echo "${RELEASES}" | jq -rM + echo "==================" + release_tag=$(echo "${RELEASES}" | jq -r '.[] | select(.package_name == "midenc" or .package_name == "cargo-miden") | .tag' | head -n1) + if [ -z "${release_tag}" ] || [ "${release_tag}" = "null" ]; then + echo "midenc or cargo-miden crate was not released in this run. Skipping artifact upload." + echo "release_tag=" >> "${GITHUB_OUTPUT}" + exit 0 + fi + echo "release_tag=${release_tag}" >> "${GITHUB_OUTPUT}" + - name: Add target + if: ${{ steps.midenc-release.outputs.release_tag != '' }} + run: | + rustup target add ${{ matrix.target }} + - name: Install cargo-make + if: ${{ steps.midenc-release.outputs.release_tag != '' }} + run: | + if ! cargo make --version 2>/dev/null; then + cargo install cargo-make --force + fi + - name: build binaries + if: ${{ steps.midenc-release.outputs.release_tag != '' }} + run: | + set -e + ARGS="--release --target ${{ matrix.target }}" + cargo make --profile production midenc ${ARGS} + cargo make --profile production cargo-miden ${ARGS} + - name: upload + if: ${{ steps.midenc-release.outputs.release_tag != '' }} + env: + RELEASE_TAG: ${{ steps.midenc-release.outputs.release_tag }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -e + mv bin/midenc midenc-${{ matrix.target }} + gh release upload ${RELEASE_TAG} midenc-${{ matrix.target }} + mv bin/cargo-miden cargo-miden-${{ matrix.target }} + gh release upload ${RELEASE_TAG} cargo-miden-${{ matrix.target }} + + release: + name: prepare the next release + runs-on: ubuntu-latest + if: ${{ github.repository_owner == '0xMiden' }} + permissions: + contents: write + pull-requests: write + concurrency: + group: release-plz-${{ github.ref }} + cancel-in-progress: false + steps: + - *checkout + - *install-rust + - name: Create release PR + uses: release-plz/action@v0.5 + with: + command: release-pr env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/release_old.yml b/.github/workflows/release_old.yml new file mode 100644 index 000000000..36fc3852d --- /dev/null +++ b/.github/workflows/release_old.yml @@ -0,0 +1,36 @@ +# The old release process left for releasing the Miden SDK until it moves to a separate repo. +# +# Runs `release-plz release` only after the release PR (starts with `release-plz-`) +# is merged to the next branch. Publishes any unpublished crates. +# Does nothing if all crates are already published (i.e. have their versions on crates.io). +# Does not create/update release PRs. +# +# See CONTRIBUTING.md for more details. + +name: release-miden-sdk + +on: + push: + branches: + - next + +jobs: + publish: + name: publish any unpublished packages + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v5 + - name: Install Rust + run: | + rustup update --no-self-update + rustc --version + - name: Publish + uses: release-plz/action@v0.5 + with: + # Only run the `release` command that publishes any unpublished crates. + command: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} diff --git a/.github/workflows/trigger-deploy-docs.yml b/.github/workflows/trigger-deploy-docs.yml new file mode 100644 index 000000000..78b17ecfb --- /dev/null +++ b/.github/workflows/trigger-deploy-docs.yml @@ -0,0 +1,23 @@ +name: Trigger Aggregator Docs Rebuild + +on: + push: + branches: [next] + paths: + - "docs/external/**" + +jobs: + notify: + runs-on: ubuntu-latest + + permissions: + contents: read + + steps: + - name: Send repository_dispatch to aggregator + uses: peter-evans/repository-dispatch@a628c95fd17070f003ea24579a56e6bc89b25766 + with: + # PAT (Personal Access Token) that grants permission to trigger the rebuild workflow at the docs repository + token: ${{ secrets.DOCS_REPO_TOKEN }} + repository: ${{ vars.DOCS_AGGREGATOR_REPO }} + event-type: rebuild diff --git a/.gitignore b/.gitignore index bfe20b171..80e438e95 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ bin/litcheck .crates2.json # Docs platform ignores -.vscode +.vscode/* +!.vscode/tasks.json +!.vscode/launch.json .code .idea site/ @@ -14,4 +16,6 @@ env/ *.out node_modules/ *DS_Store -*.iml \ No newline at end of file +*.iml +book/ + diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..fb2af45c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,218 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_codegen_masm2'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-codegen-masm"], + "filter": { + "name": "midenc_codegen_masm", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_dialect_hir'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-dialect-hir"], + "filter": { + "name": "midenc_dialect_hir", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_hir_symbol'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-hir-symbol"], + "filter": { + "name": "midenc_hir_symbol", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_hir'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-hir"], + "filter": { + "name": "midenc_hir", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_hir_type'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-hir-type"], + "filter": { + "name": "midenc_hir_type", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_session'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-session"], + "filter": { + "name": "midenc_session", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_frontend_wasm2'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-frontend-wasm2"], + "filter": { + "name": "midenc_frontend_wasm2", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'midenc'", + "cargo": { + "args": ["build", "--bin=midenc", "--package=midenc"], + "filter": { + "name": "midenc", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'midenc'", + "cargo": { + "args": ["test", "--no-run", "--bin=midenc", "--package=midenc"], + "filter": { + "name": "midenc", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_driver'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-driver"], + "filter": { + "name": "midenc_driver", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_compile'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-compile"], + "filter": { + "name": "midenc_compile", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'midenc_debug'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=midenc-debug"], + "filter": { + "name": "midenc_debug", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'cargo-miden'", + "cargo": { + "args": ["build", "--bin=cargo-miden", "--package=cargo-miden"], + "filter": { + "name": "cargo-miden", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in library 'miden_integration_tests'", + "cargo": { + "args": ["test", "--no-run", "--lib", "--package=miden-integration-tests"], + "filter": { + "name": "miden_integration_tests", + "kind": "lib" + } + }, + "args": [], + "cwd": "${workspaceFolder}", + "sourceLanguages": ["rust"] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..2448e0a01 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,27 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "cargo", + "command": "check", + "problemMatcher": [ + "$rustc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "label": "rust: cargo check" + }, + { + "type": "cargo", + "command": "make", + "args": ["test"], + "problemMatcher": [ + "$rustc" + ], + "group": "test", + "label": "rust: cargo test" + } + ] +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7c5dd3904..dfea75e1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,17 +4,18 @@ TBD ## Release Process -### Prerequisites +### Release of the Miden Compiler -Install `release-plz` CLI tool following the instructions [here](https://release-plz.ieni.dev/docs/usage/installation) -Install `cargo-semver-checks` CLI tool [here](https://github.com/obi1kenobi/cargo-semver-checks#installation) to use as an extra check in `release-plz` and bump the major versions on semver violations. +1. Merging to `main` will create a new release PR containing any unreleased changes. +2. Optional. Change the proposed crate version, CHANGELOG edits. +3. The release PR gets merged to `main` when we are ready to publish the release. +4. The crates are published to crates.io, a new git tag is created, as well as a GitHub release +5. A job is run to pre-build the executable for our supported targets and upload them to the created Github release. +6. Merge the `main` branch back to the `next` branch. -### Release of the Miden Compiler and Miden SDK crates +### Release of the Miden SDK crates -The release process for the Miden Compiler and Miden SDK is managed using the `release-plz` tool. The following steps outline the process for creating a new release: - -1. Run `release-plz update` in the repo root folder to update the crates versions and generate changelogs. -2. Create a release PR naming the branch with the `release-plz-` suffix (its important to use this suffix to trigger the crate publishing on CI in step 4). -3. Review the changes in the release PR, commit edits if needed and merge it into the main branch. +1. Create a release PR against the `next` branch naming the branch with the `release-plz-` prefix (its important to use this prefix to trigger the crate publishing on CI in the later step). +2. Manually bump ALL the SDK crate versions and update the `sdk/sdk/CHANGELOG.md` +3. Review the changes in the release PR, and merge it into the `next` branch. 4. The CI will automatically run `release-plz release` after the release PR is merged to publish the new versions to crates.io. -5. Set a git tag for the published crates to mark the release. diff --git a/Cargo.lock b/Cargo.lock index 1690530e1..58e537b10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -8,15 +8,15 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" dependencies = [ - "lazy_static 1.5.0", + "lazy_static", "regex", ] [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "cpp_demangle", "fallible-iterator", @@ -30,30 +30,19 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - -[[package]] -name = "aes" -version = "0.8.4" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "const-random", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -79,9 +68,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -99,19 +88,16 @@ dependencies = [ ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "anes" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -124,43 +110,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell_polyfill", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.88" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "anymap2" @@ -170,15 +157,9 @@ checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" [[package]] name = "arrayref" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" - -[[package]] -name = "arrayvec" -version = "0.5.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -197,180 +178,22 @@ dependencies = [ [[package]] name = "ascii-canvas" -version = "3.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ "term", ] -[[package]] -name = "async-broadcast" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b" -dependencies = [ - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.1.1", - "futures-lite 2.3.0", - "slab", -] - -[[package]] -name = "async-fs" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279cf904654eeebfa37ac9bb1598880884924aab82e290aa65c9e77a0e142e06" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "blocking", - "futures-lite 1.13.0", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" -dependencies = [ - "async-lock 3.4.0", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.3.0", - "parking", - "polling 3.7.3", - "rustix 0.38.37", - "slab", - "tracing", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener 5.3.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-process" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88" -dependencies = [ - "async-io 1.13.0", - "async-lock 2.8.0", - "async-signal", - "blocking", - "cfg-if", - "event-listener 3.1.0", - "futures-lite 1.13.0", - "rustix 0.38.37", - "windows-sys 0.48.0", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - -[[package]] -name = "async-signal" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637e00349800c0bdf8bfc21ebbc0b6524abea702b0da4168ac00d070d0c0b9f3" -dependencies = [ - "async-io 2.3.4", - "async-lock 3.4.0", - "atomic-waker", - "cfg-if", - "futures-core", - "futures-io", - "rustix 0.38.37", - "signal-hook-registry", - "slab", - "windows-sys 0.59.0", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" -version = "0.1.82" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -379,22 +202,11 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "auth-git2" -version = "0.5.4" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51bd0e4592409df8631ca807716dc1e5caafae5d01ce0157c966c71c7e49c3c" +checksum = "4888bf91cce63baf1670512d0f12b5d636179a4abbad6504812ac8ab124b3efe" dependencies = [ "dirs", "git2", @@ -403,15 +215,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -431,24 +243,6 @@ dependencies = [ "backtrace", ] -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -456,10 +250,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64ct" -version = "1.6.0" +name = "bech32" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "beef" @@ -473,35 +267,23 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "serde 1.0.210", + "serde", ] [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitcode" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee1bce7608560cd4bf0296a4262d0dbf13e6bcec5ff2105724c8ab88cc7fc784" -dependencies = [ - "arrayvec 0.7.6", - "bytemuck", - "glam", - "serde 1.0.210", -] +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -511,85 +293,69 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] -name = "bitmaps" -version = "2.1.0" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ - "typenum", + "funty", + "radium", + "tap", + "wyz", ] [[package]] name = "blake3" -version = "1.5.4" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", - "arrayvec 0.7.6", + "arrayvec", "cc", "cfg-if", "constant_time_eq", ] [[package]] -name = "block-buffer" -version = "0.10.4" +name = "blink-alloc" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +checksum = "e669f146bb8b2327006ed94c69cf78c8ec81c100192564654230a40b4f091d82" dependencies = [ - "generic-array", + "allocator-api2", ] [[package]] -name = "block-padding" -version = "0.3.3" +name = "block-buffer" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] -[[package]] -name = "blocking" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" -dependencies = [ - "async-channel", - "async-task", - "futures-io", - "futures-lite 2.3.0", - "piper", -] - [[package]] name = "bstr" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", - "regex-automata 0.4.7", - "serde 1.0.210", + "regex-automata", + "serde", ] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytemuck" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -599,120 +365,40 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "camino" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" -dependencies = [ - "serde 1.0.210", -] - -[[package]] -name = "cargo-component" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deedd1f20ddb574d9d198d979499f628f2865718190830523cb329e264ef1469" -dependencies = [ - "anyhow", - "bytes", - "cargo-component-core", - "cargo-config2", - "cargo_metadata", - "clap", - "futures", - "heck 0.5.0", - "indexmap 2.5.0", - "libc", - "log", - "p256", - "parse_arg", - "pretty_env_logger", - "rand_core", - "rpassword", - "semver 1.0.23", - "serde 1.0.210", - "serde_json", - "shell-escape", - "tempfile", - "tokio", - "tokio-util", - "toml_edit 0.22.20", - "url", - "wasi-preview1-component-adapter-provider", - "wasm-metadata 0.215.0", - "wasm-pkg-client", - "wasmparser 0.215.0", - "which", - "wit-bindgen-core", - "wit-bindgen-rust", - "wit-component 0.215.0", - "wit-parser 0.215.0", -] - -[[package]] -name = "cargo-component-core" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2942b6673d038ed6da6c7a5964372c540253dee1508c55b0f5f7614fb2c6c371" -dependencies = [ - "anyhow", - "clap", - "dirs", - "futures", - "indexmap 2.5.0", - "libc", - "log", - "owo-colors", - "semver 1.0.23", - "serde 1.0.210", - "tokio", - "tokio-util", - "toml_edit 0.22.20", - "unicode-width", - "url", - "wasm-pkg-client", - "windows-sys 0.52.0", - "wit-component 0.215.0", - "wit-parser 0.215.0", -] - -[[package]] -name = "cargo-config2" -version = "0.1.29" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1124054becb9262cc15c5e96e82f0d782f2aed3a3034d1f71a6385a6fa9e9595" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ - "home", - "serde 1.0.210", - "serde_derive", - "toml_edit 0.22.20", + "serde", ] [[package]] name = "cargo-generate" -version = "0.22.0" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a921a6f140ee9981c471b4ffdfc53372b8bc3fe417f83e87c124c6018ab6929" +checksum = "85a4d22feb993dd24d64547c18f4603c082c9bf10299b46cd372c5c258d5fdb7" dependencies = [ "anstyle", "anyhow", "auth-git2", + "cargo-util-schemas", "clap", - "console", + "console 0.16.0", "dialoguer", - "env_logger 0.11.5", + "env_logger", "fs-err", "git2", "gix-config", - "heck 0.5.0", + "heck", "home", "ignore", - "indexmap 2.5.0", + "indexmap", "indicatif", "liquid", "liquid-core", @@ -721,53 +407,54 @@ dependencies = [ "log", "names", "paste", - "path-absolutize", "regex", "remove_dir_all", "rhai", "sanitize-filename", - "semver 1.0.23", - "serde 1.0.210", + "semver 1.0.26", + "serde", "tempfile", - "thiserror", + "thiserror 2.0.12", "time", - "toml 0.8.19", + "toml 0.8.23", "walkdir", ] [[package]] name = "cargo-miden" -version = "0.0.7" +version = "0.4.1" dependencies = [ "anyhow", - "cargo-component", - "cargo-component-core", "cargo-generate", "cargo_metadata", "clap", - "env_logger 0.11.5", + "env_logger", "log", + "miden-mast-package", "midenc-compile", "midenc-session", - "parse_arg", + "parse_arg 0.1.6", "path-absolutize", - "semver 1.0.23", + "semver 1.0.26", + "serde", + "serde_json", + "toml_edit 0.23.4", ] [[package]] name = "cargo-platform" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" dependencies = [ - "serde 1.0.210", + "serde", ] [[package]] name = "cargo-util" -version = "0.2.14" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc680c90073156fb5280c0c0127b779eef1f6292e41f7d6621acba3041e81c7d" +checksum = "c95ec8b2485b20aed818bd7460f8eecc6c87c35c84191b353a3aba9aa1736c36" dependencies = [ "anyhow", "core-foundation", @@ -783,21 +470,37 @@ dependencies = [ "tempfile", "tracing", "walkdir", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "cargo-util-schemas" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dc1a6f7b5651af85774ae5a34b4e8be397d9cf4bc063b7e6dbd99a841837830" +dependencies = [ + "semver 1.0.26", + "serde", + "serde-untagged", + "serde-value", + "thiserror 2.0.12", + "toml 0.8.23", + "unicode-xid", + "url", ] [[package]] name = "cargo_metadata" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.23", - "serde 1.0.210", + "semver 1.0.26", + "serde", "serde_json", - "thiserror", + "thiserror 2.0.12", ] [[package]] @@ -807,28 +510,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" [[package]] -name = "castaway" -version = "0.2.3" +name = "cast" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" -dependencies = [ - "rustversion", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] -name = "cbc" -version = "0.1.2" +name = "castaway" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" dependencies = [ - "cipher", + "rustversion", ] [[package]] name = "cc" -version = "1.1.18" +version = "1.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", @@ -837,9 +537,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -849,81 +549,97 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", - "num-traits 0.2.19", - "serde 1.0.210", + "num-traits", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] -name = "cipher" -version = "0.4.4" +name = "ciborium" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" dependencies = [ - "crypto-common", - "inout", + "ciborium-io", + "ciborium-ll", + "serde", ] [[package]] -name = "clap" -version = "4.5.17" +name = "ciborium-io" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" -dependencies = [ - "clap_builder", - "clap_derive", -] +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" [[package]] -name = "clap_builder" -version = "4.5.17" +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", - "terminal_size", + "terminal_size 0.4.2", ] [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "compact_str" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" dependencies = [ "castaway", "cfg-if", @@ -934,59 +650,55 @@ dependencies = [ ] [[package]] -name = "concat-idents" -version = "1.1.5" +name = "compact_str" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" +checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a" dependencies = [ - "quote", - "syn 2.0.77", + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", ] [[package]] -name = "concurrent-queue" -version = "2.5.0" +name = "concat-idents" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +checksum = "f76990911f2267d837d9d0ad060aa63aaad170af40904b29461734c339030d4d" dependencies = [ - "crossbeam-utils", + "quote", + "syn", ] [[package]] -name = "config" -version = "0.11.0" +name = "console" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1b9d958c2b1368a663f05538fc1b5975adce1e19f435acceae987aceeeb369" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ - "lazy_static 1.5.0", - "nom", - "rust-ini", - "serde 1.0.210", - "serde-hjson", - "serde_json", - "toml 0.5.11", - "yaml-rust", + "encode_unicode", + "libc", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.59.0", ] [[package]] name = "console" -version = "0.15.8" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" dependencies = [ "encode_unicode", - "lazy_static 1.5.0", "libc", - "unicode-width", - "windows-sys 0.52.0", + "once_cell", + "unicode-width 0.2.0", + "windows-sys 0.60.2", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-random" version = "0.1.18" @@ -1002,7 +714,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.16", "once_cell", "tiny-keccak", ] @@ -1013,17 +725,11 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -1046,42 +752,78 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] -name = "cranelift-bforest" -version = "0.108.1" +name = "cranelift-bitset" +version = "0.120.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29daf137addc15da6bab6eae2c4a11e274b1d270bf2759508e62f6145e863ef6" -dependencies = [ - "cranelift-entity", -] +checksum = "db7b2ee9eec6ca8a716d900d5264d678fb2c290c58c46c8da7f94ee268175d17" [[package]] name = "cranelift-entity" -version = "0.108.1" +version = "0.120.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eabb8d36b0ca8906bec93c78ea516741cac2d7e6b266fa7b0ffddcc09004990" +checksum = "d75418674520cb400c8772bfd6e11a62736c78fc1b6e418195696841d1bf91f1" +dependencies = [ + "cranelift-bitset", +] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -1098,9 +840,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" @@ -1108,12 +850,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "crossterm_winapi", "futures-core", "mio", "parking_lot", - "rustix 0.38.37", + "rustix 0.38.44", "signal-hook", "signal-hook-mio", "winapi", @@ -1130,21 +872,9 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core", - "subtle", - "zeroize", -] +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -1167,9 +897,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -1177,72 +907,85 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.77", + "syn", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.77", + "syn", ] [[package]] -name = "der" -version = "0.7.9" +name = "deadpool" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "5ed5957ff93768adf7a65ab167a17835c3d2c3c50d084fe305174c112f468e2f" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "deadpool-runtime", + "num_cpus", + "tokio", +] + +[[package]] +name = "deadpool-runtime" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" +dependencies = [ + "tokio", +] + +[[package]] +name = "deadpool-sync" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524bc3df0d57e98ecd022e21ba31166c2625e7d3e5bcc4510efaeeab4abcab04" +dependencies = [ + "deadpool-runtime", ] [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", - "serde 1.0.210", ] [[package]] -name = "derivative" -version = "2.2.0" +name = "derive_more" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "derive_more-impl", ] [[package]] -name = "derive_more" -version = "0.99.18" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "convert_case", "proc-macro2", "quote", - "rustc_version 0.4.1", - "syn 2.0.77", + "syn", ] [[package]] @@ -1251,10 +994,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ - "console", + "console 0.15.11", "shell-words", "tempfile", - "thiserror", + "thiserror 1.0.69", "zeroize", ] @@ -1271,135 +1014,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", - "subtle", -] - -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys 0.3.7", ] [[package]] name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "libc", - "redox_users", - "winapi", + "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.60.2", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "libc", - "redox_users", - "winapi", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "dissimilar" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" - -[[package]] -name = "doc-comment" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" - -[[package]] -name = "docker_credential" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" -dependencies = [ - "base64 0.21.7", - "serde 1.0.210", - "serde_json", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" [[package]] name = "either" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "pem-rfc7468", - "pkcs8", - "rand_core", - "sec1", - "subtle", - "zeroize", -] +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ena" @@ -1412,210 +1072,126 @@ dependencies = [ [[package]] name = "encode_unicode" -version = "0.3.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[package]] -name = "encoding_rs" -version = "0.8.34" +name = "env_filter" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ - "cfg-if", + "log", + "regex", ] [[package]] -name = "enumflags2" -version = "0.7.10" +name = "env_logger" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ - "enumflags2_derive", - "serde 1.0.210", + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] -name = "enumflags2_derive" -version = "0.7.10" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "env_filter" -version = "0.1.2" +name = "erased-serde" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" dependencies = [ - "log", - "regex", + "serde", + "typeid", ] [[package]] -name = "env_logger" -version = "0.10.2" +name = "errno" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", + "libc", + "windows-sys 0.60.2", ] [[package]] -name = "env_logger" -version = "0.11.5" +name = "fallible-iterator" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "humantime", - "log", -] +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] -name = "equivalent" -version = "1.0.1" +name = "fallible-streaming-iterator" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] -name = "errno" -version = "0.3.9" +name = "faster-hex" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" dependencies = [ - "libc", - "windows-sys 0.52.0", + "heapless", + "serde", ] [[package]] -name = "event-listener" -version = "2.5.3" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "event-listener" -version = "3.1.0" +name = "filetime" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", ] [[package]] -name = "event-listener" -version = "5.3.1" +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] -name = "event-listener-strategy" -version = "0.5.2" +name = "flate2" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ - "event-listener 5.3.1", - "pin-project-lite", + "crc32fast", + "miniz_oxide", ] [[package]] -name = "expect-test" -version = "1.5.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e0be0a561335815e06dab7c62e50353134c796e7a6155402a64bcff66b6a5e0" -dependencies = [ - "dissimilar", - "once_cell", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "faster-hex" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" - -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" - -[[package]] -name = "ff" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" -dependencies = [ - "rand_core", - "subtle", -] - -[[package]] -name = "filetime" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" -dependencies = [ - "cfg-if", - "libc", - "libredox", - "windows-sys 0.59.0", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "fnv" -version = "1.0.7" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" @@ -1628,13 +1204,23 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.11.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +checksum = "88d7be93788013f265201256d58f04936a8079ad5dc898743aa20525f503b683" dependencies = [ "autocfg", ] +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "fs_at" version = "0.2.1" @@ -1645,15 +1231,21 @@ dependencies = [ "cfg-if", "cvt", "libc", - "nix 0.29.0", + "nix", "windows-sys 0.52.0", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1666,9 +1258,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1676,15 +1268,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1693,66 +1285,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.3.0" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand 2.1.1", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1768,10 +1332,11 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb949699c3e4df3a183b1d2142cb24277057055ed23c68ed58894f76c517223" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ + "cc", "cfg-if", "libc", "log", @@ -1787,25 +1352,38 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" dependencies = [ "fallible-iterator", "stable_deref_trait", @@ -1813,11 +1391,11 @@ dependencies = [ [[package]] name = "git2" -version = "0.19.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" +checksum = "2deb07a133b1520dc1a5690e9bd08950108873d7ed5de38dcc74d3b5ebffa110" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "libc", "libgit2-sys", "log", @@ -1828,23 +1406,23 @@ dependencies = [ [[package]] name = "gix-actor" -version = "0.32.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc19e312cd45c4a66cd003f909163dc2f8e1623e30a0c0c6df3776e89b308665" +checksum = "58ebbb8f41071c7cf318a0b1db667c34e1df49db7bf387d282a4e61a3b97882c" dependencies = [ "bstr", "gix-date", "gix-utils", "itoa", - "thiserror", - "winnow 0.6.18", + "thiserror 2.0.12", + "winnow", ] [[package]] name = "gix-config" -version = "0.40.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78e797487e6ca3552491de1131b4f72202f282fb33f198b1c34406d765b42bb0" +checksum = "48f3c8f357ae049bfb77493c2ec9010f58cfc924ae485e1116c3718fc0f0d881" dependencies = [ "bstr", "gix-config-value", @@ -1856,69 +1434,72 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror", + "thiserror 2.0.12", "unicode-bom", - "winnow 0.6.18", + "winnow", ] [[package]] name = "gix-config-value" -version = "0.14.8" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03f76169faa0dec598eac60f83d7fcdd739ec16596eca8fb144c88973dbe6f8c" +checksum = "9f012703eb67e263c6c1fc96649fec47694dd3e5d2a91abfc65e4a6a6dc85309" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "bstr", "gix-path", "libc", - "thiserror", + "thiserror 2.0.12", ] [[package]] name = "gix-date" -version = "0.9.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c84b7af01e68daf7a6bb8bb909c1ff5edb3ce4326f1f43063a5a96d3c3c8a5" +checksum = "d7235bdf4d9d54a6901928e3a37f91c16f419e6957f520ed929c3d292b84226e" dependencies = [ "bstr", "itoa", "jiff", - "thiserror", + "smallvec", + "thiserror 2.0.12", ] [[package]] name = "gix-features" -version = "0.38.2" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac7045ac9fe5f9c727f38799d002a7ed3583cd777e3322a7c4b43e3cf437dc69" +checksum = "56f4399af6ec4fd9db84dd4cf9656c5c785ab492ab40a7c27ea92b4241923fed" dependencies = [ - "gix-hash", + "gix-path", "gix-trace", "gix-utils", "libc", "prodash", - "sha1_smol", "walkdir", ] [[package]] name = "gix-fs" -version = "0.11.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bfe6249cfea6d0c0e0990d5226a4cb36f030444ba9e35e0639275db8f98575" +checksum = "67a0637149b4ef24d3ea55f81f77231401c8463fae6da27331c987957eb597c7" dependencies = [ - "fastrand 2.1.1", + "bstr", + "fastrand", "gix-features", + "gix-path", "gix-utils", + "thiserror 2.0.12", ] [[package]] name = "gix-glob" -version = "0.16.5" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111" +checksum = "90181472925b587f6079698f79065ff64786e6d6c14089517a1972bca99fb6e9" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "bstr", "gix-features", "gix-path", @@ -1926,62 +1507,78 @@ dependencies = [ [[package]] name = "gix-hash" -version = "0.14.2" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" +checksum = "8d4900562c662852a6b42e2ef03442eccebf24f047d8eab4f23bc12ef0d785d8" dependencies = [ "faster-hex", - "thiserror", + "gix-features", + "sha1-checked", + "thiserror 2.0.12", +] + +[[package]] +name = "gix-hashtable" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b5cb3c308b4144f2612ff64e32130e641279fcf1a84d8d40dad843b4f64904" +dependencies = [ + "gix-hash", + "hashbrown 0.14.5", + "parking_lot", ] [[package]] name = "gix-lock" -version = "14.0.0" +version = "17.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bc7fe297f1f4614774989c00ec8b1add59571dc9b024b4c00acb7dedd4e19d" +checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" dependencies = [ "gix-tempfile", "gix-utils", - "thiserror", + "thiserror 2.0.12", ] [[package]] name = "gix-object" -version = "0.44.0" +version = "0.49.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5b801834f1de7640731820c2df6ba88d95480dc4ab166a5882f8ff12b88efa" +checksum = "d957ca3640c555d48bb27f8278c67169fa1380ed94f6452c5590742524c40fbb" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", + "gix-hashtable", + "gix-path", "gix-utils", "gix-validate", "itoa", "smallvec", - "thiserror", - "winnow 0.6.18", + "thiserror 2.0.12", + "winnow", ] [[package]] name = "gix-path" -version = "0.10.11" +version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebfc4febd088abdcbc9f1246896e57e37b7a34f6909840045a1767c6dafac7af" +checksum = "c6279d323d925ad4790602105ae27df4b915e7a7d81e4cdba2603121c03ad111" dependencies = [ "bstr", "gix-trace", + "gix-validate", "home", "once_cell", - "thiserror", + "thiserror 2.0.12", ] [[package]] name = "gix-ref" -version = "0.47.0" +version = "0.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae0d8406ebf9aaa91f55a57f053c5a1ad1a39f60fdf0303142b7be7ea44311e5" +checksum = "d1b7985657029684d759f656b09abc3e2c73085596d5cdb494428823970a7762" dependencies = [ "gix-actor", "gix-features", @@ -1994,27 +1591,27 @@ dependencies = [ "gix-utils", "gix-validate", "memmap2", - "thiserror", - "winnow 0.6.18", + "thiserror 2.0.12", + "winnow", ] [[package]] name = "gix-sec" -version = "0.10.8" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe4d52f30a737bbece5276fab5d3a8b276dc2650df963e293d0673be34e7a5f" +checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "gix-path", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "gix-tempfile" -version = "14.0.2" +version = "17.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046b4927969fa816a150a0cda2e62c80016fe11fb3c3184e4dddf4e542f108aa" +checksum = "c750e8c008453a2dba67a2b0d928b7716e05da31173a3f5e351d5457ad4470aa" dependencies = [ "gix-fs", "libc", @@ -2025,71 +1622,54 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.10" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cae0e8661c3ff92688ce1c8b8058b3efb312aba9492bbe93661a21705ab431b" +checksum = "e2ccaf54b0b1743a695b482ca0ab9d7603744d8d10b2e5d1a332fef337bee658" [[package]] name = "gix-utils" -version = "0.1.12" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" +checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" dependencies = [ - "fastrand 2.1.1", + "fastrand", "unicode-normalization", ] [[package]] name = "gix-validate" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f2badbb64e57b404593ee26b752c26991910fd0d81fe6f9a71c1a8309b6c86" +checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" dependencies = [ "bstr", - "thiserror", + "thiserror 2.0.12", ] -[[package]] -name = "glam" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28091a37a5d09b555cb6628fd954da299b536433834f5b8e59eba78e0cbbf8a" - [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "globset" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core", - "subtle", + "regex-automata", + "regex-syntax", ] [[package]] name = "h2" -version = "0.4.6" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -2097,7 +1677,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.5.0", + "indexmap", "slab", "tokio", "tokio-util", @@ -2105,10 +1685,23 @@ dependencies = [ ] [[package]] -name = "hashbrown" -version = "0.12.3" +name = "half" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hash32" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] [[package]] name = "hashbrown" @@ -2118,41 +1711,49 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", - "serde 1.0.210", ] [[package]] -name = "heck" -version = "0.4.1" +name = "hashbrown" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] -name = "heck" -version = "0.5.0" +name = "hashlink" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.4", +] [[package]] -name = "hermit-abi" -version = "0.1.19" +name = "heapless" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "libc", + "hash32", + "stable_deref_trait", ] [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -2160,53 +1761,26 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "http" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] -[[package]] -name = "http-auth" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "150fa4a9462ef926824cf4519c84ed652ca8f4fbae34cb8af045b5cbcaf98822" -dependencies = [ - "memchr", -] - [[package]] name = "http-body" version = "1.0.1" @@ -2219,12 +1793,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -2232,37 +1806,37 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "human-panic" -version = "2.0.1" +version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c5a08ed290eac04006e21e63d32e90086b6182c7cd0452d10f4264def1fec9a" +checksum = "ac63a746b187e95d51fe16850eb04d1cfef203f6af98e6c405a6f262ad3df00a" dependencies = [ "anstream", "anstyle", "backtrace", "os_info", - "serde 1.0.210", + "serde", "serde_derive", - "toml 0.8.19", + "toml 0.9.2", "uuid", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" -version = "1.4.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -2271,6 +1845,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -2279,55 +1854,52 @@ dependencies = [ ] [[package]] -name = "hyper-rustls" -version = "0.27.3" +name = "hyper-timeout" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "futures-util", - "http", "hyper", "hyper-util", - "rustls", - "rustls-pki-types", + "pin-project-lite", "tokio", - "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] name = "hyper-util" -version = "0.1.8" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "libc", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core", ] [[package]] @@ -2340,116 +1912,187 @@ dependencies = [ ] [[package]] -name = "id-arena" -version = "2.2.1" +name = "icu_collections" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] [[package]] -name = "ident_case" -version = "1.0.1" +name = "icu_locale_core" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_normalizer" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", ] [[package]] -name = "ignore" -version = "0.4.23" +name = "icu_normalizer_data" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata 0.4.7", - "same-file", - "walkdir", - "winapi-util", +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", ] [[package]] -name = "im-rc" -version = "15.1.0" +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ - "bitmaps", - "rand_core", - "rand_xoshiro", - "sized-chunks", - "typenum", - "version_check", + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", ] [[package]] -name = "indenter" -version = "0.3.3" +name = "id-arena" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] -name = "indexmap" -version = "1.9.3" +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde 1.0.210", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" -version = "2.5.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.14.5", - "serde 1.0.210", + "hashbrown 0.15.4", + "serde", ] [[package]] name = "indicatif" -version = "0.17.8" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" +checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ - "console", - "instant", - "number_prefix", + "console 0.16.0", "portable-atomic", - "unicode-width", + "unicode-width 0.2.0", + "unit-prefix", + "web-time", ] [[package]] -name = "inout" -version = "0.1.3" +name = "indoc" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "block-padding", - "generic-array", -] +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" [[package]] name = "instability" -version = "0.3.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b23a0c8dfe501baac4adf6ebbfa6eddf8f0c07f56b058cc1288017e32397846c" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" dependencies = [ + "darling", + "indoc", + "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -2467,41 +2110,38 @@ version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "189d0897e4cbe8c75efedf3502c18c887b05046e59d28404d4d8e46cbc4d1e86" dependencies = [ - "memoffset 0.9.1", + "memoffset", ] [[package]] name = "inventory" -version = "0.3.15" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] [[package]] -name = "io-lifetimes" -version = "1.0.11" +name = "io-uring" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" dependencies = [ - "hermit-abi 0.3.9", + "bitflags 2.9.1", + "cfg-if", "libc", - "windows-sys 0.48.0", ] -[[package]] -name = "ipnet" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" - [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.4.0", + "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2518,95 +2158,98 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itertools" -version = "0.11.0" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.1.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a45489186a6123c128fdf6016183fcfab7113e1820eb813127e036e287233fb" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ + "jiff-static", "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", "windows-sys 0.59.0", ] +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "jiff-tzdb" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" [[package]] name = "jiff-tzdb-platform" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ "jiff-tzdb", ] [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] -[[package]] -name = "jwt" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" -dependencies = [ - "base64 0.13.1", - "crypto-common", - "digest", - "hmac", - "serde 1.0.210", - "serde_json", - "sha2", -] - [[package]] name = "keccak" version = "0.1.5" @@ -2616,66 +2259,46 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "keyring" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "363387f0019d714aa60cc30ab4fe501a747f4c08fc58f069dd14be971bd495a0" -dependencies = [ - "byteorder", - "lazy_static 1.5.0", - "linux-keyutils", - "secret-service", - "security-framework", - "windows-sys 0.52.0", -] - [[package]] name = "kstring" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "558bf9508a558512042d3095138b1f7b8fe90c5467d94f9f1da28b3731c5dbd1" dependencies = [ - "serde 1.0.210", + "serde", "static_assertions", ] [[package]] name = "lalrpop" -version = "0.20.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" dependencies = [ "ascii-canvas", "bit-set", "ena", - "itertools 0.11.0", + "itertools 0.14.0", "lalrpop-util", - "petgraph", + "petgraph 0.7.1", "regex", - "regex-syntax 0.8.4", + "regex-syntax", + "sha3", "string_cache", "term", - "tiny-keccak", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" dependencies = [ - "regex-automata 0.4.7", + "rustversion", ] -[[package]] -name = "lazy_static" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" - [[package]] name = "lazy_static" version = "1.5.0" @@ -2683,35 +2306,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - -[[package]] -name = "lexical-core" -version = "0.7.6" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" -dependencies = [ - "arrayvec 0.5.2", - "bitflags 1.3.2", - "cfg-if", - "ryu", - "static_assertions", -] +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.158" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libgit2-sys" -version = "0.17.0+1.8.1" +version = "0.18.2+1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" +checksum = "1c42fe03df2bd3c53a3a9c7317ad91d80c81cd1fb0caec8d7cc4cd2bfa10c222" dependencies = [ "cc", "libc", @@ -2723,26 +2333,37 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "libc", "redox_syscall", ] +[[package]] +name = "libsqlite3-sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libssh2-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc8a030b787e2119a731f1951d6a773e2280c660f8ec4b0f5e1505a386e71ee" +checksum = "220e4f05ad4a218192533b300327f5150e809b54c4ec83b5a1d91833601811b9" dependencies = [ "cc", "libc", @@ -2754,9 +2375,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.20" +version = "1.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472" +checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d" dependencies = [ "cc", "libc", @@ -2770,90 +2391,83 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" -[[package]] -name = "linux-keyutils" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761e49ec5fd8a5a463f9b84e877c373d888935b71c6be78f3767fe2ae6bed18e" -dependencies = [ - "bitflags 2.6.0", - "libc", -] - [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "liquid" -version = "0.26.9" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cdcc72b82748f47c2933c172313f5a9aea5b2c4eb3fa4c66b4ea55bb60bb4b1" +checksum = "2a494c3f9dad3cb7ed16f1c51812cbe4b29493d6c2e5cd1e2b87477263d9534d" dependencies = [ - "doc-comment", "liquid-core", "liquid-derive", "liquid-lib", - "serde 1.0.210", + "serde", ] [[package]] name = "liquid-core" -version = "0.26.9" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2752e978ffc53670f3f2e8b3ef09f348d6f7b5474a3be3f8a5befe5382e4effb" +checksum = "fc623edee8a618b4543e8e8505584f4847a4e51b805db1af6d9af0a3395d0d57" dependencies = [ "anymap2", - "itertools 0.13.0", + "itertools 0.14.0", "kstring", "liquid-derive", - "num-traits 0.2.19", "pest", "pest_derive", "regex", - "serde 1.0.210", + "serde", "time", ] [[package]] name = "liquid-derive" -version = "0.26.8" +version = "0.26.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b51f1d220e3fa869e24cfd75915efe3164bd09bb11b3165db3f37f57bf673e3" +checksum = "de66c928222984aea59fcaed8ba627f388aaac3c1f57dcb05cc25495ef8faefe" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "liquid-lib" -version = "0.26.9" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b1a298d3d2287ee5b1e43840d885b8fdfc37d3f4e90d82aacfd04d021618da" +checksum = "9befeedd61f5995bc128c571db65300aeb50d62e4f0542c88282dbcb5f72372a" dependencies = [ - "itertools 0.13.0", + "itertools 0.14.0", "liquid-core", - "once_cell", "percent-encoding", "regex", "time", "unicode-segmentation", ] +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -2861,41 +2475,75 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "logos" -version = "0.14.1" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1ceb190eb9bdeecdd8f1ad6a71d6d632a50905948771718741b5461fb01e13" +checksum = "7251356ef8cb7aec833ddf598c6cb24d17b689d20b993f9d11a3d764e34e6458" dependencies = [ - "logos-derive", + "logos-derive 0.14.4", +] + +[[package]] +name = "logos" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab6f536c1af4c7cc81edf73da1f8029896e7e1e16a219ef09b184e76a296f3db" +dependencies = [ + "logos-derive 0.15.0", ] [[package]] name = "logos-codegen" -version = "0.14.1" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f80069600c0d66734f5ff52cc42f2dabd6b29d205f333d61fd7832e9e9963f" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "syn", +] + +[[package]] +name = "logos-codegen" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90be66cb7bd40cb5cc2e9cfaf2d1133b04a3d93b72344267715010a466e0915a" +checksum = "189bbfd0b61330abea797e5e9276408f2edbe4f822d7ad08685d67419aafb34e" dependencies = [ "beef", "fnv", - "lazy_static 1.5.0", + "lazy_static", "proc-macro2", "quote", - "regex-syntax 0.8.4", - "syn 2.0.77", + "regex-syntax", + "rustc_version 0.4.1", + "syn", ] [[package]] name = "logos-derive" -version = "0.14.1" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24fb722b06a9dc12adb0963ed585f19fc61dc5413e6a9be9422ef92c091e731d" +dependencies = [ + "logos-codegen 0.14.4", +] + +[[package]] +name = "logos-derive" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45154231e8e96586b39494029e58f12f8ffcb5ecf80333a603a13aa205ea8cbd" +checksum = "ebfe8e1a19049ddbfccbd14ac834b215e11b85b90bab0c2dba7c7b92fb5d5cba" dependencies = [ - "logos-codegen", + "logos-codegen 0.15.0", ] [[package]] @@ -2913,168 +2561,272 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.4", ] [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" -version = "0.9.5" +version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +name = "miden" +version = "0.7.0" dependencies = [ - "autocfg", + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", ] [[package]] name = "miden-air" -version = "0.10.5" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2702f8adb96844e3521f49149f6c3d4773ecdd2a96a3169e3c025a2e3ee32b5e" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" dependencies = [ "miden-core", - "miden-thiserror", + "thiserror 2.0.12", "winter-air", "winter-prover", ] [[package]] name = "miden-assembly" -version = "0.10.5" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror 2.0.12", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae9cef4fbafb4fe26da18574bcdbd78815857cfe1099760782701ababb076c2" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" dependencies = [ "aho-corasick", "lalrpop", "lalrpop-util", + "log", "miden-core", - "miden-miette", - "miden-thiserror", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type 0.1.5", + "regex", "rustc_version 0.4.1", + "semver 1.0.26", "smallvec", - "tracing", - "unicode-width", + "thiserror 2.0.12", ] [[package]] -name = "miden-base-sys" -version = "0.0.7" +name = "miden-base" +version = "0.7.0" dependencies = [ - "miden-assembly", + "miden-base-macros", + "miden-base-sys", "miden-stdlib-sys", ] [[package]] -name = "miden-core" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3f6db878d6b56c1566cd5b832908675566d3919b9a3523d630dfb5e2f7422d" +name = "miden-base-macros" +version = "0.7.0" dependencies = [ - "lock_api", - "loom", - "memchr", - "miden-crypto", - "miden-formatting", - "miden-miette", - "miden-thiserror", + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-client" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6d100dc163a259a942d2883803c94fedce6f8e2e93beda8f1fc071f0258a40" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "deadpool", + "deadpool-sync", + "hex", + "miden-lib", + "miden-node-proto-build", + "miden-objects", + "miden-remote-prover-client", + "miden-tx", + "miette", + "prost", + "prost-build", + "protox 0.7.2", + "rand 0.9.2", + "rusqlite", + "rusqlite_migration", + "thiserror 2.0.12", + "tonic", + "tonic-build", + "tracing", + "web-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", "num-derive", - "num-traits 0.2.19", - "parking_lot", + "num-traits", + "thiserror 2.0.12", "winter-math", "winter-utils", ] [[package]] name = "miden-crypto" -version = "0.10.0" +version = "0.15.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6fad06fc3af260ed3c4235821daa2132813d993f96d446856036ae97e9606dd" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" dependencies = [ "blake3", "cc", "glob", "num", "num-complex", - "rand", - "rand_core", + "rand 0.9.2", + "rand_core 0.9.3", "sha3", + "thiserror 2.0.12", "winter-crypto", "winter-math", "winter-utils", ] +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "serde", + "serde_spanned 1.0.0", + "thiserror 2.0.12", +] + [[package]] name = "miden-formatting" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-integration-node-tests" +version = "0.4.1" +dependencies = [ + "anyhow", + "fs2", + "miden-client", + "miden-core", + "miden-integration-tests", + "miden-mast-package", + "miden-objects", + "midenc-frontend-wasm", + "rand 0.9.2", + "temp-dir", + "tokio", + "uuid", ] [[package]] name = "miden-integration-tests" -version = "0.0.7" +version = "0.4.1" dependencies = [ "anyhow", "blake3", "cargo-miden", "cargo-util", - "cargo_metadata", "concat-idents", - "derive_more", - "env_logger 0.11.5", - "expect-test", + "env_logger", "filetime", "glob", "log", "miden-assembly", "miden-core", - "miden-integration-tests-rust-fib", + "miden-mast-package", + "miden-objects", "miden-processor", - "miden-stdlib", "midenc-codegen-masm", "midenc-compile", "midenc-debug", + "midenc-dialect-arith", + "midenc-dialect-cf", + "midenc-dialect-hir", + "midenc-expect-test", "midenc-frontend-wasm", "midenc-hir", - "midenc-hir-transform", + "midenc-hir-eval", "midenc-session", "proptest", "sha2", @@ -3083,23 +2835,44 @@ dependencies = [ ] [[package]] -name = "miden-integration-tests-rust-fib" -version = "0.0.0" +name = "miden-lib" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97cbdddaf0c2edae3b2844db1d2a4746d498e2d865ac0ff5d76b700a9221e71" +dependencies = [ + "miden-assembly", + "miden-objects", + "miden-processor", + "miden-stdlib", + "regex", + "thiserror 2.0.12", + "walkdir", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] [[package]] name = "miden-miette" -version = "7.1.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c532250422d933f15b148fb81e4522a5d649c178ab420d0d596c86228da35570" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" dependencies = [ "backtrace", "backtrace-ext", "cfg-if", "futures", "indenter", - "lazy_static 1.5.0", + "lazy_static", "miden-miette-derive", - "miden-thiserror", "owo-colors", "regex", "rustc_version 0.2.3", @@ -3110,61 +2883,128 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "syn 2.0.77", - "terminal_size", + "syn", + "terminal_size 0.3.0", "textwrap", + "thiserror 2.0.12", "trybuild", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "miden-miette-derive" -version = "7.1.0" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc759f0a2947acae217a2f32f722105cacc57d17d5f93bc16362142943a4edd" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", +] + +[[package]] +name = "miden-node-proto-build" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1674a01e850ad65e8cdd7fa864b728dddc17e3d90944b1ff96933211bb1c806b" +dependencies = [ + "anyhow", + "prost", + "protox 0.8.0", + "tonic-build", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom 0.3.3", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "serde", + "thiserror 2.0.12", + "toml 0.8.23", ] [[package]] name = "miden-processor" -version = "0.10.5" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e7b212b152b69373e89b069a18cb01742ef2c3f9c328e7b24c44e44f022e52" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" dependencies = [ "miden-air", "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror 2.0.12", "tracing", "winter-prover", ] [[package]] -name = "miden-sdk" -version = "0.0.7" +name = "miden-prover" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d2479d594077a0f66b50a5bb1441bdf3824426a6c6b5ddda0b4df3031b0f2f" dependencies = [ - "miden-base-sys", - "miden-sdk-alloc", - "miden-stdlib-sys", + "miden-air", + "miden-debug-types", + "miden-processor", + "tracing", + "winter-maybe-async", + "winter-prover", +] + +[[package]] +name = "miden-remote-prover-client" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a421a9b5e8f09bda3d91a2d17f7d6804107bd5bb775a61a82b4fe47204d1f3ae" +dependencies = [ + "getrandom 0.3.3", + "miden-node-proto-build", + "miden-objects", + "miden-tx", + "miette", + "prost", + "prost-build", + "protox 0.8.0", + "thiserror 2.0.12", + "tokio", + "tonic", + "tonic-build", + "tonic-web-wasm-client", ] [[package]] name = "miden-sdk-alloc" -version = "0.0.7" +version = "0.7.0" [[package]] name = "miden-stdlib" -version = "0.10.5" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41623ad4f4ea6449760f70ab8928c682c3824d735d3e330f07e3d24d1ad20bfa" +checksum = "5f6d69ecc86e53b9e732b66ef198d444a7c853676fd79e875dc3523dba33d70d" dependencies = [ + "env_logger", "miden-assembly", + "miden-core", + "miden-processor", + "miden-utils-sync", + "thiserror 2.0.12", ] [[package]] name = "miden-stdlib-sys" -version = "0.0.7" +version = "0.7.0" [[package]] name = "miden-thiserror" @@ -3183,62 +3023,125 @@ checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", +] + +[[package]] +name = "miden-tx" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e3b4f65ef4d94f071b354e5c8cec01aff311bf9f9376bbf905b98c6955eeb8" +dependencies = [ + "miden-lib", + "miden-objects", + "miden-processor", + "miden-prover", + "miden-verifier", + "rand 0.9.2", + "thiserror 2.0.12", + "tokio", +] + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", + "parking_lot", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror 2.0.12", + "tracing", + "winter-verifier", ] [[package]] name = "midenc" -version = "0.0.7" +version = "0.4.1" dependencies = [ - "env_logger 0.11.5", + "env_logger", "human-panic", "midenc-driver", ] [[package]] -name = "midenc-codegen-masm" -version = "0.0.7" +name = "midenc-benchmark-runner" +version = "0.4.1" dependencies = [ "anyhow", - "bitcode", - "cranelift-entity", - "env_logger 0.11.5", - "intrusive-collections", + "clap", + "criterion", +] + +[[package]] +name = "midenc-codegen-masm" +version = "0.4.1" +dependencies = [ + "env_logger", "inventory", + "itertools 0.14.0", "log", "miden-assembly", + "miden-assembly-syntax", "miden-core", + "miden-lib", + "miden-mast-package", "miden-processor", - "miden-stdlib", "miden-thiserror", + "midenc-dialect-arith", + "midenc-dialect-cf", + "midenc-dialect-hir", + "midenc-dialect-scf", + "midenc-dialect-ub", + "midenc-expect-test", "midenc-hir", "midenc-hir-analysis", - "midenc-hir-transform", "midenc-session", - "paste", - "petgraph", + "petgraph 0.8.3", "proptest", - "rustc-hash 1.1.0", - "serde 1.0.210", - "serde_bytes", + "serde", "smallvec", ] [[package]] name = "midenc-compile" -version = "0.0.7" +version = "0.4.1" dependencies = [ "clap", - "either", - "intrusive-collections", "inventory", "log", "miden-assembly", + "miden-mast-package", "miden-thiserror", "midenc-codegen-masm", + "midenc-dialect-hir", + "midenc-dialect-scf", "midenc-frontend-wasm", "midenc-hir", - "midenc-hir-analysis", "midenc-hir-transform", "midenc-session", "wat", @@ -3246,7 +3149,7 @@ dependencies = [ [[package]] name = "midenc-debug" -version = "0.0.7" +version = "0.4.1" dependencies = [ "clap", "crossterm", @@ -3255,26 +3158,81 @@ dependencies = [ "log", "miden-assembly", "miden-core", + "miden-debug-types", + "miden-lib", + "miden-mast-package", "miden-processor", - "miden-stdlib", "miden-thiserror", "midenc-codegen-masm", "midenc-hir", "midenc-session", "proptest", "ratatui", - "serde 1.0.210", + "serde", "signal-hook", "syntect", "tokio", "tokio-util", - "toml 0.8.19", + "toml 0.8.23", "tui-input", ] +[[package]] +name = "midenc-dialect-arith" +version = "0.4.1" +dependencies = [ + "midenc-hir", + "paste", +] + +[[package]] +name = "midenc-dialect-cf" +version = "0.4.1" +dependencies = [ + "log", + "midenc-dialect-arith", + "midenc-hir", +] + +[[package]] +name = "midenc-dialect-hir" +version = "0.4.1" +dependencies = [ + "env_logger", + "log", + "midenc-dialect-arith", + "midenc-dialect-cf", + "midenc-expect-test", + "midenc-hir", + "midenc-hir-analysis", + "midenc-hir-transform", +] + +[[package]] +name = "midenc-dialect-scf" +version = "0.4.1" +dependencies = [ + "bitvec", + "env_logger", + "log", + "midenc-dialect-arith", + "midenc-dialect-cf", + "midenc-dialect-ub", + "midenc-expect-test", + "midenc-hir", + "midenc-hir-transform", +] + +[[package]] +name = "midenc-dialect-ub" +version = "0.4.1" +dependencies = [ + "midenc-hir", +] + [[package]] name = "midenc-driver" -version = "0.0.7" +version = "0.4.1" dependencies = [ "clap", "log", @@ -3285,194 +3243,226 @@ dependencies = [ "midenc-session", ] +[[package]] +name = "midenc-expect-test" +version = "0.4.1" +dependencies = [ + "once_cell", + "similar", +] + [[package]] name = "midenc-frontend-wasm" -version = "0.0.7" +version = "0.4.1" dependencies = [ "addr2line", "anyhow", - "derive_more", - "expect-test", + "cranelift-entity", "gimli", - "indexmap 2.5.0", + "indexmap", "log", "miden-core", "miden-thiserror", + "midenc-dialect-arith", + "midenc-dialect-cf", + "midenc-dialect-hir", + "midenc-dialect-ub", + "midenc-expect-test", "midenc-hir", - "midenc-hir-type", + "midenc-hir-symbol", "midenc-session", - "rustc-hash 1.1.0", - "smallvec", - "wasmparser 0.214.0", + "wasmparser 0.227.1", "wat", ] [[package]] name = "midenc-hir" -version = "0.0.7" +version = "0.4.1" dependencies = [ "anyhow", - "cranelift-entity", - "derive_more", - "either", - "indexmap 2.5.0", + "bitflags 2.9.1", + "bitvec", + "blink-alloc", + "compact_str 0.9.0", + "env_logger", + "hashbrown 0.14.5", + "hashbrown 0.15.4", "intrusive-collections", "inventory", - "lalrpop", - "lalrpop-util", "log", - "miden-assembly", "miden-core", "miden-thiserror", "midenc-hir-macros", "midenc-hir-symbol", - "midenc-hir-type", + "midenc-hir-type 0.4.2", "midenc-session", - "num-bigint", - "num-traits 0.2.19", - "paste", - "petgraph", "pretty_assertions", "rustc-demangle", - "rustc-hash 1.1.0", - "serde 1.0.210", - "serde_bytes", - "serde_repr", + "rustc-hash", + "semver 1.0.26", "smallvec", - "typed-arena", - "unicode-width", ] [[package]] name = "midenc-hir-analysis" -version = "0.0.7" +version = "0.4.1" dependencies = [ - "anyhow", - "cranelift-bforest", - "cranelift-entity", - "intrusive-collections", - "inventory", + "bitvec", + "blink-alloc", + "env_logger", + "log", + "midenc-dialect-arith", + "midenc-dialect-cf", + "midenc-dialect-hir", + "midenc-expect-test", + "midenc-hir", +] + +[[package]] +name = "midenc-hir-eval" +version = "0.4.1" +dependencies = [ + "log", "miden-thiserror", + "midenc-dialect-arith", + "midenc-dialect-cf", + "midenc-dialect-hir", + "midenc-dialect-scf", + "midenc-dialect-ub", "midenc-hir", "midenc-session", - "pretty_assertions", - "rustc-hash 1.1.0", - "smallvec", ] [[package]] name = "midenc-hir-macros" -version = "0.0.7" +version = "0.4.1" dependencies = [ "Inflector", + "darling", "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "midenc-hir-symbol" -version = "0.0.7" +version = "0.4.1" dependencies = [ "Inflector", - "rustc-hash 1.1.0", - "serde 1.0.210", - "toml 0.8.19", + "compact_str 0.9.0", + "hashbrown 0.14.5", + "hashbrown 0.15.4", + "lock_api", + "miden-formatting", + "parking_lot", + "rustc-hash", + "serde", + "toml 0.8.23", ] [[package]] name = "midenc-hir-transform" -version = "0.0.7" +version = "0.4.1" dependencies = [ - "anyhow", - "inventory", "log", "midenc-hir", "midenc-hir-analysis", "midenc-session", - "pretty_assertions", - "rustc-hash 1.1.0", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", "smallvec", + "thiserror 2.0.12", ] [[package]] name = "midenc-hir-type" -version = "0.0.7" +version = "0.4.2" dependencies = [ - "serde 1.0.210", + "miden-formatting", + "serde", "serde_repr", "smallvec", + "thiserror 2.0.12", ] [[package]] name = "midenc-session" -version = "0.0.7" +version = "0.4.1" dependencies = [ + "anyhow", "clap", "inventory", "log", "miden-assembly", - "miden-base-sys", + "miden-assembly-syntax", "miden-core", + "miden-debug-types", + "miden-lib", + "miden-mast-package", "miden-stdlib", "miden-thiserror", "midenc-hir-macros", "midenc-hir-symbol", "parking_lot", - "serde 1.0.210", - "serde_repr", "termcolor", ] [[package]] name = "miette" -version = "7.2.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" dependencies = [ + "backtrace", + "backtrace-ext", "cfg-if", "miette-derive", - "thiserror", - "unicode-width", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size 0.4.2", + "textwrap", + "unicode-width 0.1.14", ] [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", ] [[package]] name = "mio" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", - "wasi", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -3486,9 +3476,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" [[package]] name = "names" @@ -3496,7 +3486,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc" dependencies = [ - "rand", + "rand 0.8.5", ] [[package]] @@ -3505,41 +3495,18 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", -] - [[package]] name = "nix" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cfg-if", "cfg_aliases", "libc", ] -[[package]] -name = "nom" -version = "5.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" -dependencies = [ - "lexical-core", - "memchr", - "version_check", -] - [[package]] name = "normpath" version = "1.3.0" @@ -3551,12 +3518,11 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.60.2", ] [[package]] @@ -3570,7 +3536,7 @@ dependencies = [ "num-integer", "num-iter", "num-rational", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3580,7 +3546,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3589,7 +3555,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3606,7 +3572,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] @@ -3615,7 +3581,7 @@ version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3626,7 +3592,7 @@ checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3637,16 +3603,7 @@ checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ "num-bigint", "num-integer", - "num-traits 0.2.19", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.19", + "num-traits", ] [[package]] @@ -3660,16 +3617,20 @@ dependencies = [ ] [[package]] -name = "number_prefix" -version = "0.4.0" +name = "num_cpus" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] [[package]] name = "object" -version = "0.36.4" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "flate2", "memchr", @@ -3677,71 +3638,27 @@ dependencies = [ ] [[package]] -name = "oci-client" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f5098b86f972ac3484f7c9011bbbbd64aaa7e21d10d2c1a91fefb4ad0ba2ad9" -dependencies = [ - "bytes", - "chrono", - "futures-util", - "http", - "http-auth", - "jwt", - "lazy_static 1.5.0", - "olpc-cjson", - "regex", - "reqwest", - "serde 1.0.210", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tracing", - "unicase", -] - -[[package]] -name = "oci-wasm" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3493d1985a31c5fbd4b37f72a319aab88b55908185a5a799219c6152e9da9b" -dependencies = [ - "anyhow", - "chrono", - "oci-client", - "serde 1.0.210", - "serde_json", - "sha2", - "tokio", - "wit-component 0.215.0", - "wit-parser 0.215.0", -] - -[[package]] -name = "olpc-cjson" -version = "0.1.3" +name = "once_cell" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d637c9c15b639ccff597da8f4fa968300651ad2f1e968aefc3b4927a6fb2027a" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ - "serde 1.0.210", - "serde_json", - "unicode-normalization", + "portable-atomic", ] [[package]] -name = "once_cell" -version = "1.19.0" +name = "once_cell_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "onig" -version = "6.4.0" +version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "libc", "once_cell", "onig_sys", @@ -3749,25 +3666,31 @@ dependencies = [ [[package]] name = "onig_sys" -version = "69.8.1" +version = "69.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" dependencies = [ "cc", "pkg-config", ] +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -3787,65 +3710,32 @@ version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" dependencies = [ - "num-traits 0.2.19", -] - -[[package]] -name = "ordered-stream" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" -dependencies = [ - "futures-core", - "pin-project-lite", + "num-traits", ] [[package]] name = "os_info" -version = "3.8.2" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" dependencies = [ "log", - "serde 1.0.210", + "plist", + "serde", "windows-sys 0.52.0", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "owo-colors" -version = "4.1.0" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -3853,9 +3743,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", @@ -3866,9 +3756,18 @@ dependencies = [ [[package]] name = "parse_arg" -version = "0.1.4" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f05bccc8b6036fec4e0c511954e3997987a82acb6a0b50642ecf7c744fe225" +dependencies = [ + "parse_arg 1.0.1", +] + +[[package]] +name = "parse_arg" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14248cc8eced350e20122a291613de29e4fa129ba2731818c4cdbb44fccd3e55" +checksum = "5bddc33f680b79eaf1e2e56da792c3c2236f86985bbc3a886e8ddee17ae4d3a4" [[package]] name = "paste" @@ -3894,58 +3793,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "pbjson" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" -dependencies = [ - "base64 0.21.7", - "serde 1.0.210", -] - -[[package]] -name = "pbjson-build" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" -dependencies = [ - "heck 0.4.1", - "itertools 0.11.0", - "prost", - "prost-types", -] - -[[package]] -name = "pbjson-types" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" -dependencies = [ - "bytes", - "chrono", - "pbjson", - "pbjson-build", - "prost", - "prost-build", - "serde 1.0.210", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -3954,20 +3801,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", - "thiserror", + "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -3975,72 +3822,82 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] [[package]] name = "petgraph" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.5.0", + "indexmap", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.4", + "indexmap", ] [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -4049,68 +3906,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "piper" -version = "0.2.4" +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" dependencies = [ - "atomic-waker", - "fastrand 2.1.1", - "futures-io", + "base64", + "indexmap", + "quick-xml", + "serde", + "time", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "plotters" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ - "der", - "spki", + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", ] [[package]] -name = "pkg-config" -version = "0.3.30" +name = "plotters-backend" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] -name = "polling" -version = "2.8.0" +name = "plotters-svg" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", ] [[package]] -name = "polling" -version = "3.7.3" +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.4.0", - "pin-project-lite", - "rustix 0.38.37", - "tracing", - "windows-sys 0.59.0", + "portable-atomic", ] [[package]] -name = "portable-atomic" -version = "1.7.0" +name = "potential_utf" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -4120,9 +3984,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -4135,83 +3999,58 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "pretty_assertions" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ "diff", "yansi", ] -[[package]] -name = "pretty_env_logger" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" -dependencies = [ - "env_logger 0.10.2", - "log", -] - [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 2.0.77", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" -version = "28.0.0" +version = "29.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" +checksum = "f04bb108f648884c23b98a0e940ebc2c93c0c3b89f04dbaf7eb8256ce617d1bc" +dependencies = [ + "log", + "parking_lot", +] [[package]] name = "proptest" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.6.0", - "lazy_static 1.5.0", - "num-traits 0.2.19", - "rand", - "rand_chacha", + "bitflags 2.9.1", + "lazy_static", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -4219,9 +4058,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", "prost-derive", @@ -4229,101 +4068,123 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.6" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "bytes", - "heck 0.5.0", - "itertools 0.12.1", + "heck", + "itertools 0.14.0", "log", "multimap", "once_cell", - "petgraph", + "petgraph 0.7.1", "prettyplease", "prost", "prost-types", "regex", - "syn 2.0.77", + "syn", "tempfile", ] [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "prost-reflect" -version = "0.13.1" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5eec97d5d34bdd17ad2db2219aabf46b054c6c41bd5529767c9ce55be5898f" +checksum = "7b5edd582b62f5cde844716e66d92565d7faf7ab1445c8cebce6e00fba83ddb2" dependencies = [ - "logos", + "logos 0.14.4", "miette", "once_cell", "prost", "prost-types", ] +[[package]] +name = "prost-reflect" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37587d5a8a1b3dc9863403d084fc2254b91ab75a702207098837950767e2260b" +dependencies = [ + "logos 0.15.0", + "miette", + "prost", + "prost-types", +] + [[package]] name = "prost-types" -version = "0.12.6" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" dependencies = [ "prost", ] [[package]] name = "protox" -version = "0.6.1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f352af331bf637b8ecc720f7c87bf903d2571fa2e14a66e9b2558846864b54a" +dependencies = [ + "bytes", + "miette", + "prost", + "prost-reflect 0.14.7", + "prost-types", + "protox-parse 0.7.0", + "thiserror 1.0.69", +] + +[[package]] +name = "protox" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac532509cee918d40f38c3e12f8ef9230f215f017d54de7dd975015538a42ce7" +checksum = "424c2bd294b69c49b949f3619362bc3c5d28298cd1163b6d1a62df37c16461aa" dependencies = [ "bytes", "miette", "prost", - "prost-reflect", + "prost-reflect 0.15.3", "prost-types", - "protox-parse", - "thiserror", + "protox-parse 0.8.0", + "thiserror 2.0.12", ] [[package]] name = "protox-parse" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6c33f43516fe397e2f930779d720ca12cd057f7da4cd6326a0ef78d69dee96" +checksum = "a3a462d115462c080ae000c29a47f0b3985737e5d3a995fcdbcaa5c782068dde" dependencies = [ - "logos", + "logos 0.14.4", "miette", "prost-types", - "thiserror", + "thiserror 1.0.69", ] [[package]] -name = "ptree" -version = "0.4.0" +name = "protox-parse" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0de80796b316aec75344095a6d2ef68ec9b8f573b9e7adc821149ba3598e270" +checksum = "57927f9dbeeffcce7192404deee6157a640cbb3fe8ac11eabbe571565949ab75" dependencies = [ - "ansi_term", - "atty", - "config", - "directories", - "petgraph", - "serde 1.0.210", - "serde-value", - "tint", + "logos 0.15.0", + "miette", + "prost-types", + "thiserror 2.0.12", ] [[package]] @@ -4333,61 +4194,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quinn" -version = "0.11.5" +name = "quick-xml" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "8927b0664f5c5a98265138b7e3f90aa19a6b21353182469ace36d4ac527b7b1b" dependencies = [ - "bytes", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash 2.0.0", - "rustls", - "socket2 0.5.7", - "thiserror", - "tokio", - "tracing", + "memchr", ] [[package]] -name = "quinn-proto" -version = "0.11.8" +name = "quote" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ - "bytes", - "rand", - "ring", - "rustc-hash 2.0.0", - "rustls", - "slab", - "thiserror", - "tinyvec", - "tracing", + "proc-macro2", ] [[package]] -name = "quinn-udp" -version = "0.5.5" +name = "r-efi" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" -dependencies = [ - "libc", - "once_cell", - "socket2 0.5.7", - "tracing", - "windows-sys 0.59.0", -] +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] -name = "quote" -version = "1.0.37" +name = "radium" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "rand" @@ -4396,8 +4230,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -4407,7 +4251,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -4416,194 +4270,141 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] -name = "rand_xorshift" -version = "0.3.0" +name = "rand_core" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "rand_core", + "getrandom 0.3.3", ] [[package]] -name = "rand_xoshiro" -version = "0.6.0" +name = "rand_xorshift" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core", + "rand_core 0.9.3", ] [[package]] name = "ratatui" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdef7f9be5c0122f890d58bdf4d964349ba6a6161f705907526d891efabba57d" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "cassowary", - "compact_str", + "compact_str 0.8.1", "crossterm", + "indoc", "instability", "itertools 0.13.0", "lru", "paste", "strum", - "strum_macros", "unicode-segmentation", "unicode-truncate", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] -name = "redox_syscall" -version = "0.5.4" +name = "rayon" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ - "bitflags 2.6.0", + "either", + "rayon-core", ] [[package]] -name = "redox_users" -version = "0.4.6" +name = "rayon-core" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ - "getrandom", - "libredox", - "thiserror", + "crossbeam-deque", + "crossbeam-utils", ] [[package]] -name = "regex" -version = "1.10.6" +name = "redox_syscall" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "bitflags 2.9.1", ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "redox_users" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "regex-syntax 0.6.29", + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", ] [[package]] -name = "regex-automata" -version = "0.4.7" +name = "regex" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-automata", + "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "regex-automata" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "remove_dir_all" -version = "0.8.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c914caef075f03e9d5c568e2e71b3d3cf17dc61a5481ff379bb744721be0a75a" +checksum = "808cc0b475acf76adf36f08ca49429b12aad9f678cb56143d5b3cb49b9a1dd08" dependencies = [ "cfg-if", "cvt", "fs_at", "libc", "normpath", - "windows-sys 0.52.0", -] - -[[package]] -name = "reqwest" -version = "0.12.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "ipnet", - "js-sys", - "log", - "mime", - "once_cell", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pemfile", - "rustls-pki-types", - "serde 1.0.210", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-rustls", - "tokio-socks", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "windows-registry", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", + "windows-sys 0.59.0", ] [[package]] name = "rhai" -version = "1.19.0" +version = "1.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61797318be89b1a268a018a92a7657096d83f3ecb31418b9e9c16dcbb043b702" +checksum = "2780e813b755850e50b178931aaf94ed24f6817f46aaaf5d21c13c12d939a249" dependencies = [ "ahash", - "bitflags 2.6.0", + "bitflags 2.9.1", "instant", - "num-traits 0.2.19", + "num-traits", "once_cell", "rhai_codegen", "smallvec", @@ -4619,68 +4420,58 @@ checksum = "a5a11a05ee1ce44058fa3d5961d05194fdbe3ad6b40f904af764d81b86450e6b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] -name = "rpassword" -version = "7.3.1" +name = "rusqlite" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" +checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7" dependencies = [ - "libc", - "rtoolbox", - "windows-sys 0.48.0", + "bitflags 2.9.1", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", ] [[package]] -name = "rtoolbox" -version = "0.0.2" +name = "rusqlite_migration" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e" +checksum = "a324f81362b5cd8f2eeef82d032172fdf2ca70aeec64962ff55a56874fe5ec41" dependencies = [ - "libc", - "windows-sys 0.48.0", + "log", + "rusqlite", ] -[[package]] -name = "rust-ini" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" - [[package]] name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - -[[package]] -name = "rustc-hash" -version = "1.1.0" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -4697,42 +4488,42 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.26", ] [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", "errno", - "io-lifetimes", "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", ] [[package]] name = "rustix" -version = "0.38.37" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.4.14", - "windows-sys 0.52.0", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -4742,26 +4533,31 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.1.3" +name = "rustls-native-certs" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ - "base64 0.22.1", + "openssl-probe", "rustls-pki-types", + "schannel", + "security-framework", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -4770,9 +4566,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rusty-fork" @@ -4788,18 +4584,18 @@ dependencies = [ [[package]] name = "ruzstd" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c3938e133aac070997ddc684d4b393777d293ba170f2988c8fd5ea2ad4ce21" +checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f" dependencies = [ "twox-hash", ] [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -4812,76 +4608,41 @@ dependencies = [ [[package]] name = "sanitize-filename" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" +checksum = "bc984f4f9ceb736a7bb755c3e3bd17dc56370af2600c9780dcc48c66453da34d" dependencies = [ - "lazy_static 1.5.0", "regex", ] [[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sec1" -version = "0.7.3" +name = "schannel" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", + "windows-sys 0.59.0", ] [[package]] -name = "secrecy" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" -dependencies = [ - "serde 1.0.210", - "zeroize", -] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] -name = "secret-service" -version = "3.1.0" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5204d39df37f06d1944935232fd2dfe05008def7ca599bf28c0800366c8a8f9" -dependencies = [ - "aes", - "cbc", - "futures-util", - "generic-array", - "hkdf", - "num", - "once_cell", - "rand", - "serde 1.0.210", - "sha2", - "zbus", -] +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.11.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.1", "core-foundation", "core-foundation-sys", "libc", @@ -4890,9 +4651,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -4909,11 +4670,11 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ - "serde 1.0.210", + "serde", ] [[package]] @@ -4924,29 +4685,22 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" - -[[package]] -name = "serde" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] -name = "serde-hjson" -version = "0.9.1" +name = "serde-untagged" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +checksum = "299d9c19d7d466db4ab10addd5703e4c615dec2a5a16dbbafe191045e87ee66e" dependencies = [ - "lazy_static 1.5.0", - "num-traits 0.1.43", - "regex", - "serde 0.8.23", + "erased-serde", + "serde", + "typeid", ] [[package]] @@ -4956,114 +4710,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" dependencies = [ "ordered-float", - "serde 1.0.210", -] - -[[package]] -name = "serde_bytes" -version = "0.11.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" -dependencies = [ - "serde 1.0.210", + "serde", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", "memchr", "ryu", - "serde 1.0.210", + "serde", ] [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "serde_spanned" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" -dependencies = [ - "serde 1.0.210", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde 1.0.210", -] - -[[package]] -name = "serde_with" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.5.0", - "serde 1.0.210", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.9.0" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.77", + "serde", ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "serde_spanned" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" dependencies = [ - "indexmap 2.5.0", - "itoa", - "ryu", - "serde 1.0.210", - "unsafe-libyaml", + "serde", ] [[package]] @@ -5078,35 +4777,26 @@ dependencies = [ ] [[package]] -name = "sha1_smol" -version = "1.0.1" +name = "sha1-checked" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" +dependencies = [ + "digest", + "sha1", +] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] -[[package]] -name = "sha256" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" -dependencies = [ - "async-trait", - "bytes", - "hex", - "sha2", - "tokio", -] - [[package]] name = "sha3" version = "0.10.8" @@ -5123,7 +4813,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ - "lazy_static 1.5.0", + "lazy_static", ] [[package]] @@ -5146,9 +4836,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -5167,53 +4857,39 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.2" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] [[package]] -name = "signature" -version = "2.2.0" +name = "similar" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" [[package]] name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "sized-chunks" -version = "0.6.5" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "smartstring" @@ -5234,49 +4910,20 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] -[[package]] -name = "spdx" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc" -dependencies = [ - "smallvec", -] - [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -5291,12 +4938,11 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", "phf_shared", "precomputed-hash", @@ -5304,9 +4950,9 @@ dependencies = [ [[package]] name = "strip-ansi-escapes" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" dependencies = [ "vte", ] @@ -5332,11 +4978,11 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.77", + "syn", ] [[package]] @@ -5347,18 +4993,18 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-color" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" dependencies = [ "is_ci", ] [[package]] name = "supports-hyperlinks" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" [[package]] name = "supports-unicode" @@ -5368,9 +5014,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -5378,23 +5024,20 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.77" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] -name = "sync_wrapper" -version = "1.0.1" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ - "futures-core", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -5409,57 +5052,53 @@ dependencies = [ "fnv", "once_cell", "onig", - "regex-syntax 0.8.4", - "serde 1.0.210", + "regex-syntax", + "serde", "serde_derive", "serde_json", - "thiserror", + "thiserror 1.0.69", "walkdir", "yaml-rust", ] [[package]] -name = "system-configuration" -version = "0.6.1" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.6.0", - "core-foundation", - "system-configuration-sys", -] +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] -name = "system-configuration-sys" -version = "0.6.0" +name = "target-triple" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "temp-dir" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83176759e9416cf81ee66cb6508dbfe9c96f20b8b56265a39917551c23c70964" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", - "fastrand 2.1.1", - "rustix 0.38.37", - "windows-sys 0.52.0", + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix 1.0.8", + "windows-sys 0.59.0", ] [[package]] name = "term" -version = "0.7.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "dirs-next", - "rustversion", - "winapi", + "windows-sys 0.60.2", ] [[package]] @@ -5487,111 +5126,151 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" dependencies = [ - "rustix 0.38.37", + "rustix 0.38.44", "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix 1.0.8", + "windows-sys 0.59.0", +] + [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] name = "thin-vec" -version = "0.2.13" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144f754d318415ac792f9d69fc87abbbfc043ce2ef041c60f16ad828f638717d" + +[[package]] +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a38c90d48152c236a3ab59271da4f4ae63d678c5d7ad6b7714d7cb9760be5e4b" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] [[package]] name = "thiserror" -version = "1.0.63" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ - "thiserror-impl", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde 1.0.210", + "serde", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", ] [[package]] -name = "tint" -version = "1.0.1" +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7af24570664a3074673dbbf69a65bdae0ae0b72f2949b1adfbacb736ee4d6896" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ - "lazy_static 0.2.11", + "displaydoc", + "zerovec", ] [[package]] -name = "tiny-keccak" -version = "2.0.2" +name = "tinytemplate" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" dependencies = [ - "crunchy", + "serde", + "serde_json", ] [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -5604,137 +5283,249 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", - "socket2 0.5.7", + "slab", + "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "tokio-rustls" -version = "0.26.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] [[package]] -name = "tokio-socks" -version = "0.5.2" +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "either", - "futures-util", - "thiserror", - "tokio", + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", ] [[package]] -name = "tokio-util" -version = "0.7.12" +name = "toml_edit" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "7211ff1b8f0d3adae1663b7da9ffe396eabe1ca25f0b0bee42b0da29a9ddce93" dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", ] [[package]] -name = "toml" -version = "0.5.11" +name = "toml_parser" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ - "serde 1.0.210", + "winnow", ] [[package]] -name = "toml" -version = "0.8.19" +name = "toml_write" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" -dependencies = [ - "indexmap 2.5.0", - "serde 1.0.210", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.20", -] +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] -name = "toml_datetime" -version = "0.6.8" +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tonic" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "7e581ba15a835f4d9ea06c55ab1bd4dce26fc53752c69a04aac00703bfb49ba9" dependencies = [ - "serde 1.0.210", + "async-trait", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-native-certs", + "socket2", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "tonic-build" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "eac6f67be712d12f0b41328db3137e0d0757645d8904b4cb7d51cd9c2279e847" dependencies = [ - "indexmap 2.5.0", - "toml_datetime", - "winnow 0.5.40", + "prettyplease", + "proc-macro2", + "prost-build", + "prost-types", + "quote", + "syn", ] [[package]] -name = "toml_edit" -version = "0.22.20" +name = "tonic-web-wasm-client" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "66e3bb7acca55e6790354be650f4042d418fcf8e2bc42ac382348f2b6bf057e5" dependencies = [ - "indexmap 2.5.0", - "serde 1.0.210", - "serde_spanned", - "toml_datetime", - "winnow 0.6.18", + "base64", + "byteorder", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "httparse", + "js-sys", + "pin-project", + "thiserror 2.0.12", + "tonic", + "tower-service", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", ] [[package]] name = "tower" -version = "0.4.13" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "pin-project", + "indexmap", "pin-project-lite", + "slab", + "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -5751,11 +5542,10 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5763,20 +5553,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -5795,14 +5585,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -5819,27 +5609,28 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.99" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" dependencies = [ "dissimilar", "glob", - "serde 1.0.210", + "serde", "serde_derive", "serde_json", + "target-triple", "termcolor", - "toml 0.8.19", + "toml 0.9.2", ] [[package]] name = "tui-input" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd137780d743c103a391e06fe952487f914b299a4fe2c3626677f6a6339a7c6b" +checksum = "e5d1733c47f1a217b7deff18730ff7ca4ecafc5771368f715ab072d679a36114" dependencies = [ "ratatui", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] @@ -5859,27 +5650,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" [[package]] -name = "typenum" -version = "1.17.0" +name = "typeid" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] -name = "ucd-trie" -version = "0.1.6" +name = "typenum" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] -name = "uds_windows" -version = "1.1.0" +name = "ucd-trie" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" -dependencies = [ - "memoffset 0.9.1", - "tempfile", - "winapi", -] +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unarray" @@ -5887,21 +5673,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-bom" version = "2.0.3" @@ -5910,9 +5681,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-linebreak" @@ -5922,18 +5693,18 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "unicode-truncate" @@ -5943,26 +5714,32 @@ checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ "itertools 0.13.0", "unicode-segmentation", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] -name = "unsafe-libyaml" -version = "0.2.11" +name = "unit-prefix" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" [[package]] name = "untrusted" @@ -5972,16 +5749,21 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde 1.0.210", ] +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" version = "0.2.2" @@ -5990,18 +5772,20 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.10.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom", + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -6017,39 +5801,22 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vte" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" -dependencies = [ - "utf8parse", - "vte_generate_state_changes", -] - -[[package]] -name = "vte_generate_state_changes" -version = "0.1.2" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" dependencies = [ - "proc-macro2", - "quote", + "memchr", ] [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -6070,374 +5837,128 @@ dependencies = [ ] [[package]] -name = "warg-api" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44b422328c3a86be288f569694aa97df958ade0cd9514ed00bc562952c6778e" -dependencies = [ - "indexmap 2.5.0", - "itertools 0.12.1", - "serde 1.0.210", - "serde_with", - "thiserror", - "warg-crypto", - "warg-protocol", -] - -[[package]] -name = "warg-client" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1af3c0a73c56613c152fb99048af889a57063756299460c8fc26fea52ddccc" -dependencies = [ - "anyhow", - "async-recursion", - "async-trait", - "bytes", - "clap", - "dialoguer", - "dirs", - "futures-util", - "indexmap 2.5.0", - "itertools 0.12.1", - "keyring", - "libc", - "normpath", - "once_cell", - "pathdiff", - "ptree", - "reqwest", - "secrecy", - "semver 1.0.23", - "serde 1.0.210", - "serde_json", - "sha256", - "tempfile", - "thiserror", - "tokio", - "tokio-util", - "tracing", - "url", - "walkdir", - "warg-api", - "warg-crypto", - "warg-protocol", - "warg-transparency", - "wasm-compose", - "wasm-encoder 0.41.2", - "wasmparser 0.121.2", - "wasmprinter", - "windows-sys 0.52.0", -] - -[[package]] -name = "warg-crypto" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52fb6f3a64e3fef5425a0ab2b4354f1e49a699b76d58dd91d632483634f10474" -dependencies = [ - "anyhow", - "base64 0.21.7", - "digest", - "hex", - "leb128", - "once_cell", - "p256", - "rand_core", - "secrecy", - "serde 1.0.210", - "sha2", - "signature", - "thiserror", -] - -[[package]] -name = "warg-protobuf" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bbb2d353497af3f6334bce11bfe69d638eedbb6d5059992acb2a05dd0beef5b" -dependencies = [ - "anyhow", - "pbjson", - "pbjson-build", - "pbjson-types", - "prost", - "prost-build", - "prost-types", - "protox", - "regex", - "serde 1.0.210", - "warg-crypto", -] - -[[package]] -name = "warg-protocol" -version = "0.9.0" +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a710c66d8b5f2a7b046ecd2d45121530f561fd5c699b9c85ee49d332cfe773" -dependencies = [ - "anyhow", - "base64 0.21.7", - "hex", - "indexmap 2.5.0", - "pbjson-types", - "prost", - "prost-types", - "semver 1.0.23", - "serde 1.0.210", - "serde_with", - "thiserror", - "warg-crypto", - "warg-protobuf", - "warg-transparency", - "wasmparser 0.121.2", -] +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "warg-transparency" -version = "0.9.0" +name = "wasi" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b950a71a544b7ac8f5a5e95f43886ac97c3fe5c7080b955b1b534037596d7be" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ - "anyhow", - "indexmap 2.5.0", - "prost", - "thiserror", - "warg-crypto", - "warg-protobuf", + "wit-bindgen-rt", ] -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasi-preview1-component-adapter-provider" -version = "24.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36e6cadfa74538edd5409b6f8c79628436529138e9618b7373bec7aae7805835" - [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wasm-compose" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd324927af875ebedb1b820c00e3c585992d33c2c787c5021fe6d8982527359b" -dependencies = [ - "anyhow", - "heck 0.4.1", - "im-rc", - "indexmap 2.5.0", - "log", - "petgraph", - "serde 1.0.210", - "serde_derive", - "serde_yaml", - "smallvec", - "wasm-encoder 0.41.2", - "wasmparser 0.121.2", - "wat", -] - -[[package]] -name = "wasm-encoder" -version = "0.41.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "972f97a5d8318f908dded23594188a90bcd09365986b1163e66d70170e5287ae" -dependencies = [ - "leb128", - "wasmparser 0.121.2", -] - -[[package]] -name = "wasm-encoder" -version = "0.215.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb56df3e06b8e6b77e37d2969a50ba51281029a9aeb3855e76b7f49b6418847" -dependencies = [ - "leb128", - "wasmparser 0.215.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.216.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c23aebea22c8a75833ae08ed31ccc020835b12a41999e58c31464271b94a88" -dependencies = [ - "leb128", - "wasmparser 0.216.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.217.0" +name = "wasm-bindgen-macro-support" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b88b0814c9a2b323a9b46c687e726996c255ac8b64aa237dd11c81ed4854760" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ - "leb128", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", ] [[package]] -name = "wasm-metadata" -version = "0.215.0" +name = "wasm-bindgen-shared" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6bb07c5576b608f7a2a9baa2294c1a3584a249965d695a9814a496cb6d232f" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ - "anyhow", - "indexmap 2.5.0", - "serde 1.0.210", - "serde_derive", - "serde_json", - "spdx", - "wasm-encoder 0.215.0", - "wasmparser 0.215.0", + "unicode-ident", ] [[package]] -name = "wasm-metadata" -version = "0.216.0" +name = "wasm-encoder" +version = "0.235.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c8154d703a6b0e45acf6bd172fa002fc3c7058a9f7615e517220aeca27c638" +checksum = "b3bc393c395cb621367ff02d854179882b9a351b4e0c93d1397e6090b53a5c2a" dependencies = [ - "anyhow", - "indexmap 2.5.0", - "serde 1.0.210", - "serde_derive", - "serde_json", - "spdx", - "wasm-encoder 0.216.0", - "wasmparser 0.216.0", + "leb128fmt", + "wasmparser 0.235.0", ] [[package]] -name = "wasm-pkg-client" -version = "0.5.1" +name = "wasm-encoder" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0904880365efa1b4f81e34948d27fabf762837fea05852bbb0c1674c43a802" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" dependencies = [ - "anyhow", - "async-trait", - "base64 0.22.1", - "bytes", - "dirs", - "docker_credential", - "futures-util", - "oci-client", - "oci-wasm", - "secrecy", - "serde 1.0.210", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tokio-util", - "toml 0.8.19", - "tracing", - "tracing-subscriber", - "url", - "warg-client", - "warg-crypto", - "warg-protocol", - "wasm-pkg-common", - "wit-component 0.216.0", + "leb128fmt", + "wasmparser 0.239.0", ] [[package]] -name = "wasm-pkg-common" -version = "0.5.1" +name = "wasm-metadata" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0287f3ce5f1e03c4cf88bdd533b01dcd315c321eb09cb3dd35cfdf4031d912f9" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" dependencies = [ "anyhow", - "bytes", - "dirs", - "futures-util", - "http", - "reqwest", - "semver 1.0.23", - "serde 1.0.210", - "serde_json", - "sha2", - "thiserror", - "tokio", - "toml 0.8.19", - "tracing", + "indexmap", + "wasm-encoder 0.239.0", + "wasmparser 0.239.0", ] [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -6448,117 +5969,89 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.121.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" -dependencies = [ - "bitflags 2.6.0", - "indexmap 2.5.0", - "semver 1.0.23", -] - -[[package]] -name = "wasmparser" -version = "0.214.0" +version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5309c1090e3e84dad0d382f42064e9933fdaedb87e468cc239f0eabea73ddcb6" +checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ - "ahash", - "bitflags 2.6.0", - "hashbrown 0.14.5", - "indexmap 2.5.0", - "semver 1.0.23", - "serde 1.0.210", + "bitflags 2.9.1", + "indexmap", + "semver 1.0.26", ] [[package]] name = "wasmparser" -version = "0.215.0" +version = "0.235.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fbde0881f24199b81cf49b6ff8f9c145ac8eb1b7fc439adb5c099734f7d90e" +checksum = "161296c618fa2d63f6ed5fffd1112937e803cb9ec71b32b01a76321555660917" dependencies = [ - "ahash", - "bitflags 2.6.0", - "hashbrown 0.14.5", - "indexmap 2.5.0", - "semver 1.0.23", - "serde 1.0.210", + "bitflags 2.9.1", + "indexmap", + "semver 1.0.26", ] [[package]] name = "wasmparser" -version = "0.216.0" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdee6bea3619d311fb4b299721e89a986c3470f804b6d534340e412589028e3" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" dependencies = [ - "ahash", - "bitflags 2.6.0", - "hashbrown 0.14.5", - "indexmap 2.5.0", - "semver 1.0.23", + "bitflags 2.9.1", + "hashbrown 0.15.4", + "indexmap", + "semver 1.0.26", ] [[package]] name = "wasmprinter" -version = "0.2.80" +version = "0.227.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60e73986a6b7fdfedb7c5bf9e7eb71135486507c8fbc4c0c42cffcb6532988b7" +checksum = "32475a0459db5639e989206dd8833fb07110ec092a7cb3468c82341989cac4d3" dependencies = [ "anyhow", - "wasmparser 0.121.2", + "termcolor", + "wasmparser 0.227.1", ] [[package]] name = "wast" -version = "217.0.0" +version = "235.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79004ecebded92d3c710d4841383368c7f04b63d0992ddd6b0c7d5029b7629b7" +checksum = "1eda4293f626c99021bb3a6fbe4fbbe90c0e31a5ace89b5f620af8925de72e13" dependencies = [ "bumpalo", - "leb128", + "leb128fmt", "memchr", - "unicode-width", - "wasm-encoder 0.217.0", + "unicode-width 0.2.0", + "wasm-encoder 0.235.0", ] [[package]] name = "wat" -version = "1.217.0" +version = "1.235.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c126271c3d92ca0f7c63e4e462e40c69cca52fd4245fcda730d1cf558fb55088" +checksum = "e777e0327115793cb96ab220b98f85327ec3d11f34ec9e8d723264522ef206aa" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki-roots" -version = "0.26.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "which" -version = "6.0.3" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "either", - "home", - "rustix 0.38.37", - "winsafe", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -6594,86 +6087,104 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.58.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.58.0", - "windows-targets 0.52.6", + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "windows-collections" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-targets 0.52.6", + "windows-core", ] [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", + "windows-link", "windows-result", "windows-strings", - "windows-targets 0.52.6", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", ] [[package]] name = "windows-implement" -version = "0.58.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "windows-interface" -version = "0.58.0" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] -name = "windows-registry" +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-result", - "windows-strings", - "windows-targets 0.52.6", + "windows-core", + "windows-link", ] [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-result", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -6703,6 +6214,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -6727,13 +6247,38 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6746,6 +6291,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6758,6 +6309,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6770,12 +6327,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6788,6 +6357,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6800,6 +6375,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6812,6 +6393,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -6825,34 +6412,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.5.40" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winnow" -version = "0.6.18" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] -[[package]] -name = "winsafe" -version = "0.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" - [[package]] name = "winter-air" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72f12b88ebb060b52c0e9aece9bb64a9fc38daf7ba689dd5ce63271b456c883" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" dependencies = [ "libm", "winter-crypto", @@ -6863,9 +6441,9 @@ dependencies = [ [[package]] name = "winter-crypto" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00fbb724d2d9fbfd3aa16ea27f5e461d4fe1d74b0c9e0ed1bf79e9e2a955f4d5" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" dependencies = [ "blake3", "sha3", @@ -6875,9 +6453,9 @@ dependencies = [ [[package]] name = "winter-fri" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ab6077cf4c23c0411f591f4ba29378e27f26acb8cef3c51cadd93daaf6080b3" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" dependencies = [ "winter-crypto", "winter-math", @@ -6886,29 +6464,28 @@ dependencies = [ [[package]] name = "winter-math" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004f85bb051ce986ec0b9a2bd90aaf81b83e3c67464becfdf7db31f14c1019ba" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" dependencies = [ "winter-utils", ] [[package]] name = "winter-maybe-async" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce0f4161cdde50de809b3869c1cb083a09e92e949428ea28f04c0d64045875c" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" dependencies = [ - "proc-macro2", "quote", - "syn 2.0.77", + "syn", ] [[package]] name = "winter-prover" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17e3dbae97050f58e01ed4f12906e247841575a0518632e052941a1c37468df" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" dependencies = [ "tracing", "winter-air", @@ -6921,119 +6498,136 @@ dependencies = [ [[package]] name = "winter-utils" -version = "0.9.2" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" +dependencies = [ + "rayon", +] + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d71ec2c97685c7e7460a30e27f955d26b8426e7c2db0ddb55a6e0537141f53" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] name = "wit-bindgen-core" -version = "0.30.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7e3df01cd43cfa1cb52602e4fc05cb2b62217655f6705639b6953eb0a3fed2" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" dependencies = [ "anyhow", - "heck 0.5.0", - "wit-parser 0.215.0", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", ] [[package]] name = "wit-bindgen-rust" -version = "0.30.0" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a767d1a8eb4e908bfc53febc48b87ada545703b16fe0148ee7736a29a01417" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" dependencies = [ "anyhow", - "heck 0.5.0", - "indexmap 2.5.0", + "heck", + "indexmap", "prettyplease", - "syn 2.0.77", - "wasm-metadata 0.215.0", + "syn", + "wasm-metadata", "wit-bindgen-core", - "wit-component 0.215.0", + "wit-component", ] [[package]] -name = "wit-component" -version = "0.215.0" +name = "wit-bindgen-rust-macro" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f725e3885fc5890648be5c5cbc1353b755dc932aa5f1aa7de968b912a3280743" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" dependencies = [ "anyhow", - "bitflags 2.6.0", - "indexmap 2.5.0", - "log", - "serde 1.0.210", - "serde_derive", - "serde_json", - "wasm-encoder 0.215.0", - "wasm-metadata 0.215.0", - "wasmparser 0.215.0", - "wit-parser 0.215.0", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", ] [[package]] name = "wit-component" -version = "0.216.0" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2ca3ece38ea2447a9069b43074ba73d96dde1944cba276c54e41371745f9dc" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" dependencies = [ "anyhow", - "bitflags 2.6.0", - "indexmap 2.5.0", + "bitflags 2.9.1", + "indexmap", "log", - "serde 1.0.210", + "serde", "serde_derive", "serde_json", - "wasm-encoder 0.216.0", - "wasm-metadata 0.216.0", - "wasmparser 0.216.0", - "wit-parser 0.216.0", + "wasm-encoder 0.239.0", + "wasm-metadata", + "wasmparser 0.239.0", + "wit-parser", ] [[package]] name = "wit-parser" -version = "0.215.0" +version = "0.239.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "935a97eaffd57c3b413aa510f8f0b550a4a9fe7d59e79cd8b89a83dcb860321f" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" dependencies = [ "anyhow", "id-arena", - "indexmap 2.5.0", + "indexmap", "log", - "semver 1.0.23", - "serde 1.0.210", + "semver 1.0.26", + "serde", "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.215.0", + "wasmparser 0.239.0", ] [[package]] -name = "wit-parser" -version = "0.216.0" +name = "writeable" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d108165c1167a4ccc8a803dcf5c28e0a51d6739fd228cc7adce768632c764c" -dependencies = [ - "anyhow", - "id-arena", - "indexmap 2.5.0", - "log", - "semver 1.0.23", - "serde 1.0.210", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser 0.216.0", -] +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] -name = "xdg-home" -version = "1.3.0" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ - "libc", - "windows-sys 0.59.0", + "tap", ] [[package]] @@ -7047,95 +6641,73 @@ dependencies = [ [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] -name = "zbus" -version = "3.15.2" +name = "yoke" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "675d170b632a6ad49804c8cf2105d7c31eddd3312555cffd4b740e08e97c25e6" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ - "async-broadcast", - "async-executor", - "async-fs", - "async-io 1.13.0", - "async-lock 2.8.0", - "async-process", - "async-recursion", - "async-task", - "async-trait", - "blocking", - "byteorder", - "derivative", - "enumflags2", - "event-listener 2.5.3", - "futures-core", - "futures-sink", - "futures-util", - "hex", - "nix 0.26.4", - "once_cell", - "ordered-stream", - "rand", - "serde 1.0.210", - "serde_repr", - "sha1", - "static_assertions", - "tracing", - "uds_windows", - "winapi", - "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "zbus_macros" -version = "3.15.2" +name = "yoke-derive" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7131497b0f887e8061b430c530240063d33bf9455fa34438f388a245da69e0a5" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ - "proc-macro-crate", "proc-macro2", "quote", - "regex", - "syn 1.0.109", - "zvariant_utils", + "syn", + "synstructure", ] [[package]] -name = "zbus_names" -version = "2.6.1" +name = "zerocopy" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "437d738d3750bed6ca9b8d423ccc7a8eb284f6b1d6d4e225a0e4e6258d864c8d" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "serde 1.0.210", - "static_assertions", - "zvariant", + "zerocopy-derive", ] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "zerocopy-derive" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ - "byteorder", - "zerocopy-derive", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "zerocopy-derive" -version = "0.7.35" +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn", + "synstructure", ] [[package]] @@ -7145,39 +6717,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zvariant" -version = "3.15.2" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eef2be88ba09b358d3b58aca6e41cd853631d44787f319a1383ca83424fb2db" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" dependencies = [ - "byteorder", - "enumflags2", - "libc", - "serde 1.0.210", - "static_assertions", - "zvariant_derive", + "displaydoc", + "yoke", + "zerofrom", ] [[package]] -name = "zvariant_derive" -version = "3.15.2" +name = "zerovec" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c24dc0bed72f5f90d1f8bb5b07228cbf63b3c6e9f82d82559d4bae666e7ed9" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", - "zvariant_utils", + "yoke", + "zerofrom", + "zerovec-derive", ] [[package]] -name = "zvariant_utils" -version = "1.0.1" +name = "zerovec-derive" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7234f0d811589db492d16893e3f21e8e2fd282e6d01b0cddee310322062cc200" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 26bfea1f4..05f72ee7d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,11 @@ [workspace] resolver = "2" members = [ + "benches", "codegen/*", - "frontend-wasm", + "dialects/*", + "eval", + "frontend/*", "hir", "hir-analysis", "hir-macros", @@ -17,22 +20,22 @@ members = [ "sdk/*", "tools/*", "tests/integration", + "tests/integration-node", ] exclude = [ "sdk/.cargo", - "tests/rust-apps/fib", - "tests/rust-apps-wasm", - "cargo-ext/tests/data", + "tests/", + "examples", ] [workspace.package] -version = "0.0.7" -rust-version = "1.80" +version = "0.4.1" +rust-version = "1.90" authors = ["Miden contributors"] description = "An intermediate representation and compiler for Miden Assembly" -repository = "https://github.com/0xPolygonMiden/compiler" -homepage = "https://github.com/0xPolygonMiden/compiler" -documentation = "https://github.com/0xPolygonMiden/compiler" +repository = "https://github.com/0xMiden/compiler" +homepage = "https://github.com/0xMiden/compiler" +documentation = "https://github.com/0xMiden/compiler" categories = ["compilers"] keywords = ["compiler", "miden"] license = "MIT" @@ -41,73 +44,125 @@ edition = "2021" publish = false [workspace.dependencies] -anyhow = "1.0" +anyhow = { version = "1.0", default-features = false } bitflags = "2.4" -bitcode = { version = "0.6.3", default-features = false, features = ["serde"] } -clap = { version = "4.1", default-features = false, features = [ +bitvec = { version = "1.0", default-features = false, features = ["alloc"] } +blink-alloc = { version = "0.3", default-features = false, features = [ + "alloc", + "nightly", +] } +clap = { version = "4.5", default-features = false, features = [ "derive", "std", "env", "help", + "suggestions", + "error-context", ] } -cranelift-entity = "0.108" -cranelift-bforest = "0.108" +cranelift-entity = "0.120" +compact_str = { version = "0.9", default-features = false } env_logger = "0.11" -either = { version = "1.10", default-features = false } -expect-test = "1.4.1" +hashbrown = { version = "0.15", features = ["nightly"] } +hashbrown_old_nightly_hack = { package = "hashbrown", version = "0.14.5", features = [ + "allocator-api2", + "nightly", +] } Inflector = "0.11" intrusive-collections = "0.9" inventory = "0.3" log = "0.4" + +# Miden Dependencies +miden-assembly = { version = "0.17.1", default-features = false } +miden-core = { version = "0.17.1", default-features = false } +miden-debug-types = { version = "0.17.1", default-features = false } +miden-assembly-syntax = { version = "0.17.1", default-features = false } +miden-formatting = { version = "0.1", default-features = false } +miden-lib = { version = "0.11.0", default-features = false, features = [ + "with-debug-info", +] } +miden-objects = { version = "0.11.0", default-features = false } +miden-processor = { version = "0.17.1", default-features = false } +miden-stdlib = { version = "0.17.1", default-features = false, features = [ + "with-debug-info", +] } +miden-mast-package = { version = "0.17.1", default-features = false } miette = { package = "miden-miette", version = "7.1.1" } -#miette = { version = "7.1", git = "https://github.com/bitwalker/miette", branch = "no-std" } paste = "1.0" parking_lot = "0.12" parking_lot_core = "0.9" -petgraph = "0.6" +petgraph = { version = "0.8", default-features = false, features = [ + "graphmap" +] } pretty_assertions = "1.0" proptest = "1.4" -rustc-hash = "1.1" -serde = { version = "1.0.208", features = ["serde_derive", "alloc", "rc"] } -serde_repr = "0.1.19" -serde_bytes = "0.11.15" -smallvec = { version = "1.13", features = [ +proc-macro2 = "1.0" +quote = "1.0" +rustc-hash = { version = "2.0", default-features = false } +semver = { version = "1.0", default-features = false } +serde = { version = "1.0", default-features = false, features = [ + "serde_derive", + "alloc", + "rc", +] } +heck = "0.5" + +smallvec = { version = "1.14", default-features = false, features = [ "union", "const_generics", "const_new", "drain_filter", ] } -smallstr = { version = "0.3", features = ["union"] } +syn = { version = "2.0", features = [ + "full", + "parsing", + "derive", + "extra-traits", + "printing", +] } thiserror = { package = "miden-thiserror", version = "1.0" } -#thiserror = { version = "1.0", git = "https://github.com/bitwalker/thiserror", branch = "no-std" } toml = { version = "0.8", features = ["preserve_order"] } -derive_more = "0.99" -indexmap = "2.2" -miden-assembly = { version = "0.10.3" } -miden-core = { version = "0.10.3" } -miden-parsing = "0.1" -miden-processor = { version = "0.10.3" } -miden-stdlib = { version = "0.10.3", features = ["with-debug-info"] } -#miden-assembly = { git = "https://github.com/0xPolygonMiden/miden-vm", rev = "828557c28ca1d159bfe42195e7ea73256ce4aa06" } -#miden-core = { git = "https://github.com/0xPolygonMiden/miden-vm", rev = "828557c28ca1d159bfe42195e7ea73256ce4aa06" } -#miden-processor = { git = "https://github.com/0xPolygonMiden/miden-vm", rev = "828557c28ca1d159bfe42195e7ea73256ce4aa06" } -#miden-stdlib = { git = "https://github.com/0xPolygonMiden/miden-vm", rev = "828557c28ca1d159bfe42195e7ea73256ce4aa06" } -midenc-codegen-masm = { version = "0.0.7", path = "codegen/masm" } -midenc-hir = { version = "0.0.7", path = "hir" } -midenc-hir-analysis = { version = "0.0.7", path = "hir-analysis" } -midenc-hir-macros = { version = "0.0.7", path = "hir-macros" } -midenc-hir-symbol = { version = "0.0.7", path = "hir-symbol" } -midenc-hir-transform = { version = "0.0.7", path = "hir-transform" } -midenc-hir-type = { version = "0.0.7", path = "hir-type" } -midenc-frontend-wasm = { version = "0.0.7", path = "frontend-wasm" } -midenc-compile = { version = "0.0.7", path = "midenc-compile" } -midenc-driver = { version = "0.0.7", path = "midenc-driver" } -midenc-debug = { version = "0.0.7", path = "midenc-debug" } -midenc-session = { version = "0.0.7", path = "midenc-session" } -cargo-miden = { version = "0.0.7", path = "tools/cargo-miden" } -miden-integration-tests = { version = "0.0.0", path = "tests/integration" } +tokio = { version = "1.39.2", features = ["rt", "time", "macros", "rt-multi-thread"] } wat = "1.0.69" -blake3 = "1.5" +wasmparser = { version = "0.227", default-features = false, features = [ + "features", + "component-model", + "validate", + "simd", +] } + +# Workspace crates +midenc-codegen-masm = { version = "0.4.1", path = "codegen/masm" } +midenc-dialect-arith = { version = "0.4.1", path = "dialects/arith" } +midenc-dialect-hir = { version = "0.4.1", path = "dialects/hir" } +midenc-dialect-scf = { version = "0.4.1", path = "dialects/scf" } +midenc-dialect-cf = { version = "0.4.1", path = "dialects/cf" } +midenc-dialect-ub = { version = "0.4.1", path = "dialects/ub" } +midenc-hir = { version = "0.4.1", path = "hir" } +midenc-hir-analysis = { version = "0.4.1", path = "hir-analysis" } +midenc-hir-eval = { version = "0.4.1", path = "eval" } +midenc-hir-macros = { version = "0.4.1", path = "hir-macros" } +midenc-hir-symbol = { version = "0.4.1", path = "hir-symbol" } +midenc-hir-transform = { version = "0.4.1", path = "hir-transform" } +midenc-hir-type = { version = "0.4.2", path = "hir-type" } +midenc-frontend-wasm = { version = "0.4.1", path = "frontend/wasm" } +midenc-compile = { version = "0.4.1", path = "midenc-compile" } +midenc-driver = { version = "0.4.1", path = "midenc-driver" } +midenc-debug = { version = "0.4.1", path = "midenc-debug" } +midenc-session = { version = "0.4.1", path = "midenc-session" } +cargo-miden = { version = "0.4.1", path = "tools/cargo-miden" } +miden-integration-tests = { path = "tests/integration" } +midenc-expect-test = { path = "tools/expect-test" } + +[patch.crates-io] +#miden-assembly = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } +#miden-core = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } +#miden-client = { git = "https://github.com/0xMiden/miden-client", rev = "f98ae87fc3f77e269c3c1e412d1b8ac650aa6fe3" } +#miden-processor = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } +#miden-lib = { git = "https://github.com/0xMiden/miden-base", rev = "64ba4d0f8a077dd7d8f2643ebafa155f95b1c241" } +#miden-stdlib = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } +#miden-mast-package = { git = "https://github.com/0xMiden/miden-vm", rev = "614cd7f9b52f45238b0ab59c71ebb49325051e5d" } + [profile.dev] lto = false @@ -118,7 +173,7 @@ codegen-units = 1 opt-level = 2 debug = true codegen-units = 1 -lto = "thin" +lto = "fat" # The following crates are always built with optimizations [profile.test.package.proptest] @@ -127,9 +182,6 @@ opt-level = 3 [profile.test.package.rand_chacha] opt-level = 3 -[profile.dev.package.expect-test] -opt-level = 3 - [profile.dev.package.dissimilar] opt-level = 3 @@ -141,3 +193,24 @@ opt-level = 3 [profile.dev.package.miden-crypto] opt-level = 3 + +# Speed up the local network integration tests (proving times) +# ============================================================ +[profile.dev.package.miden-processor] +opt-level = 3 + +[profile.dev.package.miden-prover] +opt-level = 3 + +[profile.dev.package.winter-prover] +opt-level = 3 + +[profile.dev.package.miden-client] +opt-level = 3 + +[profile.dev.package.miden-lib] +opt-level = 3 + +[profile.dev.package.miden-tx] +opt-level = 3 +# ============================================================ diff --git a/Makefile.toml b/Makefile.toml index 817428243..5eaed34ca 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -21,6 +21,9 @@ RUST_BACKTRACE = { value = "${BACKTRACE_DEFAULT}", condition = { env_not_set = [ MIDENC_BIN_DIR = "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/bin" MIDENC_INSTALL_DIR = "${MIDENC_BIN_DIR}/${CARGO_MAKE_RUST_TARGET_TRIPLE}" +# Workaround for https://github.com/0xMiden/miden-vm/issues/2025 +RUST_MIN_STACK = 16777216 + [env.development] MIDENC_BUILD_PROFILE = "debug" @@ -155,6 +158,18 @@ description = "Run cargo-bloat" command = "cargo" args = ["bloat", "${@}"] +[tasks.bench] +category = "Benchmarks" +description = "Runs benchmarks written in Rust" +command = "cargo" +args = [ + "run", + "@@remove-empty(CARGO_MAKE_CARGO_VERBOSE_FLAGS)", + "-p", + "midenc-benchmark-runner", + "${@}", +] + [tasks.midenc] category = "Build" description = "Builds midenc and installs it to the bin folder" @@ -165,8 +180,24 @@ args = [ "build", "-p", "midenc", - "--out-dir", + "--artifact-dir", + "${MIDENC_BIN_DIR}", + "@@split(CARGO_MAKE_TASK_ARGS, )", +] + +[tasks.cargo-miden] +category = "Build" +description = "Builds cargo-miden and installs it to the bin folder" +command = "cargo" +args = [ + "-Z", + "unstable-options", + "build", + "-p", + "cargo-miden", + "--artifact-dir", "${MIDENC_BIN_DIR}", + "@@split(CARGO_MAKE_TASK_ARGS, )", ] [tasks.build] @@ -177,7 +208,12 @@ run_task = [{ name = ["midenc"] }] [tasks.install] category = "Install" description = "Installs the compiler via cargo" -run_task = [{ name = ["install-midenc"] }] +run_task = [ + { name = [ + "install-midenc", + "install-cargo-miden", + ] }, +] [tasks.check] category = "Build" @@ -185,23 +221,87 @@ description = "Runs cargo check on the workspace" command = "cargo" args = ["check", "${@}"] +[tasks.check-each] +category = "Build" +description = "Runs cargo check on each member of the workspace independently" +workspace = true +command = "cargo" +args = ["check", "${@}"] + [tasks.clean] category = "Build" description = "Clean build artifacts" command = "cargo" args = ["clean", "${@}"] +[tasks.update] +category = "Dependencies" +description = "Update dependencies to latest semver-compatible version" +command = "cargo" +args = ["update"] + +[tasks.outdated] +category = "Dependencies" +description = "Look for dependencies with newer, possibly-incompatible versions" +command = "cargo" +args = ["outdated", "--workspace", "--root-deps-only"] + +[tasks.check-deps] +category = "Dependencies" +description = "Check dependencies for lack of use, duplicates, vulnerabilities, etc." +run_task = [ + { name = [ + "unused", + "duplicates", + "audit", + ] }, +] + +[tasks.unused] +category = "Dependencies" +description = "Check for unused dependencies" +command = "cargo" +args = ["machete", "--with-metadata"] + +[tasks.duplicates] +category = "Dependencies" +description = "Check dependencies for multiple versions of the same crate" +command = "cargo" +args = ["tree", "--duplicate"] + +[tasks.audit] +category = "Dependencies" +description = "Check dependencies for known vulnerabilities, deprecations" +command = "cargo" +args = ["audit"] + [tasks.test] category = "Test" -description = "Runs all tests" +description = "Runs all tests including integration node tests" dependencies = ["test-rust"] +[tasks.install-cargo-miden] +category = "Install" +description = "Builds cargo-miden and installs it globally via the cargo bin directory" +command = "cargo" +args = [ + "install", + "--locked", + "--path", + "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/tools/cargo-miden", + "--${MIDENC_BUILD_PROFILE}", + "--force", + "--bin", + "cargo-miden", +] + [tasks.install-midenc] category = "Install" description = "Builds midenc and installs it globally via the cargo bin directory" command = "cargo" args = [ "install", + "--locked", "--path", "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/midenc", "--${MIDENC_BUILD_PROFILE}", @@ -210,11 +310,6 @@ args = [ "midenc", ] -[tasks.install-cargo-component] -category = "Test" -description = "Install cargo-component extension" -command = "cargo" -args = ["install", "cargo-component@0.16.0"] [tasks.test-rust] category = "Test" @@ -227,7 +322,6 @@ args = [ "@@split(CARGO_MAKE_CARGO_BUILD_TEST_FLAGS, )", "${@}", ] -dependencies = ["install-cargo-component"] [tasks.test-lit] category = "Test" @@ -250,22 +344,52 @@ description = "Set up the litcheck utility" install_crate = { crate_name = "litcheck", test_arg = "--help" } [tasks.docs] -category = "Build" -description = "Builds the compiler documentation" -command = "docs/mkdocs" -args = ["build", "-d", "target/docs/site"] +category = "Documentation" +description = "Opens the public compiler documentation" +dependencies = ["docusaurus"] +cwd = "./docs/external" +command = "npm" +args = ["run", "start:dev"] -[tasks.serve-docs] +[tasks.docusaurus] category = "Build" -description = "Opens the compiler documentation" -command = "docs/mkdocs" -args = ["serve"] +cwd = "./docs/external" +command = "npm" +args = ["install"] -[tasks.mkdocs] +[tasks.internal-docs] category = "Documentation" -description = "Run the mkdocs command-line tool" -command = "docs/mkdocs" -args = ["${@}"] +description = "Builds the internal compiler documentation" +dependencies = ["mdbook", "mdbook-linkcheck", "mdbook-alerts", "mdbook-katex"] +command = "mdbook" +args = ["build", "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/docs/internal"] + +[tasks.mdbook] +category = "Build" +install_crate = { crate_name = "mdbook", binary = "mdbook", test_arg = [ + "--help", +] } + +[tasks.mdbook-linkcheck] +category = "Build" +install_crate = { crate_name = "mdbook-linkcheck" } + +[tasks.mdbook-alerts] +category = "Build" +install_crate = { crate_name = "mdbook-alerts" } + +[tasks.mdbook-katex] +category = "Build" +install_crate = { crate_name = "mdbook-katex" } + +[tasks.book] +category = "Build" +description = "Opens the compiler documentation" +install_crate = { crate_name = "mdbook", binary = "mdbook", test_arg = [ + "--help", +] } +command = "mdbook" +args = ["serve", "--open", "${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/docs"] [tasks.clippy] description = "Runs clippy on the workspace" @@ -281,3 +405,12 @@ args = [ "-D", "warnings", ] + +[tasks.publish-dry-run] +category = "Test" +description = "Simulate publishing to uncover any build errors when each crate folder is built in isolation" +command = "cargo" +args = [ + "publish", + "--dry-run", +] diff --git a/README.md b/README.md index fdca7dc39..e3455c972 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Miden Compiler -> [!IMPORTANT] +> [!IMPORTANT] > This project is a work-in-progress, so if you encounter bugs or other > things which are not covered in the issue tracker, there is a good chance we know > about them, but please do report them anyway so we can ensure they are tracked @@ -12,18 +12,18 @@ or as means of compiling WebAssembly (Wasm) produced by another compiler to Mide This repo is broken into the following high-level components: -* Miden HIR (high-level intermediate representation) and it's supporting crates; -providing everything needed to build and compile IR for a program you want to -emit Miden Assembly for. -* The Wasm frontend; a library which can be used to convert a program compiled to `.wasm` to HIR -* The `midenc` executable, which provides a command-line tool that provides a convenient way -to compile Wasm or HIR modules/programs to Miden Assembly and test them. +- Miden HIR (high-level intermediate representation) and it's supporting crates; + providing everything needed to build and compile IR for a program you want to + emit Miden Assembly for. +- The Wasm frontend; a library which can be used to convert a program compiled to `.wasm` to HIR +- The `midenc` executable, which provides a command-line tool that provides a convenient way + to compile Wasm or HIR modules/programs to Miden Assembly and test them. -> [!TIP] -> We've published initial [documentation](https://0xpolygonmiden.github.io/compiler) -> in mdBook format for easier reading, also accessible in the `docs` directory. This documentation +> [!TIP] +> We've published initial [documentation](https://0xMiden.github.io/compiler) +> in mdBook format for easier reading, also accessible in the `docs` directory. This documentation > covers how to get started with the compiler, provides a couple guides for currently supported -> use cases, and contains appendices that go into detail about various design aspects of the +> use cases, and contains appendices that go into detail about various design aspects of the > toolchain. ## Building @@ -50,6 +50,12 @@ To run the compiler test suite: This will run all of the unit tests in the workspace, as well as all of our `lit` tests. +## Docs + +The documentation in the `docs/external` folder is built using Docusaurus and is automatically absorbed into the main [miden-docs](https://github.com/0xMiden/miden-docs) repository for the main documentation website. Changes to the `next` branch trigger an automated deployment workflow. The docs folder requires npm packages to be installed before building. + +The `docs/internal` folder corresponds to internal docs, which are hosted using mdbook and Github Pages here: [0xmiden.github.io/compiler/](0xmiden.github.io/compiler/). These md files are not exported to the main docs. + ## Packaging TBD diff --git a/benches/Cargo.toml b/benches/Cargo.toml new file mode 100644 index 000000000..6372def62 --- /dev/null +++ b/benches/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "midenc-benchmark-runner" +version.workspace = true +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +description = "Benchmarks for the Miden compiler" +publish = false + +[[bin]] +name = "is_prime" +path = "src/is_prime.rs" + +[dependencies] +anyhow.workspace = true +clap.workspace = true + +[dev-dependencies] +criterion = "0.5" + +[[bench]] +name = "is_prime_bench" +harness = false diff --git a/benches/README.md b/benches/README.md new file mode 100644 index 000000000..5382615e5 --- /dev/null +++ b/benches/README.md @@ -0,0 +1,54 @@ +# Miden Compiler Benchmarks + +Benchmarks for measuring VM cycles and performance of Miden programs. + +## Usage + +### From project root (recommended) + +```bash +# Run is_prime benchmark +cargo make bench --bin is_prime + +# Custom input +cargo make bench --bin is_prime -- --input 97 + +# Multiple iterations +cargo make bench --bin is_prime -- --input 97 --iterations 5 + +# Custom source file +cargo make bench --bin is_prime -- --source examples/is-prime/src/lib.rs --input 29 +``` + +### Direct cargo commands + +```bash +# Run is_prime benchmark +cargo run -p midenc-benchmark-runner --bin is_prime + +# Custom input +cargo run -p midenc-benchmark-runner --bin is_prime -- --input 97 + +# Multiple iterations +cargo run -p midenc-benchmark-runner --bin is_prime -- --input 29 --iterations 5 + +# Criterion benchmarks +cargo bench +``` + +## Benchmark Results + +| Input | VM Cycles | Prime? | +| ------------- | --------- | ------ | +| 13 | 533 | ✓ | +| 97 | 805 | ✓ | +| 4,397 | 3,525 | ✓ | +| 285,191 | 24,741 | ✓ | +| 87,019,979 | 423,221 | ✓ | +| 2,147,483,647 | 2,101,189 | ✓ | + +## Adding benchmarks + +1. Add binary to `src/` +2. Add `[[bin]]` entry to `Cargo.toml` +3. Use `BenchmarkRunner` from the lib diff --git a/benches/benches/is_prime_bench.rs b/benches/benches/is_prime_bench.rs new file mode 100644 index 000000000..2b56a04b1 --- /dev/null +++ b/benches/benches/is_prime_bench.rs @@ -0,0 +1,76 @@ +//! Criterion benchmark for the is_prime program +//! +//! This provides detailed performance analysis using the Criterion benchmarking framework. + +use std::path::PathBuf; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use midenc_benchmark_runner::BenchmarkRunner; + +fn bench_is_prime_compilation(c: &mut Criterion) { + let runner = BenchmarkRunner::new().expect("Failed to create benchmark runner"); + let source_path = PathBuf::from("../examples/is-prime/src/lib.rs"); + + c.bench_function("is_prime_compilation", |b| { + b.iter(|| { + runner + .compile_rust_to_masm(black_box(&source_path)) + .expect("Compilation failed") + }) + }); +} + +fn bench_is_prime_execution(c: &mut Criterion) { + let runner = BenchmarkRunner::new().expect("Failed to create benchmark runner"); + let source_path = PathBuf::from("../examples/is-prime/src/lib.rs"); + + // Pre-compile the program + let masm_path = runner.compile_rust_to_masm(&source_path).expect("Failed to compile program"); + + let mut group = c.benchmark_group("is_prime_execution"); + + // Test with different input values + for input in [7, 29, 97, 997, 9973].iter() { + group.bench_with_input(format!("is_prime({input})"), input, |b, &input| { + b.iter(|| { + runner + .execute_masm(black_box(&masm_path), black_box(&[input as u64])) + .expect("Execution failed") + }) + }); + } + + group.finish(); +} + +fn bench_is_prime_full_pipeline(c: &mut Criterion) { + let runner = BenchmarkRunner::new().expect("Failed to create benchmark runner"); + let source_path = PathBuf::from("../examples/is-prime/src/lib.rs"); + + let mut group = c.benchmark_group("is_prime_full_pipeline"); + + // Test full compilation + execution pipeline + for input in [29, 97, 997].iter() { + group.bench_with_input(format!("full_pipeline_is_prime({input})"), input, |b, &input| { + b.iter(|| { + runner + .run_benchmark( + black_box(&source_path), + black_box(&[input as u64]), + &format!("is_prime({input})"), + ) + .expect("Benchmark failed") + }) + }); + } + + group.finish(); +} + +criterion_group!( + benches, + bench_is_prime_compilation, + bench_is_prime_execution, + bench_is_prime_full_pipeline +); +criterion_main!(benches); diff --git a/benches/src/is_prime.rs b/benches/src/is_prime.rs new file mode 100644 index 000000000..3a87d2730 --- /dev/null +++ b/benches/src/is_prime.rs @@ -0,0 +1,84 @@ +//! Benchmark for the is_prime program +//! +//! This benchmark compiles the is_prime Rust program to Miden assembly +//! and measures its execution performance in the Miden VM. + +use std::path::PathBuf; + +use clap::Parser; +use midenc_benchmark_runner::BenchmarkRunner; + +#[derive(Parser)] +struct Config { + /// The number to test for primality + #[arg(short = 'i', long, value_name = "NUMBER", default_value = "29")] + input: usize, + /// Path to the is_prime source file + #[arg( + short = 's', + long, + value_name = "PATH", + default_value = "examples/is-prime/src/lib.rs" + )] + source: PathBuf, + /// Number of iterations to run + #[arg(short = 'n', long, value_name = "COUNT", default_value = "1")] + iterations: usize, +} + +fn main() -> anyhow::Result<()> { + let config = Config::parse(); + + println!("Is Prime Benchmark"); + println!("=================="); + println!("Input number: {}", config.input); + println!("Source file: {}", config.source.display()); + println!("Iterations: {}", config.iterations); + println!(); + + let runner = BenchmarkRunner::new()?; + + let mut total_cycles = 0; + let mut total_compile_time = 0; + let mut total_execution_time = 0; + + for i in 1..=config.iterations { + if config.iterations > 1 { + println!("--- Iteration {i} ---"); + } + + let stats = runner.run_benchmark( + &config.source, + &[config.input as u64], + &format!("is_prime({})", config.input), + )?; + + total_cycles += stats.vm_cycles; + total_compile_time += stats.compile_time_ms; + total_execution_time += stats.execution_time_ms; + + if config.iterations > 1 { + println!(); + } + } + + if config.iterations > 1 { + println!("==============================================================================="); + println!("Average results over {} iterations:", config.iterations); + println!("-------------------------------------------------------------------------------"); + println!("Average VM cycles: {}", total_cycles / config.iterations); + println!( + "Average compilation time: {} ms", + total_compile_time / config.iterations as u128 + ); + println!( + "Average execution time: {} ms", + total_execution_time / config.iterations as u128 + ); + println!("Total compilation time: {total_compile_time} ms"); + println!("Total execution time: {total_execution_time} ms"); + println!("==============================================================================="); + } + + Ok(()) +} diff --git a/benches/src/lib.rs b/benches/src/lib.rs new file mode 100644 index 000000000..ff224a423 --- /dev/null +++ b/benches/src/lib.rs @@ -0,0 +1,200 @@ +//! Common benchmarking framework for Miden compiler programs +//! +//! This module provides utilities for compiling Rust programs to Miden assembly +//! and measuring their execution performance in the Miden VM. + +use std::{ + path::{Path, PathBuf}, + time::Instant, +}; + +use anyhow::{Context, Result}; + +/// Execution statistics for a Miden program +#[derive(Debug, Clone)] +pub struct ExecutionStats { + /// Total VM cycles executed + pub vm_cycles: usize, + /// Compilation time in milliseconds + pub compile_time_ms: u128, + /// Execution time in milliseconds + pub execution_time_ms: u128, +} + +impl ExecutionStats { + /// Create execution stats from midenc output + pub fn from_midenc_output( + output: &str, + compile_time_ms: u128, + execution_time_ms: u128, + ) -> Result { + // Parse the VM cycles from midenc output + let vm_cycles = Self::parse_vm_cycles(output)?; + + Ok(Self { + vm_cycles, + compile_time_ms, + execution_time_ms, + }) + } + + /// Parse VM cycles from midenc run output + fn parse_vm_cycles(output: &str) -> Result { + for line in output.lines() { + if line.contains("VM cycles:") { + // Look for pattern like "VM cycles: 805 extended to 1024 steps" + if let Some(cycles_part) = line.split("VM cycles:").nth(1) { + if let Some(cycles_str) = cycles_part.split_whitespace().next() { + return cycles_str.parse().with_context(|| { + format!("Failed to parse VM cycles from: {cycles_str}") + }); + } + } + } + } + Err(anyhow::anyhow!("Could not find VM cycles in output")) + } + + /// Print formatted execution statistics + pub fn print(&self, program_name: &str) { + println!("==============================================================================="); + println!("Benchmark results for: {program_name}"); + println!("-------------------------------------------------------------------------------"); + println!( + "VM cycles: {} extended to {} steps", + self.vm_cycles, + self.vm_cycles.next_power_of_two() + ); + println!("Compilation time: {} ms", self.compile_time_ms); + println!("Execution time: {} ms", self.execution_time_ms); + println!("==============================================================================="); + } +} + +/// A benchmark runner for Miden programs +pub struct BenchmarkRunner; + +impl BenchmarkRunner { + /// Create a new benchmark runner + pub fn new() -> Result { + Ok(Self) + } + + /// Compile a Rust source file to Miden assembly using cargo miden + pub fn compile_rust_to_masm(&self, source_path: &Path) -> Result { + let compile_start = Instant::now(); + + // Convert to absolute path if relative + let abs_source_path = if source_path.is_absolute() { + source_path.to_path_buf() + } else { + std::env::current_dir()?.join(source_path) + }; + + // Get the directory containing the source file + let project_dir = abs_source_path.parent() + .and_then(|p| p.parent()) // Go up from src/ to project root + .ok_or_else(|| anyhow::anyhow!("Could not determine project directory"))?; + + // Use cargo miden to build the project + let mut cmd = std::process::Command::new("cargo"); + cmd.arg("miden") + .arg("build") + .arg("--release") + .arg("--manifest-path") + .arg(project_dir.join("Cargo.toml")) + .current_dir(project_dir); + + let output = cmd.output().with_context(|| "Failed to execute cargo miden build")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(anyhow::anyhow!("cargo miden build failed: {}", stderr)); + } + + let compile_time = compile_start.elapsed(); + println!("Compilation completed in {} ms", compile_time.as_millis()); + + // Find the generated .masp file + let target_dir = project_dir.join("target").join("miden").join("release"); + let project_name = project_dir + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| anyhow::anyhow!("Could not determine project name"))?; + + // Convert hyphens to underscores for the MASP filename + let masp_filename = project_name.replace('-', "_"); + let masp_path = target_dir.join(format!("{masp_filename}.masp")); + + if !masp_path.exists() { + return Err(anyhow::anyhow!("Expected MASP file not found: {}", masp_path.display())); + } + + Ok(masp_path) + } + + /// Execute a Miden assembly program using midenc run and return execution statistics + pub fn execute_masm(&self, masm_path: &Path, inputs: &[u64]) -> Result { + let execution_start = Instant::now(); + + // Create inputs file in TOML format + let inputs_content = format!( + r#"[inputs] +stack = [{}]"#, + inputs.iter().map(|i| i.to_string()).collect::>().join(", ") + ); + + let inputs_file = masm_path.with_extension("inputs"); + std::fs::write(&inputs_file, inputs_content) + .with_context(|| format!("Failed to write inputs file: {}", inputs_file.display()))?; + + // Use midenc run to execute the program + let mut cmd = std::process::Command::new("midenc"); + cmd.arg("run").arg(masm_path).arg("--inputs").arg(&inputs_file); + + let output = cmd.output().with_context(|| "Failed to execute midenc run")?; + + let execution_time = execution_start.elapsed(); + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(anyhow::anyhow!("midenc run failed: {}", stderr)); + } + + let stdout = String::from_utf8_lossy(&output.stdout); + println!("Program executed successfully"); + println!("{stdout}"); + + // Clean up inputs file + let _ = std::fs::remove_file(&inputs_file); + + ExecutionStats::from_midenc_output(&stdout, 0, execution_time.as_millis()) + } + + /// Run a complete benchmark: compile Rust to MASM and execute + pub fn run_benchmark( + &self, + source_path: &Path, + inputs: &[u64], + program_name: &str, + ) -> Result { + println!("Running benchmark for: {program_name}"); + + let compile_start = Instant::now(); + let masm_path = self.compile_rust_to_masm(source_path)?; + let compile_time = compile_start.elapsed(); + + let mut stats = self.execute_masm(&masm_path, inputs)?; + stats.compile_time_ms = compile_time.as_millis(); + + stats.print(program_name); + + Ok(stats) + } +} + +impl Default for BenchmarkRunner { + fn default() -> Self { + Self::new().expect("Failed to create benchmark runner") + } +} diff --git a/codegen/masm/CHANGELOG.md b/codegen/masm/CHANGELOG.md index b911fa63b..829ceb883 100644 --- a/codegen/masm/CHANGELOG.md +++ b/codegen/masm/CHANGELOG.md @@ -6,6 +6,109 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-codegen-masm-v0.4.0...midenc-codegen-masm-v0.4.1) - 2025-09-03 + +### Fixed + +- reverse only non-intrinsic modules when assembling a Program +- use `Assembler::add_module` for intrinsics when assembling a `Library` +- remove intrinsics exports from compiled MASM library #637 + +### Other + +- Add 128-bit wide arithmetic support to the compiler. +- Fix bug with argument scheduler for binary ops. + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-codegen-masm-v0.1.5...midenc-codegen-masm-v0.4.0) - 2025-08-15 + +### Added + +- implement advice map API in Miden SDK +- add `crypto::hmerge()` in Miden SDK (`hmerge` VM intruction); + +### Fixed + +- add an extra memory page after the data segments to accommodate for +- start `Linker::reserved_memory_pages` on the next page after rustc +- *(codegen)* missing result renaming in ptrtoint lowering +- two-operand optimized stack scheduling when only one value is on the stack #606 +- fix the manual two-operand ops stack scheduling, remove `BinaryOp` gate +- do binary stack scheduling optimizations only for the binary ops (implementing +- `Rodata::bytes_to_elements()` check if `into_remainder()` returns + +### Other + +- rename `io` to `advice`, export modules in stdlib SDK +- Add the test that executes counter contract, basic wallet and p2id note script on the local node ([#555](https://github.com/0xMiden/compiler/pull/555)) +- make `DEFAULT_RESERVATION` include the extra page to +- add comments with stack state in `heap_init` +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) +- clean up +- simplify `test_arg_order` test and move it to `misc` module +- `hmerge` intrinsic to accept digests as a pointer and load +- make `bytes_to_elements` infallible + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-codegen-masm-v0.1.0...midenc-codegen-masm-v0.1.5) - 2025-07-01 + +### Added + +- small integers in `struct` support in result in cross-context calls + +### Fixed + +- add immediate pointer to `OpEmitter::load_small` +- call `truncate_stack` before returning from a `call`-able procedure +- mask calculation in `trunc_int32` function (128 -> 255 for u8); + +### Other + +- Merge pull request #572 from 0xMiden/greenhat/i560-init-account-note +- add `store_small_imm` function doc and assert +- don't delegate in `store_small_imm` to `store_word_imm`, +- add comments +- remove redundant `store` tests +- reduce stack manipulation in `store_small` +- assert that type to be loaded and the pointee type have the +- use `NativePtr::is_word_aligned` +- remove `expect-test` in favor of `midenc-expect-test` + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-codegen-masm-v0.0.7...midenc-codegen-masm-v0.0.8) - 2025-04-24 + +### Added +- *(types)* clean up hir-type for use outside the compiler +- *(codegen)* migrate to element-addressable vm +- implement cross-context Call op, rename IR Exec back to Call + +### Fixed +- *(codegen)* incorrect conversion of u64 literal to miden repr +- *(codegen)* incomplete global/data segment lowering +- *(codegen)* lowering of builtin.global_symbol +- *(codegen)* bitcasts should allow pointer-to-pointer casts +- [#406](https://github.com/0xMiden/compiler/pull/406) order the program modules with intrinsics being the first when passed +- relax the module name validation in ConvertHirToMasm to support the Wasm CM naming +- skip the assembler registration for the already registered module +- recover Wasm CM interfaces as module names in exports after +- skip registering already registered modules in Assembler +- fallback to `LibraryPath` unchecked ctor (Wasm CM styles names) +- sort module imports and functions in PrettyPrint impl to stabilize + +### Other +- treat warnings as compiler errors, +- update expected masm code +- update Miden VM to v0.13.2 and uncomment the Miden package +- *(codegen)* improve tracing/debug output in a few places +- update rust toolchain, clean up deps +- rename hir2 crates +- remove old contents of hir, hir-analysis, hir-transform +- switch from recognizing intrinsics module by name(substring) +- update to the latest `miden-mast-package` (renamed from +- update the Miden VM with updated `miden-package` crate +- update rust toolchain to 1-16 nightly @ 1.86.0 +- normalize use of fxhash-based hash maps +- rename Call IR op to Exec +- switch to `Package` without rodata, +- [**breaking**] move `Package` to `miden-package` in the VM repo + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-codegen-masm-v0.0.6...midenc-codegen-masm-v0.0.7) - 2024-09-17 ### Other diff --git a/codegen/masm/Cargo.toml b/codegen/masm/Cargo.toml index 0a3b9cb96..7222103d3 100644 --- a/codegen/masm/Cargo.toml +++ b/codegen/masm/Cargo.toml @@ -13,31 +13,33 @@ edition.workspace = true [features] default = ["std"] -std = ["bitcode/std"] +std = ["midenc-hir/std", "midenc-dialect-hir/std", "petgraph/std"] [dependencies] -anyhow.workspace = true -bitcode.workspace = true -cranelift-entity.workspace = true -intrusive-collections.workspace = true inventory.workspace = true log.workspace = true miden-assembly.workspace = true +miden-assembly-syntax.workspace = true miden-core.workspace = true +miden-mast-package.workspace = true miden-processor.workspace = true -miden-stdlib.workspace = true -midenc-hir = { workspace = true, features = ["serde"] } +miden-lib.workspace = true +midenc-hir.workspace = true midenc-hir-analysis.workspace = true -midenc-hir-transform.workspace = true -midenc-session = { workspace = true, features = ["serde"] } -paste.workspace = true +midenc-dialect-arith.workspace = true +midenc-dialect-cf.workspace = true +midenc-dialect-hir.workspace = true +midenc-dialect-scf.workspace = true +midenc-dialect-ub.workspace = true +midenc-session.workspace = true petgraph.workspace = true -rustc-hash.workspace = true serde.workspace = true -serde_bytes.workspace = true smallvec.workspace = true thiserror.workspace = true [dev-dependencies] +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging +midenc-expect-test = { path = "../../tools/expect-test" } proptest.workspace = true env_logger.workspace = true +itertools = "0.14.0" diff --git a/codegen/masm/intrinsics/advice.masm b/codegen/masm/intrinsics/advice.masm new file mode 100644 index 000000000..34199f809 --- /dev/null +++ b/codegen/masm/intrinsics/advice.masm @@ -0,0 +1,49 @@ +#! ADV_PUSH_MAPVALN: Pushes a list of field elements onto the advice stack. +#! The list is looked up in the advice map using `key` as the key. +#! +#! Stack Input: [key3, key2, key1, key0, ...] +#! Stack Output: [num_elements, ...] (returns the number of elements pushed) +#! +#! The key is a Word (4 field elements) used to lookup values in the advice map +export.adv_push_mapvaln + # Stack: [key3, key2, key1, key0, ...] + + # Call the VM instruction to push values from advice map + adv.push_mapvaln + # Stack: [key3, key2, key1, key0, ...] + # Side effect: [n, ele1, ele2, ...] pushed to advice stack where n is the count + + # Drop the key word from the stack + dropw + # Stack: [...] + + # The adv.push_mapvaln instruction pushes the count on top of the advice stack + # We need to pop it from the advice stack to return it + # Note: The advice stack now has [ele1, ele2, ...] with count removed + adv_push.1 + # Stack: [num_elements, ...] +end + +#! Emits an event to request a Falcon signature loaded to the advice stack +#! MSG and PK are expected to be on the operand stack. +#! +#! Inputs: [msg3, msg2, msg1, msg0, pk3, pk2, pk1, pk0, ...] +#! Outputs: [...] +export.emit_falcon_sig_to_stack + # 131087 == FALCON_SIG_TO_STACK from miden contracts + emit.131087 + dropw dropw +end + +#! Insert words loaded from a memory range into the advice map under the given key +#! +#! Inputs: [key3, key2, key1, key0, start_addr, end_addr, ...] +#! Outputs: [...] +#! Side effects: advice_map[key] <- mem[start_addr..end_addr] +export.adv_insert_mem + # VM op expects [KEY, a, b, ...] and leaves them on the stack + adv.insert_mem + # Drop a,b and the key word from the stack so the proc has no outputs + drop drop + dropw +end diff --git a/codegen/masm/intrinsics/crypto.masm b/codegen/masm/intrinsics/crypto.masm new file mode 100644 index 000000000..005c11308 --- /dev/null +++ b/codegen/masm/intrinsics/crypto.masm @@ -0,0 +1,48 @@ +#! HMERGE: Wrapper for hmerge that takes pointers to digests array and result +#! +#! Stack Input: [digests_ptr, result_ptr, ...] +#! Stack Output: [...] (both values are consumed) +#! Side Effect: Results written to memory at result_ptr +#! +#! The digests_ptr points to an array of 2 digests [A, B] (8 field elements total) +#! Memory layout: [a0, a1, a2, a3, b0, b1, b2, b3] +export.hmerge + # Stack: [digests_ptr, result_ptr, ...] + + # Load first digest (A) from digests_ptr + dup.0 + push.4 + div + padw + movup.4 + mem_loadw + # Stack: [a3, a2, a1, a0, digests_ptr, result_ptr, ...] + + # Load second digest (B) from digests_ptr + 16 (4 felts * 4 bytes) + movup.4 + push.16 + add + push.4 + div + padw + movup.4 + mem_loadw + # Stack: [b3, b2, b1, b0, a3, a2, a1, a0, result_ptr, ...] + + # The hmerge instruction expects [B, A] on the stack, which we already have + + # Perform the hash + hmerge + # Stack: [r3, r2, r1, r0, result_ptr, ...] + + # Store result at result_ptr + movup.4 + push.4 + div + # Stack: [result_ptr/4, r0, r1, r2, r3, ...] + mem_storew + # Stack: [r0, r1, r2, r3, ...] + + # Clean up + dropw +end diff --git a/codegen/masm/intrinsics/i128.masm b/codegen/masm/intrinsics/i128.masm new file mode 100644 index 000000000..0956a60cb --- /dev/null +++ b/codegen/masm/intrinsics/i128.masm @@ -0,0 +1,103 @@ +# Adds `a` to `b`, wrapping to 128 bits. +export.add # [bhh bmh bml bll ahh amh aml all ...] + movup.7 # [all bhh bmh bml bll ahh amh aml ...] + movup.4 # [bll all bhh bmh bml ahh amh aml ...] + u32overflowing_add # [oll rll bhh bmh bml ahh amh aml ...] + movup.7 # [aml oll rll bhh bmh bml ahh amh ...] + movup.5 # [bml aml oll rll bhh bmh ahh amh ...] + u32overflowing_add3 # [oml rml rll bhh bmh ahh amh ...] + movup.6 # [amh oml rml rll bhh bmh ahh ...] + movup.5 # [bmh amh oml rml rll bhh ahh ...] + u32overflowing_add3 # [omh rmh rml rll bhh ahh ...] + movup.5 # [ahh omh rmh rml rll bhh ...] + movup.5 # [bhh ahh omh rmh rml rll ...] + u32wrapping_add3 # [rhh rmh rml rll ...] +end + +# Subtracts `b` from `a`, wrapping to 128 bits. +export.sub # [bhh bmh bml bll ahh amh aml all ...] + movup.7 # [all bhh bmh bml bll ahh amh aml ...] + movup.4 # [bll all bhh bmh bml ahh amh aml ...] + u32overflowing_sub # [ull rll bhh bmh bml ahh amh aml ...] + movup.7 # [aml ull rll bhh bmh bml ahh amh ...] + movup.5 # [bml aml ull rll bhh bmh ahh amh ...] + u32overflowing_sub # [uml rml ull rll bhh bmh ahh amh ...] + swap.1 # [rml uml ull rll bhh bmh ahh amh ...] + movup.2 # [ull rml uml rll bhh bmh ahh amh ...] + u32overflowing_sub # [uml' rml uml rll bhh bmh ahh amh ...] + movup.2 # [uml uml' rml rll bhh bmh ahh amh ...] + or # [uml rml rll bhh bmh ahh amh ...] + movup.6 # [amh uml rml rll bhh bmh ahh ...] + movup.5 # [bmh amh uml rml rll bhh ahh ...] + u32overflowing_sub # [umh rmh uml rml rll bhh ahh ...] + swap.1 # [rmh umh uml rml rll bhh ahh ...] + movup.2 # [uml rmh umh rml rll bhh ahh ...] + u32overflowing_sub # [umh' rmh umh rml rll bhh ahh ...] + movup.2 # [umh umh' rmh rml rll bhh ahh ...] + or # [umh rmh rml rll bhh ahh ...] + movup.5 # [ahh umh rmh rml rll bhh ...] + movup.5 # [bhh ahh umh rmh rml rll ...] + u32wrapping_sub # [rhh umh rmh rml rll ...] + swap.1 # [umh rhh rmh rml rll ...] + u32wrapping_sub # [rhh rmh rml rll ...] +end + +# Multiplies `a` with `b`, wrapping to 128 bits. +# ahh amh aml all +# x bhh bmh bml bll +# ------------------------------------------- +# ovfl allxbll +# ovfl amlxbll 0 +# ovfl amhxbll 0 0 +# ahhxbll 0 0 0 bll x4 uses, ahh x1 uses +# ovfl allxbml 0 +# ovfl amlxbml 0 0 +# amhxbml 0 0 0 amh x2 uses, bml x3 uses +# ovfl allxbmh 0 0 +# amlxbmh 0 0 0 aml x3 uses, bmh x2 uses +# allxbhh 0 0 0 all x4 uses, bhh x1 uses +# ------------------------------------------- +# rhh rmh rml rll + +export.mul # [bhh bmh bml bll ahh amh aml all ... ] + dup.7 # [all bhh bmh bml bll ahh amh aml all ... ] + dup.4 # [bll all bhh bmh bml bll ahh amh aml all ... ] + u32overflowing_mul # [o rll bhh bmh bml bll ahh amh aml all ... ] + dup.8 # [aml o rll bhh bmh bml bll ahh amh aml all ... ] + dup.6 # [bll aml o rll bhh bmh bml bll ahh amh aml all ... ] + u32overflowing_madd # [o rml' rll bhh bmh bml bll ahh amh aml all ... ] + dup.8 # [amh o rml' rll bhh bmh bml bll ahh amh aml all ... ] + dup.7 # [bll amh o rml' rll bhh bmh bml bll ahh amh aml all ... ] + u32overflowing_madd # [o rmh' rml' rll bhh bmh bml bll ahh amh aml all ... ] + movup.8 # [ahh o rmh' rml' rll bhh bmh bml bll amh aml all ... ] + movup.8 # [bll ahh o rmh' rml' rll bhh bmh bml amh aml all ... ] + u32wrapping_madd # [rhh' rmh' rml' rll bhh bmh bml amh aml all ... ] + movup.2 # [rml' rhh' rmh' rll bhh bmh bml amh aml all ... ] + dup.9 # [all rml' rhh' rmh' rll bhh bmh bml amh aml all ... ] + dup.7 # [bml all rml' rhh' rmh' rll bhh bmh bml amh aml all ... ] + u32overflowing_madd # [o rml rhh' rmh' rll bhh bmh bml amh aml all ... ] + dup.9 # [aml o rml rhh' rmh' rll bhh bmh bml amh aml all ... ] + dup.8 # [bml aml o rml rhh' rmh' rll bhh bmh bml amh aml all ... ] + u32overflowing_madd # [o rmh' rml rhh' rmh' rll bhh bmh bml amh aml all ... ] + movup.9 # [amh o rmh' rml rhh' rmh' rll bhh bmh bml aml all ... ] + movup.9 # [bml amh o rmh' rml rhh' rmh' rll bhh bmh aml all ... ] + u32wrapping_madd # [rhh' rmh' rml rhh' rmh' rll bhh bmh aml all ... ] + swap.1 # [rmh' rhh' rml rhh' rmh' rll bhh bmh aml all ... ] + dup.9 # [all rmh' rhh' rml rhh' rmh' rll bhh bmh aml all ... ] + dup.8 # [bmh all rmh' rhh' rml rhh' rmh' rll bhh bmh aml all ... ] + u32overflowing_madd # [o rmh' rhh' rml rhh' rmh' rll bhh bmh aml all ... ] + movup.9 # [aml o rmh' rhh' rml rhh' rmh' rll bhh bmh all ... ] + movup.9 # [bmh aml o rmh' rhh' rml rhh' rmh' rll bhh all ... ] + u32wrapping_madd # [rhh' rmh' rhh' rml rhh' rmh' rll bhh all ... ] + movup.8 # [all rhh' rmh' rhh' rml rhh' rmh' rll bhh ... ] + movup.8 # [bhh all rhh' rmh' rhh' rml rhh' rmh' rll ... ] + u32wrapping_madd # [rhh' rmh' rhh' rml rhh' rmh' rll ... ] + swap.1 # [rmh' rhh' rhh' rml rhh' rmh' rll ... ] + movup.5 # [rmh' rmh' rhh' rhh' rml rhh' rll ... ] + u32overflowing_add # [o rmh rhh' rhh' rml rhh' rll ... ] + movup.5 # [rhh' o rmh rhh' rhh' rml rll ... ] + movup.3 # [rhh' rhh' o rmh rhh' rml rll ... ] + u32wrapping_add3 # [rhh' rmh rhh' rml rll ... ] + movup.2 # [rhh' rhh' rmh rml rll ... ] + u32wrapping_add # [rhh rmh rml rll ... ] +end diff --git a/codegen/masm/intrinsics/mem.masm b/codegen/masm/intrinsics/mem.masm index b52903213..ee95ce0c6 100644 --- a/codegen/masm/intrinsics/mem.masm +++ b/codegen/masm/intrinsics/mem.masm @@ -1,94 +1,110 @@ # The location where we store information about the dynamic heap -const.HEAP_INFO_ADDR=0x80000000 # (address in words) +const.HEAP_INFO_ADDR=0x80000000 # 2^31 (address in elements) + +# The location of the heap_top value +const.HEAP_INFO_TOP=HEAP_INFO_ADDR + +# The location of the heap_size value +const.HEAP_INFO_SIZE=HEAP_INFO_ADDR + 1 + +# The location of the heap_base value +const.HEAP_INFO_BASE=HEAP_INFO_ADDR + 2 + +# The location of the MAGIC value +const.HEAP_INFO_MAGIC=HEAP_INFO_ADDR + 3 + # The address beyond which the dynamic heap cannot be allowed to grow -const.HEAP_END=0x10000000 # 2^30 / 4 (i.e. byte address, not word) +const.HEAP_END=0x10000000 # 2^30 * 4 (i.e. byte address, not element address) + # The assertion error code used when intrinsics are used without calling heap_init -const.HEAP_ERR=0x68656170 # b"heap" -const.NEG1=4294967295 # u32::MAX +const.HEAP_ERR="rust heap has not been initialized" + +# u32::MAX | -1i32 +const.NEG1=4294967295 + # The magic bytes used to verify that the heap was properly initialized const.MAGIC=0xDEADBEEF + +# The size in bytes of each page of memory const.PAGE_SIZE=65536 # Checks the HEAP_INFO magic to ensure heap initialization has taken place # # This consumes the input element. -proc.verify_heap_magic # [input] - u32assert.err=HEAP_ERR +proc.verify_heap_magic + mem_load.HEAP_INFO_MAGIC push.MAGIC assert_eq.err=HEAP_ERR end # Intrinsic used to initialize the heap globals manipulated by memory intrinsics # -# This must be called before any other heap intrinsics are called. This is checked -# by each intrinsic +# This must be called before any other heap intrinsics are called. This is checked by each intrinsic export.heap_init # [heap_base] - dup.0 push.0 swap.1 push.MAGIC # [MAGIC, heap_base, heap_size, heap_top] + dup.0 # [heap_base, heap_base] + push.0 # [0, heap_base, heap_base] + swap.1 # [heap_base, 0, heap_base] + push.MAGIC # [MAGIC, heap_base, heap_size(0), heap_top(heap_base)] mem_storew.HEAP_INFO_ADDR dropw end # Get the (byte) address where the base of the heap starts export.heap_base - padw mem_loadw.HEAP_INFO_ADDR - exec.verify_heap_magic movdn.2 drop drop + exec.verify_heap_magic + mem_load.HEAP_INFO_BASE end # Get the (byte) address of the top of the heap export.heap_top_unchecked - mem_load.HEAP_INFO_ADDR + mem_load.HEAP_INFO_TOP end # Get the (byte) address of the top of the heap export.heap_top - padw mem_loadw.HEAP_INFO_ADDR - exec.verify_heap_magic drop drop + exec.verify_heap_magic + mem_load.HEAP_INFO_TOP end # Intrinsic corresponding to the `memory_size` instruction export.memory_size - padw mem_loadw.HEAP_INFO_ADDR - exec.verify_heap_magic drop swap.1 drop + exec.verify_heap_magic + mem_load.HEAP_INFO_SIZE end # Intrinsic corresponding to the `memory_grow` instruction export.memory_grow # [num_pages] - padw mem_loadw.HEAP_INFO_ADDR # [MAGIC, heap_base, heap_size, heap_top, num_pages] - dup.0 exec.verify_heap_magic # [MAGIC, heap_base, heap_size, heap_top, num_pages] - swap.3 drop # [heap_base, heap_size, MAGIC, num_pages] - dup.1 movdn.4 # [heap_base, heap_size, MAGIC, num_pages, heap_size] - swap.1 # [heap_size, heap_base, MAGIC, num_pages, heap_size] - movup.3 # [num_pages, heap_size, heap_base, MAGIC, heap_size] - u32overflowing_add # [overflowed, heap_size + num_pages, heap_base, MAGIC, heap_size] - if.true # [new_heap_size, heap_base, MAGIC, heap_size] + exec.verify_heap_magic + mem_load.HEAP_INFO_SIZE # [heap_size, num_pages] + dup.0 # [heap_size, heap_size, num_pages] + swap.2 # [num_pages, heap_size, heap_size] + u32overflowing_add # [overflowed, heap_size + num_pages, heap_size] + if.true # [new_heap_size, heap_size] # Cannot grow the memory, return -1 - dropw # [] + drop drop push.NEG1 else # Success, recompute the heap_top, and make sure it doesn't exceed HEAP_END - dup.0 # [new_heap_size, new_heap_size, heap_base, MAGIC, heap_size] - push.PAGE_SIZE # [PAGE_SIZE, new_heap_size, new_heap_size, heap_base, MAGIC, heap_size] - dup.3 # [heap_base, PAGE_SIZE, new_heap_size, new_heap_size, heap_base, MAGIC, heap_size] - movdn.2 # [PAGE_SIZE, new_heap_size, heap_base, ..] - u32overflowing_madd # [overflow, PAGE_SIZE * new_heap_size + heap_base, ..] - if.true # [new_heap_top, new_heap_size, heap_base, MAGIC, heap_size] - # Overflow, drop the changes and return -1 - dropw drop - push.NEG1 - else - # Ensure the new heap_top is <= HEAP_END - dup.0 u32lte.HEAP_END - if.true - # Write updated heap information, and return the old heap size (in pages) - swap.2 # [heap_base, new_heap_size, new_heap_top, MAGIC, heap_size] - movup.3 # [MAGIC, heap_base, new_heap_size, new_heap_top, heap_size] - mem_storew.HEAP_INFO_ADDR - dropw - else + mem_load.HEAP_INFO_BASE # [heap_base, new_heap_size, heap_size] + dup.1 # [new_heap_size, heap_base, new_heap_size, heap_size] + push.PAGE_SIZE # [PAGE_SIZE, new_heap_size, heap_base, new_heap_size, heap_size] + u32overflowing_madd # [overflowed, PAGE_SIZE * new_heap_size + heap_base, ..] + if.true # [new_heap_top, new_heap_size, heap_size] # Overflow, drop the changes and return -1 - dropw drop + drop drop drop push.NEG1 - end + else # [new_heap_top, new_heap_size, heap_size] + # Ensure the new heap_top is <= HEAP_END + dup.0 u32lte.HEAP_END + if.true + # Write updated heap information, and return the old heap size (in pages) + mem_store.HEAP_INFO_TOP # [new_heap_size, heap_size] + mem_store.HEAP_INFO_SIZE # [heap_size] + else + # Overflow, drop the changes and return -1 + drop drop drop + push.NEG1 + end end end end @@ -119,170 +135,100 @@ export.extract_element # [element_index, w3, w2, w1, w0] movup.2 cdrop end -# See `load_felt` for safe usage -proc.load_felt_unchecked # [waddr, index] - # prepare the stack to receive the loaded word - # [waddr, 0, 0, 0, 0, index] - padw movup.4 - # load the word which contains the desired element - mem_loadw # [w3, w2, w1, w0, index] - - # select the desired element - movup.4 - exec.extract_element -end - # Load a field element from the given native pointer triplet. # -# A native pointer triplet consists of a word address which contains the -# start of the data; an element index, which indicates which element of -# the word the data starts in; and a byte offset, which indicates which -# byte is the start of the data. +# A native pointer tuple consists of an element address where the data begins, and a byte offset, +# which is the offset of the first byte, in the 32-bit representation of that element. # # A field element must be naturally aligned, i.e. it's byte offset must be zero. -export.load_felt # [waddr, index, offset] +export.load_felt # [addr, offset] # assert the pointer is felt-aligned, then load - movup.2 assertz exec.load_felt_unchecked + swap.1 assertz mem_load end -# Load a single 32-bit machine word from the given native pointer triplet. +# Load a single 32-bit machine word from the given native pointer tuple. # -# A native pointer triplet consists of a word address which contains the -# start of the data; an element index, which indicates which element of -# the word the data starts in; and a byte offset, which indicates which -# byte is the start of the data. -export.load_sw # [waddr, index, offset] +# A native pointer tuple consists of an element address where the data begins, and a byte offset, +# which is the offset of the first byte, in the 32-bit representation of that element. +export.load_sw # [addr, offset] # check for alignment and offset validity - dup.2 eq.0 - dup.3 push.8 u32lt assert # offset must be < 8 + dup.1 eq.0 # [offset == 0, addr, offset] + # offset must be < 4 + dup.2 push.4 u32lt assert # [offset == 0, addr, offset] # if the pointer is naturally aligned.. - if.true + if.true # [addr, offset] # drop the byte offset - movup.2 drop + swap.1 drop # load the element containing the data we want - exec.load_felt_unchecked - else - # check if the load starts in the first element - dup.1 eq.0 - if.true - # the load is across both the first and second elements - # drop the element index - swap.1 drop - # load - padw movup.4 mem_loadw # [w3, w2, w1, w0, offset] - # drop the unused elements - drop drop - # shift low bits - push.32 dup.3 # [offset, 32, w1, w0, offset] - u32overflowing_sub assertz # [32 - offset, w1, w0, offset] - u32shr # [lo, w0, offset] - # shift high bits left by the offset - swap.2 # [offset, w0, lo] - u32shl # [hi, lo] - # combine the two halves - u32or # [result] - else - # check if the load starts in the second element - dup.1 eq.1 - if.true - # the load is across both the second and third elements - # drop the element idnex - swap.1 drop - # load - padw movup.4 mem_loadw # [w3, w2, w1, w0, offset] - # drop the unused elements - drop movup.2 drop # [w2, w1, offset] - # shift the low bits - push.32 dup.3 # [offset, 32, w2, w1, offset] - u32overflowing_sub assertz # [32 - offset, w2, w1, offset] - u32shr # [lo, w1, offset] - # shift high bits left by the offset - swap.2 # [offset, w1, lo] - u32shl # [hi, lo] - # combine the two halves - u32or # [result] - else - # check if the load starts in the third element - swap.1 eq.2 - if.true - # the load is across both the third and fourth elements - padw movup.4 mem_loadw # [w3, w2, w1, w0, offset] - # drop the unused elements - movup.3 movup.3 drop drop # [w3, w2, offset] - # shift the low bits - push.32 dup.3 # [offset, 32, w3, w2, offset] - u32overflowing_sub assertz # [32 - offset, w3, w2, offset] - u32shr # [lo, w2, offset] - # shift the high bits left by the offset - swap.2 # [offset, w2, lo] - u32shl # [hi, lo] - # combine the two halves - u32or # [result] - else - # the load crosses a word boundary - # start with the word containing the low bits - dup.0 # [waddr, waddr, offset] - u32overflowing_add.1 assertz # [waddr + 1, waddr, offset] - # load the low bits - mem_load # [w0, waddr, offset] - # shift the low bits - push.32 dup.3 # [offset, 32, w0, waddr, offset] - u32overflowing_sub assertz # [32 - offset, w0, waddr, offset] - u32shr # [lo, waddr, offset] - # load the word with the high bits, drop unused elements - swap.1 padw movup.4 mem_loadw movdn.3 drop drop drop # [w3, lo, offset] - # shift high bits - movup.2 u32shl # [hi, lo] - # combine the two halves - u32or # [result] - end - end - end + mem_load + else # [addr, offset] + # the load crosses an element boundary + # + # 1. load the first element + dup.0 mem_load # [e0, addr, offset] + # 2. load the second element + swap.1 # [addr, e0, offset] + push.1 u32overflowing_add # [overflowed, addr + 1, e0, offset] + assertz mem_load # [e1, e0, offset] + # shift low bits + push.32 dup.3 # [offset, 32, e1, e0, offset] + u32overflowing_sub assertz # [32 - offset, e1, e0, offset] + u32shr # [lo, e0, offset] + # shift high bits left by the offset + swap.2 # [offset, e0, lo] + u32shl # [hi, lo] + # combine the two halves + u32or # [result] end end -# This handles emitting code that handles aligning an unaligned double -# machine-word value which is split across three machine words (field elements). +# This handles emitting code that handles aligning an unaligned 64-bit value which is split across +# three elements. # -# To recap: +# To recap our terminology and memory mapping spec: # -# * A machine word is a 32-bit chunk stored in a single field element -# * A double word is a pair of 32-bit chunks -# * A quad word is a quartet of 32-bit chunks (i.e. a Miden "word") -# * An unaligned double-word requires three 32-bit chunks to represent, -# since the first chunk does not contain a full 32-bits, so an extra is -# needed to hold those bits. +# * A Miden "word" is a quartet of field elements, capable of representing 16 bytes +# * A machine word is a 32-bit chunk stored in a single field element. The term "machine" here +# refers to the fact that the abstract machine presented by the compiler IR is a 32-bit machine, +# where the "native" integral type is a 32-bit integer. In typical compiler parlance, a "machine +# word" refers to the default integer type of a machine's general purpose registers. +# * A double word is a pair of 32-bit chunks, i.e. a single 64-bit value. +# * A quad word is a quartet of 32-bit chunks (and naturally corresponds to a Miden "word") +# * An unaligned double-word requires three 32-bit chunks to represent, since the first chunk does +# not contain a full 32-bits, so an extra is needed to hold those bits. # -# As an example, assume the pointer we are dereferencing is a u64 value, -# which has 8-byte alignment, and the value is stored 40 bytes from the -# nearest quad-word-aligned boundary. To load the value, we must fetch -# the full quad-word from the aligned address, drop the first word, as -# it is unused, and then recombine the 64 bits we need spread across -# the remaining three words to obtain the double-word value we actually want. +# As an example, assume the pointer we are dereferencing is a u64 value, which has 8-byte +# alignment, and the value is stored 40 bytes from the nearest element address divisible by 4 +# (i.e. Miden word-aligned). To load the value, we must fetch the full Miden word from that +# address, drop the first element, as it is unused, and then recombine the 64 bits we need spread +# across the remaining three elements to obtain the double-word value we actually want. # # The data, on the stack, is shown below: # -# If we visualize which bytes are contained in each 32-bit chunk on the stack, -# when loaded by `mem_loadw`, we get: +# If we visualize which bytes are contained in each 32-bit chunk on the stack, when loaded by +# `mem_loadw`, we get: # # [, 9..=12, 5..=8, 0..=4] # -# These byte indices are relative to the nearest word-aligned address, in the -# same order as they would occur in a byte-addressable address space. The -# significance of each byte depends on the value being dereferenced, but Miden -# is a little-endian machine, so typically the most significant bytes come first -# (i.e. also commonly referred to as "high" vs "low" bits). +# These byte indices are relative to the nearest Miden word-aligned address, in the same order as +# they would occur in a byte-addressable address space. The significance of each byte depends on +# the value being dereferenced, but Miden is a little-endian machine, so typically the most +# significant bytes come first (i.e. also commonly referred to as "high" vs "low" bits). # -# If we visualize the layout of the bits of our u64 value spread across the -# three chunks, we get: +# If we visualize the layout of the bits of our u64 value spread across the three chunks, we get: # -# [, 00000000111111111111111111111111, 111111111111111111111111111111, 11111111111111111111111100000000] +# [ +# , +# 00000000111111111111111111111111, +# 111111111111111111111111111111, +# 11111111111111111111111100000000 +# ] # -# As illustrated above, what should be a double-word value is occupying three words. -# To "realign" the value, i.e. ensure that it is naturally aligned and fits in two -# words, we have to perform a sequence of shifts and masks to get the bits where -# they belong. This function performs those steps, with the assumption that the caller -# has three values on the operand stack representing any unaligned double-word value +# As illustrated above, what should be a double-word value is occupying three elements. To +# "realign" the value, i.e. ensure that it is naturally aligned and fits in two elements, we have +# to perform a sequence of shifts and masks to get the bits where they belong. This function +# performs those steps, with the assumption that the caller has three values on the operand stack +# representing any unaligned double-word value export.realign_dw # [chunk_hi, chunk_mid, chunk_lo, offset] # We will refer to the parts of our desired double-word value # as two parts, `x_hi` and `x_lo`. @@ -342,131 +288,59 @@ export.realign_dw # [chunk_hi, chunk_mid, chunk_lo, offset] swap.1 # [x_hi, x_lo] end -# Shift a double-word (64-bit, in two 32-bit chunks) value by the given offset +# Shift a double-word (64-bit, in two 32-bit chunks) value by the given offset. +# # Returns three 32-bit chunks [chunk_lo, chunk_mid, chunk_hi] export.offset_dw # [value_hi, value_lo, offset] dup.0 - dup.3 u32shr # [chunk_hi, value_hi, value_lo, offset] + dup.3 u32shl # [chunk_hi, value_hi, value_lo, offset] + movdn.3 # [value_hi, value_lo, offset, chunk_hi] push.32 dup.3 u32wrapping_sub # [32 - offset, value_hi, value_lo, offset, chunk_hi] - u32shl # [ chunk_mid_hi, value_lo, offset, chunk_hi] + u32shr # [ chunk_mid_hi, value_lo, offset, chunk_hi] dup.1 # [ value_lo, chunk_mid_hi, value_lo, offset, chunk_hi] dup.3 # [ offset, value_lo, chunk_mid_hi, value_lo, offset, chunk_hi] - u32shr # [ chunk_mid_lo, chunk_mid_hi, value_lo, offset, chunk_hi] + u32shl # [ chunk_mid_lo, chunk_mid_hi, value_lo, offset, chunk_hi] u32or # [ chunk_mid, value_lo, offset, chunk_hi] + movdn.2 # [ value_lo, offset, chunk_mid, chunk_hi] push.32 movup.2 u32wrapping_sub # [32 - offset, value_lo, offset, chunk_mid, chunk_hi] - u32shl # [ chunk_lo, chunk_mid, chunk_hi] + u32shr # [ chunk_lo, chunk_mid, chunk_hi] end -# Load a pair of machine words (32-bit elements) to the operand stack -export.load_dw # [waddr, index, offset] +# Load a machine double-word (64-bit value, in two 32-bit chunks) to the operand stack +export.load_dw # [addr, offset] # check for alignment and offset validity - dup.2 eq.0 - dup.3 push.8 u32lt assert # offset must be < 8 - # convert offset from bytes to bits - movup.3 push.8 u32wrapping_mul movdn.3 # [waddr, index, offset, value_hi, value_lo] + dup.1 eq.0 # [offset == 0, addr, offset] + # offset must be < 4 + dup.2 push.4 u32lt assert # [offset == 0, addr, offset] # if the pointer is naturally aligned.. if.true - # drop byte offset - movup.2 drop # [waddr, index] - # check which element to start at - dup.1 eq.0 - if.true - # drop index - swap.1 drop # [waddr] - # load first two elements - padw movup.4 mem_loadw # [w3, w2, w1, w0] - # drop last two elements, and we're done - drop drop swap.1 # [w0, w1] - else - dup.1 eq.1 - if.true - # drop index - swap.1 drop # [waddr] - # load second and third elements - padw movup.4 mem_loadw # [w3, w2, w1, w0] - # drop unused elements, and we're done - movup.3 drop drop swap.1 # [w1, w2] - else - swap.1 eq.2 - if.true - # load third and fourth elements, drop unused, and we're done - padw movup.4 mem_loadw # [w3, w2, w1, w0] - movup.3 movup.3 drop drop swap.1 # [w2, w3] - else - # load first element of next word - dup.0 u32overflowing_add.1 assertz # [waddr + 1, waddr] - mem_load # [w0, waddr] - # load fourth element, and we're done - swap.1 padw movup.4 mem_loadw # [w3, w2, w1, w0, lo] - movdn.3 drop drop drop # [hi, lo] - end - end - end - else # unaligned; an unaligned double-word spans three elements - # check if we start in the first element - dup.1 eq.0 - if.true - # memory layout: [, lo, mid, hi] - # drop the index - swap.1 drop # [waddr, offset] - # load three elements containing the double-word on the stack - padw movup.4 mem_loadw # [w3, w2, w1, w0, offset] - drop # [w2, w1, w0, offset] - # move into stack order (hi bytes first) - swap.2 # [w0, w1, w2, offset] - # re-align it, and we're done; realign_dw gets [w0, w1, w2, offset] - exec.realign_dw - else - # check if we start in the second element - dup.1 eq.1 - if.true - # memory layout: [lo, mid, hi, ] - # drop the index - swap.1 drop - # load three elements containing the double-word on the stack - padw movup.4 mem_loadw # [w3, w2, w1, w0, offset] - movup.3 drop # [w3, w2, w1, offset] - # move into stack order - swap.2 # [w1, w2, w3, offset] - # re-align it, and we're done; realign_dw gets [w1, w2, w3, offset] - exec.realign_dw - else - # check if we start in the third element - swap.1 eq.2 # [waddr, offset] - if.true - # memory layout: [mid, hi, ..] [, , , lo] - # load one element from the next word - dup.0 u32overflowing_add.1 assertz # [waddr + 1, waddr, offset] - mem_load # [chunk_lo, waddr, offset] - # load two elements from the first word - padw movup.5 # [waddr, 0, 0, 0, 0, chunk_lo, offset] - mem_loadw # [chunk_mid, chunk_hi, ?, ?, chunk_lo, offset] - swap.3 drop # [chunk_hi, ?, chunk_mid, chunk_lo, offset] - swap.1 drop # [chunk_hi, chunk_mid, chunk_lo, offset] - # re-align it, and we're done - exec.realign_dw - else - # memory layout: [hi, ..], [, , lo, mid] - # load the two least-significant elements from the next word first - dup.0 u32overflowing_add.1 assertz # [waddr + 1, waddr, offset] - padw movup.4 # [waddr + 1, 0, 0, 0, 0, waddr, offset] - mem_loadw drop drop # [lo, mid, waddr, offset] - swap.1 # [mid, lo, waddr, offset] - # load the most significant element from the first word - padw movup.6 # [waddr, 0, 0, 0, 0, mid, lo, offset] - mem_loadw movdn.3 drop drop drop # [hi, mid, lo, offset] - # re-align it, and we're done - exec.realign_dw - end - end - end + # drop offset + swap.1 drop # [addr] + # load second element + dup.0 push.1 u32overflowing_add assertz # [addr + 1, addr] + mem_load # [e1, addr] + # load first element + swap.1 mem_load # [e0, e1] + else + # unaligned; an unaligned double-word spans three elements + # + # convert offset from bytes to bits + swap.1 push.8 u32wrapping_mul swap.1 # [addr, bit_offset] + + # load the three elements containing the double-word on the stack + dup.0 push.2 u32overflowing_add assertz mem_load # [e2, addr, bit_offset] + dup.1 push.1 add mem_load # [e1, e2, addr, bit_offset] + movup.2 mem_load # [e0, e1, e2, bit_offset] + + # re-align it, and we're done + exec.realign_dw end end -# Given an element index, a new element, and a word, in that order, replace the element -# at the specified index, leaving the modified word on top of the stack +# Given an element index, a new element, and a word, in that order, replace the element at the +# specified index, leaving the modified word on top of the stack # # The element index must be in the range 0..=3. export.replace_element # [element_index, value, w3, w2, w1, w0] @@ -488,325 +362,214 @@ export.replace_element # [element_index, value, w3, w2, w1, w0] movdn.3 # [w3', w2', w1', w0'] end -# See `store_felt` for safe usage -proc.store_felt_unchecked # [waddr, index, value] - # prepare the stack to receive the loaded word - # [waddr, 0, 0, 0, 0, waddr, index, value] - padw dup.4 - # load the original word - mem_loadw # [w3, w2, w1, w0, waddr, index, value] - - # rewrite the desired element - movup.6 # [value, w3, w2, w1, w0, waddr, index] - movup.6 # [index, value, w3, w2, w1, w0, waddr] - exec.replace_element # [w3', w2', w1', w0', waddr] - - # store the updated word - movup.4 mem_storew - dropw -end - -# Store a field element to the given native pointer triplet. +# Store a field element to the given native pointer tuple. # -# A native pointer triplet consists of a word address which contains the -# start of the data; an element index, which indicates which element of -# the word the data starts in; and a byte offset, which indicates which -# byte is the start of the data. +# A native pointer tuple consists of an element address where the data begins, and a byte offset, +# which is the offset of the first byte, in the 32-bit representation of that element. # # A field element must be naturally aligned, i.e. it's byte offset must be zero. -export.store_felt # [waddr, index, offset, value] +export.store_felt # [addr, offset, value] # assert the pointer is felt-aligned, then load - movup.2 assertz exec.store_felt_unchecked + swap.1 assertz mem_store end -# Store a single 32-bit machine word from the given native pointer triplet. +# Store a single 32-bit machine word from the given native pointer tuple. # -# A native pointer triplet consists of a word address which contains the -# start of the data; an element index, which indicates which element of -# the word the data starts in; and a byte offset, which indicates which -# byte is the start of the data. -export.store_sw # [waddr, index, offset, value] +# A native pointer tuple consists of an element address where the data begins, and a byte offset, +# which is the offset of the first byte, in the 32-bit representation of that element. +export.store_sw # [addr, offset, value] # check for alignment and offset validity - dup.2 eq.0 - dup.3 push.8 u32lt assert # offset must be < 8 + dup.1 eq.0 # [offset == 0, addr, offset, value] + # offset must be < 4 + dup.2 push.4 u32lt assert # if the pointer is naturally aligned.. - if.true + if.true # [addr, offset, value] # drop the byte offset - movup.2 drop - # load the element containing the data we want - exec.store_felt_unchecked + swap.1 drop # [addr, value] + # store the element + mem_store # [] else - # check if the store starts in the first element - dup.1 eq.0 - if.true - # the store is across both the first and second elements - # drop the element index - swap.1 drop - # load current value - padw dup.4 mem_loadw # [w3, w2, w1, w0, waddr, offset, value] - - # compute the bit shift - push.32 dup.6 sub # [rshift, w3..w0, waddr, offset, value] - - # compute the masks - push.4294967295 dup.1 u32shl # [mask_hi, rshift, w3..w0, waddr, offset, value] - dup.0 u32not # [mask_lo, mask_hi, rshift, w3, w2, w1, w0, waddr, offset, value] - - # manipulate the bits of the two target elements, such that the 32-bit word - # we're storing is placed at the correct offset from the start of the memory - # cell when viewing the cell as a set of 4 32-bit chunks - movup.5 u32and # [w1_masked, mask_hi, rshift, w3, w2, w0, waddr, offset, value] - movup.5 movup.2 u32and # [w0_masked, w1_masked, rshift, w3, w2, waddr, offset, value] - - # now, we need to shift/mask/split the 32-bit value into two elements, then - # combine them with the preserved bits of the original contents of the cell - # - # first, the contents of w0 - dup.7 movup.7 u32shr u32or # [w0', w1_masked, rshift, w3..w2, waddr, value] - # then the contents of w1 - swap.1 - movup.6 movup.3 u32shl u32or # [w1', w0', w3, w2, waddr] - - # ensure word is in order - movup.3 movup.3 # [w3, w2, w1', w0', waddr] - - # finally, write back the updated word, and clean up the operand stack - movup.4 mem_storew dropw - else - # check if the load starts in the second element - dup.1 eq.1 - if.true - # the load is across both the second and third elements - # drop the element index - swap.1 drop - - # load current value - padw dup.4 mem_loadw # [w3, w2, w1, w0, waddr, offset, value] - - # compute the bit shift - push.32 dup.6 sub # [rshift, w3..w0, waddr, offset, value] - - # compute the masks - push.4294967295 dup.1 u32shl # [mask_hi, rshift, w3..w0, waddr, offset, value] - dup.0 u32not # [mask_lo, mask_hi, rshift, w3, w2, w1, w0, waddr, offset, value] - - # manipulate the bits of the two target elements, such that the 32-bit word - # we're storing is placed at the correct offset from the start of the memory - # cell when viewing the cell as a set of 4 32-bit chunks - movup.4 u32and # [w2_masked, mask_hi, rshift, w3, w1, w0, waddr, offset, value] - movup.4 movup.2 u32and # [w1_masked, w2_masked, rshift, w3, w0, waddr, offset, value] - - # now, we need to shift/mask/split the 32-bit value into two elements, then - # combine them with the preserved bits of the original contents of the cell - # - # first, the contents of w1 - dup.7 movup.7 u32shr u32or # [w1', w2_masked, rshift, w3, w0, waddr, value] - # then the contents of w2 - swap.1 - movup.6 movup.3 u32shl u32or # [w2', w1', w3, w0, waddr] - - # ensure the elements are in order - movup.3 swap.3 # [w3, w2', w1', w0, waddr] - - # finally, write back the updated word, and clean up the operand stack - movup.4 mem_storew dropw - else - # check if the load starts in the third element - swap.1 eq.2 - if.true - # the load is across both the third and fourth elements - # load current value - padw dup.4 mem_loadw # [w3, w2, w1, w0, waddr, offset, value] - - # compute the bit shift - push.32 dup.6 sub # [rshift, w3..w0, waddr, offset, value] - - # compute the masks - push.4294967295 dup.1 u32shl # [mask_hi, rshift, w3..w0, waddr, offset, value] - dup.0 u32not # [mask_lo, mask_hi, rshift, w3, w2, w1, w0, waddr, offset, value] - - # manipulate the bits of the two target elements, such that the 32-bit word - # we're storing is placed at the correct offset from the start of the memory - # cell when viewing the cell as a set of 4 32-bit chunks - movup.3 u32and # [w3_masked, mask_hi, rshift, w2, w1, w0, waddr, offset, value] - movup.3 movup.2 u32and # [w2_masked, w3_masked, rshift, w1, w0, waddr, offset, value] - - # now, we need to shift/mask/split the 32-bit value into two elements, then - # combine them with the preserved bits of the original contents of the cell - # - # first, the contents of w2 - dup.7 movup.7 u32shr u32or # [w2', w3_masked, rshift, w1, w0, waddr, value] - # then the contents of w3 - swap.1 - movup.6 movup.3 u32shl u32or # [w3', w2', w1, w0, waddr] - - # finally, write back the updated word, and clean up the operand stack - movup.4 mem_storew dropw - else - # the load crosses a word boundary, start with the word containing the highest-addressed bits - - # compute the address for the second word - dup.0 # [waddr, waddr, offset, value] - u32overflowing_add.1 assertz # [waddr + 1, waddr, offset, value] - - # load the element we need to mix bits with - mem_load # [w0, waddr, offset, value] - - # compute the bit shift - push.32 dup.3 sub # [rshift, w0, waddr, offset, value] - - # compute the masks - push.4294967295 dup.1 u32shl # [mask_hi, rshift, w0, waddr, offset, value] - dup.0 u32not # [mask_lo, mask_hi, rshift, w0, waddr, offset, value] - - # mask out the bits of the value that are being overwritten - movup.3 u32and # [w0', mask_hi, rshift, waddr, offset, value] - - # extract the bits to be stored in this word - dup.5 movup.3 u32shl u32or # [w0'', mask_hi, waddr, offset, value] - - # store the updated element - dup.2 add.1 # [waddr + 1, w0'', mask_hi, waddr, offset, value] - mem_store # [mask_hi, waddr, offset, value] - - # next, update the last element of the lowest addressed word - padw dup.5 mem_loadw # [w3, w2, w1, w0, mask_hi, waddr, offset, value] - - # mask out the bits of the value that are being overwritten - movup.4 u32and # [w3_masked, w2, w1, w0, waddr, offset, value] - - # extract the bits to be stored in this word and combine them - movup.6 movup.6 u32shr u32or # [w3', w2, w1, w0, waddr] - - # write updated word - movup.4 mem_storew - - # clean up operand stack - dropw - end - end - end + # convert offset to bits + swap.1 push.8 u32wrapping_mul swap.1 # [addr, bit_offset, value] + + # the store is across an element boundary + # + # load the current value of the two elements involved + dup.0 push.1 u32overflowing_add assertz mem_load # [e1, addr, bit_offset, value] + dup.1 mem_load # [e0, e1, addr, bit_offset, value] + + # compute the bit shift + push.32 dup.4 sub # [32 - bit_offset, e0, e1, addr, bit_offset, value] + + # compute the masks + push.4294967295 dup.1 u32shr # [mask_lo, rshift, e0, e1, addr, bit_offset, value] + dup.0 u32not # [mask_hi, mask_lo, rshift, e0, e1, addr, bit_offset, value] + + # manipulate the bits of the two target elements, such that the 32-bit word we're storing + # is placed at the correct offset from the start of the memory cell when viewing the cell + # as a set of 4 32-bit chunks + # + # First, mask out the bits we're going to overwrite in the two elements we loaded + movup.4 u32and # [e1_masked, mask_lo, rshift, e0, addr, bit_offset, value] + movup.3 movup.2 u32and # [e0_masked, e1_masked, rshift, addr, bit_offset, value] + + # now, we need to shift/mask/split the 32-bit value into two elements, then + # combine them with the preserved bits of the original contents of the cell + # + # first, the contents of e0 + dup.5 movup.5 # [bit_offset, value, e0_masked, e1_masked, rshift, addr, value] + u32shl # [value << bit_offset, e0_masked, e1_masked, rshift, addr, value] + u32or # [e0', e1_masked, rshift, addr, value] + + # then the contents of w1 + swap.1 # [e1_masked, e0', rshift, addr, value] + movup.4 # [value, e1_masked, e0', rshift, addr] + movup.3 # [rshift, value, e1_masked, e0', addr] + u32shr # [value >> rshift, e1_masked, e0', addr] + u32or # [e1', e0', addr] + + # finally, write back the updated elements + dup.2 add.1 mem_store # [e0', addr] + swap.1 mem_store # [] end end -# Store a double 32-bit machine word from the given native pointer triplet. +# Store a 64-bit value, i.e. two 32-bit machine words from the given native pointer tuple. # -# A native pointer triplet consists of a word address which contains the -# start of the data; an element index, which indicates which element of -# the word the data starts in; and a byte offset, which indicates which -# byte is the start of the data. -export.store_dw # [waddr, index, offset, value_hi, value_lo] +# A native pointer tuple consists of an element address where the data begins, and a byte offset, +# which is the offset of the first byte, in the 32-bit representation of that element. +export.store_dw # [addr, offset, value_hi, value_lo] # check for alignment and offset validity - dup.2 eq.0 - dup.3 push.8 u32lt assert # offset must be < 8 - # convert offset from bytes to bits - movup.3 push.8 u32wrapping_mul movdn.3 # [offset == 0, waddr, index, offset, value_hi, value_lo] + dup.1 eq.0 # [offset == 0, addr, offset] + # offset must be < 4 + dup.2 push.4 u32lt assert # if the pointer is naturally aligned.. - if.true + if.true # [addr, offset, value_hi, value_lo] # drop byte offset - movup.2 drop # [waddr, index, value_hi, value_lo] - # check which element to start at - dup.1 eq.0 - if.true - # drop index - swap.1 drop # [waddr, value_hi, value_lo] - swap.2 # [value_lo, value_hi, waddr] - padw dup.6 mem_loadw # [w3, w2, w1, w0, value_lo, value_hi, waddr] - swap.2 drop # [w2, w3, w0, value_lo, value_hi, waddr] - swap.2 drop # [w3, w2, value_lo, value_hi, waddr] - movup.4 # [waddr, w3, w2, value_lo, value_hi] - mem_storew - # cleanup the operand stack - dropw - else - dup.1 eq.1 - if.true - # drop index - swap.1 drop # [waddr, value_hi, value_lo] - # store as the second and third elements of the word - swap.2 # [value_lo, value_hi, waddr] - padw dup.6 mem_loadw # [w3, w2, w1, w0, value_lo, value_hi, waddr] - movup.4 swap.2 drop # [w3, value_lo, w1, w0, value_hi, waddr] - movup.4 swap.3 drop # [w3, value_lo, value_hi, w0, waddr] - movup.4 mem_storew - # cleanup the operand stack - dropw - else - swap.1 eq.2 - if.true - # store as the third and fourth elements of the word - swap.2 # [value_lo, value_hi, waddr] - padw dup.6 mem_loadw # [w3, w2, w1, w0, value_lo, value_hi, waddr] - movup.5 swap.2 drop # [w3, value_hi, w1, w0, value_lo, waddr] - drop movup.3 # [value_lo, value_hi, w1, w0, waddr] - movup.4 mem_storew - # cleanup the operand stack - dropw - else - # store the first element of the next word - swap.2 # [value_lo, value_hi, waddr] - dup.2 u32overflowing_add.1 assertz # [waddr + 1, value_lo, value_hi, waddr] - mem_store # [value_hi, waddr] - # store the fourth element - padw dup.5 mem_loadw # [w3, w2, w1, w0, value_hi, waddr] - drop movup.3 movup.4 # [waddr, value_hi, w2, w1, w0] - mem_storew dropw - end - end - end - else # unaligned; an unaligned double-word spans three elements - # [waddr, index, offset, value_hi, value_lo] - movup.2 # [offset, waddr, index, value_hi, value_lo] - movup.4 # [value_lo, offset, waddr, index, value_hi] - movup.4 # [value_hi, value_lo, offset, waddr, index] - exec.offset_dw # [chunk_lo, chunk_mid, chunk_hi, waddr, index] - movup.4 # [index, chunk_lo, chunk_mid, chunk_hi, waddr] - # check if we start in the first element - dup.0 eq.0 - if.true - # target memory layout: [0, lo, mid, hi] - # drop the index - drop # [lo, mid, hi, waddr] - padw dup.7 mem_loadw # [w3, w2, w1, w0, lo, mid, hi, waddr] - movdn.3 # [w2, w1, w0, w3, lo, mid, hi, waddr] - drop drop drop # [w3, lo, mid, hi, waddr] - movup.4 mem_storew - dropw - else - # check if we start in the second element - dup.0 eq.1 - if.true - # target memory layout: [lo, mid, hi, 0] - # drop the index - drop # [lo, mid, hi, waddr] - padw dup.7 mem_loadw # [w3, w2, w1, w0, lo, mid, hi, waddr] - drop drop drop # [w0, lo, mid, hi, waddr] - movdn.3 # [lo, mid, hi, w0, waddr] - movup.4 mem_storew - dropw - else - # check if we start in the third element - eq.2 # [lo, mid, hi, waddr] - if.true - # target memory layout: [mid, hi, ..], [..lo] - padw dup.7 mem_loadw # [w3, w2, w1, w0, lo, mid, hi, waddr] - drop drop movup.4 movup.4 # [mid, hi, w1, w0, lo, waddr] - dup.5 mem_storew dropw # [lo, waddr] - swap.1 u32overflowing_add.1 assertz # [waddr + 1, lo] - mem_store - else - # target memory layout: [hi, ..], [..lo, mid] - padw dup.7 mem_loadw # [w3, w2, w1, w0, lo, mid, hi, waddr] - drop movup.5 # [hi, w2, w1, w0, lo, mid, waddr] - dup.6 mem_storew dropw # [lo, mid, waddr] - movup.2 u32overflowing_add.1 assertz # [waddr + 1, lo, mid] - dup.0 movdn.3 # [waddr + 1, lo, mid, waddr + 1] - padw movup.4 mem_loadw # [w3, w2, w1, w0, lo, mid, waddr + 1] - movup.5 swap.4 drop # [w3, w2, w1, mid, lo, waddr + 1] - movup.4 swap.3 drop # [w3, w2, lo, mid, waddr + 1] - movup.4 mem_storew dropw - end - end - end + swap.1 drop # [addr, value_hi, value_lo] + swap.1 dup.1 # [addr, value_hi, addr, value_lo] + mem_store # [addr, value_lo] + push.1 u32overflowing_add assertz # [addr + 1, value_lo] + mem_store + else # [addr, offset, value_hi, value_lo] + # unaligned; an unaligned double-word spans three elements + # + # convert offset from bytes to bits + swap.1 push.8 u32wrapping_mul swap.1 # [addr, bit_offset, value_hi, value_lo] + + movdn.3 # [bit_offset, value_hi, value_lo, addr] + dup.0 movdn.4 # [bit_offset, value_hi, value_lo, addr, bit_offset] + movdn.2 # [value_hi, value_lo, bit_offset, addr, bit_offset] + exec.offset_dw # [chunk_lo, chunk_mid, chunk_hi, addr, bit_offset] + + # compute the bit shift + push.32 movup.5 sub # [32 - bit_offset, lo, mid, hi, addr] + + # compute the masks + push.4294967295 swap.1 u32shr # [mask_hi, lo, mid, hi, addr] + dup.0 u32not # [mask_lo, mask_hi, lo, mid, hi, addr] + + # combine the bits of the lo and hi chunks with their corresponding elements, so that we + # do not overwrite memory that is out of bounds of the actual value being stored. + # + # 1. load e2 (lo) + dup.5 push.2 u32overflowing_add assertz mem_load + # 2. mask e2 (lo) + u32and # [e2_masked, mask_hi, lo, mid, hi, addr] + # 3. load e0 (hi) + dup.5 mem_load # [e0, e2_masked, mask_hi, lo, mid, hi, addr] + # 4. mask e0 (hi) + movup.2 u32and # [e0_masked, e2_masked, lo, mid, hi, addr] + # 5. combine e2 & lo + movdn.2 u32or # [e2', e0_masked, mid, hi, addr] + # 6. store e2 + dup.4 add.2 mem_store # [e0_masked, mid, hi, addr] + # 7. store e1 (mid) + swap.1 dup.3 add.1 mem_store # [e0_masked, hi, addr] + # 8. combine e0 & hi + u32or # [e0', addr] + # 9. store e0 + swap.1 mem_store # [] + end +end + +# Write `count` copies of `value` to memory, starting at `dst`. +# +# * `dst` is expected to be an address in byte-addressable space, _not_ an element address. +# * `value` must be a 32-bit value or smaller +export.memset_sw # [size, dst, count, value] + # prepare to loop until `count` iterations have been performed + push.0 # [i, dst, size, count, value] + dup.3 # [count, i, dst, size, count, value] + push.0 u32gte # [count > 0, i, dst, size, count, value] + + # compute address for next value to be written + while.true # [i, dst, size, count, value] + # offset the pointer by the current iteration count * aligned size of value, and trap + # if it overflows + dup.1 # [dst, i, dst, size, count, value] + dup.1 # [i, dst, i, dst, size, count, value] + dup.4 # [size, i, dst, i, dst, size, count, value] + u32overflowing_madd assertz # [size * i + dst, i, dst, size, count, value] + + # copy value to top of stack, swap with pointer + dup.5 # [value, dst', i, dst, size, count, value] + swap.1 # [dst', value, i, dst, size, count, value] + + # convert pointer to native representation + u32divmod.4 swap.1 # [dst', dst_offset', value, i, dst, size, count, value] + + # write value to destination + exec.store_sw # [i, dst, size, count, value] + + # increment iteration count, determine whether to continue the loop + push.1 u32overflowing_add assertz # [i++, dst, size, count, value] + dup.0 # [i++, i++, dst, size, count, value] + dup.4 # [count, i++, i++, dst, size, count, value] + u32gte # [i++ >= count, i++, dst, size, count, value] end + + # cleanup operand stack + dropw drop +end + +# Write `count` copies of `value` to memory, starting at `dst`. +# +# * `dst` is expected to be an address in byte-addressable space, _not_ an element address. +# * `value` must be a 64-bit value or smaller +export.memset_dw # [size, dst, count, value_hi, value_lo] + # prepare to loop until `count` iterations have been performed + push.0 # [i, dst, size, count, value_hi, value_lo] + dup.3 # [count, i, dst, size, count, value_hi, value_lo] + push.0 u32gte # [count > 0, i, dst, size, count, value_hi, value_lo] + + # compute address for next value to be written + while.true # [i, dst, size, count, value_hi, value_lo] + # offset the pointer by the current iteration count * aligned size of value, and trap + # if it overflows + dup.1 # [dst, i, dst, size, count, value_hi, value_lo] + dup.1 # [i, dst, i, dst, size, count, value_hi, value_lo] + dup.4 # [size, i, dst, i, dst, size, count, value_hi, value_lo] + u32overflowing_madd assertz # [size * i + dst, i, dst, size, count, value_hi, value_lo] + + # copy value to top of stack, swap with pointer + dup.6 # [value_lo, dst', i, dst, size, count, value_hi, value_lo] + dup.6 # [value_hi, value_lo, dst', i, dst, size, count, value_hi, value_lo] + movup.2 # [dst', value_hi, value_lo, i, dst, size, count, value_hi, value_lo] + + # convert pointer to native representation + u32divmod.4 swap.1 # [dst', dst_offset', value_hi, value_lo, i, dst, size, count, vh, vl] + + # write value to destination + exec.store_dw # [i, dst, size, count, value_hi, value_lo] + + # increment iteration count, determine whether to continue the loop + push.1 u32overflowing_add assertz # [i++, dst, size, count, value_hi, value_lo] + dup.0 # [i++, i++, dst, size, count, value_hi, value_lo] + dup.4 # [count, i++, i++, dst, size, count, value_hi, value_lo] + u32gte # [i++ >= count, i++, dst, size, count, value_hi, value_lo] + end + + # cleanup operand stack + dropw drop drop end diff --git a/codegen/masm/proptest-regressions/opt/operands/solver.txt b/codegen/masm/proptest-regressions/opt/operands/solver.txt new file mode 100644 index 000000000..1de352bf7 --- /dev/null +++ b/codegen/masm/proptest-regressions/opt/operands/solver.txt @@ -0,0 +1,10 @@ +# Seeds for failure cases proptest has generated in the past. It is +# automatically read and these particular cases re-run before any +# novel cases are generated. +# +# It is recommended to check this file in to source control so that +# everyone who runs the test benefits from these saved cases. +cc f58e010ebf51b7e447de3e17299e1830c018c9fb6025f3d84ee1e794b6af1251 # shrinks to problem = ProblemInputs { stack: [0 => Operand { word: [I32], operand: Value(v2) }, 1 => Operand { word: [I32], operand: Value(v3) }, 2 => Operand { word: [I32], operand: Value(v1) }, 3 => Operand { word: [I32], operand: Value(v0) }, 4 => Operand { word: [I32], operand: Value(v4) }, 5 => Operand { word: [I32], operand: Value(v5) }, 6 => Operand { word: [I32], operand: Value(v6) }], expected: [v0, v1, v2], constraints: [Move, Move, Move] } +cc 40325158803f2ab1561bddb9d1daa3d53a8beaf6ab0782579e3d7d762f15bca6 # shrinks to problem = ProblemInputs { stack: [0 => Operand { word: [I32], operand: Value(v4) }, 1 => Operand { word: [I32], operand: Value(v2) }, 2 => Operand { word: [I32], operand: Value(v3) }, 3 => Operand { word: [I32], operand: Value(v0) }, 4 => Operand { word: [I32], operand: Value(v1) }], expected: [v0, v1, v2, v3], constraints: [Move, Move, Move, Move] } +cc 55e7169c0fec43312f12c0092c701f7a4321a30f09d4ace9bb4607554ac0278b # shrinks to problem = ProblemInputs { stack: [0 => Operand { word: [I32], operand: Value(v4) }, 1 => Operand { word: [I32], operand: Value(v3) }, 2 => Operand { word: [I32], operand: Value(v0) }, 3 => Operand { word: [I32], operand: Value(v1) }, 4 => Operand { word: [I32], operand: Value(v2) }], expected: [v0, v1], constraints: [Move, Copy] } +cc e72332cb8544f2079eeb6e02bdc65edbf06d85c804d6450a23df8cb964d44dde # shrinks to problem = ProblemInputs { stack: [0 => Operand { word: [I32], operand: Value(v2) }, 1 => Operand { word: [I32], operand: Value(v0) }, 2 => Operand { word: [I32], operand: Value(v3) }, 3 => Operand { word: [I32], operand: Value(v1) }], expected: [v0, v1], constraints: [Move, Move] } diff --git a/codegen/masm/src/artifact.rs b/codegen/masm/src/artifact.rs new file mode 100644 index 000000000..993cff293 --- /dev/null +++ b/codegen/masm/src/artifact.rs @@ -0,0 +1,550 @@ +use alloc::{ + collections::{BTreeMap, BTreeSet}, + sync::Arc, +}; +use core::fmt; + +use miden_assembly::{ast::InvocationTarget, library::LibraryExport, Library}; +use miden_core::{Program, Word}; +use miden_mast_package::{MastArtifact, Package, ProcedureName}; +use midenc_hir::{constants::ConstantData, dialects::builtin, interner::Symbol}; +use midenc_session::{ + diagnostics::{Report, SourceSpan, Span}, + Session, +}; + +use crate::{lower::NativePtr, masm, TraceEvent}; + +pub struct MasmComponent { + pub id: builtin::ComponentId, + /// The symbol name of the component initializer function + /// + /// This function is responsible for initializing global variables and writing data segments + /// into memory at program startup, and at cross-context call boundaries (in callee prologue). + pub init: Option, + /// The symbol name of the program entrypoint, if this component is executable. + /// + /// If unset, it indicates that the component is a library, even if it could be made executable. + pub entrypoint: Option, + /// The kernel library to link against + pub kernel: Option, + /// The rodata segments of this component keyed by the offset of the segment + pub rodata: Vec, + /// The address of the start of the global heap + pub heap_base: u32, + /// The address of the `__stack_pointer` global, if such a global has been defined + pub stack_pointer: Option, + /// The set of modules in this component + pub modules: Vec>, +} + +/// Represents a read-only data segment, combined with its content digest +#[derive(Clone, PartialEq, Eq)] +pub struct Rodata { + /// The component to which this read-only data segment belongs + pub component: builtin::ComponentId, + /// The content digest computed for `data` + pub digest: Word, + /// The address at which the data for this segment begins + pub start: NativePtr, + /// The raw binary data for this segment + pub data: Arc, +} +impl fmt::Debug for Rodata { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Rodata") + .field("digest", &format_args!("{}", &self.digest)) + .field("start", &self.start) + .field_with("data", |f| { + f.debug_struct("ConstantData") + .field("len", &self.data.len()) + .finish_non_exhaustive() + }) + .finish() + } +} +impl Rodata { + pub fn size_in_bytes(&self) -> usize { + self.data.len() + } + + pub fn size_in_felts(&self) -> usize { + self.data.len().next_multiple_of(4) / 4 + } + + pub fn size_in_words(&self) -> usize { + self.size_in_felts().next_multiple_of(4) / 4 + } + + /// Attempt to convert this rodata object to its equivalent representation in felts + /// + /// See [Self::bytes_to_elements] for more details. + pub fn to_elements(&self) -> Vec { + Self::bytes_to_elements(self.data.as_slice()) + } + + /// Attempt to convert the given bytes to their equivalent representation in felts + /// + /// The resulting felts will be in padded out to the nearest number of words, i.e. if the data + /// only takes up 3 felts worth of bytes, then the resulting `Vec` will contain 4 felts, so that + /// the total size is a valid number of words. + pub fn bytes_to_elements(bytes: &[u8]) -> Vec { + use miden_core::FieldElement; + use miden_processor::Felt; + + let mut felts = Vec::with_capacity(bytes.len() / 4); + let mut iter = bytes.iter().copied().array_chunks::<4>(); + felts.extend(iter.by_ref().map(|chunk| Felt::new(u32::from_le_bytes(chunk) as u64))); + if let Some(remainder) = iter.into_remainder() { + if remainder.len() > 0 { + let mut chunk = [0u8; 4]; + for (i, byte) in remainder.into_iter().enumerate() { + chunk[i] = byte; + } + felts.push(Felt::new(u32::from_le_bytes(chunk) as u64)); + } + } + + let size_in_felts = bytes.len().next_multiple_of(4) / 4; + let size_in_words = size_in_felts.next_multiple_of(4) / 4; + let padding = (size_in_words * 4).abs_diff(felts.len()); + felts.resize(felts.len() + padding, Felt::ZERO); + debug_assert_eq!(felts.len() % 4, 0, "expected to be a valid number of words"); + felts + } +} + +inventory::submit! { + midenc_session::CompileFlag::new("test_harness") + .long("test-harness") + .action(midenc_session::FlagAction::SetTrue) + .help("If present, causes the code generator to emit extra code for the VM test harness") + .help_heading("Testing") +} + +impl fmt::Display for MasmComponent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use crate::intrinsics::INTRINSICS_MODULE_NAMES; + + for module in self.modules.iter() { + // Skip printing the standard library modules and intrinsics + // modules to focus on the user-defined modules and avoid the + // stack overflow error when printing large programs + // https://github.com/0xMiden/miden-formatting/issues/4 + let module_name = module.path().path(); + if INTRINSICS_MODULE_NAMES.contains(&module_name.as_ref()) { + continue; + } + if ["std"].contains(&module.namespace().as_str()) { + continue; + } else { + writeln!(f, "# mod {}\n", &module_name)?; + writeln!(f, "{module}")?; + } + } + Ok(()) + } +} + +impl MasmComponent { + pub fn assemble( + &self, + link_libraries: &[Arc], + link_packages: &BTreeMap>, + session: &Session, + ) -> Result { + if let Some(entrypoint) = self.entrypoint.as_ref() { + self.assemble_program(entrypoint, link_libraries, link_packages, session) + .map(MastArtifact::Executable) + } else { + self.assemble_library(link_libraries, link_packages, session) + .map(MastArtifact::Library) + } + } + + fn assemble_program( + &self, + entrypoint: &InvocationTarget, + link_libraries: &[Arc], + _link_packages: &BTreeMap>, + session: &Session, + ) -> Result, Report> { + use miden_assembly::Assembler; + + let debug_mode = session.options.emit_debug_decorators(); + + log::debug!( + target: "assembly", + "assembling executable with entrypoint '{entrypoint}' (debug_mode={debug_mode})" + ); + let mut assembler = + Assembler::new(session.source_manager.clone()).with_debug_mode(debug_mode); + + let mut lib_modules = BTreeSet::default(); + // Link extra libraries + for library in link_libraries.iter().cloned() { + for module in library.module_infos() { + log::debug!(target: "assembly", "registering '{}' with assembler", module.path()); + lib_modules.insert(module.path().clone()); + } + assembler.link_dynamic_library(library)?; + } + + // Assemble library + log::debug!(target: "assembly", "start adding the following modules with assembler: {}", + self.modules.iter().map(|m| m.path().to_string()).collect::>().join(", ")); + + let mut modules = Vec::with_capacity(self.modules.len()); + for module in self.modules.iter().cloned() { + if lib_modules.contains(module.path()) { + log::warn!( + target: "assembly", + "module '{}' is already registered with the assembler as library's module, \ + skipping", + module.path() + ); + continue; + } + + if module.path().to_string().starts_with("intrinsics") { + log::debug!(target: "assembly", "adding intrinsics '{}' to assembler", module.path()); + assembler.compile_and_statically_link(module)?; + } else { + log::debug!(target: "assembly", "adding '{}' for assembler", module.path()); + modules.push(module); + } + } + + // We need to add modules according to their dependencies (add the dependency before the dependent) + // Workaround until https://github.com/0xMiden/miden-vm/issues/1669 is implemented + for module in modules.into_iter().rev() { + assembler.compile_and_statically_link(module)?; + } + + let emit_test_harness = session.get_flag("test_harness"); + let main = self.generate_main(entrypoint, emit_test_harness)?; + log::debug!(target: "assembly", "generated executable module:\n{main}"); + let program = assembler.assemble_program(main)?; + let advice_map: miden_core::AdviceMap = + self.rodata.iter().map(|rodata| (rodata.digest, rodata.to_elements())).collect(); + Ok(Arc::new(program.with_advice_map(advice_map))) + } + + fn assemble_library( + &self, + link_libraries: &[Arc], + _link_packages: &BTreeMap>, + session: &Session, + ) -> Result, Report> { + use miden_assembly::Assembler; + + let debug_mode = session.options.emit_debug_decorators(); + log::debug!( + target: "assembly", + "assembling library of {} modules (debug_mode={})", + self.modules.len(), + debug_mode + ); + + let mut assembler = + Assembler::new(session.source_manager.clone()).with_debug_mode(debug_mode); + + let mut lib_modules = Vec::new(); + // Link extra libraries + for library in link_libraries.iter().cloned() { + for module in library.module_infos() { + log::debug!(target: "assembly", "registering '{}' with assembler", module.path()); + lib_modules.push(module.path().clone()); + } + assembler.link_dynamic_library(library)?; + } + + // Assemble library + log::debug!(target: "assembly", "start adding the following modules with assembler: {}", + self.modules.iter().map(|m| m.path().to_string()).collect::>().join(", ")); + let mut modules = Vec::with_capacity(self.modules.len()); + for module in self.modules.iter().cloned() { + if lib_modules.contains(module.path()) { + log::warn!( + target: "assembly", + "module '{}' is already registered with the assembler as library's module, \ + skipping", + module.path() + ); + continue; + } + if module.path().to_string().starts_with("intrinsics") { + log::debug!(target: "assembly", "adding intrinsics '{}' to assembler", module.path()); + assembler.compile_and_statically_link(module)?; + } else { + log::debug!(target: "assembly", "adding '{}' for assembler", module.path()); + modules.push(module); + } + } + let lib = assembler.assemble_library(modules)?; + + let advice_map: miden_core::AdviceMap = + self.rodata.iter().map(|rodata| (rodata.digest, rodata.to_elements())).collect(); + + let converted_exports = recover_wasm_cm_interfaces(&lib); + + // Get a reference to the library MAST, then drop the library so we can obtain a mutable + // reference so we can modify its advice map data + let mut mast_forest = lib.mast_forest().clone(); + drop(lib); + { + let mast = Arc::get_mut(&mut mast_forest).expect("expected unique reference"); + mast.advice_map_mut().extend(advice_map); + } + + // Reconstruct the library with the updated MAST + Ok(Library::new(mast_forest, converted_exports).map(Arc::new)?) + } + + /// Generate an executable module which when run expects the raw data segment data to be + /// provided on the advice stack in the same order as initialization, and the operands of + /// the entrypoint function on the operand stack. + fn generate_main( + &self, + entrypoint: &InvocationTarget, + emit_test_harness: bool, + ) -> Result, Report> { + use masm::{Instruction as Inst, Op}; + + let mut exe = Box::new(masm::Module::new_executable()); + let span = SourceSpan::default(); + let body = { + let mut block = masm::Block::new(span, Vec::with_capacity(64)); + // Invoke component initializer, if present + if let Some(init) = self.init.as_ref() { + block.push(Op::Inst(Span::new(span, Inst::Exec(init.clone())))); + } + + // Initialize test harness, if requested + if emit_test_harness { + self.emit_test_harness(&mut block); + } + + // Invoke the program entrypoint + block.push(Op::Inst(Span::new( + span, + Inst::Trace(TraceEvent::FrameStart.as_u32().into()), + ))); + block.push(Op::Inst(Span::new(span, Inst::Exec(entrypoint.clone())))); + block + .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); + + // Truncate the stack to 16 elements on exit + let truncate_stack = InvocationTarget::AbsoluteProcedurePath { + name: ProcedureName::new("truncate_stack").unwrap(), + path: masm::LibraryPath::new_from_components( + masm::LibraryNamespace::new("std").unwrap(), + [masm::Ident::new("sys").unwrap()], + ), + }; + block.push(Op::Inst(Span::new(span, Inst::Exec(truncate_stack)))); + block + }; + let start = masm::Procedure::new( + span, + masm::Visibility::Public, + masm::ProcedureName::main(), + 0, + body, + ); + exe.define_procedure(masm::Export::Procedure(start))?; + Ok(Arc::from(exe)) + } + + fn emit_test_harness(&self, block: &mut masm::Block) { + use masm::{Instruction as Inst, IntValue, Op}; + use miden_core::{Felt, FieldElement}; + + let span = SourceSpan::default(); + + let pipe_words_to_memory = masm::ProcedureName::new("pipe_words_to_memory").unwrap(); + let std_mem = masm::LibraryPath::new("std::mem").unwrap(); + + // Step 1: Get the number of initializers to run + // => [inits] on operand stack + block.push(Op::Inst(Span::new(span, Inst::AdvPush(1.into())))); + + // Step 2: Evaluate the initial state of the loop condition `inits > 0` + // => [inits, inits] + block.push(Op::Inst(Span::new(span, Inst::Dup0))); + // => [inits > 0, inits] + block.push(Op::Inst(Span::new(span, Inst::Push(IntValue::U8(0).into())))); + block.push(Op::Inst(Span::new(span, Inst::Gt))); + + // Step 3: Loop until `inits == 0` + let mut loop_body = Vec::with_capacity(16); + + // State of operand stack on entry to `loop_body`: [inits] + // State of advice stack on entry to `loop_body`: [dest_ptr, num_words, ...] + // + // Step 3a: Compute next value of `inits`, i.e. `inits'` + // => [inits - 1] + loop_body.push(Op::Inst(Span::new(span, Inst::SubImm(Felt::ONE.into())))); + + // Step 3b: Copy initializer data to memory + // => [num_words, dest_ptr, inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::AdvPush(2.into())))); + // => [C, B, A, dest_ptr, inits'] on operand stack + loop_body + .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameStart.as_u32().into())))); + loop_body.push(Op::Inst(Span::new( + span, + Inst::Exec(InvocationTarget::AbsoluteProcedurePath { + name: pipe_words_to_memory, + path: std_mem, + }), + ))); + loop_body + .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); + // Drop C, B, A + loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); + loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); + loop_body.push(Op::Inst(Span::new(span, Inst::DropW))); + // => [inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::Drop))); + + // Step 3c: Evaluate loop condition `inits' > 0` + // => [inits', inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::Dup0))); + // => [inits' > 0, inits'] + loop_body.push(Op::Inst(Span::new(span, Inst::Push(IntValue::U8(0).into())))); + loop_body.push(Op::Inst(Span::new(span, Inst::Gt))); + + // Step 4: Enter (or skip) loop + block.push(Op::While { + span, + body: masm::Block::new(span, loop_body), + }); + + // Step 5: Drop `inits` after loop is evaluated + block.push(Op::Inst(Span::new(span, Inst::Drop))); + } +} + +/// Try to recognize Wasm CM interfaces and transform those exports to have Wasm interface encoded +/// as module name. +/// +/// Temporary workaround for: +/// +/// 1. Temporary exporting multiple interfaces from the same(Wasm core) module (an interface is +/// encoded in the function name) +/// +/// 2. Assembler using the current module name to generate exports. +/// +fn recover_wasm_cm_interfaces( + lib: &Library, +) -> BTreeMap { + use crate::intrinsics::INTRINSICS_MODULE_NAMES; + + let mut exports = BTreeMap::new(); + for export in lib.exports() { + if INTRINSICS_MODULE_NAMES.contains(&export.name.module.to_string().as_str()) + || export.name.name.as_str().starts_with("cabi") + { + // Preserve intrinsics modules and internal Wasm CM `cabi_*` functions + exports.insert(export.name.clone(), export.clone()); + continue; + } + + if let Some((component, interface)) = export.name.name.as_str().rsplit_once('/') { + let export_node_id = lib.get_export_node_id(&export.name); + + // Wasm CM interface + let (interface, function) = + interface.rsplit_once('#').expect("invalid wasm component model identifier"); + + let mut component_parts = component.split(':').map(Arc::from); + let ns = masm::LibraryNamespace::User( + component_parts.next().expect("invalid wasm component model identifier"), + ); + let component_parts = component_parts + .map(Span::unknown) + .map(masm::Ident::from_raw_parts) + .chain([masm::Ident::from_raw_parts(Span::unknown(Arc::from(interface)))]); + let path = masm::LibraryPath::new_from_components(ns, component_parts); + let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts( + Span::unknown(Arc::from(function)), + )); + let new_export = masm::QualifiedProcedureName::new(path, name); + + let new_lib_export = LibraryExport::new(export_node_id, new_export.clone()); + + exports.insert(new_export, new_lib_export.clone()); + } else { + // Non-Wasm CM interface, preserve as is + exports.insert(export.name.clone(), export.clone()); + } + } + exports +} + +#[cfg(test)] +mod tests { + use miden_core::FieldElement; + use proptest::prelude::*; + + use super::*; + + fn validate_bytes_to_elements(bytes: &[u8]) { + let result = Rodata::bytes_to_elements(bytes); + + // Each felt represents 4 bytes + let expected_felts = bytes.len().div_ceil(4); + // Felts should be padded to a multiple of 4 (1 word = 4 felts) + let expected_total_felts = expected_felts.div_ceil(4) * 4; + + assert_eq!( + result.len(), + expected_total_felts, + "For {} bytes, expected {} felts (padded from {} felts), but got {}", + bytes.len(), + expected_total_felts, + expected_felts, + result.len() + ); + + // Verify padding is zeros + for (i, felt) in result.iter().enumerate().skip(expected_felts) { + assert_eq!(*felt, miden_processor::Felt::ZERO, "Padding at index {i} should be zero"); + } + } + + #[test] + fn test_bytes_to_elements_edge_cases() { + validate_bytes_to_elements(&[]); + validate_bytes_to_elements(&[1]); + validate_bytes_to_elements(&[0u8; 4]); + validate_bytes_to_elements(&[0u8; 15]); + validate_bytes_to_elements(&[0u8; 16]); + validate_bytes_to_elements(&[0u8; 17]); + validate_bytes_to_elements(&[0u8; 31]); + validate_bytes_to_elements(&[0u8; 32]); + validate_bytes_to_elements(&[0u8; 33]); + validate_bytes_to_elements(&[0u8; 64]); + } + + proptest! { + #![proptest_config(ProptestConfig::with_cases(1000))] + #[test] + fn proptest_bytes_to_elements(bytes in prop::collection::vec(any::(), 0..=1000)) { + validate_bytes_to_elements(&bytes); + } + + #[test] + fn proptest_bytes_to_elements_word_boundaries(size_factor in 0u32..=100) { + // Test specifically around word boundaries + // Test sizes around multiples of 16 (since 1 word = 4 felts = 16 bytes) + let base_size = size_factor * 16; + for offset in -2i32..=2 { + let size = (base_size as i32 + offset).max(0) as usize; + let bytes = vec![0u8; size]; + validate_bytes_to_elements(&bytes); + } + } + } +} diff --git a/codegen/masm/src/codegen/emit/binary.rs b/codegen/masm/src/codegen/emit/binary.rs deleted file mode 100644 index 40b46f205..000000000 --- a/codegen/masm/src/codegen/emit/binary.rs +++ /dev/null @@ -1,1493 +0,0 @@ -use midenc_hir::{assert_matches, Felt, Immediate, Overflow, SourceSpan, Type}; - -use super::OpEmitter; -use crate::masm::Op; - -impl<'a> OpEmitter<'a> { - pub fn eq(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected eq operands to be the same type"); - match &ty { - Type::I128 | Type::U128 => { - self.eq_i128(span); - } - Type::I64 | Type::U64 => { - self.eq_int64(span); - } - Type::Felt - | Type::Ptr(_) - | Type::U32 - | Type::I32 - | Type::U16 - | Type::I16 - | Type::I8 - | Type::U8 - | Type::I1 => { - self.emit(Op::Eq, span); - } - ty => unimplemented!("eq is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn eq_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected eq operands to be the same type"); - match &ty { - Type::I128 | Type::U128 => { - self.push_immediate(imm, span); - self.eq_i128(span); - } - Type::I64 | Type::U64 => { - self.push_immediate(imm, span); - self.eq_int64(span); - } - Type::Felt | Type::Ptr(_) | Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit(Op::EqImm(imm.as_felt().unwrap()), span); - } - Type::I32 | Type::I16 | Type::I8 => { - self.emit(Op::EqImm(Felt::new(imm.as_i32().unwrap() as u32 as u64)), span); - } - ty => unimplemented!("eq is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn neq(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected neq operands to be the same type"); - match &ty { - Type::I128 | Type::U128 => { - self.neq_i128(span); - } - Type::I64 | Type::U64 => self.neq_int64(span), - Type::Felt - | Type::Ptr(_) - | Type::U32 - | Type::I32 - | Type::U16 - | Type::I16 - | Type::I8 - | Type::U8 - | Type::I1 => { - self.emit(Op::Neq, span); - } - ty => unimplemented!("neq is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn neq_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected neq operands to be the same type"); - match &ty { - Type::I128 | Type::U128 => { - self.push_immediate(imm, span); - self.neq_i128(span); - } - Type::I64 | Type::U64 => { - self.push_immediate(imm, span); - self.neq_int64(span) - } - Type::Felt | Type::Ptr(_) | Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit(Op::NeqImm(imm.as_felt().unwrap()), span); - } - Type::I32 | Type::I16 | Type::I8 => { - self.emit(Op::NeqImm(Felt::new(imm.as_i32().unwrap() as u32 as u64)), span); - } - ty => unimplemented!("neq is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn gt(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected gt operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Gt, span); - } - Type::U64 => { - self.gt_u64(span); - } - Type::I64 => { - self.gt_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit(Op::U32Gt, span); - } - Type::I32 => self.emit(Op::Exec("intrinsics::i32::is_gt".parse().unwrap()), span), - ty => unimplemented!("gt is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn gt_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected gt operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::GtImm(imm.as_felt().unwrap()), span); - } - Type::U64 => { - self.push_u64(imm.as_u64().unwrap(), span); - self.gt_u64(span); - } - Type::I64 => { - self.push_i64(imm.as_i64().unwrap(), span); - self.gt_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit_all(&[Op::PushU32(imm.as_u32().unwrap()), Op::U32Gt], span); - } - Type::I32 => { - self.emit_all( - &[ - Op::PushU32(imm.as_i32().unwrap() as u32), - Op::Exec("intrinsics::i32::is_gt".parse().unwrap()), - ], - span, - ); - } - ty => unimplemented!("gt is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn gte(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected gte operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Gte, span); - } - Type::U64 => { - self.gte_u64(span); - } - Type::I64 => { - self.gte_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit(Op::U32Gte, span); - } - Type::I32 => self.emit(Op::Exec("intrinsics::i32::is_gte".parse().unwrap()), span), - ty => unimplemented!("gte is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn gte_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected gte operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::GteImm(imm.as_felt().unwrap()), span); - } - Type::U64 => { - self.push_u64(imm.as_u64().unwrap(), span); - self.gte_u64(span); - } - Type::I64 => { - self.push_i64(imm.as_i64().unwrap(), span); - self.gte_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit_all(&[Op::PushU32(imm.as_u32().unwrap()), Op::U32Gte], span); - } - Type::I32 => { - self.emit_all( - &[ - Op::PushU32(imm.as_i32().unwrap() as u32), - Op::Exec("intrinsics::i32::is_gte".parse().unwrap()), - ], - span, - ); - } - ty => unimplemented!("gte is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn lt(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected lt operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Lt, span); - } - Type::U64 => { - self.lt_u64(span); - } - Type::I64 => { - self.lt_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit(Op::U32Lt, span); - } - Type::I32 => self.emit(Op::Exec("intrinsics::i32::is_lt".parse().unwrap()), span), - ty => unimplemented!("lt is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn lt_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected lt operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::LtImm(imm.as_felt().unwrap()), span); - } - Type::U64 => { - self.push_u64(imm.as_u64().unwrap(), span); - self.lt_u64(span); - } - Type::I64 => { - self.push_i64(imm.as_i64().unwrap(), span); - self.lt_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit_all(&[Op::PushU32(imm.as_u32().unwrap()), Op::U32Lt], span); - } - Type::I32 => { - self.emit_all( - &[ - Op::PushU32(imm.as_i32().unwrap() as u32), - Op::Exec("intrinsics::i32::is_lt".parse().unwrap()), - ], - span, - ); - } - ty => unimplemented!("lt is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn lte(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected lte operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Lte, span); - } - Type::U64 => { - self.lte_u64(span); - } - Type::I64 => { - self.lte_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit(Op::U32Lte, span); - } - Type::I32 => self.emit(Op::Exec("intrinsics::i32::is_lte".parse().unwrap()), span), - ty => unimplemented!("lte is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn lte_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected lte operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::LteImm(imm.as_felt().unwrap()), span); - } - Type::U64 => { - self.push_u64(imm.as_u64().unwrap(), span); - self.lte_u64(span); - } - Type::I64 => { - self.push_i64(imm.as_i64().unwrap(), span); - self.lte_i64(span); - } - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.emit_all(&[Op::PushU32(imm.as_u32().unwrap()), Op::U32Lte], span); - } - Type::I32 => { - self.emit_all( - &[ - Op::PushU32(imm.as_i32().unwrap() as u32), - Op::Exec("intrinsics::i32::is_lte".parse().unwrap()), - ], - span, - ); - } - ty => unimplemented!("lte is not yet implemented for {ty}"), - } - self.push(Type::I1); - } - - pub fn add(&mut self, overflow: Overflow, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected add operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Add, span); - } - Type::U64 => { - self.add_u64(overflow, span); - } - Type::I64 => { - self.add_i64(overflow, span); - } - Type::U32 => { - self.add_u32(overflow, span); - } - Type::I32 => { - self.add_i32(overflow, span); - } - ty @ (Type::U16 | Type::U8 | Type::I1) => { - self.add_uint(ty.size_in_bits() as u32, overflow, span); - } - ty => unimplemented!("add is not yet implemented for {ty}"), - } - if overflow.is_overflowing() { - self.push(Type::I1); - } - self.push(ty); - } - - pub fn add_imm(&mut self, imm: Immediate, overflow: Overflow, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected add operands to be the same type"); - match &ty { - Type::Felt if imm == 1 => self.emit(Op::Incr, span), - Type::Felt => { - self.emit(Op::AddImm(imm.as_felt().unwrap()), span); - } - Type::U64 => { - self.push_immediate(imm, span); - self.add_u64(overflow, span); - } - Type::I64 => { - self.add_imm_i64(imm.as_i64().unwrap(), overflow, span); - } - Type::U32 => { - self.add_imm_u32(imm.as_u32().unwrap(), overflow, span); - } - Type::I32 => { - self.add_imm_i32(imm.as_i32().unwrap(), overflow, span); - } - ty @ (Type::U16 | Type::U8 | Type::I1) => { - self.add_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, overflow, span); - } - ty => unimplemented!("add is not yet implemented for {ty}"), - } - if overflow.is_overflowing() { - self.push(Type::I1); - } - self.push(ty); - } - - pub fn sub(&mut self, overflow: Overflow, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected sub operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Sub, span); - } - Type::U64 => { - self.sub_u64(overflow, span); - } - Type::I64 => { - self.sub_i64(overflow, span); - } - Type::U32 => { - self.sub_u32(overflow, span); - } - Type::I32 => { - self.sub_i32(overflow, span); - } - ty @ (Type::U16 | Type::U8 | Type::I1) => { - self.sub_uint(ty.size_in_bits() as u32, overflow, span); - } - ty => unimplemented!("sub is not yet implemented for {ty}"), - } - if overflow.is_overflowing() { - self.push(Type::I1); - } - self.push(ty); - } - - pub fn sub_imm(&mut self, imm: Immediate, overflow: Overflow, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected sub operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::SubImm(imm.as_felt().unwrap()), span); - } - Type::U64 => { - self.push_immediate(imm, span); - self.sub_u64(overflow, span); - } - Type::I64 => { - self.sub_imm_i64(imm.as_i64().unwrap(), overflow, span); - } - Type::U32 => { - self.sub_imm_u32(imm.as_u32().unwrap(), overflow, span); - } - Type::I32 => { - self.sub_imm_i32(imm.as_i32().unwrap(), overflow, span); - } - ty @ (Type::U16 | Type::U8 | Type::I1) => { - self.sub_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, overflow, span); - } - ty => unimplemented!("sub is not yet implemented for {ty}"), - } - if overflow.is_overflowing() { - self.push(Type::I1); - } - self.push(ty); - } - - pub fn mul(&mut self, overflow: Overflow, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected mul operands to be the same type"); - match &ty { - Type::I128 | Type::U128 => { - // We can use the Karatsuba algorithm for multiplication here: - // - // x = x_hi * 2^63 + x_lo - // y = y_hi * 2^63 + x_lo - // - // z2 = x_hi * y_hi - // z0 = x_lo * y_lo - // z1 = (x_hi + x_lo) * (y_hi + y_lo) - z2 - z0 - // - // z = z2 * (2^63)^2 + z1 * 2^63 + z0 - // - // We assume the stack holds two words representing x and y, with y on top of the - // stack - todo!() - } - Type::Felt => { - assert_matches!( - overflow, - Overflow::Unchecked | Overflow::Wrapping, - "only unchecked or wrapping semantics are supported for felt" - ); - self.emit(Op::Mul, span); - } - Type::U64 => self.mul_u64(overflow, span), - Type::I64 => self.mul_i64(overflow, span), - Type::U32 => self.mul_u32(overflow, span), - Type::I32 => self.mul_i32(overflow, span), - ty @ (Type::U16 | Type::U8) => { - self.mul_uint(ty.size_in_bits() as u32, overflow, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: mul expects integer operands, got {ty}") - } - ty => unimplemented!("mul for {ty} is not supported"), - } - if overflow.is_overflowing() { - self.push(Type::I1); - } - self.push(ty); - } - - pub fn mul_imm(&mut self, imm: Immediate, overflow: Overflow, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected mul operands to be the same type"); - match &ty { - Type::Felt => { - assert_matches!( - overflow, - Overflow::Unchecked | Overflow::Wrapping, - "only unchecked or wrapping semantics are supported for felt" - ); - self.emit(Op::MulImm(imm.as_felt().unwrap()), span); - } - Type::U64 => { - self.push_immediate(imm, span); - self.mul_u64(overflow, span); - } - Type::I64 => self.mul_imm_i64(imm.as_i64().unwrap(), overflow, span), - Type::U32 => self.mul_imm_u32(imm.as_u32().unwrap(), overflow, span), - Type::I32 => self.mul_imm_i32(imm.as_i32().unwrap(), overflow, span), - ty @ (Type::U16 | Type::U8) => { - self.mul_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, overflow, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: mul expects integer operands, got {ty}") - } - ty => unimplemented!("mul for {ty} is not supported"), - } - if overflow.is_overflowing() { - self.push(Type::I1); - } - self.push(ty); - } - - pub fn checked_div(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected div operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Div, span); - } - Type::U64 => self.checked_div_u64(span), - Type::I64 => self.checked_div_i64(span), - Type::U32 => self.checked_div_u32(span), - Type::I32 => self.checked_div_i32(span), - ty @ (Type::U16 | Type::U8) => { - self.checked_div_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: div expects integer operands, got {ty}") - } - ty => unimplemented!("div for {ty} is not supported"), - } - self.push(ty); - } - - pub fn checked_div_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected div operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Div, span); - } - Type::U64 => { - assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); - self.push_immediate(imm, span); - self.checked_div_u64(span); - } - Type::I64 => self.checked_div_imm_i64(imm.as_i64().unwrap(), span), - Type::U32 => self.checked_div_imm_u32(imm.as_u32().unwrap(), span), - Type::I32 => self.checked_div_imm_i32(imm.as_i32().unwrap(), span), - ty @ (Type::U16 | Type::U8) => { - self.checked_div_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: div expects integer operands, got {ty}") - } - ty => unimplemented!("div for {ty} is not supported"), - } - self.push(ty); - } - - pub fn unchecked_div(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected div operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Div, span); - } - Type::U64 => self.unchecked_div_u64(span), - Type::I64 => self.checked_div_i64(span), - Type::U32 | Type::U16 | Type::U8 => self.unchecked_div_u32(span), - Type::I32 => self.checked_div_i32(span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: div expects integer operands, got {ty}") - } - ty => unimplemented!("div for {ty} is not supported"), - } - self.push(ty); - } - - pub fn unchecked_div_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected div operands to be the same type"); - match &ty { - Type::Felt => { - self.emit(Op::Div, span); - } - Type::U64 => { - assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); - self.push_immediate(imm, span); - self.unchecked_div_u64(span); - } - Type::I64 => self.checked_div_imm_i64(imm.as_i64().unwrap(), span), - Type::U32 => self.unchecked_div_imm_u32(imm.as_u32().unwrap(), span), - Type::I32 => self.checked_div_imm_i32(imm.as_i32().unwrap(), span), - ty @ (Type::U16 | Type::U8) => { - self.unchecked_div_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: div expects integer operands, got {ty}") - } - ty => unimplemented!("div for {ty} is not supported"), - } - self.push(ty); - } - - pub fn checked_mod(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected mod operands to be the same type"); - match &ty { - Type::U64 => self.checked_mod_u64(span), - Type::U32 => self.checked_mod_u32(span), - ty @ (Type::U16 | Type::U8) => { - self.checked_mod_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: mod expects integer operands, got {ty}") - } - ty => unimplemented!("mod for {ty} is not supported"), - } - self.push(ty); - } - - pub fn checked_mod_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected mod operands to be the same type"); - match &ty { - Type::U64 => { - assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); - self.push_immediate(imm, span); - self.checked_mod_u64(span); - } - Type::U32 => self.checked_mod_imm_u32(imm.as_u32().unwrap(), span), - ty @ (Type::U16 | Type::U8) => { - self.checked_mod_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: mod expects integer operands, got {ty}") - } - ty => unimplemented!("mod for {ty} is not supported"), - } - self.push(ty); - } - - pub fn unchecked_mod(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected mod operands to be the same type"); - match &ty { - Type::U64 => self.unchecked_mod_u64(span), - Type::U32 => self.unchecked_mod_u32(span), - ty @ (Type::U16 | Type::U8) => { - self.unchecked_mod_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: mod expects integer operands, got {ty}") - } - ty => unimplemented!("mod for {ty} is not supported"), - } - self.push(ty); - } - - pub fn unchecked_mod_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected mod operands to be the same type"); - match &ty { - Type::U64 => { - assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); - self.push_immediate(imm, span); - self.unchecked_mod_u64(span); - } - Type::U32 => self.unchecked_mod_imm_u32(imm.as_u32().unwrap(), span), - ty @ (Type::U16 | Type::U8) => { - self.unchecked_mod_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: mod expects integer operands, got {ty}") - } - ty => unimplemented!("mod for {ty} is not supported"), - } - self.push(ty); - } - - pub fn checked_divmod(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected divmod operands to be the same type"); - match &ty { - Type::U64 => self.checked_divmod_u64(span), - Type::U32 => self.checked_divmod_u32(span), - ty @ (Type::U16 | Type::U8) => { - self.checked_divmod_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: divmod expects integer operands, got {ty}") - } - ty => unimplemented!("divmod for {ty} is not supported"), - } - self.push(ty.clone()); - self.push(ty); - } - - pub fn checked_divmod_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected divmod operands to be the same type"); - match &ty { - Type::U64 => { - assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); - self.push_immediate(imm, span); - self.checked_divmod_u64(span); - } - Type::U32 => self.checked_divmod_imm_u32(imm.as_u32().unwrap(), span), - ty @ (Type::U16 | Type::U8) => { - self.checked_divmod_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: divmod expects integer operands, got {ty}") - } - ty => unimplemented!("divmod for {ty} is not supported"), - } - self.push(ty.clone()); - self.push(ty); - } - - pub fn unchecked_divmod(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected divmod operands to be the same type"); - match &ty { - Type::U64 => self.unchecked_divmod_u64(span), - Type::U32 => self.unchecked_divmod_u32(span), - ty @ (Type::U16 | Type::U8) => { - self.unchecked_divmod_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: divmod expects integer operands, got {ty}") - } - ty => unimplemented!("divmod for {ty} is not supported"), - } - self.push(ty.clone()); - self.push(ty); - } - - pub fn unchecked_divmod_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected divmod operands to be the same type"); - match &ty { - Type::U64 => { - assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); - self.push_immediate(imm, span); - self.unchecked_divmod_u64(span); - } - Type::U32 => self.unchecked_divmod_imm_u32(imm.as_u32().unwrap(), span), - ty @ (Type::U16 | Type::U8) => { - self.unchecked_divmod_imm_uint( - imm.as_u32().unwrap(), - ty.size_in_bits() as u32, - span, - ); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: divmod expects integer operands, got {ty}") - } - ty => unimplemented!("divmod for {ty} is not supported"), - } - self.push(ty.clone()); - self.push(ty); - } - - pub fn exp(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected exp operands to be the same type"); - match &ty { - Type::U64 => todo!("exponentiation by squaring"), - Type::Felt => { - self.emit(Op::Exp, span); - } - Type::U32 => { - self.emit_all(&[Op::Exp, Op::U32Assert], span); - } - Type::I32 => { - self.emit(Op::Exec("intrinsics::i32::ipow".parse().unwrap()), span); - } - ty @ (Type::U16 | Type::U8) => { - self.emit_all(&[Op::Exp, Op::U32Assert], span); - self.int32_to_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: exp expects integer operands, got {ty}") - } - ty => unimplemented!("mod for {ty} is not supported"), - } - self.push(ty); - } - - pub fn exp_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected exp operands to be the same type"); - let exp: u8 = - imm.as_u64().unwrap().try_into().expect("invalid exponent: must be value < 64"); - match &ty { - Type::U64 => todo!("exponentiation by squaring"), - Type::Felt => { - self.emit(Op::ExpImm(exp), span); - } - Type::U32 => { - self.emit_all(&[Op::ExpImm(exp), Op::U32Assert], span); - } - Type::I32 => { - self.emit_all( - &[Op::PushU8(exp), Op::Exec("intrinsics::i32::ipow".parse().unwrap())], - span, - ); - } - ty @ (Type::U16 | Type::U8) => { - self.emit_all(&[Op::ExpImm(exp), Op::U32Assert], span); - self.int32_to_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: exp expects integer operands, got {ty}") - } - ty => unimplemented!("mod for {ty} is not supported"), - } - self.push(ty); - } - - pub fn and(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected and operands to be the same type"); - assert_eq!(ty, Type::I1, "expected and operands to be of boolean type"); - self.emit(Op::And, span); - self.push(ty); - } - - pub fn and_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected and operands to be the same type"); - assert_eq!(ty, Type::I1, "expected and operands to be of boolean type"); - self.emit(Op::AndImm(imm.as_bool().unwrap()), span); - self.push(ty); - } - - pub fn or(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected or operands to be the same type"); - assert_eq!(ty, Type::I1, "expected or operands to be of boolean type"); - self.emit(Op::Or, span); - self.push(ty); - } - - pub fn or_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected or operands to be the same type"); - assert_eq!(ty, Type::I1, "expected or operands to be of boolean type"); - self.emit(Op::OrImm(imm.as_bool().unwrap()), span); - self.push(ty); - } - - pub fn xor(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected xor operands to be the same type"); - assert_eq!(ty, Type::I1, "expected xor operands to be of boolean type"); - self.emit(Op::Xor, span); - self.push(ty); - } - - pub fn xor_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected xor operands to be the same type"); - assert_eq!(ty, Type::I1, "expected xor operands to be of boolean type"); - self.emit(Op::XorImm(imm.as_bool().unwrap()), span); - self.push(ty); - } - - pub fn band(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected band operands to be the same type"); - match &ty { - Type::U128 | Type::I128 => { - // AND the high bits - // - // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] - self.emit_all( - &[ - // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] - Op::Movup(5), - Op::Movup(5), - ], - span, - ); - self.band_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] - // AND the low bits - self.emit_all( - &[ - // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] - Op::Movdn(5), - Op::Movdn(5), - ], - span, - ); - self.band_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] - self.emit_all( - &[ - // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] - Op::Movup(3), - Op::Movup(3), - ], - span, - ); - } - Type::U64 | Type::I64 => self.band_int64(span), - Type::U32 | Type::I32 | Type::U16 | Type::I16 | Type::U8 | Type::I8 => { - self.band_u32(span) - } - Type::I1 => self.emit(Op::And, span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: band expects integer operands, got {ty}") - } - ty => unimplemented!("band for {ty} is not supported"), - } - self.push(ty); - } - - pub fn band_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected band operands to be the same type"); - match &ty { - Type::U128 | Type::I128 => { - self.push_immediate(imm, span); - // AND the high bits - // - // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] - self.emit_all( - &[ - // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] - Op::Movup(5), - Op::Movup(5), - ], - span, - ); - self.band_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] - // AND the low bits - self.emit_all( - &[ - // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] - Op::Movdn(5), - Op::Movdn(5), - ], - span, - ); - self.band_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] - self.emit_all( - &[ - // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] - Op::Movup(3), - Op::Movup(3), - ], - span, - ); - } - Type::U64 | Type::I64 => { - self.push_immediate(imm, span); - self.band_int64(span); - } - Type::U32 | Type::U16 | Type::U8 => self.band_imm_u32(imm.as_u32().unwrap(), span), - Type::I32 | Type::I16 | Type::I8 => { - self.band_imm_u32(imm.as_i64().unwrap() as u64 as u32, span) - } - Type::I1 => self.emit(Op::AndImm(imm.as_bool().unwrap()), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: band expects integer operands, got {ty}") - } - ty => unimplemented!("band for {ty} is not supported"), - } - self.push(ty); - } - - pub fn bor(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected bor operands to be the same type"); - match &ty { - Type::U128 | Type::I128 => { - // OR the high bits - // - // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] - self.emit_all( - &[ - // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] - Op::Movup(5), - Op::Movup(5), - ], - span, - ); - self.bor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] - // OR the low bits - self.emit_all( - &[ - // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] - Op::Movdn(5), - Op::Movdn(5), - ], - span, - ); - self.bor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] - self.emit_all( - &[ - // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] - Op::Movup(3), - Op::Movup(3), - ], - span, - ); - } - Type::U64 | Type::I64 => self.bor_int64(span), - Type::U32 | Type::I32 | Type::U16 | Type::I16 | Type::U8 | Type::I8 => { - self.bor_u32(span) - } - Type::I1 => self.emit(Op::Or, span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: bor expects integer operands, got {ty}") - } - ty => unimplemented!("bor for {ty} is not supported"), - } - self.push(ty); - } - - pub fn bor_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected bor operands to be the same type"); - match &ty { - Type::U128 | Type::I128 => { - self.push_immediate(imm, span); - // OR the high bits - // - // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] - self.emit_all( - &[ - // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] - Op::Movup(5), - Op::Movup(5), - ], - span, - ); - self.bor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] - // OR the low bits - self.emit_all( - &[ - // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] - Op::Movdn(5), - Op::Movdn(5), - ], - span, - ); - self.bor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] - self.emit_all( - &[ - // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] - Op::Movup(3), - Op::Movup(3), - ], - span, - ); - } - Type::U64 | Type::I64 => { - self.push_immediate(imm, span); - self.bor_int64(span); - } - Type::U32 | Type::U16 | Type::U8 => self.bor_imm_u32(imm.as_u32().unwrap(), span), - Type::I32 | Type::I16 | Type::I8 => { - self.bor_imm_u32(imm.as_i64().unwrap() as u64 as u32, span) - } - Type::I1 => self.emit(Op::AndImm(imm.as_bool().unwrap()), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: bor expects integer operands, got {ty}") - } - ty => unimplemented!("bor for {ty} is not supported"), - } - self.push(ty); - } - - pub fn bxor(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected bxor operands to be the same type"); - match &ty { - Type::U128 | Type::I128 => { - // XOR the high bits - // - // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] - self.emit_all( - &[ - // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] - Op::Movup(5), - Op::Movup(5), - ], - span, - ); - self.bxor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] - // XOR the low bits - self.emit_all( - &[ - // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] - Op::Movdn(5), - Op::Movdn(5), - ], - span, - ); - self.bxor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] - self.emit_all( - &[ - // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] - Op::Movup(3), - Op::Movup(3), - ], - span, - ); - } - Type::U64 | Type::I64 => self.bxor_int64(span), - Type::U32 | Type::I32 => self.bxor_u32(span), - ty @ (Type::U16 | Type::I16 | Type::U8 | Type::I8) => { - self.bxor_u32(span); - self.trunc_int32(ty.size_in_bits() as u32, span); - } - Type::I1 => self.emit(Op::Xor, span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: bxor expects integer operands, got {ty}") - } - ty => unimplemented!("bxor for {ty} is not supported"), - } - self.push(ty); - } - - pub fn bxor_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected bxor operands to be the same type"); - match &ty { - Type::U128 | Type::I128 => { - self.push_immediate(imm, span); - // XOR the high bits - // - // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] - self.emit_all( - &[ - // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] - Op::Movup(5), - Op::Movup(5), - ], - span, - ); - self.bxor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] - // XOR the low bits - self.emit_all( - &[ - // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] - Op::Movdn(5), - Op::Movdn(5), - ], - span, - ); - self.bxor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] - self.emit_all( - &[ - // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] - Op::Movup(3), - Op::Movup(3), - ], - span, - ); - } - Type::U64 | Type::I64 => { - self.push_immediate(imm, span); - self.bxor_int64(span); - } - Type::U32 => self.bxor_imm_u32(imm.as_u32().unwrap(), span), - Type::I32 => self.bxor_imm_u32(imm.as_i64().unwrap() as u64 as u32, span), - ty @ (Type::U16 | Type::U8) => { - self.bxor_imm_u32(imm.as_u32().unwrap(), span); - self.trunc_int32(ty.size_in_bits() as u32, span); - } - ty @ (Type::I16 | Type::I8) => { - self.bxor_imm_u32(imm.as_i64().unwrap() as u64 as u32, span); - self.trunc_int32(ty.size_in_bits() as u32, span); - } - Type::I1 => self.emit(Op::XorImm(imm.as_bool().unwrap()), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: bxor expects integer operands, got {ty}") - } - ty => unimplemented!("bxor for {ty} is not supported"), - } - self.push(ty); - } - - pub fn shl(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 | Type::I64 => self.shl_u64(span), - Type::U32 | Type::I32 => self.shl_u32(span), - ty @ (Type::U16 | Type::I16 | Type::U8 | Type::I8) => { - self.shl_u32(span); - self.trunc_int32(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: shl expects integer operands, got {ty}") - } - ty => unimplemented!("shl for {ty} is not supported"), - } - self.push(ty); - } - - pub fn shl_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(imm.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 | Type::I64 => { - assert!(imm.as_u32().unwrap() < 64, "invalid shift value: must be < 64"); - self.push_immediate(imm, span); - self.shl_u64(span); - } - Type::U32 => self.shl_imm_u32(imm.as_u32().unwrap(), span), - Type::I32 => self.shl_imm_u32(imm.as_u32().unwrap(), span), - ty @ (Type::U16 | Type::I16 | Type::U8 | Type::I8) => { - self.shl_imm_u32(imm.as_u32().unwrap(), span); - self.trunc_int32(ty.size_in_bits() as u32, span); - } - ty if !ty.is_integer() => { - panic!("invalid binary operand: shl expects integer operands, got {ty}") - } - ty => unimplemented!("shl for {ty} is not supported"), - } - self.push(ty); - } - - pub fn shr(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 => self.shr_u64(span), - Type::I64 => self.shr_i64(span), - Type::U32 | Type::U16 | Type::U8 => self.shr_u32(span), - Type::I32 => self.shr_i32(span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: shr expects integer operands, got {ty}") - } - ty => unimplemented!("shr for {ty} is not supported"), - } - self.push(ty); - } - - pub fn shr_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(imm.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 => { - let shift = imm.as_u32().unwrap(); - assert!(shift < 64, "invalid shift value: must be < 64, got {shift}"); - self.push_immediate(imm, span); - self.shr_u64(span); - } - Type::I64 => self.shr_imm_i64(imm.as_u32().unwrap(), span), - Type::U32 | Type::U16 | Type::U8 => self.shr_imm_u32(imm.as_u32().unwrap(), span), - Type::I32 => self.shr_imm_i32(imm.as_u32().unwrap(), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: shr expects integer operands, got {ty}") - } - ty => unimplemented!("shr for {ty} is not supported"), - } - self.push(ty); - } - - pub fn rotl(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 | Type::I64 => self.rotl_u64(span), - Type::U32 | Type::I32 => self.rotl_u32(span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: rotl expects integer operands, got {ty}") - } - ty => unimplemented!("rotl for {ty} is not supported"), - } - self.push(ty); - } - - pub fn rotl_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(imm.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 | Type::I64 => { - self.push_immediate(imm, span); - self.rotl_u64(span); - } - Type::U32 | Type::I32 => self.rotl_imm_u32(imm.as_u32().unwrap(), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: rotl expects integer operands, got {ty}") - } - ty => unimplemented!("rotl for {ty} is not supported"), - } - self.push(ty); - } - - pub fn rotr(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 | Type::I64 => self.rotr_u64(span), - Type::U32 | Type::I32 => self.rotr_u32(span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: rotr expects integer operands, got {ty}") - } - ty => unimplemented!("rotr for {ty} is not supported"), - } - self.push(ty); - } - - pub fn rotr_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(imm.ty(), Type::U32, "expected shift operand to be u32"); - match &ty { - Type::U64 | Type::I64 => { - self.push_immediate(imm, span); - self.rotr_u64(span); - } - Type::U32 | Type::I32 => self.rotr_imm_u32(imm.as_u32().unwrap(), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: rotr expects integer operands, got {ty}") - } - ty => unimplemented!("rotr for {ty} is not supported"), - } - self.push(ty); - } - - pub fn min(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected min operands to be the same type"); - match &ty { - Type::U64 => self.min_u64(span), - Type::I64 => self.min_i64(span), - Type::U32 | Type::U16 | Type::U8 | Type::I1 => self.min_u32(span), - Type::I32 => self.min_i32(span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: min expects integer operands, got {ty}") - } - ty => unimplemented!("min for {ty} is not supported"), - } - self.push(ty); - } - - pub fn min_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected min operands to be the same type"); - match &ty { - Type::U64 => { - self.push_immediate(imm, span); - self.min_u64(span); - } - Type::I64 => self.min_imm_i64(imm.as_i64().unwrap(), span), - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.min_imm_u32(imm.as_u32().unwrap(), span) - } - Type::I32 => self.min_imm_i32(imm.as_i32().unwrap(), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: min expects integer operands, got {ty}") - } - ty => unimplemented!("min for {ty} is not supported"), - } - self.push(ty); - } - - pub fn max(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected max operands to be the same type"); - match &ty { - Type::U64 => self.max_u64(span), - Type::I64 => self.max_i64(span), - Type::U32 | Type::U16 | Type::U8 | Type::I1 => self.max_u32(span), - Type::I32 => self.max_i32(span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: max expects integer operands, got {ty}") - } - ty => unimplemented!("max for {ty} is not supported"), - } - self.push(ty); - } - - pub fn max_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected max operands to be the same type"); - match &ty { - Type::U64 => { - self.push_immediate(imm, span); - self.max_u64(span); - } - Type::I64 => self.max_imm_i64(imm.as_i64().unwrap(), span), - Type::U32 | Type::U16 | Type::U8 | Type::I1 => { - self.max_imm_u32(imm.as_u32().unwrap(), span) - } - Type::I32 => self.max_imm_i32(imm.as_i32().unwrap(), span), - ty if !ty.is_integer() => { - panic!("invalid binary operand: max expects integer operands, got {ty}") - } - ty => unimplemented!("max for {ty} is not supported"), - } - self.push(ty); - } -} diff --git a/codegen/masm/src/codegen/emit/felt.rs b/codegen/masm/src/codegen/emit/felt.rs deleted file mode 100644 index 886bc39e1..000000000 --- a/codegen/masm/src/codegen/emit/felt.rs +++ /dev/null @@ -1,176 +0,0 @@ -use midenc_hir::{Felt, FieldElement, SourceSpan}; - -use super::OpEmitter; -use crate::masm::Op; - -/// The value zero, as a field element -pub const ZERO: Felt = Felt::ZERO; - -/// The value 2^32, as a field element -pub const U32_FIELD_MODULUS: Felt = Felt::new(2u64.pow(32)); - -#[allow(unused)] -impl<'a> OpEmitter<'a> { - /// This operation checks if the field element on top of the stack is zero. - /// - /// This operation does not consume the input, and pushes a boolean value on the stack. - /// - /// # Stack effects - /// - /// `[a, ..] => [a == 0, a, ..]` - #[inline(always)] - pub fn felt_is_zero(&mut self, span: SourceSpan) { - self.emit_all(&[Op::Dup(0), Op::EqImm(ZERO)], span); - } - - /// This operation asserts the field element on top of the stack is zero. - /// - /// This operation does not consume the input. - /// - /// # Stack effects - /// - /// `[a, ..] => [a, ..]` - #[inline(always)] - pub fn assert_felt_is_zero(&mut self, span: SourceSpan) { - self.emit_all(&[Op::Dup(0), Op::Assertz], span); - } - - /// Convert a field element to i128 by zero-extension. - /// - /// This consumes the field element on top of the stack. - /// - /// # Stack effects - /// - /// `[a, ..] => [0, 0, a_hi, a_lo]` - #[inline] - pub fn felt_to_i128(&mut self, span: SourceSpan) { - self.emit_all(&[Op::U32Split, Op::Push2([ZERO, ZERO])], span); - } - - /// Convert a field element to u64 by zero-extension. - /// - /// This consumes the field element on top of the stack. - /// - /// # Stack effects - /// - /// `[a, ..] => [a_hi, a_lo]` - #[inline(always)] - pub fn felt_to_u64(&mut self, span: SourceSpan) { - self.emit(Op::U32Split, span); - } - - /// Convert a field element to i64 by zero-extension. - /// - /// Asserts if the field element is too large to represent as an i64. - /// - /// This consumes the field element on top of the stack. - /// - /// # Stack effects - /// - /// `[a, ..] => [a_hi, a_lo]` - #[inline(always)] - pub fn felt_to_i64(&mut self, span: SourceSpan) { - self.felt_to_u64(span); - } - - /// Convert a field element value to an unsigned N-bit integer, where N <= 32 - /// - /// Conversion will trap if the input value is too large to fit in an unsigned N-bit integer. - pub fn felt_to_uint(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 1, 32); - self.emit_all( - &[ - // Split into u32 limbs - Op::U32Split, - // Assert most significant 32 bits are unused - Op::Assertz, - ], - span, - ); - if n < 32 { - // Convert to N-bit integer - self.int32_to_uint(n, span); - } - } - - /// Convert a field element value to a signed N-bit integer, where N <= 32 - /// - /// Conversion will trap if the input value is too large to fit in a signed N-bit integer. - pub fn felt_to_int(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 1, 32); - self.emit_all( - &[ - // Split into u32 limbs - Op::U32Split, - // Assert most significant 32 bits are unused - Op::Assertz, - ], - span, - ); - // Assert the sign bit isn't set - self.assert_unsigned_int32(span); - if n < 32 { - // Convert to signed N-bit integer - self.int32_to_int(n, span); - } - } - - /// Zero-extend a field element value to N-bits, where N >= 64 - /// - /// N must be a power of two, or this function will panic. - pub fn zext_felt(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 64, 256); - match n { - 64 => self.felt_to_u64(span), - 128 => self.felt_to_i128(span), - n => { - // Convert to u64 and zero-extend - self.felt_to_u64(span); - self.zext_int64(n, span); - } - } - } - - /// Emits code to sign-extend a field element value to an N-bit integer, where N >= 64 - /// - /// Field elements are unsigned, so sign-extension here is indicating that the target - /// integer type is a signed type, so we have one less bit available to use. - /// - /// N must be a power of two, or this function will panic. - pub fn sext_felt(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 64, 256); - match n { - 64 => self.felt_to_i64(span), - 128 => self.felt_to_i128(span), - n => { - // Convert to i64 and sign-extend - self.felt_to_i64(span); - self.sext_int64(n, span); - } - } - } - - /// Truncates a field element on top of the stack to an N-bit integer, where N <= 32. - /// - /// Truncation on field elements is not well-defined, because field elements do not have - /// a specified bitwise representation. To implement semantics equivalent to the other types - /// which _do_ have a specified representation, we first convert the input field element to u32, - /// and then masking out any additional unused bits of the u32 representation. - /// - /// This should produce outputs which are identical to equivalent u64 values, i.e. the same - /// value in both u64 and felt representation will be truncated to the same u32 value. - #[inline] - pub fn trunc_felt(&mut self, n: u32, span: SourceSpan) { - // Apply a field modulus of 2^32, i.e. `a mod 2^32`, converting - // the field element into the u32 range. Miden defines values in - // this range as having a standard unsigned binary representation. - self.emit(Op::U32Cast, span); - self.trunc_int32(n, span); - } - - /// Make `n` copies of the element on top of the stack - #[inline(always)] - pub fn dup_felt(&mut self, count: u8, span: SourceSpan) { - self.emit_n(count as usize, Op::Dup(0), span); - } -} diff --git a/codegen/masm/src/codegen/emit/int32.rs b/codegen/masm/src/codegen/emit/int32.rs deleted file mode 100644 index 3fcf06ff8..000000000 --- a/codegen/masm/src/codegen/emit/int32.rs +++ /dev/null @@ -1,1011 +0,0 @@ -use midenc_hir::{Felt, FieldElement, Overflow, SourceSpan}; - -use super::{felt, OpEmitter}; -use crate::masm::Op; - -pub const SIGN_BIT: u32 = 1 << 31; - -#[allow(unused)] -impl<'a> OpEmitter<'a> { - /// Emits code to apply a constant 32-bit mask, `mask`, to a u32 value on top of the stack. - /// - /// The value on top of the stack IS consumed. - /// - /// NOTE: This function does not validate that the value on top of the stack is - /// a valid u32 - the caller is responsible for such validation. - /// - /// # Stack Effects - /// - /// `[a, ..] => [a & mask, ..]` - #[inline] - pub fn const_mask_u32(&mut self, mask: u32, span: SourceSpan) { - self.emit_all(&[Op::PushU32(mask), Op::U32And], span); - } - - /// Emits code to apply a 32-bit mask, `mask`, to a u32 value, `input`. - /// - /// Both `mask` and `input` are operands on the stack, with `mask` on top. - /// - /// While `mask` is consumed by this operation, `input` IS NOT consumed. - /// - /// NOTE: This function assumes that the caller has validated that both values are valid u32. - /// - /// # Stack Effects - /// - /// `[mask, input, ..] => [input & mask, input]` - #[inline] - pub fn mask_u32(&mut self, span: SourceSpan) { - self.emit_all(&[Op::Dup(1), Op::U32And], span); - } - - /// Emits code to check if all bits of `flags` are set in the u32 value on top of the stack. - /// - /// The value on top of the stack IS NOT consumed. - /// - /// NOTE: This function does not validate that the value on top of the stack is - /// a valid u32 - the caller is responsible for such validation. - /// - /// # Stack Effects - /// - /// `[a, ..] => [a & flags == flags, a]` - #[inline] - pub fn is_const_flag_set_u32(&mut self, flags: u32, span: SourceSpan) { - self.emit(Op::Dup(0), span); - self.const_mask_u32(flags, span); - self.emit(Op::EqImm(Felt::new(flags as u64)), span); - } - - /// Emits code to check if all bits of `mask` are set in `input`. - /// - /// Both `mask` and `input` are operands on the stack, with `mask` on top. - /// - /// While `mask` is consumed by this operation, `input` IS NOT consumed. - /// - /// NOTE: This function assumes that the caller has validated that both values are valid u32. - /// - /// # Stack Effects - /// - /// `[mask, input, ..] => [input & mask == mask, input]` - #[inline] - pub fn is_flag_set_u32(&mut self, span: SourceSpan) { - self.emit_all( - &[ - Op::Dup(1), // [input, mask, input] - Op::Dup(1), // [mask, input, mask, input] - Op::U32And, // [input & mask, mask, input] - Op::Eq, // [input & mask == mask, input] - ], - span, - ); - } - - /// Check if a 32-bit integer value on the operand stack has its sign bit set. - /// - /// The value on top of the stack IS NOT consumed. - /// - /// See `is_const_flag_set` for semantics and stack effects. - #[inline] - pub fn is_signed_int32(&mut self, span: SourceSpan) { - self.is_const_flag_set_u32(SIGN_BIT, span); - } - - /// Check if a 32-bit integer value on the operand stack does not have its sign bit set. - /// - /// The value on top of the stack IS NOT consumed. - #[inline(always)] - pub fn is_unsigned_int32(&mut self, span: SourceSpan) { - self.is_signed_int32(span); - self.emit(Op::Not, span); - } - - /// Emits code to assert that a 32-bit value on the operand stack has the i32 sign bit set. - /// - /// The value on top of the stack IS NOT consumed. - /// - /// See `is_signed` for semantics and stack effects of the signedness check. - #[inline] - pub fn assert_signed_int32(&mut self, span: SourceSpan) { - self.is_signed_int32(span); - self.emit(Op::Assert, span); - } - - /// Emits code to assert that a 32-bit value on the operand stack does not have the i32 sign bit - /// set. - /// - /// The value on top of the stack IS NOT consumed. - /// - /// See `is_signed` for semantics and stack effects of the signedness check. - #[inline] - pub fn assert_unsigned_int32(&mut self, span: SourceSpan) { - self.is_signed_int32(span); - self.emit(Op::Assertz, span); - } - - /// Assert that the 32-bit value on the stack is a valid i32 value - pub fn assert_i32(&mut self, span: SourceSpan) { - // Copy the value on top of the stack - self.emit(Op::Dup(0), span); - // Assert the value does not overflow i32::MAX or underflow i32::MIN - // This can be checked by validating that when interpreted as a u32, - // the value is <= i32::MIN, which is 1 more than i32::MAX. - self.push_i32(i32::MIN, span); - self.emit(Op::U32Lte, span); - self.emit(Op::Assert, span); - } - - /// Emits code to assert that a 32-bit value on the operand stack is equal to the given constant - /// value. - /// - /// The value on top of the stack IS NOT consumed. - /// - /// # Stack Effects - /// - /// `[input, ..] => [input, ..]` - #[inline] - pub fn assert_eq_imm_u32(&mut self, value: u32, span: SourceSpan) { - self.emit_all(&[Op::Dup(0), Op::EqImm(Felt::new(value as u64)), Op::Assert], span); - } - - /// Emits code to assert that two 32-bit values, `expected` and `value`, on top of the operand - /// stack are equal, without consuming `value`. - /// - /// The `expected` operand is consumed, while the `value` operand IS NOT. - /// - /// # Stack Effects - /// - /// `[expected, input, ..] => [input, ..]` - #[inline] - pub fn assert_eq_u32(&mut self, span: SourceSpan) { - self.emit_all(&[Op::Dup(1), Op::AssertEq], span); - } - - /// Emits code to select a constant u32 value, using the `n`th value on the operand - /// stack as the condition for the select. - /// - /// This function pushes `b` then `a` on the stack, moves the `n`th value to the top - /// of the stack, and then executes a conditional drop. This has the effect of consuming - /// all three operands, placing only a single value back on the operand stack; the - /// selected value, either `a` or `b`. Use `dup_select` if you would rather copy - /// the conditional rather than move it. - pub fn mov_select_int32(&mut self, a: u32, b: u32, n: u8, span: SourceSpan) { - assert_valid_stack_index!(n); - // If the value we need will get pushed off the end of the stack, - // bring it closer first, and adjust our `n` accordingly - if n > 13 { - self.emit(Op::Movup(n), span); - self.select_int32(a, b, span); - } else { - self.emit_all(&[Op::PushU32(b), Op::PushU32(a), Op::Movup(n + 2), Op::Cdrop], span); - } - } - - /// Same semantics as `mov_select`, but copies the `n`th value on the operand - /// stack rather than moving it. - /// - /// # Stack Effects - /// - /// Moves `c` to the top of the stack, where `c` is the `n`th value on the operand stack, - /// then applies `select`. - pub fn dup_select_int32(&mut self, a: u32, b: u32, n: u8, span: SourceSpan) { - assert_valid_stack_index!(n); - // If the value we need will get pushed off the end of the stack, - // bring it closer first, and adjust our `n` accordingly - if n > 13 { - self.emit(Op::Dup(n), span); - self.select_int32(a, b, span); - } else { - self.emit_all(&[Op::PushU32(b), Op::PushU32(a), Op::Dup(n + 2), Op::Cdrop], span); - } - } - - /// Emits code to select between two u32 constants, given a boolean value on top of the stack - /// - /// # Stack Effects - /// - /// `[c, a, b, ..] => [d, ..] where d is c == 1 ? a : b` - pub fn select_int32(&mut self, a: u32, b: u32, span: SourceSpan) { - self.emit_all(&[Op::PushU32(b), Op::PushU32(a), Op::Movup(2), Op::Cdrop], span); - } - - /// Convert an i32/u32 value on the stack to a signed N-bit integer value - /// - /// Execution traps if the value cannot fit in the signed N-bit range. - pub fn int32_to_int(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 1, 32); - // Push is_signed on the stack - self.is_signed_int32(span); - // Pop the is_signed flag, and replace it with a selected mask - // for the upper reserved bits of the N-bit range - let reserved = 32 - n; - // Add one bit to the reserved bits to represent the sign bit, - // and subtract it from the shift to account for the loss - let mask = (2u32.pow(reserved + 1) - 1) << (n - 1); - self.select_int32(mask, 0, span); - self.emit_all( - &[ - // Copy the input to the top of the stack for the masking op - Op::Dup(1), - // Copy the mask value for the masking op - Op::Dup(1), - // Apply the mask - Op::U32And, - // Assert that the masked bits and the mask are equal - Op::AssertEq, - ], - span, - ); - } - - /// Convert an i32/u32 value on the stack to a signed N-bit integer value - /// - /// Places a boolean on top of the stack indicating if the conversion was successful - pub fn try_int32_to_int(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 1, 32); - // Push is_signed on the stack - self.is_signed_int32(span); - // Pop the is_signed flag, and replace it with a selected mask - // for the upper reserved bits of the N-bit range - let reserved = 32 - n; - // Add one bit to the reserved bits to represent the sign bit, - // and subtract it from the shift to account for the loss - let mask = (2u32.pow(reserved + 1) - 1) << (n - 1); - self.select_int32(mask, 0, span); - self.emit_all( - &[ - // Copy the input to the top of the stack for the masking op - Op::Dup(1), - // Copy the mask value for the masking op - Op::Dup(1), - // Apply the mask - Op::U32And, - // Assert that the masked bits and the mask are equal - Op::Eq, - ], - span, - ); - } - - /// Convert an i32/u32 value on the stack to an unsigned N-bit integer value - /// - /// Execution traps if the value cannot fit in the unsigned N-bit range. - pub fn int32_to_uint(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 1, 32); - // Mask the value and ensure that the unused bits above the N-bit range are 0 - let reserved = 32 - n; - let mask = (2u32.pow(reserved) - 1) << n; - self.emit_all( - &[ - // Copy the input - Op::Dup(1), - // Apply the mask - Op::PushU32(mask), - Op::U32And, - // Assert the masked value is all 0s - Op::Assertz, - ], - span, - ); - } - - /// Convert an i32/u32 value on the stack to an unsigned N-bit integer value - /// - /// Places a boolean on top of the stack indicating if the conversion was successful - pub fn try_int32_to_uint(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 1, 32); - // Mask the value and ensure that the unused bits above the N-bit range are 0 - let reserved = 32 - n; - let mask = (2u32.pow(reserved) - 1) << n; - self.emit_all( - &[ - // Copy the input - Op::Dup(1), - // Apply the mask - Op::PushU32(mask), - Op::U32And, - // Assert the masked value is all 0s - Op::EqImm(Felt::ZERO), - ], - span, - ); - } - - /// Emit code to truncate a 32-bit value on top of the operand stack, to N bits, where N is <= - /// 32 - /// - /// This consumes the input value, and leaves an N-bit value on the stack. - /// - /// NOTE: This function does not validate the input as < 2^32, the caller is expected to - /// validate this. - #[inline] - pub fn trunc_int32(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 1, 32); - // Mask out any bits between N and 32. - let unused_bits = 32 - n; - if unused_bits > 0 { - self.const_mask_u32(1 << ((32 - unused_bits) - 1), span); - } - } - - /// Emit code to zero-extend a 32-bit value to N bits, where N <= 128 - /// - /// This operation assumes all N-bit integers greater than 32 bits use 32-bit limbs. - /// - /// NOTE: This operation does not check the sign bit, it is assumed the value is - /// either an unsigned integer, or a non-negative signed integer. - #[inline] - pub fn zext_int32(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 32); - // Only values larger than 32 bits require padding - if n <= 32 { - return; - } - let num_bits = n % 32; - let num_elements = (n / 32) + (num_bits > 0) as u32; - let needed = num_elements - 1; - self.emit_n(needed as usize, Op::PushU32(0), span); - } - - /// Emit code to sign-extend a signed 32-bit value to N bits, where N <= 128 - /// - /// This operation assumes all N-bit integers greater than 32 bits use 32-bit limbs. - /// - /// NOTE: This operation treats the most significant bit as the sign bit, it is - /// assumed the value is an i32, it is up to the caller to ensure this is a valid - /// operation to perform on the input. - #[inline] - pub fn sext_int32(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 32); - self.is_signed_int32(span); - self.select_int32(u32::MAX, 0, span); - self.pad_int32(n, span); - } - - /// Emit code to pad a 32-bit value out to N bits, where N >= 32. - /// - /// N must be a power of two. - /// - /// The padding value is expected on top of the stack, followed by the 32-bit value to pad. - /// - /// This operation assumes all N-bit integers greater than 32 bits use 32-bit limbs. - /// - /// The padding value will be duplicated for each additional 32-bit limb needed to - /// ensure that there are enough limbs on the stack to represent an N-bit integer. - #[inline] - pub fn pad_int32(&mut self, n: u32, span: SourceSpan) { - assert_valid_integer_size!(n, 32); - // We need one element for each 32-bit limb - let num_elements = n / 32; - // We already have the input u32, as well as the pad value, so deduct - // those elements from the number needed. - let needed = num_elements.saturating_sub(2); - self.emit_n(needed as usize, Op::Dup(0), span); - } - - /// Push a u32 value on the stack - #[inline(always)] - pub fn push_u32(&mut self, i: u32, span: SourceSpan) { - self.emit(Op::PushU32(i), span); - } - - /// Push a i32 value on the stack - #[inline(always)] - pub fn push_i32(&mut self, i: i32, span: SourceSpan) { - self.emit(Op::PushU32(i as u32), span); - } - - /// This is the inverse operation of the Miden VM `u32split` instruction. - /// - /// This takes two 32-bit limbs, and produces a felt. - /// - /// NOTE: It is expected that the caller has validated that the limbs are valid u32 values. - pub fn u32unsplit(&mut self, span: SourceSpan) { - self.emit_all(&[Op::MulImm(felt::U32_FIELD_MODULUS), Op::Add], span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a + b`. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - #[inline(always)] - pub fn add_u32(&mut self, overflow: Overflow, span: SourceSpan) { - self.emit( - match overflow { - Overflow::Unchecked => Op::Add, - Overflow::Checked => return self.emit_all(&[Op::Add, Op::U32Assert], span), - Overflow::Wrapping => Op::U32WrappingAdd, - Overflow::Overflowing => Op::U32OverflowingAdd, - }, - span, - ); - } - - /// Pops two i32 values off the stack, `b` and `a`, and performs `a + b`. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - #[inline(always)] - pub fn add_i32(&mut self, overflow: Overflow, span: SourceSpan) { - self.emit( - match overflow { - Overflow::Unchecked | Overflow::Wrapping => Op::U32WrappingAdd, - Overflow::Checked => Op::Exec("intrinsics::i32::checked_add".parse().unwrap()), - Overflow::Overflowing => { - Op::Exec("intrinsics::i32::overflowing_add".parse().unwrap()) - } - }, - span, - ) - } - - /// Pops a u32 value off the stack, `a`, and performs `a + `. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - /// - /// Adding zero is a no-op. - #[inline] - pub fn add_imm_u32(&mut self, imm: u32, overflow: Overflow, span: SourceSpan) { - if imm == 0 { - return; - } - self.emit( - match overflow { - Overflow::Unchecked if imm == 1 => Op::Incr, - Overflow::Unchecked => Op::AddImm(Felt::new(imm as u64)), - Overflow::Checked => { - return self - .emit_all(&[Op::AddImm(Felt::new(imm as u64)), Op::U32Assert], span); - } - Overflow::Wrapping => Op::U32WrappingAddImm(imm), - Overflow::Overflowing => Op::U32OverflowingAddImm(imm), - }, - span, - ); - } - - /// Pops a i32 value off the stack, `a`, and performs `a + `. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - /// - /// Adding zero is a no-op. - #[inline] - pub fn add_imm_i32(&mut self, imm: i32, overflow: Overflow, span: SourceSpan) { - if imm == 0 { - return; - } - match overflow { - Overflow::Unchecked | Overflow::Wrapping => { - self.add_imm_u32(imm as u32, overflow, span) - } - Overflow::Checked => { - self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::checked_add".parse().unwrap()), - ], - span, - ); - } - Overflow::Overflowing => self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::overflowing_add".parse().unwrap()), - ], - span, - ), - } - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a - b`. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - pub fn sub_u32(&mut self, overflow: Overflow, span: SourceSpan) { - self.emit( - match overflow { - Overflow::Unchecked => Op::Sub, - Overflow::Checked => { - return self.emit_all(&[Op::Sub, Op::U32Assert], span); - } - Overflow::Wrapping => Op::U32WrappingSub, - Overflow::Overflowing => Op::U32OverflowingSub, - }, - span, - ); - } - - /// Pops two i32 values off the stack, `b` and `a`, and performs `a - b`. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - pub fn sub_i32(&mut self, overflow: Overflow, span: SourceSpan) { - match overflow { - Overflow::Unchecked | Overflow::Wrapping => self.sub_u32(overflow, span), - Overflow::Checked => { - self.emit(Op::Exec("intrinsics::i32::checked_sub".parse().unwrap()), span) - } - Overflow::Overflowing => { - self.emit(Op::Exec("intrinsics::i32::overflowing_sub".parse().unwrap()), span) - } - } - } - - /// Pops a u32 value off the stack, `a`, and performs `a - `. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - /// - /// Subtracting zero is a no-op. - #[inline] - pub fn sub_imm_u32(&mut self, imm: u32, overflow: Overflow, span: SourceSpan) { - if imm == 0 { - return; - } - self.emit( - match overflow { - Overflow::Unchecked => Op::SubImm(Felt::new(imm as u64)), - Overflow::Checked => { - return self.emit_all(&[Op::SubImm(Felt::new(imm as u64)), Op::U32Assert], span) - } - Overflow::Wrapping => Op::U32WrappingSubImm(imm), - Overflow::Overflowing => Op::U32OverflowingSubImm(imm), - }, - span, - ); - } - - /// Pops a i32 value off the stack, `a`, and performs `a - `. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - /// - /// Subtracting zero is a no-op. - #[inline] - pub fn sub_imm_i32(&mut self, imm: i32, overflow: Overflow, span: SourceSpan) { - if imm == 0 { - return; - } - match overflow { - Overflow::Unchecked | Overflow::Wrapping => { - self.sub_imm_u32(imm as u32, overflow, span) - } - Overflow::Checked => self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::checked_sub".parse().unwrap()), - ], - span, - ), - Overflow::Overflowing => self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::overflowing_sub".parse().unwrap()), - ], - span, - ), - } - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a * b`. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - pub fn mul_u32(&mut self, overflow: Overflow, span: SourceSpan) { - self.emit( - match overflow { - Overflow::Unchecked => Op::Mul, - Overflow::Checked => return self.emit_all(&[Op::Mul, Op::U32Assert], span), - Overflow::Wrapping => Op::U32WrappingMul, - Overflow::Overflowing => Op::U32OverflowingMul, - }, - span, - ); - } - - /// Pops two i32 values off the stack, `b` and `a`, and performs `a * b`. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - pub fn mul_i32(&mut self, overflow: Overflow, span: SourceSpan) { - match overflow { - Overflow::Unchecked | Overflow::Wrapping => { - self.emit(Op::Exec("intrinsics::i32::wrapping_mul".parse().unwrap()), span) - } - Overflow::Checked => { - self.emit(Op::Exec("intrinsics::i32::checked_mul".parse().unwrap()), span) - } - Overflow::Overflowing => { - self.emit(Op::Exec("intrinsics::i32::overflowing_mul".parse().unwrap()), span) - } - } - } - - /// Pops a u32 value off the stack, `a`, and performs `a * `. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - /// - /// Multiplying by zero is transformed into a sequence which drops the input value - /// and pushes a constant zero on the stack. - /// - /// Multiplying by one is a no-op. - #[inline] - pub fn mul_imm_u32(&mut self, imm: u32, overflow: Overflow, span: SourceSpan) { - match imm { - 0 => { - self.emit_all(&[Op::Drop, Op::PushU32(0)], span); - } - 1 => (), - imm => { - self.emit( - match overflow { - Overflow::Unchecked => Op::MulImm(Felt::new(imm as u64)), - Overflow::Checked => { - return self.emit_all( - &[Op::MulImm(Felt::new(imm as u64)), Op::U32Assert], - span, - ) - } - Overflow::Wrapping => Op::U32WrappingMulImm(imm), - Overflow::Overflowing => Op::U32OverflowingMulImm(imm), - }, - span, - ); - } - } - } - - /// Pops a i32 value off the stack, `a`, and performs `a * `. - /// - /// See the [Overflow] type for how overflow semantics can change the operation. - /// - /// Multiplying by zero is transformed into a sequence which drops the input value - /// and pushes a constant zero on the stack. - /// - /// Multiplying by one is a no-op. - #[inline] - pub fn mul_imm_i32(&mut self, imm: i32, overflow: Overflow, span: SourceSpan) { - match imm { - 0 => { - self.emit_all(&[Op::Drop, Op::PushU32(0)], span); - } - 1 => (), - imm => match overflow { - Overflow::Unchecked | Overflow::Wrapping => self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::wrapping_mul".parse().unwrap()), - ], - span, - ), - Overflow::Checked => self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::checked_mul".parse().unwrap()), - ], - span, - ), - Overflow::Overflowing => self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::overflowing_mul".parse().unwrap()), - ], - span, - ), - }, - } - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a / b`. - /// - /// This operation is checked, so if the operands or result are not valid u32, execution traps. - pub fn checked_div_u32(&mut self, span: SourceSpan) { - self.emit_all(&[Op::U32Div, Op::U32Assert], span); - } - - /// Pops two i32 values off the stack, `b` and `a`, and performs `a / b`. - /// - /// This operation is checked, so if the operands or result are not valid i32, execution traps. - pub fn checked_div_i32(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i32::checked_div".parse().unwrap()), span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a / `. - /// - /// This function will panic if the divisor is zero. - /// - /// This operation is checked, so if the operand or result are not valid u32, execution traps. - pub fn checked_div_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert_ne!(imm, 0, "division by zero is not allowed"); - self.emit_all(&[Op::U32DivImm(imm), Op::U32Assert], span); - } - - /// Pops a i32 value off the stack, `a`, and performs `a / `. - /// - /// This function will panic if the divisor is zero. - /// - /// This operation is checked, so if the operand or result are not valid i32, execution traps. - pub fn checked_div_imm_i32(&mut self, imm: i32, span: SourceSpan) { - assert_ne!(imm, 0, "division by zero is not allowed"); - self.emit_all( - &[ - Op::PushU32(imm as u32), - Op::Exec("intrinsics::i32::checked_div".parse().unwrap()), - ], - span, - ); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a / b`. - /// - /// This operation is unchecked, so the result is not guaranteed to be a valid u32 - pub fn unchecked_div_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Div, span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a / `. - /// - /// This function will panic if the divisor is zero. - pub fn unchecked_div_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert_ne!(imm, 0, "division by zero is not allowed"); - self.emit(Op::U32DivImm(imm), span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a % b`. - /// - /// This operation is checked, so if the operands or result are not valid u32, execution traps. - pub fn checked_mod_u32(&mut self, span: SourceSpan) { - self.emit_all(&[Op::U32Mod, Op::U32Assert], span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a % `. - /// - /// This function will panic if the divisor is zero. - /// - /// This operation is checked, so if the operand or result are not valid u32, execution traps. - pub fn checked_mod_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert_ne!(imm, 0, "division by zero is not allowed"); - self.emit_all(&[Op::U32ModImm(imm), Op::U32Assert], span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a % b`. - /// - /// This operation is unchecked, so the result is not guaranteed to be a valid u32 - pub fn unchecked_mod_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Mod, span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a % `. - /// - /// This function will panic if the divisor is zero. - pub fn unchecked_mod_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert_ne!(imm, 0, "division by zero is not allowed"); - self.emit(Op::U32ModImm(imm), span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the - /// stack. - /// - /// This operation is checked, so if the operands or result are not valid u32, execution traps. - pub fn checked_divmod_u32(&mut self, span: SourceSpan) { - self.emit_all(&[Op::U32DivMod, Op::U32Assert], span); - } - - /// Pops a u32 value off the stack, `a`, and pushes `a / `, then `a % ` on the stack. - /// - /// This operation is checked, so if the operands or result are not valid u32, execution traps. - pub fn checked_divmod_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert_ne!(imm, 0, "division by zero is not allowed"); - self.emit_all(&[Op::U32DivModImm(imm), Op::U32Assert], span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the - /// stack. - /// - /// This operation is unchecked, so the result is not guaranteed to be a valid u32 - pub fn unchecked_divmod_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32DivMod, span); - } - - /// Pops a u32 value off the stack, `a`, and pushes `a / `, then `a % ` on the stack. - /// - /// This operation is unchecked, so the result is not guaranteed to be a valid u32 - pub fn unchecked_divmod_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert_ne!(imm, 0, "division by zero is not allowed"); - self.emit(Op::U32DivModImm(imm), span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a & b` - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn band_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32And, span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a & ` - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn band_imm_u32(&mut self, imm: u32, span: SourceSpan) { - self.emit_all(&[Op::PushU32(imm), Op::U32And], span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a | b` - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn bor_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Or, span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a | ` - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn bor_imm_u32(&mut self, imm: u32, span: SourceSpan) { - self.emit_all(&[Op::PushU32(imm), Op::U32Or], span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a ^ b` - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn bxor_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Xor, span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a ^ ` - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn bxor_imm_u32(&mut self, imm: u32, span: SourceSpan) { - self.emit_all(&[Op::PushU32(imm), Op::U32Xor], span); - } - - /// Pops a u32 value off the stack, `a`, and performs `!a` - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn bnot_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32WrappingSubImm(-1i32 as u32), span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a << b` - /// - /// Execution traps if `b` > 31. - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn shl_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Shl, span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a << ` - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn shl_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert!(imm < 32, "invalid shift value: must be < 32, got {imm}"); - self.emit(Op::U32ShlImm(imm), span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and performs `a >> b` - /// - /// Execution traps if `b` > 31. - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn shr_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Shr, span); - } - - /// Pops two i32 values off the stack, `b` and `a`, and performs `a >> b` - /// - /// Execution traps if `b` > 31. - /// - /// This operation is checked, if the operands or result are not valid i32, execution traps. - pub fn shr_i32(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i32::checked_shr".parse().unwrap()), span); - } - - /// Pops a u32 value off the stack, `a`, and performs `a >> ` - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn shr_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert!(imm < 32, "invalid shift value: must be < 32, got {imm}"); - self.emit(Op::U32ShrImm(imm), span); - } - - /// Pops a i32 value off the stack, `a`, and performs `a >> ` - /// - /// This operation is checked, if the operand or result are not valid i32, execution traps. - pub fn shr_imm_i32(&mut self, imm: u32, span: SourceSpan) { - assert!(imm < 32, "invalid shift value: must be < 32, got {imm}"); - self.emit_all( - &[Op::PushU32(imm), Op::Exec("intrinsics::i32::checked_shr".parse().unwrap())], - span, - ); - } - - /// Pops two u32 values off the stack, `b` and `a`, and rotates the bits of `a` left by `b` bits - /// - /// Execution traps if `b` > 31. - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn rotl_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Rotl, span); - } - - /// Pops a u32 value off the stack, `a`, and rotates the bits of `a` left by `imm` bits - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn rotl_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert!(imm < 32, "invalid rotation value: must be < 32, got {imm}"); - self.emit(Op::U32RotlImm(imm), span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and rotates the bits of `a` right by `b` - /// bits - /// - /// Execution traps if `b` > 31. - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn rotr_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Rotr, span); - } - - /// Pops a u32 value off the stack, `a`, and rotates the bits of `a` right by `imm` bits - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn rotr_imm_u32(&mut self, imm: u32, span: SourceSpan) { - assert!(imm < 32, "invalid rotation value: must be < 32, got {imm}"); - self.emit(Op::U32RotrImm(imm), span); - } - - /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the - /// stack - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn min_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Min, span); - } - - /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the - /// stack - /// - /// This operation is checked, if the operands or result are not valid i32, execution traps. - pub fn min_i32(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i32::min".parse().unwrap()), span); - } - - /// Pops a u32 value off the stack, `a`, and puts the result of `min(a, imm)` on the stack - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn min_imm_u32(&mut self, imm: u32, span: SourceSpan) { - self.emit_all(&[Op::PushU32(imm), Op::U32Min], span); - } - - /// Pops a i32 value off the stack, `a`, and puts the result of `min(a, imm)` on the stack - /// - /// This operation is checked, if the operand or result are not valid i32, execution traps. - pub fn min_imm_i32(&mut self, imm: i32, span: SourceSpan) { - self.emit_all( - &[Op::PushU32(imm as u32), Op::Exec("intrinsics::i32::min".parse().unwrap())], - span, - ); - } - - /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the - /// stack - /// - /// This operation is checked, if the operands or result are not valid u32, execution traps. - pub fn max_u32(&mut self, span: SourceSpan) { - self.emit(Op::U32Max, span); - } - - /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the - /// stack - /// - /// This operation is checked, if the operands or result are not valid i32, execution traps. - pub fn max_i32(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i32::max".parse().unwrap()), span); - } - - /// Pops a u32 value off the stack, `a`, and puts the result of `max(a, imm)` on the stack - /// - /// This operation is checked, if the operand or result are not valid u32, execution traps. - pub fn max_imm_u32(&mut self, imm: u32, span: SourceSpan) { - self.emit_all(&[Op::PushU32(imm), Op::U32Max], span); - } - - /// Pops a i32 value off the stack, `a`, and puts the result of `max(a, imm)` on the stack - /// - /// This operation is checked, if the operand or result are not valid i32, execution traps. - pub fn max_imm_i32(&mut self, imm: i32, span: SourceSpan) { - self.emit_all( - &[Op::PushU32(imm as u32), Op::Exec("intrinsics::i32::max".parse().unwrap())], - span, - ); - } -} diff --git a/codegen/masm/src/codegen/emit/mem.rs b/codegen/masm/src/codegen/emit/mem.rs deleted file mode 100644 index 9a26b945b..000000000 --- a/codegen/masm/src/codegen/emit/mem.rs +++ /dev/null @@ -1,1636 +0,0 @@ -use midenc_hir::{self as hir, Felt, FieldElement, SourceSpan, StructType, Type}; - -use super::OpEmitter; -use crate::masm::{NativePtr, Op}; - -/// Allocation -impl<'a> OpEmitter<'a> { - /// Allocate a procedure-local memory slot of sufficient size to store a value - /// indicated by the given pointer type, i.e the pointee type dictates the - /// amount of memory allocated. - /// - /// The address of that slot is placed on the operand stack. - pub fn alloca(&mut self, ptr: &Type, span: SourceSpan) { - match ptr { - Type::Ptr(pointee) => { - let local = self.function.alloc_local(pointee.as_ref().clone()); - self.emit(Op::LocAddr(local), span); - self.push(ptr.clone()); - } - ty => panic!("expected a pointer type, got {ty}"), - } - } - - /// Return the base address of the heap - #[allow(unused)] - pub fn heap_base(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::mem::heap_base".parse().unwrap()), span); - self.push(Type::Ptr(Box::new(Type::U8))); - } - - /// Return the address of the top of the heap - #[allow(unused)] - pub fn heap_top(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::mem::heap_top".parse().unwrap()), span); - self.push(Type::Ptr(Box::new(Type::U8))); - } - - /// Grow the heap (from the perspective of Wasm programs) by N pages, returning the previous - /// size of the heap (in pages) if successful, or -1 if the heap could not be grown. - pub fn mem_grow(&mut self, span: SourceSpan) { - let _num_pages = self.stack.pop().expect("operand stack is empty"); - self.emit(Op::Exec("intrinsics::mem::memory_grow".parse().unwrap()), span); - self.push(Type::I32); - } - - /// Returns the size (in pages) of the heap (from the perspective of Wasm programs) - pub fn mem_size(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::mem::memory_size".parse().unwrap()), span); - self.push(Type::U32); - } -} - -/// Loads -impl<'a> OpEmitter<'a> { - /// Load a value corresponding to the type of the given local, from the memory allocated for - /// that local. - /// - /// Internally, this pushes the address of the local on the stack, then delegates to - /// [OpEmitter::load] - pub fn load_local(&mut self, local: hir::LocalId, span: SourceSpan) { - let ty = self.function.local(local).ty.clone(); - self.emit(Op::LocAddr(local), span); - self.push(Type::Ptr(Box::new(ty.clone()))); - self.load(ty, span) - } - - /// Load a value corresponding to the pointee type of a pointer operand on the stack. - /// - /// The type of the pointer determines what address space the pointer value represents; - /// either the Miden-native address space (word-addressable), or the IR's byte-addressable - /// address space. - pub fn load(&mut self, ty: Type, span: SourceSpan) { - let ptr = self.stack.pop().expect("operand stack is empty"); - match ptr.ty() { - Type::Ptr(_) => { - // Convert the pointer to a native pointer representation - self.emit_native_ptr(span); - match &ty { - Type::I128 => self.load_quad_word(None, span), - Type::I64 | Type::U64 => self.load_double_word(None, span), - Type::Felt => self.load_felt(None, span), - Type::I32 | Type::U32 => self.load_word(None, span), - ty @ (Type::I16 | Type::U16 | Type::U8 | Type::I8 | Type::I1) => { - self.load_word(None, span); - self.trunc_int32(ty.size_in_bits() as u32, span); - } - ty => todo!("support for loading {ty} is not yet implemented"), - } - self.push(ty); - } - ty if !ty.is_pointer() => { - panic!("invalid operand to load: expected pointer, got {ty}") - } - ty => unimplemented!("load support for pointers of type {ty} is not implemented"), - } - } - - /// Load a value of type `ty` from `addr`. - /// - /// NOTE: The address represented by `addr` is in the IR's byte-addressable address space. - pub fn load_imm(&mut self, addr: u32, ty: Type, span: SourceSpan) { - let ptr = NativePtr::from_ptr(addr); - match &ty { - Type::I128 => self.load_quad_word(Some(ptr), span), - Type::I64 | Type::U64 => self.load_double_word(Some(ptr), span), - Type::Felt => self.load_felt(Some(ptr), span), - Type::I32 | Type::U32 => self.load_word(Some(ptr), span), - Type::I16 | Type::U16 | Type::U8 | Type::I8 | Type::I1 => { - self.load_word(Some(ptr), span); - self.trunc_int32(ty.size_in_bits() as u32, span); - } - ty => todo!("support for loading {ty} is not yet implemented"), - } - self.push(ty); - } - - /// Emit a sequence of instructions to translate a raw pointer value to - /// a native pointer value, as a triple of `(waddr, index, offset)`, in - /// that order on the stack. - /// - /// Instructions which must act on a pointer will expect the stack to have - /// these values in that order so that they can perform any necessary - /// re-alignment. - fn emit_native_ptr(&mut self, span: SourceSpan) { - self.emit_all( - &[ - // Copy the address - // - // [addr, addr] - Op::Dup(0), - // Obtain the absolute offset - // - // [abs_offset, addr] - Op::U32ModImm(16), - // Obtain the byte offset - // - // [abs_offset, abs_offset, addr] - Op::Dup(0), - // [offset, abs_offset, addr] - Op::U32ModImm(4), - // Obtain the element index - // - // [abs_offset, offset, addr] - Op::Swap(1), - // [index, byte_offset, addr] - Op::U32DivImm(4), - // Translate the address to Miden's address space - // - // [addr, index, offset] - Op::Movup(2), - // [waddr, index, offset] - Op::U32DivImm(16), - ], - span, - ); - } - - /// Load a field element from a naturally aligned address, either immediate or dynamic - /// - /// A native pointer triplet is expected on the stack if an immediate is not given. - fn load_felt(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.load_felt_imm(imm, span); - } - - self.emit(Op::Exec("intrinsics::mem::load_felt".parse().unwrap()), span); - } - - fn load_felt_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - assert!(ptr.is_element_aligned(), "felt values must be naturally aligned"); - match ptr.index { - 0 => self.emit(Op::MemLoadImm(ptr.waddr), span), - 1 => { - self.emit_all( - &[ - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - Op::Drop, - Op::Drop, - Op::Swap(1), - Op::Drop, - ], - span, - ); - } - 2 => { - self.emit_all( - &[ - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - Op::Drop, - Op::Movdn(2), - Op::Drop, - Op::Drop, - ], - span, - ); - } - 3 => { - self.emit_all( - &[ - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - Op::Movdn(3), - Op::Drop, - Op::Drop, - Op::Drop, - ], - span, - ); - } - _ => unreachable!(), - } - } - - /// Loads a single 32-bit machine word, i.e. a single field element, not the Miden notion of a - /// word - /// - /// Expects a native pointer triplet on the stack if an immediate address is not given. - fn load_word(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.load_word_imm(imm, span); - } - - self.emit(Op::Exec("intrinsics::mem::load_sw".parse().unwrap()), span); - } - - /// Loads a single 32-bit machine word from the given immediate address. - fn load_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - let is_aligned = ptr.is_element_aligned(); - let rshift = 32 - ptr.offset as u32; - match ptr.index { - 0 if is_aligned => self.emit(Op::MemLoadImm(ptr.waddr), span), - 0 => { - self.emit_all( - &[ - // Load a quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - Op::Drop, - Op::Drop, - // shift low bits - Op::U32ShrImm(rshift), - // shift high bits left by the offset - Op::Swap(1), - Op::U32ShlImm(ptr.offset as u32), - // OR the high and low bits together - Op::U32Or, - ], - span, - ); - } - 1 if is_aligned => self.emit_all( - &[ - // Load a quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop w3, w2 - Op::Drop, - Op::Drop, - // Drop w1 - Op::Swap(1), - Op::Drop, - ], - span, - ), - 1 => { - self.emit_all( - &[ - // Load a quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop unused elements - Op::Drop, - Op::Movup(2), - Op::Drop, - // Shift the low bits - Op::U32ShrImm(rshift), - // Shift the high bits - Op::Swap(1), - Op::U32ShlImm(ptr.offset as u32), - // OR the high and low bits together - Op::U32Or, - ], - span, - ); - } - 2 if is_aligned => self.emit_all( - &[ - // Load a quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop w3 - Op::Drop, - // Move w2 to bottom - Op::Movdn(2), - // Drop w1, w0 - Op::Drop, - Op::Drop, - ], - span, - ), - 2 => { - self.emit_all( - &[ - // Load a quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop unused elements - Op::Movup(3), - Op::Movup(3), - Op::Drop, - Op::Drop, - // Shift low bits - Op::U32ShrImm(rshift), - // Shift high bits - Op::U32ShlImm(ptr.offset as u32), - // OR the high and low bits together - Op::U32Or, - ], - span, - ); - } - 3 if is_aligned => self.emit_all( - &[ - // Load a quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Move w3 to bottom - Op::Movdn(3), - // Drop the three unused elements - Op::Drop, - Op::Drop, - Op::Drop, - ], - span, - ), - 3 => { - self.emit_all( - &[ - // Load the quad-word containing the low bits - Op::MemLoadImm(ptr.waddr + 1), - // Shift the low bits - Op::U32ShrImm(rshift), - // Load the quad-word containing the high bits - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop unused elements - Op::Movdn(3), - Op::Drop, - Op::Drop, - Op::Drop, - // Shift the high bits - Op::U32ShlImm(ptr.offset as u32), - // OR the high and low bits together - Op::U32Or, - ], - span, - ); - } - _ => unreachable!(), - } - } - - /// Load a pair of machine words (32-bit elements) to the operand stack - fn load_double_word(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.load_double_word_imm(imm, span); - } - - self.emit(Op::Exec("intrinsics::mem::load_dw".parse().unwrap()), span); - } - - fn load_double_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - let aligned = ptr.is_element_aligned(); - match ptr.index { - 0 if aligned => { - self.emit_all( - &[ - // Load quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Move the two elements we need to the bottom temporarily - Op::Movdn(4), - Op::Movdn(4), - // Drop the unused elements - Op::Drop, - Op::Drop, - ], - span, - ); - } - 0 => { - // An unaligned double-word load spans three elements - self.emit_all( - &[ - // Load quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Move the unused element to the top and drop it - Op::Movup(4), - Op::Drop, - // Move into stack order for realign_dw - Op::Swap(2), - ], - span, - ); - self.realign_double_word(ptr, span); - } - 1 if aligned => { - self.emit_all( - &[ - // Load quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop the first word, its unused - Op::Drop, - // Move the last word up and drop it, also unused - Op::Movup(3), - Op::Drop, - ], - span, - ); - } - 1 => { - // An unaligned double-word load spans three elements - self.emit_all( - &[ - // Load a quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop the unused element - Op::Drop, - // Move into stack order for realign_dw - Op::Swap(2), - ], - span, - ); - self.realign_double_word(ptr, span); - } - 2 if aligned => { - self.emit_all( - &[ - // Load quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop unused words - Op::Drop, - Op::Drop, - ], - span, - ); - } - 2 => { - // An unaligned double-word load spans three elements, - // and in this case, two quad-words, because the last - // element is across a quad-word boundary - self.emit_all( - &[ - // Load the second quad-word first - Op::Padw, - Op::MemLoadwImm(ptr.waddr + 1), - // Move the element we need to the bottom temporarily - Op::Movdn(4), - // Drop the three unused elements of this word - Op::Drop, - Op::Drop, - Op::Drop, - // Load the first quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - // Drop the two unused elements - Op::Drop, - Op::Drop, - // Move into stack order for realign_dw - Op::Swap(2), - ], - span, - ); - self.realign_double_word(ptr, span); - } - 3 if aligned => { - self.emit_all( - &[ - // Load second word, drop unused elements - Op::Padw, - Op::MemLoadwImm(ptr.waddr + 1), - Op::Movup(4), - Op::Drop, - Op::Movup(3), - Op::Drop, - // Load first word, drop unused elements - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - Op::Drop, - Op::Drop, - Op::Drop, - ], - span, - ); - } - 3 => { - self.emit_all( - &[ - // Load second word, drop unused element - Op::Padw, - Op::MemLoadwImm(ptr.waddr + 1), - Op::Movup(4), - Op::Drop, - // Load first word, drop unused elements - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - Op::Drop, - Op::Drop, - Op::Drop, - // Move into stack order for realign_dw - Op::Swap(2), - ], - span, - ); - self.realign_double_word(ptr, span); - } - _ => unimplemented!("unaligned loads are not yet implemented: {ptr:#?}"), - } - } - - /// Load a quartet of machine words (32-bit elements) to the operand stack - fn load_quad_word(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.load_quad_word_imm(imm, span); - } - self.emit(Op::Exec("intrinsics::mem::load_qw".parse().unwrap()), span); - } - - fn load_quad_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - // For all other cases, more complicated loads are required - let aligned = ptr.is_element_aligned(); - match ptr.index { - // Naturally-aligned - 0 if aligned => self.emit_all( - &[ - // Load the word - Op::Padw, - // [w3, w2, w1, w0] - Op::MemLoadwImm(ptr.waddr), - // Swap the element order to lowest-address-first - // [w2, w3, w1, w0] - Op::Swap(1), - // [w1, w3, w2, w0] - Op::Swap(2), - // [w3, w1, w2, w0] - Op::Swap(1), - // [w0, w1, w2, w3] - Op::Swap(3), - ], - span, - ), - 0 => { - // An unaligned quad-word load spans five elements - self.emit_all( - &[ - // Load first element of second quad-word - // [e] - Op::MemLoadImm(ptr.waddr + 1), - // Load first quad-word - Op::Padw, - // [d, c, b, a, e] - Op::MemLoadwImm(ptr.waddr), - // [a, c, b, d, e] - Op::Swap(3), - // [c, a, b, d, e] - Op::Swap(1), - // [a, b, c, d, e] - Op::Movdn(2), - ], - span, - ); - self.realign_quad_word(ptr, span); - } - 1 if aligned => { - self.emit_all( - &[ - // Load first element of second quad-word - // [d] - Op::MemLoadImm(ptr.waddr + 1), - // Load first quad-word - Op::Padw, - // [c, b, a, _, d] - Op::MemLoadwImm(ptr.waddr), - // [_, b, a, c, d] - Op::Swap(3), - Op::Drop, - // [a, b, c, d] - Op::Swap(1), - ], - span, - ); - } - 1 => { - // An unaligned double-word load spans five elements - self.emit_all( - &[ - // Load first two elements of second quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr + 1), - Op::Drop, - // [e, d] - Op::Drop, - // Load last three elements of first quad-word - Op::Padw, - // [c, b, a, _, e, d] - Op::MemLoadwImm(ptr.waddr), - // [_, b, a, c, e, d] - Op::Swap(3), - // [b, a, c, e, d] - Op::Drop, - // [e, a, c, b, d] - Op::Swap(3), - // [d, a, c, b, e] - Op::Swap(4), - // [b, a, c, d, e] - Op::Swap(3), - // [a, b, c, d, e] - Op::Swap(1), - ], - span, - ); - self.realign_quad_word(ptr, span); - } - 2 if aligned => { - self.emit_all( - &[ - // Load first two elements of second quad-word - Op::Padw, - // [_, _, d, c] - Op::MemLoadwImm(ptr.waddr), - // Drop last two elements - Op::Drop, - // [d, c] - Op::Drop, - // Load last two elements of first quad-word - Op::Padw, - // [b, a, _, _, d, c] - Op::MemLoadwImm(ptr.waddr), - // [d, a, _, _, b, c] - Op::Swap(4), - // [a, _, _, b, c, d] - Op::Movdn(5), - // [_, _, a, b, c, d] - Op::Swap(2), - Op::Drop, - Op::Drop, - ], - span, - ); - } - 2 => { - // An unaligned double-word load spans five elements - self.emit_all( - &[ - // Load the first three elements of the second quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr + 1), - // [e, d, c] - Op::Drop, - // Load the last two elements of the first quad-word - Op::Padw, - // [b, a, _, _, e, d, c] - Op::MemLoadwImm(ptr.waddr), - // [a, _, _, b, e, d, c] - Op::Movdn(3), - // [_, _, a, b, e, d, c] - Op::Movdn(2), - // [c, _, a, b, e, d, _] - Op::Swap(6), - // [e, _, a, b, c, d, _] - Op::Swap(4), - // [_, _, a, b, c, d, e] - Op::Swap(6), - Op::Drop, - // [a, b, c, d, e] - Op::Drop, - ], - span, - ); - self.realign_quad_word(ptr, span); - } - 3 if aligned => { - self.emit_all( - &[ - // Load first three elements of second quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr + 1), - Op::Drop, - // Load last element of first quad-word - Op::Padw, - Op::MemLoadwImm(ptr.waddr), - Op::Movdn(3), - Op::Drop, - Op::Drop, - Op::Drop, - ], - span, - ); - } - 3 => { - // An unaligned quad-word load spans five elements, - self.emit_all( - &[ - // Load second quad-word - Op::Padw, - // [e, d, c, b] - Op::MemLoadwImm(ptr.waddr + 1), - // Load last element of first quad-word - Op::Padw, - // [a, _, _, _, e, d, c, b] - Op::MemLoadwImm(ptr.waddr), - // [_, _, _, a, e, d, c, b] - Op::Movdn(3), - Op::Drop, - Op::Drop, - // [a, e, d, c, b] - Op::Drop, - // [e, a, d, c, b] - Op::Swap(1), - // [b, a, d, c, e] - Op::Swap(4), - // [d, a, b, c, e] - Op::Swap(2), - // [a, b, c, d, e] - Op::Movdn(3), - ], - span, - ); - self.realign_quad_word(ptr, span); - } - _ => unimplemented!("unaligned loads are not yet implemented: {ptr:#?}"), - } - } - - /// This handles emitting code that handles aligning an unaligned double machine-word value - /// which is split across three machine words (field elements). - /// - /// To recap: - /// - /// * A machine word is a 32-bit chunk stored in a single field element - /// * A double word is a pair of 32-bit chunks - /// * A quad word is a quartet of 32-bit chunks (i.e. a Miden "word") - /// * An unaligned double-word requires three 32-bit chunks to represent, since the first chunk - /// does not contain a full 32-bits, so an extra is needed to hold those bits. - /// - /// As an example, assume the pointer we are dereferencing is a u64 value, - /// which has 8-byte alignment, and the value is stored 40 bytes from the - /// nearest quad-word-aligned boundary. To load the value, we must fetch - /// the full quad-word from the aligned address, drop the first word, as - /// it is unused, and then recombine the 64 bits we need spread across - /// the remaining three words to obtain the double-word value we actually want. - /// - /// The data, on the stack, is shown below: - /// - /// ```text,ignore - /// # If we visualize which bytes are contained in each 32-bit chunk on the stack, we get: - /// [0..=4, 5..=8, 9..=12] - /// - /// # These byte indices are relative to the nearest word-aligned address, in the same order - /// # as they would occur in a byte-addressable address space. The significance of each byte - /// # depends on the value being dereferenced, but Miden is a little-endian machine, so typically - /// # the most significant bytes come first (i.e. also commonly referred to as "high" vs "low" bits). - /// # - /// # If we visualize the layout of the bits of our u64 value spread across the three chunks, we get: - /// [00000000111111111111111111111111, 111111111111111111111111111111, 11111111111111111111111100000000] - /// ``` - /// - /// As illustrated above, what should be a double-word value is occupying three words. To - /// "realign" the value, i.e. ensure that it is naturally aligned and fits in two words, we - /// have to perform a sequence of shifts and masks to get the bits where they belong. This - /// function performs those steps, with the assumption that the caller has three values on - /// the operand stack representing any unaligned double-word value - fn realign_double_word(&mut self, _ptr: NativePtr, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::mem::realign_dw".parse().unwrap()), span); - } - - /// This handles emitting code that handles aligning an unaligned quad machine-word value - /// which is split across five machine words (field elements). - /// - /// To recap: - /// - /// * A machine word is a 32-bit chunk stored in a single field element - /// * A double word is a pair of 32-bit chunks - /// * A quad word is a quartet of 32-bit chunks (i.e. a Miden "word") - /// * An unaligned quad-word requires five 32-bit chunks to represent, since the first chunk - /// does not contain a full 32-bits, so an extra is needed to hold those bits. - /// - /// See the example in [OpEmitter::realign_quad_word] for more details on how bits are - /// laid out in each word, and what is required to realign unaligned words. - fn realign_quad_word(&mut self, ptr: NativePtr, span: SourceSpan) { - // The stack starts as: [chunk_hi, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo] - // - // We will refer to the parts of our desired quad-word value - // as four parts, `x_hi2`, `x_hi1`, `x_lo2`, and `x_lo1`, where - // the integer suffix should appear in decreasing order on the - // stack when we're done. - self.emit_all( - &[ - // Re-align the high bits by shifting out the offset - // - // This gives us the first half of `x_hi2`. - // - // [x_hi2_hi, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk__lo] - Op::U32ShlImm(ptr.offset as u32), - // Move the value below the other chunks temporarily - // - // [chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk__lo, x_hi2_hi] - Op::Movdn(5), - // We must split the `chunk_mid_hi` chunk into two parts, - // one containing the bits to be combined with `x_hi2_hi`; - // the other to be combined with `x_hi1_hi`. - // - // First, we duplicate the chunk, since we need two - // copies of it: - // - // [chunk_mid_hi, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2_hi] - Op::Dup(0), - // Then, we shift the chunk right by 32 - offset bits, - // re-aligning the low bits of `x_hi2`, and isolating them. - // - // [x_hi2_lo, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2_hi] - Op::U32ShrImm(32 - ptr.offset as u32), - // Move the high bits of `x_hi2` back to the top - // - // [x_hi2_hi, x_hi2_lo, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo] - Op::Movup(3), - // OR the two parts of the `x_hi2` chunk together - // - // [x_hi2, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo] - Op::U32Or, - // Move `x_hi2` to the bottom for later - // - // [chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2] - Op::Movdn(5), - // Now, we need to re-align the high bits of `x_hi1` by shifting - // the remaining copy of `chunk_mid_hi`, similar to what we did for `x_hi2` - // - // This gives us the first half of `x_hi1` - // - // [x_hi1_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2] - Op::U32ShlImm(ptr.offset as u32), - // Next, move the chunk containing the low bits of `x_hi1` to the top temporarily - // - // [chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1_hi] - Op::Movdn(5), - // Duplicate it, as we need two copies - // - // [chunk_mid_mid, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1_hi] - Op::Dup(0), - // Shift the value right, as done previously for the low bits of `x_hi2` - // - // [x_hi1_lo, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1_hi] - Op::U32ShrImm(32 - ptr.offset as u32), - // Move the high bits of `x_hi1` to the top - Op::Movup(5), - // OR the two halves together, giving us our second word, `x_hi1` - // - // [x_hi1, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2] - Op::U32Or, - // Move the word to the bottom of the stack - // - // [chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] - Op::Movdn(5), - // Now, we need to re-align the high bits of `x_lo2` by shifting - // the remaining copy of `chunk_mid_mid`, as done previously. - // - // [x_lo2_hi, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] - Op::U32ShlImm(ptr.offset as u32), - // Next, move the chunk containing the low bits of `x_lo2` to the top temporarily - // - // [chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2_hi] - Op::Movdn(5), - // Duplicate it, as done previously - // - // [chunk_mid_lo, chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2_hi] - Op::Dup(0), - // Shift the value right to get the low bits of `x_lo2` - // - // [x_lo2_lo, chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2_hi] - Op::U32ShrImm(32 - ptr.offset as u32), - // Move the high bits of `x_lo2` to the top - // - // [x_lo2_hi, x_lo2_lo, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] - Op::Movup(6), - // OR the two halves together, giving us our third word, `x_lo2` - // - // [x_lo2, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] - Op::U32Or, - // Move to the bottom of the stack - // - // [chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2] - Op::Movdn(5), - // Re-align the high bits of `x_lo1` - // - // [x_lo1_hi, chunk_lo, x_hi2, x_hi1, x_lo2] - Op::U32ShlImm(ptr.offset as u32), - // Move the chunk containing the low bits to the top - // - // [chunk_lo, x_hi2, x_hi1, x_lo2, x_lo1_hi] - Op::Movdn(5), - // Shift the value right to get the low bits of `x_lo1` - Op::U32ShrImm(32 - ptr.offset as u32), - // Move the high bits of `x_lo1` to the top - // - // [x_lo1_hi, x_lo1_lo, x_hi2, x_hi1, x_lo2] - Op::Movup(5), - // OR the two halves together, giving us our fourth word, `x_lo1` - // - // [x_lo1, x_hi2, x_hi1, x_lo2] - Op::U32Or, - // Move to the bottom - // - // [x_hi2, x_hi1, x_lo2, x_lo1] - Op::Movdn(5), - ], - span, - ); - } -} - -/// Stores -impl<'a> OpEmitter<'a> { - /// Store a value of the type given by the specified [hir::LocalId], using the memory allocated - /// for that local. - /// - /// Internally, this pushes the address of the given local on the stack, and delegates to - /// [OpEmitter::store] to perform the actual store. - pub fn store_local(&mut self, local: hir::LocalId, span: SourceSpan) { - let ty = self.function.local(local).ty.clone(); - self.emit(Op::LocAddr(local), span); - self.push(Type::Ptr(Box::new(ty))); - self.store(span) - } - - /// Store a value of type `value` to the address in the Miden address space - /// which corresponds to a pointer in the IR's byte-addressable address space. - /// - /// The type of the pointer is given as `ptr`, and can be used for both validation and - /// determining alignment. - pub fn store(&mut self, span: SourceSpan) { - let ptr = self.stack.pop().expect("operand stack is empty"); - let value = self.stack.pop().expect("operand stack is empty"); - let ptr_ty = ptr.ty(); - assert!(ptr_ty.is_pointer(), "expected store operand to be a pointer, got {ptr_ty}"); - let value_ty = value.ty(); - assert!(!value_ty.is_zst(), "cannot store a zero-sized type in memory"); - match ptr_ty { - Type::Ptr(_) => { - // Convert the pointer to a native pointer representation - self.emit_native_ptr(span); - match value_ty { - Type::I128 => self.store_quad_word(None, span), - Type::I64 | Type::U64 => self.store_double_word(None, span), - Type::Felt => self.store_felt(None, span), - Type::I32 | Type::U32 => self.store_word(None, span), - ref ty if ty.size_in_bytes() <= 4 => self.store_small(ty, None, span), - Type::Array(ref elem_ty, _) => self.store_array(elem_ty, None, span), - Type::Struct(ref struct_ty) => self.store_struct(struct_ty, None, span), - ty => unimplemented!( - "invalid store: support for storing {ty} has not been implemented" - ), - } - } - ty if !ty.is_pointer() => { - panic!("invalid operand to store: expected pointer, got {ty}") - } - ty => unimplemented!("store support for pointers of type {ty} is not implemented"), - } - } - - /// Store a value of type `ty` to `addr`. - /// - /// NOTE: The address represented by `addr` is in the IR's byte-addressable address space. - pub fn store_imm(&mut self, addr: u32, span: SourceSpan) { - let value = self.stack.pop().expect("operand stack is empty"); - let value_ty = value.ty(); - assert!(!value_ty.is_zst(), "cannot store a zero-sized type in memory"); - let ptr = NativePtr::from_ptr(addr); - match value_ty { - Type::I128 => self.store_quad_word(Some(ptr), span), - Type::I64 | Type::U64 => self.store_double_word(Some(ptr), span), - Type::Felt => self.store_felt(Some(ptr), span), - Type::I32 | Type::U32 => self.store_word(Some(ptr), span), - ref ty if ty.size_in_bytes() <= 4 => self.store_small(ty, Some(ptr), span), - Type::Array(ref elem_ty, _) => self.store_array(elem_ty, Some(ptr), span), - Type::Struct(ref struct_ty) => self.store_struct(struct_ty, Some(ptr), span), - ty => { - unimplemented!("invalid store: support for storing {ty} has not been implemented") - } - } - } - - pub fn memset(&mut self, span: SourceSpan) { - let dst = self.stack.pop().expect("operand stack is empty"); - let count = self.stack.pop().expect("operand stack is empty"); - let value = self.stack.pop().expect("operand stack is empty"); - assert_eq!(count.ty(), Type::U32, "expected count operand to be a u32"); - let ty = value.ty(); - assert!(dst.ty().is_pointer()); - assert_eq!(&ty, dst.ty().pointee().unwrap(), "expected value and pointee type to match"); - - // Prepare to loop until `count` iterations have been performed - let current_block = self.current_block; - let body = self.function.create_block(); - self.emit_all( - &[ - // [dst, count, value..] - Op::PushU32(0), // [i, dst, count, value..] - Op::Dup(2), // [count, i, dst, count, value..] - Op::GteImm(Felt::ZERO), // [count > 0, i, dst, count, value..] - Op::While(body), - ], - span, - ); - - // Loop body - compute address for next value to be written - let value_size = value.ty().size_in_bytes(); - self.switch_to_block(body); - self.emit_all( - &[ - // [i, dst, count, value..] - // Offset the pointer by the current iteration count * aligned size of value, and - // trap if it overflows - Op::Dup(1), // [dst, i, dst, count, value] - Op::Dup(1), // [i, dst, i, dst, count, value] - Op::PushU32(value_size.try_into().expect("invalid value size")), /* [value_size, i, - * dst, ..] */ - Op::U32OverflowingMadd, // [value_size * i + dst, i, dst, count, value] - Op::Assertz, // [aligned_dst, i, dst, count, value..] - ], - span, - ); - - // Loop body - move value to top of stack, swap with pointer - self.push(value); - self.push(count); - self.push(dst.clone()); - self.push(dst.ty()); - self.push(dst.ty()); - self.dup(4, span); // [value, aligned_dst, i, dst, count, value] - self.swap(1, span); // [aligned_dst, value, i, dst, count, value] - - // Loop body - write value to destination - self.store(span); // [i, dst, count, value] - - // Loop body - increment iteration count, determine whether to continue loop - self.emit_all( - &[ - Op::U32WrappingAddImm(1), - Op::Dup(0), // [i++, i++, dst, count, value] - Op::Dup(3), // [count, i++, i++, dst, count, value] - Op::U32Gte, // [i++ >= count, i++, dst, count, value] - ], - span, - ); - - // Cleanup - at end of 'while' loop, drop the 4 operands remaining on the stack - self.switch_to_block(current_block); - self.dropn(4, span); - } - - /// Copy `count * sizeof(*ty)` from a source address to a destination address. - /// - /// The order of operands on the stack is `src`, `dst`, then `count`. - /// - /// The addresses on the stack are interpreted based on the pointer type: native pointers are - /// in the Miden address space; non-native pointers are assumed to be in the IR's byte - /// addressable address space, and require translation. - /// - /// The semantics of this instruction are as follows: - /// - /// * The `` - pub fn memcpy(&mut self, span: SourceSpan) { - let src = self.stack.pop().expect("operand stack is empty"); - let dst = self.stack.pop().expect("operand stack is empty"); - let count = self.stack.pop().expect("operand stack is empty"); - assert_eq!(count.ty(), Type::U32, "expected count operand to be a u32"); - let ty = src.ty(); - assert!(ty.is_pointer()); - assert_eq!(ty, dst.ty(), "expected src and dst operands to have the same type"); - let value_ty = ty.pointee().unwrap(); - let value_size = u32::try_from(value_ty.size_in_bytes()).expect("invalid value size"); - - // Use optimized intrinsics when available - match value_size { - // Word-sized values have an optimized intrinsic we can lean on - 16 => { - self.emit_all( - &[ - // [src, dst, count] - Op::Movup(2), // [count, src, dst] - Op::Exec("std::mem::memcopy".parse().unwrap()), - ], - span, - ); - return; - } - // Values which can be broken up into word-sized chunks can piggy-back on the - // intrinsic for word-sized values, but we have to compute a new `count` by - // multiplying `count` by the number of words in each value - size if size % 16 == 0 => { - let factor = size / 16; - self.emit_all( - &[ - // [src, dst, count] - Op::Movup(2), // [count, src, dst] - Op::U32OverflowingMulImm(factor), - Op::Assertz, // [count * (size / 16), src, dst] - Op::Exec("std::mem::memcopy".parse().unwrap()), - ], - span, - ); - return; - } - // For now, all other values fallback to the default implementation - _ => (), - } - - // Prepare to loop until `count` iterations have been performed - let current_block = self.current_block; - let body = self.function.create_block(); - self.emit_all( - &[ - // [src, dst, count] - Op::PushU32(0), // [i, src, dst, count] - Op::Dup(3), // [count, i, src, dst, count] - Op::GteImm(Felt::ZERO), // [count > 0, i, src, dst, count] - Op::While(body), - ], - span, - ); - - // Loop body - compute address for next value to be written - self.switch_to_block(body); - - // Compute the source and destination addresses - self.emit_all( - &[ - // [i, src, dst, count] - Op::Dup(2), // [dst, i, src, dst, count] - Op::Dup(1), // [i, dst, i, src, dst, count] - Op::PushU32(value_size), // [offset, i, dst, i, src, dst, count] - Op::U32OverflowingMadd, - Op::Assertz, // [new_dst := i * offset + dst, i, src, dst, count] - Op::Dup(2), // [src, new_dst, i, src, dst, count] - Op::Dup(2), // [i, src, new_dst, i, src, dst, count] - Op::PushU32(value_size), // [offset, i, src, new_dst, i, src, dst, count] - Op::U32OverflowingMadd, - Op::Assertz, // [new_src := i * offset + src, new_dst, i, src, dst, count] - ], - span, - ); - - // Load the source value - self.push(count.clone()); - self.push(dst.clone()); - self.push(src.clone()); - self.push(Type::U32); - self.push(dst.clone()); - self.push(src.clone()); - self.load(value_ty.clone(), span); // [value, new_dst, i, src, dst, count] - - // Write to the destination - self.swap(1, span); // [new_dst, value, i, src, dst, count] - self.store(span); // [i, src, dst, count] - - // Increment iteration count, determine whether to continue loop - self.emit_all( - &[ - Op::U32WrappingAddImm(1), - Op::Dup(0), // [i++, i++, src, dst, count] - Op::Dup(4), // [count, i++, i++, src, dst, count] - Op::U32Gte, // [i++ >= count, i++, src, dst, count] - ], - span, - ); - - // Cleanup - at end of 'while' loop, drop the 4 operands remaining on the stack - self.switch_to_block(current_block); - self.dropn(4, span); - } - - /// Store a quartet of machine words (32-bit elements) to the operand stack - fn store_quad_word(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.store_quad_word_imm(imm, span); - } - self.emit(Op::Exec("intrinsics::mem::store_qw".parse().unwrap()), span); - } - - fn store_quad_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - // For all other cases, more complicated loads are required - let aligned = ptr.is_element_aligned(); - match ptr.index { - // Naturally-aligned - 0 if aligned => self.emit_all( - &[ - // Stack: [a, b, c, d] - // Swap to highest-address-first order - // [d, b, c, a] - Op::Swap(3), - // [c, d, b, a] - Op::Movup(2), - // [d, c, b, a] - Op::Swap(1), - // Write to heap - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ), - _ => { - todo!("quad-word stores currently require 32-byte alignment") - } - } - } - - /// Store a pair of machine words (32-bit elements) to the operand stack - fn store_double_word(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.store_double_word_imm(imm, span); - } - - self.emit(Op::Exec("intrinsics::mem::store_dw".parse().unwrap()), span); - } - - fn store_double_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - // For all other cases, more complicated stores are required - let aligned = ptr.is_element_aligned(); - match ptr.index { - // Naturally-aligned - 0 if aligned => self.emit_all( - &[ - // Swap value to highest-address-first order - Op::Swap(1), - // Load existing word - Op::Padw, - // [d, c, b, a, v_lo, v_hi] - Op::MemLoadwImm(ptr.waddr), - // Replace bottom two elements with value - // [b, c, d, a, v_lo, v_hi] - Op::Swap(2), - // [c, d, a, v_lo, v_hi] - Op::Drop, - // [a, d, c, v_lo, v_hi] - Op::Swap(2), - // [d, c, v_lo, v_hi] - Op::Drop, - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ), - _ => { - // TODO: Optimize double-word stores when pointer is contant - self.emit_all( - &[Op::PushU8(ptr.offset), Op::PushU8(ptr.index), Op::PushU32(ptr.waddr)], - span, - ); - self.emit(Op::Exec("intrinsics::mem::store_dw".parse().unwrap()), span); - } - } - } - - /// Stores a single 32-bit machine word, i.e. a single field element, not the Miden notion of a - /// word - /// - /// Expects a native pointer triplet on the stack if an immediate address is not given. - fn store_word(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.store_word_imm(imm, span); - } - - self.emit(Op::Exec("intrinsics::mem::store_sw".parse().unwrap()), span); - } - - /// Stores a single 32-bit machine word to the given immediate address. - fn store_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - let is_aligned = ptr.is_element_aligned(); - let rshift = 32 - ptr.offset as u32; - match ptr.index { - 0 if is_aligned => self.emit(Op::MemStoreImm(ptr.waddr), span), - 0 => { - let mask_hi = u32::MAX << rshift; - let mask_lo = u32::MAX >> (ptr.offset as u32); - self.emit_all( - &[ - // Load the word - Op::Padw, - // [w3, w2, w1, w0, value] - Op::MemLoadwImm(ptr.waddr), - // [w1, w3, w2, w0, value] - Op::Movup(2), - Op::PushU32(mask_lo), - // [w1_masked, w3, w2, w0, value] - Op::U32And, - // [w0, w1_masked, w3, w2, value] - Op::Movup(3), - Op::PushU32(mask_hi), - // [w0_masked, w1_masked, w3, w2, value] - Op::U32And, - // [value, w0_masked, w1_masked, w3, w2, value] - Op::Dup(4), - // [value, w0_masked, w1_masked, w3, w2, value] - Op::U32ShrImm(ptr.offset as u32), - // [w0', w1_masked, w3, w2, value] - Op::U32Or, - // [w1_masked, w0', w3, w2, value] - Op::Swap(1), - Op::Movup(4), - Op::U32ShlImm(rshift), - // [w1', w0', w3, w2] - Op::U32Or, - Op::Movup(3), - // [w3, w2, w1', w0'] - Op::Movup(3), - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ); - } - 1 if is_aligned => self.emit_all( - &[ - // Load a quad-word - Op::Padw, - // [d, c, _, a, value] - Op::MemLoadwImm(ptr.waddr), - // [value, d, c, _, a] - Op::Movup(4), - // [_, d, c, value, a] - Op::Swap(3), - // [d, c, value, a] - Op::Drop, - // Write the word back to the cell - Op::MemStorewImm(ptr.waddr), - // Clean up the operand stack - Op::Dropw, - ], - span, - ), - 1 => { - let mask_hi = u32::MAX << rshift; - let mask_lo = u32::MAX >> (ptr.offset as u32); - self.emit_all( - &[ - Op::Padw, - // the load is across both the second and third elements - // [w3, w2, w1, w0, value] - Op::MemLoadwImm(ptr.waddr), - // [w2, w3, w1, w0, value] - Op::Swap(1), - Op::PushU32(mask_lo), - // [w2_masked, w3, w1, w0, value] - Op::U32And, - // [w1, w2_masked, w3, w0, value] - Op::Movup(2), - Op::PushU32(mask_hi), - // [w1_masked, w2_masked, w3, w0, value] - Op::U32And, - // [value, w1_masked, w2_masked, w3, w0, value] - Op::Dup(4), - Op::U32ShrImm(ptr.offset as u32), - // [w1', w2_masked, w3, w0, value] - Op::U32Or, - // [w2_masked, w1', w3, w0, value] - Op::Swap(1), - // [value, w2_masked, w1', w3, w0] - Op::Movup(4), - Op::U32ShlImm(rshift), - // [w2', w1', w3, w0, value] - Op::U32Or, - // [w0, w2', w1', w3, value] - Op::Movup(3), - // [w3, w2', w1', w0, value] - Op::Swap(3), - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ); - } - 2 if is_aligned => self.emit_all( - &[ - // Load a quad-word - Op::Padw, - // [d, _, b, a, value] - Op::MemLoadwImm(ptr.waddr), - // [value, d, _, b, a] - Op::Movup(4), - // [_, d, value, b, a] - Op::Swap(2), - Op::Drop, - // Write the word back to the cell - Op::MemStorewImm(ptr.waddr), - // Clean up the operand stack - Op::Dropw, - ], - span, - ), - 2 => { - let mask_hi = u32::MAX << rshift; - let mask_lo = u32::MAX >> (ptr.offset as u32); - self.emit_all( - &[ - // the load is across both the third and fourth elements - Op::Padw, - // [w3, w2, w1, w0, value] - Op::MemLoadwImm(ptr.waddr), - Op::PushU32(mask_lo), - // [w3_masked, w2, w1, w0, value] - Op::U32And, - // [w2, w3_masked, w1, w0, value] - Op::Swap(1), - Op::PushU32(mask_hi), - // [w2_masked, w3_masked, w1, w0, value] - Op::U32And, - // [value, w2_masked, w3_masked, w1, w0, value] - Op::Dup(4), - Op::U32ShrImm(ptr.offset as u32), - // [w2', w3_masked, w1, w0, value] - Op::U32Or, - // [w3_masked, w2', w1, w0, value] - Op::Swap(1), - // [value, w3_masked, w2', w1, w0] - Op::Movup(4), - Op::U32ShlImm(rshift), - // [w3', w2', w1, w0] - Op::U32Or, - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ); - } - 3 if is_aligned => self.emit_all( - &[ - // Load a quad-word - Op::Padw, - // [_, c, b, a, value] - Op::MemLoadwImm(ptr.waddr), - // [c, b, a, value] - Op::Drop, - // [value, c, b, a] - Op::Movup(3), - // Write the word back to the cell - Op::MemStorewImm(ptr.waddr), - // Clean up the operand stack - Op::Dropw, - ], - span, - ), - 3 => { - // This is a rather annoying edge case, as it requires us to store bits - // across two different words. We start with the "hi" bits that go at - // the end of the first word, and then handle the "lo" bits in a simpler - // fashion - let mask_hi = u32::MAX << rshift; - let mask_lo = u32::MAX >> (ptr.offset as u32); - self.emit_all( - &[ - // the load crosses a word boundary, start with the element containing - // the highest-addressed bits - // [w0, value] - Op::MemLoadImm(ptr.waddr + 1), - Op::PushU32(mask_lo), - // [w0_masked, value] - Op::U32And, - // [value, w0_masked, value] - Op::Dup(1), - // [w0', value] - Op::U32ShlImm(rshift), - Op::U32Or, - // Store it - // [value] - Op::MemStoreImm(ptr.waddr + 1), - // Load the first word - Op::Padw, - // [w3, w2, w1, w0, value] - Op::MemLoadwImm(ptr.waddr), - Op::PushU32(mask_hi), - // [w3_masked, w2, w1, w0, value] - Op::U32And, - // [value, w3_masked, w2, w1, w0] - Op::Movup(4), - Op::U32ShrImm(ptr.offset as u32), - // [w3', w2, w1, w0] - Op::U32Or, - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ); - } - _ => unreachable!(), - } - } - - /// Store a field element to a naturally aligned address, either immediate or dynamic - /// - /// A native pointer triplet is expected on the stack if an immediate is not given. - fn store_felt(&mut self, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.store_felt_imm(imm, span); - } - - self.emit(Op::Exec("intrinsics::mem::store_felt".parse().unwrap()), span); - } - - fn store_felt_imm(&mut self, ptr: NativePtr, span: SourceSpan) { - assert!(ptr.is_element_aligned(), "felt values must be naturally aligned"); - match ptr.index { - 0 => self.emit(Op::MemStoreImm(ptr.waddr), span), - 1 => { - self.emit_all( - &[ - Op::Padw, - // [d, c, _, a, value] - Op::MemLoadwImm(ptr.waddr), - // [value, d, c, _, a] - Op::Movup(4), - // [_, d, c, value, a] - Op::Swap(3), - // [d, c, value, a] - Op::Drop, - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ); - } - 2 => { - self.emit_all( - &[ - Op::Padw, - // [d, _, b, a, value] - Op::MemLoadwImm(ptr.waddr), - // [value, d, _, b, a] - Op::Movup(4), - // [_, d, value, b, a] - Op::Swap(2), - Op::Drop, - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ); - } - 3 => { - self.emit_all( - &[ - Op::Padw, - // [_, c, b, a, value] - Op::MemLoadwImm(ptr.waddr), - // [c, b, a, value] - Op::Drop, - // [value, c, b, a] - Op::Movup(3), - Op::MemStorewImm(ptr.waddr), - Op::Dropw, - ], - span, - ); - } - _ => unreachable!(), - } - } - - fn store_small(&mut self, ty: &Type, ptr: Option, span: SourceSpan) { - if let Some(imm) = ptr { - return self.store_small_imm(ty, imm, span); - } - - let type_size = ty.size_in_bits(); - if type_size == 32 { - self.store_word(ptr, span); - return; - } - - // Duplicate the address - self.emit_all(&[Op::Dup(2), Op::Dup(2), Op::Dup(2)], span); - - // Load the current 32-bit value at `ptr` - self.load_word(ptr, span); - - // Mask out the bits we're going to be writing from the loaded value - let mask = u32::MAX << type_size; - self.const_mask_u32(mask, span); - - // Mix in the bits we want to write: [masked, addr1, addr2, addr3, value] - self.emit(Op::Movup(5), span); - self.bor_u32(span); - - // Store the combined bits: [value, addr1, addr2, addr3] - self.emit(Op::Movdn(4), span); - self.store_word(ptr, span); - } - - fn store_small_imm(&mut self, ty: &Type, ptr: NativePtr, span: SourceSpan) { - assert!(ptr.alignment() as usize >= ty.min_alignment()); - - let type_size = ty.size_in_bits(); - if type_size == 32 { - self.store_word_imm(ptr, span); - return; - } - - // Load the current 32-bit value at `ptr` - self.load_word_imm(ptr, span); - - // Mask out the bits we're going to be writing from the loaded value - let mask = u32::MAX << type_size; - self.const_mask_u32(mask, span); - - // Mix in the bits we want to write - self.emit(Op::Movup(4), span); - self.bor_u32(span); - - // Store the combined bits - self.store_word_imm(ptr, span); - } - - fn store_array(&mut self, _element_ty: &Type, _ptr: Option, _span: SourceSpan) { - todo!() - } - - fn store_struct(&mut self, _ty: &StructType, _ptr: Option, _span: SourceSpan) { - todo!() - } -} diff --git a/codegen/masm/src/codegen/emit/mod.rs b/codegen/masm/src/codegen/emit/mod.rs deleted file mode 100644 index 6f8eb0760..000000000 --- a/codegen/masm/src/codegen/emit/mod.rs +++ /dev/null @@ -1,1922 +0,0 @@ -use midenc_hir::diagnostics::Span; - -/// The field modulus for Miden's prime field -pub const P: u64 = (2u128.pow(64) - 2u128.pow(32) + 1) as u64; - -/// Assert that an argument specifying an integer size in bits follows the rules about what -/// integer sizes we support as a general rule. -macro_rules! assert_valid_integer_size { - ($n:ident) => { - assert!($n > 0, "invalid integer size: size in bits must be non-zero"); - assert!( - $n.is_power_of_two(), - "invalid integer size: size in bits must be a power of two, got {}", - $n - ); - }; - - ($n:ident, $min:literal) => { - assert_valid_integer_size!($n); - assert!( - $n >= $min, - "invalid integer size: expected size in bits greater than or equal to {}, got {}", - $n, - $min - ); - }; - - ($n:ident, $min:ident) => { - assert_valid_integer_size!($n); - assert!( - $n >= $min, - "invalid integer size: expected size in bits greater than or equal to {}, got {}", - $n, - $min - ); - }; - - ($n:ident, $min:literal, $max:literal) => { - assert_valid_integer_size!($n, $min); - assert!( - $n <= $max, - "invalid integer size: expected size in bits less than or equal to {}, got {}", - $n, - $max - ); - }; - - ($n:ident, $min:ident, $max:literal) => { - assert_valid_integer_size!($n, $min); - assert!( - $n <= $max, - "invalid integer size: expected size in bits less than or equal to {}, got {}", - $n, - $max - ); - }; -} - -/// Assert that an argument specifying a zero-based stack index does not access out of bounds -macro_rules! assert_valid_stack_index { - ($idx:ident) => { - assert!( - $idx < 16, - "invalid stack index: only the first 16 elements on the stack are directly \ - accessible, got {}", - $idx - ); - }; - - ($idx:expr) => { - assert!( - ($idx) < 16, - "invalid stack index: only the first 16 elements on the stack are directly \ - accessible, got {}", - $idx - ); - }; -} - -pub mod binary; -pub mod felt; -pub mod int128; -pub mod int32; -pub mod int64; -pub mod mem; -pub mod primop; -pub mod smallint; -pub mod unary; - -use core::ops::{Deref, DerefMut}; - -use miden_assembly::ast::InvokeKind; -use midenc_hir::{self as hir, diagnostics::SourceSpan, Immediate, Type}; - -use super::{Operand, OperandStack}; -use crate::masm::{self as masm, Op}; - -/// This structure is used to emit the Miden Assembly ops corresponding to an IR instruction. -/// -/// When dropped, it ensures that the operand stack is updated to reflect the results of the -/// instruction it was created on behalf of. -pub struct InstOpEmitter<'a> { - dfg: &'a hir::DataFlowGraph, - inst: hir::Inst, - emitter: OpEmitter<'a>, -} -impl<'a> InstOpEmitter<'a> { - #[inline(always)] - pub fn new( - function: &'a mut masm::Function, - dfg: &'a hir::DataFlowGraph, - inst: hir::Inst, - block: masm::BlockId, - stack: &'a mut OperandStack, - ) -> Self { - Self { - dfg, - inst, - emitter: OpEmitter::new(function, block, stack), - } - } - - pub fn exec(&mut self, callee: hir::FunctionIdent, span: SourceSpan) { - let import = self.dfg.get_import(&callee).unwrap(); - self.emitter.exec(import, span); - } - - pub fn syscall(&mut self, callee: hir::FunctionIdent, span: SourceSpan) { - let import = self.dfg.get_import(&callee).unwrap(); - self.emitter.syscall(import, span); - } - - #[inline(always)] - pub fn value_type(&self, value: hir::Value) -> &Type { - self.dfg.value_type(value) - } - - #[inline(always)] - pub fn dfg<'c, 'b: 'c>(&'b self) -> &'c hir::DataFlowGraph { - self.dfg - } -} -impl<'a> Deref for InstOpEmitter<'a> { - type Target = OpEmitter<'a>; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.emitter - } -} -impl<'a> DerefMut for InstOpEmitter<'a> { - #[inline(always)] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.emitter - } -} -impl<'a> Drop for InstOpEmitter<'a> { - fn drop(&mut self) { - let results = self.dfg.inst_results(self.inst); - for (i, result) in results.iter().copied().enumerate() { - self.emitter.stack.rename(i, result); - } - } -} - -/// This structure is used to emit Miden Assembly ops into a given function and block. -/// -/// The [OpEmitter] carries limited context of its own, and expects to receive arguments -/// to it's various builder functions to provide necessary context for specific constructs. -pub struct OpEmitter<'a> { - stack: &'a mut OperandStack, - function: &'a mut masm::Function, - current_block: masm::BlockId, -} -impl<'a> OpEmitter<'a> { - #[inline(always)] - pub fn new( - function: &'a mut masm::Function, - block: masm::BlockId, - stack: &'a mut OperandStack, - ) -> Self { - Self { - stack, - function, - current_block: block, - } - } - - #[cfg(test)] - #[inline(always)] - pub fn stack_len(&self) -> usize { - self.stack.len() - } - - #[inline(always)] - pub fn stack<'c, 'b: 'c>(&'b self) -> &'c OperandStack { - self.stack - } - - #[inline(always)] - pub fn stack_mut<'c, 'b: 'c>(&'b mut self) -> &'c mut OperandStack { - self.stack - } - - #[inline] - fn maybe_register_invoke(&mut self, op: &masm::Op) { - match op { - Op::Exec(id) => { - self.function.register_absolute_invocation_target(InvokeKind::Exec, *id) - } - Op::Call(id) => { - self.function.register_absolute_invocation_target(InvokeKind::Call, *id) - } - Op::Syscall(id) => { - self.function.register_absolute_invocation_target(InvokeKind::SysCall, *id) - } - _ => (), - } - } - - /// Emit `op` to the current block - #[inline(always)] - pub fn emit(&mut self, op: masm::Op, span: SourceSpan) { - self.maybe_register_invoke(&op); - self.current_block().push(op, span) - } - - /// Emit `n` copies of `op` to the current block - #[inline(always)] - pub fn emit_n(&mut self, count: usize, op: masm::Op, span: SourceSpan) { - self.maybe_register_invoke(&op); - self.current_block().push_n(count, op, span); - } - - /// Emit `ops` to the current block - #[inline(always)] - pub fn emit_all(&mut self, ops: &[masm::Op], span: SourceSpan) { - for op in ops { - self.maybe_register_invoke(op); - } - self.current_block().extend(ops.iter().copied().map(|op| Span::new(span, op))); - } - - /// Emit `n` copies of the sequence `ops` to the current block - #[inline(always)] - pub fn emit_repeat(&mut self, count: usize, ops: &[Span]) { - for op in ops { - self.maybe_register_invoke(op); - } - self.current_block().push_repeat(ops, count); - } - - /// Emit `n` copies of the sequence `ops` to the current block - #[inline] - pub fn emit_template(&mut self, count: usize, template: F) - where - F: Fn(usize) -> [Span; N], - { - for op in template(0) { - self.maybe_register_invoke(&op); - } - - let block = self.current_block(); - for n in 0..count { - let ops = template(n); - block.extend_from_slice(&ops); - } - } - - /// Push an immediate value on the operand stack - /// - /// This has no effect on the state of the emulated operand stack - #[inline] - pub fn push_immediate(&mut self, imm: Immediate, span: SourceSpan) { - match imm { - Immediate::I1(i) => self.emit(Op::PushU8(i as u8), span), - Immediate::I8(i) => self.emit(Op::PushU8(i as u8), span), - Immediate::U8(i) => self.emit(Op::PushU8(i), span), - Immediate::U16(i) => self.emit(Op::PushU32(i as u32), span), - Immediate::I16(i) => self.emit(Op::PushU32(i as u16 as u32), span), - Immediate::U32(i) => self.emit(Op::PushU32(i), span), - Immediate::I32(i) => self.emit(Op::PushU32(i as u32), span), - Immediate::U64(i) => self.push_u64(i, span), - Immediate::I64(i) => self.push_i64(i, span), - Immediate::U128(i) => self.push_u128(i, span), - Immediate::I128(i) => self.push_i128(i, span), - Immediate::Felt(i) => self.emit(Op::Push(i), span), - Immediate::F64(_) => unimplemented!("floating-point immediates are not supported"), - } - } - - /// Push a literal on the operand stack, and update the emulated stack accordingly - pub fn literal>(&mut self, imm: I, span: SourceSpan) { - let imm = imm.into(); - self.push_immediate(imm, span); - self.stack.push(imm); - } - - #[inline(always)] - pub fn pop(&mut self) -> Option { - self.stack.pop() - } - - /// Push an operand on the stack - #[inline(always)] - pub fn push>(&mut self, operand: O) { - self.stack.push(operand) - } - - /// Duplicate an item on the stack to the top - #[inline] - #[track_caller] - pub fn dup(&mut self, i: u8, span: SourceSpan) { - assert_valid_stack_index!(i); - let index = i as usize; - let i = self.stack.effective_index(index) as u8; - self.stack.dup(index); - // Emit low-level instructions corresponding to the operand we duplicated - let last = self.stack.peek().expect("operand stack is empty"); - let n = last.size(); - let offset = (n - 1) as u8; - for _ in 0..n { - self.emit(Op::Dup(i + offset), span); - } - } - - /// Move an item on the stack to the top - #[inline] - #[track_caller] - pub fn movup(&mut self, i: u8, span: SourceSpan) { - assert_valid_stack_index!(i); - let index = i as usize; - let i = self.stack.effective_index(index) as u8; - self.stack.movup(index); - // Emit low-level instructions corresponding to the operand we moved - let moved = self.stack.peek().expect("operand stack is empty"); - let n = moved.size(); - let offset = (n - 1) as u8; - for _ in 0..n { - self.emit(Op::Movup(i + offset), span); - } - } - - /// Move an item from the top of the stack to the `n`th position - #[inline] - #[track_caller] - pub fn movdn(&mut self, i: u8, span: SourceSpan) { - assert_valid_stack_index!(i); - let index = i as usize; - let i = self.stack.effective_index_inclusive(index) as u8; - let top = self.stack.peek().expect("operand stack is empty"); - let top_size = top.size(); - self.stack.movdn(index); - // Emit low-level instructions corresponding to the operand we moved - for _ in 0..top_size { - self.emit(Op::Movdn(i), span); - } - } - - /// Swap an item with the top of the stack - #[inline] - #[track_caller] - pub fn swap(&mut self, i: u8, span: SourceSpan) { - assert!(i > 0, "swap requires a non-zero index"); - assert_valid_stack_index!(i); - let index = i as usize; - let src = self.stack[0].size() as u8; - let dst = self.stack[index].size() as u8; - let i = self.stack.effective_index(index) as u8; - self.stack.swap(index); - match (src, dst) { - (1, 1) => { - self.emit(Op::Swap(i), span); - } - (1, n) if i == 1 => { - // We can simply move the top element below the `dst` operand - self.emit(Op::Movdn(i + (n - 1)), span); - } - (n, 1) if i == n => { - // We can simply move the `dst` element to the top - self.emit(Op::Movup(i), span); - } - (n, m) if i == n => { - // We can simply move `dst` down - for _ in 0..n { - self.emit(Op::Movdn(i + (m - 1)), span); - } - } - (n, m) => { - assert!(i >= n); - let offset = m - 1; - for _ in 0..n { - self.emit(Op::Movdn(i + offset), span); - } - let i = (i as i8 + (m as i8 - n as i8)) as u8; - match i - 1 { - 1 => { - assert_eq!(m, 1); - self.emit(Op::Swap(1), span); - } - i => { - for _ in 0..m { - self.emit(Op::Movup(i), span); - } - } - } - } - } - } - - /// Drop the top operand on the stack - #[inline] - #[track_caller] - pub fn drop(&mut self, span: SourceSpan) { - let elem = self.stack.pop().expect("operand stack is empty"); - match elem.size() { - 1 => { - self.emit(Op::Drop, span); - } - 4 => { - self.emit(Op::Dropw, span); - } - n => { - for _ in 0..n { - self.emit(Op::Drop, span); - } - } - } - } - - /// Drop the top `n` operands on the stack - #[inline] - #[track_caller] - pub fn dropn(&mut self, n: usize, span: SourceSpan) { - assert!(self.stack.len() >= n); - assert_ne!(n, 0); - let raw_len: usize = self.stack.iter().rev().take(n).map(|o| o.size()).sum(); - self.stack.dropn(n); - match raw_len { - 1 => { - self.emit(Op::Drop, span); - } - 4 => { - self.emit(Op::Dropw, span); - } - n => { - self.emit_n(n / 4, Op::Dropw, span); - self.emit_n(n % 4, Op::Drop, span); - } - } - } - - /// Remove all but the top `n` values on the operand stack - pub fn truncate_stack(&mut self, n: usize, span: SourceSpan) { - let stack_size = self.stack.len(); - let num_to_drop = stack_size - n; - - if num_to_drop == 0 { - return; - } - - if stack_size == num_to_drop { - let raw_size = self.stack.raw_len(); - self.stack.dropn(num_to_drop); - self.emit_n(raw_size / 4, Op::Dropw, span); - self.emit_n(raw_size % 4, Op::Dropw, span); - return; - } - - // This is the common case, and can be handled simply - // by moving the value to the bottom of the stack and - // dropping everything in-between - if n == 1 { - match stack_size { - 2 => { - self.swap(1, span); - self.drop(span); - } - n => { - self.movdn(n as u8 - 1, span); - self.dropn(n - 1, span); - } - } - return; - } - - // TODO: This is a very neive algorithm for clearing - // the stack of all but the top `n` values, we should - // come up with a smarter/more efficient method - for offset in 0..num_to_drop { - let index = stack_size - 1 - offset; - self.drop_operand_at_position(index, span); - } - } - - /// Remove the `n`th value from the top of the operand stack - pub fn drop_operand_at_position(&mut self, n: usize, span: SourceSpan) { - match n { - 0 => { - self.drop(span); - } - 1 => { - self.swap(1, span); - self.drop(span); - } - n => { - self.movup(n as u8, span); - self.drop(span); - } - } - } - - /// Copy the `n`th operand on the stack, and make it the `m`th operand on the stack. - /// - /// If the operand is for a commutative, binary operator, indicated by - /// `is_commutative_binary_operand`, and the desired position is just below the top of - /// stack, this function may leave it on top of the stack instead, since the order of the - /// operands is not strict. This can result in fewer stack manipulation instructions in some - /// scenarios. - pub fn copy_operand_to_position( - &mut self, - n: usize, - m: usize, - is_commutative_binary_operand: bool, - span: SourceSpan, - ) { - match (n, m) { - (0, 0) => { - self.dup(0, span); - } - (actual, 0) => { - self.dup(actual as u8, span); - } - (actual, 1) => { - // If the dependent is binary+commutative, we can - // leave operands in either the 0th or 1st position, - // as long as both operands are on top of the stack - if !is_commutative_binary_operand { - self.dup(actual as u8, span); - self.swap(1, span); - } else { - self.dup(actual as u8, span); - } - } - (actual, expected) => { - self.dup(actual as u8, span); - self.movdn(expected as u8, span); - } - } - } - - /// Make the `n`th operand on the stack, the `m`th operand on the stack. - /// - /// If the operand is for a commutative, binary operator, indicated by - /// `is_commutative_binary_operand`, and the desired position is one of the first two items - /// on the stack, this function may leave the operand in it's current position if it is - /// already one of the first two items on the stack, since the order of the operands is not - /// strict. This can result in fewer stack manipulation instructions in some scenarios. - pub fn move_operand_to_position( - &mut self, - n: usize, - m: usize, - is_commutative_binary_operand: bool, - span: SourceSpan, - ) { - match (n, m) { - (n, m) if n == m => (), - (1, 0) | (0, 1) => { - // If the dependent is binary+commutative, we can - // leave operands in either the 0th or 1st position, - // as long as both operands are on top of the stack - if !is_commutative_binary_operand { - self.swap(1, span); - } - } - (actual, 0) => { - self.movup(actual as u8, span); - } - (actual, 1) => { - self.movup(actual as u8, span); - self.swap(1, span); - } - (actual, expected) => { - self.movup(actual as u8, span); - self.movdn(expected as u8, span); - } - } - } - - /// Get mutable access to the current block we're emitting to - #[inline(always)] - pub fn current_block<'c, 'b: 'c>(&'b mut self) -> &'c mut masm::Block { - self.function.body.block_mut(self.current_block) - } - - #[allow(unused)] - #[inline] - pub fn switch_to_block(&mut self, block: masm::BlockId) -> masm::BlockId { - let prev = self.current_block; - self.current_block = block; - prev - } - - #[allow(unused)] - #[inline(always)] - pub fn position(&self) -> masm::BlockId { - self.current_block - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{AbiParam, Felt, FieldElement, Overflow, Signature}; - - use super::*; - use crate::{codegen::TypedValue, masm::Function}; - - #[test] - fn op_emitter_stack_manipulation_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - let three = Immediate::U8(3); - let four = Immediate::U64(2u64.pow(32)); - let five = Immediate::U64(2u64.pow(32) | 2u64.pow(33) | u32::MAX as u64); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - emitter.literal(three, SourceSpan::default()); - emitter.literal(four, SourceSpan::default()); - emitter.literal(five, SourceSpan::default()); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 5); - assert_eq!(ops[0].into_inner(), Op::PushU32(1)); - assert_eq!(ops[1].into_inner(), Op::PushU32(2)); - assert_eq!(ops[2].into_inner(), Op::PushU8(3)); - assert_eq!(ops[3].into_inner(), Op::Push2([Felt::new(1), Felt::ZERO])); - assert_eq!(ops[4].into_inner(), Op::Push2([Felt::new(3), Felt::new(u32::MAX as u64)])); - } - - assert_eq!(emitter.stack()[0], five); - assert_eq!(emitter.stack()[1], four); - assert_eq!(emitter.stack()[2], three); - assert_eq!(emitter.stack()[3], two); - assert_eq!(emitter.stack()[4], one); - - emitter.dup(0, SourceSpan::default()); - assert_eq!(emitter.stack()[0], five); - assert_eq!(emitter.stack()[1], five); - assert_eq!(emitter.stack()[2], four); - assert_eq!(emitter.stack()[3], three); - assert_eq!(emitter.stack()[4], two); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 7); - assert_eq!(ops[5].into_inner(), Op::Dup(1)); - assert_eq!(ops[6].into_inner(), Op::Dup(1)); - } - - assert_eq!(emitter.stack().effective_index(3), 6); - emitter.dup(3, SourceSpan::default()); - assert_eq!(emitter.stack()[0], three); - assert_eq!(emitter.stack()[1], five); - assert_eq!(emitter.stack()[2], five); - assert_eq!(emitter.stack()[3], four); - assert_eq!(emitter.stack()[4], three); - assert_eq!(emitter.stack()[5], two); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 8); - assert_eq!(ops[6].into_inner(), Op::Dup(1)); - assert_eq!(ops[7].into_inner(), Op::Dup(6)); - } - - assert_eq!(emitter.stack().effective_index(1), 1); - emitter.swap(1, SourceSpan::default()); - assert_eq!(emitter.stack().effective_index(1), 2); - assert_eq!(emitter.stack()[0], five); - assert_eq!(emitter.stack()[1], three); - assert_eq!(emitter.stack()[2], five); - assert_eq!(emitter.stack()[3], four); - assert_eq!(emitter.stack()[4], three); - assert_eq!(emitter.stack()[5], two); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 9); - assert_eq!(ops[7].into_inner(), Op::Dup(6)); - assert_eq!(ops[8].into_inner(), Op::Movdn(2)); - } - - assert_eq!(emitter.stack().effective_index(3), 5); - emitter.swap(3, SourceSpan::default()); - assert_eq!(emitter.stack()[0], four); - assert_eq!(emitter.stack()[1], three); - assert_eq!(emitter.stack()[2], five); - assert_eq!(emitter.stack()[3], five); - assert_eq!(emitter.stack()[4], three); - assert_eq!(emitter.stack()[5], two); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 13); - assert_eq!(ops[8].into_inner(), Op::Movdn(2)); // [five_a, five_b, three, five_c, five_d, four_a, four_b] - assert_eq!(ops[9].into_inner(), Op::Movdn(6)); // [five_b, three, five_c, five_d, four_a, four_b, five_a] - assert_eq!(ops[10].into_inner(), Op::Movdn(6)); // [three, five_c, five_d, four_a, four_b, five_a, five_b] - assert_eq!(ops[11].into_inner(), Op::Movup(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[12].into_inner(), Op::Movup(4)); // [four_a, four_b, three, five_c, - // five_d, - // five_a, - // five_b] - } - - emitter.movdn(2, SourceSpan::default()); - assert_eq!(emitter.stack()[0], three); - assert_eq!(emitter.stack()[1], five); - assert_eq!(emitter.stack()[2], four); - assert_eq!(emitter.stack()[3], five); - assert_eq!(emitter.stack()[4], three); - assert_eq!(emitter.stack()[5], two); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 15); - assert_eq!(ops[9].into_inner(), Op::Movdn(6)); // [five_b, three, five_c, five_d, four_a, four_b, five_a] - assert_eq!(ops[10].into_inner(), Op::Movdn(6)); // [three, five_c, five_d, four_a, four_b, five_a, five_b] - assert_eq!(ops[11].into_inner(), Op::Movup(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[12].into_inner(), Op::Movup(4)); // [four_a, four_b, three, five_c, five_d, five_a, five_b] - assert_eq!(ops[13].into_inner(), Op::Movdn(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[14].into_inner(), Op::Movdn(4)); // [three, five_c, five_d, four_a, - // four_b, - // five_a, - // five_b] - } - - emitter.movup(2, SourceSpan::default()); - assert_eq!(emitter.stack()[0], four); - assert_eq!(emitter.stack()[1], three); - assert_eq!(emitter.stack()[2], five); - assert_eq!(emitter.stack()[3], five); - assert_eq!(emitter.stack()[4], three); - assert_eq!(emitter.stack()[5], two); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 17); - assert_eq!(ops[13].into_inner(), Op::Movdn(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[14].into_inner(), Op::Movdn(4)); // [three, five_c, five_d, four_a, four_b, five_a, five_b] - assert_eq!(ops[15].into_inner(), Op::Movup(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[16].into_inner(), Op::Movup(4)); // [four_a, four_b, three, five_c, - // five_d, - // five_a, - // five_b] - } - - emitter.drop(SourceSpan::default()); - assert_eq!(emitter.stack()[0], three); - assert_eq!(emitter.stack()[1], five); - assert_eq!(emitter.stack()[2], five); - assert_eq!(emitter.stack()[3], three); - assert_eq!(emitter.stack()[4], two); - assert_eq!(emitter.stack()[5], one); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 19); - assert_eq!(ops[15].into_inner(), Op::Movup(4)); // [four_b, three, five_c, five_d, four_a, five_a, five_b] - assert_eq!(ops[16].into_inner(), Op::Movup(4)); // [four_a, four_b, three, five_c, five_d, five_a, five_b] - assert_eq!(ops[17].into_inner(), Op::Drop); // [four_b, three, five_c, five_d, five_a, five_b] - assert_eq!(ops[18].into_inner(), Op::Drop); // [three, five_c, five_d, five_a, five_b] - } - - emitter.copy_operand_to_position(5, 3, false, SourceSpan::default()); - assert_eq!(emitter.stack()[0], three); - assert_eq!(emitter.stack()[1], five); - assert_eq!(emitter.stack()[2], five); - assert_eq!(emitter.stack()[3], one); - assert_eq!(emitter.stack()[4], three); - assert_eq!(emitter.stack()[5], two); - - emitter.drop_operand_at_position(4, SourceSpan::default()); - assert_eq!(emitter.stack()[0], three); - assert_eq!(emitter.stack()[1], five); - assert_eq!(emitter.stack()[2], five); - assert_eq!(emitter.stack()[3], one); - assert_eq!(emitter.stack()[4], two); - - emitter.move_operand_to_position(4, 2, false, SourceSpan::default()); - assert_eq!(emitter.stack()[0], three); - assert_eq!(emitter.stack()[1], five); - assert_eq!(emitter.stack()[2], two); - assert_eq!(emitter.stack()[3], five); - assert_eq!(emitter.stack()[4], one); - } - - #[test] - fn op_emitter_copy_operand_to_position_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let v14 = hir::Value::from_u32(14); - let v13 = hir::Value::from_u32(13); - let v15 = hir::Value::from_u32(15); - let v11 = hir::Value::from_u32(11); - let v10 = hir::Value::from_u32(10); - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - - emitter.push(TypedValue { - value: v2, - ty: Type::U32, - }); - emitter.push(TypedValue { - value: v1, - ty: Type::U32, - }); - emitter.push(TypedValue { - value: v10, - ty: Type::U32, - }); - emitter.push(TypedValue { - value: v11, - ty: Type::U32, - }); - emitter.push(TypedValue { - value: v15, - ty: Type::U32, - }); - emitter.push(TypedValue { - value: v13, - ty: Type::U32, - }); - emitter.push(TypedValue { - value: v14, - ty: Type::U32, - }); - - assert_eq!(emitter.stack().find(&v10), Some(4)); - - assert_eq!(emitter.stack()[4], v10); - assert_eq!(emitter.stack()[2], v15); - emitter.copy_operand_to_position(4, 2, false, SourceSpan::default()); - assert_eq!(emitter.stack()[5], v10); - assert_eq!(emitter.stack()[2], v10); - - { - let block = emitter.current_block(); - let ops = block.ops.as_slice(); - assert_eq!(ops.len(), 2); - assert_eq!(ops[0].into_inner(), Op::Dup(4)); - assert_eq!(ops[1].into_inner(), Op::Movdn(2)); - } - } - - #[test] - fn op_emitter_u32_add_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.add_imm(one, Overflow::Checked, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.add(Overflow::Checked, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - - emitter.add_imm(one, Overflow::Overflowing, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::I1); - - emitter.swap(1, SourceSpan::default()); - emitter.drop(SourceSpan::default()); - emitter.dup(0, SourceSpan::default()); - emitter.add(Overflow::Overflowing, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::I1); - } - - #[test] - fn op_emitter_u32_sub_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.sub_imm(one, Overflow::Checked, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.sub(Overflow::Checked, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - - emitter.sub_imm(one, Overflow::Overflowing, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::I1); - - emitter.swap(1, SourceSpan::default()); - emitter.drop(SourceSpan::default()); - emitter.dup(0, SourceSpan::default()); - emitter.sub(Overflow::Overflowing, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::I1); - } - - #[test] - fn op_emitter_u32_mul_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.mul_imm(one, Overflow::Checked, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.mul(Overflow::Checked, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - - emitter.mul_imm(one, Overflow::Overflowing, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::I1); - - emitter.swap(1, SourceSpan::default()); - emitter.drop(SourceSpan::default()); - emitter.dup(0, SourceSpan::default()); - emitter.mul(Overflow::Overflowing, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::I1); - } - - #[test] - fn op_emitter_u32_eq_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.eq_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], one); - - emitter.assert(None, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], one); - - emitter.dup(0, SourceSpan::default()); - emitter.eq(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_u32_neq_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.neq_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], one); - - emitter.assertz(None, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], one); - - emitter.dup(0, SourceSpan::default()); - emitter.neq(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_i1_and_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let t = Immediate::I1(true); - let f = Immediate::I1(false); - - emitter.literal(t, SourceSpan::default()); - emitter.literal(f, SourceSpan::default()); - - emitter.and_imm(t, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], t); - - emitter.and(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_i1_or_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let t = Immediate::I1(true); - let f = Immediate::I1(false); - - emitter.literal(t, SourceSpan::default()); - emitter.literal(f, SourceSpan::default()); - - emitter.or_imm(t, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], t); - - emitter.or(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_i1_xor_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let t = Immediate::I1(true); - let f = Immediate::I1(false); - - emitter.literal(t, SourceSpan::default()); - emitter.literal(f, SourceSpan::default()); - - emitter.xor_imm(t, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], t); - - emitter.xor(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_i1_not_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let t = Immediate::I1(true); - - emitter.literal(t, SourceSpan::default()); - - emitter.not(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_u32_gt_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.gt_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], one); - - emitter.drop(SourceSpan::default()); - emitter.dup(0, SourceSpan::default()); - emitter.gt(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_u32_gte_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.gte_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], one); - - emitter.drop(SourceSpan::default()); - emitter.dup(0, SourceSpan::default()); - emitter.gte(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_u32_lt_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.lt_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], one); - - emitter.drop(SourceSpan::default()); - emitter.dup(0, SourceSpan::default()); - emitter.lt(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_u32_lte_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.lte_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I1); - assert_eq!(emitter.stack()[1], one); - - emitter.drop(SourceSpan::default()); - emitter.dup(0, SourceSpan::default()); - emitter.lte(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_u32_checked_div_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.checked_div_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.checked_div(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_unchecked_div_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.unchecked_div_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.unchecked_div(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_checked_mod_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.checked_mod_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.checked_mod(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_unchecked_mod_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.unchecked_mod_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.unchecked_mod(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_checked_divmod_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.checked_divmod_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 3); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::U32); - assert_eq!(emitter.stack()[2], one); - - emitter.checked_divmod(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 3); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::U32); - assert_eq!(emitter.stack()[2], one); - } - - #[test] - fn op_emitter_u32_unchecked_divmod_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.unchecked_divmod_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 3); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::U32); - assert_eq!(emitter.stack()[2], one); - - emitter.unchecked_divmod(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 3); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], Type::U32); - assert_eq!(emitter.stack()[2], one); - } - - #[test] - fn op_emitter_u32_exp_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.exp_imm(two, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.exp(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_band_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.band_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.band(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_bor_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.bor_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.bor(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_bxor_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.bxor_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.bxor(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_shl_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.shl_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.shl(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_shr_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.shr_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.shr(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_rotl_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.rotl_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.rotl(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_rotr_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.rotr_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.rotr(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_min_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.min_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.min(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_max_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - - emitter.max_imm(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::U32); - assert_eq!(emitter.stack()[1], one); - - emitter.max(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_trunc_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let max = Immediate::U32(u32::MAX); - - emitter.literal(max, SourceSpan::default()); - - emitter.trunc(&Type::U16, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U16); - } - - #[test] - fn op_emitter_u32_zext_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let one = Immediate::U16(1); - - emitter.literal(one, SourceSpan::default()); - - emitter.zext(&Type::U32, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_sext_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let num = Immediate::I16(-128); - - emitter.literal(num, SourceSpan::default()); - - emitter.sext(&Type::I32, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I32); - } - - #[test] - fn op_emitter_u32_cast_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let num = Immediate::U32(128); - - emitter.literal(num, SourceSpan::default()); - - emitter.cast(&Type::I32, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I32); - } - - #[test] - fn op_emitter_u32_inttoptr_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let addr = Immediate::U32(128); - let ptr = Type::Ptr(Box::new(Type::Array(Box::new(Type::U64), 8))); - - emitter.literal(addr, SourceSpan::default()); - - emitter.inttoptr(&ptr, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], ptr); - } - - #[test] - fn op_emitter_u32_is_odd_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let num = Immediate::U32(128); - - emitter.literal(num, SourceSpan::default()); - - emitter.is_odd(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::I1); - } - - #[test] - fn op_emitter_u32_popcnt_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let num = Immediate::U32(128); - - emitter.literal(num, SourceSpan::default()); - - emitter.popcnt(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_bnot_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let num = Immediate::U32(128); - - emitter.literal(num, SourceSpan::default()); - - emitter.bnot(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_pow2_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let ten = Immediate::U32(10); - - emitter.literal(ten, SourceSpan::default()); - - emitter.pow2(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_incr_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let ten = Immediate::U32(10); - - emitter.literal(ten, SourceSpan::default()); - - emitter.incr(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_inv_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let ten = Immediate::Felt(Felt::new(10)); - - emitter.literal(ten, SourceSpan::default()); - - emitter.inv(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::Felt); - } - - #[test] - fn op_emitter_neg_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let ten = Immediate::Felt(Felt::new(10)); - - emitter.literal(ten, SourceSpan::default()); - - emitter.neg(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::Felt); - } - - #[test] - fn op_emitter_u32_assert_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let ten = Immediate::U32(10); - - emitter.literal(ten, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - - emitter.assert(None, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 0); - } - - #[test] - fn op_emitter_u32_assertz_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let ten = Immediate::U32(10); - - emitter.literal(ten, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - - emitter.assertz(None, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 0); - } - - #[test] - fn op_emitter_u32_assert_eq_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let ten = Immediate::U32(10); - - emitter.literal(ten, SourceSpan::default()); - emitter.literal(ten, SourceSpan::default()); - emitter.literal(ten, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 3); - - emitter.assert_eq_imm(ten, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - - emitter.assert_eq(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 0); - } - - #[test] - fn op_emitter_u32_select_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let t = Immediate::I1(true); - let one = Immediate::U32(1); - let two = Immediate::U32(2); - - emitter.literal(one, SourceSpan::default()); - emitter.literal(two, SourceSpan::default()); - emitter.literal(t, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 3); - - emitter.select(SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - } - - #[test] - fn op_emitter_u32_exec_test() { - use midenc_hir::ExternalFunction; - - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let return_ty = Type::Array(Box::new(Type::U32), 1); - let callee = ExternalFunction { - id: "test::add".parse().unwrap(), - signature: Signature::new( - [AbiParam::new(Type::U32), AbiParam::new(Type::I1)], - [AbiParam::new(return_ty.clone())], - ), - }; - - let t = Immediate::I1(true); - let one = Immediate::U32(1); - - emitter.literal(t, SourceSpan::default()); - emitter.literal(one, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - - emitter.exec(&callee, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], return_ty); - } - - #[test] - fn op_emitter_u32_load_test() { - let mut function = setup(); - let entry = function.body.id(); - let mut stack = OperandStack::default(); - let mut emitter = OpEmitter::new(&mut function, entry, &mut stack); - - let addr = Type::Ptr(Box::new(Type::U8)); - - emitter.push(addr); - assert_eq!(emitter.stack_len(), 1); - - emitter.load(Type::U32, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 1); - assert_eq!(emitter.stack()[0], Type::U32); - - emitter.load_imm(128, Type::I32, SourceSpan::default()); - assert_eq!(emitter.stack_len(), 2); - assert_eq!(emitter.stack()[0], Type::I32); - assert_eq!(emitter.stack()[1], Type::U32); - } - - #[inline] - fn setup() -> Function { - Function::new( - "test::test".parse().unwrap(), - Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]), - ) - } -} diff --git a/codegen/masm/src/codegen/emit/primop.rs b/codegen/masm/src/codegen/emit/primop.rs deleted file mode 100644 index f6cb2a181..000000000 --- a/codegen/masm/src/codegen/emit/primop.rs +++ /dev/null @@ -1,343 +0,0 @@ -use midenc_hir::{ - self as hir, diagnostics::SourceSpan, ArgumentExtension, ArgumentPurpose, Felt, FieldElement, - Immediate, Type, -}; - -use super::{int64, OpEmitter}; -use crate::masm::Op; - -impl<'a> OpEmitter<'a> { - /// Assert that an integer value on the stack has the value 1 - /// - /// This operation consumes the input value. - pub fn assert(&mut self, code: Option, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let code = code.unwrap_or_default(); - match arg.ty() { - Type::Felt - | Type::U32 - | Type::I32 - | Type::U16 - | Type::I16 - | Type::U8 - | Type::I8 - | Type::I1 => { - self.emit(Op::AssertWithError(code), span); - } - Type::I128 | Type::U128 => { - self.emit_all( - &[ - Op::Pushw([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]), - Op::AssertEqwWithError(code), - ], - span, - ); - } - Type::U64 | Type::I64 => { - self.emit_all(&[Op::AssertzWithError(code), Op::AssertWithError(code)], span); - } - ty if !ty.is_integer() => { - panic!("invalid argument to assert: expected integer, got {ty}") - } - ty => unimplemented!("support for assert on {ty} is not implemented"), - } - } - - /// Assert that an integer value on the stack has the value 0 - /// - /// This operation consumes the input value. - pub fn assertz(&mut self, code: Option, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let code = code.unwrap_or_default(); - match arg.ty() { - Type::Felt - | Type::U32 - | Type::I32 - | Type::U16 - | Type::I16 - | Type::U8 - | Type::I8 - | Type::I1 => { - self.emit(Op::AssertzWithError(code), span); - } - Type::U64 | Type::I64 => { - self.emit_all(&[Op::AssertzWithError(code), Op::AssertzWithError(code)], span); - } - Type::U128 | Type::I128 => { - self.emit_all(&[Op::Pushw([Felt::ZERO; 4]), Op::AssertEqwWithError(code)], span); - } - ty if !ty.is_integer() => { - panic!("invalid argument to assertz: expected integer, got {ty}") - } - ty => unimplemented!("support for assertz on {ty} is not implemented"), - } - } - - /// Assert that the top two integer values on the stack have the same value - /// - /// This operation consumes the input values. - pub fn assert_eq(&mut self, span: SourceSpan) { - let rhs = self.pop().expect("operand stack is empty"); - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, rhs.ty(), "expected assert_eq operands to have the same type"); - match ty { - Type::Felt - | Type::U32 - | Type::I32 - | Type::U16 - | Type::I16 - | Type::U8 - | Type::I8 - | Type::I1 => { - self.emit(Op::AssertEq, span); - } - Type::U128 | Type::I128 => self.emit(Op::AssertEqw, span), - Type::U64 | Type::I64 => { - self.emit_all( - &[ - // compare the hi bits - Op::Movup(2), - Op::AssertEq, - // compare the low bits - Op::AssertEq, - ], - span, - ); - } - ty if !ty.is_integer() => { - panic!("invalid argument to assert_eq: expected integer, got {ty}") - } - ty => unimplemented!("support for assert_eq on {ty} is not implemented"), - } - } - - /// Emit code to assert that an integer value on the stack has the same value - /// as the provided immediate. - /// - /// This operation consumes the input value. - pub fn assert_eq_imm(&mut self, imm: Immediate, span: SourceSpan) { - let lhs = self.pop().expect("operand stack is empty"); - let ty = lhs.ty(); - assert_eq!(ty, imm.ty(), "expected assert_eq_imm operands to have the same type"); - match ty { - Type::Felt - | Type::U32 - | Type::I32 - | Type::U16 - | Type::I16 - | Type::U8 - | Type::I8 - | Type::I1 => { - self.emit_all(&[Op::EqImm(imm.as_felt().unwrap()), Op::Assert], span); - } - Type::I128 | Type::U128 => { - self.push_immediate(imm, span); - self.emit(Op::AssertEqw, span) - } - Type::I64 | Type::U64 => { - let imm = match imm { - Immediate::I64(i) => i as u64, - Immediate::U64(i) => i, - _ => unreachable!(), - }; - let (hi, lo) = int64::to_raw_parts(imm); - self.emit_all( - &[ - Op::EqImm(Felt::new(hi as u64)), - Op::Assert, - Op::EqImm(Felt::new(lo as u64)), - Op::Assert, - ], - span, - ) - } - ty if !ty.is_integer() => { - panic!("invalid argument to assert_eq: expected integer, got {ty}") - } - ty => unimplemented!("support for assert_eq on {ty} is not implemented"), - } - } - - /// Emit code to select between two values of the same type, based on a boolean condition. - /// - /// The semantics of this instruction are basically the same as Miden's `cdrop` instruction, - /// but with support for selecting between any of the representable integer/pointer types as - /// values. Given three values on the operand stack (in order of appearance), `c`, `b`, and - /// `a`: - /// - /// * Pop `c` from the stack. This value must be an i1/boolean, or execution will trap. - /// * Pop `b` and `a` from the stack, and push back `b` if `c` is true, or `a` if `c` is false. - /// - /// This operation will assert that the selected value is a valid value for the given type. - pub fn select(&mut self, span: SourceSpan) { - let c = self.stack.pop().expect("operand stack is empty"); - let b = self.stack.pop().expect("operand stack is empty"); - let a = self.stack.pop().expect("operand stack is empty"); - assert_eq!(c.ty(), Type::I1, "expected selector operand to be an i1"); - let ty = a.ty(); - assert_eq!(ty, b.ty(), "expected selections to be of the same type"); - match &ty { - Type::Felt - | Type::U32 - | Type::I32 - | Type::U16 - | Type::I16 - | Type::U8 - | Type::I8 - | Type::I1 => self.emit(Op::Cdrop, span), - Type::I128 | Type::U128 => self.emit(Op::Cdropw, span), - Type::I64 | Type::U64 => { - // Perform two conditional drops, one for each 32-bit limb - // corresponding to the value which is being selected - self.emit_all( - &[ - // stack starts as [c, b_hi, b_lo, a_hi, a_lo] - Op::Dup(0), // [c, c, b_hi, b_lo, a_hi, a_lo] - Op::Movdn(6), // [c, b_hi, b_lo, a_hi, a_lo, c] - Op::Movup(3), // [a_hi, c, b_hi, b_lo, a_lo, c] - Op::Movup(2), // [b_hi, a_hi, c, b_lo, a_lo, c] - Op::Movup(6), // [c, b_hi, a_hi, c, b_lo, a_lo] - Op::Cdrop, // [d_hi, c, b_lo, a_lo] - Op::Movdn(4), // [c, b_lo, a_lo, d_hi] - Op::Cdrop, // [d_lo, d_hi] - Op::Swap(1), // [d_hi, d_lo] - ], - span, - ); - } - ty if !ty.is_integer() => { - panic!("invalid argument to assert_eq: expected integer, got {ty}") - } - ty => unimplemented!("support for assert_eq on {ty} is not implemented"), - } - self.push(ty); - } - - /// Execute the given procedure. - /// - /// A function called using this operation is invoked in the same memory context as the caller. - pub fn exec(&mut self, callee: &hir::ExternalFunction, span: SourceSpan) { - let import = callee; - let callee = import.id; - let signature = &import.signature; - for i in 0..signature.arity() { - let param = &signature.params[i]; - let arg = self.stack.pop().expect("operand stack is empty"); - let ty = arg.ty(); - // Validate the purpose matches - match param.purpose { - ArgumentPurpose::StructReturn => { - assert_eq!( - i, 0, - "invalid function signature: sret parameters must be the first parameter, \ - and only one sret parameter is allowed" - ); - assert_eq!( - signature.results.len(), - 0, - "invalid function signature: a function with sret parameters cannot also \ - have results" - ); - assert!( - ty.is_pointer(), - "invalid exec to {callee}: invalid argument for sret parameter, expected \ - {}, got {ty}", - ¶m.ty - ); - } - ArgumentPurpose::Default => (), - } - // Validate that the argument type is valid for the parameter ABI - match param.extension { - // Types must match exactly - ArgumentExtension::None => { - assert_eq!( - ty, param.ty, - "invalid call to {callee}: invalid argument type for parameter at index \ - {i}" - ); - } - // Caller can provide a smaller type which will be zero-extended to the expected - // type - // - // However, the argument must be an unsigned integer, and of smaller or equal size - // in order for the types to differ - ArgumentExtension::Zext if ty != param.ty => { - assert!( - param.ty.is_unsigned_integer(), - "invalid function signature: zero-extension is only valid for unsigned \ - integer types" - ); - assert!( - ty.is_unsigned_integer(), - "invalid call to {callee}: invalid argument type for parameter at index \ - {i}, expected unsigned integer type, got {ty}" - ); - let expected_size = param.ty.size_in_bits(); - let provided_size = param.ty.size_in_bits(); - assert!( - provided_size <= expected_size, - "invalid call to {callee}: invalid argument type for parameter at index \ - {i}, expected integer width to be <= {expected_size} bits" - ); - // Zero-extend this argument - self.stack.push(arg); - self.zext(¶m.ty, span); - self.stack.drop(); - } - // Caller can provide a smaller type which will be sign-extended to the expected - // type - // - // However, the argument must be an integer which can fit in the range of the - // expected type - ArgumentExtension::Sext if ty != param.ty => { - assert!( - param.ty.is_signed_integer(), - "invalid function signature: sign-extension is only valid for signed \ - integer types" - ); - assert!( - ty.is_integer(), - "invalid call to {callee}: invalid argument type for parameter at index \ - {i}, expected integer type, got {ty}" - ); - let expected_size = param.ty.size_in_bits(); - let provided_size = param.ty.size_in_bits(); - if ty.is_unsigned_integer() { - assert!( - provided_size < expected_size, - "invalid call to {callee}: invalid argument type for parameter at \ - index {i}, expected unsigned integer width to be < {expected_size} \ - bits" - ); - } else { - assert!( - provided_size <= expected_size, - "invalid call to {callee}: invalid argument type for parameter at \ - index {i}, expected integer width to be <= {expected_size} bits" - ); - } - // Push the operand back on the stack for `sext` - self.stack.push(arg); - self.sext(¶m.ty, span); - self.stack.drop(); - } - ArgumentExtension::Zext | ArgumentExtension::Sext => (), - } - } - - for result in signature.results.iter().rev() { - self.push(result.ty.clone()); - } - - self.emit(Op::Exec(callee), span); - } - - /// Execute the given procedure as a syscall. - /// - /// A function called using this operation is invoked in the same memory context as the caller. - pub fn syscall(&mut self, _callee: &hir::ExternalFunction, _span: SourceSpan) { - todo!() - } -} diff --git a/codegen/masm/src/codegen/emit/unary.rs b/codegen/masm/src/codegen/emit/unary.rs deleted file mode 100644 index 6387c567a..000000000 --- a/codegen/masm/src/codegen/emit/unary.rs +++ /dev/null @@ -1,1000 +0,0 @@ -use midenc_hir::{Felt, FieldElement, Overflow}; - -use super::*; - -impl<'a> OpEmitter<'a> { - /// Truncate an integral value of type `src` to type `dst` - /// - /// Truncation is defined in terms of the bitwise representation of the type. - /// Specifically, the source value will have any excess bits above the bitwidth of - /// the `dst` type either zeroed, or dropped, depending on the `src` type. For example, - /// u64 is represented as two 32-bit limbs, each in a field element on the operand stack; - /// while u16 is represented as 16 bits in a single field element. Truncating from u64 - /// to u16 results in dropping the 32-bit limb containing the most significant bits, and - /// then masking out the most significant 16 bits of the remaining 32-bit limb, leaving - /// a 16-bit value on the operand stack. - /// - /// NOTE: Field elements do not have a formal bitwise representation. When truncating to - /// felt and the source value is negative, the resulting felt will be `Felt::ZERO`. When - /// the value is non-negative, the source value will be mapped to the field element range - /// using the field modulus of `2^64 - 2^32 + 1`, and then convert the representation to - /// felt by multiplying the 32-bit limbs (the only values which can be truncated to felt - /// are u64, i64, and i128, all of which use multiple 32-bit limbs). - /// - /// This function assumes that an integer value of type `src` is on top of the operand stack, - /// and will ensure a value of type `dst` is on the operand stack after truncation, or that - /// execution traps. - pub fn trunc(&mut self, dst: &Type, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let src = arg.ty(); - assert!( - src.is_integer() && dst.is_integer(), - "invalid truncation of {src} to {dst}: only integer-to-integer casts are supported" - ); - let n = dst.size_in_bits() as u32; - match (&src, dst) { - // If the types are equivalent, it's a no-op - (src, dst) if src == dst => (), - (Type::Felt, _) if n <= 32 => self.trunc_felt(n, span), - // Truncating i128 to u128, and vice versa is a bitcast - (Type::I128 | Type::U128, Type::U128 | Type::I128) => (), - // Truncating to felt - (Type::U128 | Type::I128, Type::Felt) => self.trunc_i128_to_felt(span), - // Truncating a 128-bit integer to 64 bits or smaller - (Type::U128 | Type::I128, _) if n <= 64 => self.trunc_i128(n, span), - // Truncating i64/u64 to felt - (Type::I64 | Type::U64, Type::Felt) => self.trunc_int64_to_felt(span), - // Truncating i64 to u64, and vice versa is a bitcast - (Type::I64 | Type::U64, Type::U64 | Type::I64) => (), - // Truncating a u64/i64 to 32 bits or smaller - (Type::I64 | Type::U64, _) if n <= 32 => self.trunc_int64(n, span), - // Truncating a felt to 32 bits or smaller - (Type::Felt, _) if n <= 32 => self.trunc_felt(n, span), - // Truncating i32 to u32, and vice versa is a bitcast - (Type::I32 | Type::U32, Type::U32 | Type::I32) => (), - // Truncating an i32/u32 to smaller than 32 bits - (Type::I32 | Type::U32, _) if n <= 32 => self.trunc_int32(n, span), - // Truncating i16 to u16, and vice versa is a bitcast - (Type::I16 | Type::U16, Type::U16 | Type::I16) => (), - // Truncating an i16/u16 to smaller than 16 bits - (Type::I16 | Type::U16, _) if n <= 16 => self.trunc_int32(n, span), - // Truncating i8 to u8, and vice versa is a bitcast - (Type::I8 | Type::U8, Type::U8 | Type::I8) => (), - // Truncating an i8/u8 to smaller than 8 bits - (Type::I8 | Type::U8, _) if n <= 8 => self.trunc_int32(n, span), - (src, dst) => unimplemented!("unsupported truncation of {src} to {dst}"), - } - self.push(dst.clone()); - } - - /// Zero-extend an unsigned integral value of type `src` to type `dst` - /// - /// This function will panic if the source or target types are not unsigned integral types. - /// Despite its type name, i1 is an unsigned value, because it may only represent 1 or 0. - /// - /// Zero-extension is defined in terms of the bitwise representation of the type. - /// Specifically, the source value will have any excess bits above the bitwidth of - /// the `src` type either added as zeroes, or set to zero, depending on the `dst` type. - /// For example, u16 is represented as 16 bits in a single field element, while u64 is - /// represented as two 32-bit limbs, each in a separate field element. Zero-extending - /// from u16 to u64 requires only pushing a new element of `Felt::ZERO` on the operand stack. - /// Since the upper 16 bits of the original 32-bit field element value must already be zero, - /// we only needed to pad out the representation with an extra zero element to obtain the - /// corresponding u64. - /// - /// NOTE: Field elements do not have a formal bitwise representation. However, types with a - /// bitwidth of 32 bits or smaller are transparently represented as field elements in the VM, - /// so zero-extending to felt from such a type is a no-op. Even though a field element is - /// notionally a 64-bit value in memory, it is not equivalent in range to a 64-bit integer, - /// so 64-bit integers and above require the use of multiple 32-bit limbs, to provide a two's - /// complement bitwise representation; so there are no types larger than 32-bits that are - /// zero-extendable to felt, but are not representable as a felt transparently. - /// - /// This function assumes that an integer value of type `src` is on top of the operand stack, - /// and will ensure a value of type `dst` is on the operand stack after truncation, or that - /// execution traps. - pub fn zext(&mut self, dst: &Type, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let src = arg.ty(); - let src_bits = src.size_in_bits() as u32; - let dst_bits = dst.size_in_bits() as u32; - assert!( - src_bits <= dst_bits, - "invalid zero-extension from {src} to {dst}: cannot zero-extend to a smaller type" - ); - match (&src, dst) { - // If the types are equivalent, it's a no-op, but only if they are integers - (src, dst) if src == dst => (), - // Zero-extending a u64 to i128 simply requires pushing a 0u64 on the stack - (Type::U64, Type::U128 | Type::I128) => self.push_u64(0, span), - (Type::Felt, Type::U64 | Type::U128 | Type::I128) => self.zext_felt(dst_bits, span), - (Type::U32, Type::U64 | Type::I64 | Type::U128 | Type::I128) => { - self.zext_int32(dst_bits, span) - } - (Type::I1 | Type::U8 | Type::U16, Type::U64 | Type::I64 | Type::U128 | Type::I128) => { - self.zext_smallint(src_bits, dst_bits, span) - } - // Zero-extending to u32/i32 from smaller integers is a no-op - (Type::I1 | Type::U8 | Type::U16, Type::U32 | Type::I32) => (), - // Zero-extending to felt, from types that fit in felt, is a no-op - (Type::I1 | Type::U8 | Type::U16 | Type::U32, Type::Felt) => (), - (src, dst) if dst.is_signed_integer() => panic!( - "invalid zero-extension from {src} to {dst}: value may not fit in range, use \ - explicit cast instead" - ), - (src, dst) => panic!("unsupported zero-extension from {src} to {dst}"), - } - self.push(dst.clone()); - } - - /// Sign-extend an integral value of type `src` to type `dst` - /// - /// This function will panic if the target type is not a signed integral type. - /// To extend unsigned integer types to a larger unsigned integer type, use `zext`. - /// To extend signed integer types to an equal or larger unsigned type, use `cast`. - /// - /// Sign-extension is defined in terms of the bitwise representation of the type. - /// Specifically, the sign bit of the source value will be propagated to all excess - /// bits added to the representation of `src` to represent `dst`. - /// - /// For example, i16 is represented as 16 bits in a single field element, while i64 is - /// represented as two 32-bit limbs, each in a separate field element. Sign-extending - /// the i16 value -128, to i64, requires propagating the sign bit value, 1 since -128 - /// is a negative number, to the most significant 32-bits of the input element, as well - /// as pushing an additional element representing `u32::MAX` on the operand stack. This - /// gives us a bitwise representation where the most significant 48 bits are all 1s, and - /// the last 16 bits are the same as the original input value, giving us the i64 - /// representation of -128. - /// - /// NOTE: Field elements cannot be sign-extended to i64, you must an explicit cast, as the - /// range of the field means that it is not guaranteed that the felt will fit in the i64 - /// range. - /// - /// This function assumes that an integer value of type `src` is on top of the operand stack, - /// and will ensure a value of type `dst` is on the operand stack after truncation, or that - /// execution traps. - pub fn sext(&mut self, dst: &Type, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let src = arg.ty(); - assert!( - src.is_integer() && dst.is_signed_integer(), - "invalid sign-extension of {src} to {dst}: only integer-to-signed-integer casts are \ - supported" - ); - let src_bits = src.size_in_bits() as u32; - let dst_bits = dst.size_in_bits() as u32; - assert!( - src_bits <= dst_bits, - "invalid zero-extension from {src} to {dst}: cannot zero-extend to a smaller type" - ); - match (&src, dst) { - // If the types are equivalent, it's a no-op - (src, dst) if src == dst => (), - (Type::U64 | Type::I64, Type::I128) => self.sext_int64(128, span), - (Type::Felt, Type::I64 | Type::I128) => self.sext_felt(dst_bits, span), - (Type::I32 | Type::U32, Type::I64 | Type::I128) => self.sext_int32(dst_bits, span), - ( - Type::I1 | Type::I8 | Type::U8 | Type::I16 | Type::U16, - Type::I32 | Type::I64 | Type::I128, - ) => self.sext_smallint(src_bits, dst_bits, span), - (src, dst) => panic!("unsupported sign-extension from {src} to {dst}"), - } - self.push(dst.clone()); - } - - pub fn bitcast(&mut self, dst: &Type, _span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let src = arg.ty(); - assert!( - src.is_integer() && dst.is_integer(), - "invalid cast of {src} to {dst}: only integer-to-integer bitcasts are supported" - ); - self.push(dst.clone()); - } - - /// Convert between two integral types, given as `src` and `dst`, - /// indicating the direction of the conversion. - /// - /// This function will panic if either type is not an integer. - /// - /// The specific semantics of a cast are dependent on the pair of types involved, - /// but the general rules are as follows: - /// - /// * Any integer-to-integer cast is allowed - /// * Casting a signed integer to an unsigned integer will assert that the input value is - /// unsigned - /// * Casting a type with a larger range to a type with a smaller one will assert that the input - /// value fits within that range, e.g. u8 to i8, i16 to i8, etc. - /// * Casting to a larger unsigned type will use zero-extension - /// * Casting a signed type to a larger signed type will use sign-extension - /// * Casting an unsigned type to a larger signed type will use zero-extension - /// - /// As a rule, the input value must be representable in the target type, or an - /// assertion will be raised. Casts are intended to faithfully translate a value - /// in one type to the same value in another type. - /// - /// This function assumes that an integer value of type `src` is on top of the operand stack, - /// and will ensure a value of type `dst` is on the operand stack after truncation, or that - /// execution traps. - pub fn cast(&mut self, dst: &Type, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let src = arg.ty(); - assert!( - src.is_integer() && dst.is_integer(), - "invalid cast of {src} to {dst}: only integer-to-integer casts are supported" - ); - - let src_bits = src.size_in_bits() as u32; - let dst_bits = dst.size_in_bits() as u32; - match (&src, dst) { - // u128 - (Type::U128, Type::I128) => self.assert_unsigned_int128(span), - (Type::U128, Type::I64) => self.u128_to_i64(span), - (Type::U128 | Type::I128, Type::U64) => self.int128_to_u64(span), - (Type::U128 | Type::I128, Type::Felt) => self.int128_to_felt(span), - (Type::U128 | Type::I128, Type::U32) => self.int128_to_u32(span), - (Type::U128 | Type::I128, Type::U16 | Type::U8 | Type::I1) => { - self.int128_to_u32(span); - self.int32_to_uint(dst_bits, span); - } - (Type::U128, Type::I32) => { - self.int128_to_u32(span); - self.assert_unsigned_int32(span); - } - (Type::U128, Type::I16 | Type::I8) => { - self.int128_to_u32(span); - self.int32_to_int(dst_bits, span); - } - // i128 - (Type::I128, Type::I64) => self.i128_to_i64(span), - (Type::I128, Type::I32 | Type::I16 | Type::I8) => { - self.i128_to_i64(span); - self.i64_to_int(dst_bits, span); - } - // i64 - (Type::I64, Type::I128) => self.sext_int64(128, span), - (Type::I64, Type::U128) => self.zext_int64(128, span), - (Type::I64, Type::U64) => self.assert_unsigned_int64(span), - (Type::I64, Type::Felt) => self.i64_to_felt(span), - (Type::I64, Type::U32 | Type::U16 | Type::U8 | Type::I1) => { - self.assert_unsigned_int64(span); - self.u64_to_uint(dst_bits, span); - } - (Type::I64, Type::I32 | Type::I16 | Type::I8) => { - self.i64_to_int(dst_bits, span); - } - // u64 - (Type::U64, Type::I128 | Type::U128) => self.zext_int64(128, span), - (Type::U64, Type::I64) => self.assert_i64(span), - (Type::U64, Type::Felt) => self.u64_to_felt(span), - (Type::U64, Type::U32 | Type::U16 | Type::U8 | Type::I1) => { - self.u64_to_uint(dst_bits, span); - } - (Type::U64, Type::I32 | Type::I16 | Type::I8) => { - // Convert to N bits as unsigned - self.u64_to_uint(dst_bits, span); - // Verify that the input value is still unsigned - self.assert_unsigned_smallint(dst_bits, span); - } - // felt - (Type::Felt, Type::I64 | Type::I128) => self.sext_felt(dst_bits, span), - (Type::Felt, Type::U128) => self.zext_felt(dst_bits, span), - (Type::Felt, Type::U64) => self.felt_to_u64(span), - (Type::Felt, Type::U32 | Type::U16 | Type::U8 | Type::I1) => { - self.felt_to_uint(dst_bits, span); - } - (Type::Felt, Type::I32 | Type::I16 | Type::I8) => { - self.felt_to_int(dst_bits, span); - } - // u32 - (Type::U32, Type::I64 | Type::U64 | Type::I128) => self.zext_int32(dst_bits, span), - (Type::U32, Type::I32) => self.assert_i32(span), - (Type::U32, Type::U16 | Type::U8 | Type::I1) => { - self.int32_to_uint(dst_bits, span); - } - (Type::U32, Type::I16 | Type::I8) => self.int32_to_int(dst_bits, span), - // i32 - (Type::I32, Type::I64 | Type::I128) => self.sext_int32(dst_bits, span), - (Type::I32, Type::U64) => { - self.assert_i32(span); - self.emit(Op::PushU32(0), span); - } - (Type::I32, Type::U32) => { - self.assert_i32(span); - } - (Type::I32, Type::U16 | Type::U8 | Type::I1) => { - self.int32_to_uint(dst_bits, span); - } - (Type::I32, Type::I16 | Type::I8) => self.int32_to_int(dst_bits, span), - // i8/i16 - (Type::I8 | Type::I16, Type::I32 | Type::I64 | Type::I128) => { - self.sext_smallint(src_bits, dst_bits, span); - } - (Type::I8 | Type::I16, Type::U32 | Type::U64) => { - self.assert_unsigned_smallint(src_bits, span); - self.zext_smallint(src_bits, dst_bits, span); - } - (Type::I16, Type::U16) | (Type::I8, Type::U8) => { - self.assert_unsigned_smallint(src_bits, span); - } - (Type::I16, Type::U8 | Type::I1) => self.int32_to_int(dst_bits, span), - (Type::I16, Type::I8) => self.int32_to_int(dst_bits, span), - (Type::I8, Type::I1) => { - self.emit_all( - &[ - // Assert that input is either 0 or 1 - // - // NOTE: The comparison here is unsigned, so the sign - // bit being set will make the i8 larger than 0 or 1 - Op::Dup(0), - Op::PushU32(2), - Op::Lt, - Op::Assert, - ], - span, - ); - } - // i1 - (Type::I1, _) => self.zext_smallint(src_bits, dst_bits, span), - (src, dst) => unimplemented!("unsupported cast from {src} to {dst}"), - } - self.push(dst.clone()); - } - - /// Cast `arg` to a pointer value - pub fn inttoptr(&mut self, ty: &Type, span: SourceSpan) { - assert!(ty.is_pointer(), "exected pointer typed argument"); - // For now, we're strict about the types of values we'll allow casting from - let arg = self.stack.pop().expect("operand stack is empty"); - match arg.ty() { - // We allow i32 here because Wasm uses it - Type::U32 | Type::I32 => { - self.push(ty.clone()); - } - Type::Felt => { - self.emit(Op::U32Assert, span); - self.push(ty.clone()); - } - int => panic!("invalid inttoptr cast: cannot cast value of type {int} to {ty}"), - } - } - - /// Check if an integral value on the operand stack is an odd number. - /// - /// The result is placed on the stack as a boolean value. - /// - /// This operation consumes the input operand. - pub fn is_odd(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - match arg.ty() { - // For both signed and unsigned types, - // values <= bitwidth of a felt can use - // the native instruction because the sign - // bit does not change whether the value is - // odd or not - Type::I1 - | Type::U8 - | Type::I8 - | Type::U16 - | Type::I16 - | Type::U32 - | Type::I32 - | Type::Felt => { - self.emit(Op::IsOdd, span); - } - // For i64/u64, we use the native instruction - // on the lower limb to check for odd/even - Type::I64 | Type::U64 => { - self.emit_all(&[Op::Drop, Op::IsOdd], span); - } - // For i128, same as above, but more elements are dropped - Type::I128 | Type::U128 => { - self.emit_n(3, Op::Drop, span); - self.emit(Op::IsOdd, span); - } - Type::F64 => { - unimplemented!("is_odd support for floating-point values is not yet implemented") - } - ty => panic!("expected integral type for is_odd opcode, got {ty}"), - } - self.push(Type::I1); - } - - /// Compute the integral base-2 logarithm of the value on top of the operand stack, and - /// place the result back on the operand stack as a u32 value. - /// - /// This operation consumes the input operand. - pub fn ilog2(&mut self, span: SourceSpan) { - let ty = self.stack.peek().expect("operand stack is empty").ty(); - match &ty { - Type::Felt => self.emit(Op::Ilog2, span), - Type::I128 | Type::U128 | Type::I64 | Type::U64 => { - // Compute the number of leading zeros - // - // NOTE: This function handles popping the input and pushing - // a u32 result on the stack for us, so we can omit any stack - // manipulation here. - self.clz(span); - let bits = ty.size_in_bits(); - // ilog2 is bits - clz - 1 - self.emit_all( - &[ - Op::PushU8(bits as u8), - Op::Swap(1), - Op::Sub, - Op::U32OverflowingSubImm(1), - Op::Assertz, - ], - span, - ); - } - Type::I32 | Type::U32 | Type::I16 | Type::U16 | Type::I8 | Type::U8 => { - let _ = self.stack.pop(); - self.emit_all( - &[ - // Compute ilog2 on the advice stack - Op::Ilog2, - // Drop the operand - Op::Drop, - // Move the result to the operand stack - Op::AdvPush(1), - ], - span, - ); - self.push(Type::U32); - } - Type::I1 => { - // 2^0 == 1 - let _ = self.stack.pop(); - self.emit_all(&[Op::Drop, Op::PushU8(0)], span); - self.push(Type::U32); - } - ty if !ty.is_integer() => { - panic!("invalid ilog2 on {ty}: only integral types are supported") - } - ty => unimplemented!("ilog2 for {ty} is not supported"), - } - } - - /// Count the number of non-zero bits in the integral value on top of the operand stack, - /// and place the count back on the stack as a u32 value. - /// - /// This operation consumes the input operand. - pub fn popcnt(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - match arg.ty() { - Type::I128 | Type::U128 => { - self.emit_all( - &[ - // [x3, x2, x1, x0] - Op::U32Popcnt, - // [popcnt3, x2, x1, x0] - Op::Swap(1), - // [x2, popcnt3, x1, x0] - Op::U32Popcnt, - // [popcnt2, popcnt3, x1, x0] - Op::Add, - // [popcnt_hi, x1, x0] - Op::Movdn(2), - // [x1, x0, popcnt] - Op::U32Popcnt, - // [popcnt1, x0, popcnt] - Op::Swap(1), - // [x0, popcnt1, popcnt] - Op::U32Popcnt, - // [popcnt0, popcnt1, popcnt] - // - // This last instruction adds all three values together mod 2^32 - Op::U32WrappingAdd3, - ], - span, - ); - } - Type::I64 | Type::U64 => { - self.emit_all( - &[ - // Get popcnt of high bits - Op::U32Popcnt, - // Swap to low bits and repeat - Op::Swap(1), - Op::U32Popcnt, - // Add both counts to get the total count - Op::Add, - ], - span, - ); - } - Type::I32 | Type::U32 | Type::I16 | Type::U16 | Type::I8 | Type::U8 | Type::I1 => { - self.emit(Op::U32Popcnt, span); - } - ty if !ty.is_integer() => { - panic!("invalid popcnt on {ty}: only integral types are supported") - } - ty => unimplemented!("popcnt for {ty} is not supported"), - } - self.push(Type::U32); - } - - /// Count the number of leading zero bits in the integral value on top of the operand stack, - /// and place the count back on the stack as a u32 value. - /// - /// This operation is implemented so that it consumes the input operand. - pub fn clz(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - match arg.ty() { - Type::I128 | Type::U128 => { - let u64_clz = "std::math::u64::clz".parse().unwrap(); - // We decompose the 128-bit value into two 64-bit limbs, and use the standard - // library intrinsics to get the count for those limbs. We then add the count - // for the low bits to that of the high bits, if the high bits are all zero, - // otherwise we take just the high bit count. - self.emit_all( - &[ - // Count leading zeros in the high bits - Op::Exec(u64_clz), // [hi_clz, lo_hi, lo_lo] - // Count leading zeros in the low bits - Op::Movup(2), // [lo_lo, hi_clz, lo_hi] - Op::Movup(2), // [lo_hi, lo_lo, hi_clz] - Op::Exec(u64_clz), // [lo_clz, hi_clz] - // Add the low bit leading zeros to those of the high bits, if the high - // bits are all zeros; otherwise return only the - // high bit count - Op::PushU32(0), // [0, lo_clz, hi_clz] - Op::Dup(2), // [hi_clz, 0, lo_clz, hi_clz] - Op::LtImm(Felt::new(32)), // [hi_clz < 32, 0, lo_clz, hi_clz] - Op::Cdrop, // [hi_clz < 32 ? 0 : lo_clz, hi_clz] - Op::Add, - ], - span, - ); - } - Type::I64 | Type::U64 => { - self.emit(Op::Exec("std::math::u64::clz".parse().unwrap()), span); - } - Type::I32 | Type::U32 => { - self.emit(Op::U32Clz, span); - } - Type::I16 | Type::U16 => { - // There are always 16 leading zeroes from the perspective of the - // MASM u32clz instruction for values of (i|u)16 type, so subtract - // that from the count - self.emit_all( - &[ - Op::U32Clz, - // Subtract the excess bits from the count - Op::U32WrappingSubImm(16), - ], - span, - ); - } - Type::I8 | Type::U8 => { - // There are always 24 leading zeroes from the perspective of the - // MASM u32clz instruction for values of (i|u)8 type, so subtract - // that from the count - self.emit_all( - &[ - Op::U32Clz, - // Subtract the excess bits from the count - Op::U32WrappingSubImm(24), - ], - span, - ); - } - Type::I1 => { - // There is exactly one leading zero if false, or zero if true - self.emit(Op::EqImm(Felt::ZERO), span); - } - ty if !ty.is_integer() => { - panic!("invalid clz on {ty}: only integral types are supported") - } - ty => unimplemented!("clz for {ty} is not supported"), - } - self.push(Type::U32); - } - - /// Count the number of leading one bits in the integral value on top of the operand stack, - /// and place the count back on the stack as a u32 value. - /// - /// This operation is implemented so that it consumes the input operand. - pub fn clo(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - match arg.ty() { - // The implementation here is effectively the same as `clz`, just with minor adjustments - Type::I128 | Type::U128 => { - let u64_clo = "std::math::u64::clo".parse().unwrap(); - // We decompose the 128-bit value into two 64-bit limbs, and use the standard - // library intrinsics to get the count for those limbs. We then add the count - // for the low bits to that of the high bits, if the high bits are all one, - // otherwise we take just the high bit count. - self.emit_all( - &[ - // Count leading ones in the high bits - Op::Exec(u64_clo), // [hi_clo, lo_hi, lo_lo] - // Count leading ones in the low bits - Op::Movup(2), // [lo_lo, hi_clo, lo_hi] - Op::Movup(2), // [lo_hi, lo_lo, hi_clo] - Op::Exec(u64_clo), // [lo_clo, hi_clo] - // Add the low bit leading ones to those of the high bits, if the high bits - // are all one; otherwise return only the high bit count - Op::PushU32(0), // [0, lo_clo, hi_clo] - Op::Dup(2), // [hi_clo, 0, lo_clo, hi_clo] - Op::LtImm(Felt::new(32)), // [hi_clo < 32, 0, lo_clo, hi_clo] - Op::Cdrop, // [hi_clo < 32 ? 0 : lo_clo, hi_clo] - Op::Add, - ], - span, - ); - } - Type::I64 | Type::U64 => { - self.emit(Op::Exec("std::math::u64::clo".parse().unwrap()), span) - } - Type::I32 | Type::U32 => { - self.emit(Op::U32Clo, span); - } - Type::I16 | Type::U16 => { - // There are always 16 leading zeroes from the perspective of the - // MASM u32clo instruction for values of (i|u)16 type, so to get - // the correct count, we need to bitwise-OR in a 16 bits of leading - // ones, then subtract that from the final count. - self.emit_all( - &[ - // OR in the leading 16 ones - Op::PushU32(u32::MAX << 16), - Op::U32Or, - // Obtain the count - Op::U32Clo, - // Subtract the leading bits we added from the count - Op::U32WrappingSubImm(16), - ], - span, - ); - } - Type::I8 | Type::U8 => { - // There are always 24 leading zeroes from the perspective of the - // MASM u32clo instruction for values of (i|u)8 type, so as with the - // 16-bit values, we need to bitwise-OR in 24 bits of leading ones, - // then subtract them from the final count. - self.emit_all( - &[ - // OR in the leading 24 ones - Op::PushU32(u32::MAX << 8), - Op::U32Or, - // Obtain the count - Op::U32Clo, - // Subtract the excess bits from the count - Op::U32WrappingSubImm(24), - ], - span, - ); - } - Type::I1 => { - // There is exactly one leading one if true, or zero if false - self.emit(Op::EqImm(Felt::ONE), span); - } - ty if !ty.is_integer() => { - panic!("invalid clo on {ty}: only integral types are supported") - } - ty => unimplemented!("clo for {ty} is not supported"), - } - self.push(Type::U32); - } - - /// Count the number of trailing zero bits in the integral value on top of the operand stack, - /// and place the count back on the stack as a u32 value. - /// - /// This operation is implemented so that it consumes the input operand. - pub fn ctz(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - match arg.ty() { - Type::I128 | Type::U128 => { - let u64_ctz = "std::math::u64::ctz".parse().unwrap(); - // We decompose the 128-bit value into two 64-bit limbs, and use the standard - // library intrinsics to get the count for those limbs. We then add the count - // for the low bits to that of the high bits, if the high bits are all one, - // otherwise we take just the high bit count. - self.emit_all( - &[ - // Count trailing zeros in the high bits - Op::Exec(u64_ctz), // [hi_ctz, lo_hi, lo_lo] - // Count trailing zeros in the low bits - Op::Movup(2), // [lo_lo, hi_ctz, lo_hi] - Op::Movup(2), // [lo_hi, lo_lo, hi_ctz] - Op::Exec(u64_ctz), // [lo_ctz, hi_ctz] - // Add the high bit trailing zeros to those of the low bits, if the low - // bits are all zero; otherwise return only the low - // bit count - Op::Swap(1), - Op::PushU32(0), // [0, hi_ctz, lo_ctz] - Op::Dup(2), // [lo_ctz, 0, hi_ctz, lo_ctz] - Op::LtImm(Felt::new(32)), // [lo_ctz < 32, 0, hi_ctz, lo_ctz] - Op::Cdrop, // [lo_ctz < 32 ? 0 : hi_ctz, lo_ctz] - Op::Add, - ], - span, - ); - } - Type::I64 | Type::U64 => { - self.emit(Op::Exec("std::math::u64::ctz".parse().unwrap()), span) - } - Type::I32 | Type::U32 => self.emit(Op::U32Ctz, span), - Type::I16 | Type::U16 => { - // Clamp the total number of trailing zeros to 16 - self.emit_all( - &[ - // Obtain the count - Op::U32Ctz, - // Clamp to 16 - // operand_stack: [16, ctz] - Op::PushU8(16), - // operand_stack: [ctz, 16, ctz] - Op::Dup(1), - // operand_stack: [ctz >= 16, 16, ctz] - Op::GteImm(Felt::new(16)), - // operand_stack: [actual_ctz] - Op::Cdrop, - ], - span, - ); - } - Type::I8 | Type::U8 => { - // Clamp the total number of trailing zeros to 8 - self.emit_all( - &[ - // Obtain the count - Op::U32Ctz, - // Clamp to 8 - // operand_stack: [8, ctz] - Op::PushU8(8), - // operand_stack: [ctz, 8, ctz] - Op::Dup(1), - // operand_stack: [ctz >= 8, 8, ctz] - Op::GteImm(Felt::new(8)), - // operand_stack: [actual_ctz] - Op::Cdrop, - ], - span, - ); - } - Type::I1 => { - // There is exactly one trailing zero if false, or zero if true - self.emit(Op::EqImm(Felt::ZERO), span); - } - ty if !ty.is_integer() => { - panic!("invalid ctz on {ty}: only integral types are supported") - } - ty => unimplemented!("ctz for {ty} is not supported"), - } - self.push(Type::U32); - } - - /// Count the number of trailing one bits in the integral value on top of the operand stack, - /// and place the count back on the stack as a u32 value. - /// - /// This operation is implemented so that it consumes the input operand. - pub fn cto(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - match arg.ty() { - Type::I128 | Type::U128 => { - let u64_cto = "std::math::u64::cto".parse().unwrap(); - // We decompose the 128-bit value into two 64-bit limbs, and use the standard - // library intrinsics to get the count for those limbs. We then add the count - // for the low bits to that of the high bits, if the high bits are all one, - // otherwise we take just the high bit count. - self.emit_all( - &[ - // Count trailing ones in the high bits - Op::Exec(u64_cto), // [hi_cto, lo_hi, lo_lo] - // Count trailing ones in the low bits - Op::Movup(2), // [lo_lo, hi_cto, lo_hi] - Op::Movup(2), // [lo_hi, lo_lo, hi_cto] - Op::Exec(u64_cto), // [lo_cto, hi_cto] - // Add the high bit trailing ones to those of the low bits, if the low bits - // are all one; otherwise return only the low bit count - Op::Swap(1), - Op::PushU32(0), // [0, hi_cto, lo_cto] - Op::Dup(2), // [lo_cto, 0, hi_cto, lo_cto] - Op::LtImm(Felt::new(32)), // [lo_cto < 32, 0, hi_cto, lo_cto] - Op::Cdrop, // [lo_cto < 32 ? 0 : hi_cto, lo_cto] - Op::Add, - ], - span, - ); - } - Type::I64 | Type::U64 => { - self.emit(Op::Exec("std::math::u64::cto".parse().unwrap()), span) - } - Type::I32 | Type::U32 | Type::I16 | Type::U16 | Type::I8 | Type::U8 => { - // The number of trailing ones is de-facto clamped by the bitwidth of - // the value, since all of the padding bits are leading zeros. - self.emit(Op::U32Cto, span) - } - Type::I1 => { - // There is exactly one trailing one if true, or zero if false - self.emit(Op::EqImm(Felt::ONE), span); - } - ty if !ty.is_integer() => { - panic!("invalid cto on {ty}: only integral types are supported") - } - ty => unimplemented!("cto for {ty} is not supported"), - } - self.push(Type::U32); - } - - /// Invert the bitwise representation of the integral value on top of the operand stack. - /// - /// This has the effect of changing all 1 bits to 0s, and all 0 bits to 1s. - /// - /// This operation consumes the input operand. - pub fn bnot(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let ty = arg.ty(); - match &ty { - Type::I1 => self.emit(Op::Not, span), - Type::I8 - | Type::U8 - | Type::I16 - | Type::U16 - | Type::I32 - | Type::U32 - | Type::I64 - | Type::U64 - | Type::I128 - | Type::U128 => { - let num_elements = ty.size_in_bits() / 32; - match num_elements { - 0 | 1 => { - self.emit(Op::U32Not, span); - } - 2 => { - self.emit_repeat( - 2, - &[Span::new(span, Op::Swap(1)), Span::new(span, Op::U32Not)], - ); - } - n => { - self.emit_template(n, |n| { - [Span::new(span, Op::Movup(n as u8)), Span::new(span, Op::U32Not)] - }); - } - } - } - ty if !ty.is_integer() => { - panic!("invalid bnot on {ty}, only integral types are supported") - } - ty => unimplemented!("bnot for {ty} is not supported"), - } - self.push(ty); - } - - /// Invert the boolean value on top of the operand stack. - /// - /// This operation consumes the input operand. - pub fn not(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - assert_eq!(arg.ty(), Type::I1, "logical NOT requires a boolean value"); - self.emit(Op::Not, span); - self.push(Type::I1); - } - - /// Compute 2^N, where N is the integral value on top of the operand stack, as - /// a value of the same type as the input. - /// - /// The input value must be < 64, or execution will trap. - /// - /// This operation consumes the input operand. - pub fn pow2(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let ty = arg.ty(); - match &ty { - Type::U64 => { - self.emit_all( - &[ - // Assert that the high bits are zero - Op::Assertz, - // This asserts if value > 63, thus result is guaranteed to fit in u64 - Op::Pow2, - // Obtain the u64 representation by splitting the felt result - Op::U32Split, - ], - span, - ); - } - Type::I64 => { - self.emit(Op::Exec("intrinsics::i64::pow2".parse().unwrap()), span); - } - Type::Felt => { - self.emit(Op::Pow2, span); - } - Type::U32 => { - self.emit_all(&[Op::Pow2, Op::U32Assert], span); - } - Type::I32 => { - self.emit(Op::Exec("intrinsics::i32::pow2".parse().unwrap()), span); - } - Type::U8 | Type::U16 => { - self.emit_all(&[Op::Pow2, Op::U32Assert], span); - // Cast u32 to u8/u16 - self.int32_to_uint(ty.size_in_bits() as u32, span); - } - ty if !ty.is_unsigned_integer() => { - panic!( - "invalid unary operand: pow2 only permits unsigned integer operands, got {ty}" - ) - } - ty => unimplemented!("pow2 for {ty} is not supported"), - } - self.push(ty); - } - - /// Increment the operand on top of the stack by 1. - /// - /// The input value must be an integer, and overflow has wrapping semantics. - /// - /// This operation consumes the input operand. - pub fn incr(&mut self, span: SourceSpan) { - let arg = self.stack.pop().expect("operand stack is empty"); - let ty = arg.ty(); - match &ty { - // For this specific case, wrapping u64 arithmetic works for both i64/u64 - Type::I64 | Type::U64 => { - self.push_u64(1, span); - self.add_u64(Overflow::Wrapping, span); - } - Type::Felt => { - self.emit(Op::Incr, span); - } - // For this specific case, wrapping u32 arithmetic works for both i32/u32 - Type::I32 | Type::U32 => { - self.add_imm_u32(1, Overflow::Wrapping, span); - } - // We need to wrap the result for smallint types - Type::I8 | Type::U8 | Type::I16 | Type::U16 => { - let bits = ty.size_in_bits() as u32; - self.add_imm_u32(1, Overflow::Wrapping, span); - self.unchecked_mod_imm_u32(2u32.pow(bits), span); - } - ty if !ty.is_integer() => { - panic!("invalid unary operand: incr requires an integer operand, got {ty}") - } - ty => unimplemented!("incr for {ty} is not supported"), - } - self.push(ty); - } - - /// Compute the modular multiplicative inverse of the operand on top of the stack, `n`, i.e. - /// `n^-1 mod P`. - /// - /// This operation consumes the input operand. - pub fn inv(&mut self, span: SourceSpan) { - let arg = self.pop().expect("operand stack is empty"); - let ty = arg.ty(); - match &ty { - Type::Felt => { - self.emit(Op::Inv, span); - } - ty if !ty.is_integer() => { - panic!("invalid unary operand: inv requires an integer, got {ty}") - } - ty => unimplemented!("inv for {ty} is not supported"), - } - self.push(ty); - } - - /// Compute the modular negation of the operand on top of the stack, `n`, i.e. `-n mod P`. - /// - /// This operation consumes the input operand. - pub fn neg(&mut self, span: SourceSpan) { - let arg = self.pop().expect("operand stack is empty"); - let ty = arg.ty(); - match &ty { - Type::Felt => { - self.emit(Op::Neg, span); - } - ty if !ty.is_integer() => { - panic!("invalid unary operand: neg requires an integer, got {ty}") - } - ty => unimplemented!("neg for {ty} is not supported"), - } - self.push(ty); - } -} diff --git a/codegen/masm/src/codegen/emitter.rs b/codegen/masm/src/codegen/emitter.rs deleted file mode 100644 index 13773f604..000000000 --- a/codegen/masm/src/codegen/emitter.rs +++ /dev/null @@ -1,1256 +0,0 @@ -use std::{collections::BTreeMap, rc::Rc}; - -use cranelift_entity::SecondaryMap; -use midenc_hir::{ - self as hir, - adt::SparseMap, - assert_matches, - diagnostics::{SourceSpan, Span}, -}; -use midenc_hir_analysis::{ - DominatorTree, GlobalVariableLayout, LivenessAnalysis, Loop, LoopAnalysis, -}; -use smallvec::SmallVec; - -use super::{ - emit::{InstOpEmitter, OpEmitter}, - opt::{OperandMovementConstraintSolver, SolverError}, - scheduler::{BlockInfo, InstInfo, Schedule, ScheduleOp}, - Constraint, OperandStack, -}; -use crate::masm::{self, Op}; - -pub struct FunctionEmitter<'a> { - f: &'a hir::Function, - f_prime: &'a mut masm::Function, - domtree: &'a DominatorTree, - loops: &'a LoopAnalysis, - liveness: &'a LivenessAnalysis, - locals: BTreeMap, - globals: &'a GlobalVariableLayout, - visited: SecondaryMap, -} - -struct BlockEmitter<'b, 'f: 'b> { - function: &'b mut FunctionEmitter<'f>, - block_infos: &'b SparseMap>, - block_info: Rc, - /// The "controlling" loop corresponds to the current maximum loop depth - /// reached along the control flow path reaching this block. When we reach - /// a loopback edge in the control flow graph, we emit a trailing duplicate of the - /// instructions in the loop header to which we are branching. The controlling - /// loop is used during this process to determine what action, if any, must - /// be taken to exit the current loop in order to reach the target loop. - /// - /// Because we expect the IR we process to have been treeified, each block - /// can only have a single controlling loop, or none. This is because the only - /// blocks with multiple predecessors are loop headers, where the additional - /// predecessors must be loopback edges. Since a loopback edge does not modify - /// the controlling loop of the loop header block, it can only have a single - /// controlling loop. - controlling_loop: Option, - stack: OperandStack, - target: masm::BlockId, - visited: bool, -} - -/// Represents a task to execute during function emission -#[derive(Debug)] -enum Task { - /// Emit a block into a fresh block - Block { - /// The block to emit - block: hir::Block, - /// If set, the given loop should be used as the controlling - /// loop when determining what loop level is being exited - /// from. - /// - /// This gets set on any blocks emitted along a loopback edge - /// in the control flow graph, and is otherwise None. - controlling_loop: Option, - /// The state of the operand stack at block entry - stack: OperandStack, - }, - /// Emit a block by appending it to an existing block - Inline { - /// The block to inline into - target: masm::BlockId, - /// The block to emit - block: hir::Block, - /// If set, the given loop should be used as the controlling - /// loop when determining what loop level is being exited - /// from. - /// - /// This gets set on any blocks emitted along a loopback edge - /// in the control flow graph, and is otherwise None. - controlling_loop: Option, - /// The state of the operand stack at block entry - stack: OperandStack, - }, -} - -/// The task stack used during function emission -type Tasks = SmallVec<[Task; 4]>; - -impl<'a> FunctionEmitter<'a> { - pub fn new( - f: &'a hir::Function, - f_prime: &'a mut masm::Function, - domtree: &'a DominatorTree, - loops: &'a LoopAnalysis, - liveness: &'a LivenessAnalysis, - globals: &'a GlobalVariableLayout, - ) -> Self { - // Allocate procedure locals for each local variable - let locals = BTreeMap::from_iter( - f.dfg.locals().map(|local| (local.id, f_prime.alloc_local(local.ty.clone()))), - ); - - Self { - f, - f_prime, - domtree, - loops, - liveness, - locals, - globals, - visited: SecondaryMap::new(), - } - } - - pub fn emit(mut self, schedule: Schedule, stack: OperandStack) { - let mut tasks = Tasks::from_iter([Task::Block { - block: self.f.dfg.entry_block(), - controlling_loop: None, - stack, - }]); - while let Some(task) = tasks.pop() { - match task { - Task::Block { - block: block_id, - controlling_loop, - stack, - } => { - let block_info = schedule.block_info(block_id); - let target = block_info.target; - let block_schedule = schedule.get(block_id); - let visited = core::mem::replace(&mut self.visited[block_id], true); - let emitter = BlockEmitter { - function: &mut self, - block_infos: &schedule.block_infos, - block_info, - controlling_loop, - target, - stack, - visited, - }; - emitter.emit(block_schedule, &mut tasks); - } - Task::Inline { - target, - block: block_id, - controlling_loop, - stack, - } => { - let block_info = schedule.block_info(block_id); - let block_schedule = schedule.get(block_id); - let visited = core::mem::replace(&mut self.visited[block_id], true); - let emitter = BlockEmitter { - function: &mut self, - block_infos: &schedule.block_infos, - block_info, - controlling_loop, - target, - stack, - visited, - }; - emitter.emit(block_schedule, &mut tasks); - } - } - } - } -} - -impl<'b, 'f: 'b> BlockEmitter<'b, 'f> { - pub fn emit(mut self, block_schedule: &[ScheduleOp], tasks: &mut Tasks) { - // Before we emit any scheduling operations, compare the current stack - // against the set of live-in values expected by this block. If there are - // any values on the stack which are not live-in, then they should be dropped - // here. - // - // This situation occurs when the scheduler deems that a value must - // be copied for all uses in a given block because it is live in at least one - // successor, causing the value to be kept on the stack when transferring control - // to any successor of that block. When this interacts with conditional branches, - // where the value is only used in a subset of successors, there will be at least - // one successor where the value is left dangling and essentially never cleaned - // up. This causes issues with operand stack coherence in loops. We can't avoid - // making the copy in the original block, instead responsibility for cleaning - // up these unused values is pushed into the successor on entry. - self.drop_unused_operands(); - - // Continue normally, by emitting the contents of the block based on the given schedule - for op in block_schedule.iter() { - match op { - ScheduleOp::Inst(inst_info) => self.emit_inst(inst_info, tasks), - ScheduleOp::Drop(value) => { - let mut emitter = self.emitter(); - let pos = emitter - .stack() - .find(value) - .expect("could not find value on the operand stack"); - emitter.drop_operand_at_position(pos, SourceSpan::default()); - } - } - } - } - - fn emit_inst(&mut self, inst_info: &InstInfo, tasks: &mut Tasks) { - use midenc_hir::Instruction; - - // Move instruction operands into place, minimizing unnecessary stack manipulation ops - // - // NOTE: This does not include block arguments for control flow instructions, those are - // handled separately within the specific handlers for those instructions - let args = self.function.f.dfg.inst_args(inst_info.inst); - let span = self.function.f.dfg.inst_span(inst_info.inst); - self.schedule_operands(args, inst_info.plain_arguments(), span) - .unwrap_or_else(|err| { - panic!( - "failed to schedule operands: {:?} \n for inst: {} {:?}\n with error: \ - {err:?}\n constraints: {:?}\n stack: {:?}", - args, - inst_info.inst, - self.function.f.dfg.inst(inst_info.inst), - inst_info.plain_arguments(), - self.stack, - ) - }); - - match self.function.f.dfg.inst(inst_info.inst) { - ix @ (Instruction::RetImm(_) | Instruction::Ret(_)) => self.emit_ret(inst_info, ix), - Instruction::Br(ref op) => self.emit_br(inst_info, op, tasks), - Instruction::CondBr(ref op) => self.emit_cond_br(inst_info, op, tasks), - Instruction::GlobalValue(op) => self.emit_global_value(inst_info, op), - Instruction::UnaryOpImm(op) => self.emit_unary_imm_op(inst_info, op), - Instruction::UnaryOp(op) => self.emit_unary_op(inst_info, op), - Instruction::BinaryOpImm(op) => self.emit_binary_imm_op(inst_info, op), - Instruction::BinaryOp(op) => self.emit_binary_op(inst_info, op), - Instruction::Test(op) => self.emit_test_op(inst_info, op), - Instruction::Load(op) => self.emit_load_op(inst_info, op), - Instruction::PrimOp(op) => self.emit_primop(inst_info, op), - Instruction::PrimOpImm(op) => self.emit_primop_imm(inst_info, op), - Instruction::Call(op) => self.emit_call_op(inst_info, op), - Instruction::InlineAsm(op) => self.emit_inline_asm(inst_info, op), - Instruction::Switch(_) => { - panic!("expected switch instructions to have been rewritten before stackification") - } - Instruction::LocalVar(ref op) => { - let span = self.function.f.dfg.inst_span(inst_info.inst); - let local = self.function.locals[&op.local]; - let args = op.args.as_slice(&self.function.f.dfg.value_lists); - let mut emitter = self.inst_emitter(inst_info.inst); - match op.op { - hir::Opcode::Store => { - assert_eq!(args.len(), 1); - emitter.store_local(local, span); - } - hir::Opcode::Load => { - emitter.load_local(local, span); - } - opcode => unimplemented!("unrecognized local variable op '{opcode}'"), - } - } - } - } - - fn emit_ret(&mut self, inst_info: &InstInfo, ix: &hir::Instruction) { - use midenc_hir::Instruction; - assert!( - !self.visited, - "invalid control flow graph: unexpected return instruction in loop in {}", - self.function.f.dfg.inst_block(inst_info.inst).unwrap(), - ); - - let span = self.function.f.dfg.inst_span(inst_info.inst); - let num_args = self.function.f.dfg.inst_args(inst_info.inst).len(); - let level = self.controlling_loop_level().unwrap_or(0); - - let mut emitter = self.emitter(); - // Upon return, the operand stack should only contain the function result(s), - // so empty the stack before proceeding. - emitter.truncate_stack(num_args, span); - // If this instruction is the immediate variant, we need to push the return - // value on the stack at this point. - if let Instruction::RetImm(hir::RetImm { arg, .. }) = ix { - emitter.literal(*arg, span); - } - - // If we're in a loop, push N zeroes on the stack, where N is the current loop depth - for _ in 0..level { - emitter.literal(false, span); - } - } - - /// Lower an unconditional branch instruction. - /// - /// There are two ways in which code generation lowers these instructions, depending on - /// whether we have visited the successor block previously, nor not. - /// - /// * If this is the first visit to the successor, then due to the transformation passes - /// we expect to have been run on the input IR (namely treeification and block inlining), - /// it should be the case that these unconditional branches only exist in the IR when the - /// current block is a loop header, or the successor is a loop header. - /// - /// * If we have visited the successor previously, then we are emitting code for a loopback - /// edge, and the successor must be a loop header. We must emit the loop header inline in the - /// current block, up to the terminator, and then emit instructions to either continue the - /// loop, or exit the current loop to the target loop. - fn emit_br(&mut self, inst_info: &InstInfo, op: &hir::Br, tasks: &mut Tasks) { - let destination = op.successor.destination; - - let is_first_visit = !self.visited; - let in_loop_header = self.block_info.is_loop_header(); - - // Move block arguments into position - let span = self.function.f.dfg.inst_span(inst_info.inst); - let args = op.successor.args.as_slice(&self.function.f.dfg.value_lists); - self.schedule_operands(args, inst_info.block_arguments(destination), span) - .unwrap_or_else(|err| { - panic!("failed to schedule operands for {}: {err:?}", inst_info.inst) - }); - // Rename operands on stack to destination block parameters - let params = self.function.f.dfg.block_params(destination); - for (idx, param) in params.iter().enumerate() { - self.stack.rename(idx, *param); - } - - if is_first_visit { - let controlling_loop = self.target_controlling_loop(destination); - if in_loop_header { - // We're in a loop header, emit the target block inside a while loop - let body_blk = self.masm_block_id(destination); - self.emit_ops([Op::PushU8(1), Op::While(body_blk)], span); - tasks.push(Task::Block { - block: destination, - controlling_loop, - stack: self.stack.clone(), - }); - } else { - // We're in a normal block, emit the target block inline - tasks.push(Task::Inline { - target: self.target, - block: destination, - controlling_loop, - stack: self.stack.clone(), - }); - } - } else { - // We should only be emitting code for a block more than once if that block - // is a loop header. All other blocks should only be visited a single time. - assert!(in_loop_header, "unexpected cycle at {}", self.block_info.source); - - // Calculate - let current_level = self.controlling_loop_level().unwrap_or_else(|| { - panic!("expected controlling loop to be set in {}", self.block_info.source) - }); - let target_level = self.loop_level(self.block_info.source); - let mut emitter = self.emitter(); - emitter.literal(true, span); - for _ in 0..(current_level - target_level) { - emitter.literal(false, span); - } - } - } - - fn emit_cond_br(&mut self, inst_info: &InstInfo, op: &hir::CondBr, tasks: &mut Tasks) { - let cond = op.cond; - let then_dest = op.then_dest.destination; - let else_dest = op.else_dest.destination; - - // Ensure `cond` is on top of the stack, and remove it at the same time - assert_eq!( - self.stack.pop().unwrap().as_value(), - Some(cond), - "expected {} on top of the stack", - cond - ); - - let span = self.function.f.dfg.inst_span(inst_info.inst); - if !self.visited { - let then_blk = self.masm_block_id(then_dest); - let else_blk = self.masm_block_id(else_dest); - - // If the current block is a loop header, we're emitting a conditional loop, - // otherwise we're emitting a simple if/else conditional expression. - if self.block_info.is_loop_header() { - let body_blk = self.function.f_prime.create_block(); - // We always unconditionally enter the loop the first time - self.emit_ops([Op::PushU8(1), Op::While(body_blk)], span); - self.emit_op_to(body_blk, Op::If(then_blk, else_blk), span); - } else { - self.emit_op(Op::If(then_blk, else_blk), span); - } - - let successors = [ - (then_dest, then_blk, op.then_dest.args), - (else_dest, else_blk, op.else_dest.args), - ]; - for (block, masm_block, args) in successors.into_iter() { - // Make a copy of the operand stack in the current block - // to be used as the state of the operand stack in the - // successor block - let mut stack = self.stack.clone(); - - // Move block arguments for this successor into place, along - // the control flow edge to that successor, i.e. we do not emit - // these stack ops in the current block, but in the successor block - let args = args.as_slice(&self.function.f.dfg.value_lists); - self.schedule_operands_in_block( - args, - inst_info.block_arguments(block), - masm_block, - &mut stack, - span, - ) - .unwrap_or_else(|err| { - panic!( - "failed to schedule operands for successor {block} of {}: {err:?}", - inst_info.inst - ) - }); - - // Now that the block arguments are in place, we need to rename - // the stack operands to use the value names the successor expects - let params = self.function.f.dfg.block_params(block); - for (idx, param) in params.iter().enumerate() { - stack.rename(idx, *param); - } - - // Enqueue a task to emit code for the successor block - let controlling_loop = self.target_controlling_loop(block); - tasks.push(Task::Block { - block, - controlling_loop, - stack, - }); - } - } else { - // We should only be emitting code for a block more than once if that block - // is a loop header. All other blocks should only be visited a single time. - assert!( - self.block_info.is_loop_header(), - "unexpected cycle caused by branch to {}", - self.block_info.source, - ); - - let current_level = self.controlling_loop_level().unwrap_or_else(|| { - panic!("expected controlling loop to be set in {}", self.block_info.source) - }); - let target_level = self.loop_level(self.block_info.source); - // Continue the target loop when it is reached, the top of the stack - // prior to this push.1 instruction holds the actual conditional, which - // will be evaluated by the `if.true` nested inside the target `while.true` - let mut emitter = self.emitter(); - emitter.literal(true, span); - for _ in 0..(current_level - target_level) { - emitter.literal(false, span); - } - } - } - - fn emit_global_value(&mut self, inst_info: &InstInfo, op: &hir::GlobalValueOp) { - use midenc_hir::Immediate; - - assert_eq!(op.op, hir::Opcode::GlobalValue); - let addr = self - .function - .globals - .get_computed_addr(&self.function.f.id, op.global) - .unwrap_or_else(|| { - panic!( - "expected linker to identify all undefined symbols, but failed on func id: \ - {}, gv: {}", - self.function.f.id, op.global - ) - }); - let span = self.function.f.dfg.inst_span(inst_info.inst); - match self.function.f.dfg.global_value(op.global) { - hir::GlobalValueData::Load { ref ty, offset, .. } => { - let mut emitter = self.inst_emitter(inst_info.inst); - let offset = *offset; - let addr = if offset >= 0 { - addr + (offset as u32) - } else { - addr - offset.unsigned_abs() - }; - emitter.load_imm(addr, ty.clone(), span); - } - global @ (hir::GlobalValueData::IAddImm { .. } - | hir::GlobalValueData::Symbol { .. }) => { - let ty = self - .function - .f - .dfg - .value_type(self.function.f.dfg.first_result(inst_info.inst)) - .clone(); - let mut emitter = self.inst_emitter(inst_info.inst); - let offset = global.offset(); - let addr = if offset >= 0 { - addr + (offset as u32) - } else { - addr - offset.unsigned_abs() - }; - emitter.literal(Immediate::U32(addr), span); - // "cast" the immediate to the expected type - emitter.stack_mut().pop(); - emitter.stack_mut().push(ty); - } - } - } - - fn emit_unary_imm_op(&mut self, inst_info: &InstInfo, op: &hir::UnaryOpImm) { - use midenc_hir::Immediate; - - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst_info.inst); - match op.op { - hir::Opcode::ImmI1 => { - assert_matches!(op.imm, Immediate::I1(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmI8 => { - assert_matches!(op.imm, Immediate::I8(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmU8 => { - assert_matches!(op.imm, Immediate::U8(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmI16 => { - assert_matches!(op.imm, Immediate::I16(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmU16 => { - assert_matches!(op.imm, Immediate::U16(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmI32 => { - assert_matches!(op.imm, Immediate::I32(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmU32 => { - assert_matches!(op.imm, Immediate::U32(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmI64 => { - assert_matches!(op.imm, Immediate::I64(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmU64 => { - assert_matches!(op.imm, Immediate::U64(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmFelt => { - assert_matches!(op.imm, Immediate::Felt(_)); - emitter.literal(op.imm, span); - } - hir::Opcode::ImmF64 => { - assert_matches!(op.imm, Immediate::F64(_)); - emitter.literal(op.imm, span); - } - opcode => unimplemented!("unrecognized unary with immediate opcode: '{opcode}'"), - } - } - - fn emit_unary_op(&mut self, inst_info: &InstInfo, op: &hir::UnaryOp) { - let inst = inst_info.inst; - let result = self.function.f.dfg.first_result(inst); - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst); - match op.op { - hir::Opcode::Neg => emitter.neg(span), - hir::Opcode::Inv => emitter.inv(span), - hir::Opcode::Incr => emitter.incr(span), - hir::Opcode::Ilog2 => emitter.ilog2(span), - hir::Opcode::Pow2 => emitter.pow2(span), - hir::Opcode::Not => emitter.not(span), - hir::Opcode::Bnot => emitter.bnot(span), - hir::Opcode::Popcnt => emitter.popcnt(span), - hir::Opcode::Clz => emitter.clz(span), - hir::Opcode::Ctz => emitter.ctz(span), - hir::Opcode::Clo => emitter.clo(span), - hir::Opcode::Cto => emitter.cto(span), - // This opcode is a no-op - hir::Opcode::PtrToInt => { - let result_ty = emitter.value_type(result).clone(); - let stack = emitter.stack_mut(); - stack.pop().expect("operand stack is empty"); - stack.push(result_ty); - } - // We lower this cast to an assertion, to ensure the value is a valid pointer - hir::Opcode::IntToPtr => { - let ptr_ty = emitter.value_type(result).clone(); - emitter.inttoptr(&ptr_ty, span); - } - // The semantics of cast for now are basically your standard integer coercion rules - // - // We may eliminate this in favor of more specific casts in the future - hir::Opcode::Cast => { - let dst_ty = emitter.value_type(result).clone(); - emitter.cast(&dst_ty, span); - } - hir::Opcode::Bitcast => { - let dst_ty = emitter.value_type(result).clone(); - emitter.bitcast(&dst_ty, span); - } - hir::Opcode::Trunc => { - let dst_ty = emitter.value_type(result).clone(); - emitter.trunc(&dst_ty, span); - } - hir::Opcode::Zext => { - let dst_ty = emitter.value_type(result).clone(); - emitter.zext(&dst_ty, span); - } - hir::Opcode::Sext => { - let dst_ty = emitter.value_type(result).clone(); - emitter.sext(&dst_ty, span); - } - hir::Opcode::IsOdd => emitter.is_odd(span), - opcode => unimplemented!("unrecognized unary opcode: '{opcode}'"), - } - } - - fn emit_binary_imm_op(&mut self, inst_info: &InstInfo, op: &hir::BinaryOpImm) { - use midenc_hir::Overflow; - - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst_info.inst); - let overflow = op.overflow.unwrap_or(Overflow::Checked); - match op.op { - hir::Opcode::Eq => emitter.eq_imm(op.imm, span), - hir::Opcode::Neq => emitter.neq_imm(op.imm, span), - hir::Opcode::Gt => emitter.gt_imm(op.imm, span), - hir::Opcode::Gte => emitter.gte_imm(op.imm, span), - hir::Opcode::Lt => emitter.lt_imm(op.imm, span), - hir::Opcode::Lte => emitter.lte_imm(op.imm, span), - hir::Opcode::Add => emitter.add_imm(op.imm, overflow, span), - hir::Opcode::Sub => emitter.sub_imm(op.imm, overflow, span), - hir::Opcode::Mul => emitter.mul_imm(op.imm, overflow, span), - hir::Opcode::Div if overflow.is_checked() => emitter.checked_div_imm(op.imm, span), - hir::Opcode::Div => emitter.unchecked_div_imm(op.imm, span), - hir::Opcode::Min => emitter.min_imm(op.imm, span), - hir::Opcode::Max => emitter.max_imm(op.imm, span), - hir::Opcode::Mod if overflow.is_checked() => emitter.checked_mod_imm(op.imm, span), - hir::Opcode::Mod => emitter.unchecked_mod_imm(op.imm, span), - hir::Opcode::DivMod if overflow.is_checked() => { - emitter.checked_divmod_imm(op.imm, span) - } - hir::Opcode::DivMod => emitter.unchecked_divmod_imm(op.imm, span), - hir::Opcode::Exp => emitter.exp_imm(op.imm, span), - hir::Opcode::And => emitter.and_imm(op.imm, span), - hir::Opcode::Band => emitter.band_imm(op.imm, span), - hir::Opcode::Or => emitter.or_imm(op.imm, span), - hir::Opcode::Bor => emitter.bor_imm(op.imm, span), - hir::Opcode::Xor => emitter.xor_imm(op.imm, span), - hir::Opcode::Bxor => emitter.bxor_imm(op.imm, span), - hir::Opcode::Shl => emitter.shl_imm(op.imm, span), - hir::Opcode::Shr => emitter.shr_imm(op.imm, span), - hir::Opcode::Rotl => emitter.rotl_imm(op.imm, span), - hir::Opcode::Rotr => emitter.rotr_imm(op.imm, span), - opcode => unimplemented!("unrecognized binary with immediate opcode: '{opcode}'"), - } - } - - fn emit_binary_op(&mut self, inst_info: &InstInfo, op: &hir::BinaryOp) { - use midenc_hir::Overflow; - - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst_info.inst); - let overflow = op.overflow.unwrap_or(Overflow::Checked); - match op.op { - hir::Opcode::Eq => emitter.eq(span), - hir::Opcode::Neq => emitter.neq(span), - hir::Opcode::Gt => emitter.gt(span), - hir::Opcode::Gte => emitter.gte(span), - hir::Opcode::Lt => emitter.lt(span), - hir::Opcode::Lte => emitter.lte(span), - hir::Opcode::Add => emitter.add(overflow, span), - hir::Opcode::Sub => emitter.sub(overflow, span), - hir::Opcode::Mul => emitter.mul(overflow, span), - hir::Opcode::Div if overflow.is_checked() => emitter.checked_div(span), - hir::Opcode::Div => emitter.unchecked_div(span), - hir::Opcode::Min => emitter.min(span), - hir::Opcode::Max => emitter.max(span), - hir::Opcode::Mod if overflow.is_checked() => emitter.checked_mod(span), - hir::Opcode::Mod => emitter.unchecked_mod(span), - hir::Opcode::DivMod if overflow.is_checked() => emitter.checked_divmod(span), - hir::Opcode::DivMod => emitter.unchecked_divmod(span), - hir::Opcode::Exp => emitter.exp(span), - hir::Opcode::And => emitter.and(span), - hir::Opcode::Band => emitter.band(span), - hir::Opcode::Or => emitter.or(span), - hir::Opcode::Bor => emitter.bor(span), - hir::Opcode::Xor => emitter.xor(span), - hir::Opcode::Bxor => emitter.bxor(span), - hir::Opcode::Shl => emitter.shl(span), - hir::Opcode::Shr => emitter.shr(span), - hir::Opcode::Rotl => emitter.rotl(span), - hir::Opcode::Rotr => emitter.rotr(span), - opcode => unimplemented!("unrecognized binary opcode: '{opcode}'"), - } - } - - fn emit_test_op(&mut self, _inst_info: &InstInfo, op: &hir::Test) { - unimplemented!("unrecognized test opcode: '{}'", &op.op); - } - - fn emit_load_op(&mut self, inst_info: &InstInfo, op: &hir::LoadOp) { - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst_info.inst); - emitter.load(op.ty.clone(), span); - } - - fn emit_primop_imm(&mut self, inst_info: &InstInfo, op: &hir::PrimOpImm) { - let args = op.args.as_slice(&self.function.f.dfg.value_lists); - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst_info.inst); - match op.op { - hir::Opcode::Assert => { - assert_eq!(args.len(), 1); - emitter.assert( - Some(op.imm.as_u32().expect("invalid assertion error code immediate")), - span, - ); - } - hir::Opcode::Assertz => { - assert_eq!(args.len(), 1); - emitter.assertz( - Some(op.imm.as_u32().expect("invalid assertion error code immediate")), - span, - ); - } - hir::Opcode::AssertEq => { - emitter.assert_eq_imm(op.imm, span); - } - // Store a value at a constant address - hir::Opcode::Store => { - emitter.store_imm( - op.imm.as_u32().expect("invalid address immediate: out of range"), - span, - ); - } - opcode => unimplemented!("unrecognized primop with immediate opcode: '{opcode}'"), - } - } - - fn emit_primop(&mut self, inst_info: &InstInfo, op: &hir::PrimOp) { - let args = op.args.as_slice(&self.function.f.dfg.value_lists); - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst_info.inst); - match op.op { - // Pop a value of the given type off the stack and assert it's value is one - hir::Opcode::Assert => { - assert_eq!(args.len(), 1); - emitter.assert(None, span); - } - // Pop a value of the given type off the stack and assert it's value is zero - hir::Opcode::Assertz => { - assert_eq!(args.len(), 1); - emitter.assertz(None, span); - } - // Pop two values of the given type off the stack and assert equality - hir::Opcode::AssertEq => { - assert_eq!(args.len(), 2); - emitter.assert_eq(span); - } - // Allocate a local and push its address on the operand stack - hir::Opcode::Alloca => { - assert!(args.is_empty()); - let result = emitter.dfg().first_result(inst_info.inst); - let ty = emitter.value_type(result).clone(); - emitter.alloca(&ty, span); - } - // Store a value at a given pointer - hir::Opcode::Store => { - assert_eq!(args.len(), 2); - emitter.store(span); - } - // Grow the heap by `num_pages` pages - hir::Opcode::MemGrow => { - assert_eq!(args.len(), 1); - emitter.mem_grow(span); - } - // Return the size of the heap in pages - hir::Opcode::MemSize => { - assert_eq!(args.len(), 0); - emitter.mem_size(span); - } - // Write `count` copies of `value` starting at the destination address - hir::Opcode::MemSet => { - assert_eq!(args.len(), 3); - emitter.memset(span); - } - // Copy `count * sizeof(ctrl_ty)` bytes from source to destination address - hir::Opcode::MemCpy => { - assert_eq!(args.len(), 3); - emitter.memcpy(span); - } - // Conditionally select between two values - hir::Opcode::Select => { - assert_eq!(args.len(), 3); - emitter.select(span); - } - // This instruction should not be reachable at runtime, so we emit an assertion - // that will always fail if for some reason it is reached - hir::Opcode::Unreachable => { - // assert(false) - emitter.emit_all(&[Op::PushU32(0), Op::Assert], span); - } - opcode => unimplemented!("unrecognized primop with immediate opcode: '{opcode}'"), - } - } - - fn emit_call_op(&mut self, inst_info: &InstInfo, op: &hir::Call) { - assert_ne!(op.callee, self.function.f.id, "unexpected recursive call"); - - let span = self.function.f.dfg.inst_span(inst_info.inst); - let mut emitter = self.inst_emitter(inst_info.inst); - match op.op { - hir::Opcode::Syscall => emitter.syscall(op.callee, span), - hir::Opcode::Call => emitter.exec(op.callee, span), - opcode => unimplemented!("unrecognized procedure call opcode: '{opcode}'"), - } - } - - fn emit_inline_asm(&mut self, inst_info: &InstInfo, op: &hir::InlineAsm) { - use super::TypedValue; - - // Port over the blocks from the inline assembly chunk, except the body block, which will - // be inlined at the current block - let mut mapped = SecondaryMap::::new(); - for (inline_blk, _) in op.blocks.iter() { - if inline_blk == op.body { - continue; - } - let mapped_blk = self.function.f_prime.create_block(); - mapped[inline_blk] = mapped_blk; - } - - // Inline the body, rewriting any references to other blocks - let original_body_block = op.body; - let mapped_body_block = self.masm_block_id(self.block_info.source); - let mut rewrites = SmallVec::<[(masm::BlockId, masm::BlockId); 4]>::from_iter([( - original_body_block, - mapped_body_block, - )]); - self.rewrite_inline_assembly_block(op, &mut rewrites, &mapped); - - // Pop arguments, push results - self.stack.dropn(op.args.len(&self.function.f.dfg.value_lists)); - for result in self.function.f.dfg.inst_results(inst_info.inst).iter().copied().rev() { - let ty = self.function.f.dfg.value_type(result).clone(); - self.stack.push(TypedValue { value: result, ty }); - } - } - - fn rewrite_inline_assembly_block( - &mut self, - asm: &hir::InlineAsm, - rewrites: &mut SmallVec<[(masm::BlockId, masm::BlockId); 4]>, - mapped_blocks: &SecondaryMap, - ) { - while let Some((prev, new)) = rewrites.pop() { - for mut op in asm.blocks[prev].ops.iter().cloned() { - let span = op.span(); - match &mut *op { - Op::If(ref mut then_blk, ref mut else_blk) => { - let prev_then_blk = *then_blk; - let prev_else_blk = *else_blk; - *then_blk = mapped_blocks[prev_then_blk]; - *else_blk = mapped_blocks[prev_else_blk]; - rewrites.push((prev_then_blk, *then_blk)); - rewrites.push((prev_else_blk, *else_blk)); - } - Op::While(ref mut body_blk) | Op::Repeat(_, ref mut body_blk) => { - let prev_body_blk = *body_blk; - *body_blk = mapped_blocks[prev_body_blk]; - rewrites.push((prev_body_blk, *body_blk)); - } - Op::Exec(id) => { - self.function.f_prime.register_absolute_invocation_target( - miden_assembly::ast::InvokeKind::Exec, - *id, - ); - } - Op::Call(id) => { - self.function.f_prime.register_absolute_invocation_target( - miden_assembly::ast::InvokeKind::Call, - *id, - ); - } - Op::Syscall(id) => { - self.function.f_prime.register_absolute_invocation_target( - miden_assembly::ast::InvokeKind::SysCall, - *id, - ); - } - Op::LocAddr(_) - | Op::LocLoad(_) - | Op::LocLoadw(_) - | Op::LocStore(_) - | Op::LocStorew(_) => { - unimplemented!( - "locals are not currently supported in inline assembly blocks" - ) - } - _ => (), - } - self.function.f_prime.body.block_mut(new).push(op.into_inner(), span); - } - } - } - - /// Drop the operands on the stack which are no longer live upon entry into - /// the current block. - /// - /// This is intended to be called before scheduling any instructions in the block. - fn drop_unused_operands(&mut self) { - // We start by computing the set of unused operands on the stack at this point - // in the program. We will use the resulting vectors to schedule instructions - // that will move those operands to the top of the stack to be discarded - let pp = hir::ProgramPoint::Block(self.block_info.source); - let mut unused = SmallVec::<[hir::Value; 4]>::default(); - let mut constraints = SmallVec::<[Constraint; 4]>::default(); - for operand in self.stack.iter().rev() { - let value = operand.as_value().expect("unexpected non-ssa value on stack"); - // If the given value is not live on entry to this block, it should be dropped - if !self.function.liveness.is_live_at(&value, pp) { - log::trace!( - "should drop {value} at {} (visited={})", - self.block_info.source, - self.visited - ); - unused.push(value); - constraints.push(Constraint::Move); - } - } - - // Next, emit the optimal set of moves to get the unused operands to the top - if !unused.is_empty() { - // If the number of unused operands is greater than the number - // of used operands, then we will schedule manually, since this - // is a pathological use case for the operand scheduler. - let num_used = self.stack.len() - unused.len(); - if unused.len() > num_used { - // In this case, we emit code starting from the top - // of the stack, i.e. if we encounter an unused value - // on top, then we increment a counter and check the - // next value, and so on, until we reach a used value - // or the end of the stack. At that point, we emit drops - // for the unused batch, and reset the counter. - // - // If we encounter a used value on top, or we have dropped - // an unused batch and left a used value on top, we look - // to see if the next value is used/unused: - // - // * If used, we increment the counter until we reach an - // unused value or the end of the stack. We then move any - // unused value found to the top and drop it, subtract 1 - // from the counter, and resume where we left off - // - // * If unused, we check if it is just a single unused value, - // or if there is a string of unused values starting there. - // In the former case, we swap it to the top of the stack and - // drop it, and start over. In the latter case, we move the - // used value on top of the stack down past the last unused - // value, and then drop the unused batch. - let mut batch_size = 0; - let mut current_index = 0; - let mut unused_batch = false; - while self.stack.len() > current_index { - let value = self.stack[current_index].as_value().unwrap(); - let is_unused = unused.contains(&value); - // If we're looking at the top operand, start - // a new batch of either used or unused operands - if current_index == 0 { - unused_batch = is_unused; - current_index += 1; - batch_size += 1; - continue; - } - - // If we're putting together a batch of unused values, - // and the current value is unused, extend the batch - if unused_batch && is_unused { - batch_size += 1; - current_index += 1; - continue; - } - - // If we're putting together a batch of unused values, - // and the current value is used, drop the unused values - // we've found so far, and then reset our cursor to the top - if unused_batch { - let mut emitter = self.emitter(); - emitter.dropn(batch_size, SourceSpan::default()); - batch_size = 0; - current_index = 0; - continue; - } - - // If we're putting together a batch of used values, - // and the current value is used, extend the batch - if !is_unused { - batch_size += 1; - current_index += 1; - continue; - } - - // Otherwise, we have found more unused value(s) behind - // a batch of used value(s), so we need to determine the - // best course of action - match batch_size { - // If we've only found a single used value so far, - // and there is more than two unused values behind it, - // then move the used value down the stack and drop the unused. - 1 => { - let unused_chunk_size = self - .stack - .iter() - .rev() - .skip(1) - .take_while(|o| unused.contains(&o.as_value().unwrap())) - .count(); - let mut emitter = self.emitter(); - if unused_chunk_size > 1 { - emitter.movdn(unused_chunk_size as u8, SourceSpan::default()); - emitter.dropn(unused_chunk_size, SourceSpan::default()); - } else { - emitter.swap(1, SourceSpan::default()); - emitter.drop(SourceSpan::default()); - } - } - // We've got multiple unused values together, so choose instead - // to move the unused value to the top and drop it - _ => { - let mut emitter = self.emitter(); - emitter.movup(current_index as u8, SourceSpan::default()); - emitter.drop(SourceSpan::default()); - } - } - batch_size = 0; - current_index = 0; - } - } else { - self.schedule_operands(&unused, &constraints, SourceSpan::default()) - .unwrap_or_else(|err| { - panic!( - "failed to schedule unused operands for {}: {err:?}", - self.block_info.source - ) - }); - let mut emitter = self.emitter(); - emitter.dropn(unused.len(), SourceSpan::default()); - } - } - } - - fn schedule_operands( - &mut self, - expected: &[hir::Value], - constraints: &[Constraint], - span: SourceSpan, - ) -> Result<(), SolverError> { - match OperandMovementConstraintSolver::new(expected, constraints, &self.stack) { - Ok(solver) => { - let mut emitter = self.emitter(); - solver.solve_and_apply(&mut emitter, span) - } - Err(SolverError::AlreadySolved) => Ok(()), - Err(err) => { - panic!("unexpected error constructing operand movement constraint solver: {err:?}") - } - } - } - - fn schedule_operands_in_block( - &mut self, - expected: &[hir::Value], - constraints: &[Constraint], - block: masm::BlockId, - stack: &mut OperandStack, - span: SourceSpan, - ) -> Result<(), SolverError> { - match OperandMovementConstraintSolver::new(expected, constraints, stack) { - Ok(solver) => { - let mut emitter = OpEmitter::new(self.function.f_prime, block, stack); - solver.solve_and_apply(&mut emitter, span) - } - Err(SolverError::AlreadySolved) => Ok(()), - Err(err) => { - panic!("unexpected error constructing operand movement constraint solver: {err:?}") - } - } - } - - fn target_controlling_loop(&self, target_block: hir::Block) -> Option { - use core::cmp::Ordering; - - let is_first_visit = !self.visited; - let current_block = self.block_info.source; - let current_loop = self.function.loops.innermost_loop(current_block); - let target_loop = self.function.loops.innermost_loop(target_block); - match (current_loop, target_loop) { - // No loops involved - (None, None) => { - assert!(is_first_visit); - // TODO: This assertion is temporary commented out until https://github.com/0xPolygonMiden/compiler/issues/201 - // assert_eq!( - // self.controlling_loop, - // None, - // "unexpected controlling loop: {:?}, parent: {:?}", - // self.function.loops.loop_header(self.controlling_loop.unwrap()), - // self.function - // .loops - // .loop_parent(self.controlling_loop.unwrap()) - // .map(|l| self.function.loops.loop_header(l)) - // ); - None - } - // Entering a top-level loop, set the controlling loop - (None, controlling_loop @ Some(_)) => { - for l in self.function.loops.loops() { - log::debug!( - "l {:?} is loop header {:?}", - l, - self.function.loops.loop_header(l) - ); - log::debug!( - "l in loop with current_block {:?}", - self.function.loops.is_in_loop(current_block, l) - ); - log::debug!( - "l in loop with target_block {:?}", - self.function.loops.is_in_loop(target_block, l) - ); - } - assert!(is_first_visit); - assert_eq!( - self.controlling_loop, None, - "expected no controlling loop entering {target_block} from {current_block}" - ); - controlling_loop - } - // Escaping a loop - (Some(_), None) => { - assert!(is_first_visit); - // We're emitting a block along an exit edge of a loop, it must be the - // case here that the source block dominates the target block, so we - // leave the controlling loop alone, since it will be used to calculate - // the depth we're exiting from - assert!( - self.function.domtree.dominates( - current_block, - target_block, - &self.function.f.dfg - ), - "expected {current_block} to dominate {target_block} here" - ); - assert_matches!(self.controlling_loop, Some(_)); - self.controlling_loop - } - (Some(src), Some(dst)) => { - let src_level = self.function.loops.level(src); - let dst_level = self.function.loops.level(dst); - if is_first_visit { - // We have not visited the target block before.. - match src_level.cmp(&dst_level) { - // We're emitting a block along an exit edge of a loop, so we - // expect that the source block dominates the target block, and - // as such we will leave the controlling loop alone as it will - // be used to calculate the depth we're exiting to - Ordering::Greater => { - assert!( - self.function.domtree.dominates( - current_block, - target_block, - &self.function.f.dfg - ), - "expected {current_block} to dominate {target_block} here" - ); - self.controlling_loop - } - // If we're entering a nested loop, then we need to update the controlling - // loop to reflect the loop we've entered - Ordering::Less => Some(dst), - Ordering::Equal => self.controlling_loop, - } - } else { - // We're looping back to the loop header, or a parent loop header, - // so leave the controlling loop unmodified, it will be reset by - // the emit_inst handling - self.controlling_loop - } - } - } - } - - fn masm_block_id(&self, block: hir::Block) -> masm::BlockId { - self.block_infos.get(block).unwrap().target - } - - /// Get a mutable reference to the current block of code in the stack machine IR - #[inline(always)] - fn current_block(&mut self) -> &mut masm::Block { - self.function.f_prime.body.block_mut(self.target) - } - - /// Get a mutable reference to a specific block of code in the stack machine IR - #[inline(always)] - fn block(&mut self, block: masm::BlockId) -> &mut masm::Block { - self.function.f_prime.body.block_mut(block) - } - - #[inline] - fn emit_op(&mut self, op: Op, span: SourceSpan) { - self.current_block().push(op, span); - } - - #[inline] - fn emit_op_to(&mut self, block: masm::BlockId, op: Op, span: SourceSpan) { - self.block(block).push(op, span); - } - - #[inline] - fn emit_ops(&mut self, ops: impl IntoIterator, span: SourceSpan) { - self.current_block().extend(ops.into_iter().map(|op| Span::new(span, op))); - } - - fn controlling_loop_level(&self) -> Option { - self.controlling_loop.map(|lp| self.function.loops.level(lp).level()) - } - - fn loop_level(&self, block: hir::Block) -> usize { - self.function.loops.loop_level(block).level() - } - - #[inline(always)] - fn inst_emitter<'short, 'long: 'short>( - &'long mut self, - inst: hir::Inst, - ) -> InstOpEmitter<'short> { - InstOpEmitter::new( - self.function.f_prime, - &self.function.f.dfg, - inst, - self.target, - &mut self.stack, - ) - } - - #[inline(always)] - fn emitter<'short, 'long: 'short>(&'long mut self) -> OpEmitter<'short> { - OpEmitter::new(self.function.f_prime, self.target, &mut self.stack) - } -} diff --git a/codegen/masm/src/codegen/mod.rs b/codegen/masm/src/codegen/mod.rs deleted file mode 100644 index 2f025858e..000000000 --- a/codegen/masm/src/codegen/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -mod emit; -mod emitter; -mod opt; -mod scheduler; -mod stack; - -pub use self::{ - emitter::FunctionEmitter, - scheduler::Scheduler, - stack::{Constraint, Operand, OperandStack, TypedValue}, -}; diff --git a/codegen/masm/src/codegen/opt/operands/context.rs b/codegen/masm/src/codegen/opt/operands/context.rs deleted file mode 100644 index 0110816f5..000000000 --- a/codegen/masm/src/codegen/opt/operands/context.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::{collections::BTreeMap, num::NonZeroU8}; - -use midenc_hir as hir; - -use super::{SolverError, Stack, ValueOrAlias}; -use crate::codegen::Constraint; - -/// The context associated with an instance of [OperandMovementConstraintSolver]. -/// -/// Contained in this context is the current state of the stack, the expected operands, -/// the constraints on those operands, and metadata about copied operands. -#[derive(Debug)] -pub struct SolverContext { - stack: Stack, - expected: Stack, - copies: CopyInfo, -} -impl SolverContext { - pub fn new( - expected: &[hir::Value], - constraints: &[Constraint], - stack: &crate::codegen::OperandStack, - ) -> Result { - // Compute the expected output on the stack, as well as alias/copy information - let mut stack = Stack::from(stack); - let mut expected_output = Stack::default(); - let mut copies = CopyInfo::default(); - for (value, constraint) in expected.iter().rev().zip(constraints.iter().rev()) { - let value = ValueOrAlias::from(*value); - match constraint { - // If we observe a value with move semantics, then it is - // always referencing the original value - Constraint::Move => { - expected_output.push(value); - } - // If we observe a value with copy semantics, then the expected - // output is always an alias, because the original would need to - // be preserved - Constraint::Copy => { - expected_output.push(copies.push(value)); - } - } - } - - // Rename multiple occurrences of the same value on the operand stack, if present - let mut dupes = CopyInfo::default(); - for operand in stack.iter_mut().rev() { - operand.value = dupes.push_if_duplicate(operand.value); - } - - // Determine if the stack is already in the desired order - let requires_copies = copies.copies_required(); - let is_solved = !requires_copies - && expected_output.iter().rev().all(|op| &stack[op.pos as usize] == op); - if is_solved { - return Err(SolverError::AlreadySolved); - } - - Ok(Self { - stack, - expected: expected_output, - copies, - }) - } - - /// Returns the number of operands expected by the current instruction - #[inline] - pub fn arity(&self) -> usize { - self.expected.len() - } - - /// Get a reference to the copy analysis results - #[inline(always)] - pub fn copies(&self) -> &CopyInfo { - &self.copies - } - - /// Get a reference to the state of the stack at the current program point - #[inline(always)] - pub fn stack(&self) -> &Stack { - &self.stack - } - - /// Get a [Stack] representing the state of the stack for a valid solution. - /// - /// NOTE: The returned stack only contains the expected operands, not the full stack - #[inline(always)] - pub fn expected(&self) -> &Stack { - &self.expected - } - - /// Return true if the given stack matches what is expected - /// if a solution was correctly found. - pub fn is_solved(&self, pending: &Stack) -> bool { - debug_assert!(pending.len() >= self.expected.len()); - self.expected.iter().all(|o| pending.contains(o)) - } -} - -#[derive(Debug, Default)] -pub struct CopyInfo { - copies: BTreeMap, - num_copies: u8, -} -impl CopyInfo { - /// Returns the number of copies recorded in this structure - #[inline(always)] - pub fn len(&self) -> usize { - self.num_copies as usize - } - - /// Returns true if there are no copied values - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.num_copies == 0 - } - - /// Push a new copy of `value`, returning an alias of that value - /// - /// NOTE: It is expected that `value` is not an alias. - pub fn push(&mut self, value: ValueOrAlias) -> ValueOrAlias { - use std::collections::btree_map::Entry; - - assert!(!value.is_alias()); - - self.num_copies += 1; - match self.copies.entry(value) { - Entry::Vacant(entry) => { - entry.insert(1); - value.copy(unsafe { NonZeroU8::new_unchecked(1) }) - } - Entry::Occupied(mut entry) => { - let next_id = entry.get_mut(); - *next_id += 1; - value.copy(unsafe { NonZeroU8::new_unchecked(*next_id) }) - } - } - } - - /// Push a copy of `value`, but only if `value` has already been seen - /// at least once, i.e. `value` is a duplicate. - /// - /// NOTE: It is expected that `value` is not an alias. - pub fn push_if_duplicate(&mut self, value: ValueOrAlias) -> ValueOrAlias { - use std::collections::btree_map::Entry; - - assert!(!value.is_alias()); - - match self.copies.entry(value) { - // `value` is not a duplicate - Entry::Vacant(entry) => { - entry.insert(0); - value - } - // `value` is a duplicate, record it as such - Entry::Occupied(mut entry) => { - self.num_copies += 1; - let next_id = entry.get_mut(); - *next_id += 1; - value.copy(unsafe { NonZeroU8::new_unchecked(*next_id) }) - } - } - } - - /// Returns true if `value` has at least one copy - pub fn has_copies(&self, value: &ValueOrAlias) -> bool { - self.copies.get(value).map(|count| *count > 0).unwrap_or(false) - } - - /// Returns true if any of the values seen so far have copies - pub fn copies_required(&self) -> bool { - self.copies.values().any(|count| *count > 0) - } -} diff --git a/codegen/masm/src/codegen/opt/operands/mod.rs b/codegen/masm/src/codegen/opt/operands/mod.rs deleted file mode 100644 index 1de7d43a2..000000000 --- a/codegen/masm/src/codegen/opt/operands/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -mod context; -mod solver; -mod stack; -mod tactics; - -use std::{fmt, num::NonZeroU8}; - -use midenc_hir as hir; - -pub use self::solver::{OperandMovementConstraintSolver, SolverError}; -use self::{context::SolverContext, stack::Stack}; - -/// This represents a specific action that should be taken by -/// the code generator with regard to an operand on the stack. -/// -/// The output of the optimizer is a sequence of these actions, -/// the effect of which is to place all of the current instruction's -/// operands exactly where they need to be, just when they are -/// needed. -#[derive(Debug, Copy, Clone)] -pub enum Action { - /// Copy the operand at the given index to the top of the stack - Copy(u8), - /// Swap the operand at the given index with the one on top of the stack - Swap(u8), - /// Move the operand at the given index to the top of the stack - MoveUp(u8), - /// Move the operand at the top of the stack to the given index - MoveDown(u8), -} - -/// This is a [midenc_hir::Value], but with a modified encoding that lets -/// us uniquely identify aliases of a value on the operand stack during -/// analysis. -/// -/// Aliases of a value are treated as unique values for purposes of operand -/// stack management, but are associated with multiple copies of a value -/// on the stack. -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct ValueOrAlias(u32); -impl ValueOrAlias { - const ALIAS_MASK: u32 = (u8::MAX as u32) << 23; - - /// Create a new [Value] with the given numeric identifier. - /// - /// The given identifier must have the upper 8 bits zeroed, or this function will panic. - pub fn new(id: u32) -> Self { - assert_eq!(id & Self::ALIAS_MASK, 0); - Self(id) - } - - /// Create an aliased copy of this value, using `id` to uniquely identify the alias. - /// - /// NOTE: You must ensure that each alias of the same value gets a unique identifier, - /// or you may observe strange behavior due to two aliases that should be distinct, - /// being treated as if they have the same identity. - pub fn copy(self, id: NonZeroU8) -> Self { - Self(self.id() | ((id.get() as u32) << 23)) - } - - /// Get an un-aliased copy of this value - pub fn unaliased(self) -> Self { - Self(self.id()) - } - - /// Convert this value into an alias, using `id` to uniquely identify the alias. - /// - /// NOTE: You must ensure that each alias of the same value gets a unique identifier, - /// or you may observe strange behavior due to two aliases that should be distinct, - /// being treated as if they have the same identity. - pub fn set_alias(&mut self, id: NonZeroU8) { - self.0 = self.id() | ((id.get() as u32) << 23); - } - - /// Get the raw u32 value of the original [midenc_hir::Value] - pub fn id(self) -> u32 { - self.0 & !Self::ALIAS_MASK - } - - /// Get the unique alias identifier for this value, if this value is an alias - pub fn alias(self) -> Option { - NonZeroU8::new(((self.0 & Self::ALIAS_MASK) >> 23) as u8) - } - - /// Get the unique alias identifier for this value, if this value is an alias - pub fn unwrap_alias(self) -> NonZeroU8 { - NonZeroU8::new(((self.0 & Self::ALIAS_MASK) >> 23) as u8) - .unwrap_or_else(|| panic!("expected {self:?} to be an alias")) - } - - /// Returns true if this value is an alias - pub fn is_alias(&self) -> bool { - self.0 & Self::ALIAS_MASK != 0 - } -} -impl Ord for ValueOrAlias { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.id().cmp(&other.id()).then_with(|| { - let self_alias = self.alias().map(|nz| nz.get()).unwrap_or(0); - let other_alias = self.alias().map(|nz| nz.get()).unwrap_or(0); - self_alias.cmp(&other_alias) - }) - } -} -impl PartialEq for ValueOrAlias { - fn eq(&self, other: &hir::Value) -> bool { - self.id() == other.as_u32() - } -} -impl PartialOrd for ValueOrAlias { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl From for ValueOrAlias { - #[inline] - fn from(value: hir::Value) -> Self { - Self::new(value.as_u32()) - } -} -impl From for hir::Value { - #[inline] - fn from(value: ValueOrAlias) -> Self { - Self::from_u32(value.id()) - } -} -impl fmt::Debug for ValueOrAlias { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.alias() { - None => write!(f, "v{}", self.id()), - Some(alias) => write!(f, "v{}.{alias}", self.id()), - } - } -} -#[cfg(test)] -impl proptest::arbitrary::Arbitrary for ValueOrAlias { - type Parameters = (); - type Strategy = proptest::strategy::Map, fn(u8) -> Self>; - - fn arbitrary_with((): Self::Parameters) -> Self::Strategy { - use proptest::strategy::Strategy; - proptest::arbitrary::any::().prop_map(|id| ValueOrAlias(id as u32)) - } -} - -/// This is an simple representation of an operand on the operand stack -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Operand { - /// The position of this operand on the corresponding stack - pub pos: u8, - /// The value this operand corresponds to - pub value: ValueOrAlias, -} -impl From<(usize, ValueOrAlias)> for Operand { - #[inline(always)] - fn from(pair: (usize, ValueOrAlias)) -> Self { - Self { - pos: pair.0 as u8, - value: pair.1, - } - } -} -impl PartialEq for Operand { - #[inline(always)] - fn eq(&self, other: &ValueOrAlias) -> bool { - self.value.eq(other) - } -} diff --git a/codegen/masm/src/codegen/opt/operands/solver.rs b/codegen/masm/src/codegen/opt/operands/solver.rs deleted file mode 100644 index 79e2a1fd6..000000000 --- a/codegen/masm/src/codegen/opt/operands/solver.rs +++ /dev/null @@ -1,792 +0,0 @@ -use midenc_hir::{self as hir, SourceSpan}; -use smallvec::SmallVec; - -use super::{tactics::Tactic, *}; -use crate::codegen::Constraint; - -/// This error type is produced by the [OperandMovementConstraintSolver] -#[derive(Debug)] -pub enum SolverError { - /// The current operand stack represents a valid solution already - AlreadySolved, - /// All of the tactics we tried failed - NoSolution, -} - -/// The [OperandMovementConstraintSolver] is used to produce a solution to the following problem: -/// -/// An instruction is being emitted which requires some specific set of operands, in a particular -/// order. These operands are known to be on the operand stack, but their usage is constrained by a -/// rule that determines whether a specific use of an operand can consume the operand, or must copy -/// it and consume the copy. Furthermore, the operands on the stack are not guaranteed to be in the -/// desired order, so we must also move operands into position while operating within the bounds of -/// the move/copy constraints. -/// -/// Complicating matters further, a naive approach to solving this problem will produce a lot of -/// unnecessary stack manipulation instructions in the emitted code. We would like the code we emit -/// to match what a human might write if facing the same set of constraints. As a result, we are -/// looking for a solution to this problem that is also the "smallest" solution, i.e. the least -/// expensive solution in terms of cycle count. -/// -/// ## Implementation -/// -/// With that context in mind, what we have here is a non-trivial optimization problem. If we could -/// treat the operand stack as an array, and didn't have to worry about copies, we could solve this -/// using a standard minimum-swap solution, but neither of those are true here. The copy constraint, -/// when present, means that even if the stack is in the exact order we need, we must still find a -/// way to copy the operands we are required to copy, move the ones we are required to consume, and -/// do so in such a way that getting them into the required order on top of the stack takes the -/// minimum number of steps. -/// -/// Even this would be relatively straightforward, but an additional problem is that the MASM -/// instruction set does not provide us a way to swap two operands at arbitrary positions on the -/// stack. We are forced to move operands to the top of the stack before we can move them elsewhere -/// (either by swapping them with the current operand on top of the stack, or by moving the operand -/// up to the top, shifting all the remaining operands on the stack down by one). However, moving a -/// value up/down the stack also has the effect of shifting other values on the stack, which may -/// shift them in to, or out of, position. -/// -/// Long story short, all of this must be taken into consideration at once, which is extremely -/// difficult to express in a way that is readable/maintainable, but also debuggable if something -/// goes wrong. -/// -/// To address these concerns, the [OperandMovementConstraintSolver] is architected as follows: -/// -/// We expect to receive as input to the solver: -/// -/// * The set of expected operand values -/// * The set of move/copy constraints corresponding to each of the expected operands -/// * The current state of the operand stack at this point in the program -/// -/// The solver produces one of three possible outcomes: -/// -/// * `Ok(solution)`, where `solution` is a vector of actions the code generator must take to get -/// the operands into place correctly -/// * `Err(AlreadySolved)`, indicating that the solver is not needed, and the stack is usable as-is -/// * `Err(_)`, indicating an unrecoverable error that prevented the solver from finding a solution -/// with the given inputs -/// -/// When the solver is constructed, it performs the following steps: -/// -/// 1. Identify and rename aliased values to make them unique (i.e. multiple uses of the same value -/// will be uniqued) -/// 2. Determine if any expected operands require copying (if so, then the solver is always -/// required) -/// 3. Determine if the solver is required for the given inputs, and if not, return -/// `Err(AlreadySolved)` -/// -/// When the solver is run, it attempts to find an optimal solution using the following algorithm: -/// -/// 1. Pick a tactic to try and produce a solution for the given set of constraints. -/// 2. If the tactic failed, go back to step 1. -/// 3. If the tactic succeeded, take the best solution between the one we just produced, and the -/// last one produced (if applicable). -/// 4. If we have optimization fuel remaining, go back to step 1 and see if we can find a better -/// solution. -/// 5. If we have a solution, and either run out of optimization fuel, or tactics to try, then that -/// solution is returned. -/// 6. If we haven't found a solution, then return an error -pub struct OperandMovementConstraintSolver { - context: SolverContext, - tactics: SmallVec<[Box; 4]>, - /// An integer representing the amount of optimization fuel we have available - fuel: usize, -} -impl OperandMovementConstraintSolver { - /// Construct a new solver for the given expected operands, constraints, and operand stack - /// state. - pub fn new( - expected: &[hir::Value], - constraints: &[Constraint], - stack: &crate::codegen::OperandStack, - ) -> Result { - assert_eq!(expected.len(), constraints.len()); - - let context = SolverContext::new(expected, constraints, stack)?; - - Ok(Self { - context, - tactics: Default::default(), - fuel: 25, - }) - } - - /// Set the quantity of optimization fuel the solver has to work with - #[allow(unused)] - pub fn set_optimization_fuel(&mut self, fuel: usize) { - self.fuel = fuel; - } - - /// Compute a solution that can be used to get the stack into the correct state - pub fn solve(mut self) -> Result, SolverError> { - use super::tactics::*; - - // We use a few heuristics to guide which tactics we try: - // - // * If all operands are copies, we only apply copy-all - // * If copies are needed, we only apply tactics which support copies, or a mix of copies - // and moves. - // * If no copies are needed, we start with the various move up/down + swap patterns, as - // many common patterns are solved in two moves or less with them. If no tactics are - // successful, move-all is used as the fallback. - // * If we have no optimization fuel, we do not attempt to look for better solutions once - // we've found one. - // * If we have optimization fuel, we will try additional tactics looking for a solution - // until we have exhausted the fuel, assuming the solution we do have can be minimized. - // For example, a solution which requires less than two actions is by definition optimal - // already, so we never waste time on optimization in such cases. - - // The tactics are pushed in reverse order - if self.tactics.is_empty() { - if self.context.copies().is_empty() { - self.tactics.push(Box::new(Linear)); - self.tactics.push(Box::new(SwapAndMoveUp)); - self.tactics.push(Box::new(MoveUpAndSwap)); - self.tactics.push(Box::new(MoveDownAndSwap)); - } else { - self.tactics.push(Box::new(Linear)); - self.tactics.push(Box::new(CopyAll)); - } - } - - // Now that we know what constraints are in place, we can derive - // a strategy to solve for those constraints. The overall strategy - // is a restricted backtracking search based on a number of predefined - // tactics for permuting the stack. The search is restricted because - // we do not try every possible combination of tactics, and instead - // follow a shrinking strategy that always subdivides the problem if - // a larger tactic doesn't succeed first. The search proceeds until - // a solution is derived, or we cannot proceed any further, in which - // case we fall back to the most naive approach possible - copying - // items to the top of the stack one after another until all arguments - // are in place. - // - // Some tactics are derived simply by the number of elements involved, - // others based on the fact that all copies are required, or all moves. - // Many solutions are trivially derived from a given set of constraints, - // we aim simply to recognize common patterns recognized by a human and - // apply those solutions in such a way that we produce code like we would - // by hand when preparing instruction operands - let mut best_solution: Option> = None; - let mut builder = SolutionBuilder::new(&self.context); - while let Some(mut tactic) = self.tactics.pop() { - match tactic.apply(&mut builder) { - // The tactic was applied successfully - Ok(_) => { - if builder.is_valid() { - let solution = builder.take(); - let solution_size = solution.len(); - let best_size = best_solution.as_ref().map(|best| best.len()); - match best_size { - Some(best_size) if best_size > solution_size => { - best_solution = Some(solution); - log::trace!( - "a better solution ({solution_size} vs {best_size}) was found \ - using tactic {}", - tactic.name() - ); - } - Some(best_size) => { - log::trace!( - "a solution of size {solution_size} was found using tactic \ - {}, but it is no better than the best found so far \ - ({best_size})", - tactic.name() - ); - } - None => { - best_solution = Some(solution); - log::trace!( - "an initial solution of size {solution_size} was found using \ - tactic {}", - tactic.name() - ); - } - } - } else { - log::trace!( - "a partial solution was found using tactic {}, but is not sufficient \ - on its own", - tactic.name() - ); - builder.discard(); - } - } - Err(_) => { - log::trace!("tactic {} could not be applied", tactic.name()); - builder.discard(); - } - } - let remaining_fuel = self.fuel.saturating_sub(tactic.cost(&self.context)); - if remaining_fuel == 0 { - log::trace!("no more optimization fuel, using the best solution found so far"); - break; - } - self.fuel = remaining_fuel; - } - - best_solution.take().ok_or(SolverError::NoSolution) - } - - #[track_caller] - pub fn solve_and_apply( - self, - emitter: &mut crate::codegen::emit::OpEmitter<'_>, - span: SourceSpan, - ) -> Result<(), SolverError> { - match self.context.arity() { - // No arguments, nothing to solve - 0 => Ok(()), - // Only one argument, solution is trivial - 1 => { - let expected = self.context.expected()[0]; - if let Some(current_position) = self.context.stack().position(&expected.value) { - if current_position > 0 { - emitter.move_operand_to_position(current_position, 0, false, span); - } - } else { - assert!( - self.context.copies().has_copies(&expected.value), - "{:?} was not found on the operand stack", - expected.value - ); - let current_position = - self.context.stack().position(&expected.value.unaliased()).unwrap_or_else( - || { - panic!( - "{:?} was not found on the operand stack", - expected.value.unaliased() - ) - }, - ); - emitter.copy_operand_to_position(current_position, 0, false, span); - } - - Ok(()) - } - // Run the solver for more than 1 argument - _ => { - let actions = self.solve()?; - for action in actions.into_iter() { - match action { - Action::Copy(index) => { - emitter.copy_operand_to_position(index as usize, 0, false, span); - } - Action::Swap(index) => { - emitter.swap(index, span); - } - Action::MoveUp(index) => { - emitter.movup(index, span); - } - Action::MoveDown(index) => { - emitter.movdn(index, span); - } - } - } - - Ok(()) - } - } - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{self as hir, Type}; - use proptest::{prelude::*, test_runner::TestRunner}; - - use super::*; - - #[allow(unused)] - fn setup() { - use log::LevelFilter; - let _ = env_logger::builder() - .filter_level(LevelFilter::Trace) - .format_timestamp(None) - .is_test(true) - .try_init(); - } - - #[test] - fn operand_movement_constraint_solver_example() { - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - let v3 = hir::Value::from_u32(3); - let v4 = hir::Value::from_u32(4); - let v5 = hir::Value::from_u32(5); - let v6 = hir::Value::from_u32(6); - - let tests = [[v2, v1, v3, v4, v5, v6], [v2, v4, v3, v1, v5, v6]]; - - for test in tests.into_iter() { - let mut stack = crate::codegen::OperandStack::default(); - for value in test.into_iter().rev() { - stack.push(crate::codegen::TypedValue { - ty: Type::I32, - value, - }); - } - let expected = [v1, v2, v3, v4, v5]; - let constraints = [Constraint::Move; 5]; - - match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { - Ok(solver) => { - let result = solver.solve().expect("no solution found"); - assert!(result.len() <= 3, "expected solution of 3 moves or less"); - } - Err(SolverError::AlreadySolved) => panic!("already solved"), - Err(err) => panic!("invalid solver context: {err:?}"), - } - } - } - - #[test] - fn operand_movement_constraint_solver_two_moves() { - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - let v3 = hir::Value::from_u32(3); - let v4 = hir::Value::from_u32(4); - let v5 = hir::Value::from_u32(5); - let v6 = hir::Value::from_u32(6); - - // Should take two moves - let tests = [ - [v5, v4, v2, v3, v1, v6], - [v4, v5, v1, v2, v3, v6], - [v5, v2, v1, v3, v4, v6], - [v1, v3, v2, v4, v5, v6], - [v5, v2, v1, v4, v3, v6], - [v1, v3, v4, v2, v5, v6], - [v4, v3, v2, v1, v5, v6], - [v4, v3, v2, v1, v5, v6], - ]; - - for test in tests.into_iter() { - let mut stack = crate::codegen::OperandStack::default(); - for value in test.into_iter().rev() { - stack.push(crate::codegen::TypedValue { - ty: Type::I32, - value, - }); - } - let expected = [v1, v2, v3, v4, v5]; - let constraints = [Constraint::Move; 5]; - - match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { - Ok(solver) => { - let result = solver.solve().expect("no solution found"); - assert!( - result.len() <= 2, - "expected solution of 2 moves or less, got {result:?}" - ); - } - Err(SolverError::AlreadySolved) => panic!("already solved"), - Err(err) => panic!("invalid solver context: {err:?}"), - } - } - } - - #[test] - fn operand_movement_constraint_solver_one_move() { - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - let v3 = hir::Value::from_u32(3); - let v4 = hir::Value::from_u32(4); - let v5 = hir::Value::from_u32(5); - let v6 = hir::Value::from_u32(6); - - // Should take one move - let tests = [ - [v2, v3, v1, v4, v5, v6], - [v4, v1, v2, v3, v5, v6], - [v4, v2, v3, v1, v5, v6], - [v2, v1, v3, v4, v5, v6], - ]; - - for test in tests.into_iter() { - let mut stack = crate::codegen::OperandStack::default(); - for value in test.into_iter().rev() { - stack.push(crate::codegen::TypedValue { - ty: Type::I32, - value, - }); - } - let expected = [v1, v2, v3, v4, v5]; - let constraints = [Constraint::Move; 5]; - - match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { - Ok(solver) => { - let result = solver.solve().expect("no solution found"); - assert!( - result.len() <= 1, - "expected solution of 1 move or less, got {result:?}" - ); - } - Err(SolverError::AlreadySolved) => panic!("already solved"), - Err(err) => panic!("invalid solver context: {err:?}"), - } - } - } - - /// This test reproduces https://github.com/0xPolygonMiden/compiler/issues/200 - /// where v7 value should be duplicated on the stack - #[test] - fn operand_movement_constraint_solver_duplicate() { - setup(); - let v7 = hir::Value::from_u32(7); - let v16 = hir::Value::from_u32(16); - let v32 = hir::Value::from_u32(32); - let v0 = hir::Value::from_u32(0); - - let tests = [[v32, v7, v16, v0]]; - - for test in tests.into_iter() { - let mut stack = crate::codegen::OperandStack::default(); - for value in test.into_iter().rev() { - stack.push(crate::codegen::TypedValue { - ty: Type::I32, - value, - }); - } - // The v7 is expected to be twice on stack - let expected = [v7, v7, v32, v16]; - let constraints = [Constraint::Copy; 4]; - - match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { - Ok(solver) => { - let _result = solver.solve().expect("no solution found"); - } - Err(SolverError::AlreadySolved) => panic!("already solved"), - Err(err) => panic!("invalid solver context: {err:?}"), - } - } - } - - // Strategy: - // - // 1. Generate a set of 1..16 operands to form a stack (called `stack`), with no more than 2 - // pairs of duplicate operands - // 2. Generate a set of up to 8 constraints (called `constraints`) by sampling `stack` twice, - // and treating duplicate samples as copies - // 3. Generate the set of expected operands by mapping `constraints` to values - #[derive(Debug)] - struct ProblemInputs { - stack: crate::codegen::OperandStack, - expected: Vec, - constraints: Vec, - } - - fn shuffled_value_stack(size: usize) -> proptest::strategy::Shuffle>> { - let mut next_id = 0; - let mut raw_stack = Vec::with_capacity(size); - raw_stack.resize_with(size, || { - let id = next_id; - next_id += 1; - hir::Value::from_u32(id) - }); - Just(raw_stack).prop_shuffle() - } - - fn copy_all(arity: usize) -> impl Strategy { - Just((1usize << arity) - 1) - } - - fn copy_some(range: core::ops::RangeInclusive) -> impl Strategy { - let max = *range.end(); - proptest::bits::usize::sampled(0..max, range) - } - - fn copy_any(arity: usize) -> impl Strategy { - let min = core::cmp::min(1, arity); - prop_oneof![ - CopyStrategy::all(arity), - CopyStrategy::none(arity), - CopyStrategy::some(min..=arity), - ] - } - - #[derive(Debug, Clone)] - struct CopyStrategy { - strategy: proptest::strategy::BoxedStrategy, - arity: u8, - min: u8, - max: u8, - } - impl CopyStrategy { - /// The simplest strategy, always solvable by copying - pub fn all(arity: usize) -> Self { - assert!(arity <= 16); - let max = arity as u8; - let strategy = if arity == 0 { - Just(0usize).boxed() - } else if arity == 1 { - Just(1usize).boxed() - } else { - proptest::bits::usize::sampled(1..arity, 0..arity).boxed() - }; - Self { - strategy, - arity: max, - min: max, - max, - } - } - - /// The next simplest strategy, avoids complicating strategies with copies - pub fn none(arity: usize) -> Self { - assert!(arity <= 16); - let max = arity as u8; - let strategy = if arity == 0 { - Just(0usize).boxed() - } else if arity == 1 { - Just(1usize).boxed() - } else { - proptest::bits::usize::sampled(1..arity, 0..arity).boxed() - }; - Self { - strategy, - arity: max, - min: 0, - max: 0, - } - } - - /// The most complicated strategy, - pub fn some(range: core::ops::RangeInclusive) -> Self { - let min = *range.start(); - let max = *range.end(); - assert!(max <= 16); - let strategy = if max == 0 { - Just(0usize).boxed() - } else if max == 1 { - Just(1usize).boxed() - } else { - proptest::bits::usize::sampled(0..max, range).boxed() - }; - let arity = max as u8; - Self { - strategy, - arity, - min: min as u8, - max: arity, - } - } - } - impl Strategy for CopyStrategy { - type Tree = CopyStrategyValueTree; - type Value = usize; - - fn new_tree(&self, runner: &mut TestRunner) -> proptest::strategy::NewTree { - let tree = self.strategy.new_tree(runner)?; - Ok(CopyStrategyValueTree { - tree, - arity: self.arity, - min: self.min, - max: self.max, - prev: (self.min, self.max), - hi: (0, self.max), - }) - } - } - - struct CopyStrategyValueTree { - tree: Box>, - arity: u8, - min: u8, - max: u8, - prev: (u8, u8), - hi: (u8, u8), - } - impl proptest::strategy::ValueTree for CopyStrategyValueTree { - type Value = usize; - - fn current(&self) -> Self::Value { - match (self.min, self.max) { - (0, 0) => 0, - (min, max) if min == max => (1 << max as usize) - 1, - _ => self.tree.current(), - } - } - - fn simplify(&mut self) -> bool { - match (self.min, self.max) { - (0, 0) => { - self.hi = (0, 0); - self.min = self.arity; - self.max = self.arity; - true - } - (min, max) if min == max => { - self.hi = (min, max); - false - } - current => { - self.hi = current; - if !self.tree.simplify() { - self.min = 0; - self.max = 0; - } - true - } - } - } - - fn complicate(&mut self) -> bool { - match (self.min, self.max) { - current if current == self.hi => false, - (0, 0) => { - self.min = self.prev.0; - self.max = self.prev.1; - true - } - (min, max) if min == max => { - self.min = 0; - self.max = 0; - true - } - _ => self.tree.complicate(), - } - } - } - - fn make_problem_inputs( - raw_stack: Vec, - arity: usize, - copies: usize, - ) -> ProblemInputs { - use proptest::bits::BitSetLike; - - let mut stack = crate::codegen::OperandStack::default(); - let mut expected = Vec::with_capacity(arity); - let mut constraints = Vec::with_capacity(arity); - for value in raw_stack.into_iter().rev() { - stack.push(crate::codegen::TypedValue { - ty: hir::Type::I32, - value, - }); - } - for id in 0..arity { - let value = hir::Value::from_u32(id as u32); - expected.push(value); - if copies.test(id) { - constraints.push(Constraint::Copy); - } else { - constraints.push(Constraint::Move); - } - } - ProblemInputs { - stack, - expected, - constraints, - } - } - - prop_compose! { - fn generate_copy_any_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 0..=stack_size))) - (copies in copy_any(arity), raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { - make_problem_inputs(raw_stack, arity, copies) - } - } - - prop_compose! { - fn generate_copy_none_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 0..=stack_size))) - (raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { - make_problem_inputs(raw_stack, arity, 0) - } - } - - prop_compose! { - fn generate_copy_all_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 0..=stack_size))) - (copies in copy_all(arity), raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { - make_problem_inputs(raw_stack, arity, copies) - } - } - - prop_compose! { - fn generate_copy_some_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 1..=stack_size))) - (copies in copy_some(1..=arity), raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { - make_problem_inputs(raw_stack, arity, copies) - } - } - - fn run_solver(problem: ProblemInputs) -> Result<(), TestCaseError> { - match OperandMovementConstraintSolver::new( - &problem.expected, - &problem.constraints, - &problem.stack, - ) { - Ok(mut solver) => { - solver.set_optimization_fuel(10); - let result = solver.solve(); - // We are expecting solutions for all inputs - prop_assert!( - result.is_ok(), - "solver returned error {result:?} for problem: {problem:#?}" - ); - let actions = result.unwrap(); - // We are expecting that if all operands are copies, that the number of actions is - // equal to the number of copies - if problem.constraints.iter().all(|c| matches!(c, Constraint::Copy)) { - prop_assert_eq!(actions.len(), problem.expected.len()); - } - // We are expecting that applying `actions` to the input stack will produce a stack - // that has all of the expected operands on top of the stack, - // ordered by id, e.g. [v1, v2, ..vN] - let mut stack = problem.stack.clone(); - for action in actions.into_iter() { - match action { - Action::Copy(index) => { - stack.dup(index as usize); - } - Action::Swap(index) => { - stack.swap(index as usize); - } - Action::MoveUp(index) => { - stack.movup(index as usize); - } - Action::MoveDown(index) => { - stack.movdn(index as usize); - } - } - } - for index in 0..problem.expected.len() { - let expected = hir::Value::from_u32(index as u32); - prop_assert_eq!( - &stack[index], - &expected, - "solution did not place {} at the correct location on the stack", - expected - ); - } - - Ok(()) - } - Err(SolverError::AlreadySolved) => Ok(()), - Err(err) => panic!("invalid solver context: {err:?}"), - } - } - - proptest! { - #![proptest_config(ProptestConfig::with_cases(1000))] - - #[test] - fn operand_movement_constraint_solver_copy_any(problem in generate_copy_any_problem()) { - run_solver(problem)? - } - - #[test] - fn operand_movement_constraint_solver_copy_none(problem in generate_copy_none_problem()) { - run_solver(problem)?; - } - - #[test] - fn operand_movement_constraint_solver_copy_all(problem in generate_copy_all_problem()) { - run_solver(problem)?; - } - - #[test] - fn operand_movement_constraint_solver_copy_some(problem in generate_copy_some_problem()) { - run_solver(problem)?; - } - } -} diff --git a/codegen/masm/src/codegen/opt/operands/stack.rs b/codegen/masm/src/codegen/opt/operands/stack.rs deleted file mode 100644 index 7b1c1148d..000000000 --- a/codegen/masm/src/codegen/opt/operands/stack.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::collections::VecDeque; - -use super::*; - -/// This implements a stack data structure for [Operand] -#[derive(Default, Debug, Clone)] -pub struct Stack { - stack: Vec, -} -impl FromIterator for Stack { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let mut stack = VecDeque::new(); - for value in iter.into_iter() { - stack.push_front(Operand { pos: 0, value }); - } - let mut stack = Vec::from(stack); - for (pos, operand) in stack.iter_mut().rev().enumerate() { - operand.pos = pos as u8; - } - Self { stack } - } -} -impl From<&crate::codegen::OperandStack> for Stack { - fn from(stack: &crate::codegen::OperandStack) -> Self { - Self::from_iter(stack.iter().rev().map(|o| { - o.as_value() - .unwrap_or_else(|| panic!("expected value operand, got {o:#?}")) - .into() - })) - } -} -impl Stack { - pub fn len(&self) -> usize { - self.stack.len() - } - - pub fn push(&mut self, value: ValueOrAlias) { - self.stack.push(Operand { pos: 0, value }); - if self.stack.len() > 1 { - for (pos, operand) in self.iter_mut().rev().enumerate() { - operand.pos = pos as u8; - } - } - } - - pub fn position(&self, value: &ValueOrAlias) -> Option { - self.stack.iter().rev().position(|o| value == &o.value) - } - - pub fn contains(&self, operand: &Operand) -> bool { - self[operand.pos as usize].value == operand.value - } - - pub fn iter(&self) -> impl DoubleEndedIterator { - self.stack.iter() - } - - pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { - self.stack.iter_mut() - } - - pub fn dup(&mut self, n: usize, alias_id: core::num::NonZeroU8) { - let value = self[n].value; - self.stack.push(Operand { - pos: 0, - value: value.copy(alias_id), - }); - for (pos, operand) in self.stack.iter_mut().rev().enumerate() { - operand.pos = pos as u8; - } - } - - pub fn swap(&mut self, n: usize) { - let len = self.stack.len(); - let a = len - 1; - let b = a - n; - let a_pos = self.stack[a].pos; - let b_pos = self.stack[b].pos; - self.stack.swap(a, b); - self.stack[a].pos = a_pos; - self.stack[b].pos = b_pos; - } - - pub fn movup(&mut self, n: usize) { - let len = self.stack.len(); - let mid = len - (n + 1); - let (_, r) = self.stack.split_at_mut(mid); - r.rotate_left(1); - for (pos, operand) in r.iter_mut().rev().enumerate() { - operand.pos = pos as u8; - } - } - - pub fn movdn(&mut self, n: usize) { - let len = self.stack.len(); - let mid = len - (n + 1); - let (_, r) = self.stack.split_at_mut(mid); - r.rotate_right(1); - for (pos, operand) in r.iter_mut().rev().enumerate() { - operand.pos = pos as u8; - } - } - - pub fn reset_to(&mut self, snapshot: &Self) { - self.stack.clear(); - let x = self.stack.capacity(); - let y = snapshot.stack.capacity(); - if x != y { - let a = core::cmp::max(x, y); - if a > x { - self.stack.reserve(a - x); - } - } - self.stack.extend_from_slice(&snapshot.stack); - } - - pub fn get(&self, index: usize) -> Option<&Operand> { - let len = self.stack.len(); - self.stack.get(len - index - 1) - } -} -impl core::ops::Index for Stack { - type Output = Operand; - - fn index(&self, index: usize) -> &Self::Output { - let len = self.stack.len(); - &self.stack[len - index - 1] - } -} -impl core::ops::IndexMut for Stack { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - let len = self.stack.len(); - &mut self.stack[len - index - 1] - } -} diff --git a/codegen/masm/src/codegen/opt/operands/tactics/linear.rs b/codegen/masm/src/codegen/opt/operands/tactics/linear.rs deleted file mode 100644 index 5b29a44af..000000000 --- a/codegen/masm/src/codegen/opt/operands/tactics/linear.rs +++ /dev/null @@ -1,213 +0,0 @@ -use midenc_hir::adt::SmallSet; -use petgraph::prelude::{DiGraphMap, Direction}; - -use super::*; - -/// This tactic produces a solution for the given constraints by traversing -/// the stack top-to-bottom, copying/evicting/swapping as needed to put -/// the expected value for the current working index in place. -/// -/// This tactic does make an effort to avoid needless moves by searching -/// for swap opportunities that will place multiple expected operands in -/// place at once using the optimal number of swaps. In cases where this -/// cannot be done however, it will perform as few swaps as it can while -/// still making progress. -#[derive(Default)] -pub struct Linear; -impl Tactic for Linear { - fn cost(&self, context: &SolverContext) -> usize { - core::cmp::max(context.copies().len(), 1) - } - - fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult { - let mut graph = DiGraphMap::::new(); - - // Materialize copies - let mut materialized = SmallSet::::default(); - for b in builder.context().expected().iter().rev() { - // Where is B - if let Some(_b_at) = builder.get_current_position(&b.value) { - log::trace!( - "no copy needed for {:?} from index {} to top of stack", - b.value, - b.pos - ); - materialized.insert(b.value); - } else { - // B isn't on the stack because it is a copy we haven't materialized yet - assert!(b.value.is_alias()); - let b_at = builder.unwrap_current_position(&b.value.unaliased()); - log::trace!( - "materializing copy of {:?} from index {} to top of stack", - b.value, - b.pos - ); - builder.dup(b_at, b.value.unwrap_alias()); - materialized.insert(b.value); - } - } - - // Visit each materialized operand and, if out of place, add it to the graph - // along with the node occupying its expected location on the stack. The occupying - // node is then considered materialized and visited as well. - let mut current_index = 0; - let mut materialized = materialized.into_vec(); - loop { - if current_index >= materialized.len() { - break; - } - let value = materialized[current_index]; - let currently_at = builder.unwrap_current_position(&value); - if let Some(expected_at) = builder.get_expected_position(&value) { - if currently_at == expected_at { - log::trace!( - "{value:?} at index {currently_at} is expected there, no movement needed" - ); - current_index += 1; - continue; - } - let occupied_by = builder.unwrap_current(expected_at); - log::trace!( - "{value:?} at index {currently_at}, is expected at index {expected_at}, which \ - is currently occupied by {occupied_by:?}" - ); - let from = graph.add_node(Operand { - pos: currently_at, - value, - }); - let to = graph.add_node(Operand { - pos: expected_at, - value: occupied_by, - }); - graph.add_edge(from, to, ()); - if !materialized.contains(&occupied_by) { - materialized.push(occupied_by); - } - } else { - // `value` is not an expected operand, but is occupying a spot - // on the stack needed by one of the expected operands. We can - // create a connected component with `value` by finding the root - // of the path which leads to `value` from an expected operand, - // then adding an edge from `value` back to that operand. This - // forms a cycle which will allow all expected operands to be - // swapped into place, and the unused operand evicted, without - // requiring excess moves. - let operand = Operand { - pos: currently_at, - value, - }; - let mut parent = graph.neighbors_directed(operand, Direction::Incoming).next(); - // There must have been an immediate parent to `value`, or it would - // have an expected position on the stack, and only expected operands - // are materialized initially. - let mut root = parent.unwrap(); - log::trace!( - "{value:?} at index {currently_at}, is not an expected operand; but must be \ - moved to make space for {:?}", - root.value - ); - let mut seen = std::collections::BTreeSet::default(); - seen.insert(root); - while let Some(parent_operand) = parent { - root = parent_operand; - parent = graph.neighbors_directed(parent_operand, Direction::Incoming).next(); - } - log::trace!( - "forming component with {value:?} by adding edge to {:?}, the start of the \ - path which led to it", - root.value - ); - graph.add_edge(operand, root, ()); - } - current_index += 1; - } - - // Compute the strongly connected components of the graph we've constructed, - // and use that to drive our decisions about moving operands into place. - let components = petgraph::algo::kosaraju_scc(&graph); - log::trace!( - "found the following connected components when analyzing required operand moves: \ - {components:?}" - ); - for component in components.into_iter() { - // A component of two or more elements indicates a cycle of operands. - // - // To determine the order in which swaps must be performed, we first look - // to see if any of the elements are on top of the stack. If so, we swap - // it with its parent in the graph, and so on until we reach the edge that - // completes the cycle (i.e. brings us back to the operand we started with). - // - // If we didn't have an operand on top of the stack yet, we pick the operand - // that is closest to the top of the stack to move to the top, so as not to - // disturb the positions of the other operands. We then proceed as described - // above. The only additional step required comes at the end, where we move - // whatever operand ended up on top of the stack to the original position of - // the operand we started with. - // - // # Examples - // - // Consider a component of 3 operands: B -> A -> C -> B - // - // We can put all three operands in position by first swapping B with A, - // putting B into position; and then A with C, putting A into position, - // and leaving C in position as a result. - // - // Let's extend it one operand further: B -> A -> C -> D -> B - // - // The premise is the same, B with A, A with C, then C with D, the result - // is that they all end up in position at the end. - // - // Here's a diagram of how the state changes as we perform the swaps - // - // 0 1 2 3 - // C -> D -> B -> A -> C - // - // 0 1 2 3 - // D C B A - // - // 0 1 2 3 - // B C D A - // - // 0 1 2 3 - // A C D B - // - if component.len() > 1 { - // Find the operand at the shallowest depth on the stack to move. - let start = component.iter().min_by(|a, b| a.pos.cmp(&b.pos)).copied().unwrap(); - log::trace!( - "resolving component {component:?} by starting from {:?} at index {}", - start.value, - start.pos - ); - - // If necessary, move the starting operand to the top of the stack - let start_position = start.pos; - if start_position > 0 { - builder.movup(start_position); - } - - // Do the initial swap to set up our state for the remaining swaps - let mut child = - graph.neighbors_directed(start, Direction::Outgoing).next().unwrap(); - // Swap each child with its parent until we reach the edge that forms a cycle - while child != start { - log::trace!( - "swapping {:?} with {:?} at index {}", - builder.unwrap_current(0), - child.value, - child.pos - ); - builder.swap(child.pos); - child = graph.neighbors_directed(child, Direction::Outgoing).next().unwrap(); - } - - // If necessary, move the final operand to the original starting position - if start_position > 0 { - builder.movdn(start_position) - } - } - } - - Ok(()) - } -} diff --git a/codegen/masm/src/codegen/opt/operands/tactics/mod.rs b/codegen/masm/src/codegen/opt/operands/tactics/mod.rs deleted file mode 100644 index 4b8c1093a..000000000 --- a/codegen/masm/src/codegen/opt/operands/tactics/mod.rs +++ /dev/null @@ -1,271 +0,0 @@ -use core::num::NonZeroU8; - -use super::{Action, Operand, SolverContext, Stack, ValueOrAlias}; - -mod copy_all; -mod linear; -mod move_down_and_swap; -mod move_up_and_swap; -mod swap_and_move_up; - -pub use self::{ - copy_all::CopyAll, linear::Linear, move_down_and_swap::MoveDownAndSwap, - move_up_and_swap::MoveUpAndSwap, swap_and_move_up::SwapAndMoveUp, -}; - -/// An error returned by an [OperandMovementConstraintSolver] tactic -#[derive(Debug)] -pub enum TacticError { - /// The tactic could not be applied due to a precondition - /// that is required for the tactic to succeed. For example, - /// a tactic that does not handle copies will have a precondition - /// that there are no copy constraints, and will not attempt - /// to compute a solution if there are. - PreconditionFailed, - /// The tactic could not be applied because the pattern it - /// looks for could not be found in the current context. - NotApplicable, -} - -/// The type of result produced by a [Tactic] -pub type TacticResult = Result<(), TacticError>; - -/// A [Tactic] implements an algorithm for solving operand movement constraints -/// that adhere to a specific pattern or patterns. -/// -/// Tactics should attempt to fail early by first recognizing whether the state -/// of the stack adheres to the pattern which the tactic is designed to solve, -/// and only then should it actually compute the specific actions needed to lay -/// out the stack as expected. -/// -/// A tactic does not need to check if the result of computing a solution actually -/// solves all of the constraints, that is done by [OperandMovementConstraintSolver]. -/// -/// Tactics can have an associated cost, which is used when iterating over multiple -/// tactics looking for the best solution. You should strive to make the cost reflect -/// the computational complexity of the tactic to the degree possible. The default -/// cost for all tactics is 1. -pub trait Tactic { - /// The name of this tactic to use in informational messages. - /// - /// The default name of each tactic is the name of the implementing type. - fn name(&self) -> &'static str { - let name = core::any::type_name::(); - match name.find(|c: char| c.is_ascii_uppercase()) { - None => name, - Some(index) => name.split_at(index).1, - } - } - - /// The computational cost of this tactic in units of optimization fuel. - /// - /// The provided context can be used to compute a cost dynamically based on - /// the number of expected operands, the constraints, and the size of the stack. - /// - /// The default cost is 1. - fn cost(&self, _context: &SolverContext) -> usize { - 1 - } - - /// Apply this tactic using the provided [SolutionBuilder]. - fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult; -} - -/// This struct is constructed by an [OperandMovementConstraintSolver], and provided -/// to each [Tactic] it applies in search of a solution. -/// -/// The purpose of this builder is to abstract over the solver context, the pending -/// state of the stack, and to ensure that the solution computed by a [Tactic] is -/// captured accurately. -#[derive(Debug, Clone)] -pub struct SolutionBuilder<'a> { - /// The current solver context - context: &'a SolverContext, - /// The state of the stack after applying `actions` - pending: Stack, - /// The actions that represent the solution constructed so far. - actions: Vec, -} -impl<'a> SolutionBuilder<'a> { - #[doc(hidden)] - pub fn new(context: &'a SolverContext) -> Self { - Self { - context, - pending: context.stack().clone(), - actions: vec![], - } - } - - /// Return the number of operands expected by the current instruction being emitted - pub fn arity(&self) -> usize { - self.context.arity() - } - - /// Return true if the current context requires operand copies to be made - pub fn requires_copies(&self) -> bool { - !self.context.copies().is_empty() - } - - /// Return the total number of copied operands expected - pub fn num_copies(&self) -> usize { - self.context.copies().len() - } - - /// Get a reference to the underlying context of the solver - #[inline(always)] - pub fn context(&self) -> &'a SolverContext { - self.context - } - - /// Get a reference to the state of the stack after applying the pending solution - #[inline(always)] - pub fn stack(&self) -> &Stack { - &self.pending - } - - /// Take the current solution and reset the builder - pub fn take(&mut self) -> Vec { - let actions = core::mem::take(&mut self.actions); - self.pending.reset_to(self.context.stack()); - actions - } - - /// Discard the current solution, and reset back to the initial state - pub fn discard(&mut self) { - self.actions.clear(); - self.pending.reset_to(self.context.stack()); - } - - /// Check if the pending solution is a valid solution - pub fn is_valid(&self) -> bool { - self.context.is_solved(&self.pending) - } - - /// Get the value expected at `index` - pub fn get_expected(&self, index: u8) -> Option { - self.context.expected().get(index as usize).map(|o| o.value) - } - - /// Get the value expected at `index` or panic - #[track_caller] - pub fn unwrap_expected(&self, index: u8) -> ValueOrAlias { - match self.get_expected(index) { - Some(value) => value, - None => panic!( - "expected operand {index} does not exist: there are only {} expected operands", - self.context.arity() - ), - } - } - - /// Get the value currently at `index` in this solution - #[allow(unused)] - pub fn get_current(&self, index: u8) -> Option { - self.pending.get(index as usize).map(|o| o.value) - } - - /// Get the value currently at `index` in this solution - #[track_caller] - pub fn unwrap_current(&self, index: u8) -> ValueOrAlias { - match self.pending.get(index as usize) { - Some(operand) => operand.value, - None => panic!( - "operand {index} does not exist: the stack contains only {} operands", - self.pending.len() - ), - } - } - - /// Get the position at which `value` is expected - pub fn get_expected_position(&self, value: &ValueOrAlias) -> Option { - self.context.expected().position(value).map(|index| index as u8) - } - - #[track_caller] - pub fn unwrap_expected_position(&self, value: &ValueOrAlias) -> u8 { - match self.get_expected_position(value) { - Some(pos) => pos, - None => panic!("value {value:?} is not an expected operand"), - } - } - - /// Get the current position of `value` in this solution - #[inline] - pub fn get_current_position(&self, value: &ValueOrAlias) -> Option { - self.pending.position(value).map(|index| index as u8) - } - - /// Get the current position of `value` in this solution, or panic - #[track_caller] - pub fn unwrap_current_position(&self, value: &ValueOrAlias) -> u8 { - match self.get_current_position(value) { - Some(pos) => pos, - None => panic!("value {value:?} not found on operand stack"), - } - } - - /// Returns true if the value expected at `index` is currently at that index - pub fn is_expected(&self, index: u8) -> bool { - self.get_expected(index) - .map(|v| v.eq(&self.pending[index as usize].value)) - .unwrap_or(true) - } - - /// Duplicate the operand at `index` to the top of the stack - /// - /// This records a `Copy` action, and updates the state of the stack - pub fn dup(&mut self, index: u8, alias_id: NonZeroU8) { - self.pending.dup(index as usize, alias_id); - self.actions.push(Action::Copy(index)); - } - - /// Swap the operands at `index` and the top of the stack - /// - /// This records a `Swap` action, and updates the state of the stack - pub fn swap(&mut self, index: u8) { - self.pending.swap(index as usize); - self.actions.push(Action::Swap(index)); - } - - /// Move the operand at `index` to the top of the stack - /// - /// This records a `MoveUp` action, and updates the state of the stack - #[track_caller] - pub fn movup(&mut self, index: u8) { - assert_ne!(index, 0); - if index == 1 { - self.swap(index); - } else { - self.pending.movup(index as usize); - self.actions.push(Action::MoveUp(index)); - } - } - - /// Move the operand at the top of the stack to `index` - /// - /// This records a `MoveDown` action, and updates the state of the stack - #[track_caller] - pub fn movdn(&mut self, index: u8) { - assert_ne!(index, 0); - if index == 1 { - self.swap(index); - } else { - self.pending.movdn(index as usize); - self.actions.push(Action::MoveDown(index)); - } - } - - /// Evicts the operand on top of the stack by moving it down past the last expected operand. - pub fn evict(&mut self) { - self.evict_from(0) - } - - /// Same as `evict`, but assumes that we're evicting an operand at `index` - #[inline] - pub fn evict_from(&mut self, index: u8) { - if index > 0 { - self.movup(index); - } - self.movdn(self.context.arity() as u8); - } -} diff --git a/codegen/masm/src/codegen/scheduler.rs b/codegen/masm/src/codegen/scheduler.rs deleted file mode 100644 index 85c309623..000000000 --- a/codegen/masm/src/codegen/scheduler.rs +++ /dev/null @@ -1,1436 +0,0 @@ -use std::{cmp::Ordering, collections::VecDeque, rc::Rc}; - -use cranelift_entity::SecondaryMap; -use midenc_hir::{ - self as hir, - adt::{SmallMap, SmallSet, SparseMap, SparseMapValue}, - assert_matches, BranchInfo, ProgramPoint, -}; -use midenc_hir_analysis::{ - dependency_graph::{ArgumentNode, DependencyGraph, Node, NodeId}, - DominatorTree, LivenessAnalysis, Loop, LoopAnalysis, OrderedTreeGraph, -}; -use smallvec::SmallVec; - -use crate::{codegen::Constraint, masm}; - -/// Information about a block's successor -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Successor { - pub block: hir::Block, - pub arg_count: u16, -} - -#[derive(Debug)] -pub struct ValueInfo { - /// The value in question - pub value: hir::Value, - /// The node corresponding to this value (i.e. a Result or Stack node) - pub node: NodeId, - /// If the value is not explicitly used, but is live after the current - /// block, it is considered used for purposes of dead code analysis - pub is_externally_used: bool, - /// The instruction nodes in the dependency graph which use this value. - /// - /// This vector is sorted such that the earlier a user appears in it, - /// the later they are scheduled in the block. - users: SmallVec<[NodeId; 1]>, -} -impl ValueInfo { - pub fn is_used(&self) -> bool { - self.is_externally_used || !self.users.is_empty() - } - - /// Return the [NodeId] of the first user of this value to be emitted - pub fn first_user(&self) -> Option { - self.users.last().copied() - } - - /// Return the [NodeId] of the last user of this value to be emitted - #[allow(unused)] - pub fn last_user(&self) -> Option { - self.users.first().copied() - } -} - -/// Represents important metadata about an instruction used -/// to schedule its execution and data/control dependencies. -#[derive(Debug)] -pub struct InstInfo { - /// The id of the instruction - pub inst: hir::Inst, - /// The node id of this instruction - pub node: NodeId, - /// The number of plain arguments this instruction expects - pub arity: u8, - /// Both plain arguments and block arguments are stored in this - /// vector; plain arguments always come first, and block arguments - /// start immediately after the last plain argument. - /// - /// The arguments for each block are stored consecutively based on - /// the order of the successors in the instruction. So if there is - /// one plain argument, and two blocks with two arguments each, then - /// the layout of all arguments will be: - /// - /// ```text,ignore - /// [plain, block0_arg0, block0_arg1, block1_arg0, block1_arg1] - /// ``` - pub args: SmallVec<[Constraint; 4]>, - /// Information about the values produced by this instruction as results - /// - /// This vector is sorted by the first use of each result, such that the - /// earlier a value appears in it, the earlier that value is used in - /// the scheduled block. - pub results: SmallVec<[ValueInfo; 2]>, - /// The set of dependencies which must be scheduled before - /// the instruction starts executing - /// - /// This set is populated in argument order - pub pre: SmallSet, - /// The set of dependencies which must be scheduled after - /// the instruction finishes executing. - /// - /// This is largely relevant only for control flow instructions, - /// particularly those such as `cond_br`, which may push down - /// materialization of certain data dependencies into the control - /// flow edge itself, rather than before the instruction starts - /// execution. This can avoid unnecessarily executing instructions - /// that aren't ultimately used due to a runtime condition. - pub post: SmallSet, - /// The successor blocks and argument count for this instruction - pub successors: SmallVec<[Successor; 2]>, -} -impl SparseMapValue for InstInfo { - fn key(&self) -> hir::Inst { - self.inst - } -} -impl InstInfo { - #[inline] - pub const fn arity(&self) -> usize { - self.arity as usize - } - - /// Get the constraints for the plain arguments of this instruction - pub fn plain_arguments(&self) -> &[Constraint] { - &self.args[..self.arity()] - } - - /// Get the constraints for the arguments of successor `block` - pub fn block_arguments(&self, block: hir::Block) -> &[Constraint] { - let range = self.block_argv_range(block); - &self.args[range] - } - - fn block_argv_range(&self, block: hir::Block) -> core::ops::Range { - let mut start_idx = self.arity(); - let mut arg_count = 0; - for successor in self.successors.iter() { - if successor.block == block { - arg_count = successor.arg_count as usize; - break; - } - start_idx += successor.arg_count as usize; - } - start_idx..(start_idx + arg_count) - } -} - -/// [BlockInfo] describes information about a block relevant to scheduling -/// and code generation. Namely, it provides access to the computed dependency -/// graph and tree graph used to schedule the block; but it also provides -/// convenient access to commonly-queried block information. -#[derive(Debug)] -pub struct BlockInfo { - /// The source HIR block this info is based on - pub source: hir::Block, - /// The target MASM block which will be emitted from this info - pub target: masm::BlockId, - /// If set, indicates that this block is the loop header - /// for the specified loop. - pub loop_header: Option, - /// The dependency graph of this block - pub depgraph: DependencyGraph, - /// The topologically-ordered tree graph of this block - pub treegraph: OrderedTreeGraph, -} -impl BlockInfo { - #[inline(always)] - pub fn is_loop_header(&self) -> bool { - self.loop_header.is_some() - } -} -impl SparseMapValue for BlockInfo { - fn key(&self) -> hir::Block { - self.source - } -} - -/// [Schedule] describes an instruction scheduling plan for a single IR function. -/// -/// The plan describes the order in which blocks will be scheduled, -/// and the schedule for instructions in each block. -#[derive(Debug)] -pub struct Schedule { - pub block_infos: SparseMap>, - pub block_schedules: SecondaryMap>, -} -impl Schedule { - pub fn new() -> Self { - Self { - block_infos: Default::default(), - block_schedules: SecondaryMap::new(), - } - } - - #[inline] - pub fn block_info(&self, block: hir::Block) -> Rc { - self.block_infos.get(block).cloned().unwrap() - } - - #[inline] - pub fn get(&self, block: hir::Block) -> &[ScheduleOp] { - self.block_schedules[block].as_slice() - } -} - -/// [ScheduleOp] describes an action to be performed by the code generator. -/// -/// Code generation is driven by constructing a schedule consisting of these operations -/// in a specific order that represents a valid execution of the original IR, and -/// then traversing the schedule start-to-finish, translating these high-level operations -/// into equivalent Miden Assembly code. By performing code generation in this manner, -/// we are able to see a high-level description of how the compiler plans to generate -/// code for a function, and we also enable the code generator to make last-minute -/// optimizations based on the local context of each [ScheduleOp], much like a peephole -/// optimizer would. -#[derive(Debug, Clone)] -pub enum ScheduleOp { - /// Emit the given instruction, using the provided analysis - Inst(Rc), - /// Drop the first occurrence of the given value on the operand stack - Drop(hir::Value), -} - -/// Meta-instruction for the abstract scheduling machine used -/// to emit an instruction schedule for a function. -#[derive(Debug)] -pub enum Plan { - /// Start a new block context - /// - /// This represents entering a block, so all further instructions - /// are scheduled in the context of the given block until an ExitBlock - /// meta-instruction is encountered. - Start, - /// Schedule execution of an instruction's pre-requisites - PreInst(Rc), - /// Schedule execution of the given instruction - Inst(Rc), - /// Schedule execution of any instruction cleanup - /// - /// This is primarily intended to support things like pushing materialization - /// of data dependencies down along control flow edges when they are conditionally - /// required, but can also be used to schedule instruction cleanup/etc. - PostInst(Rc), - /// Schedule an unused instruction result to be dropped from the operand stack - Drop(hir::Value), - /// Indicate that the current block context should be popped for the previous - /// one on the block stack - Finish, -} - -pub struct Scheduler<'a> { - f: &'a hir::Function, - f_prime: &'a mut masm::Function, - domtree: &'a DominatorTree, - loops: &'a LoopAnalysis, - liveness: &'a LivenessAnalysis, - schedule: Schedule, -} -impl<'a> Scheduler<'a> { - pub fn new( - f: &'a hir::Function, - f_prime: &'a mut masm::Function, - domtree: &'a DominatorTree, - loops: &'a LoopAnalysis, - liveness: &'a LivenessAnalysis, - ) -> Self { - Self { - f, - f_prime, - domtree, - loops, - liveness, - schedule: Schedule::new(), - } - } - - pub fn build(mut self) -> Schedule { - self.precompute_block_infos(); - - let mut blockq = SmallVec::<[hir::Block; 8]>::from_slice(self.domtree.cfg_postorder()); - while let Some(block_id) = blockq.pop() { - let schedule = &mut self.schedule.block_schedules[block_id]; - let block_info = self.schedule.block_infos.get(block_id).cloned().unwrap(); - let block_scheduler = BlockScheduler { - f: self.f, - liveness: self.liveness, - block_info, - inst_infos: Default::default(), - worklist: SmallVec::from_iter([Plan::Start]), - }; - block_scheduler.schedule(schedule); - } - - self.schedule - } - - fn precompute_block_infos(&mut self) { - let entry_block_id = self.f.dfg.entry_block(); - - for block_id in self.domtree.cfg_postorder().iter().rev().copied() { - // Ensure we have a target block for each source IR block being scheduled - let masm_block_id = if block_id == entry_block_id { - self.f_prime.body.id() - } else { - self.f_prime.create_block() - }; - - // Set the controlling loop - let loop_header = self.loops.is_loop_header(block_id); - let depgraph = build_dependency_graph(block_id, self.f, self.liveness); - let treegraph = OrderedTreeGraph::new(&depgraph) - .expect("unable to topologically sort treegraph for block"); - - let info = Rc::new(BlockInfo { - source: block_id, - target: masm_block_id, - loop_header, - depgraph, - treegraph, - }); - - self.schedule.block_infos.insert(info); - } - } -} - -struct BlockScheduler<'a> { - f: &'a hir::Function, - liveness: &'a LivenessAnalysis, - block_info: Rc, - inst_infos: SparseMap>, - worklist: SmallVec<[Plan; 4]>, -} -impl<'a> BlockScheduler<'a> { - pub fn schedule(mut self, scheduled_ops: &mut Vec) { - // Planning tasks are added to the worklist in reverse, e.g. we push - // Plan::Finish before Plan::Start. So by popping tasks off the stack - // here, we will emit scheduling operations in "normal" order. - while let Some(plan) = self.worklist.pop() { - match plan { - Plan::Start => self.visit_block(), - Plan::Finish => continue, - // We're emitting code required to execute an instruction, such as materialization - // of data dependencies used as direct arguments. This is only - // emitted when an instruction has arguments which are derived from - // the results of an instruction that has not been scheduled yet - Plan::PreInst(inst_info) => self.schedule_pre_inst(inst_info), - // We're emitting code for an instruction whose pre-requisite dependencies are - // already materialized, so we need only worry about how a specific - // instruction is lowered. - Plan::Inst(inst_info) => self.schedule_inst(inst_info, scheduled_ops), - // We're emitting code for an instruction that has started executing, and in some - // specific cases, may have dependencies which have been deferred - // until this point. This is only emitted currently for block - // arguments which are conditionally materialized - Plan::PostInst(inst_info) => self.schedule_post_inst(inst_info), - Plan::Drop(value) => { - scheduled_ops.push(ScheduleOp::Drop(value)); - } - } - } - } - - /// Visit the current block and enqueue planning operations to drive scheduling - fn visit_block(&mut self) { - // When all scheduling meta-ops are complete for this block, this instruction - // will switch context back to the parent block in the stack - self.worklist.push(Plan::Finish); - - // The treegraph iterator visits each node before any of that node's successors, - // i.e. dependencies. As a result, all code that emits planning tasks (i.e. Plan), - // must be written in the order opposite of the resulting scheduling tasks (i.e. - // ScheduleOp). - // - // This is critical to internalize, because reasoning about dependencies and when things - // are live/dead is _inverted_ here. You must ensure that when planning things based on - // dependency order or liveness, that you do so taking this workflow inversion into account. - // - // The actual scheduling tasks are handled in the proper program execution order, but we - // have to handle this inversion somewhere, and planning seemed like the best place, since - // it is relatively limited in size and scope. - // - // The goal here is to produce a stack of scheduling instructions that when evaluated, - // will emit code in the correct program order, i.e. instructions which produce results - // used by another instruction will be emitted so that those results are available on - // the operand stack when we lower the instruction, and we need only copy/move those - // operands into place. - let current_block_info = self.block_info.clone(); - for node_id in current_block_info.treegraph.iter() { - match node_id.into() { - // Result nodes are treegraph roots by two paths: - // - // * The result is used multiple times. Here, in the planning phase, we must have - // just - // finished visiting all of its dependents, so to ensure that during scheduling the - // result is materialized before its first use, we must do so here. If this result - // is one of many produced by the same instruction, we must - // determine if this is the first result to be seen, or the last. - // The last result to be visited during planning is the - // one that will actually materialize all of the results for the corresponding - // instruction. Thus, if this is not the last result visited. - // - // * The result is never used, in which case, like above, we must determine if this - // result - // should force materialization of the instruction. The caveat here is that if this - // is the only result produced by its instruction, and it is not - // live after the end of the current block, then we will avoid - // materializing at all if the instruction has no side effects. - // If it _does_ have side effects, then we will force materialization of the result, - // but then schedule it to be immediately dropped - Node::Result { value, .. } => { - self.maybe_force_materialize_inst_results(value, node_id) - } - // During the planning phase, there is only one useful thing to do with this node - // type, which is to determine if it has any dependents in the - // current block, and if not, schedule a drop of the value if - // liveness analysis tells us that the value is not used after the - // current block. - Node::Stack(value) => { - // If this value is live after the end of this block, it cannot be dropped - if self - .liveness - .is_live_after(&value, ProgramPoint::Block(current_block_info.source)) - { - continue; - } - // If this value is used within this block, then the last use will consume it - if current_block_info.treegraph.num_predecessors(node_id) > 0 { - continue; - } - // Otherwise, we must ensure it gets dropped, so do so immediately - self.worklist.push(Plan::Drop(value)); - } - // We will only ever observe the instruction node type as a treegraph root - // when it has no results (and thus no dependents/predecessors in the graph), or - // multiple results, because in all other cases it will always have a single - // predecessor of Result type. - Node::Inst { id: inst, .. } => { - let inst_info = self.get_or_analyze_inst_info(inst, node_id); - self.plan_inst(inst_info); - } - // It can never be the case that argument nodes are unused or multiply-used, - // they will always be successors of an Inst node - Node::Argument(_) => unreachable!(), - } - } - } - - fn plan_inst(&mut self, inst_info: Rc) { - // Only push an item for post-inst scheduling if we have something to do - if !inst_info.post.is_empty() { - // This meta-op will emit code that is required along the control flow edge - // represented by the branch. - self.worklist.push(Plan::PostInst(inst_info.clone())); - } - // Only push an item for pre-inst scheduling if we have something to do - if inst_info.pre.is_empty() { - // This meta-op will emit code for the unconditional branch - self.worklist.push(Plan::Inst(inst_info)); - } else { - // This meta-op will emit code for the unconditional branch - self.worklist.push(Plan::Inst(inst_info.clone())); - // This meta-op will emit code that is required to evaluate the instruction - // - // It is also responsible for scheduling dependencies - self.worklist.push(Plan::PreInst(inst_info)); - } - } - - /// Schedule pre-requisites for an instruction based on the given analysis, see [Plan::PreInst] - /// docs for more. - /// - /// This function, while nominally occurring during the scheduling phase, actually emits - /// planning tasks which are processed _before_ the [Plan::Inst] task corresponding to - /// `inst_info`. As a result, we visit the pre-requisites in planning order, _not_ execution - /// order. - fn schedule_pre_inst(&mut self, inst_info: Rc) { - // Schedule dependencies for execution in the order that they must execute - if inst_info - .pre - .as_slice() - .is_sorted_by(|a, b| self.block_info.treegraph.cmp_scheduling(*a, *b).is_le()) - { - self.schedule_inst_dependencies(&inst_info, inst_info.pre.as_slice()); - } else { - let mut deps = inst_info.pre.clone().into_vec(); - deps.sort_by(|a, b| self.block_info.treegraph.cmp_scheduling(*a, *b)); - self.schedule_inst_dependencies(&inst_info, deps.as_slice()); - } - } - - fn schedule_inst_dependencies(&mut self, inst_info: &InstInfo, dependencies: &[NodeId]) { - for dependency_id in dependencies.iter().copied() { - // If the dependency is a treegraph root, we do not need to do anything, - // as that dependency will be scheduled at a more appropriate time prior - // the code generated here, and the dependency will be available on the - // operand stack - if self.block_info.treegraph.is_root(dependency_id) { - continue; - } - - // Otherwise, dispatch based on the node type of the dependency - match dependency_id.into() { - // We are the only user of this result, otherwise it would be a - // treegraph root. As a result, we simply need to determine whether - // or not the instruction which produces it must be materialized by - // us, or via another result - Node::Result { value, .. } => { - self.maybe_materialize_inst_results(value, dependency_id, inst_info) - } - // We avoid adding these nodes as pre-requisites as they are assumed to - // be on the operand stack already, but we handle it gracefully here anyway - Node::Stack(_) => continue, - // This is a control dependency, so it must be materialized - // - // Currently, control dependencies are only attached to a single instruction, - // so we do not need to check if another dependent will materialize it, it is - // definitely on us. - Node::Inst { id: inst, .. } => { - let inst_info = self.get_or_analyze_inst_info(inst, dependency_id); - self.plan_inst(inst_info); - } - // This node type is never added as a pre-requisite - Node::Argument(_) => unreachable!(), - } - } - } - - /// Schedule execution of a given instruction, see [Plan::Inst] docs for specific semantics. - fn schedule_inst(&mut self, inst_info: Rc, scheduled_ops: &mut Vec) { - scheduled_ops.push(ScheduleOp::Inst(inst_info.clone())); - // Ensure that any unused results are dropped immediately - let inst_results = self.f.dfg.inst_results(inst_info.inst); - for result in inst_results.iter().copied() { - let is_used = inst_info.results.iter().any(|v| v.value == result && v.is_used()); - if !is_used { - scheduled_ops.push(ScheduleOp::Drop(result)); - } - } - } - - /// Schedule instructions which were deferred until after an instruction executes. - /// - /// See [Plan::PostInst] docs for more. - fn schedule_post_inst(&mut self, inst_info: Rc) { - todo!("post-execution instruction dependencies are not supported yet: {inst_info:?}"); - } - - /// We are visiting a `Node::Result` during planning, and need to determine whether or - /// not to materialize the result at this point, or if we must defer to another result of - /// the same instruction which is visited later in planning, OR if the result will be - /// materialized but unused due to the instruction having side effects. - /// - /// The name of this function refers to the fact that we may have to "force" materialization - /// of an instruction when the Result node has no predecessors in the graph, but is either - /// live _after_ the current block, or its instruction has side effects that require it to - /// be materialized anyway. However, we only force materialize in the latter case if there - /// are no direct dependents on the instruction itself, so as to avoid materializing the - /// same instruction multiple times (once for the result, once for the dependent). - fn maybe_force_materialize_inst_results(&mut self, result: hir::Value, result_node: NodeId) { - let inst_node = self.block_info.depgraph.unwrap_child(result_node); - let inst = inst_node.unwrap_inst(); - debug_assert_eq!(inst, self.f.dfg.value_data(result).unwrap_inst()); - let inst_info = self.get_or_analyze_inst_info(inst, inst_node); - - // Do not force materialization because the first result scheduled is responsible for that - let is_first_result_used = inst_info.results.first().unwrap().node == result_node; - if !is_first_result_used { - return; - } - - // We're the first result scheduled, whether used or not. If the result is - // not actually used, we may need to force materialization if the following - // three conditions hold: - // - // * There are no results used - // * The instruction has side effects - // * There are no dependencies on the instruction itself present in the graph, not counting - // the edges produced by results of the instruction. - // - // However, if any of the results are used, we must materialize them now, since - // we are scheduled before any of the others. - let is_used = inst_info.results.iter().any(|v| v.is_used()); - let has_side_effects = self.f.dfg.inst(inst).has_side_effects(); - let has_dependent_insts = self - .block_info - .depgraph - .predecessors(inst_node) - .any(|p| p.dependent.is_instruction()); - - if is_used || (has_side_effects && !has_dependent_insts) { - self.plan_inst(inst_info); - } - } - - fn maybe_materialize_inst_results( - &mut self, - result: hir::Value, - result_node: NodeId, - dependent_info: &InstInfo, - ) { - let inst_node = self.block_info.depgraph.unwrap_child(result_node); - let inst = inst_node.unwrap_inst(); - debug_assert_eq!(inst, self.f.dfg.value_data(result).unwrap_inst()); - let inst_info = self.get_or_analyze_inst_info(inst, inst_node); - - // We must materialize the instruction the first time it is referenced - let is_first_result_used = inst_info.results.first().unwrap().node == result_node; - // If this is not the first result of the referenced instruction to be - // scheduled, then the instruction results are already materialized - if !is_first_result_used { - return; - } - - // If this result is the first one to be used, but we are not the first user, - // we also do nothing, since the first user materializes - let first_user = inst_info.results[0].first_user().unwrap(); - let is_first_use = first_user == dependent_info.node; - if !is_first_use { - return; - } - - // If the result belongs to an instruction which is a treegraph node, then - // we never materialize the results, because they must already have been - // materialized - if self.block_info.treegraph.is_root(inst_node) { - return; - } - - // We're the first use of the referenced instruction, so materialize its - // results, and drop any that have no uses. - self.plan_inst(inst_info); - } - - /// Get the analysis for `inst`, or perform the analysis now and cache it for future queries - fn get_or_analyze_inst_info(&mut self, inst: hir::Inst, inst_node_id: NodeId) -> Rc { - match self.inst_infos.get(inst).cloned() { - Some(info) => info, - None => { - let info = self.analyze_inst(inst, inst_node_id); - self.inst_infos.insert(info.clone()); - info - } - } - } - - /// Analyze an instruction node from the current block's dependency graph. - /// - /// This analysis produces an [InstInfo] that is used during scheduling and - /// code generation. It provides information about how dependencies of the instruction - /// should be scheduled, and whether to copy or move data dependencies when needed, - /// along with some commonly requested pieces of information about the instruction, - /// such as the number of arguments, its successors, etc. - /// - /// NOTE: This can be called either during planning or scheduling, but the - /// analysis is always scheduling-oriented. - fn analyze_inst(&self, inst: hir::Inst, inst_node_id: NodeId) -> Rc { - let inst_args = self.f.dfg.inst_args(inst); - let arity = inst_args.len() as u8; - - let results = self.analyze_inst_results(inst); - let mut inst_info = Box::new(InstInfo { - inst, - node: inst_node_id, - arity, - args: Default::default(), - results, - pre: Default::default(), - post: Default::default(), - successors: Default::default(), - }); - - match self.f.dfg.analyze_branch(inst) { - BranchInfo::SingleDest(hir::SuccessorInfo { - destination: block, - args: block_args, - }) => { - inst_info.successors.push(Successor { - block, - arg_count: block_args.len() as u16, - }); - for (succ_idx, arg) in self - .block_info - .depgraph - .successors(inst_node_id) - .filter(|succ| succ.dependency.is_argument()) - .enumerate() - { - let arg_id = arg.dependency; - let arg_source_id = self.block_info.depgraph.unwrap_child(arg_id); - if !arg_source_id.is_stack() { - inst_info.pre.insert(arg_source_id); - } - match arg.dependency.into() { - Node::Argument(ArgumentNode::Direct { index, .. }) => { - debug_assert_eq!( - succ_idx, index as usize, - "successor ordering constraint violation: {arg:?}" - ); - inst_info.args.push(self.constraint( - inst_args[index as usize], - arg_id, - arg_source_id, - None, - )); - } - Node::Argument(ArgumentNode::Indirect { index, .. }) => { - debug_assert_eq!( - succ_idx + inst_args.len(), - index as usize, - "successor ordering constraint violation: {arg:?}" - ); - let succ = hir::SuccessorInfo { - destination: block, - args: block_args, - }; - inst_info.args.push(self.constraint( - block_args[index as usize], - arg_id, - arg_source_id, - Some(&[succ]), - )); - } - Node::Argument(arg) => { - panic!("invalid argument type for single-destination branch: {arg:?}") - } - _ => unreachable!(), - } - } - } - BranchInfo::MultiDest(ref succs) => { - for succ in succs.iter() { - inst_info.successors.push(Successor { - block: succ.destination, - arg_count: succ.args.len() as u16, - }); - } - for (succ_idx, arg) in self - .block_info - .depgraph - .successors(inst_node_id) - .filter(|succ| succ.dependency.is_argument()) - .enumerate() - { - let arg_id = arg.dependency; - let arg_source_id = self.block_info.depgraph.unwrap_child(arg_id); - match arg.dependency.into() { - Node::Argument(ArgumentNode::Direct { index, .. }) => { - debug_assert_eq!( - succ_idx, index as usize, - "successor ordering constraint violation: {arg:?}" - ); - if !arg_source_id.is_stack() { - inst_info.pre.insert(arg_source_id); - } - inst_info.args.push(self.constraint( - inst_args[index as usize], - arg_id, - arg_source_id, - Some(succs), - )); - } - Node::Argument(ArgumentNode::Indirect { - successor, index, .. - }) => { - debug_assert_eq!( - succ_idx - - inst_args.len() - - succs[..(successor as usize)] - .iter() - .map(|succ| succ.args.len()) - .sum::(), - index as usize, - "successor ordering constraint violation: {arg:?}" - ); - if !arg_source_id.is_stack() { - inst_info.pre.insert(arg_source_id); - } - let block_arg = succs[successor as usize].args[index as usize]; - inst_info.args.push(self.constraint( - block_arg, - arg_id, - arg_source_id, - Some(succs), - )); - } - Node::Argument(ArgumentNode::Conditional { - successor, index, .. - }) => { - debug_assert_eq!( - succ_idx - - inst_args.len() - - succs[..(successor as usize)] - .iter() - .map(|succ| succ.args.len()) - .sum::(), - index as usize, - "successor ordering constraint violation: {arg:?}" - ); - if !arg_source_id.is_stack() { - // TODO: We need to figure out a better way to avoid - // materializing conditionally-required results. For - // now, we treat these like regular argument dependencies - // so that they are on the operand stack when needed. - //inst_info.post.insert(arg_source_id); - inst_info.pre.insert(arg_source_id); - } - let block_arg = succs[successor as usize].args[index as usize]; - inst_info.args.push(self.constraint( - block_arg, - arg_id, - arg_source_id, - Some(succs), - )); - } - _ => unreachable!(), - } - } - } - BranchInfo::NotABranch => { - for (succ_idx, arg) in self - .block_info - .depgraph - .successors(inst_node_id) - .filter(|succ| succ.dependency.is_argument()) - .enumerate() - { - let arg_id = arg.dependency; - let arg_source_id = self.block_info.depgraph.unwrap_child(arg_id); - match arg.dependency.into() { - Node::Argument(ArgumentNode::Direct { index, .. }) => { - debug_assert_eq!( - succ_idx, index as usize, - "successor ordering constraint violation: {arg:?}" - ); - if !arg_source_id.is_stack() { - inst_info.pre.insert(arg_source_id); - } - inst_info.args.push(self.constraint( - inst_args[index as usize], - arg_id, - arg_source_id, - None, - )); - } - Node::Argument(arg) => { - panic!("invalid argument type for non-branching instruction: {arg:?}") - } - _ => unreachable!(), - } - } - } - } - - // Add any control dependencies after the argument dependencies to - // ensure that any results they produce do not interfere with the - // placement of operands on the stack (to the degree possible). - for succ in self - .block_info - .depgraph - .successors(inst_node_id) - .filter(|succ| !succ.dependency.is_argument()) - { - let succ_node_id = if succ.dependency.is_instruction() { - succ.dependency - } else { - assert!(succ.dependency.is_result()); - self.block_info.depgraph.unwrap_child(succ.dependency) - }; - inst_info.pre.insert(succ_node_id); - } - - Rc::from(inst_info) - } - - /// Analyze the liveness/usage of the results produced by `inst`, and determine - /// the order that they will be scheduled in. - fn analyze_inst_results(&self, inst: hir::Inst) -> SmallVec<[ValueInfo; 2]> { - let mut infos = SmallVec::<[ValueInfo; 2]>::default(); - // NOTE: Instruction results are presumed to appear on the stack in - // the same order as produced by the instruction. - for (result_idx, value) in self.f.dfg.inst_results(inst).iter().copied().enumerate() { - let result_node = Node::Result { - value, - index: result_idx as u8, - }; - let result_node_id = result_node.id(); - - // A result is "externally used" if it is live after the current block - let is_externally_used = - self.liveness.is_live_after(&value, ProgramPoint::Block(self.block_info.source)); - - let mut info = ValueInfo { - value, - node: result_node_id, - is_externally_used, - users: Default::default(), - }; - - // Record all of the instructions in the current block which use this result - for pred in self.block_info.depgraph.predecessors(result_node_id) { - if pred.dependent.is_argument() { - info.users.push(self.block_info.depgraph.unwrap_parent(pred.dependent)); - } else { - assert!(pred.dependent.is_instruction()); - info.users.push(pred.dependent); - } - } - - // Sort users in scheduling order, i.e. the earlier they appear in the - // list, the earlier they are visited during planning, and thus the later - // they are scheduled in the block during codegen. - info.users - .sort_unstable_by(|a, b| self.block_info.treegraph.cmp_scheduling(*a, *b)); - infos.push(info); - } - - // Sort the results by the scheduling order of their first use, i.e. the earlier - // they appear in the list, the later they are visited during planning, and - // thus the earlier they are actually used. - infos.sort_unstable_by(|a, b| match (a.first_user(), b.first_user()) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Greater, - (Some(_), None) => Ordering::Less, - (Some(a), Some(b)) => self.block_info.treegraph.cmp_scheduling(a, b).reverse(), - }); - infos - } - - /// Analyze `arg` and its usage at the current program point to determine what - /// type of operand constraint applies to it, i.e. whether the corresponding - /// operand can be moved/consumed, or must be copied first. - /// - /// An operand must be copied unless all of the following are true: - /// - /// * This is the last use (in the current block) of the value - /// * The value is not live in any successor, with the exception of successors which define the - /// value (as seen along loopback edges) - /// - /// If both of those properties hold, then the operand can be consumed directly. - fn constraint( - &self, - arg: hir::Value, - arg_node: NodeId, - arg_sourced_from: NodeId, - successors: Option<&[hir::SuccessorInfo<'_>]>, - ) -> Constraint { - if cfg!(debug_assertions) { - assert_matches!( - arg_sourced_from.into(), - Node::Stack(_) | Node::Result { .. }, - "unexpected argument source: {:?}", - arg_sourced_from - ); - } - let mut transitive_dependents = - transitive_instruction_dependents(arg_sourced_from, &self.block_info); - let this_dependent = self.block_info.depgraph.unwrap_parent(arg_node); - transitive_dependents.remove(&this_dependent); - let is_last_dependent = transitive_dependents - .iter() - .copied() - .all(|td| self.block_info.treegraph.is_scheduled_after(td, this_dependent)); - let is_live_after = match arg_node.into() { - Node::Argument(ArgumentNode::Direct { .. }) => successors - .map(|jts| { - jts.iter().any(|jt| { - let defined_by = self.f.dfg.block_args(jt.destination).contains(&arg); - let is_live_at = - self.liveness.is_live_at(&arg, ProgramPoint::Block(jt.destination)); - is_live_at && !defined_by - }) - }) - .unwrap_or_else(|| { - self.liveness.is_live_after(&arg, ProgramPoint::Block(self.block_info.source)) - }), - Node::Argument(ArgumentNode::Indirect { successor, .. }) - | Node::Argument(ArgumentNode::Conditional { successor, .. }) => { - let successors = successors.unwrap(); - let successor = successors[successor as usize].destination; - let defined_by = self.f.dfg.block_args(successor).contains(&arg); - let is_live_at = self.liveness.is_live_at(&arg, ProgramPoint::Block(successor)); - is_live_at && !defined_by - } - _ => unreachable!(), - }; - let is_last_use = !is_live_after && is_last_dependent; - if is_last_use { - Constraint::Move - } else { - Constraint::Copy - } - } -} - -fn transitive_instruction_dependents( - stack_or_result_node: NodeId, - current_block_info: &BlockInfo, -) -> SmallSet { - current_block_info - .depgraph - .predecessors(stack_or_result_node) - .map(|p| { - assert!(p.dependent.is_argument()); - current_block_info.depgraph.unwrap_parent(p.dependent) - }) - .collect() -} - -fn build_dependency_graph( - block_id: hir::Block, - function: &hir::Function, - liveness: &LivenessAnalysis, -) -> DependencyGraph { - let mut graph = DependencyGraph::default(); - - // This set represents the values which are guaranteed to be materialized for an instruction - let mut materialized_args = SmallSet::::default(); - // This map represents values used as block arguments, and the successors which use them - let mut block_arg_uses = SmallMap::>::default(); - - // For each instruction, record it and it's arguments/results in the graph - for (inst_index, inst) in function.dfg.block_insts(block_id).enumerate() { - materialized_args.clear(); - block_arg_uses.clear(); - - let node_id = graph.add_node(Node::Inst { - id: inst, - pos: inst_index as u16, - }); - - let pp = ProgramPoint::Inst(inst); - for (arg_idx, arg) in function.dfg.inst_args(inst).iter().copied().enumerate() { - materialized_args.insert(arg); - let arg_node = ArgumentNode::Direct { - inst, - index: arg_idx.try_into().expect("too many arguments"), - }; - graph.add_data_dependency(node_id, arg_node, arg, pp, function); - } - - // Ensure all result nodes are added to the graph, otherwise unused results will not be - // present in the graph which will cause problems when we check for those results later - for (result_idx, result) in function.dfg.inst_results(inst).iter().copied().enumerate() { - let result_node = Node::Result { - value: result, - index: result_idx as u8, - }; - let result_node_id = graph.add_node(result_node); - graph.add_dependency(result_node_id, node_id); - } - - match function.dfg.analyze_branch(inst) { - BranchInfo::SingleDest(succ) => { - // Add edges representing these data dependencies in later blocks - for (arg_idx, arg) in succ.args.iter().copied().enumerate() { - let arg_node = ArgumentNode::Indirect { - inst, - index: arg_idx.try_into().expect("too many successor arguments"), - successor: 0, - }; - graph.add_data_dependency(node_id, arg_node, arg, pp, function); - } - } - BranchInfo::MultiDest(ref succs) => { - // Preprocess the arguments which are used so we can determine materialization - // requirements - for succ in succs.iter() { - for arg in succ.args.iter().copied() { - block_arg_uses - .entry(arg) - .or_insert_with(Default::default) - .insert(succ.destination); - } - } - // For each successor, check if we should implicitly require an argument along that - // edge due to liveness analysis indicating that it is used - // somewhere downstream. We only consider block arguments passed to - // at least one other successor, and which are not already explicitly - // provided to this successor. - let materialization_threshold = succs.len(); - // Finally, add edges to the dependency graph representing the nature of each - // argument - for (succ_idx, succ) in succs.iter().enumerate() { - for (arg_idx, arg) in succ.args.iter().copied().enumerate() { - let is_conditionally_materialized = - block_arg_uses[&arg].len() < materialization_threshold; - let must_materialize = - materialized_args.contains(&arg) || !is_conditionally_materialized; - let index = arg_idx.try_into().expect("too many successor arguments"); - let successor = succ_idx.try_into().expect("too many successors"); - let arg_node = if must_materialize { - ArgumentNode::Indirect { - inst, - index, - successor, - } - } else { - ArgumentNode::Conditional { - inst, - index, - successor, - } - }; - graph.add_data_dependency(node_id, arg_node, arg, pp, function); - } - } - } - BranchInfo::NotABranch => (), - } - } - - // HACK: If there are any instruction nodes with no predecessors, with the exception of the - // block terminator, then we must add a control dependency to the graph to reflect the fact - // that the instruction must have been placed in this block intentionally. However, we are - // free to schedule the instruction as we see fit to avoid de-optimizing the normal - // instruction schedule unintentionally. - // - // We also avoid adding control dependencies for instructions without side effects that are not - // live beyond the current block, as those are dead code and should be eliminated in the DCE - // step. - // - // The actual scheduling decision for the instruction is deferred to `analyze_inst`, where we - // treat the instruction similarly to argument materialization, and either make it a - // pre-requisite of the instruction or execute it in the post-execution phase depending on - // the terminator type - assign_control_dependencies(&mut graph, block_id, function, liveness); - - // Eliminate dead code as indicated by the state of the dependency graph - dce(&mut graph, block_id, function, liveness); - - graph -} - -/// This function performs two primary tasks: -/// -/// 1. Ensure that there are edges in the graph between instructions that must not be reordered -/// past each other during scheduling. An obvious example is loads and stores: you might have two -/// independent expression trees that read and write to the same memory location - but if the -/// order in which the corresponding loads and stores are scheduled changes, it can change the -/// behavior of the program. Other examples include function calls with side effects and inline -/// assembly. -/// -/// 2. Ensure that even if an instruction has no predecessors, it still gets scheduled if it has -/// side effects. This can happen if there are no other effectful instructions in the same block. -/// We add an edge from the block terminator to these instructions, which will guarantee that -/// they are executed before leaving the block. -/// -/// We call these instruction->instruction dependencies "control dependencies", since control flow -/// in the block depends on them being executed first. If such instructions have no predecessors -/// in the graph, then we assume that they can be scheduled anywhere in the block; but beyond that -/// we ensure that any two effectful instructions are never reordered relative to each other, -/// unless we can prove that they have no effect on each other (we don't actually try to prove this -/// at the moment, but in the future we may). -/// -/// NOTE: This function only assigns control dependencies for instructions _with_ side effects. An -/// instruction with no dependents, and no side effects, is treated as dead code, since by -/// definition its effects cannot be visible. It should be noted however that we are quite -/// conservative about determining if an instruction has side effects - e.g., all function calls are -/// assumed to have side effects at this point in time. -fn assign_control_dependencies( - graph: &mut DependencyGraph, - block_id: hir::Block, - function: &hir::Function, - liveness: &LivenessAnalysis, -) { - let terminator = { - let block = function.dfg.block(block_id); - let id = block.last().unwrap(); - Node::Inst { - id, - pos: (block.len() - 1) as u16, - } - }; - let terminator_id = terminator.into(); - - let mut reads = vec![]; - let mut writes = vec![]; - for (inst_index, inst) in function.dfg.block_insts(block_id).enumerate() { - let opcode = function.dfg.inst(inst).opcode(); - // Skip the block terminator - if opcode.is_terminator() { - continue; - } - - let node = Node::Inst { - id: inst, - pos: inst_index as u16, - }; - let node_id = node.id(); - - // Does this instruction have memory effects? - // - // If it reads memory, ensure that there is an edge in the graph from the last observed - // store. In effect, this makes the read dependent on the most recent write, even if there - // is no direct connection between the two instructions otherwise. - // - // If it writes memory, ensure that there is an edge in the graph from the last observed - // load. In effect, this makes the write dependent on the most recent read, even if there - // is no direct connection between the two instructions otherwise. - // - // If it both reads and writes, ensure there are edges to both the last load and store. - let mem_read = opcode.reads_memory(); - let mem_write = opcode.writes_memory(); - // Ensure store-store ordering as well as load-store ordering - if mem_read || mem_write { - // Have there been any stores observed? - if let Some(last_store) = writes.last().copied() { - // Only add the dependency if there is no path from this instruction to that one - if !graph.is_reachable_from(node_id, last_store) { - graph.add_dependency(node_id, last_store); - } - } - reads.push(node_id); - } - if mem_write { - // Have there been any loads observed? - if let Some(last_load) = reads.last().copied() { - if !graph.is_reachable_from(node_id, last_load) { - graph.add_dependency(node_id, last_load); - } - } - writes.push(node_id); - } - - // At this point, we want to handle adding a control dependency from the terminator to this - // instruction, if there are no other nodes on which to attach one, and if the instruction - // requires one. - - // Skip instructions with transitive dependents on at least one result, or a direct - // dependent - let has_dependents = graph.predecessors(node_id).any(|pred| { - if pred.dependent.is_result() { - graph.num_predecessors(pred.dependent) > 0 - } else { - true - } - }); - if has_dependents { - continue; - } - - // Instructions with no side effects require a control dependency if at least - // one result is live after the end of the current block. We add the dependency - // to the instruction results if present, otherwise to the instruction itself. - let pp = ProgramPoint::Block(block_id); - let mut live_results = SmallVec::<[NodeId; 2]>::default(); - for pred in graph.predecessors(node_id) { - match pred.dependent.into() { - Node::Result { value, .. } => { - let is_live_after = liveness.is_live_after(&value, pp); - if is_live_after { - live_results.push(pred.dependent); - } - } - _ => continue, - } - } - - let has_live_results = !live_results.is_empty(); - for result_node in live_results.into_iter() { - graph.add_dependency(terminator_id, result_node); - } - - // Instructions with side effects but no live results require a control dependency - if opcode.has_side_effects() && !has_live_results { - // Only add one if there is no other transitive dependency that accomplishes - // the same goal - if !graph.is_reachable_from(terminator_id, node_id) { - graph.add_dependency(terminator_id, node_id); - } - continue; - } - } -} - -fn dce( - graph: &mut DependencyGraph, - block_id: hir::Block, - function: &hir::Function, - liveness: &LivenessAnalysis, -) { - // Perform dead-code elimination - // - // Find all instruction nodes in the graph, and if none of the instruction results - // are used, or are live beyond it's containing block; and the instruction has no - // side-effects, then remove all of the nodes related to that instruction, continuing - // until there are no more nodes to process. - let mut worklist = VecDeque::<(hir::Inst, NodeId)>::from_iter( - function.dfg.block_insts(block_id).enumerate().map(|(i, inst)| { - ( - inst, - Node::Inst { - id: inst, - pos: i as u16, - } - .into(), - ) - }), - ); - let mut remove_nodes = Vec::::default(); - while let Some((inst, inst_node)) = worklist.pop_front() { - // If the instruction is not dead at this point, leave it alone - if !is_dead_instruction(inst, block_id, function, liveness, graph) { - continue; - } - let inst_block = function.dfg.insts[inst].block; - let inst_args = function.dfg.inst_args(inst); - let branch_info = function.dfg.analyze_branch(inst); - // Visit the immediate successors of the instruction node in the dependency graph, - // these by construction may only be Argument or BlockArgument nodes. - for succ in graph.successors(inst_node) { - let dependency_node_id = succ.dependency; - // For each argument, remove the edge from instruction to argument, and from - // argument to the item it references. If the argument references an instruction - // result in the same block, add that instruction back to the worklist to check - // again in case we have made it dead - match succ.dependency.into() { - Node::Argument(ArgumentNode::Direct { index, .. }) => { - let value = inst_args[index as usize]; - match function.dfg.value_data(value) { - hir::ValueData::Inst { - inst: value_inst, .. - } => { - let value_inst = *value_inst; - let value_inst_block = function.dfg.insts[value_inst].block; - if value_inst_block == inst_block { - let pos = function - .dfg - .block_insts(inst_block) - .position(|id| id == value_inst) - .unwrap(); - // Check `value_inst` later to see if it has been made dead - worklist.push_back(( - value_inst, - Node::Inst { - id: value_inst, - pos: pos as u16, - } - .into(), - )); - } - } - hir::ValueData::Param { .. } => {} - } - } - Node::Argument( - ArgumentNode::Indirect { - successor, index, .. - } - | ArgumentNode::Conditional { - successor, index, .. - }, - ) => { - let successor = successor as usize; - let index = index as usize; - let value = match &branch_info { - BranchInfo::SingleDest(succ) => { - assert_eq!(successor, 0); - succ.args[index] - } - BranchInfo::MultiDest(ref succs) => succs[successor].args[index], - BranchInfo::NotABranch => unreachable!( - "indirect/conditional arguments are only valid as successors of a \ - branch instruction" - ), - }; - match function.dfg.value_data(value) { - hir::ValueData::Inst { - inst: value_inst, .. - } => { - let value_inst = *value_inst; - let value_inst_block = function.dfg.insts[value_inst].block; - if value_inst_block == inst_block { - let pos = function - .dfg - .block_insts(inst_block) - .position(|id| id == value_inst) - .unwrap(); - // Check `value_inst` later to see if it has been made dead - worklist.push_back(( - value_inst, - Node::Inst { - id: value_inst, - pos: pos as u16, - } - .into(), - )); - } - } - hir::ValueData::Param { .. } => {} - } - } - // This is a control dependency added intentionally, skip it - Node::Inst { .. } => continue, - // No other node types are possible - Node::Result { .. } | Node::Stack(_) => { - unreachable!("invalid successor for instruction node") - } - } - remove_nodes.push(dependency_node_id); - } - - // Remove all of the result nodes because the instruction is going away - for pred in graph.predecessors(inst_node) { - remove_nodes.push(pred.dependent); - } - - // Remove the instruction last - remove_nodes.push(inst_node); - - // All of the nodes to be removed are queued, so remove them now before we proceed - for remove_id in remove_nodes.iter().copied() { - graph.remove_node(remove_id); - } - } -} - -fn is_dead_instruction( - inst: hir::Inst, - block_id: hir::Block, - function: &hir::Function, - liveness: &LivenessAnalysis, - graph: &DependencyGraph, -) -> bool { - let results = function.dfg.inst_results(inst); - let has_side_effects = function.dfg.inst(inst).has_side_effects(); - if results.is_empty() && !has_side_effects { - return true; - } - - let pp = ProgramPoint::Block(block_id); - let is_live = results.iter().copied().enumerate().any(|(result_idx, result)| { - let result_node = Node::Result { - value: result, - index: result_idx as u8, - }; - if graph.num_predecessors(result_node) > 0 { - return true; - } - liveness.is_live_after(&result, pp) - }); - - !is_live && !has_side_effects -} diff --git a/codegen/masm/src/codegen/stack.rs b/codegen/masm/src/codegen/stack.rs deleted file mode 100644 index eace644e1..000000000 --- a/codegen/masm/src/codegen/stack.rs +++ /dev/null @@ -1,1300 +0,0 @@ -use core::{ - fmt, - hash::{Hash, Hasher}, - ops::{Index, IndexMut}, -}; - -use midenc_hir::{Felt, FieldElement, Immediate, Type, Value}; -use smallvec::{smallvec, SmallVec}; - -/// This represents a constraint an operand's usage at -/// a given program point, namely when used as an instruction -/// or block argument. -#[derive(Debug, Copy, Clone)] -pub enum Constraint { - /// The operand should be moved, consuming it - /// from the stack and making it unavailable for - /// further use. - Move, - /// The operand should be copied, preserving the - /// original value for later use. - Copy, -} - -/// A [TypedValue] is a pair of an SSA value with its known type -#[derive(Debug, Clone)] -pub struct TypedValue { - pub value: Value, - pub ty: Type, -} -impl Eq for TypedValue {} -impl PartialEq for TypedValue { - fn eq(&self, other: &Self) -> bool { - self.value == other.value - } -} -impl Ord for TypedValue { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.value.cmp(&other.value) - } -} -impl PartialOrd for TypedValue { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.value.cmp(&other.value)) - } -} -impl Hash for TypedValue { - fn hash(&self, state: &mut H) { - self.value.hash(state); - } -} -impl AsRef for TypedValue { - #[inline(always)] - fn as_ref(&self) -> &Value { - &self.value - } -} -impl fmt::Display for TypedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}: {}", &self.value, &self.ty) - } -} - -/// A [ConstantValue] is either an immediate value, or a large immediate that has been -/// split into raw bytes. -#[derive(Clone)] -pub enum ConstantValue { - Imm(Immediate), - Bytes(SmallVec<[u8; 16]>), -} -impl ConstantValue { - #[inline] - pub fn ty(&self) -> Type { - match self { - Self::Imm(imm) => imm.ty(), - Self::Bytes(ref bytes) => Type::Array(Box::new(Type::U8), bytes.len()), - } - } -} -impl Eq for ConstantValue {} -impl PartialEq for ConstantValue { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Imm(ref a), Self::Imm(ref b)) => a.cmp(b).is_eq(), - (Self::Bytes(ref a), Self::Bytes(ref b)) => a == b, - (..) => false, - } - } -} -impl PartialEq for ConstantValue { - fn eq(&self, other: &Immediate) -> bool { - match self { - Self::Imm(ref a) => a.cmp(other).is_eq(), - _ => false, - } - } -} -impl From for ConstantValue { - fn from(imm: Immediate) -> Self { - Self::Imm(imm) - } -} -impl From<&[u8]> for ConstantValue { - fn from(bytes: &[u8]) -> Self { - Self::Bytes(SmallVec::from(bytes)) - } -} -impl fmt::Debug for ConstantValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Imm(ref imm) => fmt::Debug::fmt(imm, f), - Self::Bytes(ref bytes) => { - if !bytes.is_empty() { - write!(f, "Bytes(0x")?; - for b in bytes.iter().rev() { - write!(f, "{:02x}", b)?; - } - write!(f, ")")?; - } - Ok(()) - } - } - } -} - -/// Represents the type of operand represented on the operand stack -#[derive(Clone)] -pub enum OperandType { - /// The operand is a literal, unassociated with any value in the IR - Const(ConstantValue), - /// The operand is an SSA value of known type - Value(TypedValue), - /// The operand is an intermediate runtime value of a known type, but - /// unassociated with any value in the IR - Type(Type), -} -impl OperandType { - /// Get the type representation of this operand - pub fn ty(&self) -> Type { - match self { - Self::Const(imm) => imm.ty(), - Self::Value(TypedValue { ref ty, .. }) => ty.clone(), - Self::Type(ref ty) => ty.clone(), - } - } -} -impl fmt::Debug for OperandType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Const(value) => write!(f, "Const({value:?})"), - Self::Value(value) => write!(f, "Value({value})"), - Self::Type(ty) => write!(f, "Type({ty})"), - } - } -} -impl Eq for OperandType {} -impl PartialEq for OperandType { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Value(a), Self::Value(b)) => a == b, - (Self::Value(_), _) | (_, Self::Value(_)) => false, - (Self::Const(ref a), Self::Const(ref b)) => a == b, - (Self::Const(_), _) | (_, Self::Const(_)) => false, - (Self::Type(ref a), Self::Type(ref b)) => a == b, - } - } -} -impl PartialEq for OperandType { - fn eq(&self, other: &Type) -> bool { - match self { - Self::Type(a) => a == other, - _ => false, - } - } -} -impl PartialEq for OperandType { - fn eq(&self, other: &Immediate) -> bool { - match self { - Self::Const(a) => a == other, - _ => false, - } - } -} -impl PartialEq for OperandType { - fn eq(&self, other: &Value) -> bool { - match self { - Self::Value(this) => &this.value == other, - _ => false, - } - } -} -impl From for OperandType { - fn from(value: TypedValue) -> Self { - Self::Value(value) - } -} -impl From for OperandType { - fn from(ty: Type) -> Self { - Self::Type(ty) - } -} -impl From for OperandType { - fn from(value: bool) -> Self { - Self::Const(Immediate::I1(value).into()) - } -} -impl From for OperandType { - fn from(value: u8) -> Self { - Self::Const(Immediate::U8(value).into()) - } -} -impl From for OperandType { - fn from(value: u16) -> Self { - Self::Const(Immediate::U16(value).into()) - } -} -impl From for OperandType { - fn from(value: u32) -> Self { - Self::Const(Immediate::U32(value).into()) - } -} -impl From for OperandType { - fn from(value: u64) -> Self { - Self::Const(Immediate::U64(value).into()) - } -} -impl From for OperandType { - fn from(value: Felt) -> Self { - Self::Const(Immediate::Felt(value).into()) - } -} -impl From for OperandType { - fn from(value: Immediate) -> Self { - Self::Const(value.into()) - } -} -impl From<&[u8]> for OperandType { - fn from(value: &[u8]) -> Self { - Self::Const(value.into()) - } -} - -/// This type represents a logical operand on the stack, which may consist -/// of one or more "parts", up to a word in size, on the actual stack. -/// -/// The [OperandStack] operates in terms of [Operand], but when emitting -/// Miden Assembly, we must know how to translate operand-oriented operations -/// into equivalent element-/word-oriented operations. This is accomplished -/// by tracking the low-level representation of a given operand in this struct. -#[derive(Debug, Clone)] -pub struct Operand { - /// The section of stack corresponding to this operand, containing - /// up to a full word of elements. No chunk will ever exceed a word - /// in size. This field behaves like a miniature [OperandStack], i.e. - /// elements are pushed and popped off the end to modify it. - /// - /// An operand is encoded on this stack in order of lowest - /// addressed bytes first. For example, given a struct operand, - /// the first field of the struct will be closest to the top of - /// the stack. - word: SmallVec<[Type; 4]>, - /// The high-level operand represented by this item. - /// - /// If the operand stack is manipulated in such a way that the operand - /// is torn apart, say one field of a struct is popped; then this will - /// be set to a `Type` operand, representing what high-level information - /// we have about the remaining parts of the original operand on the stack. - operand: OperandType, -} -impl Default for Operand { - fn default() -> Self { - Self { - word: smallvec![Type::Felt], - operand: Felt::ZERO.into(), - } - } -} -impl PartialEq for Operand { - #[inline(always)] - fn eq(&self, other: &Value) -> bool { - self.operand.eq(other) - } -} -impl PartialEq for Operand { - #[inline(always)] - fn eq(&self, other: &Immediate) -> bool { - self.operand.eq(other) - } -} -impl PartialEq for &Operand { - #[inline(always)] - fn eq(&self, other: &Immediate) -> bool { - self.operand.eq(other) - } -} -impl PartialEq for Operand { - #[inline(always)] - fn eq(&self, other: &Type) -> bool { - self.operand.eq(other) - } -} -impl PartialEq for &Operand { - #[inline(always)] - fn eq(&self, other: &Type) -> bool { - self.operand.eq(other) - } -} -impl From for Operand { - #[inline] - fn from(imm: Immediate) -> Self { - Self::new(imm.into()) - } -} -impl From for Operand { - #[inline] - fn from(imm: u32) -> Self { - Self::new(Immediate::U32(imm).into()) - } -} -impl TryFrom<&Operand> for Value { - type Error = (); - - fn try_from(operand: &Operand) -> Result { - match operand.operand { - OperandType::Value(TypedValue { value, .. }) => Ok(value), - _ => Err(()), - } - } -} -#[cfg(test)] -impl TryFrom<&Operand> for Immediate { - type Error = (); - - fn try_from(operand: &Operand) -> Result { - match operand.operand { - OperandType::Const(ConstantValue::Imm(ref imm)) => Ok(*imm), - _ => Err(()), - } - } -} -#[cfg(test)] -impl TryFrom<&Operand> for Type { - type Error = (); - - fn try_from(operand: &Operand) -> Result { - match operand.operand { - OperandType::Type(ref ty) => Ok(ty.clone()), - _ => Err(()), - } - } -} -#[cfg(test)] -impl TryFrom for Type { - type Error = (); - - fn try_from(operand: Operand) -> Result { - match operand.operand { - OperandType::Type(ty) => Ok(ty), - _ => Err(()), - } - } -} -impl From for Operand { - #[inline] - fn from(ty: Type) -> Self { - Self::new(OperandType::Type(ty)) - } -} -impl From for Operand { - #[inline] - fn from(value: TypedValue) -> Self { - Self::new(OperandType::Value(value)) - } -} -impl Operand { - pub fn new(operand: OperandType) -> Self { - let ty = operand.ty(); - let mut word = ty.to_raw_parts().expect("invalid operand type"); - assert!(!word.is_empty(), "invalid operand: must be a sized type"); - assert!(word.len() <= 4, "invalid operand: must be smaller than or equal to a word"); - if word.len() > 1 { - word.reverse(); - } - Self { word, operand } - } - - /// Get the size of this operand in field elements - pub fn size(&self) -> usize { - self.word.len() - } - - /// Get the [OperandType] representing the value of this operand - #[inline(always)] - pub fn value(&self) -> &OperandType { - &self.operand - } - - /// Get this operand as a [Value] - #[inline] - pub fn as_value(&self) -> Option { - self.try_into().ok() - } - - /// Get the [Type] of this operand - #[inline] - pub fn ty(&self) -> Type { - self.operand.ty() - } - - /// Pop a single field element from the underlying stack segment corresponding to this operand, - /// as a new [Operand]. - /// - /// For operands that fit in a field element, this is equivalent to cloning the operand. - /// For operands that are larger than a field element, the type will be split at the fourth - /// byte, this may destroy the semantics of a higher-level type (i.e. a large integer might - /// become a smaller one, or an array of raw bytes). It is assumed that the caller is doing - /// this intentionally. - #[allow(unused)] - pub fn pop(&mut self) -> Operand { - if self.word.len() == 1 { - self.clone() - } else { - match &mut self.operand { - OperandType::Const(ref mut imm) => match imm { - ConstantValue::Bytes(ref mut bytes) => { - assert!(bytes.len() > 4); - let taken = ConstantValue::Bytes(SmallVec::from(&bytes[0..4])); - let new_bytes = SmallVec::from(&bytes[4..]); - *bytes = new_bytes; - let ty = self.word.pop().unwrap(); - Self { - word: smallvec![ty], - operand: OperandType::Const(taken), - } - } - ConstantValue::Imm(Immediate::U64(i)) => { - let lo = *i & (u32::MAX as u64); - let hi = *i & !(u32::MAX as u64); - *imm = ConstantValue::Imm(Immediate::U32(lo as u32)); - let ty = self.word.pop().unwrap(); - Self { - word: smallvec![ty], - operand: Immediate::U32((hi >> 32) as u32).into(), - } - } - ConstantValue::Imm(Immediate::I64(i)) => { - let i = *i as u64; - let lo = i & (u32::MAX as u64); - let hi = i & !(u32::MAX as u64); - *imm = ConstantValue::Imm(Immediate::U32(lo as u32)); - let ty = self.word.pop().unwrap(); - Self { - word: smallvec![ty], - operand: Immediate::U32((hi >> 32) as u32).into(), - } - } - ConstantValue::Imm(Immediate::F64(f)) => { - let bytes = f.to_le_bytes(); - let hi = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - let lo = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]); - *imm = ConstantValue::Imm(Immediate::U32(lo)); - let ty = self.word.pop().unwrap(); - Self { - word: smallvec![ty], - operand: Immediate::U32(hi).into(), - } - } - ConstantValue::Imm(Immediate::I128(i)) => { - let i = *i as u128; - let bytes = i.to_le_bytes(); - let hi = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - *imm = ConstantValue::Bytes(SmallVec::from(&bytes[4..])); - let ty = self.word.pop().unwrap(); - Self { - word: smallvec![ty], - operand: Immediate::U32(hi).into(), - } - } - ConstantValue::Imm(_) => unreachable!(), - }, - OperandType::Value(ref tv) => match tv.ty.clone().split(4) { - (ty, Some(rest)) => { - let operand = OperandType::Type(ty); - self.operand = OperandType::Type(rest); - let ty = self.word.pop().unwrap(); - Self { - word: smallvec![ty], - operand, - } - } - (_, None) => unreachable!(), - }, - OperandType::Type(ref ty) => match ty.clone().split(4) { - (ty, Some(rest)) => { - let operand = OperandType::Type(ty); - self.operand = OperandType::Type(rest); - let ty = self.word.pop().unwrap(); - Self { - word: smallvec![ty], - operand, - } - } - (_, None) => unreachable!(), - }, - } - } - } -} - -/// This structure emulates the state of the VM's operand stack while -/// generating code from the SSA representation of a function. -/// -/// In order to emit efficient and correct stack manipulation code, we must be able to -/// reason about where values are on the operand stack at a given program point. This -/// structure tracks what SSA values have been pushed on the operand stack, where they are -/// on the stack relative to the top, and whether a given stack slot aliases multiple -/// values. -/// -/// In addition to the state tracked, this structure also has an API that mimics the -/// stack manipulation instructions we can emit in the code generator, so that as we -/// emit instructions and modify this structure at the same time, 1:1. -#[derive(Clone)] -pub struct OperandStack { - stack: Vec, -} -impl Default for OperandStack { - fn default() -> Self { - Self { - stack: Vec::with_capacity(16), - } - } -} -impl OperandStack { - /// Renames the `n`th operand from the top of the stack to `value` - /// - /// The type is assumed to remain unchanged - pub fn rename(&mut self, n: usize, value: Value) { - match &mut self[n].operand { - OperandType::Value(TypedValue { - value: ref mut prev_value, - .. - }) => { - *prev_value = value; - } - prev => { - let ty = prev.ty(); - *prev = OperandType::Value(TypedValue { value, ty }); - } - } - } - - /// Searches for the position on the stack containing the operand corresponding to `value`. - /// - /// NOTE: This function will panic if `value` is not on the stack - pub fn find(&self, value: &Value) -> Option { - self.stack.iter().rev().position(|v| v == value) - } - - /// Returns true if the operand stack is empty - #[allow(unused)] - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.stack.is_empty() - } - - /// Returns the number of field elements on the stack - #[inline] - pub fn raw_len(&self) -> usize { - self.stack.iter().map(|operand| operand.size()).sum() - } - - /// Returns the index in the actual runtime stack which corresponds to - /// the first element of the operand at `index`. - #[track_caller] - pub fn effective_index(&self, index: usize) -> usize { - assert!( - index < self.stack.len(), - "expected {} to be less than {}", - index, - self.stack.len() - ); - - self.stack.iter().rev().take(index).map(|o| o.size()).sum() - } - - /// Returns the index in the actual runtime stack which corresponds to - /// the last element of the operand at `index`. - #[track_caller] - pub fn effective_index_inclusive(&self, index: usize) -> usize { - assert!(index < self.stack.len()); - - self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum::() - 1 - } - - /// Returns the number of operands on the stack - #[inline] - pub fn len(&self) -> usize { - self.stack.len() - } - - /// Returns the operand on top of the stack, without consuming it - #[inline] - pub fn peek(&self) -> Option<&Operand> { - self.stack.last() - } - - /// Returns the word on top of the stack, without consuming it - /// - /// NOTE: A word will always be 4 field elements, so if an operand is - /// larger than a field element, it may be split into an appropriately- - /// sized operand in order to fit. - #[allow(unused)] - #[inline] - pub fn peekw(&self) -> Option<[Operand; 4]> { - use core::mem::MaybeUninit; - - let end = self.stack.len().checked_sub(1)?; - if self.raw_len() < 4 { - return None; - } - - let mut word = MaybeUninit::<[Operand; 4]>::uninit(); - let mut stack = self.stack[(end - 3)..].to_vec(); - let ptr = word.as_mut_ptr() as *mut Operand; - let mut index = 0usize; - while index < 4 { - let mut elem = stack.pop().unwrap(); - let ptr = unsafe { ptr.add(index) }; - match elem.size() { - 1 => { - unsafe { - ptr.write(elem); - } - index += 1; - } - _ => { - let a = elem.pop(); - unsafe { - ptr.write(a); - } - index += 1; - stack.push(elem); - } - } - } - - Some(unsafe { MaybeUninit::assume_init(word) }) - } - - /// Pushes a word of zeroes on top of the stack - #[allow(unused)] - pub fn padw(&mut self) { - let default = Operand { - word: smallvec![Type::U32], - operand: 0u32.into(), - }; - self.stack.push(default.clone()); - self.stack.push(default.clone()); - self.stack.push(default.clone()); - self.stack.push(default); - } - - /// Pushes an operand on top of the stack - #[inline] - pub fn push>(&mut self, value: V) { - self.stack.push(value.into()); - } - - /// Pushes a word of operands on top of the stack - /// - /// NOTE: This function will panic if any of the operands are larger than a field element. - #[inline] - #[allow(unused)] - pub fn pushw(&mut self, mut word: [Operand; 4]) { - assert!( - word.iter().all(|op| op.size() == 1), - "a word must be exactly 4 field elements in size" - ); - word.reverse(); - self.stack.extend(word); - } - - /// Pops the operand on top of the stack - #[inline] - pub fn pop(&mut self) -> Option { - self.stack.pop() - } - - /// Pops the first word on top of the stack - /// - /// NOTE: A word will always be 4 field elements, so if an operand is - /// larger than a field element, it may be split into an appropriately- - /// sized operand in order to fit. - #[allow(unused)] - pub fn popw(&mut self) -> Option<[Operand; 4]> { - use core::mem::MaybeUninit; - - if self.raw_len() < 4 { - return None; - } - - let mut word = MaybeUninit::<[Operand; 4]>::uninit(); - let ptr = word.as_mut_ptr() as *mut Operand; - let mut index = 0usize; - while index < 4 { - let mut elem = self.stack.pop().unwrap(); - let ptr = unsafe { ptr.add(index) }; - match elem.size() { - 1 => { - unsafe { - ptr.write(elem); - } - index += 1; - } - _ => { - let a = elem.pop(); - unsafe { - ptr.write(a); - } - index += 1; - self.stack.push(elem); - } - } - } - - Some(unsafe { MaybeUninit::assume_init(word) }) - } - - /// Drops the top operand on the stack - pub fn drop(&mut self) { - self.stack.pop().expect("operand stack is empty"); - } - - /// Drops the top word on the stack - /// - /// NOTE: A word will always be 4 field elements, so if an operand is - /// larger than a field element, it may be split to accommodate the request. - #[allow(unused)] - pub fn dropw(&mut self) { - assert!(self.raw_len() >= 4, "expected at least a word on the operand stack"); - let mut dropped = 0usize; - while let Some(mut elem) = self.stack.pop() { - let needed = 4 - dropped; - let size = elem.size(); - dropped += size; - match size { - n if needed == n => break, - n if needed < n => { - for _ in 0..needed { - elem.pop(); - } - self.stack.push(elem); - break; - } - _ => continue, - } - } - } - - /// Drops the top `n` operands on the stack - #[inline] - pub fn dropn(&mut self, n: usize) { - let len = self.stack.len(); - assert!(n <= len, "unable to drop {} operands, operand stack only has {}", n, len); - self.stack.truncate(len - n); - } - - /// Duplicates the operand in the `n`th position on the stack - /// - /// If `n` is 0, duplicates the top of the stack. - pub fn dup(&mut self, n: usize) { - let operand = self[n].clone(); - self.stack.push(operand); - } - - /// Swaps the `n`th operand from the top of the stack, with the top of the stack - /// - /// If `n` is 1, it swaps the first two operands on the stack. - /// - /// NOTE: This function will panic if `n` is 0, or out of bounds. - pub fn swap(&mut self, n: usize) { - assert_ne!(n, 0, "invalid swap, index must be in the range 1..=15"); - let len = self.stack.len(); - assert!( - n < len, - "invalid operand stack index ({}), only {} operands are available", - n, - len - ); - let a = len - 1; - let b = a - n; - self.stack.swap(a, b); - } - - /// Moves the `n`th operand to the top of the stack - /// - /// If `n` is 1, this is equivalent to `swap(1)`. - /// - /// NOTE: This function will panic if `n` is 0, or out of bounds. - pub fn movup(&mut self, n: usize) { - assert_ne!(n, 0, "invalid move, index must be in the range 1..=15"); - let len = self.stack.len(); - assert!( - n < len, - "invalid operand stack index ({}), only {} operands are available", - n, - len - ); - // Pick the midpoint by counting backwards from the end - let mid = len - (n + 1); - // Split the stack, and rotate the half that - // contains our desired value to place it on top. - let (_, r) = self.stack.split_at_mut(mid); - r.rotate_left(1); - } - - /// Makes the operand on top of the stack, the `n`th operand on the stack - /// - /// If `n` is 1, this is equivalent to `swap(1)`. - /// - /// NOTE: This function will panic if `n` is 0, or out of bounds. - pub fn movdn(&mut self, n: usize) { - assert_ne!(n, 0, "invalid move, index must be in the range 1..=15"); - let len = self.stack.len(); - assert!( - n < len, - "invalid operand stack index ({}), only {} operands are available", - n, - len - ); - // Split the stack so that the desired position is in the top half - let mid = len - (n + 1); - let (_, r) = self.stack.split_at_mut(mid); - // Move all elements above the `n`th position up by one, moving the top element to the `n`th - // position - r.rotate_right(1); - } - - #[allow(unused)] - #[inline(always)] - pub fn iter(&self) -> impl DoubleEndedIterator { - self.stack.iter() - } -} -impl Index for OperandStack { - type Output = Operand; - - fn index(&self, index: usize) -> &Self::Output { - let len = self.stack.len(); - assert!( - index < len, - "invalid operand stack index ({}): only {} operands are available", - index, - len - ); - let effective_len: usize = self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum(); - assert!( - effective_len <= 16, - "invalid operand stack index ({}): requires access to more than 16 elements, which is \ - not supported in Miden", - index - ); - &self.stack[len - index - 1] - } -} -impl IndexMut for OperandStack { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - let len = self.stack.len(); - assert!( - index < len, - "invalid operand stack index ({}): only {} elements are available", - index, - len - ); - let effective_len: usize = self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum(); - assert!( - effective_len <= 16, - "invalid operand stack index ({}): requires access to more than 16 elements, which is \ - not supported in Miden", - index - ); - &mut self.stack[len - index - 1] - } -} - -impl fmt::Debug for OperandStack { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[derive(Debug)] - #[allow(unused)] - struct StackEntry<'a> { - index: usize, - value: &'a Operand, - } - - f.debug_list() - .entries( - self.stack - .iter() - .rev() - .enumerate() - .map(|(index, value)| StackEntry { index, value }), - ) - .finish() - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::StructType; - - use super::*; - - #[test] - fn operand_stack_homogenous_operand_sizes_test() { - let mut stack = OperandStack::default(); - - let zero = Immediate::U32(0); - let one = Immediate::U32(1); - let two = Immediate::U32(2); - let three = Immediate::U32(3); - let four = Immediate::U32(4); - let five = Immediate::U32(5); - let six = Immediate::U32(6); - let seven = Immediate::U32(7); - - #[inline] - fn as_imms(word: [Operand; 4]) -> [Immediate; 4] { - [ - (&word[0]).try_into().unwrap(), - (&word[1]).try_into().unwrap(), - (&word[2]).try_into().unwrap(), - (&word[3]).try_into().unwrap(), - ] - } - - #[inline] - fn as_imm(operand: Operand) -> Immediate { - (&operand).try_into().unwrap() - } - - // push - stack.push(zero); - stack.push(one); - stack.push(two); - stack.push(three); - assert_eq!(stack.len(), 4); - assert_eq!(stack[0], three); - assert_eq!(stack[1], two); - assert_eq!(stack[2], one); - assert_eq!(stack[3], zero); - - // peek - assert_eq!(stack.peek().unwrap(), three); - - // peekw - assert_eq!(stack.peekw().map(as_imms), Some([three, two, one, zero])); - - // dup - stack.dup(0); - assert_eq!(stack.len(), 5); - assert_eq!(stack[0], three); - assert_eq!(stack[1], three); - assert_eq!(stack[2], two); - assert_eq!(stack[3], one); - assert_eq!(stack[4], zero); - - stack.dup(3); - assert_eq!(stack.len(), 6); - assert_eq!(stack[0], one); - assert_eq!(stack[1], three); - assert_eq!(stack[2], three); - assert_eq!(stack[3], two); - assert_eq!(stack[4], one); - assert_eq!(stack[5], zero); - - // drop - stack.drop(); - assert_eq!(stack.len(), 5); - assert_eq!(stack[0], three); - assert_eq!(stack[1], three); - assert_eq!(stack[2], two); - assert_eq!(stack[3], one); - assert_eq!(stack[4], zero); - - // padw - stack.padw(); - assert_eq!(stack.len(), 9); - assert_eq!(stack[0], zero); - assert_eq!(stack[1], zero); - assert_eq!(stack[2], zero); - assert_eq!(stack[3], zero); - assert_eq!(stack[4], three); - assert_eq!(stack[5], three); - - // popw - assert_eq!(stack.popw().map(as_imms), Some([zero, zero, zero, zero])); - assert_eq!(stack.len(), 5); - - // pushw - stack.pushw([four.into(), five.into(), six.into(), seven.into()]); - assert_eq!(stack.len(), 9); - assert_eq!(stack[0], four); - assert_eq!(stack[1], five); - assert_eq!(stack[2], six); - assert_eq!(stack[3], seven); - assert_eq!(stack[4], three); - assert_eq!(stack[5], three); - - // dropw - stack.dropw(); - assert_eq!(stack.len(), 5); - assert_eq!(stack[0], three); - assert_eq!(stack[1], three); - assert_eq!(stack[2], two); - assert_eq!(stack[3], one); - assert_eq!(stack[4], zero); - - // swap - stack.swap(2); - assert_eq!(stack.len(), 5); - assert_eq!(stack[0], two); - assert_eq!(stack[1], three); - assert_eq!(stack[2], three); - assert_eq!(stack[3], one); - assert_eq!(stack[4], zero); - - stack.swap(1); - assert_eq!(stack.len(), 5); - assert_eq!(stack[0], three); - assert_eq!(stack[1], two); - assert_eq!(stack[2], three); - assert_eq!(stack[3], one); - assert_eq!(stack[4], zero); - - // movup - stack.movup(2); - assert_eq!(stack.len(), 5); - assert_eq!(stack[0], three); - assert_eq!(stack[1], three); - assert_eq!(stack[2], two); - assert_eq!(stack[3], one); - assert_eq!(stack[4], zero); - - // movdn - stack.movdn(3); - assert_eq!(stack.len(), 5); - assert_eq!(stack[0], three); - assert_eq!(stack[1], two); - assert_eq!(stack[2], one); - assert_eq!(stack[3], three); - assert_eq!(stack[4], zero); - - // pop - assert_eq!(stack.pop().map(as_imm), Some(three)); - assert_eq!(stack.len(), 4); - assert_eq!(stack[0], two); - assert_eq!(stack[1], one); - assert_eq!(stack[2], three); - assert_eq!(stack[3], zero); - - // dropn - stack.dropn(2); - assert_eq!(stack.len(), 2); - assert_eq!(stack[0], three); - assert_eq!(stack[1], zero); - } - - #[test] - fn operand_stack_values_test() { - use midenc_hir::Value; - let mut stack = OperandStack::default(); - - let zero = Value::from_u32(0); - let one = Value::from_u32(1); - let two = Value::from_u32(2); - let three = Value::from_u32(3); - - // push - stack.push(TypedValue { - value: zero, - ty: Type::Ptr(Box::new(Type::U8)), - }); - stack.push(TypedValue { - value: one, - ty: Type::Array(Box::new(Type::U8), 4), - }); - stack.push(TypedValue { - value: two, - ty: Type::U32, - }); - stack.push(TypedValue { - value: three, - ty: Type::Struct(StructType::new([Type::U64, Type::U8])), - }); - assert_eq!(stack.len(), 4); - assert_eq!(stack.raw_len(), 6); - - assert_eq!(stack.find(&zero), Some(3)); - assert_eq!(stack.find(&one), Some(2)); - assert_eq!(stack.find(&two), Some(1)); - assert_eq!(stack.find(&three), Some(0)); - - // dup - stack.dup(0); - assert_eq!(stack.find(&three), Some(0)); - - stack.dup(3); - assert_eq!(stack.find(&one), Some(0)); - - // drop - stack.drop(); - assert_eq!(stack.find(&one), Some(3)); - assert_eq!(stack.find(&three), Some(0)); - assert_eq!(stack[1], three); - - // padw - stack.padw(); - assert_eq!(stack.find(&one), Some(7)); - assert_eq!(stack.find(&three), Some(4)); - - // rename - let four = Value::from_u32(4); - stack.rename(1, four); - assert_eq!(stack.find(&four), Some(1)); - assert_eq!(stack.find(&three), Some(4)); - - // pop - let top = stack.pop().unwrap(); - assert_eq!((&top).try_into(), Ok(Immediate::U32(0))); - assert_eq!(stack.find(&four), Some(0)); - assert_eq!(stack[1], Immediate::U32(0)); - assert_eq!(stack[2], Immediate::U32(0)); - assert_eq!(stack.find(&three), Some(3)); - - // dropn - stack.dropn(3); - assert_eq!(stack.find(&four), None); - assert_eq!(stack.find(&three), Some(0)); - assert_eq!(stack[1], three); - assert_eq!(stack.find(&two), Some(2)); - assert_eq!(stack.find(&one), Some(3)); - assert_eq!(stack.find(&zero), Some(4)); - - // swap - stack.swap(3); - assert_eq!(stack.find(&one), Some(0)); - assert_eq!(stack.find(&three), Some(1)); - assert_eq!(stack.find(&two), Some(2)); - assert_eq!(stack[3], three); - - stack.swap(1); - assert_eq!(stack.find(&three), Some(0)); - assert_eq!(stack.find(&one), Some(1)); - assert_eq!(stack.find(&two), Some(2)); - assert_eq!(stack.find(&zero), Some(4)); - - // movup - stack.movup(2); - assert_eq!(stack.find(&two), Some(0)); - assert_eq!(stack.find(&three), Some(1)); - assert_eq!(stack.find(&one), Some(2)); - assert_eq!(stack.find(&zero), Some(4)); - - // movdn - stack.movdn(3); - assert_eq!(stack.find(&three), Some(0)); - assert_eq!(stack.find(&one), Some(1)); - assert_eq!(stack[2], three); - assert_eq!(stack.find(&two), Some(3)); - assert_eq!(stack.find(&zero), Some(4)); - } - - #[test] - fn operand_stack_heterogenous_operand_sizes_test() { - let mut stack = OperandStack::default(); - - let zero = Immediate::U32(0); - let one = Immediate::U32(1); - let two = Type::U64; - let three = Type::U64; - let struct_a = - Type::Struct(StructType::new([Type::Ptr(Box::new(Type::U8)), Type::U16, Type::U32])); - - // push - stack.push(zero); - stack.push(one); - stack.push(two.clone()); - stack.push(three.clone()); - stack.push(struct_a.clone()); - assert_eq!(stack.len(), 5); - assert_eq!(stack.raw_len(), 9); - assert_eq!(stack[0], struct_a); - assert_eq!(stack[1], three); - assert_eq!(stack[2], two); - assert_eq!(stack[3], one); - assert_eq!(stack[4], zero); - - // peek - assert_eq!(stack.peek().unwrap(), struct_a); - - // peekw - let word = stack.peekw().unwrap(); - let struct_parts = struct_a.clone().to_raw_parts().unwrap(); - let u64_parts = three.clone().to_raw_parts().unwrap(); - assert_eq!(struct_parts.len(), 3); - assert_eq!(u64_parts.len(), 2); - assert_eq!(&word[0], &struct_parts[0]); - assert_eq!(&word[1], &struct_parts[1]); - assert_eq!(&word[2], &struct_parts[2]); - assert_eq!(&word[3], &u64_parts[0]); - - // dup - stack.dup(0); - assert_eq!(stack.len(), 6); - assert_eq!(stack.raw_len(), 12); - assert_eq!(stack[0], struct_a); - assert_eq!(stack[1], struct_a); - assert_eq!(stack[2], three); - assert_eq!(stack[3], two); - assert_eq!(stack[4], one); - assert_eq!(stack[5], zero); - assert_eq!(stack.effective_index(3), 8); - - stack.dup(3); - assert_eq!(stack.len(), 7); - assert_eq!(stack.raw_len(), 14); - assert_eq!(stack[0], two); - assert_eq!(stack[1], struct_a); - assert_eq!(stack[2], struct_a); - - // drop - stack.drop(); - assert_eq!(stack.len(), 6); - assert_eq!(stack.raw_len(), 12); - assert_eq!(stack[0], struct_a); - assert_eq!(stack[1], struct_a); - assert_eq!(stack[2], three); - assert_eq!(stack[3], two); - assert_eq!(stack[4], one); - assert_eq!(stack[5], zero); - - // swap - stack.swap(2); - assert_eq!(stack.len(), 6); - assert_eq!(stack.raw_len(), 12); - assert_eq!(stack[0], three); - assert_eq!(stack[1], struct_a); - assert_eq!(stack[2], struct_a); - assert_eq!(stack[3], two); - assert_eq!(stack[4], one); - - stack.swap(1); - assert_eq!(stack.len(), 6); - assert_eq!(stack.raw_len(), 12); - assert_eq!(stack[0], struct_a); - assert_eq!(stack[1], three); - assert_eq!(stack[2], struct_a); - assert_eq!(stack[3], two); - assert_eq!(stack[4], one); - assert_eq!(stack[5], zero); - - // movup - stack.movup(4); - assert_eq!(stack.len(), 6); - assert_eq!(stack.raw_len(), 12); - assert_eq!(stack[0], one); - assert_eq!(stack[1], struct_a); - assert_eq!(stack[2], three); - assert_eq!(stack[3], struct_a); - assert_eq!(stack[4], two); - assert_eq!(stack[5], zero); - - // movdn - stack.movdn(3); - assert_eq!(stack.len(), 6); - assert_eq!(stack.raw_len(), 12); - assert_eq!(stack[0], struct_a); - assert_eq!(stack[1], three); - assert_eq!(stack[2], struct_a); - assert_eq!(stack[3], one); - assert_eq!(stack[4], two); - - // pop - let operand: Type = stack.pop().unwrap().try_into().unwrap(); - assert_eq!(operand, struct_a); - assert_eq!(stack.len(), 5); - assert_eq!(stack.raw_len(), 9); - assert_eq!(stack[0], three); - assert_eq!(stack[1], struct_a); - assert_eq!(stack[2], one); - assert_eq!(stack[3], two); - - // dropn - stack.dropn(2); - assert_eq!(stack.len(), 3); - assert_eq!(stack.raw_len(), 4); - assert_eq!(stack[0], one); - assert_eq!(stack[1], two); - assert_eq!(stack[2], zero); - } -} diff --git a/codegen/masm/src/compiler/masm.rs b/codegen/masm/src/compiler/masm.rs deleted file mode 100644 index 034aec93f..000000000 --- a/codegen/masm/src/compiler/masm.rs +++ /dev/null @@ -1,75 +0,0 @@ -use miden_assembly::Library as CompiledLibrary; -use midenc_hir::Symbol; -use midenc_session::{diagnostics::Report, Emit, OutputMode, OutputType, Session}; - -use crate::{Library, MastArtifact, Module, Program}; - -/// The artifact produced by lowering an [hir::Program] to Miden Assembly -/// -/// This type is used in compilation pipelines to abstract over the type of output requested. -pub enum MasmArtifact { - /// An executable program, with a defined entrypoint - Executable(Box), - /// A library, linkable into a program as needed - Library(Box), -} - -impl MasmArtifact { - pub fn assemble(&self, session: &Session) -> Result { - match self { - Self::Executable(program) => program.assemble(session).map(MastArtifact::Executable), - Self::Library(library) => library.assemble(session).map(MastArtifact::Library), - } - } - - /// Get an iterator over the modules in this library - pub fn modules(&self) -> impl Iterator + '_ { - match self { - Self::Executable(ref program) => program.library().modules(), - Self::Library(ref lib) => lib.modules(), - } - } - - pub fn insert(&mut self, module: Box) { - match self { - Self::Executable(ref mut program) => program.insert(module), - Self::Library(ref mut lib) => lib.insert(module), - } - } - - pub fn link_library(&mut self, lib: CompiledLibrary) { - match self { - Self::Executable(ref mut program) => program.link_library(lib), - Self::Library(ref mut library) => library.link_library(lib), - } - } - - pub fn unwrap_executable(self) -> Box { - match self { - Self::Executable(program) => program, - Self::Library(_) => panic!("tried to unwrap a mast library as an executable"), - } - } -} - -impl Emit for MasmArtifact { - fn name(&self) -> Option { - None - } - - fn output_type(&self, _mode: OutputMode) -> OutputType { - OutputType::Masm - } - - fn write_to( - &self, - writer: W, - mode: OutputMode, - session: &Session, - ) -> std::io::Result<()> { - match self { - Self::Executable(ref prog) => prog.write_to(writer, mode, session), - Self::Library(ref lib) => lib.write_to(writer, mode, session), - } - } -} diff --git a/codegen/masm/src/compiler/mast.rs b/codegen/masm/src/compiler/mast.rs deleted file mode 100644 index a59113210..000000000 --- a/codegen/masm/src/compiler/mast.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::sync::Arc; - -use miden_assembly::Library as CompiledLibrary; -use miden_processor::{Digest, MastForest}; -use midenc_hir::Symbol; -use midenc_session::{Emit, OutputMode, OutputType, Session}; - -/// The artifact produced by lowering a [Program] to a Merkelized Abstract Syntax Tree -/// -/// This type is used in compilation pipelines to abstract over the type of output requested. -#[derive(Clone)] -pub enum MastArtifact { - /// A MAST artifact which can be executed by the VM directly - Executable(Arc), - /// A MAST artifact which can be used as a dependency by a [miden_core::Program] - Library(Arc), -} - -impl MastArtifact { - pub fn unwrap_program(self) -> Arc { - match self { - Self::Executable(prog) => prog, - Self::Library(_) => panic!("attempted to unwrap 'mast' library as program"), - } - } - - /// Get the content digest associated with this artifact - pub fn digest(&self) -> Digest { - match self { - Self::Executable(ref prog) => prog.hash(), - Self::Library(ref lib) => *lib.digest(), - } - } - - /// Get the underlying [MastForest] for this artifact - pub fn mast_forest(&self) -> &MastForest { - match self { - Self::Executable(ref prog) => prog.mast_forest(), - Self::Library(ref lib) => lib.mast_forest(), - } - } -} - -impl Emit for MastArtifact { - fn name(&self) -> Option { - None - } - - fn output_type(&self, mode: OutputMode) -> OutputType { - match mode { - OutputMode::Text => OutputType::Mast, - OutputMode::Binary => OutputType::Masl, - } - } - - fn write_to( - &self, - writer: W, - mode: OutputMode, - session: &Session, - ) -> std::io::Result<()> { - match self { - Self::Executable(ref prog) => { - if matches!(mode, OutputMode::Binary) { - log::warn!( - "unable to write 'masl' output type for miden_core::Program: skipping.." - ); - } - prog.write_to(writer, mode, session) - } - Self::Library(ref lib) => lib.write_to(writer, mode, session), - } - } -} diff --git a/codegen/masm/src/compiler/mod.rs b/codegen/masm/src/compiler/mod.rs deleted file mode 100644 index e895a6bfd..000000000 --- a/codegen/masm/src/compiler/mod.rs +++ /dev/null @@ -1,154 +0,0 @@ -mod masm; -mod mast; - -use midenc_hir::{ - self as hir, - pass::{RewritePass, RewriteSet}, -}; -use midenc_session::{diagnostics::Report, Session}; - -pub use self::{masm::MasmArtifact, mast::MastArtifact}; -use crate::{intrinsics, ConvertHirToMasm, Program}; - -pub type CompilerResult = Result; - -/// [MasmCompiler] is a compiler from Miden IR to MASM IR, an intermediate representation -/// of Miden Assembly which is used within the Miden compiler framework for various purposes, -/// and can be emitted directly to textual Miden Assembly. -/// -/// The [MasmCompiler] is designed to compile a [midenc_hir::Program] -/// -/// can be used to take a linked [midenc_hir::Program] and -/// compile it to MASM IR, an intermediate representation of Miden Assembly -/// used within the compiler. -pub struct MasmCompiler<'a> { - session: &'a Session, - analyses: hir::pass::AnalysisManager, -} -impl<'a> MasmCompiler<'a> { - pub fn new(session: &'a Session) -> Self { - Self { - session, - analyses: hir::pass::AnalysisManager::new(), - } - } - - /// Compile an [hir::Program] that has been linked and is ready to be compiled. - pub fn compile(&mut self, mut input: Box) -> CompilerResult { - use midenc_hir::pass::ConversionPass; - - let mut rewrites = default_rewrites([], self.session); - - let modules = input.modules_mut().take(); - for mut module in modules.into_iter() { - rewrites.apply(&mut module, &mut self.analyses, self.session)?; - input.modules_mut().insert(module); - } - - let mut convert_to_masm = ConvertHirToMasm::::default(); - let mut artifact = convert_to_masm.convert(input, &mut self.analyses, self.session)?; - - // Ensure intrinsics modules are linked - artifact.insert(Box::new( - intrinsics::load("intrinsics::mem", &self.session.source_manager) - .expect("undefined intrinsics module"), - )); - artifact.insert(Box::new( - intrinsics::load("intrinsics::i32", &self.session.source_manager) - .expect("undefined intrinsics module"), - )); - artifact.insert(Box::new( - intrinsics::load("intrinsics::i64", &self.session.source_manager) - .expect("undefined intrinsics module"), - )); - - Ok(artifact) - } - - /// Compile a single [hir::Module] as a program. - /// - /// It is assumed that the given module has been validated, and that all necessary - /// rewrites have been applied. If one of these invariants is not upheld, compilation - /// may fail. - pub fn compile_module(&mut self, input: Box) -> CompilerResult> { - assert!(input.entrypoint().is_some(), "cannot compile a program without an entrypoint"); - - let program = - hir::ProgramBuilder::new(&self.session.diagnostics).with_module(input)?.link()?; - - match self.compile(program)? { - MasmArtifact::Executable(program) => Ok(program), - _ => unreachable!("expected compiler to produce an executable, got a library"), - } - } - - /// Compile a set of [hir::Module] as a program. - /// - /// It is assumed that the given modules have been validated, and that all necessary - /// rewrites have been applied. If one of these invariants is not upheld, compilation - /// may fail. - pub fn compile_modules>>( - &mut self, - input: I, - ) -> CompilerResult> { - let mut builder = hir::ProgramBuilder::new(&self.session.diagnostics); - for module in input.into_iter() { - builder.add_module(module)?; - } - - let program = builder.link()?; - - assert!(program.has_entrypoint(), "cannot compile a program without an entrypoint"); - - match self.compile(program)? { - MasmArtifact::Executable(program) => Ok(program), - _ => unreachable!("expected compiler to produce an executable, got a library"), - } - } -} - -pub fn default_rewrites

(registered: P, session: &Session) -> RewriteSet -where - P: IntoIterator>>, -

::IntoIter: ExactSizeIterator, -{ - use midenc_hir::pass::ModuleRewritePassAdapter; - - let registered = registered.into_iter(); - - // If no rewrites were explicitly enabled, and conversion to Miden Assembly is, - // then we must ensure that the basic transformation passes are applied. - // - // Otherwise, assume that the intent was to skip those rewrites and do not add them - let mut rewrites = RewriteSet::default(); - if registered.len() == 0 { - if session.should_codegen() { - let fn_rewrites = default_function_rewrites(session); - for rewrite in fn_rewrites { - rewrites.push(ModuleRewritePassAdapter::new(rewrite)); - } - } - } else { - rewrites.extend(registered); - } - - rewrites -} - -pub fn default_function_rewrites(session: &Session) -> RewriteSet { - use midenc_hir_transform as transforms; - - // If no rewrites were explicitly enabled, and conversion to Miden Assembly is, - // then we must ensure that the basic transformation passes are applied. - // - // Otherwise, assume that the intent was to skip those rewrites and do not add them - let mut rewrites = RewriteSet::default(); - if session.should_codegen() { - rewrites.push(transforms::SplitCriticalEdges); - rewrites.push(transforms::Treeify); - rewrites.push(transforms::InlineBlocks); - rewrites.push(transforms::ApplySpills); - } - - rewrites -} diff --git a/codegen/masm/src/convert.rs b/codegen/masm/src/convert.rs deleted file mode 100644 index c54390a78..000000000 --- a/codegen/masm/src/convert.rs +++ /dev/null @@ -1,193 +0,0 @@ -use miden_assembly::LibraryPath; -use midenc_hir::{ - self as hir, - pass::{AnalysisManager, ConversionPass, ConversionResult}, - ConversionPassRegistration, PassInfo, -}; -use midenc_hir_analysis as analysis; -use midenc_session::Session; - -use crate::{ - codegen::{FunctionEmitter, OperandStack, Scheduler, TypedValue}, - masm, MasmArtifact, -}; - -type ProgramGlobalVariableAnalysis = analysis::GlobalVariableAnalysis; -type ModuleGlobalVariableAnalysis = analysis::GlobalVariableAnalysis; - -/// Convert an HIR program or module to Miden Assembly -/// -/// This pass assumes the following statements are true, and may fail if any are not: -/// -/// * The IR has been validated, or is known to be valid -/// * If converting a single module, it must be self-contained -/// * If converting multiple modules, they must be linked into a [Program], in order to ensure that -/// there are no undefined symbols, and that the placement of global variables in linear memory -/// has been fixed. -/// * There are no critical edges in the control flow graph, or the [SplitCriticalEdges] rewrite has -/// been applied. -/// * The control flow graph is a tree, with the exception of loop header blocks. This means that -/// the only blocks with more than one predecessor are loop headers. See the [Treeify] rewrite for -/// more information. -/// -/// Any further optimizations or rewrites are considered optional. -#[derive(ConversionPassRegistration)] -pub struct ConvertHirToMasm(core::marker::PhantomData); -impl Default for ConvertHirToMasm { - fn default() -> Self { - Self(core::marker::PhantomData) - } -} -impl PassInfo for ConvertHirToMasm { - const DESCRIPTION: &'static str = "Convert an HIR module or program to Miden Assembly\n\nSee \ - the module documentation for ConvertHirToMasm for more \ - details"; - const FLAG: &'static str = "convert-hir-to-masm"; - const SUMMARY: &'static str = "Convert an HIR module or program to Miden Assembly"; -} - -impl ConversionPass for ConvertHirToMasm { - type From = Box; - type To = MasmArtifact; - - fn convert( - &mut self, - mut program: Self::From, - analyses: &mut AnalysisManager, - session: &Session, - ) -> ConversionResult { - // Ensure global variable analysis is computed - let globals = - analyses.get_or_compute::(&program, session)?; - - let mut artifact = if program.has_entrypoint() { - masm::Program::from_hir(&program, &globals) - .map(Box::new) - .map(MasmArtifact::Executable)? - } else { - MasmArtifact::Library(Box::new(masm::Library::from_hir(&program, &globals))) - }; - - // Move link libraries to artifact - let libraries = core::mem::take(program.libraries_mut()); - for lib in libraries.into_values() { - artifact.link_library(lib); - } - - // Remove the set of modules to compile from the program - let modules = program.modules_mut().take(); - - for module in modules.into_iter() { - // Convert the module - let mut convert_to_masm = ConvertHirToMasm::::default(); - let masm_module = convert_to_masm.convert(module, analyses, session)?; - - // Add to the final Miden Assembly program - artifact.insert(masm_module); - } - - Ok(artifact) - } -} - -impl ConversionPass for ConvertHirToMasm { - type From = Box; - type To = Box; - - fn convert( - &mut self, - mut module: Self::From, - analyses: &mut AnalysisManager, - session: &Session, - ) -> ConversionResult { - use miden_assembly::ast::ModuleKind; - use midenc_hir::ProgramAnalysisKey; - - let kind = if module.is_kernel() { - ModuleKind::Kernel - } else { - ModuleKind::Library - }; - let name = LibraryPath::new(&module.name).unwrap_or_else(|err| { - panic!("invalid module name '{}': {}", module.name.as_str(), err) - }); - let mut masm_module = Box::new(masm::Module::new(name, kind)); - - // Compute import information for this module - masm_module.imports = module.imports(); - - // If we don't have a program-wide global variable analysis, compute it using the module - // global table. - if !analyses.is_available::(&ProgramAnalysisKey) { - analyses.get_or_compute::(&module, session)?; - } - - // Removing a function via this cursor will move the cursor to - // the next function in the module. Once the end of the module - // is reached, the cursor will point to the null object, and - // `remove` will return `None`. - while let Some(function) = module.pop_front() { - let mut convert_to_masm = ConvertHirToMasm::<&hir::Function>::default(); - let masm_function = convert_to_masm.convert(&function, analyses, session)?; - masm_module.push_back(Box::new(masm_function)); - } - - Ok(masm_module) - } -} - -impl<'a> ConversionPass for ConvertHirToMasm<&'a hir::Function> { - type From = &'a hir::Function; - type To = masm::Function; - - fn convert( - &mut self, - f: Self::From, - analyses: &mut AnalysisManager, - session: &Session, - ) -> ConversionResult { - use midenc_hir::ProgramAnalysisKey; - - let mut f_prime = masm::Function::new(f.id, f.signature.clone()); - - // Start at the function entry - { - let entry = f.dfg.entry_block(); - - let globals = analyses - .get::(&ProgramAnalysisKey) - .map(|result| result.layout().clone()) - .unwrap_or_else(|| { - let result = analyses.expect::( - &f.id.module, - "expected global variable analysis to be available", - ); - result.layout().clone() - }); - - let domtree = analyses.get_or_compute::(f, session)?; - let loops = analyses.get_or_compute::(f, session)?; - let liveness = analyses.get_or_compute::(f, session)?; - - let mut stack = OperandStack::default(); - for arg in f.dfg.block_args(entry).iter().rev().copied() { - let ty = f.dfg.value_type(arg).clone(); - stack.push(TypedValue { value: arg, ty }); - } - - let scheduler = Scheduler::new(f, &mut f_prime, &domtree, &loops, &liveness); - let schedule = scheduler.build(); - - /* - if f.id.function.as_str().contains("get_inputs") { - dbg!(&schedule); - } - */ - let emitter = - FunctionEmitter::new(f, &mut f_prime, &domtree, &loops, &liveness, &globals); - emitter.emit(schedule, stack); - } - - Ok(f_prime) - } -} diff --git a/codegen/masm/src/data_segments.rs b/codegen/masm/src/data_segments.rs new file mode 100644 index 000000000..672076d24 --- /dev/null +++ b/codegen/masm/src/data_segments.rs @@ -0,0 +1,351 @@ +//! Module for handling data segment merging for Miden VM +//! +//! Miden VM requires data to be word-aligned (16-byte aligned) for hash/unhash instructions. +//! This module merges all data segments into a single segment and pads to 16-byte alignment. + +use std::fmt::Debug; + +use miden_core::utils::ToHex; +use midenc_hir::SmallVec; +use midenc_session::diagnostics::Report; + +/// Word size in bytes for Miden VM alignment requirements +const WORD_SIZE_BYTES: usize = 16; + +/// Represents a data segment with resolved offset +#[derive(Clone)] +pub struct ResolvedDataSegment { + /// The absolute offset in linear memory where this segment starts + pub offset: u32, + /// The initialization data + pub data: Vec, + /// Whether this is readonly data + pub readonly: bool, +} + +impl ResolvedDataSegment { + /// Returns the end offset of this segment + fn end_offset(&self) -> u32 { + self.offset + self.data.len() as u32 + } + + /// Pads the segment data to be aligned to WORD_SIZE_BYTES (16 bytes) + pub fn pad_to_word_alignment(&mut self) { + let remainder = self.data.len() % WORD_SIZE_BYTES; + if remainder != 0 { + let padding_size = WORD_SIZE_BYTES - remainder; + self.data.resize(self.data.len() + padding_size, 0); + } + } +} + +impl Debug for ResolvedDataSegment { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ResolvedDataSegment") + .field("offset", &self.offset) + .field("size", &self.data.len()) + .field("readonly", &self.readonly) + .field("data", &self.data.to_hex()) + .finish() + } +} + +/// Returns an error if any two segments overlap +fn validate_no_overlaps(segments: &[ResolvedDataSegment]) -> Result<(), Report> { + assert!(segments.is_sorted_by_key(|s| s.offset), "Segments must be sorted by offset"); + for window in segments.windows(2) { + let current = &window[0]; + let next = &window[1]; + if current.end_offset() > next.offset { + let error_msg = format!( + "Data segments overlap: segment at offset {:#x} (size {}) overlaps with segment \ + at offset {:#x}", + current.offset, + current.data.len(), + next.offset + ); + return Err(Report::msg(error_msg)); + } + } + Ok(()) +} + +/// Merges segment metadata (names and readonly status) +/// Returns a whether all segments are readonly +fn merge_metadata(segments: &[ResolvedDataSegment]) -> bool { + let mut all_readonly = true; + + for segment in segments.iter() { + all_readonly = all_readonly && segment.readonly; + } + + all_readonly +} + +/// Copies all segment data into the merged buffer at their respective offsets +fn copy_segments_to_buffer(segments: &[ResolvedDataSegment], buffer: &mut [u8], base_offset: u32) { + for segment in segments { + let relative_offset = (segment.offset - base_offset) as usize; + let end = relative_offset + segment.data.len(); + buffer[relative_offset..end].copy_from_slice(&segment.data); + } +} + +/// Merge all data segments into a single segment and pad to 16-byte alignment +/// +/// Requirements: +/// - Segments cannot have their offsets moved +/// - Segments must not overlap (returns error if they do) +/// - All segments are merged into one +/// - The resulting data is padded with zeros to be divisible by 16 +/// +/// Returns `None` if the input segments are empty, otherwise returns the merged segment. +pub fn merge_data_segments( + mut segments: SmallVec<[ResolvedDataSegment; 2]>, +) -> Result, Report> { + if segments.is_empty() { + return Ok(None); + } + + // Early return for single segment - just pad it + if segments.len() == 1 { + let mut segment = segments.pop().unwrap(); + segment.pad_to_word_alignment(); + return Ok(Some(segment)); + } + + segments.sort_by_key(|s| s.offset); + + validate_no_overlaps(&segments)?; + + let base_offset = segments[0].offset; + let last_segment = segments.last().unwrap(); + let initial_size = (last_segment.end_offset() - base_offset) as usize; + + let mut merged_data = vec![0u8; initial_size]; + + copy_segments_to_buffer(&segments, &mut merged_data, base_offset); + + let all_readonly = merge_metadata(&segments); + + let mut merged_segment = ResolvedDataSegment { + offset: base_offset, + data: merged_data, + readonly: all_readonly, + }; + + merged_segment.pad_to_word_alignment(); + + Ok(Some(merged_segment)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_empty_segments() { + let segments = SmallVec::new(); + let merged = merge_data_segments(segments).unwrap(); + assert!(merged.is_none()); + } + + #[test] + fn test_single_segment_with_padding() { + let mut segments = SmallVec::new(); + segments.push(ResolvedDataSegment { + offset: 10, + data: vec![1, 2, 3], + readonly: true, + }); + + let merged = merge_data_segments(segments).unwrap().unwrap(); + + assert_eq!(merged.offset, 10); + // 3 bytes padded to 16 + assert_eq!(merged.data.len(), 16); + assert_eq!(&merged.data[0..3], &[1, 2, 3]); + assert_eq!(&merged.data[3..], &[0; 13]); + } + + #[test] + fn test_padding_bytes_are_zeros() { + // Test various data sizes to verify padding is always zeros + let test_cases = vec![ + (vec![0x42], 1usize, 15), // 1 byte data, 15 bytes padding + (vec![0xaa, 0xbb], 2, 14), // 2 bytes data, 14 bytes padding + (vec![0x11; 5], 5, 11), // 5 bytes data, 11 bytes padding + (vec![0xff; 13], 13, 3), // 13 bytes data, 3 bytes padding + (vec![0x77; 15], 15, 1), // 15 bytes data, 1 byte padding + (vec![0x88; 16], 16, 0), // 16 bytes data, no padding + (vec![0x99; 17], 17, 15), // 17 bytes data, 15 bytes padding + (vec![0xcc; 31], 31, 1), // 31 bytes data, 1 byte padding + ]; + + for (data, data_len, expected_padding) in test_cases { + let mut segments = SmallVec::new(); + segments.push(ResolvedDataSegment { + offset: 1000, + data: data.clone(), + readonly: false, + }); + + let merged = merge_data_segments(segments).unwrap().unwrap(); + + let result_data = &merged.data; + let expected_total_len = data_len.next_multiple_of(16); // Round up to nearest 16 + assert_eq!(result_data.len(), expected_total_len); + + // Verify original data is preserved + assert_eq!(&result_data[0..data_len], &data[..]); + + // Verify all padding bytes are zeros + if expected_padding > 0 { + let padding_slice = &result_data[data_len..]; + assert_eq!(padding_slice.len(), expected_padding); + assert!( + padding_slice.iter().all(|&b| b == 0), + "Padding bytes should all be zero for {data_len} bytes of data" + ); + } + } + } + + #[test] + fn test_data_accessible_at_original_offsets() { + // Test that after merging, data can be accessed at original offsets + let mut segments = SmallVec::new(); + + // Segment 1 at offset 100 + segments.push(ResolvedDataSegment { + offset: 100, + data: vec![0xaa, 0xbb, 0xcc, 0xdd], + readonly: false, + }); + + // Segment 2 at offset 110 (gap of 6 bytes) + segments.push(ResolvedDataSegment { + offset: 110, + data: vec![0x11, 0x22], + readonly: false, + }); + + // Segment 3 at offset 120 + segments.push(ResolvedDataSegment { + offset: 120, + data: vec![0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa], + readonly: false, + }); + + let merged = merge_data_segments(segments).unwrap().unwrap(); + + assert_eq!(merged.offset, 100); + + // Verify each segment's data is at the correct position + let data = &merged.data; + + // Data from segment 1 (offset 100-103) + assert_eq!(&data[0..4], &[0xaa, 0xbb, 0xcc, 0xdd]); + + // Gap (offset 104-109) should be zeros + assert_eq!(&data[4..10], &[0, 0, 0, 0, 0, 0]); + + // Data from segment 2 (offset 110-111) + assert_eq!(&data[10..12], &[0x11, 0x22]); + + // Gap (offset 112-119) should be zeros + assert_eq!(&data[12..20], &[0, 0, 0, 0, 0, 0, 0, 0]); + + // Data from segment 3 (offset 120-125) + assert_eq!(&data[20..26], &[0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa]); + + // Total size should be padded to multiple of 16 + // Range is 100-126 (26 bytes), padded to 32 + assert_eq!(data.len(), 32); + + // Padding should be zeros + assert_eq!(&data[26..], &[0; 6]); + } + + #[test] + fn test_merge_segments_with_gap() { + let mut segments = SmallVec::new(); + segments.push(ResolvedDataSegment { + offset: 0, + data: vec![1, 2, 3, 4], + readonly: true, + }); + segments.push(ResolvedDataSegment { + offset: 8, + data: vec![5, 6, 7, 8], + readonly: true, + }); + + let merged = merge_data_segments(segments).unwrap().unwrap(); + + assert_eq!(merged.offset, 0); + // 4 bytes + 4 gap + 4 bytes = 12, padded to 16 + assert_eq!(merged.data.len(), 16); + assert_eq!(merged.data, vec![1, 2, 3, 4, 0, 0, 0, 0, 5, 6, 7, 8, 0, 0, 0, 0]); + } + + #[test] + fn test_overlapping_segments_error() { + let mut segments = SmallVec::new(); + segments.push(ResolvedDataSegment { + offset: 0, + data: vec![1, 2, 3, 4, 5, 6], + readonly: true, + }); + segments.push(ResolvedDataSegment { + offset: 4, + data: vec![7, 8], + readonly: false, + }); + + // This should return an error because segments overlap + let result = merge_data_segments(segments); + assert!(result.is_err()); + let err = result.unwrap_err(); + let err_msg = format!("{err}"); + assert!(err_msg.contains("Data segments overlap")); + } + + #[test] + fn test_p2id_case() { + // Simulate the p2id case with .rodata and .data segments + let mut segments = SmallVec::new(); + segments.push(ResolvedDataSegment { + offset: 1048576, // 0x100000 + data: vec![0; 44], // Size of the .rodata string + readonly: true, + }); + segments.push(ResolvedDataSegment { + offset: 1048620, // 0x10002C + data: vec![0; 76], // Size of the .data segment + readonly: false, + }); + + let merged = merge_data_segments(segments).unwrap().unwrap(); + + assert_eq!(merged.offset, 1048576); + // .rodata (44) + gap (0) + .data (76) = 120, padded to 128 + assert_eq!(merged.data.len(), 128); + assert!(!merged.readonly); + } + + #[test] + fn test_already_aligned_size() { + let mut segments = SmallVec::new(); + segments.push(ResolvedDataSegment { + offset: 100, + data: vec![1; 32], // Already divisible by 16 + readonly: true, + }); + + let merged = merge_data_segments(segments).unwrap().unwrap(); + + assert_eq!(merged.offset, 100); + assert_eq!(merged.data.len(), 32); // No padding needed + } +} diff --git a/codegen/masm/src/emit/binary.rs b/codegen/masm/src/emit/binary.rs new file mode 100644 index 000000000..e314be957 --- /dev/null +++ b/codegen/masm/src/emit/binary.rs @@ -0,0 +1,1552 @@ +use core::assert_matches::assert_matches; + +use miden_core::Felt; +use midenc_hir::{Immediate, Overflow, SourceSpan, Type}; + +use super::{masm, OpEmitter}; + +impl OpEmitter<'_> { + pub fn eq(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected eq operands to be the same type"); + match &ty { + Type::I128 | Type::U128 => { + self.eq_i128(span); + } + Type::I64 | Type::U64 => { + self.eq_int64(span); + } + Type::Felt + | Type::Ptr(_) + | Type::U32 + | Type::I32 + | Type::U16 + | Type::I16 + | Type::I8 + | Type::U8 + | Type::I1 => { + self.emit(masm::Instruction::Eq, span); + } + ty => unimplemented!("eq is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + #[allow(unused)] + pub fn eq_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected eq operands to be the same type"); + match &ty { + Type::I128 | Type::U128 => { + self.push_immediate(imm, span); + self.eq_i128(span); + } + Type::I64 | Type::U64 => { + self.push_immediate(imm, span); + self.eq_int64(span); + } + Type::Felt | Type::Ptr(_) | Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit(masm::Instruction::EqImm(imm.as_felt().unwrap().into()), span); + } + Type::I32 | Type::I16 | Type::I8 => { + self.emit( + masm::Instruction::EqImm(Felt::new(imm.as_i32().unwrap() as u32 as u64).into()), + span, + ); + } + ty => unimplemented!("eq is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + pub fn neq(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected neq operands to be the same type"); + match &ty { + Type::I128 | Type::U128 => { + self.neq_i128(span); + } + Type::I64 | Type::U64 => self.neq_int64(span), + Type::Felt + | Type::Ptr(_) + | Type::U32 + | Type::I32 + | Type::U16 + | Type::I16 + | Type::I8 + | Type::U8 + | Type::I1 => { + self.emit(masm::Instruction::Neq, span); + } + ty => unimplemented!("neq is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + #[allow(unused)] + pub fn neq_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected neq operands to be the same type"); + match &ty { + Type::I128 | Type::U128 => { + self.push_immediate(imm, span); + self.neq_i128(span); + } + Type::I64 | Type::U64 => { + self.push_immediate(imm, span); + self.neq_int64(span) + } + Type::Felt | Type::Ptr(_) | Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit(masm::Instruction::NeqImm(imm.as_felt().unwrap().into()), span); + } + Type::I32 | Type::I16 | Type::I8 => { + self.emit( + masm::Instruction::NeqImm( + Felt::new(imm.as_i32().unwrap() as u32 as u64).into(), + ), + span, + ); + } + ty => unimplemented!("neq is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + pub fn gt(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected gt operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Gt, span); + } + Type::U64 => { + self.gt_u64(span); + } + Type::I64 => { + self.gt_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit(masm::Instruction::U32Gt, span); + } + Type::I32 => self.raw_exec("intrinsics::i32::is_gt", span), + ty => unimplemented!("gt is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + #[allow(unused)] + pub fn gt_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected gt operands to be the same type"); + match &ty { + Type::Felt => { + self.emit_all( + [masm::Instruction::PushFelt(imm.as_felt().unwrap()), masm::Instruction::Gt], + span, + ); + } + Type::U64 => { + self.push_u64(imm.as_u64().unwrap(), span); + self.gt_u64(span); + } + Type::I64 => { + self.push_i64(imm.as_i64().unwrap(), span); + self.gt_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit_all( + [masm::Instruction::PushU32(imm.as_u32().unwrap()), masm::Instruction::U32Gt], + span, + ); + } + Type::I32 => { + self.emit(masm::Instruction::PushU32(imm.as_i32().unwrap() as u32), span); + self.raw_exec("intrinsics::i32::is_gt", span); + } + ty => unimplemented!("gt is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + pub fn gte(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected gte operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Gte, span); + } + Type::U64 => { + self.gte_u64(span); + } + Type::I64 => { + self.gte_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit(masm::Instruction::U32Gte, span); + } + Type::I32 => self.raw_exec("intrinsics::i32::is_gte", span), + ty => unimplemented!("gte is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + #[allow(unused)] + pub fn gte_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected gte operands to be the same type"); + match &ty { + Type::Felt => { + self.emit_all( + [masm::Instruction::PushFelt(imm.as_felt().unwrap()), masm::Instruction::Gte], + span, + ); + } + Type::U64 => { + self.push_u64(imm.as_u64().unwrap(), span); + self.gte_u64(span); + } + Type::I64 => { + self.push_i64(imm.as_i64().unwrap(), span); + self.gte_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit_all( + [masm::Instruction::PushU32(imm.as_u32().unwrap()), masm::Instruction::U32Gte], + span, + ); + } + Type::I32 => { + self.emit(masm::Instruction::PushU32(imm.as_i32().unwrap() as u32), span); + self.raw_exec("intrinsics::i32::is_gte", span); + } + ty => unimplemented!("gte is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + pub fn lt(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected lt operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Lt, span); + } + Type::U64 => { + self.lt_u64(span); + } + Type::I64 => { + self.lt_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit(masm::Instruction::U32Lt, span); + } + Type::I32 => self.raw_exec("intrinsics::i32::is_lt", span), + ty => unimplemented!("lt is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + #[allow(unused)] + pub fn lt_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected lt operands to be the same type"); + match &ty { + Type::Felt => { + self.emit_all( + [masm::Instruction::PushFelt(imm.as_felt().unwrap()), masm::Instruction::Lt], + span, + ); + } + Type::U64 => { + self.push_u64(imm.as_u64().unwrap(), span); + self.lt_u64(span); + } + Type::I64 => { + self.push_i64(imm.as_i64().unwrap(), span); + self.lt_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit_all( + [masm::Instruction::PushU32(imm.as_u32().unwrap()), masm::Instruction::U32Lt], + span, + ); + } + Type::I32 => { + self.emit(masm::Instruction::PushU32(imm.as_i32().unwrap() as u32), span); + self.raw_exec("intrinsics::i32::is_lt", span); + } + ty => unimplemented!("lt is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + pub fn lte(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected lte operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Lte, span); + } + Type::U64 => { + self.lte_u64(span); + } + Type::I64 => { + self.lte_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit(masm::Instruction::U32Lte, span); + } + Type::I32 => self.raw_exec("intrinsics::i32::is_lte", span), + ty => unimplemented!("lte is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + #[allow(unused)] + pub fn lte_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected lte operands to be the same type"); + match &ty { + Type::Felt => { + self.emit_all( + [masm::Instruction::PushFelt(imm.as_felt().unwrap()), masm::Instruction::Lte], + span, + ); + } + Type::U64 => { + self.push_u64(imm.as_u64().unwrap(), span); + self.lte_u64(span); + } + Type::I64 => { + self.push_i64(imm.as_i64().unwrap(), span); + self.lte_i64(span); + } + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.emit_all( + [masm::Instruction::PushU32(imm.as_u32().unwrap()), masm::Instruction::U32Lte], + span, + ); + } + Type::I32 => { + self.emit(masm::Instruction::PushU32(imm.as_i32().unwrap() as u32), span); + self.raw_exec("intrinsics::i32::is_lte", span); + } + ty => unimplemented!("lte is not yet implemented for {ty}"), + } + self.push(Type::I1); + } + + pub fn add(&mut self, overflow: Overflow, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected add operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Add, span); + } + Type::I128 => { + self.add_i128(overflow, span); + } + Type::U64 => { + self.add_u64(overflow, span); + } + Type::I64 => { + self.add_i64(overflow, span); + } + Type::U32 => { + self.add_u32(overflow, span); + } + Type::I32 => { + self.add_i32(overflow, span); + } + ty @ (Type::U16 | Type::U8 | Type::I1) => { + self.add_uint(ty.size_in_bits() as u32, overflow, span); + } + ty => unimplemented!("add is not yet implemented for {ty}"), + } + if overflow.is_overflowing() { + self.push(Type::I1); + } + self.push(ty); + } + + #[allow(unused)] + pub fn add_imm(&mut self, imm: Immediate, overflow: Overflow, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected add operands to be the same type"); + match &ty { + Type::Felt if imm == 1 => self.emit(masm::Instruction::Incr, span), + Type::Felt => { + self.emit(masm::Instruction::AddImm(imm.as_felt().unwrap().into()), span); + } + Type::U64 => { + self.push_immediate(imm, span); + self.add_u64(overflow, span); + } + Type::I64 => { + self.add_imm_i64(imm.as_i64().unwrap(), overflow, span); + } + Type::U32 => { + self.add_imm_u32(imm.as_u32().unwrap(), overflow, span); + } + Type::I32 => { + self.add_imm_i32(imm.as_i32().unwrap(), overflow, span); + } + ty @ (Type::U16 | Type::U8 | Type::I1) => { + self.add_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, overflow, span); + } + ty => unimplemented!("add is not yet implemented for {ty}"), + } + if overflow.is_overflowing() { + self.push(Type::I1); + } + self.push(ty); + } + + pub fn sub(&mut self, overflow: Overflow, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected sub operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Sub, span); + } + Type::I128 => { + self.sub_i128(overflow, span); + } + Type::U64 => { + self.sub_u64(overflow, span); + } + Type::I64 => { + self.sub_i64(overflow, span); + } + Type::U32 => { + self.sub_u32(overflow, span); + } + Type::I32 => { + self.sub_i32(overflow, span); + } + ty @ (Type::U16 | Type::U8 | Type::I1) => { + self.sub_uint(ty.size_in_bits() as u32, overflow, span); + } + ty => unimplemented!("sub is not yet implemented for {ty}"), + } + if overflow.is_overflowing() { + self.push(Type::I1); + } + self.push(ty); + } + + #[allow(unused)] + pub fn sub_imm(&mut self, imm: Immediate, overflow: Overflow, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected sub operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::SubImm(imm.as_felt().unwrap().into()), span); + } + Type::U64 => { + self.push_immediate(imm, span); + self.sub_u64(overflow, span); + } + Type::I64 => { + self.sub_imm_i64(imm.as_i64().unwrap(), overflow, span); + } + Type::U32 => { + self.sub_imm_u32(imm.as_u32().unwrap(), overflow, span); + } + Type::I32 => { + self.sub_imm_i32(imm.as_i32().unwrap(), overflow, span); + } + ty @ (Type::U16 | Type::U8 | Type::I1) => { + self.sub_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, overflow, span); + } + ty => unimplemented!("sub is not yet implemented for {ty}"), + } + if overflow.is_overflowing() { + self.push(Type::I1); + } + self.push(ty); + } + + pub fn mul(&mut self, overflow: Overflow, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected mul operands to be the same type"); + match &ty { + Type::Felt => { + assert_matches!( + overflow, + Overflow::Unchecked | Overflow::Wrapping, + "only unchecked or wrapping semantics are supported for felt" + ); + self.emit(masm::Instruction::Mul, span); + } + Type::I128 | Type::U128 => self.mul_i128(span), + Type::U64 => self.mul_u64(overflow, span), + Type::I64 => self.mul_i64(overflow, span), + Type::U32 => self.mul_u32(overflow, span), + Type::I32 => self.mul_i32(overflow, span), + ty @ (Type::U16 | Type::U8) => { + self.mul_uint(ty.size_in_bits() as u32, overflow, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: mul expects integer operands, got {ty}") + } + ty => unimplemented!("mul for {ty} is not supported"), + } + if overflow.is_overflowing() { + self.push(Type::I1); + } + self.push(ty); + } + + #[allow(unused)] + pub fn mul_imm(&mut self, imm: Immediate, overflow: Overflow, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected mul operands to be the same type"); + match &ty { + Type::Felt => { + assert_matches!( + overflow, + Overflow::Unchecked | Overflow::Wrapping, + "only unchecked or wrapping semantics are supported for felt" + ); + self.emit(masm::Instruction::MulImm(imm.as_felt().unwrap().into()), span); + } + Type::U64 => { + self.push_immediate(imm, span); + self.mul_u64(overflow, span); + } + Type::I64 => self.mul_imm_i64(imm.as_i64().unwrap(), overflow, span), + Type::U32 => self.mul_imm_u32(imm.as_u32().unwrap(), overflow, span), + Type::I32 => self.mul_imm_i32(imm.as_i32().unwrap(), overflow, span), + ty @ (Type::U16 | Type::U8) => { + self.mul_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, overflow, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: mul expects integer operands, got {ty}") + } + ty => unimplemented!("mul for {ty} is not supported"), + } + if overflow.is_overflowing() { + self.push(Type::I1); + } + self.push(ty); + } + + pub fn checked_div(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected div operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Div, span); + } + Type::U64 => self.checked_div_u64(span), + Type::I64 => self.checked_div_i64(span), + Type::U32 => self.checked_div_u32(span), + Type::I32 => self.checked_div_i32(span), + ty @ (Type::U16 | Type::U8) => { + self.checked_div_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: div expects integer operands, got {ty}") + } + ty => unimplemented!("div for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn checked_div_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected div operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Div, span); + } + Type::U64 => { + assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); + self.push_immediate(imm, span); + self.checked_div_u64(span); + } + Type::I64 => self.checked_div_imm_i64(imm.as_i64().unwrap(), span), + Type::U32 => self.checked_div_imm_u32(imm.as_u32().unwrap(), span), + Type::I32 => self.checked_div_imm_i32(imm.as_i32().unwrap(), span), + ty @ (Type::U16 | Type::U8) => { + self.checked_div_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: div expects integer operands, got {ty}") + } + ty => unimplemented!("div for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn unchecked_div(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected div operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Div, span); + } + Type::U64 => self.unchecked_div_u64(span), + Type::I64 => self.checked_div_i64(span), + Type::U32 | Type::U16 | Type::U8 => self.unchecked_div_u32(span), + Type::I32 => self.checked_div_i32(span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: div expects integer operands, got {ty}") + } + ty => unimplemented!("div for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn unchecked_div_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected div operands to be the same type"); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Div, span); + } + Type::U64 => { + assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); + self.push_immediate(imm, span); + self.unchecked_div_u64(span); + } + Type::I64 => self.checked_div_imm_i64(imm.as_i64().unwrap(), span), + Type::U32 => self.unchecked_div_imm_u32(imm.as_u32().unwrap(), span), + Type::I32 => self.checked_div_imm_i32(imm.as_i32().unwrap(), span), + ty @ (Type::U16 | Type::U8) => { + self.unchecked_div_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: div expects integer operands, got {ty}") + } + ty => unimplemented!("div for {ty} is not supported"), + } + self.push(ty); + } + + pub fn checked_mod(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected mod operands to be the same type"); + match &ty { + Type::U64 => self.checked_mod_u64(span), + Type::U32 => self.checked_mod_u32(span), + ty @ (Type::U16 | Type::U8) => { + self.checked_mod_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: mod expects integer operands, got {ty}") + } + ty => unimplemented!("mod for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn checked_mod_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected mod operands to be the same type"); + match &ty { + Type::U64 => { + assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); + self.push_immediate(imm, span); + self.checked_mod_u64(span); + } + Type::U32 => self.checked_mod_imm_u32(imm.as_u32().unwrap(), span), + ty @ (Type::U16 | Type::U8) => { + self.checked_mod_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: mod expects integer operands, got {ty}") + } + ty => unimplemented!("mod for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn unchecked_mod(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected mod operands to be the same type"); + match &ty { + Type::U64 => self.unchecked_mod_u64(span), + Type::U32 => self.unchecked_mod_u32(span), + ty @ (Type::U16 | Type::U8) => { + self.unchecked_mod_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: mod expects integer operands, got {ty}") + } + ty => unimplemented!("mod for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn unchecked_mod_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected mod operands to be the same type"); + match &ty { + Type::U64 => { + assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); + self.push_immediate(imm, span); + self.unchecked_mod_u64(span); + } + Type::U32 => self.unchecked_mod_imm_u32(imm.as_u32().unwrap(), span), + ty @ (Type::U16 | Type::U8) => { + self.unchecked_mod_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: mod expects integer operands, got {ty}") + } + ty => unimplemented!("mod for {ty} is not supported"), + } + self.push(ty); + } + + pub fn checked_divmod(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected divmod operands to be the same type"); + match &ty { + Type::U64 => self.checked_divmod_u64(span), + Type::U32 => self.checked_divmod_u32(span), + ty @ (Type::U16 | Type::U8) => { + self.checked_divmod_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: divmod expects integer operands, got {ty}") + } + ty => unimplemented!("divmod for {ty} is not supported"), + } + self.push(ty.clone()); + self.push(ty); + } + + #[allow(unused)] + pub fn checked_divmod_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected divmod operands to be the same type"); + match &ty { + Type::U64 => { + assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); + self.push_immediate(imm, span); + self.checked_divmod_u64(span); + } + Type::U32 => self.checked_divmod_imm_u32(imm.as_u32().unwrap(), span), + ty @ (Type::U16 | Type::U8) => { + self.checked_divmod_imm_uint(imm.as_u32().unwrap(), ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: divmod expects integer operands, got {ty}") + } + ty => unimplemented!("divmod for {ty} is not supported"), + } + self.push(ty.clone()); + self.push(ty); + } + + #[allow(unused)] + pub fn unchecked_divmod(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected divmod operands to be the same type"); + match &ty { + Type::U64 => self.unchecked_divmod_u64(span), + Type::U32 => self.unchecked_divmod_u32(span), + ty @ (Type::U16 | Type::U8) => { + self.unchecked_divmod_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: divmod expects integer operands, got {ty}") + } + ty => unimplemented!("divmod for {ty} is not supported"), + } + self.push(ty.clone()); + self.push(ty); + } + + #[allow(unused)] + pub fn unchecked_divmod_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected divmod operands to be the same type"); + match &ty { + Type::U64 => { + assert_ne!(imm.as_u64().unwrap(), 0, "invalid division by zero"); + self.push_immediate(imm, span); + self.unchecked_divmod_u64(span); + } + Type::U32 => self.unchecked_divmod_imm_u32(imm.as_u32().unwrap(), span), + ty @ (Type::U16 | Type::U8) => { + self.unchecked_divmod_imm_uint( + imm.as_u32().unwrap(), + ty.size_in_bits() as u32, + span, + ); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: divmod expects integer operands, got {ty}") + } + ty => unimplemented!("divmod for {ty} is not supported"), + } + self.push(ty.clone()); + self.push(ty); + } + + pub fn exp(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected exp operands to be the same type"); + match &ty { + Type::U64 => todo!("exponentiation by squaring"), + Type::Felt => { + self.emit(masm::Instruction::Exp, span); + } + Type::U32 => { + self.emit_all([masm::Instruction::Exp, masm::Instruction::U32Assert], span); + } + Type::I32 => { + self.raw_exec("intrinsics::i32::ipow", span); + } + ty @ (Type::U16 | Type::U8) => { + self.emit_all([masm::Instruction::Exp, masm::Instruction::U32Assert], span); + self.int32_to_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: exp expects integer operands, got {ty}") + } + ty => unimplemented!("mod for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn exp_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected exp operands to be the same type"); + let exp: u8 = + imm.as_u64().unwrap().try_into().expect("invalid exponent: must be value < 64"); + match &ty { + Type::U64 => todo!("exponentiation by squaring"), + Type::Felt => { + self.emit(masm::Instruction::ExpImm(Felt::new(exp as u64).into()), span); + } + Type::U32 => { + self.emit_all( + [ + masm::Instruction::ExpImm(Felt::new(exp as u64).into()), + masm::Instruction::U32Assert, + ], + span, + ); + } + Type::I32 => { + self.emit(masm::Instruction::PushU8(exp), span); + self.raw_exec("intrinsics::i32::ipow", span); + } + ty @ (Type::U16 | Type::U8) => { + self.emit_all( + [ + masm::Instruction::ExpImm(Felt::new(exp as u64).into()), + masm::Instruction::U32Assert, + ], + span, + ); + self.int32_to_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: exp expects integer operands, got {ty}") + } + ty => unimplemented!("mod for {ty} is not supported"), + } + self.push(ty); + } + + pub fn and(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected and operands to be the same type"); + assert_eq!(ty, Type::I1, "expected and operands to be of boolean type"); + self.emit(masm::Instruction::And, span); + self.push(ty); + } + + #[allow(unused)] + pub fn and_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected and operands to be the same type"); + assert_eq!(ty, Type::I1, "expected and operands to be of boolean type"); + self.emit_all( + [masm::Instruction::PushU8(imm.as_bool().unwrap() as u8), masm::Instruction::And], + span, + ); + self.push(ty); + } + + pub fn or(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected or operands to be the same type"); + assert_eq!(ty, Type::I1, "expected or operands to be of boolean type"); + self.emit(masm::Instruction::Or, span); + self.push(ty); + } + + #[allow(unused)] + pub fn or_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected or operands to be the same type"); + assert_eq!(ty, Type::I1, "expected or operands to be of boolean type"); + self.emit_all( + [masm::Instruction::PushU8(imm.as_bool().unwrap() as u8), masm::Instruction::Or], + span, + ); + self.push(ty); + } + + pub fn xor(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected xor operands to be the same type"); + assert_eq!(ty, Type::I1, "expected xor operands to be of boolean type"); + self.emit(masm::Instruction::Xor, span); + self.push(ty); + } + + #[allow(unused)] + pub fn xor_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected xor operands to be the same type"); + assert_eq!(ty, Type::I1, "expected xor operands to be of boolean type"); + self.emit_all( + [masm::Instruction::PushU8(imm.as_bool().unwrap() as u8), masm::Instruction::Xor], + span, + ); + self.push(ty); + } + + pub fn band(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected band operands to be the same type"); + match &ty { + Type::U128 | Type::I128 => { + // AND the high bits + // + // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] + self.emit_all( + [ + // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] + masm::Instruction::MovUp5, + masm::Instruction::MovUp5, + ], + span, + ); + self.band_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] + // AND the low bits + self.emit_all( + [ + // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] + masm::Instruction::MovDn5, + masm::Instruction::MovDn5, + ], + span, + ); + self.band_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] + self.emit_all( + [ + // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] + masm::Instruction::MovUp3, + masm::Instruction::MovUp3, + ], + span, + ); + } + Type::U64 | Type::I64 => self.band_int64(span), + Type::U32 | Type::I32 | Type::U16 | Type::I16 | Type::U8 | Type::I8 => { + self.band_u32(span) + } + Type::I1 => self.emit(masm::Instruction::And, span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: band expects integer operands, got {ty}") + } + ty => unimplemented!("band for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn band_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected band operands to be the same type"); + match &ty { + Type::U128 | Type::I128 => { + self.push_immediate(imm, span); + // AND the high bits + // + // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] + self.emit_all( + [ + // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] + masm::Instruction::MovUp5, + masm::Instruction::MovUp5, + ], + span, + ); + self.band_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] + // AND the low bits + self.emit_all( + [ + // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] + masm::Instruction::MovDn5, + masm::Instruction::MovDn5, + ], + span, + ); + self.band_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] + self.emit_all( + [ + // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] + masm::Instruction::MovUp3, + masm::Instruction::MovUp3, + ], + span, + ); + } + Type::U64 | Type::I64 => { + self.push_immediate(imm, span); + self.band_int64(span); + } + Type::U32 | Type::U16 | Type::U8 => self.band_imm_u32(imm.as_u32().unwrap(), span), + Type::I32 | Type::I16 | Type::I8 => { + self.band_imm_u32(imm.as_i64().unwrap() as u64 as u32, span) + } + Type::I1 => self.emit_all( + [masm::Instruction::PushU8(imm.as_bool().unwrap() as u8), masm::Instruction::And], + span, + ), + ty if !ty.is_integer() => { + panic!("invalid binary operand: band expects integer operands, got {ty}") + } + ty => unimplemented!("band for {ty} is not supported"), + } + self.push(ty); + } + + pub fn bor(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected bor operands to be the same type"); + match &ty { + Type::U128 | Type::I128 => { + // OR the high bits + // + // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] + self.emit_all( + [ + // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] + masm::Instruction::MovUp5, + masm::Instruction::MovUp5, + ], + span, + ); + self.bor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] + // OR the low bits + self.emit_all( + [ + // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] + masm::Instruction::MovDn5, + masm::Instruction::MovDn5, + ], + span, + ); + self.bor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] + self.emit_all( + [ + // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] + masm::Instruction::MovUp3, + masm::Instruction::MovUp3, + ], + span, + ); + } + Type::U64 | Type::I64 => self.bor_int64(span), + Type::U32 | Type::I32 | Type::U16 | Type::I16 | Type::U8 | Type::I8 => { + self.bor_u32(span) + } + Type::I1 => self.emit(masm::Instruction::Or, span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: bor expects integer operands, got {ty}") + } + ty => unimplemented!("bor for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn bor_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected bor operands to be the same type"); + match &ty { + Type::U128 | Type::I128 => { + self.push_immediate(imm, span); + // OR the high bits + // + // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] + self.emit_all( + [ + // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] + masm::Instruction::MovUp5, + masm::Instruction::MovUp5, + ], + span, + ); + self.bor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] + // OR the low bits + self.emit_all( + [ + // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] + masm::Instruction::MovDn5, + masm::Instruction::MovDn5, + ], + span, + ); + self.bor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] + self.emit_all( + [ + // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] + masm::Instruction::MovUp3, + masm::Instruction::MovUp3, + ], + span, + ); + } + Type::U64 | Type::I64 => { + self.push_immediate(imm, span); + self.bor_int64(span); + } + Type::U32 | Type::U16 | Type::U8 => self.bor_imm_u32(imm.as_u32().unwrap(), span), + Type::I32 | Type::I16 | Type::I8 => { + self.bor_imm_u32(imm.as_i64().unwrap() as u64 as u32, span) + } + Type::I1 => self.emit_all( + [masm::Instruction::PushU8(imm.as_bool().unwrap().into()), masm::Instruction::And], + span, + ), + ty if !ty.is_integer() => { + panic!("invalid binary operand: bor expects integer operands, got {ty}") + } + ty => unimplemented!("bor for {ty} is not supported"), + } + self.push(ty); + } + + pub fn bxor(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected bxor operands to be the same type"); + match &ty { + Type::U128 | Type::I128 => { + // XOR the high bits + // + // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] + self.emit_all( + [ + // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] + masm::Instruction::MovUp5, + masm::Instruction::MovUp5, + ], + span, + ); + self.bxor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] + // XOR the low bits + self.emit_all( + [ + // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] + masm::Instruction::MovDn5, + masm::Instruction::MovDn5, + ], + span, + ); + self.bxor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] + self.emit_all( + [ + // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] + masm::Instruction::MovUp3, + masm::Instruction::MovUp3, + ], + span, + ); + } + Type::U64 | Type::I64 => self.bxor_int64(span), + Type::U32 | Type::I32 => self.bxor_u32(span), + ty @ (Type::U16 | Type::I16 | Type::U8 | Type::I8) => { + self.bxor_u32(span); + self.trunc_int32(ty.size_in_bits() as u32, span); + } + Type::I1 => self.emit(masm::Instruction::Xor, span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: bxor expects integer operands, got {ty}") + } + ty => unimplemented!("bxor for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn bxor_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected bxor operands to be the same type"); + match &ty { + Type::U128 | Type::I128 => { + self.push_immediate(imm, span); + // XOR the high bits + // + // [b_hi_hi, b_hi_lo, b_lo_hi, b_lo_lo, a_hi_hi, ..] + self.emit_all( + [ + // [a_hi_hi, a_hi_lo, b_hi_hi, b_hi_lo, ..] + masm::Instruction::MovUp5, + masm::Instruction::MovUp5, + ], + span, + ); + self.bxor_int64(span); // [band_hi_hi, band_hi_lo, b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo] + // XOR the low bits + self.emit_all( + [ + // [b_lo_hi, b_lo_lo, a_lo_hi, a_lo_lo, band_hi_hi, band_hi_lo] + masm::Instruction::MovDn5, + masm::Instruction::MovDn5, + ], + span, + ); + self.bxor_int64(span); // [band_lo_hi, band_lo_lo, band_hi_hi, band_hi_lo] + self.emit_all( + [ + // [band_hi_hi, band_hi_lo, band_lo_hi, band_lo_lo] + masm::Instruction::MovUp3, + masm::Instruction::MovUp3, + ], + span, + ); + } + Type::U64 | Type::I64 => { + self.push_immediate(imm, span); + self.bxor_int64(span); + } + Type::U32 => self.bxor_imm_u32(imm.as_u32().unwrap(), span), + Type::I32 => self.bxor_imm_u32(imm.as_i64().unwrap() as u64 as u32, span), + ty @ (Type::U16 | Type::U8) => { + self.bxor_imm_u32(imm.as_u32().unwrap(), span); + self.trunc_int32(ty.size_in_bits() as u32, span); + } + ty @ (Type::I16 | Type::I8) => { + self.bxor_imm_u32(imm.as_i64().unwrap() as u64 as u32, span); + self.trunc_int32(ty.size_in_bits() as u32, span); + } + Type::I1 => self.emit_all( + [masm::Instruction::PushU8(imm.as_bool().unwrap().into()), masm::Instruction::Xor], + span, + ), + ty if !ty.is_integer() => { + panic!("invalid binary operand: bxor expects integer operands, got {ty}") + } + ty => unimplemented!("bxor for {ty} is not supported"), + } + self.push(ty); + } + + pub fn shl(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); + match &ty { + Type::U64 | Type::I64 => self.shl_u64(span), + Type::U32 | Type::I32 => self.shl_u32(span), + ty @ (Type::U16 | Type::I16 | Type::U8 | Type::I8) => { + self.shl_u32(span); + self.trunc_int32(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: shl expects integer operands, got {ty}") + } + ty => unimplemented!("shl for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn shl_imm(&mut self, imm: u32, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + match &ty { + Type::U64 | Type::I64 => { + assert!(imm < 64, "invalid shift value: must be < 64"); + self.push_u32(imm, span); + self.shl_u64(span); + } + Type::U32 => self.shl_imm_u32(imm, span), + Type::I32 => self.shl_imm_u32(imm, span), + ty @ (Type::U16 | Type::I16 | Type::U8 | Type::I8) => { + self.shl_imm_u32(imm, span); + self.trunc_int32(ty.size_in_bits() as u32, span); + } + ty if !ty.is_integer() => { + panic!("invalid binary operand: shl expects integer operands, got {ty}") + } + ty => unimplemented!("shl for {ty} is not supported"), + } + self.push(ty); + } + + pub fn shr(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); + match &ty { + Type::U64 => self.shr_u64(span), + Type::I64 => self.shr_i64(span), + Type::U32 | Type::U16 | Type::U8 => self.shr_u32(span), + Type::I32 => self.shr_i32(span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: shr expects integer operands, got {ty}") + } + ty => unimplemented!("shr for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn shr_imm(&mut self, imm: u32, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + match &ty { + Type::U64 => { + assert!(imm < 64, "invalid shift value: must be < 64, got {imm}"); + self.push_u32(imm, span); + self.shr_u64(span); + } + Type::I64 => self.shr_imm_i64(imm, span), + Type::U32 | Type::U16 | Type::U8 => self.shr_imm_u32(imm, span), + Type::I32 => self.shr_imm_i32(imm, span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: shr expects integer operands, got {ty}") + } + ty => unimplemented!("shr for {ty} is not supported"), + } + self.push(ty); + } + + pub fn rotl(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); + match &ty { + Type::U64 | Type::I64 => self.rotl_u64(span), + Type::U32 | Type::I32 => self.rotl_u32(span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: rotl expects integer operands, got {ty}") + } + ty => unimplemented!("rotl for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn rotl_imm(&mut self, imm: u32, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + match &ty { + Type::U64 | Type::I64 => { + self.push_u32(imm, span); + self.rotl_u64(span); + } + Type::U32 | Type::I32 => self.rotl_imm_u32(imm, span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: rotl expects integer operands, got {ty}") + } + ty => unimplemented!("rotl for {ty} is not supported"), + } + self.push(ty); + } + + pub fn rotr(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(rhs.ty(), Type::U32, "expected shift operand to be u32"); + match &ty { + Type::U64 | Type::I64 => self.rotr_u64(span), + Type::U32 | Type::I32 => self.rotr_u32(span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: rotr expects integer operands, got {ty}") + } + ty => unimplemented!("rotr for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn rotr_imm(&mut self, imm: u32, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + match &ty { + Type::U64 | Type::I64 => { + self.push_u32(imm, span); + self.rotr_u64(span); + } + Type::U32 | Type::I32 => self.rotr_imm_u32(imm, span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: rotr expects integer operands, got {ty}") + } + ty => unimplemented!("rotr for {ty} is not supported"), + } + self.push(ty); + } + + pub fn min(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected min operands to be the same type"); + match &ty { + Type::U64 => self.min_u64(span), + Type::I64 => self.min_i64(span), + Type::U32 | Type::U16 | Type::U8 | Type::I1 => self.min_u32(span), + Type::I32 => self.min_i32(span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: min expects integer operands, got {ty}") + } + ty => unimplemented!("min for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn min_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected min operands to be the same type"); + match &ty { + Type::U64 => { + self.push_immediate(imm, span); + self.min_u64(span); + } + Type::I64 => self.min_imm_i64(imm.as_i64().unwrap(), span), + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.min_imm_u32(imm.as_u32().unwrap(), span) + } + Type::I32 => self.min_imm_i32(imm.as_i32().unwrap(), span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: min expects integer operands, got {ty}") + } + ty => unimplemented!("min for {ty} is not supported"), + } + self.push(ty); + } + + pub fn max(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected max operands to be the same type"); + match &ty { + Type::U64 => self.max_u64(span), + Type::I64 => self.max_i64(span), + Type::U32 | Type::U16 | Type::U8 | Type::I1 => self.max_u32(span), + Type::I32 => self.max_i32(span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: max expects integer operands, got {ty}") + } + ty => unimplemented!("max for {ty} is not supported"), + } + self.push(ty); + } + + #[allow(unused)] + pub fn max_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected max operands to be the same type"); + match &ty { + Type::U64 => { + self.push_immediate(imm, span); + self.max_u64(span); + } + Type::I64 => self.max_imm_i64(imm.as_i64().unwrap(), span), + Type::U32 | Type::U16 | Type::U8 | Type::I1 => { + self.max_imm_u32(imm.as_u32().unwrap(), span) + } + Type::I32 => self.max_imm_i32(imm.as_i32().unwrap(), span), + ty if !ty.is_integer() => { + panic!("invalid binary operand: max expects integer operands, got {ty}") + } + ty => unimplemented!("max for {ty} is not supported"), + } + self.push(ty); + } +} diff --git a/codegen/masm/src/emit/felt.rs b/codegen/masm/src/emit/felt.rs new file mode 100644 index 000000000..36eae3f08 --- /dev/null +++ b/codegen/masm/src/emit/felt.rs @@ -0,0 +1,183 @@ +use miden_core::{Felt, FieldElement}; +use midenc_hir::SourceSpan; + +use super::{masm, OpEmitter}; + +/// The value zero, as a field element +pub const ZERO: Felt = Felt::ZERO; + +/// The value 2^32, as a field element +pub const U32_FIELD_MODULUS: Felt = Felt::new(2u64.pow(32)); + +#[allow(unused)] +impl OpEmitter<'_> { + /// This operation checks if the field element on top of the stack is zero. + /// + /// This operation does not consume the input, and pushes a boolean value on the stack. + /// + /// # Stack effects + /// + /// `[a, ..] => [a == 0, a, ..]` + #[inline(always)] + pub fn felt_is_zero(&mut self, span: SourceSpan) { + self.emit_all([masm::Instruction::Dup0, masm::Instruction::EqImm(ZERO.into())], span); + } + + /// This operation asserts the field element on top of the stack is zero. + /// + /// This operation does not consume the input. + /// + /// # Stack effects + /// + /// `[a, ..] => [a, ..]` + #[inline(always)] + pub fn assert_felt_is_zero(&mut self, span: SourceSpan) { + self.emit_all([masm::Instruction::Dup0, masm::Instruction::Assertz], span); + } + + /// Convert a field element to i128 by zero-extension. + /// + /// This consumes the field element on top of the stack. + /// + /// # Stack effects + /// + /// `[a, ..] => [0, 0, a_hi, a_lo]` + #[inline] + pub fn felt_to_i128(&mut self, span: SourceSpan) { + self.emit_all( + [ + masm::Instruction::U32Split, + masm::Instruction::PushFelt(ZERO), + masm::Instruction::PushFelt(ZERO), + ], + span, + ); + } + + /// Convert a field element to u64 by zero-extension. + /// + /// This consumes the field element on top of the stack. + /// + /// # Stack effects + /// + /// `[a, ..] => [a_hi, a_lo]` + #[inline(always)] + pub fn felt_to_u64(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Split, span); + } + + /// Convert a field element to i64 by zero-extension. + /// + /// Asserts if the field element is too large to represent as an i64. + /// + /// This consumes the field element on top of the stack. + /// + /// # Stack effects + /// + /// `[a, ..] => [a_hi, a_lo]` + #[inline(always)] + pub fn felt_to_i64(&mut self, span: SourceSpan) { + self.felt_to_u64(span); + } + + /// Convert a field element value to an unsigned N-bit integer, where N <= 32 + /// + /// Conversion will trap if the input value is too large to fit in an unsigned N-bit integer. + pub fn felt_to_uint(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 1, 32); + self.emit_all( + [ + // Split into u32 limbs + masm::Instruction::U32Split, + // Assert most significant 32 bits are unused + masm::Instruction::Assertz, + ], + span, + ); + if n < 32 { + // Convert to N-bit integer + self.int32_to_uint(n, span); + } + } + + /// Convert a field element value to a signed N-bit integer, where N <= 32 + /// + /// Conversion will trap if the input value is too large to fit in a signed N-bit integer. + pub fn felt_to_int(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 1, 32); + self.emit_all( + [ + // Split into u32 limbs + masm::Instruction::U32Split, + // Assert most significant 32 bits are unused + masm::Instruction::Assertz, + ], + span, + ); + // Assert the sign bit isn't set + self.assert_unsigned_int32(span); + if n < 32 { + // Convert to signed N-bit integer + self.int32_to_int(n, span); + } + } + + /// Zero-extend a field element value to N-bits, where N >= 64 + /// + /// N must be a power of two, or this function will panic. + pub fn zext_felt(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 64, 256); + match n { + 64 => self.felt_to_u64(span), + 128 => self.felt_to_i128(span), + n => { + // Convert to u64 and zero-extend + self.felt_to_u64(span); + self.zext_int64(n, span); + } + } + } + + /// Emits code to sign-extend a field element value to an N-bit integer, where N >= 64 + /// + /// Field elements are unsigned, so sign-extension here is indicating that the target + /// integer type is a signed type, so we have one less bit available to use. + /// + /// N must be a power of two, or this function will panic. + pub fn sext_felt(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 64, 256); + match n { + 64 => self.felt_to_i64(span), + 128 => self.felt_to_i128(span), + n => { + // Convert to i64 and sign-extend + self.felt_to_i64(span); + self.sext_int64(n, span); + } + } + } + + /// Truncates a field element on top of the stack to an N-bit integer, where N <= 32. + /// + /// Truncation on field elements is not well-defined, because field elements do not have + /// a specified bitwise representation. To implement semantics equivalent to the other types + /// which _do_ have a specified representation, we first convert the input field element to u32, + /// and then masking out any additional unused bits of the u32 representation. + /// + /// This should produce outputs which are identical to equivalent u64 values, i.e. the same + /// value in both u64 and felt representation will be truncated to the same u32 value. + #[inline] + pub fn trunc_felt(&mut self, n: u32, span: SourceSpan) { + // Apply a field modulus of 2^32, i.e. `a mod 2^32`, converting + // the field element into the u32 range. Miden defines values in + // this range as having a standard unsigned binary representation. + self.emit(masm::Instruction::U32Cast, span); + self.trunc_int32(n, span); + } + + /// Make `n` copies of the element on top of the stack + #[inline(always)] + pub fn dup_felt(&mut self, count: u8, span: SourceSpan) { + self.emit_n(count as usize, masm::Instruction::Dup0, span); + } +} diff --git a/codegen/masm/src/codegen/emit/int128.rs b/codegen/masm/src/emit/int128.rs similarity index 80% rename from codegen/masm/src/codegen/emit/int128.rs rename to codegen/masm/src/emit/int128.rs index 16f44940a..9d14ba8e6 100644 --- a/codegen/masm/src/codegen/emit/int128.rs +++ b/codegen/masm/src/emit/int128.rs @@ -1,10 +1,9 @@ -use midenc_hir::SourceSpan; +use midenc_hir::{Overflow, SourceSpan}; -use super::OpEmitter; -use crate::masm::Op; +use super::{masm, OpEmitter}; #[allow(unused)] -impl<'a> OpEmitter<'a> { +impl OpEmitter<'_> { /// Checks if the i128 value on the stack has its sign bit set. #[inline(always)] pub fn is_signed_int128(&mut self, span: SourceSpan) { @@ -79,7 +78,7 @@ impl<'a> OpEmitter<'a> { // // What remains on the stack at this point are the low 64-bits, // which is also our result. - self.emit_n(2, Op::Assertz, span); + self.emit_n(2, masm::Instruction::Assertz, span); } /// Convert a 128-bit value to u32 @@ -96,7 +95,7 @@ impl<'a> OpEmitter<'a> { // // What remains on the stack at this point are the low 32-bits, // which is also our result. - self.emit_n(3, Op::Assertz, span); + self.emit_n(3, masm::Instruction::Assertz, span); } /// Convert a unsigned 128-bit value to i64 @@ -128,38 +127,38 @@ impl<'a> OpEmitter<'a> { // Determine if this value is signed or not self.is_signed_int32(span); // Preserving the is_signed flag, select the expected hi bits value - self.emit(Op::Dup(0), span); + self.emit(masm::Instruction::Dup0, span); self.select_int32(u32::MAX, 0, span); // Move the most significant 64 bits to top of stack self.move_int64_up(2, span); // Move expected value to top of stack - self.emit(Op::Movup(2), span); + self.emit(masm::Instruction::MovUp2, span); // Assert the most significant 32 bits match, without consuming them self.assert_eq_u32(span); self.emit_all( - &[ + [ // Assert that both 32-bit limbs of the most significant 64 bits match, // consuming them in the process - Op::AssertEq, + masm::Instruction::AssertEq, // At this point, the stack is: [is_signed, x1, x0] // // Select an expected value for the sign bit based on the is_signed flag - Op::Swap(1), + masm::Instruction::Swap1, ], span, ); // [is_sign_bit_set, x1, is_signed, x0] self.is_const_flag_set_u32(1 << 31, span); self.emit_all( - &[ + [ // [is_signed, is_sign_bit_set, x1, x0] - Op::Movup(2), + masm::Instruction::MovUp2, // Assert that the flags are equal: either the input was signed and the // sign bit was set, or the input was unsigned, and the sign bit was unset, // any other combination will trap. // // [x1, x0] - Op::AssertEq, + masm::Instruction::AssertEq, ], span, ); @@ -172,7 +171,7 @@ impl<'a> OpEmitter<'a> { /// NOTE: This function does not validate the i128, that is left up to the caller. #[inline] pub fn trunc_i128_to_felt(&mut self, span: SourceSpan) { - self.emit_n(2, Op::Drop, span); + self.emit_n(2, masm::Instruction::Drop, span); self.trunc_int64_to_felt(span); } @@ -189,10 +188,10 @@ impl<'a> OpEmitter<'a> { assert_valid_integer_size!(n, 1, 64); match n { 64 => { - self.emit_n(2, Op::Drop, span); + self.emit_n(2, masm::Instruction::Drop, span); } 32 => { - self.emit_n(3, Op::Drop, span); + self.emit_n(3, masm::Instruction::Drop, span); } n => { self.trunc_int32(n, span); @@ -205,13 +204,13 @@ impl<'a> OpEmitter<'a> { #[inline] pub fn eq_i128(&mut self, span: SourceSpan) { self.emit_all( - &[ - Op::Eqw, + [ + masm::Instruction::Eqw, // Move the boolean below the elements we're going to drop - Op::Movdn(8), + masm::Instruction::MovDn8, // Drop both i128 values - Op::Dropw, - Op::Dropw, + masm::Instruction::DropW, + masm::Instruction::DropW, ], span, ); @@ -222,6 +221,36 @@ impl<'a> OpEmitter<'a> { #[inline] pub fn neq_i128(&mut self, span: SourceSpan) { self.eq_i128(span); - self.emit(Op::Not, span); + self.emit(masm::Instruction::Not, span); + } + + /// Pop two i128 values off the stack, `b` and `a`, and place the result of `a + b` on the + /// stack. + /// + /// For now we're only supporting wrapping add for signed values. + #[inline] + pub fn add_i128(&mut self, overflow: Overflow, span: SourceSpan) { + assert!( + matches!(overflow, Overflow::Wrapping), + "Only 128bit *wrapping* adds implemented as yet." + ); + + self.raw_exec("intrinsics::i128::add", span); + } + + /// Pops two i128 values off the stack, `b` and `a`, and performs `a - b`. + pub fn sub_i128(&mut self, overflow: Overflow, span: SourceSpan) { + assert!( + matches!(overflow, Overflow::Wrapping), + "Only 128bit *wrapping* subs implemented as yet." + ); + + self.raw_exec("intrinsics::i128::sub", span); + } + + /// Pops two i128 values off the stack, `b` and `a`, and performs `a * b`. + #[inline] + pub fn mul_i128(&mut self, span: SourceSpan) { + self.raw_exec("intrinsics::i128::mul", span); } } diff --git a/codegen/masm/src/emit/int32.rs b/codegen/masm/src/emit/int32.rs new file mode 100644 index 000000000..b20e6f7ec --- /dev/null +++ b/codegen/masm/src/emit/int32.rs @@ -0,0 +1,1037 @@ +use miden_core::{Felt, FieldElement}; +use midenc_hir::{Overflow, SourceSpan}; + +use super::{dup_from_offset, felt, masm, movup_from_offset, OpEmitter}; + +pub const SIGN_BIT: u32 = 1 << 31; + +#[allow(unused)] +impl OpEmitter<'_> { + /// Emits code to apply a constant 32-bit mask, `mask`, to a u32 value on top of the stack. + /// + /// The value on top of the stack IS consumed. + /// + /// NOTE: This function does not validate that the value on top of the stack is + /// a valid u32 - the caller is responsible for such validation. + /// + /// # Stack Effects + /// + /// `[a, ..] => [a & mask, ..]` + #[inline] + pub fn const_mask_u32(&mut self, mask: u32, span: SourceSpan) { + self.emit_all([masm::Instruction::PushU32(mask), masm::Instruction::U32And], span); + } + + /// Emits code to apply a 32-bit mask, `mask`, to a u32 value, `input`. + /// + /// Both `mask` and `input` are operands on the stack, with `mask` on top. + /// + /// While `mask` is consumed by this operation, `input` IS NOT consumed. + /// + /// NOTE: This function assumes that the caller has validated that both values are valid u32. + /// + /// # Stack Effects + /// + /// `[mask, input, ..] => [input & mask, input]` + #[inline] + pub fn mask_u32(&mut self, span: SourceSpan) { + self.emit_all([masm::Instruction::Dup1, masm::Instruction::U32And], span); + } + + /// Emits code to check if all bits of `flags` are set in the u32 value on top of the stack. + /// + /// The value on top of the stack IS NOT consumed. + /// + /// NOTE: This function does not validate that the value on top of the stack is + /// a valid u32 - the caller is responsible for such validation. + /// + /// # Stack Effects + /// + /// `[a, ..] => [a & flags == flags, a]` + #[inline] + pub fn is_const_flag_set_u32(&mut self, flags: u32, span: SourceSpan) { + self.emit(masm::Instruction::Dup0, span); + self.const_mask_u32(flags, span); + self.emit(masm::Instruction::EqImm(Felt::new(flags as u64).into()), span); + } + + /// Emits code to check if all bits of `mask` are set in `input`. + /// + /// Both `mask` and `input` are operands on the stack, with `mask` on top. + /// + /// While `mask` is consumed by this operation, `input` IS NOT consumed. + /// + /// NOTE: This function assumes that the caller has validated that both values are valid u32. + /// + /// # Stack Effects + /// + /// `[mask, input, ..] => [input & mask == mask, input]` + #[inline] + pub fn is_flag_set_u32(&mut self, span: SourceSpan) { + self.emit_all( + [ + masm::Instruction::Dup1, // [input, mask, input] + masm::Instruction::Dup1, // [mask, input, mask, input] + masm::Instruction::U32And, // [input & mask, mask, input] + masm::Instruction::Eq, // [input & mask == mask, input] + ], + span, + ); + } + + /// Check if a 32-bit integer value on the operand stack has its sign bit set. + /// + /// The value on top of the stack IS NOT consumed. + /// + /// See `is_const_flag_set` for semantics and stack effects. + #[inline] + pub fn is_signed_int32(&mut self, span: SourceSpan) { + self.is_const_flag_set_u32(SIGN_BIT, span); + } + + /// Check if a 32-bit integer value on the operand stack does not have its sign bit set. + /// + /// The value on top of the stack IS NOT consumed. + #[inline(always)] + pub fn is_unsigned_int32(&mut self, span: SourceSpan) { + self.is_signed_int32(span); + self.emit(masm::Instruction::Not, span); + } + + /// Emits code to assert that a 32-bit value on the operand stack has the i32 sign bit set. + /// + /// The value on top of the stack IS NOT consumed. + /// + /// See `is_signed` for semantics and stack effects of the signedness check. + #[inline] + pub fn assert_signed_int32(&mut self, span: SourceSpan) { + self.is_signed_int32(span); + self.emit(masm::Instruction::Assert, span); + } + + /// Emits code to assert that a 32-bit value on the operand stack does not have the i32 sign bit + /// set. + /// + /// The value on top of the stack IS NOT consumed. + /// + /// See `is_signed` for semantics and stack effects of the signedness check. + #[inline] + pub fn assert_unsigned_int32(&mut self, span: SourceSpan) { + self.is_signed_int32(span); + self.emit(masm::Instruction::Assertz, span); + } + + /// Assert that the 32-bit value on the stack is a valid i32 value + pub fn assert_i32(&mut self, span: SourceSpan) { + // Copy the value on top of the stack + self.emit(masm::Instruction::Dup0, span); + // Assert the value does not overflow i32::MAX or underflow i32::MIN + // This can be checked by validating that when interpreted as a u32, + // the value is <= i32::MIN, which is 1 more than i32::MAX. + self.push_i32(i32::MIN, span); + self.emit(masm::Instruction::U32Lte, span); + self.emit(masm::Instruction::Assert, span); + } + + /// Emits code to assert that a 32-bit value on the operand stack is equal to the given constant + /// value. + /// + /// The value on top of the stack IS NOT consumed. + /// + /// # Stack Effects + /// + /// `[input, ..] => [input, ..]` + #[inline] + pub fn assert_eq_imm_u32(&mut self, value: u32, span: SourceSpan) { + self.emit_all( + [ + masm::Instruction::Dup0, + masm::Instruction::EqImm(Felt::new(value as u64).into()), + masm::Instruction::Assert, + ], + span, + ); + } + + /// Emits code to assert that two 32-bit values, `expected` and `value`, on top of the operand + /// stack are equal, without consuming `value`. + /// + /// The `expected` operand is consumed, while the `value` operand IS NOT. + /// + /// # Stack Effects + /// + /// `[expected, input, ..] => [input, ..]` + #[inline] + pub fn assert_eq_u32(&mut self, span: SourceSpan) { + self.emit_all([masm::Instruction::Dup1, masm::Instruction::AssertEq], span); + } + + /// Emits code to select a constant u32 value, using the `n`th value on the operand + /// stack as the condition for the select. + /// + /// This function pushes `b` then `a` on the stack, moves the `n`th value to the top + /// of the stack, and then executes a conditional drop. This has the effect of consuming + /// all three operands, placing only a single value back on the operand stack; the + /// selected value, either `a` or `b`. Use `dup_select` if you would rather copy + /// the conditional rather than move it. + pub fn mov_select_int32(&mut self, a: u32, b: u32, n: u8, span: SourceSpan) { + assert_valid_stack_index!(n); + // If the value we need will get pushed off the end of the stack, + // bring it closer first, and adjust our `n` accordingly + if n > 13 { + self.emit(movup_from_offset(n as usize), span); + self.select_int32(a, b, span); + } else { + self.emit_all( + [ + masm::Instruction::PushU32(b), + masm::Instruction::PushU32(a), + movup_from_offset(n as usize + 2), + masm::Instruction::CDrop, + ], + span, + ); + } + } + + /// Same semantics as `mov_select`, but copies the `n`th value on the operand + /// stack rather than moving it. + /// + /// # Stack Effects + /// + /// Moves `c` to the top of the stack, where `c` is the `n`th value on the operand stack, + /// then applies `select`. + pub fn dup_select_int32(&mut self, a: u32, b: u32, n: u8, span: SourceSpan) { + assert_valid_stack_index!(n); + // If the value we need will get pushed off the end of the stack, + // bring it closer first, and adjust our `n` accordingly + if n > 13 { + self.emit(dup_from_offset(n as usize), span); + self.select_int32(a, b, span); + } else { + self.emit_all( + [ + masm::Instruction::PushU32(b), + masm::Instruction::PushU32(a), + dup_from_offset(n as usize + 2), + masm::Instruction::CDrop, + ], + span, + ); + } + } + + /// Emits code to select between two u32 constants, given a boolean value on top of the stack + /// + /// # Stack Effects + /// + /// `[c, a, b, ..] => [d, ..] where d is c == 1 ? a : b` + pub fn select_int32(&mut self, a: u32, b: u32, span: SourceSpan) { + self.emit_all( + [ + masm::Instruction::PushU32(b), + masm::Instruction::PushU32(a), + masm::Instruction::MovUp2, + masm::Instruction::CDrop, + ], + span, + ); + } + + /// Convert an i32/u32 value on the stack to a signed N-bit integer value + /// + /// Execution traps if the value cannot fit in the signed N-bit range. + pub fn int32_to_int(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 1, 32); + // Push is_signed on the stack + self.is_signed_int32(span); + // Pop the is_signed flag, and replace it with a selected mask + // for the upper reserved bits of the N-bit range + let reserved = 32 - n; + // Add one bit to the reserved bits to represent the sign bit, + // and subtract it from the shift to account for the loss + let mask = (2u32.pow(reserved + 1) - 1) << (n - 1); + self.select_int32(mask, 0, span); + self.emit_all( + [ + // Copy the input to the top of the stack for the masking op + masm::Instruction::Dup1, + // Copy the mask value for the masking op + masm::Instruction::Dup1, + // Apply the mask + masm::Instruction::U32And, + // Assert that the masked bits and the mask are equal + masm::Instruction::AssertEq, + ], + span, + ); + } + + /// Convert an i32/u32 value on the stack to a signed N-bit integer value + /// + /// Places a boolean on top of the stack indicating if the conversion was successful + pub fn try_int32_to_int(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 1, 32); + // Push is_signed on the stack + self.is_signed_int32(span); + // Pop the is_signed flag, and replace it with a selected mask + // for the upper reserved bits of the N-bit range + let reserved = 32 - n; + // Add one bit to the reserved bits to represent the sign bit, + // and subtract it from the shift to account for the loss + let mask = (2u32.pow(reserved + 1) - 1) << (n - 1); + self.select_int32(mask, 0, span); + self.emit_all( + [ + // Copy the input to the top of the stack for the masking op + masm::Instruction::Dup1, + // Copy the mask value for the masking op + masm::Instruction::Dup1, + // Apply the mask + masm::Instruction::U32And, + // Assert that the masked bits and the mask are equal + masm::Instruction::Eq, + ], + span, + ); + } + + /// Convert an i32/u32 value on the stack to an unsigned N-bit integer value + /// + /// Execution traps if the value cannot fit in the unsigned N-bit range. + pub fn int32_to_uint(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 1, 32); + // Mask the value and ensure that the unused bits above the N-bit range are 0 + let reserved = 32 - n; + let mask = (2u32.pow(reserved) - 1) << n; + self.emit_all( + [ + // Copy the input + masm::Instruction::Dup1, + // Apply the mask + masm::Instruction::PushU32(mask), + masm::Instruction::U32And, + // Assert the masked value is all 0s + masm::Instruction::Assertz, + ], + span, + ); + } + + /// Convert an i32/u32 value on the stack to an unsigned N-bit integer value + /// + /// Places a boolean on top of the stack indicating if the conversion was successful + pub fn try_int32_to_uint(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 1, 32); + // Mask the value and ensure that the unused bits above the N-bit range are 0 + let reserved = 32 - n; + let mask = (2u32.pow(reserved) - 1) << n; + self.emit_all( + [ + // Copy the input + masm::Instruction::Dup1, + // Apply the mask + masm::Instruction::PushU32(mask), + masm::Instruction::U32And, + // Assert the masked value is all 0s + masm::Instruction::EqImm(Felt::ZERO.into()), + ], + span, + ); + } + + /// Emit code to truncate a 32-bit value on top of the operand stack, to N bits, where N is <= + /// 32 + /// + /// This consumes the input value, and leaves an N-bit value on the stack. + /// + /// NOTE: This function does not validate the input as < 2^32, the caller is expected to + /// validate this. + #[inline] + pub fn trunc_int32(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 1, 32); + // Mask out any bits between N and 32. + let unused_bits = 32 - n; + if unused_bits > 0 { + self.const_mask_u32((1 << (32 - unused_bits)) - 1, span); + } + } + + /// Emit code to zero-extend a 32-bit value to N bits, where N <= 128 + /// + /// This operation assumes all N-bit integers greater than 32 bits use 32-bit limbs. + /// + /// NOTE: This operation does not check the sign bit, it is assumed the value is + /// either an unsigned integer, or a non-negative signed integer. + #[inline] + pub fn zext_int32(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 32); + // Only values larger than 32 bits require padding + if n <= 32 { + return; + } + let num_bits = n % 32; + let num_elements = (n / 32) + (num_bits > 0) as u32; + let needed = num_elements - 1; + self.emit_n(needed as usize, masm::Instruction::PushU32(0), span); + } + + /// Emit code to sign-extend a signed 32-bit value to N bits, where N <= 128 + /// + /// This operation assumes all N-bit integers greater than 32 bits use 32-bit limbs. + /// + /// NOTE: This operation treats the most significant bit as the sign bit, it is + /// assumed the value is an i32, it is up to the caller to ensure this is a valid + /// operation to perform on the input. + #[inline] + pub fn sext_int32(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 32); + self.is_signed_int32(span); + self.select_int32(u32::MAX, 0, span); + self.pad_int32(n, span); + } + + /// Emit code to pad a 32-bit value out to N bits, where N >= 32. + /// + /// N must be a power of two. + /// + /// The padding value is expected on top of the stack, followed by the 32-bit value to pad. + /// + /// This operation assumes all N-bit integers greater than 32 bits use 32-bit limbs. + /// + /// The padding value will be duplicated for each additional 32-bit limb needed to + /// ensure that there are enough limbs on the stack to represent an N-bit integer. + #[inline] + pub fn pad_int32(&mut self, n: u32, span: SourceSpan) { + assert_valid_integer_size!(n, 32); + // We need one element for each 32-bit limb + let num_elements = n / 32; + // We already have the input u32, as well as the pad value, so deduct + // those elements from the number needed. + let needed = num_elements.saturating_sub(2); + self.emit_n(needed as usize, masm::Instruction::Dup0, span); + } + + /// Push a u32 value on the stack + #[inline(always)] + pub fn push_u32(&mut self, i: u32, span: SourceSpan) { + self.emit(masm::Instruction::PushU32(i), span); + } + + /// Push a i32 value on the stack + #[inline(always)] + pub fn push_i32(&mut self, i: i32, span: SourceSpan) { + self.emit(masm::Instruction::PushU32(i as u32), span); + } + + /// This is the inverse operation of the Miden VM `u32split` instruction. + /// + /// This takes two 32-bit limbs, and produces a felt. + /// + /// NOTE: It is expected that the caller has validated that the limbs are valid u32 values. + pub fn u32unsplit(&mut self, span: SourceSpan) { + self.emit_all( + [ + masm::Instruction::MulImm(felt::U32_FIELD_MODULUS.into()), + masm::Instruction::Add, + ], + span, + ); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a + b`. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + #[inline(always)] + pub fn add_u32(&mut self, overflow: Overflow, span: SourceSpan) { + self.emit( + match overflow { + Overflow::Unchecked => masm::Instruction::Add, + Overflow::Checked => { + return self + .emit_all([masm::Instruction::Add, masm::Instruction::U32Assert], span) + } + Overflow::Wrapping => masm::Instruction::U32WrappingAdd, + Overflow::Overflowing => masm::Instruction::U32OverflowingAdd, + }, + span, + ); + } + + /// Pops two i32 values off the stack, `b` and `a`, and performs `a + b`. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + #[inline(always)] + pub fn add_i32(&mut self, overflow: Overflow, span: SourceSpan) { + match overflow { + Overflow::Unchecked | Overflow::Wrapping => { + self.emit(masm::Instruction::U32WrappingAdd, span) + } + Overflow::Checked => self.raw_exec("intrinsics::i32::checked_add", span), + Overflow::Overflowing => self.raw_exec("intrinsics::i32::overflowing_add", span), + } + } + + /// Pops a u32 value off the stack, `a`, and performs `a + `. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + /// + /// Adding zero is a no-op. + #[inline] + pub fn add_imm_u32(&mut self, imm: u32, overflow: Overflow, span: SourceSpan) { + if imm == 0 { + return; + } + self.emit( + match overflow { + Overflow::Unchecked if imm == 1 => masm::Instruction::AddImm(Felt::ONE.into()), + Overflow::Unchecked => masm::Instruction::AddImm(Felt::new(imm as u64).into()), + Overflow::Checked => { + return self.emit_all( + [ + masm::Instruction::AddImm(Felt::new(imm as u64).into()), + masm::Instruction::U32Assert, + ], + span, + ); + } + Overflow::Wrapping => masm::Instruction::U32WrappingAddImm(imm.into()), + Overflow::Overflowing => masm::Instruction::U32OverflowingAddImm(imm.into()), + }, + span, + ); + } + + /// Pops a i32 value off the stack, `a`, and performs `a + `. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + /// + /// Adding zero is a no-op. + #[inline] + pub fn add_imm_i32(&mut self, imm: i32, overflow: Overflow, span: SourceSpan) { + if imm == 0 { + return; + } + match overflow { + Overflow::Unchecked | Overflow::Wrapping => { + self.add_imm_u32(imm as u32, overflow, span) + } + Overflow::Checked => { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::checked_add", span); + } + Overflow::Overflowing => { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::overflowing_add", span); + } + } + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a - b`. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + pub fn sub_u32(&mut self, overflow: Overflow, span: SourceSpan) { + self.emit( + match overflow { + Overflow::Unchecked => masm::Instruction::Sub, + Overflow::Checked => { + return self + .emit_all([masm::Instruction::Sub, masm::Instruction::U32Assert], span); + } + Overflow::Wrapping => masm::Instruction::U32WrappingSub, + Overflow::Overflowing => masm::Instruction::U32OverflowingSub, + }, + span, + ); + } + + /// Pops two i32 values off the stack, `b` and `a`, and performs `a - b`. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + pub fn sub_i32(&mut self, overflow: Overflow, span: SourceSpan) { + match overflow { + Overflow::Unchecked | Overflow::Wrapping => self.sub_u32(overflow, span), + Overflow::Checked => self.raw_exec("intrinsics::i32::checked_sub", span), + Overflow::Overflowing => self.raw_exec("intrinsics::i32::overflowing_sub", span), + } + } + + /// Pops a u32 value off the stack, `a`, and performs `a - `. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + /// + /// Subtracting zero is a no-op. + #[inline] + pub fn sub_imm_u32(&mut self, imm: u32, overflow: Overflow, span: SourceSpan) { + if imm == 0 { + return; + } + self.emit( + match overflow { + Overflow::Unchecked => masm::Instruction::SubImm(Felt::new(imm as u64).into()), + Overflow::Checked => { + return self.emit_all( + [ + masm::Instruction::SubImm(Felt::new(imm as u64).into()), + masm::Instruction::U32Assert, + ], + span, + ) + } + Overflow::Wrapping => masm::Instruction::U32WrappingSubImm(imm.into()), + Overflow::Overflowing => masm::Instruction::U32OverflowingSubImm(imm.into()), + }, + span, + ); + } + + /// Pops a i32 value off the stack, `a`, and performs `a - `. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + /// + /// Subtracting zero is a no-op. + #[inline] + pub fn sub_imm_i32(&mut self, imm: i32, overflow: Overflow, span: SourceSpan) { + if imm == 0 { + return; + } + match overflow { + Overflow::Unchecked | Overflow::Wrapping => { + self.sub_imm_u32(imm as u32, overflow, span) + } + Overflow::Checked => { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::checked_sub", span); + } + Overflow::Overflowing => { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::overflowing_sub", span); + } + } + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a * b`. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + pub fn mul_u32(&mut self, overflow: Overflow, span: SourceSpan) { + self.emit( + match overflow { + Overflow::Unchecked => masm::Instruction::Mul, + Overflow::Checked => { + return self + .emit_all([masm::Instruction::Mul, masm::Instruction::U32Assert], span) + } + Overflow::Wrapping => masm::Instruction::U32WrappingMul, + Overflow::Overflowing => masm::Instruction::U32OverflowingMul, + }, + span, + ); + } + + /// Pops two i32 values off the stack, `b` and `a`, and performs `a * b`. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + pub fn mul_i32(&mut self, overflow: Overflow, span: SourceSpan) { + match overflow { + Overflow::Unchecked | Overflow::Wrapping => { + self.raw_exec("intrinsics::i32::wrapping_mul", span) + } + Overflow::Checked => self.raw_exec("intrinsics::i32::checked_mul", span), + Overflow::Overflowing => self.raw_exec("intrinsics::i32::overflowing_mul", span), + } + } + + /// Pops a u32 value off the stack, `a`, and performs `a * `. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + /// + /// Multiplying by zero is transformed into a sequence which drops the input value + /// and pushes a constant zero on the stack. + /// + /// Multiplying by one is a no-op. + #[inline] + pub fn mul_imm_u32(&mut self, imm: u32, overflow: Overflow, span: SourceSpan) { + match imm { + 0 => { + self.emit_all([masm::Instruction::Drop, masm::Instruction::PushU32(0)], span); + } + 1 => (), + imm => { + self.emit( + match overflow { + Overflow::Unchecked => { + masm::Instruction::MulImm(Felt::new(imm as u64).into()) + } + Overflow::Checked => { + return self.emit_all( + [ + masm::Instruction::MulImm(Felt::new(imm as u64).into()), + masm::Instruction::U32Assert, + ], + span, + ) + } + Overflow::Wrapping => masm::Instruction::U32WrappingMulImm(imm.into()), + Overflow::Overflowing => { + masm::Instruction::U32OverflowingMulImm(imm.into()) + } + }, + span, + ); + } + } + } + + /// Pops a i32 value off the stack, `a`, and performs `a * `. + /// + /// See the [Overflow] type for how overflow semantics can change the operation. + /// + /// Multiplying by zero is transformed into a sequence which drops the input value + /// and pushes a constant zero on the stack. + /// + /// Multiplying by one is a no-op. + #[inline] + pub fn mul_imm_i32(&mut self, imm: i32, overflow: Overflow, span: SourceSpan) { + match imm { + 0 => { + self.emit_all([masm::Instruction::Drop, masm::Instruction::PushU32(0)], span); + } + 1 => (), + imm => match overflow { + Overflow::Unchecked | Overflow::Wrapping => { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::wrapping_mul", span); + } + Overflow::Checked => { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::checked_mul", span) + } + Overflow::Overflowing => { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::overflowing_mul", span); + } + }, + } + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a / b`. + /// + /// This operation is checked, so if the operands or result are not valid u32, execution traps. + pub fn checked_div_u32(&mut self, span: SourceSpan) { + self.emit_all([masm::Instruction::U32Div, masm::Instruction::U32Assert], span); + } + + /// Pops two i32 values off the stack, `b` and `a`, and performs `a / b`. + /// + /// This operation is checked, so if the operands or result are not valid i32, execution traps. + pub fn checked_div_i32(&mut self, span: SourceSpan) { + self.raw_exec("intrinsics::i32::checked_div", span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a / `. + /// + /// This function will panic if the divisor is zero. + /// + /// This operation is checked, so if the operand or result are not valid u32, execution traps. + pub fn checked_div_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.emit_all( + [masm::Instruction::U32DivImm(imm.into()), masm::Instruction::U32Assert], + span, + ); + } + + /// Pops a i32 value off the stack, `a`, and performs `a / `. + /// + /// This function will panic if the divisor is zero. + /// + /// This operation is checked, so if the operand or result are not valid i32, execution traps. + pub fn checked_div_imm_i32(&mut self, imm: i32, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::checked_div", span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a / b`. + /// + /// This operation is unchecked, so the result is not guaranteed to be a valid u32 + pub fn unchecked_div_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Div, span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a / `. + /// + /// This function will panic if the divisor is zero. + pub fn unchecked_div_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.emit(masm::Instruction::U32DivImm(imm.into()), span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a % b`. + /// + /// This operation is checked, so if the operands or result are not valid u32, execution traps. + pub fn checked_mod_u32(&mut self, span: SourceSpan) { + self.emit_all([masm::Instruction::U32Mod, masm::Instruction::U32Assert], span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a % `. + /// + /// This function will panic if the divisor is zero. + /// + /// This operation is checked, so if the operand or result are not valid u32, execution traps. + pub fn checked_mod_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.emit_all( + [masm::Instruction::U32ModImm(imm.into()), masm::Instruction::U32Assert], + span, + ); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a % b`. + /// + /// This operation is unchecked, so the result is not guaranteed to be a valid u32 + pub fn unchecked_mod_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Mod, span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a % `. + /// + /// This function will panic if the divisor is zero. + pub fn unchecked_mod_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.emit(masm::Instruction::U32ModImm(imm.into()), span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. + /// + /// This operation is checked, so if the operands or result are not valid u32, execution traps. + pub fn checked_divmod_u32(&mut self, span: SourceSpan) { + self.emit_all([masm::Instruction::U32DivMod, masm::Instruction::U32Assert], span); + } + + /// Pops a u32 value off the stack, `a`, and pushes `a / `, then `a % ` on the stack. + /// + /// This operation is checked, so if the operands or result are not valid u32, execution traps. + pub fn checked_divmod_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.emit_all( + [masm::Instruction::U32DivModImm(imm.into()), masm::Instruction::U32Assert], + span, + ); + } + + /// Pops two u32 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the + /// stack. + /// + /// This operation is unchecked, so the result is not guaranteed to be a valid u32 + pub fn unchecked_divmod_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32DivMod, span); + } + + /// Pops a u32 value off the stack, `a`, and pushes `a / `, then `a % ` on the stack. + /// + /// This operation is unchecked, so the result is not guaranteed to be a valid u32 + pub fn unchecked_divmod_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert_ne!(imm, 0, "division by zero is not allowed"); + self.emit(masm::Instruction::U32DivModImm(imm.into()), span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a & b` + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn band_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32And, span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a & ` + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn band_imm_u32(&mut self, imm: u32, span: SourceSpan) { + self.emit_all([masm::Instruction::PushU32(imm), masm::Instruction::U32And], span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a | b` + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn bor_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Or, span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a | ` + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn bor_imm_u32(&mut self, imm: u32, span: SourceSpan) { + self.emit_all([masm::Instruction::PushU32(imm), masm::Instruction::U32Or], span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a ^ b` + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn bxor_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Xor, span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a ^ ` + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn bxor_imm_u32(&mut self, imm: u32, span: SourceSpan) { + self.emit_all([masm::Instruction::PushU32(imm), masm::Instruction::U32Xor], span); + } + + /// Pops a u32 value off the stack, `a`, and performs `!a` + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn bnot_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32WrappingSubImm((-1i32 as u32).into()), span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a << b` + /// + /// Execution traps if `b` > 31. + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn shl_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Shl, span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a << ` + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn shl_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert!(imm < 32, "invalid shift value: must be < 32, got {imm}"); + self.emit(masm::Instruction::U32ShlImm((imm as u8).into()), span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and performs `a >> b` + /// + /// Execution traps if `b` > 31. + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn shr_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Shr, span); + } + + /// Pops two i32 values off the stack, `b` and `a`, and performs `a >> b` + /// + /// Execution traps if `b` > 31. + /// + /// This operation is checked, if the operands or result are not valid i32, execution traps. + pub fn shr_i32(&mut self, span: SourceSpan) { + self.raw_exec("intrinsics::i32::checked_shr", span); + } + + /// Pops a u32 value off the stack, `a`, and performs `a >> ` + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn shr_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert!(imm < 32, "invalid shift value: must be < 32, got {imm}"); + self.emit(masm::Instruction::U32ShrImm((imm as u8).into()), span); + } + + /// Pops a i32 value off the stack, `a`, and performs `a >> ` + /// + /// This operation is checked, if the operand or result are not valid i32, execution traps. + pub fn shr_imm_i32(&mut self, imm: u32, span: SourceSpan) { + assert!(imm < 32, "invalid shift value: must be < 32, got {imm}"); + self.emit(masm::Instruction::PushU32(imm), span); + self.raw_exec("intrinsics::i32::checked_shr", span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and rotates the bits of `a` left by `b` bits + /// + /// Execution traps if `b` > 31. + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn rotl_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Rotl, span); + } + + /// Pops a u32 value off the stack, `a`, and rotates the bits of `a` left by `imm` bits + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn rotl_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert!(imm < 32, "invalid rotation value: must be < 32, got {imm}"); + self.emit(masm::Instruction::U32RotlImm((imm as u8).into()), span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and rotates the bits of `a` right by `b` + /// bits + /// + /// Execution traps if `b` > 31. + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn rotr_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Rotr, span); + } + + /// Pops a u32 value off the stack, `a`, and rotates the bits of `a` right by `imm` bits + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn rotr_imm_u32(&mut self, imm: u32, span: SourceSpan) { + assert!(imm < 32, "invalid rotation value: must be < 32, got {imm}"); + self.emit(masm::Instruction::U32RotrImm((imm as u8).into()), span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the + /// stack + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn min_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Min, span); + } + + /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `min(a, b)` on the + /// stack + /// + /// This operation is checked, if the operands or result are not valid i32, execution traps. + pub fn min_i32(&mut self, span: SourceSpan) { + self.raw_exec("intrinsics::i32::min", span); + } + + /// Pops a u32 value off the stack, `a`, and puts the result of `min(a, imm)` on the stack + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn min_imm_u32(&mut self, imm: u32, span: SourceSpan) { + self.emit_all([masm::Instruction::PushU32(imm), masm::Instruction::U32Min], span); + } + + /// Pops a i32 value off the stack, `a`, and puts the result of `min(a, imm)` on the stack + /// + /// This operation is checked, if the operand or result are not valid i32, execution traps. + pub fn min_imm_i32(&mut self, imm: i32, span: SourceSpan) { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::min", span); + } + + /// Pops two u32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the + /// stack + /// + /// This operation is checked, if the operands or result are not valid u32, execution traps. + pub fn max_u32(&mut self, span: SourceSpan) { + self.emit(masm::Instruction::U32Max, span); + } + + /// Pops two i32 values off the stack, `b` and `a`, and puts the result of `max(a, b)` on the + /// stack + /// + /// This operation is checked, if the operands or result are not valid i32, execution traps. + pub fn max_i32(&mut self, span: SourceSpan) { + self.raw_exec("intrinsics::i32::max", span); + } + + /// Pops a u32 value off the stack, `a`, and puts the result of `max(a, imm)` on the stack + /// + /// This operation is checked, if the operand or result are not valid u32, execution traps. + pub fn max_imm_u32(&mut self, imm: u32, span: SourceSpan) { + self.emit_all([masm::Instruction::PushU32(imm), masm::Instruction::U32Max], span); + } + + /// Pops a i32 value off the stack, `a`, and puts the result of `max(a, imm)` on the stack + /// + /// This operation is checked, if the operand or result are not valid i32, execution traps. + pub fn max_imm_i32(&mut self, imm: i32, span: SourceSpan) { + self.emit(masm::Instruction::PushU32(imm as u32), span); + self.raw_exec("intrinsics::i32::max", span); + } +} diff --git a/codegen/masm/src/codegen/emit/int64.rs b/codegen/masm/src/emit/int64.rs similarity index 76% rename from codegen/masm/src/codegen/emit/int64.rs rename to codegen/masm/src/emit/int64.rs index 21f78402c..8fca61cdf 100644 --- a/codegen/masm/src/codegen/emit/int64.rs +++ b/codegen/masm/src/emit/int64.rs @@ -1,10 +1,10 @@ -use midenc_hir::{Felt, FieldElement, Overflow, SourceSpan}; +use miden_core::{Felt, FieldElement}; +use midenc_hir::{Overflow, SourceSpan, Span}; -use super::{OpEmitter, P}; -use crate::masm::{self as masm, Op}; +use super::{dup_from_offset, masm, movup_from_offset, OpEmitter, P}; #[allow(unused)] -impl<'a> OpEmitter<'a> { +impl OpEmitter<'_> { /// Convert a u64 value to felt. /// /// This operation will assert at runtime if the value is larger than the felt field. @@ -14,7 +14,7 @@ impl<'a> OpEmitter<'a> { // Assert that value is <= P, then unsplit the limbs to get a felt self.push_u64(P, span); self.lt_u64(span); - self.emit(Op::Assert, span); + self.emit(masm::Instruction::Assert, span); self.u32unsplit(span); } @@ -32,14 +32,14 @@ impl<'a> OpEmitter<'a> { /// Conversion will trap if the input value is too large to fit in an N-bit integer. pub fn u64_to_uint(&mut self, n: u32, span: SourceSpan) { self.emit_all( - &[ + [ // Assert hi bits are zero - Op::Assertz, + masm::Instruction::Assertz, // Check that the remaining bits fit in range - Op::Dup(0), - Op::Push(Felt::new(2u64.pow(n) - 1)), - Op::U32Lte, - Op::Assert, + masm::Instruction::Dup0, + masm::Instruction::PushFelt(Felt::new(2u64.pow(n) - 1)), + masm::Instruction::U32Lte, + masm::Instruction::Assert, ], span, ); @@ -50,16 +50,16 @@ impl<'a> OpEmitter<'a> { /// Conversion will trap if the input value is too large to fit in an N-bit integer. pub fn i64_to_int(&mut self, n: u32, span: SourceSpan) { self.emit_all( - &[ + [ // Assert hi bits are all zero or all one // [x_hi, x_hi, x_lo] - Op::Dup(0), + masm::Instruction::Dup0, // [is_unsigned, x_hi, x_lo] - Op::EqImm(Felt::ZERO), + masm::Instruction::EqImm(Felt::ZERO.into()), // [is_unsigned, is_unsigned, ..] - Op::Dup(0), + masm::Instruction::Dup0, // [is_unsigned, x_hi, is_unsigned, x_lo] - Op::Movdn(2), + masm::Instruction::MovDn2, ], span, ); @@ -67,11 +67,11 @@ impl<'a> OpEmitter<'a> { // [mask, x_hi, is_unsigned, x_lo] self.select_int32(0, u32::MAX, span); self.emit_all( - &[ + [ // [is_unsigned, x_lo] - Op::AssertEq, + masm::Instruction::AssertEq, // [x_lo, is_unsigned, x_lo] - Op::Dup(1), + masm::Instruction::Dup1, ], span, ); @@ -88,17 +88,17 @@ impl<'a> OpEmitter<'a> { // [sign_bits, is_unsigned, x_lo] self.const_mask_u32(!value_bits, span); self.emit_all( - &[ + [ // [sign_bits, sign_bits, ..] - Op::Dup(0), + masm::Instruction::Dup0, // [0, sign_bits, sign_bits, is_unsigned, x_lo] - Op::PushU32(0), + masm::Instruction::PushU32(0), // [is_unsigned, 0, sign_bits, sign_bits, x_lo] - Op::Movup(3), + masm::Instruction::MovUp3, // [expected_sign_bits, sign_bits, x_lo] - Op::Cdrop, + masm::Instruction::CDrop, // [x_lo] - Op::AssertEq, + masm::Instruction::AssertEq, ], span, ); @@ -134,7 +134,7 @@ impl<'a> OpEmitter<'a> { #[inline] pub fn trunc_int64(&mut self, n: u32, span: SourceSpan) { assert_valid_integer_size!(n, 1, 32); - self.emit(Op::Drop, span); + self.emit(masm::Instruction::Drop, span); match n { 32 => (), n => self.trunc_int32(n, span), @@ -164,7 +164,7 @@ impl<'a> OpEmitter<'a> { /// Assert that there is a valid 64-bit integer value on the operand stack pub fn assert_int64(&mut self, span: SourceSpan) { - self.emit(Op::U32Assert2, span); + self.emit(masm::Instruction::U32Assert2, span); } /// Checks if the 64-bit value on the stack has its sign bit set. @@ -189,7 +189,7 @@ impl<'a> OpEmitter<'a> { // the value is <= i64::MIN, which is 1 more than i64::MAX. self.push_i64(i64::MIN, span); self.lte_u64(span); - self.emit(Op::Assert, span); + self.emit(masm::Instruction::Assert, span); } /// Duplicate the i64/u64 value on top of the stack @@ -206,7 +206,7 @@ impl<'a> OpEmitter<'a> { pub fn copy_int64_from(&mut self, n: u8, span: SourceSpan) { assert_valid_stack_index!(n + 1); // copy limbs such that the order is preserved - self.emit_n(2, Op::Dup(n + 1), span); + self.emit_n(2, dup_from_offset(n as usize + 1), span); } /// Move a 64-bit value to the top of the stack, i.e. `movup(N)` for 64-bit values @@ -222,15 +222,16 @@ impl<'a> OpEmitter<'a> { 0 => (), 1 => { // Move the top of the stack past the 64 bit value - self.emit(Op::Movdn(2), span); + self.emit(masm::Instruction::MovDn2, span); } n => { + let n = n as usize; self.emit_all( - &[ + [ // Move the low 32 bits to the top - Op::Movup(n + 1), + movup_from_offset(n + 1), // Move the high 32 bits to the top - Op::Movup(n + 1), + movup_from_offset(n + 1), ], span, ); @@ -248,7 +249,7 @@ impl<'a> OpEmitter<'a> { #[inline] pub fn push_u64(&mut self, value: u64, span: SourceSpan) { let (hi, lo) = to_raw_parts(value); - from_raw_parts(lo, hi, self.current_block(), span); + from_raw_parts(lo, hi, self.current_block, span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a < b` on the stack. @@ -256,7 +257,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn lt_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::lt".parse().unwrap()), span); + self.raw_exec("std::math::u64::lt", span); } /// Pops two i64 values off the stack, `b` and `a`, and pushes `a < b` on the stack. @@ -264,7 +265,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn lt_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::lt".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::lt", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a <= b` on the stack. @@ -272,7 +273,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn lte_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::lte".parse().unwrap()), span); + self.raw_exec("std::math::u64::lte", span); } /// Pops two i64 values off the stack, `b` and `a`, and pushes `a <= b` on the stack. @@ -280,7 +281,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn lte_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::lte".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::lte", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a > b` on the stack. @@ -288,7 +289,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn gt_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::gt".parse().unwrap()), span); + self.raw_exec("std::math::u64::gt", span); } /// Pops two i64 values off the stack, `b` and `a`, and pushes `a > b` on the stack. @@ -296,7 +297,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn gt_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::gt".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::gt", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a >= b` on the stack. @@ -304,7 +305,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn gte_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::gte".parse().unwrap()), span); + self.raw_exec("std::math::u64::gte", span); } /// Pops two i64 values off the stack, `b` and `a`, and pushes `a >= b` on the stack. @@ -312,7 +313,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn gte_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::gte".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::gte", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a == b` on the stack. @@ -320,7 +321,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn eq_int64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::eq".parse().unwrap()), span); + self.raw_exec("std::math::u64::eq", span); } /// Pops a u64 value off the stack, `a`, and pushes `a == 0` on the stack. @@ -328,7 +329,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the value is not a valid u64, execution will trap. #[inline] pub fn is_zero_int64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::eqz".parse().unwrap()), span); + self.raw_exec("std::math::u64::eqz", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `min(a, b)` on the stack. @@ -336,19 +337,19 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn min_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::min".parse().unwrap()), span); + self.raw_exec("std::math::u64::min", span); } /// Pops two i64 values off the stack, `b` and `a`, and pushes `min(a, b)` on the stack. /// /// This operation is checked, so if the values are not valid i64, execution will trap. pub fn min_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::min".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::min", span); } pub fn min_imm_i64(&mut self, imm: i64, span: SourceSpan) { self.push_i64(imm, span); - self.emit(Op::Exec("intrinsics::i64::min".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::min", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `max(a, b)` on the stack. @@ -356,19 +357,19 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn max_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::max".parse().unwrap()), span); + self.raw_exec("std::math::u64::max", span); } /// Pops two i64 values off the stack, `b` and `a`, and pushes `max(a, b)` on the stack. /// /// This operation is checked, so if the values are not valid i64, execution will trap. pub fn max_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::max".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::max", span); } pub fn max_imm_i64(&mut self, imm: i64, span: SourceSpan) { self.push_i64(imm, span); - self.emit(Op::Exec("intrinsics::i64::max".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::max", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a != b` on the stack. @@ -376,7 +377,7 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, so if the values are not valid u64, execution will trap. #[inline] pub fn neq_int64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::neq".parse().unwrap()), span); + self.raw_exec("std::math::u64::neq", span); } /// Pops two u64 values off the stack, `b` and `a`, and performs `a + b`. @@ -395,16 +396,14 @@ impl<'a> OpEmitter<'a> { pub fn add_u64(&mut self, overflow: Overflow, span: SourceSpan) { match overflow { Overflow::Checked => { - self.emit_all( - &[Op::Exec("std::math::u64::overflowing_add".parse().unwrap()), Op::Assertz], - span, - ); + self.raw_exec("std::math::u64::overflowing_add", span); + self.emit(masm::Instruction::Assertz, span); } Overflow::Unchecked | Overflow::Wrapping => { - self.emit(Op::Exec("std::math::u64::wrapping_add".parse().unwrap()), span); + self.raw_exec("std::math::u64::wrapping_add", span); } Overflow::Overflowing => { - self.emit(Op::Exec("std::math::u64::overflowing_add".parse().unwrap()), span); + self.raw_exec("std::math::u64::overflowing_add", span); } } } @@ -414,15 +413,11 @@ impl<'a> OpEmitter<'a> { /// See the [Overflow] type for how overflow semantics can change the operation. #[inline(always)] pub fn add_i64(&mut self, overflow: Overflow, span: SourceSpan) { - self.emit( + self.raw_exec( match overflow { - Overflow::Unchecked | Overflow::Wrapping => { - Op::Exec("std::math::u64::wrapping_add".parse().unwrap()) - } - Overflow::Checked => Op::Exec("intrinsics::i64::checked_add".parse().unwrap()), - Overflow::Overflowing => { - Op::Exec("intrinsics::i64::overflowing_add".parse().unwrap()) - } + Overflow::Unchecked | Overflow::Wrapping => "std::math::u64::wrapping_add", + Overflow::Checked => "intrinsics::i64::checked_add", + Overflow::Overflowing => "intrinsics::i64::overflowing_add", }, span, ) @@ -442,11 +437,9 @@ impl<'a> OpEmitter<'a> { match overflow { Overflow::Unchecked | Overflow::Wrapping => self.add_u64(overflow, span), Overflow::Checked => { - self.emit(Op::Exec("intrinsics::i64::checked_add".parse().unwrap()), span); - } - Overflow::Overflowing => { - self.emit(Op::Exec("intrinsics::i64::overflowing_add".parse().unwrap()), span) + self.raw_exec("intrinsics::i64::checked_add", span); } + Overflow::Overflowing => self.raw_exec("intrinsics::i64::overflowing_add", span), } } @@ -466,16 +459,14 @@ impl<'a> OpEmitter<'a> { pub fn sub_u64(&mut self, overflow: Overflow, span: SourceSpan) { match overflow { Overflow::Checked => { - self.emit_all( - &[Op::Exec("std::math::u64::overflowing_sub".parse().unwrap()), Op::Assertz], - span, - ); + self.raw_exec("std::math::u64::overflowing_sub", span); + self.emit(masm::Instruction::Assertz, span); } Overflow::Unchecked | Overflow::Wrapping => { - self.emit(Op::Exec("std::math::u64::wrapping_sub".parse().unwrap()), span); + self.raw_exec("std::math::u64::wrapping_sub", span); } Overflow::Overflowing => { - self.emit(Op::Exec("std::math::u64::overflowing_sub".parse().unwrap()), span); + self.raw_exec("std::math::u64::overflowing_sub", span); } } } @@ -486,12 +477,8 @@ impl<'a> OpEmitter<'a> { pub fn sub_i64(&mut self, overflow: Overflow, span: SourceSpan) { match overflow { Overflow::Unchecked | Overflow::Wrapping => self.sub_u64(overflow, span), - Overflow::Checked => { - self.emit(Op::Exec("intrinsics::i64::checked_sub".parse().unwrap()), span) - } - Overflow::Overflowing => { - self.emit(Op::Exec("intrinsics::i64::overflowing_sub".parse().unwrap()), span) - } + Overflow::Checked => self.raw_exec("intrinsics::i64::checked_sub", span), + Overflow::Overflowing => self.raw_exec("intrinsics::i64::overflowing_sub", span), } } @@ -508,12 +495,8 @@ impl<'a> OpEmitter<'a> { self.push_i64(imm, span); match overflow { Overflow::Unchecked | Overflow::Wrapping => self.sub_u64(overflow, span), - Overflow::Checked => { - self.emit(Op::Exec("intrinsics::i64::checked_sub".parse().unwrap()), span) - } - Overflow::Overflowing => { - self.emit(Op::Exec("intrinsics::i64::overflowing_sub".parse().unwrap()), span) - } + Overflow::Checked => self.raw_exec("intrinsics::i64::checked_sub", span), + Overflow::Overflowing => self.raw_exec("intrinsics::i64::overflowing_sub", span), } } @@ -533,26 +516,16 @@ impl<'a> OpEmitter<'a> { pub fn mul_u64(&mut self, overflow: Overflow, span: SourceSpan) { match overflow { Overflow::Checked => { - self.emit_all( - &[ - Op::Exec("std::math::u64::overflowing_mul".parse().unwrap()), - Op::Exec("std::math::u64::overflowing_eqz".parse().unwrap()), - Op::Assertz, - ], - span, - ); + self.raw_exec("std::math::u64::overflowing_mul", span); + self.raw_exec("std::math::u64::eqz", span); + self.emit(masm::Instruction::Assertz, span); } Overflow::Unchecked | Overflow::Wrapping => { - self.emit(Op::Exec("std::math::u64::wrapping_mul".parse().unwrap()), span); + self.raw_exec("std::math::u64::wrapping_mul", span); } Overflow::Overflowing => { - self.emit_all( - &[ - Op::Exec("std::math::u64::overflowing_mul".parse().unwrap()), - Op::Exec("std::math::u64::overflowing_eqz".parse().unwrap()), - ], - span, - ); + self.raw_exec("std::math::u64::overflowing_mul", span); + self.raw_exec("std::math::u64::eqz", span); } } } @@ -563,14 +536,10 @@ impl<'a> OpEmitter<'a> { pub fn mul_i64(&mut self, overflow: Overflow, span: SourceSpan) { match overflow { Overflow::Unchecked | Overflow::Wrapping => { - self.emit(Op::Exec("intrinsics::i64::wrapping_mul".parse().unwrap()), span) - } - Overflow::Checked => { - self.emit(Op::Exec("intrinsics::i64::checked_mul".parse().unwrap()), span) - } - Overflow::Overflowing => { - self.emit(Op::Exec("intrinsics::i64::overflowing_mul".parse().unwrap()), span) + self.raw_exec("intrinsics::i64::wrapping_mul", span) } + Overflow::Checked => self.raw_exec("intrinsics::i64::checked_mul", span), + Overflow::Overflowing => self.raw_exec("intrinsics::i64::overflowing_mul", span), } } @@ -586,21 +555,29 @@ impl<'a> OpEmitter<'a> { pub fn mul_imm_i64(&mut self, imm: i64, overflow: Overflow, span: SourceSpan) { match imm { 0 => { - self.emit_all(&[Op::Drop, Op::Drop, Op::PushU32(0), Op::PushU32(0)], span); + self.emit_all( + [ + masm::Instruction::Drop, + masm::Instruction::Drop, + masm::Instruction::PushU32(0), + masm::Instruction::PushU32(0), + ], + span, + ); } 1 => (), imm => match overflow { Overflow::Unchecked | Overflow::Wrapping => { self.push_i64(imm, span); - self.emit(Op::Exec("intrinsics::i64::wrapping_mul".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::wrapping_mul", span); } Overflow::Checked => { self.push_i64(imm, span); - self.emit(Op::Exec("intrinsics::i64::checked_mul".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::checked_mul", span); } Overflow::Overflowing => { self.push_i64(imm, span); - self.emit(Op::Exec("intrinsics::i64::overflowing_mul".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::overflowing_mul", span); } }, } @@ -612,7 +589,8 @@ impl<'a> OpEmitter<'a> { /// Both the operands and result are validated to ensure they are valid u64 values. #[inline] pub fn checked_div_u64(&mut self, span: SourceSpan) { - self.emit_all(&[Op::U32Assertw, Op::Exec("std::math::u64::div".parse().unwrap())], span); + self.emit(masm::Instruction::U32AssertW, span); + self.raw_exec("std::math::u64::div", span); } /// Pops two i64 values off the stack, `b` and `a`, and pushes the result of `a / b` on the @@ -621,7 +599,7 @@ impl<'a> OpEmitter<'a> { /// Both the operands and result are validated to ensure they are valid u64 values. #[inline] pub fn checked_div_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::checked_div".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::checked_div", span); } /// Pops a i64 value off the stack, `a`, and performs `a / `. @@ -632,7 +610,7 @@ impl<'a> OpEmitter<'a> { pub fn checked_div_imm_i64(&mut self, imm: i64, span: SourceSpan) { assert_ne!(imm, 0, "division by zero is not allowed"); self.push_i64(imm, span); - self.emit(Op::Exec("intrinsics::i64::checked_div".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::checked_div", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a / b` on the @@ -641,7 +619,7 @@ impl<'a> OpEmitter<'a> { /// This operation is unchecked, it is up to the caller to ensure validity of the operands. #[inline] pub fn unchecked_div_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::div".parse().unwrap()), span); + self.raw_exec("std::math::u64::div", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a % b` on the @@ -650,7 +628,8 @@ impl<'a> OpEmitter<'a> { /// Both the operands and result are validated to ensure they are valid u64 values. #[inline] pub fn checked_mod_u64(&mut self, span: SourceSpan) { - self.emit_all(&[Op::U32Assertw, Op::Exec("std::math::u64::mod".parse().unwrap())], span); + self.emit(masm::Instruction::U32AssertW, span); + self.raw_exec("std::math::u64::mod", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes the result of `a % b` on the @@ -659,7 +638,7 @@ impl<'a> OpEmitter<'a> { /// This operation is unchecked, it is up to the caller to ensure validity of the operands. #[inline] pub fn unchecked_mod_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::mod".parse().unwrap()), span); + self.raw_exec("std::math::u64::mod", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the @@ -668,7 +647,8 @@ impl<'a> OpEmitter<'a> { /// Both the operands and result are validated to ensure they are valid u64 values. #[inline] pub fn checked_divmod_u64(&mut self, span: SourceSpan) { - self.emit_all(&[Op::U32Assertw, Op::Exec("std::math::u64::divmod".parse().unwrap())], span); + self.emit(masm::Instruction::U32AssertW, span); + self.raw_exec("std::math::u64::divmod", span); } /// Pops two u64 values off the stack, `b` and `a`, and pushes `a / b`, then `a % b` on the @@ -677,7 +657,7 @@ impl<'a> OpEmitter<'a> { /// This operation is unchecked, it is up to the caller to ensure validity of the operands. #[inline] pub fn unchecked_divmod_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::divmod".parse().unwrap()), span); + self.raw_exec("std::math::u64::divmod", span); } /// Pops two 64-bit values off the stack, `b` and `a`, and pushes `a & b` on the stack. @@ -685,7 +665,7 @@ impl<'a> OpEmitter<'a> { /// Both the operands and result are validated to ensure they are valid int64 values. #[inline] pub fn band_int64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::and".parse().unwrap()), span); + self.raw_exec("std::math::u64::and", span); } /// Pops two 64-bit values off the stack, `b` and `a`, and pushes `a | b` on the stack. @@ -693,7 +673,7 @@ impl<'a> OpEmitter<'a> { /// Both the operands and result are validated to ensure they are valid int64 values. #[inline] pub fn bor_int64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::or".parse().unwrap()), span); + self.raw_exec("std::math::u64::or", span); } /// Pops two 64-bit values off the stack, `b` and `a`, and pushes `a ^ b` on the stack. @@ -701,7 +681,7 @@ impl<'a> OpEmitter<'a> { /// Both the operands and result are validated to ensure they are valid int64 values. #[inline] pub fn bxor_int64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::xor".parse().unwrap()), span); + self.raw_exec("std::math::u64::xor", span); } /// Pops a u32 value, `b`, and a u64 value, `a`, off the stack and pushes `a << b` on the stack. @@ -711,7 +691,7 @@ impl<'a> OpEmitter<'a> { /// The operation will trap if the shift value is > 63. #[inline] pub fn shl_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::shl".parse().unwrap()), span); + self.raw_exec("std::math::u64::shl", span); } /// Pops a u32 value, `b`, and a u64 value, `a`, off the stack and pushes `a >> b` on the stack. @@ -721,7 +701,7 @@ impl<'a> OpEmitter<'a> { /// The operation will trap if the shift value is > 63. #[inline] pub fn shr_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::shr".parse().unwrap()), span); + self.raw_exec("std::math::u64::shr", span); } /// Arithmetic shift right (i.e. signedness is preserved) @@ -733,7 +713,7 @@ impl<'a> OpEmitter<'a> { /// The operation will trap if the shift value is > 63. #[inline] pub fn shr_i64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("intrinsics::i64::checked_shr".parse().unwrap()), span); + self.raw_exec("intrinsics::i64::checked_shr", span); } /// Pops a i64 value off the stack, `a`, and performs `a >> ` @@ -741,10 +721,8 @@ impl<'a> OpEmitter<'a> { /// This operation is checked, if the operand or result are not valid i64, execution traps. pub fn shr_imm_i64(&mut self, imm: u32, span: SourceSpan) { assert!(imm < 63, "invalid shift value: must be < 63, got {imm}"); - self.emit_all( - &[Op::PushU32(imm), Op::Exec("intrinsics::i64::checked_shr".parse().unwrap())], - span, - ); + self.emit(masm::Instruction::PushU32(imm), span); + self.raw_exec("intrinsics::i64::checked_shr", span); } /// Pops a u32 value, `b`, and a u64 value, `a`, off the stack and rotates the bitwise @@ -754,7 +732,7 @@ impl<'a> OpEmitter<'a> { /// The operation will trap if the rotation value is > 63. #[inline] pub fn rotl_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::rotl".parse().unwrap()), span); + self.raw_exec("std::math::u64::rotl", span); } /// Pops a u32 value, `b`, and a u64 value, `a`, off the stack and rotates the bitwise @@ -764,7 +742,7 @@ impl<'a> OpEmitter<'a> { /// The operation will trap if the rotation value is > 63. #[inline] pub fn rotr_u64(&mut self, span: SourceSpan) { - self.emit(Op::Exec("std::math::u64::rotr".parse().unwrap()), span); + self.raw_exec("std::math::u64::rotr", span); } } @@ -774,14 +752,21 @@ impl<'a> OpEmitter<'a> { /// and `lo` is the least significant limb. #[inline(always)] pub fn to_raw_parts(value: u64) -> (u32, u32) { - let bytes = value.to_le_bytes(); - let hi = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); - let lo = u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]); + let bytes = value.to_be_bytes(); + let hi = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + let lo = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]); (hi, lo) } /// Construct a u64/i64 constant from raw parts, i.e. two 32-bit little-endian limbs #[inline] -pub fn from_raw_parts(lo: u32, hi: u32, block: &mut masm::Block, span: SourceSpan) { - block.push(Op::Push2([Felt::new(lo as u64), Felt::new(hi as u64)]), span); +pub fn from_raw_parts(lo: u32, hi: u32, block: &mut Vec, span: SourceSpan) { + block.push(masm::Op::Inst(Span::new( + span, + masm::Instruction::PushFelt(Felt::new(lo as u64)), + ))); + block.push(masm::Op::Inst(Span::new( + span, + masm::Instruction::PushFelt(Felt::new(hi as u64)), + ))); } diff --git a/codegen/masm/src/emit/mem.rs b/codegen/masm/src/emit/mem.rs new file mode 100644 index 000000000..44adb4a1a --- /dev/null +++ b/codegen/masm/src/emit/mem.rs @@ -0,0 +1,1048 @@ +use miden_core::{Felt, FieldElement}; +use midenc_hir::{ + dialects::builtin::LocalVariable, AddressSpace, ArrayType, PointerType, SourceSpan, StructType, + Type, +}; + +use super::{masm, OpEmitter}; +use crate::lower::NativePtr; + +/// Allocation +impl OpEmitter<'_> { + /// Grow the heap (from the perspective of Wasm programs) by N pages, returning the previous + /// size of the heap (in pages) if successful, or -1 if the heap could not be grown. + pub fn mem_grow(&mut self, span: SourceSpan) { + let _num_pages = self.stack.pop().expect("operand stack is empty"); + self.raw_exec("intrinsics::mem::memory_grow", span); + self.push(Type::I32); + } + + /// Returns the size (in pages) of the heap (from the perspective of Wasm programs) + pub fn mem_size(&mut self, span: SourceSpan) { + self.raw_exec("intrinsics::mem::memory_size", span); + self.push(Type::U32); + } +} + +/// Loads +impl OpEmitter<'_> { + /// Load a value corresponding to the type of the given local, from the memory allocated for + /// that local. + /// + /// Internally, this pushes the address of the local on the stack, then delegates to + /// [OpEmitter::load] + pub fn load_local(&mut self, local: &LocalVariable, span: SourceSpan) { + let local_index = local.absolute_offset(); + let ty = local.ty(); + self.emit(masm::Instruction::Locaddr((local_index as u16).into()), span); + self.push(Type::from(PointerType::new_with_address_space( + ty.clone(), + AddressSpace::Element, + ))); + self.load(ty, span) + } + + /// Load a value corresponding to the pointee type of a pointer operand on the stack. + /// + /// The type of the pointer determines what address space the pointer value represents; + /// either the Miden-native address space (word-addressable), or the IR's byte-addressable + /// address space. + pub fn load(&mut self, ty: Type, span: SourceSpan) { + let ptr = self.stack.pop().expect("operand stack is empty"); + match ptr.ty() { + Type::Ptr(ref ptr_ty) => { + // Convert the pointer to a native pointer representation + self.convert_to_native_ptr(ptr_ty, span); + assert_eq!( + ty.size_in_bits(), + ptr_ty.pointee().size_in_bits(), + "The size of the type of the value being loaded ({ty}) is different from the \ + type of the pointee ({})", + ptr_ty.pointee() + ); + match &ty { + Type::I128 => self.load_quad_word(None, span), + Type::I64 | Type::U64 => self.load_double_word(None, span), + Type::Felt => self.load_felt(None, span), + Type::I32 | Type::U32 => self.load_word(None, span), + ty @ (Type::I16 | Type::U16 | Type::U8 | Type::I8 | Type::I1) => { + self.load_small(ty, None, span); + } + ty => todo!("support for loading {ty} is not yet implemented"), + } + self.push(ty); + } + ty if !ty.is_pointer() => { + panic!("invalid operand to load: expected pointer, got {ty}") + } + ty => unimplemented!("load support for pointers of type {ty} is not implemented"), + } + } + + /// Load a value of type `ty` from `addr`. + /// + /// NOTE: The address represented by `addr` is in the IR's byte-addressable address space. + #[allow(unused)] + pub fn load_imm(&mut self, addr: u32, ty: Type, span: SourceSpan) { + let ptr = NativePtr::from_ptr(addr); + match &ty { + Type::I128 => self.load_quad_word(Some(ptr), span), + Type::I64 | Type::U64 => self.load_double_word(Some(ptr), span), + Type::Felt => self.load_felt(Some(ptr), span), + Type::I32 | Type::U32 => self.load_word(Some(ptr), span), + Type::I16 | Type::U16 | Type::U8 | Type::I8 | Type::I1 => { + self.load_small(&ty, Some(ptr), span); + } + ty => todo!("support for loading {ty} is not yet implemented"), + } + self.push(ty); + } + + /// Emit a sequence of instructions to translate a raw pointer value to a native pointer value, + /// as a tuple of `(element_addr, byte_offset)`, in that order on the stack. + /// + /// Instructions which must act on a pointer will expect the stack to have these values in that + /// order so that they can perform any necessary re-alignment. + fn convert_to_native_ptr(&mut self, ty: &PointerType, span: SourceSpan) { + if ty.is_byte_pointer() { + self.emit_all( + [ + // Obtain the byte offset and element address + // + // [offset, addr] + masm::Instruction::U32DivModImm(4.into()), + // Swap fields into expected order + masm::Instruction::Swap1, + ], + span, + ); + } else { + self.emit_all([masm::Instruction::PushU8(0), masm::Instruction::Swap1], span); + } + } + + /// Push a [NativePtr] value to the operand stack in the expected stack representation. + fn push_native_ptr(&mut self, ptr: NativePtr, span: SourceSpan) { + self.emit_all( + [masm::Instruction::PushU8(ptr.offset), masm::Instruction::PushU32(ptr.addr)], + span, + ); + } + + /// Load a field element from a naturally aligned address, either immediate or dynamic + /// + /// A native pointer triplet is expected on the stack if an immediate is not given. + fn load_felt(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.load_felt_imm(imm, span); + } + + self.raw_exec("intrinsics::mem::load_felt", span); + } + + fn load_felt_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + assert!(ptr.is_element_aligned(), "felt values must be naturally aligned"); + self.emit(masm::Instruction::MemLoadImm(ptr.addr.into()), span); + } + + /// Loads a single 32-bit machine word, i.e. a single field element, not the Miden notion of a + /// word + /// + /// Expects a native pointer triplet on the stack if an immediate address is not given. + fn load_word(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.load_word_imm(imm, span); + } + + self.raw_exec("intrinsics::mem::load_sw", span); + } + + /// Loads a single 32-bit machine word from the given immediate address. + fn load_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + if ptr.is_element_aligned() { + self.emit_all( + [masm::Instruction::MemLoadImm(ptr.addr.into()), masm::Instruction::U32Assert], + span, + ); + } else { + // Delegate to load_sw intrinsic to handle the details of unaligned loads + self.push_native_ptr(ptr, span); + self.raw_exec("intrinsics::mem::load_sw", span); + } + } + + /// Load a pair of machine words (32-bit elements) to the operand stack + fn load_double_word(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.load_double_word_imm(imm, span); + } + + self.raw_exec("intrinsics::mem::load_dw", span); + } + + /// Load a sub-word value (u8, u16, etc.) from memory + /// + /// For sub-word loads, we need to load from the element-aligned address + /// and then extract the correct bits based on the byte offset. + /// + /// If `ptr` is None, this function expects the stack to contain: [element_addr, byte_offset] + /// If `ptr` is Some, it uses the immediate pointer value. + /// + /// The approach: + /// 1. Load the 32-bit word containing the target byte(s) + /// 2. Shift right by (byte_offset * 8) bits to move the target byte(s) to the low end + /// 3. Mask to extract only the bits we need based on the type size + /// + /// After execution, the stack will contain: [loaded_value] + fn load_small(&mut self, ty: &Type, ptr: Option, span: SourceSpan) { + // If we have an immediate pointer, handle it based on alignment + if let Some(imm) = ptr { + if imm.is_element_aligned() { + // For aligned loads, we can use MemLoadImm + self.emit(masm::Instruction::MemLoadImm(imm.addr.into()), span); + // The value is already loaded, just need to mask it + let mask = match ty.size_in_bits() { + 1 => 0x1, + 8 => 0xff, + 16 => 0xffff, + _ => unreachable!("load_small called with non-small type"), + }; + self.emit_all([masm::Instruction::PushU32(mask), masm::Instruction::U32And], span); + return; + } else { + self.emit(masm::Instruction::PushU32(imm.addr), span); + self.emit(masm::Instruction::PushU8(imm.offset), span); + } + } + + // Stack: [element_addr, byte_offset] + + // First, load the aligned word containing our value + // We need to temporarily move the offset out of the way + self.emit(masm::Instruction::Swap1, span); // [byte_offset, element_addr] + self.emit(masm::Instruction::Dup1, span); // [element_addr, byte_offset, element_addr] + self.emit(masm::Instruction::MemLoad, span); // [loaded_word, byte_offset, element_addr] + + // Now we need to extract the correct byte(s) based on the offset + // Stack: [loaded_word, byte_offset, element_addr] + self.emit(masm::Instruction::Swap1, span); // [byte_offset, loaded_word, element_addr] + + // Shift right by (offset * 8) bits to get our byte at the low end + self.emit_all( + [ + masm::Instruction::PushU8(8), // [8, byte_offset, loaded_word, element_addr] + masm::Instruction::U32WrappingMul, // [offset*8, loaded_word, element_addr] + masm::Instruction::U32Shr, // [shifted_word, element_addr] + ], + span, + ); + + // Clean up the element address + self.emit(masm::Instruction::Swap1, span); // [element_addr, shifted_word] + self.emit(masm::Instruction::Drop, span); // [shifted_word] + + // Mask to get only the bits we need + let mask = match ty.size_in_bits() { + 1 => 0x1, + 8 => 0xff, + 16 => 0xffff, + _ => unreachable!("load_small called with non-small type"), + }; + + self.emit_all([masm::Instruction::PushU32(mask), masm::Instruction::U32And], span); + } + + fn load_double_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + if ptr.is_element_aligned() { + self.emit_all( + [ + masm::Instruction::MemLoadImm((ptr.addr + 1).into()), + masm::Instruction::MemLoadImm(ptr.addr.into()), + masm::Instruction::U32Assert2, + ], + span, + ) + } else { + // Delegate to load_dw to handle the details of unaligned loads + self.push_native_ptr(ptr, span); + self.raw_exec("intrinsics::mem::load_dw", span); + } + } + + /// Load a quartet of machine words (32-bit elements) to the operand stack + fn load_quad_word(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.load_quad_word_imm(imm, span); + } + self.raw_exec("intrinsics::mem::load_qw", span); + } + + fn load_quad_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + // For all other cases, more complicated loads are required + if ptr.is_word_aligned() { + self.emit_all( + [ + // [w3, w2, w1, w0] + masm::Instruction::PadW, + masm::Instruction::MemLoadWImm(ptr.addr.into()), + // Swap the element order to lowest-address-first + // [w2, w1, w0, w3] + masm::Instruction::MovDn3, + // [w1, w0, w2, w3] + masm::Instruction::MovDn2, + // [w0, w1, w2, w3] + masm::Instruction::Swap1, + masm::Instruction::U32AssertW, + ], + span, + ); + } else if ptr.is_element_aligned() { + self.emit_all( + [ + masm::Instruction::MemLoadImm((ptr.addr + 3).into()), + masm::Instruction::MemLoadImm((ptr.addr + 2).into()), + masm::Instruction::MemLoadImm((ptr.addr + 1).into()), + masm::Instruction::MemLoadImm(ptr.addr.into()), + masm::Instruction::U32AssertW, + ], + span, + ); + } else { + // Delegate to load_qw to handle the details of unaligned loads + self.push_native_ptr(ptr, span); + self.raw_exec("intrinsics::mem::load_qw", span); + } + } + + /// This handles emitting code that handles aligning an unaligned double machine-word value + /// which is split across three machine words (field elements). + /// + /// To recap: + /// + /// * A machine word is a 32-bit chunk stored in a single field element + /// * A double word is a pair of 32-bit chunks + /// * A quad word is a quartet of 32-bit chunks (i.e. a Miden "word") + /// * An unaligned double-word requires three 32-bit chunks to represent, since the first chunk + /// does not contain a full 32-bits, so an extra is needed to hold those bits. + /// + /// As an example, assume the pointer we are dereferencing is a u64 value, + /// which has 8-byte alignment, and the value is stored 40 bytes from the + /// nearest quad-word-aligned boundary. To load the value, we must fetch + /// the full quad-word from the aligned address, drop the first word, as + /// it is unused, and then recombine the 64 bits we need spread across + /// the remaining three words to obtain the double-word value we actually want. + /// + /// The data, on the stack, is shown below: + /// + /// ```text,ignore + /// # If we visualize which bytes are contained in each 32-bit chunk on the stack, we get: + /// [0..=4, 5..=8, 9..=12] + /// + /// # These byte indices are relative to the nearest word-aligned address, in the same order + /// # as they would occur in a byte-addressable address space. The significance of each byte + /// # depends on the value being dereferenced, but Miden is a little-endian machine, so typically + /// # the most significant bytes come first (i.e. also commonly referred to as "high" vs "low" bits). + /// # + /// # If we visualize the layout of the bits of our u64 value spread across the three chunks, we get: + /// [00000000111111111111111111111111, 111111111111111111111111111111, 11111111111111111111111100000000] + /// ``` + /// + /// As illustrated above, what should be a double-word value is occupying three words. To + /// "realign" the value, i.e. ensure that it is naturally aligned and fits in two words, we + /// have to perform a sequence of shifts and masks to get the bits where they belong. This + /// function performs those steps, with the assumption that the caller has three values on + /// the operand stack representing any unaligned double-word value + #[allow(unused)] + fn realign_double_word(&mut self, _ptr: NativePtr, span: SourceSpan) { + self.raw_exec("intrinsics::mem::realign_dw", span); + } + + /// This handles emitting code that handles aligning an unaligned quad machine-word value + /// which is split across five machine words (field elements). + /// + /// To recap: + /// + /// * A machine word is a 32-bit chunk stored in a single field element + /// * A double word is a pair of 32-bit chunks + /// * A quad word is a quartet of 32-bit chunks (i.e. a Miden "word") + /// * An unaligned quad-word requires five 32-bit chunks to represent, since the first chunk + /// does not contain a full 32-bits, so an extra is needed to hold those bits. + /// + /// See the example in [OpEmitter::realign_quad_word] for more details on how bits are + /// laid out in each word, and what is required to realign unaligned words. + #[allow(unused)] + fn realign_quad_word(&mut self, ptr: NativePtr, span: SourceSpan) { + // The stack starts as: [chunk_hi, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo] + // + // We will refer to the parts of our desired quad-word value + // as four parts, `x_hi2`, `x_hi1`, `x_lo2`, and `x_lo1`, where + // the integer suffix should appear in decreasing order on the + // stack when we're done. + self.emit_all( + [ + // Re-align the high bits by shifting out the offset + // + // This gives us the first half of `x_hi2`. + // + // [x_hi2_hi, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk__lo] + masm::Instruction::U32ShlImm(ptr.offset.into()), + // Move the value below the other chunks temporarily + // + // [chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk__lo, x_hi2_hi] + masm::Instruction::MovDn5, + // We must split the `chunk_mid_hi` chunk into two parts, + // one containing the bits to be combined with `x_hi2_hi`; + // the other to be combined with `x_hi1_hi`. + // + // First, we duplicate the chunk, since we need two + // copies of it: + // + // [chunk_mid_hi, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2_hi] + masm::Instruction::Dup0, + // Then, we shift the chunk right by 32 - offset bits, + // re-aligning the low bits of `x_hi2`, and isolating them. + // + // [x_hi2_lo, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2_hi] + masm::Instruction::U32ShrImm((32 - ptr.offset).into()), + // Move the high bits of `x_hi2` back to the top + // + // [x_hi2_hi, x_hi2_lo, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo] + masm::Instruction::MovUp3, + // OR the two parts of the `x_hi2` chunk together + // + // [x_hi2, chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo] + masm::Instruction::U32Or, + // Move `x_hi2` to the bottom for later + // + // [chunk_mid_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2] + masm::Instruction::MovDn5, + // Now, we need to re-align the high bits of `x_hi1` by shifting + // the remaining copy of `chunk_mid_hi`, similar to what we did for `x_hi2` + // + // This gives us the first half of `x_hi1` + // + // [x_hi1_hi, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2] + masm::Instruction::U32ShlImm(ptr.offset.into()), + // Next, move the chunk containing the low bits of `x_hi1` to the top temporarily + // + // [chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1_hi] + masm::Instruction::MovDn5, + // Duplicate it, as we need two copies + // + // [chunk_mid_mid, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1_hi] + masm::Instruction::Dup0, + // Shift the value right, as done previously for the low bits of `x_hi2` + // + // [x_hi1_lo, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1_hi] + masm::Instruction::U32ShrImm((32 - ptr.offset).into()), + // Move the high bits of `x_hi1` to the top + masm::Instruction::MovUp5, + // OR the two halves together, giving us our second word, `x_hi1` + // + // [x_hi1, chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2] + masm::Instruction::U32Or, + // Move the word to the bottom of the stack + // + // [chunk_mid_mid, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] + masm::Instruction::MovDn5, + // Now, we need to re-align the high bits of `x_lo2` by shifting + // the remaining copy of `chunk_mid_mid`, as done previously. + // + // [x_lo2_hi, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] + masm::Instruction::U32ShlImm(ptr.offset.into()), + // Next, move the chunk containing the low bits of `x_lo2` to the top temporarily + // + // [chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2_hi] + masm::Instruction::MovDn5, + // Duplicate it, as done previously + // + // [chunk_mid_lo, chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2_hi] + masm::Instruction::Dup0, + // Shift the value right to get the low bits of `x_lo2` + // + // [x_lo2_lo, chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2_hi] + masm::Instruction::U32ShrImm((32 - ptr.offset).into()), + // Move the high bits of `x_lo2` to the top + // + // [x_lo2_hi, x_lo2_lo, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] + masm::Instruction::MovUp6, + // OR the two halves together, giving us our third word, `x_lo2` + // + // [x_lo2, chunk_mid_lo, chunk_lo, x_hi2, x_hi1] + masm::Instruction::U32Or, + // Move to the bottom of the stack + // + // [chunk_mid_lo, chunk_lo, x_hi2, x_hi1, x_lo2] + masm::Instruction::MovDn5, + // Re-align the high bits of `x_lo1` + // + // [x_lo1_hi, chunk_lo, x_hi2, x_hi1, x_lo2] + masm::Instruction::U32ShlImm(ptr.offset.into()), + // Move the chunk containing the low bits to the top + // + // [chunk_lo, x_hi2, x_hi1, x_lo2, x_lo1_hi] + masm::Instruction::MovDn5, + // Shift the value right to get the low bits of `x_lo1` + masm::Instruction::U32ShrImm((32 - ptr.offset).into()), + // Move the high bits of `x_lo1` to the top + // + // [x_lo1_hi, x_lo1_lo, x_hi2, x_hi1, x_lo2] + masm::Instruction::MovUp5, + // OR the two halves together, giving us our fourth word, `x_lo1` + // + // [x_lo1, x_hi2, x_hi1, x_lo2] + masm::Instruction::U32Or, + // Move to the bottom + // + // [x_hi2, x_hi1, x_lo2, x_lo1] + masm::Instruction::MovDn5, + ], + span, + ); + } +} + +/// Stores +impl OpEmitter<'_> { + /// Store a value of the type given by the specified [hir::LocalId], using the memory allocated + /// for that local. + /// + /// Internally, this pushes the address of the given local on the stack, and delegates to + /// [OpEmitter::store] to perform the actual store. + pub fn store_local(&mut self, local: &LocalVariable, span: SourceSpan) { + let local_index = local.absolute_offset(); + self.emit(masm::Instruction::Locaddr((local_index as u16).into()), span); + self.push(Type::from(PointerType::new_with_address_space( + local.ty(), + AddressSpace::Element, + ))); + self.store(span) + } + + /// Store a value of type `value` to the address in the Miden address space + /// which corresponds to a pointer in the IR's byte-addressable address space. + /// + /// The type of the pointer is given as `ptr`, and can be used for both validation and + /// determining alignment. + pub fn store(&mut self, span: SourceSpan) { + let ptr = self.stack.pop().expect("operand stack is empty"); + let value = self.stack.pop().expect("operand stack is empty"); + let ptr_ty = ptr.ty(); + assert!(ptr_ty.is_pointer(), "expected store operand to be a pointer, got {ptr_ty}"); + let value_ty = value.ty(); + assert!(!value_ty.is_zst(), "cannot store a zero-sized type in memory"); + match ptr_ty { + Type::Ptr(ref ptr_ty) => { + // Convert the pointer to a native pointer representation + self.convert_to_native_ptr(ptr_ty, span); + assert_eq!( + value_ty.size_in_bits(), + ptr_ty.pointee().size_in_bits(), + "The size of the type of the value being stored ({value_ty}) is different \ + from the type of the pointee ({})", + ptr_ty.pointee() + ); + match value_ty { + Type::I128 => self.store_quad_word(None, span), + Type::I64 | Type::U64 => self.store_double_word(None, span), + Type::Felt => self.store_felt(None, span), + Type::I32 | Type::U32 => self.store_word(None, span), + ref ty if ty.size_in_bytes() <= 4 => self.store_small(ty, None, span), + Type::Array(ref array_ty) => self.store_array(array_ty, None, span), + Type::Struct(ref struct_ty) => self.store_struct(struct_ty, None, span), + ty => unimplemented!( + "invalid store: support for storing {ty} has not been implemented" + ), + } + } + ty if !ty.is_pointer() => { + panic!("invalid operand to store: expected pointer, got {ty}") + } + ty => unimplemented!("store support for pointers of type {ty} is not implemented"), + } + } + + /// Store a value of type `ty` to `addr`. + /// + /// NOTE: The address represented by `addr` is in the IR's byte-addressable address space. + pub fn store_imm(&mut self, addr: u32, span: SourceSpan) { + let value = self.stack.pop().expect("operand stack is empty"); + let value_ty = value.ty(); + assert!(!value_ty.is_zst(), "cannot store a zero-sized type in memory"); + let ptr = NativePtr::from_ptr(addr); + match value_ty { + Type::I128 => self.store_quad_word(Some(ptr), span), + Type::I64 | Type::U64 => self.store_double_word(Some(ptr), span), + Type::Felt => self.store_felt(Some(ptr), span), + Type::I32 | Type::U32 => self.store_word(Some(ptr), span), + ref ty if ty.size_in_bytes() <= 4 => self.store_small(ty, Some(ptr), span), + Type::Array(ref array_ty) => self.store_array(array_ty, Some(ptr), span), + Type::Struct(ref struct_ty) => self.store_struct(struct_ty, Some(ptr), span), + ty => { + unimplemented!("invalid store: support for storing {ty} has not been implemented") + } + } + } + + /// Write `count` copies of `value` to `dst` + pub fn memset(&mut self, span: SourceSpan) { + let dst = self.stack.pop().expect("operand stack is empty"); + let count = self.stack.pop().expect("operand stack is empty"); + let value = self.stack.pop().expect("operand stack is empty"); + assert_eq!(count.ty(), Type::U32, "expected count operand to be a u32"); + let ty = value.ty(); + assert!(dst.ty().is_pointer()); + assert_eq!(&ty, dst.ty().pointee().unwrap(), "expected value and pointee type to match"); + let value_size = u32::try_from(ty.size_in_bytes()).expect("invalid value size"); + + // Create new block for loop body and switch to it temporarily + let mut body = Vec::default(); + let mut body_emitter = OpEmitter::new(self.invoked, &mut body, self.stack); + + // Loop body - compute address for next value to be written + body_emitter.emit_all( + [ + // [i, dst, count, value..] + // Offset the pointer by the current iteration count * aligned size of value, and + // trap if it overflows + masm::Instruction::Dup1, // [dst, i, dst, count, value] + masm::Instruction::Dup1, // [i, dst, i, dst, count, value] + masm::Instruction::PushU32(value_size), /* [value_size, i, + * dst, ..] */ + masm::Instruction::U32OverflowingMadd, // [value_size * i + dst, i, dst, count, value] + masm::Instruction::Assertz, // [aligned_dst, i, dst, count, value..] + ], + span, + ); + + // Loop body - move value to top of stack, swap with pointer + body_emitter.push(value); + body_emitter.push(count); + body_emitter.push(dst.clone()); + body_emitter.push(dst.ty()); + body_emitter.push(dst.ty()); + body_emitter.dup(4, span); // [value, aligned_dst, i, dst, count, value] + body_emitter.swap(1, span); // [aligned_dst, value, i, dst, count, value] + + // Loop body - write value to destination + body_emitter.store(span); // [i, dst, count, value] + + // Loop body - increment iteration count, determine whether to continue loop + body_emitter.emit_all( + [ + masm::Instruction::U32WrappingAddImm(1.into()), + masm::Instruction::Dup0, // [i++, i++, dst, count, value] + masm::Instruction::Dup3, // [count, i++, i++, dst, count, value] + masm::Instruction::U32Gte, // [i++ >= count, i++, dst, count, value] + ], + span, + ); + + // Switch back to original block and emit loop header and 'while.true' instruction + // + // Loop header - prepare to loop until `count` iterations have been performed + self.emit_all( + [ + // [dst, count, value..] + masm::Instruction::PushU32(0), // [i, dst, count, value..] + masm::Instruction::Dup2, // [count, i, dst, count, value..] + masm::Instruction::PushFelt(Felt::ZERO), + masm::Instruction::Gte, // [count > 0, i, dst, count, value..] + ], + span, + ); + self.current_block.push(masm::Op::While { + span, + body: masm::Block::new(span, body), + }); + + // Cleanup - at end of 'while' loop, drop the 4 operands remaining on the stack + self.dropn(4, span); + } + + /// Copy `count * sizeof(*ty)` from a source address to a destination address. + /// + /// The order of operands on the stack is `src`, `dst`, then `count`. + /// + /// The addresses on the stack are interpreted based on the pointer type: native pointers are + /// in the Miden address space; non-native pointers are assumed to be in the IR's byte + /// addressable address space, and require translation. + /// + /// The semantics of this instruction are as follows: + /// + /// * The `` + pub fn memcpy(&mut self, span: SourceSpan) { + let src = self.stack.pop().expect("operand stack is empty"); + let dst = self.stack.pop().expect("operand stack is empty"); + let count = self.stack.pop().expect("operand stack is empty"); + assert_eq!(count.ty(), Type::U32, "expected count operand to be a u32"); + let ty = src.ty(); + assert!(ty.is_pointer()); + assert_eq!(ty, dst.ty(), "expected src and dst operands to have the same type"); + let value_ty = ty.pointee().unwrap(); + let value_size = u32::try_from(value_ty.size_in_bytes()).expect("invalid value size"); + + // Use optimized intrinsics when available + match value_size { + // Word-sized values have an optimized intrinsic we can lean on + 16 => { + // We have to convert byte addresses to element addresses + self.emit_all( + [ + // Convert `src` to element address, and assert aligned to an element address + // + // TODO: We should probably also assert that the address is word-aligned, but + // that is going to happen anyway. That said, the closer to the source the + // better for debugging. + masm::Instruction::U32DivModImm(4.into()), + masm::Instruction::Assertz, + // Convert `dst` to an element address the same way + masm::Instruction::Swap1, + masm::Instruction::U32DivModImm(4.into()), + masm::Instruction::Assertz, + // Swap with `count` to get us into the correct ordering: [count, src, dst] + masm::Instruction::Swap2, + ], + span, + ); + self.raw_exec("std::mem::memcopy_words", span); + return; + } + // Values which can be broken up into word-sized chunks can piggy-back on the + // intrinsic for word-sized values, but we have to compute a new `count` by + // multiplying `count` by the number of words in each value + size if size > 16 && size.is_multiple_of(16) => { + let factor = size / 16; + self.emit_all( + [ + // Convert `src` to element address, and assert aligned to an element address + // + // TODO: We should probably also assert that the address is word-aligned, but + // that is going to happen anyway. That said, the closer to the source the + // better for debugging. + masm::Instruction::U32DivModImm(4.into()), + masm::Instruction::Assertz, + // Convert `dst` to an element address the same way + masm::Instruction::Swap1, + masm::Instruction::U32DivModImm(4.into()), + masm::Instruction::Assertz, + // Swap with `count` to get us into the correct ordering: [count, src, dst] + masm::Instruction::Swap2, + // Compute the corrected count + masm::Instruction::U32OverflowingMulImm(factor.into()), + masm::Instruction::Assertz, // [count * (size / 16), src, dst] + ], + span, + ); + self.raw_exec("std::mem::memcopy_words", span); + return; + } + // For now, all other values fallback to the default implementation + _ => (), + } + + // Create new block for loop body and switch to it temporarily + let mut body = Vec::default(); + let mut body_emitter = OpEmitter::new(self.invoked, &mut body, self.stack); + + // Loop body - compute address for next value to be written + // Compute the source and destination addresses + body_emitter.emit_all( + [ + // [i, src, dst, count] + masm::Instruction::Dup2, // [dst, i, src, dst, count] + masm::Instruction::Dup1, // [i, dst, i, src, dst, count] + masm::Instruction::PushU32(value_size), // [offset, i, dst, i, src, dst, count] + masm::Instruction::U32OverflowingMadd, + masm::Instruction::Assertz, // [new_dst := i * offset + dst, i, src, dst, count] + masm::Instruction::Dup2, // [src, new_dst, i, src, dst, count] + masm::Instruction::Dup2, // [i, src, new_dst, i, src, dst, count] + masm::Instruction::PushU32(value_size), // [offset, i, src, new_dst, i, src, dst, count] + masm::Instruction::U32OverflowingMadd, + masm::Instruction::Assertz, // [new_src := i * offset + src, new_dst, i, src, dst, count] + ], + span, + ); + + // Load the source value + body_emitter.push(count.clone()); + body_emitter.push(dst.clone()); + body_emitter.push(src.clone()); + body_emitter.push(Type::U32); + body_emitter.push(dst.clone()); + body_emitter.push(src.clone()); + body_emitter.load(value_ty.clone(), span); // [value, new_dst, i, src, dst, count] + + // Write to the destination + body_emitter.swap(1, span); // [new_dst, value, i, src, dst, count] + body_emitter.store(span); // [i, src, dst, count] + + // Increment iteration count, determine whether to continue loop + body_emitter.emit_all( + [ + masm::Instruction::U32WrappingAddImm(1.into()), + masm::Instruction::Dup0, // [i++, i++, src, dst, count] + masm::Instruction::Dup4, // [count, i++, i++, src, dst, count] + masm::Instruction::U32Gte, // [i++ >= count, i++, src, dst, count] + ], + span, + ); + + // Switch back to original block and emit loop header and 'while.true' instruction + // + // Loop header - prepare to loop until `count` iterations have been performed + self.emit_all( + [ + // [src, dst, count] + masm::Instruction::PushU32(0), // [i, src, dst, count] + masm::Instruction::Dup3, // [count, i, src, dst, count] + masm::Instruction::PushFelt(Felt::ZERO), + masm::Instruction::Gte, // [count > 0, i, src, dst, count] + ], + span, + ); + self.current_block.push(masm::Op::While { + span, + body: masm::Block::new(span, body), + }); + + // Cleanup - at end of 'while' loop, drop the 4 operands remaining on the stack + self.dropn(4, span); + } + + /// Store a quartet of machine words (32-bit elements) to the operand stack + fn store_quad_word(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.store_quad_word_imm(imm, span); + } + self.raw_exec("intrinsics::mem::store_qw", span); + } + + fn store_quad_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + if ptr.is_word_aligned() { + self.emit_all( + [ + // Stack: [a, b, c, d] + masm::Instruction::U32AssertW, + // Swap to highest-address-first order + // [d, b, c, a] + masm::Instruction::Swap3, + // [c, d, b, a] + masm::Instruction::MovUp2, + // [d, c, b, a] + masm::Instruction::Swap1, + // Write to heap + masm::Instruction::MemStoreWImm(ptr.addr.into()), + masm::Instruction::DropW, + ], + span, + ); + } else if ptr.is_element_aligned() { + self.emit_all( + [ + masm::Instruction::U32AssertW, + masm::Instruction::MemStoreImm(ptr.addr.into()), + masm::Instruction::MemStoreImm((ptr.addr + 1).into()), + masm::Instruction::MemStoreImm((ptr.addr + 2).into()), + masm::Instruction::MemStoreImm((ptr.addr + 3).into()), + ], + span, + ); + } else { + // Delegate to `store_qw` to handle unaligned stores + self.push_native_ptr(ptr, span); + self.raw_exec("intrinsics::mem::store_qw", span); + } + } + + /// Store a pair of machine words (32-bit elements) to the operand stack + fn store_double_word(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.store_double_word_imm(imm, span); + } + + self.raw_exec("intrinsics::mem::store_dw", span); + } + + fn store_double_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + if ptr.is_element_aligned() { + self.emit_all( + [ + masm::Instruction::U32Assert2, + masm::Instruction::MemStoreImm(ptr.addr.into()), + masm::Instruction::MemStoreImm((ptr.addr + 1).into()), + ], + span, + ); + } else { + // Delegate to `store_dw` to handle unaligned stores + self.push_native_ptr(ptr, span); + self.raw_exec("intrinsics::mem::store_dw", span); + } + } + + /// Stores a single 32-bit machine word, i.e. a single field element, not the Miden notion of a + /// word + /// + /// Expects a native pointer triplet on the stack if an immediate address is not given. + fn store_word(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.store_word_imm(imm, span); + } + + self.raw_exec("intrinsics::mem::store_sw", span); + } + + /// Stores a single 32-bit machine word to the given immediate address. + fn store_word_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + if ptr.is_element_aligned() { + self.emit_all( + [masm::Instruction::U32Assert, masm::Instruction::MemStoreImm(ptr.addr.into())], + span, + ); + } else { + // Delegate to `store_sw` to handle unaligned stores + self.push_native_ptr(ptr, span); + self.raw_exec("intrinsics::mem::store_sw", span); + } + } + + /// Store a field element to a naturally aligned address, either immediate or dynamic + /// + /// A native pointer triplet is expected on the stack if an immediate is not given. + fn store_felt(&mut self, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + return self.store_felt_imm(imm, span); + } + + self.raw_exec("intrinsics::mem::store_felt", span); + } + + fn store_felt_imm(&mut self, ptr: NativePtr, span: SourceSpan) { + assert!(ptr.is_element_aligned(), "felt values must be naturally aligned"); + self.emit(masm::Instruction::MemStoreImm(ptr.addr.into()), span); + } + + /// Store a sub-word value (u8, u16, etc.) to memory + /// + /// For sub-word stores, we need to: + /// 1. Load the current 32-bit word at the target address + /// 2. Mask out the bits where we'll place the new value + /// 3. Shift the new value to the correct bit position + /// 4. Combine with OR and store back + /// + /// This function expects the stack to contain: [addr, offset, value] + /// where: + /// - addr: The element-aligned address + /// - offset: The byte offset within the element (0-3) + /// - value: The value to store (already truncated to the correct size) + /// + /// The approach preserves other bytes in the same word while updating only + /// the target byte(s). + fn store_small(&mut self, ty: &Type, ptr: Option, span: SourceSpan) { + if let Some(imm) = ptr { + self.store_small_imm(ty, imm, span); + return; + } + + let type_size = ty.size_in_bits(); + if type_size == 32 { + self.store_word(ptr, span); + return; + } + + // Stack: [addr, offset, value] + // Load the current aligned value + self.emit_all( + [ + masm::Instruction::Dup0, // [addr, addr, offset, value] + masm::Instruction::MemLoad, // [prev, addr, offset, value] + ], + span, + ); + + // Calculate bit offset and create mask + let type_size_mask = (1u32 << type_size) - 1; + self.emit_all( + [ + masm::Instruction::Dup2, // [offset, prev, addr, offset, value] + masm::Instruction::PushU8(8), // [8, offset, prev, addr, offset, value] + masm::Instruction::U32WrappingMul, // [bit_offset, prev, addr, offset, value] + masm::Instruction::PushU32(type_size_mask), // [type_mask, bit_offset, prev, addr, offset, value] + masm::Instruction::Swap1, // [bit_offset, type_mask, prev, addr, offset, value] + masm::Instruction::U32Shl, // [shifted_mask, prev, addr, offset, value] + masm::Instruction::U32Not, // [mask, prev, addr, offset, value] + masm::Instruction::Swap1, // [prev, mask, addr, offset, value] + masm::Instruction::U32And, // [masked_prev, addr, offset, value] + ], + span, + ); + + // Shift value to correct position and combine + self.emit_all( + [ + masm::Instruction::MovUp3, // [value, masked_prev, addr, offset] + masm::Instruction::MovUp3, // [offset, value, masked_prev, addr] + masm::Instruction::PushU8(8), // [8, offset, value, masked_prev, addr] + masm::Instruction::U32WrappingMul, // [bit_offset, value, masked_prev, addr] + masm::Instruction::U32Shl, // [shifted_value, masked_prev, addr] + masm::Instruction::U32Or, // [new_value, addr] + masm::Instruction::Swap1, // [addr, new_value] + masm::Instruction::MemStore, // [] + ], + span, + ); + } + + /// Store a sub-word value using an immediate pointer + /// + /// This function stores sub-word values (u8, u16, etc.) to memory at a specific immediate address. + /// It handles both aligned and unaligned addresses by: + /// 1. Loading the current 32-bit word at the element-aligned address + /// 2. Masking out the bits where the new value will be placed + /// 3. Shifting the new value to the correct bit position based on the byte offset + /// 4. Combining with OR and storing back + /// + /// # Stack Effects + /// - Before: [value] (where value is already truncated to the correct size) + /// - After: [] + fn store_small_imm(&mut self, ty: &Type, imm: NativePtr, span: SourceSpan) { + assert!(imm.alignment() as usize >= ty.min_alignment()); + + // For immediate pointers, we always load from the element-aligned address + // The offset determines which byte(s) within that element we're modifying + self.emit(masm::Instruction::MemLoadImm(imm.addr.into()), span); + + // Calculate bit offset from byte offset + let bit_offset = imm.offset * 8; + + // Create a mask that clears the bits where we'll place the new value + let type_size = ty.size_in_bits(); + debug_assert!(type_size < 32, "type_size must be less than 32 bits"); + let type_size_mask = (1u32 << type_size) - 1; + let mask = !(type_size_mask << bit_offset); + + // Apply mask to the loaded value + self.const_mask_u32(mask, span); + + // Get the value and shift it to the correct position + self.emit(masm::Instruction::MovUp4, span); // Move value to top + if bit_offset > 0 { + self.emit(masm::Instruction::U32ShlImm(bit_offset.into()), span); + } + + // Combine the masked previous value with the shifted new value + self.bor_u32(span); + + // Store the combined bits back to the element-aligned address + self.emit(masm::Instruction::MemStoreImm(imm.addr.into()), span); + } + + fn store_array(&mut self, _ty: &ArrayType, _ptr: Option, _span: SourceSpan) { + todo!() + } + + fn store_struct(&mut self, _ty: &StructType, _ptr: Option, _span: SourceSpan) { + todo!() + } +} diff --git a/codegen/masm/src/emit/mod.rs b/codegen/masm/src/emit/mod.rs new file mode 100644 index 000000000..3fa0a1e3f --- /dev/null +++ b/codegen/masm/src/emit/mod.rs @@ -0,0 +1,2073 @@ +use midenc_session::diagnostics::Span; + +/// The field modulus for Miden's prime field +pub const P: u64 = (2u128.pow(64) - 2u128.pow(32) + 1) as u64; + +/// Assert that an argument specifying an integer size in bits follows the rules about what +/// integer sizes we support as a general rule. +macro_rules! assert_valid_integer_size { + ($n:ident) => { + assert!($n > 0, "invalid integer size: size in bits must be non-zero"); + assert!( + $n.is_power_of_two(), + "invalid integer size: size in bits must be a power of two, got {}", + $n + ); + }; + + ($n:ident, $min:literal) => { + assert_valid_integer_size!($n); + assert!( + $n >= $min, + "invalid integer size: expected size in bits greater than or equal to {}, got {}", + $n, + $min + ); + }; + + ($n:ident, $min:ident) => { + assert_valid_integer_size!($n); + assert!( + $n >= $min, + "invalid integer size: expected size in bits greater than or equal to {}, got {}", + $n, + $min + ); + }; + + ($n:ident, $min:literal, $max:literal) => { + assert_valid_integer_size!($n, $min); + assert!( + $n <= $max, + "invalid integer size: expected size in bits less than or equal to {}, got {}", + $n, + $max + ); + }; + + ($n:ident, $min:ident, $max:literal) => { + assert_valid_integer_size!($n, $min); + assert!( + $n <= $max, + "invalid integer size: expected size in bits less than or equal to {}, got {}", + $n, + $max + ); + }; +} + +/// Assert that an argument specifying a zero-based stack index does not access out of bounds +macro_rules! assert_valid_stack_index { + ($idx:ident) => { + assert!( + $idx < 16, + "invalid stack index: only the first 16 elements on the stack are directly \ + accessible, got {}", + $idx + ); + }; + + ($idx:expr) => { + assert!( + ($idx) < 16, + "invalid stack index: only the first 16 elements on the stack are directly \ + accessible, got {}", + $idx + ); + }; +} + +pub mod binary; +pub mod felt; +pub mod int128; +pub mod int32; +pub mod int64; +pub mod mem; +pub mod primop; +pub mod smallint; +pub mod unary; + +use alloc::collections::BTreeSet; +use core::ops::{Deref, DerefMut}; + +use miden_assembly::ast::InvokeKind; +use midenc_hir::{Immediate, Operation, SourceSpan, Type, ValueRef}; + +use super::{Operand, OperandStack}; +use crate::{ + masm::{self as masm, Op}, + TraceEvent, +}; + +/// This structure is used to emit the Miden Assembly ops corresponding to an IR instruction. +/// +/// When dropped, it ensures that the operand stack is updated to reflect the results of the +/// instruction it was created on behalf of. +pub struct InstOpEmitter<'a> { + inst: &'a Operation, + emitter: OpEmitter<'a>, +} +impl<'a> InstOpEmitter<'a> { + #[inline(always)] + pub fn new( + inst: &'a Operation, + invoked: &'a mut BTreeSet, + block: &'a mut Vec, + stack: &'a mut OperandStack, + ) -> Self { + Self { + inst, + emitter: OpEmitter::new(invoked, block, stack), + } + } +} +impl<'a> Deref for InstOpEmitter<'a> { + type Target = OpEmitter<'a>; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.emitter + } +} +impl DerefMut for InstOpEmitter<'_> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.emitter + } +} +impl Drop for InstOpEmitter<'_> { + fn drop(&mut self) { + for (i, result) in self.inst.results().iter().copied().enumerate() { + self.emitter.stack.rename(i, result as ValueRef); + } + } +} + +/// This structure is used to emit Miden Assembly ops into a given function and block. +/// +/// The [OpEmitter] carries limited context of its own, and expects to receive arguments +/// to it's various builder functions to provide necessary context for specific constructs. +pub struct OpEmitter<'a> { + stack: &'a mut OperandStack, + invoked: &'a mut BTreeSet, + current_block: &'a mut Vec, +} +impl<'a> OpEmitter<'a> { + #[inline(always)] + pub fn new( + invoked: &'a mut BTreeSet, + block: &'a mut Vec, + stack: &'a mut OperandStack, + ) -> Self { + Self { + stack, + invoked, + current_block: block, + } + } + + #[cfg(test)] + #[inline(always)] + pub fn stack_len(&self) -> usize { + self.stack.len() + } + + #[inline(always)] + #[allow(unused)] + pub fn stack<'c, 'b: 'c>(&'b self) -> &'c OperandStack { + self.stack + } + + #[inline] + fn maybe_register_invoke(&mut self, op: &masm::Instruction) { + match op { + masm::Instruction::Exec(id) => { + self.invoked.insert(masm::Invoke::new(InvokeKind::Exec, id.clone())); + } + masm::Instruction::Call(id) => { + self.invoked.insert(masm::Invoke::new(InvokeKind::Call, id.clone())); + } + masm::Instruction::SysCall(id) => { + self.invoked.insert(masm::Invoke::new(InvokeKind::SysCall, id.clone())); + } + _ => (), + } + } + + /// Emit an 'exec' for `callee`, where `callee` is an absolute procedure path. + /// + /// It is assumed that all necessary operands are on the operand stack in the correct position + fn raw_exec(&mut self, callee: &str, span: SourceSpan) { + let callee: midenc_hir::FunctionIdent = callee.parse().unwrap(); + let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts(Span::new( + span, + callee.function.as_str().into(), + ))); + let path = masm::LibraryPath::new(callee.module.as_str()).unwrap(); + let target = masm::InvocationTarget::AbsoluteProcedurePath { name, path }; + self.emit(masm::Instruction::Trace(TraceEvent::FrameStart.as_u32().into()), span); + self.emit(masm::Instruction::Nop, span); + self.emit(masm::Instruction::Exec(target), span); + self.emit(masm::Instruction::Trace(TraceEvent::FrameEnd.as_u32().into()), span); + self.emit(masm::Instruction::Nop, span); + } + + /// Emit `op` to the current block + #[inline(always)] + pub fn emit(&mut self, inst: masm::Instruction, span: SourceSpan) { + self.maybe_register_invoke(&inst); + self.current_block.push(Op::Inst(Span::new(span, inst))); + } + + /// Emit `n` copies of `op` to the current block + #[inline(always)] + pub fn emit_n(&mut self, count: usize, inst: masm::Instruction, span: SourceSpan) { + self.maybe_register_invoke(&inst); + let op = Op::Inst(Span::new(span, inst)); + for _ in 0..count { + self.current_block.push(op.clone()); + } + } + + /// Emit `ops` to the current block + #[inline(always)] + pub fn emit_all(&mut self, ops: impl IntoIterator, span: SourceSpan) { + for op in ops { + self.emit(op, span); + } + } + + /// Emit `n` copies of the sequence `ops` to the current block + #[inline(always)] + pub fn emit_repeat(&mut self, count: usize, ops: &[Span]) { + for op in ops { + self.maybe_register_invoke(op.inner()); + } + for _ in 0..count { + self.current_block.extend(ops.iter().cloned().map(|inst| { + let (span, inst) = inst.into_parts(); + Op::Inst(Span::new(span, inst)) + })); + } + } + + /// Emit `n` copies of the sequence `ops` to the current block + #[inline] + pub fn emit_template(&mut self, count: usize, template: F) + where + F: Fn(usize) -> [Span; N], + { + for op in template(0) { + self.maybe_register_invoke(op.inner()); + } + + for n in 0..count { + self.current_block.extend(template(n).into_iter().map(|inst| { + let (span, inst) = inst.into_parts(); + Op::Inst(Span::new(span, inst)) + })); + } + } + + /// Push an immediate value on the operand stack + /// + /// This has no effect on the state of the emulated operand stack + #[inline] + pub fn push_immediate(&mut self, imm: Immediate, span: SourceSpan) { + match imm { + Immediate::I1(i) => self.emit(masm::Instruction::PushU8(i as u8), span), + Immediate::I8(i) => self.emit(masm::Instruction::PushU8(i as u8), span), + Immediate::U8(i) => self.emit(masm::Instruction::PushU8(i), span), + Immediate::U16(i) => self.emit(masm::Instruction::PushU32(i as u32), span), + Immediate::I16(i) => self.emit(masm::Instruction::PushU32(i as u16 as u32), span), + Immediate::U32(i) => self.emit(masm::Instruction::PushU32(i), span), + Immediate::I32(i) => self.emit(masm::Instruction::PushU32(i as u32), span), + Immediate::U64(i) => self.push_u64(i, span), + Immediate::I64(i) => self.push_i64(i, span), + Immediate::U128(i) => self.push_u128(i, span), + Immediate::I128(i) => self.push_i128(i, span), + Immediate::Felt(i) => self.emit(masm::Instruction::PushFelt(i), span), + Immediate::F64(_) => unimplemented!("floating-point immediates are not supported"), + } + } + + /// Push a literal on the operand stack, and update the emulated stack accordingly + pub fn literal>(&mut self, imm: I, span: SourceSpan) { + let imm = imm.into(); + self.push_immediate(imm, span); + self.stack.push(imm); + } + + #[inline(always)] + pub fn pop(&mut self) -> Option { + self.stack.pop() + } + + /// Push an operand on the stack + #[inline(always)] + pub fn push>(&mut self, operand: O) { + self.stack.push(operand) + } + + /// Duplicate an item on the stack to the top + #[inline] + #[track_caller] + pub fn dup(&mut self, i: u8, span: SourceSpan) { + assert_valid_stack_index!(i); + let index = i as usize; + let i = self.stack.effective_index(index); + self.stack.dup(index); + // Emit low-level instructions corresponding to the operand we duplicated + let last = self.stack.peek().expect("operand stack is empty"); + let n = last.size(); + let offset = n - 1; + for _ in 0..n { + self.emit(dup_from_offset(i + offset), span); + } + } + + /// Move an item on the stack to the top + #[inline] + #[track_caller] + pub fn movup(&mut self, i: u8, span: SourceSpan) { + assert_valid_stack_index!(i); + let index = i as usize; + let i = self.stack.effective_index(index); + self.stack.movup(index); + // Emit low-level instructions corresponding to the operand we moved + let moved = self.stack.peek().expect("operand stack is empty"); + let n = moved.size(); + let offset = n - 1; + for _ in 0..n { + self.emit(movup_from_offset(i + offset), span); + } + } + + /// Move an item from the top of the stack to the `n`th position + #[inline] + #[track_caller] + pub fn movdn(&mut self, i: u8, span: SourceSpan) { + assert_valid_stack_index!(i); + let index = i as usize; + let i = self.stack.effective_index_inclusive(index); + let top = self.stack.peek().expect("operand stack is empty"); + let top_size = top.size(); + self.stack.movdn(index); + // Emit low-level instructions corresponding to the operand we moved + for _ in 0..top_size { + self.emit(movdn_from_offset(i), span); + } + } + + /// Swap an item with the top of the stack + #[inline] + #[track_caller] + pub fn swap(&mut self, i: u8, span: SourceSpan) { + assert!(i > 0, "swap requires a non-zero index"); + assert_valid_stack_index!(i); + let index = i as usize; + let src = self.stack[0].size(); + let dst = self.stack[index].size(); + let i = self.stack.effective_index(index); + self.stack.swap(index); + match (src, dst) { + (1, 1) => { + self.emit(swap_from_offset(i), span); + } + (1, n) if i == 1 => { + // We can simply move the top element below the `dst` operand + self.emit(movdn_from_offset(i + (n - 1)), span); + } + (n, 1) if i == n => { + // We can simply move the `dst` element to the top + self.emit(movup_from_offset(i), span); + } + (n, m) if i == n => { + // We can simply move `dst` down + for _ in 0..n { + self.emit(movdn_from_offset(i + (m - 1)), span); + } + } + (n, m) => { + assert!(i >= n); + let offset = m - 1; + for _ in 0..n { + self.emit(movdn_from_offset(i + offset), span); + } + let i = (i as i8 + (m as i8 - n as i8)) as u8 as usize; + match i - 1 { + 1 => { + assert_eq!(m, 1); + self.emit(masm::Instruction::Swap1, span); + } + i => { + for _ in 0..m { + self.emit(movup_from_offset(i), span); + } + } + } + } + } + } + + /// Drop the top operand on the stack + #[inline] + #[track_caller] + pub fn drop(&mut self, span: SourceSpan) { + let elem = self.stack.pop().expect("operand stack is empty"); + match elem.size() { + 1 => { + self.emit(masm::Instruction::Drop, span); + } + 4 => { + self.emit(masm::Instruction::DropW, span); + } + n => { + for _ in 0..n { + self.emit(masm::Instruction::Drop, span); + } + } + } + } + + /// Drop the top `n` operands on the stack + #[inline] + #[track_caller] + pub fn dropn(&mut self, n: usize, span: SourceSpan) { + assert!(self.stack.len() >= n); + assert_ne!(n, 0); + let raw_len: usize = self.stack.iter().rev().take(n).map(|o| o.size()).sum(); + self.stack.dropn(n); + match raw_len { + 1 => { + self.emit(masm::Instruction::Drop, span); + } + 4 => { + self.emit(masm::Instruction::DropW, span); + } + n => { + self.emit_n(n / 4, masm::Instruction::DropW, span); + self.emit_n(n % 4, masm::Instruction::Drop, span); + } + } + } + + /// Remove all but the top `n` values on the operand stack + pub fn truncate_stack(&mut self, n: usize, span: SourceSpan) { + let stack_size = self.stack.len(); + let num_to_drop = stack_size - n; + + if num_to_drop == 0 { + return; + } + + if stack_size == num_to_drop { + let raw_size = self.stack.raw_len(); + self.stack.dropn(num_to_drop); + self.emit_n(raw_size / 4, masm::Instruction::DropW, span); + self.emit_n(raw_size % 4, masm::Instruction::DropW, span); + return; + } + + // This is the common case, and can be handled simply + // by moving the value to the bottom of the stack and + // dropping everything in-between + if n == 1 { + match stack_size { + 2 => { + self.swap(1, span); + self.drop(span); + } + n => { + self.movdn(n as u8 - 1, span); + self.dropn(n - 1, span); + } + } + return; + } + + // TODO: This is a very neive algorithm for clearing + // the stack of all but the top `n` values, we should + // come up with a smarter/more efficient method + for offset in 0..num_to_drop { + let index = stack_size - 1 - offset; + self.drop_operand_at_position(index, span); + } + } + + /// Remove the `n`th value from the top of the operand stack + pub fn drop_operand_at_position(&mut self, n: usize, span: SourceSpan) { + match n { + 0 => { + self.drop(span); + } + 1 => { + self.swap(1, span); + self.drop(span); + } + n => { + self.movup(n as u8, span); + self.drop(span); + } + } + } + + /// Copy the `n`th operand on the stack, and make it the `m`th operand on the stack. + /// + /// If the operand is for a commutative, binary operator, indicated by + /// `is_commutative_binary_operand`, and the desired position is just below the top of + /// stack, this function may leave it on top of the stack instead, since the order of the + /// operands is not strict. This can result in fewer stack manipulation instructions in some + /// scenarios. + pub fn copy_operand_to_position( + &mut self, + n: usize, + m: usize, + is_commutative_binary_operand: bool, + span: SourceSpan, + ) { + match (n, m) { + (0, 0) => { + self.dup(0, span); + } + (actual, 0) => { + self.dup(actual as u8, span); + } + (actual, 1) => { + // If the dependent is binary+commutative, we can + // leave operands in either the 0th or 1st position, + // as long as both operands are on top of the stack + if !is_commutative_binary_operand { + self.dup(actual as u8, span); + self.swap(1, span); + } else { + self.dup(actual as u8, span); + } + } + (actual, expected) => { + self.dup(actual as u8, span); + self.movdn(expected as u8, span); + } + } + } + + /// Make the `n`th operand on the stack, the `m`th operand on the stack. + /// + /// If the operand is for a commutative, binary operator, indicated by + /// `is_commutative_binary_operand`, and the desired position is one of the first two items + /// on the stack, this function may leave the operand in it's current position if it is + /// already one of the first two items on the stack, since the order of the operands is not + /// strict. This can result in fewer stack manipulation instructions in some scenarios. + pub fn move_operand_to_position( + &mut self, + n: usize, + m: usize, + is_commutative_binary_operand: bool, + span: SourceSpan, + ) { + match (n, m) { + (n, m) if n == m => (), + (1, 0) | (0, 1) => { + // If the dependent is binary+commutative, we can + // leave operands in either the 0th or 1st position, + // as long as both operands are on top of the stack + if !is_commutative_binary_operand { + self.swap(1, span); + } + } + (actual, 0) => { + self.movup(actual as u8, span); + } + (actual, 1) => { + self.movup(actual as u8, span); + self.swap(1, span); + } + (actual, expected) => { + self.movup(actual as u8, span); + self.movdn(expected as u8, span); + } + } + } + + /// Get mutable access to the current block we're emitting to + #[cfg(test)] + #[inline(always)] + pub fn current_block<'c, 'b: 'c>(&'b mut self) -> &'c mut Vec { + self.current_block + } +} + +pub fn dup_from_offset(offset: usize) -> masm::Instruction { + match offset { + 0 => masm::Instruction::Dup0, + 1 => masm::Instruction::Dup1, + 2 => masm::Instruction::Dup2, + 3 => masm::Instruction::Dup3, + 4 => masm::Instruction::Dup4, + 5 => masm::Instruction::Dup5, + 6 => masm::Instruction::Dup6, + 7 => masm::Instruction::Dup7, + 8 => masm::Instruction::Dup8, + 9 => masm::Instruction::Dup9, + 10 => masm::Instruction::Dup10, + 11 => masm::Instruction::Dup11, + 12 => masm::Instruction::Dup12, + 13 => masm::Instruction::Dup13, + 14 => masm::Instruction::Dup14, + 15 => masm::Instruction::Dup15, + invalid => panic!("invalid stack offset for dup: {invalid} is out of range"), + } +} + +pub fn swap_from_offset(offset: usize) -> masm::Instruction { + match offset { + 1 => masm::Instruction::Swap1, + 2 => masm::Instruction::Swap2, + 3 => masm::Instruction::Swap3, + 4 => masm::Instruction::Swap4, + 5 => masm::Instruction::Swap5, + 6 => masm::Instruction::Swap6, + 7 => masm::Instruction::Swap7, + 8 => masm::Instruction::Swap8, + 9 => masm::Instruction::Swap9, + 10 => masm::Instruction::Swap10, + 11 => masm::Instruction::Swap11, + 12 => masm::Instruction::Swap12, + 13 => masm::Instruction::Swap13, + 14 => masm::Instruction::Swap14, + 15 => masm::Instruction::Swap15, + invalid => panic!("invalid stack offset for swap: {invalid} is out of range"), + } +} + +#[allow(unused)] +pub fn swapw_from_offset(offset: usize) -> masm::Instruction { + match offset { + 1 => masm::Instruction::SwapW1, + 2 => masm::Instruction::SwapW2, + 3 => masm::Instruction::SwapW3, + invalid => panic!("invalid stack offset for swapw: {invalid} is out of range"), + } +} + +pub fn movup_from_offset(offset: usize) -> masm::Instruction { + match offset { + 2 => masm::Instruction::MovUp2, + 3 => masm::Instruction::MovUp3, + 4 => masm::Instruction::MovUp4, + 5 => masm::Instruction::MovUp5, + 6 => masm::Instruction::MovUp6, + 7 => masm::Instruction::MovUp7, + 8 => masm::Instruction::MovUp8, + 9 => masm::Instruction::MovUp9, + 10 => masm::Instruction::MovUp10, + 11 => masm::Instruction::MovUp11, + 12 => masm::Instruction::MovUp12, + 13 => masm::Instruction::MovUp13, + 14 => masm::Instruction::MovUp14, + 15 => masm::Instruction::MovUp15, + invalid => panic!("invalid stack offset for movup: {invalid} is out of range"), + } +} + +pub fn movdn_from_offset(offset: usize) -> masm::Instruction { + match offset { + 2 => masm::Instruction::MovDn2, + 3 => masm::Instruction::MovDn3, + 4 => masm::Instruction::MovDn4, + 5 => masm::Instruction::MovDn5, + 6 => masm::Instruction::MovDn6, + 7 => masm::Instruction::MovDn7, + 8 => masm::Instruction::MovDn8, + 9 => masm::Instruction::MovDn9, + 10 => masm::Instruction::MovDn10, + 11 => masm::Instruction::MovDn11, + 12 => masm::Instruction::MovDn12, + 13 => masm::Instruction::MovDn13, + 14 => masm::Instruction::MovDn14, + 15 => masm::Instruction::MovDn15, + invalid => panic!("invalid stack offset for movdn: {invalid} is out of range"), + } +} + +#[allow(unused)] +pub fn movupw_from_offset(offset: usize) -> masm::Instruction { + match offset { + 2 => masm::Instruction::MovUpW2, + 3 => masm::Instruction::MovUpW3, + invalid => panic!("invalid stack offset for movupw: {invalid} is out of range"), + } +} + +#[allow(unused)] +pub fn movdnw_from_offset(offset: usize) -> masm::Instruction { + match offset { + 2 => masm::Instruction::MovDnW2, + 3 => masm::Instruction::MovDnW3, + invalid => panic!("invalid stack offset for movdnw: {invalid} is out of range"), + } +} + +#[cfg(test)] +mod tests { + use alloc::rc::Rc; + + use midenc_hir::{ + AbiParam, ArrayType, Context, Felt, FieldElement, Overflow, PointerType, Signature, + ValueRef, + }; + + use super::*; + use crate::masm::{self, Op}; + + #[test] + fn op_emitter_stack_manipulation_test() { + let mut block = Vec::default(); + + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + let three = Immediate::U8(3); + let four = Immediate::U64(2u64.pow(32)); + let five = Immediate::U64(2u64.pow(32) | 2u64.pow(33) | u32::MAX as u64); + + let span = SourceSpan::default(); + emitter.literal(one, span); + emitter.literal(two, span); + emitter.literal(three, span); + emitter.literal(four, span); + emitter.literal(five, span); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 7); + assert_eq!(&ops[0], &Op::Inst(Span::new(span, masm::Instruction::PushU32(1)))); + assert_eq!(&ops[1], &Op::Inst(Span::new(span, masm::Instruction::PushU32(2)))); + assert_eq!(&ops[2], &Op::Inst(Span::new(span, masm::Instruction::PushU8(3)))); + assert_eq!( + &ops[3], + &Op::Inst(Span::new(span, masm::Instruction::PushFelt(Felt::ZERO))) + ); + assert_eq!(&ops[4], &Op::Inst(Span::new(span, masm::Instruction::PushFelt(Felt::ONE)))); + assert_eq!( + &ops[5], + &Op::Inst(Span::new(span, masm::Instruction::PushFelt(Felt::new(u32::MAX as u64)))) + ); + assert_eq!( + &ops[6], + &Op::Inst(Span::new(span, masm::Instruction::PushFelt(Felt::new(3)))) + ); + } + + assert_eq!(emitter.stack()[0], five); + assert_eq!(emitter.stack()[1], four); + assert_eq!(emitter.stack()[2], three); + assert_eq!(emitter.stack()[3], two); + assert_eq!(emitter.stack()[4], one); + + emitter.dup(0, SourceSpan::default()); + assert_eq!(emitter.stack()[0], five); + assert_eq!(emitter.stack()[1], five); + assert_eq!(emitter.stack()[2], four); + assert_eq!(emitter.stack()[3], three); + assert_eq!(emitter.stack()[4], two); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 9); + assert_eq!(&ops[7], &Op::Inst(Span::new(span, masm::Instruction::Dup1))); + assert_eq!(&ops[8], &Op::Inst(Span::new(span, masm::Instruction::Dup1))); + } + + assert_eq!(emitter.stack().effective_index(3), 6); + emitter.dup(3, SourceSpan::default()); + assert_eq!(emitter.stack()[0], three); + assert_eq!(emitter.stack()[1], five); + assert_eq!(emitter.stack()[2], five); + assert_eq!(emitter.stack()[3], four); + assert_eq!(emitter.stack()[4], three); + assert_eq!(emitter.stack()[5], two); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 10); + assert_eq!(&ops[8], &Op::Inst(Span::new(span, masm::Instruction::Dup1))); + assert_eq!(&ops[9], &Op::Inst(Span::new(span, masm::Instruction::Dup6))); + } + + assert_eq!(emitter.stack().effective_index(1), 1); + emitter.swap(1, SourceSpan::default()); + assert_eq!(emitter.stack().effective_index(1), 2); + assert_eq!(emitter.stack()[0], five); + assert_eq!(emitter.stack()[1], three); + assert_eq!(emitter.stack()[2], five); + assert_eq!(emitter.stack()[3], four); + assert_eq!(emitter.stack()[4], three); + assert_eq!(emitter.stack()[5], two); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 11); + assert_eq!(&ops[10], &Op::Inst(Span::new(span, masm::Instruction::MovDn2))); + } + + assert_eq!(emitter.stack().effective_index(3), 5); + emitter.swap(3, SourceSpan::default()); + assert_eq!(emitter.stack()[0], four); + assert_eq!(emitter.stack()[1], three); + assert_eq!(emitter.stack()[2], five); + assert_eq!(emitter.stack()[3], five); + assert_eq!(emitter.stack()[4], three); + assert_eq!(emitter.stack()[5], two); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 15); + assert_eq!(&ops[10], &Op::Inst(Span::new(span, masm::Instruction::MovDn2))); // [five_a, five_b, three, five_c, five_d, four_a, four_b] + assert_eq!(&ops[11], &Op::Inst(Span::new(span, masm::Instruction::MovDn6))); // [five_b, three, five_c, five_d, four_a, four_b, five_a] + assert_eq!(&ops[12], &Op::Inst(Span::new(span, masm::Instruction::MovDn6))); // [three, five_c, five_d, four_a, four_b, five_a, five_b] + assert_eq!(&ops[13], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); // [four_b, three, five_c, five_d, four_a, five_a, five_b] + assert_eq!(&ops[14], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); // [four_a, four_b, three, five_c, + // five_d, + // five_a, + // five_b] + } + + emitter.movdn(2, SourceSpan::default()); + assert_eq!(emitter.stack()[0], three); + assert_eq!(emitter.stack()[1], five); + assert_eq!(emitter.stack()[2], four); + assert_eq!(emitter.stack()[3], five); + assert_eq!(emitter.stack()[4], three); + assert_eq!(emitter.stack()[5], two); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 17); + assert_eq!(&ops[12], &Op::Inst(Span::new(span, masm::Instruction::MovDn6))); // [three, five_c, five_d, four_a, four_b, five_a, five_b] + assert_eq!(&ops[13], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); // [four_b, three, five_c, five_d, four_a, five_a, five_b] + assert_eq!(&ops[14], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); // [four_a, four_b, three, five_c, five_d, five_a, five_b] + assert_eq!(&ops[15], &Op::Inst(Span::new(span, masm::Instruction::MovDn4))); // [four_b, three, five_c, five_d, four_a, five_a, five_b] + assert_eq!(&ops[16], &Op::Inst(Span::new(span, masm::Instruction::MovDn4))); // [three, five_c, five_d, four_a, + // four_b, + // five_a, + // five_b] + } + + emitter.movup(2, SourceSpan::default()); + assert_eq!(emitter.stack()[0], four); + assert_eq!(emitter.stack()[1], three); + assert_eq!(emitter.stack()[2], five); + assert_eq!(emitter.stack()[3], five); + assert_eq!(emitter.stack()[4], three); + assert_eq!(emitter.stack()[5], two); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 19); + assert_eq!(&ops[16], &Op::Inst(Span::new(span, masm::Instruction::MovDn4))); // [three, five_c, five_d, four_a, four_b, five_a, five_b] + assert_eq!(&ops[17], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); // [four_b, three, five_c, five_d, four_a, five_a, five_b] + assert_eq!(&ops[18], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); // [four_a, four_b, three, five_c, + // five_d, + // five_a, + // five_b] + } + + emitter.drop(SourceSpan::default()); + assert_eq!(emitter.stack()[0], three); + assert_eq!(emitter.stack()[1], five); + assert_eq!(emitter.stack()[2], five); + assert_eq!(emitter.stack()[3], three); + assert_eq!(emitter.stack()[4], two); + assert_eq!(emitter.stack()[5], one); + + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 21); + assert_eq!(&ops[18], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); // [four_a, four_b, three, five_c, five_d, five_a, five_b] + assert_eq!(&ops[19], &Op::Inst(Span::new(span, masm::Instruction::Drop))); // [four_b, three, five_c, five_d, five_a, five_b] + assert_eq!(&ops[20], &Op::Inst(Span::new(span, masm::Instruction::Drop))); // [three, five_c, five_d, five_a, five_b] + } + + emitter.copy_operand_to_position(5, 3, false, SourceSpan::default()); + assert_eq!(emitter.stack()[0], three); + assert_eq!(emitter.stack()[1], five); + assert_eq!(emitter.stack()[2], five); + assert_eq!(emitter.stack()[3], one); + assert_eq!(emitter.stack()[4], three); + assert_eq!(emitter.stack()[5], two); + + emitter.drop_operand_at_position(4, SourceSpan::default()); + assert_eq!(emitter.stack()[0], three); + assert_eq!(emitter.stack()[1], five); + assert_eq!(emitter.stack()[2], five); + assert_eq!(emitter.stack()[3], one); + assert_eq!(emitter.stack()[4], two); + + emitter.move_operand_to_position(4, 2, false, SourceSpan::default()); + assert_eq!(emitter.stack()[0], three); + assert_eq!(emitter.stack()[1], five); + assert_eq!(emitter.stack()[2], two); + assert_eq!(emitter.stack()[3], five); + assert_eq!(emitter.stack()[4], one); + } + + #[test] + fn op_emitter_copy_operand_to_position_test() { + let mut block = Vec::default(); + let mut invoked = BTreeSet::default(); + let mut stack = OperandStack::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let context = Rc::new(Context::default()); + + let unused_block = context.create_block_with_params(vec![Type::U32; 7]); + let unused_block = unused_block.borrow(); + let unused_block_args = unused_block.arguments(); + + let v0 = unused_block_args[0] as ValueRef; + let v1 = unused_block_args[1] as ValueRef; + let v2 = unused_block_args[2] as ValueRef; + let v3 = unused_block_args[3] as ValueRef; + let v4 = unused_block_args[4] as ValueRef; + let v5 = unused_block_args[5] as ValueRef; + let v6 = unused_block_args[6] as ValueRef; + + emitter.push(v1); + emitter.push(v0); + emitter.push(v3); + emitter.push(v4); + emitter.push(v6); + emitter.push(v5); + emitter.push(v2); + + assert_eq!(emitter.stack().find(&v3), Some(4)); + + assert_eq!(emitter.stack()[4], v3); + assert_eq!(emitter.stack()[2], v6); + emitter.copy_operand_to_position(4, 2, false, SourceSpan::default()); + assert_eq!(emitter.stack()[5], v3); + assert_eq!(emitter.stack()[2], v3); + + let span = SourceSpan::default(); + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 2); + assert_eq!(&ops[0], &Op::Inst(Span::new(span, masm::Instruction::Dup4))); + assert_eq!(&ops[1], &Op::Inst(Span::new(span, masm::Instruction::MovDn2))); + } + } + + #[test] + fn op_emitter_u32_add_test() { + let mut block = Vec::default(); + let mut invoked = BTreeSet::default(); + let mut stack = OperandStack::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.add_imm(one, Overflow::Checked, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.add(Overflow::Checked, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + + emitter.add_imm(one, Overflow::Overflowing, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::I1); + + emitter.swap(1, SourceSpan::default()); + emitter.drop(SourceSpan::default()); + emitter.dup(0, SourceSpan::default()); + emitter.add(Overflow::Overflowing, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::I1); + } + + #[test] + fn op_emitter_u32_sub_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.sub_imm(one, Overflow::Checked, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.sub(Overflow::Checked, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + + emitter.sub_imm(one, Overflow::Overflowing, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::I1); + + emitter.swap(1, SourceSpan::default()); + emitter.drop(SourceSpan::default()); + emitter.dup(0, SourceSpan::default()); + emitter.sub(Overflow::Overflowing, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::I1); + } + + #[test] + fn op_emitter_u32_mul_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.mul_imm(one, Overflow::Checked, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.mul(Overflow::Checked, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + + emitter.mul_imm(one, Overflow::Overflowing, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::I1); + + emitter.swap(1, SourceSpan::default()); + emitter.drop(SourceSpan::default()); + emitter.dup(0, SourceSpan::default()); + emitter.mul(Overflow::Overflowing, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::I1); + } + + #[test] + fn op_emitter_u32_eq_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.eq_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], one); + + emitter.assert(None, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], one); + + emitter.dup(0, SourceSpan::default()); + emitter.eq(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_u32_neq_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.neq_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], one); + + emitter.assertz(None, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], one); + + emitter.dup(0, SourceSpan::default()); + emitter.neq(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_i1_and_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let t = Immediate::I1(true); + let f = Immediate::I1(false); + + emitter.literal(t, SourceSpan::default()); + emitter.literal(f, SourceSpan::default()); + + emitter.and_imm(t, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], t); + + emitter.and(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_i1_or_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let t = Immediate::I1(true); + let f = Immediate::I1(false); + + emitter.literal(t, SourceSpan::default()); + emitter.literal(f, SourceSpan::default()); + + emitter.or_imm(t, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], t); + + emitter.or(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_i1_xor_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let t = Immediate::I1(true); + let f = Immediate::I1(false); + + emitter.literal(t, SourceSpan::default()); + emitter.literal(f, SourceSpan::default()); + + emitter.xor_imm(t, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], t); + + emitter.xor(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_i1_not_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let t = Immediate::I1(true); + + emitter.literal(t, SourceSpan::default()); + + emitter.not(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_u32_gt_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.gt_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], one); + + emitter.drop(SourceSpan::default()); + emitter.dup(0, SourceSpan::default()); + emitter.gt(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_u32_gte_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.gte_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], one); + + emitter.drop(SourceSpan::default()); + emitter.dup(0, SourceSpan::default()); + emitter.gte(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_u32_lt_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.lt_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], one); + + emitter.drop(SourceSpan::default()); + emitter.dup(0, SourceSpan::default()); + emitter.lt(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_u32_lte_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.lte_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I1); + assert_eq!(emitter.stack()[1], one); + + emitter.drop(SourceSpan::default()); + emitter.dup(0, SourceSpan::default()); + emitter.lte(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_u32_checked_div_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.checked_div_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.checked_div(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_unchecked_div_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.unchecked_div_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.unchecked_div(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_checked_mod_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.checked_mod_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.checked_mod(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_unchecked_mod_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.unchecked_mod_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.unchecked_mod(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_checked_divmod_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.checked_divmod_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 3); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::U32); + assert_eq!(emitter.stack()[2], one); + + emitter.checked_divmod(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 3); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::U32); + assert_eq!(emitter.stack()[2], one); + } + + #[test] + fn op_emitter_u32_unchecked_divmod_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.unchecked_divmod_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 3); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::U32); + assert_eq!(emitter.stack()[2], one); + + emitter.unchecked_divmod(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 3); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], Type::U32); + assert_eq!(emitter.stack()[2], one); + } + + #[test] + fn op_emitter_u32_exp_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.exp_imm(two, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.exp(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_band_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.band_imm(one, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.band(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_bor_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.bor_imm(one, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.bor(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_bxor_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.bxor_imm(one, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.bxor(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_shl_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.shl_imm(1, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.shl(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_shr_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.shr_imm(1, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.shr(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_rotl_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.rotl_imm(1, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.rotl(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_rotr_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.rotr_imm(1, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.rotr(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_min_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.min_imm(one, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.min(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_max_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + + emitter.max_imm(one, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::U32); + assert_eq!(emitter.stack()[1], one); + + emitter.max(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_trunc_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let max = Immediate::U32(u32::MAX); + + emitter.literal(max, SourceSpan::default()); + + emitter.trunc(&Type::U16, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U16); + } + + #[test] + fn op_emitter_u32_zext_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let one = Immediate::U16(1); + + emitter.literal(one, SourceSpan::default()); + + emitter.zext(&Type::U32, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_sext_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let num = Immediate::I16(-128); + + emitter.literal(num, SourceSpan::default()); + + emitter.sext(&Type::I32, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I32); + } + + #[test] + fn op_emitter_u32_cast_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let num = Immediate::U32(128); + + emitter.literal(num, SourceSpan::default()); + + emitter.cast(&Type::I32, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I32); + } + + #[test] + fn op_emitter_u32_inttoptr_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let addr = Immediate::U32(128); + let ptr = Type::from(PointerType::new(Type::from(ArrayType::new(Type::U64, 8)))); + + emitter.literal(addr, SourceSpan::default()); + + emitter.inttoptr(&ptr, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], ptr); + } + + #[test] + fn op_emitter_u32_is_odd_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let num = Immediate::U32(128); + + emitter.literal(num, SourceSpan::default()); + + emitter.is_odd(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I1); + } + + #[test] + fn op_emitter_u32_popcnt_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let num = Immediate::U32(128); + + emitter.literal(num, SourceSpan::default()); + + emitter.popcnt(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_bnot_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let num = Immediate::U32(128); + + emitter.literal(num, SourceSpan::default()); + + emitter.bnot(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u128_bnot_shadowing_fix_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + // Push a U128-typed operand; value is irrelevant for emission semantics here + emitter.push(Type::U128); + assert_eq!(emitter.stack_len(), 1); + + let span = SourceSpan::default(); + emitter.bnot(span); + + // Type preserved, no panic + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U128); + + // For 128-bit values, limb count is 4; template should emit MovUp4 + U32Not per limb + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 8); + assert_eq!(&ops[0], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[1], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + assert_eq!(&ops[2], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[3], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + assert_eq!(&ops[4], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[5], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + assert_eq!(&ops[6], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[7], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + } + } + + #[test] + fn op_emitter_i128_bnot_shadowing_fix_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + // Push an I128-typed operand + emitter.push(Type::I128); + assert_eq!(emitter.stack_len(), 1); + + let span = SourceSpan::default(); + emitter.bnot(span); + + // Type preserved, no panic + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::I128); + + // Expect same instruction pattern as U128 + { + let ops = emitter.current_block(); + assert_eq!(ops.len(), 8); + assert_eq!(&ops[0], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[1], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + assert_eq!(&ops[2], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[3], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + assert_eq!(&ops[4], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[5], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + assert_eq!(&ops[6], &Op::Inst(Span::new(span, masm::Instruction::MovUp4))); + assert_eq!(&ops[7], &Op::Inst(Span::new(span, masm::Instruction::U32Not))); + } + } + + #[test] + fn op_emitter_u32_pow2_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let ten = Immediate::U32(10); + + emitter.literal(ten, SourceSpan::default()); + + emitter.pow2(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_incr_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let ten = Immediate::U32(10); + + emitter.literal(ten, SourceSpan::default()); + + emitter.incr(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_inv_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let ten = Immediate::Felt(Felt::new(10)); + + emitter.literal(ten, SourceSpan::default()); + + emitter.inv(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::Felt); + } + + #[test] + fn op_emitter_neg_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let ten = Immediate::Felt(Felt::new(10)); + + emitter.literal(ten, SourceSpan::default()); + + emitter.neg(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::Felt); + } + + #[test] + fn op_emitter_u32_assert_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let ten = Immediate::U32(10); + + emitter.literal(ten, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + + emitter.assert(None, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 0); + } + + #[test] + fn op_emitter_u32_assertz_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let ten = Immediate::U32(10); + + emitter.literal(ten, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + + emitter.assertz(None, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 0); + } + + #[test] + fn op_emitter_u32_assert_eq_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let ten = Immediate::U32(10); + + emitter.literal(ten, SourceSpan::default()); + emitter.literal(ten, SourceSpan::default()); + emitter.literal(ten, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 3); + + emitter.assert_eq_imm(ten, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + + emitter.assert_eq(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 0); + } + + #[test] + fn op_emitter_u32_select_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let t = Immediate::I1(true); + let one = Immediate::U32(1); + let two = Immediate::U32(2); + + emitter.literal(one, SourceSpan::default()); + emitter.literal(two, SourceSpan::default()); + emitter.literal(t, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 3); + + emitter.select(SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + } + + #[test] + fn op_emitter_u32_exec_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let return_ty = Type::from(ArrayType::new(Type::U32, 1)); + let callee = masm::InvocationTarget::AbsoluteProcedurePath { + path: masm::LibraryPath::new_from_components( + masm::LibraryNamespace::new("test").unwrap(), + [], + ), + name: masm::ProcedureName::new("add").unwrap(), + }; + let signature = Signature::new( + [AbiParam::new(Type::U32), AbiParam::new(Type::I1)], + [AbiParam::new(return_ty.clone())], + ); + + let t = Immediate::I1(true); + let one = Immediate::U32(1); + + emitter.literal(t, SourceSpan::default()); + emitter.literal(one, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + + emitter.exec(callee, &signature, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], return_ty); + } + + #[test] + fn op_emitter_u32_load_test() { + let mut block = Vec::default(); + let mut stack = OperandStack::default(); + let mut invoked = BTreeSet::default(); + let mut emitter = OpEmitter::new(&mut invoked, &mut block, &mut stack); + + let addr = Type::from(PointerType::new(Type::U32)); + + emitter.push(addr); + assert_eq!(emitter.stack_len(), 1); + + emitter.load(Type::U32, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 1); + assert_eq!(emitter.stack()[0], Type::U32); + + emitter.load_imm(128, Type::I32, SourceSpan::default()); + assert_eq!(emitter.stack_len(), 2); + assert_eq!(emitter.stack()[0], Type::I32); + assert_eq!(emitter.stack()[1], Type::U32); + } +} diff --git a/codegen/masm/src/emit/primop.rs b/codegen/masm/src/emit/primop.rs new file mode 100644 index 000000000..70793b29e --- /dev/null +++ b/codegen/masm/src/emit/primop.rs @@ -0,0 +1,386 @@ +use miden_assembly_syntax::parser::WordValue; +use midenc_hir::{ + self as hir, ArgumentExtension, ArgumentPurpose, Felt, FieldElement, Immediate, SourceSpan, + Type, +}; + +use super::{int64, masm, OpEmitter}; +use crate::TraceEvent; + +impl OpEmitter<'_> { + /// Assert that an integer value on the stack has the value 1 + /// + /// This operation consumes the input value. + pub fn assert(&mut self, _code: Option, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + Type::Felt + | Type::U32 + | Type::I32 + | Type::U16 + | Type::I16 + | Type::U8 + | Type::I8 + | Type::I1 => { + self.emit(masm::Instruction::Assert, span); + } + Type::I128 | Type::U128 => { + self.emit_all( + [ + masm::Instruction::PushWord(WordValue([ + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + Felt::ONE, + ])), + masm::Instruction::AssertEqw, + ], + span, + ); + } + Type::U64 | Type::I64 => { + self.emit_all([masm::Instruction::Assertz, masm::Instruction::Assert], span); + } + ty if !ty.is_integer() => { + panic!("invalid argument to assert: expected integer, got {ty}") + } + ty => unimplemented!("support for assert on {ty} is not implemented"), + } + } + + /// Assert that an integer value on the stack has the value 0 + /// + /// This operation consumes the input value. + pub fn assertz(&mut self, _code: Option, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + Type::Felt + | Type::U32 + | Type::I32 + | Type::U16 + | Type::I16 + | Type::U8 + | Type::I8 + | Type::I1 => { + self.emit(masm::Instruction::Assertz, span); + } + Type::U64 | Type::I64 => { + self.emit_all([masm::Instruction::Assertz, masm::Instruction::Assertz], span); + } + Type::U128 | Type::I128 => { + self.emit_all( + [ + masm::Instruction::PushWord(WordValue([Felt::ZERO; 4])), + masm::Instruction::AssertEqw, + ], + span, + ); + } + ty if !ty.is_integer() => { + panic!("invalid argument to assertz: expected integer, got {ty}") + } + ty => unimplemented!("support for assertz on {ty} is not implemented"), + } + } + + /// Assert that the top two integer values on the stack have the same value + /// + /// This operation consumes the input values. + pub fn assert_eq(&mut self, span: SourceSpan) { + let rhs = self.pop().expect("operand stack is empty"); + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, rhs.ty(), "expected assert_eq operands to have the same type"); + match ty { + Type::Felt + | Type::U32 + | Type::I32 + | Type::U16 + | Type::I16 + | Type::U8 + | Type::I8 + | Type::I1 => { + self.emit(masm::Instruction::AssertEq, span); + } + Type::U128 | Type::I128 => self.emit(masm::Instruction::AssertEqw, span), + Type::U64 | Type::I64 => { + self.emit_all( + [ + // compare the hi bits + masm::Instruction::MovUp2, + masm::Instruction::AssertEq, + // compare the low bits + masm::Instruction::AssertEq, + ], + span, + ); + } + ty if !ty.is_integer() => { + panic!("invalid argument to assert_eq: expected integer, got {ty}") + } + ty => unimplemented!("support for assert_eq on {ty} is not implemented"), + } + } + + /// Emit code to assert that an integer value on the stack has the same value + /// as the provided immediate. + /// + /// This operation consumes the input value. + #[allow(unused)] + pub fn assert_eq_imm(&mut self, imm: Immediate, span: SourceSpan) { + let lhs = self.pop().expect("operand stack is empty"); + let ty = lhs.ty(); + assert_eq!(ty, imm.ty(), "expected assert_eq_imm operands to have the same type"); + match ty { + Type::Felt + | Type::U32 + | Type::I32 + | Type::U16 + | Type::I16 + | Type::U8 + | Type::I8 + | Type::I1 => { + self.emit_all( + [ + masm::Instruction::EqImm(imm.as_felt().unwrap().into()), + masm::Instruction::Assert, + ], + span, + ); + } + Type::I128 | Type::U128 => { + self.push_immediate(imm, span); + self.emit(masm::Instruction::AssertEqw, span) + } + Type::I64 | Type::U64 => { + let imm = match imm { + Immediate::I64(i) => i as u64, + Immediate::U64(i) => i, + _ => unreachable!(), + }; + let (hi, lo) = int64::to_raw_parts(imm); + self.emit_all( + [ + masm::Instruction::EqImm(Felt::new(hi as u64).into()), + masm::Instruction::Assert, + masm::Instruction::EqImm(Felt::new(lo as u64).into()), + masm::Instruction::Assert, + ], + span, + ) + } + ty if !ty.is_integer() => { + panic!("invalid argument to assert_eq: expected integer, got {ty}") + } + ty => unimplemented!("support for assert_eq on {ty} is not implemented"), + } + } + + /// Emit code to select between two values of the same type, based on a boolean condition. + /// + /// The semantics of this instruction are basically the same as Miden's `cdrop` instruction, + /// but with support for selecting between any of the representable integer/pointer types as + /// values. Given three values on the operand stack (in order of appearance), `c`, `b`, and + /// `a`: + /// + /// * Pop `c` from the stack. This value must be an i1/boolean, or execution will trap. + /// * Pop `b` and `a` from the stack, and push back `b` if `c` is true, or `a` if `c` is false. + /// + /// This operation will assert that the selected value is a valid value for the given type. + pub fn select(&mut self, span: SourceSpan) { + let c = self.stack.pop().expect("operand stack is empty"); + let b = self.stack.pop().expect("operand stack is empty"); + let a = self.stack.pop().expect("operand stack is empty"); + assert_eq!(c.ty(), Type::I1, "expected selector operand to be an i1"); + let ty = a.ty(); + assert_eq!(ty, b.ty(), "expected selections to be of the same type"); + match &ty { + Type::Felt + | Type::U32 + | Type::I32 + | Type::U16 + | Type::I16 + | Type::U8 + | Type::I8 + | Type::I1 => self.emit(masm::Instruction::CDrop, span), + Type::I128 | Type::U128 => self.emit(masm::Instruction::CDropW, span), + Type::I64 | Type::U64 => { + // Perform two conditional drops, one for each 32-bit limb + // corresponding to the value which is being selected + self.emit_all( + [ + // stack starts as [c, b_hi, b_lo, a_hi, a_lo] + masm::Instruction::Dup0, // [c, c, b_hi, b_lo, a_hi, a_lo] + masm::Instruction::MovDn5, // [c, b_hi, b_lo, a_hi, a_lo, c] + masm::Instruction::MovUp3, // [a_hi, c, b_hi, b_lo, a_lo, c] + masm::Instruction::MovUp2, // [b_hi, a_hi, c, b_lo, a_lo, c] + masm::Instruction::MovUp5, // [c, b_hi, a_hi, c, b_lo, a_lo] + masm::Instruction::CDrop, // [d_hi, c, b_lo, a_lo] + masm::Instruction::MovDn3, // [c, b_lo, a_lo, d_hi] + masm::Instruction::CDrop, // [d_lo, d_hi] + masm::Instruction::Swap1, // [d_hi, d_lo] + ], + span, + ); + } + ty if !ty.is_integer() => { + panic!("invalid argument to assert_eq: expected integer, got {ty}") + } + ty => unimplemented!("support for assert_eq on {ty} is not implemented"), + } + self.push(ty); + } + + /// Execute the given procedure. + /// + /// A function called using this operation is invoked in the same memory context as the caller. + pub fn exec( + &mut self, + callee: masm::InvocationTarget, + signature: &hir::Signature, + span: SourceSpan, + ) { + self.process_call_signature(&callee, signature, span); + + self.emit(masm::Instruction::Trace(TraceEvent::FrameStart.as_u32().into()), span); + self.emit(masm::Instruction::Nop, span); + self.emit(masm::Instruction::Exec(callee), span); + self.emit(masm::Instruction::Trace(TraceEvent::FrameEnd.as_u32().into()), span); + self.emit(masm::Instruction::Nop, span); + } + + /// Execute the given procedure in a new context. + /// + /// A function called using this operation is invoked in a new memory context. + pub fn call( + &mut self, + callee: masm::InvocationTarget, + signature: &hir::Signature, + span: SourceSpan, + ) { + self.process_call_signature(&callee, signature, span); + + self.emit(masm::Instruction::Trace(TraceEvent::FrameStart.as_u32().into()), span); + self.emit(masm::Instruction::Nop, span); + self.emit(masm::Instruction::Call(callee), span); + self.emit(masm::Instruction::Trace(TraceEvent::FrameEnd.as_u32().into()), span); + self.emit(masm::Instruction::Nop, span); + } + + fn process_call_signature( + &mut self, + callee: &masm::InvocationTarget, + signature: &hir::Signature, + span: SourceSpan, + ) { + for i in 0..signature.arity() { + let param = &signature.params[i]; + let arg = self.stack.pop().expect("operand stack is empty"); + let ty = arg.ty(); + // Validate the purpose matches + match param.purpose { + ArgumentPurpose::StructReturn => { + assert_eq!( + i, 0, + "invalid function signature: sret parameters must be the first parameter, \ + and only one sret parameter is allowed" + ); + assert_eq!( + signature.results.len(), + 0, + "invalid function signature: a function with sret parameters cannot also \ + have results" + ); + assert!( + ty.is_pointer(), + "invalid exec to {callee}: invalid argument for sret parameter, expected \ + {}, got {ty}", + ¶m.ty + ); + } + ArgumentPurpose::Default => (), + } + // Validate that the argument type is valid for the parameter ABI + match param.extension { + // Types must match exactly + ArgumentExtension::None => { + assert_eq!( + ty, param.ty, + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}" + ); + } + // Caller can provide a smaller type which will be zero-extended to the expected + // type + // + // However, the argument must be an unsigned integer, and of smaller or equal size + // in order for the types to differ + ArgumentExtension::Zext if ty != param.ty => { + assert!( + param.ty.is_unsigned_integer(), + "invalid function signature: zero-extension is only valid for unsigned \ + integer types" + ); + assert!( + ty.is_unsigned_integer(), + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}, expected unsigned integer type, got {ty}" + ); + let expected_size = param.ty.size_in_bits(); + let provided_size = param.ty.size_in_bits(); + assert!( + provided_size <= expected_size, + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}, expected integer width to be <= {expected_size} bits" + ); + // Zero-extend this argument + self.stack.push(arg); + self.zext(¶m.ty, span); + self.stack.drop(); + } + // Caller can provide a smaller type which will be sign-extended to the expected + // type + // + // However, the argument must be an integer which can fit in the range of the + // expected type + ArgumentExtension::Sext if ty != param.ty => { + assert!( + param.ty.is_signed_integer(), + "invalid function signature: sign-extension is only valid for signed \ + integer types" + ); + assert!( + ty.is_integer(), + "invalid call to {callee}: invalid argument type for parameter at index \ + {i}, expected integer type, got {ty}" + ); + let expected_size = param.ty.size_in_bits(); + let provided_size = param.ty.size_in_bits(); + if ty.is_unsigned_integer() { + assert!( + provided_size < expected_size, + "invalid call to {callee}: invalid argument type for parameter at \ + index {i}, expected unsigned integer width to be < {expected_size} \ + bits" + ); + } else { + assert!( + provided_size <= expected_size, + "invalid call to {callee}: invalid argument type for parameter at \ + index {i}, expected integer width to be <= {expected_size} bits" + ); + } + // Push the operand back on the stack for `sext` + self.stack.push(arg); + self.sext(¶m.ty, span); + self.stack.drop(); + } + ArgumentExtension::Zext | ArgumentExtension::Sext => (), + } + } + + for result in signature.results.iter().rev() { + self.push(result.ty.clone()); + } + } +} diff --git a/codegen/masm/src/codegen/emit/smallint.rs b/codegen/masm/src/emit/smallint.rs similarity index 91% rename from codegen/masm/src/codegen/emit/smallint.rs rename to codegen/masm/src/emit/smallint.rs index b45b41631..bd1e040b7 100644 --- a/codegen/masm/src/codegen/emit/smallint.rs +++ b/codegen/masm/src/emit/smallint.rs @@ -8,13 +8,12 @@ //! For signed smallint operations, we implement them in terms of a two's complement representation, //! using a set of common primitives. The only thing that changes are which bits are considered by //! those primitives. -use midenc_hir::{diagnostics::SourceSpan, Overflow}; +use midenc_hir::{Overflow, SourceSpan}; -use super::OpEmitter; -use crate::masm::Op; +use super::{masm, OpEmitter}; #[allow(unused)] -impl<'a> OpEmitter<'a> { +impl OpEmitter<'_> { /// Check that a u32 value on the stack can fit in the unsigned N-bit integer range #[inline(always)] pub fn is_valid_uint(&mut self, n: u32, span: SourceSpan) { @@ -34,7 +33,7 @@ impl<'a> OpEmitter<'a> { assert_valid_integer_size!(n, 1, 32); match n { // i1 is never signed - 1 => self.emit(Op::PushU32(0), span), + 1 => self.emit(masm::Instruction::PushU32(0), span), n => self.is_const_flag_set_u32(1 << (n - 1), span), } } @@ -47,7 +46,7 @@ impl<'a> OpEmitter<'a> { 1 => (), n => { self.is_signed_smallint(n, span); - self.emit(Op::Assert, span); + self.emit(masm::Instruction::Assert, span); } } } @@ -71,13 +70,13 @@ impl<'a> OpEmitter<'a> { #[inline] pub fn int_to_u64(&mut self, n: u32, span: SourceSpan) { self.assert_unsigned_smallint(n, span); - self.emit(Op::PushU32(0), span); + self.emit(masm::Instruction::PushU32(0), span); } /// Convert an unsigned N-bit integer to u64 #[inline(always)] pub fn uint_to_u64(&mut self, _: u32, span: SourceSpan) { - self.emit(Op::PushU32(0), span); + self.emit(masm::Instruction::PushU32(0), span); } /// Convert a signed N-bit integer to i128 @@ -90,7 +89,7 @@ impl<'a> OpEmitter<'a> { #[inline(always)] pub fn uint_to_i128(&mut self, _n: u32, span: SourceSpan) { // zero-extend to i128 - self.emit_n(3, Op::PushU32(0), span); + self.emit_n(3, masm::Instruction::PushU32(0), span); } /// Sign-extend the N-bit value on the stack to M-bits, where M is >= N and <= 256. @@ -115,16 +114,16 @@ impl<'a> OpEmitter<'a> { self.is_signed_smallint(n, span); if is_large { // Make a copy for selecting padding later - self.emit(Op::Dup(0), span); + self.emit(masm::Instruction::Dup0, span); self.select_int32(sign_bits, 0, span); self.emit_all( - &[ + [ // Move the input value to the top of the stack - Op::Movup(2), + masm::Instruction::MovUp2, // Sign-extend to i32 - Op::U32Or, + masm::Instruction::U32Or, // Move the is_signed flag back to the top - Op::Swap(1), + masm::Instruction::Swap1, ], span, ); @@ -135,7 +134,7 @@ impl<'a> OpEmitter<'a> { } else { self.select_int32(sign_bits, 0, span); // Sign-extend to i32 - self.emit(Op::U32Or, span); + self.emit(masm::Instruction::U32Or, span); } } @@ -318,17 +317,21 @@ impl<'a> OpEmitter<'a> { match overflow { Overflow::Unchecked => (), Overflow::Checked => self.int32_to_uint(n, span), - Overflow::Wrapping => self.emit(Op::U32ModImm(2u32.pow(n)), span), + Overflow::Wrapping => self.emit_all( + [masm::Instruction::PushU32(2u32.pow(n)), masm::Instruction::U32Mod], + span, + ), Overflow::Overflowing => { self.try_int32_to_uint(n, span); self.emit_all( - &[ + [ // move result to top, and wrap it at 2^n - Op::Swap(1), - Op::U32ModImm(2u32.pow(n)), + masm::Instruction::Swap1, + masm::Instruction::PushU32(2u32.pow(n)), + masm::Instruction::U32Mod, // move is_valid flag to top, and invert it - Op::Swap(1), - Op::Not, + masm::Instruction::Swap1, + masm::Instruction::Not, ], span, ); diff --git a/codegen/masm/src/emit/unary.rs b/codegen/masm/src/emit/unary.rs new file mode 100644 index 000000000..7c9607097 --- /dev/null +++ b/codegen/masm/src/emit/unary.rs @@ -0,0 +1,1029 @@ +use miden_core::{Felt, FieldElement}; +use midenc_hir::Overflow; + +use super::*; + +impl OpEmitter<'_> { + /// Truncate an integral value of type `src` to type `dst` + /// + /// Truncation is defined in terms of the bitwise representation of the type. + /// Specifically, the source value will have any excess bits above the bitwidth of + /// the `dst` type either zeroed, or dropped, depending on the `src` type. For example, + /// u64 is represented as two 32-bit limbs, each in a field element on the operand stack; + /// while u16 is represented as 16 bits in a single field element. Truncating from u64 + /// to u16 results in dropping the 32-bit limb containing the most significant bits, and + /// then masking out the most significant 16 bits of the remaining 32-bit limb, leaving + /// a 16-bit value on the operand stack. + /// + /// NOTE: Field elements do not have a formal bitwise representation. When truncating to + /// felt and the source value is negative, the resulting felt will be `Felt::ZERO`. When + /// the value is non-negative, the source value will be mapped to the field element range + /// using the field modulus of `2^64 - 2^32 + 1`, and then convert the representation to + /// felt by multiplying the 32-bit limbs (the only values which can be truncated to felt + /// are u64, i64, and i128, all of which use multiple 32-bit limbs). + /// + /// This function assumes that an integer value of type `src` is on top of the operand stack, + /// and will ensure a value of type `dst` is on the operand stack after truncation, or that + /// execution traps. + pub fn trunc(&mut self, dst: &Type, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let src = arg.ty(); + assert!( + src.is_integer() && dst.is_integer(), + "invalid truncation of {src} to {dst}: only integer-to-integer casts are supported" + ); + let n = dst.size_in_bits() as u32; + match (&src, dst) { + // If the types are equivalent, it's a no-op + (src, dst) if src == dst => (), + (Type::Felt, _) if n <= 32 => self.trunc_felt(n, span), + // Truncating i128 to u128, and vice versa is a bitcast + (Type::I128 | Type::U128, Type::U128 | Type::I128) => (), + // Truncating to felt + (Type::U128 | Type::I128, Type::Felt) => self.trunc_i128_to_felt(span), + // Truncating a 128-bit integer to 64 bits or smaller + (Type::U128 | Type::I128, _) if n <= 64 => self.trunc_i128(n, span), + // Truncating i64/u64 to felt + (Type::I64 | Type::U64, Type::Felt) => self.trunc_int64_to_felt(span), + // Truncating i64 to u64, and vice versa is a bitcast + (Type::I64 | Type::U64, Type::U64 | Type::I64) => (), + // Truncating a u64/i64 to 32 bits or smaller + (Type::I64 | Type::U64, _) if n <= 32 => self.trunc_int64(n, span), + // Truncating a felt to 32 bits or smaller + (Type::Felt, _) if n <= 32 => self.trunc_felt(n, span), + // Truncating i32 to u32, and vice versa is a bitcast + (Type::I32 | Type::U32, Type::U32 | Type::I32) => (), + // Truncating an i32/u32 to smaller than 32 bits + (Type::I32 | Type::U32, _) if n <= 32 => self.trunc_int32(n, span), + // Truncating i16 to u16, and vice versa is a bitcast + (Type::I16 | Type::U16, Type::U16 | Type::I16) => (), + // Truncating an i16/u16 to smaller than 16 bits + (Type::I16 | Type::U16, _) if n <= 16 => self.trunc_int32(n, span), + // Truncating i8 to u8, and vice versa is a bitcast + (Type::I8 | Type::U8, Type::U8 | Type::I8) => (), + // Truncating an i8/u8 to smaller than 8 bits + (Type::I8 | Type::U8, _) if n <= 8 => self.trunc_int32(n, span), + (src, dst) => unimplemented!("unsupported truncation of {src} to {dst}"), + } + self.push(dst.clone()); + } + + /// Zero-extend an unsigned integral value of type `src` to type `dst` + /// + /// This function will panic if the source or target types are not unsigned integral types. + /// Despite its type name, i1 is an unsigned value, because it may only represent 1 or 0. + /// + /// Zero-extension is defined in terms of the bitwise representation of the type. + /// Specifically, the source value will have any excess bits above the bitwidth of + /// the `src` type either added as zeroes, or set to zero, depending on the `dst` type. + /// For example, u16 is represented as 16 bits in a single field element, while u64 is + /// represented as two 32-bit limbs, each in a separate field element. Zero-extending + /// from u16 to u64 requires only pushing a new element of `Felt::ZERO` on the operand stack. + /// Since the upper 16 bits of the original 32-bit field element value must already be zero, + /// we only needed to pad out the representation with an extra zero element to obtain the + /// corresponding u64. + /// + /// NOTE: Field elements do not have a formal bitwise representation. However, types with a + /// bitwidth of 32 bits or smaller are transparently represented as field elements in the VM, + /// so zero-extending to felt from such a type is a no-op. Even though a field element is + /// notionally a 64-bit value in memory, it is not equivalent in range to a 64-bit integer, + /// so 64-bit integers and above require the use of multiple 32-bit limbs, to provide a two's + /// complement bitwise representation; so there are no types larger than 32-bits that are + /// zero-extendable to felt, but are not representable as a felt transparently. + /// + /// This function assumes that an integer value of type `src` is on top of the operand stack, + /// and will ensure a value of type `dst` is on the operand stack after truncation, or that + /// execution traps. + pub fn zext(&mut self, dst: &Type, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let src = arg.ty(); + let src_bits = src.size_in_bits() as u32; + let dst_bits = dst.size_in_bits() as u32; + assert!( + src_bits <= dst_bits, + "invalid zero-extension from {src} to {dst}: cannot zero-extend to a smaller type" + ); + match (&src, dst) { + // If the types are equivalent, it's a no-op, but only if they are integers + (src, dst) if src == dst => (), + // Zero-extending a u64 to i128 simply requires pushing a 0u64 on the stack + (Type::U64, Type::U128 | Type::I128) => self.push_u64(0, span), + (Type::Felt, Type::U64 | Type::U128 | Type::I128) => self.zext_felt(dst_bits, span), + (Type::U32, Type::U64 | Type::I64 | Type::U128 | Type::I128) => { + self.zext_int32(dst_bits, span) + } + (Type::I1 | Type::U8 | Type::U16, Type::U64 | Type::I64 | Type::U128 | Type::I128) => { + self.zext_smallint(src_bits, dst_bits, span) + } + // Zero-extending to u32/i32 from smaller integers is a no-op + (Type::I1 | Type::U8 | Type::U16, Type::U32 | Type::I32) => (), + // Zero-extending to felt, from types that fit in felt, is a no-op + (Type::I1 | Type::U8 | Type::U16 | Type::U32, Type::Felt) => (), + (src, dst) if dst.is_signed_integer() => panic!( + "invalid zero-extension from {src} to {dst}: value may not fit in range, use \ + explicit cast instead" + ), + (src, dst) => panic!("unsupported zero-extension from {src} to {dst}"), + } + self.push(dst.clone()); + } + + /// Sign-extend an integral value of type `src` to type `dst` + /// + /// This function will panic if the target type is not a signed integral type. + /// To extend unsigned integer types to a larger unsigned integer type, use `zext`. + /// To extend signed integer types to an equal or larger unsigned type, use `cast`. + /// + /// Sign-extension is defined in terms of the bitwise representation of the type. + /// Specifically, the sign bit of the source value will be propagated to all excess + /// bits added to the representation of `src` to represent `dst`. + /// + /// For example, i16 is represented as 16 bits in a single field element, while i64 is + /// represented as two 32-bit limbs, each in a separate field element. Sign-extending + /// the i16 value -128, to i64, requires propagating the sign bit value, 1 since -128 + /// is a negative number, to the most significant 32-bits of the input element, as well + /// as pushing an additional element representing `u32::MAX` on the operand stack. This + /// gives us a bitwise representation where the most significant 48 bits are all 1s, and + /// the last 16 bits are the same as the original input value, giving us the i64 + /// representation of -128. + /// + /// NOTE: Field elements cannot be sign-extended to i64, you must an explicit cast, as the + /// range of the field means that it is not guaranteed that the felt will fit in the i64 + /// range. + /// + /// This function assumes that an integer value of type `src` is on top of the operand stack, + /// and will ensure a value of type `dst` is on the operand stack after truncation, or that + /// execution traps. + pub fn sext(&mut self, dst: &Type, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let src = arg.ty(); + assert!( + src.is_integer() && dst.is_signed_integer(), + "invalid sign-extension of {src} to {dst}: only integer-to-signed-integer casts are \ + supported" + ); + let src_bits = src.size_in_bits() as u32; + let dst_bits = dst.size_in_bits() as u32; + assert!( + src_bits <= dst_bits, + "invalid zero-extension from {src} to {dst}: cannot zero-extend to a smaller type" + ); + match (&src, dst) { + // If the types are equivalent, it's a no-op + (src, dst) if src == dst => (), + (Type::U64 | Type::I64, Type::I128) => self.sext_int64(128, span), + (Type::Felt, Type::I64 | Type::I128) => self.sext_felt(dst_bits, span), + (Type::I32 | Type::U32, Type::I64 | Type::I128) => self.sext_int32(dst_bits, span), + ( + Type::I1 | Type::I8 | Type::U8 | Type::I16 | Type::U16, + Type::I32 | Type::I64 | Type::I128, + ) => self.sext_smallint(src_bits, dst_bits, span), + (src, dst) => panic!("unsupported sign-extension from {src} to {dst}"), + } + self.push(dst.clone()); + } + + pub fn bitcast(&mut self, dst: &Type, _span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let src = arg.ty(); + assert!( + (src.is_integer() && dst.is_integer()) || (src.is_pointer() && dst.is_pointer()), + "invalid cast of {src} to {dst}: only integer-to-integer or pointer-to-pointer \ + bitcasts are supported" + ); + self.push(dst.clone()); + } + + /// Convert between two integral types, given as `src` and `dst`, + /// indicating the direction of the conversion. + /// + /// This function will panic if either type is not an integer. + /// + /// The specific semantics of a cast are dependent on the pair of types involved, + /// but the general rules are as follows: + /// + /// * Any integer-to-integer cast is allowed + /// * Casting a signed integer to an unsigned integer will assert that the input value is + /// unsigned + /// * Casting a type with a larger range to a type with a smaller one will assert that the input + /// value fits within that range, e.g. u8 to i8, i16 to i8, etc. + /// * Casting to a larger unsigned type will use zero-extension + /// * Casting a signed type to a larger signed type will use sign-extension + /// * Casting an unsigned type to a larger signed type will use zero-extension + /// + /// As a rule, the input value must be representable in the target type, or an + /// assertion will be raised. Casts are intended to faithfully translate a value + /// in one type to the same value in another type. + /// + /// This function assumes that an integer value of type `src` is on top of the operand stack, + /// and will ensure a value of type `dst` is on the operand stack after truncation, or that + /// execution traps. + pub fn cast(&mut self, dst: &Type, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let src = arg.ty(); + assert!( + src.is_integer() && dst.is_integer(), + "invalid cast of {src} to {dst}: only integer-to-integer casts are supported" + ); + + let src_bits = src.size_in_bits() as u32; + let dst_bits = dst.size_in_bits() as u32; + match (&src, dst) { + // u128 + (Type::U128, Type::I128) => self.assert_unsigned_int128(span), + (Type::U128, Type::I64) => self.u128_to_i64(span), + (Type::U128 | Type::I128, Type::U64) => self.int128_to_u64(span), + (Type::U128 | Type::I128, Type::Felt) => self.int128_to_felt(span), + (Type::U128 | Type::I128, Type::U32) => self.int128_to_u32(span), + (Type::U128 | Type::I128, Type::U16 | Type::U8 | Type::I1) => { + self.int128_to_u32(span); + self.int32_to_uint(dst_bits, span); + } + (Type::U128, Type::I32) => { + self.int128_to_u32(span); + self.assert_unsigned_int32(span); + } + (Type::U128, Type::I16 | Type::I8) => { + self.int128_to_u32(span); + self.int32_to_int(dst_bits, span); + } + // i128 + (Type::I128, Type::I64) => self.i128_to_i64(span), + (Type::I128, Type::I32 | Type::I16 | Type::I8) => { + self.i128_to_i64(span); + self.i64_to_int(dst_bits, span); + } + // i64 + (Type::I64, Type::I128) => self.sext_int64(128, span), + (Type::I64, Type::U128) => self.zext_int64(128, span), + (Type::I64, Type::U64) => self.assert_unsigned_int64(span), + (Type::I64, Type::Felt) => self.i64_to_felt(span), + (Type::I64, Type::U32 | Type::U16 | Type::U8 | Type::I1) => { + self.assert_unsigned_int64(span); + self.u64_to_uint(dst_bits, span); + } + (Type::I64, Type::I32 | Type::I16 | Type::I8) => { + self.i64_to_int(dst_bits, span); + } + // u64 + (Type::U64, Type::I128 | Type::U128) => self.zext_int64(128, span), + (Type::U64, Type::I64) => self.assert_i64(span), + (Type::U64, Type::Felt) => self.u64_to_felt(span), + (Type::U64, Type::U32 | Type::U16 | Type::U8 | Type::I1) => { + self.u64_to_uint(dst_bits, span); + } + (Type::U64, Type::I32 | Type::I16 | Type::I8) => { + // Convert to N bits as unsigned + self.u64_to_uint(dst_bits, span); + // Verify that the input value is still unsigned + self.assert_unsigned_smallint(dst_bits, span); + } + // felt + (Type::Felt, Type::I64 | Type::I128) => self.sext_felt(dst_bits, span), + (Type::Felt, Type::U128) => self.zext_felt(dst_bits, span), + (Type::Felt, Type::U64) => self.felt_to_u64(span), + (Type::Felt, Type::U32 | Type::U16 | Type::U8 | Type::I1) => { + self.felt_to_uint(dst_bits, span); + } + (Type::Felt, Type::I32 | Type::I16 | Type::I8) => { + self.felt_to_int(dst_bits, span); + } + // u32 + (Type::U32, Type::I64 | Type::U64 | Type::I128) => self.zext_int32(dst_bits, span), + (Type::U32, Type::I32) => self.assert_i32(span), + (Type::U32, Type::U16 | Type::U8 | Type::I1) => { + self.int32_to_uint(dst_bits, span); + } + (Type::U32, Type::I16 | Type::I8) => self.int32_to_int(dst_bits, span), + // i32 + (Type::I32, Type::I64 | Type::I128) => self.sext_int32(dst_bits, span), + (Type::I32, Type::U64) => { + self.assert_i32(span); + self.emit(masm::Instruction::PushU32(0), span); + } + (Type::I32, Type::U32) => { + self.assert_i32(span); + } + (Type::I32, Type::U16 | Type::U8 | Type::I1) => { + self.int32_to_uint(dst_bits, span); + } + (Type::I32, Type::I16 | Type::I8) => self.int32_to_int(dst_bits, span), + // i8/i16 + (Type::I8 | Type::I16, Type::I32 | Type::I64 | Type::I128) => { + self.sext_smallint(src_bits, dst_bits, span); + } + (Type::I8 | Type::I16, Type::U32 | Type::U64) => { + self.assert_unsigned_smallint(src_bits, span); + self.zext_smallint(src_bits, dst_bits, span); + } + (Type::I16, Type::U16) | (Type::I8, Type::U8) => { + self.assert_unsigned_smallint(src_bits, span); + } + (Type::I16, Type::U8 | Type::I1) => self.int32_to_int(dst_bits, span), + (Type::I16, Type::I8) => self.int32_to_int(dst_bits, span), + (Type::I8, Type::I1) => { + self.emit_all( + [ + // Assert that input is either 0 or 1 + // + // NOTE: The comparison here is unsigned, so the sign + // bit being set will make the i8 larger than 0 or 1 + masm::Instruction::Dup0, + masm::Instruction::PushU32(2), + masm::Instruction::Lt, + masm::Instruction::Assert, + ], + span, + ); + } + // i1 + (Type::I1, _) => self.zext_smallint(src_bits, dst_bits, span), + (src, dst) => unimplemented!("unsupported cast from {src} to {dst}"), + } + self.push(dst.clone()); + } + + /// Cast `arg` to a pointer value + pub fn inttoptr(&mut self, ty: &Type, span: SourceSpan) { + assert!(ty.is_pointer(), "exected pointer typed argument"); + // For now, we're strict about the types of values we'll allow casting from + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + // We allow i32 here because Wasm uses it + Type::U32 | Type::I32 => { + self.push(ty.clone()); + } + Type::Felt => { + self.emit(masm::Instruction::U32Assert, span); + self.push(ty.clone()); + } + int => panic!("invalid inttoptr cast: cannot cast value of type {int} to {ty}"), + } + } + + /// Check if an integral value on the operand stack is an odd number. + /// + /// The result is placed on the stack as a boolean value. + /// + /// This operation consumes the input operand. + pub fn is_odd(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + // For both signed and unsigned types, + // values <= bitwidth of a felt can use + // the native instruction because the sign + // bit does not change whether the value is + // odd or not + Type::I1 + | Type::U8 + | Type::I8 + | Type::U16 + | Type::I16 + | Type::U32 + | Type::I32 + | Type::Felt => { + self.emit(masm::Instruction::IsOdd, span); + } + // For i64/u64, we use the native instruction + // on the lower limb to check for odd/even + Type::I64 | Type::U64 => { + self.emit_all([masm::Instruction::Drop, masm::Instruction::IsOdd], span); + } + // For i128, same as above, but more elements are dropped + Type::I128 | Type::U128 => { + self.emit_n(3, masm::Instruction::Drop, span); + self.emit(masm::Instruction::IsOdd, span); + } + Type::F64 => { + unimplemented!("is_odd support for floating-point values is not yet implemented") + } + ty => panic!("expected integral type for is_odd opcode, got {ty}"), + } + self.push(Type::I1); + } + + /// Compute the integral base-2 logarithm of the value on top of the operand stack, and + /// place the result back on the operand stack as a u32 value. + /// + /// This operation consumes the input operand. + pub fn ilog2(&mut self, span: SourceSpan) { + let ty = self.stack.peek().expect("operand stack is empty").ty(); + match &ty { + Type::Felt => self.emit(masm::Instruction::ILog2, span), + Type::I128 | Type::U128 | Type::I64 | Type::U64 => { + // Compute the number of leading zeros + // + // NOTE: This function handles popping the input and pushing + // a u32 result on the stack for us, so we can omit any stack + // manipulation here. + self.clz(span); + let bits = ty.size_in_bits(); + // ilog2 is bits - clz - 1 + self.emit_all( + [ + masm::Instruction::PushU8(bits as u8), + masm::Instruction::Swap1, + masm::Instruction::Sub, + masm::Instruction::U32OverflowingSubImm(1.into()), + masm::Instruction::Assertz, + ], + span, + ); + } + Type::I32 | Type::U32 | Type::I16 | Type::U16 | Type::I8 | Type::U8 => { + let _ = self.stack.pop(); + self.emit_all( + [ + // Compute ilog2 on the advice stack + masm::Instruction::ILog2, + // Drop the operand + masm::Instruction::Drop, + // Move the result to the operand stack + masm::Instruction::AdvPush(1.into()), + ], + span, + ); + self.push(Type::U32); + } + Type::I1 => { + // 2^0 == 1 + let _ = self.stack.pop(); + self.emit_all([masm::Instruction::Drop, masm::Instruction::PushU8(0)], span); + self.push(Type::U32); + } + ty if !ty.is_integer() => { + panic!("invalid ilog2 on {ty}: only integral types are supported") + } + ty => unimplemented!("ilog2 for {ty} is not supported"), + } + } + + /// Count the number of non-zero bits in the integral value on top of the operand stack, + /// and place the count back on the stack as a u32 value. + /// + /// This operation consumes the input operand. + pub fn popcnt(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + Type::I128 | Type::U128 => { + self.emit_all( + [ + // [x3, x2, x1, x0] + masm::Instruction::U32Popcnt, + // [popcnt3, x2, x1, x0] + masm::Instruction::Swap1, + // [x2, popcnt3, x1, x0] + masm::Instruction::U32Popcnt, + // [popcnt2, popcnt3, x1, x0] + masm::Instruction::Add, + // [popcnt_hi, x1, x0] + masm::Instruction::MovDn2, + // [x1, x0, popcnt] + masm::Instruction::U32Popcnt, + // [popcnt1, x0, popcnt] + masm::Instruction::Swap1, + // [x0, popcnt1, popcnt] + masm::Instruction::U32Popcnt, + // [popcnt0, popcnt1, popcnt] + // + // This last instruction adds all three values together mod 2^32 + masm::Instruction::U32WrappingAdd3, + ], + span, + ); + } + Type::I64 | Type::U64 => { + self.emit_all( + [ + // Get popcnt of high bits + masm::Instruction::U32Popcnt, + // Swap to low bits and repeat + masm::Instruction::Swap1, + masm::Instruction::U32Popcnt, + // Add both counts to get the total count + masm::Instruction::Add, + ], + span, + ); + } + Type::I32 | Type::U32 | Type::I16 | Type::U16 | Type::I8 | Type::U8 | Type::I1 => { + self.emit(masm::Instruction::U32Popcnt, span); + } + ty if !ty.is_integer() => { + panic!("invalid popcnt on {ty}: only integral types are supported") + } + ty => unimplemented!("popcnt for {ty} is not supported"), + } + self.push(Type::U32); + } + + /// Count the number of leading zero bits in the integral value on top of the operand stack, + /// and place the count back on the stack as a u32 value. + /// + /// This operation is implemented so that it consumes the input operand. + pub fn clz(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + Type::I128 | Type::U128 => { + // We decompose the 128-bit value into two 64-bit limbs, and use the standard + // library intrinsics to get the count for those limbs. We then add the count + // for the low bits to that of the high bits, if the high bits are all zero, + // otherwise we take just the high bit count. + // + // Count leading zeros in the high bits + self.raw_exec("std::math::u64::clz", span); + self.emit_all( + [ + // [hi_clz, lo_hi, lo_lo] + // Count leading zeros in the low bits + masm::Instruction::MovUp2, // [lo_lo, hi_clz, lo_hi] + masm::Instruction::MovUp2, // [lo_hi, lo_lo, hi_clz] + ], + span, + ); + self.raw_exec("std::math::u64::clz", span); // [lo_clz, hi_clz] + self.emit_all( + [ + // Add the low bit leading zeros to those of the high bits, if the high + // bits are all zeros; otherwise return only the + // high bit count + masm::Instruction::PushU32(0), // [0, lo_clz, hi_clz] + masm::Instruction::Dup2, // [hi_clz, 0, lo_clz, hi_clz] + masm::Instruction::PushFelt(Felt::new(32)), + masm::Instruction::Lt, // [hi_clz < 32, 0, lo_clz, hi_clz] + masm::Instruction::CDrop, // [hi_clz < 32 ? 0 : lo_clz, hi_clz] + masm::Instruction::Add, + ], + span, + ); + } + Type::I64 | Type::U64 => { + self.raw_exec("std::math::u64::clz", span); + } + Type::I32 | Type::U32 => { + self.emit(masm::Instruction::U32Clz, span); + } + Type::I16 | Type::U16 => { + // There are always 16 leading zeroes from the perspective of the + // MASM u32clz instruction for values of (i|u)16 type, so subtract + // that from the count + self.emit_all( + [ + masm::Instruction::U32Clz, + // Subtract the excess bits from the count + masm::Instruction::U32WrappingSubImm(16.into()), + ], + span, + ); + } + Type::I8 | Type::U8 => { + // There are always 24 leading zeroes from the perspective of the + // MASM u32clz instruction for values of (i|u)8 type, so subtract + // that from the count + self.emit_all( + [ + masm::Instruction::U32Clz, + // Subtract the excess bits from the count + masm::Instruction::U32WrappingSubImm(24.into()), + ], + span, + ); + } + Type::I1 => { + // There is exactly one leading zero if false, or zero if true + self.emit(masm::Instruction::EqImm(Felt::ZERO.into()), span); + } + ty if !ty.is_integer() => { + panic!("invalid clz on {ty}: only integral types are supported") + } + ty => unimplemented!("clz for {ty} is not supported"), + } + self.push(Type::U32); + } + + /// Count the number of leading one bits in the integral value on top of the operand stack, + /// and place the count back on the stack as a u32 value. + /// + /// This operation is implemented so that it consumes the input operand. + pub fn clo(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + // The implementation here is effectively the same as `clz`, just with minor adjustments + Type::I128 | Type::U128 => { + // We decompose the 128-bit value into two 64-bit limbs, and use the standard + // library intrinsics to get the count for those limbs. We then add the count + // for the low bits to that of the high bits, if the high bits are all one, + // otherwise we take just the high bit count. + // + // Count leading ones in the high bits + self.raw_exec("std::math::u64::clo", span); // [hi_clo, lo_hi, lo_lo] + self.emit_all( + [ + // Count leading ones in the low bits + masm::Instruction::MovUp2, // [lo_lo, hi_clo, lo_hi] + masm::Instruction::MovUp2, // [lo_hi, lo_lo, hi_clo] + ], + span, + ); + self.raw_exec("std::math::u64::clo", span); // [lo_clo, hi_clo] + self.emit_all( + [ + // Add the low bit leading ones to those of the high bits, if the high bits + // are all one; otherwise return only the high bit count + masm::Instruction::PushU32(0), // [0, lo_clo, hi_clo] + masm::Instruction::Dup2, // [hi_clo, 0, lo_clo, hi_clo] + masm::Instruction::PushFelt(Felt::new(32)), + masm::Instruction::Lt, // [hi_clo < 32, 0, lo_clo, hi_clo] + masm::Instruction::CDrop, // [hi_clo < 32 ? 0 : lo_clo, hi_clo] + masm::Instruction::Add, + ], + span, + ); + } + Type::I64 | Type::U64 => self.raw_exec("std::math::u64::clo", span), + Type::I32 | Type::U32 => { + self.emit(masm::Instruction::U32Clo, span); + } + Type::I16 | Type::U16 => { + // There are always 16 leading zeroes from the perspective of the + // MASM u32clo instruction for values of (i|u)16 type, so to get + // the correct count, we need to bitwise-OR in a 16 bits of leading + // ones, then subtract that from the final count. + self.emit_all( + [ + // OR in the leading 16 ones + masm::Instruction::PushU32(u32::MAX << 16), + masm::Instruction::U32Or, + // Obtain the count + masm::Instruction::U32Clo, + // Subtract the leading bits we added from the count + masm::Instruction::U32WrappingSubImm(16.into()), + ], + span, + ); + } + Type::I8 | Type::U8 => { + // There are always 24 leading zeroes from the perspective of the + // MASM u32clo instruction for values of (i|u)8 type, so as with the + // 16-bit values, we need to bitwise-OR in 24 bits of leading ones, + // then subtract them from the final count. + self.emit_all( + [ + // OR in the leading 24 ones + masm::Instruction::PushU32(u32::MAX << 8), + masm::Instruction::U32Or, + // Obtain the count + masm::Instruction::U32Clo, + // Subtract the excess bits from the count + masm::Instruction::U32WrappingSubImm(24.into()), + ], + span, + ); + } + Type::I1 => { + // There is exactly one leading one if true, or zero if false + self.emit(masm::Instruction::EqImm(Felt::ONE.into()), span); + } + ty if !ty.is_integer() => { + panic!("invalid clo on {ty}: only integral types are supported") + } + ty => unimplemented!("clo for {ty} is not supported"), + } + self.push(Type::U32); + } + + /// Count the number of trailing zero bits in the integral value on top of the operand stack, + /// and place the count back on the stack as a u32 value. + /// + /// This operation is implemented so that it consumes the input operand. + pub fn ctz(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + Type::I128 | Type::U128 => { + // We decompose the 128-bit value into two 64-bit limbs, and use the standard + // library intrinsics to get the count for those limbs. We then add the count + // for the low bits to that of the high bits, if the high bits are all one, + // otherwise we take just the high bit count. + // + // Count trailing zeros in the high bits + self.raw_exec("std::math::u64::ctz", span); // [hi_ctz, lo_hi, lo_lo] + self.emit_all( + [ + // Count trailing zeros in the low bits + masm::Instruction::MovUp2, // [lo_lo, hi_ctz, lo_hi] + masm::Instruction::MovUp2, // [lo_hi, lo_lo, hi_ctz] + ], + span, + ); + self.raw_exec("std::math::u64::ctz", span); // [lo_ctz, hi_ctz] + self.emit_all( + [ + // Add the high bit trailing zeros to those of the low bits, if the low + // bits are all zero; otherwise return only the low + // bit count + masm::Instruction::Swap1, + masm::Instruction::PushU32(0), // [0, hi_ctz, lo_ctz] + masm::Instruction::Dup2, // [lo_ctz, 0, hi_ctz, lo_ctz] + masm::Instruction::PushFelt(Felt::new(32)), + masm::Instruction::Lt, // [lo_ctz < 32, 0, hi_ctz, lo_ctz] + masm::Instruction::CDrop, // [lo_ctz < 32 ? 0 : hi_ctz, lo_ctz] + masm::Instruction::Add, + ], + span, + ); + } + Type::I64 | Type::U64 => self.raw_exec("std::math::u64::ctz", span), + Type::I32 | Type::U32 => self.emit(masm::Instruction::U32Ctz, span), + Type::I16 | Type::U16 => { + // Clamp the total number of trailing zeros to 16 + self.emit_all( + [ + // Obtain the count + masm::Instruction::U32Ctz, + // Clamp to 16 + // operand_stack: [16, ctz] + masm::Instruction::PushU8(16), + // operand_stack: [ctz, 16, ctz] + masm::Instruction::Dup1, + // operand_stack: [ctz >= 16, 16, ctz] + masm::Instruction::PushFelt(Felt::new(16)), + masm::Instruction::Gte, + // operand_stack: [actual_ctz] + masm::Instruction::CDrop, + ], + span, + ); + } + Type::I8 | Type::U8 => { + // Clamp the total number of trailing zeros to 8 + self.emit_all( + [ + // Obtain the count + masm::Instruction::U32Ctz, + // Clamp to 8 + // operand_stack: [8, ctz] + masm::Instruction::PushU8(8), + // operand_stack: [ctz, 8, ctz] + masm::Instruction::Dup1, + // operand_stack: [ctz >= 8, 8, ctz] + masm::Instruction::PushFelt(Felt::new(8)), + masm::Instruction::Gte, + // operand_stack: [actual_ctz] + masm::Instruction::CDrop, + ], + span, + ); + } + Type::I1 => { + // There is exactly one trailing zero if false, or zero if true + self.emit(masm::Instruction::EqImm(Felt::ZERO.into()), span); + } + ty if !ty.is_integer() => { + panic!("invalid ctz on {ty}: only integral types are supported") + } + ty => unimplemented!("ctz for {ty} is not supported"), + } + self.push(Type::U32); + } + + /// Count the number of trailing one bits in the integral value on top of the operand stack, + /// and place the count back on the stack as a u32 value. + /// + /// This operation is implemented so that it consumes the input operand. + pub fn cto(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + match arg.ty() { + Type::I128 | Type::U128 => { + // We decompose the 128-bit value into two 64-bit limbs, and use the standard + // library intrinsics to get the count for those limbs. We then add the count + // for the low bits to that of the high bits, if the high bits are all one, + // otherwise we take just the high bit count. + // + // Count trailing ones in the high bits + self.raw_exec("std::math::u64::cto", span); // [hi_cto, lo_hi, lo_lo] + self.emit_all( + [ + // Count trailing ones in the low bits + masm::Instruction::MovUp2, // [lo_lo, hi_cto, lo_hi] + masm::Instruction::MovUp2, // [lo_hi, lo_lo, hi_cto] + ], + span, + ); + self.raw_exec("std::math::u64::cto", span); // [lo_cto, hi_cto] + self.emit_all( + [ + // Add the high bit trailing ones to those of the low bits, if the low bits + // are all one; otherwise return only the low bit count + masm::Instruction::Swap1, + masm::Instruction::PushU32(0), // [0, hi_cto, lo_cto] + masm::Instruction::Dup2, // [lo_cto, 0, hi_cto, lo_cto] + masm::Instruction::PushFelt(Felt::new(32)), + masm::Instruction::Lt, // [lo_cto < 32, 0, hi_cto, lo_cto] + masm::Instruction::CDrop, // [lo_cto < 32 ? 0 : hi_cto, lo_cto] + masm::Instruction::Add, + ], + span, + ); + } + Type::I64 | Type::U64 => self.raw_exec("std::math::u64::cto", span), + Type::I32 | Type::U32 | Type::I16 | Type::U16 | Type::I8 | Type::U8 => { + // The number of trailing ones is de-facto clamped by the bitwidth of + // the value, since all of the padding bits are leading zeros. + self.emit(masm::Instruction::U32Cto, span) + } + Type::I1 => { + // There is exactly one trailing one if true, or zero if false + self.emit(masm::Instruction::EqImm(Felt::ONE.into()), span); + } + ty if !ty.is_integer() => { + panic!("invalid cto on {ty}: only integral types are supported") + } + ty => unimplemented!("cto for {ty} is not supported"), + } + self.push(Type::U32); + } + + /// Invert the bitwise representation of the integral value on top of the operand stack. + /// + /// This has the effect of changing all 1 bits to 0s, and all 0 bits to 1s. + /// + /// This operation consumes the input operand. + pub fn bnot(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let ty = arg.ty(); + match &ty { + Type::I1 => self.emit(masm::Instruction::Not, span), + Type::I8 + | Type::U8 + | Type::I16 + | Type::U16 + | Type::I32 + | Type::U32 + | Type::I64 + | Type::U64 + | Type::I128 + | Type::U128 => { + let num_elements = ty.size_in_bits() / 32; + match num_elements { + 0 | 1 => { + self.emit(masm::Instruction::U32Not, span); + } + 2 => { + self.emit_repeat( + 2, + &[ + Span::new(span, masm::Instruction::Swap1), + Span::new(span, masm::Instruction::U32Not), + ], + ); + } + n => { + self.emit_template(n, |_| { + [ + Span::new(span, movup_from_offset(n)), + Span::new(span, masm::Instruction::U32Not), + ] + }); + } + } + } + ty if !ty.is_integer() => { + panic!("invalid bnot on {ty}, only integral types are supported") + } + ty => unimplemented!("bnot for {ty} is not supported"), + } + self.push(ty); + } + + /// Invert the boolean value on top of the operand stack. + /// + /// This operation consumes the input operand. + pub fn not(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + assert_eq!(arg.ty(), Type::I1, "logical NOT requires a boolean value"); + self.emit(masm::Instruction::Not, span); + self.push(Type::I1); + } + + /// Compute 2^N, where N is the integral value on top of the operand stack, as + /// a value of the same type as the input. + /// + /// The input value must be < 64, or execution will trap. + /// + /// This operation consumes the input operand. + pub fn pow2(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let ty = arg.ty(); + match &ty { + Type::U64 => { + self.emit_all( + [ + // Assert that the high bits are zero + masm::Instruction::Assertz, + // This asserts if value > 63, thus result is guaranteed to fit in u64 + masm::Instruction::Pow2, + // Obtain the u64 representation by splitting the felt result + masm::Instruction::U32Split, + ], + span, + ); + } + Type::I64 => { + self.raw_exec("intrinsics::i64::pow2", span); + } + Type::Felt => { + self.emit(masm::Instruction::Pow2, span); + } + Type::U32 => { + self.emit_all([masm::Instruction::Pow2, masm::Instruction::U32Assert], span); + } + Type::I32 => { + self.raw_exec("intrinsics::i32::pow2", span); + } + Type::U8 | Type::U16 => { + self.emit_all([masm::Instruction::Pow2, masm::Instruction::U32Assert], span); + // Cast u32 to u8/u16 + self.int32_to_uint(ty.size_in_bits() as u32, span); + } + ty if !ty.is_unsigned_integer() => { + panic!( + "invalid unary operand: pow2 only permits unsigned integer operands, got {ty}" + ) + } + ty => unimplemented!("pow2 for {ty} is not supported"), + } + self.push(ty); + } + + /// Increment the operand on top of the stack by 1. + /// + /// The input value must be an integer, and overflow has wrapping semantics. + /// + /// This operation consumes the input operand. + pub fn incr(&mut self, span: SourceSpan) { + let arg = self.stack.pop().expect("operand stack is empty"); + let ty = arg.ty(); + match &ty { + // For this specific case, wrapping u64 arithmetic works for both i64/u64 + Type::I64 | Type::U64 => { + self.push_u64(1, span); + self.add_u64(Overflow::Wrapping, span); + } + Type::Felt => { + self.emit(masm::Instruction::Incr, span); + } + // For this specific case, wrapping u32 arithmetic works for both i32/u32 + Type::I32 | Type::U32 => { + self.add_imm_u32(1, Overflow::Wrapping, span); + } + // We need to wrap the result for smallint types + Type::I8 | Type::U8 | Type::I16 | Type::U16 => { + let bits = ty.size_in_bits() as u32; + self.add_imm_u32(1, Overflow::Wrapping, span); + self.unchecked_mod_imm_u32(2u32.pow(bits), span); + } + ty if !ty.is_integer() => { + panic!("invalid unary operand: incr requires an integer operand, got {ty}") + } + ty => unimplemented!("incr for {ty} is not supported"), + } + self.push(ty); + } + + /// Compute the modular multiplicative inverse of the operand on top of the stack, `n`, i.e. + /// `n^-1 mod P`. + /// + /// This operation consumes the input operand. + pub fn inv(&mut self, span: SourceSpan) { + let arg = self.pop().expect("operand stack is empty"); + let ty = arg.ty(); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Inv, span); + } + ty if !ty.is_integer() => { + panic!("invalid unary operand: inv requires an integer, got {ty}") + } + ty => unimplemented!("inv for {ty} is not supported"), + } + self.push(ty); + } + + /// Compute the modular negation of the operand on top of the stack, `n`, i.e. `-n mod P`. + /// + /// This operation consumes the input operand. + pub fn neg(&mut self, span: SourceSpan) { + let arg = self.pop().expect("operand stack is empty"); + let ty = arg.ty(); + match &ty { + Type::Felt => { + self.emit(masm::Instruction::Neg, span); + } + ty if !ty.is_integer() => { + panic!("invalid unary operand: neg requires an integer, got {ty}") + } + ty => unimplemented!("neg for {ty} is not supported"), + } + self.push(ty); + } +} diff --git a/codegen/masm/src/emitter.rs b/codegen/masm/src/emitter.rs new file mode 100644 index 000000000..9c08f0d78 --- /dev/null +++ b/codegen/masm/src/emitter.rs @@ -0,0 +1,357 @@ +use alloc::collections::BTreeSet; + +use miden_assembly::diagnostics::WrapErr; +use midenc_hir::{Block, Operation, ProgramPoint, ValueRange, ValueRef}; +use midenc_hir_analysis::analyses::LivenessAnalysis; +use midenc_session::diagnostics::{SourceSpan, Spanned}; +use smallvec::SmallVec; + +use crate::{ + emit::{InstOpEmitter, OpEmitter}, + linker::LinkInfo, + masm, + opt::{operands::SolverOptions, OperandMovementConstraintSolver, SolverError}, + Constraint, OperandStack, +}; + +pub(crate) struct BlockEmitter<'b> { + pub liveness: &'b LivenessAnalysis, + pub link_info: &'b LinkInfo, + pub invoked: &'b mut BTreeSet, + pub target: Vec, + pub stack: OperandStack, +} + +impl BlockEmitter<'_> { + pub fn nest<'nested, 'current: 'nested>(&'current mut self) -> BlockEmitter<'nested> { + BlockEmitter { + liveness: self.liveness, + link_info: self.link_info, + invoked: self.invoked, + target: Default::default(), + stack: self.stack.clone(), + } + } + + pub fn emit(mut self, block: &Block) -> masm::Block { + self.emit_inline(block); + self.into_emitted_block(block.span()) + } + + pub fn emit_inline(&mut self, block: &Block) { + // Drop any unused block arguments on block entry + let block_ref = block.as_block_ref(); + let mut index = 0; + let unused_params = ValueRange::<2>::from(block.arguments()); + for next_param in unused_params { + if self.liveness.is_live_at_start(next_param, block_ref) { + index += 1; + continue; + } + + self.emitter().drop_operand_at_position(index, next_param.span()); + } + + // Drop any operands that may have been inherited from a predecessor where they are live, + // but they are dead on entry to this block. We do this now, rather than later, so that + // we keep the operand stack clean. + { + if let Some(next_op) = block.body().front().get() { + self.drop_unused_operands_at(&next_op, |value| { + // If the given value is not live at this op, it should be dropped + self.liveness.is_live_before(value, &next_op) + }); + } + } + + // Continue normally, by emitting the contents of the block based on the given schedule + for op in block.body() { + self.emit_inst(&op); + + // Drop any dead instruction results immediately + if op.has_results() { + let span = op.span(); + index = 0; + let results = ValueRange::<2>::from(op.results().all()); + for next_result in results { + if self.liveness.is_live_after(next_result, &op) { + index += 1; + continue; + } + + log::trace!(target: "codegen", "dropping dead instruction result {next_result} at index {index}"); + + self.emitter().drop_operand_at_position(index, span); + } + } + + // Drop any operands on the stack that did not live across this operation + if let Some(next_op) = op.as_operation_ref().next() { + let next_op = next_op.borrow(); + self.drop_unused_operands_at(&next_op, |value| { + // If the given value is not live at this op, it should be dropped + self.liveness.is_live_before(value, &next_op) + }); + } + } + } + + pub fn into_emitted_block(mut self, span: SourceSpan) -> masm::Block { + let ops = core::mem::take(&mut self.target); + masm::Block::new(span, ops) + } + + fn emit_inst(&mut self, op: &Operation) { + use crate::HirLowering; + + // If any values on the operand stack are no longer live, drop them now to avoid wasting + // operand stack space on operands that will never be used. + //self.drop_unused_operands_at(op); + + let lowering = op.as_trait::().unwrap_or_else(|| { + panic!("illegal operation: no lowering has been defined for '{}'", op.name()) + }); + + // Schedule operands for this instruction + lowering + .schedule_operands(self) + .wrap_err("failed during operand scheduling") + .unwrap_or_else(|err| panic!("{err}")); + + // Emit the Miden Assembly for this instruction to the current block + lowering + .emit(self) + .wrap_err("failed while emitting instruction lowering") + .unwrap_or_else(|err| panic!("{err}")); + } + + /// Drop the operands on the stack which are no longer live upon entry into + /// the current program point. + /// + /// This is intended to be called before scheduling `op` + pub fn drop_unused_operands_at(&mut self, op: &Operation, is_live: F) + where + F: Fn(ValueRef) -> bool, + { + log::trace!(target: "codegen", "dropping unused operands at: {op}"); + // We start by computing the set of unused operands on the stack at this point + // in the program. We will use the resulting vectors to schedule instructions + // that will move those operands to the top of the stack to be discarded + let mut unused = SmallVec::<[ValueRef; 4]>::default(); + let mut constraints = SmallVec::<[Constraint; 4]>::default(); + for operand in self.stack.iter().rev() { + let value = operand.as_value().expect("unexpected non-ssa value on stack"); + if !is_live(value) { + log::trace!(target: "codegen", "should drop {value} at {}", ProgramPoint::before(op)); + unused.push(value); + constraints.push(Constraint::Move); + } + } + + log::trace!(target: "codegen", "found unused operands {unused:?} with constraints {constraints:?}"); + + // Next, emit the optimal set of moves to get the unused operands to the top + if !unused.is_empty() { + // If the number of unused operands is greater than the number + // of used operands, then we will schedule manually, since this + // is a pathological use case for the operand scheduler. + let num_used = self.stack.len() - unused.len(); + log::trace!(target: "codegen", "there are {num_used} used operands out of {}", self.stack.len()); + if unused.len() > num_used { + // In this case, we emit code starting from the top + // of the stack, i.e. if we encounter an unused value + // on top, then we increment a counter and check the + // next value, and so on, until we reach a used value + // or the end of the stack. At that point, we emit drops + // for the unused batch, and reset the counter. + // + // If we encounter a used value on top, or we have dropped + // an unused batch and left a used value on top, we look + // to see if the next value is used/unused: + // + // * If used, we increment the counter until we reach an + // unused value or the end of the stack. We then move any + // unused value found to the top and drop it, subtract 1 + // from the counter, and resume where we left off + // + // * If unused, we check if it is just a single unused value, + // or if there is a string of unused values starting there. + // In the former case, we swap it to the top of the stack and + // drop it, and start over. In the latter case, we move the + // used value on top of the stack down past the last unused + // value, and then drop the unused batch. + let mut batch_size = 0; + let mut current_index = 0; + let mut unused_batch = false; + while self.stack.len() > current_index { + let value = self.stack[current_index].as_value().unwrap(); + let is_unused = unused.contains(&value); + // If we're looking at the top operand, start + // a new batch of either used or unused operands + if current_index == 0 { + unused_batch = is_unused; + current_index += 1; + batch_size += 1; + continue; + } + + // If we're putting together a batch of unused values, + // and the current value is unused, extend the batch + if unused_batch && is_unused { + batch_size += 1; + current_index += 1; + continue; + } + + // If we're putting together a batch of unused values, + // and the current value is used, drop the unused values + // we've found so far, and then reset our cursor to the top + if unused_batch { + let mut emitter = self.emitter(); + emitter.dropn(batch_size, op.span()); + batch_size = 0; + current_index = 0; + continue; + } + + // If we're putting together a batch of used values, + // and the current value is used, extend the batch + if !is_unused { + batch_size += 1; + current_index += 1; + continue; + } + + // Otherwise, we have found more unused value(s) behind + // a batch of used value(s), so we need to determine the + // best course of action + match batch_size { + // If we've only found a single used value so far, + // and there is more than two unused values behind it, + // then move the used value down the stack and drop the unused. + 1 => { + let unused_chunk_size = self + .stack + .iter() + .rev() + .skip(1) + .take_while(|o| unused.contains(&o.as_value().unwrap())) + .count(); + let mut emitter = self.emitter(); + if unused_chunk_size > 1 { + emitter.movdn(unused_chunk_size as u8, op.span()); + emitter.dropn(unused_chunk_size, op.span()); + } else { + emitter.swap(1, op.span()); + emitter.drop(op.span()); + } + } + // We've got multiple unused values together, so choose instead + // to move the unused value to the top and drop it + _ => { + let mut emitter = self.emitter(); + emitter.movup(current_index as u8, op.span()); + emitter.drop(op.span()); + } + } + batch_size = 0; + current_index = 0; + } + + // We may have accumulated a batch comprising the rest of the stack, handle that + // here. + if unused_batch && batch_size > 0 { + log::trace!(target: "codegen", "dropping {batch_size} operands from {:?}", &self.stack); + // It should only be possible to hit this point if the entire stack is unused + assert_eq!(batch_size, self.stack.len()); + match batch_size { + 1 => { + self.emitter().drop(op.span()); + } + _ => { + self.emitter().dropn(batch_size, op.span()); + } + } + } + } else { + self.schedule_operands(&unused, &constraints, op.span(), Default::default()) + .unwrap_or_else(|err| { + panic!( + "failed to schedule unused operands for {}: {err:?}", + ProgramPoint::before(op) + ) + }); + let mut emitter = self.emitter(); + emitter.dropn(unused.len(), op.span()); + } + } + } + + pub fn schedule_operands( + &mut self, + expected: &[ValueRef], + constraints: &[Constraint], + span: SourceSpan, + options: SolverOptions, + ) -> Result<(), SolverError> { + match OperandMovementConstraintSolver::new_with_options( + expected, + constraints, + &self.stack, + options, + ) { + Ok(solver) => { + let mut emitter = self.emitter(); + solver.solve_and_apply(&mut emitter, span) + } + Err(SolverError::AlreadySolved) => Ok(()), + Err(err) => { + panic!("unexpected error constructing operand movement constraint solver: {err:?}") + } + } + } + + /// Obtain the constraints that apply to this operation's operands, based on the provided + /// liveness analysis. + pub fn constraints_for( + &self, + op: &Operation, + operands: &ValueRange<'_, 4>, + ) -> SmallVec<[Constraint; 4]> { + operands + .iter() + .enumerate() + .map(|(index, value)| { + if self.liveness.is_live_after_entry(value, op) { + Constraint::Copy + } else { + // Check if this is the last use of `value` by this operation + let remaining = operands.slice(..index); + if remaining.contains(value) { + Constraint::Copy + } else { + Constraint::Move + } + } + }) + .collect() + } + + #[inline] + pub fn emit_op(&mut self, op: masm::Op) { + self.target.push(op); + } + + #[inline(always)] + pub fn inst_emitter<'short, 'long: 'short>( + &'long mut self, + inst: &'long Operation, + ) -> InstOpEmitter<'short> { + InstOpEmitter::new(inst, self.invoked, &mut self.target, &mut self.stack) + } + + #[inline(always)] + pub fn emitter<'short, 'long: 'short>(&'long mut self) -> OpEmitter<'short> { + OpEmitter::new(self.invoked, &mut self.target, &mut self.stack) + } +} diff --git a/codegen/masm/src/emulator/breakpoints.rs b/codegen/masm/src/emulator/breakpoints.rs deleted file mode 100644 index 19058ac4a..000000000 --- a/codegen/masm/src/emulator/breakpoints.rs +++ /dev/null @@ -1,364 +0,0 @@ -use std::collections::BTreeSet; - -use midenc_hir::FunctionIdent; -use rustc_hash::{FxHashMap, FxHashSet}; - -use super::{Addr, BreakpointEvent, EmulatorEvent, Instruction, InstructionPointer}; -use crate::BlockId; - -/// A breakpoint can be used to force the emulator to suspend -/// execution when a specific event or condition is reached. -/// -/// When hit, control is handed back to the owner of the emulator -/// so that they can inspect the state, potentially make changes, -/// and then resume execution if desired. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Breakpoint { - /// Break after each cycle - All, - /// Break when the cycle count reaches N - Cycle(usize), - /// Step until control reaches the given instruction pointer value - At(InstructionPointer), - /// Break at loop instructions - /// - /// The break will start on the looping instruction itself, and when - /// execution resumes, will break either at the next nested loop, or - /// if a complete iteration is reached, one of two places depending on - /// the type of looping instruction we're in: - /// - /// * `while.true` will break at the `while.true` on each iteration - /// * `repeat.n` will break at the top of the loop body on each iteration - Loops, - /// Break when the given function is called - Called(FunctionIdent), - /// Break when the given watchpoint is hit - /// - /// This is also referred to as a watchpoint - Watch(WatchpointId), -} - -/// The unique identifier associated with an active [Watchpoint] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WatchpointId(usize); -impl WatchpointId { - #[inline] - const fn index(self) -> usize { - self.0 - } -} - -/// A [Watchpoint] specifies a region of memory that will trigger -/// a breakpoint in the emulator when it is written to. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Watchpoint { - pub addr: u32, - pub size: u32, - mode: WatchMode, -} -impl Watchpoint { - pub const fn new(addr: u32, size: u32, mode: WatchMode) -> Self { - Self { addr, size, mode } - } - - pub fn mode(&self) -> WatchMode { - self.mode - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum WatchMode { - /// Treat the watchpoint like a breakpoint - Break, - /// Raise a warning when the watchpoint is hit, but do not break - Warn, - /// Publish an event when this watchpoint is hit, but do not break - Event, - /// The watchpoint is inactive - Disabled, -} - -/// The [BreakpointManager] is responsible for tracking what break- -/// and watchpoints have been created, activated/deactivated, and for -/// informing the emulator when a breakpoint was hit. -#[derive(Debug, Default)] -pub struct BreakpointManager { - /// True if we should break every cycle - break_every_cycle: bool, - /// True if we should break when returning from the current function - pub break_on_return: bool, - /// True if we should break at every loop instruction - break_loops: bool, - /// The set of cycle counts at which a breakpoint will be triggered - break_at_cycles: BTreeSet, - /// The set of functions we should break on when called - break_on_calls: FxHashSet, - /// The set of address ranges that will trigger a watchpoint - break_on_writes: Vec, - /// A mapping of blocks to instruction indices which will trigger a breakpoint - break_on_reached: FxHashMap>, -} -impl BreakpointManager { - /// Returns all watchpoints that are currently managed by this [BreakpointManager] - pub fn watchpoints(&self) -> impl Iterator + '_ { - self.break_on_writes.iter().copied() - } - - #[allow(unused)] - pub fn has_watchpoints(&self) -> bool { - !self.break_on_writes.is_empty() - } - - pub fn has_break_on_reached(&self) -> bool { - !self.break_on_reached.is_empty() - } - - /// Returns all breakpoints that are currently managed by this [BreakpointManager] - pub fn breakpoints(&self) -> impl Iterator { - BreakpointIter::new(self) - } - - /// Create a [Watchpoint] that monitors the specified memory region, using `mode` - /// to determine how writes to that region should be handled by the watchpoint - pub fn watch(&mut self, addr: u32, size: u32, mode: WatchMode) -> WatchpointId { - let id = WatchpointId(self.break_on_writes.len()); - self.break_on_writes.push(Watchpoint { addr, size, mode }); - id - } - - /// Set the watch mode for a [Watchpoint] using the identifier returned by [watch] - pub fn watch_mode(&mut self, id: WatchpointId, mode: WatchMode) { - self.break_on_writes[id.index()].mode = mode; - } - - /// Disables a [Watchpoint] using the identifier returned by [watch] - pub fn unwatch(&mut self, id: WatchpointId) { - self.break_on_writes[id.index()].mode = WatchMode::Disabled; - } - - /// Clears all watchpoints - pub fn unwatch_all(&mut self) { - self.break_on_writes.clear(); - } - - /// Set the given breakpoint - pub fn set(&mut self, bp: Breakpoint) { - use std::collections::hash_map::Entry; - - match bp { - Breakpoint::All => { - self.break_every_cycle = true; - } - Breakpoint::Cycle(cycle) => { - self.break_at_cycles.insert(cycle); - } - Breakpoint::At(ip) => match self.break_on_reached.entry(ip.block) { - Entry::Vacant(entry) => { - entry.insert(FxHashSet::from_iter([ip.index])); - } - Entry::Occupied(mut entry) => { - entry.get_mut().insert(ip.index); - } - }, - Breakpoint::Loops => { - self.break_loops = true; - } - Breakpoint::Called(id) => { - self.break_on_calls.insert(id); - } - Breakpoint::Watch(id) => { - self.break_on_writes[id.index()].mode = WatchMode::Break; - } - } - } - - /// Unset/disable the given breakpoint - pub fn unset(&mut self, bp: Breakpoint) { - match bp { - Breakpoint::All => { - self.break_every_cycle = false; - } - Breakpoint::Cycle(cycle) => { - self.break_at_cycles.remove(&cycle); - } - Breakpoint::At(ip) => { - if let Some(indices) = self.break_on_reached.get_mut(&ip.block) { - indices.remove(&ip.index); - } - } - Breakpoint::Loops => { - self.break_loops = false; - } - Breakpoint::Called(id) => { - self.break_on_calls.remove(&id); - } - Breakpoint::Watch(id) => { - self.unwatch(id); - } - } - } - - /// Clear all breakpoints, but leaves watchpoints in place - pub fn unset_all(&mut self) { - self.break_every_cycle = false; - self.break_at_cycles.clear(); - self.break_loops = false; - self.break_on_calls.clear(); - self.break_on_reached.clear(); - } - - /// Clear all breakpoints and watchpoints - pub fn clear(&mut self) { - self.unset_all(); - self.unwatch_all(); - } - - /// Force the emulator to break the next time we return from a function - pub fn break_on_return(&mut self, value: bool) { - self.break_on_return = value; - } - - /// Respond to emulator events, and return true if at least one breakpoint was hit - pub fn handle_event( - &mut self, - event: EmulatorEvent, - ip: Option, - ) -> Option { - use core::cmp::Ordering; - - match event { - EmulatorEvent::EnterFunction(id) => { - if self.break_on_calls.contains(&id) { - Some(BreakpointEvent::Called(id)) - } else { - None - } - } - EmulatorEvent::EnterLoop(block) if self.break_loops => { - Some(BreakpointEvent::Loop(block)) - } - EmulatorEvent::EnterLoop(block) => { - if self.should_break_at(block, 0) { - Some(BreakpointEvent::Reached(InstructionPointer::new(block))) - } else { - None - } - } - EmulatorEvent::CycleStart(cycle) => { - let mut cycle_hit = false; - self.break_at_cycles.retain(|break_at_cycle| match cycle.cmp(break_at_cycle) { - Ordering::Equal => { - cycle_hit = true; - false - } - Ordering::Greater => false, - Ordering::Less => true, - }); - if cycle_hit { - Some(BreakpointEvent::ReachedCycle(cycle)) - } else if self.break_every_cycle { - Some(BreakpointEvent::Step) - } else { - None - } - } - EmulatorEvent::ExitFunction(_) if self.break_on_return => { - Some(BreakpointEvent::StepOut) - } - EmulatorEvent::ExitFunction(_) - | EmulatorEvent::ExitLoop(_) - | EmulatorEvent::Jump(_) => match ip { - Some(Instruction { ip, .. }) => { - let break_at_current_ip = self.should_break_at(ip.block, ip.index); - if break_at_current_ip { - Some(BreakpointEvent::Reached(ip)) - } else if self.break_every_cycle { - Some(BreakpointEvent::Step) - } else { - None - } - } - None => { - if self.break_every_cycle { - Some(BreakpointEvent::Step) - } else { - None - } - } - }, - EmulatorEvent::MemoryWrite { addr, size } => { - self.matches_watchpoint(addr, size).copied().map(BreakpointEvent::Watch) - } - EmulatorEvent::Stopped | EmulatorEvent::Suspended => None, - EmulatorEvent::Breakpoint(bp) => Some(bp), - } - } - - pub fn should_break_at(&self, block: BlockId, index: usize) -> bool { - self.break_on_reached - .get(&block) - .map(|indices| indices.contains(&index)) - .unwrap_or(false) - } - - #[inline] - #[allow(unused)] - pub fn should_break_on_write(&self, addr: Addr, size: u32) -> bool { - self.matches_watchpoint(addr, size).is_some() - } - - fn matches_watchpoint(&self, addr: Addr, size: u32) -> Option<&Watchpoint> { - let end_addr = addr + size; - self.break_on_writes.iter().find(|wp| { - let wp_end = wp.addr + wp.size; - if let WatchMode::Break = wp.mode { - addr <= wp_end && end_addr >= wp.addr - } else { - false - } - }) - } -} - -struct BreakpointIter { - bps: Vec, -} -impl BreakpointIter { - fn new(bpm: &BreakpointManager) -> Self { - let mut iter = BreakpointIter { - bps: Vec::with_capacity(4), - }; - iter.bps.extend(bpm.break_on_writes.iter().enumerate().filter_map(|(i, wp)| { - if wp.mode == WatchMode::Break { - Some(Breakpoint::Watch(WatchpointId(i))) - } else { - None - } - })); - iter.bps.extend(bpm.break_at_cycles.iter().copied().map(Breakpoint::Cycle)); - for (block, indices) in bpm.break_on_reached.iter() { - if indices.is_empty() { - continue; - } - let block = *block; - for index in indices.iter().copied() { - iter.bps.push(Breakpoint::At(InstructionPointer { block, index })) - } - } - if bpm.break_loops { - iter.bps.push(Breakpoint::Loops); - } - if bpm.break_every_cycle { - iter.bps.push(Breakpoint::All); - } - iter - } -} -impl Iterator for BreakpointIter { - type Item = Breakpoint; - - fn next(&mut self) -> Option { - self.bps.pop() - } -} -impl core::iter::FusedIterator for BreakpointIter {} diff --git a/codegen/masm/src/emulator/debug.rs b/codegen/masm/src/emulator/debug.rs deleted file mode 100644 index a0041dfb4..000000000 --- a/codegen/masm/src/emulator/debug.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::fmt; - -use midenc_hir::{Felt, FunctionIdent, OperandStack}; - -use super::{Addr, InstructionPointer, InstructionWithOp}; - -/// Represents basic information about a frame on the call stack -pub struct CallFrame { - pub function: FunctionIdent, - pub fp: Addr, - pub ip: Option, -} - -/// Represents the current state of the program being executed for use in debugging/troubleshooting -pub struct DebugInfo<'a> { - /// The current cycle count - pub cycle: usize, - /// The current function being executed - pub function: FunctionIdent, - /// The address at which locals for the current function begin - pub fp: Addr, - /// The current instruction pointer metadata, if one is pending - pub ip: Option, - /// The current state of the operand stack - pub stack: &'a OperandStack, -} -impl DebugInfo<'_> { - pub fn to_owned(self) -> DebugInfoWithStack { - let stack = self.stack.clone(); - DebugInfoWithStack { - cycle: self.cycle, - function: self.function, - fp: self.fp, - ip: self.ip, - stack, - } - } -} -impl<'a> fmt::Debug for DebugInfo<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use midenc_hir::Stack; - - f.debug_struct("DebugInfo") - .field("cycle", &self.cycle) - .field("function", &self.function) - .field("fp", &self.fp) - .field("ip", &self.ip) - .field("stack", &self.stack.debug()) - .finish() - } -} - -/// Same as [DebugInfo], but takes a clone of the operand stack, rather than a reference -pub struct DebugInfoWithStack { - /// The current cycle count - pub cycle: usize, - /// The current function being executed - pub function: FunctionIdent, - /// The address at which locals for the current function begin - pub fp: Addr, - /// The current instruction pointer metadata, if one is pending - pub ip: Option, - /// The current state of the operand stack - pub stack: OperandStack, -} -impl fmt::Debug for DebugInfoWithStack { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use midenc_hir::Stack; - - f.debug_struct("DebugInfo") - .field("cycle", &self.cycle) - .field("function", &self.function) - .field("fp", &self.fp) - .field("ip", &self.ip) - .field("stack", &self.stack.debug()) - .finish() - } -} diff --git a/codegen/masm/src/emulator/events.rs b/codegen/masm/src/emulator/events.rs deleted file mode 100644 index 5e539a982..000000000 --- a/codegen/masm/src/emulator/events.rs +++ /dev/null @@ -1,70 +0,0 @@ -use midenc_hir::FunctionIdent; - -use super::{Addr, InstructionPointer}; -use crate::BlockId; - -/// A control-flow event that occurred as a side-effect of -/// advancing the instruction pointer. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum ControlEffect { - /// No control effects occurred - None, - /// We jumped to a nested block - Enter, - /// We jumped to a parent block - Exit, - /// We jumped back to the start of a while loop - Loopback, - /// We started the `n`th iteration of a repeat block - Repeat(u16), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum BreakpointEvent { - /// Breakpoint was hit because we always break on each step - Step, - /// Breakpoint was hit because we are stepping out of the current function - StepOut, - /// Breakpoint for a specific clock cycle was reached - ReachedCycle(usize), - /// Breakpoint for a specific instruction pointer value was reached - Reached(InstructionPointer), - /// Breakpoint for a loop was hit - Loop(BlockId), - /// Breakpoint for the given function was hit - Called(FunctionIdent), - /// The given watchpoint was hit as a breakpoint - Watch(super::Watchpoint), -} - -#[derive(Debug, Copy, Clone)] -pub enum EmulatorEvent { - /// The start of a new cycle has begun - CycleStart(usize), - /// The specified function was called and the emulator is at the first instruction in its body - EnterFunction(FunctionIdent), - /// The emulator has returned from the specified function, and the emulator is at the first - /// instruction following it in the caller, or if there are no more instructions in the caller, - /// waiting to return from the caller function on the next resumption. - ExitFunction(FunctionIdent), - /// The emulator has entered a loop, whose body is the specified block. - /// - /// The emulator is at the first instruction in that block. - EnterLoop(BlockId), - /// The emulator has exited a loop, whose body is the specified block, and is at the first - /// instruction following it in the enclosing block. If there are no more instructions after - /// the loop, the emulator will return from the enclosing function on the next resumption. - ExitLoop(BlockId), - /// Control has transferred to `block` - /// - /// This event is only used when the control flow instruction was not a loop instruction - Jump(BlockId), - /// The emulator just performed a store to `addr` of `size` bytes - MemoryWrite { addr: Addr, size: u32 }, - /// The emulator has reached a breakpoint - Breakpoint(BreakpointEvent), - /// The emulator has suspended, and can be resumed at will - Suspended, - /// The emulator has reached the end of the program and has stopped executing - Stopped, -} diff --git a/codegen/masm/src/emulator/functions.rs b/codegen/masm/src/emulator/functions.rs deleted file mode 100644 index 45c6165ca..000000000 --- a/codegen/masm/src/emulator/functions.rs +++ /dev/null @@ -1,774 +0,0 @@ -use std::{cell::RefCell, fmt, rc::Rc, sync::Arc}; - -use midenc_hir::Felt; -use smallvec::{smallvec, SmallVec}; - -use super::{Addr, ControlEffect, EmulationError, Emulator, InstructionPointer}; -use crate::{BlockId, Function, Op}; - -/// The type signature for native Rust functions callable from MASM IR -pub type NativeFn = dyn FnMut(&mut Emulator, &[Felt]) -> Result<(), EmulationError>; - -/// We allow functions in the emulator to be defined in either MASM IR, or native Rust. -/// -/// Functions implemented in Rust are given a mutable reference to the emulator, so they -/// have virtually unlimited power, but are correspondingly very unsafe. With great -/// power comes great responsibility, etc. -#[derive(Clone)] -pub enum Stub { - /// This function has a definition in Miden Assembly - Asm(Arc), - /// This function has a native Rust implementation - Native(Rc>>), -} - -/// This enum represents a frame on the control stack -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum ControlFrame { - /// A control frame used to revisit a single instruction - /// used to control entry into a loop, e.g. `while.true` - Loopback(InstructionPointer), - /// Control is in a normal block - Block(InstructionPointer), - /// Control was transferred into a while.true loop - While(InstructionPointer), - /// Control was transferred to a repeat loop - Repeat(RepeatState), -} -impl Default for ControlFrame { - fn default() -> Self { - Self::Block(InstructionPointer::new(BlockId::from_u32(0))) - } -} -impl ControlFrame { - pub const fn ip(&self) -> InstructionPointer { - match self { - Self::Loopback(ip) - | Self::Block(ip) - | Self::While(ip) - | Self::Repeat(RepeatState { ip, .. }) => *ip, - } - } - - /// Move the instruction pointer forward one instruction and return a copy to the caller - fn move_next(&mut self) -> InstructionPointer { - match self { - Self::Block(ref mut ip) - | Self::While(ref mut ip) - | Self::Repeat(RepeatState { ref mut ip, .. }) => { - ip.index += 1; - *ip - } - Self::Loopback(_) => panic!("cannot move a loopback control frame"), - } - } - - /// Move the instruction pointer backward one instruction and return a copy to the caller - #[allow(unused)] - fn move_prev(&mut self) -> InstructionPointer { - match self { - Self::Block(ref mut ip) - | Self::While(ref mut ip) - | Self::Repeat(RepeatState { ref mut ip, .. }) => { - let index = ip.index.saturating_sub(1); - ip.index = index; - *ip - } - Self::Loopback(_) => panic!("cannot move a loopback control frame"), - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct RepeatState { - /// The instruction pointer in the repeat block - pub ip: InstructionPointer, - /// Corresponds to `n` in `repeat.n`, i.e. the number of iterations to perform - pub n: u16, - /// The number of iterations completed so far - pub iterations: u16, -} - -#[derive(Debug)] -pub struct ControlStack { - /// The control frame for the current instruction being executed - current: ControlFrame, - /// The next instruction to be executed - pending: Option, - /// The control frame corresponding to the next instruction - pending_frame: Option, - /// Pending frames from which to fetch the next instruction - frames: SmallVec<[ControlFrame; 2]>, -} -impl ControlStack { - pub fn new(ip: InstructionPointer) -> Self { - let current = ControlFrame::Block(ip); - Self { - current, - pending: None, - pending_frame: Some(current), - frames: smallvec![], - } - } - - /// The instruction pointer corresponding to the currently executing instruction - #[inline(always)] - pub const fn ip(&self) -> InstructionPointer { - self.current.ip() - } - - /// Push a new control frame for a repeat loop on the stack, and move the instruction - /// pointer so that the pending instruction is the first instruction of the body - #[inline] - pub fn enter_repeat(&mut self, block: BlockId, n: u16) { - let ip = InstructionPointer::new(block); - let pending_frame = self.pending_frame.replace(ControlFrame::Repeat(RepeatState { - ip, - n, - iterations: 0, - })); - self.pending = None; - self.current = ControlFrame::Repeat(RepeatState { - ip, - n, - iterations: 0, - }); - if let Some(pending_frame) = pending_frame { - self.frames.push(pending_frame); - } - } - - /// Push a new control frame for a while loop on the stack, and move the instruction - /// pointer so that the pending instruction is the first instruction of the body - #[inline] - pub fn enter_while_loop(&mut self, block: BlockId) { - let ip = InstructionPointer::new(block); - let pending_frame = self.pending_frame.replace(ControlFrame::While(ip)); - // Make sure we preserve the pending frame for when we loopback to the - // while the final time, and skip over it - if let Some(pending_frame) = pending_frame { - self.frames.push(pending_frame); - } - // We need to revisit the `while.true` at least once, so we stage a special - // control frame that expires as soon as that instruction is visited. - self.frames.push(ControlFrame::Loopback(self.current.ip())); - self.pending = None; - self.current = ControlFrame::While(ip); - } - - /// Push a new control frame for a normal block on the stack, and move the instruction - /// pointer so that the pending instruction is the first instruction of the body - #[inline] - pub fn enter_block(&mut self, block: BlockId) { - let ip = InstructionPointer::new(block); - let pending_frame = self.pending_frame.replace(ControlFrame::Block(ip)); - self.pending = None; - self.current = ControlFrame::Block(ip); - if let Some(pending_frame) = pending_frame { - self.frames.push(pending_frame); - } - } - - /// Get the next instruction to execute without moving the instruction pointer - pub fn peek(&self) -> Option { - match self.pending { - None => self.pending_frame.map(|frame| Instruction { - continuing_from: None, - ip: frame.ip(), - effect: ControlEffect::Enter, - }), - pending @ Some(_) => pending, - } - } - - pub fn next(&mut self, function: &Function) -> Option { - if self.pending.is_none() { - let pending_frame = self.pending_frame?; - let ip = pending_frame.ip(); - let effect = if ip.index == 0 { - ControlEffect::Enter - } else { - ControlEffect::None - }; - self.pending = Some(Instruction { - continuing_from: None, - ip, - effect, - }); - } - - let pending = self.pending.take()?; - let mut pending_frame = self.pending_frame.unwrap(); - let current_frame = pending_frame; - - if is_last_instruction(current_frame, function) { - let pending_frame_and_effect = self.find_continuation_frame(current_frame, function); - match pending_frame_and_effect { - Some((pending_frame, effect)) => { - self.pending = Some(Instruction { - continuing_from: Some(current_frame), - ip: pending_frame.ip(), - effect, - }); - self.pending_frame = Some(pending_frame); - self.current = current_frame; - } - None => { - self.pending = None; - self.pending_frame = None; - self.current = current_frame; - } - } - } else { - pending_frame.move_next(); - self.pending = Some(Instruction { - continuing_from: None, - ip: pending_frame.ip(), - effect: ControlEffect::None, - }); - self.pending_frame = Some(pending_frame); - self.current = current_frame; - } - - Some(pending) - } - - fn find_continuation_frame( - &mut self, - current: ControlFrame, - function: &Function, - ) -> Option<(ControlFrame, ControlEffect)> { - match current { - ControlFrame::Loopback(_) => { - // This frame is usually preceded by a top-level block frame, but if - // the body of a function starts with a loop header, then there may not - // be any parent frames, in which case we're returning from the function - let continuation = self.frames.pop()?; - return Some((continuation, ControlEffect::Exit)); - } - ControlFrame::While(_) => { - // There will always be a frame available when a while frame is on the stack - let continuation = self.frames.pop().unwrap(); - return Some((continuation, ControlEffect::Loopback)); - } - ControlFrame::Repeat(repeat) => { - let next_iteration = repeat.iterations + 1; - if next_iteration < repeat.n { - let ip = InstructionPointer::new(repeat.ip.block); - let pending_frame = ControlFrame::Repeat(RepeatState { - ip, - iterations: next_iteration, - n: repeat.n, - }); - let effect = ControlEffect::Repeat(next_iteration); - return Some((pending_frame, effect)); - } - } - _ => (), - } - - let mut current = current; - loop { - let transfer_to = self.frames.pop(); - match current { - ControlFrame::While(_) => { - break Some((transfer_to.unwrap(), ControlEffect::Loopback)); - } - ControlFrame::Repeat(mut repeat) => { - let next_iteration = repeat.iterations + 1; - if next_iteration < repeat.n { - if let Some(transfer_to) = transfer_to { - self.frames.push(transfer_to); - } - let ip = InstructionPointer::new(repeat.ip.block); - repeat.iterations = next_iteration; - repeat.ip = ip; - - let pending_frame = ControlFrame::Repeat(repeat); - let effect = ControlEffect::Repeat(next_iteration); - break Some((pending_frame, effect)); - } - current = transfer_to?; - } - ControlFrame::Loopback(_) | ControlFrame::Block(_) => { - let pending_frame = transfer_to?; - if is_valid_instruction(pending_frame.ip(), function) { - break Some((pending_frame, ControlEffect::Exit)); - } - current = pending_frame; - } - } - } - } -} - -#[inline(always)] -fn is_last_instruction(frame: ControlFrame, function: &Function) -> bool { - match frame { - ControlFrame::Loopback(_) => true, - frame => { - let ip = frame.ip(); - ip.index >= function.block(ip.block).ops.len().saturating_sub(1) - } - } -} - -#[inline(always)] -fn is_valid_instruction(ip: InstructionPointer, function: &Function) -> bool { - ip.index < function.block(ip.block).ops.len() -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Instruction { - /// The new instruction pointer value - pub ip: InstructionPointer, - /// If set, this instruction is in a control frame which was suspended - /// and is now being resumed/continued. The given frame was the state - /// of that frame when the instruction pointer was advanced. - pub continuing_from: Option, - /// The control flow effect that occurred when advancing the instruction pointer - pub effect: ControlEffect, -} -impl Instruction { - pub fn with_op(self, function: &Function) -> Option { - self.op(function).map(|op| InstructionWithOp { - ip: self.ip, - continuing_from: self.continuing_from, - effect: self.effect, - op, - }) - } - - #[inline(always)] - pub fn op(&self, function: &Function) -> Option { - function.body.get(self.ip).map(|op| op.into_inner()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct InstructionWithOp { - /// The new instruction pointer value - pub ip: InstructionPointer, - /// If set, this instruction is in a control frame which was suspended - /// and is now being resumed/continued. The given frame was the state - /// of that frame when the instruction pointer was advanced. - pub continuing_from: Option, - /// The control flow effect that occurred when advancing the instruction pointer - pub effect: ControlEffect, - /// The op the instruction pointer points to - pub op: Op, -} - -/// This struct represents an activation record for a function on the call stack -/// -/// When a program begins executing, an activation record is created for the entry point, -/// and any calls made from the entry get their own activation record, recursively down the -/// call graph. -/// -/// The activation record contains state about the execution of that function of interest -/// to the emulator, in particular, the instruction pointer, the frame pointer for locals, -/// and the function-local control stack -pub struct Activation { - function: Arc, - fp: Addr, - control_stack: ControlStack, -} -impl fmt::Debug for Activation { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Activation") - .field("function", &self.function.name) - .field("fp", &self.fp) - .field("control_stack", &self.control_stack) - .finish() - } -} -impl Activation { - /// Create a new activation record for `function`, using `fp` as the frame pointer for this - /// activation - pub fn new(function: Arc, fp: Addr) -> Self { - let block = function.body.id(); - let control_stack = ControlStack::new(InstructionPointer::new(block)); - Self { - function, - fp, - control_stack, - } - } - - #[cfg(test)] - pub fn at(function: Arc, fp: Addr, control_stack: ControlStack) -> Self { - Self { - function, - fp, - control_stack, - } - } - - #[inline(always)] - pub fn function(&self) -> &Function { - &self.function - } - - #[inline(always)] - pub fn fp(&self) -> Addr { - self.fp - } - - #[inline(always)] - pub fn ip(&self) -> InstructionPointer { - self.control_stack.ip() - } - - /// Advance to the next instruction, returning the current [InstructionWithOp] - /// - /// If all code in the function has been executed, None will be returned. - #[inline] - pub fn next(&mut self) -> Option { - self.move_next().and_then(|ix| ix.with_op(&self.function)) - } - - /// Advance to the next instruction, returning the current [Instruction] - /// - /// If all code in the function has been executed, None will be returned. - pub fn move_next(&mut self) -> Option { - self.control_stack.next(&self.function) - } - - /// Peek at the [Instruction] which will be returned when [move_next] is called next - #[inline(always)] - pub fn peek(&self) -> Option { - self.control_stack.peek() - } - - /// Peek at the [InstructionWithOp] corresponding to the next instruction to be returned from - /// [move_next] - pub fn peek_with_op(&self) -> Option { - self.control_stack.peek().and_then(|ix| ix.with_op(&self.function)) - } - - /// Peek at the [Op] coresponding to the next instruction to be returned from [move_next] - #[allow(unused)] - pub fn peek_op(&self) -> Option { - self.control_stack.peek().and_then(|ix| ix.op(&self.function)) - } - - /// Set the instruction pointer to the first instruction of `block` - /// - /// This helper ensures the internal state of the activation record is maintained - pub(super) fn enter_block(&mut self, block: BlockId) { - self.control_stack.enter_block(block); - } - - /// Set the instruction pointer to the first instruction of `block`, which is the body of - /// a while loop. - /// - /// This ensures that when we attempt to leave the while loop, the "next" instruction will - /// be the `while.true` itself, so that its condition gets re-evaluated. This will result - /// in an infinite loop unless the value of the condition is zero, or the operand stack is - /// exhausted, causing an assertion. - pub(super) fn enter_while_loop(&mut self, block: BlockId) { - self.control_stack.enter_while_loop(block); - } - - /// Set the instruction pointer to the first instruction of `block`, which is the body of - /// a repeat loop with `n` iterations. - /// - /// We use an auxiliary structure to track repeat loops, so this works a bit differently - /// than `enter_while_loop`, but has the same effect. The state we track is used to determine - /// when we've executed `count` iterations of the loop and should exit to the next instruction - /// following the `repeat.N`. - pub(super) fn repeat_block(&mut self, block: BlockId, count: u16) { - self.control_stack.enter_repeat(block, count); - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{assert_matches, Signature, SourceSpan}; - - use super::*; - - #[test] - fn activation_record_start_of_block() { - let mut activation = Activation::new(test_function(), 0); - let body_blk = activation.function.body.id(); - assert_eq!( - activation.peek(), - Some(Instruction { - continuing_from: None, - ip: InstructionPointer { - block: body_blk, - index: 0 - }, - effect: ControlEffect::Enter, - }) - ); - assert_eq!(activation.peek_op(), Some(Op::PushU8(2))); - - // Advance the instruction pointer - assert_matches!( - activation.next(), - Some(InstructionWithOp { - op: Op::PushU8(2), - .. - }) - ); - assert_eq!( - activation.peek_with_op(), - Some(InstructionWithOp { - op: Op::PushU8(1), - continuing_from: None, - ip: InstructionPointer { - block: body_blk, - index: 1 - }, - effect: ControlEffect::None - }) - ); - } - - #[test] - fn activation_record_if_true_entry() { - let function = test_function(); - let body_blk = function.body.id(); - let control_stack = ControlStack::new(InstructionPointer { - block: body_blk, - index: 5, - }); - let mut activation = Activation::at(test_function(), 0, control_stack); - - assert_eq!( - activation.peek(), - Some(Instruction { - continuing_from: None, - ip: InstructionPointer { - block: body_blk, - index: 5 - }, - effect: ControlEffect::Enter - }) - ); - let Some(Op::If(then_blk, _)) = activation.peek_op() else { - panic!("expected if.true, got {:?}", activation.peek_with_op()) - }; - - // Enter the truthy branch of the if.true - activation.enter_block(then_blk); - - assert_eq!( - activation.peek_with_op(), - Some(InstructionWithOp { - op: Op::PushU8(1), - continuing_from: None, - ip: InstructionPointer { - block: then_blk, - index: 0 - }, - effect: ControlEffect::Enter, - }) - ); - - // Advance the instruction pointer - assert_matches!( - activation.next(), - Some(InstructionWithOp { - op: Op::PushU8(1), - effect: ControlEffect::Enter, - .. - }) - ); - assert_eq!( - activation.peek_with_op(), - Some(InstructionWithOp { - op: Op::While(BlockId::from_u32(3)), - continuing_from: None, - ip: InstructionPointer { - block: then_blk, - index: 1 - }, - effect: ControlEffect::None - }) - ); - } - - #[test] - fn activation_record_nested_control_flow_exit() { - let function = test_function(); - let body_blk = function.body.id(); - let control_stack = ControlStack::new(InstructionPointer { - block: body_blk, - index: 5, - }); - let mut activation = Activation::at(test_function(), 0, control_stack); - - let next = activation.next().unwrap(); - assert_eq!(ControlEffect::None, next.effect); - let Op::If(then_blk, _) = next.op else { - panic!("expected if.true, got {next:?}") - }; - - // Enter the truthy branch of the if.true - activation.enter_block(then_blk); - - // Step over the first instruction, to the `while.true` - assert_matches!( - activation.next(), - Some(InstructionWithOp { - op: Op::PushU8(1), - effect: ControlEffect::Enter, - .. - }) - ); - - let Some(Op::While(loop_body)) = activation.peek_op() else { - panic!("expected while.true, got {:?}", activation.peek_op()) - }; - - // Enter loop body - activation.next().unwrap(); - activation.enter_while_loop(loop_body); - - assert_eq!( - activation.peek_with_op(), - Some(InstructionWithOp { - op: Op::Dup(1), - continuing_from: None, - ip: InstructionPointer { - block: loop_body, - index: 0 - }, - effect: ControlEffect::Enter - }) - ); - - // Advance the instruction pointer to the end of the loop body - let next = activation.next().unwrap(); - assert_eq!(next.op, Op::Dup(1)); - let next = activation.next().unwrap(); - assert_eq!(next.op, Op::Dup(1)); - let next = activation.next().unwrap(); - assert_eq!(next.op, Op::Incr); - - // Ensure things are normal at the last instruction - assert_eq!( - activation.peek_with_op(), - Some(InstructionWithOp { - op: Op::U32Lt, - continuing_from: None, - ip: InstructionPointer { - block: loop_body, - index: 3 - }, - effect: ControlEffect::None - }) - ); - - // Advance the instruction pointer, obtaining the last instruction of the loop body - assert_eq!(activation.next().map(|ix| ix.op), Some(Op::U32Lt)); - - // Exit while.true - assert_eq!( - activation.peek_with_op(), - Some(InstructionWithOp { - op: Op::While(loop_body), - continuing_from: Some(ControlFrame::While(InstructionPointer { - block: loop_body, - index: 3 - })), - ip: InstructionPointer { - block: then_blk, - index: 1 - }, - effect: ControlEffect::Loopback, - }) - ); - assert_eq!( - activation.next(), - Some(InstructionWithOp { - op: Op::While(loop_body), - continuing_from: Some(ControlFrame::While(InstructionPointer { - block: loop_body, - index: 3 - })), - ip: InstructionPointer { - block: then_blk, - index: 1 - }, - effect: ControlEffect::Loopback, - }) - ); - - // Exit if.true - let callee = "test::foo".parse().unwrap(); - assert_eq!( - activation.peek_with_op(), - Some(InstructionWithOp { - op: Op::Exec(callee), - continuing_from: Some(ControlFrame::Loopback(InstructionPointer { - block: then_blk, - index: 1 - })), - ip: InstructionPointer { - block: body_blk, - index: 6, - }, - effect: ControlEffect::Exit, - }) - ); - assert_eq!( - activation.next(), - Some(InstructionWithOp { - op: Op::Exec(callee), - continuing_from: Some(ControlFrame::Loopback(InstructionPointer { - block: then_blk, - index: 1 - })), - ip: InstructionPointer { - block: body_blk, - index: 6, - }, - effect: ControlEffect::Exit, - }) - ); - - // Return from the function - assert_matches!(activation.next(), None); - } - - fn test_function() -> Arc { - let span = SourceSpan::default(); - let mut function = - Function::new("test::main".parse().unwrap(), Signature::new(vec![], vec![])); - let then_blk = function.create_block(); - let else_blk = function.create_block(); - let while_blk = function.create_block(); - { - let body = function.block_mut(function.body.id()); - body.push(Op::PushU8(2), span); - body.push(Op::PushU8(1), span); - body.push(Op::Dup(1), span); - body.push(Op::Dup(1), span); - body.push(Op::U32Lt, span); - body.push(Op::If(then_blk, else_blk), span); - body.push(Op::Exec("test::foo".parse().unwrap()), span); - } - { - let then_body = function.block_mut(then_blk); - then_body.push(Op::PushU8(1), span); - then_body.push(Op::While(while_blk), span); - } - { - let else_body = function.block_mut(else_blk); - else_body.push(Op::U32Max, span); - } - { - let while_body = function.block_mut(while_blk); - while_body.push(Op::Dup(1), span); - while_body.push(Op::Dup(1), span); - while_body.push(Op::Incr, span); - while_body.push(Op::U32Lt, span); - } - - Arc::new(function) - } -} diff --git a/codegen/masm/src/emulator/memory.rs b/codegen/masm/src/emulator/memory.rs deleted file mode 100644 index 1eb01db5f..000000000 --- a/codegen/masm/src/emulator/memory.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::{ - collections::BTreeSet, - fmt::Debug, - ops::{Index, IndexMut}, -}; - -use miden_core::FieldElement; -use midenc_hir::Felt; - -const EMPTY_WORD: [Felt; 4] = [Felt::ZERO; 4]; - -pub struct Memory { - memory: Vec<[Felt; 4]>, - set_memory_addrs: BTreeSet, -} - -impl Memory { - pub fn new(memory_size: usize) -> Self { - Self { - memory: vec![EMPTY_WORD; memory_size], - set_memory_addrs: Default::default(), - } - } - - pub fn len(&self) -> usize { - self.memory.len() - } - - pub fn reset(&mut self) { - for addr in self.set_memory_addrs.iter() { - self.memory[*addr] = EMPTY_WORD; - } - self.set_memory_addrs = Default::default(); - } -} - -impl Index for Memory { - type Output = [Felt; 4]; - - fn index(&self, index: usize) -> &Self::Output { - &self.memory[index] - } -} - -impl IndexMut for Memory { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.set_memory_addrs.insert(index); - &mut self.memory[index] - } -} - -impl Debug for Memory { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for addr in self.set_memory_addrs.iter() { - write!(f, "{}: {:?}, ", addr, self[*addr])?; - } - Ok(()) - } -} diff --git a/codegen/masm/src/emulator/mod.rs b/codegen/masm/src/emulator/mod.rs deleted file mode 100644 index 526ccb7a1..000000000 --- a/codegen/masm/src/emulator/mod.rs +++ /dev/null @@ -1,1905 +0,0 @@ -mod breakpoints; -mod debug; -mod events; -mod functions; -mod memory; - -use std::{cell::RefCell, cmp, rc::Rc, sync::Arc}; - -use memory::Memory; -use miden_assembly::{ast::ProcedureName, LibraryNamespace}; -use midenc_hir::{assert_matches, Felt, FieldElement, FunctionIdent, Ident, OperandStack, Stack}; -use rustc_hash::{FxHashMap, FxHashSet}; - -use self::functions::{Activation, Stub}; -pub use self::{ - breakpoints::*, - debug::{CallFrame, DebugInfo, DebugInfoWithStack}, - events::{BreakpointEvent, ControlEffect, EmulatorEvent}, - functions::{Instruction, InstructionWithOp, NativeFn}, -}; -use crate::{BlockId, Function, Module, Op, Program}; - -/// This type represents the various sorts of errors which can occur when -/// running the emulator on a MASM program. Some errors may result in panics, -/// but those which we can handle are represented here. -#[derive(Debug, Clone, thiserror::Error, PartialEq)] -pub enum EmulationError { - /// The given module is already loaded - #[error("unable to load module: '{0}' is already loaded")] - AlreadyLoaded(Ident), - /// The given function is already loaded - #[error("unable to load function: '{0}' is already loaded")] - DuplicateFunction(FunctionIdent), - /// The given function cannot be found - #[error("unable to invoke function: '{0}' is not defined")] - UndefinedFunction(FunctionIdent), - /// The emulator ran out of available memory - #[error("system limit: out of memory")] - OutOfMemory, - /// The emulator was terminated due to a program failing to terminate in its budgeted time - #[error("execution terminated prematurely: maximum cycle count reached")] - CycleBudgetExceeded, - /// A breakpoint was reached, so execution was suspended and can be resumed - #[error("execution suspended by breakpoint")] - BreakpointHit(BreakpointEvent), - /// An attempt was made to run the emulator without specifying an entrypoint - #[error("unable to start the emulator without an entrypoint")] - NoEntrypoint, -} - -/// The size/type of pointers in the emulator -pub type Addr = u32; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct InstructionPointer { - /// The block in which the instruction pointer is located - pub block: BlockId, - /// The index of the instruction pointed to - pub index: usize, -} -impl InstructionPointer { - pub const fn new(block: BlockId) -> Self { - Self { block, index: 0 } - } -} - -/// This enum represents the state transitions for the emulator. -/// -/// * The emulator starts in `Init` -/// * Once some code is loaded, it becomes `Loaded` -/// * Once the emulator has started executing some code, it becomes `Started` -/// * If the emulator suspends due to a breakpoint or stepping, it becomes `Suspended` -/// * Once the emulator finishes executing whatever entrypoint was invoked, it becomes `Stopped` -/// * If an error occurs between `Started` and `Stopped`, it becomes `Faulted` -/// -/// Once `Started`, it is not possible to `start` the emulator again until it reaches the -/// `Stopped` state, or is explicitly reset to the `Init` or `Loaded` states using `reset` -/// or `stop` respectively. -#[derive(Debug, Default)] -enum Status { - /// The emulator is in its initial state - /// - /// In this state, the emulator cannot execute any code because there - /// is no code loaded yet. - #[default] - Init, - /// A program has been loaded into the emulator, but not yet started - /// - /// This is the clean initial state from which a program or function can - /// start executing. Once the emulator leaves this status, the state of - /// the emulator is "dirty", i.e. it is no longer a clean slate. - Loaded, - /// The emulator has started running the current program, or a specified function. - Started, - /// The emulator is suspended, and awaiting resumption - Suspended, - /// The emulator finished running the current program, or a specified function, - /// and the state of the emulator has not yet been reset. - Stopped, - /// The emulator has stopped due to an error, and cannot proceed further - Faulted(EmulationError), -} - -/// [Emulator] provides us with a means to execute our MASM IR directly -/// without having to emit "real" MASM and run it via the Miden VM. -/// In other words, it's a convenient way to run tests to verify the -/// expected behavior of a program without all of the baggage of the -/// Miden VM. -/// -/// [Emulator] is necessarily a more limited execution environment: -/// -/// * It only handles instructions which are defined in the [Op] enum -/// * Anything related to proving, calling contracts, etc. is not supported -/// * The default environment is empty, i.e. there are no Miden VM standard -/// library functions available. Users must emit Miden IR for all functions -/// they wish to call, or alternatively, provide native stubs. -pub struct Emulator { - status: Status, - functions: FxHashMap, - locals: FxHashMap, - modules_loaded: FxHashMap>, - modules_pending: FxHashSet, - memory: Memory, - stack: OperandStack, - advice_stack: OperandStack, - callstack: Vec, - hp_start: u32, - hp: u32, - lp_start: u32, - lp: u32, - breakpoints: BreakpointManager, - step_over: Option, - clk: usize, - clk_limit: usize, - entrypoint: Option, - print_trace: bool, -} -impl Default for Emulator { - fn default() -> Self { - Self::new( - Self::DEFAULT_HEAP_SIZE, - Self::DEFAULT_HEAP_START, - Self::DEFAULT_LOCALS_START, - false, - ) - } -} -impl Emulator { - pub const DEFAULT_HEAP_SIZE: u32 = (4 * Self::PAGE_SIZE) / 16; - pub const DEFAULT_HEAP_START: u32 = (2 * Self::PAGE_SIZE) / 16; - pub const DEFAULT_LOCALS_START: u32 = (3 * Self::PAGE_SIZE) / 16; - const PAGE_SIZE: u32 = 64 * 1024; - - /// Construct a new, empty emulator with: - /// - /// * A linear memory heap of `memory_size` words - /// * The start of the usable heap set to `hp` (an address in words) - /// * The start of the reserved heap used for locals set to `lp` (an address in words) - pub fn new(memory_size: u32, hp: u32, lp: u32, print_stack: bool) -> Self { - let memory = Memory::new(memory_size as usize); - Self { - status: Status::Init, - functions: Default::default(), - locals: Default::default(), - modules_loaded: Default::default(), - modules_pending: Default::default(), - memory, - stack: Default::default(), - advice_stack: Default::default(), - callstack: vec![], - hp_start: hp, - hp, - lp_start: lp, - lp, - breakpoints: Default::default(), - step_over: None, - clk: 0, - clk_limit: usize::MAX, - entrypoint: None, - print_trace: print_stack, - } - } - - /// Place a cap on the number of cycles the emulator will execute before failing with an error - pub fn set_max_cycles(&mut self, max: usize) { - self.clk_limit = max; - } - - /// Returns all watchpoints that are currently managed by this [BreakpointManager] - pub fn watchpoints(&self) -> impl Iterator + '_ { - self.breakpoints.watchpoints() - } - - /// Returns all breakpoints that are currently managed by this [BreakpointManager] - pub fn breakpoints(&self) -> impl Iterator { - self.breakpoints.breakpoints() - } - - /// Sets a breakpoint for the emulator - pub fn set_breakpoint(&mut self, bp: Breakpoint) { - self.breakpoints.set(bp); - } - - /// Removes the given breakpoint from the emulator - pub fn clear_breakpoint(&mut self, bp: Breakpoint) { - self.breakpoints.unset(bp); - } - - /// Removes the all breakpoints from the emulator - pub fn clear_breakpoints(&mut self) { - self.breakpoints.unset_all(); - } - - /// Sets a watchpoint in the emulator - pub fn set_watchpoint(&mut self, addr: Addr, size: u32, mode: WatchMode) -> WatchpointId { - self.breakpoints.watch(addr, size, mode) - } - - /// Sets a watchpoint in the emulator - pub fn clear_watchpoint(&mut self, id: WatchpointId) { - self.breakpoints.unwatch(id); - } - - /// Set the watch mode for a [Watchpoint] using the identifier returned by [watch] - pub fn watchpoint_mode(&mut self, id: WatchpointId, mode: WatchMode) { - self.breakpoints.watch_mode(id, mode); - } - - /// Clears all watchpoints - pub fn clear_watchpoints(&mut self) { - self.breakpoints.unwatch_all(); - } - - /// Clear all breakpoints and watchpoints - pub fn clear_break_and_watchpoints(&mut self) { - self.breakpoints.clear(); - } - - /// Get's debug information about the current emulator state - pub fn info(&self) -> Option> { - let current = self.callstack.last()?; - // This returns the pending activation state for the current function, - // i.e. the next instruction to be executed, what control flow effects - // will occur to reach that instruction, and the actual instruction pointer - let ip = current.peek_with_op(); - Some(DebugInfo { - cycle: self.clk, - function: current.function().name, - fp: current.fp(), - ip, - stack: &self.stack, - }) - } - - /// Get a stacktrace for the code running in the emulator - pub fn stacktrace(&self) -> Vec { - let mut frames = Vec::with_capacity(self.callstack.len()); - for frame in self.callstack.iter() { - frames.push(CallFrame { - function: frame.function().name, - fp: frame.fp(), - ip: Some(frame.ip()), - }) - } - frames - } - - /// Get the instruction pointer that will be next executed by the emulator - pub fn current_ip(&self) -> Option { - self.callstack.last().and_then(|activation| activation.peek()) - } - - /// Get the name of the function that is currently executing - pub fn current_function(&self) -> Option { - self.callstack.last().map(|activation| activation.function().name) - } - - /// Get access to the current state of the operand stack - pub fn stack(&mut self) -> &OperandStack { - &self.stack - } - - /// Get mutable access to the current state of the operand stack - pub fn stack_mut(&mut self) -> &mut OperandStack { - &mut self.stack - } - - /// Load `program` into this emulator - /// - /// This resets the emulator state, as only one program may be loaded at a time. - pub fn load_program(&mut self, program: Arc) -> Result<(), EmulationError> { - // Ensure the emulator state is reset - if !matches!(self.status, Status::Init) { - self.reset(); - } - - let modules = program.unwrap_frozen_modules(); - let mut cursor = modules.front(); - while let Some(module) = cursor.clone_pointer() { - self.load_module(module)?; - cursor.move_next(); - } - self.entrypoint = Some(program.entrypoint()); - - // TODO: Load data segments - - self.status = Status::Loaded; - - Ok(()) - } - - /// Load `module` into this emulator - /// - /// An error is returned if a module with the same name is already loaded. - pub fn load_module(&mut self, module: Arc) -> Result<(), EmulationError> { - use std::collections::hash_map::Entry; - - assert_matches!( - self.status, - Status::Init | Status::Loaded, - "cannot load modules once execution has started without calling stop() or reset() \ - first" - ); - - match self.modules_loaded.entry(module.id) { - Entry::Occupied(_) => return Err(EmulationError::AlreadyLoaded(module.id)), - Entry::Vacant(entry) => { - entry.insert(module.clone()); - } - } - - // Register module dependencies - for import in module.imports.iter() { - let name = Ident::with_empty_span(import.name); - if self.modules_loaded.contains_key(&name) { - continue; - } - self.modules_pending.insert(name); - } - self.modules_pending.remove(&module.id); - - // Load functions from this module - let functions = module.unwrap_frozen_functions(); - let mut cursor = functions.front(); - while let Some(function) = cursor.clone_pointer() { - self.load_function(function)?; - cursor.move_next(); - } - - self.status = Status::Loaded; - - Ok(()) - } - - /// Reloads a loaded module, `name`. - /// - /// This function will panic if the named module is not currently loaded. - pub fn reload_module(&mut self, module: Arc) -> Result<(), EmulationError> { - self.unload_module(module.id); - self.load_module(module) - } - - /// Unloads a loaded module, `name`. - /// - /// This function will panic if the named module is not currently loaded. - pub fn unload_module(&mut self, name: Ident) { - assert_matches!( - self.status, - Status::Loaded, - "cannot unload modules once execution has started without calling stop() or reset() \ - first" - ); - - let prev = self - .modules_loaded - .remove(&name) - .expect("cannot reload a module that was not previously loaded"); - - // Unload all functions associated with the previous load - for f in prev.functions() { - self.functions.remove(&f.name); - self.locals.remove(&f.name); - } - - // Determine if we need to add `name` to `modules_pending` if there are dependents still - // loaded - for module in self.modules_loaded.values() { - if module.imports.is_import(&name) { - self.modules_pending.insert(name); - break; - } - } - } - - /// Load `function` into this emulator - fn load_function(&mut self, function: Arc) -> Result<(), EmulationError> { - let id = function.name; - if self.functions.contains_key(&id) { - return Err(EmulationError::DuplicateFunction(id)); - } - let fp = self.lp; - self.lp += function.locals().len() as u32; - self.functions.insert(id, Stub::Asm(function)); - self.locals.insert(id, fp); - - Ok(()) - } - - /// Load `function` into this emulator, with the given identifier - /// - /// Because we don't know the set of [FuncId] that have already been allocated, - /// we leave the choice up to the caller. We assert that functions do - /// not get defined twice to catch conflicts, just in case. - pub fn load_nif( - &mut self, - id: FunctionIdent, - function: Box, - ) -> Result<(), EmulationError> { - assert_matches!( - self.status, - Status::Init | Status::Loaded, - "cannot load nifs once execution has started without calling stop() or reset() first" - ); - - if self.functions.contains_key(&id) { - return Err(EmulationError::DuplicateFunction(id)); - } - self.functions.insert(id, Stub::Native(Rc::new(RefCell::new(function)))); - - Ok(()) - } - - /// Allocate space for `value` on the emulator heap, and copy it's contents there. - /// - /// NOTE: The smallest unit of addressable memory is 4 bytes (32 bits). If you provide - /// a value that is smaller than this, or is not a multiple of 4, the data will be padded - /// with zeroes to ensure that it is. - pub fn write_bytes_to_memory(&mut self, value: &[u8]) -> u32 { - let addr = self.hp; - if value.is_empty() { - return addr; - } - - let mut elem_idx = 0; - for chunk in value.chunks(4) { - let elem = match chunk.len() { - 4 => u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]), - 3 => u32::from_le_bytes([chunk[0], chunk[1], chunk[2], 0]), - 2 => u32::from_le_bytes([chunk[0], chunk[1], 0, 0]), - 1 => u32::from_le_bytes([chunk[0], 0, 0, 0]), - 0 => 0, - _ => unreachable!(), - }; - if elem_idx == 4 { - elem_idx = 0; - assert!(self.hp + 1 < self.lp, "heap has overflowed into reserved region"); - self.hp += 1; - } - self.memory[self.hp as usize][elem_idx] = Felt::new(elem as u64); - elem_idx += 1; - } - - addr - } - - /// Allocate enough words to hold `size` bytes of memory - /// - /// Returns the pointer as a byte-addressable address - pub fn malloc(&mut self, size: usize) -> u32 { - let addr = self.hp; - - if size == 0 { - return addr; - } - - let size = size as u32; - let extra = size % 16; - let words = (size / 16) + (extra > 0) as u32; - assert!(self.hp + words < self.lp, "heap has overflowed into reserved region"); - self.hp += words; - - addr * 16 - } - - /// Write `value` to the word at `addr`, and element `index` - pub fn store(&mut self, addr: usize, value: Felt) { - use crate::NativePtr; - - let ptr = NativePtr::from_ptr(addr.try_into().expect("invalid address")); - let addr = ptr.waddr as usize; - assert_eq!(ptr.offset, 0, "invalid store: unaligned address {addr:#?}"); - assert!(addr < self.memory.len(), "invalid address"); - - self.memory[addr][ptr.index as usize] = value; - } - - /// Start executing the current program by `invoke`ing the top-level initialization block (the - /// entrypoint). - /// - /// This function will run the program to completion, and return the state of the operand stack - /// on exit. - /// - /// NOTE: If no entrypoint has been loaded, an error is returned. - /// - /// The emulator is automatically reset when it exits successfully. - pub fn start(&mut self) -> Result, EmulationError> { - match self.status { - Status::Init => return Err(EmulationError::NoEntrypoint), - Status::Loaded => (), - Status::Stopped => { - self.stop(); - } - Status::Started | Status::Suspended => panic!( - "cannot start the emulator when it is already started without calling stop() or \ - reset() first" - ), - Status::Faulted(ref err) => return Err(err.clone()), - } - - let main_fn = self.entrypoint.unwrap_or_else(|| FunctionIdent { - module: LibraryNamespace::EXEC_PATH.into(), - function: ProcedureName::MAIN_PROC_NAME.into(), - }); - - // Run to completion - let stack = self.invoke(main_fn, &[]).map_err(|err| match err { - EmulationError::UndefinedFunction(f) if f == main_fn => EmulationError::NoEntrypoint, - err => err, - })?; - - // Reset the emulator on exit - self.stop(); - - // Return the output contained on the operand stack - Ok(stack) - } - - /// Start emulation by `enter`ing the top-level initialization block (the entrypoint). - /// - /// This should be called instead of `start` when stepping through a program rather than - /// executing it to completion in one call. - /// - /// NOTE: If no entrypoint has been loaded, an error is returned. - /// - /// It is up to the caller to reset the emulator when the program exits, unlike `start`. - pub fn init(&mut self) -> Result { - match self.status { - Status::Init => return Err(EmulationError::NoEntrypoint), - Status::Loaded => (), - Status::Stopped => { - self.stop(); - } - Status::Started | Status::Suspended => panic!( - "cannot start the emulator when it is already started without calling stop() or \ - reset() first" - ), - Status::Faulted(ref err) => return Err(err.clone()), - } - - let main_fn = FunctionIdent { - module: LibraryNamespace::EXEC_PATH.into(), - function: ProcedureName::MAIN_PROC_NAME.into(), - }; - - // Step into the entrypoint - self.enter(main_fn, &[]).map_err(|err| match err { - EmulationError::UndefinedFunction(f) if f == main_fn => EmulationError::NoEntrypoint, - err => err, - }) - } - - /// Stop running the currently executing function, and reset the cycle counter, operand stack, - /// and linear memory. - /// - /// This function preserves loaded code, breakpoints, and other configuration items. - /// - /// If an attempt is made to run the emulator in the stopped state, a panic will occur - pub fn stop(&mut self) { - self.callstack.clear(); - self.stack.clear(); - self.memory.reset(); - self.hp = self.hp_start; - self.lp = self.lp_start; - self.step_over = None; - self.clk = 0; - self.status = Status::Loaded; - } - - /// Reset the emulator state to its initial state at creation. - /// - /// In addition to resetting the cycle counter, operand stack, and linear memory, - /// this function also unloads all code, and clears all breakpoints. Only the - /// configuration used to initialize the emulator is preserved. - /// - /// To use the emulator after calling this function, you must load a program or module again. - pub fn reset(&mut self) { - self.stop(); - self.functions.clear(); - self.locals.clear(); - self.modules_loaded.clear(); - self.modules_pending.clear(); - self.breakpoints.clear(); - self.status = Status::Init; - } - - /// Run the emulator by invoking `callee` with `args` placed on the - /// operand stack in FIFO order. - /// - /// If a fatal error occurs during emulation, `Err` is returned, - /// e.g. if `callee` has not been loaded. - /// - /// When `callee` returns, it's result will be returned wrapped in `Ok`. - /// For functions with no return value, this will be `Ok(None)`, or all - /// others it will be `Ok(Some(value))`. - pub fn invoke( - &mut self, - callee: FunctionIdent, - args: &[Felt], - ) -> Result, EmulationError> { - assert_matches!( - self.status, - Status::Loaded, - "cannot start executing a function when the emulator is already started without \ - calling stop() or reset() first" - ); - let fun = self - .functions - .get(&callee) - .cloned() - .ok_or(EmulationError::UndefinedFunction(callee))?; - self.status = Status::Started; - match fun { - Stub::Asm(ref function) => match self.invoke_function(function.clone(), args) { - done @ Ok(_) => { - self.status = Status::Stopped; - done - } - Err(err @ EmulationError::BreakpointHit(_)) => { - self.status = Status::Suspended; - Err(err) - } - Err(err) => { - self.status = Status::Faulted(err.clone()); - Err(err) - } - }, - Stub::Native(function) => { - let mut function = function.borrow_mut(); - function(self, args)?; - Ok(self.stack.clone()) - } - } - } - - /// Invoke a function defined in MASM IR, placing the given arguments on the - /// operand stack in FIFO order, and suspending immediately if any breakpoints - /// would have been triggered by the invocation. - #[inline] - fn invoke_function( - &mut self, - function: Arc, - args: &[Felt], - ) -> Result, EmulationError> { - // Place the arguments on the operand stack - //assert_eq!(args.len(), function.arity()); - for arg in args.iter().copied().rev() { - self.stack.push(arg); - } - - // Schedule `function` - let name = function.name; - let fp = self.locals[&name]; - let state = Activation::new(function, fp); - self.callstack.push(state); - - match self - .breakpoints - .handle_event(EmulatorEvent::EnterFunction(name), self.current_ip()) - { - Some(bp) => Err(EmulationError::BreakpointHit(bp)), - None => { - self.run()?; - - Ok(self.stack.clone()) - } - } - } - - /// Run the emulator by invoking `callee` with `args` placed on the - /// operand stack in FIFO order. - /// - /// If a fatal error occurs during emulation, `Err` is returned, - /// e.g. if `callee` has not been loaded. - /// - /// When `callee` returns, it's result will be returned wrapped in `Ok`. - /// For functions with no return value, this will be `Ok(None)`, or all - /// others it will be `Ok(Some(value))`. - pub fn enter( - &mut self, - callee: FunctionIdent, - args: &[Felt], - ) -> Result { - assert_matches!( - self.status, - Status::Loaded, - "cannot start executing a function when the emulator is already started without \ - calling stop() or reset() first" - ); - - let fun = self - .functions - .get(&callee) - .cloned() - .ok_or(EmulationError::UndefinedFunction(callee))?; - self.status = Status::Started; - match fun { - Stub::Asm(ref function) => self.enter_function(function.clone(), args), - Stub::Native(function) => { - let mut function = function.borrow_mut(); - function(self, args)?; - Ok(EmulatorEvent::ExitFunction(callee)) - } - } - } - - /// Stage a MASM IR function for execution by the emulator, placing the given arguments on the - /// operand stack in FIFO order, then immediately suspending execution until the next - /// resumption. - #[inline] - fn enter_function( - &mut self, - function: Arc, - args: &[Felt], - ) -> Result { - // Place the arguments on the operand stack - //assert_eq!(args.len(), function.arity()); - for arg in args.iter().copied().rev() { - self.stack.push(arg); - } - - // Schedule `function` - let name = function.name; - let fp = self.locals[&name]; - let state = Activation::new(function, fp); - self.callstack.push(state); - - self.status = Status::Suspended; - - Ok(EmulatorEvent::Suspended) - } - - /// Resume execution when the emulator suspended due to a breakpoint - #[inline] - pub fn resume(&mut self) -> Result { - assert_matches!( - self.status, - Status::Suspended, - "cannot resume the emulator from any state other than suspended" - ); - self.run() - } -} - -/// Pops the top element off the advice stack -macro_rules! adv_pop { - ($emu:ident) => { - $emu.advice_stack.pop().expect("advice stack is empty") - }; - - ($emu:ident, $msg:literal) => { - $emu.advice_stack.pop().expect($msg) - }; - - ($emu:ident, $msg:literal, $($arg:expr),+) => { - match $emu.advice_stack.pop() { - Some(value) => value, - None => panic!($msg, $($arg),*), - } - } -} - -/// Pops the top word off the advice stack -macro_rules! adv_popw { - ($emu:ident) => { - $emu.advice_stack.popw().expect("advice stack does not contain a full word") - }; - - ($emu:ident, $msg:literal) => { - $emu.advice_stack.popw().expect($msg) - }; - - ($emu:ident, $msg:literal, $($arg:expr),+) => {{ - match $emu.advice_stack.popw() { - Some(value) => value, - None => panic!($msg, $($arg),*), - } - }} -} - -/// Pops the top element off the stack -macro_rules! pop { - ($emu:ident) => { - $emu.stack.pop().expect("operand stack is empty") - }; - - ($emu:ident, $msg:literal) => { - $emu.stack.pop().expect($msg) - }; - - ($emu:ident, $msg:literal, $($arg:expr),+) => { - match $emu.stack.pop() { - Some(value) => value, - None => panic!($msg, $($arg),*), - } - } -} - -/// Peeks the top element of the stack -macro_rules! peek { - ($emu:ident) => { - $emu.stack.peek().expect("operand stack is empty") - }; - - ($emu:ident, $msg:literal) => { - $emu.stack.peek().expect($msg) - }; - - ($emu:ident, $msg:literal, $($arg:expr),+) => { - match $emu.stack.peek() { - Some(value) => value, - None => panic!($msg, $($arg),*), - } - } -} - -/// Pops the top word off the stack -macro_rules! popw { - ($emu:ident) => { - $emu.stack.popw().expect("operand stack does not contain a full word") - }; - - ($emu:ident, $msg:literal) => { - $emu.stack.popw().expect($msg) - }; - - ($emu:ident, $msg:literal, $($arg:expr),+) => {{ - match $emu.stack.popw() { - Some(value) => value, - None => panic!($msg, $($arg),*), - } - }} -} - -/// Pops the top two elements off the stack, returning them in order of appearance -macro_rules! pop2 { - ($emu:ident) => {{ - let b = pop!($emu); - let a = pop!($emu); - (b, a) - }}; -} - -/// Pops a u32 value from the top of the stack, and asserts if it is out of range -macro_rules! pop_u32 { - ($emu:ident) => {{ - let value = pop!($emu).as_int(); - assert!(value < 2u64.pow(32), "assertion failed: {value} is not a valid u32, value is out of range"); - value as u32 - }}; - - ($emu:ident, $format:literal $(, $args:expr)*) => {{ - let value = pop!($emu).as_int(); - assert!(value < 2u64.pow(32), $format, value, $($args),*); - value as u32 - }} -} - -/// Peeks a u32 value from the top of the stack, and asserts if it is out of range -#[allow(unused)] -macro_rules! peek_u32 { - ($emu:ident) => {{ - let value = peek!($emu).as_int(); - assert!(value < 2u64.pow(32), "assertion failed: {value} is not a valid u32, value is out of range"); - value as u32 - }}; - - ($emu:ident, $format:literal $(, $args:expr)*) => {{ - let value = peek!($emu).as_int(); - assert!(value < 2u64.pow(32), $format, value, $($args),*); - value as u32 - }} -} - -/// Pops a pointer value from the top of the stack, and asserts if it is not a valid boolean -macro_rules! pop_addr { - ($emu:ident) => {{ - let addr = pop_u32!($emu, "expected valid 32-bit address, got {}") as usize; - assert!( - addr < $emu.memory.len(), - "out of bounds memory access, addr: {}, available memory: {}", - addr, - $emu.memory.len() - ); - addr - }}; -} - -/// Pops a boolean value from the top of the stack, and asserts if it is not a valid boolean -macro_rules! pop_bool { - ($emu:ident) => {{ - let value = pop!($emu).as_int(); - assert!( - value < 2, - "assertion failed: {value} is not a valid boolean, value must be either 1 or 0" - ); - value == 1 - }}; -} - -/// Applies a binary operator that produces a result of the same input type: -/// -/// 1. The top two elements of the stack -/// 2. The top element of the stack and an immediate. -macro_rules! binop { - ($emu:ident, $op:ident) => {{ - use core::ops::*; - let b = pop!($emu); - let a = pop!($emu); - $emu.stack.push(a.$op(b)); - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - use core::ops::*; - let a = pop!($emu); - $emu.stack.push(a.$op($imm)); - }}; -} - -/// Applies a binary operator to two u32 values, either: -/// -/// 1. The top two elements of the stack -/// 2. The top element of the stack and an immediate. -macro_rules! binop32 { - ($emu:ident, $op:ident) => {{ - #[allow(unused)] - use core::ops::*; - let b = pop_u32!($emu); - let a = pop_u32!($emu); - $emu.stack.push_u32(a.$op(b)); - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - #[allow(unused)] - use core::ops::*; - let a = pop_u32!($emu); - $emu.stack.push_u32(a.$op($imm)); - }}; -} - -/// Applies a checked binary operator to two u32 values, either: -/// -/// 1. The top two elements of the stack -/// 2. The top element of the stack and an immediate. -macro_rules! binop_unchecked_u32 { - ($emu:ident, $op:ident) => {{ - #[allow(unused)] - use core::ops::*; - let b = pop!($emu); - let a = pop!($emu); - $emu.stack.push(Felt::new(a.as_int().$op(b.as_int()))); - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - #[allow(unused)] - use core::ops::*; - let a = pop!($emu); - $emu.stack.push(Felt::new(a.as_int().$op($imm))); - }}; -} - -/// Applies an overflowing binary operator to two u32 values, either: -/// -/// 1. The top two elements of the stack -/// 2. The top element of the stack and an immediate. -macro_rules! binop_overflowing_u32 { - ($emu:ident, $op:ident) => {{ - paste::paste! { - binop_overflowing_u32_impl!($emu, []); - } - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - paste::paste! { - binop_overflowing_u32_impl!($emu, [], $imm); - } - }}; -} - -#[doc(hidden)] -macro_rules! binop_overflowing_u32_impl { - ($emu:ident, $op:ident) => {{ - #[allow(unused)] - use core::ops::*; - let b = pop_u32!($emu); - let a = pop_u32!($emu); - let (result, overflowed) = a.$op(b); - $emu.stack.push_u32(result); - $emu.stack.push_u8(overflowed as u8); - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - #[allow(unused)] - use core::ops::*; - let a = pop_u32!($emu); - let (result, overflowed) = a.$op($imm); - $emu.stack.push_u32(result); - $emu.stack.push_u8(overflowed as u8); - }}; -} - -/// Applies a wrapping binary operator to two u32 values, either: -/// -/// 1. The top two elements of the stack -/// 2. The top element of the stack and an immediate. -macro_rules! binop_wrapping_u32 { - ($emu:ident, $op:ident) => {{ - paste::paste! { - binop_wrapping_u32_impl!($emu, []); - } - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - paste::paste! { - binop_wrapping_u32_impl!($emu, [], $imm); - } - }}; -} - -#[doc(hidden)] -macro_rules! binop_wrapping_u32_impl { - ($emu:ident, $op:ident) => {{ - #[allow(unused)] - use core::ops::*; - let b = pop_u32!($emu); - let a = pop_u32!($emu); - $emu.stack.push_u32(a.$op(b)); - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - #[allow(unused)] - use core::ops::*; - let a = pop_u32!($emu); - $emu.stack.push_u32(a.$op($imm)); - }}; -} - -/// Applies a binary comparison operator, to either: -/// -/// 1. The top two elements of the stack -/// 2. The top element of the stack and an immediate. -macro_rules! comparison { - ($emu:ident, $op:ident) => {{ - let b = pop!($emu).as_int(); - let a = pop!($emu).as_int(); - let result: bool = a.$op(&b); - $emu.stack.push_u8(result as u8); - }}; - - ($emu:ident, $op:ident, $imm:expr) => {{ - let a = pop!($emu).as_int(); - let result: bool = a.$op(&$imm); - $emu.stack.push_u8(result as u8); - }}; -} - -impl Emulator { - /// Step the emulator forward one cycle, returning the type of event produced - /// during that cycle, or an error. - pub fn step(&mut self) -> Result { - match self - .breakpoints - .handle_event(EmulatorEvent::CycleStart(self.clk), self.current_ip()) - { - Some(bp) => { - self.status = Status::Suspended; - Ok(EmulatorEvent::Breakpoint(bp)) - } - None => match self.run_once() { - Ok(EmulatorEvent::Stopped) => { - self.status = Status::Stopped; - Ok(EmulatorEvent::Stopped) - } - suspended @ Ok(_) => { - self.status = Status::Suspended; - suspended - } - Err(err) => { - self.status = Status::Faulted(err.clone()); - Err(err) - } - }, - } - } - - /// Step the emulator forward one step, but stepping past any nested blocks or function calls, - /// returning the type of event produced during that cycle, or an error. - pub fn step_over(&mut self) -> Result { - match self.step_over.take() { - None => self.step(), - Some(ip) => { - self.breakpoints.set(Breakpoint::At(ip)); - match self.run() { - Ok(EmulatorEvent::Stopped) => { - self.status = Status::Stopped; - Ok(EmulatorEvent::Stopped) - } - Ok(EmulatorEvent::Breakpoint(bp)) | Err(EmulationError::BreakpointHit(bp)) => { - self.status = Status::Suspended; - if self.current_ip().map(|ix| ix.ip) == Some(ip) { - return Ok(EmulatorEvent::Suspended); - } - Ok(EmulatorEvent::Breakpoint(bp)) - } - Ok(event) => panic!( - "unexpected event produced by emulator loop when stepping over: {event:?}" - ), - Err(err) => { - self.status = Status::Faulted(err.clone()); - Err(err) - } - } - } - } - } - - /// Step the emulator forward until control returns from the current function. - pub fn step_out(&mut self) -> Result { - let current_function = self.current_function(); - self.breakpoints.break_on_return(true); - match self.run() { - Ok(EmulatorEvent::Stopped) => { - self.status = Status::Stopped; - Ok(EmulatorEvent::Stopped) - } - Ok(EmulatorEvent::Breakpoint(bp)) | Err(EmulationError::BreakpointHit(bp)) => { - self.status = Status::Suspended; - if self.current_function() == current_function { - return Ok(EmulatorEvent::Suspended); - } - Ok(EmulatorEvent::Breakpoint(bp)) - } - Ok(event) => { - panic!("unexpected event produced by emulator loop when stepping over: {event:?}") - } - Err(err) => { - self.status = Status::Faulted(err.clone()); - Err(err) - } - } - } - - /// Run the emulator until all calls are completed, the cycle budget is exhausted, - /// or a breakpoint is hit. - /// - /// It is expected that the caller has set up the operand stack with the correct - /// number of arguments. If not, undefined behavior (from the perspective of the - /// MASM program) will result. - #[inline(never)] - fn run(&mut self) -> Result { - // This is the core interpreter loop for MASM IR, it runs until one of the - // following occurs: - // - // * We run out of code to execute, i.e. the function is returning normally - // * Execution was explicitly aborted from within the function - // * Execution traps due to a MASM invariant being violated, indicating the - // code is malformed. - // * Execution traps due to a runtime system error, e.g. out of memory - // * Execution traps due to exceeding the predefined execution budget - // * Execution breaks due to a breakpoint - let mut event = self.step()?; - loop { - match event { - // We should suspend when encountering these events - event @ EmulatorEvent::Breakpoint(_) => break Ok(event), - event @ EmulatorEvent::Stopped => break Ok(event), - ev => { - // We must handle catching certain breakpoints when using this event loop - match self.breakpoints.handle_event(ev, self.current_ip()) { - Some(bp) => break Ok(EmulatorEvent::Breakpoint(bp)), - None => match ev { - // There was no code remaining in the current function, effectively - // returning from it. Since no instructions were dispatched, we don't - // count the cycle, and resume immediately at the continuation point - // in the caller - EmulatorEvent::ExitFunction(_) => { - if self.callstack.is_empty() { - break Ok(EmulatorEvent::Stopped); - } - event = self.run_once()?; - continue; - } - _ => { - event = self.step()?; - } - }, - } - } - } - } - } - - #[inline(never)] - fn run_once(&mut self) -> Result { - const U32_P: u64 = 2u64.pow(32); - - // If there are no more activation records, we're done - if self.callstack.is_empty() { - return Ok(EmulatorEvent::Stopped); - } - - // Terminate execution early if we reach a predetermined number of cycles - self.clk += 1; - if self.clk > self.clk_limit { - return Err(EmulationError::CycleBudgetExceeded); - } - - let mut state = self.callstack.pop().unwrap(); - let current_function = state.function().name; - - // Reset the next instruction to break at when stepping over instructions - self.step_over = None; - - // If we have breakpoints set that require it, we may need to - // break execution before executing the instruction that is pending - if self.breakpoints.break_on_return || self.breakpoints.has_break_on_reached() { - match state.peek() { - Some(Instruction { ip, .. }) - if self.breakpoints.should_break_at(ip.block, ip.index) => - { - self.callstack.push(state); - return Ok(EmulatorEvent::Breakpoint(BreakpointEvent::Reached(ip))); - } - None if self.breakpoints.break_on_return => { - self.callstack.push(state); - self.breakpoints.break_on_return(false); - return Ok(EmulatorEvent::Breakpoint(BreakpointEvent::StepOut)); - } - _ => (), - } - } - - // Advance the instruction pointer, returning the instruction - // that it previously pointed to, along with what, if any, - // control flow effect occurred to reach it - let ix_with_op = state.next(); - if let Some(ix_with_op) = ix_with_op { - if self.print_trace { - eprintln!("mem: {:?}", self.memory); - eprintln!("stk: {}", self.stack.debug()); - eprintln!("op>: {:?}", ix_with_op.op); - } - match ix_with_op.op { - Op::Padw => { - self.stack.padw(); - } - Op::Push(v) => { - self.stack.push(v); - } - Op::Push2([a, b]) => { - self.stack.push(a); - self.stack.push(b); - } - Op::Pushw(word) => { - self.stack.pushw(word); - } - Op::PushU8(i) => { - self.stack.push_u8(i); - } - Op::PushU16(i) => { - self.stack.push_u16(i); - } - Op::PushU32(i) => { - self.stack.push_u32(i); - } - Op::Drop => { - self.stack.drop(); - } - Op::Dropw => { - self.stack.dropw(); - } - Op::Dup(pos) => { - self.stack.dup(pos as usize); - } - Op::Dupw(pos) => { - self.stack.dupw(pos as usize); - } - Op::Swap(pos) => { - self.stack.swap(pos as usize); - } - Op::Swapw(pos) => { - self.stack.swapw(pos as usize); - } - Op::Swapdw => { - self.stack.swapdw(); - } - Op::Movup(pos) => { - self.stack.movup(pos as usize); - } - Op::Movupw(pos) => { - self.stack.movupw(pos as usize); - } - Op::Movdn(pos) => { - self.stack.movdn(pos as usize); - } - Op::Movdnw(pos) => { - self.stack.movdnw(pos as usize); - } - Op::Cswap => { - let cond = pop_bool!(self); - if cond { - self.stack.swap(1); - } - } - Op::Cswapw => { - let cond = pop_bool!(self); - if cond { - self.stack.swapw(1); - } - } - Op::Cdrop => { - let cond = pop_bool!(self); - let (b, a) = pop2!(self); - if cond { - self.stack.push(b); - } else { - self.stack.push(a); - } - } - Op::Cdropw => { - let cond = pop_bool!(self); - let b = popw!(self); - let a = popw!(self); - if cond { - self.stack.pushw(b); - } else { - self.stack.pushw(a); - } - } - Op::AdvPush(n) => { - assert!( - n > 0 && n <= 16, - "invalid adv_push operand: must be a value in the range 1..=16, got {n}" - ); - for _ in 0..n { - let value = adv_pop!(self); - self.stack.push(value); - } - } - Op::AdvLoadw => { - let word = adv_popw!(self); - self.stack.dropw(); - self.stack.pushw(word); - } - Op::AdvPipe => { - // We're overwriting the first two words, C and B, so drop them - self.stack.dropw(); - self.stack.dropw(); - // The third word, A, is saved, but unused - let a = popw!(self); - // The memory address to write to is the first element of the fourth word - let addr = pop_addr!(self); - // We update the original address += 2, and restore A - self.stack.push_u32(addr as u32 + 2); - self.stack.pushw(a); - // We then move words D and E from the advice stack to the operand stack, - // while also writing those words to memory starting at `addr` - let d = adv_popw!(self); - self.stack.pushw(d); - self.memory[addr] = d; - let e = adv_popw!(self); - self.stack.pushw(e); - self.memory[addr + 1] = e; - // Lastly, since we performed a memory write here, suspend like we do for other - // memory-modifying ops - self.callstack.push(state); - return Ok(EmulatorEvent::MemoryWrite { - addr: addr as u32, - size: 16, - }); - } - Op::AdvInjectPushU64Div => { - const HI_MASK: u64 = u64::MAX << 32; - const LO_MASK: u64 = u32::MAX as u64; - let b_hi = pop_u32!(self) as u64; - let b_lo = pop_u32!(self) as u64; - let b = (b_hi << 32) | b_lo; - assert!(b > 0, "assertion failed: division by zero"); - let a_hi = pop_u32!(self) as u64; - let a_lo = pop_u32!(self) as u64; - let a = (a_hi << 32) | a_lo; - let q = a / b; - let q_hi = (q & HI_MASK) >> 32; - let q_lo = q & LO_MASK; - let r = a % b; - let r_hi = (r & HI_MASK) >> 32; - let r_lo = r & LO_MASK; - self.advice_stack.push_u32(r_hi as u32); - self.advice_stack.push_u32(r_lo as u32); - self.advice_stack.push_u32(q_hi as u32); - self.advice_stack.push_u32(q_lo as u32); - self.stack.push_u32(a_lo as u32); - self.stack.push_u32(a_hi as u32); - self.stack.push_u32(b_lo as u32); - self.stack.push_u32(b_hi as u32); - } - Op::AdvInjectInsertMem - | Op::AdvInjectInsertHperm - | Op::AdvInjectInsertHdword - | Op::AdvInjectInsertHdwordImm(_) - | Op::AdvInjectPushMTreeNode - | Op::AdvInjectPushMapVal - | Op::AdvInjectPushMapValImm(_) - | Op::AdvInjectPushMapValN - | Op::AdvInjectPushMapValNImm(_) => unimplemented!(), - Op::Assert => { - let cond = pop_bool!(self); - assert!(cond, "assertion failed: expected true, got false"); - } - Op::Assertz => { - let cond = pop_bool!(self); - assert!(!cond, "assertion failed: expected false, got true"); - } - Op::AssertEq => { - let (b, a) = pop2!(self); - assert_eq!(a, b, "equality assertion failed"); - } - Op::AssertEqw => { - let b = popw!(self); - let a = popw!(self); - assert_eq!(a, b, "equality assertion failed"); - } - Op::LocAddr(id) => { - let addr = state.fp() + id.as_usize() as u32; - debug_assert!(addr < self.memory.len() as u32); - self.stack.push_u32(addr * 16); - } - Op::LocStore(id) => { - let addr = (state.fp() + id.as_usize() as u32) as usize; - debug_assert!(addr < self.memory.len()); - let value = pop!(self); - self.memory[addr][0] = value; - return Ok(EmulatorEvent::MemoryWrite { - addr: addr as u32, - size: 4, - }); - } - Op::LocStorew(id) => { - let addr = (state.fp() + id.as_usize() as u32) as usize; - assert!(addr < self.memory.len() - 4, "out of bounds memory access"); - let word = - self.stack.peekw().expect("operand stack does not contain a full word"); - self.memory[addr] = word; - return Ok(EmulatorEvent::MemoryWrite { - addr: addr as u32, - size: 16, - }); - } - Op::LocLoad(id) => { - let addr = (state.fp() + id.as_usize() as u32) as usize; - debug_assert!(addr < self.memory.len()); - self.stack.push(self.memory[addr][0]); - } - Op::LocLoadw(id) => { - let addr = (state.fp() + id.as_usize() as u32) as usize; - debug_assert!(addr < self.memory.len()); - self.stack.dropw(); - self.stack.pushw(self.memory[addr]); - } - Op::MemLoad => { - let addr = pop_addr!(self); - self.stack.push(self.memory[addr][0]); - } - Op::MemLoadImm(addr) => { - let addr = addr as usize; - assert!(addr < self.memory.len(), "out of bounds memory access"); - self.stack.push(self.memory[addr][0]); - } - Op::MemLoadw => { - let addr = pop_addr!(self); - self.stack.dropw(); - let mut word = self.memory[addr]; - word.reverse(); - self.stack.pushw(word); - } - Op::MemLoadwImm(addr) => { - let addr = addr as usize; - assert!(addr < self.memory.len() - 4, "out of bounds memory access"); - self.stack.dropw(); - let mut word = self.memory[addr]; - word.reverse(); - self.stack.pushw(word); - } - Op::MemStore => { - let addr = pop_addr!(self); - let value = pop!(self); - self.memory[addr][0] = value; - self.callstack.push(state); - return Ok(EmulatorEvent::MemoryWrite { - addr: addr as u32, - size: 4, - }); - } - Op::MemStoreImm(addr) => { - let addr = addr as usize; - assert!(addr < self.memory.len(), "out of bounds memory access"); - let value = self.stack.pop().expect("operand stack is empty"); - self.memory[addr][0] = value; - self.callstack.push(state); - return Ok(EmulatorEvent::MemoryWrite { - addr: addr as u32, - size: 4, - }); - } - Op::MemStorew => { - let addr = pop_addr!(self); - let mut word = - self.stack.peekw().expect("operand stack does not contain a full word"); - word.reverse(); - self.memory[addr] = word; - self.callstack.push(state); - return Ok(EmulatorEvent::MemoryWrite { - addr: addr as u32, - size: 16, - }); - } - Op::MemStorewImm(addr) => { - let addr = addr as usize; - assert!(addr < self.memory.len() - 4, "out of bounds memory access"); - let mut word = - self.stack.peekw().expect("operand stack does not contain a full word"); - word.reverse(); - self.memory[addr] = word; - self.callstack.push(state); - return Ok(EmulatorEvent::MemoryWrite { - addr: addr as u32, - size: 16, - }); - } - Op::If(then_blk, else_blk) => { - self.step_over = Some(state.ip()); - let cond = pop_bool!(self); - let dest = if cond { - state.enter_block(then_blk); - then_blk - } else { - state.enter_block(else_blk); - else_blk - }; - self.callstack.push(state); - return Ok(EmulatorEvent::Jump(dest)); - } - Op::While(body_blk) => { - self.step_over = Some(state.ip()); - let cond = pop_bool!(self); - if cond { - state.enter_while_loop(body_blk); - self.callstack.push(state); - return Ok(EmulatorEvent::EnterLoop(body_blk)); - } - } - Op::Repeat(n, body_blk) => { - self.step_over = Some(state.ip()); - state.repeat_block(body_blk, n); - self.callstack.push(state); - return Ok(EmulatorEvent::EnterLoop(body_blk)); - } - Op::Exec(callee) => { - let fun = self - .functions - .get(&callee) - .cloned() - .ok_or(EmulationError::UndefinedFunction(callee))?; - self.step_over = Some(state.ip()); - match fun { - Stub::Asm(ref function) => { - let fp = self.locals[&function.name]; - let callee_state = Activation::new(function.clone(), fp); - // Suspend caller and scheduled callee next - self.callstack.push(state); - self.callstack.push(callee_state); - return Ok(EmulatorEvent::EnterFunction(function.name)); - } - Stub::Native(_function) => unimplemented!(), - } - } - Op::Call(_callee) | Op::Syscall(_callee) => unimplemented!(), - Op::Add => binop!(self, add), - Op::AddImm(imm) => binop!(self, add, imm), - Op::Sub => binop!(self, sub), - Op::SubImm(imm) => binop!(self, sub, imm), - Op::Mul => binop!(self, mul), - Op::MulImm(imm) => binop!(self, mul, imm), - Op::Div => binop!(self, div), - Op::DivImm(imm) => binop!(self, div, imm), - Op::Neg => { - let a = self.stack.pop().expect("operand stack is empty"); - self.stack.push(-a); - } - Op::Inv => { - let a = self.stack.pop().expect("operand stack is empty"); - self.stack.push(a.inv()); - } - Op::Incr => binop!(self, add, Felt::ONE), - Op::Ilog2 => { - let a = peek!(self).as_int(); - assert!(a > 0, "invalid ilog2 argument: expected {a} to be > 0"); - self.advice_stack.push_u32(a.ilog2()); - } - Op::Pow2 => { - let a = pop!(self).as_int(); - assert!( - a < 64, - "invalid power of two: expected {a} to be a value less than 64" - ); - let two = Felt::new(2); - self.stack.push(two.exp(a)); - } - Op::Exp => { - let (b, a) = pop2!(self); - let b = b.as_int(); - assert!( - b < 64, - "invalid power of two: expected {b} to be a value less than 64" - ); - self.stack.push(a.exp(b)); - } - Op::ExpImm(pow) | Op::ExpBitLength(pow) => { - let pow = pow as u64; - let a = pop!(self); - assert!( - pow < 64, - "invalid power of two: expected {pow} to be a value less than 64" - ); - self.stack.push(a.exp(pow)); - } - Op::Not => { - let a = pop_bool!(self); - self.stack.push_u8(!a as u8); - } - Op::And => { - let b = pop_bool!(self); - let a = pop_bool!(self); - self.stack.push_u8((b & a) as u8); - } - Op::AndImm(b) => { - let a = pop_bool!(self); - self.stack.push_u8((a & b) as u8); - } - Op::Or => { - let b = pop_bool!(self); - let a = pop_bool!(self); - self.stack.push_u8((b | a) as u8); - } - Op::OrImm(b) => { - let a = pop_bool!(self); - self.stack.push_u8((a | b) as u8); - } - Op::Xor => { - let b = pop_bool!(self); - let a = pop_bool!(self); - self.stack.push_u8((b ^ a) as u8); - } - Op::XorImm(b) => { - let a = pop_bool!(self); - self.stack.push_u8((a ^ b) as u8); - } - Op::Eq => comparison!(self, eq), - Op::EqImm(imm) => comparison!(self, eq, imm.as_int()), - Op::Neq => comparison!(self, ne), - Op::NeqImm(imm) => comparison!(self, ne, imm.as_int()), - Op::Gt => comparison!(self, gt), - Op::GtImm(imm) => comparison!(self, gt, imm.as_int()), - Op::Gte => comparison!(self, ge), - Op::GteImm(imm) => comparison!(self, ge, imm.as_int()), - Op::Lt => comparison!(self, lt), - Op::LtImm(imm) => comparison!(self, lt, imm.as_int()), - Op::Lte => comparison!(self, le), - Op::LteImm(imm) => comparison!(self, le, imm.as_int()), - Op::IsOdd => { - let a = pop!(self).as_int(); - self.stack.push_u8((a % 2 == 0) as u8); - } - Op::Eqw => { - let b = popw!(self); - let a = popw!(self); - self.stack.push_u8((a == b) as u8); - } - Op::Clk => { - self.stack.push(Felt::new(self.clk as u64)); - } - Op::Sdepth => { - self.stack.push(Felt::new(self.stack.len() as u64)); - } - Op::U32Test => { - let top = self.stack.peek().expect("operand stack is empty").as_int(); - self.stack.push_u8((top < U32_P) as u8); - } - Op::U32Testw => { - let word = self.stack.peekw().expect("operand stack is empty"); - let is_true = word.iter().all(|elem| elem.as_int() < U32_P); - self.stack.push_u8(is_true as u8); - } - Op::U32Assert => { - let top = self.stack.peek().expect("operand stack is empty").as_int(); - assert!(top < U32_P, "assertion failed: {top} is larger than 2^32"); - } - Op::U32Assert2 => { - let a = self.stack.peek().expect("operand stack is empty").as_int(); - let b = self.stack.peek().expect("operand stack is empty").as_int(); - assert!(a < U32_P, "assertion failed: {a} is larger than 2^32"); - assert!(b < U32_P, "assertion failed: {b} is larger than 2^32"); - } - Op::U32Assertw => { - let word = self.stack.peekw().expect("operand stack is empty"); - for elem in word.into_iter() { - assert!( - elem.as_int() < U32_P, - "assertion failed: {elem} is larger than 2^32" - ); - } - } - Op::U32Cast => { - let a = pop!(self).as_int(); - self.stack.push(Felt::new(a % U32_P)); - } - Op::U32Split => { - let a = pop!(self).as_int(); - let hi = a / U32_P; - let lo = a % U32_P; - self.stack.push(Felt::new(lo)); - self.stack.push(Felt::new(hi)); - } - Op::U32OverflowingAdd => binop_overflowing_u32!(self, add), - Op::U32OverflowingAddImm(imm) => binop_overflowing_u32!(self, add, imm), - Op::U32WrappingAdd => binop_wrapping_u32!(self, add), - Op::U32WrappingAddImm(imm) => binop_wrapping_u32!(self, add, imm), - Op::U32OverflowingAdd3 => todo!(), - Op::U32WrappingAdd3 => todo!(), - Op::U32OverflowingSub => binop_overflowing_u32!(self, sub), - Op::U32OverflowingSubImm(imm) => binop_overflowing_u32!(self, sub, imm), - Op::U32WrappingSub => binop_wrapping_u32!(self, sub), - Op::U32WrappingSubImm(imm) => binop_wrapping_u32!(self, sub, imm), - Op::U32OverflowingMul => binop_overflowing_u32!(self, mul), - Op::U32OverflowingMulImm(imm) => binop_overflowing_u32!(self, mul, imm), - Op::U32WrappingMul => binop_wrapping_u32!(self, mul), - Op::U32WrappingMulImm(imm) => binop_wrapping_u32!(self, mul, imm), - Op::U32OverflowingMadd => { - let b = pop_u32!(self) as u64; - let a = pop_u32!(self) as u64; - let c = pop_u32!(self) as u64; - let result = a * b + c; - let d = result % 2u64.pow(32); - let e = result / 2u64.pow(32); - self.stack.push(Felt::new(d)); - self.stack.push(Felt::new(e)); - } - Op::U32WrappingMadd => { - let b = pop_u32!(self) as u64; - let a = pop_u32!(self) as u64; - let c = pop_u32!(self) as u64; - let d = (a * b + c) % 2u64.pow(32); - self.stack.push(Felt::new(d)); - } - Op::U32Div => binop_unchecked_u32!(self, div), - Op::U32DivImm(imm) => binop_unchecked_u32!(self, div, imm as u64), - Op::U32Mod => { - let b = pop!(self).as_int(); - let a = pop!(self).as_int(); - self.stack.push(Felt::new(a % b)); - } - Op::U32ModImm(imm) => { - let a = pop!(self).as_int(); - self.stack.push(Felt::new(a % imm as u64)); - } - Op::U32DivMod => { - let b = pop!(self).as_int(); - let a = pop!(self).as_int(); - self.stack.push(Felt::new(a / b)); - self.stack.push(Felt::new(a % b)); - } - Op::U32DivModImm(b) => { - let b = b as u64; - let a = pop!(self).as_int(); - self.stack.push(Felt::new(a / b)); - self.stack.push(Felt::new(a % b)); - } - Op::U32And => binop32!(self, bitand), - Op::U32Or => binop32!(self, bitor), - Op::U32Xor => binop32!(self, bitxor), - Op::U32Not => { - let a = pop_u32!(self); - self.stack.push_u32(!a); - } - Op::U32Shl => binop_wrapping_u32!(self, shl), - Op::U32ShlImm(imm) => binop_wrapping_u32!(self, shl, imm), - Op::U32Shr => binop_wrapping_u32!(self, shr), - Op::U32ShrImm(imm) => binop_wrapping_u32!(self, shr, imm), - Op::U32Rotl => { - let b = pop_u32!(self); - let a = pop_u32!(self); - self.stack.push_u32(a.rotate_left(b)); - } - Op::U32RotlImm(imm) => { - let a = pop_u32!(self); - self.stack.push_u32(a.rotate_left(imm)); - } - Op::U32Rotr => { - let b = pop_u32!(self); - let a = pop_u32!(self); - self.stack.push_u32(a.rotate_right(b)); - } - Op::U32RotrImm(imm) => { - let a = pop_u32!(self); - self.stack.push_u32(a.rotate_right(imm)); - } - Op::U32Popcnt => { - let a = pop_u32!(self); - self.stack.push_u32(a.count_ones()); - } - Op::U32Clz => { - let a = pop_u32!(self); - self.stack.push_u32(a.leading_zeros()); - } - Op::U32Clo => { - let a = pop_u32!(self); - self.stack.push_u32(a.leading_ones()); - } - Op::U32Ctz => { - let a = pop_u32!(self); - self.stack.push_u32(a.trailing_zeros()); - } - Op::U32Cto => { - let a = pop_u32!(self); - self.stack.push_u32(a.trailing_ones()); - } - Op::U32Gt => comparison!(self, gt), - Op::U32Gte => comparison!(self, ge), - Op::U32Lt => comparison!(self, lt), - Op::U32Lte => comparison!(self, le), - Op::U32Min => { - let b = pop!(self).as_int(); - let a = pop!(self).as_int(); - self.stack.push(Felt::new(cmp::min(a, b))); - } - Op::U32Max => { - let b = pop!(self).as_int(); - let a = pop!(self).as_int(); - self.stack.push(Felt::new(cmp::max(a, b))); - } - Op::Breakpoint => { - self.callstack.push(state); - return Ok(EmulatorEvent::Breakpoint(BreakpointEvent::Step)); - } - Op::DebugStack => { - dbg!(self.stack.debug()); - } - Op::DebugStackN(n) => { - dbg!(self.stack.debug().take(n as usize)); - } - Op::DebugMemory - | Op::DebugMemoryAt(_) - | Op::DebugMemoryRange(..) - | Op::DebugFrame - | Op::DebugFrameAt(_) - | Op::DebugFrameRange(..) => (), - Op::Emit(_) | Op::Trace(_) => (), - op => unimplemented!("missing opcode implementation for {op:?}"), - } - - match ix_with_op.effect { - ControlEffect::Repeat(_) | ControlEffect::Loopback => { - self.callstack.push(state); - return Ok(EmulatorEvent::EnterLoop(ix_with_op.ip.block)); - } - ControlEffect::Enter => { - self.callstack.push(state); - return Ok(EmulatorEvent::Jump(ix_with_op.ip.block)); - } - ControlEffect::Exit => { - self.callstack.push(state); - return Ok(EmulatorEvent::Jump(ix_with_op.ip.block)); - } - ControlEffect::None => (), - } - - // Suspend the current activation record - self.callstack.push(state); - - Ok(EmulatorEvent::Suspended) - } else { - // No more code left in the current function - Ok(EmulatorEvent::ExitFunction(current_function)) - } - } -} diff --git a/codegen/masm/src/events.rs b/codegen/masm/src/events.rs new file mode 100644 index 000000000..74cb59763 --- /dev/null +++ b/codegen/masm/src/events.rs @@ -0,0 +1,63 @@ +//! This module contains the set of compiler-emitted event codes, and their explanations +use core::num::NonZeroU32; + +/// This event is emitted via `trace`, and indicates that a procedure call frame is entered +/// +/// The mnemonic here is F = frame, 0 = open +pub const TRACE_FRAME_START: u32 = 0xf0; + +/// This event is emitted via `trace`, and indicates that a procedure call frame is exited +/// +/// The mnemonic here is F = frame, C = close +pub const TRACE_FRAME_END: u32 = 0xfc; + +/// A typed wrapper around the raw trace events known to the compiler +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u32)] +pub enum TraceEvent { + FrameStart, + FrameEnd, + AssertionFailed(Option), + Unknown(u32), +} +impl TraceEvent { + #[inline(always)] + pub fn is_frame_start(&self) -> bool { + matches!(self, Self::FrameStart) + } + + #[inline(always)] + pub fn is_frame_end(&self) -> bool { + matches!(self, Self::FrameEnd) + } + + pub fn as_u32(self) -> u32 { + match self { + Self::FrameStart => TRACE_FRAME_START, + Self::FrameEnd => TRACE_FRAME_END, + Self::AssertionFailed(None) => 0, + Self::AssertionFailed(Some(code)) => code.get(), + Self::Unknown(event) => event, + } + } +} +impl From for TraceEvent { + fn from(raw: u32) -> Self { + match raw { + TRACE_FRAME_START => Self::FrameStart, + TRACE_FRAME_END => Self::FrameEnd, + _ => Self::Unknown(raw), + } + } +} +impl From for u32 { + fn from(event: TraceEvent) -> Self { + match event { + TraceEvent::FrameStart => TRACE_FRAME_START, + TraceEvent::FrameEnd => TRACE_FRAME_END, + TraceEvent::AssertionFailed(None) => 0, + TraceEvent::AssertionFailed(Some(code)) => code.get(), + TraceEvent::Unknown(code) => code, + } + } +} diff --git a/codegen/masm/src/intrinsics.rs b/codegen/masm/src/intrinsics.rs new file mode 100644 index 000000000..391a396cb --- /dev/null +++ b/codegen/masm/src/intrinsics.rs @@ -0,0 +1,86 @@ +use miden_assembly::{ + ast::{Module, ModuleKind}, + LibraryPath, +}; +use midenc_session::diagnostics::{PrintDiagnostic, SourceLanguage, SourceManager, Uri}; + +pub const I32_INTRINSICS_MODULE_NAME: &str = "intrinsics::i32"; +pub const I64_INTRINSICS_MODULE_NAME: &str = "intrinsics::i64"; +pub const I128_INTRINSICS_MODULE_NAME: &str = "intrinsics::i128"; +pub const MEM_INTRINSICS_MODULE_NAME: &str = "intrinsics::mem"; +pub const CRYPTO_INTRINSICS_MODULE_NAME: &str = "intrinsics::crypto"; +pub const ADVICE_INTRINSICS_MODULE_NAME: &str = "intrinsics::advice"; + +pub const INTRINSICS_MODULE_NAMES: [&str; 6] = [ + I32_INTRINSICS_MODULE_NAME, + I64_INTRINSICS_MODULE_NAME, + I128_INTRINSICS_MODULE_NAME, + MEM_INTRINSICS_MODULE_NAME, + CRYPTO_INTRINSICS_MODULE_NAME, + ADVICE_INTRINSICS_MODULE_NAME, +]; + +const I32_INTRINSICS: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i32.masm")); +const I64_INTRINSICS: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i64.masm")); +const I128_INTRINSICS: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i128.masm")); +const MEM_INTRINSICS: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/mem.masm")); +const CRYPTO_INTRINSICS: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/crypto.masm")); +const ADVICE_INTRINSICS: &str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/advice.masm")); + +/// This is a mapping of intrinsics module name to the raw MASM source for that module +const INTRINSICS: [(&str, &str, &str); 6] = [ + ( + I32_INTRINSICS_MODULE_NAME, + I32_INTRINSICS, + concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i32.masm"), + ), + ( + I64_INTRINSICS_MODULE_NAME, + I64_INTRINSICS, + concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i64.masm"), + ), + ( + I128_INTRINSICS_MODULE_NAME, + I128_INTRINSICS, + concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i128.masm"), + ), + ( + MEM_INTRINSICS_MODULE_NAME, + MEM_INTRINSICS, + concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/mem.masm"), + ), + ( + CRYPTO_INTRINSICS_MODULE_NAME, + CRYPTO_INTRINSICS, + concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/crypto.masm"), + ), + ( + ADVICE_INTRINSICS_MODULE_NAME, + ADVICE_INTRINSICS, + concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/advice.masm"), + ), +]; + +/// This helper loads the named module from the set of intrinsics modules defined in this crate. +/// +/// Expects the fully-qualified name to be given, e.g. `intrinsics::mem` +pub fn load>(name: N, source_manager: &dyn SourceManager) -> Option> { + let name = name.as_ref(); + let (name, source, filename) = INTRINSICS.iter().copied().find(|(n, ..)| *n == name)?; + let filename = Uri::new(filename); + let source_file = source_manager.load(SourceLanguage::Masm, filename, source.to_string()); + let path = LibraryPath::new(name).expect("invalid module name"); + match Module::parse(path, ModuleKind::Library, source_file.clone()) { + Ok(module) => Some(module), + Err(err) => { + let err = PrintDiagnostic::new(err); + panic!("failed to parse intrinsic module: {err}"); + } + } +} diff --git a/codegen/masm/src/lib.rs b/codegen/masm/src/lib.rs index 6257cddc7..127c11828 100644 --- a/codegen/masm/src/lib.rs +++ b/codegen/masm/src/lib.rs @@ -1,28 +1,141 @@ -#![feature(array_windows)] -#![feature(iter_array_chunks)] #![feature(debug_closure_helpers)] +#![feature(assert_matches)] +#![feature(const_type_id)] +#![feature(array_chunks)] +#![feature(iter_array_chunks)] +#![feature(iterator_try_collect)] +#![deny(warnings)] extern crate alloc; -mod codegen; -mod compiler; -mod convert; -mod emulator; -mod masm; -mod packaging; -#[cfg(test)] -mod tests; +mod artifact; +mod data_segments; +mod emit; +mod emitter; +mod events; +pub mod intrinsics; +mod linker; +mod lower; +mod opt; +mod stack; + +pub mod masm { + pub use miden_assembly_syntax::{ + ast::*, + debuginfo::{SourceSpan, Span, Spanned}, + parser::IntValue, + KernelLibrary, Library, LibraryNamespace, LibraryPath, + }; +} +pub(crate) use self::lower::HirLowering; pub use self::{ - compiler::{ - default_function_rewrites, default_rewrites, CompilerResult, MasmArtifact, MasmCompiler, - MastArtifact, - }, - convert::ConvertHirToMasm, - emulator::{ - Breakpoint, BreakpointEvent, CallFrame, DebugInfo, DebugInfoWithStack, EmulationError, - Emulator, EmulatorEvent, InstructionPointer, WatchMode, Watchpoint, WatchpointId, - }, - masm::*, - packaging::*, + artifact::{MasmComponent, Rodata}, + events::{TraceEvent, TRACE_FRAME_END, TRACE_FRAME_START}, + lower::{NativePtr, ToMasmComponent}, + stack::{Constraint, Operand, OperandStack}, }; + +pub fn register_dialect_hooks(context: &midenc_hir::Context) { + use midenc_dialect_arith as arith; + use midenc_dialect_cf as cf; + use midenc_dialect_hir as hir; + use midenc_dialect_scf as scf; + use midenc_dialect_ub as ub; + use midenc_hir::dialects::builtin; + + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + //info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); +} diff --git a/codegen/masm/src/linker.rs b/codegen/masm/src/linker.rs new file mode 100644 index 000000000..8633e8a34 --- /dev/null +++ b/codegen/masm/src/linker.rs @@ -0,0 +1,302 @@ +use midenc_hir::{ + dialects::builtin::{self, DataSegmentError, SegmentRef}, + Alignable, FxHashMap, Symbol, +}; + +const DEFAULT_PAGE_SIZE: u32 = 2u32.pow(16); +/// Currently, Wasm modules produced by rustc reserve 16 pages for the Rust stack +/// (see __stack_pointer global variable value in Wasm). +// We start our reserved memory from the next page after the rustc reserved for the +// Rust stack to avoid overlapping with Rust `static` vars. +const DEFAULT_RESERVATION: u32 = 17; + +pub struct LinkInfo { + component: builtin::ComponentId, + globals_layout: GlobalVariableLayout, + segment_layout: builtin::DataSegmentLayout, + reserved_memory_pages: u32, + page_size: u32, +} + +impl LinkInfo { + #[cfg(test)] + pub fn new(id: builtin::ComponentId) -> Self { + Self { + component: id, + globals_layout: Default::default(), + segment_layout: Default::default(), + reserved_memory_pages: 0, + page_size: DEFAULT_PAGE_SIZE, + } + } + + #[inline] + pub fn component(&self) -> &builtin::ComponentId { + &self.component + } + + pub fn has_globals(&self) -> bool { + !self.globals_layout.offsets.is_empty() + } + + pub fn has_data_segments(&self) -> bool { + !self.segment_layout.is_empty() + } + + pub fn globals_layout(&self) -> &GlobalVariableLayout { + &self.globals_layout + } + + #[allow(unused)] + pub fn segment_layout(&self) -> &builtin::DataSegmentLayout { + &self.segment_layout + } + + #[inline(always)] + pub fn reserved_memory_pages(&self) -> u32 { + self.reserved_memory_pages + } + + #[inline] + pub fn reserved_memory_bytes(&self) -> usize { + self.reserved_memory_pages() as usize * self.page_size() as usize + } + + #[inline(always)] + pub fn page_size(&self) -> u32 { + self.page_size + } +} + +pub struct Linker { + globals_layout: GlobalVariableLayout, + segment_layout: builtin::DataSegmentLayout, + reserved_memory_pages: u32, + page_size: u32, +} + +impl Default for Linker { + fn default() -> Self { + Self::new(DEFAULT_RESERVATION, DEFAULT_PAGE_SIZE) + } +} + +impl Linker { + pub fn new(reserved_memory_pages: u32, page_size: u32) -> Self { + let page_size = if page_size > 0 { + assert!(page_size.is_power_of_two()); + page_size + } else { + DEFAULT_PAGE_SIZE + }; + let globals_start = reserved_memory_pages * page_size; + Self { + globals_layout: GlobalVariableLayout::new(globals_start, page_size), + segment_layout: Default::default(), + reserved_memory_pages, + page_size, + } + } + + pub fn link(mut self, component: &builtin::Component) -> Result { + // Gather information needed to compute component data layout + + // 1. Verify that the component is non-empty + let body = component.body(); + if body.is_empty() { + // This component has no definition + return Err(LinkerError::Undefined); + } + + // 2. Visit each Module in the component and discover Segment and GlobalVariable items + let body = body.entry(); + for item in body.body() { + if let Some(module) = item.downcast_ref::() { + let module_body = module.body(); + if module_body.is_empty() { + continue; + } + + let module_body = module_body.entry(); + for item in module_body.body() { + if let Some(segment) = item.downcast_ref::() { + log::debug!(target: "linker", + "inserting segment at offset {:#x}, size: {} bytes", + segment.offset(), + segment.size_in_bytes() + ); + self.segment_layout + .insert(unsafe { SegmentRef::from_raw(segment) }) + .map_err(|err| LinkerError::InvalidSegment { + id: component.id(), + err, + })?; + continue; + } + + if let Some(global) = item.downcast_ref::() { + if global.is_declaration() { + continue; + } + self.globals_layout.insert(global); + } + } + } + } + + // 3. Layout global variables in the next page following the last data segment + let next_available_offset = self.segment_layout.next_available_offset(); + let reserved_offset = (self.reserved_memory_pages * self.page_size).next_multiple_of(4); + // We add a page after the data segments to avoid overlapping with Rust `static` vars which + // are placed after data segments (if present). + let next_available_offset_after_rust_statics = next_available_offset + DEFAULT_PAGE_SIZE; + log::debug!(target: "linker", + "next_available_offset (after Rust statics) from segments: {:#x}, reserved_offset: {:#x}, \ + segment_count: {}", + next_available_offset_after_rust_statics, + reserved_offset, + self.segment_layout.len() + ); + self.globals_layout.update_global_table_offset(core::cmp::max( + reserved_offset, + next_available_offset_after_rust_statics, + )); + log::debug!(target: "linker", + "global_table_offset set to: {:#x}", + self.globals_layout.global_table_offset() + ); + + Ok(LinkInfo { + component: component.id(), + globals_layout: core::mem::take(&mut self.globals_layout), + segment_layout: core::mem::take(&mut self.segment_layout), + reserved_memory_pages: self.reserved_memory_pages, + page_size: self.page_size, + }) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum LinkerError { + /// The provided component is undefined (i.e. we only know its interface, but have none of + /// the actual definitions). + #[error("invalid root component: expected definition, got declaration")] + Undefined, + /// Multiple segments were defined in the same component with the same offset + #[error("invalid component: '{id}' has invalid data segment: {err}")] + InvalidSegment { + id: builtin::ComponentId, + #[source] + err: DataSegmentError, + }, +} + +/// This struct contains data about the layout of global variables in linear memory +#[derive(Default, Clone)] +pub struct GlobalVariableLayout { + global_table_offset: u32, + stack_pointer: Option, + next_offset: u32, + page_size: u32, + offsets: FxHashMap, +} +impl GlobalVariableLayout { + fn new(global_table_offset: u32, page_size: u32) -> Self { + Self { + global_table_offset, + stack_pointer: None, + next_offset: global_table_offset, + page_size, + offsets: Default::default(), + } + } + + /// Get the address/offset at which global variables will start being allocated + #[allow(unused)] + pub fn global_table_offset(&self) -> u32 { + self.global_table_offset + } + + /// Get the address/offset at which the global stack pointer variable will be allocated + pub fn stack_pointer_offset(&self) -> Option { + self.stack_pointer + } + + /// Get the address/offset of the next page boundary following the last inserted global variable + pub fn next_page_boundary(&self) -> u32 { + self.next_offset.next_multiple_of(self.page_size) + } + + /// Get the statically-allocated address at which the global variable `gv` is to be placed. + /// + /// This function returns `None` if the given global variable is unresolvable. + pub fn get_computed_addr(&self, gv: builtin::GlobalVariableRef) -> Option { + self.offsets.get(&gv).copied() + } + + /// Update the global table offset and adjust existing global variable offsets if necessary. + /// + /// This method should be used instead of directly modifying the `global_table_offset` field. + /// If globals have already been inserted, their offsets will be adjusted to maintain + /// their relative positions from the new base offset. + pub fn update_global_table_offset(&mut self, new_offset: u32) { + let old_offset = self.global_table_offset; + + // Update the base offset + self.global_table_offset = new_offset; + + // If there are existing globals, we need to adjust their offsets + if !self.offsets.is_empty() { + // Calculate the difference between old and new offset + let offset_diff = new_offset as i32 - old_offset as i32; + + // Update all existing global offsets + for offset in self.offsets.values_mut() { + *offset = (*offset as i32 + offset_diff) as u32; + } + + // Update the stack pointer offset if it exists + if let Some(sp_offset) = self.stack_pointer.as_mut() { + *sp_offset = (*sp_offset as i32 + offset_diff) as u32; + } + + // Update the next offset to maintain the same relative position + self.next_offset = (self.next_offset as i32 + offset_diff) as u32; + } else { + // If no globals have been inserted yet, just update next_offset to match + self.next_offset = new_offset; + } + + log::debug!(target: "linker", + "GlobalVariableLayout: updated global_table_offset from {old_offset:#x} to {new_offset:#x}" + ); + } + + pub fn insert(&mut self, gv: &builtin::GlobalVariable) { + let key = unsafe { builtin::GlobalVariableRef::from_raw(gv) }; + + // Ensure the stack pointer is tracked and uses the same offset globally + let is_stack_pointer = gv.name() == "__stack_pointer"; + if is_stack_pointer { + if let Some(offset) = self.stack_pointer { + let _ = self.offsets.try_insert(key, offset); + return; + } + } + + let ty = gv.ty(); + let offset = self.next_offset.align_up(ty.min_alignment() as u32); + if self.offsets.try_insert(key, offset).is_ok() { + log::debug!(target: "linker", + "GlobalVariableLayout: allocated global '{}' at offset {:#x} (size: {} bytes)", + gv.name(), + offset, + ty.size_in_bytes() + ); + if is_stack_pointer { + self.stack_pointer = Some(offset); + } + self.next_offset = offset + ty.size_in_bytes() as u32; + } + } +} diff --git a/codegen/masm/src/lower.rs b/codegen/masm/src/lower.rs new file mode 100644 index 000000000..29c706c77 --- /dev/null +++ b/codegen/masm/src/lower.rs @@ -0,0 +1,6 @@ +mod component; +mod lowering; +mod native_ptr; +mod utils; + +pub use self::{component::ToMasmComponent, lowering::HirLowering, native_ptr::NativePtr}; diff --git a/codegen/masm/src/lower/component.rs b/codegen/masm/src/lower/component.rs new file mode 100644 index 000000000..0767f9143 --- /dev/null +++ b/codegen/masm/src/lower/component.rs @@ -0,0 +1,603 @@ +use alloc::{collections::BTreeSet, sync::Arc}; + +use miden_assembly::{ast::InvocationTarget, LibraryPath}; +use miden_assembly_syntax::parser::WordValue; +use miden_mast_package::ProcedureName; +use midenc_hir::{ + diagnostics::IntoDiagnostic, dialects::builtin, pass::AnalysisManager, CallConv, FunctionIdent, + Op, SourceSpan, Span, Symbol, ValueRef, +}; +use midenc_hir_analysis::analyses::LivenessAnalysis; +use midenc_session::{ + diagnostics::{Report, Spanned}, + TargetEnv, +}; +use smallvec::SmallVec; + +use crate::{ + artifact::MasmComponent, + emitter::BlockEmitter, + linker::{LinkInfo, Linker}, + masm, TraceEvent, +}; + +/// This trait represents a conversion pass from some HIR entity to a Miden Assembly component. +pub trait ToMasmComponent { + fn to_masm_component(&self, analysis_manager: AnalysisManager) + -> Result; +} + +/// 1:1 conversion from HIR component to MASM component +impl ToMasmComponent for builtin::Component { + fn to_masm_component( + &self, + analysis_manager: AnalysisManager, + ) -> Result { + // Get the current compiler context + let context = self.as_operation().context_rc(); + + // Run the linker for this component in order to compute its data layout + let link_info = Linker::default().link(self).map_err(Report::msg)?; + + // Get the library path of the component + let component_path = link_info.component().to_library_path(); + + // Get the entrypoint, if specified + let entrypoint = match context.session().options.entrypoint.as_deref() { + Some(entry) => { + let entry_id = entry.parse::().map_err(|_| { + Report::msg(format!("invalid entrypoint identifier: '{entry}'")) + })?; + let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts( + Span::new(entry_id.function.span, entry_id.function.as_str().into()), + )); + + // Check if we're inside the synthetic "wrapper" component used for pure Rust + // compilation. Since the user does not know about it, their entrypoint does not + // include the synthetic component path. We append the user-provided path to the + // root component path here if needed. + // + // TODO(pauls): Narrow this to only be true if the target env is not 'rollup', we + // cannot currently do so because we do not have sufficient Cargo metadata yet in + // 'cargo miden build' to detect the target env, and we default it to 'rollup' + let is_wrapper = component_path.path() == "root_ns:root@1.0.0"; + let path = if is_wrapper { + component_path.clone().append_unchecked(entry_id.module) + } else { + // We're compiling a Wasm component and the component id is included + // in the entrypoint. + LibraryPath::new(entry_id.module).into_diagnostic()? + }; + Some(masm::InvocationTarget::AbsoluteProcedurePath { name, path }) + } + None => None, + }; + + // If we have global variables or data segments, we will require a component initializer + // function, as well as a module to hold component-level functions such as init + let requires_init = link_info.has_globals() || link_info.has_data_segments(); + let init = if requires_init { + Some(masm::InvocationTarget::AbsoluteProcedurePath { + name: masm::ProcedureName::new("init").unwrap(), + path: component_path, + }) + } else { + None + }; + + // Initialize the MASM component with basic information we have already + let id = link_info.component().clone(); + + // Define the initial component modules set + // + // The top-level component module is always defined, but may be empty + let modules = + vec![Arc::new(masm::Module::new(masm::ModuleKind::Library, id.to_library_path()))]; + + let rodata = data_segments_to_rodata(&link_info)?; + + let kernel = if matches!(context.session().options.target, TargetEnv::Rollup { .. }) { + Some(miden_lib::transaction::TransactionKernel::kernel()) + } else { + None + }; + + // Compute the first page boundary after the end of the globals table (or reserved memory + // if no globals) to use as the start of the dynamic heap when the program is executed + let heap_base = core::cmp::max( + link_info.reserved_memory_bytes(), + link_info.globals_layout().next_page_boundary() as usize, + ); + let heap_base = u32::try_from(heap_base) + .expect("unable to allocate dynamic heap: global table too large"); + let stack_pointer = link_info.globals_layout().stack_pointer_offset(); + let mut masm_component = MasmComponent { + id, + init, + entrypoint, + kernel, + rodata, + heap_base, + stack_pointer, + modules, + }; + let builder = MasmComponentBuilder { + analysis_manager, + component: &mut masm_component, + link_info: &link_info, + init_body: Default::default(), + invoked_from_init: Default::default(), + }; + + builder.build(self)?; + + Ok(masm_component) + } +} + +fn data_segments_to_rodata(link_info: &LinkInfo) -> Result, Report> { + use midenc_hir::constants::ConstantData; + + use crate::data_segments::{merge_data_segments, ResolvedDataSegment}; + let mut resolved = SmallVec::<[ResolvedDataSegment; 2]>::new(); + for sref in link_info.segment_layout().iter() { + let s = sref.borrow(); + resolved.push(ResolvedDataSegment { + offset: *s.offset(), + data: s.initializer().as_slice().to_vec(), + readonly: *s.readonly(), + }); + } + Ok(match merge_data_segments(resolved).map_err(Report::msg)? { + None => alloc::vec::Vec::new(), + Some(merged) => { + let data = alloc::sync::Arc::new(ConstantData::from(merged.data)); + let felts = crate::Rodata::bytes_to_elements(data.as_slice()); + let digest = miden_core::crypto::hash::Rpo256::hash_elements(&felts); + alloc::vec![crate::Rodata { + component: link_info.component().clone(), + digest, + start: super::NativePtr::from_ptr(merged.offset), + data, + }] + } + }) +} + +struct MasmComponentBuilder<'a> { + component: &'a mut MasmComponent, + analysis_manager: AnalysisManager, + link_info: &'a LinkInfo, + init_body: Vec, + invoked_from_init: BTreeSet, +} + +impl MasmComponentBuilder<'_> { + /// Convert the component body to Miden Assembly + pub fn build(mut self, component: &builtin::Component) -> Result<(), Report> { + use masm::{Instruction as Inst, InvocationTarget, Op}; + + // If a component-level init is required, emit code to initialize the heap before any other + // initialization code. + if self.component.init.is_some() { + let span = component.span(); + + // Heap metadata initialization + let heap_base = self.component.heap_base; + self.init_body.push(masm::Op::Inst(Span::new(span, Inst::PushU32(heap_base)))); + let heap_init = masm::ProcedureName::new("heap_init").unwrap(); + let memory_intrinsics = masm::LibraryPath::new("intrinsics::mem").unwrap(); + self.init_body.push(Op::Inst(Span::new( + span, + Inst::Trace(TraceEvent::FrameStart.as_u32().into()), + ))); + self.init_body.push(Op::Inst(Span::new( + span, + Inst::Exec(InvocationTarget::AbsoluteProcedurePath { + name: heap_init, + path: memory_intrinsics, + }), + ))); + self.init_body + .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); + + // Data segment initialization + self.emit_data_segment_initialization(); + } + + // Translate component body + let region = component.body(); + let block = region.entry(); + for op in block.body() { + if let Some(module) = op.downcast_ref::() { + self.define_module(module)?; + } else if let Some(interface) = op.downcast_ref::() { + self.define_interface(interface)?; + } else if let Some(function) = op.downcast_ref::() { + self.define_function(function)?; + } else { + panic!( + "invalid component-level operation: '{}' is not supported in a component body", + op.name() + ) + } + } + + // Finalize the component-level init, if required + if self.component.init.is_some() { + let module = + Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference"); + + let init_name = masm::ProcedureName::new("init").unwrap(); + let init_body = core::mem::take(&mut self.init_body); + let init = masm::Procedure::new( + Default::default(), + masm::Visibility::Private, + init_name, + 0, + masm::Block::new(component.span(), init_body), + ); + + module.define_procedure(masm::Export::Procedure(init))?; + } else { + assert!( + self.init_body.is_empty(), + "the need for an 'init' function was not expected, but code was generated for one" + ); + } + + Ok(()) + } + + fn define_interface(&mut self, interface: &builtin::Interface) -> Result<(), Report> { + let component_path = self.component.id.to_library_path(); + let interface_path = component_path.append_unchecked(interface.name()); + let mut masm_module = + Box::new(masm::Module::new(masm::ModuleKind::Library, interface_path)); + let builder = MasmModuleBuilder { + module: &mut masm_module, + analysis_manager: self + .analysis_manager + .nest(interface.as_operation().as_operation_ref()), + link_info: self.link_info, + init_body: &mut self.init_body, + invoked_from_init: &mut self.invoked_from_init, + }; + builder.build_from_interface(interface)?; + + self.component.modules.push(Arc::from(masm_module)); + + Ok(()) + } + + fn define_module(&mut self, module: &builtin::Module) -> Result<(), Report> { + let component_path = self.component.id.to_library_path(); + let module_path = component_path.append_unchecked(module.name()); + let mut masm_module = Box::new(masm::Module::new(masm::ModuleKind::Library, module_path)); + let builder = MasmModuleBuilder { + module: &mut masm_module, + analysis_manager: self.analysis_manager.nest(module.as_operation_ref()), + link_info: self.link_info, + init_body: &mut self.init_body, + invoked_from_init: &mut self.invoked_from_init, + }; + builder.build(module)?; + + self.component.modules.push(Arc::from(masm_module)); + + Ok(()) + } + + fn define_function(&mut self, function: &builtin::Function) -> Result<(), Report> { + let builder = MasmFunctionBuilder::new(function)?; + let procedure = builder.build( + function, + self.analysis_manager.nest(function.as_operation_ref()), + self.link_info, + )?; + + let module = + Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference"); + assert_eq!( + module.path().num_components(), + 1, + "expected top-level namespace module, but one has not been defined (in '{}' of '{}')", + module.path(), + function.path() + ); + module.define_procedure(masm::Export::Procedure(procedure))?; + + Ok(()) + } + + /// Emit the sequence of instructions necessary to consume rodata from the advice stack and + /// populate the global heap with the data segments of this component, verifying that the + /// commitments match. + fn emit_data_segment_initialization(&mut self) { + use masm::{Instruction as Inst, InvocationTarget, Op}; + + // Emit data segment initialization code + // + // NOTE: This depends on the program being executed with the data for all data segments + // having been placed in the advice map with the same commitment and encoding used here. + // The program will fail to execute if this is not set up correctly. + let pipe_preimage_to_memory = masm::ProcedureName::new("pipe_preimage_to_memory").unwrap(); + let std_mem = masm::LibraryPath::new("std::mem").unwrap(); + + let span = SourceSpan::default(); + for rodata in self.component.rodata.iter() { + // Push the commitment hash (`COM`) for this data onto the operand stack + + // WARNING: These two are equivalent, shouldn't this be a no-op? + let word = rodata.digest.as_elements(); + let word_value = [word[0], word[1], word[2], word[3]]; + + self.init_body + .push(Op::Inst(Span::new(span, Inst::PushWord(WordValue(word_value))))); + // Move rodata from the advice map, using the commitment as key, to the advice stack + self.init_body + .push(Op::Inst(Span::new(span, Inst::SysEvent(masm::SystemEventNode::PushMapVal)))); + // write_ptr + assert!(rodata.start.is_word_aligned(), "rodata segments must be word-aligned"); + self.init_body.push(Op::Inst(Span::new(span, Inst::PushU32(rodata.start.addr)))); + // num_words + self.init_body + .push(Op::Inst(Span::new(span, Inst::PushU32(rodata.size_in_words() as u32)))); + // [num_words, write_ptr, COM, ..] -> [write_ptr'] + self.init_body.push(Op::Inst(Span::new( + span, + Inst::Trace(TraceEvent::FrameStart.as_u32().into()), + ))); + self.init_body.push(Op::Inst(Span::new( + span, + Inst::Exec(InvocationTarget::AbsoluteProcedurePath { + name: pipe_preimage_to_memory.clone(), + path: std_mem.clone(), + }), + ))); + self.init_body + .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into())))); + // drop write_ptr' + self.init_body.push(Op::Inst(Span::new(span, Inst::Drop))); + } + } +} + +struct MasmModuleBuilder<'a> { + module: &'a mut masm::Module, + analysis_manager: AnalysisManager, + link_info: &'a LinkInfo, + init_body: &'a mut Vec, + invoked_from_init: &'a mut BTreeSet, +} + +impl MasmModuleBuilder<'_> { + pub fn build(mut self, module: &builtin::Module) -> Result<(), Report> { + let region = module.body(); + let block = region.entry(); + for op in block.body() { + if let Some(function) = op.downcast_ref::() { + self.define_function(function)?; + } else if let Some(gv) = op.downcast_ref::() { + self.emit_global_variable_initializer(gv)?; + } else if op.is::() { + continue; + } else { + panic!( + "invalid module-level operation: '{}' is not legal in a MASM module body", + op.name() + ) + } + } + + Ok(()) + } + + pub fn build_from_interface(mut self, interface: &builtin::Interface) -> Result<(), Report> { + let region = interface.body(); + let block = region.entry(); + for op in block.body() { + if let Some(function) = op.downcast_ref::() { + self.define_function(function)?; + } else { + panic!( + "invalid interface-level operation: '{}' is not legal in a MASM module body", + op.name() + ) + } + } + + Ok(()) + } + + fn define_function(&mut self, function: &builtin::Function) -> Result<(), Report> { + let builder = MasmFunctionBuilder::new(function)?; + + let procedure = builder.build( + function, + self.analysis_manager.nest(function.as_operation_ref()), + self.link_info, + )?; + + self.module.define_procedure(masm::Export::Procedure(procedure))?; + + Ok(()) + } + + fn emit_global_variable_initializer( + &mut self, + gv: &builtin::GlobalVariable, + ) -> Result<(), Report> { + // We don't emit anything for declarations + if gv.is_declaration() { + return Ok(()); + } + + // We compute liveness for global variables independently + let analysis_manager = self.analysis_manager.nest(gv.as_operation_ref()); + let liveness = analysis_manager.get_analysis::()?; + + // Emit the initializer block + let initializer_region = gv.region(0); + let initializer_block = initializer_region.entry(); + let mut block_emitter = BlockEmitter { + liveness: &liveness, + link_info: self.link_info, + invoked: self.invoked_from_init, + target: Default::default(), + stack: Default::default(), + }; + block_emitter.emit_inline(&initializer_block); + + // Sanity checks + assert_eq!(block_emitter.stack.len(), 1, "expected only global variable value on stack"); + let return_ty = block_emitter.stack.peek().unwrap().ty(); + assert_eq!( + &return_ty, + gv.ty(), + "expected initializer to return value of same type as declaration" + ); + + // Write the initialized value to the computed storage offset for this global + let computed_addr = self + .link_info + .globals_layout() + .get_computed_addr(gv.as_global_var_ref()) + .expect("undefined global variable"); + block_emitter.emitter().store_imm(computed_addr, gv.span()); + + // Extend the generated init function with the code to initialize this global + let mut body = core::mem::take(&mut block_emitter.target); + self.init_body.append(&mut body); + + Ok(()) + } +} + +struct MasmFunctionBuilder { + span: midenc_hir::SourceSpan, + name: masm::ProcedureName, + visibility: masm::Visibility, + num_locals: u16, +} + +impl MasmFunctionBuilder { + pub fn new(function: &builtin::Function) -> Result { + use midenc_hir::{Symbol, Visibility}; + + let name = function.name(); + let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts(Span::new( + name.span, + name.as_str().into(), + ))); + let visibility = match function.visibility() { + Visibility::Public => masm::Visibility::Public, + // TODO(pauls): Support internal visibility in MASM + Visibility::Internal => masm::Visibility::Public, + Visibility::Private => masm::Visibility::Private, + }; + let locals_required = function.locals().iter().map(|ty| ty.size_in_felts()).sum::(); + let num_locals = u16::try_from(locals_required).map_err(|_| { + let context = function.as_operation().context(); + context + .diagnostics() + .diagnostic(miden_assembly::diagnostics::Severity::Error) + .with_message("cannot emit masm for function") + .with_primary_label( + function.span(), + "local storage exceeds procedure limit: no more than u16::MAX elements are \ + supported", + ) + .into_report() + })?; + + Ok(Self { + span: function.span(), + name, + visibility, + num_locals, + }) + } + + pub fn build( + self, + function: &builtin::Function, + analysis_manager: AnalysisManager, + link_info: &LinkInfo, + ) -> Result { + use alloc::collections::BTreeSet; + + use midenc_hir_analysis::analyses::LivenessAnalysis; + + log::trace!(target: "codegen", "lowering {}", function.as_operation()); + + let liveness = analysis_manager.get_analysis::()?; + + let mut invoked = BTreeSet::default(); + let entry = function.entry_block(); + let mut stack = crate::OperandStack::default(); + { + let entry_block = entry.borrow(); + for arg in entry_block.arguments().iter().rev().copied() { + stack.push(arg as ValueRef); + } + } + let mut emitter = BlockEmitter { + liveness: &liveness, + link_info, + invoked: &mut invoked, + target: Default::default(), + stack, + }; + + // For component export functions, invoke the `init` procedure first if needed. + // It loads the data segments and global vars into memory. + if function.signature().cc == CallConv::CanonLift + && (link_info.has_globals() || link_info.has_data_segments()) + { + let component_path = link_info.component().to_library_path(); + let init = InvocationTarget::AbsoluteProcedurePath { + name: ProcedureName::new("init").unwrap(), + path: component_path, + }; + let span = SourceSpan::default(); + // Add init call to the emitter's target before emitting the function body + emitter + .target + .push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(init)))); + } + + let mut body = emitter.emit(&entry.borrow()); + + if function.signature().cc == CallConv::CanonLift { + // Truncate the stack to 16 elements on exit in the component export function + // since it is expected to be `call`ed so it has a requirement to have + // no more than 16 elements on the stack when it returns. + // See https://0xmiden.github.io/miden-vm/user_docs/assembly/execution_contexts.html + // Since the VM's `drop` instruction not letting stack size go beyond the 16 elements + // we most likely end up with stack size > 16 elements at the end. + // See https://github.com/0xPolygonMiden/miden-vm/blob/c4acf49510fda9ba80f20cee1a9fb1727f410f47/processor/src/stack/mod.rs?plain=1#L226-L253 + let truncate_stack = InvocationTarget::AbsoluteProcedurePath { + name: ProcedureName::new("truncate_stack").unwrap(), + path: masm::LibraryPath::new_from_components( + masm::LibraryNamespace::new("std").unwrap(), + [masm::Ident::new("sys").unwrap()], + ), + }; + let span = SourceSpan::default(); + body.push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(truncate_stack)))); + } + let Self { + span, + name, + visibility, + num_locals, + } = self; + + let mut procedure = masm::Procedure::new(span, visibility, name, num_locals, body); + + procedure.extend_invoked(invoked); + + Ok(procedure) + } +} diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_1_case.hir b/codegen/masm/src/lower/expected/utils_emit_binary_search_1_case.hir new file mode 100644 index 000000000..f2d45ac2d --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_1_case.hir @@ -0,0 +1,15 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = scf.index_switch v0 : u32 + case 0 { + ^block1: + v3 = arith.constant 0 : u32; + scf.yield v3; + } + default { + ^block2: + v4 = arith.mul v0, v1 : u32 #[overflow = checked]; + scf.yield v4; + }; + builtin.ret v2; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_1_case.masm b/codegen/masm/src/lower/expected/utils_emit_binary_search_1_case.masm new file mode 100644 index 000000000..61eaaca57 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_1_case.masm @@ -0,0 +1,16 @@ + + dup.0 + dup.0 + eq.0 + if.true + drop + drop + drop + push.0 + else + movup.2 + mul + u32assert + swap.1 + drop + end diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_2_cases.hir b/codegen/masm/src/lower/expected/utils_emit_binary_search_2_cases.hir new file mode 100644 index 000000000..e08c6b981 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_2_cases.hir @@ -0,0 +1,20 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = scf.index_switch v0 : u32 + case 0 { + ^block1: + v3 = arith.constant 0 : u32; + scf.yield v3; + } + case 1 { + ^block2: + v4 = arith.constant 1 : u32; + scf.yield v4; + } + default { + ^block3: + v5 = arith.mul v0, v1 : u32 #[overflow = checked]; + scf.yield v5; + }; + builtin.ret v2; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_2_cases.masm b/codegen/masm/src/lower/expected/utils_emit_binary_search_2_cases.masm new file mode 100644 index 000000000..2c3faebbe --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_2_cases.masm @@ -0,0 +1,23 @@ + + dup.0 + dup.0 + push.1 + u32lte + if.true + eq.0 + if.true + drop + drop + push.0 + else + drop + drop + push.1 + end + else + movup.2 + mul + u32assert + swap.1 + drop + end diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_3_cases.hir b/codegen/masm/src/lower/expected/utils_emit_binary_search_3_cases.hir new file mode 100644 index 000000000..3d43af5b5 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_3_cases.hir @@ -0,0 +1,25 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = scf.index_switch v0 : u32 + case 0 { + ^block1: + v3 = arith.constant 0 : u32; + scf.yield v3; + } + case 1 { + ^block2: + v4 = arith.constant 1 : u32; + scf.yield v4; + } + case 2 { + ^block3: + v5 = arith.constant 2 : u32; + scf.yield v5; + } + default { + ^block4: + v6 = arith.mul v0, v1 : u32 #[overflow = checked]; + scf.yield v6; + }; + builtin.ret v2; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_3_cases.masm b/codegen/masm/src/lower/expected/utils_emit_binary_search_3_cases.masm new file mode 100644 index 000000000..31760dd7a --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_3_cases.masm @@ -0,0 +1,32 @@ + + dup.0 + dup.0 + push.1 + u32lte + if.true + eq.0 + if.true + drop + drop + push.0 + else + drop + drop + push.1 + end + else + dup.0 + eq.2 + if.true + drop + drop + drop + push.2 + else + movup.2 + mul + u32assert + swap.1 + drop + end + end diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_4_cases.hir b/codegen/masm/src/lower/expected/utils_emit_binary_search_4_cases.hir new file mode 100644 index 000000000..3dc46fe2c --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_4_cases.hir @@ -0,0 +1,30 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = scf.index_switch v0 : u32 + case 0 { + ^block1: + v3 = arith.constant 0 : u32; + scf.yield v3; + } + case 1 { + ^block2: + v4 = arith.constant 1 : u32; + scf.yield v4; + } + case 2 { + ^block3: + v5 = arith.constant 2 : u32; + scf.yield v5; + } + case 3 { + ^block4: + v6 = arith.constant 3 : u32; + scf.yield v6; + } + default { + ^block5: + v7 = arith.mul v0, v1 : u32 #[overflow = checked]; + scf.yield v7; + }; + builtin.ret v2; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_4_cases.masm b/codegen/masm/src/lower/expected/utils_emit_binary_search_4_cases.masm new file mode 100644 index 000000000..ab30e0917 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_4_cases.masm @@ -0,0 +1,39 @@ + + dup.0 + dup.0 + push.1 + u32lte + if.true + eq.0 + if.true + drop + drop + push.0 + else + drop + drop + push.1 + end + else + dup.0 + push.3 + u32lte + if.true + eq.2 + if.true + drop + drop + push.2 + else + drop + drop + push.3 + end + else + movup.2 + mul + u32assert + swap.1 + drop + end + end diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_5_cases.hir b/codegen/masm/src/lower/expected/utils_emit_binary_search_5_cases.hir new file mode 100644 index 000000000..17a279636 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_5_cases.hir @@ -0,0 +1,35 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = scf.index_switch v0 : u32 + case 0 { + ^block1: + v3 = arith.constant 0 : u32; + scf.yield v3; + } + case 1 { + ^block2: + v4 = arith.constant 1 : u32; + scf.yield v4; + } + case 2 { + ^block3: + v5 = arith.constant 2 : u32; + scf.yield v5; + } + case 3 { + ^block4: + v6 = arith.constant 3 : u32; + scf.yield v6; + } + case 4 { + ^block5: + v7 = arith.constant 4 : u32; + scf.yield v7; + } + default { + ^block6: + v8 = arith.mul v0, v1 : u32 #[overflow = checked]; + scf.yield v8; + }; + builtin.ret v2; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_5_cases.masm b/codegen/masm/src/lower/expected/utils_emit_binary_search_5_cases.masm new file mode 100644 index 000000000..1536e6267 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_5_cases.masm @@ -0,0 +1,48 @@ + + dup.0 + dup.0 + push.1 + u32lte + if.true + eq.0 + if.true + drop + drop + push.0 + else + drop + drop + push.1 + end + else + dup.0 + push.3 + u32lte + if.true + eq.2 + if.true + drop + drop + push.2 + else + drop + drop + push.3 + end + else + dup.0 + eq.4 + if.true + drop + drop + drop + push.4 + else + movup.2 + mul + u32assert + swap.1 + drop + end + end + end diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_7_cases.hir b/codegen/masm/src/lower/expected/utils_emit_binary_search_7_cases.hir new file mode 100644 index 000000000..a671639e2 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_7_cases.hir @@ -0,0 +1,45 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = scf.index_switch v0 : u32 + case 0 { + ^block1: + v3 = arith.constant 0 : u32; + scf.yield v3; + } + case 1 { + ^block2: + v4 = arith.constant 1 : u32; + scf.yield v4; + } + case 2 { + ^block3: + v5 = arith.constant 2 : u32; + scf.yield v5; + } + case 3 { + ^block4: + v6 = arith.constant 3 : u32; + scf.yield v6; + } + case 4 { + ^block5: + v7 = arith.constant 4 : u32; + scf.yield v7; + } + case 5 { + ^block6: + v8 = arith.constant 5 : u32; + scf.yield v8; + } + case 6 { + ^block7: + v9 = arith.constant 6 : u32; + scf.yield v9; + } + default { + ^block8: + v10 = arith.mul v0, v1 : u32 #[overflow = checked]; + scf.yield v10; + }; + builtin.ret v2; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_binary_search_7_cases.masm b/codegen/masm/src/lower/expected/utils_emit_binary_search_7_cases.masm new file mode 100644 index 000000000..cf4c7b406 --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_binary_search_7_cases.masm @@ -0,0 +1,75 @@ + + dup.0 + dup.0 + push.3 + u32lte + if.true + dup.0 + push.1 + u32lte + if.true + eq.0 + if.true + drop + drop + push.0 + else + drop + drop + push.1 + end + else + dup.0 + push.3 + u32lte + if.true + eq.2 + if.true + drop + drop + push.2 + else + drop + drop + push.3 + end + else + movup.2 + mul + u32assert + swap.1 + drop + end + end + else + dup.0 + push.5 + u32lte + if.true + eq.4 + if.true + drop + drop + push.4 + else + drop + drop + push.5 + end + else + dup.0 + eq.6 + if.true + drop + drop + drop + push.6 + else + movup.2 + mul + u32assert + swap.1 + drop + end + end + end diff --git a/codegen/masm/src/lower/expected/utils_emit_if.hir b/codegen/masm/src/lower/expected/utils_emit_if.hir new file mode 100644 index 000000000..bf547b88f --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_if.hir @@ -0,0 +1,15 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = arith.constant 0 : u32; + v3 = arith.eq v0, v1 : i1; + v4 = scf.if v3 : u32 { + ^block1: + v5 = arith.constant 1 : u32; + scf.yield v5; + } else { + ^block2: + v6 = arith.mul v0, v2 : u32 #[overflow = checked]; + scf.yield v6; + }; + builtin.ret v4; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_if.masm b/codegen/masm/src/lower/expected/utils_emit_if.masm new file mode 100644 index 000000000..ef3eb770c --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_if.masm @@ -0,0 +1,13 @@ + + push.0 + movup.2 + dup.2 + eq + if.true + drop + drop + push.1 + else + mul + u32assert + end diff --git a/codegen/masm/src/lower/expected/utils_emit_if_nested.hir b/codegen/masm/src/lower/expected/utils_emit_if_nested.hir new file mode 100644 index 000000000..12b4722bf --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_if_nested.hir @@ -0,0 +1,23 @@ +public builtin.function @test(v0: u32, v1: u32) -> u32 { +^block0(v0: u32, v1: u32): + v2 = arith.eq v0, v1 : i1; + v3 = scf.if v2 : u32 { + ^block1: + v4 = arith.constant 1 : u32; + scf.yield v4; + } else { + ^block2: + v5 = arith.lt v0, v1 : i1; + v6 = scf.if v5 : u32 { + ^block3: + v7 = arith.constant 2 : u32; + scf.yield v7; + } else { + ^block4: + v8 = arith.mul v0, v1 : u32 #[overflow = checked]; + scf.yield v8; + }; + scf.yield v6; + }; + builtin.ret v3; +}; \ No newline at end of file diff --git a/codegen/masm/src/lower/expected/utils_emit_if_nested.masm b/codegen/masm/src/lower/expected/utils_emit_if_nested.masm new file mode 100644 index 000000000..2efe84f5d --- /dev/null +++ b/codegen/masm/src/lower/expected/utils_emit_if_nested.masm @@ -0,0 +1,21 @@ + + dup.0 + dup.2 + eq + if.true + drop + drop + push.1 + else + dup.0 + dup.2 + u32lt + if.true + drop + drop + push.2 + else + mul + u32assert + end + end diff --git a/codegen/masm/src/lower/lowering.rs b/codegen/masm/src/lower/lowering.rs new file mode 100644 index 000000000..d9f5ade33 --- /dev/null +++ b/codegen/masm/src/lower/lowering.rs @@ -0,0 +1,1295 @@ +use midenc_dialect_arith as arith; +use midenc_dialect_cf as cf; +use midenc_dialect_hir as hir; +use midenc_dialect_scf as scf; +use midenc_dialect_ub as ub; +use midenc_hir::{ + dialects::builtin, + traits::{BinaryOp, Commutative}, + Op, OpExt, Span, SymbolTable, Value, ValueRange, ValueRef, +}; +use midenc_session::diagnostics::{Report, Severity, Spanned}; +use smallvec::{smallvec, SmallVec}; + +use super::*; +use crate::{emitter::BlockEmitter, masm, opt::operands::SolverOptions, Constraint}; + +/// This trait is registered with all ops, of all dialects, which are legal for lowering to MASM. +/// +/// The [BlockEmitter] is responsible for then invoking the methods of this trait to facilitate +/// the lowering to Miden Assembly of whole components. +pub trait HirLowering: Op { + /// This method is invoked once operands have been scheduled for this operation. + /// + /// Implementations are expected to: + /// + /// * Emit Miden Assembly that matches the semantics of the HIR instruction + /// * Ensure that the operand stack reflects any effects the operation has (both when consuming + /// its operands, and producing results). However, it is permitted to elide stack effects + /// that are transient during execution of the operation, so long as those effects are not + /// visible outside the instruction (i.e. it should not be possible for other instructions + /// to witness such transient effects). + /// * Ensure that the operand stack state is consistent at control flow join points, i.e. it + /// is not valid to allow the stack to diverge based on conditional control flow, in terms + /// of where each SSA value is found, and in terms of stack depth. + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report>; + + /// This method is invoked in order to emit any necessary operand stack manipulation sequences, + /// such that the instruction operands are in their place. + /// + /// By default, this uses our operand stack constraint solver to generate a solution for + /// moving the instruction operands into place in the order they are expected, using liveness + /// information to compute constraints. + /// + /// For operations that can support more efficient schedules due to their semantics, such as + /// the commutativity property, this method can be overridden to incorporate that information, + /// and provide a custom schedule. + fn schedule_operands(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let op = self.as_operation(); + + // Move instruction operands into place, minimizing unnecessary stack manipulation ops + // + // NOTE: This does not include block arguments for control flow instructions, those are + // handled separately within the specific handlers for those instructions + let args = self.required_operands(); + if args.is_empty() { + return Ok(()); + } + + let mut constraints = emitter.constraints_for(op, &args); + let mut args = args.into_smallvec(); + + // All of Miden's binary ops expect the right-hand operand on top of the stack, this + // requires us to invert the expected order of operands from the standard ordering in the + // IR + // + // TODO(pauls): We should probably assign a dedicated trait for this type of argument + // ordering override, rather than assuming that all BinaryOp impls need it + if op.implements::() { + args.swap(0, 1); + constraints.swap(0, 1); + } + + log::trace!(target: "codegen", "scheduling operands for {op}"); + for arg in args.iter() { + log::trace!(target: "codegen", "{arg} is live at/after entry: {}", emitter.liveness.is_live_after_entry(*arg, op)); + } + log::trace!(target: "codegen", "starting with stack: {:#?}", &emitter.stack); + emitter + .schedule_operands( + &args, + &constraints, + op.span(), + SolverOptions { + strict: !op.implements::(), + ..Default::default() + }, + ) + .unwrap_or_else(|err| { + panic!( + "failed to schedule operands: {args:?}\nfor inst '{}'\nwith error: \ + {err:?}\nconstraints: {constraints:?}\nstack: {:#?}", + op.name(), + &emitter.stack, + ) + }); + log::trace!(target: "codegen", "stack after scheduling: {:#?}", &emitter.stack); + + Ok(()) + } + + /// Returns the set of operands that must be scheduled on entry to this operation. + /// + /// Typically, this excludes successor operands, but it depends on the specific operation. In + /// order to abstract over these details, this function can be used to customize just this + /// detail of operand scheduling. + fn required_operands(&self) -> ValueRange<'_, 4> { + ValueRange::from(self.operands().all()) + } +} + +impl HirLowering for builtin::Ret { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let span = self.span(); + let argc = self.num_operands(); + + // Upon return, the operand stack should only contain the function result(s), + // so empty the stack before proceeding. + emitter.emitter().truncate_stack(argc, span); + + Ok(()) + } +} + +impl HirLowering for builtin::RetImm { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let span = self.span(); + let mut emitter = emitter.emitter(); + + // Upon return, the operand stack should only contain the function result(s), + // so empty the stack before proceeding. + emitter.truncate_stack(0, span); + + // We need to push the return value on the stack at this point. + emitter.literal(*self.value(), span); + + Ok(()) + } +} + +impl HirLowering for scf::If { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let cond = self.condition().as_value_ref(); + + // Ensure `cond` is on top of the stack, and remove it at the same time + assert_eq!( + emitter.stack.pop().unwrap().as_value(), + Some(cond), + "expected {cond} on top of the stack" + ); + + let then_body = self.then_body(); + let else_body = self.else_body(); + + utils::emit_if(emitter, self.as_operation(), &then_body, &else_body) + } +} + +impl HirLowering for scf::While { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let span = self.span(); + + // Emit as follows: + // + // hir.while { + // + // } do { + // + // } + // + // to: + // + // push.1 + // while.true + // + // if.true + // + // push.1 + // else + // push.0 + // end + // end + let num_condition_forwarded_operands = self.condition_op().borrow().forwarded().len(); + let (stack_on_loop_exit, loop_body) = { + let before = self.before(); + let before_block = before.entry(); + let input_stack = emitter.stack.clone(); + + let mut body_emitter = emitter.nest(); + + // Rename the 'hir.while' operands to match the 'before' region's entry block args + assert_eq!(self.operands().len(), before_block.num_arguments()); + for (index, arg) in before_block.arguments().iter().copied().enumerate() { + body_emitter.stack.rename(index, arg as ValueRef); + } + let before_stack = body_emitter.stack.clone(); + + // Emit the 'before' block, which represents the loop header + body_emitter.emit_inline(&before_block); + + // Remove the 'hir.condition' condition flag from the operand stack, but do not emit any + // instructions to do so, as this will be handled by the 'if.true' instruction + body_emitter.stack.drop(); + + // Take a snapshot of the stack at this point, as it represents the state of the stack + // on exit from the loop, and perform the following modifications: + // + // 1. Rename the forwarded condition operands to the 'hir.while' results + // 2. Check that all values on the operand stack at this point have definitions which + // dominate the successor (i.e. the next op after the 'hir.while' op). We can do this + // cheaply by asserting that all of the operands were present on the stack before the + // 'hir.while', or are a result, as any new operands are by definition something + // introduced within the loop itself + let mut stack_on_loop_exit = body_emitter.stack.clone(); + // 1 + assert_eq!(num_condition_forwarded_operands, self.num_results()); + for (index, result) in self.results().all().iter().copied().enumerate() { + stack_on_loop_exit.rename(index, result as ValueRef); + } + // 2 + for (index, value) in stack_on_loop_exit.iter().rev().enumerate() { + let value = value.as_value().unwrap(); + let is_result = self.results().all().iter().any(|r| *r as ValueRef == value); + let is_dominating_def = input_stack.find(&value).is_some(); + assert!( + is_result || is_dominating_def, + "{value} at stack depth {index} incorrectly escapes its dominance frontier" + ); + } + + let enter_loop_body = { + let mut body_emitter = body_emitter.nest(); + + // Rename the `hir.condition` forwarded operands to match the 'after' region's entry block args + let after = self.after(); + let after_block = after.entry(); + assert_eq!(num_condition_forwarded_operands, after_block.num_arguments()); + for (index, arg) in after_block.arguments().iter().copied().enumerate() { + body_emitter.stack.rename(index, arg as ValueRef); + } + + // Emit the "after" block + body_emitter.emit_inline(&after_block); + + // At this point, control yields from "after" back to "before" to re-evaluate the loop + // condition. We must ensure that the yielded operands are renamed just as before, then + // push a `push.1` on the stack to re-enter the loop to retry the condition + assert_eq!(self.yield_op().borrow().yielded().len(), before_block.num_arguments()); + for (index, arg) in before_block.arguments().iter().copied().enumerate() { + body_emitter.stack.rename(index, arg as ValueRef); + } + + if before_stack != body_emitter.stack { + panic!( + "unexpected observable stack effect leaked from regions of {} + +stack on entry to 'before': {before_stack:#?} +stack on exit from 'after': {:#?} + ", + self.as_operation(), + &body_emitter.stack + ); + } + + // Re-enter the "before" block to retry the condition + body_emitter.emitter().push_immediate(true.into(), span); + + body_emitter.into_emitted_block(span) + }; + + let exit_loop_body = { + let mut body_emitter = body_emitter.nest(); + + // Exit the loop + body_emitter.emitter().push_immediate(false.into(), span); + + body_emitter.into_emitted_block(span) + }; + + body_emitter.emit_op(masm::Op::If { + span, + then_blk: enter_loop_body, + else_blk: exit_loop_body, + }); + + (stack_on_loop_exit, body_emitter.into_emitted_block(span)) + }; + + emitter.stack = stack_on_loop_exit; + + // Always enter loop on first iteration + emitter.emit_op(masm::Op::Inst(Span::new(span, masm::Instruction::PushU8(1)))); + emitter.emit_op(masm::Op::While { + span, + body: loop_body, + }); + + Ok(()) + } + + fn required_operands(&self) -> ValueRange<'_, 4> { + ValueRange::from(self.inits()) + } +} + +impl HirLowering for scf::IndexSwitch { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + // Lowering 'hir.index_switch' is done by lowering to a sequence of if/else ops, comparing + // the selector against each non-default case to determine whether control should enter + // that block. The final else contains the default case. + let mut cases = self.cases().iter().copied().collect::>(); + cases.sort(); + + // We have N cases, plus a default case + // + // 1. If we have exactly 1 non-default case, we can lower to an `hir.if` + // 2. If we have N non-default non-contiguous (or N < 3 contiguous) cases, lower to: + // + // if selector == case1 { + // + // } else { + // if selector == case2 { + // + // } else { + // if selector == caseN { + // + // } else { + // + // } + // } + // } + // + // if selector < case3 { + // if selector == case1 { + // + // } else { + // + // } + // } else { + // if selector < case4 { + // + // } else { + // if selector == case4 { + // + // } else { + // + // } + // } + // } + // + // 3. If we have N non-default contiguous cases, use binary search to reduce search space: + // + // if selector < case3 { + // if selector == case1 { + // + // } else { + // + // } + // } else { + // if selector < case4 { + // + // } else { + // if selector == case4 { + // + // } else { + // + // } + // } + // } + // + // We do not try to use the binary search approach with non-contiguous cases, as we would + // be forced to emit duplicate copies of the fallback branch, and it isn't clear the size + // tradeoff would be worth it without branch hints. + + assert!(!cases.is_empty()); + if cases.len() == 1 { + return utils::emit_binary_search(self, emitter, &[], &cases, 0, 1); + } + + // Emit binary-search-optimized 'hir.if' sequence + // + // Partition such that the condition for the `then` branch guarantees that no fallback + // branch is needed, i.e. an even number of cases must be in the first partition + let num_cases = cases.len(); + let midpoint = cases[0].midpoint(cases[cases.len() - 1]); + let partition_point = core::cmp::min( + cases.len(), + cases.partition_point(|item| *item < midpoint).next_multiple_of(2), + ); + let (a, b) = cases.split_at(partition_point); + utils::emit_binary_search(self, emitter, a, b, midpoint, num_cases) + } + + fn required_operands(&self) -> ValueRange<'_, 4> { + ValueRange::from(self.operands().group(0)) + } +} + +impl HirLowering for scf::Yield { + fn emit(&self, _emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + // Lowering 'hir.yield' is a no-op, as it is simply forwarding operands to another region, + // and the semantics of that are handled by the lowering of the containing op + log::trace!(target: "codegen", "yielding {:#?}", &_emitter.stack); + Ok(()) + } +} + +impl HirLowering for scf::Condition { + fn emit(&self, _emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + // Lowering 'hir.condition' is a no-op, as it is simply forwarding operands to another + // region, and the semantics of that are handled by the lowering of the containing op + log::trace!(target: "codegen", "conditionally yielding {:#?}", &_emitter.stack); + Ok(()) + } +} + +impl HirLowering for arith::Constant { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let value = *self.value(); + + emitter.inst_emitter(self.as_operation()).literal(value, self.span()); + + Ok(()) + } +} + +impl HirLowering for hir::Assert { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let code = *self.code(); + + emitter.emitter().assert(Some(code), self.span()); + + Ok(()) + } +} + +impl HirLowering for hir::Assertz { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let code = *self.code(); + + emitter.emitter().assertz(Some(code), self.span()); + + Ok(()) + } +} + +impl HirLowering for hir::AssertEq { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.emitter().assert_eq(self.span()); + + Ok(()) + } +} + +impl HirLowering for hir::Breakpoint { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.emit_op(masm::Op::Inst(Span::new(self.span(), masm::Instruction::Breakpoint))); + + Ok(()) + } +} + +impl HirLowering for ub::Unreachable { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + // This instruction, if reached, must cause the VM to trap, so we emit an assertion that + // always fails to guarantee this, i.e. assert(false) + let span = self.span(); + let mut op_emitter = emitter.emitter(); + op_emitter.emit(masm::Instruction::PushU32(0), span); + op_emitter.emit(masm::Instruction::Assert, span); + + Ok(()) + } +} + +impl HirLowering for ub::Poison { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + use midenc_hir::Type; + + // This instruction represents a value that results from undefined behavior in a program. + // The presence of it does not indicate that a program is invalid, but rather, the fact that + // undefined behavior resulting from control flow to unreachable code produces effectively + // any value in the domain of the type associated with the poison result. + // + // For our purposes, we choose a value that will appear obvious in a debugger, should it + // ever appear as an operand to an instruction; and a value that we could emit debug asserts + // for should we ever wish to do so. We could also catch the evaluation of poison under an + // emulator for the IR itself. + let span = self.span(); + let mut op_emitter = emitter.inst_emitter(self.as_operation()); + op_emitter.literal( + { + match self.value().as_immediate() { + Ok(imm) => imm, + Err(Type::U256) => { + return Err(self + .as_operation() + .context() + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid operation") + .with_primary_label( + span, + "the lowering for u256 immediates is not yet implemented", + ) + .into_report()); + } + Err(Type::F64) => { + return Err(self + .as_operation() + .context() + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid operation") + .with_primary_label( + span, + "the lowering for f64 immediates is not yet implemented", + ) + .into_report()); + } + Err(ty) => panic!("unexpected poison type: {ty}"), + } + }, + span, + ); + + Ok(()) + } +} + +impl HirLowering for arith::Add { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).add(*self.overflow(), self.span()); + Ok(()) + } +} + +impl HirLowering for arith::AddOverflowing { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter + .inst_emitter(self.as_operation()) + .add(midenc_hir::Overflow::Overflowing, self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Sub { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).sub(*self.overflow(), self.span()); + Ok(()) + } +} + +impl HirLowering for arith::SubOverflowing { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter + .inst_emitter(self.as_operation()) + .sub(midenc_hir::Overflow::Overflowing, self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Mul { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).mul(*self.overflow(), self.span()); + Ok(()) + } +} + +impl HirLowering for arith::MulOverflowing { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter + .inst_emitter(self.as_operation()) + .mul(midenc_hir::Overflow::Overflowing, self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Exp { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).exp(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Div { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).checked_div(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Sdiv { + fn emit(&self, _emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + todo!("signed division lowering not implemented yet"); + } +} + +impl HirLowering for arith::Mod { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).checked_mod(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Smod { + fn emit(&self, _emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + todo!("signed modular division lowering not implemented yet"); + } +} + +impl HirLowering for arith::Divmod { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).checked_divmod(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Sdivmod { + fn emit(&self, _emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + todo!("signed division + modular division lowering not implemented yet"); + } +} + +impl HirLowering for arith::And { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).and(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Or { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).or(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Xor { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).xor(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Band { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).band(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Bor { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).bor(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Bxor { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).bxor(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Shl { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).shl(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Shr { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).shr(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Ashr { + fn emit(&self, _emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + todo!("arithmetic shift right not yet implemented"); + } +} + +impl HirLowering for arith::Rotl { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).rotl(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Rotr { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).rotr(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Eq { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).eq(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Neq { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).neq(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Gt { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).gt(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Gte { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).gte(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Lt { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).lt(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Lte { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).lte(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Min { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).min(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Max { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).max(self.span()); + Ok(()) + } +} + +impl HirLowering for hir::PtrToInt { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result_ty = self.result().ty().clone(); + let mut inst_emitter = emitter.inst_emitter(self.as_operation()); + inst_emitter.pop().expect("operand stack is empty"); + inst_emitter.push(result_ty); + Ok(()) + } +} + +impl HirLowering for hir::IntToPtr { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result = self.result(); + emitter.inst_emitter(self.as_operation()).inttoptr(result.ty(), self.span()); + Ok(()) + } +} + +impl HirLowering for hir::Cast { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result = self.result(); + emitter.inst_emitter(self.as_operation()).cast(result.ty(), self.span()); + Ok(()) + } +} + +impl HirLowering for hir::Bitcast { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result = self.result(); + emitter.inst_emitter(self.as_operation()).bitcast(result.ty(), self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Trunc { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result = self.result(); + emitter.inst_emitter(self.as_operation()).trunc(result.ty(), self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Zext { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result = self.result(); + emitter.inst_emitter(self.as_operation()).zext(result.ty(), self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Sext { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result = self.result(); + emitter.inst_emitter(self.as_operation()).sext(result.ty(), self.span()); + Ok(()) + } +} + +impl HirLowering for hir::Exec { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + use midenc_hir::{CallOpInterface, CallableOpInterface}; + + let callee = self.resolve().ok_or_else(|| { + let context = self.as_operation().context(); + context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid call operation: unable to resolve callee") + .with_primary_label( + self.span(), + "this symbol path is not resolvable from this operation", + ) + .with_help( + "Make sure that all referenced symbols are reachable via the root symbol \ + table, and use absolute paths to refer to symbols in ancestor/sibling modules", + ) + .into_report() + })?; + let callee = callee.borrow(); + let callee_path = callee.path(); + let signature = match callee.as_symbol_operation().as_trait::() { + Some(callable) => callable.signature(), + None => { + let context = self.as_operation().context(); + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid call operation: callee is not a callable op") + .with_primary_label( + self.span(), + format!( + "this symbol resolved to a '{}' op, which does not implement Callable", + callee.as_symbol_operation().name() + ), + ) + .into_report()); + } + }; + + // Convert the path components to an absolute procedure path + let mut path = callee_path.to_library_path(); + let name = masm::ProcedureName::from_raw_parts( + path.pop().expect("expected at least two path components"), + ); + let callee = masm::InvocationTarget::AbsoluteProcedurePath { name, path }; + + emitter.inst_emitter(self.as_operation()).exec(callee, signature, self.span()); + + Ok(()) + } +} + +impl HirLowering for hir::Call { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + use midenc_hir::{CallOpInterface, CallableOpInterface}; + + let callee = self.resolve().ok_or_else(|| { + let context = self.as_operation().context(); + context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid call operation: unable to resolve callee") + .with_primary_label( + self.span(), + "this symbol path is not resolvable from this operation", + ) + .with_help( + "Make sure that all referenced symbols are reachable via the root symbol \ + table, and use absolute paths to refer to symbols in ancestor/sibling modules", + ) + .into_report() + })?; + let callee = callee.borrow(); + let callee_path = callee.path(); + let signature = match callee.as_symbol_operation().as_trait::() { + Some(callable) => callable.signature(), + None => { + let context = self.as_operation().context(); + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid call operation: callee is not a callable op") + .with_primary_label( + self.span(), + format!( + "this symbol resolved to a '{}' op, which does not implement Callable", + callee.as_symbol_operation().name() + ), + ) + .into_report()); + } + }; + + // Convert the path components to an absolute procedure path + let mut path = callee_path.to_library_path(); + let name = masm::ProcedureName::from_raw_parts( + path.pop().expect("expected at least two path components"), + ); + let callee = masm::InvocationTarget::AbsoluteProcedurePath { name, path }; + + emitter.inst_emitter(self.as_operation()).call(callee, signature, self.span()); + + Ok(()) + } +} + +impl HirLowering for hir::Load { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let result = self.result(); + emitter.inst_emitter(self.as_operation()).load(result.ty().clone(), self.span()); + Ok(()) + } +} + +impl HirLowering for hir::LoadLocal { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).load_local(self.local(), self.span()); + Ok(()) + } +} + +impl HirLowering for hir::Store { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.emitter().store(self.span()); + Ok(()) + } +} + +impl HirLowering for hir::StoreLocal { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.emitter().store_local(self.local(), self.span()); + Ok(()) + } +} + +impl HirLowering for hir::MemGrow { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).mem_grow(self.span()); + Ok(()) + } +} + +impl HirLowering for hir::MemSize { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).mem_size(self.span()); + Ok(()) + } +} + +impl HirLowering for hir::MemSet { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).memset(self.span()); + Ok(()) + } +} + +impl HirLowering for hir::MemCpy { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).memcpy(self.span()); + Ok(()) + } +} + +impl HirLowering for cf::Select { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).select(self.span()); + Ok(()) + } +} + +impl HirLowering for cf::CondBr { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let then_dest = self.then_dest().successor(); + let else_dest = self.else_dest().successor(); + + // This lowering is only legal if it represents a choice between multiple exits + assert_eq!( + then_dest.borrow().num_successors(), + 0, + "illegal cf.cond_br: only exit blocks are supported" + ); + assert_eq!( + else_dest.borrow().num_successors(), + 0, + "illegal cf.cond_br: only exit blocks are supported" + ); + + // Drop the condition if no longer live + if !emitter + .liveness + .is_live_after(self.condition().as_value_ref(), self.as_operation()) + { + emitter.stack.drop(); + } + + let span = self.span(); + let then_blk = { + let mut emitter = emitter.nest(); + + // At this point is when we need to schedule the successor operands for this block + let then_operand = self.then_dest(); + let successor_operands = ValueRange::from(then_operand.arguments); + let constraints = emitter.constraints_for(self.as_operation(), &successor_operands); + let successor_operands = successor_operands.into_smallvec(); + emitter + .schedule_operands(&successor_operands, &constraints, span, Default::default()) + .unwrap_or_else(|err| { + panic!( + "failed to schedule operands: {successor_operands:?}\nfor inst '{}'\nwith \ + error: {err:?}\nconstraints: {constraints:?}\nstack: {:#?}", + self.as_operation().name(), + &emitter.stack, + ) + }); + + // Rename any uses of the block arguments of `then_dest` to the values given as + // successor operands. + let then_block = then_dest.borrow(); + for (index, block_argument) in then_block.arguments().iter().copied().enumerate() { + emitter.stack.rename(index, block_argument as ValueRef); + } + + emitter.emit(&then_dest.borrow()) + }; + + let else_blk = { + let mut emitter = emitter.nest(); + + // At this point is when we need to schedule the successor operands for this block + let else_operand = self.else_dest(); + let successor_operands = ValueRange::from(else_operand.arguments); + let constraints = emitter.constraints_for(self.as_operation(), &successor_operands); + let successor_operands = successor_operands.into_smallvec(); + emitter + .schedule_operands(&successor_operands, &constraints, span, Default::default()) + .unwrap_or_else(|err| { + panic!( + "failed to schedule operands: {successor_operands:?}\nfor inst '{}'\nwith \ + error: {err:?}\nconstraints: {constraints:?}\nstack: {:#?}", + self.as_operation().name(), + &emitter.stack, + ) + }); + + // Rename any uses of the block arguments of `else_dest` to the values given as + // successor operands. + let else_block = else_dest.borrow(); + for (index, block_argument) in else_block.arguments().iter().copied().enumerate() { + emitter.stack.rename(index, block_argument as ValueRef); + } + + emitter.emit(&else_dest.borrow()) + }; + + let span = self.span(); + emitter.emit_op(masm::Op::If { + span, + then_blk, + else_blk, + }); + + Ok(()) + } + + fn required_operands(&self) -> ValueRange<'_, 4> { + ValueRange::from(smallvec![self.condition().as_value_ref()]) + } + + // We only schedule the condition here + fn schedule_operands(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let op = self.as_operation(); + + let condition = self.condition().as_value_ref(); + let constraints = emitter.constraints_for(op, &ValueRange::Borrowed(&[condition])); + + let span = op.span(); + let top = emitter.stack[0].as_value(); + if top == Some(condition) { + if matches!(constraints[0], Constraint::Copy) { + emitter.emitter().dup(0, span); + } + return Ok(()); + } else { + let index = emitter.stack.find(&condition).unwrap() as u8; + match constraints[0] { + Constraint::Copy => { + emitter.emitter().dup(index, span); + } + Constraint::Move => { + if index == 1 { + emitter.emitter().swap(1, span); + } else { + emitter.emitter().movup(index, span); + } + } + } + } + + Ok(()) + } +} + +impl HirLowering for arith::Incr { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).incr(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Neg { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).neg(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Inv { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).inv(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Ilog2 { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).ilog2(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Pow2 { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).pow2(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Not { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).not(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Bnot { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).bnot(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::IsOdd { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).is_odd(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Popcnt { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).popcnt(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Clz { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).clz(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Ctz { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).ctz(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Clo { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).clo(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Cto { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + emitter.inst_emitter(self.as_operation()).cto(self.span()); + Ok(()) + } +} + +impl HirLowering for arith::Join { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let mut inst_emitter = emitter.inst_emitter(self.as_operation()); + inst_emitter.pop().expect("operand stack is empty"); + inst_emitter.pop().expect("operand stack is empty"); + inst_emitter.push(self.result().as_value_ref()); + Ok(()) + } +} + +impl HirLowering for arith::Split { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let mut inst_emitter = emitter.inst_emitter(self.as_operation()); + inst_emitter.pop().expect("operand stack is empty"); + inst_emitter.push(self.result_low().as_value_ref()); + inst_emitter.push(self.result_high().as_value_ref()); + Ok(()) + } +} + +impl HirLowering for builtin::GlobalSymbol { + fn emit(&self, emitter: &mut BlockEmitter<'_>) -> Result<(), Report> { + let context = self.as_operation().context(); + + // 1. Resolve symbol to computed address in global layout + let current_module = self + .nearest_parent_op::() + .expect("expected 'hir.global_symbol' op to have a module ancestor"); + let symbol = current_module.borrow().resolve(&self.symbol().path).ok_or_else(|| { + context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid symbol reference") + .with_primary_label( + self.span(), + "unable to resolve this symbol in the current module", + ) + .into_report() + })?; + + let global_variable = symbol + .borrow() + .downcast_ref::() + .map(|gv| unsafe { builtin::GlobalVariableRef::from_raw(gv) }) + .ok_or_else(|| { + context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid symbol reference") + .with_primary_label( + self.span(), + format!( + "this symbol resolves to a '{}', but a 'hir.global_variable' was \ + expected", + symbol.borrow().as_symbol_operation().name() + ), + ) + .into_report() + })?; + + let computed_addr = emitter + .link_info + .globals_layout() + .get_computed_addr(global_variable) + .expect("link error: missing global variable in computed global layout"); + let addr = computed_addr.checked_add_signed(*self.offset()).ok_or_else(|| { + context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid global symbol offset") + .with_primary_label( + self.span(), + "the specified offset is invalid for the referenced symbol", + ) + .with_help( + "the offset is invalid because the computed address under/overflows the \ + address space", + ) + .into_report() + })?; + + // 2. Push computed address on the stack as the result + emitter.inst_emitter(self.as_operation()).literal(addr, self.span()); + + Ok(()) + } +} diff --git a/codegen/masm/src/lower/native_ptr.rs b/codegen/masm/src/lower/native_ptr.rs new file mode 100644 index 000000000..3292a7121 --- /dev/null +++ b/codegen/masm/src/lower/native_ptr.rs @@ -0,0 +1,81 @@ +use serde::{Deserialize, Serialize}; + +/// This represents a descriptor for a pointer translated from the IR into a form suitable for +/// referencing data in Miden's linear memory. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct NativePtr { + /// This is the address of the element containing the first byte of data + /// + /// Each element is assumed to be a 32-bit value/chunk + pub addr: u32, + /// This is the byte offset into the 32-bit chunk referenced by `index` + /// + /// This offset is where the data referenced by the pointer actually starts. + pub offset: u8, + /// This is the assumed address space of the pointer value. + /// + /// This address space is unknown by default, but can be specified if known statically. + /// The address space determines whether the pointer is valid in certain contexts. For + /// example, attempting to load a pointer with address space 0 is invalid if not operating + /// in the root context. + /// + /// Currently this has no effect, but is here as we expand support for multiple memories. + pub addrspace: midenc_hir::AddressSpace, +} +impl NativePtr { + pub fn new(addr: u32, offset: u8) -> Self { + Self { + addr, + offset, + addrspace: Default::default(), + } + } + + /// Translates a raw pointer (assumed to be in a byte-addressable address space) to a native + /// pointer value, in the default [hir::AddressSpace]. + pub fn from_ptr(addr: u32) -> Self { + // The native word address for `addr` is derived by splitting the byte-addressable space + // into 32-bit chunks, each chunk belonging to a single field element, i.e. each element + // of the native address space represents 32 bits of byte-addressable memory. + // + // By dividing `addr` by 4, we get the element address where the data starts. + let eaddr = addr / 4; + // If our address is not element-aligned, we need to determine what byte offset contains + // the first byte of the data. + let offset = (addr % 4) as u8; + Self { + addr: eaddr, + offset, + addrspace: Default::default(), + } + } + + /// Returns true if this pointer is aligned to a word boundary + pub const fn is_word_aligned(&self) -> bool { + self.offset == 0 && self.addr.is_multiple_of(4) + } + + /// Returns true if this pointer is aligned to a field element boundary + pub const fn is_element_aligned(&self) -> bool { + self.offset == 0 + } + + /// Returns true if this pointer is not element aligned + pub const fn is_unaligned(&self) -> bool { + self.offset > 0 + } + + /// Returns the byte alignment implied by this pointer value. + /// + /// For example, a pointer to the first word in linear memory, i.e. address 1, with an offset + /// of 2, is equivalent to an address in byte-addressable memory of 6, which has an implied + /// alignment of 2 bytes. + pub const fn alignment(&self) -> u32 { + 2u32.pow(self.as_ptr().trailing_zeros()) + } + + /// Converts this native pointer back to a byte-addressable pointer value + pub const fn as_ptr(&self) -> u32 { + (self.addr * 4) + self.offset as u32 + } +} diff --git a/codegen/masm/src/lower/utils.rs b/codegen/masm/src/lower/utils.rs new file mode 100644 index 000000000..6cd932b4c --- /dev/null +++ b/codegen/masm/src/lower/utils.rs @@ -0,0 +1,900 @@ +use midenc_dialect_scf as scf; +use midenc_hir::{Op, Operation, Region, Report, Spanned, ValueRef}; +use smallvec::SmallVec; + +use crate::{emitter::BlockEmitter, masm, Constraint}; + +/// Emit a conditonal branch-like region, e.g. `scf.if`. +/// +/// This assumes that the conditional value on top of the stack has been removed from the emitter's +/// view of the stack, but has not yet been consumed by the caller. +pub fn emit_if( + emitter: &mut BlockEmitter<'_>, + op: &Operation, + then_body: &Region, + else_body: &Region, +) -> Result<(), Report> { + let span = op.span(); + let then_dest = then_body.entry(); + let else_dest = else_body.entry_block_ref(); + + let (then_stack, then_blk) = { + let mut then_emitter = emitter.nest(); + then_emitter.emit_inline(&then_dest); + // Rename the yielded values on the stack for us to check against + let mut then_stack = then_emitter.stack.clone(); + for (index, result) in op.results().all().into_iter().enumerate() { + then_stack.rename(index, *result as ValueRef); + } + let then_block = then_emitter.into_emitted_block(then_dest.span()); + (then_stack, then_block) + }; + + let else_blk = match else_dest { + None => { + assert!( + op.results().is_empty(), + "an elided 'hir.if' else block requires the '{}' to have no results", + op.name() + ); + + masm::Block::new(span, Default::default()) + } + Some(dest) => { + let dest = dest.borrow(); + let mut else_emitter = emitter.nest(); + else_emitter.emit_inline(&dest); + + // Rename the yielded values on the stack for us to check against + let mut else_stack = else_emitter.stack.clone(); + for (index, result) in op.results().all().into_iter().enumerate() { + else_stack.rename(index, *result as ValueRef); + } + + // Schedule realignment of the stack if needed + if then_stack != else_stack { + schedule_stack_realignment(&then_stack, &else_stack, &mut else_emitter); + } + + if cfg!(debug_assertions) { + let mut else_stack = else_emitter.stack.clone(); + for (index, result) in op.results().all().into_iter().enumerate() { + else_stack.rename(index, *result as ValueRef); + } + if then_stack != else_stack { + panic!( + "unexpected observable stack effect leaked from regions of {op} + +stack on exit from 'then': {then_stack:#?} +stack on exit from 'else': {else_stack:#?} +", + ); + } + } + + else_emitter.into_emitted_block(dest.span()) + } + }; + + emitter.emit_op(masm::Op::If { + span, + then_blk, + else_blk, + }); + + emitter.stack = then_stack; + + Ok(()) +} + +/// Emit a sequence of nested branches that perform a binary search for a case which matches some +/// selector value on top of the operand stack. +/// +/// For now, this has the following requirements: +/// +/// * The selector value is on top of the stack when called, and the emitter is still aware of it +/// * The cases which have been partitioned into `a` and `b` are contiguous, e.g. `[1, 2]` and +/// `[3, 4]`. +/// * If `a` is empty, then `b` is processed such that the fallback case will be emitted once only +/// a single case in `b` remains (as a branch between that case and the fallback case). If there +/// are two cases, then the search will partition them into the "then" branch, with the fallback +/// in the "else" branch. +/// * If `a` is non-empty, and `b` is empty, then `num_cases` dictates whether we handle `a` as +/// described in the previous bullet point, i.e. if the total number of cases is 2, and `a` has +/// two cases, then we will partition them into the "then" branch, and emit the fallback in the +/// "else" branch. Otherwise, if the number of cases is greater than 2, and `a` has <= 2 cases, +/// then they will be emitted into the "then" branch without emitting the fallback case. +/// Otherwise, `a` will be partitioned such that we can recursively call this function and rely +/// on only emitting the fallback case once, in the final "else" branch. +/// +/// # Parameters +/// +/// * `midpoint` is the case value (real or otherwise) representing the approximate middle of the +/// range of cases. It is used to derive the actual partition point that produced `a` and `b` +/// * `a` is the set of cases which are < the partition point derived from `midpoint` +/// * `b` is the set of cases which are >= the partition point derived from `midpoint` +/// * `num_cases` is the total number of cases that were partitioned into `a` and `b`. This is used +/// to inform us whether or not there are additional cases to be emitted, or if we should emit +/// the fallback case once the search is exhausted. +pub fn emit_binary_search( + op: &scf::IndexSwitch, + emitter: &mut BlockEmitter<'_>, + a: &[u32], + b: &[u32], + midpoint: u32, + num_cases: usize, +) -> Result<(), Report> { + let span = op.span(); + let selector = op.selector().as_value_ref(); + + match a { + [] => { + match b { + [then_case] => { + // There is only a single case to emit, so we can emit an 'hir.if' with fallback + // + // Emit `selector == then_case` + // + // NOTE: We duplicate the selector if it is live in either the case region or + // the fallback region + let then_index = op.get_case_index_for_selector(*then_case).unwrap(); + let then_body = op.get_case_region(then_index); + let else_body = op.default_region(); + let is_live_after = emitter + .liveness + .is_live_at_start(selector, then_body.borrow().entry_block_ref().unwrap()) + || emitter + .liveness + .is_live_at_start(selector, else_body.entry_block_ref().unwrap()); + if is_live_after { + emitter.emitter().dup(0, span); + } + emitter.emitter().eq_imm(b[0].into(), span); + + // Remove the condition for the if from the emitter's view of the stack + emitter.stack.drop(); + + // Emit as 'hir.if' + emit_if(emitter, op.as_operation(), &then_body.borrow(), &else_body) + } + [_then_case, else_case] => { + // This is similar to the case of a = [_, _], b is non-empty + // + // We must emit an `hir.if` for then/else cases in the first branch, and place + // the fallback in the second branch. + // + // Emit `selector <= else_case ? (selector == then_case : then_case ? else_case) ? fallback` + { + let mut emitter = emitter.emitter(); + emitter.dup(0, span); + emitter.lte_imm((*else_case).into(), span); + } + + // Remove the condition for the branch selection from the emitter's view of the + // stack + emitter.stack.drop(); + + let (then_blk, then_stack) = { + let mut then_emitter = emitter.nest(); + emit_binary_search(op, &mut then_emitter, b, &[], midpoint, usize::MAX)?; + let then_stack = then_emitter.stack.clone(); + (then_emitter.into_emitted_block(span), then_stack) + }; + + let (else_blk, else_stack) = { + let default_region = op.default_region(); + let is_live_after = emitter + .liveness + .is_live_at_start(selector, default_region.entry_block_ref().unwrap()); + let mut else_emitter = emitter.nest(); + if !is_live_after { + // Consume the original selector + else_emitter.emitter().drop(span); + } + else_emitter.emit_inline(&default_region.entry()); + // Rename the yielded values on the stack for us to check against + let mut else_stack = else_emitter.stack.clone(); + for (index, result) in op.results().all().into_iter().enumerate() { + else_stack.rename(index, *result as ValueRef); + } + (else_emitter.into_emitted_block(span), else_stack) + }; + + if then_stack != else_stack { + panic!( + "unexpected observable stack effect leaked from regions of {} + +stack on exit from 'then': {then_stack:#?} +stack on exit from 'else': {else_stack:#?} + ", + op.as_operation() + ); + } + + emitter.emit_op(masm::Op::If { + span, + then_blk, + else_blk, + }); + + emitter.stack = then_stack; + + Ok(()) + } + _ => panic!( + "unexpected partitioning of switch cases: a = empty, b = {b:#?}, midpoint = \ + {midpoint}" + ), + } + } + [_then_case, else_case] if b.is_empty() && num_cases == 2 => { + // There were exactly two cases and we are handling them here, but we must also emit + // a fallback branch in the case where an out of range selector value is given + // + // We must emit an `hir.if` for then/else cases in the first branch, and place + // the fallback in the second branch. + // + // Emit `selector <= else_case ? (selector == then_case : then_case ? else_case) ? fallback` + { + let mut emitter = emitter.emitter(); + emitter.dup(0, span); + emitter.lte_imm((*else_case).into(), span); + } + + // Remove the condition for the branch selection from the emitter's view of the + // stack + emitter.stack.drop(); + + let (then_blk, then_stack) = { + let mut then_emitter = emitter.nest(); + emit_binary_search(op, &mut then_emitter, a, &[], midpoint, usize::MAX)?; + let then_stack = then_emitter.stack.clone(); + (then_emitter.into_emitted_block(span), then_stack) + }; + + let (else_blk, else_stack) = { + let default_region = op.default_region(); + let is_live_after = emitter + .liveness + .is_live_at_start(selector, default_region.entry_block_ref().unwrap()); + let mut else_emitter = emitter.nest(); + if !is_live_after { + // Consume the original selector + else_emitter.emitter().drop(span); + } + else_emitter.emit_inline(&default_region.entry()); + // Rename the yielded values on the stack for us to check against + let mut else_stack = else_emitter.stack.clone(); + for (index, result) in op.results().all().into_iter().enumerate() { + else_stack.rename(index, *result as ValueRef); + } + (else_emitter.into_emitted_block(span), else_stack) + }; + + if then_stack != else_stack { + panic!( + "unexpected observable stack effect leaked from regions of {} + + stack on exit from 'then': {then_stack:#?} + stack on exit from 'else': {else_stack:#?} + ", + op.as_operation() + ); + } + + emitter.emit_op(masm::Op::If { + span, + then_blk, + else_blk, + }); + + emitter.stack = then_stack; + + Ok(()) + } + [then_case, else_case] if b.is_empty() && num_cases > 2 => { + // We can emit 'a' as an 'hir.if' with no fallback, as this is a subset of the total + // cases and we were given enough to populate a single `hir.if`. + // + // Emit `selector == then_case` + let then_index = op.get_case_index_for_selector(*then_case).unwrap(); + let then_body = op.get_case_region(then_index); + let else_index = op.get_case_index_for_selector(*else_case).unwrap(); + let else_body = op.get_case_region(else_index); + let is_live_after = emitter + .liveness + .is_live_at_start(selector, then_body.borrow().entry_block_ref().unwrap()) + || emitter + .liveness + .is_live_at_start(selector, else_body.borrow().entry_block_ref().unwrap()); + if is_live_after { + emitter.emitter().dup(0, span); + } + emitter.emitter().eq_imm((*then_case).into(), span); + + // Remove the selector from the emitter's view of the stack + emitter.stack.drop(); + + // Emit as 'hir.if' + emit_if(emitter, op.as_operation(), &then_body.borrow(), &else_body.borrow()) + } + [_then_case, else_case] => { + // We need to emit an 'hir.if' to split the search at the midpoint, and emit 'a' in + // the then region, and then recurse with 'b' on the else region + // + // Emit `selector < partition_point` + { + let mut emitter = emitter.emitter(); + emitter.dup(0, span); + emitter.lte_imm((*else_case).into(), span); + } + + // Remove the selector used for this branch selection from the emitter's view of the + // stack + emitter.stack.drop(); + + let (then_blk, then_stack) = { + let mut then_emitter = emitter.nest(); + emit_binary_search(op, &mut then_emitter, a, &[], midpoint, usize::MAX)?; + let then_stack = then_emitter.stack.clone(); + (then_emitter.into_emitted_block(span), then_stack) + }; + + // If we have exactly + let (else_blk, else_stack) = { + let mut else_emitter = emitter.nest(); + let midpoint = b[0].midpoint(b[b.len() - 1]); + let partition_point = core::cmp::min( + b.len(), + b.partition_point(|item| *item < midpoint).next_multiple_of(2), + ); + let (b_then, b_else) = b.split_at(partition_point); + emit_binary_search(op, &mut else_emitter, b_then, b_else, midpoint, b.len())?; + let else_stack = else_emitter.stack.clone(); + (else_emitter.into_emitted_block(span), else_stack) + }; + + if then_stack != else_stack { + panic!( + "unexpected observable stack effect leaked from regions of {} + +stack on exit from 'then': {then_stack:#?} +stack on exit from 'else': {else_stack:#?} + ", + op.as_operation() + ); + } + + emitter.emit_op(masm::Op::If { + span, + then_blk, + else_blk, + }); + + emitter.stack = then_stack; + + Ok(()) + } + a => { + { + let mut emitter = emitter.emitter(); + emitter.dup(0, span); + emitter.lte_imm(midpoint.into(), span); + } + + // Remove the selector used for this branch selection from the emitter's view of the + // stack + emitter.stack.drop(); + + let (then_blk, then_stack) = { + let mut then_emitter = emitter.nest(); + let midpoint = a[0].midpoint(a[a.len() - 1]); + let partition_point = core::cmp::min( + a.len(), + a.partition_point(|item| *item < midpoint).next_multiple_of(2), + ); + let (a_then, a_else) = a.split_at(partition_point); + emit_binary_search(op, &mut then_emitter, a_then, a_else, midpoint, a.len())?; + let then_stack = then_emitter.stack.clone(); + (then_emitter.into_emitted_block(span), then_stack) + }; + + let (else_blk, else_stack) = { + let mut else_emitter = emitter.nest(); + let midpoint = b[0].midpoint(b[b.len() - 1]); + let partition_point = core::cmp::min( + b.len(), + b.partition_point(|item| *item < midpoint).next_multiple_of(2), + ); + let (b_then, b_else) = b.split_at(partition_point); + emit_binary_search(op, &mut else_emitter, b_then, b_else, midpoint, b.len())?; + let else_stack = else_emitter.stack.clone(); + (else_emitter.into_emitted_block(span), else_stack) + }; + + if then_stack != else_stack { + panic!( + "unexpected observable stack effect leaked from regions of {} + +stack on exit from 'then': {then_stack:#?} +stack on exit from 'else': {else_stack:#?} + ", + op.as_operation() + ); + } + + emitter.emit_op(masm::Op::If { + span, + then_blk, + else_blk, + }); + + emitter.stack = then_stack; + + Ok(()) + } + } +} + +/// This analyzes the `lhs` and `rhs` operand stacks, and computes the set of actions required to +/// make `rhs` match `lhs`. Those actions are then applied to `emitter`, such that its stack will +/// match `lhs` once value renaming has been applied. +/// +/// NOTE: It is expected that `emitter`'s stack is the same size as `lhs`, and that `lhs` and `rhs` +/// are the same size. +pub fn schedule_stack_realignment( + lhs: &crate::OperandStack, + rhs: &crate::OperandStack, + emitter: &mut BlockEmitter<'_>, +) { + use crate::opt::{OperandMovementConstraintSolver, SolverError}; + + if lhs.is_empty() && rhs.is_empty() { + return; + } + + assert_eq!(lhs.len(), rhs.len()); + + log::trace!(target: "codegen", "stack realignment required, scheduling moves.."); + log::trace!(target: "codegen", " desired stack state: {lhs:#?}"); + log::trace!(target: "codegen", " misaligned stack state: {rhs:#?}"); + + let mut constraints = SmallVec::<[Constraint; 8]>::with_capacity(lhs.len()); + constraints.resize(lhs.len(), Constraint::Move); + + let expected = lhs + .iter() + .rev() + .map(|o| o.as_value().expect("unexpected operand type")) + .collect::>(); + match OperandMovementConstraintSolver::new(&expected, &constraints, rhs) { + Ok(solver) => { + solver + .solve_and_apply(&mut emitter.emitter(), Default::default()) + .unwrap_or_else(|err| { + panic!( + "failed to realign stack\nwith error: {err:?}\nconstraints: \ + {constraints:?}\nexpected: {lhs:#?}\nstack: {rhs:#?}", + ) + }); + } + Err(SolverError::AlreadySolved) => (), + Err(err) => { + panic!("unexpected error constructing operand movement constraint solver: {err:?}") + } + } +} + +#[cfg(test)] +mod tests { + use alloc::rc::Rc; + + use midenc_dialect_arith::ArithOpBuilder; + use midenc_dialect_scf::StructuredControlFlowOpBuilder; + use midenc_expect_test::expect_file; + use midenc_hir::{ + dialects::builtin::{self, BuiltinOpBuilder, FunctionBuilder, FunctionRef}, + formatter::PrettyPrint, + pass::AnalysisManager, + version::Version, + AbiParam, Context, Ident, OpBuilder, Signature, Type, + }; + use midenc_hir_analysis::analyses::LivenessAnalysis; + + use super::*; + use crate::{linker::LinkInfo, OperandStack}; + + #[test] + fn util_emit_if_test() -> Result<(), Report> { + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let mut builder = OpBuilder::new(context.clone()); + + let function_ref = builder.create_function( + Ident::with_empty_span("test".into()), + Signature::new( + [AbiParam::new(Type::U32), AbiParam::new(Type::U32)], + [AbiParam::new(Type::U32)], + ), + )?; + + let (a, b) = { + let span = function_ref.span(); + let mut builder = FunctionBuilder::new(function_ref, &mut builder); + let entry = builder.entry_block(); + let a = builder.entry_block().borrow().arguments()[0] as ValueRef; + let b = builder.entry_block().borrow().arguments()[1] as ValueRef; + + // Unused in `then` branch, used on `else` branch + let count = builder.u32(0, span); + + let is_eq = builder.eq(a, b, span)?; + let conditional = builder.r#if(is_eq, &[Type::U32], span)?; + + let then_region = conditional.borrow().then_body().as_region_ref(); + let then_block = builder.create_block_in_region(then_region); + builder.switch_to_block(then_block); + let is_true = builder.u32(1, span); + builder.r#yield([is_true], span)?; + + let else_region = conditional.borrow().else_body().as_region_ref(); + let else_block = builder.create_block_in_region(else_region); + builder.switch_to_block(else_block); + let is_false = builder.mul(a, count, span)?; + builder.r#yield([is_false], span)?; + + builder.switch_to_block(entry); + builder.ret(Some(conditional.borrow().results()[0] as ValueRef), span)?; + + (a, b) + }; + + // Obtain liveness + let analysis_manager = AnalysisManager::new(function_ref.as_operation_ref(), None); + let liveness = analysis_manager.get_analysis::()?; + + // Generate linker info + let link_info = LinkInfo::new(builtin::ComponentId { + namespace: "root".into(), + name: "root".into(), + version: Version::new(1, 0, 0), + }); + + let mut stack = OperandStack::default(); + stack.push(b); + stack.push(a); + + // Instantiate block emitter + let mut invoked = Default::default(); + let emitter = BlockEmitter { + liveness: &liveness, + link_info: &link_info, + invoked: &mut invoked, + target: Default::default(), + stack, + }; + + // Lower input + let function = function_ref.borrow(); + let entry = function.entry_block(); + let body = emitter.emit(&entry.borrow()); + + // Verify emitted block contents + let input = format!("{}", function.as_operation()); + expect_file!["expected/utils_emit_if.hir"].assert_eq(&input); + + let output = body.to_pretty_string(); + expect_file!["expected/utils_emit_if.masm"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn util_emit_if_nested_test() -> Result<(), Report> { + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let mut builder = OpBuilder::new(context.clone()); + + let function_ref = builder.create_function( + Ident::with_empty_span("test".into()), + Signature::new( + [AbiParam::new(Type::U32), AbiParam::new(Type::U32)], + [AbiParam::new(Type::U32)], + ), + )?; + + let (a, b) = { + let span = function_ref.span(); + let mut builder = FunctionBuilder::new(function_ref, &mut builder); + let entry = builder.entry_block(); + let a = builder.entry_block().borrow().arguments()[0] as ValueRef; + let b = builder.entry_block().borrow().arguments()[1] as ValueRef; + + let is_eq = builder.eq(a, b, span)?; + let conditional = builder.r#if(is_eq, &[Type::U32], span)?; + + let then_region = conditional.borrow().then_body().as_region_ref(); + let then_block = builder.create_block_in_region(then_region); + builder.switch_to_block(then_block); + let case1 = builder.u32(1, span); + builder.r#yield([case1], span)?; + + let else_region = conditional.borrow().else_body().as_region_ref(); + let else_block = builder.create_block_in_region(else_region); + builder.switch_to_block(else_block); + + let is_lt = builder.lt(a, b, span)?; + let nested = builder.r#if(is_lt, &[Type::U32], span)?; + let then_region = nested.borrow().then_body().as_region_ref(); + let then_block = builder.create_block_in_region(then_region); + builder.switch_to_block(then_block); + let case2 = builder.u32(2, span); + builder.r#yield([case2], span)?; + + let else_region = nested.borrow().else_body().as_region_ref(); + let nested_else_block = builder.create_block_in_region(else_region); + builder.switch_to_block(nested_else_block); + let case3 = builder.mul(a, b, span)?; + builder.r#yield([case3], span)?; + + builder.switch_to_block(else_block); + builder.r#yield([nested.borrow().results()[0] as ValueRef], span)?; + + builder.switch_to_block(entry); + builder.ret(Some(conditional.borrow().results()[0] as ValueRef), span)?; + + (a, b) + }; + + // Obtain liveness + let analysis_manager = AnalysisManager::new(function_ref.as_operation_ref(), None); + let liveness = analysis_manager.get_analysis::()?; + + // Generate linker info + let link_info = LinkInfo::new(builtin::ComponentId { + namespace: "root".into(), + name: "root".into(), + version: Version::new(1, 0, 0), + }); + + let mut stack = OperandStack::default(); + stack.push(b); + stack.push(a); + + // Instantiate block emitter + let mut invoked = Default::default(); + let emitter = BlockEmitter { + liveness: &liveness, + link_info: &link_info, + invoked: &mut invoked, + target: Default::default(), + stack, + }; + + // Lower input + let function = function_ref.borrow(); + let entry = function.entry_block(); + let body = emitter.emit(&entry.borrow()); + + // Verify emitted block contents + let input = format!("{}", function.as_operation()); + expect_file!["expected/utils_emit_if_nested.hir"].assert_eq(&input); + + let output = body.to_pretty_string(); + expect_file!["expected/utils_emit_if_nested.masm"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn util_emit_binary_search_single_case_test() -> Result<(), Report> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let (function, block) = generate_emit_binary_search_test(1, context.clone())?; + + // Verify emitted block contents + let input = format!("{}", function.borrow().as_operation()); + expect_file!["expected/utils_emit_binary_search_1_case.hir"].assert_eq(&input); + + let output = block.to_pretty_string(); + expect_file!["expected/utils_emit_binary_search_1_case.masm"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn util_emit_binary_search_two_cases_test() -> Result<(), Report> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let (function, block) = generate_emit_binary_search_test(2, context.clone())?; + + // Verify emitted block contents + let input = format!("{}", function.borrow().as_operation()); + expect_file!["expected/utils_emit_binary_search_2_cases.hir"].assert_eq(&input); + + let output = block.to_pretty_string(); + expect_file!["expected/utils_emit_binary_search_2_cases.masm"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn util_emit_binary_search_three_cases_test() -> Result<(), Report> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let (function, block) = generate_emit_binary_search_test(3, context.clone())?; + + // Verify emitted block contents + let input = format!("{}", function.borrow().as_operation()); + expect_file!["expected/utils_emit_binary_search_3_cases.hir"].assert_eq(&input); + + let output = block.to_pretty_string(); + expect_file!["expected/utils_emit_binary_search_3_cases.masm"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn util_emit_binary_search_four_cases_test() -> Result<(), Report> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let (function, block) = generate_emit_binary_search_test(4, context.clone())?; + + // Verify emitted block contents + let input = format!("{}", function.borrow().as_operation()); + expect_file!["expected/utils_emit_binary_search_4_cases.hir"].assert_eq(&input); + + let output = block.to_pretty_string(); + expect_file!["expected/utils_emit_binary_search_4_cases.masm"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn util_emit_binary_search_five_cases_test() -> Result<(), Report> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let (function, block) = generate_emit_binary_search_test(5, context.clone())?; + + // Verify emitted block contents + let input = format!("{}", function.borrow().as_operation()); + expect_file!["expected/utils_emit_binary_search_5_cases.hir"].assert_eq(&input); + + let output = block.to_pretty_string(); + expect_file!["expected/utils_emit_binary_search_5_cases.masm"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn util_emit_binary_search_seven_cases_test() -> Result<(), Report> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let context = Rc::new(Context::default()); + crate::register_dialect_hooks(&context); + + let (function, block) = generate_emit_binary_search_test(7, context.clone())?; + + // Verify emitted block contents + let input = format!("{}", function.borrow().as_operation()); + expect_file!["expected/utils_emit_binary_search_7_cases.hir"].assert_eq(&input); + + let output = block.to_pretty_string(); + expect_file!["expected/utils_emit_binary_search_7_cases.masm"].assert_eq(&output); + + Ok(()) + } + + fn generate_emit_binary_search_test( + num_cases: usize, + context: Rc, + ) -> Result<(FunctionRef, masm::Block), Report> { + let mut builder = OpBuilder::new(context.clone()); + + let function_ref = builder.create_function( + Ident::with_empty_span("test".into()), + Signature::new( + [AbiParam::new(Type::U32), AbiParam::new(Type::U32)], + [AbiParam::new(Type::U32)], + ), + )?; + + let (a, b) = { + let span = function_ref.span(); + let mut builder = FunctionBuilder::new(function_ref, &mut builder); + let entry = builder.entry_block(); + let a = builder.entry_block().borrow().arguments()[0] as ValueRef; + let b = builder.entry_block().borrow().arguments()[1] as ValueRef; + + let cases = SmallVec::<[_; 4]>::from_iter(0u32..(num_cases as u32)); + let switch = builder.index_switch(a, cases, &[Type::U32], span)?; + + let fallback_region = switch.borrow().default_region().as_region_ref(); + let case_regions = (0..num_cases).map(|index| switch.borrow().get_case_region(index)); + + for (case, case_region) in case_regions.enumerate() { + let case_block = builder.create_block_in_region(case_region); + builder.switch_to_block(case_block); + let case_result = builder.u32(case as u32, span); + builder.r#yield([case_result], span)?; + } + + let fallback_block = builder.create_block_in_region(fallback_region); + builder.switch_to_block(fallback_block); + let fallback_result = builder.mul(a, b, span)?; + builder.r#yield([fallback_result], span)?; + + builder.switch_to_block(entry); + builder.ret(Some(switch.borrow().results()[0] as ValueRef), span)?; + + (a, b) + }; + + // Obtain liveness + let analysis_manager = AnalysisManager::new(function_ref.as_operation_ref(), None); + let liveness = analysis_manager.get_analysis::()?; + + // Generate linker info + let link_info = LinkInfo::new(builtin::ComponentId { + namespace: "root".into(), + name: "root".into(), + version: Version::new(1, 0, 0), + }); + + let mut stack = OperandStack::default(); + stack.push(b); + stack.push(a); + + // Instantiate block emitter + let mut invoked = Default::default(); + let emitter = BlockEmitter { + liveness: &liveness, + link_info: &link_info, + invoked: &mut invoked, + target: Default::default(), + stack, + }; + + // Lower input + let function = function_ref.borrow(); + let entry = function.entry_block(); + let body = emitter.emit(&entry.borrow()); + + Ok((function_ref, body)) + } +} diff --git a/codegen/masm/src/masm/function.rs b/codegen/masm/src/masm/function.rs deleted file mode 100644 index 74e15d29e..000000000 --- a/codegen/masm/src/masm/function.rs +++ /dev/null @@ -1,424 +0,0 @@ -use std::{collections::BTreeSet, fmt, sync::Arc}; - -use cranelift_entity::EntityRef; -use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListAtomicLink}; -use miden_assembly::{ - ast::{self, ProcedureName}, - LibraryNamespace, LibraryPath, -}; -use midenc_hir::{ - diagnostics::{SourceSpan, Span, Spanned}, - formatter::PrettyPrint, - AttributeSet, FunctionIdent, Ident, Signature, Type, -}; -use smallvec::SmallVec; - -use super::*; - -intrusive_adapter!(pub FunctionListAdapter = Box: Function { link: LinkedListAtomicLink }); -intrusive_adapter!(pub FrozenFunctionListAdapter = Arc: Function { link: LinkedListAtomicLink }); - -/// This represents a function in Miden Assembly -#[derive(Spanned, Clone)] -pub struct Function { - link: LinkedListAtomicLink, - #[span] - pub span: SourceSpan, - /// The attributes associated with this function - pub attrs: AttributeSet, - /// The name of this function - pub name: FunctionIdent, - /// The type signature of this function - pub signature: Signature, - /// The [Region] which forms the body of this function - pub body: Region, - /// The set of procedures invoked from the body of this function - invoked: BTreeSet, - /// Locals allocated for this function - locals: SmallVec<[Local; 1]>, - /// The next available local index - next_local_id: usize, -} -impl Function { - pub fn new(name: FunctionIdent, signature: Signature) -> Self { - Self { - link: Default::default(), - span: SourceSpan::UNKNOWN, - attrs: Default::default(), - name, - signature, - body: Default::default(), - invoked: Default::default(), - locals: Default::default(), - next_local_id: 0, - } - } - - /// Returns true if this function is decorated with the `entrypoint` attribute. - pub fn is_entrypoint(&self) -> bool { - use midenc_hir::symbols; - - self.attrs.has(&symbols::Entrypoint) - } - - /// Return the number of arguments expected on the operand stack - #[inline] - pub fn arity(&self) -> usize { - self.signature.arity() - } - - /// Return the number of results produced by this function - #[inline] - pub fn num_results(&self) -> usize { - self.signature.results.len() - } - - /// Allocate a new local in this function, using the provided data - /// - /// The index of the local is returned as it's identifier - pub fn alloc_local(&mut self, ty: Type) -> LocalId { - let num_words = ty.size_in_words(); - let next_id = self.next_local_id; - assert!( - (next_id + num_words) < (u8::MAX as usize), - "unable to allocate a local of type {}: unable to allocate enough local memory", - &ty - ); - let id = LocalId::new(next_id); - self.next_local_id += num_words; - let local = Local { id, ty }; - self.locals.push(local); - id - } - - /// Allocate `n` locals for use by this function. - /// - /// Each local can be independently accessed, but they are all of type `Felt` - pub fn alloc_n_locals(&mut self, n: u16) { - assert!( - (self.next_local_id + n as usize) < u16::MAX as usize, - "unable to allocate {n} locals" - ); - - let num_locals = self.locals.len(); - self.locals.resize_with(num_locals + n as usize, || { - let id = LocalId::new(self.next_local_id); - self.next_local_id += 1; - Local { id, ty: Type::Felt } - }); - } - - /// Get the local with the given identifier - pub fn local(&self, id: LocalId) -> &Local { - self.locals.iter().find(|l| l.id == id).expect("invalid local id") - } - - /// Return the locals allocated in this function as a slice - #[inline] - pub fn locals(&self) -> &[Local] { - self.locals.as_slice() - } - - /// Get a reference to the entry block for this function - pub fn body(&self) -> &Block { - self.body.block(self.body.body) - } - - /// Get a mutable reference to the entry block for this function - pub fn body_mut(&mut self) -> &mut Block { - self.body.block_mut(self.body.body) - } - - /// Allocate a new code block in this function - #[inline(always)] - pub fn create_block(&mut self) -> BlockId { - self.body.create_block() - } - - /// Get a reference to a [Block] by [BlockId] - #[inline(always)] - pub fn block(&self, id: BlockId) -> &Block { - self.body.block(id) - } - - /// Get a mutable reference to a [Block] by [BlockId] - #[inline(always)] - pub fn block_mut(&mut self, id: BlockId) -> &mut Block { - self.body.block_mut(id) - } - - pub fn invoked(&self) -> impl Iterator + '_ { - self.invoked.iter() - } - - pub fn register_invoked(&mut self, kind: ast::InvokeKind, target: ast::InvocationTarget) { - self.invoked.insert(ast::Invoke { kind, target }); - } - - #[inline(never)] - pub fn register_absolute_invocation_target( - &mut self, - kind: ast::InvokeKind, - target: FunctionIdent, - ) { - let module_name_span = target.module.span; - let module_id = ast::Ident::new_unchecked(Span::new( - module_name_span, - Arc::from(target.module.as_str().to_string().into_boxed_str()), - )); - let name_span = target.function.span; - let id = ast::Ident::new_unchecked(Span::new( - name_span, - Arc::from(target.function.as_str().to_string().into_boxed_str()), - )); - let path = LibraryPath::new(target.module.as_str()).unwrap_or_else(|_| { - LibraryPath::new_from_components(LibraryNamespace::Anon, [module_id]) - }); - let name = ast::ProcedureName::new_unchecked(id); - self.register_invoked(kind, ast::InvocationTarget::AbsoluteProcedurePath { name, path }); - } - - /// Return an implementation of [std::fmt::Display] for this function - pub fn display<'a, 'b: 'a>(&'b self, imports: &'b ModuleImportInfo) -> DisplayMasmFunction<'a> { - DisplayMasmFunction { - function: self, - imports, - } - } - - pub fn from_ast(module: Ident, proc: &ast::Procedure) -> Box { - use midenc_hir::{Linkage, Symbol}; - - let proc_span = proc.name().span(); - let proc_name = Symbol::intern(AsRef::::as_ref(proc.name())); - let id = FunctionIdent { - module, - function: Ident::new(proc_name, proc_span), - }; - - let mut signature = Signature::new(vec![], vec![]); - let visibility = proc.visibility(); - if !visibility.is_exported() { - signature.linkage = Linkage::Internal; - } else if visibility.is_syscall() { - signature.cc = midenc_hir::CallConv::Kernel; - } - - let mut function = Box::new(Self::new(id, signature)); - if proc.is_entrypoint() { - function.attrs.set(midenc_hir::attributes::ENTRYPOINT); - } - - function.alloc_n_locals(proc.num_locals()); - - function.invoked.extend(proc.invoked().cloned()); - function.body = Region::from_block(module, proc.body()); - - function - } - - pub fn to_ast( - &self, - imports: &midenc_hir::ModuleImportInfo, - locals: &BTreeSet, - tracing_enabled: bool, - ) -> ast::Procedure { - let visibility = if self.signature.is_kernel() { - ast::Visibility::Syscall - } else if self.signature.is_public() { - ast::Visibility::Public - } else { - ast::Visibility::Private - }; - - let id = ast::Ident::new_unchecked(Span::new( - self.name.function.span, - Arc::from(self.name.function.as_str().to_string().into_boxed_str()), - )); - let name = ast::ProcedureName::new_unchecked(id); - - let mut body = self.body.to_block(imports, locals); - - // Emit trace events on entry/exit from the procedure body, if not already present - if tracing_enabled { - emit_trace_frame_events(self.span, &mut body); - } - - let num_locals = u16::try_from(self.locals.len()).expect("too many locals"); - let mut proc = ast::Procedure::new(self.span, visibility, name, num_locals, body); - proc.extend_invoked(self.invoked().cloned()); - proc - } -} - -fn emit_trace_frame_events(span: SourceSpan, body: &mut ast::Block) { - use midenc_hir::{TRACE_FRAME_END, TRACE_FRAME_START}; - - let ops = body.iter().as_slice(); - let has_frame_start = match ops.get(1) { - Some(ast::Op::Inst(inst)) => match inst.inner() { - ast::Instruction::Trace(imm) => { - matches!(imm, ast::Immediate::Value(val) if val.into_inner() == TRACE_FRAME_START) - } - _ => false, - }, - _ => false, - }; - - // If we have the frame start event, we do not need to emit any further events - if has_frame_start { - return; - } - - // Because [ast::Block] does not have a mutator that lets us insert an op at the start, we need - // to push the events at the end, then use access to the mutable slice via `iter_mut` to move - // elements around. - body.push(ast::Op::Inst(Span::new(span, ast::Instruction::Nop))); - body.push(ast::Op::Inst(Span::new(span, ast::Instruction::Trace(TRACE_FRAME_END.into())))); - body.push(ast::Op::Inst(Span::new(span, ast::Instruction::Nop))); - body.push(ast::Op::Inst(Span::new( - span, - ast::Instruction::Trace(TRACE_FRAME_START.into()), - ))); - let ops = body.iter_mut().into_slice(); - ops.rotate_right(2); -} - -impl fmt::Debug for Function { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Function") - .field("name", &self.name) - .field("signature", &self.signature) - .field("attrs", &self.attrs) - .field("locals", &self.locals) - .field("body", &self.body) - .finish() - } -} - -#[doc(hidden)] -pub struct DisplayMasmFunction<'a> { - function: &'a Function, - imports: &'a ModuleImportInfo, -} -impl<'a> midenc_hir::formatter::PrettyPrint for DisplayMasmFunction<'a> { - fn render(&self) -> midenc_hir::formatter::Document { - use midenc_hir::formatter::*; - - if self.function.name.module.as_str() == LibraryNamespace::EXEC_PATH - && self.function.name.function.as_str() == ProcedureName::MAIN_PROC_NAME - { - let body = self.function.body.display(Some(self.function.name), self.imports); - return indent(4, const_text("begin") + nl() + body.render()) - + nl() - + const_text("end") - + nl(); - } - - let visibility = if self.function.signature.is_kernel() { - ast::Visibility::Syscall - } else if self.function.signature.is_public() { - ast::Visibility::Public - } else { - ast::Visibility::Private - }; - let name = if ast::Ident::validate(self.function.name.function).is_ok() { - text(self.function.name.function.as_str()) - } else { - text(format!("\"{}\"", self.function.name.function.as_str())) - }; - let mut doc = display(visibility) + const_text(".") + name; - if !self.function.locals.is_empty() { - doc += const_text(".") + display(self.function.locals.len()); - } - - let body = self.function.body.display(Some(self.function.name), self.imports); - doc + indent(4, nl() + body.render()) + nl() + const_text("end") + nl() + nl() - } -} -impl<'a> fmt::Display for DisplayMasmFunction<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} - -pub type FunctionList = LinkedList; -pub type FunctionListIter<'a> = intrusive_collections::linked_list::Iter<'a, FunctionListAdapter>; - -pub type FrozenFunctionList = LinkedList; -pub type FrozenFunctionListIter<'a> = - intrusive_collections::linked_list::Iter<'a, FrozenFunctionListAdapter>; - -pub(super) enum Functions { - Open(FunctionList), - Frozen(FrozenFunctionList), -} -impl Clone for Functions { - fn clone(&self) -> Self { - match self { - Self::Open(list) => { - let mut new_list = FunctionList::new(Default::default()); - for f in list.iter() { - new_list.push_back(Box::new(f.clone())); - } - Self::Open(new_list) - } - Self::Frozen(list) => { - let mut new_list = FrozenFunctionList::new(Default::default()); - for f in list.iter() { - new_list.push_back(Arc::from(Box::new(f.clone()))); - } - Self::Frozen(new_list) - } - } - } -} -impl Default for Functions { - fn default() -> Self { - Self::Open(Default::default()) - } -} -impl Functions { - pub fn iter(&self) -> impl Iterator + '_ { - match self { - Self::Open(ref list) => FunctionsIter::Open(list.iter()), - Self::Frozen(ref list) => FunctionsIter::Frozen(list.iter()), - } - } - - pub fn push_back(&mut self, function: Box) { - match self { - Self::Open(ref mut list) => { - list.push_back(function); - } - Self::Frozen(_) => panic!("cannot insert function into frozen module"), - } - } - - pub fn freeze(&mut self) { - if let Self::Open(ref mut functions) = self { - let mut frozen = FrozenFunctionList::default(); - - while let Some(function) = functions.pop_front() { - frozen.push_back(Arc::from(function)); - } - - *self = Self::Frozen(frozen); - } - } -} - -enum FunctionsIter<'a> { - Open(FunctionListIter<'a>), - Frozen(FrozenFunctionListIter<'a>), -} -impl<'a> Iterator for FunctionsIter<'a> { - type Item = &'a Function; - - fn next(&mut self) -> Option { - match self { - Self::Open(ref mut iter) => iter.next(), - Self::Frozen(ref mut iter) => iter.next(), - } - } -} diff --git a/codegen/masm/src/masm/intrinsics.rs b/codegen/masm/src/masm/intrinsics.rs deleted file mode 100644 index a72400b86..000000000 --- a/codegen/masm/src/masm/intrinsics.rs +++ /dev/null @@ -1,47 +0,0 @@ -use miden_assembly::{ast::ModuleKind, LibraryPath}; -use midenc_hir::diagnostics::{PrintDiagnostic, SourceManager}; - -use super::Module; - -const I32_INTRINSICS: &str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i32.masm")); -const I64_INTRINSICS: &str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i64.masm")); -const MEM_INTRINSICS: &str = - include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/mem.masm")); - -/// This is a mapping of intrinsics module name to the raw MASM source for that module -const INTRINSICS: [(&str, &str, &str); 3] = [ - ( - "intrinsics::i32", - I32_INTRINSICS, - concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i32.masm"), - ), - ( - "intrinsics::i64", - I64_INTRINSICS, - concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/i64.masm"), - ), - ( - "intrinsics::mem", - MEM_INTRINSICS, - concat!(env!("CARGO_MANIFEST_DIR"), "/intrinsics/mem.masm"), - ), -]; - -/// This helper loads the named module from the set of intrinsics modules defined in this crate. -/// -/// Expects the fully-qualified name to be given, e.g. `intrinsics::mem` -pub fn load>(name: N, source_manager: &dyn SourceManager) -> Option { - let name = name.as_ref(); - let (name, source, filename) = INTRINSICS.iter().copied().find(|(n, ..)| *n == name)?; - let source_file = source_manager.load(filename, source.to_string()); - let path = LibraryPath::new(name).expect("invalid module name"); - match Module::parse(ModuleKind::Library, path, source_file.clone()) { - Ok(module) => Some(module), - Err(err) => { - let err = PrintDiagnostic::new(err); - panic!("failed to parse intrinsic module: {err}"); - } - } -} diff --git a/codegen/masm/src/masm/mod.rs b/codegen/masm/src/masm/mod.rs deleted file mode 100644 index 68f363109..000000000 --- a/codegen/masm/src/masm/mod.rs +++ /dev/null @@ -1,110 +0,0 @@ -mod function; -pub mod intrinsics; -mod module; -mod program; -mod region; - -pub use midenc_hir::{ - Local, LocalId, MasmBlock as Block, MasmBlockId as BlockId, MasmImport as Import, MasmOp as Op, - ModuleImportInfo, -}; -use serde::{Deserialize, Serialize}; - -pub use self::{ - function::{FrozenFunctionList, Function, FunctionList}, - module::{FrozenModuleTree, Module, ModuleTree}, - program::{Library, Program}, - region::Region, -}; - -/// This represents a descriptor for a pointer translated from the IR into a form suitable for -/// referencing data in Miden's linear memory. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct NativePtr { - /// This is the address of the word containing the first byte of data - pub waddr: u32, - /// This is the element index of the word referenced by `waddr` containing the first byte of - /// data - /// - /// Each element is assumed to be a 32-bit value/chunk - pub index: u8, - /// This is the byte offset into the 32-bit chunk referenced by `index` - /// - /// This offset is where the data referenced by the pointer actually starts. - pub offset: u8, - /// This is the assumed address space of the pointer value. - /// - /// This address space is unknown by default, but can be specified if known statically. - /// The address space determines whether the pointer is valid in certain contexts. For - /// example, attempting to load a pointer with address space 0 is invalid if not operating - /// in the root context. - /// - /// Currently this has no effect, but is here as we expand support for multiple memories. - pub addrspace: midenc_hir::AddressSpace, -} -impl NativePtr { - pub fn new(waddr: u32, index: u8, offset: u8) -> Self { - Self { - waddr, - index, - offset, - addrspace: Default::default(), - } - } - - /// Translates a raw pointer (assumed to be in a byte-addressable address space) to - /// a native pointer value, in the default [hir::AddressSpace]. - pub fn from_ptr(addr: u32) -> Self { - // The native word address for `addr` is derived by splitting - // the byte-addressable space into 32-bit chunks, each chunk - // belonging to a single field element. Thus, each word of the - // native address space represents 128 bits of byte-addressable - // memory. - // - // By dividing `addr` by 16, we get the word index (i.e. address) - // where the data starts. - let waddr = addr / 16; - // If our address is not word-aligned, we need to determine what - // element index contains the 32-bit chunk where the data begins - let woffset = addr % 16; - let index = (woffset / 4) as u8; - // If our address is not element-aligned, we need to determine - // what byte offset contains the first byte of the data - let offset = (woffset % 4) as u8; - Self { - waddr, - index, - offset, - addrspace: Default::default(), - } - } - - /// Returns true if this pointer is aligned to a word boundary - pub const fn is_word_aligned(&self) -> bool { - self.index == 0 && self.offset == 0 - } - - /// Returns true if this pointer is aligned to a field element boundary - pub const fn is_element_aligned(&self) -> bool { - self.offset == 0 - } - - /// Returns true if this pointer is not word or element aligned - pub const fn is_unaligned(&self) -> bool { - self.offset > 0 - } - - /// Returns the byte alignment implied by this pointer value. - /// - /// For example, a pointer to the first word in linear memory, i.e. address 0, - /// with an element index of 1, and offset of 16, is equivalent to an address - /// in byte-addressable memory of 48, which has an implied alignment of 16 bytes. - pub const fn alignment(&self) -> u32 { - 2u32.pow(self.as_ptr().trailing_zeros()) - } - - /// Converts this native pointer back to a byte-addressable pointer value - pub const fn as_ptr(&self) -> u32 { - (self.waddr * 16) + (self.index as u32 * 4) + self.offset as u32 - } -} diff --git a/codegen/masm/src/masm/module.rs b/codegen/masm/src/masm/module.rs deleted file mode 100644 index 417bcffbf..000000000 --- a/codegen/masm/src/masm/module.rs +++ /dev/null @@ -1,411 +0,0 @@ -use std::{collections::BTreeSet, fmt, path::Path, sync::Arc}; - -use intrusive_collections::{intrusive_adapter, RBTree, RBTreeAtomicLink}; -use miden_assembly::{ - ast::{self, ModuleKind}, - LibraryPath, -}; -use midenc_hir::{ - diagnostics::{Report, SourceFile, SourceSpan, Span, Spanned}, - formatter::PrettyPrint, - FunctionIdent, Ident, Symbol, -}; - -use super::{function::Functions, FrozenFunctionList, Function, ModuleImportInfo}; - -/// This represents a single compiled Miden Assembly module in a form that is -/// designed to integrate well with the rest of our IR. You can think of this -/// as an intermediate representation corresponding to the Miden Assembly AST, -/// i.e. [miden_assembly::ast::Module]. -/// -/// Functions are stored in a [Module] in a linked list, so as to allow precise -/// ordering of functions in the module body. We typically access all of the -/// functions in a given module, so O(1) access to a specific function is not -/// of primary importance. -#[derive(Clone)] -pub struct Module { - link: RBTreeAtomicLink, - pub span: SourceSpan, - /// The kind of this module, e.g. kernel or library - pub kind: ModuleKind, - /// The name of this module, e.g. `std::math::u64` - pub id: Ident, - pub name: LibraryPath, - /// The module-scoped documentation for this module - pub docs: Option, - /// The modules to import, along with their local aliases - pub imports: ModuleImportInfo, - /// The functions defined in this module - functions: Functions, - /// The set of re-exported functions declared in this module - reexports: Vec, -} -impl Module { - /// Create a new, empty [Module] with the given name and kind. - pub fn new(name: LibraryPath, kind: ModuleKind) -> Self { - let id = Ident::with_empty_span(Symbol::intern(name.path())); - Self { - link: Default::default(), - kind, - span: SourceSpan::UNKNOWN, - id, - name, - docs: None, - imports: Default::default(), - functions: Default::default(), - reexports: Default::default(), - } - } - - /// Parse a [Module] from `source` using the given [ModuleKind] and [LibraryPath] - pub fn parse( - kind: ModuleKind, - path: LibraryPath, - source: Arc, - ) -> Result { - let span = source.source_span(); - let mut parser = ast::Module::parser(kind); - let ast = parser.parse(path, source)?; - Ok(Self::from_ast(&ast, span)) - } - - /// Returns true if this module is a kernel module - pub fn is_kernel(&self) -> bool { - self.kind.is_kernel() - } - - /// Returns true if this module is an executable module - pub fn is_executable(&self) -> bool { - self.kind.is_executable() - } - - /// If this module contains a function marked with the `entrypoint` attribute, - /// return the fully-qualified name of that function - pub fn entrypoint(&self) -> Option { - if !self.is_executable() { - return None; - } - - self.functions.iter().find_map(|f| { - if f.is_entrypoint() { - Some(f.name) - } else { - None - } - }) - } - - /// Returns true if this module contains a [Function] `name` - pub fn contains(&self, name: Ident) -> bool { - self.functions.iter().any(|f| f.name.function == name) - } - - pub fn from_ast(ast: &ast::Module, span: SourceSpan) -> Self { - let mut module = Self::new(ast.path().clone(), ast.kind()); - module.span = span; - module.docs = ast.docs().map(|s| s.to_string()); - - let mut imports = ModuleImportInfo::default(); - for import in ast.imports() { - let span = import.name.span(); - let alias = Symbol::intern(import.name.as_str()); - let name = if import.is_aliased() { - Symbol::intern(import.path.last()) - } else { - alias - }; - imports.insert(midenc_hir::MasmImport { span, name, alias }); - } - - for export in ast.procedures() { - match export { - ast::Export::Alias(ref alias) => { - module.reexports.push(alias.clone()); - } - ast::Export::Procedure(ref proc) => { - let function = Function::from_ast(module.id, proc); - module.functions.push_back(function); - } - } - } - - module - } - - /// Freezes this program, preventing further modifications - pub fn freeze(mut self: Box) -> Arc { - self.functions.freeze(); - Arc::from(self) - } - - /// Get an iterator over the functions in this module - pub fn functions(&self) -> impl Iterator + '_ { - self.functions.iter() - } - - /// Access the frozen functions list of this module, and panic if not frozen - pub fn unwrap_frozen_functions(&self) -> &FrozenFunctionList { - match self.functions { - Functions::Frozen(ref functions) => functions, - Functions::Open(_) => panic!("expected module to be frozen"), - } - } - - /// Append a function to the end of this module - /// - /// NOTE: This function will panic if the module has been frozen - pub fn push_back(&mut self, function: Box) { - self.functions.push_back(function); - } - - /// Convert this module into its [miden_assembly::ast::Module] representation. - pub fn to_ast(&self, tracing_enabled: bool) -> Result { - let mut ast = ast::Module::new(self.kind, self.name.clone()).with_span(self.span); - ast.set_docs(self.docs.clone().map(Span::unknown)); - - // Create module import table - for ir_import in self.imports.iter() { - let span = ir_import.span; - let name = - ast::Ident::new_with_span(span, ir_import.alias.as_str()).map_err(Report::msg)?; - let path = LibraryPath::new(ir_import.name.as_str()).expect("invalid import path"); - let import = ast::Import { - span, - name, - path, - uses: 1, - }; - let _ = ast.define_import(import); - } - - // Translate functions - let locals = BTreeSet::from_iter(self.functions.iter().map(|f| f.name)); - - for reexport in self.reexports.iter() { - ast.define_procedure(ast::Export::Alias(reexport.clone()))?; - } - - for function in self.functions.iter() { - ast.define_procedure(ast::Export::Procedure(function.to_ast( - &self.imports, - &locals, - tracing_enabled, - )))?; - } - - Ok(ast) - } - - /// Write this module to a new file under `dir`, assuming `dir` is the root directory for a - /// program. - /// - /// For example, if this module is named `std::math::u64`, then it will be written to - /// `

/std/math/u64.masm` - pub fn write_to_directory>( - &self, - dir: P, - session: &midenc_session::Session, - ) -> std::io::Result<()> { - use midenc_session::{Emit, OutputMode}; - - let mut path = dir.as_ref().to_path_buf(); - assert!(path.is_dir()); - for component in self.name.components() { - path.push(component.as_ref()); - } - assert!(path.set_extension("masm")); - - let ast = self.to_ast(false).map_err(std::io::Error::other)?; - ast.write_to_file(&path, OutputMode::Text, session) - } -} -impl midenc_hir::formatter::PrettyPrint for Module { - fn render(&self) -> midenc_hir::formatter::Document { - use midenc_hir::formatter::*; - - let mut doc = Document::Empty; - if let Some(docs) = self.docs.as_ref() { - let fragment = - docs.lines().map(text).reduce(|acc, line| acc + nl() + text("#! ") + line); - - if let Some(fragment) = fragment { - doc += fragment; - } - } - - for (i, import) in self.imports.iter().enumerate() { - if i > 0 { - doc += nl(); - } - if import.is_aliased() { - doc += flatten( - const_text("use") - + const_text(".") - + text(format!("{}", import.name)) - + const_text("->") - + text(format!("{}", import.alias)), - ); - } else { - doc += - flatten(const_text("use") + const_text(".") + text(format!("{}", import.name))); - } - } - - if !self.imports.is_empty() { - doc += nl() + nl(); - } - - for (i, export) in self.reexports.iter().enumerate() { - if i > 0 { - doc += nl(); - } - doc += export.render(); - } - - if !self.reexports.is_empty() { - doc += nl() + nl(); - } - - for (i, func) in self.functions.iter().enumerate() { - if i > 0 { - doc += nl(); - } - let func = func.display(&self.imports); - doc += func.render(); - } - - doc - } -} -impl fmt::Display for Module { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} -impl midenc_session::Emit for Module { - fn name(&self) -> Option { - Some(self.id.as_symbol()) - } - - fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { - midenc_session::OutputType::Masm - } - - fn write_to( - &self, - writer: W, - mode: midenc_session::OutputMode, - session: &midenc_session::Session, - ) -> std::io::Result<()> { - let ast = self.to_ast(false).map_err(std::io::Error::other)?; - ast.write_to(writer, mode, session) - } -} - -intrusive_adapter!(pub ModuleTreeAdapter = Box: Module { link: RBTreeAtomicLink }); -intrusive_adapter!(pub FrozenModuleTreeAdapter = Arc: Module { link: RBTreeAtomicLink }); -impl<'a> intrusive_collections::KeyAdapter<'a> for ModuleTreeAdapter { - type Key = Ident; - - #[inline] - fn get_key(&self, module: &'a Module) -> Ident { - module.id - } -} -impl<'a> intrusive_collections::KeyAdapter<'a> for FrozenModuleTreeAdapter { - type Key = Ident; - - #[inline] - fn get_key(&self, module: &'a Module) -> Ident { - module.id - } -} - -pub type ModuleTree = RBTree; -pub type ModuleTreeIter<'a> = intrusive_collections::rbtree::Iter<'a, ModuleTreeAdapter>; - -pub type FrozenModuleTree = RBTree; -pub type FrozenModuleTreeIter<'a> = - intrusive_collections::rbtree::Iter<'a, FrozenModuleTreeAdapter>; - -pub(super) enum Modules { - Open(ModuleTree), - Frozen(FrozenModuleTree), -} -impl Default for Modules { - fn default() -> Self { - Self::Open(Default::default()) - } -} -impl Clone for Modules { - fn clone(&self) -> Self { - let mut out = ModuleTree::default(); - for module in self.iter() { - out.insert(Box::new(module.clone())); - } - Self::Open(out) - } -} -impl Modules { - pub fn len(&self) -> usize { - match self { - Self::Open(ref tree) => tree.iter().count(), - Self::Frozen(ref tree) => tree.iter().count(), - } - } - - pub fn iter(&self) -> impl Iterator + '_ { - match self { - Self::Open(ref tree) => ModulesIter::Open(tree.iter()), - Self::Frozen(ref tree) => ModulesIter::Frozen(tree.iter()), - } - } - - pub fn get(&self, name: &Q) -> Option<&Module> - where - Q: ?Sized + Ord, - Ident: core::borrow::Borrow, - { - match self { - Self::Open(ref tree) => tree.find(name).get(), - Self::Frozen(ref tree) => tree.find(name).get(), - } - } - - pub fn insert(&mut self, module: Box) { - match self { - Self::Open(ref mut tree) => { - tree.insert(module); - } - Self::Frozen(_) => panic!("cannot insert module into frozen program"), - } - } - - pub fn freeze(&mut self) { - if let Self::Open(ref mut modules) = self { - let mut frozen = FrozenModuleTree::default(); - - let mut open = modules.front_mut(); - while let Some(module) = open.remove() { - frozen.insert(module.freeze()); - } - - *self = Self::Frozen(frozen); - } - } -} - -enum ModulesIter<'a> { - Open(ModuleTreeIter<'a>), - Frozen(FrozenModuleTreeIter<'a>), -} -impl<'a> Iterator for ModulesIter<'a> { - type Item = &'a Module; - - fn next(&mut self) -> Option { - match self { - Self::Open(ref mut iter) => iter.next(), - Self::Frozen(ref mut iter) => iter.next(), - } - } -} diff --git a/codegen/masm/src/masm/program.rs b/codegen/masm/src/masm/program.rs deleted file mode 100644 index 5a989c7ce..000000000 --- a/codegen/masm/src/masm/program.rs +++ /dev/null @@ -1,641 +0,0 @@ -use std::{fmt, path::Path, sync::Arc}; - -use hir::{Signature, Symbol}; -use miden_assembly::{ - ast::{ModuleKind, ProcedureName}, - KernelLibrary, Library as CompiledLibrary, LibraryNamespace, -}; -use miden_core::crypto::hash::Rpo256; -use midenc_hir::{ - self as hir, diagnostics::Report, DataSegmentTable, Felt, FieldElement, FunctionIdent, - GlobalVariableTable, Ident, SourceSpan, -}; -use midenc_hir_analysis::GlobalVariableAnalysis; -use midenc_session::{Emit, Session}; - -use super::{module::Modules, *}; -use crate::packaging::Rodata; - -inventory::submit! { - midenc_session::CompileFlag::new("test_harness") - .long("test-harness") - .action(midenc_session::FlagAction::SetTrue) - .help("If present, causes the code generator to emit extra code for the VM test harness") - .help_heading("Testing") -} - -/// A [Program] represents a complete set of modules which are intended to be shipped and executed -/// together. -#[derive(Clone)] -pub struct Program { - /// The code for this program - library: Library, - /// The function identifier for the program entrypoint, if applicable - entrypoint: FunctionIdent, - /// The base address of the dynamic heap, as computed by the codegen backend - /// - /// Defaults to an offset which is two 64k pages from the start of linear memory, - /// or, if available, the next byte following the both the reserved linear memory region as - /// declared in HIR, and the global variables of the program. - heap_base: u32, -} -impl Program { - /// Create a new [Program] initialized from an [hir::Program]. - /// - /// The resulting [Program] will have the following: - /// - /// * Data segments described by the original [hir::Program] - /// * The entrypoint function which will be invoked after the initialization phase of startup - /// * If an entrypoint is set, an executable [Module] which performs initialization and then - /// invokes the entrypoint - /// - /// None of the HIR modules will have been added yet - pub fn from_hir( - program: &hir::Program, - globals: &GlobalVariableAnalysis, - ) -> Result { - let Some(entrypoint) = program.entrypoint() else { - return Err(Report::msg("invalid program: no entrypoint")); - }; - let library = Library::from_hir(program, globals); - - // Compute the first page boundary after the end of the globals table to use as the start - // of the dynamic heap when the program is executed - let heap_base = program.reserved_memory_bytes() - + u32::try_from( - program.globals().size_in_bytes().next_multiple_of(program.page_size() as usize), - ) - .expect("unable to allocate dynamic heap: global table too large"); - Ok(Self { - library, - entrypoint, - heap_base, - }) - } - - /// Get the raw [Rodata] segments for this program - pub fn rodatas(&self) -> &[Rodata] { - self.library.rodata.as_slice() - } - - /// Link this [Program] against the given kernel during assembly - pub fn link_kernel(&mut self, kernel: KernelLibrary) { - self.library.link_kernel(kernel); - } - - /// Link this [Program] against the given library during assembly - pub fn link_library(&mut self, library: CompiledLibrary) { - self.library.link_library(library); - } - - /// Get the set of [CompiledLibrary] this program links against - pub fn link_libraries(&self) -> &[CompiledLibrary] { - self.library.link_libraries() - } - - /// Generate an executable module which when run expects the raw data segment data to be - /// provided on the advice stack in the same order as initialization, and the operands of - /// the entrypoint function on the operand stack. - fn generate_main(&self, entrypoint: FunctionIdent, emit_test_harness: bool) -> Box { - let mut exe = Box::new(Module::new(LibraryNamespace::Exec.into(), ModuleKind::Executable)); - let start_id = FunctionIdent { - module: Ident::with_empty_span(Symbol::intern(LibraryNamespace::EXEC_PATH)), - function: Ident::with_empty_span(Symbol::intern(ProcedureName::MAIN_PROC_NAME)), - }; - let start_sig = Signature::new([], []); - let mut start = Box::new(Function::new(start_id, start_sig)); - { - let body = start.body_mut(); - // Initialize dynamic heap - body.push(Op::PushU32(self.heap_base), SourceSpan::default()); - body.push( - Op::Exec("intrinsics::mem::heap_init".parse().unwrap()), - SourceSpan::default(), - ); - // Initialize data segments from advice stack - self.emit_data_segment_initialization(body); - // Possibly initialize test harness - if emit_test_harness { - self.emit_test_harness(body); - } - // Invoke the program entrypoint - body.push(Op::Exec(entrypoint), SourceSpan::default()); - } - exe.push_back(start); - exe - } - - fn emit_test_harness(&self, block: &mut Block) { - let span = SourceSpan::default(); - - // Advice Stack: [dest_ptr, num_words, ...] - block.push(Op::AdvPush(2), span); // => [num_words, dest_ptr] on operand stack - block.push(Op::Exec("std::mem::pipe_words_to_memory".parse().unwrap()), span); - // Drop HASH - block.push(Op::Dropw, span); - // Drop dest_ptr - block.push(Op::Drop, span); - } - - /// Emit the sequence of instructions necessary to consume rodata from the advice stack and - /// populate the global heap with the data segments of this program, verifying that the - /// commitments match. - fn emit_data_segment_initialization(&self, block: &mut Block) { - // Emit data segment initialization code - // - // NOTE: This depends on the program being executed with the data for all data - // segments having been placed in the advice map with the same commitment and - // encoding used here. The program will fail to execute if this is not set up - // correctly. - // - // TODO(pauls): To facilitate automation of this, we should emit an inputs file to - // disk that maps each segment to a commitment and its data encoded as binary. This - // can then be loaded into the advice provider during VM init. - let pipe_preimage_to_memory = "std::mem::pipe_preimage_to_memory".parse().unwrap(); - for rodata in self.library.rodata.iter() { - let span = SourceSpan::default(); - - // Move rodata from advice map to advice stack - block.push(Op::Pushw(rodata.digest.into()), span); // COM - block.push(Op::AdvInjectPushMapVal, span); - // write_ptr - block.push(Op::PushU32(rodata.start.waddr), span); - // num_words - block.push(Op::PushU32(rodata.size_in_words() as u32), span); - // [num_words, write_ptr, COM, ..] -> [write_ptr'] - block.push(Op::Exec(pipe_preimage_to_memory), span); - // drop write_ptr' - block.push(Op::Drop, span); - } - } - - #[inline(always)] - pub fn entrypoint(&self) -> FunctionIdent { - self.entrypoint - } - - #[inline(always)] - pub fn stack_pointer(&self) -> Option { - self.library.stack_pointer - } - - /// Freezes this program, preventing further modifications - pub fn freeze(mut self: Box) -> Arc { - self.library.modules.freeze(); - Arc::from(self) - } - - /// Get an iterator over the modules in this program - pub fn modules(&self) -> impl Iterator + '_ { - self.library.modules.iter() - } - - /// Access the frozen module tree of this program, and panic if not frozen - pub fn unwrap_frozen_modules(&self) -> &FrozenModuleTree { - self.library.unwrap_frozen_modules() - } - - /// Insert a module into this program. - /// - /// The insertion order is not preserved - modules are ordered by name. - /// - /// NOTE: This function will panic if the program has been frozen - pub fn insert(&mut self, module: Box) { - self.library.insert(module) - } - - /// Get a reference to a module in this program by name - pub fn get(&self, name: &Q) -> Option<&Module> - where - Q: ?Sized + Ord, - Ident: core::borrow::Borrow, - { - self.library.get(name) - } - - /// Returns true if this program contains a [Module] named `name` - pub fn contains(&self, name: N) -> bool - where - Ident: PartialEq, - { - self.library.contains(name) - } - - /// Write this [Program] to the given output directory. - pub fn write_to_directory>( - &self, - path: P, - session: &Session, - ) -> std::io::Result<()> { - let path = path.as_ref(); - assert!(path.is_dir()); - - self.library.write_to_directory(path, session)?; - - let main = self.generate_main(self.entrypoint, /* test_harness= */ false); - main.write_to_directory(path, session)?; - - Ok(()) - } - - // Assemble this program to MAST - pub fn assemble(&self, session: &Session) -> Result, Report> { - use miden_assembly::{Assembler, CompileOptions}; - - let debug_mode = session.options.emit_debug_decorators(); - - log::debug!( - "assembling executable with entrypoint '{}' (debug_mode={})", - self.entrypoint, - debug_mode - ); - let mut assembler = - Assembler::new(session.source_manager.clone()).with_debug_mode(debug_mode); - - // Link extra libraries - for library in self.library.libraries.iter() { - if log::log_enabled!(log::Level::Debug) { - for module in library.module_infos() { - log::debug!("registering '{}' with assembler", module.path()); - } - } - assembler.add_library(library)?; - } - - // Assemble library - for module in self.library.modules.iter() { - log::debug!("adding '{}' to assembler", module.id.as_str()); - let kind = module.kind; - let module = module.to_ast(debug_mode).map(Box::new)?; - assembler.add_module_with_options( - module, - CompileOptions { - kind, - warnings_as_errors: false, - path: None, - }, - )?; - } - - let emit_test_harness = session.get_flag("test_harness"); - let main = self.generate_main(self.entrypoint, emit_test_harness); - let main = main.to_ast(debug_mode).map(Box::new)?; - assembler.assemble_program(main).map(Arc::new) - } - - pub(crate) fn library(&self) -> &Library { - &self.library - } -} - -impl fmt::Display for Program { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.library, f) - } -} - -impl Emit for Program { - fn name(&self) -> Option { - None - } - - fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { - midenc_session::OutputType::Masm - } - - fn write_to( - &self, - mut writer: W, - mode: midenc_session::OutputMode, - _session: &Session, - ) -> std::io::Result<()> { - assert_eq!( - mode, - midenc_session::OutputMode::Text, - "binary mode is not supported for masm ir programs" - ); - writer.write_fmt(format_args!("{}\n", self)) - } -} - -/// A [Library] represents a set of modules and its dependencies, which are compiled/assembled -/// together into a single artifact, and then linked into a [Program] for execution at a later -/// time. -/// -/// Modules are stored in a [Library] in a B-tree map, keyed by the module name. This is done to -/// make accessing modules by name efficient, and to ensure a stable ordering for compiled programs -/// when emitted as text. -#[derive(Default, Clone)] -pub struct Library { - /// The set of modules which belong to this program - modules: Modules, - /// The set of libraries to link this program against - libraries: Vec, - /// The kernel library to link against - kernel: Option, - /// The rodata segments of this program keyed by the offset of the segment - rodata: Vec, - /// The address of the `__stack_pointer` global, if such a global has been defined - stack_pointer: Option, -} -impl Library { - /// Create a new, empty [Library] - pub fn empty() -> Self { - Self::default() - } - - /// Create a new [Library] initialized from an [hir::Program]. - /// - /// The resulting [Library] will have the following: - /// - /// * Data segments described by the original [hir::Program] - /// - /// None of the HIR modules will have been added yet - pub fn from_hir( - program: &hir::Program, - globals: &GlobalVariableAnalysis, - ) -> Self { - let stack_pointer = program.globals().find("__stack_pointer".parse().unwrap()); - let stack_pointer = if let Some(stack_pointer) = stack_pointer { - let global_table_offset = globals.layout().global_table_offset(); - Some(global_table_offset + unsafe { program.globals().offset_of(stack_pointer) }) - } else { - None - }; - let rodata = compute_rodata( - globals.layout().global_table_offset(), - program.globals(), - program.segments(), - ); - Self { - modules: Modules::default(), - libraries: vec![], - kernel: None, - rodata, - stack_pointer, - } - } - - pub fn rodatas(&self) -> &[Rodata] { - self.rodata.as_slice() - } - - /// Link this [Library] against the given kernel during assembly - pub fn link_kernel(&mut self, kernel: KernelLibrary) { - self.kernel = Some(kernel); - } - - /// Link this [Library] against the given library during assembly - pub fn link_library(&mut self, library: CompiledLibrary) { - self.libraries.push(library); - } - - /// Get the set of [CompiledLibrary] this library links against - pub fn link_libraries(&self) -> &[CompiledLibrary] { - self.libraries.as_slice() - } - - /// Freezes this library, preventing further modifications - pub fn freeze(mut self: Box) -> Arc { - self.modules.freeze(); - Arc::from(self) - } - - /// Get an iterator over the modules in this library - pub fn modules(&self) -> impl Iterator + '_ { - self.modules.iter() - } - - /// Access the frozen module tree of this library, and panic if not frozen - pub fn unwrap_frozen_modules(&self) -> &FrozenModuleTree { - match self.modules { - Modules::Frozen(ref modules) => modules, - Modules::Open(_) => panic!("expected program to be frozen"), - } - } - - /// Insert a module into this library. - /// - /// The insertion order is not preserved - modules are ordered by name. - /// - /// NOTE: This function will panic if the program has been frozen - pub fn insert(&mut self, module: Box) { - self.modules.insert(module); - } - - /// Get a reference to a module in this library by name - pub fn get(&self, name: &Q) -> Option<&Module> - where - Q: ?Sized + Ord, - Ident: core::borrow::Borrow, - { - self.modules.get(name) - } - - /// Returns true if this library contains a [Module] named `name` - pub fn contains(&self, name: N) -> bool - where - Ident: PartialEq, - { - self.modules.iter().any(|m| m.id == name) - } - - /// Write this [Library] to the given output directory. - pub fn write_to_directory>( - &self, - path: P, - session: &Session, - ) -> std::io::Result<()> { - let path = path.as_ref(); - assert!(path.is_dir()); - - for module in self.modules.iter() { - module.write_to_directory(path, session)?; - } - - Ok(()) - } - - // Assemble this library to MAST - pub fn assemble(&self, session: &Session) -> Result, Report> { - use miden_assembly::Assembler; - - let debug_mode = session.options.emit_debug_decorators(); - log::debug!( - "assembling library of {} modules (debug_mode={})", - self.modules().count(), - debug_mode - ); - - let mut assembler = - Assembler::new(session.source_manager.clone()).with_debug_mode(debug_mode); - - // Link extra libraries - for library in self.libraries.iter() { - if log::log_enabled!(log::Level::Debug) { - for module in library.module_infos() { - log::debug!("registering '{}' with assembler", module.path()); - } - } - assembler.add_library(library)?; - } - - // Assemble library - let mut modules = Vec::with_capacity(self.modules.len()); - for module in self.modules.iter() { - log::debug!("adding '{}' to assembler", module.id.as_str()); - let module = module.to_ast(debug_mode).map(Box::new)?; - modules.push(module); - } - assembler.assemble_library(modules).map(Arc::new) - } -} - -impl fmt::Display for Library { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for module in self.modules.iter() { - // Don't print intrinsic modules - if module.id.as_str().starts_with("intrinsics::") { - continue; - } - if ["intrinsics", "std"].contains(&module.name.namespace().as_str()) { - // Skip printing the standard library modules and intrinsics - // modules to focus on the user-defined modules and avoid the - // stack overflow error when printing large programs - // https://github.com/0xPolygonMiden/miden-formatting/issues/4 - continue; - } else { - writeln!(f, "# mod {}\n", &module.name)?; - writeln!(f, "{}", module)?; - } - } - Ok(()) - } -} - -impl Emit for Library { - fn name(&self) -> Option { - None - } - - fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { - midenc_session::OutputType::Masm - } - - fn write_to( - &self, - mut writer: W, - mode: midenc_session::OutputMode, - _session: &Session, - ) -> std::io::Result<()> { - assert_eq!( - mode, - midenc_session::OutputMode::Text, - "binary mode is not supported for masm ir libraries" - ); - writer.write_fmt(format_args!("{}\n", self)) - } -} - -/// Compute the metadata for each non-empty rodata segment in the program. -/// -/// This consists of the data itself, as well as a content digest, which will be used to place -/// that data in the advice map when the program starts. -fn compute_rodata( - global_table_offset: u32, - globals: &GlobalVariableTable, - segments: &DataSegmentTable, -) -> Vec { - let mut rodatas = Vec::with_capacity(segments.iter().count() + 1); - - // Convert global variable initializers to a data segment, and place it at the computed - // global table offset in linear memory. - let extra = if !globals.is_empty() { - let size = globals.size_in_bytes(); - let offset = global_table_offset; - let mut data = vec![0; size]; - for gv in globals.iter() { - if let Some(init) = gv.initializer() { - let offset = unsafe { globals.offset_of(gv.id()) } as usize; - let init = globals.get_constant(init); - let init_bytes = init.as_slice(); - assert!(offset + init_bytes.len() <= data.len()); - let dst = &mut data[offset..(offset + init_bytes.len())]; - dst.copy_from_slice(init_bytes); - } - } - // Don't bother emitting anything for zeroed segments - if data.iter().any(|&b| b != 0) { - Some((size as u32, offset, Arc::new(midenc_hir::ConstantData::from(data)))) - } else { - None - } - } else { - None - }; - - // Process all segments, ignoring zeroed segments (as Miden's memory is always zeroed) - for (size, offset, segment_data) in segments - .iter() - .filter_map(|segment| { - if segment.is_zeroed() { - None - } else { - Some((segment.size(), segment.offset(), segment.init())) - } - }) - .chain(extra) - { - let base = NativePtr::from_ptr(offset); - - // TODO(pauls): Do we ever have a need for data segments which are not aligned - // to an word boundary? If so, we need to implement that - // support when emitting the entry for a program - assert_eq!( - base.offset, - 0, - "unsupported data segment alignment {}: must be aligned to a 32 byte boundary", - base.alignment() - ); - assert_eq!( - base.index, - 0, - "unsupported data segment alignment {}: must be aligned to a 32 byte boundary", - base.alignment() - ); - - // Compute the commitment for the data - let num_elements = (size.next_multiple_of(4) / 4) as usize; - let num_words = num_elements.next_multiple_of(4) / 4; - let padding = (num_words * 4).abs_diff(num_elements); - let mut elements = Vec::with_capacity(num_elements + padding); - // TODO(pauls): If the word containing the first element overlaps with the - // previous segment, then ensure the overlapping elements - // are mixed together, so that the data is preserved, and - // the commitment is correct - let mut iter = segment_data.as_slice().iter().copied().array_chunks::<4>(); - elements.extend(iter.by_ref().map(|bytes| Felt::new(u32::from_le_bytes(bytes) as u64))); - if let Some(remainder) = iter.into_remainder() { - let mut chunk = [0u8; 4]; - for (i, byte) in remainder.into_iter().enumerate() { - chunk[i] = byte; - } - elements.push(Felt::new(u32::from_le_bytes(chunk) as u64)); - } - elements.resize(num_elements + padding, Felt::ZERO); - let digest = Rpo256::hash_elements(&elements); - - log::debug!( - "computed commitment for data segment at offset {offset} ({size} bytes, \ - {num_elements} elements): '{digest}'" - ); - - rodatas.push(Rodata { - digest, - start: base, - data: segment_data, - }); - } - - rodatas -} diff --git a/codegen/masm/src/masm/region.rs b/codegen/masm/src/masm/region.rs deleted file mode 100644 index c710469cd..000000000 --- a/codegen/masm/src/masm/region.rs +++ /dev/null @@ -1,236 +0,0 @@ -use std::{collections::BTreeSet, fmt}; - -use cranelift_entity::PrimaryMap; -use miden_assembly::ast; -use midenc_hir::{diagnostics::Span, formatter::PrettyPrint, FunctionIdent, Ident}; -use smallvec::smallvec; - -use super::*; -use crate::InstructionPointer; - -/// This struct represents a region of code in Miden Assembly. -/// -/// A region is a tree of blocks with isolated scope. In many -/// ways a [Region] is basically a [Function], just without any -/// identity. Additionally, a [Region] does not have local variables, -/// those must be provided by a parent [Function]. -/// -/// In short, this represents both the body of a function, and the -/// body of a `begin` block in Miden Assembly. -#[derive(Debug, Clone)] -pub struct Region { - pub body: BlockId, - pub blocks: PrimaryMap, -} -impl Default for Region { - fn default() -> Self { - let mut blocks = PrimaryMap::::default(); - let id = blocks.next_key(); - let body = blocks.push(Block { - id, - ops: smallvec![], - }); - Self { body, blocks } - } -} -impl Region { - /// Get the [BlockId] for the block which forms the body of this region - #[inline(always)] - pub const fn id(&self) -> BlockId { - self.body - } - - /// Get a reference to a [Block] by [BlockId] - #[inline] - pub fn block(&self, id: BlockId) -> &Block { - &self.blocks[id] - } - - /// Get a mutable reference to a [Block] by [BlockId] - #[inline] - pub fn block_mut(&mut self, id: BlockId) -> &mut Block { - &mut self.blocks[id] - } - - /// Get the instruction under `ip`, if valid - pub fn get(&self, ip: InstructionPointer) -> Option> { - self.blocks[ip.block].ops.get(ip.index).copied() - } - - /// Allocate a new code block in this region - pub fn create_block(&mut self) -> BlockId { - let id = self.blocks.next_key(); - self.blocks.push(Block { - id, - ops: smallvec![], - }); - id - } - - /// Render the code in this region as Miden Assembly, at the specified indentation level (in - /// units of 4 spaces) - pub fn display<'a, 'b: 'a>( - &'b self, - function: Option, - imports: &'b ModuleImportInfo, - ) -> DisplayRegion<'a> { - DisplayRegion { - region: self, - function, - imports, - } - } - - /// Convert this [Region] to a [miden_assembly::ast::Block] using the provided - /// local/external function maps to handle calls present in the body of the region. - pub fn to_block( - &self, - imports: &ModuleImportInfo, - locals: &BTreeSet, - ) -> ast::Block { - emit_block(self.body, &self.blocks, imports, locals) - } - - /// Create a [Region] from a [miden_assembly::ast::CodeBody] and the set of imports - /// and local procedures which will be used to map references to procedures to their - /// fully-qualified names. - pub fn from_block(current_module: Ident, code: &ast::Block) -> Self { - let mut region = Self::default(); - - let body = region.body; - import_block(current_module, &mut region, body, code); - - region - } -} -impl core::ops::Index for Region { - type Output = Op; - - #[inline] - fn index(&self, ip: InstructionPointer) -> &Self::Output { - &self.blocks[ip.block].ops[ip.index] - } -} - -#[doc(hidden)] -pub struct DisplayRegion<'a> { - region: &'a Region, - function: Option, - imports: &'a ModuleImportInfo, -} -impl<'a> midenc_hir::formatter::PrettyPrint for DisplayRegion<'a> { - fn render(&self) -> midenc_hir::formatter::Document { - use midenc_hir::DisplayMasmBlock; - - let block = DisplayMasmBlock::new( - self.function, - Some(self.imports), - &self.region.blocks, - self.region.body, - ); - - block.render() - } -} -impl<'a> fmt::Display for DisplayRegion<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} - -/// Import code from a [miden_assembly::ast::Block] into the specified [Block] in `region`. -fn import_block( - current_module: Ident, - region: &mut Region, - current_block_id: BlockId, - block: &ast::Block, -) { - for op in block.iter() { - match op { - ast::Op::Inst(ix) => { - let span = ix.span(); - let current_block = region.block_mut(current_block_id); - let ops = Op::from_masm(current_module, (**ix).clone()); - current_block.extend(ops.into_iter().map(|op| Span::new(span, op))); - } - ast::Op::If { - span, - ref then_blk, - ref else_blk, - .. - } => { - let then_blk_id = region.create_block(); - let else_blk_id = region.create_block(); - import_block(current_module, region, then_blk_id, then_blk); - import_block(current_module, region, else_blk_id, else_blk); - region.block_mut(current_block_id).push(Op::If(then_blk_id, else_blk_id), *span); - } - ast::Op::Repeat { - span, - count, - ref body, - .. - } => { - let body_blk = region.create_block(); - import_block(current_module, region, body_blk, body); - let count = u16::try_from(*count).unwrap_or_else(|_| { - panic!("invalid repeat count: expected {count} to be less than 255") - }); - region.block_mut(current_block_id).push(Op::Repeat(count, body_blk), *span); - } - ast::Op::While { span, ref body, .. } => { - let body_blk = region.create_block(); - import_block(current_module, region, body_blk, body); - region.block_mut(current_block_id).push(Op::While(body_blk), *span); - } - } - } -} - -/// Emit a [miden_assembly::ast::CodeBlock] by recursively visiting a tree of blocks -/// starting with `block_id`, using the provided imports and local/external procedure maps. -#[allow(clippy::only_used_in_recursion)] -fn emit_block( - block_id: BlockId, - blocks: &PrimaryMap, - imports: &ModuleImportInfo, - locals: &BTreeSet, -) -> ast::Block { - let current_block = &blocks[block_id]; - let mut ops = Vec::with_capacity(current_block.ops.len()); - for op in current_block.ops.iter().copied() { - let span = op.span(); - match op.into_inner() { - Op::If(then_blk, else_blk) => { - let then_blk = emit_block(then_blk, blocks, imports, locals); - let else_blk = emit_block(else_blk, blocks, imports, locals); - ops.push(ast::Op::If { - span, - then_blk, - else_blk, - }); - } - Op::While(blk) => { - let body = emit_block(blk, blocks, imports, locals); - ops.push(ast::Op::While { span, body }); - } - Op::Repeat(n, blk) => { - let body = emit_block(blk, blocks, imports, locals); - ops.push(ast::Op::Repeat { - span, - count: n as u32, - body, - }); - } - op => { - ops.extend( - op.into_masm(imports, locals) - .into_iter() - .map(|inst| ast::Op::Inst(Span::new(span, inst))), - ); - } - } - } - - ast::Block::new(Default::default(), ops) -} diff --git a/codegen/masm/src/codegen/opt/mod.rs b/codegen/masm/src/opt/mod.rs similarity index 100% rename from codegen/masm/src/codegen/opt/mod.rs rename to codegen/masm/src/opt/mod.rs diff --git a/codegen/masm/src/opt/operands/context.rs b/codegen/masm/src/opt/operands/context.rs new file mode 100644 index 000000000..d49da1bba --- /dev/null +++ b/codegen/masm/src/opt/operands/context.rs @@ -0,0 +1,184 @@ +use core::num::NonZeroU8; + +use midenc_hir::{self as hir, hashbrown, FxHashMap}; +use smallvec::SmallVec; + +use super::{SolverError, SolverOptions, Stack, ValueOrAlias}; +use crate::{Constraint, OperandStack}; + +/// The context associated with an instance of [OperandMovementConstraintSolver]. +/// +/// Contained in this context is the current state of the stack, the expected operands, whether the +/// expected operands may be out of order, the constraints on those operands, and metadata about +/// copied operands. +#[derive(Debug)] +pub struct SolverContext { + options: SolverOptions, + stack: Stack, + expected: Stack, + copies: CopyInfo, +} +impl SolverContext { + pub fn new( + expected: &[hir::ValueRef], + constraints: &[Constraint], + stack: &OperandStack, + options: SolverOptions, + ) -> Result { + // Compute the expected output on the stack, as well as alias/copy information + let stack = Stack::from(stack); + let mut expected_output = Stack::default(); + let mut copies = CopyInfo::default(); + for (value, constraint) in expected.iter().rev().zip(constraints.iter().rev()) { + let value = ValueOrAlias::from(*value); + match constraint { + // If we observe a value with move semantics, then it is always referencing the + // original value + Constraint::Move => { + expected_output.push(value); + } + // If we observe a value with copy semantics, then the expected output is always an + // alias, because the original needs to be preserved. + // + // NOTE: Just because an expected operand has a copy constraint applied, does not + // mean that there aren't multiple copies on the input stack already - it indicates + // that all of those copies must be preserved, and a _new_ copy must be materialized + // by the solution we produce. + Constraint::Copy => { + expected_output.push(copies.push(value)); + } + } + } + + // Determine if the stack is already in the desired order + let context = Self { + options, + stack, + expected: expected_output, + copies, + }; + let is_solved = !context.copies.copies_required() + && context.stack.len() >= context.expected.len() + && context.is_solved(&context.stack); + if is_solved { + return Err(SolverError::AlreadySolved); + } + + Ok(context) + } + + /// Access the current [SolverOptions] + #[allow(unused)] + #[inline(always)] + pub fn options(&self) -> &SolverOptions { + &self.options + } + + /// Returns the number of operands expected by the current instruction + #[inline] + pub fn arity(&self) -> usize { + self.expected.len() + } + + /// Get a reference to the copy analysis results + #[inline(always)] + pub fn copies(&self) -> &CopyInfo { + &self.copies + } + + /// Get a reference to the state of the stack at the current program point + #[inline(always)] + pub fn stack(&self) -> &Stack { + &self.stack + } + + /// Get a [Stack] representing the state of the stack for a valid solution. + /// + /// NOTE: The returned stack only contains the expected operands, not the full stack + #[inline(always)] + pub fn expected(&self) -> &Stack { + &self.expected + } + + /// Returns true if the current solver requires a strict solution + #[inline(always)] + pub fn is_strict(&self) -> bool { + self.options.strict + } + + /// Return true if the given stack matches what is expected + /// if a solution was correctly found. + pub fn is_solved(&self, pending: &Stack) -> bool { + debug_assert!(pending.len() >= self.expected.len()); + + let is_strict_solution = + self.expected.iter().rev().eq(pending.iter().rev().take(self.expected.len())); + + is_strict_solution || (!self.is_strict() && self.is_non_strict_solution(pending)) + } + + /// Return whether all of the expected operands are at the top of the pending stack but in any + /// order. + fn is_non_strict_solution(&self, pending: &Stack) -> bool { + let mut expected = self.expected.iter().rev().collect::>(); + let mut pending = pending.iter().rev().take(expected.len()).collect::>(); + + expected.sort(); + pending.sort(); + + expected == pending + } +} + +#[derive(Debug, Default)] +pub struct CopyInfo { + copies: FxHashMap, + num_copies: u8, +} +impl CopyInfo { + /// Returns the number of copies recorded in this structure + #[inline(always)] + pub fn len(&self) -> usize { + self.num_copies as usize + } + + /// Returns true if there are no copied values + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.num_copies == 0 + } + + /// Push a new copy of `value`, returning an alias of that value + /// + /// NOTE: It is expected that `value` is not an alias. + pub fn push(&mut self, value: ValueOrAlias) -> ValueOrAlias { + use hashbrown::hash_map::Entry; + + assert!(!value.is_alias()); + + self.num_copies += 1; + match self.copies.entry(value) { + Entry::Vacant(entry) => { + entry.insert(1); + value.copy(unsafe { NonZeroU8::new_unchecked(1) }) + } + Entry::Occupied(mut entry) => { + let next_id = entry.get_mut(); + *next_id += 1; + value.copy(unsafe { NonZeroU8::new_unchecked(*next_id) }) + } + } + } + + /// Returns true if `value` has at least one copy + pub fn has_copies(&self, value: &ValueOrAlias) -> bool { + // Make sure we're checking for copies of the unaliased value + let value = value.unaliased(); + self.copies.get(&value).map(|count| *count > 0).unwrap_or(false) + } + + /// Returns true if any of the values seen so far have copies + pub fn copies_required(&self) -> bool { + self.copies.values().any(|count| *count > 0) + } +} diff --git a/codegen/masm/src/opt/operands/mod.rs b/codegen/masm/src/opt/operands/mod.rs new file mode 100644 index 000000000..eb4f6eb76 --- /dev/null +++ b/codegen/masm/src/opt/operands/mod.rs @@ -0,0 +1,30 @@ +mod context; +mod solver; +mod stack; +mod tactics; +#[cfg(test)] +mod testing; + +pub use midenc_hir::{StackOperand as Operand, ValueOrAlias}; + +pub use self::solver::{OperandMovementConstraintSolver, SolverError, SolverOptions}; +use self::{context::SolverContext, stack::Stack}; + +/// This represents a specific action that should be taken by +/// the code generator with regard to an operand on the stack. +/// +/// The output of the optimizer is a sequence of these actions, +/// the effect of which is to place all of the current instruction's +/// operands exactly where they need to be, just when they are +/// needed. +#[derive(Debug, Copy, Clone)] +pub enum Action { + /// Copy the operand at the given index to the top of the stack + Copy(u8), + /// Swap the operand at the given index with the one on top of the stack + Swap(u8), + /// Move the operand at the given index to the top of the stack + MoveUp(u8), + /// Move the operand at the top of the stack to the given index + MoveDown(u8), +} diff --git a/codegen/masm/src/opt/operands/solver.rs b/codegen/masm/src/opt/operands/solver.rs new file mode 100644 index 000000000..92a1986f3 --- /dev/null +++ b/codegen/masm/src/opt/operands/solver.rs @@ -0,0 +1,559 @@ +use midenc_hir::{self as hir, SourceSpan}; +use smallvec::SmallVec; + +use super::{tactics::Tactic, *}; +use crate::Constraint; + +/// This error type is produced by the [OperandMovementConstraintSolver] +#[derive(Debug)] +pub enum SolverError { + /// The current operand stack represents a valid solution already + AlreadySolved, + /// All of the tactics we tried failed + NoSolution, +} + +/// Configures the behavior of the [OperandMovementConstraintSolver]. +#[derive(Debug, Copy, Clone)] +pub struct SolverOptions { + /// This flag conveys to the solver that the solution(s) it computes must adhere strictly to + /// the order of the expected operands provided to the solver. + /// + /// When `false`, the solver is only required to ensure that the solution(s) it computes place + /// the expected operands provided to it on top of the operand stack; however, the order of + /// operands in the solution does not matter. + /// + /// This flag defaults to true. + /// + /// WARNING: Setting this to `false` is only useful when producing an operand schedule for + /// commutative instructions, where we don't care/need to know the order of the operands, as + /// the instruction semantics are equivalent in any permutation. Using non-strict operation in + /// any other use case is likely to lead to miscompilation. + pub strict: bool, + /// An integer representing the amount of optimization fuel we have available + /// + /// The more fuel, the more effort will be spent attempting to find an optimal solution. + pub fuel: usize, +} + +impl Default for SolverOptions { + fn default() -> Self { + Self { + strict: true, + fuel: 25, + } + } +} + +/// The [OperandMovementConstraintSolver] is used to produce a solution to the following problem: +/// +/// An instruction is being emitted which requires some specific set of operands, in a particular +/// order. These operands are known to be on the operand stack, but their usage is constrained by a +/// rule that determines whether a specific use of an operand can consume the operand, or must copy +/// it and consume the copy. Furthermore, the operands on the stack are not guaranteed to be in the +/// desired order, so we must also move operands into position while operating within the bounds of +/// the move/copy constraints. +/// +/// Complicating matters further, a naive approach to solving this problem will produce a lot of +/// unnecessary stack manipulation instructions in the emitted code. We would like the code we emit +/// to match what a human might write if facing the same set of constraints. As a result, we are +/// looking for a solution to this problem that is also the "smallest" solution, i.e. the least +/// expensive solution in terms of cycle count. +/// +/// ## Implementation +/// +/// With that context in mind, what we have here is a non-trivial optimization problem. If we could +/// treat the operand stack as an array, and didn't have to worry about copies, we could solve this +/// using a standard minimum-swap solution, but neither of those are true here. The copy constraint, +/// when present, means that even if the stack is in the exact order we need, we must still find a +/// way to copy the operands we are required to copy, move the ones we are required to consume, and +/// do so in such a way that getting them into the required order on top of the stack takes the +/// minimum number of steps. +/// +/// Even this would be relatively straightforward, but an additional problem is that the MASM +/// instruction set does not provide us a way to swap two operands at arbitrary positions on the +/// stack. We are forced to move operands to the top of the stack before we can move them elsewhere +/// (either by swapping them with the current operand on top of the stack, or by moving the operand +/// up to the top, shifting all the remaining operands on the stack down by one). However, moving a +/// value up/down the stack also has the effect of shifting other values on the stack, which may +/// shift them in to, or out of, position. +/// +/// Long story short, all of this must be taken into consideration at once, which is extremely +/// difficult to express in a way that is readable/maintainable, but also debuggable if something +/// goes wrong. +/// +/// To address these concerns, the [OperandMovementConstraintSolver] is architected as follows: +/// +/// We expect to receive as input to the solver: +/// +/// * The set of expected operand values +/// * The set of move/copy constraints corresponding to each of the expected operands +/// * The current state of the operand stack at this point in the program +/// +/// The solver produces one of three possible outcomes: +/// +/// * `Ok(solution)`, where `solution` is a vector of actions the code generator must take to get +/// the operands into place correctly +/// * `Err(AlreadySolved)`, indicating that the solver is not needed, and the stack is usable as-is +/// * `Err(_)`, indicating an unrecoverable error that prevented the solver from finding a solution +/// with the given inputs +/// +/// When the solver is constructed, it performs the following steps: +/// +/// 1. Identify and rename aliased values to make them unique (i.e. multiple uses of the same value +/// will be uniqued) +/// 2. Determine if any expected operands require copying (if so, then the solver is always +/// required) +/// 3. Determine if the solver is required for the given inputs, and if not, return +/// `Err(AlreadySolved)` +/// +/// When the solver is run, it attempts to find an optimal solution using the following algorithm: +/// +/// 1. Pick a tactic to try and produce a solution for the given set of constraints. +/// 2. If the tactic failed, go back to step 1. +/// 3. If the tactic succeeded, take the best solution between the one we just produced, and the +/// last one produced (if applicable). +/// 4. If we have optimization fuel remaining, go back to step 1 and see if we can find a better +/// solution. +/// 5. If we have a solution, and either run out of optimization fuel, or tactics to try, then that +/// solution is returned. +/// 6. If we haven't found a solution, then return an error +pub struct OperandMovementConstraintSolver { + context: SolverContext, + tactics: SmallVec<[Box; 4]>, + fuel: usize, +} +impl OperandMovementConstraintSolver { + /// Construct a new solver for the given expected operands, constraints, and operand stack + /// state. + pub fn new( + expected: &[hir::ValueRef], + constraints: &[Constraint], + stack: &crate::OperandStack, + ) -> Result { + Self::new_with_options(expected, constraints, stack, Default::default()) + } + + /// Same as [Self::new], but takes [SolverOptions] to customize the behavior of the solver. + pub fn new_with_options( + expected: &[hir::ValueRef], + constraints: &[Constraint], + stack: &crate::OperandStack, + options: SolverOptions, + ) -> Result { + assert_eq!(expected.len(), constraints.len()); + + let fuel = options.fuel; + let context = SolverContext::new(expected, constraints, stack, options)?; + + Ok(Self { + context, + tactics: Default::default(), + fuel, + }) + } + + /// Compute a solution that can be used to get the stack into the correct state + pub fn solve(mut self) -> Result, SolverError> { + use super::tactics::*; + + // We use a few heuristics to guide which tactics we try: + // + // * If all operands are copies, we only apply copy-all + // * If copies are needed, we only apply tactics which support copies, or a mix of copies + // and moves. + // * If no copies are needed, we start with the various move up/down + swap patterns, as + // many common patterns are solved in two moves or less with them. If no tactics are + // successful, move-all is used as the fallback. + // * If we have no optimization fuel, we do not attempt to look for better solutions once + // we've found one. + // * If we have optimization fuel, we will try additional tactics looking for a solution + // until we have exhausted the fuel, assuming the solution we do have can be minimized. + // For example, a solution which requires less than two actions is by definition optimal + // already, so we never waste time on optimization in such cases. + + // The tactics are pushed in reverse order + if self.tactics.is_empty() { + let is_binary = self.context.arity() == 2; + if is_binary { + self.tactics.push(Box::new(TwoArgs)); + } else if self.context.copies().is_empty() { + self.tactics.push(Box::new(Linear)); + self.tactics.push(Box::new(SwapAndMoveUp)); + self.tactics.push(Box::new(MoveUpAndSwap)); + self.tactics.push(Box::new(MoveDownAndSwap)); + } else { + self.tactics.push(Box::new(Linear)); + self.tactics.push(Box::new(CopyAll)); + } + } + + // Now that we know what constraints are in place, we can derive + // a strategy to solve for those constraints. The overall strategy + // is a restricted backtracking search based on a number of predefined + // tactics for permuting the stack. The search is restricted because + // we do not try every possible combination of tactics, and instead + // follow a shrinking strategy that always subdivides the problem if + // a larger tactic doesn't succeed first. The search proceeds until + // a solution is derived, or we cannot proceed any further, in which + // case we fall back to the most naive approach possible - copying + // items to the top of the stack one after another until all arguments + // are in place. + // + // Some tactics are derived simply by the number of elements involved, + // others based on the fact that all copies are required, or all moves. + // Many solutions are trivially derived from a given set of constraints, + // we aim simply to recognize common patterns recognized by a human and + // apply those solutions in such a way that we produce code like we would + // by hand when preparing instruction operands + let mut best_solution: Option> = None; + let mut builder = SolutionBuilder::new(&self.context); + while let Some(mut tactic) = self.tactics.pop() { + match tactic.apply(&mut builder) { + // The tactic was applied successfully + Ok(_) => { + if builder.is_valid() { + let solution = builder.take(); + let solution_size = solution.len(); + let best_size = best_solution.as_ref().map(|best| best.len()); + match best_size { + Some(best_size) if best_size > solution_size => { + best_solution = Some(solution); + log::trace!( + "a better solution ({solution_size} vs {best_size}) was found \ + using tactic {}", + tactic.name() + ); + } + Some(best_size) => { + log::trace!( + "a solution of size {solution_size} was found using tactic \ + {}, but it is no better than the best found so far \ + ({best_size})", + tactic.name() + ); + } + None => { + best_solution = Some(solution); + log::trace!( + "an initial solution of size {solution_size} was found using \ + tactic {}", + tactic.name() + ); + } + } + } else { + log::trace!( + "a partial solution was found using tactic {}, but is not sufficient \ + on its own", + tactic.name() + ); + builder.discard(); + } + } + Err(_) => { + log::trace!("tactic {} could not be applied", tactic.name()); + builder.discard(); + } + } + let remaining_fuel = self.fuel.saturating_sub(tactic.cost(&self.context)); + if remaining_fuel == 0 { + log::trace!("no more optimization fuel, using the best solution found so far"); + break; + } + self.fuel = remaining_fuel; + } + + best_solution.take().ok_or(SolverError::NoSolution) + } + + #[cfg(test)] + pub fn solve_with_tactic( + self, + ) -> Result>, SolverError> { + use super::tactics::*; + + let mut builder = SolutionBuilder::new(&self.context); + let mut tactic = ::default(); + match tactic.apply(&mut builder) { + // The tactic was applied successfully + Ok(_) => { + if builder.is_valid() { + Ok(Some(builder.take())) + } else { + log::trace!( + "a partial solution was found using tactic {}, but is not sufficient on \ + its own", + tactic.name() + ); + Ok(None) + } + } + Err(_) => { + log::trace!("tactic {} could not be applied", tactic.name()); + Err(SolverError::NoSolution) + } + } + } + + pub fn solve_and_apply( + self, + emitter: &mut crate::emit::OpEmitter<'_>, + span: SourceSpan, + ) -> Result<(), SolverError> { + match self.context.arity() { + // No arguments, nothing to solve + 0 => Ok(()), + // Only one argument, solution is trivial + 1 => { + let expected = self.context.expected()[0]; + if let Some(current_position) = self.context.stack().position(&expected) { + if current_position > 0 { + emitter.move_operand_to_position(current_position, 0, false, span); + } + } else { + assert!( + self.context.copies().has_copies(&expected.unaliased()), + "{:?} was not found on the operand stack copies", + expected.unaliased() + ); + let current_position = + self.context.stack().position(&expected.unaliased()).unwrap_or_else(|| { + panic!("{:?} was not found on the operand stack", expected.unaliased()) + }); + emitter.copy_operand_to_position(current_position, 0, false, span); + } + + Ok(()) + } + // Run the solver for more than 1 argument + _ => { + let actions = self.solve()?; + for action in actions.into_iter() { + match action { + Action::Copy(index) => { + emitter.copy_operand_to_position(index as usize, 0, false, span); + } + Action::Swap(index) => { + emitter.swap(index, span); + } + Action::MoveUp(index) => { + emitter.movup(index, span); + } + Action::MoveDown(index) => { + emitter.movdn(index, span); + } + } + } + + Ok(()) + } + } + } +} + +#[cfg(test)] +mod tests { + use alloc::rc::Rc; + + use midenc_hir::{self as hir, Type}; + use proptest::prelude::*; + + use super::{super::testing, *}; + + #[test] + fn operand_movement_constraint_solver_example() { + use hir::Context; + + let context = Rc::new(Context::default()); + + let block = context.create_block_with_params(vec![Type::I32; 6]); + let block = block.borrow(); + let block_args = block.arguments(); + let v1 = block_args[0] as hir::ValueRef; + let v2 = block_args[1] as hir::ValueRef; + let v3 = block_args[2] as hir::ValueRef; + let v4 = block_args[3] as hir::ValueRef; + let v5 = block_args[4] as hir::ValueRef; + let v6 = block_args[5] as hir::ValueRef; + + let tests = [[v2, v1, v3, v4, v5, v6], [v2, v4, v3, v1, v5, v6]]; + + for test in tests.into_iter() { + let mut stack = crate::OperandStack::default(); + for value in test.into_iter().rev() { + stack.push(value); + } + let expected = [v1, v2, v3, v4, v5]; + let constraints = [Constraint::Move; 5]; + + match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { + Ok(solver) => { + let result = solver.solve().expect("no solution found"); + assert!(result.len() <= 3, "expected solution of 3 moves or less"); + } + Err(SolverError::AlreadySolved) => panic!("already solved"), + Err(err) => panic!("invalid solver context: {err:?}"), + } + } + } + + #[test] + fn operand_movement_constraint_solver_two_moves() { + use hir::Context; + + let context = Rc::new(Context::default()); + + let block = context.create_block_with_params(vec![Type::I32; 6]); + let block = block.borrow(); + let block_args = block.arguments(); + let v1 = block_args[0] as hir::ValueRef; + let v2 = block_args[1] as hir::ValueRef; + let v3 = block_args[2] as hir::ValueRef; + let v4 = block_args[3] as hir::ValueRef; + let v5 = block_args[4] as hir::ValueRef; + let v6 = block_args[5] as hir::ValueRef; + + // Should take two moves + let tests = [ + [v5, v4, v2, v3, v1, v6], + [v4, v5, v1, v2, v3, v6], + [v5, v2, v1, v3, v4, v6], + [v1, v3, v2, v4, v5, v6], + [v5, v2, v1, v4, v3, v6], + [v1, v3, v4, v2, v5, v6], + [v4, v3, v2, v1, v5, v6], + [v4, v3, v2, v1, v5, v6], + ]; + + for test in tests.into_iter() { + let mut stack = crate::OperandStack::default(); + for value in test.into_iter().rev() { + stack.push(value); + } + let expected = [v1, v2, v3, v4, v5]; + let constraints = [Constraint::Move; 5]; + + match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { + Ok(solver) => { + let result = solver.solve().expect("no solution found"); + assert!( + result.len() <= 2, + "expected solution of 2 moves or less, got {result:?}" + ); + } + Err(SolverError::AlreadySolved) => panic!("already solved"), + Err(err) => panic!("invalid solver context: {err:?}"), + } + } + } + + #[test] + fn operand_movement_constraint_solver_one_move() { + use hir::Context; + + let context = Rc::new(Context::default()); + + let block = context.create_block_with_params(vec![Type::I32; 6]); + let block = block.borrow(); + let block_args = block.arguments(); + let v1 = block_args[0] as hir::ValueRef; + let v2 = block_args[1] as hir::ValueRef; + let v3 = block_args[2] as hir::ValueRef; + let v4 = block_args[3] as hir::ValueRef; + let v5 = block_args[4] as hir::ValueRef; + let v6 = block_args[5] as hir::ValueRef; + + // Should take one move + let tests = [ + [v2, v3, v1, v4, v5, v6], + [v4, v1, v2, v3, v5, v6], + [v4, v2, v3, v1, v5, v6], + [v2, v1, v3, v4, v5, v6], + ]; + + for test in tests.into_iter() { + let mut stack = crate::OperandStack::default(); + for value in test.into_iter().rev() { + stack.push(value); + } + let expected = [v1, v2, v3, v4, v5]; + let constraints = [Constraint::Move; 5]; + + match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { + Ok(solver) => { + let result = solver.solve().expect("no solution found"); + assert!( + result.len() <= 1, + "expected solution of 1 move or less, got {result:?}" + ); + } + Err(SolverError::AlreadySolved) => panic!("already solved"), + Err(err) => panic!("invalid solver context: {err:?}"), + } + } + } + + /// This test reproduces https://github.com/0xMiden/compiler/issues/200 + /// where v7 value should be duplicated on the stack + #[test] + fn operand_movement_constraint_solver_duplicate() { + use hir::Context; + + testing::logger_setup(); + + let context = Rc::new(Context::default()); + + let block = context.create_block_with_params(vec![Type::I32; 6]); + let block = block.borrow(); + let block_args = block.arguments(); + let v7 = block_args[0] as hir::ValueRef; + let v16 = block_args[1] as hir::ValueRef; + let v32 = block_args[2] as hir::ValueRef; + let v0 = block_args[3] as hir::ValueRef; + + let tests = [[v32, v7, v16, v0]]; + + for test in tests.into_iter() { + let mut stack = crate::OperandStack::default(); + for value in test.into_iter().rev() { + stack.push(value); + } + // The v7 is expected to be twice on stack + let expected = [v7, v7, v32, v16]; + let constraints = [Constraint::Copy; 4]; + + match OperandMovementConstraintSolver::new(&expected, &constraints, &stack) { + Ok(solver) => { + let _result = solver.solve().expect("no solution found"); + } + Err(SolverError::AlreadySolved) => panic!("already solved"), + Err(err) => panic!("invalid solver context: {err:?}"), + } + } + } + + proptest! { + #![proptest_config(ProptestConfig::with_cases(1000))] + + #[test] + fn operand_movement_constraint_solver_copy_any(problem in testing::generate_copy_any_problem()) { + testing::solve_problem(problem)? + } + + #[test] + fn operand_movement_constraint_solver_copy_none(problem in testing::generate_copy_none_problem()) { + testing::solve_problem(problem)? + } + + #[test] + fn operand_movement_constraint_solver_copy_all(problem in testing::generate_copy_all_problem()) { + testing::solve_problem(problem)? + } + + #[test] + fn operand_movement_constraint_solver_copy_some(problem in testing::generate_copy_some_problem()) { + testing::solve_problem(problem)? + } + } +} diff --git a/codegen/masm/src/opt/operands/stack.rs b/codegen/masm/src/opt/operands/stack.rs new file mode 100644 index 000000000..121d453f0 --- /dev/null +++ b/codegen/masm/src/opt/operands/stack.rs @@ -0,0 +1,124 @@ +use super::*; + +/// This implements a stack data structure for [ValueOrAlias] +#[derive(Default, Debug, Clone)] +pub struct Stack { + stack: Vec, +} +impl FromIterator for Stack { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut stack = iter.into_iter().collect::>(); + stack.reverse(); + + Self { stack } + } +} +impl From<&crate::OperandStack> for Stack { + fn from(stack: &crate::OperandStack) -> Self { + Self::from_iter(stack.iter().rev().map(|o| { + o.as_value() + .unwrap_or_else(|| panic!("expected value operand, got {o:#?}")) + .into() + })) + } +} +impl Stack { + pub fn len(&self) -> usize { + self.stack.len() + } + + pub fn push(&mut self, value: ValueOrAlias) { + self.stack.push(value); + } + + pub fn position(&self, value: &ValueOrAlias) -> Option { + self.stack.iter().rev().position(|stack_value| value == stack_value) + } + + pub fn position_skip(&self, start_index: usize, value: &ValueOrAlias) -> Option { + self.stack + .iter() + .rev() + .skip(start_index) + .position(|stack_value| value == stack_value) + .map(|pos| pos + start_index) + } + + pub fn iter(&self) -> impl DoubleEndedIterator { + self.stack.iter() + } + + #[allow(dead_code)] + pub fn iter_mut(&mut self) -> impl DoubleEndedIterator { + self.stack.iter_mut() + } + + pub fn dup(&mut self, n: usize, alias_id: core::num::NonZeroU8) { + let value = self[n]; + self.stack.push(value.copy(alias_id)); + } + + pub fn swap(&mut self, n: usize) { + let len = self.stack.len(); + let a_idx = len - 1; + let b_idx = a_idx - n; + self.stack.swap(a_idx, b_idx); + } + + pub fn movup(&mut self, n: usize) { + let len = self.stack.len(); + let mid = len - (n + 1); + let (_, r) = self.stack.split_at_mut(mid); + r.rotate_left(1); + } + + pub fn movdn(&mut self, n: usize) { + let len = self.stack.len(); + let mid = len - (n + 1); + let (_, r) = self.stack.split_at_mut(mid); + r.rotate_right(1); + } + + pub fn reset_to(&mut self, snapshot: &Self) { + self.stack.clear(); + let x = self.stack.capacity(); + let y = snapshot.stack.capacity(); + if x != y { + let a = core::cmp::max(x, y); + if a > x { + self.stack.reserve(a - x); + } + } + self.stack.extend_from_slice(&snapshot.stack); + } + + pub fn get(&self, index: usize) -> Option<&ValueOrAlias> { + let len = self.stack.len(); + self.stack.get(len - index - 1) + } +} +impl core::ops::Index for Stack { + type Output = ValueOrAlias; + + fn index(&self, index: usize) -> &Self::Output { + let len = self.stack.len(); + let index = len + .checked_sub(index) + .and_then(|idx| idx.checked_sub(1)) + .expect("invalid stack index"); + &self.stack[index] + } +} +impl core::ops::IndexMut for Stack { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + let len = self.stack.len(); + let index = len + .checked_sub(index) + .and_then(|idx| idx.checked_sub(1)) + .expect("invalid stack index"); + &mut self.stack[index] + } +} diff --git a/codegen/masm/src/codegen/opt/operands/tactics/copy_all.rs b/codegen/masm/src/opt/operands/tactics/copy_all.rs similarity index 100% rename from codegen/masm/src/codegen/opt/operands/tactics/copy_all.rs rename to codegen/masm/src/opt/operands/tactics/copy_all.rs diff --git a/codegen/masm/src/opt/operands/tactics/linear.rs b/codegen/masm/src/opt/operands/tactics/linear.rs new file mode 100644 index 000000000..42cd62973 --- /dev/null +++ b/codegen/masm/src/opt/operands/tactics/linear.rs @@ -0,0 +1,266 @@ +use midenc_hir::adt::SmallSet; +use petgraph::prelude::{DiGraphMap, Direction}; + +use super::*; + +/// This tactic produces a solution for the given constraints by traversing +/// the stack top-to-bottom, copying/evicting/swapping as needed to put +/// the expected value for the current working index in place. +/// +/// This tactic does make an effort to avoid needless moves by searching +/// for swap opportunities that will place multiple expected operands in +/// place at once using the optimal number of swaps. In cases where this +/// cannot be done however, it will perform as few swaps as it can while +/// still making progress. +#[derive(Default)] +pub struct Linear; +impl Tactic for Linear { + fn cost(&self, context: &SolverContext) -> usize { + core::cmp::max(context.copies().len(), 1) + } + + fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult { + let mut changed = true; + while changed { + changed = false; + + let mut graph = DiGraphMap::::new(); + + // Materialize copies + let mut materialized = SmallSet::::default(); + for (b_pos, b_value) in builder.context().expected().iter().rev().enumerate() { + // Where is B + if let Some(_b_at) = builder.get_current_position(b_value) { + log::trace!( + "no copy needed for {b_value:?} from index {b_pos} to top of stack", + ); + materialized.insert(*b_value); + } else { + // B isn't on the stack because it is a copy we haven't materialized yet + assert!(b_value.is_alias()); + let b_at = builder.unwrap_current_position(&b_value.unaliased()); + log::trace!( + "materializing copy of {b_value:?} from index {b_pos} to top of stack", + ); + builder.dup(b_at, b_value.unwrap_alias()); + materialized.insert(*b_value); + changed = true; + } + } + + // Visit each materialized operand and, if out of place, add it to the graph + // along with the node occupying its expected location on the stack. The occupying + // node is then considered materialized and visited as well. + let mut current_index = 0; + let mut materialized = materialized.into_vec(); + loop { + if current_index >= materialized.len() { + break; + } + let value = materialized[current_index]; + let currently_at = builder.unwrap_current_position(&value); + if let Some(expected_at) = builder.get_expected_position(&value) { + if currently_at == expected_at { + log::trace!( + "{value:?} at index {currently_at} is expected there, no movement \ + needed" + ); + current_index += 1; + continue; + } + let occupied_by = builder.unwrap_current(expected_at); + log::trace!( + "{value:?} at index {currently_at}, is expected at index {expected_at}, \ + which is currently occupied by {occupied_by:?}" + ); + let from = graph.add_node(Operand { + pos: currently_at, + value, + }); + let to = graph.add_node(Operand { + pos: expected_at, + value: occupied_by, + }); + graph.add_edge(from, to, ()); + if !materialized.contains(&occupied_by) { + materialized.push(occupied_by); + } + } else { + // `value` is not an expected operand, but is occupying a spot + // on the stack needed by one of the expected operands. We can + // create a connected component with `value` by finding the root + // of the path which leads to `value` from an expected operand, + // then adding an edge from `value` back to that operand. This + // forms a cycle which will allow all expected operands to be + // swapped into place, and the unused operand evicted, without + // requiring excess moves. + let operand = Operand { + pos: currently_at, + value, + }; + let mut parent = graph.neighbors_directed(operand, Direction::Incoming).next(); + // There must have been an immediate parent to `value`, or it would + // have an expected position on the stack, and only expected operands + // are materialized initially. + let mut root = parent.unwrap(); + log::trace!( + "{value:?} at index {currently_at}, is not an expected operand; but must \ + be moved to make space for {:?}", + root.value + ); + let mut seen = alloc::collections::BTreeSet::default(); + seen.insert(root); + while let Some(parent_operand) = parent { + root = parent_operand; + parent = + graph.neighbors_directed(parent_operand, Direction::Incoming).next(); + } + log::trace!( + "forming component with {value:?} by adding edge to {:?}, the start of \ + the path which led to it", + root.value + ); + graph.add_edge(operand, root, ()); + } + current_index += 1; + } + + // Compute the strongly connected components of the graph we've constructed, + // and use that to drive our decisions about moving operands into place. + let components = petgraph::algo::kosaraju_scc(&graph); + if components.is_empty() { + break; + } + log::trace!( + "found the following connected components when analyzing required operand moves: \ + {components:?}" + ); + for component in components.into_iter() { + // A component of two or more elements indicates a cycle of operands. + // + // To determine the order in which swaps must be performed, we first look + // to see if any of the elements are on top of the stack. If so, we swap + // it with its parent in the graph, and so on until we reach the edge that + // completes the cycle (i.e. brings us back to the operand we started with). + // + // If we didn't have an operand on top of the stack yet, we pick the operand + // that is closest to the top of the stack to move to the top, so as not to + // disturb the positions of the other operands. We then proceed as described + // above. The only additional step required comes at the end, where we move + // whatever operand ended up on top of the stack to the original position of + // the operand we started with. + // + // # Examples + // + // Consider a component of 3 operands: B -> A -> C -> B + // + // We can put all three operands in position by first swapping B with A, + // putting B into position; and then A with C, putting A into position, + // and leaving C in position as a result. + // + // Let's extend it one operand further: B -> A -> C -> D -> B + // + // The premise is the same, B with A, A with C, then C with D, the result + // is that they all end up in position at the end. + // + // Here's a diagram of how the state changes as we perform the swaps + // + // 0 1 2 3 + // C -> D -> B -> A -> C + // + // 0 1 2 3 + // D C B A + // + // 0 1 2 3 + // B C D A + // + // 0 1 2 3 + // A C D B + // + if component.len() > 1 { + // Find the operand at the shallowest depth on the stack to move. + let start = component.iter().min_by(|a, b| a.pos.cmp(&b.pos)).copied().unwrap(); + log::trace!( + "resolving component {component:?} by starting from {:?} at index {}", + start.value, + start.pos + ); + + // If necessary, move the starting operand to the top of the stack + let start_position = start.pos; + if start_position > 0 { + changed = true; + builder.movup(start_position); + } + + // Do the initial swap to set up our state for the remaining swaps + let mut child = + graph.neighbors_directed(start, Direction::Outgoing).next().unwrap(); + // Swap each child with its parent until we reach the edge that forms a cycle + while child != start { + log::trace!( + "swapping {:?} with {:?} at index {}", + builder.unwrap_current(0), + child.value, + child.pos + ); + builder.swap(child.pos); + changed = true; + if let Some(next_child) = + graph.neighbors_directed(child, Direction::Outgoing).next() + { + child = next_child; + } else { + // This edge case occurs when the component is of size 2, and the + // start and end nodes are being swapped to get them both into position. + // + // We verify that here to ensure we catch any exceptions we are not yet + // aware of. + assert_eq!(component.len(), 2); + break; + } + } + + // If necessary, move the final operand to the original starting position + if start_position > 0 { + builder.movdn(start_position); + changed = true; + } + } + } + + if builder.is_valid() { + break; + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + + use crate::opt::operands::{ + tactics::Linear, + testing::{self, ProblemInputs}, + }; + + prop_compose! { + fn generate_linear_problem() + (stack_size in 0usize..16) + (problem in testing::generate_stack_subset_copy_any_problem(stack_size)) -> ProblemInputs { + problem + } + } + + proptest! { + #![proptest_config(ProptestConfig::with_cases(2000))] + + #[test] + fn operand_tactics_linear_proptest(problem in generate_linear_problem()) { + testing::solve_problem_with_tactic::(problem)? + } + } +} diff --git a/codegen/masm/src/opt/operands/tactics/mod.rs b/codegen/masm/src/opt/operands/tactics/mod.rs new file mode 100644 index 000000000..d387eb069 --- /dev/null +++ b/codegen/masm/src/opt/operands/tactics/mod.rs @@ -0,0 +1,280 @@ +use core::num::NonZeroU8; + +use super::{Action, Operand, SolverContext, Stack, ValueOrAlias}; + +mod copy_all; +mod linear; +mod move_down_and_swap; +mod move_up_and_swap; +mod swap_and_move_up; +mod two_args; + +pub use self::{ + copy_all::CopyAll, linear::Linear, move_down_and_swap::MoveDownAndSwap, + move_up_and_swap::MoveUpAndSwap, swap_and_move_up::SwapAndMoveUp, two_args::TwoArgs, +}; + +/// An error returned by an [OperandMovementConstraintSolver] tactic +#[derive(Debug)] +pub enum TacticError { + /// The tactic could not be applied due to a precondition + /// that is required for the tactic to succeed. For example, + /// a tactic that does not handle copies will have a precondition + /// that there are no copy constraints, and will not attempt + /// to compute a solution if there are. + PreconditionFailed, + /// The tactic could not be applied because the pattern it + /// looks for could not be found in the current context. + NotApplicable, +} + +/// The type of result produced by a [Tactic] +pub type TacticResult = Result<(), TacticError>; + +/// A [Tactic] implements an algorithm for solving operand movement constraints +/// that adhere to a specific pattern or patterns. +/// +/// Tactics should attempt to fail early by first recognizing whether the state +/// of the stack adheres to the pattern which the tactic is designed to solve, +/// and only then should it actually compute the specific actions needed to lay +/// out the stack as expected. +/// +/// A tactic does not need to check if the result of computing a solution actually +/// solves all of the constraints, that is done by [OperandMovementConstraintSolver]. +/// +/// Tactics can have an associated cost, which is used when iterating over multiple +/// tactics looking for the best solution. You should strive to make the cost reflect +/// the computational complexity of the tactic to the degree possible. The default +/// cost for all tactics is 1. +pub trait Tactic { + /// The name of this tactic to use in informational messages. + /// + /// The default name of each tactic is the name of the implementing type. + fn name(&self) -> &'static str { + let name = core::any::type_name::(); + match name.find(|c: char| c.is_ascii_uppercase()) { + None => name, + Some(index) => name.split_at(index).1, + } + } + + /// The computational cost of this tactic in units of optimization fuel. + /// + /// The provided context can be used to compute a cost dynamically based on + /// the number of expected operands, the constraints, and the size of the stack. + /// + /// The default cost is 1. + fn cost(&self, _context: &SolverContext) -> usize { + 1 + } + + /// Apply this tactic using the provided [SolutionBuilder]. + fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult; +} + +/// This struct is constructed by an [OperandMovementConstraintSolver], and provided +/// to each [Tactic] it applies in search of a solution. +/// +/// The purpose of this builder is to abstract over the solver context, the pending +/// state of the stack, and to ensure that the solution computed by a [Tactic] is +/// captured accurately. +#[derive(Debug, Clone)] +pub struct SolutionBuilder<'a> { + /// The current solver context + context: &'a SolverContext, + /// The state of the stack after applying `actions` + pending: Stack, + /// The actions that represent the solution constructed so far. + actions: Vec, +} +impl<'a> SolutionBuilder<'a> { + #[doc(hidden)] + pub fn new(context: &'a SolverContext) -> Self { + Self { + context, + pending: context.stack().clone(), + actions: vec![], + } + } + + /// Return the number of operands expected by the current instruction being emitted + pub fn arity(&self) -> usize { + self.context.arity() + } + + /// Return true if the current context requires operand copies to be made + pub fn requires_copies(&self) -> bool { + !self.context.copies().is_empty() + } + + /// Return the total number of copied operands expected + pub fn num_copies(&self) -> usize { + self.context.copies().len() + } + + /// Returns true if the solution must be strict + pub fn requires_strict_solution(&self) -> bool { + self.context.is_strict() + } + + /// Get a reference to the underlying context of the solver + #[inline(always)] + pub fn context(&self) -> &'a SolverContext { + self.context + } + + /// Get a reference to the state of the stack after applying the pending solution + #[inline(always)] + pub fn stack(&self) -> &Stack { + &self.pending + } + + /// Take the current solution and reset the builder + pub fn take(&mut self) -> Vec { + let actions = core::mem::take(&mut self.actions); + self.pending.reset_to(self.context.stack()); + actions + } + + /// Discard the current solution, and reset back to the initial state + pub fn discard(&mut self) { + self.actions.clear(); + self.pending.reset_to(self.context.stack()); + } + + /// Check if the pending solution is a valid solution + pub fn is_valid(&self) -> bool { + self.context.is_solved(&self.pending) + } + + /// Get the value expected at `index` + pub fn get_expected(&self, index: u8) -> Option { + self.context.expected().get(index as usize).copied() + } + + /// Get the value expected at `index` or panic + #[track_caller] + pub fn unwrap_expected(&self, index: u8) -> ValueOrAlias { + match self.get_expected(index) { + Some(value) => value, + None => panic!( + "expected operand {index} does not exist: there are only {} expected operands", + self.context.arity() + ), + } + } + + /// Get the value currently at `index` in this solution + #[allow(unused)] + pub fn get_current(&self, index: u8) -> Option { + self.pending.get(index as usize).copied() + } + + /// Get the value currently at `index` in this solution + #[track_caller] + pub fn unwrap_current(&self, index: u8) -> ValueOrAlias { + self.pending.get(index as usize).copied().unwrap_or_else(|| { + panic!( + "operand {index} does not exist: the stack contains only {} operands", + self.pending.len() + ) + }) + } + + /// Get the position at which `value` is expected + pub fn get_expected_position(&self, value: &ValueOrAlias) -> Option { + self.context.expected().position(value).map(|index| index as u8) + } + + #[track_caller] + pub fn unwrap_expected_position(&self, value: &ValueOrAlias) -> u8 { + match self.get_expected_position(value) { + Some(pos) => pos, + None => panic!("value {value:?} is not an expected operand"), + } + } + + /// Get the current position of `value` in this solution + #[inline] + pub fn get_current_position(&self, value: &ValueOrAlias) -> Option { + self.pending.position(value).map(|index| index as u8) + } + + pub fn get_current_position_skip(&self, start_index: u8, value: &ValueOrAlias) -> Option { + self.pending.position_skip(start_index as usize, value).map(|index| index as u8) + } + + /// Get the current position of `value` in this solution, or panic + #[track_caller] + pub fn unwrap_current_position(&self, value: &ValueOrAlias) -> u8 { + match self.get_current_position(value) { + Some(pos) => pos, + None => panic!("value {value:?} not found on operand stack"), + } + } + + /// Returns true if the value expected at `index` is currently at that index + pub fn is_expected(&self, index: u8) -> bool { + self.get_expected(index) + .map(|v| v == self.pending[index as usize]) + .unwrap_or(false) + } + + /// Duplicate the operand at `index` to the top of the stack + /// + /// This records a `Copy` action, and updates the state of the stack + pub fn dup(&mut self, index: u8, alias_id: NonZeroU8) { + self.pending.dup(index as usize, alias_id); + self.actions.push(Action::Copy(index)); + } + + /// Swap the operands at `index` and the top of the stack + /// + /// This records a `Swap` action, and updates the state of the stack + pub fn swap(&mut self, index: u8) { + self.pending.swap(index as usize); + self.actions.push(Action::Swap(index)); + } + + /// Move the operand at `index` to the top of the stack + /// + /// This records a `MoveUp` action, and updates the state of the stack + #[track_caller] + pub fn movup(&mut self, index: u8) { + assert_ne!(index, 0); + if index == 1 { + self.swap(index); + } else { + self.pending.movup(index as usize); + self.actions.push(Action::MoveUp(index)); + } + } + + /// Move the operand at the top of the stack to `index` + /// + /// This records a `MoveDown` action, and updates the state of the stack + #[track_caller] + pub fn movdn(&mut self, index: u8) { + assert_ne!(index, 0); + if index == 1 { + self.swap(index); + } else { + self.pending.movdn(index as usize); + self.actions.push(Action::MoveDown(index)); + } + } + + /// Evicts the operand on top of the stack by moving it down past the last expected operand. + pub fn evict(&mut self) { + self.evict_from(0) + } + + /// Same as `evict`, but assumes that we're evicting an operand at `index` + #[inline] + pub fn evict_from(&mut self, index: u8) { + if index > 0 { + self.movup(index); + } + self.movdn(self.context.arity() as u8); + } +} diff --git a/codegen/masm/src/codegen/opt/operands/tactics/move_down_and_swap.rs b/codegen/masm/src/opt/operands/tactics/move_down_and_swap.rs similarity index 99% rename from codegen/masm/src/codegen/opt/operands/tactics/move_down_and_swap.rs rename to codegen/masm/src/opt/operands/tactics/move_down_and_swap.rs index 10c261b9e..8b94a6c5c 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/move_down_and_swap.rs +++ b/codegen/masm/src/opt/operands/tactics/move_down_and_swap.rs @@ -59,7 +59,7 @@ impl Tactic for MoveDownAndSwap { .enumerate() .fold(0, |acc, (offset, operand)| { builder - .get_expected_position(&operand.value) + .get_expected_position(&operand) .and_then(|operand_expected_at| { if target_pos >= operand_expected_at { Some(offset + 1) diff --git a/codegen/masm/src/codegen/opt/operands/tactics/move_up_and_swap.rs b/codegen/masm/src/opt/operands/tactics/move_up_and_swap.rs similarity index 93% rename from codegen/masm/src/codegen/opt/operands/tactics/move_up_and_swap.rs rename to codegen/masm/src/opt/operands/tactics/move_up_and_swap.rs index af24a99a2..1c459e790 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/move_up_and_swap.rs +++ b/codegen/masm/src/opt/operands/tactics/move_up_and_swap.rs @@ -87,9 +87,9 @@ impl Tactic for MoveUpAndSwap { // [a, b, c, d, e] let mut descending_pair = None; let mut last_pos = None; - for operand in builder.stack().iter().rev() { - if let Some(expected_pos) = builder.get_expected_position(&operand.value) { - let current = (operand.pos, expected_pos); + for (operand_pos, operand) in builder.stack().iter().rev().enumerate() { + if let Some(expected_pos) = builder.get_expected_position(operand) { + let current = (operand_pos as u8, expected_pos); let last_operand_pos = last_pos.replace(current); if let Some(last @ (_, last_expected_pos)) = last_operand_pos { if expected_pos >= last_expected_pos { @@ -114,24 +114,24 @@ impl Tactic for MoveUpAndSwap { if a_expected == 0 { log::trace!( "moving {:?} to the top of stack, shifting {:?} down", - builder.stack()[a_actual as usize].value, - builder.stack()[0].value + builder.stack()[a_actual as usize], + builder.stack()[0] ); builder.movup(a_actual); } else { if b_actual > 0 { log::trace!( "moving {:?} to the top of stack, shifting {:?} down", - builder.stack()[b_actual as usize].value, - builder.stack()[0].value + builder.stack()[b_actual as usize], + builder.stack()[0] ); builder.movup(b_actual); } let expected0_at = builder.unwrap_current_position(&expected0); log::trace!( "moving {:?} to the top of stack, shifting {:?} down", - builder.stack()[expected0_at as usize].value, - builder.stack()[0].value + builder.stack()[expected0_at as usize], + builder.stack()[0] ); builder.movup(expected0_at); } diff --git a/codegen/masm/src/codegen/opt/operands/tactics/swap_and_move_up.rs b/codegen/masm/src/opt/operands/tactics/swap_and_move_up.rs similarity index 95% rename from codegen/masm/src/codegen/opt/operands/tactics/swap_and_move_up.rs rename to codegen/masm/src/opt/operands/tactics/swap_and_move_up.rs index 3aeed2cce..2f61c9b7d 100644 --- a/codegen/masm/src/codegen/opt/operands/tactics/swap_and_move_up.rs +++ b/codegen/masm/src/opt/operands/tactics/swap_and_move_up.rs @@ -39,14 +39,14 @@ impl Tactic for SwapAndMoveUp { if expected1_pos == 0 { log::trace!( "swapping {expected1:?} from top of the stack, with {:?} at index 1", - builder.stack()[1].value + builder.stack()[1] ); builder.swap(1); } else { log::trace!( "swapping {expected1:?} at index {expected1_pos} to the top of the stack, with \ {:?}", - builder.stack()[0].value + builder.stack()[0] ); builder.swap(expected1_pos); } @@ -58,7 +58,7 @@ impl Tactic for SwapAndMoveUp { log::trace!( "moving {expected0:?} from index {expected0_pos} to the top of stack, shifting \ {:?} down by one", - builder.stack()[0].value + builder.stack()[0] ); builder.movup(expected0_pos); } diff --git a/codegen/masm/src/opt/operands/tactics/two_args.rs b/codegen/masm/src/opt/operands/tactics/two_args.rs new file mode 100644 index 000000000..c1f141454 --- /dev/null +++ b/codegen/masm/src/opt/operands/tactics/two_args.rs @@ -0,0 +1,465 @@ +use super::*; + +/// This tactic is for specifically optimising binary operators, especially those which are +/// commutative. The best case scenario for commutative ops is no work needs to be done. +/// Otherwise binary ops may be solved with a single swap, move or dupe, and at worst two swaps, +/// moves or dupes. +/// +/// The only criterion for success is an arity of exactly two. Then the solution will always +/// succeed, adjusted only by whether commutativity is a factor. +#[derive(Default)] +pub struct TwoArgs; + +impl Tactic for TwoArgs { + fn apply(&mut self, builder: &mut SolutionBuilder) -> TacticResult { + if builder.arity() != 2 { + return Err(TacticError::PreconditionFailed); + } + + let a = builder.unwrap_expected(0); + let b = builder.unwrap_expected(1); + + let a_orig = a.unaliased(); + let b_orig = b.unaliased(); + let duplicates = a_orig == b_orig; + + let (a_index, b_index) = if duplicates { + let a_index = + builder.get_current_position(&a_orig).ok_or(TacticError::NotApplicable)?; + if a.is_alias() || b.is_alias() { + (a_index, a_index) + } else { + let b_index = + builder.get_current_position_skip(a_index + 1, &b_orig).unwrap_or(a_index); + (a_index, b_index) + } + } else { + let a_index = + builder.get_current_position(&a_orig).ok_or(TacticError::NotApplicable)?; + let b_index = + builder.get_current_position(&b_orig).ok_or(TacticError::NotApplicable)?; + (a_index, b_index) + }; + match (a.is_alias(), b.is_alias()) { + (true, true) => self.copy_copy(builder, a, a_index, b, b_index), + (true, false) => self.copy_move(builder, a, a_index, b, b_index), + (false, true) => self.move_copy(builder, a, a_index, b, b_index), + (false, false) => self.move_move(builder, a, a_index, b, b_index), + } + } +} + +impl TwoArgs { + fn copy_copy( + &mut self, + builder: &mut SolutionBuilder, + a: ValueOrAlias, + a_index: u8, + b: ValueOrAlias, + b_index: u8, + ) -> TacticResult { + if a_index == b_index { + // Materialize two new copies of the same value + builder.dup(a_index, b.unwrap_alias()); + builder.dup(0, a.unwrap_alias()); + } else { + // Materialize copies of each value + builder.dup(b_index, b.unwrap_alias()); + builder.dup(a_index + 1, a.unwrap_alias()); + } + + Ok(()) + } + + fn copy_move( + &mut self, + builder: &mut SolutionBuilder, + a: ValueOrAlias, + a_index: u8, + _b: ValueOrAlias, + b_index: u8, + ) -> TacticResult { + // Note that for this type of solution, commutativity doesn't help us + if a_index == b_index { + // Reference to the same value, where a copy must be materialized before use. + // Move the value first, then materialize a copy + if b_index > 0 { + builder.movup(b_index); + } + builder.dup(0, a.unwrap_alias()); + } else if b_index > a_index { + // If b appears after a on the operand stack, then moving b to the top of the operand + // stack will shift a down the stack. + // + // Note that this necessarily implies that b_index > 0. + builder.movup(b_index); + builder.dup(a_index + 1, a.unwrap_alias()); + } else if b_index > 0 { + // If b is not already on top of the stack, it must be moved up, then we can make a + // copy of a to the top. + builder.movup(b_index); + builder.dup(a_index, a.unwrap_alias()); + } else { + // b_index == 0, and a_index != b_index, so we need only copy a to the top + builder.dup(a_index, a.unwrap_alias()); + } + + Ok(()) + } + + fn move_copy( + &mut self, + builder: &mut SolutionBuilder, + _a: ValueOrAlias, + a_index: u8, + b: ValueOrAlias, + b_index: u8, + ) -> TacticResult { + let commutative = !builder.requires_strict_solution(); + if a_index == b_index { + // Reference to the same value, where a copy must be materialized before use. + // Move the value first, then materialize a copy + if b_index > 0 { + builder.movup(b_index); + } + builder.dup(0, b.unwrap_alias()); + let b = builder.pending[0]; + let a = builder.pending[0].unaliased(); + builder.pending[1] = b; + builder.pending[0] = a; + } else if b_index > a_index { + // If b appears after a on the operand stack, then copying b to the top of the operand + // stack will shift a down the stack. + // + // Note that this necessarily implies that b_index > 0. + + // For commutative operations, we can elide some actions so long as a and b end up + // on top in some order. For non-commutative operations, we may need to issue an + // extra `swap` to get things in the strict order required + if commutative && a_index > 0 { + builder.movup(a_index); + builder.dup(b_index, b.unwrap_alias()); + } else if commutative { + builder.dup(b_index, b.unwrap_alias()); + } else if a_index > 0 { + builder.dup(b_index, b.unwrap_alias()); + builder.movup(a_index + 1); + } else { + builder.dup(b_index, b.unwrap_alias()); + builder.swap(1); + } + } else if b_index > 0 && commutative { + // a_index > b_index && b_index > 0 + builder.movup(a_index); + builder.dup(b_index + 1, b.unwrap_alias()); + } else if b_index > 0 { + // a_index > b_index && b_index > 0 + builder.dup(b_index, b.unwrap_alias()); + builder.movup(a_index + 1); + } else { + // b_index == 0 && a_index != b_index + builder.dup(b_index, b.unwrap_alias()); + builder.movup(a_index + 1); + } + + Ok(()) + } + + fn move_move( + &mut self, + builder: &mut SolutionBuilder, + _a: ValueOrAlias, + a_index: u8, + _b: ValueOrAlias, + b_index: u8, + ) -> TacticResult { + debug_assert_ne!(a_index, b_index); + + let commutative = !builder.requires_strict_solution(); + match (a_index, b_index) { + (0, 1) => (), + (0, b_index) => { + builder.movup(b_index); + if !commutative { + builder.swap(1); + } + } + (1, 0) if !commutative => { + builder.swap(1); + } + (a_index, 0) => { + if !commutative || a_index > 1 { + builder.movup(a_index); + } + } + (a_index, 1) => { + builder.swap(a_index); + } + (1, 2) => { + // Shift the operands to the top by moving the top element down + builder.movdn(2); + } + (a_index, b_index) if a_index > b_index => { + builder.movup(b_index); + builder.movup(a_index); + } + (a_index, b_index) => { + builder.movup(b_index); + builder.movup(a_index + 1); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use std::rc::Rc; + + use itertools::Itertools; + use midenc_hir::Context; + + use super::*; + use crate::{ + opt::{operands::SolverOptions, SolverError}, + Constraint, OperandStack, + }; + + // These are actually RHS/LHS pairs. + const ALL_CONSTRAINTS: [[Constraint; 2]; 4] = [ + [Constraint::Move, Constraint::Move], + [Constraint::Move, Constraint::Copy], + [Constraint::Copy, Constraint::Move], + [Constraint::Copy, Constraint::Copy], + ]; + + #[test] + fn solves_with_strict_operand_order() { + let hir_ctx = Rc::new(Context::default()); + + // Take every permutation of a 5 element stack and each permutation of two operand + // constraints and confirm that at most 2 actions are required to solve. + let val_refs = generate_valrefs(&hir_ctx, 5); + let total_actions = permute_stacks(&val_refs, 2, true); + + // This number should only ever go down as we add optimisations. + assert!( + total_actions <= 876, + "optimization regression, observed an unexpected increase in number of stack ops \ + needed to solve to {total_actions}" + ); + } + + #[test] + fn solves_for_any_commutative_permutation() { + let hir_ctx = Rc::new(Context::default()); + + // Take every permutation of a 5 element stack and each permutation of two operand + // constraints and confirm that at most 2 actions are required for an unordered solution. + let val_refs = generate_valrefs(&hir_ctx, 5); + let total_actions = permute_stacks(&val_refs, 2, false); + + // This number should only ever go down as we add optimisations. + // + // This value should always be smaller than that of `solves_with_strict_operand_order` + assert!( + total_actions <= 828, + "optimization regression, observed an unexpected increase in number of stack ops \ + needed to solve to {total_actions}" + ); + } + + #[test] + fn solves_optimally_for_move_move_commutative_permutation() { + let hir_ctx = Rc::new(Context::default()); + + // Take every permutation of a 3 element stack and confirm that at most 1 action is + // required for an unordered solution with move/move constraints. + let val_refs = generate_valrefs(&hir_ctx, 3); + let expected = [val_refs[0], val_refs[1]]; + let constraints = [[Constraint::Move, Constraint::Move]]; + + let total_actions = permute_stacks_advanced(&val_refs, expected, &constraints, 1, false); + + // This number should only ever go down as we add optimisations. + assert!( + total_actions <= 4, + "optimization regression, observed an unexpected increase in number of stack ops \ + needed to solve to {total_actions}" + ); + } + + #[test] + fn solves_with_materialized_copy_strict() { + let hir_ctx = Rc::new(Context::default()); + let total_actions = duplicated_stack_single_util(&hir_ctx, true); + + // This number should only ever go down as we add optimisations. + assert!( + total_actions <= 132, + "optimization regression, observed an unexpected increase in number of stack ops \ + needed to solve to {total_actions}" + ); + } + + #[test] + fn solves_with_materialized_copy_commutative() { + let hir_ctx = Rc::new(Context::default()); + let total_actions = duplicated_stack_single_util(&hir_ctx, false); + + // This number should only ever go down as we add optimisations. + assert!( + total_actions <= 132, + "optimization regression, observed an unexpected increase in number of stack ops \ + needed to solve to {total_actions}" + ); + } + + #[test] + fn solves_with_existing_copy_strict() { + let hir_ctx = Rc::new(Context::default()); + let total_actions = duplicated_stack_double_util(&hir_ctx, true); + + // This number should only ever go down as we add optimisations. + assert!( + total_actions <= 414, + "optimization regression, observed an unexpected increase in number of stack ops \ + needed to solve to {total_actions}" + ); + } + + #[test] + fn solves_with_existing_copy_commutative() { + let hir_ctx = Rc::new(Context::default()); + let total_actions = duplicated_stack_double_util(&hir_ctx, false); + + // This number should only ever go down as we add optimisations. + assert!( + total_actions <= 396, + "optimization regression, observed an unexpected increase in number of stack ops \ + needed to solve to {total_actions}" + ); + } + + fn duplicated_stack_single_util(context: &Context, strict: bool) -> usize { + // Take every permutation of a 4 element stack etc. where the two operands are the very + // same value. In this case it doesn't make sense for a Move/Move constraint to be used. + // + // The expected output is v0, v0. + let val_refs = generate_valrefs(context, 4); + let expected = [val_refs[0], val_refs[0]]; + let constraints = [ + [Constraint::Move, Constraint::Copy], + [Constraint::Copy, Constraint::Move], + [Constraint::Copy, Constraint::Copy], + ]; + + permute_stacks_advanced(&val_refs, expected, &constraints, 2, strict) + } + + fn duplicated_stack_double_util(context: &Context, strict: bool) -> usize { + // Take every permutation of a 5 element stack etc. where the two operands are the same value + // but represented twice in the input. + + // Generate 4 val refs but append a copy of v0. + let mut val_refs = generate_valrefs(context, 4); + let v0 = val_refs[0]; + val_refs.push(v0); + + let expected = [v0, v0]; + + permute_stacks_advanced(&val_refs, expected, &ALL_CONSTRAINTS, 2, strict) + } + + fn generate_valrefs(context: &Context, k: usize) -> Vec { + // The easiest? way to create a bunch of ValueRefs is to create a block with args and use them. + let block = context + .create_block_with_params(core::iter::repeat_n(midenc_hir::Type::I32, k)) + .borrow(); + + block + .arguments() + .iter() + .map(|block_arg| *block_arg as midenc_hir::ValueRef) + .collect() + } + + // Generate permutations of k values and run the two_args tactic on them all. Return the total + // number of actions required to solve ALL problems. + // + // Each solution must use a prescribed maximum number of actions and be valid. + fn permute_stacks( + val_refs: &[midenc_hir::ValueRef], + max_actions: usize, + strict: bool, + ) -> usize { + // Use just v0 and v1 at the top. The input is permuted so always using these is OK. + let expected = [val_refs[0], val_refs[1]]; + + permute_stacks_advanced(val_refs, expected, &ALL_CONSTRAINTS, max_actions, strict) + } + + fn permute_stacks_advanced( + val_refs: &[midenc_hir::ValueRef], + expected: [midenc_hir::ValueRef; 2], + constraints: &[[Constraint; 2]], + max_actions: usize, + strict: bool, + ) -> usize { + let mut total_actions = 0; + + // Permute every possible input stack variation and solve for each. + for val_refs_perm in val_refs.iter().permutations(val_refs.len()).unique() { + let mut pending = OperandStack::default(); + for value in val_refs_perm.into_iter().rev() { + pending.push(*value); + } + + for constraint_pair in constraints { + let context = SolverContext::new( + &expected, + constraint_pair, + &pending, + SolverOptions { + strict, + ..Default::default() + }, + ); + + match context { + Ok(context) => { + let mut builder = SolutionBuilder::new(&context); + + let mut tactic = TwoArgs; + let res = tactic.apply(&mut builder); + + assert!(res.is_ok(), "Tactic should always succeed: {:?}.", res.err()); + assert!( + builder.is_valid(), + "Invalid solution:\nlhs constraint: {:?}, rhs constraint: \ + {:?}\ninput: {:?}\nexpected: {:?}\noutput: {:?}", + constraint_pair[1], + constraint_pair[0], + &pending, + &context.expected(), + &builder.stack() + ); + + let num_actions = builder.take().len(); + assert!( + num_actions <= max_actions, + "expected solution to take no more than {max_actions} actions, got \ + {num_actions}" + ); + total_actions += num_actions; + } + + Err(SolverError::AlreadySolved) => {} + Err(_) => panic!("Unexpected error while building the solver context."), + } + } + } + + total_actions + } +} diff --git a/codegen/masm/src/opt/operands/testing.rs b/codegen/masm/src/opt/operands/testing.rs new file mode 100644 index 000000000..479ca98ce --- /dev/null +++ b/codegen/masm/src/opt/operands/testing.rs @@ -0,0 +1,447 @@ +use alloc::rc::Rc; +use core::fmt; + +use midenc_hir::{self as hir, Type}; +use proptest::{prelude::*, test_runner::TestRunner}; +use smallvec::SmallVec; + +use super::*; +use crate::Constraint; + +pub fn logger_setup() { + use log::LevelFilter; + let _ = env_logger::builder() + .filter_level(LevelFilter::Trace) + .format_timestamp(None) + .is_test(true) + .try_init(); +} + +// Strategy: +// +// 1. Generate a set of 1..16 operands to form a stack (called `stack`), with no more than 2 +// pairs of duplicate operands +// 2. Generate a set of up to 8 constraints (called `constraints`) by sampling `stack` twice, +// and treating duplicate samples as copies +// 3. Generate the set of expected operands by mapping `constraints` to values +pub struct ProblemInputs { + #[allow(dead_code)] + pub context: Rc, + pub block: hir::BlockRef, + pub stack: crate::OperandStack, + pub expected: SmallVec<[hir::ValueRef; 8]>, + pub constraints: SmallVec<[Constraint; 8]>, +} +impl fmt::Debug for ProblemInputs { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ProblemInputs") + .field("stack", &self.stack) + .field_with("expected", |f| { + let mut builder = f.debug_list(); + for value in self.expected.iter() { + builder.entry(&format_args!("{value}")); + } + builder.finish() + }) + .field("constraints", &self.constraints) + .finish() + } +} + +pub fn shuffled_value_stack(size: usize) -> proptest::strategy::Shuffle>> { + let mut next_id = 0; + let mut raw_stack = Vec::with_capacity(size); + raw_stack.resize_with(size, || { + let id = next_id; + next_id += 1; + id + }); + Just(raw_stack).prop_shuffle() +} + +pub fn copy_all(arity: usize) -> impl Strategy { + Just((1usize << arity) - 1) +} + +pub fn copy_some(range: core::ops::RangeInclusive) -> impl Strategy { + let max = *range.end(); + proptest::bits::usize::sampled(0..max, range) +} + +pub fn copy_any(arity: usize) -> impl Strategy { + let min = core::cmp::min(1, arity); + prop_oneof![ + CopyStrategy::all(arity), + CopyStrategy::none(arity), + CopyStrategy::some(min..=arity), + ] +} + +#[derive(Debug, Clone)] +pub struct CopyStrategy { + strategy: proptest::strategy::BoxedStrategy, + arity: u8, + min: u8, + max: u8, +} +impl CopyStrategy { + /// The simplest strategy, always solvable by copying + pub fn all(arity: usize) -> Self { + assert!(arity <= 16); + let max = arity as u8; + let strategy = if arity == 0 { + Just(0usize).boxed() + } else if arity == 1 { + Just(1usize).boxed() + } else { + proptest::bits::usize::sampled(1..arity, 0..arity).boxed() + }; + Self { + strategy, + arity: max, + min: max, + max, + } + } + + /// The next simplest strategy, avoids complicating strategies with copies + pub fn none(arity: usize) -> Self { + assert!(arity <= 16); + let max = arity as u8; + let strategy = if arity == 0 { + Just(0usize).boxed() + } else if arity == 1 { + Just(1usize).boxed() + } else { + proptest::bits::usize::sampled(1..arity, 0..arity).boxed() + }; + Self { + strategy, + arity: max, + min: 0, + max: 0, + } + } + + /// The most complicated strategy, + pub fn some(range: core::ops::RangeInclusive) -> Self { + let min = *range.start(); + let max = *range.end(); + assert!(max <= 16); + let strategy = if max == 0 { + Just(0usize).boxed() + } else if max == 1 { + Just(1usize).boxed() + } else { + proptest::bits::usize::sampled(0..max, range).boxed() + }; + let arity = max as u8; + Self { + strategy, + arity, + min: min as u8, + max: arity, + } + } +} + +impl Strategy for CopyStrategy { + type Tree = CopyStrategyValueTree; + type Value = usize; + + fn new_tree(&self, runner: &mut TestRunner) -> proptest::strategy::NewTree { + let tree = self.strategy.new_tree(runner)?; + Ok(CopyStrategyValueTree { + tree, + arity: self.arity, + min: self.min, + max: self.max, + prev: (self.min, self.max), + hi: (0, self.max), + }) + } +} + +pub struct CopyStrategyValueTree { + tree: Box>, + arity: u8, + min: u8, + max: u8, + prev: (u8, u8), + hi: (u8, u8), +} +impl proptest::strategy::ValueTree for CopyStrategyValueTree { + type Value = usize; + + fn current(&self) -> Self::Value { + match (self.min, self.max) { + (0, 0) => 0, + (min, max) if min == max => (1 << max as usize) - 1, + _ => self.tree.current(), + } + } + + fn simplify(&mut self) -> bool { + match (self.min, self.max) { + (0, 0) => { + self.hi = (0, 0); + self.min = self.arity; + self.max = self.arity; + true + } + (min, max) if min == max => { + self.hi = (min, max); + false + } + current => { + self.hi = current; + if !self.tree.simplify() { + self.min = 0; + self.max = 0; + } + true + } + } + } + + fn complicate(&mut self) -> bool { + match (self.min, self.max) { + current if current == self.hi => false, + (0, 0) => { + self.min = self.prev.0; + self.max = self.prev.1; + true + } + (min, max) if min == max => { + self.min = 0; + self.max = 0; + true + } + _ => self.tree.complicate(), + } + } +} + +pub fn make_problem_inputs(raw_stack: Vec, arity: usize, copies: usize) -> ProblemInputs { + use proptest::bits::BitSetLike; + + let context = Rc::new(hir::Context::default()); + let block = context.create_block_with_params(core::iter::repeat_n(Type::I32, raw_stack.len())); + let block_borrowed = block.borrow(); + let block_args = block_borrowed.arguments(); + let raw_stack = raw_stack + .into_iter() + .map(|index| block_args[index] as hir::ValueRef) + .collect::>(); + let mut stack = crate::OperandStack::default(); + let mut expected = SmallVec::with_capacity(arity); + let mut constraints = SmallVec::with_capacity(arity); + for value in raw_stack.into_iter().rev() { + stack.push(value); + } + for (id, value) in block_args.iter().copied().enumerate().take(arity) { + expected.push(value as hir::ValueRef); + if copies.test(id) { + constraints.push(Constraint::Copy); + } else { + constraints.push(Constraint::Move); + } + } + ProblemInputs { + context, + block, + stack, + expected, + constraints, + } +} + +prop_compose! { + fn generate_copy_count(num_expected: usize)(num_copies in 0..(num_expected + 1))(copies in copy_any(num_copies)) -> usize { + copies + } +} + +prop_compose! { + pub fn generate_stack_subset_copy_any_problem(stack_size: usize) + (raw_stack in shuffled_value_stack(stack_size), num_expected in 0..(stack_size + 1)) + (stack in Just(raw_stack), expected in Just(num_expected), copies in generate_copy_count(num_expected)) -> ProblemInputs { + make_problem_inputs(stack, expected, copies) + } +} + +prop_compose! { + pub fn generate_copy_any_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 0..=stack_size))) + (copies in copy_any(arity), raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { + make_problem_inputs(raw_stack, arity, copies) + } +} + +prop_compose! { + pub fn generate_copy_none_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 0..=stack_size))) + (raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { + make_problem_inputs(raw_stack, arity, 0) + } +} + +prop_compose! { + pub fn generate_copy_all_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 0..=stack_size))) + (copies in copy_all(arity), raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { + make_problem_inputs(raw_stack, arity, copies) + } +} + +prop_compose! { + pub fn generate_copy_some_problem()((raw_stack, arity) in (1..8usize).prop_flat_map(|stack_size| (shuffled_value_stack(stack_size), 1..=stack_size))) + (copies in copy_some(1..=arity), raw_stack in Just(raw_stack), arity in Just(arity)) -> ProblemInputs { + make_problem_inputs(raw_stack, arity, copies) + } +} + +pub fn solve_problem(problem: ProblemInputs) -> Result<(), TestCaseError> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE").is_test(true).try_init(); + let block = problem.block.borrow(); + let block_args = block.arguments(); + match OperandMovementConstraintSolver::new_with_options( + &problem.expected, + &problem.constraints, + &problem.stack, + SolverOptions { + fuel: 10, + ..Default::default() + }, + ) { + Ok(solver) => { + let result = solver.solve(); + // We are expecting solutions for all inputs + prop_assert!( + result.is_ok(), + "solver returned error {result:?} for problem: {problem:#?}" + ); + let actions = result.unwrap(); + // We are expecting that if all operands are copies, that the number of actions is + // equal to the number of copies + if problem.constraints.iter().all(|c| matches!(c, Constraint::Copy)) { + prop_assert_eq!(actions.len(), problem.expected.len()); + } + // We are expecting that applying `actions` to the input stack will produce a stack + // that has all of the expected operands on top of the stack, + // ordered by id, e.g. [v1, v2, ..vN] + let mut stack = problem.stack.clone(); + for action in actions.into_iter() { + reject_out_of_scope(action)?; + match action { + Action::Copy(index) => { + stack.dup(index as usize); + } + Action::Swap(index) => { + stack.swap(index as usize); + } + Action::MoveUp(index) => { + stack.movup(index as usize); + } + Action::MoveDown(index) => { + stack.movdn(index as usize); + } + } + } + for index in 0..problem.expected.len() { + let expected = block_args[index] as hir::ValueRef; + prop_assert_eq!( + &stack[index], + &expected, + "solution did not place {} at the correct location on the stack", + expected + ); + } + + Ok(()) + } + Err(SolverError::AlreadySolved) => Ok(()), + Err(err) => panic!("invalid solver context: {err:?}"), + } +} + +pub fn solve_problem_with_tactic( + problem: ProblemInputs, +) -> Result<(), TestCaseError> { + let block = problem.block.borrow(); + let block_args = block.arguments(); + match OperandMovementConstraintSolver::new_with_options( + &problem.expected, + &problem.constraints, + &problem.stack, + SolverOptions { + fuel: 10, + ..Default::default() + }, + ) { + Ok(solver) => { + let result = solver.solve_with_tactic::(); + // We are expecting solutions for all inputs + prop_assert!( + result.is_ok(), + "solver returned error {result:?} for problem: {problem:#?}" + ); + let actions = result.unwrap(); + prop_assert!( + actions.is_some(), + "solver indicated tactic produced only a partial solution for problem: \ + {problem:#?}" + ); + let actions = actions.unwrap(); + // We are expecting that applying `actions` to the input stack will produce a stack + // that has all of the expected operands on top of the stack, + // ordered by id, e.g. [v1, v2, ..vN] + let mut stack = problem.stack.clone(); + for action in actions.into_iter() { + reject_out_of_scope(action)?; + match action { + Action::Copy(index) => { + stack.dup(index as usize); + } + Action::Swap(index) => { + stack.swap(index as usize); + } + Action::MoveUp(index) => { + stack.movup(index as usize); + } + Action::MoveDown(index) => { + stack.movdn(index as usize); + } + } + } + for index in 0..problem.expected.len() { + let expected = block_args[index] as hir::ValueRef; + prop_assert_eq!( + &stack[index], + &expected, + "solution did not place {} at the correct location on the stack", + expected + ); + } + + Ok(()) + } + Err(SolverError::AlreadySolved) => Ok(()), + Err(err) => panic!("invalid solver context: {err:?}"), + } +} + +fn reject_out_of_scope(action: Action) -> Result<(), TestCaseError> { + // Reject test cases that are impossible to solve with operand stack management alone, i.e. + // more than 8 operands in random order all of which are copy constrained. This can easily + // push the problem into unsolvable territory without spills to memory. As such, we check for + // such cases and reject them. + let index = match action { + Action::Copy(index) + | Action::Swap(index) + | Action::MoveUp(index) + | Action::MoveDown(index) => index, + }; + if index >= 16 { + Err(TestCaseError::Reject("problem is out of scope for operand stack solver".into())) + } else { + Ok(()) + } +} diff --git a/codegen/masm/src/packaging/de.rs b/codegen/masm/src/packaging/de.rs deleted file mode 100644 index dcbfb2e67..000000000 --- a/codegen/masm/src/packaging/de.rs +++ /dev/null @@ -1,56 +0,0 @@ -use alloc::{fmt, sync::Arc}; - -use miden_assembly::Library as CompiledLibrary; -use miden_core::{utils::Deserializable, Program}; -use miden_processor::Digest; - -use crate::MastArtifact; - -pub fn deserialize_digest<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - const DIGEST_BYTES: usize = 32; - - let bytes: [u8; DIGEST_BYTES] = serde_bytes::deserialize(deserializer)?; - - Digest::try_from(bytes).map_err(serde::de::Error::custom) -} - -pub fn deserialize_mast<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - struct MastArtifactVisitor; - - impl<'de> serde::de::Visitor<'de> for MastArtifactVisitor { - type Value = MastArtifact; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("mast artifact") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - if let Some(bytes) = v.strip_prefix(b"PRG\0") { - Program::read_from_bytes(bytes) - .map(Arc::new) - .map(MastArtifact::Executable) - .map_err(serde::de::Error::custom) - } else if let Some(bytes) = v.strip_prefix(b"LIB\0") { - CompiledLibrary::read_from_bytes(bytes) - .map(Arc::new) - .map(MastArtifact::Library) - .map_err(serde::de::Error::custom) - } else { - Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Bytes(v.get(0..4).unwrap_or(v)), - &"expected valid mast artifact type tag", - )) - } - } - } - deserializer.deserialize_bytes(MastArtifactVisitor) -} diff --git a/codegen/masm/src/packaging/mod.rs b/codegen/masm/src/packaging/mod.rs deleted file mode 100644 index 868347ec8..000000000 --- a/codegen/masm/src/packaging/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -mod de; -mod package; -mod se; -#[cfg(test)] -mod tests; - -pub use self::package::{Package, PackageExport, PackageManifest, Rodata}; diff --git a/codegen/masm/src/packaging/package.rs b/codegen/masm/src/packaging/package.rs deleted file mode 100644 index 973c79b37..000000000 --- a/codegen/masm/src/packaging/package.rs +++ /dev/null @@ -1,355 +0,0 @@ -use alloc::{collections::BTreeSet, fmt, sync::Arc}; - -use miden_processor::Digest; -use midenc_hir::{formatter::DisplayHex, ConstantData, FunctionIdent, Ident, Signature, Symbol}; -use midenc_session::{diagnostics::Report, Emit, LinkLibrary, Session}; -use serde::{Deserialize, Serialize}; - -use super::{de, se}; -use crate::*; - -#[derive(Serialize, Deserialize, Clone)] -pub struct Package { - /// Name of the package - pub name: Symbol, - /// Content digest of the package - #[serde( - serialize_with = "se::serialize_digest", - deserialize_with = "de::deserialize_digest" - )] - pub digest: Digest, - /// The package type and MAST - #[serde( - serialize_with = "se::serialize_mast", - deserialize_with = "de::deserialize_mast" - )] - pub mast: MastArtifact, - /// The rodata segments required by the code in this package - pub rodata: Vec, - /// The package manifest, containing the set of exported procedures and their signatures, - /// if known. - pub manifest: PackageManifest, -} -impl fmt::Debug for Package { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Package") - .field("name", &self.name) - .field("digest", &format_args!("{}", DisplayHex::new(&self.digest.as_bytes()))) - .field_with("rodata", |f| f.debug_list().entries(self.rodata.iter()).finish()) - .field("manifest", &self.manifest) - .finish_non_exhaustive() - } -} -impl Emit for Package { - fn name(&self) -> Option { - Some(self.name) - } - - fn output_type(&self, mode: midenc_session::OutputMode) -> midenc_session::OutputType { - use midenc_session::OutputMode; - match mode { - OutputMode::Text => self.mast.output_type(mode), - OutputMode::Binary => midenc_session::OutputType::Masp, - } - } - - fn write_to( - &self, - mut writer: W, - mode: midenc_session::OutputMode, - session: &Session, - ) -> std::io::Result<()> { - use midenc_session::OutputMode; - match mode { - OutputMode::Text => self.mast.write_to(writer, mode, session), - OutputMode::Binary => { - // Write magic - writer.write_all(b"MASP\0")?; - // Write format version - writer.write_all(b"1.0\0")?; - let data = bitcode::serialize(self).map_err(std::io::Error::other)?; - writer.write_all(data.as_slice()) - } - } - } -} - -#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)] -#[serde(default)] -pub struct PackageManifest { - /// The set of exports in this package. - pub exports: BTreeSet, - /// The libraries linked against by this package, which must be provided when executing the - /// program. - pub link_libraries: Vec, -} - -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct PackageExport { - pub id: FunctionIdent, - #[serde( - serialize_with = "se::serialize_digest", - deserialize_with = "de::deserialize_digest" - )] - pub digest: Digest, - /// We don't always have a type signature for an export - #[serde(default)] - pub signature: Option, -} -impl fmt::Debug for PackageExport { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PackageExport") - .field("id", &format_args!("{}", self.id.display())) - .field("digest", &format_args!("{}", DisplayHex::new(&self.digest.as_bytes()))) - .field("signature", &self.signature) - .finish() - } -} -impl PartialOrd for PackageExport { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl Ord for PackageExport { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.id.cmp(&other.id).then_with(|| self.digest.cmp(&other.digest)) - } -} - -/// Represents a read-only data segment, combined with its content digest -#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct Rodata { - /// The content digest computed for `data` - #[serde( - serialize_with = "se::serialize_digest", - deserialize_with = "de::deserialize_digest" - )] - pub digest: Digest, - /// The address at which the data for this segment begins - pub start: NativePtr, - /// The raw binary data for this segment - pub data: Arc, -} -impl fmt::Debug for Rodata { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Rodata") - .field("digest", &format_args!("{}", DisplayHex::new(&self.digest.as_bytes()))) - .field("start", &self.start) - .field_with("data", |f| { - f.debug_struct("ConstantData") - .field("len", &self.data.len()) - .finish_non_exhaustive() - }) - .finish() - } -} -impl Rodata { - pub fn size_in_bytes(&self) -> usize { - self.data.len() - } - - pub fn size_in_felts(&self) -> usize { - self.data.len().next_multiple_of(4) / 4 - } - - pub fn size_in_words(&self) -> usize { - self.size_in_felts().next_multiple_of(4) / 4 - } - - /// Attempt to convert this rodata object to its equivalent representation in felts - /// - /// The resulting felts will be in padded out to the nearest number of words, i.e. if the data - /// only takes up 3 felts worth of bytes, then the resulting `Vec` will contain 4 felts, so that - /// the total size is a valid number of words. - pub fn to_elements(&self) -> Result, String> { - use miden_core::FieldElement; - use miden_processor::Felt; - - let data = self.data.as_slice(); - let mut felts = Vec::with_capacity(data.len() / 4); - let mut iter = data.iter().copied().array_chunks::<4>(); - felts.extend(iter.by_ref().map(|bytes| Felt::new(u32::from_le_bytes(bytes) as u64))); - if let Some(remainder) = iter.into_remainder() { - let mut chunk = [0u8; 4]; - for (i, byte) in remainder.into_iter().enumerate() { - chunk[i] = byte; - } - felts.push(Felt::new(u32::from_le_bytes(chunk) as u64)); - } - - let padding = (self.size_in_words() * 4).abs_diff(felts.len()); - felts.resize(felts.len() + padding, Felt::ZERO); - - Ok(felts) - } -} - -impl Package { - /// Create a [Package] for a [MastArtifact], using the [MasmArtifact] from which it was - /// assembled, and the [Session] that was used to compile it. - pub fn new(mast: MastArtifact, masm: &MasmArtifact, session: &Session) -> Self { - let name = Symbol::intern(session.name()); - let digest = mast.digest(); - let link_libraries = session.options.link_libraries.clone(); - let mut manifest = PackageManifest { - exports: Default::default(), - link_libraries, - }; - - // Gater all of the rodata segments for this package - let rodata = match masm { - MasmArtifact::Executable(ref prog) => prog.rodatas().to_vec(), - MasmArtifact::Library(ref lib) => lib.rodatas().to_vec(), - }; - - // Gather all of the procedure metadata for exports of this package - if let MastArtifact::Library(ref lib) = mast { - let MasmArtifact::Library(ref masm_lib) = masm else { - unreachable!(); - }; - for module_info in lib.module_infos() { - let module_path = module_info.path().path(); - let masm_module = masm_lib.get(module_path.as_ref()); - let module_span = masm_module.map(|module| module.span).unwrap_or_default(); - for (_, proc_info) in module_info.procedures() { - let proc_name = proc_info.name.as_str(); - let masm_function = masm_module.and_then(|module| { - module.functions().find(|f| f.name.function.as_str() == proc_name) - }); - let proc_span = masm_function.map(|f| f.span).unwrap_or_default(); - let id = FunctionIdent { - module: Ident::new(Symbol::intern(module_path.as_ref()), module_span), - function: Ident::new(Symbol::intern(proc_name), proc_span), - }; - let digest = proc_info.digest; - let signature = masm_function.map(|f| f.signature.clone()); - manifest.exports.insert(PackageExport { - id, - digest, - signature, - }); - } - } - } - - Self { - name, - digest, - mast, - rodata, - manifest, - } - } - - pub fn read_from_file

(path: P) -> std::io::Result - where - P: AsRef, - { - let path = path.as_ref(); - let bytes = std::fs::read(path)?; - - Self::read_from_bytes(bytes).map_err(std::io::Error::other) - } - - pub fn read_from_bytes(bytes: B) -> Result - where - B: AsRef<[u8]>, - { - use alloc::borrow::Cow; - - let bytes = bytes.as_ref(); - - let bytes = bytes - .strip_prefix(b"MASP\0") - .ok_or_else(|| Report::msg("invalid package: missing header"))?; - let bytes = bytes.strip_prefix(b"1.0\0").ok_or_else(|| { - Report::msg(format!( - "invalid package: incorrect version, expected '1.0', got '{}'", - bytes.get(0..4).map(String::from_utf8_lossy).unwrap_or(Cow::Borrowed("")), - )) - })?; - - bitcode::deserialize(bytes).map_err(Report::msg) - } - - pub fn is_program(&self) -> bool { - matches!(self.mast, MastArtifact::Executable(_)) - } - - pub fn is_library(&self) -> bool { - matches!(self.mast, MastArtifact::Library(_)) - } - - pub fn unwrap_program(&self) -> Arc { - match self.mast { - MastArtifact::Executable(ref prog) => Arc::clone(prog), - _ => panic!("expected package to contain a program, but got a library"), - } - } - - pub fn unwrap_library(&self) -> Arc { - match self.mast { - MastArtifact::Library(ref lib) => Arc::clone(lib), - _ => panic!("expected package to contain a library, but got an executable"), - } - } - - pub fn make_executable(&self, entrypoint: &FunctionIdent) -> Result { - use midenc_session::diagnostics::{SourceSpan, Span}; - - let MastArtifact::Library(ref library) = self.mast else { - return Err(Report::msg("expected library but got an executable")); - }; - - let module = library - .module_infos() - .find(|info| info.path().path() == entrypoint.module.as_str()) - .ok_or_else(|| { - Report::msg(format!( - "invalid entrypoint: library does not contain a module named '{}'", - entrypoint.module.as_str() - )) - })?; - let name = miden_assembly::ast::ProcedureName::new_unchecked( - miden_assembly::ast::Ident::new_unchecked(Span::new( - SourceSpan::UNKNOWN, - Arc::from(entrypoint.function.as_str()), - )), - ); - if let Some(digest) = module.get_procedure_digest_by_name(&name) { - let node_id = library.mast_forest().find_procedure_root(digest).ok_or_else(|| { - Report::msg( - "invalid entrypoint: malformed library - procedure exported, but digest has \ - no node in the forest", - ) - })?; - - let exports = BTreeSet::from_iter(self.manifest.exports.iter().find_map(|export| { - if export.digest == digest { - Some(export.clone()) - } else { - None - } - })); - - Ok(Self { - name: self.name, - digest, - mast: MastArtifact::Executable(Arc::new(miden_core::Program::new( - library.mast_forest().clone(), - node_id, - ))), - rodata: self.rodata.clone(), - manifest: PackageManifest { - exports, - link_libraries: self.manifest.link_libraries.clone(), - }, - }) - } else { - Err(Report::msg(format!( - "invalid entrypoint: library does not export '{}'", - entrypoint.display() - ))) - } - } -} diff --git a/codegen/masm/src/packaging/se.rs b/codegen/masm/src/packaging/se.rs deleted file mode 100644 index 095382b8e..000000000 --- a/codegen/masm/src/packaging/se.rs +++ /dev/null @@ -1,30 +0,0 @@ -use miden_core::utils::Serializable; -use miden_processor::Digest; - -use crate::MastArtifact; - -pub fn serialize_digest(digest: &Digest, serializer: S) -> Result -where - S: serde::Serializer, -{ - serde_bytes::serialize(&digest.as_bytes(), serializer) -} - -pub fn serialize_mast(mast: &MastArtifact, serializer: S) -> Result -where - S: serde::Serializer, -{ - let mut buffer = vec![]; - match mast { - MastArtifact::Executable(program) => { - buffer.extend(b"PRG\0"); - program.write_into(&mut buffer); - } - MastArtifact::Library(library) => { - buffer.extend(b"LIB\0"); - library.write_into(&mut buffer); - } - } - - serde_bytes::serialize(&buffer, serializer) -} diff --git a/codegen/masm/src/packaging/tests.rs b/codegen/masm/src/packaging/tests.rs deleted file mode 100644 index 9fdf4d7f2..000000000 --- a/codegen/masm/src/packaging/tests.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::sync::Arc; - -use miden_stdlib::StdLibrary; -use midenc_hir::{diagnostics::IntoDiagnostic, testing::TestContext, StructType, Type}; -use midenc_session::{diagnostics::Report, Emit}; - -use super::*; -use crate::{MasmArtifact, NativePtr}; - -#[test] -fn packaging_serialization() -> Result<(), Report> { - let context = TestContext::default_with_emitter(None); - let package = example_package(&context)?; - - bitcode::serialize(package.as_ref()).map_err(Report::msg)?; - - Ok(()) -} - -#[test] -fn packaging_deserialization() -> Result<(), Report> { - let context = TestContext::default_with_emitter(None); - let expected = example_package(&context)?; - - let mut bytes = vec![]; - expected - .write_to(&mut bytes, midenc_session::OutputMode::Binary, &context.session) - .into_diagnostic()?; - - let package = Package::read_from_bytes(bytes)?; - - assert_eq!(package.name, expected.name); - assert_eq!(package.digest, expected.digest); - assert_eq!(package.rodata, expected.rodata); - assert_eq!(package.manifest, expected.manifest); - assert!(package.is_program()); - - // Verify rodata serialization - assert!(!package.rodata.is_empty()); - let expected_rodata_offset = NativePtr::from_ptr(65536 * 4); - let foo_data = package - .rodata - .iter() - .find(|rodata| rodata.start == expected_rodata_offset) - .unwrap(); - let foo_bytes = foo_data.data.as_slice(); - - let foo_ty = StructType::new([Type::U8, Type::U32, Type::U64]); - let offset_u8 = foo_ty.get(0).offset as usize; - let offset_u32 = foo_ty.get(1).offset as usize; - let offset_u64 = foo_ty.get(2).offset as usize; - assert_eq!(foo_bytes[offset_u8], 1); - assert_eq!( - u32::from_be_bytes([ - foo_bytes[offset_u32], - foo_bytes[offset_u32 + 1], - foo_bytes[offset_u32 + 2], - foo_bytes[offset_u32 + 3] - ]), - 2 - ); - assert_eq!( - u32::from_be_bytes([ - foo_bytes[offset_u64], - foo_bytes[offset_u64 + 1], - foo_bytes[offset_u64 + 2], - foo_bytes[offset_u64 + 3] - ]), - 0 - ); - assert_eq!( - u32::from_be_bytes([ - foo_bytes[offset_u64 + 4], - foo_bytes[offset_u64 + 5], - foo_bytes[offset_u64 + 6], - foo_bytes[offset_u64 + 7] - ]), - 3 - ); - - // Verify the MAST - let expected = expected.unwrap_program(); - let program = package.unwrap_program(); - assert_eq!(program.hash(), expected.hash()); - assert_eq!(program.mast_forest(), expected.mast_forest()); - - Ok(()) -} - -fn example_package(context: &TestContext) -> Result, Report> { - use midenc_hir::ProgramBuilder; - - // Build a simple program - let mut builder = ProgramBuilder::new(&context.session.diagnostics); - - // Build test module with fib function - let mut mb = builder.module("test"); - midenc_hir::testing::fib1(mb.as_mut(), context); - - // Ensure we have an example data segment or two to work with - let foo_ty = StructType::new([Type::U8, Type::U32, Type::U64]); - // Initialize the struct with some data - let offset_u8 = foo_ty.get(0).offset as usize; - let offset_u32 = foo_ty.get(1).offset as usize; - let offset_u64 = foo_ty.get(2).offset as usize; - let foo_ty = Type::Struct(foo_ty); - let foo_size = foo_ty.size_in_bytes(); - let mut data = vec![0u8; foo_size]; - unsafe { - let data_ptr_range = data.as_mut_ptr_range(); - core::ptr::write(data_ptr_range.start.byte_add(offset_u8), 1u8); - core::ptr::write(data_ptr_range.start.byte_add(offset_u32).cast(), 2u32.to_be_bytes()); - core::ptr::write(data_ptr_range.start.byte_add(offset_u64).cast(), 0u32.to_be_bytes()); // hi bits - core::ptr::write(data_ptr_range.start.byte_add(offset_u64 + 4).cast(), 3u32.to_be_bytes()); // lo bits - } - mb.declare_data_segment(65536 * 4, foo_size as u32, data, true)?; - - mb.build().expect("unexpected error constructing test module"); - - // Link the program - let mut program = builder - .with_entrypoint("test::fib".parse().unwrap()) - .link() - .expect("failed to link program"); - - program.add_library(StdLibrary::default().into()); - - // Compile the program - let mut compiler = crate::MasmCompiler::new(&context.session); - let program = compiler.compile(program).expect("compilation failed").unwrap_executable(); - - // Assemble the program - let masm_artifact = MasmArtifact::Executable(program); - let mast_artifact = masm_artifact.assemble(&context.session)?; - - // Package the program - Ok(Arc::new(Package::new(mast_artifact, &masm_artifact, &context.session))) -} diff --git a/codegen/masm/src/stack.rs b/codegen/masm/src/stack.rs new file mode 100644 index 000000000..0ca962f0b --- /dev/null +++ b/codegen/masm/src/stack.rs @@ -0,0 +1,909 @@ +use core::{ + fmt, + ops::{Index, IndexMut}, +}; + +use miden_core::{Felt, FieldElement}; +use midenc_hir::{AttributeValue, Immediate, Type, ValueRef}; +use smallvec::{smallvec, SmallVec}; + +/// This represents a constraint an operand's usage at +/// a given program point, namely when used as an instruction +/// or block argument. +#[derive(Debug, Copy, Clone)] +pub enum Constraint { + /// The operand should be moved, consuming it + /// from the stack and making it unavailable for + /// further use. + Move, + /// The operand should be copied, preserving the + /// original value for later use. + Copy, +} + +/// Represents the type of operand represented on the operand stack +pub enum OperandType { + /// The operand is a literal, unassociated with any value in the IR + Const(Box), + /// The operand is an SSA value of known type + Value(ValueRef), + /// The operand is an intermediate runtime value of a known type, but + /// unassociated with any value in the IR + Type(Type), +} +impl Clone for OperandType { + fn clone(&self) -> Self { + match self { + Self::Const(value) => Self::Const(value.clone_value()), + Self::Value(value) => Self::Value(*value), + Self::Type(ty) => Self::Type(ty.clone()), + } + } +} +impl OperandType { + /// Get the type representation of this operand + pub fn ty(&self) -> Type { + match self { + Self::Const(imm) => { + imm.downcast_ref::().expect("unexpected constant value type").ty() + } + Self::Value(value) => value.borrow().ty().clone(), + Self::Type(ref ty) => ty.clone(), + } + } +} +impl fmt::Debug for OperandType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Const(value) => write!(f, "Const({value:?})"), + Self::Value(value) => write!(f, "Value({value})"), + Self::Type(ty) => write!(f, "Type({ty})"), + } + } +} +impl Eq for OperandType {} +impl PartialEq for OperandType { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Value(a), Self::Value(b)) => a == b, + (Self::Value(_), _) | (_, Self::Value(_)) => false, + (Self::Const(ref a), Self::Const(ref b)) => a == b, + (Self::Const(_), _) | (_, Self::Const(_)) => false, + (Self::Type(ref a), Self::Type(ref b)) => a == b, + } + } +} +impl PartialEq for OperandType { + fn eq(&self, other: &Type) -> bool { + match self { + Self::Type(a) => a == other, + _ => false, + } + } +} +impl PartialEq for OperandType { + fn eq(&self, other: &Immediate) -> bool { + match self { + Self::Const(a) => a.downcast_ref::().is_some_and(|a| a == other), + _ => false, + } + } +} +impl PartialEq for OperandType { + fn eq(&self, other: &dyn AttributeValue) -> bool { + match self { + Self::Const(a) => a.as_ref() == other, + _ => false, + } + } +} +impl PartialEq for OperandType { + fn eq(&self, other: &ValueRef) -> bool { + match self { + Self::Value(this) => this == other, + _ => false, + } + } +} +impl From for OperandType { + fn from(value: ValueRef) -> Self { + Self::Value(value) + } +} +impl From for OperandType { + fn from(ty: Type) -> Self { + Self::Type(ty) + } +} +impl From for OperandType { + fn from(value: Immediate) -> Self { + Self::Const(Box::new(value)) + } +} +impl From> for OperandType { + fn from(value: Box) -> Self { + Self::Const(value) + } +} + +/// This type represents a logical operand on the stack, which may consist +/// of one or more "parts", up to a word in size, on the actual stack. +/// +/// The [OperandStack] operates in terms of [Operand], but when emitting +/// Miden Assembly, we must know how to translate operand-oriented operations +/// into equivalent element-/word-oriented operations. This is accomplished +/// by tracking the low-level representation of a given operand in this struct. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Operand { + /// The section of stack corresponding to this operand, containing + /// up to a full word of elements. No chunk will ever exceed a word + /// in size. This field behaves like a miniature [OperandStack], i.e. + /// elements are pushed and popped off the end to modify it. + /// + /// An operand is encoded on this stack in order of lowest + /// addressed bytes first. For example, given a struct operand, + /// the first field of the struct will be closest to the top of + /// the stack. + word: SmallVec<[Type; 4]>, + /// The high-level operand represented by this item. + /// + /// If the operand stack is manipulated in such a way that the operand + /// is torn apart, say one field of a struct is popped; then this will + /// be set to a `Type` operand, representing what high-level information + /// we have about the remaining parts of the original operand on the stack. + operand: OperandType, +} +impl Default for Operand { + fn default() -> Self { + Self { + word: smallvec![Type::Felt], + operand: OperandType::Const(Box::new(Immediate::Felt(Felt::ZERO))), + } + } +} +impl PartialEq for Operand { + #[inline(always)] + fn eq(&self, other: &ValueRef) -> bool { + self.operand.eq(other) + } +} +impl PartialEq for Operand { + #[inline(always)] + fn eq(&self, other: &dyn AttributeValue) -> bool { + self.operand.eq(other) + } +} +impl PartialEq for Operand { + #[inline(always)] + fn eq(&self, other: &Immediate) -> bool { + self.operand.eq(other) + } +} +impl PartialEq for &Operand { + #[inline(always)] + fn eq(&self, other: &Immediate) -> bool { + self.operand.eq(other) + } +} +impl PartialEq for Operand { + #[inline(always)] + fn eq(&self, other: &Type) -> bool { + self.operand.eq(other) + } +} +impl PartialEq for &Operand { + #[inline(always)] + fn eq(&self, other: &Type) -> bool { + self.operand.eq(other) + } +} +impl From for Operand { + #[inline] + fn from(imm: Immediate) -> Self { + Self::new(imm.into()) + } +} +impl From for Operand { + #[inline] + fn from(imm: u32) -> Self { + Self::new(Immediate::U32(imm).into()) + } +} +impl TryFrom<&Operand> for ValueRef { + type Error = (); + + fn try_from(operand: &Operand) -> Result { + match operand.operand { + OperandType::Value(value) => Ok(value), + _ => Err(()), + } + } +} +#[cfg(test)] +impl TryFrom<&Operand> for Immediate { + type Error = (); + + fn try_from(operand: &Operand) -> Result { + match &operand.operand { + OperandType::Const(value) => value.downcast_ref::().copied().ok_or(()), + _ => Err(()), + } + } +} +#[cfg(test)] +impl TryFrom<&Operand> for Type { + type Error = (); + + fn try_from(operand: &Operand) -> Result { + match operand.operand { + OperandType::Type(ref ty) => Ok(ty.clone()), + _ => Err(()), + } + } +} +#[cfg(test)] +impl TryFrom for Type { + type Error = (); + + fn try_from(operand: Operand) -> Result { + match operand.operand { + OperandType::Type(ty) => Ok(ty), + _ => Err(()), + } + } +} +impl From for Operand { + #[inline] + fn from(ty: Type) -> Self { + Self::new(OperandType::Type(ty)) + } +} +impl From for Operand { + #[inline] + fn from(value: ValueRef) -> Self { + Self::new(OperandType::Value(value)) + } +} +impl Operand { + pub fn new(operand: OperandType) -> Self { + let ty = operand.ty(); + let mut word = ty.to_raw_parts().expect("invalid operand type"); + assert!(!word.is_empty(), "invalid operand: must be a sized type"); + assert!(word.len() <= 4, "invalid operand: must be smaller than or equal to a word"); + if word.len() > 1 { + word.reverse(); + } + Self { word, operand } + } + + /// Get the size of this operand in field elements + pub fn size(&self) -> usize { + self.word.len() + } + + /// Get the [OperandType] representing the value of this operand + #[inline(always)] + pub fn value(&self) -> &OperandType { + &self.operand + } + + /// Get this operand as a [Value] + #[inline] + pub fn as_value(&self) -> Option { + self.try_into().ok() + } + + /// Get the [Type] of this operand + #[inline] + pub fn ty(&self) -> Type { + self.operand.ty() + } +} + +/// This structure emulates the state of the VM's operand stack while +/// generating code from the SSA representation of a function. +/// +/// In order to emit efficient and correct stack manipulation code, we must be able to +/// reason about where values are on the operand stack at a given program point. This +/// structure tracks what SSA values have been pushed on the operand stack, where they are +/// on the stack relative to the top, and whether a given stack slot aliases multiple +/// values. +/// +/// In addition to the state tracked, this structure also has an API that mimics the +/// stack manipulation instructions we can emit in the code generator, so that as we +/// emit instructions and modify this structure at the same time, 1:1. +#[derive(Clone, PartialEq, Eq)] +pub struct OperandStack { + stack: Vec, +} +impl Default for OperandStack { + fn default() -> Self { + Self { + stack: Vec::with_capacity(16), + } + } +} +impl OperandStack { + /// Renames the `n`th operand from the top of the stack to `value` + /// + /// The type is assumed to remain unchanged + pub fn rename(&mut self, n: usize, value: ValueRef) { + match &mut self[n].operand { + OperandType::Value(ref mut prev_value) => { + *prev_value = value; + } + prev => { + *prev = OperandType::Value(value); + } + } + } + + /// Searches for the position on the stack containing the operand corresponding to `value`. + /// + /// NOTE: This function will panic if `value` is not on the stack + pub fn find(&self, value: &ValueRef) -> Option { + self.stack.iter().rev().position(|v| v == value) + } + + /// Returns true if the operand stack is empty + #[allow(unused)] + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.stack.is_empty() + } + + /// Returns the number of field elements on the stack + #[inline] + pub fn raw_len(&self) -> usize { + self.stack.iter().map(|operand| operand.size()).sum() + } + + /// Returns the index in the actual runtime stack which corresponds to + /// the first element of the operand at `index`. + #[track_caller] + pub fn effective_index(&self, index: usize) -> usize { + assert!( + index < self.stack.len(), + "expected {} to be less than {}", + index, + self.stack.len() + ); + + self.stack.iter().rev().take(index).map(|o| o.size()).sum() + } + + /// Returns the index in the actual runtime stack which corresponds to + /// the last element of the operand at `index`. + #[track_caller] + pub fn effective_index_inclusive(&self, index: usize) -> usize { + assert!(index < self.stack.len()); + + self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum::() - 1 + } + + /// Returns the number of operands on the stack + #[inline] + pub fn len(&self) -> usize { + self.stack.len() + } + + /// Returns the Some(operand) at the given index, without consuming it. + /// If the index is out of bounds, returns None. + #[inline] + pub fn get(&self, index: usize) -> Option<&Operand> { + let effective_len: usize = self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum(); + assert!( + effective_len <= 16, + "invalid operand stack index ({index}): requires access to more than 16 elements, \ + which is not supported in Miden" + ); + let len = self.stack.len(); + if index >= len { + return None; + } + self.stack.get(len - index - 1) + } + + /// Returns the operand on top of the stack, without consuming it + #[inline] + pub fn peek(&self) -> Option<&Operand> { + self.stack.last() + } + + /// Pushes an operand on top of the stack + #[inline] + pub fn push>(&mut self, value: V) { + self.stack.push(value.into()); + } + + /// Pops the operand on top of the stack + #[inline] + #[track_caller] + pub fn pop(&mut self) -> Option { + self.stack.pop() + } + + /// Drops the top operand on the stack + #[allow(clippy::should_implement_trait)] + #[track_caller] + pub fn drop(&mut self) { + self.stack.pop().expect("operand stack is empty"); + } + + /// Drops the top `n` operands on the stack + #[inline] + #[track_caller] + pub fn dropn(&mut self, n: usize) { + let len = self.stack.len(); + assert!(n <= len, "unable to drop {n} operands, operand stack only has {len}"); + self.stack.truncate(len - n); + } + + /// Duplicates the operand in the `n`th position on the stack + /// + /// If `n` is 0, duplicates the top of the stack. + #[track_caller] + pub fn dup(&mut self, n: usize) { + let operand = self[n].clone(); + self.stack.push(operand); + } + + /// Swaps the `n`th operand from the top of the stack, with the top of the stack + /// + /// If `n` is 1, it swaps the first two operands on the stack. + /// + /// NOTE: This function will panic if `n` is 0, or out of bounds. + #[track_caller] + pub fn swap(&mut self, n: usize) { + assert_ne!(n, 0, "invalid swap, index must be in the range 1..=15"); + let len = self.stack.len(); + assert!(n < len, "invalid operand stack index ({n}), only {len} operands are available"); + let a = len - 1; + let b = a - n; + self.stack.swap(a, b); + } + + /// Moves the `n`th operand to the top of the stack + /// + /// If `n` is 1, this is equivalent to `swap(1)`. + /// + /// NOTE: This function will panic if `n` is 0, or out of bounds. + pub fn movup(&mut self, n: usize) { + assert_ne!(n, 0, "invalid move, index must be in the range 1..=15"); + let len = self.stack.len(); + assert!(n < len, "invalid operand stack index ({n}), only {len} operands are available"); + // Pick the midpoint by counting backwards from the end + let mid = len - (n + 1); + // Split the stack, and rotate the half that + // contains our desired value to place it on top. + let (_, r) = self.stack.split_at_mut(mid); + r.rotate_left(1); + } + + /// Makes the operand on top of the stack, the `n`th operand on the stack + /// + /// If `n` is 1, this is equivalent to `swap(1)`. + /// + /// NOTE: This function will panic if `n` is 0, or out of bounds. + pub fn movdn(&mut self, n: usize) { + assert_ne!(n, 0, "invalid move, index must be in the range 1..=15"); + let len = self.stack.len(); + assert!(n < len, "invalid operand stack index ({n}), only {len} operands are available"); + // Split the stack so that the desired position is in the top half + let mid = len - (n + 1); + let (_, r) = self.stack.split_at_mut(mid); + // Move all elements above the `n`th position up by one, moving the top element to the `n`th + // position + r.rotate_right(1); + } + + #[allow(unused)] + #[inline(always)] + pub fn iter(&self) -> impl DoubleEndedIterator { + self.stack.iter() + } +} +impl Index for OperandStack { + type Output = Operand; + + #[track_caller] + fn index(&self, index: usize) -> &Self::Output { + let len = self.stack.len(); + assert!( + index < len, + "invalid operand stack index ({index}): only {len} operands are available" + ); + let effective_len: usize = self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum(); + assert!( + effective_len <= 16, + "invalid operand stack index ({index}): requires access to more than 16 elements, \ + which is not supported in Miden" + ); + &self.stack[len - index - 1] + } +} +impl IndexMut for OperandStack { + #[track_caller] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + let len = self.stack.len(); + assert!( + index < len, + "invalid operand stack index ({index}): only {len} elements are available" + ); + let effective_len: usize = self.stack.iter().rev().take(index + 1).map(|o| o.size()).sum(); + assert!( + effective_len <= 16, + "invalid operand stack index ({index}): requires access to more than 16 elements, \ + which is not supported in Miden" + ); + &mut self.stack[len - index - 1] + } +} + +impl fmt::Debug for OperandStack { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut builder = f.debug_list(); + for (index, value) in self.stack.iter().rev().enumerate() { + builder.entry_with(|f| write!(f, "{index} => {value:?}")); + } + builder.finish() + } +} + +#[cfg(test)] +mod tests { + use alloc::rc::Rc; + + use midenc_hir::{ArrayType, BuilderExt, Context, PointerType, StructType}; + + use super::*; + + #[test] + fn operand_stack_homogenous_operand_sizes_test() { + let mut stack = OperandStack::default(); + + let zero = Immediate::U32(0); + let one = Immediate::U32(1); + let two = Immediate::U32(2); + let three = Immediate::U32(3); + + #[inline] + #[allow(unused)] + fn as_imms(word: [Operand; 4]) -> [Immediate; 4] { + [ + (&word[0]).try_into().unwrap(), + (&word[1]).try_into().unwrap(), + (&word[2]).try_into().unwrap(), + (&word[3]).try_into().unwrap(), + ] + } + + #[inline] + fn as_imm(operand: Operand) -> Immediate { + (&operand).try_into().unwrap() + } + + // push + stack.push(zero); + stack.push(one); + stack.push(two); + stack.push(three); + assert_eq!(stack.len(), 4); + assert_eq!(stack[0], three); + assert_eq!(stack[1], two); + assert_eq!(stack[2], one); + assert_eq!(stack[3], zero); + + // peek + assert_eq!(stack.peek().unwrap(), three); + + // dup + stack.dup(0); + assert_eq!(stack.len(), 5); + assert_eq!(stack[0], three); + assert_eq!(stack[1], three); + assert_eq!(stack[2], two); + assert_eq!(stack[3], one); + assert_eq!(stack[4], zero); + + stack.dup(3); + assert_eq!(stack.len(), 6); + assert_eq!(stack[0], one); + assert_eq!(stack[1], three); + assert_eq!(stack[2], three); + assert_eq!(stack[3], two); + assert_eq!(stack[4], one); + assert_eq!(stack[5], zero); + + // drop + stack.drop(); + assert_eq!(stack.len(), 5); + assert_eq!(stack[0], three); + assert_eq!(stack[1], three); + assert_eq!(stack[2], two); + assert_eq!(stack[3], one); + assert_eq!(stack[4], zero); + + // swap + stack.swap(2); + assert_eq!(stack.len(), 5); + assert_eq!(stack[0], two); + assert_eq!(stack[1], three); + assert_eq!(stack[2], three); + assert_eq!(stack[3], one); + assert_eq!(stack[4], zero); + + stack.swap(1); + assert_eq!(stack.len(), 5); + assert_eq!(stack[0], three); + assert_eq!(stack[1], two); + assert_eq!(stack[2], three); + assert_eq!(stack[3], one); + assert_eq!(stack[4], zero); + + // movup + stack.movup(2); + assert_eq!(stack.len(), 5); + assert_eq!(stack[0], three); + assert_eq!(stack[1], three); + assert_eq!(stack[2], two); + assert_eq!(stack[3], one); + assert_eq!(stack[4], zero); + + // movdn + stack.movdn(3); + assert_eq!(stack.len(), 5); + assert_eq!(stack[0], three); + assert_eq!(stack[1], two); + assert_eq!(stack[2], one); + assert_eq!(stack[3], three); + assert_eq!(stack[4], zero); + + // pop + assert_eq!(stack.pop().map(as_imm), Some(three)); + assert_eq!(stack.len(), 4); + assert_eq!(stack[0], two); + assert_eq!(stack[1], one); + assert_eq!(stack[2], three); + assert_eq!(stack[3], zero); + + // dropn + stack.dropn(2); + assert_eq!(stack.len(), 2); + assert_eq!(stack[0], three); + assert_eq!(stack[1], zero); + } + + #[test] + fn operand_stack_values_test() { + use midenc_dialect_hir::Load; + + let mut stack = OperandStack::default(); + let context = Rc::new(Context::default()); + + let ptr_u8 = Type::from(PointerType::new(Type::U8)); + let array_u8 = Type::from(ArrayType::new(Type::U8, 4)); + let struct_ty = Type::from(StructType::new([Type::U64, Type::U8])); + let block = context.create_block_with_params([ptr_u8, array_u8, Type::U32, struct_ty]); + let block = block.borrow(); + let values = block.arguments(); + + let zero = values[0] as ValueRef; + let one = values[1] as ValueRef; + let two = values[2] as ValueRef; + let three = values[3] as ValueRef; + + drop(block); + + // push + stack.push(zero); + stack.push(one); + stack.push(two); + stack.push(three); + assert_eq!(stack.len(), 4); + assert_eq!(stack.raw_len(), 6); + + assert_eq!(stack.find(&zero), Some(3)); + assert_eq!(stack.find(&one), Some(2)); + assert_eq!(stack.find(&two), Some(1)); + assert_eq!(stack.find(&three), Some(0)); + + // dup + stack.dup(0); + assert_eq!(stack.find(&three), Some(0)); + + stack.dup(3); + assert_eq!(stack.find(&one), Some(0)); + + // drop + stack.drop(); + assert_eq!(stack.find(&one), Some(3)); + assert_eq!(stack.find(&three), Some(0)); + assert_eq!(stack[1], three); + + // padw + stack.push(Immediate::Felt(Felt::ZERO)); + stack.push(Immediate::Felt(Felt::ZERO)); + stack.push(Immediate::Felt(Felt::ZERO)); + stack.push(Immediate::Felt(Felt::ZERO)); + assert_eq!(stack.find(&one), Some(7)); + assert_eq!(stack.find(&three), Some(4)); + + // rename + let four = { + let mut builder = midenc_hir::OpBuilder::new(context.clone()); + let load_builder = builder.create::(Default::default()); + let load = load_builder(zero).unwrap(); + load.borrow().result().as_value_ref() + }; + stack.rename(1, four); + assert_eq!(stack.find(&four), Some(1)); + assert_eq!(stack.find(&three), Some(4)); + + // pop + let top = stack.pop().unwrap(); + assert_eq!((&top).try_into(), Ok(Immediate::Felt(Felt::ZERO))); + assert_eq!(stack.find(&four), Some(0)); + assert_eq!(stack[1], Immediate::Felt(Felt::ZERO)); + assert_eq!(stack[2], Immediate::Felt(Felt::ZERO)); + assert_eq!(stack.find(&three), Some(3)); + + // dropn + stack.dropn(3); + assert_eq!(stack.find(&four), None); + assert_eq!(stack.find(&three), Some(0)); + assert_eq!(stack[1], three); + assert_eq!(stack.find(&two), Some(2)); + assert_eq!(stack.find(&one), Some(3)); + assert_eq!(stack.find(&zero), Some(4)); + + // swap + stack.swap(3); + assert_eq!(stack.find(&one), Some(0)); + assert_eq!(stack.find(&three), Some(1)); + assert_eq!(stack.find(&two), Some(2)); + assert_eq!(stack[3], three); + + stack.swap(1); + assert_eq!(stack.find(&three), Some(0)); + assert_eq!(stack.find(&one), Some(1)); + assert_eq!(stack.find(&two), Some(2)); + assert_eq!(stack.find(&zero), Some(4)); + + // movup + stack.movup(2); + assert_eq!(stack.find(&two), Some(0)); + assert_eq!(stack.find(&three), Some(1)); + assert_eq!(stack.find(&one), Some(2)); + assert_eq!(stack.find(&zero), Some(4)); + + // movdn + stack.movdn(3); + assert_eq!(stack.find(&three), Some(0)); + assert_eq!(stack.find(&one), Some(1)); + assert_eq!(stack[2], three); + assert_eq!(stack.find(&two), Some(3)); + assert_eq!(stack.find(&zero), Some(4)); + } + + #[test] + fn operand_stack_heterogenous_operand_sizes_test() { + let mut stack = OperandStack::default(); + + let zero = Immediate::U32(0); + let one = Immediate::U32(1); + let two = Type::U64; + let three = Type::U64; + let struct_a = Type::from(StructType::new([ + Type::from(PointerType::new(Type::U8)), + Type::U16, + Type::U32, + ])); + + // push + stack.push(zero); + stack.push(one); + stack.push(two.clone()); + stack.push(three.clone()); + stack.push(struct_a.clone()); + assert_eq!(stack.len(), 5); + assert_eq!(stack.raw_len(), 9); + assert_eq!(stack[0], struct_a); + assert_eq!(stack[1], three); + assert_eq!(stack[2], two); + assert_eq!(stack[3], one); + assert_eq!(stack[4], zero); + + // peek + assert_eq!(stack.peek().unwrap(), struct_a); + + // dup + stack.dup(0); + assert_eq!(stack.len(), 6); + assert_eq!(stack.raw_len(), 12); + assert_eq!(stack[0], struct_a); + assert_eq!(stack[1], struct_a); + assert_eq!(stack[2], three); + assert_eq!(stack[3], two); + assert_eq!(stack[4], one); + assert_eq!(stack[5], zero); + assert_eq!(stack.effective_index(3), 8); + + stack.dup(3); + assert_eq!(stack.len(), 7); + assert_eq!(stack.raw_len(), 14); + assert_eq!(stack[0], two); + assert_eq!(stack[1], struct_a); + assert_eq!(stack[2], struct_a); + + // drop + stack.drop(); + assert_eq!(stack.len(), 6); + assert_eq!(stack.raw_len(), 12); + assert_eq!(stack[0], struct_a); + assert_eq!(stack[1], struct_a); + assert_eq!(stack[2], three); + assert_eq!(stack[3], two); + assert_eq!(stack[4], one); + assert_eq!(stack[5], zero); + + // swap + stack.swap(2); + assert_eq!(stack.len(), 6); + assert_eq!(stack.raw_len(), 12); + assert_eq!(stack[0], three); + assert_eq!(stack[1], struct_a); + assert_eq!(stack[2], struct_a); + assert_eq!(stack[3], two); + assert_eq!(stack[4], one); + + stack.swap(1); + assert_eq!(stack.len(), 6); + assert_eq!(stack.raw_len(), 12); + assert_eq!(stack[0], struct_a); + assert_eq!(stack[1], three); + assert_eq!(stack[2], struct_a); + assert_eq!(stack[3], two); + assert_eq!(stack[4], one); + assert_eq!(stack[5], zero); + + // movup + stack.movup(4); + assert_eq!(stack.len(), 6); + assert_eq!(stack.raw_len(), 12); + assert_eq!(stack[0], one); + assert_eq!(stack[1], struct_a); + assert_eq!(stack[2], three); + assert_eq!(stack[3], struct_a); + assert_eq!(stack[4], two); + assert_eq!(stack[5], zero); + + // movdn + stack.movdn(3); + assert_eq!(stack.len(), 6); + assert_eq!(stack.raw_len(), 12); + assert_eq!(stack[0], struct_a); + assert_eq!(stack[1], three); + assert_eq!(stack[2], struct_a); + assert_eq!(stack[3], one); + assert_eq!(stack[4], two); + + // pop + let operand: Type = stack.pop().unwrap().try_into().unwrap(); + assert_eq!(operand, struct_a); + assert_eq!(stack.len(), 5); + assert_eq!(stack.raw_len(), 9); + assert_eq!(stack[0], three); + assert_eq!(stack[1], struct_a); + assert_eq!(stack[2], one); + assert_eq!(stack[3], two); + + // dropn + stack.dropn(2); + assert_eq!(stack.len(), 3); + assert_eq!(stack.raw_len(), 4); + assert_eq!(stack[0], one); + assert_eq!(stack[1], two); + assert_eq!(stack[2], zero); + } +} diff --git a/codegen/masm/src/tests.rs b/codegen/masm/src/tests.rs deleted file mode 100644 index a2d185051..000000000 --- a/codegen/masm/src/tests.rs +++ /dev/null @@ -1,1282 +0,0 @@ -#![allow(unused_imports)] -use std::sync::Arc; - -use midenc_hir::{ - self as hir, - pass::{AnalysisManager, ConversionPass}, - testing::{self, TestContext}, - AbiParam, CallConv, Felt, FieldElement, FunctionIdent, Immediate, InstBuilder, Linkage, - OperandStack, ProgramBuilder, Signature, SourceSpan, Stack, Type, -}; -use prop::test_runner::{Config, TestRunner}; -use proptest::prelude::*; -use smallvec::{smallvec, SmallVec}; - -use super::*; - -const MEMORY_SIZE_BYTES: u32 = 1048576 * 2; // Twice the size of the default Rust shadow stack size -const MEMORY_SIZE_VM_WORDS: u32 = MEMORY_SIZE_BYTES / 16; -/// In miden-sdk-alloc we require all allocations to be minimally word-aligned, i.e. 32 byte -/// alignment -const MIN_ALIGN: u32 = 32; - -#[cfg(test)] -#[allow(unused_macros)] -macro_rules! assert_masm_output { - ($lhs:expr, $rhs:expr) => {{ - let lhs = $lhs; - let rhs = $rhs; - if lhs != rhs { - panic!( - r#" -assertion failed: `(left matches right)` -left: `{}`, -right: `{}`"#, - lhs, rhs - ); - } - }}; -} - -struct TestByEmulationHarness { - context: TestContext, - emulator: Emulator, -} -impl Default for TestByEmulationHarness { - fn default() -> Self { - let mut harness = Self { - context: Default::default(), - emulator: Default::default(), - }; - // Ensure we request all codegen outputs - harness - .context - .session - .options - .output_types - .insert(midenc_session::OutputType::Masm, None); - harness - .context - .session - .options - .output_types - .insert(midenc_session::OutputType::Mast, None); - harness - } -} -#[allow(unused)] -impl TestByEmulationHarness { - pub fn with_emulator_config( - memory_size: usize, - hp: usize, - lp: usize, - print_stack: bool, - ) -> Self { - let mut harness = Self { - context: TestContext::default(), - emulator: Emulator::new( - memory_size.try_into().expect("invalid memory size"), - hp.try_into().expect("invalid address"), - lp.try_into().expect("invalid address"), - print_stack, - ), - }; - - // Ensure we request all codegen outputs - harness - .context - .session - .options - .output_types - .insert(midenc_session::OutputType::Masm, None); - harness - .context - .session - .options - .output_types - .insert(midenc_session::OutputType::Mast, None); - - // Set a default, reasonable, execution limit - harness.set_cycle_budget(2000); - harness - } - - pub fn codegen( - &self, - program: &hir::Program, - function: &mut hir::Function, - ) -> CompilerResult> { - use midenc_hir::{ - pass::{Analysis, RewritePass}, - ProgramAnalysisKey, - }; - use midenc_hir_analysis as analysis; - use midenc_hir_transform as transform; - - // Analyze function - let mut analyses = AnalysisManager::new(); - - // Register program-wide analyses - let global_analysis = analysis::GlobalVariableAnalysis::::analyze( - program, - &mut analyses, - &self.context.session, - )?; - analyses.insert(ProgramAnalysisKey, global_analysis); - - // Apply pre-codegen transformations - let mut rewrites = crate::default_function_rewrites(&self.context.session); - rewrites.apply(function, &mut analyses, &self.context.session)?; - - println!("{}", function); - - // Run stackification - let mut convert_to_masm = ConvertHirToMasm::<&hir::Function>::default(); - convert_to_masm - .convert(function, &mut analyses, &self.context.session) - .map(Box::new) - } - - pub fn set_cycle_budget(&mut self, budget: usize) { - self.emulator.set_max_cycles(budget); - } - - #[allow(unused)] - pub fn set_breakpoint(&mut self, bp: Breakpoint) { - self.emulator.set_breakpoint(bp); - } - - #[allow(unused)] - pub fn clear_breakpoint(&mut self, bp: Breakpoint) { - self.emulator.clear_breakpoint(bp); - } - - #[allow(unused)] - pub fn resume_until_break(&mut self) { - match self.emulator.resume() { - Ok(_) | Err(EmulationError::BreakpointHit(_)) => (), - Err(other) => panic!("unexpected emulation error: {other}"), - } - } - - #[allow(unused)] - pub fn resume_until_break_with_info(&mut self) { - match self.emulator.resume() { - Ok(_) | Err(EmulationError::BreakpointHit(_)) => { - dbg!(self.emulator.info()); - } - Err(other) => panic!("unexpected emulation error: {other}"), - } - } - - pub fn malloc(&mut self, size: usize) -> u32 { - self.emulator.malloc(size) - } - - #[inline(always)] - pub fn store(&mut self, addr: usize, value: Felt) { - self.emulator.store(addr, value); - } - - pub fn execute_module( - &mut self, - module: Arc, - args: &[Felt], - ) -> Result, EmulationError> { - let entrypoint = module.entrypoint().expect("cannot execute a library"); - self.emulator.load_module(module).expect("failed to load module"); - self.emulator.invoke(entrypoint, args) - } - - pub fn execute_program( - &mut self, - program: Arc, - args: &[Felt], - ) -> Result, EmulationError> { - self.emulator.load_program(program).expect("failed to load program"); - { - let stack = self.emulator.stack_mut(); - for arg in args.iter().copied().rev() { - stack.push(arg); - } - } - self.emulator.start() - } - - #[allow(unused)] - pub fn execute_program_with_entry( - &mut self, - program: Arc, - entrypoint: FunctionIdent, - args: &[Felt], - ) -> Result, EmulationError> { - self.emulator.load_program(program).expect("failed to load program"); - self.emulator.invoke(entrypoint, args) - } - - #[inline] - pub fn invoke( - &mut self, - entrypoint: FunctionIdent, - args: &[Felt], - ) -> Result, EmulationError> { - self.emulator.invoke(entrypoint, args) - } - - #[inline] - pub fn enter(&mut self, entrypoint: FunctionIdent, args: &[Felt]) { - match self.emulator.enter(entrypoint, args) { - Ok(_) | Err(EmulationError::BreakpointHit(_)) => { - dbg!(self.emulator.info()); - } - Err(other) => panic!("unexpected emulation error: {other}"), - } - } - - pub fn step_with_info(&mut self) { - match self.emulator.step() { - Ok(_) | Err(EmulationError::BreakpointHit(_)) => { - dbg!(self.emulator.info()); - } - Err(other) => panic!("unexpected emulation error: {other}"), - } - } - - #[inline] - pub fn step(&mut self) -> Result { - self.emulator.step() - } - - #[inline] - pub fn step_over(&mut self) -> Result { - self.emulator.step_over() - } - - fn reset(&mut self) { - self.emulator.reset(); - } -} - -#[test] -fn issue56() { - let harness = TestByEmulationHarness::default(); - - let mut builder = ProgramBuilder::new(&harness.context.session.diagnostics); - let mut mb = builder.module("test"); - testing::issue56(mb.as_mut(), &harness.context); - mb.build().unwrap(); - - let program = builder.with_entrypoint("test::entrypoint".parse().unwrap()).link().unwrap(); - - let mut compiler = MasmCompiler::new(&harness.context.session); - compiler.compile(program).expect("compilation failed"); -} - -/// Test the emulator on the fibonacci function -#[test] -fn fib_emulator() { - let mut harness = TestByEmulationHarness::default(); - - // Build a simple program - let mut builder = ProgramBuilder::new(&harness.context.session.diagnostics); - - // Build test module with fib function - let mut mb = builder.module("test"); - testing::fib1(mb.as_mut(), &harness.context); - mb.build().expect("unexpected error constructing test module"); - - // Link the program - let program = builder - .with_entrypoint("test::fib".parse().unwrap()) - .link() - .expect("failed to link program"); - - let mut compiler = MasmCompiler::new(&harness.context.session); - let program = compiler.compile(program).expect("compilation failed").unwrap_executable(); - - println!("{}", program.get("test").unwrap()); - - // Test it via the emulator - let n = Felt::new(10); - let mut stack = harness.execute_program(program.freeze(), &[n]).expect("execution failed"); - assert_eq!(stack.len(), 1); - assert_eq!(stack.pop().map(|e| e.as_int()), Some(55)); -} - -/// Test the code generator on a very simple program with a conditional as a sanity check -#[test] -fn codegen_fundamental_if() { - let mut harness = TestByEmulationHarness::default(); - - // Build a simple program - let mut builder = ProgramBuilder::new(&harness.context.session.diagnostics); - - // Build test module with function that adds two numbers if the - // first number is odd, and multiplies them if the first number is even - let mut mb = builder.module("test"); - let id = { - let mut fb = mb - .function( - "add_odd_mul_even", - Signature::new( - [AbiParam::new(Type::U32), AbiParam::new(Type::U32)], - [AbiParam::new(Type::U32)], - ), - ) - .expect("unexpected symbol conflict"); - let entry = fb.current_block(); - let (a, b) = { - let args = fb.block_params(entry); - (args[0], args[1]) - }; - let is_odd_blk = fb.create_block(); - let is_even_blk = fb.create_block(); - let is_odd = fb.ins().is_odd(a, SourceSpan::UNKNOWN); - fb.ins().cond_br(is_odd, is_odd_blk, &[], is_even_blk, &[], SourceSpan::UNKNOWN); - fb.switch_to_block(is_odd_blk); - let c = fb.ins().add_checked(a, b, SourceSpan::UNKNOWN); - fb.ins().ret(Some(c), SourceSpan::UNKNOWN); - fb.switch_to_block(is_even_blk); - let d = fb.ins().mul_checked(a, b, SourceSpan::UNKNOWN); - fb.ins().ret(Some(d), SourceSpan::UNKNOWN); - fb.build().expect("unexpected error building function") - }; - - mb.build().expect("unexpected error constructing test module"); - - // Link the program - let program = builder.with_entrypoint(id).link().expect("failed to link program"); - - let mut compiler = MasmCompiler::new(&harness.context.session); - let program = compiler.compile(program).expect("compilation failed").unwrap_executable(); - - let a = Felt::new(3); - let b = Felt::new(4); - - let mut stack = harness.execute_program(program.freeze(), &[a, b]).expect("execution failed"); - assert_eq!(stack.len(), 1); - assert_eq!(stack.pop().map(|e| e.as_int()), Some(12)); -} - -/// Test the code generator on a very simple program with a loop as a sanity check -#[test] -fn codegen_fundamental_loops() { - let mut harness = TestByEmulationHarness::default(); - - // Build a simple program - let mut builder = ProgramBuilder::new(&harness.context.session.diagnostics); - - // Build test module with function that increments a number until a - // given iteration count is reached - let mut mb = builder.module("test"); - let id = { - let mut fb = mb - .function( - "incr_until", - Signature::new( - [AbiParam::new(Type::U32), AbiParam::new(Type::U32)], - [AbiParam::new(Type::U32)], - ), - ) - .expect("unexpected symbol conflict"); - let entry = fb.current_block(); - let (a, n) = { - let args = fb.block_params(entry); - (args[0], args[1]) - }; - let loop_header_blk = fb.create_block(); - let a1 = fb.append_block_param(loop_header_blk, Type::U32, SourceSpan::UNKNOWN); - let n1 = fb.append_block_param(loop_header_blk, Type::U32, SourceSpan::UNKNOWN); - let loop_body_blk = fb.create_block(); - let loop_exit_blk = fb.create_block(); - let result0 = fb.append_block_param(loop_exit_blk, Type::U32, SourceSpan::UNKNOWN); - fb.ins().br(loop_header_blk, &[a, n], SourceSpan::UNKNOWN); - - fb.switch_to_block(loop_header_blk); - let is_zero = fb.ins().eq_imm(n1, Immediate::U32(0), SourceSpan::UNKNOWN); - fb.ins() - .cond_br(is_zero, loop_exit_blk, &[a1], loop_body_blk, &[], SourceSpan::UNKNOWN); - - fb.switch_to_block(loop_body_blk); - let a2 = fb.ins().incr_checked(a1, SourceSpan::UNKNOWN); - let n2 = fb.ins().sub_imm_checked(n1, Immediate::U32(1), SourceSpan::UNKNOWN); - fb.ins().br(loop_header_blk, &[a2, n2], SourceSpan::UNKNOWN); - - fb.switch_to_block(loop_exit_blk); - fb.ins().ret(Some(result0), SourceSpan::UNKNOWN); - - fb.build().expect("unexpected error building function") - }; - - mb.build().expect("unexpected error constructing test module"); - - // Link the program - let program = builder.with_entrypoint(id).link().expect("failed to link program"); - - let mut compiler = MasmCompiler::new(&harness.context.session); - let program = compiler.compile(program).expect("compilation failed").unwrap_executable(); - - let a = Felt::new(3); - let n = Felt::new(4); - - let mut stack = harness.execute_program(program.freeze(), &[a, n]).expect("execution failed"); - assert_eq!(stack.len(), 1); - assert_eq!(stack.pop().map(|e| e.as_int()), Some(7)); -} - -/// Test the code generator on a simple program containing [testing::sum_matrix]. -#[test] -fn codegen_sum_matrix() { - let mut harness = TestByEmulationHarness::default(); - - // Build a simple program - let mut builder = ProgramBuilder::new(&harness.context.session.diagnostics); - - // Build test module with fib function - let mut mb = builder.module("test"); - testing::sum_matrix(mb.as_mut(), &harness.context); - mb.build().expect("unexpected error constructing test module"); - - // Link the program - let program = builder - .with_entrypoint("test::sum_matrix".parse().unwrap()) - .link() - .expect("failed to link program"); - - // Compile - let mut compiler = MasmCompiler::new(&harness.context.session); - let program = compiler.compile(program).expect("compilation failed").unwrap_executable(); - - println!("{}", program.get("test").unwrap()); - - // Prep emulator - let addr = harness.malloc(core::mem::size_of::() * 3 * 3); - let ptr = Felt::new(addr as u64); - let rows = Felt::new(3); - let cols = Felt::new(3); - - // [1, 0, 1, - // 0, 1, 0, - // 1, 1, 1] == 6 - let addr = addr as usize; - harness.store(addr, Felt::ONE); - harness.store(addr + 4, Felt::ZERO); - harness.store(addr + 8, Felt::ONE); - harness.store(addr + 12, Felt::ZERO); - harness.store(addr + 16, Felt::ONE); - harness.store(addr + 20, Felt::ZERO); - harness.store(addr + 24, Felt::ONE); - harness.store(addr + 28, Felt::ONE); - harness.store(addr + 32, Felt::ONE); - - // Execute test::sum_matrix - let mut stack = harness - .execute_program(program.freeze(), &[ptr, rows, cols]) - .expect("execution failed"); - assert_eq!(stack.len(), 1); - assert_eq!(stack.pop().map(|e| e.as_int()), Some(6)); -} - -#[test] -#[should_panic(expected = "assertion failed: expected false, got true")] -fn i32_checked_neg() { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let min = Felt::new(i32::MIN as u32 as u64); - - let neg = "intrinsics::i32::checked_neg".parse().unwrap(); - // i32::MIN - harness.invoke(neg, &[min]).expect("execution failed"); -} - -#[test] -fn codegen_mem_store_sw_load_sw() { - let context = TestContext::default(); - let mut builder = ProgramBuilder::new(&context.session.diagnostics); - let mut mb = builder.module("test"); - let id = { - let mut fb = mb - .function( - "store_load_sw", - Signature::new( - [AbiParam::new(Type::U32), AbiParam::new(Type::U32)], - [AbiParam::new(Type::U32)], - ), - ) - .expect("unexpected symbol conflict"); - let entry = fb.current_block(); - let (ptr_u32, value) = { - let args = fb.block_params(entry); - (args[0], args[1]) - }; - let ptr = fb.ins().inttoptr(ptr_u32, Type::Ptr(Type::U32.into()), SourceSpan::UNKNOWN); - fb.ins().store(ptr, value, SourceSpan::UNKNOWN); - let loaded_value = fb.ins().load(ptr, SourceSpan::UNKNOWN); - fb.ins().ret(Some(loaded_value), SourceSpan::UNKNOWN); - fb.build().expect("unexpected error building function") - }; - - mb.build().expect("unexpected error constructing test module"); - - let program = builder.with_entrypoint(id).link().expect("failed to link program"); - - let mut compiler = MasmCompiler::new(&context.session); - let program = compiler - .compile(program) - .expect("compilation failed") - .unwrap_executable() - .freeze(); - - // eprintln!("{}", program); - - fn roundtrip(program: Arc, ptr: u32, value: u32) -> u32 { - eprintln!("---------------------------------"); - eprintln!("testing store_sw/load_sw ptr: {ptr}, value: {value}"); - eprintln!("---------------------------------"); - let mut harness = TestByEmulationHarness::with_emulator_config( - MEMORY_SIZE_VM_WORDS as usize, - Emulator::DEFAULT_HEAP_START as usize, - Emulator::DEFAULT_LOCALS_START as usize, - true, - ); - let mut stack = harness - .execute_program(program.clone(), &[Felt::new(ptr as u64), Felt::new(value as u64)]) - .expect("execution failed"); - stack.pop().unwrap().as_int() as u32 - } - - TestRunner::new(Config::with_cases(1024)) - .run(&(0u32..MEMORY_SIZE_BYTES - 4, any::()), move |(ptr, value)| { - let out = roundtrip(program.clone(), ptr, value); - prop_assert_eq!(out, value); - Ok(()) - }) - .unwrap(); -} - -#[test] -fn codegen_mem_store_dw_load_dw() { - let context = TestContext::default(); - let mut builder = ProgramBuilder::new(&context.session.diagnostics); - let mut mb = builder.module("test"); - let id = { - let mut fb = mb - .function( - "store_load_dw", - Signature::new( - [AbiParam::new(Type::U32), AbiParam::new(Type::U64)], - [AbiParam::new(Type::U64)], - ), - ) - .expect("unexpected symbol conflict"); - let entry = fb.current_block(); - let (ptr_u32, value) = { - let args = fb.block_params(entry); - (args[0], args[1]) - }; - let ptr = fb.ins().inttoptr(ptr_u32, Type::Ptr(Type::U64.into()), SourceSpan::UNKNOWN); - fb.ins().store(ptr, value, SourceSpan::UNKNOWN); - let loaded_value = fb.ins().load(ptr, SourceSpan::UNKNOWN); - fb.ins().ret(Some(loaded_value), SourceSpan::UNKNOWN); - fb.build().expect("unexpected error building function") - }; - - mb.build().expect("unexpected error constructing test module"); - - let program = builder.with_entrypoint(id).link().expect("failed to link program"); - - let ir_module = program - .modules() - .iter() - .take(1) - .collect::>() - .first() - .expect("no module in IR program") - .to_string(); - - eprintln!("{}", ir_module.as_str()); - - let mut compiler = MasmCompiler::new(&context.session); - let program = compiler - .compile(program) - .expect("compilation failed") - .unwrap_executable() - .freeze(); - - eprintln!("{}", program); - - fn roundtrip(program: Arc, ptr: u32, value: u64) -> u64 { - eprintln!("---------------------------------"); - eprintln!("testing store_dw/load_dw ptr: {ptr}, value: {value}"); - eprintln!("---------------------------------"); - let mut harness = TestByEmulationHarness::with_emulator_config( - MEMORY_SIZE_VM_WORDS as usize, - Emulator::DEFAULT_HEAP_START as usize, - Emulator::DEFAULT_LOCALS_START as usize, - true, - ); - let mut args: SmallVec<[Felt; 4]> = smallvec!(Felt::new(ptr as u64)); - args.extend(value.canonicalize()); - let mut stack = harness.execute_program(program.clone(), &args).expect("execution failed"); - u64::from_stack(&mut stack) - } - - TestRunner::new(Config::with_cases(1024)) - .run(&(0u32..MEMORY_SIZE_BYTES - 4, any::()), move |(ptr, value)| { - let out = roundtrip(program.clone(), ptr, value); - prop_assert_eq!(out, value); - Ok(()) - }) - .unwrap(); -} - -#[test] -fn codegen_mem_store_felt_load_felt() { - let context = TestContext::default(); - let mut builder = ProgramBuilder::new(&context.session.diagnostics); - let mut mb = builder.module("test"); - let id = { - let mut fb = mb - .function( - "store_load_felt", - Signature::new( - [AbiParam::new(Type::U32), AbiParam::new(Type::Felt)], - [AbiParam::new(Type::Felt)], - ), - ) - .expect("unexpected symbol conflict"); - let entry = fb.current_block(); - let (ptr_u32, value) = { - let args = fb.block_params(entry); - (args[0], args[1]) - }; - let ptr = fb.ins().inttoptr(ptr_u32, Type::Ptr(Type::Felt.into()), SourceSpan::UNKNOWN); - fb.ins().store(ptr, value, SourceSpan::UNKNOWN); - let loaded_value = fb.ins().load(ptr, SourceSpan::UNKNOWN); - fb.ins().ret(Some(loaded_value), SourceSpan::UNKNOWN); - fb.build().expect("unexpected error building function") - }; - - mb.build().expect("unexpected error constructing test module"); - - let program = builder.with_entrypoint(id).link().expect("failed to link program"); - - let ir_module = program - .modules() - .iter() - .take(1) - .collect::>() - .first() - .expect("no module in IR program") - .to_string(); - - eprintln!("{}", ir_module.as_str()); - - let mut compiler = MasmCompiler::new(&context.session); - let program = compiler - .compile(program) - .expect("compilation failed") - .unwrap_executable() - .freeze(); - - eprintln!("{}", program); - - fn roundtrip(program: Arc, ptr: u32, value: Felt) -> Felt { - eprintln!("---------------------------------"); - eprintln!("testing store_felt/load_felt ptr: {ptr}, value: {value}"); - eprintln!("---------------------------------"); - let mut harness = TestByEmulationHarness::with_emulator_config( - MEMORY_SIZE_VM_WORDS as usize, - Emulator::DEFAULT_HEAP_START as usize, - Emulator::DEFAULT_LOCALS_START as usize, - true, - ); - let mut stack = harness - .execute_program(program.clone(), &[Felt::new(ptr as u64), value]) - .expect("execution failed"); - stack.pop().unwrap() - } - - TestRunner::new(Config::with_cases(1024)) - .run( - &(0u32..(MEMORY_SIZE_BYTES / MIN_ALIGN - 1), (0u64..u64::MAX).prop_map(Felt::new)), - move |(word_ptr, value)| { - // a felt memory pointer must be naturally aligned, i.e. a multiple of MIN_ALIGN - let ptr = word_ptr * MIN_ALIGN; - let out = roundtrip(program.clone(), ptr, value); - prop_assert_eq!(out, value); - Ok(()) - }, - ) - .unwrap(); -} - -#[allow(unused)] -macro_rules! proptest_unary_numeric_op { - ($ty_name:ident :: $op:ident, $ty:ty => $ret:ty, $rust_op:ident) => { - proptest_unary_numeric_op_impl!($ty_name :: $op, $ty => $ret, $rust_op, 0..$ty_name::MAX); - }; - - ($ty_name:ident :: $op:ident, $ty:ty => $ret:ty, $rust_op:ident, $strategy:expr) => { - proptest_unary_numeric_op_impl!($ty_name :: $op, $ty => $ret, $rust_op, $strategy); - }; -} - -#[allow(unused)] -macro_rules! proptest_unary_numeric_op_impl { - ($ty_name:ident :: $op:ident, $ty:ty => $ret:ty, $rust_op:ident, $strategy:expr) => { - paste::paste! { - #[test] - fn [<$ty_name _ $op>]() { - let mut harness = TestByEmulationHarness::default(); - - // Build a simple program that invokes the clz instruction - let mut builder = ProgramBuilder::new(&harness.context.session.diagnostics); - - let mut mb = builder.module("test"); - let sig = Signature { - params: vec![AbiParam::new(<$ty as ToCanonicalRepr>::ir_type())], - results: vec![AbiParam::new(<$ret as ToCanonicalRepr>::ir_type())], - cc: CallConv::SystemV, - linkage: Linkage::External, - }; - let mut fb = mb.function(stringify!([<$ty_name _ $op>]), sig).expect("unexpected symbol conflict"); - - let entry = fb.current_block(); - // Get the value for `v0` - let v0 = { - let args = fb.block_params(entry); - args[0] - }; - - let v1 = fb.ins().$op(v0, harness.context.current_span()); - fb.ins().ret(Some(v1), harness.context.current_span()); - - let entrypoint = fb.build().expect("unexpected validation error, see diagnostics output"); - mb.build().expect("unexpected error constructing test module"); - - // Link the program - let program = builder.with_entrypoint(entrypoint).link().expect("failed to link program"); - - // Compile - let mut compiler = MasmCompiler::new(&harness.context.session); - let program = compiler.compile(program).expect("compilation failed").unwrap_executable(); - - harness - .emulator - .load_program(program.freeze()) - .expect("failed to load test program"); - - let harness = RefCell::new(harness); - - proptest!(ProptestConfig { cases: 1000, failure_persistence: None, ..Default::default() }, |(n in ($strategy))| { - let mut harness = harness.borrow_mut(); - harness.emulator.stop(); - - // Convert to canonical Miden representation, N field elements, each containing a - // 32-bit chunk, highest bits closest to top of stack. - let elems = n.canonicalize(); - - let mut stack = harness.invoke(entrypoint, &elems).expect("execution failed"); - harness.emulator.stop(); - prop_assert_eq!(stack.len(), <$ret as ToCanonicalRepr>::ir_type().size_in_felts()); - - // Obtain the count of leading zeroes from stack and check that it matches the expected - // count - let result = <$ret as ToCanonicalRepr>::from_stack(&mut stack); - prop_assert_eq!(result, n.$rust_op()); - }); - } - } - }; -} - -//proptest_unary_numeric_op!(u64::clz, u64 => u32, leading_zeros); -//proptest_unary_numeric_op!(i128::clz, i128 => u32, leading_zeros); -//proptest_unary_numeric_op!(u64::ctz, u64 => u32, trailing_zeros); -//proptest_unary_numeric_op!(i128::ctz, i128 => u32, trailing_zeros); -//proptest_unary_numeric_op!(u64::clo, u64 => u32, leading_ones); -//proptest_unary_numeric_op!(i128::clo, i128 => u32, leading_ones); -//proptest_unary_numeric_op!(u64::cto, u64 => u32, trailing_ones); -//proptest_unary_numeric_op!(i128::cto, i128 => u32, trailing_ones); -//proptest_unary_numeric_op!(u64::ilog2, u64 => u32, ilog2, 1..u64::MAX); -//proptest_unary_numeric_op!(i128::ilog2, i128 => u32, ilog2, 1..i128::MAX); - -#[allow(unused)] -trait ToCanonicalRepr { - fn ir_type() -> Type; - fn canonicalize(self) -> SmallVec<[Felt; 4]>; - fn from_stack(stack: &mut OperandStack) -> Self; -} - -impl ToCanonicalRepr for u8 { - fn ir_type() -> Type { - Type::U8 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - smallvec![Felt::new(self as u64)] - } - - fn from_stack(stack: &mut OperandStack) -> Self { - let raw = stack.pop().unwrap().as_int(); - u8::try_from(raw).unwrap_or_else(|_| { - panic!("invalid result: expected valid u8, but value is out of range: {raw}") - }) - } -} - -impl ToCanonicalRepr for i8 { - fn ir_type() -> Type { - Type::I8 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - smallvec![Felt::new(self as u8 as u64)] - } - - fn from_stack(stack: &mut OperandStack) -> Self { - ::from_stack(stack) as i8 - } -} - -impl ToCanonicalRepr for u16 { - fn ir_type() -> Type { - Type::U16 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - smallvec![Felt::new(self as u64)] - } - - fn from_stack(stack: &mut OperandStack) -> Self { - let raw = stack.pop().unwrap().as_int(); - u16::try_from(raw).unwrap_or_else(|_| { - panic!("invalid result: expected valid u16, but value is out of range: {raw}") - }) - } -} - -impl ToCanonicalRepr for i16 { - fn ir_type() -> Type { - Type::I16 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - smallvec![Felt::new(self as u16 as u64)] - } - - fn from_stack(stack: &mut OperandStack) -> Self { - ::from_stack(stack) as i16 - } -} - -impl ToCanonicalRepr for u32 { - fn ir_type() -> Type { - Type::U32 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - smallvec![Felt::new(self as u64)] - } - - fn from_stack(stack: &mut OperandStack) -> Self { - let raw = stack.pop().unwrap().as_int(); - u32::try_from(raw).unwrap_or_else(|_| { - panic!("invalid result: expected valid u32, but value is out of range: {raw}") - }) - } -} - -impl ToCanonicalRepr for i32 { - fn ir_type() -> Type { - Type::I32 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - smallvec![Felt::new(self as u32 as u64)] - } - - fn from_stack(stack: &mut OperandStack) -> Self { - ::from_stack(stack) as i32 - } -} - -impl ToCanonicalRepr for u64 { - fn ir_type() -> Type { - Type::U64 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - let lo = self.rem_euclid(2u64.pow(32)); - let hi = self.div_euclid(2u64.pow(32)); - smallvec![Felt::new(hi), Felt::new(lo)] - } - - fn from_stack(stack: &mut OperandStack) -> Self { - let hi = stack.pop().unwrap().as_int() * 2u64.pow(32); - let lo = stack.pop().unwrap().as_int(); - hi + lo - } -} - -impl ToCanonicalRepr for i64 { - fn ir_type() -> Type { - Type::I64 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - (self as u64).canonicalize() - } - - fn from_stack(stack: &mut OperandStack) -> Self { - ::from_stack(stack) as i64 - } -} - -impl ToCanonicalRepr for i128 { - fn ir_type() -> Type { - Type::I128 - } - - fn canonicalize(self) -> SmallVec<[Felt; 4]> { - let lo = self.rem_euclid(2i128.pow(64)); - let hi = self.div_euclid(2i128.pow(64)); - let mut out = (hi as u64).canonicalize(); - out.extend_from_slice(&(lo as u64).canonicalize()); - out - } - - fn from_stack(stack: &mut OperandStack) -> Self { - let hi = (::from_stack(stack) as i128) * 2i128.pow(64); - let lo = ::from_stack(stack) as i128; - hi + lo - } -} - -proptest! { - #![proptest_config(ProptestConfig { cases: 1000, failure_persistence: None, ..Default::default() })] - - #[test] - fn i32_icmp(a: i32, b: i32) { - use core::cmp::Ordering; - - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsics module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - // NOTE: arguments are passed in reverse, i.e. [b, a] not [a, b] - let icmp = "intrinsics::i32::icmp".parse().unwrap(); - let is_gt = "intrinsics::i32::is_gt".parse().unwrap(); - let is_lt = "intrinsics::i32::is_lt".parse().unwrap(); - - let a_felt = Felt::new(a as u32 as u64); - let b_felt = Felt::new(b as u32 as u64); - let mut stack = harness.invoke(icmp, &[b_felt, a_felt]).expect("execution failed"); - harness.emulator.stop(); - prop_assert_eq!(stack.len(), 1); - - const NEG_ONE_U64: u64 = -1i32 as u32 as u64; - let result = match stack.pop().unwrap().as_int() { - NEG_ONE_U64 => Ordering::Less, - 0 => Ordering::Equal, - 1 => Ordering::Greater, - other => panic!("invalid comparison result, expected -1, 0, or 1 but got {other}"), - }; - - prop_assert_eq!(result, a.cmp(&b)); - - let mut stack = harness.invoke(is_gt, &[b_felt, a_felt]).expect("execution failed"); - harness.emulator.stop(); - prop_assert_eq!(stack.len(), 1); - - let result = match stack.pop().unwrap().as_int() { - 0 => false, - 1 => true, - other => panic!("invalid boolean result, expected 0 or 1, got {other}"), - }; - - prop_assert_eq!(result, a.cmp(&b).is_gt()); - - let mut stack = harness.invoke(is_lt, &[b_felt, a_felt]).expect("execution failed"); - harness.emulator.stop(); - prop_assert_eq!(stack.len(), 1); - - let result = match stack.pop().unwrap().as_int() { - 0 => false, - 1 => true, - other => panic!("invalid boolean result, expected 0 or 1, got {other}"), - }; - - prop_assert_eq!(result, a.cmp(&b).is_lt()); - } - - #[test] - fn i32_is_signed(a: i32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let is_signed = "intrinsics::i32::is_signed".parse().unwrap(); - let mut stack = harness.invoke(is_signed, &[a_felt]).expect("execution failed"); - prop_assert_eq!(stack.len(), 1); - let result = stack.pop().unwrap().as_int(); - - prop_assert_eq!(result, a.is_negative() as u64); - } - - #[test] - fn i32_overflowing_add(a: i32, b: i32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let b_felt = Felt::new(b as u32 as u64); - // NOTE: arguments are passed in reverse, i.e. [b, a] not [a, b] - let add = "intrinsics::i32::overflowing_add".parse().unwrap(); - let mut stack = harness - .invoke(add, &[b_felt, a_felt]) - .expect("execution failed"); - prop_assert_eq!(stack.len(), 2); - - let overflowed = stack.pop().unwrap() == Felt::ONE; - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - let (expected_result, expected_overflow) = a.overflowing_add(b); - - prop_assert_eq!((result, overflowed), (Ok(expected_result), expected_overflow)); - } - - #[test] - fn i32_overflowing_sub(a: i32, b: i32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let b_felt = Felt::new(b as u32 as u64); - // NOTE: arguments are passed in reverse, i.e. [b, a] not [a, b] - let sub = "intrinsics::i32::overflowing_sub".parse().unwrap(); - let mut stack = harness - .invoke(sub, &[b_felt, a_felt]) - .expect("execution failed"); - prop_assert_eq!(stack.len(), 2); - - let overflowed = stack.pop().unwrap() == Felt::ONE; - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - let (expected_result, expected_overflow) = a.overflowing_sub(b); - - prop_assert_eq!((result, overflowed), (Ok(expected_result), expected_overflow)); - } - - #[test] - fn i32_overflowing_mul(a: i32, b: i32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let b_felt = Felt::new(b as u32 as u64); - // NOTE: arguments are passed in reverse, i.e. [b, a] not [a, b] - let mul = "intrinsics::i32::overflowing_mul".parse().unwrap(); - let mut stack = harness - .invoke(mul, &[b_felt, a_felt]) - .expect("execution failed"); - prop_assert_eq!(stack.len(), 2); - - let overflowed = stack.pop().unwrap() == Felt::ONE; - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - let (expected_result, expected_overflow) = a.overflowing_mul(b); - - prop_assert_eq!((result, overflowed), (Ok(expected_result), expected_overflow)); - } - - #[test] - fn i32_unchecked_neg(a: i32) { - prop_assume!(a != i32::MIN, "unchecked_neg is meaningless for i32::MIN"); - - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let neg = "intrinsics::i32::unchecked_neg".parse().unwrap(); - let mut stack = harness.invoke(neg, &[a_felt]).expect("execution failed"); - - prop_assert_eq!(stack.len(), 1); - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - prop_assert_eq!(result, Ok(-a)); - } - - #[test] - fn i32_checked_div(a: i32, b: core::num::NonZeroI32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let b_felt = Felt::new(b.get() as u32 as u64); - let div = "intrinsics::i32::checked_div".parse().unwrap(); - let mut stack = harness.invoke(div, &[b_felt, a_felt]).expect("execution failed"); - - prop_assert_eq!(stack.len(), 1); - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - prop_assert_eq!(result, Ok(a / b.get())); - } - - #[test] - fn i32_pow2(a in 0u32..30u32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u64); - let pow2 = "intrinsics::i32::pow2".parse().unwrap(); - let mut stack = harness.invoke(pow2, &[a_felt]).expect("execution failed"); - - prop_assert_eq!(stack.len(), 1); - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - prop_assert_eq!(result, Ok(2i32.pow(a))); - } - - #[test] - fn i32_ipow(a: i32, b in 0u32..30u32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let b_felt = Felt::new(b as u64); - let ipow = "intrinsics::i32::ipow".parse().unwrap(); - let mut stack = harness.invoke(ipow, &[b_felt, a_felt]).expect("execution failed"); - - prop_assert_eq!(stack.len(), 1); - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - prop_assert_eq!(result, Ok(a.wrapping_pow(b))); - } - - #[test] - fn i32_checked_shr(a: i32, b in 0i32..32i32) { - let mut harness = TestByEmulationHarness::default(); - - harness - .emulator - .load_module( - Box::new( - intrinsics::load("intrinsics::i32", &harness.context.session.source_manager) - .expect("undefined intrinsic module"), - ) - .freeze(), - ) - .expect("failed to load intrinsics::i32"); - - let a_felt = Felt::new(a as u32 as u64); - let b_felt = Felt::new(b as u32 as u64); - let shr = "intrinsics::i32::checked_shr".parse().unwrap(); - let mut stack = harness.invoke(shr, &[b_felt, a_felt]).expect("execution failed"); - - prop_assert_eq!(stack.len(), 1); - let raw_result = stack.pop().unwrap().as_int(); - let result = u32::try_from(raw_result).map_err(|_| raw_result).map(|res| res as i32); - prop_assert_eq!(result, Ok(a >> b)); - } -} diff --git a/dialects/arith/CHANGELOG.md b/dialects/arith/CHANGELOG.md new file mode 100644 index 000000000..5b464ca3d --- /dev/null +++ b/dialects/arith/CHANGELOG.md @@ -0,0 +1,21 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-dialect-arith-v0.4.0...midenc-dialect-arith-v0.4.1) - 2025-09-03 + +### Other + +- Add type aliases `Int128` for `SizedInt<128>`. +- Address PR comments with some extra comments. +- Add 128-bit wide arithmetic support to the compiler. + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-dialect-arith-v0.1.5...midenc-dialect-arith-v0.4.0) - 2025-08-15 + +### Other + +- Add $ParentTrait pattern to verify macro + Add SameTypeOperands as a explicit dependency diff --git a/dialects/arith/Cargo.toml b/dialects/arith/Cargo.toml new file mode 100644 index 000000000..60da568af --- /dev/null +++ b/dialects/arith/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "midenc-dialect-arith" +description = "Miden IR Arithmetic Dialect" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["midenc-hir/std"] + +[dependencies] +midenc-hir.workspace = true +paste.workspace = true diff --git a/dialects/arith/src/builders.rs b/dialects/arith/src/builders.rs new file mode 100644 index 000000000..baed65142 --- /dev/null +++ b/dialects/arith/src/builders.rs @@ -0,0 +1,561 @@ +use midenc_hir::{ + dialects::builtin::FunctionBuilder, Builder, BuilderExt, Felt, OpBuilder, Overflow, Report, + SourceSpan, ValueRef, +}; + +use crate::*; + +pub trait ArithOpBuilder<'f, B: ?Sized + Builder> { + /* + fn character(&mut self, c: char, span: SourceSpan) -> Value { + self.i32((c as u32) as i32, span) + } + */ + + fn i1(&mut self, value: bool, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::I1(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn i8(&mut self, value: i8, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::I8(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn i16(&mut self, value: i16, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::I16(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn i32(&mut self, value: i32, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::I32(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn i64(&mut self, value: i64, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::I64(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn u8(&mut self, value: u8, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::U8(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn u16(&mut self, value: u16, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::U16(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn u32(&mut self, value: u32, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::U32(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn u64(&mut self, value: u64, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::U64(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn f64(&mut self, value: f64, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::F64(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn felt(&mut self, value: Felt, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::Felt(value)).unwrap(); + constant.borrow().result().as_value_ref() + } + + fn imm(&mut self, value: Immediate, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(value).unwrap(); + constant.borrow().result().as_value_ref() + } + + /// Truncates an integral value as necessary to fit in `ty`. + /// + /// NOTE: Truncating a value into a larger type has undefined behavior, it is + /// equivalent to extending a value without doing anything with the new high-order + /// bits of the resulting value. + fn trunc(&mut self, arg: ValueRef, ty: Type, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(arg, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Extends an integer into a larger integeral type, by zero-extending the value, + /// i.e. the new high-order bits of the resulting value will be all zero. + /// + /// NOTE: This function will panic if `ty` is smaller than `arg`. + /// + /// If `arg` is the same type as `ty`, `arg` is returned as-is + fn zext(&mut self, arg: ValueRef, ty: Type, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(arg, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Extends an integer into a larger integeral type, by sign-extending the value, + /// i.e. the new high-order bits of the resulting value will all match the sign bit. + /// + /// NOTE: This function will panic if `ty` is smaller than `arg`. + /// + /// If `arg` is the same type as `ty`, `arg` is returned as-is + fn sext(&mut self, arg: ValueRef, ty: Type, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(arg, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement addition which traps on overflow + fn add(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Checked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Unchecked two's complement addition. Behavior is undefined if the result overflows. + fn add_unchecked( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Unchecked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement addition which wraps around on overflow, e.g. `wrapping_add` + fn add_wrapping( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Wrapping)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement addition which wraps around on overflow, but returns a boolean flag that + /// indicates whether or not the operation overflowed, followed by the wrapped result, e.g. + /// `overflowing_add` (but with the result types inverted compared to Rust's version). + fn add_overflowing( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result<(ValueRef, ValueRef), Report> { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + let op = op.borrow(); + let overflowed = op.overflowed().as_value_ref(); + let result = op.result().as_value_ref(); + Ok((overflowed, result)) + } + + /// Two's complement subtraction which traps on under/overflow + fn sub(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Checked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Unchecked two's complement subtraction. Behavior is undefined if the result under/overflows. + fn sub_unchecked( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Unchecked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement subtraction which wraps around on under/overflow, e.g. `wrapping_sub` + fn sub_wrapping( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Wrapping)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement subtraction which wraps around on overflow, but returns a boolean flag that + /// indicates whether or not the operation under/overflowed, followed by the wrapped result, + /// e.g. `overflowing_sub` (but with the result types inverted compared to Rust's version). + fn sub_overflowing( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result<(ValueRef, ValueRef), Report> { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + let op = op.borrow(); + let overflowed = op.overflowed().as_value_ref(); + let result = op.result().as_value_ref(); + Ok((overflowed, result)) + } + + /// Two's complement multiplication which traps on overflow + fn mul(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Checked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Unchecked two's complement multiplication. Behavior is undefined if the result overflows. + fn mul_unchecked( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Unchecked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement multiplication which wraps around on overflow, e.g. `wrapping_mul` + fn mul_wrapping( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, Overflow::Wrapping)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement multiplication which wraps around on overflow, but returns a boolean flag + /// that indicates whether or not the operation overflowed, followed by the wrapped result, + /// e.g. `overflowing_mul` (but with the result types inverted compared to Rust's version). + fn mul_overflowing( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result<(ValueRef, ValueRef), Report> { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + let op = op.borrow(); + let overflowed = op.overflowed().as_value_ref(); + let result = op.result().as_value_ref(); + Ok((overflowed, result)) + } + + /// Integer division. Traps if `rhs` is zero. + fn div(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Integer Euclidean modulo. Traps if `rhs` is zero. + fn r#mod( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Combined integer Euclidean division and modulo. Traps if `rhs` is zero. + fn divmod( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result<(ValueRef, ValueRef), Report> { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + let op = op.borrow(); + let quotient = op.quotient().as_value_ref(); + let remainder = op.remainder().as_value_ref(); + Ok((quotient, remainder)) + } + + /// Exponentiation + fn exp(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Compute 2^n + fn pow2(&mut self, n: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(n)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Compute ilog2(n) + fn ilog2(&mut self, n: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(n)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Modular inverse + fn inv(&mut self, n: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(n)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Unary negation + fn neg(&mut self, n: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(n)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement unary increment by one which traps on overflow + fn incr(&mut self, lhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Logical AND + fn and(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Logical OR + fn or(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Logical XOR + fn xor(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Logical NOT + fn not(&mut self, lhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Bitwise AND + fn band(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Bitwise OR + fn bor(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Bitwise XOR + fn bxor(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Bitwise NOT + fn bnot(&mut self, lhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn rotl(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn rotr(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn shl(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn shr(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn popcnt(&mut self, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn clz(&mut self, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn ctz(&mut self, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn clo(&mut self, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn cto(&mut self, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn eq(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn neq(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Compares two integers and returns the minimum value + fn min(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Compares two integers and returns the maximum value + fn max(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn gt(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn gte(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn lt(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn lte(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn join(&mut self, hi: ValueRef, lo: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(hi, lo)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn split(&mut self, n: ValueRef, span: SourceSpan) -> Result<(ValueRef, ValueRef), Report> { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(n)?.borrow(); + let lo = op.result_low().as_value_ref(); + let hi = op.result_high().as_value_ref(); + Ok((hi, lo)) + } + + #[allow(clippy::wrong_self_convention)] + fn is_odd(&mut self, value: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(value)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +impl<'f, B: ?Sized + Builder> ArithOpBuilder<'f, B> for FunctionBuilder<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + FunctionBuilder::builder(self) + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + FunctionBuilder::builder_mut(self) + } +} + +impl<'f> ArithOpBuilder<'f, OpBuilder> for &'f mut OpBuilder { + #[inline(always)] + fn builder(&self) -> &OpBuilder { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut OpBuilder { + self + } +} + +impl ArithOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/dialects/arith/src/lib.rs b/dialects/arith/src/lib.rs new file mode 100644 index 000000000..caec65a40 --- /dev/null +++ b/dialects/arith/src/lib.rs @@ -0,0 +1,180 @@ +#![no_std] +#![feature(debug_closure_helpers)] +#![feature(unboxed_closures)] +#![feature(fn_traits)] +#![feature(ptr_metadata)] +#![feature(specialization)] +#![allow(incomplete_features)] +#![deny(warnings)] + +extern crate alloc; + +#[cfg(any(feature = "std", test))] +extern crate std; + +use alloc::boxed::Box; + +mod builders; +mod ops; + +use midenc_hir::{ + AttributeValue, Builder, BuilderExt, Dialect, DialectInfo, DialectRegistration, Immediate, + OperationRef, SourceSpan, Type, +}; + +pub use self::{builders::ArithOpBuilder, ops::*}; + +#[derive(Debug)] +pub struct ArithDialect { + info: DialectInfo, +} + +impl ArithDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl Dialect for ArithDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + builder: &mut dyn Builder, + attr: Box, + ty: &Type, + span: SourceSpan, + ) -> Option { + // Save the current insertion point + let mut builder = midenc_hir::InsertionGuard::new(builder); + + // Only integer constants are supported here + if !ty.is_integer() { + return None; + } + + // Currently, we expect folds to produce `Immediate`-valued attributes for integer-likes + if let Some(&imm) = attr.downcast_ref::() { + // If the immediate value is of the same type as the expected result type, we're ready + // to materialize the constant + let imm_ty = imm.ty(); + if &imm_ty == ty { + let op_builder = builder.create::(span); + return op_builder(imm).ok().map(|op| op.as_operation_ref()); + } + + // The immediate value has a different type than expected, but we can coerce types, so + // long as the value fits in the target type + if imm_ty.size_in_bits() > ty.size_in_bits() { + return None; + } + + let imm = match ty { + Type::I8 => match imm { + Immediate::I1(value) => Immediate::I8(value as i8), + Immediate::U8(value) => Immediate::I8(i8::try_from(value).ok()?), + _ => return None, + }, + Type::U8 => match imm { + Immediate::I1(value) => Immediate::U8(value as u8), + Immediate::I8(value) => Immediate::U8(u8::try_from(value).ok()?), + _ => return None, + }, + Type::I16 => match imm { + Immediate::I1(value) => Immediate::I16(value as i16), + Immediate::I8(value) => Immediate::I16(value as i16), + Immediate::U8(value) => Immediate::I16(value.into()), + Immediate::U16(value) => Immediate::I16(i16::try_from(value).ok()?), + _ => return None, + }, + Type::U16 => match imm { + Immediate::I1(value) => Immediate::U16(value as u16), + Immediate::I8(value) => Immediate::U16(u16::try_from(value).ok()?), + Immediate::U8(value) => Immediate::U16(value as u16), + Immediate::I16(value) => Immediate::U16(u16::try_from(value).ok()?), + _ => return None, + }, + Type::I32 => Immediate::I32(imm.as_i32()?), + Type::U32 => Immediate::U32(imm.as_u32()?), + Type::I64 => Immediate::I64(imm.as_i64()?), + Type::U64 => Immediate::U64(imm.as_u64()?), + Type::I128 => Immediate::I128(imm.as_i128()?), + Type::U128 => Immediate::U128(imm.as_u128()?), + Type::Felt => Immediate::Felt(imm.as_felt()?), + ty => unimplemented!("unrecognized integral type '{ty}'"), + }; + + let op_builder = builder.create::(span); + return op_builder(imm).ok().map(|op| op.as_operation_ref()); + } + + None + } +} + +impl DialectRegistration for ArithDialect { + const NAMESPACE: &'static str = "arith"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + } +} diff --git a/dialects/arith/src/ops.rs b/dialects/arith/src/ops.rs new file mode 100644 index 000000000..c96e75a9f --- /dev/null +++ b/dialects/arith/src/ops.rs @@ -0,0 +1,22 @@ +macro_rules! has_no_effects { + ($Op:ty) => { + impl ::midenc_hir::effects::EffectOpInterface<::midenc_hir::effects::MemoryEffect> for $Op { + fn has_no_effect(&self) -> bool { + true + } + + fn effects( + &self, + ) -> ::midenc_hir::effects::EffectIterator<::midenc_hir::effects::MemoryEffect> { + ::midenc_hir::effects::EffectIterator::from_smallvec(::midenc_hir::smallvec![]) + } + } + }; +} + +mod binary; +mod coercions; +mod constants; +mod unary; + +pub use self::{binary::*, coercions::*, constants::*, unary::*}; diff --git a/dialects/arith/src/ops/binary.rs b/dialects/arith/src/ops/binary.rs new file mode 100644 index 000000000..5ff009bad --- /dev/null +++ b/dialects/arith/src/ops/binary.rs @@ -0,0 +1,687 @@ +use midenc_hir::{derive::operation, effects::*, traits::*, *}; + +use crate::ArithDialect; + +// Implement `derive(InferTypeOpInterface)` with `#[infer]` helper attribute: +// +// * `#[infer]` on a result field indicates its type should be inferred from the type of the first +// operand field +// * `#[infer(from = field)]` on a result field indicates its type should be inferred from +// the given field. The field is expected to implement `AsRef` +// * `#[infer(type = I1)]` on a field indicates that the field should always be inferred to have the given type +// * `#[infer(with = path::to::function)]` on a field indicates that the given function should be called to +// compute the inferred type for that field +macro_rules! infer_return_ty_for_binary_op { + ($Op:ty) => { + impl InferTypeOpInterface for $Op { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let lhs = self.lhs().ty().clone(); + self.result_mut().set_type(lhs); + Ok(()) + } + } + + }; + + + ($Op:ty as $manually_specified_ty:expr) => { + paste::paste! { + impl InferTypeOpInterface for $Op { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.result_mut().set_type($manually_specified_ty); + Ok(()) + } + } + } + }; + + ($Op:ty, $($manually_specified_field_name:ident : $manually_specified_field_ty:expr),+) => { + paste::paste! { + impl InferTypeOpInterface for $Op { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let lhs = self.lhs().ty().clone(); + self.result_mut().set_type(lhs); + $( + self.[<$manually_specified_field_name _mut>]().set_type($manually_specified_field_ty); + )* + Ok(()) + } + } + } + }; +} + +/// Two's complement sum +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Add { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, + #[attr] + overflow: Overflow, +} + +infer_return_ty_for_binary_op!(Add); +has_no_effects!(Add); + +/// Two's complement sum with overflow bit +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct AddOverflowing { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + overflowed: Bool, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(AddOverflowing, overflowed: Type::I1); +has_no_effects!(AddOverflowing); + +/// Two's complement difference (subtraction) +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Sub { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, + #[attr] + overflow: Overflow, +} + +infer_return_ty_for_binary_op!(Sub); +has_no_effects!(Sub); + +/// Two's complement difference (subtraction) with underflow bit +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct SubOverflowing { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + overflowed: Bool, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(SubOverflowing, overflowed: Type::I1); +has_no_effects!(SubOverflowing); + +/// Two's complement product +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Mul { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, + #[attr] + overflow: Overflow, +} + +infer_return_ty_for_binary_op!(Mul); +has_no_effects!(Mul); + +/// Two's complement product with overflow bit +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct MulOverflowing { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + overflowed: Bool, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(MulOverflowing, overflowed: Type::I1); +has_no_effects!(MulOverflowing); + +/// Exponentiation for field elements +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Exp { + #[operand] + lhs: IntFelt, + #[operand] + rhs: IntFelt, + #[result] + result: IntFelt, +} + +infer_return_ty_for_binary_op!(Exp); +has_no_effects!(Exp); + +/// Unsigned integer division, traps on division by zero +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Div { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Div); +has_no_effects!(Div); + +/// Signed integer division, traps on division by zero or dividing the minimum signed value by -1 +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Sdiv { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Sdiv); +has_no_effects!(Sdiv); + +/// Unsigned integer Euclidean modulo, traps on division by zero +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Mod { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Mod); +has_no_effects!(Mod); + +/// Signed integer Euclidean modulo, traps on division by zero +/// +/// The result has the same sign as the dividend (lhs) +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Smod { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Smod); +has_no_effects!(Smod); + +/// Combined unsigned integer Euclidean division and remainder (modulo). +/// +/// Traps on division by zero. +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Divmod { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + remainder: AnyInteger, + #[result] + quotient: AnyInteger, +} + +has_no_effects!(Divmod); + +impl InferTypeOpInterface for Divmod { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let lhs = self.lhs().ty().clone(); + self.remainder_mut().set_type(lhs.clone()); + self.quotient_mut().set_type(lhs); + Ok(()) + } +} + +/// Combined signed integer Euclidean division and remainder (modulo). +/// +/// Traps on division by zero. +/// +/// The remainder has the same sign as the dividend (lhs) +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Sdivmod { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + remainder: AnyInteger, + #[result] + quotient: AnyInteger, +} + +has_no_effects!(Sdivmod); + +impl InferTypeOpInterface for Sdivmod { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let lhs = self.lhs().ty().clone(); + self.remainder_mut().set_type(lhs.clone()); + self.quotient_mut().set_type(lhs); + Ok(()) + } +} + +/// Logical AND +/// +/// Operands must be boolean. +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct And { + #[operand] + lhs: Bool, + #[operand] + rhs: Bool, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(And); +has_no_effects!(And); + +/// Logical OR +/// +/// Operands must be boolean. +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Or { + #[operand] + lhs: Bool, + #[operand] + rhs: Bool, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Or); +has_no_effects!(Or); + +/// Logical XOR +/// +/// Operands must be boolean. +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Xor { + #[operand] + lhs: Bool, + #[operand] + rhs: Bool, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Xor); +has_no_effects!(Xor); + +/// Bitwise AND +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Band { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Band); +has_no_effects!(Band); + +/// Bitwise OR +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Bor { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Bor); +has_no_effects!(Bor); + +/// Bitwise XOR +/// +/// Operands must be boolean. +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Bxor { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Bxor); +has_no_effects!(Bxor); + +/// Bitwise shift-left +/// +/// Shifts larger than the bitwidth of the value will be wrapped to zero. +#[operation( + dialect = ArithDialect, + traits(BinaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Shl { + #[operand] + lhs: AnyInteger, + #[operand] + shift: UInt32, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Shl); +has_no_effects!(Shl); + +/// Bitwise (logical) shift-right +/// +/// Shifts larger than the bitwidth of the value will effectively truncate the value to zero. +#[operation( + dialect = ArithDialect, + traits(BinaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Shr { + #[operand] + lhs: AnyInteger, + #[operand] + shift: UInt32, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Shr); +has_no_effects!(Shr); + +/// Arithmetic (signed) shift-right +/// +/// The result of shifts larger than the bitwidth of the value depend on the sign of the value; +/// for positive values, it rounds to zero; for negative values, it rounds to MIN. +#[operation( + dialect = ArithDialect, + traits(BinaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Ashr { + #[operand] + lhs: AnyInteger, + #[operand] + shift: UInt32, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Ashr); +has_no_effects!(Ashr); + +/// Bitwise rotate-left +/// +/// The rotation count must be < the bitwidth of the value type. +#[operation( + dialect = ArithDialect, + traits(BinaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Rotl { + #[operand] + lhs: AnyInteger, + #[operand] + shift: UInt32, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Rotl); +has_no_effects!(Rotl); + +/// Bitwise rotate-right +/// +/// The rotation count must be < the bitwidth of the value type. +#[operation( + dialect = ArithDialect, + traits(BinaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Rotr { + #[operand] + lhs: AnyInteger, + #[operand] + shift: UInt32, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Rotr); +has_no_effects!(Rotr); + +/// Equality comparison +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Eq { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Eq as Type::I1); +has_no_effects!(Eq); + +/// Inequality comparison +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Neq { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Neq as Type::I1); +has_no_effects!(Neq); + +/// Greater-than comparison +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Gt { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Gt as Type::I1); +has_no_effects!(Gt); + +/// Greater-than-or-equal comparison +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Gte { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Gte as Type::I1); +has_no_effects!(Gte); + +/// Less-than comparison +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Lt { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Lt as Type::I1); +has_no_effects!(Lt); + +/// Less-than-or-equal comparison +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Lte { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Lte as Type::I1); +has_no_effects!(Lte); + +/// Select minimum value +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Min { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Min); +has_no_effects!(Min); + +/// Select maximum value +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Max { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Max); +has_no_effects!(Max); diff --git a/dialects/arith/src/ops/coercions.rs b/dialects/arith/src/ops/coercions.rs new file mode 100644 index 000000000..6e6cf0ba9 --- /dev/null +++ b/dialects/arith/src/ops/coercions.rs @@ -0,0 +1,285 @@ +use alloc::boxed::Box; + +use midenc_hir::{ + derive::operation, effects::MemoryEffectOpInterface, matchers::Matcher, traits::*, *, +}; + +use crate::*; + +#[operation( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct Trunc { + #[operand] + operand: AnyInteger, + #[attr(hidden)] + ty: Type, + #[result] + result: AnyInteger, +} + +has_no_effects!(Trunc); + +impl InferTypeOpInterface for Trunc { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl Foldable for Trunc { + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + if let Some(mut value) = + matchers::foldable_operand_of::().matches(&self.operand().as_operand_ref()) + { + let truncated = match self.ty() { + Type::I1 => value.as_u64().map(|v| Immediate::I1((v & 0x01u64) == 1)), + Type::I8 => value.as_i64().map(|v| Immediate::I8(v as i8)), + Type::U8 => value.as_u64().map(|v| Immediate::U8(v as u8)), + Type::I16 => value.as_i64().map(|v| Immediate::I16(v as i16)), + Type::U16 => value.as_u64().map(|v| Immediate::U16(v as u16)), + Type::I32 => value.as_i64().map(|v| Immediate::I32(v as i32)), + Type::U32 => value.as_u64().map(|v| Immediate::U32(v as u32)), + Type::I64 => value.as_i128().map(|v| Immediate::I64(v as i64)), + Type::U64 => value.as_u128().map(|v| Immediate::U64(v as u64)), + _ => return FoldResult::Failed, + }; + + if let Some(truncated) = truncated { + *value = truncated; + results.push(OpFoldResult::Attribute(value)); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } + + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + if let Some(value) = operands[0].as_deref().and_then(|o| o.downcast_ref::()) { + let truncated = match self.ty() { + Type::I1 => value.as_u64().map(|v| Immediate::I1((v & 0x01u64) == 1)), + Type::I8 => value.as_i64().map(|v| Immediate::I8(v as i8)), + Type::U8 => value.as_u64().map(|v| Immediate::U8(v as u8)), + Type::I16 => value.as_i64().map(|v| Immediate::I16(v as i16)), + Type::U16 => value.as_u64().map(|v| Immediate::U16(v as u16)), + Type::I32 => value.as_i64().map(|v| Immediate::I32(v as i32)), + Type::U32 => value.as_u64().map(|v| Immediate::U32(v as u32)), + Type::I64 => value.as_i128().map(|v| Immediate::I64(v as i64)), + Type::U64 => value.as_u128().map(|v| Immediate::U64(v as u64)), + _ => return FoldResult::Failed, + }; + if let Some(truncated) = truncated { + results.push(OpFoldResult::Attribute(Box::new(truncated))); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } +} + +#[operation( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct Zext { + #[operand] + operand: AnyUnsignedInteger, + #[attr(hidden)] + ty: Type, + #[result] + result: AnyUnsignedInteger, +} + +has_no_effects!(Zext); + +impl InferTypeOpInterface for Zext { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl Foldable for Zext { + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + if let Some(mut value) = + matchers::foldable_operand_of::().matches(&self.operand().as_operand_ref()) + { + let extended = match self.ty() { + Type::U8 => value.as_u32().and_then(|v| u8::try_from(v).ok()).map(Immediate::U8), + Type::U16 => value.as_u32().and_then(|v| u16::try_from(v).ok()).map(Immediate::U16), + Type::U32 => value.as_u32().map(Immediate::U32), + Type::U64 => value.as_u64().map(Immediate::U64), + Type::U128 => value.as_u128().map(Immediate::U128), + _ => return FoldResult::Failed, + }; + + if let Some(extended) = extended { + *value = extended; + results.push(OpFoldResult::Attribute(value)); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } + + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + if let Some(value) = operands[0].as_deref().and_then(|o| o.downcast_ref::()) { + let extended = match self.ty() { + Type::U8 => value.as_u32().and_then(|v| u8::try_from(v).ok()).map(Immediate::U8), + Type::U16 => value.as_u32().and_then(|v| u16::try_from(v).ok()).map(Immediate::U16), + Type::U32 => value.as_u32().map(Immediate::U32), + Type::U64 => value.as_u64().map(Immediate::U64), + Type::U128 => value.as_u128().map(Immediate::U128), + _ => return FoldResult::Failed, + }; + if let Some(extended) = extended { + results.push(OpFoldResult::Attribute(Box::new(extended))); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } +} + +#[operation( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct Sext { + #[operand] + operand: AnySignedInteger, + #[attr(hidden)] + ty: Type, + #[result] + result: AnySignedInteger, +} + +has_no_effects!(Sext); + +impl InferTypeOpInterface for Sext { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl Foldable for Sext { + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + if let Some(mut value) = + matchers::foldable_operand_of::().matches(&self.operand().as_operand_ref()) + { + let extended = match self.ty() { + Type::I8 => value.as_i32().and_then(|v| i8::try_from(v).ok()).map(Immediate::I8), + Type::I16 => value.as_i32().and_then(|v| i16::try_from(v).ok()).map(Immediate::I16), + Type::I32 => value.as_i32().map(Immediate::I32), + Type::I64 => value.as_i64().map(Immediate::I64), + Type::I128 => value.as_i128().map(Immediate::I128), + _ => return FoldResult::Failed, + }; + + if let Some(extended) = extended { + *value = extended; + results.push(OpFoldResult::Attribute(value)); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } + + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + if let Some(value) = operands[0].as_deref().and_then(|o| o.downcast_ref::()) { + let extended = match self.ty() { + Type::I8 => value.as_i32().and_then(|v| i8::try_from(v).ok()).map(Immediate::I8), + Type::I16 => value.as_i32().and_then(|v| i16::try_from(v).ok()).map(Immediate::I16), + Type::I32 => value.as_i32().map(Immediate::I32), + Type::I64 => value.as_i64().map(Immediate::I64), + Type::I128 => value.as_i128().map(Immediate::I128), + _ => return FoldResult::Failed, + }; + if let Some(extended) = extended { + results.push(OpFoldResult::Attribute(Box::new(extended))); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } +} + +/// Join two 64bit integers into one 128 bit integer. +#[operation( + dialect = ArithDialect, + traits(BinaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Join { + #[operand] + high_limb: Int64, + #[operand] + low_limb: Int64, + #[result] + result: Int128, +} + +has_no_effects!(Join); + +impl InferTypeOpInterface for Join { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.result_mut().set_type(Type::I128); + Ok(()) + } +} + +/// Split a 128bit integer into two 64 bit integers. +/// +/// The result is a pair of 64bit integers, e.g., `v1, v2 = arith.split v0 : i64, i64;` where `v1` +/// is the high limb and `v2` is the low limb. +/// +/// It also will leave the high limb on the top of the working stack during codegen. +#[operation( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Split { + #[operand] + operand: Int128, + #[result] + result_high: Int64, + #[result] + result_low: Int64, +} + +has_no_effects!(Split); + +impl InferTypeOpInterface for Split { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.result_low_mut().set_type(Type::I64); + self.result_high_mut().set_type(Type::I64); + Ok(()) + } +} diff --git a/dialects/arith/src/ops/constants.rs b/dialects/arith/src/ops/constants.rs new file mode 100644 index 000000000..6d9a89b0d --- /dev/null +++ b/dialects/arith/src/ops/constants.rs @@ -0,0 +1,48 @@ +use alloc::boxed::Box; + +use midenc_hir::{derive::operation, effects::MemoryEffectOpInterface, traits::*, *}; + +use crate::*; + +/// An operation for expressing constant immediate values. +/// +/// This is used to materialize folded constants for the arithmetic dialect. +#[operation( + dialect = ArithDialect, + traits(ConstantLike), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct Constant { + #[attr(hidden)] + value: Immediate, + #[result] + result: AnyInteger, +} + +has_no_effects!(Constant); + +impl InferTypeOpInterface for Constant { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.value().ty(); + self.result_mut().set_type(ty); + + Ok(()) + } +} + +impl Foldable for Constant { + #[inline] + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + results.push(OpFoldResult::Attribute(self.get_attribute("value").unwrap().clone_value())); + FoldResult::Ok(()) + } + + #[inline(always)] + fn fold_with( + &self, + _operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + self.fold(results) + } +} diff --git a/dialects/arith/src/ops/unary.rs b/dialects/arith/src/ops/unary.rs new file mode 100644 index 000000000..4d6255eac --- /dev/null +++ b/dialects/arith/src/ops/unary.rs @@ -0,0 +1,235 @@ +use midenc_hir::{derive::operation, effects::MemoryEffectOpInterface, traits::*, *}; + +use crate::*; + +macro_rules! infer_return_ty_for_unary_op { + ($Op:ty) => { + impl InferTypeOpInterface for $Op { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let lhs = self.operand().ty().clone(); + self.result_mut().set_type(lhs); + Ok(()) + } + } + }; + + ($Op:ty as $manually_specified_ty:expr) => { + paste::paste! { + impl InferTypeOpInterface for $Op { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.result_mut().set_type($manually_specified_ty); + Ok(()) + } + } + } + }; +} + +/// Increment +#[operation ( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Incr { + #[operand] + operand: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_unary_op!(Incr); +has_no_effects!(Incr); + +/// Negation +#[operation ( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Neg { + #[operand] + operand: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_unary_op!(Neg); +has_no_effects!(Neg); + +/// Modular inverse +#[operation ( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Inv { + #[operand] + operand: IntFelt, + #[result] + result: IntFelt, +} + +infer_return_ty_for_unary_op!(Inv); +has_no_effects!(Inv); + +/// log2(operand) +#[operation ( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Ilog2 { + #[operand] + operand: IntFelt, + #[result] + result: IntFelt, +} + +infer_return_ty_for_unary_op!(Ilog2); +has_no_effects!(Ilog2); + +/// pow2(operand) +#[operation ( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Pow2 { + #[operand] + operand: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_unary_op!(Pow2); +has_no_effects!(Pow2); + +/// Logical NOT +#[operation ( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + + )] +pub struct Not { + #[operand] + operand: Bool, + #[result] + result: Bool, +} + +infer_return_ty_for_unary_op!(Not); +has_no_effects!(Not); + +/// Bitwise NOT +#[operation ( + dialect = ArithDialect, + traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Bnot { + #[operand] + operand: AnyInteger, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_unary_op!(Bnot); +has_no_effects!(Bnot); + +/// is_odd(operand) +#[operation ( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct IsOdd { + #[operand] + operand: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_unary_op!(IsOdd as Type::I1); +has_no_effects!(IsOdd); + +/// Count of non-zero bits (population count) +#[operation ( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Popcnt { + #[operand] + operand: AnyInteger, + #[result] + result: UInt32, +} + +infer_return_ty_for_unary_op!(Popcnt as Type::U32); +has_no_effects!(Popcnt); + +/// Count Leading Zeros +#[operation ( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Clz { + #[operand] + operand: AnyInteger, + #[result] + result: UInt32, +} + +infer_return_ty_for_unary_op!(Clz as Type::U32); +has_no_effects!(Clz); + +/// Count Trailing Zeros +#[operation ( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Ctz { + #[operand] + operand: AnyInteger, + #[result] + result: UInt32, +} + +infer_return_ty_for_unary_op!(Ctz as Type::U32); +has_no_effects!(Ctz); + +/// Count Leading Ones +#[operation ( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Clo { + #[operand] + operand: AnyInteger, + #[result] + result: UInt32, +} + +infer_return_ty_for_unary_op!(Clo as Type::U32); +has_no_effects!(Clo); + +/// Count Trailing Ones +#[operation ( + dialect = ArithDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) + )] +pub struct Cto { + #[operand] + operand: AnyInteger, + #[result] + result: UInt32, +} + +infer_return_ty_for_unary_op!(Cto as Type::U32); +has_no_effects!(Cto); diff --git a/dialects/cf/CHANGELOG.md b/dialects/cf/CHANGELOG.md new file mode 100644 index 000000000..d94e3d6c6 --- /dev/null +++ b/dialects/cf/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] diff --git a/dialects/cf/Cargo.toml b/dialects/cf/Cargo.toml new file mode 100644 index 000000000..be6d9f74d --- /dev/null +++ b/dialects/cf/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "midenc-dialect-cf" +description = "Miden IR Control Flow Dialect" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["midenc-hir/std"] + +[dependencies] +log.workspace = true +midenc-dialect-arith.workspace = true +midenc-hir.workspace = true diff --git a/dialects/cf/src/builders.rs b/dialects/cf/src/builders.rs new file mode 100644 index 000000000..0e19a15fe --- /dev/null +++ b/dialects/cf/src/builders.rs @@ -0,0 +1,107 @@ +use midenc_hir::{ + dialects::builtin::FunctionBuilder, BlockRef, Builder, BuilderExt, OpBuilder, Report, + SourceSpan, UnsafeIntrusiveEntityRef, ValueRef, +}; + +use crate::*; + +pub trait ControlFlowOpBuilder<'f, B: ?Sized + Builder> { + fn br( + &mut self, + block: BlockRef, + args: A, + span: SourceSpan, + ) -> Result, Report> + where + A: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + op_builder(block, args) + } + + fn cond_br( + &mut self, + cond: ValueRef, + then_dest: BlockRef, + then_args: T, + else_dest: BlockRef, + else_args: F, + span: SourceSpan, + ) -> Result, Report> + where + T: IntoIterator, + F: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + op_builder(cond, then_dest, then_args, else_dest, else_args) + } + + fn switch( + &mut self, + selector: ValueRef, + cases: TCases, + fallback: BlockRef, + fallback_args: TFallbackArgs, + span: SourceSpan, + ) -> Result, Report> + where + TCases: IntoIterator, + TFallbackArgs: IntoIterator, + { + let op_builder = self + .builder_mut() + .create::(span); + op_builder(selector, fallback, fallback_args, cases) + } + + fn select( + &mut self, + cond: ValueRef, + a: ValueRef, + b: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(cond, a, b)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +impl<'f, B: ?Sized + Builder> ControlFlowOpBuilder<'f, B> for FunctionBuilder<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + FunctionBuilder::builder(self) + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + FunctionBuilder::builder_mut(self) + } +} + +impl<'f> ControlFlowOpBuilder<'f, OpBuilder> for &'f mut OpBuilder { + #[inline(always)] + fn builder(&self) -> &OpBuilder { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut OpBuilder { + self + } +} + +impl ControlFlowOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/dialects/cf/src/canonicalization.rs b/dialects/cf/src/canonicalization.rs new file mode 100644 index 000000000..426098603 --- /dev/null +++ b/dialects/cf/src/canonicalization.rs @@ -0,0 +1,19 @@ +mod simplify_br_to_block_with_single_pred; +mod simplify_br_to_return; +mod simplify_cond_br_like_switch; +mod simplify_passthrough_br; +mod simplify_passthrough_cond_br; +mod simplify_successor_arguments; +mod simplify_switch_fallback_overlap; +mod split_critical_edges; + +pub use self::{ + simplify_br_to_block_with_single_pred::SimplifyBrToBlockWithSinglePred, + simplify_br_to_return::SimplifyBrToReturn, + simplify_cond_br_like_switch::SimplifyCondBrLikeSwitch, + simplify_passthrough_br::SimplifyPassthroughBr, + simplify_passthrough_cond_br::SimplifyPassthroughCondBr, + simplify_successor_arguments::RemoveUnusedSinglePredBlockArgs, + simplify_switch_fallback_overlap::SimplifySwitchFallbackOverlap, + split_critical_edges::SplitCriticalEdges, +}; diff --git a/dialects/cf/src/canonicalization/simplify_br_to_block_with_single_pred.rs b/dialects/cf/src/canonicalization/simplify_br_to_block_with_single_pred.rs new file mode 100644 index 000000000..35c3c01c6 --- /dev/null +++ b/dialects/cf/src/canonicalization/simplify_br_to_block_with_single_pred.rs @@ -0,0 +1,69 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Simplify a branch to a block that has a single predecessor. This effectively merges the two +/// blocks. +pub struct SimplifyBrToBlockWithSinglePred { + info: PatternInfo, +} + +impl SimplifyBrToBlockWithSinglePred { + pub fn new(context: Rc) -> Self { + let cf_dialect = context.get_or_register_dialect::(); + let br_op = cf_dialect.registered_name::
().expect("cf.br is not registered"); + Self { + info: PatternInfo::new( + context, + "simplify-br-to-block-with-single-predecessor", + PatternKind::Operation(br_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for SimplifyBrToBlockWithSinglePred { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for SimplifyBrToBlockWithSinglePred { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(br_op) = op.downcast_ref::
() else { + return Ok(false); + }; + + // Check that the successor block has a single predecessor. + let target = br_op.target(); + let succ = target.successor(); + let parent = op.parent().unwrap(); + if succ == parent || succ.borrow().get_single_predecessor().is_none() { + return Ok(false); + } + + // Merge the successor into the current block and erase the branch. + let operands = target + .arguments + .iter() + .map(|o| Some(o.borrow().as_value_ref())) + .collect::>(); + + drop(op); + rewriter.erase_op(operation); + rewriter.merge_blocks(succ, parent, &operands); + + Ok(true) + } +} diff --git a/dialects/cf/src/canonicalization/simplify_br_to_return.rs b/dialects/cf/src/canonicalization/simplify_br_to_return.rs new file mode 100644 index 000000000..e4c74c5a1 --- /dev/null +++ b/dialects/cf/src/canonicalization/simplify_br_to_return.rs @@ -0,0 +1,146 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + dialects::builtin::{self, BuiltinOpBuilder}, + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Simplify a branch to a block that contains only a `builtin.ret`. +/// +/// This applies in cases where we cannot rely on [super::SimplifyBrToBlockWithSinglePred], or +/// [super::SimplifyPassthroughBr] to be applied because doing so would introduce critical edges. +/// The branch is redundant when we can simply lift the return instead. +/// +/// This transformation is only safe (and only applied) when the successor: +/// +/// 1. Only contains a `builtin.return` +/// 2. Either: +/// a. Has a single predecessor +/// b. The `builtin.return` returns no value, or returns a value whose definition is either a +/// block argument of the successor, or dominates the predecessor `cf.br` +pub struct SimplifyBrToReturn { + info: PatternInfo, +} + +impl SimplifyBrToReturn { + pub fn new(context: Rc) -> Self { + let cf_dialect = context.get_or_register_dialect::(); + let br_op = cf_dialect.registered_name::
().expect("cf.br is not registered"); + Self { + info: PatternInfo::new( + context, + "simplify-br-to-return", + PatternKind::Operation(br_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for SimplifyBrToReturn { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for SimplifyBrToReturn { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(br_op) = op.downcast_ref::
() else { + return Ok(false); + }; + + let target = br_op.target(); + let succ = target.successor(); + let parent = op.parent().unwrap(); + + // If the successor is the parent block of this `cf.br`, this transform does not apply + if succ == parent { + return Ok(false); + } + + let successor = succ.borrow(); + let Some(terminator) = successor.terminator() else { + return Ok(false); + }; + + // If there are more operations in the successor block than just the return, this transform + // is not applied. + if successor.body().front().as_pointer() != Some(terminator) { + return Ok(false); + } + + // Check if the successor block contains a single `builtin.return` + let terminator_op = terminator.borrow(); + let terminator_ret_imm = terminator_op.downcast_ref::(); + let is_terminator_return = + terminator_op.is::() || terminator_ret_imm.is_some(); + if !is_terminator_return { + return Ok(false); + } + + // Determine if we're the sole predecessor of the successor block + let is_sole_predecessor = + successor.get_single_predecessor().is_some_and(|pred| pred == parent); + + // If we're the sole predecessor, we can merge the successor entirely + if is_sole_predecessor { + drop(successor); + + // Merge the successor into the current block and erase the branch. + let operands = target + .arguments + .iter() + .map(|o| Some(o.borrow().as_value_ref())) + .collect::>(); + + drop(op); + rewriter.erase_op(operation); + rewriter.merge_blocks(succ, parent, &operands); + return Ok(true); + } + + // Otherwise, we must replace the current `cf.br` with a `builtin.ret`/`builtin.ret_imm` + // that is a copy of the successor return op. Any return values by definition must either be + // a block argument of the successor, or dominating `op`, so we simply need to map the set + // of return values to their appropriate values in the current block. + // + // In the case of `builtin.return_imm` nothing needs to be done about operands as it has + // none. + let returned = ValueRange::<2>::from(terminator_op.operands().all()); + let mut new_returned = SmallVec::<[_; 4]>::default(); + if terminator_ret_imm.is_none() { + let successor_args = ValueRange::<2>::from(successor.arguments()); + + for return_value in returned.iter() { + // The return value is a block argument of the successor, track its replacement + if let Some(index) = successor_args.iter().position(|arg| arg == return_value) { + new_returned.push(target.arguments[index].borrow().as_value_ref()); + } else { + new_returned.push(return_value); + } + } + } + drop(successor); + + // Create the new `builtin.(return|return_imm)` + let new_op = if let Some(ret_imm) = terminator_ret_imm { + rewriter.ret_imm(*ret_imm.value(), ret_imm.span())?.as_operation_ref() + } else { + rewriter.ret(new_returned, terminator_op.span())?.as_operation_ref() + }; + + // Replace `op` with the new return + drop(op); + rewriter.replace_op(operation, new_op); + + Ok(true) + } +} diff --git a/dialects/cf/src/canonicalization/simplify_cond_br_like_switch.rs b/dialects/cf/src/canonicalization/simplify_cond_br_like_switch.rs new file mode 100644 index 000000000..e33e6de1d --- /dev/null +++ b/dialects/cf/src/canonicalization/simplify_cond_br_like_switch.rs @@ -0,0 +1,81 @@ +use alloc::rc::Rc; + +use midenc_dialect_arith::ArithOpBuilder; +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Simplify a 'cf.switch' that is being used like a 'cf.cond_br', by converting the former into +/// the latter predicated on a single equality check. +pub struct SimplifyCondBrLikeSwitch { + info: PatternInfo, +} + +impl SimplifyCondBrLikeSwitch { + pub fn new(context: Rc) -> Self { + let cf_dialect = context.get_or_register_dialect::(); + let switch_op = + cf_dialect.registered_name::().expect("cf.switch is not registered"); + Self { + info: PatternInfo::new( + context, + "simplify-cond-br-like-switch", + PatternKind::Operation(switch_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for SimplifyCondBrLikeSwitch { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for SimplifyCondBrLikeSwitch { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(switch_op) = op.downcast_ref::() else { + return Ok(false); + }; + + // Check if the switch is cond_br like + if switch_op.num_successors() != 2 { + return Ok(false); + } + + // Get the conditional value we need to compare for equality + let cases = switch_op.cases(); + let if_true_case = cases.get(0).unwrap(); + let else_case = switch_op.fallback(); + + // Materialize comparison + let selector = switch_op.selector().as_value_ref(); + let expected_value = rewriter.u32(*if_true_case.key().unwrap(), switch_op.span()); + let is_true = rewriter.eq(selector, expected_value, switch_op.span())?; + + // Rewrite as cf.cond_br + let new_op = rewriter.cond_br( + is_true, + if_true_case.block(), + ValueRange::<2>::from(if_true_case.arguments().as_slice()), + else_case.successor(), + ValueRange::<2>::from(else_case.arguments), + switch_op.span(), + )?; + + drop(op); + + rewriter.replace_op(operation, new_op.as_operation_ref()); + + Ok(true) + } +} diff --git a/dialects/cf/src/canonicalization/simplify_passthrough_br.rs b/dialects/cf/src/canonicalization/simplify_passthrough_br.rs new file mode 100644 index 000000000..d119542d6 --- /dev/null +++ b/dialects/cf/src/canonicalization/simplify_passthrough_br.rs @@ -0,0 +1,168 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Simplify unconditional branches to a block from that block's sole predecessor +/// +/// # Example +/// +/// ```text,ignore +/// br ^bb1 +/// ^bb1 +/// br ^bbN(...) +/// ``` +/// +/// Becomes: +/// +/// ```text,ignore +/// br ^bbN(...) +/// ``` +pub struct SimplifyPassthroughBr { + info: PatternInfo, +} + +impl SimplifyPassthroughBr { + pub fn new(context: Rc) -> Self { + let cf_dialect = context.get_or_register_dialect::(); + let br_op = cf_dialect.registered_name::
().expect("cf.br is not registered"); + Self { + info: PatternInfo::new( + context, + "simplify-passthrough-br", + PatternKind::Operation(br_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for SimplifyPassthroughBr { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for SimplifyPassthroughBr { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(br_op) = op.downcast_ref::
() else { + return Ok(false); + }; + + let successor = br_op.target(); + let dest = successor.successor(); + let mut dest_operands = successor + .arguments + .iter() + .copied() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + + // Try to collapse the successor if it points somewhere other than this block. + if dest == op.parent().unwrap() { + return Ok(false); + } + + let span = op.span(); + drop(op); + + let Some(new_dest) = collapse_branch(operation, dest, &mut dest_operands) else { + return Ok(false); + }; + + // Create a new branch with the collapsed successor. + let new_br = rewriter.br(new_dest, dest_operands, span)?; + rewriter.replace_op(operation, new_br.as_operation_ref()); + + Ok(true) + } +} + +/// Given a successor, try to collapse it to a new destination if it only contains a passthrough +/// unconditional branch. If the successor is collapsable, the function returns `Ok` with the new +/// successor, and `successor_operands` is updated to reference the new destination and values. +/// `arg_storage` is used as storage if operands to the collapsed successor need to be remapped. It +/// must outlive uses of `successor_operands`. +pub fn collapse_branch( + predecessor: OperationRef, + successor: BlockRef, + successor_operands: &mut SmallVec<[ValueRef; 4]>, +) -> Option { + // Check that the successor only contains a unconditional branch. + let succ = successor.borrow(); + let terminator = succ.terminator()?; + if succ.body().front().as_pointer() != Some(terminator) { + return None; + } + + // Check that the terminator is an unconditional branch. + let terminator_op = terminator.borrow(); + let successor_br = terminator_op.downcast_ref::
()?; + + // Check that the block arguments are only used by the terminator. + for arg in succ.arguments().iter() { + let arg = arg.borrow(); + for user in arg.iter_uses() { + if user.owner != terminator { + return None; + } + } + } + + // Don't try to collapse branches to infinite loops. + let target = successor_br.target(); + let successor_dest = target.successor(); + if successor_dest == successor { + return None; + } + + // Don't try to collapse branches when doing so would introduce a critical edge in the CFG + // + // A critical edge is an edge from a predecessor with multiple successors, to a successor with + // multiple predecessors. It is necessary to break these edges with passthrough blocks that we + // would otherwise wish to collapse during canonicalization. By avoiding introducing these edges + // we can break any existing critical edges as a separate canonicalization, without the two + // working against each other. + if predecessor.borrow().num_successors() > 1 + && successor_dest.borrow().get_unique_predecessor().is_none() + { + return None; + } + + // Update the operands to the successor. If the branch parent has no arguments, we can use the + // branch operands directly. + if !succ.has_arguments() { + successor_operands.clear(); + successor_operands.extend(target.arguments.iter().map(|o| o.borrow().as_value_ref())); + return Some(successor_dest); + } + + // Otherwise, we need to remap any argument operands. + let mut new_operands = SmallVec::default(); + for operand in target.arguments.iter() { + let value = operand.borrow().as_value_ref(); + let operand = value.borrow(); + let block_arg = operand.downcast_ref::(); + match block_arg { + Some(block_arg) if block_arg.owner() == successor => { + new_operands.push(successor_operands[block_arg.index()]); + } + _ => { + new_operands.push(value); + } + } + } + + *successor_operands = new_operands; + + Some(successor_dest) +} diff --git a/dialects/cf/src/canonicalization/simplify_passthrough_cond_br.rs b/dialects/cf/src/canonicalization/simplify_passthrough_cond_br.rs new file mode 100644 index 000000000..f1f0af138 --- /dev/null +++ b/dialects/cf/src/canonicalization/simplify_passthrough_cond_br.rs @@ -0,0 +1,107 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use super::simplify_passthrough_br::collapse_branch; +use crate::*; + +/// Simplify conditional branches to a block from that block's sole predecessor, so long as doing +/// so does not introduce a critical edge in the control flow graph. A critical edge is a control +/// flow edge from a block with multiple successors to a block with multiple predecessors. +/// +/// # Example +/// +/// ```text,ignore +/// cf.cond_br %cond, ^bb1, ^bb2 +/// ^bb1 +/// br ^bbN(...) +/// ^bb2 +/// br ^bbK(...) +/// ``` +/// +/// Becomes: +/// +/// ```text,ignore +/// cf.cond_br %cond, ^bbN(...), ^bbK(...) +/// ``` +pub struct SimplifyPassthroughCondBr { + info: PatternInfo, +} + +impl SimplifyPassthroughCondBr { + pub fn new(context: Rc) -> Self { + let cf_dialect = context.get_or_register_dialect::(); + let cond_br_op = + cf_dialect.registered_name::().expect("cf.cond_br is not registered"); + Self { + info: PatternInfo::new( + context, + "simplify-passthrough-cond-br", + PatternKind::Operation(cond_br_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for SimplifyPassthroughCondBr { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for SimplifyPassthroughCondBr { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(cond_br_op) = op.downcast_ref::() else { + return Ok(false); + }; + + let true_dest = cond_br_op.then_dest(); + let mut true_dest_operands = true_dest + .arguments + .iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + let true_dest = true_dest.successor(); + let false_dest = cond_br_op.else_dest(); + let mut false_dest_operands = false_dest + .arguments + .iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + let false_dest = false_dest.successor(); + + // Try to collapse one of the current successors. + let new_true_dest = collapse_branch(operation, true_dest, &mut true_dest_operands); + let new_false_dest = collapse_branch(operation, false_dest, &mut false_dest_operands); + if new_true_dest.is_none() && new_false_dest.is_none() { + return Ok(false); + } + let new_true_dest = new_true_dest.unwrap_or(true_dest); + let new_false_dest = new_false_dest.unwrap_or(false_dest); + + // Create a new branch with the collapsed successors. + let span = cond_br_op.span(); + let cond = cond_br_op.condition().as_value_ref(); + drop(op); + let new_cond_br = rewriter.cond_br( + cond, + new_true_dest, + true_dest_operands, + new_false_dest, + false_dest_operands, + span, + )?; + rewriter.replace_op(operation, new_cond_br.as_operation_ref()); + + Ok(true) + } +} diff --git a/dialects/cf/src/canonicalization/simplify_successor_arguments.rs b/dialects/cf/src/canonicalization/simplify_successor_arguments.rs new file mode 100644 index 000000000..a3a94c82f --- /dev/null +++ b/dialects/cf/src/canonicalization/simplify_successor_arguments.rs @@ -0,0 +1,94 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Remove redundant successor arguments for conditional branches to a block with a single +/// predecessor. +/// +/// This is only applied to `cf.cond_br`, because other canonicalization supercede this one for +/// `cf.br`. +pub struct RemoveUnusedSinglePredBlockArgs { + info: PatternInfo, +} + +impl RemoveUnusedSinglePredBlockArgs { + pub fn new(context: Rc) -> Self { + let cf_dialect = context.get_or_register_dialect::(); + let br_op = cf_dialect.registered_name::().expect("cf.cond_br is not registered"); + Self { + info: PatternInfo::new( + context, + "remove-unused-single-pred-block-args", + PatternKind::Operation(br_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for RemoveUnusedSinglePredBlockArgs { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for RemoveUnusedSinglePredBlockArgs { + fn match_and_rewrite( + &self, + mut operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let mut op = operation.borrow_mut(); + let Some(br_op) = op.downcast_mut::() else { + return Ok(false); + }; + + let then_dest = br_op.successors()[0]; + let else_dest = br_op.successors()[0]; + + let mut changed = false; + for target in [then_dest, else_dest] { + // Check that the successor block has a single predecessor. + let mut succ = target.successor(); + let parent = operation.parent().unwrap(); + if succ == parent || succ.borrow().get_single_predecessor().is_none() { + continue; + } + + // Rewrite uses of the successor block arguments with the corresponding successor + // operands + let succ_block = succ.borrow(); + // If there are no arguments, there is nothing to do for this successor + if !succ_block.has_arguments() { + continue; + } + + for (block_arg, operand) in succ_block + .arguments() + .as_value_range() + .into_iter() + .zip(br_op.operands().group(target.successor_operand_group()).as_value_range()) + { + rewriter.replace_all_uses_of_value_with(block_arg, operand); + } + drop(succ_block); + + // Remove the dead successor block arguments + succ.borrow_mut().erase_arguments(|_| true); + + // Remove the now-unnecessary successor operands + br_op.operands_mut().group_mut(target.successor_operand_group()).clear(); + + changed = true; + } + + drop(op); + + Ok(changed) + } +} diff --git a/dialects/cf/src/canonicalization/simplify_switch_fallback_overlap.rs b/dialects/cf/src/canonicalization/simplify_switch_fallback_overlap.rs new file mode 100644 index 000000000..e43fd3f91 --- /dev/null +++ b/dialects/cf/src/canonicalization/simplify_switch_fallback_overlap.rs @@ -0,0 +1,103 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Simplify a `cf.switch` with one or more cases that overlap with the fallback case, by removing +/// those cases entirely, and relying on the fallback to catch them. +/// +/// This transformation only applies if the overlapping case destinations and arguments are +/// identical. +pub struct SimplifySwitchFallbackOverlap { + info: PatternInfo, +} + +impl SimplifySwitchFallbackOverlap { + pub fn new(context: Rc) -> Self { + let cf_dialect = context.get_or_register_dialect::(); + let switch_op = + cf_dialect.registered_name::().expect("cf.switch is not registered"); + Self { + info: PatternInfo::new( + context, + "simplify-switch-fallback-overlap", + PatternKind::Operation(switch_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for SimplifySwitchFallbackOverlap { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for SimplifySwitchFallbackOverlap { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(switch_op) = op.downcast_ref::() else { + return Ok(false); + }; + + log::trace!(target: "simplify-switch-fallback-overlap", "canonicalizing {op}"); + + // Check if the switch has at least one non-default case that overlaps with the fallback + let mut non_overlapping = SmallVec::<[_; 4]>::default(); + let default_target = switch_op.fallback(); + let mut has_overlapping = false; + { + let cases = switch_op.cases(); + for case in cases.iter() { + let successor = case.block(); + if successor == default_target.successor() { + let identical_argv = ValueRange::<2>::from(case.arguments().as_slice()) + .into_iter() + .eq(ValueRange::<2>::from(default_target.arguments.as_slice())); + if identical_argv { + has_overlapping = true; + continue; + } + } + + non_overlapping.push(SwitchCase { + value: *case.key().unwrap(), + successor, + arguments: ValueRange::<4>::from(case.arguments().as_slice()) + .into_smallvec() + .into_vec(), + }); + } + } + + if !has_overlapping { + return Ok(false); + } + + // Create a new switch op with the new case configuration + let selector = switch_op.selector().as_value_ref(); + + let new_op = rewriter.switch( + selector, + non_overlapping, + default_target.successor(), + ValueRange::<2>::from(default_target.arguments.as_slice()), + switch_op.span(), + )?; + + // Replace old op + drop(op); + rewriter.replace_op(operation, new_op.as_operation_ref()); + + Ok(true) + } +} diff --git a/dialects/cf/src/canonicalization/split_critical_edges.rs b/dialects/cf/src/canonicalization/split_critical_edges.rs new file mode 100644 index 000000000..8a29d8444 --- /dev/null +++ b/dialects/cf/src/canonicalization/split_critical_edges.rs @@ -0,0 +1,141 @@ +use alloc::rc::Rc; +use core::any::TypeId; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + traits::BranchOpInterface, + *, +}; + +use crate::*; + +/// Ensure that any critical edges in the control flow graph introduced by branch-like operations +/// with multiple successors, are broken, by introducing passthrough blocks. +/// +/// NOTE: This does not conflict with the SimplifyPassthrough* canonicalization patterns, as those +/// are explicitly written to avoid introducing critical edges, and so will not undo any changes +/// performed by this pattern rewrite. +/// +/// # Example +/// +/// ```text,ignore +/// ^bb0: +/// cf.cond_br %c0, ^bb2(%v0), ^bb3 +/// ^bb1: +/// cf.cond_br %c1, ^bb2(%v1), ^bb4 +/// ^bb2(%arg) +/// ... +/// ``` +/// +/// Becomes: +/// +/// ```text,ignore +/// ^bb0: +/// cf.cond_br %c0, ^bb5, ^bb3 +/// ^bb1: +/// cf.cond_br %c1, ^bb6, ^bb4 +/// ^bb2(%arg): +/// ... +/// ^bb5: +/// cf.br ^bb2(%v0) +/// ^bb6: +/// cf.br ^bb2(%v1) +/// ``` +pub struct SplitCriticalEdges { + info: PatternInfo, +} + +impl SplitCriticalEdges { + #[allow(unused)] + pub fn new(context: Rc) -> Self { + Self { + info: PatternInfo::new( + context, + "split-critical-edges", + PatternKind::Trait(TypeId::of::()), + PatternBenefit::MAX, + ), + } + } + + pub fn for_op(context: Rc, op: OperationName) -> Self { + Self { + info: PatternInfo::new( + context, + "split-critical-edges", + PatternKind::Operation(op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for SplitCriticalEdges { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for SplitCriticalEdges { + fn match_and_rewrite( + &self, + mut operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let mut op = operation.borrow_mut(); + let Some(br_op) = op.as_trait_mut::() else { + return Ok(false); + }; + + if br_op.num_successors() < 2 { + return Ok(false); + } + + let mut critical_edges = SmallVec::<[_; 4]>::default(); + for succ in br_op.successors().all() { + let successor = succ.successor(); + if successor.borrow().get_single_predecessor().is_none() { + critical_edges.push((successor, succ.index())); + } + } + + if critical_edges.is_empty() { + return Ok(false); + } + + // For each critical edge, introduce a new block with an unconditional branch to the target + // block, moving successor operands from the original op to the new unconditional branch + for (successor, successor_index) in critical_edges { + // Remove successor operands from `br_op` + let operands = { + let mut succ_operands = br_op.get_successor_operands_mut(successor_index); + let operands = succ_operands + .forwarded() + .iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + succ_operands.forwarded_mut().clear(); + operands + }; + + // Create new empty block, and insert an unconditional branch to `successor` with the + // original operands of `br_op`. + let mut guard = InsertionGuard::new(rewriter); + let mut new_block = guard.create_block_before(successor, &[]); + guard.br(successor, operands, br_op.as_operation().span())?; + + // Rewrite successor block operand + let mut block_operand = br_op.successors_mut()[successor_index].block; + { + let mut block_operand = block_operand.borrow_mut(); + block_operand.unlink(); + } + new_block.borrow_mut().insert_use(block_operand); + } + + // We modified the operation in-place, so notify any attached listeners + rewriter.notify_operation_modified(operation); + + Ok(true) + } +} diff --git a/dialects/cf/src/lib.rs b/dialects/cf/src/lib.rs new file mode 100644 index 000000000..c9a2a2d7f --- /dev/null +++ b/dialects/cf/src/lib.rs @@ -0,0 +1,71 @@ +#![no_std] +#![feature(debug_closure_helpers)] +#![feature(unboxed_closures)] +#![feature(fn_traits)] +#![feature(ptr_metadata)] +#![feature(specialization)] +#![allow(incomplete_features)] +#![deny(warnings)] + +extern crate alloc; + +#[cfg(any(feature = "std", test))] +extern crate std; + +use alloc::boxed::Box; + +mod builders; +mod canonicalization; +mod ops; + +use midenc_hir::{ + AttributeValue, Builder, Dialect, DialectInfo, DialectRegistration, OperationRef, SourceSpan, + Type, +}; + +pub use self::{builders::ControlFlowOpBuilder, ops::*}; + +#[derive(Debug)] +pub struct ControlFlowDialect { + info: DialectInfo, +} + +impl ControlFlowDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl Dialect for ControlFlowDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + _builder: &mut dyn Builder, + _attr: Box, + _ty: &Type, + _span: SourceSpan, + ) -> Option { + None + } +} + +impl DialectRegistration for ControlFlowDialect { + const NAMESPACE: &'static str = "cf"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + } +} diff --git a/dialects/cf/src/ops.rs b/dialects/cf/src/ops.rs new file mode 100644 index 000000000..c887837fa --- /dev/null +++ b/dialects/cf/src/ops.rs @@ -0,0 +1,382 @@ +use alloc::{boxed::Box, rc::Rc, vec::Vec}; + +use midenc_hir::{ + derive::operation, effects::*, matchers::Matcher, patterns::RewritePatternSet, smallvec, + traits::*, *, +}; + +use crate::ControlFlowDialect; + +/// An unstructured control flow primitive representing an unconditional branch to `target` +#[operation( + dialect = ControlFlowDialect, + traits(Terminator), + implements(BranchOpInterface, MemoryEffectOpInterface) +)] +pub struct Br { + #[successor] + target: Successor, +} + +impl Canonicalizable for Br { + fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc) { + rewrites + .push(crate::canonicalization::SimplifyBrToBlockWithSinglePred::new(context.clone())); + rewrites.push(crate::canonicalization::SimplifyPassthroughBr::new(context.clone())); + rewrites.push(crate::canonicalization::SimplifyBrToReturn::new(context)); + } +} + +impl EffectOpInterface for Br { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } + + fn has_no_effect(&self) -> bool { + true + } +} + +impl BranchOpInterface for Br { + #[inline] + fn get_successor_for_operands( + &self, + _operands: &[Option>], + ) -> Option { + Some(self.successors()[0]) + } +} + +/// An unstructured control flow primitive representing a conditional branch to either `then_dest` +/// or `else_dest` depending on the value of `condition`, a boolean value. +#[operation( + dialect = ControlFlowDialect, + traits(Terminator), + implements(BranchOpInterface, MemoryEffectOpInterface) +)] +pub struct CondBr { + #[operand] + condition: Bool, + #[successor] + then_dest: Successor, + #[successor] + else_dest: Successor, +} + +impl Canonicalizable for CondBr { + fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc) { + let name = context + .get_or_register_dialect::() + .expect_registered_name::(); + rewrites.push(crate::canonicalization::SimplifyPassthroughCondBr::new(context.clone())); + rewrites.push(crate::canonicalization::SplitCriticalEdges::for_op(context.clone(), name)); + rewrites.push(crate::canonicalization::RemoveUnusedSinglePredBlockArgs::new(context)); + } +} + +impl EffectOpInterface for CondBr { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } + + fn has_no_effect(&self) -> bool { + true + } +} + +impl BranchOpInterface for CondBr { + fn get_successor_for_operands( + &self, + operands: &[Option>], + ) -> Option { + let value = operands[0].as_deref()?; + let cond = value.as_bool().unwrap_or_else(|| { + panic!("expected boolean immediate for '{}' condition, got: {:?}", self.name(), value) + }); + + Some(if cond { + self.successors()[0] + } else { + self.successors()[1] + }) + } +} + +/// An unstructured control flow primitive that represents a multi-way branch to one of multiple +/// branch targets, depending on the value of `selector`. +/// +/// If a specific selector value is matched by `cases`, the branch target corresponding to that +/// case is the one to which control is transferred. If no matching case is found for the selector, +/// then the `fallback` target is used instead. +/// +/// A `fallback` successor must always be provided. +#[operation( + dialect = ControlFlowDialect, + traits(Terminator), + implements(BranchOpInterface, MemoryEffectOpInterface, OpPrinter) +)] +pub struct Switch { + #[operand] + selector: UInt32, + #[successor] + fallback: Successor, + #[successors(keyed)] + cases: SwitchCase, +} + +impl OpPrinter for Switch { + fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> formatter::Document { + use formatter::*; + + let header = + display(self.op.name()) + const_text(" ") + display(self.selector().as_value_ref()); + let cases = self.cases().iter().fold(Document::Empty, |acc, case| { + let key = case.key().unwrap(); + let dest = case.block(); + let operands = case.arguments(); + let args = if operands.is_empty() { + Document::Empty + } else { + operands.iter().enumerate().fold(const_text("("), |acc, (i, o)| { + if i > 0 { + acc + const_text(", ") + display(o.borrow().as_value_ref()) + } else { + acc + display(o.borrow().as_value_ref()) + } + }) + const_text(")") + }; + acc + nl() + + const_text("case ") + + display(*key) + + const_text(" => ") + + display(dest) + + args + }); + let fallback = self.fallback(); + let fallback_dest = fallback.successor(); + let fallback_args = if fallback.arguments.is_empty() { + Document::Empty + } else { + fallback.arguments.iter().enumerate().fold(const_text("("), |acc, (i, o)| { + if i > 0 { + acc + const_text(", ") + display(o.borrow().as_value_ref()) + } else { + acc + display(o.borrow().as_value_ref()) + } + }) + const_text(")") + }; + let fallback = nl() + const_text("default => ") + display(fallback_dest) + fallback_args; + header + + const_text(" {") + + indent(4, cases + fallback) + + nl() + + const_text("}") + + const_text(";") + } +} + +impl Canonicalizable for Switch { + fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc) { + let name = context + .get_or_register_dialect::() + .expect_registered_name::(); + rewrites.push(crate::canonicalization::SimplifyCondBrLikeSwitch::new(context.clone())); + rewrites.push(crate::canonicalization::SimplifySwitchFallbackOverlap::new(context.clone())); + rewrites.push(crate::canonicalization::SplitCriticalEdges::for_op(context.clone(), name)); + } +} + +impl EffectOpInterface for Switch { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } + + fn has_no_effect(&self) -> bool { + true + } +} + +impl BranchOpInterface for Switch { + #[inline] + fn get_successor_for_operands( + &self, + operands: &[Option>], + ) -> Option { + let value = operands[0].as_deref()?; + let selector = if let Some(selector) = value.downcast_ref::() { + selector.as_u32().expect("invalid selector value for 'cf.switch'") + } else if let Some(selector) = value.downcast_ref::() { + *selector + } else if let Some(selector) = value.downcast_ref::() { + u32::try_from(*selector).expect("invalid selector value for 'cf.switch'") + } else if let Some(selector) = value.downcast_ref::() { + u32::try_from(*selector).expect("invalid selector value for 'cf.switch': out of range") + } else { + panic!("unsupported selector value type for '{}', got: {:?}", self.name(), value) + }; + + for switch_case in self.cases().iter() { + let key = *switch_case.key().unwrap(); + if selector == key { + return Some(*switch_case.info()); + } + } + + // If we reach here, no selector match was found, so use the fallback successor + Some(self.successors().all().as_slice().last().copied().unwrap()) + } +} + +/// Represents a single branch target by matching a specific selector value in a [Switch] +/// operation. +#[derive(Debug, Clone)] +pub struct SwitchCase { + pub value: u32, + pub successor: BlockRef, + pub arguments: Vec, +} + +#[doc(hidden)] +pub struct SwitchCaseRef<'a> { + pub value: u32, + pub successor: BlockOperandRef, + pub arguments: OpOperandRange<'a>, +} + +#[doc(hidden)] +pub struct SwitchCaseMut<'a> { + pub value: u32, + pub successor: BlockOperandRef, + pub arguments: OpOperandRangeMut<'a>, +} + +impl KeyedSuccessor for SwitchCase { + type Key = u32; + type Repr<'a> = SwitchCaseRef<'a>; + type ReprMut<'a> = SwitchCaseMut<'a>; + + fn key(&self) -> &Self::Key { + &self.value + } + + fn into_parts(self) -> (Self::Key, BlockRef, Vec) { + (self.value, self.successor, self.arguments) + } + + fn into_repr( + key: Self::Key, + block: BlockOperandRef, + operands: OpOperandRange<'_>, + ) -> Self::Repr<'_> { + SwitchCaseRef { + value: key, + successor: block, + arguments: operands, + } + } + + fn into_repr_mut( + key: Self::Key, + block: BlockOperandRef, + operands: OpOperandRangeMut<'_>, + ) -> Self::ReprMut<'_> { + SwitchCaseMut { + value: key, + successor: block, + arguments: operands, + } + } +} + +/// Choose a value based on a boolean condition +#[operation( + dialect = ControlFlowDialect, + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct Select { + #[operand] + cond: Bool, + #[operand] + first: AnyInteger, + #[operand] + second: AnyInteger, + #[result] + result: AnyInteger, +} + +impl EffectOpInterface for Select { + fn has_no_effect(&self) -> bool { + true + } + + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } +} + +impl InferTypeOpInterface for Select { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.first().ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl Foldable for Select { + #[inline] + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + if let Some(value) = + matchers::foldable_operand_of::().matches(&self.cond().as_operand_ref()) + { + if let Some(cond) = value.as_bool() { + let maybe_folded = if cond { + matchers::foldable_operand() + .matches(&self.first().as_operand_ref()) + .map(OpFoldResult::Attribute) + .or_else(|| Some(OpFoldResult::Value(self.first().as_value_ref()))) + } else { + matchers::foldable_operand() + .matches(&self.second().as_operand_ref()) + .map(OpFoldResult::Attribute) + .or_else(|| Some(OpFoldResult::Value(self.second().as_value_ref()))) + }; + + if let Some(folded) = maybe_folded { + results.push(folded); + return FoldResult::Ok(()); + } + } + } + + FoldResult::Failed + } + + #[inline(always)] + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + if let Some(value) = operands[0].as_deref().and_then(|o| o.downcast_ref::()) { + if let Some(cond) = value.as_bool() { + let maybe_folded = if cond { + operands[1] + .as_deref() + .map(|o| OpFoldResult::Attribute(o.clone_value())) + .or_else(|| Some(OpFoldResult::Value(self.first().as_value_ref()))) + } else { + operands[2] + .as_deref() + .map(|o| OpFoldResult::Attribute(o.clone_value())) + .or_else(|| Some(OpFoldResult::Value(self.second().as_value_ref()))) + }; + + if let Some(folded) = maybe_folded { + results.push(folded); + return FoldResult::Ok(()); + } + } + } + FoldResult::Failed + } +} diff --git a/dialects/hir/CHANGELOG.md b/dialects/hir/CHANGELOG.md new file mode 100644 index 000000000..b96ac160b --- /dev/null +++ b/dialects/hir/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-dialect-hir-v0.4.0...midenc-dialect-hir-v0.4.1) - 2025-09-03 + +### Fixed + +- only keep spills that feed a live reload dominated by this spill + +### Other + +- remove explicit symbol management +- switch to `expect_file!` in spills tests +- formatting +- add asserts for materialized spills and reloads in +- add tests for the `TransformSpills` pass + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-dialect-hir-v0.1.5...midenc-dialect-hir-v0.4.0) - 2025-08-15 + +### Other + +- Add $ParentTrait pattern to verify macro + Add SameTypeOperands as a explicit dependency + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-dialect-hir-v0.1.0...midenc-dialect-hir-v0.1.5) - 2025-07-01 + +### Fixed + +- delayed registration of scf dialect causes canonicalizations to be skipped diff --git a/dialects/hir/Cargo.toml b/dialects/hir/Cargo.toml new file mode 100644 index 000000000..3f6502755 --- /dev/null +++ b/dialects/hir/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "midenc-dialect-hir" +description = "High-level Intermediate Representation Dialect" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["midenc-hir/std"] + +[dependencies] +log.workspace = true +midenc-dialect-arith.workspace = true +midenc-dialect-cf.workspace = true +midenc-hir.workspace = true +midenc-hir-analysis.workspace = true +midenc-hir-transform.workspace = true + +[dev-dependencies] +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging +midenc-expect-test = { path = "../../tools/expect-test" } +env_logger.workspace = true diff --git a/hir/src/asm/assertions.rs b/dialects/hir/src/assertions.rs similarity index 100% rename from hir/src/asm/assertions.rs rename to dialects/hir/src/assertions.rs diff --git a/dialects/hir/src/attributes.rs b/dialects/hir/src/attributes.rs new file mode 100644 index 000000000..2174ba30c --- /dev/null +++ b/dialects/hir/src/attributes.rs @@ -0,0 +1,3 @@ +mod pointer; + +pub use self::pointer::PointerAttr; diff --git a/dialects/hir/src/attributes/pointer.rs b/dialects/hir/src/attributes/pointer.rs new file mode 100644 index 000000000..6f531f796 --- /dev/null +++ b/dialects/hir/src/attributes/pointer.rs @@ -0,0 +1,51 @@ +use alloc::boxed::Box; + +use midenc_hir::{formatter, AttributeValue, Immediate, Type}; + +/// Represents a constant pointer value +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PointerAttr { + addr: Immediate, + /// The pointee type + ty: Type, +} + +impl PointerAttr { + pub fn new(addr: Immediate, ty: Type) -> Self { + Self { addr, ty } + } + + pub fn addr(&self) -> &Immediate { + &self.addr + } + + pub fn pointee_type(&self) -> &Type { + &self.ty + } + + pub fn set_pointee_type(&mut self, ty: Type) { + self.ty = ty; + } +} + +impl formatter::PrettyPrint for PointerAttr { + fn render(&self) -> formatter::Document { + use formatter::*; + + display(self.addr) + } +} + +impl AttributeValue for PointerAttr { + fn as_any(&self) -> &dyn core::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn core::any::Any { + self + } + + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/dialects/hir/src/builders.rs b/dialects/hir/src/builders.rs new file mode 100644 index 000000000..26c106907 --- /dev/null +++ b/dialects/hir/src/builders.rs @@ -0,0 +1,541 @@ +use midenc_hir::{ + dialects::builtin::*, AsCallableSymbolRef, Builder, Immediate, Op, OpBuilder, PointerType, + Report, Signature, SourceSpan, Type, UnsafeIntrusiveEntityRef, ValueRef, +}; + +use crate::*; + +pub trait HirOpBuilder<'f, B: ?Sized + Builder> { + fn assert( + &mut self, + value: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(value) + } + + fn assert_with_error( + &mut self, + value: ValueRef, + code: u32, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(value, code) + } + + fn assertz( + &mut self, + value: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(value) + } + + fn assertz_with_error( + &mut self, + value: ValueRef, + code: u32, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(value, code) + } + + fn assert_eq( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(lhs, rhs) + } + + fn assert_eq_imm( + &mut self, + lhs: ValueRef, + rhs: Immediate, + span: SourceSpan, + ) -> Result, Report> { + use midenc_dialect_arith::ArithOpBuilder; + let rhs = self.builder_mut().imm(rhs, span); + self.assert_eq(lhs, rhs, span) + } + + fn breakpoint( + &mut self, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder() + } + + /// Grow the global heap by `num_pages` pages, in 64kb units. + /// + /// Returns the previous size (in pages) of the heap, or -1 if the heap could not be grown. + fn mem_grow(&mut self, num_pages: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(num_pages)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Return the size of the global heap in pages, where each page is 64kb. + fn mem_size(&mut self, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder()?; + Ok(op.borrow().result().as_value_ref()) + } + + /* + /// Get a [GlobalValue] which represents the address of a global variable whose symbol is `name` + /// + /// On it's own, this does nothing, you must use the resulting [GlobalValue] with a builder + /// that expects one as an argument, or use `global_value` to obtain a [Value] from it. + fn symbol>(self, name: S, span: SourceSpan) -> GlobalValue { + self.symbol_relative(name, 0, span) + } + + /// Same semantics as `symbol`, but applies a constant offset to the address of the given + /// symbol. + /// + /// If the offset is zero, this is equivalent to `symbol` + fn symbol_relative>( + &mut self, + name: S, + offset: i32, + span: SourceSpan, + ) -> GlobalValue { + self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { + name: Ident::new(Symbol::intern(name.as_ref()), span), + offset, + }) + } + + /// Get the address of a global variable whose symbol is `name` + /// + /// The type of the pointer produced is given as `ty`. It is up to the caller + /// to ensure that loading memory from that pointer is valid for the provided + /// type. + fn symbol_addr>(self, name: S, ty: Type, span: SourceSpan) -> ValueRef { + todo!() + // self.symbol_relative_addr(name, 0, ty, span) + } + + /// Same semantics as `symbol_addr`, but applies a constant offset to the address of the given + /// symbol. + /// + /// If the offset is zero, this is equivalent to `symbol_addr` + fn symbol_relative_addr>( + &mut self, + name: S, + offset: i32, + ty: Type, + span: SourceSpan, + ) -> Value { + assert!(ty.is_pointer(), "expected pointer type, got '{}'", &ty); + let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { + name: Ident::new(Symbol::intern(name.as_ref()), span), + offset, + }); + into_first_result!(self.Global(gv, ty, span)) + } + + /// Loads a value of type `ty` from the global variable whose symbol is `name`. + /// + /// NOTE: There is no requirement that the memory contents at the given symbol + /// contain a valid value of type `ty`. That is left entirely up the caller to + /// guarantee at a higher level. + fn load_symbol>( + &mut self, + name: S, + ty: Type, + span: SourceSpan, + ) -> Result { + self.load_symbol_relative(name, ty, 0, span) + } + + /// Same semantics as `load_symbol`, but a constant offset is applied to the address before + /// issuing the load. + fn load_symbol_relative>( + &mut self, + name: S, + ty: Type, + offset: i32, + span: SourceSpan, + ) -> Result { + let base = self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { + name: Ident::new(Symbol::intern(name.as_ref()), span), + offset: 0, + }); + self.load_global_relative(base, ty, offset, span) + } + + */ + + /// Loads a value of type `ty` from the address represented by `addr` + /// + /// NOTE: There is no requirement that the memory contents at the given symbol + /// contain a valid value of type `ty`. That is left entirely up the caller to + /// guarantee at a higher level. + fn load_global( + &mut self, + addr: GlobalVariableRef, + span: SourceSpan, + ) -> Result { + self.load_global_relative(addr, 0, span) + } + + /// Loads a value from a global variable. + /// + /// A constant offset is applied to the address before issuing the load. + fn load_global_relative( + &mut self, + base: GlobalVariableRef, + offset: i32, + span: SourceSpan, + ) -> Result { + // let base = &base.borrow(); + let gs_builder = GlobalSymbolBuilder::new(self.builder_mut(), span); + let global_sym = gs_builder(base, offset)?; + let addr = global_sym.borrow().results()[0].borrow().as_value_ref(); + let ty = base.borrow().ty().clone(); + let typed_addr = self.bitcast(addr, Type::from(PointerType::new(ty)), span)?; + self.load(typed_addr, span) + } + + /// Stores `value` to the global variable + fn store_global( + &mut self, + global_var: GlobalVariableRef, + value: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let gs_builder = GlobalSymbolBuilder::new(self.builder_mut(), span); + let global_sym = gs_builder(global_var, 0)?; + let addr = global_sym.borrow().results()[0].borrow().as_value_ref(); + let ty = global_var.borrow().ty().clone(); + let typed_addr = self.bitcast(addr, Type::from(PointerType::new(ty)), span)?; + self.store(typed_addr, value, span) + } + + /* + + /// Computes an address relative to the pointer produced by `base`, by applying an offset + /// given by multiplying `offset` * the size in bytes of `unit_ty`. + /// + /// The type of the pointer produced is the same as the type of the pointer given by `base` + /// + /// This is useful in some scenarios where `load_global_relative` is not, namely when computing + /// the effective address of an element of an array stored in a global variable. + fn global_addr_offset( + &mut self, + base: GlobalValue, + offset: i32, + unit_ty: Type, + span: SourceSpan, + ) -> Result { + if let GlobalValueData::Load { + ty: ref base_ty, .. + } = self.data_flow_graph().global_value(base) + { + // If the base global is a load, the target address cannot be computed until runtime, + // so expand this to the appropriate sequence of instructions to do so in that case + assert!(base_ty.is_pointer(), "expected global value to have pointer type"); + let base_ty = base_ty.clone(); + let base = self.ins().load_global(base, base_ty.clone(), span); + let addr = self.ins().ptrtoint(base, Type::U32, span); + let unit_size: i32 = unit_ty + .size_in_bytes() + .try_into() + .expect("invalid type: size is larger than 2^32"); + let computed_offset = unit_size * offset; + let offset_addr = if computed_offset >= 0 { + self.ins().add_imm_checked(addr, Immediate::U32(offset as u32), span) + } else { + self.ins().sub_imm_checked(addr, Immediate::U32(offset.unsigned_abs()), span) + }; + let ptr = self.ins().inttoptr(offset_addr, base_ty, span); + self.load(ptr, span) + } else { + // The global address can be computed statically + let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::IAddImm { + base, + offset, + ty: unit_ty.clone(), + }); + let ty = self.data_flow_graph().global_type(gv); + into_first_result!(self.Global(gv, ty, span)) + } + } + + */ + + /// Loads a value of the type pointed to by the given pointer, on to the stack + /// + /// NOTE: This function will panic if `ptr` is not a pointer typed value + fn load(&mut self, addr: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(addr)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Loads a value of the type of the given local variable, on to the stack + /// + /// NOTE: This function will panic if `local` is not valid within the current function + fn load_local(&mut self, local: LocalVariable, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(local)?; + Ok(op.borrow().result().as_value_ref()) + } + + /* + /// Loads a value from the given temporary (local variable), of the type associated with that + /// local. + fn load_local(self, local: LocalId, span: SourceSpan) -> Value { + let data = Instruction::LocalVar(LocalVarOp { + op: Opcode::Load, + local, + args: ValueList::default(), + }); + let ty = self.data_flow_graph().local_type(local).clone(); + into_first_result!(self.build(data, Type::Ptr(Box::new(ty)), span)) + } + */ + + /// Stores `value` to the address given by `ptr` + /// + /// NOTE: This function will panic if the pointer and pointee types do not match + fn store( + &mut self, + ptr: ValueRef, + value: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(ptr, value) + } + + /// Stores `value` to the given local variable. + /// + /// NOTE: This function will panic if the local variable and value types do not match + fn store_local( + &mut self, + local: LocalVariable, + value: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + assert_eq!( + value.borrow().ty(), + &local.ty(), + "cannot store a value of a different type in the given local variable" + ); + let op_builder = self.builder_mut().create::(span); + op_builder(local, value) + } + + /* + + /// Stores `value` to the given temporary (local variable). + /// + /// NOTE: This function will panic if the type of `value` does not match the type of the local + /// variable. + fn store_local(&mut self, local: LocalId, value: Value, span: SourceSpan) -> Inst { + let mut vlist = ValueList::default(); + { + let dfg = self.data_flow_graph_mut(); + let local_ty = dfg.local_type(local); + let value_ty = dfg.value_type(value); + assert_eq!(local_ty, value_ty, "expected value to be a {}, got {}", local_ty, value_ty); + vlist.push(value, &mut dfg.value_lists); + } + let data = Instruction::LocalVar(LocalVarOp { + op: Opcode::Store, + local, + args: vlist, + }); + self.build(data, Type::Unit, span).0 + } + + */ + + /// Writes `count` copies of `value` to memory starting at address `dst`. + /// + /// Each copy of `value` will be written to memory starting at the next aligned address from + /// the previous copy. This instruction will trap if the input address does not meet the + /// minimum alignment requirements of the type. + fn memset( + &mut self, + dst: ValueRef, + count: ValueRef, + value: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(dst, count, value) + } + + /// Copies `count` values from the memory at address `src`, to the memory at address `dst`. + /// + /// The unit size for `count` is determined by the `src` pointer type, i.e. a pointer to u8 + /// will copy one `count` bytes, a pointer to u16 will copy `count * 2` bytes, and so on. + /// + /// NOTE: The source and destination pointer types must match, or this function will panic. + fn memcpy( + &mut self, + src: ValueRef, + dst: ValueRef, + count: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(src, dst, count) + } + + /// This is a cast operation that permits performing arithmetic on pointer values + /// by casting a pointer to a specified integral type. + fn ptrtoint(&mut self, arg: ValueRef, ty: Type, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(arg, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// This is the inverse of `ptrtoint`, used to recover a pointer that was + /// previously cast to an integer type. It may also be used to cast arbitrary + /// integer values to pointers. + /// + /// In both cases, use of the resulting pointer must not violate the semantics + /// of the higher level language being represented in Miden IR. + fn inttoptr(&mut self, arg: ValueRef, ty: Type, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(arg, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + /* + /// This is an intrinsic which derives a new pointer from an existing pointer to an aggregate. + /// + /// In short, this represents the common need to calculate a new pointer from an existing + /// pointer, but without losing provenance of the original pointer. It is specifically + /// intended for use in obtaining a pointer to an element/field of an array/struct, of the + /// correct type, given a well typed pointer to the aggregate. + /// + /// This function will panic if the pointer is not to an aggregate type + /// + /// The new pointer is derived by statically navigating the structure of the pointee type, using + /// `offsets` to guide the traversal. Initially, the first offset is relative to the original + /// pointer, where `0` refers to the base/first field of the object. The second offset is then + /// relative to the base of the object selected by the first offset, and so on. Offsets must + /// remain in bounds, any attempt to index outside a type's boundaries will result in a + /// panic. + fn getelementptr(&mut self, ptr: ValueRef, mut indices: &[usize], span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + op_builder(arg, ty) + } */ + + /// Cast `arg` to a value of type `ty` + /// + /// NOTE: This is only supported for integral types currently, and the types must be of the same + /// size in bytes, i.e. i32 -> u32 or vice versa. + /// + /// The intention of bitcasts is to reinterpret a value with different semantics, with no + /// validation that is typically implied by casting from one type to another. + fn bitcast(&mut self, arg: ValueRef, ty: Type, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(arg, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Cast `arg` to a value of type `ty` + /// + /// NOTE: This is only valid for numeric to numeric. + /// For numeric to pointer, or pointer to numeric casts, use `inttoptr` and `ptrtoint` + /// respectively. + fn cast(&mut self, arg: ValueRef, ty: Type, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(arg, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn exec( + &mut self, + callee: C, + signature: Signature, + args: A, + span: SourceSpan, + ) -> Result, Report> + where + C: AsCallableSymbolRef, + A: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + op_builder(callee, signature, args) + } + + fn call( + &mut self, + callee: C, + signature: Signature, + args: A, + span: SourceSpan, + ) -> Result, Report> + where + C: AsCallableSymbolRef, + A: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + op_builder(callee, signature, args) + } + + /* + fn inline_asm( + self, + args: &[Value], + results: impl IntoIterator, + span: SourceSpan, + ) -> MasmBuilder { + MasmBuilder::new(self, args, results.into_iter().collect(), span) + } + */ + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +impl<'f, B: ?Sized + Builder> HirOpBuilder<'f, B> for FunctionBuilder<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + FunctionBuilder::builder(self) + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + FunctionBuilder::builder_mut(self) + } +} + +impl<'f> HirOpBuilder<'f, OpBuilder> for &'f mut OpBuilder { + #[inline(always)] + fn builder(&self) -> &OpBuilder { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut OpBuilder { + self + } +} + +impl HirOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/dialects/hir/src/lib.rs b/dialects/hir/src/lib.rs new file mode 100644 index 000000000..7cb92023f --- /dev/null +++ b/dialects/hir/src/lib.rs @@ -0,0 +1,132 @@ +#![feature(debug_closure_helpers)] +#![feature(unboxed_closures)] +#![feature(fn_traits)] +#![feature(ptr_metadata)] +#![feature(specialization)] +#![allow(incomplete_features)] +#![no_std] +#![deny(warnings)] + +extern crate alloc; + +#[cfg(any(feature = "std", test))] +extern crate std; + +pub mod assertions; +mod attributes; +mod builders; +mod ops; +pub mod transforms; + +use alloc::boxed::Box; + +use midenc_dialect_arith as arith; +use midenc_hir::{ + AttributeValue, Builder, BuilderExt, Dialect, DialectInfo, DialectRegistration, Immediate, + OperationRef, SourceSpan, Type, +}; + +pub use self::{attributes::*, builders::HirOpBuilder, ops::*}; + +#[derive(Debug)] +pub struct HirDialect { + info: DialectInfo, +} + +impl HirDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl Dialect for HirDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + builder: &mut dyn Builder, + attr: Box, + ty: &Type, + span: SourceSpan, + ) -> Option { + // Save the current insertion point + let mut builder = midenc_hir::InsertionGuard::new(builder); + + // Check for `PointerAttr` + if let Some(attr) = attr.downcast_ref::() { + let pointee_type = ty + .pointee() + .expect("unexpected pointer constant given when materializing non-pointer value") + .clone(); + let mut attr = attr.clone(); + attr.set_pointee_type(pointee_type); + let op_builder = builder.create::(span); + return op_builder(attr).ok().map(|op| op.as_operation_ref()); + } + + // If we want an integer constant, delegate to the arith dialect + if ty.is_integer() { + let dialect = builder.context().get_or_register_dialect::(); + return dialect.materialize_constant(&mut builder, attr, ty, span); + } + + // Only pointer constants are supported here for now + if !ty.is_pointer() { + return None; + } + + // Currently, we expect folds to produce `Immediate`-valued attributes for integer-likes + if let Some(&imm) = attr.downcast_ref::() { + // We're materializing a constant pointer from a integer immediate + if let Some(pointee_type) = ty.pointee() { + if let Some(addr) = imm.as_u32() { + let op_builder = builder.create::(span); + let attr = PointerAttr::new(Immediate::U32(addr), pointee_type.clone()); + return op_builder(attr).ok().map(|op| op.as_operation_ref()); + } else { + // Invalid pointer immediate + return None; + } + } + } + + None + } +} + +impl DialectRegistration for HirDialect { + const NAMESPACE: &'static str = "hir"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + } +} diff --git a/dialects/hir/src/ops.rs b/dialects/hir/src/ops.rs new file mode 100644 index 000000000..e0ec75a0f --- /dev/null +++ b/dialects/hir/src/ops.rs @@ -0,0 +1,25 @@ +macro_rules! has_no_effects { + ($Op:ty) => { + impl ::midenc_hir::effects::EffectOpInterface<::midenc_hir::effects::MemoryEffect> for $Op { + fn has_no_effect(&self) -> bool { + true + } + + fn effects( + &self, + ) -> ::midenc_hir::effects::EffectIterator<::midenc_hir::effects::MemoryEffect> { + ::midenc_hir::effects::EffectIterator::from_smallvec(::midenc_hir::smallvec![]) + } + } + }; +} + +mod assertions; +mod cast; +mod constants; +mod invoke; +mod mem; +mod primop; +mod spills; + +pub use self::{assertions::*, cast::*, constants::*, invoke::*, mem::*, primop::*, spills::*}; diff --git a/dialects/hir/src/ops/assertions.rs b/dialects/hir/src/ops/assertions.rs new file mode 100644 index 000000000..b60c27d2a --- /dev/null +++ b/dialects/hir/src/ops/assertions.rs @@ -0,0 +1,85 @@ +use midenc_hir::{derive::operation, effects::*, traits::*, *}; + +use crate::HirDialect; + +#[operation( + dialect = HirDialect, + implements(OpPrinter, MemoryEffectOpInterface) +)] +pub struct Assert { + #[operand] + value: Bool, + #[attr(hidden)] + #[default] + code: u32, +} + +impl EffectOpInterface for Assert { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new(MemoryEffect::Write)]) + } +} + +impl OpPrinter for Assert { + fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> formatter::Document { + use formatter::*; + + let doc = display(self.op.name()) + const_text(" ") + display(self.value().as_value_ref()); + let code = *self.code(); + if code == 0 { + doc + const_text(";") + } else { + doc + const_text(" #[code = ") + display(code) + const_text("];") + } + } +} + +#[operation( + dialect = HirDialect, + implements(OpPrinter, MemoryEffectOpInterface) +)] +pub struct Assertz { + #[operand] + value: Bool, + #[attr(hidden)] + #[default] + code: u32, +} + +impl EffectOpInterface for Assertz { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new(MemoryEffect::Write)]) + } +} + +impl OpPrinter for Assertz { + fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> formatter::Document { + use formatter::*; + + let doc = display(self.op.name()) + const_text(" ") + display(self.value().as_value_ref()); + let code = *self.code(); + if code == 0 { + doc + const_text(";") + } else { + doc + const_text(" #[code = ") + display(code) + const_text("];") + } + } +} + +#[operation( + dialect = HirDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(MemoryEffectOpInterface) +)] +pub struct AssertEq { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, +} + +impl EffectOpInterface for AssertEq { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new(MemoryEffect::Write)]) + } +} diff --git a/dialects/hir/src/ops/cast.rs b/dialects/hir/src/ops/cast.rs new file mode 100644 index 000000000..373d49424 --- /dev/null +++ b/dialects/hir/src/ops/cast.rs @@ -0,0 +1,236 @@ +use alloc::boxed::Box; + +use midenc_hir::{ + derive::operation, effects::MemoryEffectOpInterface, matchers::Matcher, traits::*, *, +}; + +use crate::{HirDialect, PointerAttr}; + +/* +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum CastKind { + /// Reinterpret the bits of the operand as the target type, without any consideration for + /// the original meaning of those bits. + /// + /// For example, transmuting `u32::MAX` to `i32`, produces a value of `-1`, because the input + /// value overflows when interpreted as a signed integer. + Transmute, + /// Like `Transmute`, but the input operand is checked to verify that it is a valid value + /// of both the source and target types. + /// + /// For example, a checked cast of `u32::MAX` to `i32` would assert, because the input value + /// cannot be represented as an `i32` due to overflow. + Checked, + /// Convert the input value to the target type, by zero-extending the value to the target + /// bitwidth. A cast of this type must be a widening cast, i.e. from a smaller bitwidth to + /// a larger one. + Zext, + /// Convert the input value to the target type, by sign-extending the value to the target + /// bitwidth. A cast of this type must be a widening cast, i.e. from a smaller bitwidth to + /// a larger one. + Sext, + /// Convert the input value to the target type, by truncating the excess bits. A cast of this + /// type must be a narrowing cast, i.e. from a larger bitwidth to a smaller one. + Trunc, +} + */ + +#[operation( + dialect = HirDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) + )] +pub struct PtrToInt { + #[operand] + operand: AnyPointer, + #[attr(hidden)] + ty: Type, + #[result] + result: AnyInteger, +} + +has_no_effects!(PtrToInt); + +impl InferTypeOpInterface for PtrToInt { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl Foldable for PtrToInt { + #[inline] + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + if let Some(value) = + matchers::foldable_operand_of::().matches(&self.operand().as_operand_ref()) + { + // Support folding just pointer -> 32-bit integer types for now + let value = match self.ty() { + Type::U32 => value.addr().as_u32().map(Immediate::U32), + Type::I32 => value.addr().as_u32().map(|v| Immediate::I32(v as i32)), + _ => return FoldResult::Failed, + }; + if let Some(value) = value { + results.push(OpFoldResult::Attribute(Box::new(value))); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } + + #[inline(always)] + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + if let Some(value) = operands[0].as_deref().and_then(|o| o.downcast_ref::()) { + // Support folding just pointer -> 32-bit integer types for now + let value = match self.ty() { + Type::U32 => value.addr().as_u32().map(Immediate::U32), + Type::I32 => value.addr().as_u32().map(|v| Immediate::I32(v as i32)), + _ => return FoldResult::Failed, + }; + if let Some(value) = value { + results.push(OpFoldResult::Attribute(Box::new(value))); + return FoldResult::Ok(()); + } + } + FoldResult::Failed + } +} + +#[operation( + dialect = HirDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct IntToPtr { + #[operand] + operand: AnyInteger, + #[attr(hidden)] + ty: Type, + #[result] + result: AnyPointer, +} + +has_no_effects!(IntToPtr); + +impl InferTypeOpInterface for IntToPtr { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl Foldable for IntToPtr { + #[inline] + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + if let Some(value) = + matchers::foldable_operand_of::().matches(&self.operand().as_operand_ref()) + { + results.push(OpFoldResult::Attribute(value)); + FoldResult::Ok(()) + } else { + FoldResult::Failed + } + } + + #[inline(always)] + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + if let Some(value) = operands[0].as_deref().and_then(|o| o.downcast_ref::()) { + let attr = PointerAttr::new(*value, Type::from(PointerType::new(self.ty().clone()))); + results.push(OpFoldResult::Attribute(Box::new(attr))); + FoldResult::Ok(()) + } else { + FoldResult::Failed + } + } +} + +#[operation( + dialect = HirDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Cast { + #[operand] + operand: AnyInteger, + #[attr(hidden)] + ty: Type, + #[result] + result: AnyInteger, +} + +has_no_effects!(Cast); + +impl InferTypeOpInterface for Cast { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +#[operation( + dialect = HirDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct Bitcast { + #[operand] + operand: AnyPointerOrInteger, + #[attr(hidden)] + ty: Type, + #[result] + result: AnyPointerOrInteger, +} + +has_no_effects!(Bitcast); + +impl InferTypeOpInterface for Bitcast { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl Foldable for Bitcast { + #[inline] + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + if let Some(value) = matchers::foldable_operand().matches(&self.operand().as_operand_ref()) + { + if value.is::() || value.is::() { + // Lean on materialize_constant to handle the conversion details + results.push(OpFoldResult::Attribute(value)); + return FoldResult::Ok(()); + } + } + + FoldResult::Failed + } + + #[inline(always)] + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + if let Some(value) = + operands[0].as_deref().filter(|o| o.is::() || o.is::()) + { + results.push(OpFoldResult::Attribute(value.clone_value())); + FoldResult::Ok(()) + } else { + FoldResult::Failed + } + } +} diff --git a/dialects/hir/src/ops/constants.rs b/dialects/hir/src/ops/constants.rs new file mode 100644 index 000000000..c8088065b --- /dev/null +++ b/dialects/hir/src/ops/constants.rs @@ -0,0 +1,111 @@ +use alloc::{boxed::Box, sync::Arc}; + +use midenc_hir::{ + constants::{ConstantData, ConstantId}, + derive::operation, + effects::MemoryEffectOpInterface, + traits::*, + *, +}; + +use crate::{HirDialect, PointerAttr}; + +/// An operation for expressing constant pointer values. +/// +/// This is used to materialize folded constants for the HIR dialect. +#[operation( + dialect = HirDialect, + traits(ConstantLike), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct ConstantPointer { + #[attr(hidden)] + value: PointerAttr, + #[result] + result: AnyPointer, +} + +has_no_effects!(ConstantPointer); + +impl InferTypeOpInterface for ConstantPointer { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = Type::from(PointerType::new(self.value().pointee_type().clone())); + self.result_mut().set_type(ty); + + Ok(()) + } +} + +impl Foldable for ConstantPointer { + #[inline] + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + results.push(OpFoldResult::Attribute(self.get_attribute("value").unwrap().clone_value())); + FoldResult::Ok(()) + } + + #[inline(always)] + fn fold_with( + &self, + _operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + self.fold(results) + } +} + +/// A constant operation used to define an array of arbitrary bytes. +/// +/// This is intended for use in [super::GlobalVariable] initializer regions only. For non-global +/// uses, the maximum size of immediate values is limited to a single word. This restriction does +/// not apply to global variable initializers, which are used to express the data that should be +/// placed in memory at the address allocated for the variable, without explicit load/store ops. +#[operation( + dialect = HirDialect, + name = "bytes", + traits(ConstantLike), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct ConstantBytes { + #[attr(hidden)] + id: ConstantId, + #[result] + result: AnyArrayOf, +} + +has_no_effects!(ConstantBytes); + +impl InferTypeOpInterface for ConstantBytes { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let len = self.size_in_bytes(); + self.result_mut().set_type(Type::from(ArrayType::new(Type::U8, len))); + + Ok(()) + } +} + +impl Foldable for ConstantBytes { + #[inline] + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + results.push(OpFoldResult::Attribute(self.get_attribute("id").unwrap().clone_value())); + FoldResult::Ok(()) + } + + #[inline(always)] + fn fold_with( + &self, + _operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + self.fold(results) + } +} + +impl ConstantBytes { + pub fn size_in_bytes(&self) -> usize { + self.as_operation().context().get_constant_size_in_bytes(*self.id()) + } + + pub fn value(&self) -> Arc { + self.as_operation().context().get_constant(*self.id()) + } +} diff --git a/dialects/hir/src/ops/invoke.rs b/dialects/hir/src/ops/invoke.rs new file mode 100644 index 000000000..91badbd1f --- /dev/null +++ b/dialects/hir/src/ops/invoke.rs @@ -0,0 +1,172 @@ +use midenc_hir::{derive::operation, traits::*, *}; + +use crate::HirDialect; + +#[operation( + dialect = HirDialect, + implements(CallOpInterface, InferTypeOpInterface, OpPrinter) +)] +pub struct Exec { + #[symbol(callable)] + callee: SymbolPath, + #[attr(hidden)] + signature: Signature, + #[operands] + arguments: AnyType, +} + +impl InferTypeOpInterface for Exec { + fn infer_return_types(&mut self, context: &Context) -> Result<(), Report> { + let span = self.span(); + let owner = self.as_operation_ref(); + let signature = self.signature().clone(); + for (i, result) in signature.results().iter().enumerate() { + let value = context.make_result(span, result.ty.clone(), owner, i as u8); + self.op.results.push(value); + } + Ok(()) + } +} + +impl OpPrinter for Exec { + fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> formatter::Document { + use formatter::*; + + let op = self.as_operation(); + let prelude = print::render_operation_results(op) + display(op.name()) + const_text(" "); + let callee = const_text("@") + display(self.callee()); + let args = op.operands().iter().enumerate().fold(const_text("("), |acc, (i, arg)| { + if i > 0 { + acc + const_text(", ") + display(arg.borrow().as_value_ref()) + } else { + acc + display(arg.borrow().as_value_ref()) + } + }) + const_text(")"); + let results = print::render_operation_result_types(op); + prelude + callee + args + results + } +} + +/* +#[operation( + dialect = HirDialect, + implements(CallOpInterface) +)] +pub struct ExecIndirect { + #[attr] + signature: Signature, + /// TODO(pauls): Change this to FunctionType + #[operand] + callee: AnyType, +} + */ +impl CallOpInterface for Exec { + #[inline(always)] + fn callable_for_callee(&self) -> Callable { + self.callee().into() + } + + fn set_callee(&mut self, callable: Callable) { + let callee = callable.unwrap_symbol_path(); + self.callee_mut().path = callee; + } + + #[inline(always)] + fn arguments(&self) -> OpOperandRange<'_> { + self.operands().group(0) + } + + #[inline(always)] + fn arguments_mut(&mut self) -> OpOperandRangeMut<'_> { + self.operands_mut().group_mut(0) + } + + fn resolve(&self) -> Option { + let callee = self.callee(); + let symbol_table = self.as_operation().nearest_symbol_table()?; + let symbol_table = symbol_table.borrow(); + let symbol_table = symbol_table.as_symbol_table().unwrap(); + symbol_table.resolve(&callee.path) + } + + fn resolve_in_symbol_table(&self, symbols: &dyn SymbolTable) -> Option { + let callee = self.callee(); + symbols.resolve(&callee.path) + } +} + +// TODO(pauls): Validate that the arguments/results of the callee of this operation do not contain +// any types which are invalid for cross-context calls +#[operation( + dialect = HirDialect, + implements(CallOpInterface, InferTypeOpInterface) +)] +pub struct Call { + #[symbol(callable)] + callee: SymbolPath, + #[attr] + signature: Signature, + #[operands] + arguments: AnyType, +} + +impl InferTypeOpInterface for Call { + fn infer_return_types(&mut self, context: &Context) -> Result<(), Report> { + let span = self.span(); + let owner = self.as_operation_ref(); + let signature = self.signature().clone(); + for (i, result) in signature.results().iter().enumerate() { + let value = context.make_result(span, result.ty.clone(), owner, i as u8); + self.op.results.push(value); + } + Ok(()) + } +} + +/* +#[operation( + dialect = HirDialect, + implements(CallOpInterface) +)] +pub struct CallIndirect { + #[attr] + signature: Signature, + /// TODO(pauls): Change this to FunctionType + #[operand] + callee: AnyType, +} + */ +impl CallOpInterface for Call { + #[inline(always)] + fn callable_for_callee(&self) -> Callable { + self.callee().into() + } + + fn set_callee(&mut self, callable: Callable) { + let callee = callable.unwrap_symbol_path(); + self.callee_mut().path = callee; + } + + #[inline(always)] + fn arguments(&self) -> OpOperandRange<'_> { + self.operands().group(0) + } + + #[inline(always)] + fn arguments_mut(&mut self) -> OpOperandRangeMut<'_> { + self.operands_mut().group_mut(0) + } + + fn resolve(&self) -> Option { + let callee = self.callee(); + let symbol_table = self.as_operation().nearest_symbol_table()?; + let symbol_table = symbol_table.borrow(); + let symbol_table = symbol_table.as_symbol_table().unwrap(); + symbol_table.resolve(&callee.path) + } + + fn resolve_in_symbol_table(&self, symbols: &dyn SymbolTable) -> Option { + let callee = self.callee(); + symbols.resolve(&callee.path) + } +} diff --git a/dialects/hir/src/ops/mem.rs b/dialects/hir/src/ops/mem.rs new file mode 100644 index 000000000..d56f8fa91 --- /dev/null +++ b/dialects/hir/src/ops/mem.rs @@ -0,0 +1,147 @@ +use alloc::boxed::Box; + +use midenc_hir::{ + derive::operation, dialects::builtin::LocalVariable, effects::*, smallvec, traits::*, *, +}; +use midenc_hir_transform::SpillLike; + +use crate::HirDialect; + +/// Store `value` on the heap at `addr` +#[operation( + dialect = HirDialect, + implements(MemoryEffectOpInterface) +)] +pub struct Store { + #[operand] + addr: AnyPointer, + #[operand] + value: AnyType, +} + +impl EffectOpInterface for Store { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Write, + self.addr().as_value_ref() + )]) + } +} + +/// Store `value` on in procedure local memory +#[operation( + dialect = HirDialect, + implements(MemoryEffectOpInterface, SpillLike) +)] +pub struct StoreLocal { + #[attr] + local: LocalVariable, + #[operand] + value: AnyType, +} + +impl EffectOpInterface for StoreLocal { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Write, + Box::new(*self.local()) as Box + )]) + } +} + +impl SpillLike for StoreLocal { + fn spilled(&self) -> OpOperand { + self.value().as_operand_ref() + } + + fn spilled_value(&self) -> ValueRef { + self.value().as_value_ref() + } +} + +/// Load `result` from the heap at `addr` +/// +/// The type of load is determined by the pointer operand type - cast the pointer to the type you +/// wish to load, so long as such a load is safe according to the semantics of your high-level +/// language. +#[operation( + dialect = HirDialect, + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Load { + #[operand] + addr: AnyPointer, + #[result] + result: AnyType, +} + +impl EffectOpInterface for Load { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Read, + self.addr().as_value_ref() + )]) + } +} + +impl InferTypeOpInterface for Load { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let _span = self.span(); + let pointee = { + let addr = self.addr(); + let addr_value = addr.value(); + addr_value.ty().pointee().cloned() + }; + match pointee { + Some(pointee) => { + self.result_mut().set_type(pointee); + Ok(()) + } + None => { + // let addr = self.addr(); + // let addr_value = addr.value(); + // let addr_ty = addr_value.ty(); + // Err(context + // .session + // .diagnostics + // .diagnostic(midenc_session::diagnostics::Severity::Error) + // .with_message("invalid operand for 'load'") + // .with_primary_label( + // span, + // format!("invalid 'addr' operand, expected pointer, got '{addr_ty}'"), + // ) + // .into_report()) + Ok(()) + } + } + } +} + +#[operation( + dialect = HirDialect, + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct LoadLocal { + #[attr] + local: LocalVariable, + #[result] + result: AnyType, +} + +impl EffectOpInterface for LoadLocal { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Read, + Box::new(*self.local()) as Box + )]) + } +} + +impl InferTypeOpInterface for LoadLocal { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.local().ty(); + self.result_mut().set_type(ty); + + Ok(()) + } +} diff --git a/dialects/hir/src/ops/primop.rs b/dialects/hir/src/ops/primop.rs new file mode 100644 index 000000000..0305d3c1e --- /dev/null +++ b/dialects/hir/src/ops/primop.rs @@ -0,0 +1,112 @@ +use midenc_hir::{derive::operation, effects::*, smallvec, traits::*, *}; + +use crate::HirDialect; + +#[operation( + dialect = HirDialect, + traits(SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct MemGrow { + #[operand] + pages: UInt32, + #[result] + result: UInt32, +} + +impl EffectOpInterface for MemGrow { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![ + EffectInstance::new(MemoryEffect::Read), + EffectInstance::new(MemoryEffect::Write), + ]) + } +} + +impl InferTypeOpInterface for MemGrow { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.result_mut().set_type(Type::U32); + Ok(()) + } +} + +#[operation( + dialect = HirDialect, + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct MemSize { + #[result] + result: UInt32, +} + +impl EffectOpInterface for MemSize { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new(MemoryEffect::Read),]) + } +} + +impl InferTypeOpInterface for MemSize { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.result_mut().set_type(Type::U32); + Ok(()) + } +} + +#[operation( + dialect = HirDialect, + implements(MemoryEffectOpInterface) +)] +pub struct MemSet { + #[operand] + addr: AnyPointer, + #[operand] + count: UInt32, + #[operand] + value: AnyType, +} + +impl EffectOpInterface for MemSet { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Write, + self.addr().as_value_ref() + ),]) + } +} + +#[operation( + dialect = HirDialect, + implements(MemoryEffectOpInterface) +)] +pub struct MemCpy { + #[operand] + source: AnyPointer, + #[operand] + destination: AnyPointer, + #[operand] + count: UInt32, +} + +impl EffectOpInterface for MemCpy { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![ + EffectInstance::new_for_value(MemoryEffect::Read, self.source().as_value_ref()), + EffectInstance::new_for_value(MemoryEffect::Write, self.destination().as_value_ref()), + ]) + } +} + +#[operation( + dialect = HirDialect, + implements(MemoryEffectOpInterface) +)] +pub struct Breakpoint {} + +impl EffectOpInterface for Breakpoint { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![ + EffectInstance::new(MemoryEffect::Read), + EffectInstance::new(MemoryEffect::Write), + ]) + } +} diff --git a/dialects/hir/src/ops/spills.rs b/dialects/hir/src/ops/spills.rs new file mode 100644 index 000000000..b684ef3ff --- /dev/null +++ b/dialects/hir/src/ops/spills.rs @@ -0,0 +1,76 @@ +use midenc_hir::{derive::operation, effects::*, traits::*, *}; +use midenc_hir_transform::{ReloadLike, SpillLike}; + +use crate::HirDialect; + +#[operation( + dialect = HirDialect, + traits(SameTypeOperands, SameOperandsAndResultType), + implements(MemoryEffectOpInterface, SpillLike) +)] +pub struct Spill { + #[operand] + value: AnyType, +} + +impl SpillLike for Spill { + fn spilled(&self) -> OpOperand { + self.value().as_operand_ref() + } + + fn spilled_value(&self) -> ValueRef { + self.value().as_value_ref() + } +} + +impl EffectOpInterface for Spill { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Write, + self.spilled_value() + ),]) + } +} + +#[operation( + dialect = HirDialect, + traits(SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface, ReloadLike) +)] +pub struct Reload { + #[operand] + spill: AnyType, + #[result] + result: AnyType, +} + +impl ReloadLike for Reload { + fn spilled(&self) -> OpOperand { + self.spill().as_operand_ref() + } + + fn spilled_value(&self) -> ValueRef { + self.spill().as_value_ref() + } + + fn reloaded(&self) -> ValueRef { + self.result().as_value_ref() + } +} + +impl EffectOpInterface for Reload { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Read, + self.spilled_value() + )]) + } +} + +impl InferTypeOpInterface for Reload { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.spill().ty(); + self.result_mut().set_type(ty); + Ok(()) + } +} diff --git a/dialects/hir/src/transforms.rs b/dialects/hir/src/transforms.rs new file mode 100644 index 000000000..76247647f --- /dev/null +++ b/dialects/hir/src/transforms.rs @@ -0,0 +1,3 @@ +mod spill; + +pub use self::spill::TransformSpills; diff --git a/dialects/hir/src/transforms/spill.rs b/dialects/hir/src/transforms/spill.rs new file mode 100644 index 000000000..1f04093a3 --- /dev/null +++ b/dialects/hir/src/transforms/spill.rs @@ -0,0 +1,166 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + adt::SmallDenseMap, + dialects::builtin::{Function, FunctionRef, LocalVariable}, + pass::{Pass, PassExecutionState, PostPassStatus}, + BlockRef, BuilderExt, EntityMut, Op, OpBuilder, OperationName, OperationRef, Report, Rewriter, + SourceSpan, Spanned, Symbol, ValueRef, +}; +use midenc_hir_analysis::analyses::SpillAnalysis; +use midenc_hir_transform::{self as transforms, ReloadLike, SpillLike, TransformSpillsInterface}; + +pub struct TransformSpills; + +impl Pass for TransformSpills { + type Target = Function; + + fn name(&self) -> &'static str { + "transform-spills" + } + + fn argument(&self) -> &'static str { + "transform-spills" + } + + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn initialize(&mut self, context: Rc) -> Result<(), Report> { + context.get_or_register_dialect::(); + + Ok(()) + } + + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + let function = op.into_entity_ref(); + log::debug!(target: "insert-spills", "computing and inserting spills for {}", function.as_operation()); + + if function.is_declaration() { + log::debug!(target: "insert-spills", "function has no body, no spills needed!"); + state.preserved_analyses_mut().preserve_all(); + state.set_post_pass_status(PostPassStatus::Unchanged); + return Ok(()); + } + let mut analysis = + state.analysis_manager().get_analysis_for::()?; + + if !analysis.has_spills() { + log::debug!(target: "insert-spills", "no spills needed!"); + state.preserved_analyses_mut().preserve_all(); + state.set_post_pass_status(PostPassStatus::Unchanged); + return Ok(()); + } + + // Take ownership of the spills analysis so that we can mutate the analysis state during + // spill/reload materialization. + let analysis = Rc::make_mut(&mut analysis); + + // Place spills and reloads, rewrite IR to ensure live ranges we aimed to split are actually + // split. + let mut interface = TransformSpillsImpl { + function: function.as_function_ref(), + locals: Default::default(), + }; + + let op = function.as_operation_ref(); + drop(function); + + let transform_result = transforms::transform_spills( + op, + analysis, + &mut interface, + state.analysis_manager().clone(), + )?; + + state.set_post_pass_status(transform_result); + + Ok(()) + } +} + +struct TransformSpillsImpl { + function: FunctionRef, + locals: SmallDenseMap, +} + +impl TransformSpillsInterface for TransformSpillsImpl { + fn create_unconditional_branch( + &self, + builder: &mut OpBuilder, + destination: BlockRef, + arguments: &[ValueRef], + span: SourceSpan, + ) -> Result<(), Report> { + use midenc_dialect_cf::ControlFlowOpBuilder; + + builder.br(destination, arguments.iter().copied(), span)?; + + Ok(()) + } + + fn create_spill( + &self, + builder: &mut OpBuilder, + value: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = builder.create::(span); + op_builder(value).map(|op| op.as_operation_ref()) + } + + fn create_reload( + &self, + builder: &mut OpBuilder, + value: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = builder.create::(span); + op_builder(value).map(|op| op.as_operation_ref()) + } + + fn convert_spill_to_store( + &mut self, + rewriter: &mut dyn Rewriter, + spill: OperationRef, + ) -> Result<(), Report> { + use crate::HirOpBuilder; + + let spilled = spill.borrow().as_trait::().unwrap().spilled_value(); + let mut function = self.function; + let local = *self.locals.entry(spilled).or_insert_with(|| { + let mut function = function.borrow_mut(); + function.alloc_local(spilled.borrow().ty().clone()) + }); + + let store = rewriter.store_local(local, spilled, spill.span())?; + + rewriter.replace_op(spill, store.as_operation_ref()); + + Ok(()) + } + + fn convert_reload_to_load( + &mut self, + rewriter: &mut dyn Rewriter, + reload: OperationRef, + ) -> Result<(), Report> { + use crate::HirOpBuilder; + + let spilled = reload.borrow().as_trait::().unwrap().spilled_value(); + let local = self.locals[&spilled]; + let reloaded = rewriter.load_local(local, reload.span())?; + + rewriter.replace_op_with_values(reload, &[Some(reloaded)]); + + Ok(()) + } +} + +#[cfg(test)] +pub mod tests; diff --git a/dialects/hir/src/transforms/spill/expected/materialize_spills_branch_cfg_after.hir b/dialects/hir/src/transforms/spill/expected/materialize_spills_branch_cfg_after.hir new file mode 100644 index 000000000..f2948a082 --- /dev/null +++ b/dialects/hir/src/transforms/spill/expected/materialize_spills_branch_cfg_after.hir @@ -0,0 +1,40 @@ +public builtin.function @test::spill_branch(v0: ptr) -> u32 { +^block1(v0: ptr): + v1 = hir.ptr_to_int v0 : u32; + v2 = arith.constant 32 : u32; + v3 = arith.add v1, v2 : u32 #[overflow = unchecked]; + v4 = hir.int_to_ptr v3 : ptr; + v5 = hir.load v4 : u128; + v6 = arith.constant 64 : u32; + v7 = arith.add v1, v6 : u32 #[overflow = unchecked]; + v8 = hir.int_to_ptr v7 : ptr; + v9 = hir.load v8 : u128; + v10 = arith.constant 0 : u32; + v11 = arith.eq v1, v10 : i1; + cf.cond_br v11 ^block2, ^block3; +^block2: + v12 = arith.constant 1 : u64; + hir.store_local v1 #[local = lv0]; + v13 = hir.exec @test/example(v8, v5, v9, v9, v12) : u32 + hir.store v4, v9; + v14 = arith.constant 5 : u32; + v26 = hir.load_local : u32 #[local = lv0]; + v15 = arith.add v26, v14 : u32 #[overflow = unchecked]; + cf.br ^block5; +^block5: + cf.br ^block4(v13); +^block3: + v16 = arith.constant 8 : u32; + v17 = arith.add v1, v16 : u32 #[overflow = unchecked]; + cf.br ^block6; +^block6: + cf.br ^block4(v17); +^block4(v18: u32): + v19 = arith.constant 72 : u32; + v20 = arith.add v1, v19 : u32 #[overflow = unchecked]; + v21 = arith.add v20, v18 : u32 #[overflow = unchecked]; + v22 = hir.int_to_ptr v21 : ptr; + hir.store v4, v9; + v23 = hir.load v22 : u64; + builtin.ret v3; +}; \ No newline at end of file diff --git a/dialects/hir/src/transforms/spill/expected/materialize_spills_branch_cfg_before.hir b/dialects/hir/src/transforms/spill/expected/materialize_spills_branch_cfg_before.hir new file mode 100644 index 000000000..b7c157c8f --- /dev/null +++ b/dialects/hir/src/transforms/spill/expected/materialize_spills_branch_cfg_before.hir @@ -0,0 +1,34 @@ +public builtin.function @test::spill_branch(v0: ptr) -> u32 { +^block1(v0: ptr): + v1 = hir.ptr_to_int v0 : u32; + v2 = arith.constant 32 : u32; + v3 = arith.add v1, v2 : u32 #[overflow = unchecked]; + v4 = hir.int_to_ptr v3 : ptr; + v5 = hir.load v4 : u128; + v6 = arith.constant 64 : u32; + v7 = arith.add v1, v6 : u32 #[overflow = unchecked]; + v8 = hir.int_to_ptr v7 : ptr; + v9 = hir.load v8 : u128; + v10 = arith.constant 0 : u32; + v11 = arith.eq v1, v10 : i1; + cf.cond_br v11 ^block2, ^block3; +^block2: + v12 = arith.constant 1 : u64; + v13 = hir.exec @test/example(v8, v5, v9, v9, v12) : u32 + hir.store v4, v9; + v14 = arith.constant 5 : u32; + v15 = arith.add v1, v14 : u32 #[overflow = unchecked]; + cf.br ^block4(v13); +^block3: + v16 = arith.constant 8 : u32; + v17 = arith.add v1, v16 : u32 #[overflow = unchecked]; + cf.br ^block4(v17); +^block4(v18: u32): + v19 = arith.constant 72 : u32; + v20 = arith.add v1, v19 : u32 #[overflow = unchecked]; + v21 = arith.add v20, v18 : u32 #[overflow = unchecked]; + v22 = hir.int_to_ptr v21 : ptr; + hir.store v4, v9; + v23 = hir.load v22 : u64; + builtin.ret v3; +}; \ No newline at end of file diff --git a/dialects/hir/src/transforms/spill/expected/materialize_spills_intra_block_after.hir b/dialects/hir/src/transforms/spill/expected/materialize_spills_intra_block_after.hir new file mode 100644 index 000000000..151c64dc2 --- /dev/null +++ b/dialects/hir/src/transforms/spill/expected/materialize_spills_intra_block_after.hir @@ -0,0 +1,24 @@ +public builtin.function @test::spill(v0: ptr) -> u32 { +^block1(v0: ptr): + v1 = hir.ptr_to_int v0 : u32; + v2 = arith.constant 32 : u32; + v3 = arith.add v1, v2 : u32 #[overflow = unchecked]; + v4 = hir.int_to_ptr v3 : ptr; + v5 = hir.load v4 : u128; + v6 = arith.constant 64 : u32; + v7 = arith.add v1, v6 : u32 #[overflow = unchecked]; + v8 = hir.int_to_ptr v7 : ptr; + v9 = hir.load v8 : u128; + v10 = arith.constant 1 : u64; + hir.store_local v3 #[local = lv0]; + hir.store_local v4 #[local = lv1]; + v11 = hir.exec @test/example(v8, v5, v9, v9, v10) : u32 + v12 = arith.constant 72 : u32; + v13 = arith.add v1, v12 : u32 #[overflow = unchecked]; + v18 = hir.load_local : ptr #[local = lv1]; + hir.store v18, v13; + v14 = hir.int_to_ptr v13 : ptr; + v15 = hir.load v14 : u64; + v19 = hir.load_local : u32 #[local = lv0]; + builtin.ret v19; +}; \ No newline at end of file diff --git a/dialects/hir/src/transforms/spill/expected/materialize_spills_intra_block_before.hir b/dialects/hir/src/transforms/spill/expected/materialize_spills_intra_block_before.hir new file mode 100644 index 000000000..fc03d0c69 --- /dev/null +++ b/dialects/hir/src/transforms/spill/expected/materialize_spills_intra_block_before.hir @@ -0,0 +1,20 @@ +public builtin.function @test::spill(v0: ptr) -> u32 { +^block1(v0: ptr): + v1 = hir.ptr_to_int v0 : u32; + v2 = arith.constant 32 : u32; + v3 = arith.add v1, v2 : u32 #[overflow = unchecked]; + v4 = hir.int_to_ptr v3 : ptr; + v5 = hir.load v4 : u128; + v6 = arith.constant 64 : u32; + v7 = arith.add v1, v6 : u32 #[overflow = unchecked]; + v8 = hir.int_to_ptr v7 : ptr; + v9 = hir.load v8 : u128; + v10 = arith.constant 1 : u64; + v11 = hir.exec @test/example(v8, v5, v9, v9, v10) : u32 + v12 = arith.constant 72 : u32; + v13 = arith.add v1, v12 : u32 #[overflow = unchecked]; + hir.store v4, v13; + v14 = hir.int_to_ptr v13 : ptr; + v15 = hir.load v14 : u64; + builtin.ret v3; +}; \ No newline at end of file diff --git a/dialects/hir/src/transforms/spill/tests.rs b/dialects/hir/src/transforms/spill/tests.rs new file mode 100644 index 000000000..d1775ade7 --- /dev/null +++ b/dialects/hir/src/transforms/spill/tests.rs @@ -0,0 +1,277 @@ +use alloc::{boxed::Box, rc::Rc, sync::Arc}; +use std::string::ToString; + +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_cf::ControlFlowOpBuilder as Cf; +use midenc_expect_test::expect_file; +use midenc_hir::{ + dialects::builtin::{BuiltinOpBuilder, Function, FunctionBuilder}, + pass::{Nesting, PassManager}, + AbiParam, AddressSpace, Builder, Context, Ident, Op, OpBuilder, PointerType, Report, Signature, + SourceSpan, Type, ValueRef, +}; + +use crate::{transforms::TransformSpills, HirOpBuilder}; + +type TestResult = Result; + +/// Build a simple single-block function which triggers spills and reloads, +/// then run the `TransformSpills` pass and check that spills/reloads are +/// materialized as `hir.store_local`/`hir.load_local`. +#[test] +fn materializes_spills_intra_block() -> TestResult<()> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let span = SourceSpan::UNKNOWN; + let context = Rc::new(Context::default()); + let mut ob = OpBuilder::new(context.clone()); + + let module = ob.create_module(Ident::with_empty_span("test".into()))?; + let module_body = module.borrow().body().as_region_ref(); + ob.create_block(module_body, None, &[]); + let func = ob.create_function( + Ident::with_empty_span("test::spill".into()), + Signature::new( + [AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U8, + AddressSpace::Element, + ))))], + [AbiParam::new(Type::U32)], + ), + )?; + let callee_sig = Signature::new( + [ + AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + )))), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U64), + ], + [AbiParam::new(Type::U32)], + ); + let callee = + ob.create_function(Ident::with_empty_span("example".into()), callee_sig.clone())?; + + { + let mut b = FunctionBuilder::new(func, &mut ob); + let entry = b.current_block(); + let v0 = entry.borrow().arguments()[0] as ValueRef; + let v1 = b.ptrtoint(v0, Type::U32, span)?; + let k32 = b.u32(32, span); + let v2 = b.add_unchecked(v1, k32, span)?; + let v3 = b.inttoptr( + v2, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v4 = b.load(v3, span)?; + let k64 = b.u32(64, span); + let v5 = b.add_unchecked(v1, k64, span)?; + let v6 = b.inttoptr( + v5, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v7 = b.load(v6, span)?; + let v8 = b.u64(1, span); + let _ret_from_call = b.exec(callee, callee_sig.clone(), [v6, v4, v7, v7, v8], span)?; + let k72 = b.u32(72, span); + let v9 = b.add_unchecked(v1, k72, span)?; + b.store(v3, v9, span)?; + let v10 = b.inttoptr( + v9, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U64, + AddressSpace::Element, + ))), + span, + )?; + let _v11 = b.load(v10, span)?; + b.ret(Some(v2), span)?; + } + + // We expect spills and reloads to be required around the call and later uses: + // - At the `exec` call, operand stack pressure forces spilling of two live values. + // The analysis selects the farthest next-use candidates, which here correspond to + // the results used later. + // - Before the `store` and `ret` we must reload them back. + // After running TransformSpills, those spill/reload pseudos will be materialized as: + // store_local + // load_local : #[local = lvN] + let before = func.as_operation_ref().borrow().to_string(); + + expect_file!["expected/materialize_spills_intra_block_before.hir"].assert_eq(&before); + + let mut pm = PassManager::on::(context, Nesting::Implicit); + pm.add_pass(Box::new(TransformSpills)); + pm.run(func.as_operation_ref())?; + + let after = func.as_operation_ref().borrow().to_string(); + // Check output IR: spills become store_local; reloads become load_local + expect_file!["expected/materialize_spills_intra_block_after.hir"].assert_eq(&after); + + // Also assert counts for materialized spills/reloads (similar to branching test style) + let stores = after.lines().filter(|l| l.trim_start().starts_with("hir.store_local ")).count(); + let loads = after + .lines() + .filter(|l| { + let t = l.trim_start(); + t.contains("= hir.load_local ") || t.starts_with("hir.load_local ") + }) + .count(); + assert!(stores == 2, "expected two store_local ops\n{after}"); + assert!(loads == 2, "expected two load_local ops\n{after}"); + + Ok(()) +} + +/// Build a branching CFG (then/else/merge) and validate that spills on one path and reloads on the +/// other are materialized as `store_local`/`load_local`, with edges split as needed. +#[test] +fn materializes_spills_branching_cfg() -> TestResult<()> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let span = SourceSpan::UNKNOWN; + let context = Rc::new(Context::default()); + let mut ob = OpBuilder::new(context.clone()); + + let module = ob.create_module(Ident::with_empty_span("test".into()))?; + let module_body = module.borrow().body().as_region_ref(); + ob.create_block(module_body, None, &[]); + let func = ob.create_function( + Ident::with_empty_span("test::spill_branch".into()), + Signature::new( + [AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U8, + AddressSpace::Element, + ))))], + [AbiParam::new(Type::U32)], + ), + )?; + + let callee_sig = Signature::new( + [ + AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + )))), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U64), + ], + [AbiParam::new(Type::U32)], + ); + let callee = + ob.create_function(Ident::with_empty_span("example".into()), callee_sig.clone())?; + + { + let mut b = FunctionBuilder::new(func, &mut ob); + let entry = b.current_block(); + let v0 = entry.borrow().arguments()[0] as ValueRef; + let v1 = b.ptrtoint(v0, Type::U32, span)?; + let k32 = b.u32(32, span); + let v2c = b.add_unchecked(v1, k32, span)?; + let v3c = b.inttoptr( + v2c, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v4 = b.load(v3c, span)?; + let k64 = b.u32(64, span); + let v5 = b.add_unchecked(v1, k64, span)?; + let v6 = b.inttoptr( + v5, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v7 = b.load(v6, span)?; + let zero = b.u32(0, span); + let v8 = b.eq(v1, zero, span)?; + let t = b.create_block(); + let f = b.create_block(); + Cf::cond_br(&mut b, v8, t, [], f, [], span)?; + + // then + b.switch_to_block(t); + let v9 = b.u64(1, span); + let call = b.exec(callee, callee_sig.clone(), [v6, v4, v7, v7, v9], span)?; + let v10 = call.borrow().results()[0] as ValueRef; + // Force a use of a spilled value (v1) after spills in the then-path to require a reload + b.store(v3c, v7, span)?; // use ptr after spills + let k5 = b.u32(5, span); + let _use_v1 = b.add_unchecked(v1, k5, span)?; // use v1 after spills + let join = b.create_block(); + b.br(join, [v10], span)?; + + // else + b.switch_to_block(f); + let k8 = b.u32(8, span); + let v11 = b.add_unchecked(v1, k8, span)?; + b.br(join, [v11], span)?; + + // join + let v12 = b.append_block_param(join, Type::U32, span); + b.switch_to_block(join); + let k72 = b.u32(72, span); + let v13 = b.add_unchecked(v1, k72, span)?; + let v14 = b.add_unchecked(v13, v12, span)?; + let v15 = b.inttoptr( + v14, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U64, + AddressSpace::Element, + ))), + span, + )?; + b.store(v3c, v7, span)?; + let _v16 = b.load(v15, span)?; + b.ret(Some(v2c), span)?; + } + + let before = func.as_operation_ref().borrow().to_string(); + assert!(before.contains("cf.cond_br") && before.contains("hir.exec")); + + expect_file!["expected/materialize_spills_branch_cfg_before.hir"].assert_eq(&before); + + let mut pm = PassManager::on::(context, Nesting::Implicit); + pm.add_pass(Box::new(TransformSpills)); + pm.run(func.as_operation_ref())?; + + let after = func.as_operation_ref().borrow().to_string(); + + expect_file!["expected/materialize_spills_branch_cfg_after.hir"].assert_eq(&after); + + let stores = after.lines().filter(|l| l.trim_start().starts_with("hir.store_local ")).count(); + let loads = after + .lines() + .filter(|l| { + l.trim_start().contains("= hir.load_local ") + || l.trim_start().starts_with("hir.load_local ") + }) + .count(); + assert!(stores == 1, "expected only one store_local ops\n{after}"); + assert!(loads == 1, "expected only one load_local op\n{after}"); + Ok(()) +} diff --git a/dialects/scf/CHANGELOG.md b/dialects/scf/CHANGELOG.md new file mode 100644 index 000000000..bcecbf637 --- /dev/null +++ b/dialects/scf/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-dialect-scf-v0.1.5...midenc-dialect-scf-v0.4.0) - 2025-08-15 + +### Fixed + +- cast block 345 using `zext` instead of `set_type` +- change v345's type to U32 so that all the variables are of the same type + +### Other + +- Use the midenc_hir `test` dialect for ops in the new tests. +- Use `Operation::is()` rather than an op name compare. +- Address PR feedback. +- Fix spelling in comment. +- Add a rewriter pass for folding redundant yields from SCF control flow. + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-dialect-scf-v0.1.0...midenc-dialect-scf-v0.1.5) - 2025-07-01 + +### Fixed + +- *(scf)* over-eager trivial if-to-select rewrite +- delayed registration of scf dialect causes canonicalizations to be skipped + +### Other + +- remove `expect-test` in favor of `midenc-expect-test` diff --git a/dialects/scf/Cargo.toml b/dialects/scf/Cargo.toml new file mode 100644 index 000000000..a560ca34f --- /dev/null +++ b/dialects/scf/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "midenc-dialect-scf" +description = "Miden IR Structured Control Flow Dialect" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["midenc-hir/std"] + +[dependencies] +log.workspace = true +midenc-dialect-arith.workspace = true +midenc-dialect-cf.workspace = true +midenc-dialect-ub.workspace = true +midenc-hir.workspace = true +midenc-hir-transform.workspace = true +bitvec.workspace = true + +[dev-dependencies] +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging +midenc-expect-test = { path = "../../tools/expect-test" } +env_logger.workspace = true diff --git a/dialects/scf/src/builders.rs b/dialects/scf/src/builders.rs new file mode 100644 index 000000000..9d2445eba --- /dev/null +++ b/dialects/scf/src/builders.rs @@ -0,0 +1,160 @@ +use midenc_hir::{ + dialects::builtin::FunctionBuilder, ArrayAttr, Builder, BuilderExt, OpBuilder, Region, Report, + SourceSpan, Type, UnsafeIntrusiveEntityRef, ValueRef, +}; + +use crate::*; + +pub trait StructuredControlFlowOpBuilder<'f, B: ?Sized + Builder> { + fn r#if( + &mut self, + cond: ValueRef, + results: &[Type], + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + let if_op = op_builder(cond)?; + { + let mut owner = if_op.as_operation_ref(); + let context = self.builder().context(); + for result_ty in results { + let result = context.make_result(span, result_ty.clone(), owner, 0); + owner.borrow_mut().results_mut().push(result); + } + } + Ok(if_op) + } + + fn r#while( + &mut self, + loop_init_variables: T, + results: &[Type], + span: SourceSpan, + ) -> Result, Report> + where + T: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + let mut while_op = op_builder(loop_init_variables)?; + { + let mut owner = while_op.as_operation_ref(); + let context = self.builder().context(); + for result_ty in results { + let result = context.make_result(span, result_ty.clone(), owner, 0); + owner.borrow_mut().results_mut().push(result); + } + } + { + let mut while_op = while_op.borrow_mut(); + let before_block = self + .builder() + .context() + .create_block_with_params(while_op.inits().iter().map(|v| v.borrow().ty())); + while_op.before_mut().body_mut().push_back(before_block); + let after_block = + self.builder().context().create_block_with_params(results.iter().cloned()); + while_op.after_mut().body_mut().push_back(after_block); + } + Ok(while_op) + } + + fn index_switch( + &mut self, + selector: ValueRef, + cases: T, + results: &[Type], + span: SourceSpan, + ) -> Result, Report> + where + T: IntoIterator, + { + let cases = ArrayAttr::from_iter(cases); + let num_cases = cases.len(); + let op_builder = self.builder_mut().create::(span); + let switch_op = op_builder(selector, cases)?; + let mut owner = switch_op.as_operation_ref(); + + // Create results + { + let context = self.builder().context(); + for result_ty in results { + let result = context.make_result(span, result_ty.clone(), owner, 0); + owner.borrow_mut().results_mut().push(result); + } + } + + // Create regions for all cases + { + for _ in 0..num_cases { + let region = self.builder().context().alloc_tracked(Region::default()); + owner.borrow_mut().regions_mut().push_back(region); + } + } + + Ok(switch_op) + } + + fn condition( + &mut self, + cond: ValueRef, + forwarded: T, + span: SourceSpan, + ) -> Result, Report> + where + T: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + op_builder(cond, forwarded) + } + + fn r#yield( + &mut self, + yielded: T, + span: SourceSpan, + ) -> Result, Report> + where + T: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + op_builder(yielded) + } + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +impl<'f, B: ?Sized + Builder> StructuredControlFlowOpBuilder<'f, B> for FunctionBuilder<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + FunctionBuilder::builder(self) + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + FunctionBuilder::builder_mut(self) + } +} + +impl<'f> StructuredControlFlowOpBuilder<'f, OpBuilder> for &'f mut OpBuilder { + #[inline(always)] + fn builder(&self) -> &OpBuilder { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut OpBuilder { + self + } +} + +impl StructuredControlFlowOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/dialects/scf/src/canonicalization.rs b/dialects/scf/src/canonicalization.rs new file mode 100644 index 000000000..ad61616ce --- /dev/null +++ b/dialects/scf/src/canonicalization.rs @@ -0,0 +1,24 @@ +//mod convert_do_while_to_while_true; +mod convert_trivial_if_to_select; +mod fold_constant_index_switch; +mod fold_redundant_yields; +mod if_remove_unused_results; +mod remove_loop_invariant_args_from_before_block; +//mod remove_loop_invariant_value_yielded; +mod while_condition_truth; +mod while_remove_duplicated_results; +mod while_remove_unused_args; +mod while_unused_result; + +pub use self::{ + //convert_do_while_to_while_true::ConvertDoWhileToWhileTrue, + convert_trivial_if_to_select::ConvertTrivialIfToSelect, + fold_constant_index_switch::FoldConstantIndexSwitch, + fold_redundant_yields::FoldRedundantYields, + if_remove_unused_results::IfRemoveUnusedResults, + remove_loop_invariant_args_from_before_block::RemoveLoopInvariantArgsFromBeforeBlock, + while_condition_truth::WhileConditionTruth, + while_remove_duplicated_results::WhileRemoveDuplicatedResults, + while_remove_unused_args::WhileRemoveUnusedArgs, + while_unused_result::WhileUnusedResult, +}; diff --git a/dialects/scf/src/canonicalization/convert_do_while_to_while_true.rs b/dialects/scf/src/canonicalization/convert_do_while_to_while_true.rs new file mode 100644 index 000000000..f36c67189 --- /dev/null +++ b/dialects/scf/src/canonicalization/convert_do_while_to_while_true.rs @@ -0,0 +1,249 @@ +use alloc::rc::Rc; + +use midenc_hir::{adt::SmallSet, *}; + +use crate::{ + builders::{DefaultInstBuilder, InstBuilder}, + ops::While, + Condition, Constant, HirDialect, If, +}; + +/// Identifies [While] loops in do-while form where the body of the `while` block simply forwards +/// operands to the `do` block, which consists solely of an `hir.if`, any operations that feed into +/// its condition, and an `hir.condition` where the condition is a result of the `hir.if` that is +/// constant. See below for an example. +/// +/// Such loops are produced by the control flow lifting transformation before these patterns can +/// be recognized, and the code generated for them is noisier and less efficient than when they +/// are in canonical form, which is what this canonicalization pattern is designed to do. +/// +/// Before: +/// +/// ```text,ignore +/// %input1 = ... : u32 +/// %inputN = ... : u32 +/// %true = hir.constant 1 : i1 +/// %false = hir.constant 0 : i1 +/// %out1, %outN = hir.while %input1, %inputN : u32, u32 -> .. { +/// ^bb0(%in1: u32, %in2: u32): +/// %condition = call @evaluate_condition(%in1, %in2) : (u32, u32) -> i1 +/// %should_continue, %result1, %resultN = hir.if %condition : -> i1, .. { +/// %v1 = ... : u32 +/// %vN = ... : u32 +/// hir.yield %true, %v1, %vN +/// } else { +/// hir.yield %false, %in1, %in2 +/// } +/// hir.condition %should_continue, %result1, %resultN : i1, ... +/// } do { +/// ^bb1(%arg1: u32, %argN: u32): +/// hir.yield %arg1, %argN +/// ... +/// ``` +/// +/// After: +/// +/// ```text,ignore +/// %input1 = ... : u32 +/// %inputN = ... : u32 +/// %out1, %outN = hir.while %input1, %inputN : u32, u32 -> .. { +/// ^bb0(%in1: u32, %in2: u32): +/// %condition = call @evaluate_condition(%in1, %in2) : (u32, u32) -> i1 +/// hir.condition %condition, %in1, %in2 +/// } do { +/// ^bb1(%arg1: u32, %argN: u32): +/// %v1 = ... : u32 +/// %vN = ... : u32 +/// hir.yield %v1, %vN +/// } +/// ``` +/// +/// The process looks like so: +/// +/// 1. We determine that the `after` block of the loop consists of just an `hir.yield` that forwards +/// some or all of the block arguments. This tells us that the loop is almost certainly in +/// do-while form. +/// +/// 2. We then look at the `hir.condition` of the `before` block. +/// a. Is the condition operand the result of an `hir.if`? +/// b. If so, is that result assigned a constant value in each branch? +/// c. If so, then we have a possible match for this transformation +/// +/// 3. Next, we must ensure that the `before` block only consists of operations that are used to +/// compute the `hir.if` condition used as input to the `hir.condition` op that terminates the +/// block. We do this by starting from the `hir.condition`, and then adding the defining ops of +/// any of its operands to a set, doing so recursively. We then walk the block, and if any ops +/// are encountered which are not in that set, and those ops have side effects, then it is not +/// safe for us to perform this transformation. If they do not have side effects, and they are +/// used within the body of the `hir.if`, then they can be moved as needed (or erased if dead). +/// +/// In the above example, we can see that our input IR matches all three criteria, so the transform +/// can proceed. +pub struct ConvertDoWhileToWhileTrue { + info: PatternInfo, +} + +impl ConvertDoWhileToWhileTrue { + pub fn new(context: Rc) -> Self { + let hir_dialect = context.get_or_register_dialect::(); + let while_op = hir_dialect.registered_name::().expect("hir.while is not registered"); + Self { + info: PatternInfo::new( + context, + "convert-do-while-to-while-true", + PatternKind::Operation(while_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for ConvertDoWhileToWhileTrue { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for ConvertDoWhileToWhileTrue { + fn matches(&self, _op: OperationRef) -> Result { + panic!("call match_and_rewrite") + } + + fn rewrite(&self, _op: OperationRef, _rewriter: &mut dyn Rewriter) { + panic!("call match_and_rewrite") + } + + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(while_op) = op.downcast_ref::() else { + return Ok(false); + }; + + let before_block = while_op.before().entry_block_ref().unwrap(); + let after_block = while_op.after().entry_block_ref().unwrap(); + + // Criteria #1 + let after = after_block.borrow(); + let after_term = after.terminator().unwrap(); + let after_only_yields = after_term.prev().is_none(); + + // Criteria #2 + let condition = while_op.condition_op(); + let condition_op = condition.borrow(); + // Condition must be an operation result + let condition_value = condition_op.condition().as_value_ref(); + let Some(condition_owner) = condition_value.borrow().get_defining_op() else { + return Ok(false); + }; + let condition_owner_op = condition_owner.borrow(); + // Condition owner must be an hir.if + let Some(if_op) = condition_owner_op.downcast_ref::() else { + return Ok(false); + }; + // Condition value must be a result of an hir.if that has a constant value along all paths + // which produce that result + let Some(condition_constant) = eval_condition(condition_value, if_op) else { + return Ok(false); + }; + + // Criteria #3 + if !transformation_is_safe(&condition_op) { + return Ok(false); + } + + let span = while_op.span(); + let result_types = while_op + .results() + .iter() + .map(|r| r.borrow().ty().clone()) + .collect::>(); + + // Create a new hir.while to replace the original + rewriter.set_insertion_point_before(operation); + let loop_inits = while_op.inits().into_iter().map(|o| o.borrow().as_value_ref()); + let new_while = + DefaultInstBuilder::new(rewriter).r#while(loop_inits, &result_types, span)?; + + let before_args = while_op + .before() + .entry() + .arguments() + .iter() + .map(|arg| arg.borrow().as_value_ref()) + .collect::>(); + let after_args = while_op + .before() + .entry() + .arguments() + .iter() + .map(|arg| arg.borrow().as_value_ref()) + .collect::>(); + + todo!(); + + Ok(true) + } +} + +fn transformation_is_safe(condition: &Condition) -> bool { + // Construct the set of allowed operations + let parent_block = condition.parent().unwrap(); + let mut allowed = SmallSet::::default(); + allowed.insert(condition.as_operation_ref()); + let mut worklist = SmallVec::<[_; 4]>::from_iter(condition.operands().iter().copied()); + while let Some(operand) = worklist.pop() { + if let Some(defining_op) = operand.borrow().value().get_defining_op() { + if defining_op.parent().unwrap() == parent_block { + allowed.insert(defining_op); + worklist.extend(defining_op.borrow().operands().iter().copied()); + } + } + } + + // Determine if any of the operations in `parent_block` are not allowed + let mut next_op = parent_block.borrow().body().back().as_pointer(); + while let Some(op) = next_op.take() { + next_op = op.prev(); + + if !allowed.contains(&op) { + return false; + } + } + + true +} + +fn eval_condition(value: ValueRef, if_op: &If) -> Option { + let value = value.borrow(); + let result = value.downcast_ref::().unwrap(); + let result_index = result.index(); + + let then_yield = if_op.then_yield(); + let then_yielded = then_yield.borrow().yielded()[result_index].borrow().as_value_ref(); + let definition = then_yielded.borrow().get_defining_op()?; + let definition = definition.borrow(); + let definition = definition.downcast_ref::()?; + let then_value = definition.value().as_bool()?; + + let else_yield = if_op.else_yield(); + let else_yielded = else_yield.borrow().yielded()[result_index].borrow().as_value_ref(); + let definition = else_yielded.borrow().get_defining_op()?; + let definition = definition.borrow(); + let definition = definition.downcast_ref::()?; + let else_value = definition.value().as_bool()?; + + // If the condition is the same in both branches, the transformation isn't safe, as the + // loop is non-standard (i.e. it is either infinite, or something else less clear). + if then_value == else_value { + None + } else { + // Otherwise, we want to know what the value of the loop condition is when the hir.if + // condition is true. This tells us whether the hir.if is effectively inverting the + // condition or not + Some(then_value) + } +} diff --git a/dialects/scf/src/canonicalization/convert_trivial_if_to_select.rs b/dialects/scf/src/canonicalization/convert_trivial_if_to_select.rs new file mode 100644 index 000000000..76f5c4ad1 --- /dev/null +++ b/dialects/scf/src/canonicalization/convert_trivial_if_to_select.rs @@ -0,0 +1,170 @@ +use alloc::rc::Rc; + +use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_hir::{ + adt::SmallDenseMap, + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Hoist any yielded results whose operands are defined outside an [If], to a [Select] instruction. +pub struct ConvertTrivialIfToSelect { + info: PatternInfo, +} + +impl ConvertTrivialIfToSelect { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let if_op = scf_dialect.registered_name::().expect("scf.if is not registered"); + Self { + info: PatternInfo::new( + context, + "convert-trivial-if-to-select", + PatternKind::Operation(if_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for ConvertTrivialIfToSelect { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for ConvertTrivialIfToSelect { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let num_results = op.num_results(); + if num_results == 0 { + return Ok(false); + } + + let Some(if_op) = op.downcast_ref::() else { + return Ok(false); + }; + + let span = if_op.span(); + let cond = if_op.condition().as_value_ref(); + let then_region = if_op.then_body().as_region_ref(); + let else_region = if_op.else_body().as_region_ref(); + let then_yield = if_op.then_yield(); + let else_yield = if_op.else_yield(); + let then_yield_args = then_yield + .borrow() + .yielded() + .into_iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + let else_yield_args = else_yield + .borrow() + .yielded() + .into_iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + drop(op); + + let mut non_hoistable = SmallVec::<[_; 4]>::default(); + for (true_value, false_value) in + then_yield_args.iter().copied().zip(else_yield_args.iter().copied()) + { + let true_value = true_value.borrow(); + if true_value.parent_region().unwrap() == then_region + || false_value.borrow().parent_region().unwrap() == else_region + { + non_hoistable.push(true_value.ty().clone()); + } + } + + // Early exit if there aren't any yielded results we can hoist + if non_hoistable.len() == num_results { + return Ok(false); + } + + // Create a new `scf.if` for the non-hoistable results, if there are any. + // + // Then, use either the new `scf.if`, or the original, as the anchor for inserting hoisted + // `hir.select`s. + let anchor = { + // Create a new `scf.if` with the non-hoistable results + let mut new_if = rewriter.r#if(cond, &non_hoistable, span)?; + let mut new_if_op = new_if.borrow_mut(); + new_if_op.then_body_mut().take_body(then_region); + new_if_op.else_body_mut().take_body(else_region); + new_if + }; + + // Insert `scf.select` ops for each hoisted result + let mut results = SmallVec::<[_; 4]>::with_capacity(num_results); + assert_eq!(then_yield.borrow().num_operands(), num_results); + assert_eq!(else_yield.borrow().num_operands(), num_results); + let mut true_yields = SmallVec::<[ValueRef; 4]>::default(); + let mut false_yields = SmallVec::<[ValueRef; 4]>::default(); + let mut deduplicated_selections = + SmallDenseMap::<(ValueRef, ValueRef), ValueRef, 4>::default(); + let anchor_op = anchor.borrow(); + let new_then_region = anchor_op.then_body().as_region_ref(); + let new_else_region = anchor_op.else_body().as_region_ref(); + rewriter.set_insertion_point_before(anchor.as_operation_ref()); + for (true_value, false_value) in + then_yield_args.iter().copied().zip(else_yield_args.iter().copied()) + { + let true_parent_region = true_value.borrow().parent_region().unwrap(); + let false_parent_region = false_value.borrow().parent_region().unwrap(); + if new_then_region == true_parent_region || new_else_region == false_parent_region { + results.push(Some(anchor_op.results()[true_yields.len()] as ValueRef)); + true_yields.push(true_value); + false_yields.push(false_value); + } else if true_value == false_value { + results.push(Some(true_value)); + } else if let Some(duplicate) = deduplicated_selections.get(&(true_value, false_value)) + { + results.push(Some(*duplicate)); + } else { + let selected = rewriter.select(cond, true_value, false_value, span)?; + results.push(Some(selected)); + deduplicated_selections.insert((true_value, false_value), selected); + } + } + + // Rewrite the `scf.yield` ops in the new `scf.if` + let new_then_yield = anchor_op.then_yield(); + let new_else_yield = anchor_op.else_yield(); + + rewriter.set_insertion_point_to_end(new_then_region.borrow().entry_block_ref().unwrap()); + let replacement_then_yield = rewriter.r#yield(true_yields, span)?.as_operation_ref(); + rewriter.replace_op(new_then_yield.as_operation_ref(), replacement_then_yield); + + rewriter.set_insertion_point_to_end(new_else_region.borrow().entry_block_ref().unwrap()); + let replacement_else_yield = rewriter.r#yield(false_yields, span)?; + rewriter.replace_op( + new_else_yield.as_operation_ref(), + replacement_else_yield.as_operation_ref(), + ); + + // Determine if the new `scf.if` is redundant, i.e. returns no results and does no work + let is_noop = non_hoistable.is_empty() + && anchor_op.then_body().entry().body().len() == 1 + && anchor_op.else_body().entry().body().len() == 1; + + // Drop our reference to the new `scf.if` + drop(anchor_op); + + // If the new `scf.if` is a no-op, remove it + if is_noop { + rewriter.erase_op(anchor.as_operation_ref()); + } + + // Replace the original results with the new ones + rewriter.replace_op_with_values(operation, &results); + + Ok(true) + } +} diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_but_one_switch_after.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_but_one_switch_after.hir new file mode 100644 index 000000000..191a9efd7 --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_but_one_switch_after.hir @@ -0,0 +1,27 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v8 = test.constant 44 : u32; + v5 = test.constant 11 : u32; + v6 = test.constant 22 : u32; + v7 = test.constant 33 : u32; + v14 = scf.index_switch v0 : u32 + case 1 { + ^block1: + scf.yield v6; + } + case 2 { + ^block2: + scf.yield v6; + } + case 3 { + ^block3: + scf.yield v6; + } + default { + ^block4: + scf.yield v8; + }; + v9 = test.add v5, v14 : u32 #[overflow = checked]; + v10 = test.add v9, v7 : u32 #[overflow = checked]; + builtin.ret v10; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_but_one_switch_before.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_but_one_switch_before.hir new file mode 100644 index 000000000..21dbbb740 --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_but_one_switch_before.hir @@ -0,0 +1,27 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v5 = test.constant 11 : u32; + v6 = test.constant 22 : u32; + v7 = test.constant 33 : u32; + v13, v14, v15 = scf.index_switch v0 : u32, u32, u32 + case 1 { + ^block1: + scf.yield v5, v6, v7; + } + case 2 { + ^block2: + scf.yield v5, v6, v7; + } + case 3 { + ^block3: + scf.yield v5, v6, v7; + } + default { + ^block4: + v8 = test.constant 44 : u32; + scf.yield v5, v8, v7; + }; + v9 = test.add v13, v14 : u32 #[overflow = checked]; + v10 = test.add v9, v15 : u32 #[overflow = checked]; + builtin.ret v10; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_if_switch_after.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_if_switch_after.hir new file mode 100644 index 000000000..89170623d --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_if_switch_after.hir @@ -0,0 +1,7 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 11 : u32; + v4 = test.constant 22 : u32; + v7 = test.add v3, v4 : u32 #[overflow = checked]; + builtin.ret v7; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_if_switch_before.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_if_switch_before.hir new file mode 100644 index 000000000..6e3406b89 --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_if_switch_before.hir @@ -0,0 +1,29 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 11 : u32; + v4 = test.constant 22 : u32; + v12, v13 = scf.index_switch v0 : u32, u32 + case 1 { + ^block1: + scf.yield v3, v4; + } + case 2 { + ^block4: + v5 = test.constant 0 : u32; + v6 = test.eq v0, v5 : i1; + v14, v15 = scf.if v6 : u32, u32 { + ^block2: + scf.yield v3, v4; + } else { + ^block3: + scf.yield v3, v4; + }; + scf.yield v14, v15; + } + default { + ^block5: + scf.yield v3, v4; + }; + v7 = test.add v12, v13 : u32 #[overflow = checked]; + builtin.ret v7; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_switch_if_after.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_switch_if_after.hir new file mode 100644 index 000000000..89170623d --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_switch_if_after.hir @@ -0,0 +1,7 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 11 : u32; + v4 = test.constant 22 : u32; + v7 = test.add v3, v4 : u32 #[overflow = checked]; + builtin.ret v7; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_switch_if_before.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_switch_if_before.hir new file mode 100644 index 000000000..37c73505a --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_all_switch_if_before.hir @@ -0,0 +1,29 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 11 : u32; + v4 = test.constant 22 : u32; + v5 = test.constant 0 : u32; + v6 = test.neq v0, v5 : i1; + v12, v13 = scf.if v6 : u32, u32 { + ^block1: + v14, v15 = scf.index_switch v0 : u32, u32 + case 1 { + ^block2: + scf.yield v3, v4; + } + case 2 { + ^block3: + scf.yield v3, v4; + } + default { + ^block4: + scf.yield v3, v4; + }; + scf.yield v14, v15; + } else { + ^block5: + scf.yield v3, v4; + }; + v7 = test.add v12, v13 : u32 #[overflow = checked]; + builtin.ret v7; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_different_pos_switch_after.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_different_pos_switch_after.hir new file mode 100644 index 000000000..144059a3d --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_different_pos_switch_after.hir @@ -0,0 +1,20 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 11 : u32; + v4 = test.constant 22 : u32; + v8, v9 = scf.index_switch v0 : u32, u32 + case 1 { + ^block1: + scf.yield v3, v4; + } + case 2 { + ^block2: + scf.yield v4, v3; + } + default { + ^block3: + scf.yield v3, v4; + }; + v5 = test.add v8, v9 : u32 #[overflow = checked]; + builtin.ret v5; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_different_pos_switch_before.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_different_pos_switch_before.hir new file mode 100644 index 000000000..144059a3d --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_different_pos_switch_before.hir @@ -0,0 +1,20 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 11 : u32; + v4 = test.constant 22 : u32; + v8, v9 = scf.index_switch v0 : u32, u32 + case 1 { + ^block1: + scf.yield v3, v4; + } + case 2 { + ^block2: + scf.yield v4, v3; + } + default { + ^block3: + scf.yield v3, v4; + }; + v5 = test.add v8, v9 : u32 #[overflow = checked]; + builtin.ret v5; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_effects_if_after.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_effects_if_after.hir new file mode 100644 index 000000000..e0d70e833 --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_effects_if_after.hir @@ -0,0 +1,15 @@ +public builtin.function @test(v0: u32, v1: ptr) -> u32 { +^block0(v0: u32, v1: ptr): + v3 = test.constant 11 : u32; + v4 = test.constant 0 : u32; + v5 = test.eq v0, v4 : i1; + scf.if v5{ + ^block1: + scf.yield ; + } else { + ^block2: + test.store v1, v3; + scf.yield ; + }; + builtin.ret v3; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_effects_if_before.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_effects_if_before.hir new file mode 100644 index 000000000..9bc17760f --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_effects_if_before.hir @@ -0,0 +1,15 @@ +public builtin.function @test(v0: u32, v1: ptr) -> u32 { +^block0(v0: u32, v1: ptr): + v3 = test.constant 11 : u32; + v4 = test.constant 0 : u32; + v5 = test.eq v0, v4 : i1; + v8 = scf.if v5 : u32 { + ^block1: + scf.yield v3; + } else { + ^block2: + test.store v1, v3; + scf.yield v3; + }; + builtin.ret v8; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_many_switch_after.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_many_switch_after.hir new file mode 100644 index 000000000..3cb783fdb --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_many_switch_after.hir @@ -0,0 +1,36 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v15 = test.constant 301 : u32; + v14 = test.constant 300 : u32; + v13 = test.constant 201 : u32; + v12 = test.constant 200 : u32; + v11 = test.constant 101 : u32; + v10 = test.constant 100 : u32; + v17 = test.constant 401 : u32; + v16 = test.constant 400 : u32; + v7 = test.constant 11 : u32; + v8 = test.constant 22 : u32; + v9 = test.constant 33 : u32; + v26, v28 = scf.index_switch v0 : u32, u32 + case 1 { + ^block1: + scf.yield v10, v11; + } + case 2 { + ^block2: + scf.yield v12, v13; + } + case 3 { + ^block3: + scf.yield v14, v15; + } + default { + ^block4: + scf.yield v16, v17; + }; + v18 = test.add v7, v8 : u32 #[overflow = checked]; + v19 = test.add v18, v26 : u32 #[overflow = checked]; + v20 = test.add v19, v9 : u32 #[overflow = checked]; + v21 = test.add v20, v28 : u32 #[overflow = checked]; + builtin.ret v21; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_many_switch_before.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_many_switch_before.hir new file mode 100644 index 000000000..8056f4d24 --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_many_switch_before.hir @@ -0,0 +1,36 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v7 = test.constant 11 : u32; + v8 = test.constant 22 : u32; + v9 = test.constant 33 : u32; + v24, v25, v26, v27, v28 = scf.index_switch v0 : u32, u32, u32, u32, u32 + case 1 { + ^block1: + v10 = test.constant 100 : u32; + v11 = test.constant 101 : u32; + scf.yield v7, v8, v10, v9, v11; + } + case 2 { + ^block2: + v12 = test.constant 200 : u32; + v13 = test.constant 201 : u32; + scf.yield v7, v8, v12, v9, v13; + } + case 3 { + ^block3: + v14 = test.constant 300 : u32; + v15 = test.constant 301 : u32; + scf.yield v7, v8, v14, v9, v15; + } + default { + ^block4: + v16 = test.constant 400 : u32; + v17 = test.constant 401 : u32; + scf.yield v7, v8, v16, v9, v17; + }; + v18 = test.add v24, v25 : u32 #[overflow = checked]; + v19 = test.add v18, v26 : u32 #[overflow = checked]; + v20 = test.add v19, v27 : u32 #[overflow = checked]; + v21 = test.add v20, v28 : u32 #[overflow = checked]; + builtin.ret v21; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_subset_if_switch_after.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_subset_if_switch_after.hir new file mode 100644 index 000000000..5a1373368 --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_subset_if_switch_after.hir @@ -0,0 +1,33 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v8 = test.constant 44 : u32; + v7 = test.constant 33 : u32; + v9 = test.constant 55 : u32; + v6 = test.constant 22 : u32; + v3 = test.constant 11 : u32; + v4 = test.constant 0 : u32; + v5 = test.eq v0, v4 : i1; + v17 = scf.if v5 : u32 { + ^block1: + scf.yield v6; + } else { + ^block2: + v19 = scf.index_switch v0 : u32 + case 1 { + ^block3: + scf.yield v7; + } + case 2 { + ^block4: + scf.yield v8; + } + default { + ^block5: + scf.yield v9; + }; + scf.yield v19; + }; + v10 = test.add v3, v17 : u32 #[overflow = checked]; + v11 = test.add v10, v3 : u32 #[overflow = checked]; + builtin.ret v11; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/expected/fold_redundant_yields_subset_if_switch_before.hir b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_subset_if_switch_before.hir new file mode 100644 index 000000000..7d9af48f9 --- /dev/null +++ b/dialects/scf/src/canonicalization/expected/fold_redundant_yields_subset_if_switch_before.hir @@ -0,0 +1,33 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 11 : u32; + v4 = test.constant 0 : u32; + v5 = test.eq v0, v4 : i1; + v16, v17 = scf.if v5 : u32, u32 { + ^block1: + v6 = test.constant 22 : u32; + scf.yield v3, v6; + } else { + ^block2: + v18, v19 = scf.index_switch v0 : u32, u32 + case 1 { + ^block3: + v7 = test.constant 33 : u32; + scf.yield v3, v7; + } + case 2 { + ^block4: + v8 = test.constant 44 : u32; + scf.yield v3, v8; + } + default { + ^block5: + v9 = test.constant 55 : u32; + scf.yield v3, v9; + }; + scf.yield v18, v19; + }; + v10 = test.add v16, v17 : u32 #[overflow = checked]; + v11 = test.add v10, v3 : u32 #[overflow = checked]; + builtin.ret v11; +}; \ No newline at end of file diff --git a/dialects/scf/src/canonicalization/fold_constant_index_switch.rs b/dialects/scf/src/canonicalization/fold_constant_index_switch.rs new file mode 100644 index 000000000..261cf82ef --- /dev/null +++ b/dialects/scf/src/canonicalization/fold_constant_index_switch.rs @@ -0,0 +1,99 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// A canonicalization pattern for [IndexSwitch] that folds away the operation if it has a constant +/// selector value. +pub struct FoldConstantIndexSwitch { + info: PatternInfo, +} + +impl FoldConstantIndexSwitch { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let switch_op = scf_dialect + .registered_name::() + .expect("scf.index_switch is not registered"); + Self { + info: PatternInfo::new( + context, + "fold-constant-index-switch", + PatternKind::Operation(switch_op), + PatternBenefit::MAX, + ), + } + } +} + +impl FoldConstantIndexSwitch { + fn match_constant_selector(&self, op: OperationRef) -> Option { + use midenc_hir::matchers::{self, Matcher}; + + let op = op.borrow(); + if let Some(op) = op.downcast_ref::() { + let selector = op.selector().as_value_ref(); + selector + .borrow() + .get_defining_op() + .and_then(|defined_by| { + let matcher = matchers::constant_of::(); + matcher.matches(&*defined_by.borrow()) + }) + .and_then(|imm| imm.as_u32()) + } else { + None + } + } +} + +impl Pattern for FoldConstantIndexSwitch { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for FoldConstantIndexSwitch { + fn match_and_rewrite( + &self, + op: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let Some(selector) = self.match_constant_selector(op) else { + return Ok(false); + }; + let case_region = { + let switch_operation = op.borrow(); + let switch_op = switch_operation.downcast_ref::().unwrap(); + let case_index = switch_op.get_case_index_for_selector(selector); + case_index + .map(|idx| switch_op.get_case_region(idx)) + .unwrap_or_else(|| switch_op.default_region().as_region_ref()) + }; + + let source = case_region.borrow().entry_block_ref().expect("expected non-empty region"); + let terminator = + source.borrow().terminator().expect("expected region to have a terminator"); + let results = terminator + .borrow() + .operands() + .iter() + .copied() + .map(|o| Some(o.borrow().as_value_ref())) + .collect::; 2]>>(); + + let dest = op.parent().unwrap(); + rewriter.inline_block_before(source, dest, Some(op), &[]); + rewriter.erase_op(terminator); + // Replace the operation with a potentially empty list of results. + // + // The fold mechanism doesn't support the case where the result list is empty + rewriter.replace_op_with_values(op, &results); + + Ok(true) + } +} diff --git a/dialects/scf/src/canonicalization/fold_redundant_yields.rs b/dialects/scf/src/canonicalization/fold_redundant_yields.rs new file mode 100644 index 000000000..80ad825d2 --- /dev/null +++ b/dialects/scf/src/canonicalization/fold_redundant_yields.rs @@ -0,0 +1,964 @@ +use alloc::rc::Rc; +use core::{any::TypeId, ops::Index}; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Lift common yield results from their regions to their predecessors. +pub struct FoldRedundantYields { + info: PatternInfo, +} + +impl FoldRedundantYields { + pub fn new(context: Rc) -> Self { + Self { + info: PatternInfo::new( + context, + "fold-redundant-yields", + PatternKind::Trait(TypeId::of::()), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for FoldRedundantYields { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for FoldRedundantYields { + fn match_and_rewrite( + &self, + mut op_ref: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let mut op = op_ref.borrow_mut(); + + let Some(br_op) = op.as_trait::() else { + return Ok(false); + }; + + // For each successor gather its terminator and the terminator operands. + let mut term_ops: SmallVec<[_; 4]> = SmallVec::new(); + let mut region_yields: SmallVec<[_; 4]> = SmallVec::new(); + + for succ_region in br_op.get_successor_regions(RegionBranchPoint::Parent) { + let Some(region_ref) = succ_region.successor() else { + return Ok(false); + }; + + // Several assertions follow: an SCF region always has a single block, that block must + // have a terminator which then implements RegionBranchTerminatorOpInterface. + let region = region_ref.borrow(); + assert!(region.has_one_block()); + + let block = region.entry(); + let term_op_ref = + block.terminator().expect("All region blocks must have a terminator."); + + // Got the terminator. + let term_op = term_op_ref.borrow(); + let term_op = term_op.as_trait::().expect( + "All region block terminators must impl RegionBranchTerminatorOpInterface.", + ); + + // For now we only support regions with a simple `yield` terminator. This may change + // in the future if/when we support other RegionBranchOps (e.g., `while`). + if !term_op.as_operation().is::() { + return Ok(false); + } + + // Save the terminator and each of its opands paired with their indices. + term_ops.push(term_op_ref); + region_yields.push( + term_op + .get_successor_operands(RegionBranchPoint::Parent) + .forwarded() + .iter() + .map(|opand_ref| { + let opand = opand_ref.borrow(); + (opand.index(), opand.as_value_ref()) + }) + .collect::>(), + ); + } + + if region_yields.len() < 2 { + return Ok(false); + } + + // Fold the yield opand sets down via intersection to a final set which will contain the + // redundant values. + let redundant_yield_vals = region_yields + .into_iter() + .reduce(|acc, region_yield_vals| acc.intersection(®ion_yield_vals)) + .expect("Have already checked region_yields is non-empty."); + + if redundant_yield_vals.is_empty() { + // No redundant values found. + return Ok(false); + } + + // Save a copy of the redundant result positions. + let mut redundant_result_positions = + redundant_yield_vals.iter().map(|(pos, _)| *pos).collect::>(); + + let all_results_are_redundant = redundant_yield_vals.len() == op.num_results(); + + if all_results_are_redundant && op.is_memory_effect_free() { + // The entire operation is actually redundant; just remove it. Make sure the yield + // vals are sorted first. + let mut sorted_vals = redundant_yield_vals.into_vec(); + sorted_vals.sort_unstable_by(|a, b| a.0.cmp(&b.0)); + + // Wrap each of the values in Some. + let some_vals = + sorted_vals.into_iter().map(|(_, val)| Some(val)).collect::>(); + + // Replace the operation. + drop(op); + rewriter.replace_op_with_values(op_ref, &some_vals); + } else { + // Replace all uses of the redundant results. + for (redundant_opand_pos, redundant_yield_val) in redundant_yield_vals { + let result_val_ref = + op.results().index(redundant_opand_pos).borrow().as_value_ref(); + if result_val_ref.borrow().is_used() { + rewriter.replace_all_uses_of_value_with(result_val_ref, redundant_yield_val); + } + } + + // Next remove the redundant results. Iterate for each position in reverse to avoid + // invalidating offsets as we go. + redundant_result_positions.sort_unstable_by(|a, b| b.cmp(a)); + for idx in &redundant_result_positions { + op.results_mut().group_mut(0).erase(*idx); + } + + // And remove the redundant terminator operands. + for mut term_op in term_ops { + let mut new_opands = SmallVec::<[ValueRef; 4]>::default(); + + // Make a copy of the old operands, except for the redundant value, except in the + // case where they're all redundant where we need no operands. + if !all_results_are_redundant { + for old_opand in term_op.borrow().operands().iter() { + if !redundant_result_positions.contains(&old_opand.index()) { + new_opands.push(old_opand.borrow().as_value_ref()); + } + } + } + + // Update the terminator with the new operands. + let _guard = rewriter.modify_op_in_place(term_op); + let mut term_op_mut = term_op.borrow_mut(); + term_op_mut.set_operands(new_opands); + } + } + + Ok(true) + } +} + +#[cfg(test)] +mod tests { + use alloc::{boxed::Box, format, rc::Rc, sync::Arc, vec::Vec}; + + use midenc_dialect_cf::{ControlFlowOpBuilder, SwitchCase}; + use midenc_expect_test::expect_file; + use midenc_hir::{ + dialects::{ + builtin::{self, BuiltinOpBuilder, FunctionBuilder}, + test::TestOpBuilder, + }, + pass::{Pass, PassExecutionState}, + patterns::{ + FrozenRewritePatternSet, GreedyRewriteConfig, RewritePattern, RewritePatternSet, + }, + AbiParam, BuilderExt, Context, Ident, OpBuilder, Report, Signature, SourceSpan, Type, + }; + + use super::*; + + struct SingleCanonicalizerPass { + rewrites: Rc, + should_modify: bool, + } + + impl SingleCanonicalizerPass { + fn new( + context: Rc, + pattern: Box, + should_modify: bool, + ) -> Self { + let pattern_set = RewritePatternSet::from_iter(context, [pattern]); + let rewrites = Rc::new(FrozenRewritePatternSet::new(pattern_set)); + + Self { + rewrites, + should_modify, + } + } + } + + impl Pass for SingleCanonicalizerPass { + type Target = Operation; + + fn name(&self) -> &'static str { + "test-single-rewriter" + } + + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + let op_ref = op.as_operation_ref(); + drop(op); + + let converged = patterns::apply_patterns_and_fold_greedily( + op_ref, + self.rewrites.clone(), + GreedyRewriteConfig::default(), + ); + + let changed = match converged { + Ok(b) => b, + Err(e) => { + panic!("Pass returned error: {e}"); + } + }; + + match (changed, self.should_modify) { + (true, false) => panic!("Pass modified input unexpectedly."), + (false, true) => panic!("Pass did not modify input."), + _ => {} + } + + state.set_post_pass_status(changed.into()); + + Ok(()) + } + } + + fn run_single_canonicalizer( + context: Rc, + operation: OperationRef, + name: &'static str, + should_modify: bool, + ) -> Result<(), Report> { + // UNCOMMENT TO DUMP (SHOW DIFF OF) CF INPUT. + // let input = format!("{}", &operation.borrow()); + // expect_file!["non-existentent"].assert_eq(&input); + + // Run the CF->SCF pass first, then the canonicalisation pass. Need to register the SCF + // dialect to make sure the patterns are registered. + let _scf_dialect = context.get_or_register_dialect::(); + let mut pm = + pass::PassManager::on::(context.clone(), pass::Nesting::Implicit); + pm.add_pass(Box::new(transforms::LiftControlFlowToSCF)); + pm.run(operation)?; + + // Confirm the CF->SCF transformed IR is correct. + let input = format!("{}", operation.borrow()); + let before_file_path = format!("expected/{name}_before.hir"); + expect_file![before_file_path.as_str()].assert_eq(&input); + + pm.add_pass(Box::new(SingleCanonicalizerPass::new( + context.clone(), + Box::new(FoldRedundantYields::new(context.clone())), + should_modify, + ))); + pm.run(operation)?; + + // Confirm the canonicalised IR is correct. + let output = format!("{}", operation.borrow()); + let after_file_path = format!("expected/{name}_after.hir"); + expect_file![after_file_path.as_str()].assert_eq(&output); + + Ok(()) + } + + #[test] + fn fold_redundant_yields_subset_if_switch() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let if_then = builder.create_block(); + let if_else = builder.create_block(); + let switch_on_one_block = builder.create_block(); + let switch_on_two_block = builder.create_block(); + let switch_default_block = builder.create_block(); + let if_final = builder.create_block(); + + let if_sum_lhs = builder.append_block_param(if_final, Type::U32, span); + let if_sum_rhs = builder.append_block_param(if_final, Type::U32, span); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + let redundant_val = builder.u32(11, span)?; + + let zero = builder.u32(0, span)?; + let is_zero = builder.eq(input, zero, span)?; + builder.cond_br(is_zero, if_then, [], if_else, [], span)?; + + builder.switch_to_block(if_then); + let then_non_redundant_val = builder.u32(22, span)?; + builder.br(if_final, [redundant_val, then_non_redundant_val], span)?; + + let switch_on_one_case = SwitchCase { + value: 1, + successor: switch_on_one_block, + arguments: Vec::default(), + }; + + let switch_on_two_case = SwitchCase { + value: 2, + successor: switch_on_two_block, + arguments: Vec::default(), + }; + + builder.switch_to_block(if_else); + builder.switch( + input, + [switch_on_one_case, switch_on_two_case], + switch_default_block, + Vec::default(), + span, + )?; + + builder.switch_to_block(switch_on_one_block); + let switch_on_one_non_redundant_val = builder.u32(33, span)?; + builder.br(if_final, [redundant_val, switch_on_one_non_redundant_val], span)?; + + builder.switch_to_block(switch_on_two_block); + let switch_on_two_non_redundant_val = builder.u32(44, span)?; + builder.br(if_final, [redundant_val, switch_on_two_non_redundant_val], span)?; + + builder.switch_to_block(switch_default_block); + let switch_default_non_redundant_val = builder.u32(55, span)?; + builder.br(if_final, [redundant_val, switch_default_non_redundant_val], span)?; + + // Add all the results together along with the redundant value to give them all users. + builder.switch_to_block(if_final); + let if_sum0 = builder.add(if_sum_lhs, if_sum_rhs, span)?; + let if_sum1 = builder.add(if_sum0, redundant_val, span)?; + builder.ret(Some(if_sum1), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_subset_if_switch", + true, + ) + } + + #[test] + fn fold_redundant_yields_all_if_switch() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let switch_on_one_block = builder.create_block(); + let if_then_block = builder.create_block(); + let if_else_block = builder.create_block(); + let switch_on_two_block = builder.create_block(); + let switch_default_block = builder.create_block(); + let switch_final_block = builder.create_block(); + + let sum_lhs = builder.append_block_param(switch_final_block, Type::U32, span); + let sum_rhs = builder.append_block_param(switch_final_block, Type::U32, span); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + let redundant_val0 = builder.u32(11, span)?; + let redundant_val1 = builder.u32(22, span)?; + + let switch_on_one_case = SwitchCase { + value: 1, + successor: switch_on_one_block, + arguments: Vec::default(), + }; + + let switch_on_two_case = SwitchCase { + value: 2, + successor: switch_on_two_block, + arguments: Vec::default(), + }; + + builder.switch( + input, + [switch_on_one_case, switch_on_two_case], + switch_default_block, + Vec::default(), + span, + )?; + + builder.switch_to_block(switch_on_one_block); + builder.br(switch_final_block, [redundant_val0, redundant_val1], span)?; + + builder.switch_to_block(switch_on_two_block); + let zero = builder.u32(0, span)?; + let is_zero = builder.eq(input, zero, span)?; + builder.cond_br(is_zero, if_then_block, [], if_else_block, [], span)?; + + builder.switch_to_block(if_then_block); + builder.br(switch_final_block, [redundant_val0, redundant_val1], span)?; + + builder.switch_to_block(if_else_block); + builder.br(switch_final_block, [redundant_val0, redundant_val1], span)?; + + builder.switch_to_block(switch_default_block); + builder.br(switch_final_block, [redundant_val0, redundant_val1], span)?; + + // Add all the results together along with the redundant value to give them all users. + builder.switch_to_block(switch_final_block); + let sum = builder.add(sum_lhs, sum_rhs, span)?; + builder.ret(Some(sum), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_all_if_switch", + true, + ) + } + + #[test] + fn fold_redundant_yields_all_switch_if() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let if_then_block = builder.create_block(); + let switch_on_one_block = builder.create_block(); + let switch_on_two_block = builder.create_block(); + let switch_default_block = builder.create_block(); + let if_else_block = builder.create_block(); + let if_final_block = builder.create_block(); + + let sum_lhs = builder.append_block_param(if_final_block, Type::U32, span); + let sum_rhs = builder.append_block_param(if_final_block, Type::U32, span); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + let redundant_val0 = builder.u32(11, span)?; + let redundant_val1 = builder.u32(22, span)?; + + let zero = builder.u32(0, span)?; + let is_not_zero = builder.neq(input, zero, span)?; + builder.cond_br(is_not_zero, if_then_block, [], if_else_block, [], span)?; + + let switch_on_one_case = SwitchCase { + value: 1, + successor: switch_on_one_block, + arguments: Vec::default(), + }; + + let switch_on_two_case = SwitchCase { + value: 2, + successor: switch_on_two_block, + arguments: Vec::default(), + }; + + builder.switch_to_block(if_then_block); + builder.switch( + input, + [switch_on_one_case, switch_on_two_case], + switch_default_block, + Vec::default(), + span, + )?; + + builder.switch_to_block(switch_on_one_block); + builder.br(if_final_block, [redundant_val0, redundant_val1], span)?; + + builder.switch_to_block(switch_on_two_block); + builder.br(if_final_block, [redundant_val0, redundant_val1], span)?; + + builder.switch_to_block(switch_default_block); + builder.br(if_final_block, [redundant_val0, redundant_val1], span)?; + + builder.switch_to_block(if_else_block); + builder.br(if_final_block, [redundant_val0, redundant_val1], span)?; + + builder.switch_to_block(if_final_block); + let sum = builder.add(sum_lhs, sum_rhs, span)?; + builder.ret(Some(sum), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_all_switch_if", + true, + ) + } + + #[test] + fn fold_redundant_yields_many_switch() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let switch_on_one_block = builder.create_block(); + let switch_on_two_block = builder.create_block(); + let switch_on_three_block = builder.create_block(); + let switch_on_default_block = builder.create_block(); + let switch_final_block = builder.create_block(); + let exit_block = builder.create_block(); + + let final_arg0 = builder.append_block_param(switch_final_block, Type::U32, span); + let final_arg1 = builder.append_block_param(switch_final_block, Type::U32, span); + let final_arg2 = builder.append_block_param(switch_final_block, Type::U32, span); + let final_arg3 = builder.append_block_param(switch_final_block, Type::U32, span); + let final_arg4 = builder.append_block_param(switch_final_block, Type::U32, span); + + let ret_val = builder.append_block_param(exit_block, Type::U32, span); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + + let redundant_val11 = builder.u32(11, span)?; + let redundant_val22 = builder.u32(22, span)?; + let redundant_val33 = builder.u32(33, span)?; + + let switch_on_one_case = SwitchCase { + value: 1, + successor: switch_on_one_block, + arguments: Vec::default(), + }; + + let switch_on_two_case = SwitchCase { + value: 2, + successor: switch_on_two_block, + arguments: Vec::default(), + }; + + let switch_on_three_case = SwitchCase { + value: 3, + successor: switch_on_three_block, + arguments: Vec::default(), + }; + + builder.switch( + input, + [switch_on_one_case, switch_on_two_case, switch_on_three_case], + switch_on_default_block, + Vec::default(), + span, + )?; + + builder.switch_to_block(switch_on_one_block); + let switch_on_one_non_redundant_val0 = builder.u32(100, span)?; + let switch_on_one_non_redundant_val1 = builder.u32(101, span)?; + builder.br( + switch_final_block, + [ + redundant_val11, + redundant_val22, + switch_on_one_non_redundant_val0, + redundant_val33, + switch_on_one_non_redundant_val1, + ], + span, + )?; + + builder.switch_to_block(switch_on_two_block); + let switch_on_two_non_redundant_val0 = builder.u32(200, span)?; + let switch_on_two_non_redundant_val1 = builder.u32(201, span)?; + builder.br( + switch_final_block, + [ + redundant_val11, + redundant_val22, + switch_on_two_non_redundant_val0, + redundant_val33, + switch_on_two_non_redundant_val1, + ], + span, + )?; + + builder.switch_to_block(switch_on_three_block); + let switch_on_three_non_redundant_val0 = builder.u32(300, span)?; + let switch_on_three_non_redundant_val1 = builder.u32(301, span)?; + builder.br( + switch_final_block, + [ + redundant_val11, + redundant_val22, + switch_on_three_non_redundant_val0, + redundant_val33, + switch_on_three_non_redundant_val1, + ], + span, + )?; + + builder.switch_to_block(switch_on_default_block); + let switch_on_default_non_redundant_val0 = builder.u32(400, span)?; + let switch_on_default_non_redundant_val1 = builder.u32(401, span)?; + builder.br( + switch_final_block, + [ + redundant_val11, + redundant_val22, + switch_on_default_non_redundant_val0, + redundant_val33, + switch_on_default_non_redundant_val1, + ], + span, + )?; + + // Add all the results together. + builder.switch_to_block(switch_final_block); + let sum0 = builder.add(final_arg0, final_arg1, span)?; + let sum1 = builder.add(sum0, final_arg2, span)?; + let sum2 = builder.add(sum1, final_arg3, span)?; + let sum3 = builder.add(sum2, final_arg4, span)?; + builder.br(exit_block, [sum3], span)?; + + builder.switch_to_block(exit_block); + builder.ret(Some(ret_val), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_many_switch", + true, + ) + } + + #[test] + fn fold_redundant_yields_different_pos_switch() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let switch_on_one_block = builder.create_block(); + let switch_on_two_block = builder.create_block(); + let switch_on_default_block = builder.create_block(); + let switch_final_block = builder.create_block(); + + let final_arg0 = builder.append_block_param(switch_final_block, Type::U32, span); + let final_arg1 = builder.append_block_param(switch_final_block, Type::U32, span); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + + let redundant_val11 = builder.u32(11, span)?; + let redundant_val22 = builder.u32(22, span)?; + + let switch_on_one_case = SwitchCase { + value: 1, + successor: switch_on_one_block, + arguments: Vec::default(), + }; + + let switch_on_two_case = SwitchCase { + value: 2, + successor: switch_on_two_block, + arguments: Vec::default(), + }; + + builder.switch( + input, + [switch_on_one_case, switch_on_two_case], + switch_on_default_block, + Vec::default(), + span, + )?; + + builder.switch_to_block(switch_on_one_block); + builder.br(switch_final_block, [redundant_val11, redundant_val22], span)?; + + // 'yielding' args in reverse order. + builder.switch_to_block(switch_on_two_block); + builder.br(switch_final_block, [redundant_val22, redundant_val11], span)?; + + builder.switch_to_block(switch_on_default_block); + builder.br(switch_final_block, [redundant_val11, redundant_val22], span)?; + + // Add all the results together. + builder.switch_to_block(switch_final_block); + let sum = builder.add(final_arg0, final_arg1, span)?; + builder.ret(Some(sum), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_different_pos_switch", + false, // Should not modify input. + ) + } + + #[test] + fn fold_redundant_yields_all_but_one_switch() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let switch_on_one_block = builder.create_block(); + let switch_on_two_block = builder.create_block(); + let switch_on_three_block = builder.create_block(); + let switch_on_default_block = builder.create_block(); + let switch_final_block = builder.create_block(); + let exit_block = builder.create_block(); + + let final_arg0 = builder.append_block_param(switch_final_block, Type::U32, span); + let final_arg1 = builder.append_block_param(switch_final_block, Type::U32, span); + let final_arg2 = builder.append_block_param(switch_final_block, Type::U32, span); + + let ret_val = builder.append_block_param(exit_block, Type::U32, span); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + + let redundant_val11 = builder.u32(11, span)?; + let redundant_val22 = builder.u32(22, span)?; + let redundant_val33 = builder.u32(33, span)?; + + let switch_on_one_case = SwitchCase { + value: 1, + successor: switch_on_one_block, + arguments: Vec::default(), + }; + + let switch_on_two_case = SwitchCase { + value: 2, + successor: switch_on_two_block, + arguments: Vec::default(), + }; + + let switch_on_three_case = SwitchCase { + value: 3, + successor: switch_on_three_block, + arguments: Vec::default(), + }; + + builder.switch( + input, + [switch_on_one_case, switch_on_two_case, switch_on_three_case], + switch_on_default_block, + Vec::default(), + span, + )?; + + builder.switch_to_block(switch_on_one_block); + builder.br( + switch_final_block, + [redundant_val11, redundant_val22, redundant_val33], + span, + )?; + + builder.switch_to_block(switch_on_two_block); + builder.br( + switch_final_block, + [redundant_val11, redundant_val22, redundant_val33], + span, + )?; + + builder.switch_to_block(switch_on_three_block); + builder.br( + switch_final_block, + [redundant_val11, redundant_val22, redundant_val33], + span, + )?; + + builder.switch_to_block(switch_on_default_block); + let non_redundant_val44 = builder.u32(44, span)?; + builder.br( + switch_final_block, + [redundant_val11, non_redundant_val44, redundant_val33], + span, + )?; + + // Add all the results together. + builder.switch_to_block(switch_final_block); + let sum0 = builder.add(final_arg0, final_arg1, span)?; + let sum1 = builder.add(sum0, final_arg2, span)?; + builder.br(exit_block, [sum1], span)?; + + builder.switch_to_block(exit_block); + builder.ret(Some(ret_val), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_all_but_one_switch", + true, + ) + } + + #[test] + fn fold_redundant_yields_effects_if() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new( + [ + AbiParam::new(Type::U32), + AbiParam::new(Type::Ptr(Arc::new(PointerType::new(Type::U32)))), + ], + [AbiParam::new(Type::U32)], + ); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let if_then = builder.create_block(); + let if_else = builder.create_block(); + let if_final = builder.create_block(); + + let ret_val = builder.append_block_param(if_final, Type::U32, span); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + let input_ptr = builder.current_block().borrow().arguments()[1].upcast(); + let redundant_val = builder.u32(11, span)?; + + let zero = builder.u32(0, span)?; + let is_zero = builder.eq(input, zero, span)?; + builder.cond_br(is_zero, if_then, [], if_else, [], span)?; + + builder.switch_to_block(if_then); + builder.br(if_final, [redundant_val], span)?; + + builder.switch_to_block(if_else); + builder.store(input_ptr, redundant_val, span)?; + builder.br(if_final, [redundant_val], span)?; + + // Add all the results together along with the redundant value to give them all users. + builder.switch_to_block(if_final); + builder.ret(Some(ret_val), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_effects_if", + true, + ) + } + + /* A `while` test which initially showed that this rewriter probably isn't suited for them. {{{ + + #[test] + fn fold_redundant_yields_subset_while() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + let mut builder = FunctionBuilder::new(function, &mut builder); + + let input = builder.current_block().borrow().arguments()[0].upcast(); + let redundant_val = builder.u32(11, span)?; + + // Create a while op, pass zero. + let zero = builder.u32(0, span)?; + let while_op = builder.r#while([zero], &[Type::U32], span)?; + let while_before_block = while_op.borrow().before().entry().as_block_ref(); + let while_after_block = while_op.borrow().after().entry().as_block_ref(); + + // Check the passed arg against the function arg. + builder.switch_to_block(while_before_block); + let while_cond_arg = while_before_block.borrow().arguments()[0].upcast(); + let finished = builder.eq(while_cond_arg, input, span)?; + let next_iter = builder.incr(while_cond_arg, span)?; + builder.condition(finished, [next_iter], span)?; + + // Just yield the final results from the while op. + builder.switch_to_block(while_after_block); + let yield_values = while_after_block + .borrow() + .arguments() + .iter() + .map(|a| a.borrow().as_value_ref()) + .collect::>(); + builder.r#yield(yield_values, span)?; + + // Add all the results together along with the redundant value to give them all users. + builder.switch_to_block(builder.entry_block()); + let summed_while_results = + while_op.borrow().results().iter().try_fold(zero, |acc, res| { + let res_val_ref = res.borrow().as_value_ref(); + builder.add(acc, res_val_ref, span) + })?; + let final_sum = builder.add(summed_while_results, redundant_val, span)?; + builder.ret(Some(final_sum), span)?; + + run_single_canonicalizer( + context, + function.as_operation_ref(), + "fold_redundant_yields_subset_while", + ) + } + }}} */ +} diff --git a/dialects/scf/src/canonicalization/if_remove_unused_results.rs b/dialects/scf/src/canonicalization/if_remove_unused_results.rs new file mode 100644 index 000000000..8bab5c695 --- /dev/null +++ b/dialects/scf/src/canonicalization/if_remove_unused_results.rs @@ -0,0 +1,134 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Removed unused results of an [If] instruction +pub struct IfRemoveUnusedResults { + info: PatternInfo, +} + +impl IfRemoveUnusedResults { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let if_op = scf_dialect.registered_name::().expect("scf.if is not registered"); + Self { + info: PatternInfo::new( + context, + "if-remove-unused-results", + PatternKind::Operation(if_op), + PatternBenefit::MAX, + ), + } + } + + fn transfer_body( + &self, + src: BlockRef, + dest: BlockRef, + used_results: &[OpResultRef], + rewriter: &mut dyn Rewriter, + ) { + // Move all operations to the destination block + rewriter.merge_blocks(src, dest, &[]); + + // Replace the yield op with one that returns only the used values. + let yield_op = dest.borrow().terminator().unwrap(); + let mut yield_op = unsafe { + UnsafeIntrusiveEntityRef::from_raw(yield_op.borrow().downcast_ref::().unwrap()) + }; + + let yield_ = yield_op.borrow(); + let mut used_operands = SmallVec::<[ValueRef; 4]>::with_capacity(used_results.len()); + for used_result in used_results { + let value = yield_.operands()[used_result.borrow().index()].borrow().as_value_ref(); + used_operands.push(value); + } + + let yield_operation = yield_op.as_operation_ref(); + let _guard = rewriter.modify_op_in_place(yield_operation); + let mut yield_op = yield_op.borrow_mut(); + let context = yield_op.as_operation().context_rc(); + yield_op.yielded_mut().set_operands(used_operands, yield_operation, &context); + } +} + +impl Pattern for IfRemoveUnusedResults { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for IfRemoveUnusedResults { + fn match_and_rewrite( + &self, + mut operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + // Compute the list of used results. + let used_results = operation + .borrow() + .results() + .iter() + .copied() + .filter(|result| result.borrow().is_used()) + .collect::>(); + + // Replace the operation if only a subset of its results have uses. + let num_results = operation.borrow().num_results(); + if used_results.len() == num_results { + return Ok(false); + } + + let mut op = operation.borrow_mut(); + let Some(if_op) = op.downcast_mut::() else { + return Ok(false); + }; + + // Compute the result types of the replacement operation. + let new_types = used_results + .iter() + .map(|result| result.borrow().ty().clone()) + .collect::>(); + + // Create a replacement operation with empty then and else regions. + let new_if = rewriter.r#if(if_op.condition().as_value_ref(), &new_types, if_op.span())?; + let new_if_op = new_if.borrow(); + + let new_then_block = + rewriter.create_block(new_if_op.then_body().as_region_ref(), None, &[]); + let new_else_block = + rewriter.create_block(new_if_op.else_body().as_region_ref(), None, &[]); + + // Move the bodies and replace the terminators (note there is a then and an else region + // since the operation returns results). + self.transfer_body( + if_op.then_body().entry_block_ref().unwrap(), + new_then_block, + &used_results, + rewriter, + ); + self.transfer_body( + if_op.else_body().entry_block_ref().unwrap(), + new_else_block, + &used_results, + rewriter, + ); + drop(op); + + // Replace the operation by the new one. + let mut replaced_results = SmallVec::<[_; 4]>::with_capacity(num_results); + replaced_results.resize(num_results, None); + for (index, result) in used_results.into_iter().enumerate() { + replaced_results[result.borrow().index()] = + Some(new_if_op.results()[index] as ValueRef); + } + rewriter.replace_op_with_values(operation, &replaced_results); + + Ok(true) + } +} diff --git a/dialects/scf/src/canonicalization/remove_loop_invariant_args_from_before_block.rs b/dialects/scf/src/canonicalization/remove_loop_invariant_args_from_before_block.rs new file mode 100644 index 000000000..6ed30d587 --- /dev/null +++ b/dialects/scf/src/canonicalization/remove_loop_invariant_args_from_before_block.rs @@ -0,0 +1,246 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Remove loop invariant arguments from `before` block of a [While] operation. +/// +/// A before block argument is considered loop invariant if: +/// +/// 1. i-th yield operand is equal to the i-th while operand. +/// 2. i-th yield operand is k-th after block argument which is (k+1)-th condition operand AND this +/// (k+1)-th condition operand is equal to i-th iter argument/while operand. +/// +/// For the arguments which are removed, their uses inside [While] are replaced with their +/// corresponding initial value. +/// +/// # Example +/// +/// INPUT: +/// +/// ```text,ignore +/// res = scf.while <...> iter_args(%arg0_before = %a, %arg1_before = %b, ..., %argN_before = %N) +/// { +/// ... +/// scf.condition(%cond) %arg1_before, %arg0_before, +/// %arg2_before, %arg0_before, ... +/// } do { +/// ^bb0(%arg1_after, %arg0_after_1, %arg2_after, %arg0_after_2, +/// ..., %argK_after): +/// ... +/// scf.yield %arg0_after_2, %b, %arg1_after, ..., %argN +/// } +/// ``` +/// +/// OUTPUT: +/// +/// ```text,ignore +/// res = scf.while <...> iter_args(%arg2_before = %c, ..., %argN_before = %N) +/// { +/// ... +/// scf.condition(%cond) %b, %a, %arg2_before, %a, ... +/// } do { +/// ^bb0(%arg1_after, %arg0_after_1, %arg2_after, %arg0_after_2, +/// ..., %argK_after): +/// ... +/// scf.yield %arg1_after, ..., %argN +/// } +/// ``` +/// +/// EXPLANATION: +/// +/// We iterate over each yield operand. +/// +/// 1. 0-th yield operand %arg0_after_2 is 4-th condition operand %arg0_before, which in turn is the +/// 0-th iter argument. So we remove 0-th before block argument and yield operand, and replace +/// all uses of the 0-th before block argument with its initial value %a. +/// 2. 1-th yield operand %b is equal to the 1-th iter arg's initial value. So we remove this +/// operand and the corresponding before block argument and replace all uses of 1-th before block +/// argument +/// +pub struct RemoveLoopInvariantArgsFromBeforeBlock { + info: PatternInfo, +} + +impl RemoveLoopInvariantArgsFromBeforeBlock { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let while_op = scf_dialect.registered_name::().expect("scf.while is not registered"); + Self { + info: PatternInfo::new( + context, + "remove-loop-invariant-args-from-before-block", + PatternKind::Operation(while_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for RemoveLoopInvariantArgsFromBeforeBlock { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for RemoveLoopInvariantArgsFromBeforeBlock { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(while_op) = op.downcast_ref::() else { + return Ok(false); + }; + + let before_block = while_op.before().entry_block_ref().unwrap(); + let before_args = before_block + .borrow() + .arguments() + .iter() + .map(|arg| arg.borrow().as_value_ref()) + .collect::>(); + let cond_op = while_op.condition_op(); + let cond_op_args = cond_op + .borrow() + .forwarded() + .into_iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + let yield_op = while_op.yield_op(); + let yield_op_args = yield_op + .borrow() + .yielded() + .into_iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>(); + + let mut can_simplify = false; + for (index, (init_value, yield_arg)) in while_op + .inits() + .into_iter() + .map(|o| o.borrow().as_value_ref()) + .zip(yield_op_args.iter().copied()) + .enumerate() + { + // If i-th yield operand is equal to the i-th operand of the `scf.while`, the i-th + // before block argument is loop invariant + if yield_arg == init_value { + can_simplify = true; + break; + } + + // If the i-th yield operand is k-th after block argument, then we check if the (k+1)-th + // condition op operand is equal to either the i-th before block argument or the initial + // value of i-th before block argument. If the comparison results `true`, i-th before + // block argument is loop invariant. + if let Ok(yield_op_block_arg) = yield_arg.try_downcast::() { + let cond_op_arg = cond_op_args[yield_op_block_arg.borrow().index()]; + if cond_op_arg == before_args[index] || cond_op_arg == init_value { + can_simplify = true; + break; + } + } + } + + if !can_simplify { + return Ok(false); + } + + let mut new_init_args = SmallVec::<[ValueRef; 4]>::default(); + let mut new_yield_args = SmallVec::<[ValueRef; 4]>::default(); + let mut before_block_init_val_map = SmallVec::<[Option; 8]>::default(); + before_block_init_val_map.resize(yield_op_args.len(), None); + for (index, (init_value, yield_arg)) in while_op + .inits() + .into_iter() + .map(|o| o.borrow().as_value_ref()) + .zip(yield_op_args.iter().copied()) + .enumerate() + { + if yield_arg == init_value { + before_block_init_val_map[index] = Some(init_value); + continue; + } + + if let Ok(yield_op_block_arg) = yield_arg.try_downcast::() { + let cond_op_arg = cond_op_args[yield_op_block_arg.borrow().index()]; + if cond_op_arg == before_args[index] || cond_op_arg == init_value { + before_block_init_val_map[index] = Some(init_value); + continue; + } + } + + new_init_args.push(init_value); + new_yield_args.push(yield_arg); + } + + { + let mut guard = InsertionGuard::new(rewriter); + let yield_op = yield_op.as_operation_ref(); + guard.set_insertion_point_before(yield_op); + let new_yield = guard.r#yield(new_yield_args.iter().copied(), yield_op.span())?; + guard.replace_op(yield_op, new_yield.as_operation_ref()); + } + + let mut result_types = while_op + .results() + .iter() + .map(|r| r.borrow().ty().clone()) + .collect::>(); + let new_while = + rewriter.r#while(new_init_args.iter().copied(), &result_types, while_op.span())?; + + let new_before_region = new_while.borrow().before().as_region_ref(); + result_types.clear(); + result_types.extend(new_yield_args.iter().map(|arg| arg.borrow().ty().clone())); + let new_before_block = rewriter.create_block(new_before_region, None, &result_types); + let num_before_block_args = before_block.borrow().num_arguments(); + let mut new_before_block_args = SmallVec::<[_; 4]>::with_capacity(num_before_block_args); + new_before_block_args.resize(num_before_block_args, None); + // For each i-th before block argument we find it's replacement value as: + // + // 1. If i-th before block argument is a loop invariant, we fetch it's initial value from + // `before_block_init_val_map` by querying for key `i`. + // 2. Else we fetch j-th new before block argument as the replacement value of i-th before + // block argument. + { + let mut next_new_before_block_argument = 0; + let new_before_block = new_before_block.borrow(); + for i in 0..num_before_block_args { + // If the index 'i' argument was a loop invariant we fetch it's initial value from + // `before_block_init_val_map`. + if let Some(val) = before_block_init_val_map[i] { + new_before_block_args[i] = Some(val); + } else { + new_before_block_args[i] = Some( + new_before_block.arguments()[next_new_before_block_argument] as ValueRef, + ); + next_new_before_block_argument += 1; + } + } + } + + let after_region = while_op.after().as_region_ref(); + drop(op); + + rewriter.merge_blocks(before_block, new_before_block, &new_before_block_args); + rewriter.inline_region_before(after_region, new_while.borrow().after().as_region_ref()); + + let replacements = new_while + .borrow() + .results() + .all() + .into_iter() + .map(|r| Some(*r as ValueRef)) + .collect::>(); + rewriter.replace_op_with_values(operation, &replacements); + + Ok(true) + } +} diff --git a/docs/assets/.gitkeep b/dialects/scf/src/canonicalization/remove_loop_invariant_value_yielded.rs similarity index 100% rename from docs/assets/.gitkeep rename to dialects/scf/src/canonicalization/remove_loop_invariant_value_yielded.rs diff --git a/dialects/scf/src/canonicalization/while_condition_truth.rs b/dialects/scf/src/canonicalization/while_condition_truth.rs new file mode 100644 index 000000000..0bbd9e945 --- /dev/null +++ b/dialects/scf/src/canonicalization/while_condition_truth.rs @@ -0,0 +1,101 @@ +use alloc::rc::Rc; + +use midenc_dialect_arith::ArithOpBuilder; +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Replace uses of the condition of a [While] operation within its do block with true, since +/// otherwise the block would not be evaluated. +/// +/// Before: +/// +/// ```text,ignore +/// scf.while (..) : (i1, ...) -> ... { +/// %condition = call @evaluate_condition() : () -> i1 +/// scf.condition(%condition) %condition : i1, ... +/// } do { +/// ^bb0(%arg0: i1, ...): +/// use(%arg0) +/// ... +/// ``` +/// +/// After: +/// +/// ```text,ignore +/// scf.while (..) : (i1, ...) -> ... { +/// %condition = call @evaluate_condition() : () -> i1 +/// scf.condition(%condition) %condition : i1, ... +/// } do { +/// ^bb0(%arg0: i1, ...): +/// use(%true) +/// ... +/// ``` +pub struct WhileConditionTruth { + info: PatternInfo, +} + +impl WhileConditionTruth { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let while_op = scf_dialect.registered_name::().expect("scf.while is not registered"); + Self { + info: PatternInfo::new( + context, + "while-condition-truth", + PatternKind::Operation(while_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for WhileConditionTruth { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for WhileConditionTruth { + fn match_and_rewrite( + &self, + op: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = op.borrow(); + let Some(while_op) = op.downcast_ref::() else { + return Ok(false); + }; + + let condition_operation = while_op.condition_op(); + + // These variables serve to prevent creating duplicate constants and hold constant true or + // false values + let mut constant_true = None; + + let mut replaced = false; + + let span = while_op.span(); + let condition_op = condition_operation.borrow(); + let condition = condition_op.condition().as_value_ref(); + + let forwarded = condition_op.forwarded(); + let after_region = while_op.after(); + let after_block = after_region.entry(); + for (yielded, block_arg) in forwarded.iter().zip(after_block.arguments()) { + let yielded = yielded.borrow().as_value_ref(); + if yielded == condition && block_arg.borrow().is_used() { + let constant = *constant_true.get_or_insert_with(|| rewriter.i1(true, span)); + + rewriter + .replace_all_uses_of_value_with(block_arg.borrow().as_value_ref(), constant); + replaced = true; + } + } + + Ok(replaced) + } +} diff --git a/dialects/scf/src/canonicalization/while_remove_duplicated_results.rs b/dialects/scf/src/canonicalization/while_remove_duplicated_results.rs new file mode 100644 index 000000000..38ea0c210 --- /dev/null +++ b/dialects/scf/src/canonicalization/while_remove_duplicated_results.rs @@ -0,0 +1,127 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + adt::{SmallDenseMap, SmallSet}, + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Remove duplicated [crate::ops::Condition] args in a [While] loop. +pub struct WhileRemoveDuplicatedResults { + info: PatternInfo, +} + +impl WhileRemoveDuplicatedResults { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let while_op = scf_dialect.registered_name::().expect("scf.while is not registered"); + Self { + info: PatternInfo::new( + context, + "while-remove-duplicated-results", + PatternKind::Operation(while_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for WhileRemoveDuplicatedResults { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for WhileRemoveDuplicatedResults { + fn match_and_rewrite( + &self, + operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let op = operation.borrow(); + let Some(while_op) = op.downcast_ref::() else { + return Ok(false); + }; + + let cond_op = while_op.condition_op(); + let cond_op_args = cond_op + .borrow() + .forwarded() + .iter() + .map(|v| v.borrow().as_value_ref()) + .collect::>(); + + let mut args_set = SmallSet::::default(); + for arg in cond_op_args.iter().copied() { + args_set.insert(arg); + } + + if args_set.len() == cond_op_args.len() { + // No results to remove + return Ok(false); + } + + let mut args_map = SmallDenseMap::<_, _, 4>::with_capacity(cond_op_args.len()); + let mut new_args = SmallVec::<[ValueRef; 4]>::with_capacity(cond_op_args.len()); + + for arg in cond_op_args.iter().copied() { + if !args_map.contains_key(&arg) { + args_map.insert(arg, args_map.len()); + new_args.push(arg); + } + } + + let span = op.span(); + let results = new_args + .iter() + .map(|arg| arg.borrow().ty().clone()) + .collect::>(); + let new_while_op = rewriter.r#while( + while_op.inits().into_iter().map(|o| o.borrow().as_value_ref()), + &results, + span, + )?; + + let new_while = new_while_op.borrow(); + let new_before_block = new_while.before().entry().as_block_ref(); + let new_after_block = new_while.after().entry().as_block_ref(); + let before_block = while_op.before().entry().as_block_ref(); + let after_block = while_op.after().entry().as_block_ref(); + drop(op); + + let mut after_args_mapping = SmallVec::<[_; 4]>::default(); + let mut results_mapping = SmallVec::<[_; 4]>::default(); + for arg in cond_op_args.iter() { + let pos = args_map.get(arg).copied().unwrap(); + after_args_mapping + .push(Some(new_after_block.borrow().get_argument(pos).borrow().as_value_ref())); + results_mapping.push(Some(new_while.results()[pos].borrow().as_value_ref())); + } + + let mut guard = InsertionGuard::new(rewriter); + guard.set_insertion_point_before(cond_op.as_operation_ref()); + + let new_cond_op = guard.condition( + cond_op.borrow().condition().as_value_ref(), + new_args.iter().copied(), + span, + )?; + let new_cond_op = new_cond_op.as_operation_ref(); + let cond_op = cond_op.as_operation_ref(); + guard.replace_op(cond_op, new_cond_op); + + let new_before_block_args = new_before_block + .borrow() + .arguments() + .iter() + .map(|v| Some(v.borrow().as_value_ref())) + .collect::>(); + guard.merge_blocks(before_block, new_before_block, &new_before_block_args); + guard.merge_blocks(after_block, new_after_block, &after_args_mapping); + guard.replace_op_with_values(operation, &results_mapping); + + Ok(true) + } +} diff --git a/dialects/scf/src/canonicalization/while_remove_unused_args.rs b/dialects/scf/src/canonicalization/while_remove_unused_args.rs new file mode 100644 index 000000000..52da21c80 --- /dev/null +++ b/dialects/scf/src/canonicalization/while_remove_unused_args.rs @@ -0,0 +1,134 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Remove unused init/yield args of a [While] loop. +pub struct WhileRemoveUnusedArgs { + info: PatternInfo, +} + +impl WhileRemoveUnusedArgs { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let while_op = scf_dialect.registered_name::().expect("scf.while is not registered"); + Self { + info: PatternInfo::new( + context, + "while-remove-unused-args", + PatternKind::Operation(while_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for WhileRemoveUnusedArgs { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for WhileRemoveUnusedArgs { + fn match_and_rewrite( + &self, + mut operation: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + use bitvec::prelude::{BitVec, Lsb0}; + + let mut op = operation.borrow_mut(); + let Some(while_op) = op.downcast_mut::() else { + return Ok(false); + }; + + if while_op.before().entry().arguments().iter().all(|arg| arg.borrow().is_used()) { + // All the arguments are used (nothing to remove) + return Ok(false); + } + + // Collect results mapping, new terminator args, and new result types + let yield_op = while_op.yield_op(); + let mut before_block = while_op.before().entry().as_block_ref(); + let after_block = while_op.after().entry().as_block_ref(); + let argc = while_op.before().entry().num_arguments(); + let mut new_yields = SmallVec::<[ValueRef; 4]>::with_capacity(argc); + let mut new_inits = SmallVec::<[ValueRef; 4]>::with_capacity(argc); + let mut args_to_erase = BitVec::::new(); + + { + let yield_op = yield_op.borrow(); + let before_entry = before_block.borrow(); + for (i, before_arg) in before_entry.arguments().iter().enumerate() { + let before_arg = before_arg.borrow(); + let yield_value = yield_op.yielded()[i]; + let init_value = while_op.inits()[i]; + if before_arg.is_used() { + args_to_erase.push(false); + new_yields.push(yield_value.borrow().as_value_ref()); + new_inits.push(init_value.borrow().as_value_ref()); + } else { + args_to_erase.push(true); + } + } + } + let yield_op = yield_op.as_operation_ref(); + + before_block + .borrow_mut() + .erase_arguments(|arg| *args_to_erase.get(arg.index()).unwrap()); + + let span = while_op.span(); + let new_while_op = { + let results = while_op + .results() + .all() + .iter() + .map(|r| r.borrow().ty().clone()) + .collect::>(); + drop(op); + rewriter.r#while(new_inits.iter().copied(), &results, span)? + }; + + let new_while = new_while_op.borrow(); + let new_before_block = { new_while.before().entry().as_block_ref() }; + let new_after_block = { new_while.after().entry().as_block_ref() }; + + let mut guard = InsertionGuard::new(rewriter); + guard.set_insertion_point_before(yield_op); + let new_yield_op = guard.r#yield(new_yields, yield_op.span())?; + let new_yield_op = new_yield_op.as_operation_ref(); + guard.replace_op(yield_op, new_yield_op); + + let new_before_args = new_before_block + .borrow() + .arguments() + .iter() + .map(|arg| Some(arg.borrow().as_value_ref())) + .collect::>(); + guard.merge_blocks(before_block, new_before_block, &new_before_args); + + let new_after_args = new_after_block + .borrow() + .arguments() + .iter() + .map(|arg| Some(arg.borrow().as_value_ref())) + .collect::>(); + guard.merge_blocks(after_block, new_after_block, &new_after_args); + + let results = new_while_op + .borrow() + .results() + .all() + .into_iter() + .map(|r| Some(r.borrow().as_value_ref())) + .collect::>(); + guard.replace_op_with_values(operation, &results); + + Ok(true) + } +} diff --git a/dialects/scf/src/canonicalization/while_unused_result.rs b/dialects/scf/src/canonicalization/while_unused_result.rs new file mode 100644 index 000000000..d64ea8ae6 --- /dev/null +++ b/dialects/scf/src/canonicalization/while_unused_result.rs @@ -0,0 +1,173 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + patterns::{Pattern, PatternBenefit, PatternInfo, PatternKind, RewritePattern}, + *, +}; + +use crate::*; + +/// Remove results of a [While] that are also unused in its 'after' block. +/// +/// Before: +/// +/// ```text,ignore +/// %0:2 = scf.while () : () -> (i32, i64) { +/// %condition = "test.condition"() : () -> i1 +/// %v1 = "test.get_some_value"() : () -> i32 +/// %v2 = "test.get_some_value"() : () -> i64 +/// scf.condition(%condition) %v1, %v2 : i32, i64 +/// } do { +/// ^bb0(%arg0: i32, %arg1: i64): +/// "test.use"(%arg0) : (i32) -> () +/// scf.yield +/// } +/// scf.ret %0#0 : i32 +/// +/// After: +/// +/// ```text,ignore +/// %0 = scf.while () : () -> (i32) { +/// %condition = "test.condition"() : () -> i1 +/// %v1 = "test.get_some_value"() : () -> i32 +/// %v2 = "test.get_some_value"() : () -> i64 +/// scf.condition(%condition) %v1 : i32 +/// } do { +/// ^bb0(%arg0: i32): +/// "test.use"(%arg0) : (i32) -> () +/// scf.yield +/// } +/// scf.ret %0 : i32 +/// ``` +pub struct WhileUnusedResult { + info: PatternInfo, +} + +impl WhileUnusedResult { + pub fn new(context: Rc) -> Self { + let scf_dialect = context.get_or_register_dialect::(); + let while_op = scf_dialect.registered_name::().expect("scf.while is not registered"); + Self { + info: PatternInfo::new( + context, + "while-unused-result", + PatternKind::Operation(while_op), + PatternBenefit::MAX, + ), + } + } +} + +impl Pattern for WhileUnusedResult { + fn info(&self) -> &PatternInfo { + &self.info + } +} + +impl RewritePattern for WhileUnusedResult { + fn match_and_rewrite( + &self, + op: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + let operation = op.borrow(); + let Some(while_op) = operation.downcast_ref::() else { + return Ok(false); + }; + let condition_operation = while_op.condition_op(); + let span = while_op.span(); + + let after_args = { + while_op + .after() + .entry() + .arguments() + .iter() + .map(|arg| arg.borrow().as_value_ref()) + .collect::>() + }; + let forwarded = { + condition_operation + .borrow() + .forwarded() + .iter() + .map(|o| o.borrow().as_value_ref()) + .collect::>() + }; + + // Collect results mapping, new terminator args, and new result types + let mut new_results_indices = SmallVec::<[usize; 4]>::default(); + let mut new_result_types = SmallVec::<[Type; 4]>::default(); + let mut new_term_args = SmallVec::<[ValueRef; 4]>::default(); + let mut new_arg_spans = SmallVec::<[SourceSpan; 4]>::default(); + let mut need_update = false; + + for (i, result) in while_op.results().iter().enumerate() { + let result = result.borrow(); + let after_arg = after_args[i]; + let term_arg = forwarded[i]; + + if !result.is_used() && !after_arg.borrow().is_used() { + need_update = true; + } else { + new_results_indices.push(i); + new_term_args.push(term_arg); + new_result_types.push(result.ty().clone()); + new_arg_spans.push(result.span()); + } + } + + if !need_update { + return Ok(false); + } + + { + let mut guard = InsertionGuard::new(rewriter); + let (span, condition, condition_op) = { + let cond_op = condition_operation.borrow(); + let condition = cond_op.condition().as_value_ref(); + (cond_op.span(), condition, cond_op.as_operation_ref()) + }; + guard.set_insertion_point_before(condition_op); + let new_condition = guard.condition(condition, new_term_args, span)?; + let new_condition_op = new_condition.as_operation_ref(); + guard.replace_op(condition_op, new_condition_op); + } + + let new_while = { + let inits = while_op.inits().into_iter().map(|o| o.borrow().as_value_ref()); + rewriter.r#while(inits, &new_result_types, span)? + }; + + let new_after_block = rewriter.create_block( + new_while.borrow().after().as_region_ref(), + None, + &new_result_types, + ); + + // Build new results list and new after block args (unused entries will be None) + let num_results = while_op.num_results(); + let mut new_results: SmallVec<[_; 4]> = smallvec![None; num_results]; + let mut new_after_block_args: SmallVec<[_; 4]> = smallvec![None; num_results]; + { + let new_while_op = new_while.borrow(); + let new_after_block = new_after_block.borrow(); + for (i, new_result_index) in new_results_indices.iter().copied().enumerate() { + new_results[new_result_index] = + Some(new_while_op.results()[i].borrow().as_value_ref()); + new_after_block_args[new_result_index] = + Some(new_after_block.arguments()[i].borrow().as_value_ref()); + } + } + + let before_region = while_op.before().as_region_ref(); + let new_before_region = new_while.borrow().before().as_region_ref(); + let after_block = while_op.after().entry_block_ref().unwrap(); + + rewriter.inline_region_before(before_region, new_before_region); + rewriter.merge_blocks(after_block, new_after_block, &new_after_block_args); + rewriter.replace_op_with_values(op, &new_results); + + Ok(true) + } +} diff --git a/dialects/scf/src/lib.rs b/dialects/scf/src/lib.rs new file mode 100644 index 000000000..86597de17 --- /dev/null +++ b/dialects/scf/src/lib.rs @@ -0,0 +1,73 @@ +#![no_std] +#![feature(debug_closure_helpers)] +#![feature(unboxed_closures)] +#![feature(fn_traits)] +#![feature(ptr_metadata)] +#![feature(specialization)] +#![allow(incomplete_features)] +#![deny(warnings)] + +extern crate alloc; + +#[cfg(any(feature = "std", test))] +extern crate std; + +use alloc::boxed::Box; + +mod builders; +mod canonicalization; +mod ops; +pub mod transforms; + +use midenc_hir::{ + AttributeValue, Builder, Dialect, DialectInfo, DialectRegistration, OperationRef, SourceSpan, + Type, +}; + +pub use self::{builders::StructuredControlFlowOpBuilder, ops::*}; + +#[derive(Debug)] +pub struct ScfDialect { + info: DialectInfo, +} + +impl ScfDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl Dialect for ScfDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + _builder: &mut dyn Builder, + _attr: Box, + _ty: &Type, + _span: SourceSpan, + ) -> Option { + None + } +} + +impl DialectRegistration for ScfDialect { + const NAMESPACE: &'static str = "scf"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + } +} diff --git a/dialects/scf/src/ops.rs b/dialects/scf/src/ops.rs new file mode 100644 index 000000000..7764a32df --- /dev/null +++ b/dialects/scf/src/ops.rs @@ -0,0 +1,712 @@ +use alloc::{boxed::Box, rc::Rc}; + +use midenc_hir::{derive::operation, effects::*, patterns::RewritePatternSet, traits::*, *}; + +use crate::ScfDialect; + +/// [If] is a structured control flow operation representing conditional execution. +/// +/// An [If] takes a single condition as an argument, which chooses between one of its two regions +/// based on the condition. If the condition is true, then the `then_body` region is executed, +/// otherwise `else_body`. +/// +/// Neither region allows any arguments, and both regions must be terminated with one of: +/// +/// * [Return] to return from the enclosing function directly +/// * [Unreachable] to abort execution +/// * [Yield] to return from the enclosing [If] +#[operation( + dialect = ScfDialect, + traits(SingleBlock, NoRegionArguments, HasRecursiveMemoryEffects), + implements(RegionBranchOpInterface, OpPrinter) +)] +pub struct If { + #[operand] + condition: Bool, + #[region] + then_body: Region, + #[region] + else_body: Region, +} + +impl OpPrinter for If { + fn print(&self, flags: &OpPrintingFlags, _context: &Context) -> formatter::Document { + use formatter::*; + + let result_types = print::render_operation_result_types(self.as_operation()); + let result_types = if result_types.is_empty() { + result_types + } else { + result_types + const_text(" ") + }; + let header = print::render_operation_results(self.as_operation()) + + display(self.op.name()) + + const_text(" ") + + display(self.condition().as_value_ref()) + + result_types; + let body = if self.else_body().is_empty() { + self.then_body().print(flags) + const_text(";") + } else { + let then_body = self.then_body().print(flags); + then_body + const_text(" else ") + self.else_body().print(flags) + const_text(";") + }; + header + body + } +} + +impl If { + pub fn then_yield(&self) -> UnsafeIntrusiveEntityRef { + let terminator = self.then_body().entry().terminator().unwrap(); + let term = terminator + .borrow() + .downcast_ref::() + .expect("invalid hir.if then terminator: expected yield") + as *const Yield; + unsafe { UnsafeIntrusiveEntityRef::from_raw(term) } + } + + pub fn else_yield(&self) -> UnsafeIntrusiveEntityRef { + let terminator = self.else_body().entry().terminator().unwrap(); + let term = terminator + .borrow() + .downcast_ref::() + .expect("invalid hir.if else terminator: expected yield") + as *const Yield; + unsafe { UnsafeIntrusiveEntityRef::from_raw(term) } + } +} + +impl Canonicalizable for If { + fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc) { + rewrites.push(crate::canonicalization::ConvertTrivialIfToSelect::new(context.clone())); + rewrites.push(crate::canonicalization::IfRemoveUnusedResults::new(context.clone())); + rewrites.push(crate::canonicalization::FoldRedundantYields::new(context)); + } +} + +impl RegionBranchOpInterface for If { + fn get_entry_successor_regions( + &self, + operands: &[Option>], + ) -> RegionSuccessorIter<'_> { + let condition = operands[0].as_deref().and_then(|v| v.as_bool()); + let has_then = condition.is_none_or(|v| v); + let else_possible = condition.is_none_or(|v| !v); + let has_else = else_possible && !self.else_body().is_empty(); + + let mut infos = SmallVec::<[RegionSuccessorInfo; 2]>::default(); + if has_then { + infos.push(RegionSuccessorInfo::Entering(self.then_body().as_region_ref())); + } + + if else_possible { + if has_else { + infos.push(RegionSuccessorInfo::Entering(self.else_body().as_region_ref())); + } else { + // Branching back to parent with `then` results + infos.push(RegionSuccessorInfo::Returning( + self.results().all().iter().map(|v| v.borrow().as_value_ref()).collect(), + )); + } + } + + RegionSuccessorIter::new(self.as_operation(), infos) + } + + fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_> { + match point { + RegionBranchPoint::Parent => { + // Either branch is reachable on entry (unless `else` is empty, as it is optional) + let mut infos: SmallVec<[_; 2]> = + smallvec![RegionSuccessorInfo::Entering(self.then_body().as_region_ref())]; + // Don't consider the else region if it is empty + if !self.else_body().is_empty() { + infos.push(RegionSuccessorInfo::Entering(self.else_body().as_region_ref())); + } + RegionSuccessorIter::new(self.as_operation(), infos) + } + RegionBranchPoint::Child(_) => { + // Only the parent If is reachable from then_body/else_body + RegionSuccessorIter::new( + self.as_operation(), + [RegionSuccessorInfo::Returning( + self.results().all().iter().map(|v| v.borrow().as_value_ref()).collect(), + )], + ) + } + } + } + + fn get_region_invocation_bounds( + &self, + operands: &[Option>], + ) -> SmallVec<[InvocationBounds; 1]> { + let condition = operands[0].as_deref().and_then(|v| v.as_bool()); + + if let Some(condition) = condition { + if condition { + smallvec![InvocationBounds::Exact(1), InvocationBounds::Never] + } else { + smallvec![InvocationBounds::Never, InvocationBounds::Exact(1)] + } + } else { + // Only one region is invoked, and no more than a single time + smallvec![InvocationBounds::NoMoreThan(1); 2] + } + } + + #[inline(always)] + fn is_repetitive_region(&self, _index: usize) -> bool { + false + } + + #[inline(always)] + fn has_loop(&self) -> bool { + false + } +} + +/// A while is a loop structure composed of two regions: a "before" region, and an "after" region. +/// +/// The "before" region's entry block parameters correspond to the operands expected by the +/// operation, and can be used to compute the condition that determines whether the "after" body +/// is executed or not, or simply forwarded to the "after" region. The "before" region must +/// terminate with a [Condition] operation, which will be evaluated to determine whether or not +/// to continue the loop. +/// +/// The "after" region corresponds to the loop body, and must terminate with a [Yield] operation, +/// whose operands must be of the same arity and type as the "before" region's argument list. In +/// this way, the "after" body can feed back input to the "before" body to determine whether to +/// continue the loop. +#[operation( + dialect = ScfDialect, + traits(SingleBlock, HasRecursiveMemoryEffects), + implements(RegionBranchOpInterface, LoopLikeOpInterface, OpPrinter) +)] +pub struct While { + #[operands] + inits: AnyType, + #[region] + before: Region, + #[region] + after: Region, +} + +impl OpPrinter for While { + fn print(&self, flags: &OpPrintingFlags, _context: &Context) -> formatter::Document { + use formatter::*; + + let result_types = print::render_operation_result_types(self.as_operation()); + let result_types = if result_types.is_empty() { + result_types + } else { + result_types + const_text(" ") + }; + let results = print::render_operation_results(self.as_operation()); + let operands = print::render_operation_operands(self.as_operation()); + let header = results + display(self.op.name()) + const_text(" ") + operands + result_types; + let body = self.before().print(flags) + + const_text(" do ") + + self.after().print(flags) + + const_text(";"); + header + body + } +} + +impl While { + pub fn condition_op(&self) -> UnsafeIntrusiveEntityRef { + let term = self + .before() + .entry() + .terminator() + .expect("expected before region to have a terminator"); + let cond = term + .borrow() + .downcast_ref::() + .expect("expected before region to terminate with hir.condition") + as *const Condition; + unsafe { UnsafeIntrusiveEntityRef::from_raw(cond) } + } + + pub fn yield_op(&self) -> UnsafeIntrusiveEntityRef { + let term = self + .after() + .entry() + .terminator() + .expect("expected after region to have a terminator"); + let yield_op = term + .borrow() + .downcast_ref::() + .expect("expected after region to terminate with hir.yield") + as *const Yield; + unsafe { UnsafeIntrusiveEntityRef::from_raw(yield_op) } + } +} + +impl Canonicalizable for While { + fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc) { + rewrites.push(crate::canonicalization::RemoveLoopInvariantArgsFromBeforeBlock::new( + context.clone(), + )); + //rewrites.push(crate::canonicalization::RemoveLoopInvariantValueYielded::new(context.clone())); + rewrites.push(crate::canonicalization::WhileConditionTruth::new(context.clone())); + rewrites.push(crate::canonicalization::WhileUnusedResult::new(context.clone())); + rewrites.push(crate::canonicalization::WhileRemoveDuplicatedResults::new(context.clone())); + rewrites.push(crate::canonicalization::WhileRemoveUnusedArgs::new(context.clone())); + //rewrites.push(crate::canonicalization::ConvertDoWhileToWhileTrue::new(context)); + } +} + +impl LoopLikeOpInterface for While { + fn get_region_iter_args(&self) -> Option> { + let entry = self.before().entry_block_ref()?; + Some(EntityRef::map(entry.borrow(), |block| block.arguments())) + } + + fn get_loop_header_region(&self) -> RegionRef { + self.before().as_region_ref() + } + + fn get_loop_regions(&self) -> SmallVec<[RegionRef; 2]> { + smallvec![self.before().as_region_ref(), self.after().as_region_ref()] + } + + fn get_inits_mut(&mut self) -> OpOperandRangeMut<'_> { + self.inits_mut() + } + + fn get_yielded_values_mut(&mut self) -> Option>> { + let mut yield_op = self + .after() + .entry() + .terminator() + .expect("invalid `while`: expected loop body to be terminated"); + + // The values which are yielded to each iteration + Some(EntityMut::project(yield_op.borrow_mut(), |op| op.operands_mut().group_mut(0))) + } +} + +impl RegionBranchOpInterface for While { + #[inline] + fn get_entry_successor_operands(&self, _point: RegionBranchPoint) -> SuccessorOperandRange<'_> { + // Operands being forwarded to the `before` region from outside the loop + SuccessorOperandRange::forward(self.operands().all()) + } + + fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_> { + match point { + RegionBranchPoint::Parent => { + // The only successor region when branching from outside the While op is the + // `before` region. + RegionSuccessorIter::new( + self.as_operation(), + [RegionSuccessorInfo::Entering(self.before().as_region_ref())], + ) + } + RegionBranchPoint::Child(region) => { + let before_region = self.before().as_region_ref(); + let after_region = self.after().as_region_ref(); + assert!(region == before_region || region == after_region); + + // When branching from `before`, the only successor is `after` or the While itself, + // otherwise, when branching from `after` the only successor is `before`. + if region == after_region { + RegionSuccessorIter::new( + self.as_operation(), + [RegionSuccessorInfo::Entering(before_region)], + ) + } else { + RegionSuccessorIter::new( + self.as_operation(), + [ + RegionSuccessorInfo::Returning( + self.results() + .all() + .iter() + .map(|r| r.borrow().as_value_ref()) + .collect(), + ), + RegionSuccessorInfo::Entering(after_region), + ], + ) + } + } + } + } + + #[inline] + fn get_region_invocation_bounds( + &self, + _operands: &[Option>], + ) -> SmallVec<[InvocationBounds; 1]> { + smallvec![InvocationBounds::Unknown; self.num_regions()] + } + + #[inline(always)] + fn is_repetitive_region(&self, _index: usize) -> bool { + // Both regions are in the loop (`before` -> `after` -> `before` -> `after`) + true + } + + #[inline(always)] + fn has_loop(&self) -> bool { + true + } +} + +/// The `hir.index_switch` is a control-flow operation that branches to one of the given regions +/// based on the values of the argument and the cases. The argument is always of type `u32`. +/// +/// The operation always has a "default" region and any number of case regions denoted by integer +/// constants. Control-flow transfers to the case region whose constant value equals the value of +/// the argument. If the argument does not equal any of the case values, control-flow transfer to +/// the "default" region. +/// +/// ## Example +/// +/// ```text,ignore +/// %0 = hir.index_switch %arg0 : u32 -> i32 +/// case 2 { +/// %1 = hir.constant 10 : i32 +/// scf.yield %1 : i32 +/// } +/// case 5 { +/// %2 = hir.constant 20 : i32 +/// scf.yield %2 : i32 +/// } +/// default { +/// %3 = hir.constant 30 : i32 +/// scf.yield %3 : i32 +/// } +/// ``` +#[operation( + dialect = ScfDialect, + traits(SingleBlock, HasRecursiveMemoryEffects), + implements(RegionBranchOpInterface, OpPrinter) +)] +pub struct IndexSwitch { + #[operand] + selector: UInt32, + #[attr] + cases: ArrayAttr, + #[region] + default_region: Region, +} + +impl OpPrinter for IndexSwitch { + fn print(&self, flags: &OpPrintingFlags, _context: &Context) -> formatter::Document { + use formatter::*; + + let result_types = print::render_operation_result_types(self.as_operation()); + let result_types = if result_types.is_empty() { + result_types + } else { + result_types + const_text(" ") + }; + let results = print::render_operation_results(self.as_operation()); + let header = results + + display(self.op.name()) + + const_text(" ") + + display(self.selector().as_value_ref()) + + result_types; + let cases = self.cases().iter().fold(Document::Empty, |acc, case| { + let index = self.get_case_index_for_selector(*case).unwrap(); + let region = self.get_case_region(index); + acc + nl() + + const_text("case ") + + display(*case) + + const_text(" ") + + region.borrow().print(flags) + }); + let fallback = + nl() + const_text("default ") + self.default_region().print(flags) + const_text(";"); + header + cases + fallback + } +} + +impl IndexSwitch { + pub fn num_cases(&self) -> usize { + self.cases().len() + } + + pub fn get_default_block(&self) -> BlockRef { + self.default_region().entry_block_ref().expect("default region has no blocks") + } + + pub fn get_case_index_for_selector(&self, selector: u32) -> Option { + self.cases().iter().position(|case| *case == selector) + } + + #[track_caller] + pub fn get_case_block(&self, index: usize) -> BlockRef { + let block_ref = self.get_case_region(index).borrow().entry_block_ref(); + match block_ref { + None => panic!("region for case {index} has no blocks"), + Some(block) => block, + } + } + + #[track_caller] + pub fn get_case_region(&self, mut index: usize) -> RegionRef { + let mut next_region = self.regions().front().as_pointer(); + let mut current_index = 0; + // Shift the requested index up by one to account for default region + index += 1; + while let Some(region) = next_region.take() { + if index == current_index { + return region; + } + next_region = region.next(); + current_index += 1; + } + + panic!("invalid region index `{}`: out of bounds", index - 1) + } +} + +impl RegionBranchOpInterface for IndexSwitch { + fn get_entry_successor_regions( + &self, + operands: &[Option>], + ) -> RegionSuccessorIter<'_> { + let selector = operands[0].as_deref().and_then(|v| v.as_u32()); + let selected = selector.map(|s| self.get_case_index_for_selector(s)); + + match selected { + None => { + // All regions are possible successors + let infos = + self.regions().iter().map(|r| RegionSuccessorInfo::Entering(r.as_region_ref())); + RegionSuccessorIter::new(self.as_operation(), infos) + } + Some(Some(selected)) => { + // A specific case was selected + RegionSuccessorIter::new( + self.as_operation(), + [RegionSuccessorInfo::Entering(self.get_case_region(selected))], + ) + } + Some(None) => { + // The fallback case should be used + RegionSuccessorIter::new( + self.as_operation(), + [RegionSuccessorInfo::Entering(self.default_region().as_region_ref())], + ) + } + } + } + + fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_> { + match point { + RegionBranchPoint::Parent => { + // Any region is reachable on entry + let infos = + self.regions().iter().map(|r| RegionSuccessorInfo::Entering(r.as_region_ref())); + RegionSuccessorIter::new(self.as_operation(), infos) + } + RegionBranchPoint::Child(_) => { + // Only the parent op is reachable from its regions + RegionSuccessorIter::new( + self.as_operation(), + [RegionSuccessorInfo::Returning( + self.results().all().iter().map(|v| v.borrow().as_value_ref()).collect(), + )], + ) + } + } + } + + fn get_region_invocation_bounds( + &self, + operands: &[Option>], + ) -> SmallVec<[InvocationBounds; 1]> { + let selector = operands[0].as_deref().and_then(|v| v.as_u32()); + + if let Some(selector) = selector { + let mut bounds = smallvec![InvocationBounds::Never; self.num_cases()]; + let selected = + self.get_case_index_for_selector(selector).map(|idx| idx + 1).unwrap_or(0); + bounds[selected] = InvocationBounds::Exact(1); + bounds + } else { + // Only one region is invoked, and no more than a single time + smallvec![InvocationBounds::NoMoreThan(1); self.num_cases()] + } + } + + #[inline(always)] + fn is_repetitive_region(&self, _index: usize) -> bool { + false + } + + #[inline(always)] + fn has_loop(&self) -> bool { + false + } +} + +impl Canonicalizable for IndexSwitch { + fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc) { + rewrites.push(crate::canonicalization::FoldConstantIndexSwitch::new(context.clone())); + rewrites.push(crate::canonicalization::FoldRedundantYields::new(context)); + } +} + +/// The [Condition] op is used in conjunction with [While] as the terminator of its `before` region. +/// +/// This op represents a choice between continuing the loop, or exiting the [While] loop and +/// continuing execution after the loop. +/// +/// NOTE: Attempting to use this op in any other context than the one described above is invalid, +/// and the implementation of various interfaces by this op will panic if that assumption is +/// violated. +#[operation( + dialect = ScfDialect, + traits(Terminator, ReturnLike), + implements(RegionBranchTerminatorOpInterface) +)] +pub struct Condition { + #[operand] + condition: Bool, + #[operands] + forwarded: AnyType, +} + +impl RegionBranchTerminatorOpInterface for Condition { + #[inline] + fn get_successor_operands(&self, _point: RegionBranchPoint) -> SuccessorOperandRange<'_> { + SuccessorOperandRange::forward(self.forwarded()) + } + + #[inline] + fn get_mutable_successor_operands( + &mut self, + _point: RegionBranchPoint, + ) -> SuccessorOperandRangeMut<'_> { + SuccessorOperandRangeMut::forward(self.forwarded_mut()) + } + + fn get_successor_regions( + &self, + operands: &[Option>], + ) -> SmallVec<[RegionSuccessorInfo; 2]> { + // A [While] loop has two regions: `before` (containing this op), and `after`, which this + // op branches to when the condition is true. If the condition is false, control is + // transferred back to the parent [While] operation, with the forwarded operands of the + // condition used as the results of the [While] operation. + // + // We can return a single statically-known region if we were given a constant condition + // value, otherwise we must return both possible regions. + let cond = operands[0].as_deref().and_then(|v| v.as_bool()); + let mut regions = SmallVec::<[RegionSuccessorInfo; 2]>::default(); + + let parent_op = self.parent_op().unwrap(); + let parent_op = parent_op.borrow(); + let while_op = parent_op + .downcast_ref::() + .expect("expected `Condition` op to be a child of a `While` op"); + let after_region = while_op.after().as_region_ref(); + + // We can't know the condition until runtime, so both the parent `while` op and + match cond { + None => { + regions.push(RegionSuccessorInfo::Entering(after_region)); + regions.push(RegionSuccessorInfo::Returning( + while_op.results().all().iter().map(|r| r.borrow().as_value_ref()).collect(), + )); + } + Some(true) => { + regions.push(RegionSuccessorInfo::Entering(after_region)); + } + Some(false) => { + regions.push(RegionSuccessorInfo::Returning( + while_op.results().all().iter().map(|r| r.borrow().as_value_ref()).collect(), + )); + } + } + + regions + } +} + +/// The [Yield] op is used in conjunction with [If] and [While] ops as a return-like terminator. +/// +/// * With [If], its regions must be terminated with either a [Yield] or an [Unreachable] op. +/// * With [While], a [Yield] is only valid in the `after` region, and the yielded operands must +/// match the region arguments of the `before` region. Thus to return values from the body of a +/// loop, one must first yield them from the `after` region to the `before` region using [Yield], +/// and then yield them from the `before` region by passsing them as forwarded operands of the +/// [Condition] op. +/// +/// Any number of operands can be yielded at the same time. However, when [Yield] is used in +/// conjunction with [While], the arity and type of the operands must match the region arguments +/// of the `before` region. When used in conjunction with [If], both the `if_true` and `if_false` +/// regions must yield the same arity and types. +#[operation( + dialect = ScfDialect, + traits(Terminator, ReturnLike, Pure, AlwaysSpeculatable), + implements( + RegionBranchTerminatorOpInterface, + MemoryEffectOpInterface, + ConditionallySpeculatable + ) +)] +pub struct Yield { + #[operands] + yielded: AnyType, +} + +impl RegionBranchTerminatorOpInterface for Yield { + #[inline] + fn get_successor_operands(&self, _point: RegionBranchPoint) -> SuccessorOperandRange<'_> { + SuccessorOperandRange::forward(self.yielded()) + } + + fn get_mutable_successor_operands( + &mut self, + _point: RegionBranchPoint, + ) -> SuccessorOperandRangeMut<'_> { + SuccessorOperandRangeMut::forward(self.yielded_mut()) + } + + fn get_successor_regions( + &self, + _operands: &[Option>], + ) -> SmallVec<[RegionSuccessorInfo; 2]> { + // Depending on the type of operation containing this yield, the set of successor regions + // is always known. + // + // * [While] may only have a yield to its `before` region + // * [If] may only yield to its parent + // * [IndexSwitch] may only yield to its parent + let parent_op = self.parent_op().unwrap(); + let parent_op = parent_op.borrow(); + if parent_op.is::() || parent_op.is::() { + smallvec![RegionSuccessorInfo::Returning( + parent_op.results().all().iter().map(|v| v.borrow().as_value_ref()).collect() + )] + } else if let Some(while_op) = parent_op.downcast_ref::() { + let before_region = while_op.before().as_region_ref(); + smallvec![RegionSuccessorInfo::Entering(before_region)] + } else { + panic!("unsupported parent operation for '{}': '{}'", self.name(), parent_op.name()) + } + } +} + +impl EffectOpInterface for Yield { + fn has_no_effect(&self) -> bool { + true + } + + fn effects(&self) -> EffectIterator<::midenc_hir::effects::MemoryEffect> { + EffectIterator::from_smallvec(::midenc_hir::smallvec![]) + } +} + +impl ConditionallySpeculatable for Yield { + fn speculatability(&self) -> Speculatability { + Speculatability::Speculatable + } +} diff --git a/dialects/scf/src/transforms.rs b/dialects/scf/src/transforms.rs new file mode 100644 index 000000000..63b41596b --- /dev/null +++ b/dialects/scf/src/transforms.rs @@ -0,0 +1,3 @@ +mod cfg_to_scf; + +pub use self::cfg_to_scf::LiftControlFlowToSCF; diff --git a/dialects/scf/src/transforms/cfg_to_scf.rs b/dialects/scf/src/transforms/cfg_to_scf.rs new file mode 100644 index 000000000..bece00afd --- /dev/null +++ b/dialects/scf/src/transforms/cfg_to_scf.rs @@ -0,0 +1,878 @@ +use alloc::rc::Rc; + +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_cf::{self as cf, ControlFlowOpBuilder}; +use midenc_dialect_ub::UndefinedBehaviorOpBuilder; +use midenc_hir::{ + diagnostics::Severity, + dialects::builtin, + dominance::DominanceInfo, + pass::{Pass, PassExecutionState, PostPassStatus}, + Builder, EntityMut, Forward, Op, Operation, OperationName, OperationRef, RawWalk, Report, + SmallVec, Spanned, Type, ValueRange, ValueRef, WalkResult, +}; +use midenc_hir_transform::{self as transforms, CFGToSCFInterface}; + +use crate::*; + +/// Lifts unstructured control flow operations to structured operations in the HIR dialect. +/// +/// This pass is not always guaranteed to replace all unstructured control flow operations. If a +/// region contains only a single kind of return-like operation, all unstructured control flow ops +/// will be replaced successfully. Otherwise a single unstructured switch branching to one block per +/// return-like operation kind remains. +/// +/// This pass may need to create unreachable terminators in case of infinite loops, which is only +/// supported for 'builtin.func' for now. If you potentially have infinite loops inside CFG regions +/// not belonging to 'builtin.func', consider using the `transform_cfg_to_scf` function directly +/// with a corresponding [CFGToSCFInterface::create_unreachable_terminator] implementation. +pub struct LiftControlFlowToSCF; + +impl Pass for LiftControlFlowToSCF { + type Target = Operation; + + fn name(&self) -> &'static str { + "lift-control-flow" + } + + fn argument(&self) -> &'static str { + "lift-control-flow" + } + + fn description(&self) -> &'static str { + "Lifts unstructured control flow to structured control flow" + } + + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn initialize(&mut self, context: Rc) -> Result<(), Report> { + // Ensure that when this pass is initialized, that the SCF dialect is registered + context.get_or_register_dialect::(); + + Ok(()) + } + + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + let mut transformation = ControlFlowToSCFTransformation; + let mut changed = false; + + let root = op.as_operation_ref(); + drop(op); + + log::debug!(target: "cfg-to-scf", "applying control flow lifting transformation pass starting from {}", root.borrow()); + + let result = root.raw_prewalk::(|operation: OperationRef| -> WalkResult { + let op = operation.borrow(); + if op.is::() { + if op.regions().is_empty() { + return WalkResult::Skip; + } + + let dominfo = if OperationRef::ptr_eq(&operation, &root) { + state.analysis_manager().get_analysis::() + } else { + state.analysis_manager().get_child_analysis::(operation) + }; + + let mut dominfo = match dominfo { + Ok(di) => di, + Err(err) => return WalkResult::Break(err), + }; + let dominfo = Rc::make_mut(&mut dominfo); + + let visitor = |inner: OperationRef| -> WalkResult { + log::debug!(target: "cfg-to-scf", "applying control flow lifting to {}", inner.borrow()); + let mut next_region = inner.borrow().regions().front().as_pointer(); + while let Some(region) = next_region.take() { + next_region = region.next(); + + let result = + transforms::transform_cfg_to_scf(region, &mut transformation, dominfo); + match result { + Ok(did_change) => { + log::trace!( + target: "cfg-to-scf", + "control flow lifting completed for region \ + (did_change={did_change})" + ); + changed |= did_change; + } + Err(err) => { + return WalkResult::Break(err); + } + } + } + + WalkResult::Continue(()) + }; + + drop(op); + + operation.raw_postwalk::(visitor)?; + + // Do not enter the function body in the outer walk + WalkResult::Skip + } else if op.is::() + || op.is::() + || op.is::() + { + // We only care to recurse into ops that can contain functions + log::trace!( + target: "cfg-to-scf", + "looking for functions to apply control flow lifting to in '{}'", + op.name() + ); + WalkResult::Continue(()) + } else { + // Skip all other ops + log::trace!("skipping control flow lifting for '{}'", op.name()); + WalkResult::Skip + } + }); + + if result.was_interrupted() { + state.set_post_pass_status(PostPassStatus::Unchanged); + return result.into_result(); + } + + log::debug!( + target: "cfg-to-scf", + "control flow lifting transformation pass completed successfully (changed = {changed}" + ); + if !changed { + state.preserved_analyses_mut().preserve_all(); + } + + state.set_post_pass_status(changed.into()); + + Ok(()) + } +} + +/// Implementation of [CFGToSCFInterface] used to lift unstructured control flow operations into +/// HIR's structured control flow operations. +struct ControlFlowToSCFTransformation; + +impl CFGToSCFInterface for ControlFlowToSCFTransformation { + /// Creates an `scf.if` op if `control_flow_cond_op` is a `cf.cond_br` op, or an + /// `scf.index_switch` if it is a `cf.switch`. Otherwise, returns an error. + fn create_structured_branch_region_op( + &self, + builder: &mut midenc_hir::OpBuilder, + control_flow_cond_op: midenc_hir::OperationRef, + result_types: &[midenc_hir::Type], + regions: &mut midenc_hir::SmallVec<[midenc_hir::RegionRef; 2]>, + ) -> Result { + let cf_op = control_flow_cond_op.borrow(); + if let Some(cond_br) = cf_op.downcast_ref::() { + assert_eq!(regions.len(), 2); + + let span = cond_br.span(); + let mut if_op = builder.r#if(cond_br.condition().as_value_ref(), result_types, span)?; + let mut op = if_op.borrow_mut(); + let operation = op.as_operation_ref(); + + op.then_body_mut().take_body(regions[0]); + op.else_body_mut().take_body(regions[1]); + + return Ok(operation); + } + + if let Some(switch) = cf_op.downcast_ref::() { + let span = switch.span(); + let cases = switch.cases(); + assert_eq!(regions.len(), cases.len() + 1); + let cases = cases.iter().map(|case| *case.key().unwrap()); + let mut switch_op = builder.index_switch( + switch.selector().as_value_ref(), + cases, + result_types, + span, + )?; + let mut op = switch_op.borrow_mut(); + let operation = op.as_operation_ref(); + + // If any of the case targets are duplicated, we have to duplicate the regions or + // we will fail to properly lower the input + + // The order of the regions match the original 'cf.switch', hence the fallback region + // coming first. + op.default_region_mut().take_body(regions[0]); + for (index, source_region) in regions.iter().copied().skip(1).enumerate() { + let mut case_region = op.get_case_region(index); + case_region.borrow_mut().take_body(source_region); + } + + return Ok(operation); + } + + Err(builder + .context() + .diagnostics() + .diagnostic(Severity::Error) + .with_message("control flow transformation failed") + .with_primary_label( + cf_op.span(), + "unknown control flow operation cannot be lifted to structured control flow", + ) + .into_report()) + } + + /// Creates an `scf.yield` op returning the given results. + fn create_structured_branch_region_terminator_op( + &self, + span: midenc_hir::SourceSpan, + builder: &mut midenc_hir::OpBuilder, + _branch_region_op: midenc_hir::OperationRef, + _replaced_control_flow_op: Option, + results: ValueRange<'_, 2>, + ) -> Result<(), midenc_hir::Report> { + builder.r#yield(results, span)?; + + Ok(()) + } + + /// Creates an `scf.while` op. The loop body is made the before-region of the + /// while op and terminated with an `scf.condition` op. The after-region does + /// nothing but forward the iteration variables. + fn create_structured_do_while_loop_op( + &self, + builder: &mut midenc_hir::OpBuilder, + replaced_op: midenc_hir::OperationRef, + loop_values_init: ValueRange<'_, 2>, + condition: midenc_hir::ValueRef, + loop_values_next_iter: ValueRange<'_, 2>, + loop_body: midenc_hir::RegionRef, + ) -> Result { + let span = replaced_op.span(); + + // Results are derived from the forwarded values given to `scf.condition` + let result_types = loop_values_next_iter + .iter() + .map(|v| v.borrow().ty().clone()) + .collect::>(); + let mut while_op = builder.r#while(loop_values_init, &result_types, span)?; + let mut op = while_op.borrow_mut(); + let operation = op.as_operation_ref(); + + op.before_mut().take_body(loop_body); + + builder.set_insertion_point_to_end(op.before().body().back().as_pointer().unwrap()); + + // `get_cfg_switch_value` returns a u32. We therefore need to truncate the condition to i1 + // first. It is guaranteed to be either 0 or 1 already. + let cond = builder.trunc(condition, Type::I1, span)?; + builder.condition(cond, loop_values_next_iter, span)?; + + let yielded = op + .after() + .entry() + .arguments() + .iter() + .map(|arg| arg.upcast()) + .collect::>(); + + builder.set_insertion_point_to_end(op.after().entry().as_block_ref()); + + builder.r#yield(yielded, span)?; + + Ok(operation) + } + + /// Creates an `arith.constant` with an i32 attribute of the given value. + fn get_cfg_switch_value( + &self, + span: midenc_hir::SourceSpan, + builder: &mut midenc_hir::OpBuilder, + value: u32, + ) -> midenc_hir::ValueRef { + builder.u32(value, span) + } + + /// Creates a `cf.switch` op with the given cases and flag. + fn create_cfg_switch_op( + &self, + span: midenc_hir::SourceSpan, + builder: &mut midenc_hir::OpBuilder, + flag: midenc_hir::ValueRef, + case_values: &[u32], + case_destinations: &[midenc_hir::BlockRef], + case_arguments: &[ValueRange<'_, 2>], + default_dest: midenc_hir::BlockRef, + default_args: ValueRange<'_, 2>, + ) -> Result<(), Report> { + let cases = case_values + .iter() + .copied() + .zip(case_destinations.iter().copied().zip(case_arguments)) + .map(|(value, (successor, args))| cf::SwitchCase { + value, + successor, + arguments: args.to_vec(), + }) + .collect::>(); + + builder.switch(flag, cases, default_dest, default_args, span)?; + + Ok(()) + } + + fn create_single_destination_branch( + &self, + span: midenc_hir::SourceSpan, + builder: &mut midenc_hir::OpBuilder, + _dummy_flag: midenc_hir::ValueRef, + destination: midenc_hir::BlockRef, + arguments: ValueRange<'_, 2>, + ) -> Result<(), Report> { + builder.br(destination, arguments, span)?; + Ok(()) + } + + fn create_conditional_branch( + &self, + span: midenc_hir::SourceSpan, + builder: &mut midenc_hir::OpBuilder, + condition: midenc_hir::ValueRef, + true_dest: midenc_hir::BlockRef, + true_args: ValueRange<'_, 2>, + false_dest: midenc_hir::BlockRef, + false_args: ValueRange<'_, 2>, + ) -> Result<(), Report> { + builder.cond_br(condition, true_dest, true_args, false_dest, false_args, span)?; + + Ok(()) + } + + /// Creates a `ub.poison` op of the given type. + fn get_undef_value( + &self, + span: midenc_hir::SourceSpan, + builder: &mut midenc_hir::OpBuilder, + ty: midenc_hir::Type, + ) -> midenc_hir::ValueRef { + builder.poison(ty, span) + } + + fn create_unreachable_terminator( + &self, + span: midenc_hir::SourceSpan, + builder: &mut midenc_hir::OpBuilder, + _region: midenc_hir::RegionRef, + ) -> Result { + log::trace!(target: "cfg-to-scf", "creating unreachable terminator at {}", builder.insertion_point()); + let op = builder.unreachable(span); + Ok(op.as_operation_ref()) + } +} + +#[cfg(test)] +mod tests { + use alloc::{boxed::Box, format, rc::Rc}; + + use builtin::{BuiltinOpBuilder, FunctionBuilder}; + use midenc_expect_test::expect_file; + use midenc_hir::{ + dialects::builtin, pass, AbiParam, BuilderExt, Context, Ident, OpBuilder, PointerType, + Report, Signature, SourceSpan, Type, + }; + + use super::*; + + #[test] + fn cfg_to_scf_lift_simple_conditional() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + // Define function body + let mut builder = FunctionBuilder::new(function, &mut builder); + + let if_is_zero = builder.create_block(); + let if_is_nonzero = builder.create_block(); + let exit_block = builder.create_block(); + let return_val = builder.append_block_param(exit_block, Type::U32, span); + + let block = builder.current_block(); + let input = block.borrow().arguments()[0].upcast(); + + let zero = builder.u32(0, span); + let is_zero = builder.eq(input, zero, span)?; + builder.cond_br(is_zero, if_is_zero, [], if_is_nonzero, [], span)?; + + builder.switch_to_block(if_is_zero); + let a = builder.incr(input, span)?; + builder.br(exit_block, [a], span)?; + + builder.switch_to_block(if_is_nonzero); + let b = builder.mul(input, input, span)?; + builder.br(exit_block, [b], span)?; + + builder.switch_to_block(exit_block); + builder.ret(Some(return_val), span)?; + + let operation = function.as_operation_ref(); + // Run transformation on function body + let input = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_simple_conditional_before.hir"].assert_eq(&input); + + let mut pm = pass::PassManager::on::(context, pass::Nesting::Implicit); + pm.add_pass(Box::new(LiftControlFlowToSCF)); + pm.run(operation)?; + + // Verify that the function body now consists of a single `scf.if` operation, followed by + // an `builtin.return`. + let output = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_simple_conditional_after.hir"].assert_eq(&output); + + Ok(()) + } + + /// This test ensures that the CF->SCF transformation is correctly applied to unstructured + /// conditional control flow, where one branch leads to an early exit from the function, while + /// the other branch performs additional computation before exiting. + #[test] + fn cfg_to_scf_lift_conditional_early_exit() -> Result<(), Report> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .is_test(true) + .format_timestamp(None) + .try_init(); + + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new( + [ + AbiParam::new(Type::U32), + AbiParam::new(Type::U32), + AbiParam::new(Type::U32), + AbiParam::new(Type::U32), + ], + [AbiParam::new(Type::U32)], + ); + builder(name, signature).unwrap() + }; + + // Define function body + let mut builder = FunctionBuilder::new(function, &mut builder); + + // This is the HIR we derived this test case from originally, as reported in issue #510 + // + // public builtin.function @cabi_realloc_wit_bindgen_0_28_0(v325: i32, v326: i32, v327: i32, v328: i32) -> i32 { + // ^block32(v325: i32, v326: i32, v327: i32, v328: i32): + // v330 = arith.constant 0 : i32; + // v331 = arith.neq v326, v330 : i1; + // cf.cond_br v331 ^block35, ^block36; + // ^block34(v343: i32): + // v345 = arith.eq v343, v330 : i1; + // v346 = arith.zext v345 : u32; + // v347 = hir.bitcast v346 : i32; + // v349 = arith.neq v347, v330 : i1; + // cf.cond_br v349 ^block39, ^block40; + // ^block35: + // v342 = hir.exec @miden:test-proj-underscore/test-proj-underscore@0.1.0/test_proj_underscore/_RNvCs95KLPZDDxvS_7___rustc14___rust_realloc(v325, v326, v327, v328) : i32 + // cf.br ^block34(v342); + // ^block36: + // v333 = arith.neq v328, v330 : i1; + // cf.cond_br v333 ^block37, ^block38; + // ^block37: + // v341 = hir.exec @miden:test-proj-underscore/test-proj-underscore@0.1.0/test_proj_underscore/_RNvCs95KLPZDDxvS_7___rustc12___rust_alloc(v328, v327) : i32 + // cf.br ^block34(v341); + // ^block38: + // builtin.ret v327; + // ^block39: + // ub.unreachable ; + // ^block40: + // builtin.ret v343; + // }; + + let block32 = builder.current_block(); + let block34 = builder.create_block(); + let v343 = builder.append_block_param(block34, Type::U32, span); + let block35 = builder.create_block(); + let block36 = builder.create_block(); + let block37 = builder.create_block(); + let block38 = builder.create_block(); + let block39 = builder.create_block(); + let block40 = builder.create_block(); + + let (v325, v326, v327, v328) = { + let block32 = block32.borrow(); + let args = block32.arguments(); + let arg0: midenc_hir::ValueRef = args[0].upcast(); + let arg2: midenc_hir::ValueRef = args[2].upcast(); + let arg3: midenc_hir::ValueRef = args[3].upcast(); + (arg0, args[1].upcast(), arg2, arg3) + }; + + let v330 = builder.u32(0, span); + let v331 = builder.neq(v326, v330, span)?; + builder.cond_br(v331, block35, [], block36, [], span)?; + + builder.switch_to_block(block34); + let v345 = builder.eq(v343, v330, span)?; + let v346 = builder.zext(v345, Type::U32, span)?; + let v349 = builder.neq(v346, v330, span)?; + builder.cond_br(v349, block39, [], block40, [], span)?; + + builder.switch_to_block(block35); + let v342 = builder.incr(v325, span)?; + builder.br(block34, [v342], span)?; + + builder.switch_to_block(block36); + let v333 = builder.neq(v328, v330, span)?; + builder.cond_br(v333, block37, [], block38, [], span)?; + + builder.switch_to_block(block37); + let v341 = builder.incr(v328, span)?; + builder.br(block34, [v341], span)?; + + builder.switch_to_block(block38); + builder.ret(Some(v327), span)?; + + builder.switch_to_block(block39); + builder.unreachable(span); + + builder.switch_to_block(block40); + builder.ret(Some(v343), span)?; + + let operation = function.as_operation_ref(); + + // Run transformation on function body + let input = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_conditional_early_exit_before.hir"] + .assert_eq(&input); + + let mut pm = pass::PassManager::on::(context, pass::Nesting::Implicit); + pm.add_pass(Box::new(LiftControlFlowToSCF)); + pm.run(operation)?; + + // Verify that the function body now consists of a single `scf.if` operation, followed by + // a `cf.switch`, which branches to either a return, or an unreachable. + let output = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_conditional_early_exit_after.hir"] + .assert_eq(&output); + + Ok(()) + } + + #[test] + fn cfg_to_scf_lift_simple_while_loop() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + // Define function body + let mut builder = FunctionBuilder::new(function, &mut builder); + + let loop_header = builder.create_block(); + let n = builder.append_block_param(loop_header, Type::U32, span); + let counter = builder.append_block_param(loop_header, Type::U32, span); + let if_is_zero = builder.create_block(); + let if_is_nonzero = builder.create_block(); + + let block = builder.current_block(); + let input = block.borrow().arguments()[0].upcast(); + + let zero = builder.u32(0, span); + let one = builder.u32(1, span); + builder.br(loop_header, [input, zero], span)?; + + builder.switch_to_block(loop_header); + let is_zero = builder.eq(n, zero, span)?; + builder.cond_br(is_zero, if_is_zero, [], if_is_nonzero, [], span)?; + + builder.switch_to_block(if_is_zero); + builder.ret(Some(counter), span)?; + + builder.switch_to_block(if_is_nonzero); + let n_prime = builder.sub_unchecked(n, one, span)?; + let counter_prime = builder.incr(counter, span)?; + builder.br(loop_header, [n_prime, counter_prime], span)?; + + let operation = function.as_operation_ref(); + + // Run transformation on function body + let input = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_simple_while_loop_before.hir"].assert_eq(&input); + + let mut pm = pass::PassManager::on::(context, pass::Nesting::Implicit); + pm.add_pass(Box::new(LiftControlFlowToSCF)); + pm.run(operation)?; + + // Verify that the function body now consists of a single `scf.if` operation, followed by + // an `builtin.return`. + let output = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_simple_while_loop_after.hir"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn cfg_to_scf_lift_nested_while_loop() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new( + [ + AbiParam::new(Type::from(PointerType::new(Type::U32))), + AbiParam::new(Type::U32), + AbiParam::new(Type::U32), + ], + [AbiParam::new(Type::U32)], + ); + builder(name, signature).unwrap() + }; + + // Define function body for the following pseudocode: + // + // function test(v0: *mut u32, rows: u32, cols: u32) -> u32 { + // let row_offset = 0; + // let sum = 0; + // while row_offset < rows { + // let offset = row_offset * cols; + // let col_offset = 0; + // while col_offset < cols { + // let cell = *(v0 + offset + col_offset); + // col_offset += 1; + // sum += cell; + // } + // row_offset += 1; + // } + // + // return sum; + // } + // + let mut builder = FunctionBuilder::new(function, &mut builder); + + let outer_loop_header = builder.create_block(); + let inner_loop_header = builder.create_block(); + let row_offset = builder.append_block_param(outer_loop_header, Type::U32, span); + let row_sum = builder.append_block_param(outer_loop_header, Type::U32, span); + let col_offset = builder.append_block_param(inner_loop_header, Type::U32, span); + let col_sum = builder.append_block_param(inner_loop_header, Type::U32, span); + let has_more_rows = builder.create_block(); + let no_more_rows = builder.create_block(); + let has_more_columns = builder.create_block(); + let no_more_columns = builder.create_block(); + + let block = builder.current_block(); + let ptr = block.borrow().arguments()[0].upcast(); + let num_rows = block.borrow().arguments()[1].upcast(); + let num_cols = block.borrow().arguments()[2].upcast(); + + let zero = builder.u32(0, span); + builder.br(outer_loop_header, [zero, zero], span)?; + + builder.switch_to_block(outer_loop_header); + let end_of_rows = builder.lt(row_offset, num_rows, span)?; + builder.cond_br(end_of_rows, no_more_rows, [], has_more_rows, [row_sum], span)?; + + builder.switch_to_block(no_more_rows); + builder.ret(Some(row_sum), span)?; + + builder.switch_to_block(has_more_rows); + let offset = builder.mul_unchecked(row_offset, num_cols, span)?; + builder.br(inner_loop_header, [zero, row_sum], span)?; + + builder.switch_to_block(inner_loop_header); + let end_of_cols = builder.lt(col_offset, num_cols, span)?; + builder.cond_br(end_of_cols, no_more_columns, [], has_more_columns, [col_sum], span)?; + + builder.switch_to_block(no_more_columns); + let new_row_offset = builder.incr(row_offset, span)?; + builder.br(outer_loop_header, [new_row_offset, col_sum], span)?; + + builder.switch_to_block(has_more_columns); + let addr_offset = builder.add_unchecked(offset, col_offset, span)?; + let addr = builder.unrealized_conversion_cast(ptr, Type::U32, span)?; + let cell_addr = builder.add_unchecked(addr, addr_offset, span)?; + // This represents a bitcast + let cell_ptr = builder.unrealized_conversion_cast( + cell_addr, + Type::from(PointerType::new(Type::U32)), + span, + )?; + // This represents a load + let cell = builder.unrealized_conversion_cast(cell_ptr, Type::U32, span)?; + let new_col_offset = builder.incr(col_offset, span)?; + let new_sum = builder.add_unchecked(col_sum, cell, span)?; + builder.br(inner_loop_header, [new_col_offset, new_sum], span)?; + + let operation = function.as_operation_ref(); + + // Run transformation on function body + let input = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_nested_while_loop_before.hir"].assert_eq(&input); + + let mut pm = pass::PassManager::on::(context, pass::Nesting::Implicit); + pm.add_pass(Box::new(LiftControlFlowToSCF)); + pm.run(operation)?; + + // Verify that the function body now consists of a single `scf.if` operation, followed by + // an `builtin.return`. + let output = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_nested_while_loop_after.hir"].assert_eq(&output); + + Ok(()) + } + + #[test] + fn cfg_to_scf_lift_multiple_exit_nested_while_loop() -> Result<(), Report> { + let context = Rc::new(Context::default()); + let mut builder = OpBuilder::new(context.clone()); + + let span = SourceSpan::default(); + let function = { + let builder = builder.create::(span); + let name = Ident::new("test".into(), span); + let signature = Signature::new( + [ + AbiParam::new(Type::from(PointerType::new(Type::U32))), + AbiParam::new(Type::U32), + AbiParam::new(Type::U32), + ], + [AbiParam::new(Type::U32)], + ); + builder(name, signature).unwrap() + }; + + // Define function body for the following pseudocode: + // + // function test(v0: *mut u32, rows: u32, cols: u32) -> u32 { + // let row_offset = 0; + // let sum = 0; + // while row_offset < rows { + // let offset = row_offset * cols; + // let col_offset = 0; + // while col_offset < cols { + // let cell = *(v0 + offset + col_offset); + // col_offset += 1; + // let (sum_p, overflowed) = sum.add_overflowing(cell); + // if overflowed { + // return u32::MAX; + // } + // sum += cell; + // } + // row_offset += 1; + // } + // + // return sum; + // } + // + let mut builder = FunctionBuilder::new(function, &mut builder); + + let outer_loop_header = builder.create_block(); + let inner_loop_header = builder.create_block(); + let row_offset = builder.append_block_param(outer_loop_header, Type::U32, span); + let row_sum = builder.append_block_param(outer_loop_header, Type::U32, span); + let col_offset = builder.append_block_param(inner_loop_header, Type::U32, span); + let col_sum = builder.append_block_param(inner_loop_header, Type::U32, span); + let has_more_rows = builder.create_block(); + let no_more_rows = builder.create_block(); + let has_more_columns = builder.create_block(); + let no_more_columns = builder.create_block(); + let has_overflowed = builder.create_block(); + + let block = builder.current_block(); + let ptr = block.borrow().arguments()[0].upcast(); + let num_rows = block.borrow().arguments()[1].upcast(); + let num_cols = block.borrow().arguments()[2].upcast(); + + let zero = builder.u32(0, span); + builder.br(outer_loop_header, [zero, zero], span)?; + + builder.switch_to_block(outer_loop_header); + let more_rows = builder.lt(row_offset, num_rows, span)?; + builder.cond_br(more_rows, has_more_rows, [row_sum], no_more_rows, [], span)?; + + builder.switch_to_block(no_more_rows); + builder.ret(Some(row_sum), span)?; + + builder.switch_to_block(has_more_rows); + let offset = builder.mul_unchecked(row_offset, num_cols, span)?; + builder.br(inner_loop_header, [zero, row_sum], span)?; + + builder.switch_to_block(inner_loop_header); + let more_cols = builder.lt(col_offset, num_cols, span)?; + builder.cond_br(more_cols, has_more_columns, [col_sum], no_more_columns, [], span)?; + + builder.switch_to_block(no_more_columns); + let new_row_offset = builder.incr(row_offset, span)?; + builder.br(outer_loop_header, [new_row_offset, col_sum], span)?; + + builder.switch_to_block(has_more_columns); + let addr_offset = builder.add_unchecked(offset, col_offset, span)?; + let addr = builder.unrealized_conversion_cast(ptr, Type::U32, span)?; + let cell_addr = builder.add_unchecked(addr, addr_offset, span)?; + // This represents a bitcast + let cell_ptr = builder.unrealized_conversion_cast( + cell_addr, + Type::from(PointerType::new(Type::U32)), + span, + )?; + // This represents a load + let cell = builder.unrealized_conversion_cast(cell_ptr, Type::U32, span)?; + let new_col_offset = builder.incr(col_offset, span)?; + let (overflowed, new_sum) = builder.add_overflowing(col_sum, cell, span)?; + builder.cond_br( + overflowed, + has_overflowed, + [], + inner_loop_header, + [new_col_offset, new_sum], + span, + )?; + + builder.switch_to_block(has_overflowed); + builder.ret_imm(midenc_hir::Immediate::U32(u32::MAX), span)?; + + let operation = function.as_operation_ref(); + + // Run transformation on function body + let input = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_before.hir"] + .assert_eq(&input); + + let mut pm = pass::PassManager::on::(context, pass::Nesting::Implicit); + pm.add_pass(Box::new(LiftControlFlowToSCF)); + pm.add_pass(transforms::Canonicalizer::create()); + pm.run(operation)?; + + // Verify that the function body now consists of a single `scf.if` operation, followed by + // an `builtin.return`. + let output = format!("{}", &operation.borrow()); + expect_file!["expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_after.hir"] + .assert_eq(&output); + + Ok(()) + } +} diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_conditional_early_exit_after.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_conditional_early_exit_after.hir new file mode 100644 index 000000000..6e6fb43f9 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_conditional_early_exit_after.hir @@ -0,0 +1,52 @@ +public builtin.function @test(v0: u32, v1: u32, v2: u32, v3: u32) -> u32 { +^block0(v0: u32, v1: u32, v2: u32, v3: u32): + v19 = arith.constant 1 : u32; + v18 = ub.poison u32 : u32; + v14 = arith.constant 0 : u32; + v5 = arith.constant 0 : u32; + v6 = arith.neq v1, v5 : i1; + v23, v24, v25 = scf.if v6 : u32, u32, u32 { + ^block2: + v10 = arith.incr v0 : u32; + scf.yield v10, v18, v14; + } else { + ^block3: + v11 = arith.neq v3, v5 : i1; + v34, v35, v36 = scf.if v11 : u32, u32, u32 { + ^block4: + v12 = arith.incr v3 : u32; + scf.yield v12, v18, v14; + } else { + ^block5: + scf.yield v18, v2, v19; + }; + scf.yield v34, v35, v36; + }; + v30, v31 = scf.index_switch v25 : u32, u32 + case 0 { + ^block1: + v7 = arith.eq v23, v5 : i1; + v8 = arith.zext v7 : u32; + v9 = arith.neq v8, v5 : i1; + v32, v33 = scf.if v9 : u32, u32 { + ^block6: + scf.yield v18, v19; + } else { + ^block7: + scf.yield v23, v14; + }; + scf.yield v32, v33; + } + default { + ^block13: + scf.yield v24, v14; + }; + cf.switch v31 { + case 0 => ^block8(v30) + default => ^block9 + }; +^block8(v13: u32): + builtin.ret v13; +^block9: + ub.unreachable ; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_conditional_early_exit_before.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_conditional_early_exit_before.hir new file mode 100644 index 000000000..1f856d1fc --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_conditional_early_exit_before.hir @@ -0,0 +1,26 @@ +public builtin.function @test(v0: u32, v1: u32, v2: u32, v3: u32) -> u32 { +^block0(v0: u32, v1: u32, v2: u32, v3: u32): + v5 = arith.constant 0 : u32; + v6 = arith.neq v1, v5 : i1; + cf.cond_br v6 ^block2, ^block3; +^block1(v4: u32): + v7 = arith.eq v4, v5 : i1; + v8 = arith.zext v7 : u32; + v9 = arith.neq v8, v5 : i1; + cf.cond_br v9 ^block6, ^block7; +^block2: + v10 = arith.incr v0 : u32; + cf.br ^block1(v10); +^block3: + v11 = arith.neq v3, v5 : i1; + cf.cond_br v11 ^block4, ^block5; +^block4: + v12 = arith.incr v3 : u32; + cf.br ^block1(v12); +^block5: + builtin.ret v2; +^block6: + ub.unreachable ; +^block7: + builtin.ret v4; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_after.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_after.hir new file mode 100644 index 000000000..864589fa6 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_after.hir @@ -0,0 +1,69 @@ +public builtin.function @test(v0: ptr, v1: u32, v2: u32) -> u32 { +^block0(v0: ptr, v1: u32, v2: u32): + v28 = arith.constant 2 : u32; + v27 = ub.poison u32 : u32; + v26 = arith.constant 1 : u32; + v21 = arith.constant 0 : u32; + v143, v144, v145, v146 = scf.while v21, v21 : u32, u32, u32, u32 { + ^block27(v147: u32, v148: u32): + v8 = arith.lt v147, v1 : i1; + v111, v112, v113, v114 = scf.if v8 : u32, u32, u32, u32 { + ^block3: + v9 = arith.mul v147, v2 : u32 #[overflow = unchecked]; + v183, v184, v185, v186, v187, v188, v189 = scf.while v21, v148 : u32, u32, u32, u32, u32, u32, u32 { + ^block31(v190: u32, v191: u32): + v10 = arith.lt v190, v2 : i1; + v177, v178, v179, v180, v181, v182 = scf.if v10 : u32, u32, u32, u32, u32, u32 { + ^block5: + v12 = arith.add v9, v190 : u32 #[overflow = unchecked]; + v13 = builtin.unrealized_conversion_cast v0 : u32; + v14 = arith.add v13, v12 : u32 #[overflow = unchecked]; + v15 = builtin.unrealized_conversion_cast v14 : ptr; + v16 = builtin.unrealized_conversion_cast v15 : u32; + v17 = arith.incr v190 : u32; + v18, v19 = arith.add_overflowing v191, v16 : i1, u32; + v171 = cf.select v18, v27, v17 : u32; + v172 = cf.select v18, v27, v19 : u32; + v173 = cf.select v18, v26, v27 : u32; + v174 = cf.select v18, v21, v27 : u32; + v175 = cf.select v18, v26, v21 : u32; + v176 = cf.select v18, v21, v26 : u32; + scf.yield v171, v172, v173, v174, v175, v176; + } else { + ^block24: + scf.yield v27, v27, v27, v27, v28, v21; + }; + v106 = arith.trunc v182 : i1; + scf.condition v106, v177, v178, v191, v27, v179, v180, v181; + } do { + ^block32(v192: u32, v193: u32, v194: u32, v195: u32, v196: u32, v197: u32, v198: u32): + scf.yield v192, v193; + }; + v115, v116, v117, v118 = scf.index_switch v189 : u32, u32, u32, u32 + case 1 { + ^block22: + scf.yield v186, v186, v187, v188; + } + default { + ^block6: + v11 = arith.incr v147 : u32; + scf.yield v11, v185, v21, v26; + }; + scf.yield v115, v116, v117, v118; + } else { + ^block21: + scf.yield v27, v27, v28, v21; + }; + v51 = arith.trunc v114 : i1; + scf.condition v51, v111, v112, v148, v113; + } do { + ^block28(v149: u32, v150: u32, v151: u32, v152: u32): + scf.yield v149, v150; + }; + v200 = arith.eq v146, v26 : i1; + cf.cond_br v200 ^block7, ^block4; +^block4: + builtin.ret v145; +^block7: + builtin.ret_imm 4294967295; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_before.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_before.hir new file mode 100644 index 000000000..7e4ed12b2 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_multiple_exit_nested_while_loop_before.hir @@ -0,0 +1,30 @@ +public builtin.function @test(v0: ptr, v1: u32, v2: u32) -> u32 { +^block0(v0: ptr, v1: u32, v2: u32): + v7 = arith.constant 0 : u32; + cf.br ^block1(v7, v7); +^block1(v3: u32, v4: u32): + v8 = arith.lt v3, v1 : i1; + cf.cond_br v8 ^block3(v4), ^block4; +^block2(v5: u32, v6: u32): + v10 = arith.lt v5, v2 : i1; + cf.cond_br v10 ^block5(v6), ^block6; +^block3: + v9 = arith.mul v3, v2 : u32 #[overflow = unchecked]; + cf.br ^block2(v7, v4); +^block4: + builtin.ret v4; +^block5: + v12 = arith.add v9, v5 : u32 #[overflow = unchecked]; + v13 = builtin.unrealized_conversion_cast v0 : u32; + v14 = arith.add v13, v12 : u32 #[overflow = unchecked]; + v15 = builtin.unrealized_conversion_cast v14 : ptr; + v16 = builtin.unrealized_conversion_cast v15 : u32; + v17 = arith.incr v5 : u32; + v18, v19 = arith.add_overflowing v6, v16 : i1, u32; + cf.cond_br v18 ^block7, ^block2(v17, v19); +^block6: + v11 = arith.incr v3 : u32; + cf.br ^block1(v11, v6); +^block7: + builtin.ret_imm 4294967295; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_nested_while_loop_after.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_nested_while_loop_after.hir new file mode 100644 index 000000000..cfce15343 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_nested_while_loop_after.hir @@ -0,0 +1,49 @@ +public builtin.function @test(v0: ptr, v1: u32, v2: u32) -> u32 { +^block0(v0: ptr, v1: u32, v2: u32): + v26 = ub.poison u32 : u32; + v25 = arith.constant 1 : u32; + v20 = arith.constant 0 : u32; + v7 = arith.constant 0 : u32; + v34, v35, v36 = scf.while v7, v7, v26 : u32, u32, u32 { + ^block1(v3: u32, v4: u32, v30: u32): + v8 = arith.lt v3, v1 : i1; + v65, v66, v67, v68 = scf.if v8 : u32, u32, u32, u32 { + ^block18: + scf.yield v26, v26, v25, v20; + } else { + ^block3: + v9 = arith.mul v3, v2 : u32 #[overflow = unchecked]; + v55, v56, v57 = scf.while v7, v4, v26 : u32, u32, u32 { + ^block2(v5: u32, v6: u32, v51: u32): + v10 = arith.lt v5, v2 : i1; + v69, v70, v71, v72 = scf.if v10 : u32, u32, u32, u32 { + ^block19: + scf.yield v26, v26, v25, v20; + } else { + ^block5: + v12 = arith.add v9, v5 : u32 #[overflow = unchecked]; + v13 = builtin.unrealized_conversion_cast v0 : u32; + v14 = arith.add v13, v12 : u32 #[overflow = unchecked]; + v15 = builtin.unrealized_conversion_cast v14 : ptr; + v16 = builtin.unrealized_conversion_cast v15 : u32; + v17 = arith.incr v5 : u32; + v18 = arith.add v6, v16 : u32 #[overflow = unchecked]; + scf.yield v17, v18, v20, v25; + }; + v64 = arith.trunc v72 : i1; + scf.condition v64, v69, v70, v6; + } do { + ^block17(v61: u32, v62: u32, v63: u32): + scf.yield v61, v62, v63; + }; + v11 = arith.incr v3 : u32; + scf.yield v11, v57, v20, v25; + }; + v43 = arith.trunc v68 : i1; + scf.condition v43, v65, v66, v4; + } do { + ^block12(v40: u32, v41: u32, v42: u32): + scf.yield v40, v41, v42; + }; + builtin.ret v36; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_nested_while_loop_before.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_nested_while_loop_before.hir new file mode 100644 index 000000000..5f5e41efa --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_nested_while_loop_before.hir @@ -0,0 +1,28 @@ +public builtin.function @test(v0: ptr, v1: u32, v2: u32) -> u32 { +^block0(v0: ptr, v1: u32, v2: u32): + v7 = arith.constant 0 : u32; + cf.br ^block1(v7, v7); +^block1(v3: u32, v4: u32): + v8 = arith.lt v3, v1 : i1; + cf.cond_br v8 ^block4, ^block3(v4); +^block2(v5: u32, v6: u32): + v10 = arith.lt v5, v2 : i1; + cf.cond_br v10 ^block6, ^block5(v6); +^block3: + v9 = arith.mul v3, v2 : u32 #[overflow = unchecked]; + cf.br ^block2(v7, v4); +^block4: + builtin.ret v4; +^block5: + v12 = arith.add v9, v5 : u32 #[overflow = unchecked]; + v13 = builtin.unrealized_conversion_cast v0 : u32; + v14 = arith.add v13, v12 : u32 #[overflow = unchecked]; + v15 = builtin.unrealized_conversion_cast v14 : ptr; + v16 = builtin.unrealized_conversion_cast v15 : u32; + v17 = arith.incr v5 : u32; + v18 = arith.add v6, v16 : u32 #[overflow = unchecked]; + cf.br ^block2(v17, v18); +^block6: + v11 = arith.incr v3 : u32; + cf.br ^block1(v11, v6); +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_conditional_after.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_conditional_after.hir new file mode 100644 index 000000000..6ee59e782 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_conditional_after.hir @@ -0,0 +1,15 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v2 = arith.constant 0 : u32; + v3 = arith.eq v0, v2 : i1; + v8 = scf.if v3 : u32 { + ^block1: + v4 = arith.incr v0 : u32; + scf.yield v4; + } else { + ^block2: + v5 = arith.mul v0, v0 : u32 #[overflow = checked]; + scf.yield v5; + }; + builtin.ret v8; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_conditional_before.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_conditional_before.hir new file mode 100644 index 000000000..2ad7c035d --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_conditional_before.hir @@ -0,0 +1,14 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v2 = arith.constant 0 : u32; + v3 = arith.eq v0, v2 : i1; + cf.cond_br v3 ^block1, ^block2; +^block1: + v4 = arith.incr v0 : u32; + cf.br ^block3(v4); +^block2: + v5 = arith.mul v0, v0 : u32 #[overflow = checked]; + cf.br ^block3(v5); +^block3(v1: u32): + builtin.ret v1; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_while_loop_after.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_while_loop_after.hir new file mode 100644 index 000000000..ce156c761 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_while_loop_after.hir @@ -0,0 +1,27 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v15 = ub.poison u32 : u32; + v14 = arith.constant 1 : u32; + v9 = arith.constant 0 : u32; + v3 = arith.constant 0 : u32; + v4 = arith.constant 1 : u32; + v23, v24, v25 = scf.while v0, v3, v15 : u32, u32, u32 { + ^block1(v1: u32, v2: u32, v19: u32): + v5 = arith.eq v1, v3 : i1; + v33, v34, v35, v36 = scf.if v5 : u32, u32, u32, u32 { + ^block10: + scf.yield v15, v15, v14, v9; + } else { + ^block3: + v6 = arith.sub v1, v4 : u32 #[overflow = unchecked]; + v7 = arith.incr v2 : u32; + scf.yield v6, v7, v9, v14; + }; + v32 = arith.trunc v36 : i1; + scf.condition v32, v33, v34, v2; + } do { + ^block9(v29: u32, v30: u32, v31: u32): + scf.yield v29, v30, v31; + }; + builtin.ret v25; +}; \ No newline at end of file diff --git a/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_while_loop_before.hir b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_while_loop_before.hir new file mode 100644 index 000000000..c744417e2 --- /dev/null +++ b/dialects/scf/src/transforms/expected/cfg_to_scf_lift_simple_while_loop_before.hir @@ -0,0 +1,15 @@ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = arith.constant 0 : u32; + v4 = arith.constant 1 : u32; + cf.br ^block1(v0, v3); +^block1(v1: u32, v2: u32): + v5 = arith.eq v1, v3 : i1; + cf.cond_br v5 ^block2, ^block3; +^block2: + builtin.ret v2; +^block3: + v6 = arith.sub v1, v4 : u32 #[overflow = unchecked]; + v7 = arith.incr v2 : u32; + cf.br ^block1(v6, v7); +}; \ No newline at end of file diff --git a/dialects/ub/CHANGELOG.md b/dialects/ub/CHANGELOG.md new file mode 100644 index 000000000..d94e3d6c6 --- /dev/null +++ b/dialects/ub/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] diff --git a/dialects/ub/Cargo.toml b/dialects/ub/Cargo.toml new file mode 100644 index 000000000..f79d08c87 --- /dev/null +++ b/dialects/ub/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "midenc-dialect-ub" +description = "Miden IR Undefined Behavior Dialect" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["midenc-hir/std"] + +[dependencies] +midenc-hir.workspace = true diff --git a/dialects/ub/src/attributes.rs b/dialects/ub/src/attributes.rs new file mode 100644 index 000000000..c0599025e --- /dev/null +++ b/dialects/ub/src/attributes.rs @@ -0,0 +1,3 @@ +mod poison; + +pub use self::poison::PoisonAttr; diff --git a/dialects/ub/src/attributes/poison.rs b/dialects/ub/src/attributes/poison.rs new file mode 100644 index 000000000..5315362e3 --- /dev/null +++ b/dialects/ub/src/attributes/poison.rs @@ -0,0 +1,66 @@ +use alloc::boxed::Box; + +use midenc_hir::{formatter, AttributeValue, Felt, Immediate, Type}; + +/// Represents the constant value of the 'hir.poison' operation +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct PoisonAttr { + /// The value type that was poisoned + ty: Type, +} + +impl PoisonAttr { + pub fn new(ty: Type) -> Self { + Self { ty } + } + + pub fn ty(&self) -> &Type { + &self.ty + } + + pub fn into_type(self) -> Type { + self.ty + } + + pub fn as_immediate(&self) -> Result { + Ok(match &self.ty { + Type::I1 => Immediate::I1(false), + Type::U8 => Immediate::U8(0xde), + Type::I8 => Immediate::I8(0xdeu8 as i8), + Type::U16 => Immediate::U16(0xdead), + Type::I16 => Immediate::I16(0xdeadu16 as i16), + Type::U32 => Immediate::U32(0xdeadc0de), + Type::I32 => Immediate::I32(0xdeadc0deu32 as i32), + Type::U64 => Immediate::U64(0xdeadc0dedeadc0de), + Type::I64 => Immediate::I64(0xdeadc0dedeadc0deu64 as i64), + Type::Felt => Immediate::Felt(Felt::new(0xdeadc0de)), + Type::U128 => Immediate::U128(0xdeadc0dedeadc0dedeadc0dedeadc0de), + Type::I128 => Immediate::I128(0xdeadc0dedeadc0dedeadc0dedeadc0deu128 as i128), + // We emit a pointer that can never refer to a valid object in memory + Type::Ptr(_) => Immediate::U32(u32::MAX), + ty => return Err(ty.clone()), + }) + } +} + +impl formatter::PrettyPrint for PoisonAttr { + fn render(&self) -> formatter::Document { + use formatter::*; + + display(&self.ty) + } +} + +impl AttributeValue for PoisonAttr { + fn as_any(&self) -> &dyn core::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn core::any::Any { + self + } + + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/dialects/ub/src/builders.rs b/dialects/ub/src/builders.rs new file mode 100644 index 000000000..9568723ac --- /dev/null +++ b/dialects/ub/src/builders.rs @@ -0,0 +1,58 @@ +use midenc_hir::{ + dialects::builtin::FunctionBuilder, Builder, BuilderExt, OpBuilder, SourceSpan, Type, + UnsafeIntrusiveEntityRef, ValueRef, +}; + +use crate::*; + +pub trait UndefinedBehaviorOpBuilder<'f, B: ?Sized + Builder> { + fn poison(&mut self, ty: Type, span: SourceSpan) -> ValueRef { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(PoisonAttr::new(ty)).expect("invalid poison attribute"); + op.borrow().result().as_value_ref() + } + + fn unreachable(&mut self, span: SourceSpan) -> UnsafeIntrusiveEntityRef { + let op_builder = self.builder_mut().create::(span); + op_builder().unwrap() + } + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +impl<'f, B: ?Sized + Builder> UndefinedBehaviorOpBuilder<'f, B> for FunctionBuilder<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + FunctionBuilder::builder(self) + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + FunctionBuilder::builder_mut(self) + } +} + +impl<'f> UndefinedBehaviorOpBuilder<'f, OpBuilder> for &'f mut OpBuilder { + #[inline(always)] + fn builder(&self) -> &OpBuilder { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut OpBuilder { + self + } +} + +impl UndefinedBehaviorOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/dialects/ub/src/lib.rs b/dialects/ub/src/lib.rs new file mode 100644 index 000000000..573343942 --- /dev/null +++ b/dialects/ub/src/lib.rs @@ -0,0 +1,73 @@ +#![no_std] +#![feature(debug_closure_helpers)] +#![feature(unboxed_closures)] +#![feature(fn_traits)] +#![feature(ptr_metadata)] +#![feature(specialization)] +#![allow(incomplete_features)] +#![deny(warnings)] + +extern crate alloc; + +#[cfg(any(feature = "std", test))] +extern crate std; + +use alloc::boxed::Box; + +mod attributes; +mod builders; +mod ops; + +use midenc_hir::{ + AttributeValue, Builder, BuilderExt, Dialect, DialectInfo, DialectRegistration, OperationRef, + SourceSpan, Type, +}; + +pub use self::{attributes::PoisonAttr, builders::UndefinedBehaviorOpBuilder, ops::*}; + +#[derive(Debug)] +pub struct UndefinedBehaviorDialect { + info: DialectInfo, +} + +impl UndefinedBehaviorDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl Dialect for UndefinedBehaviorDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + builder: &mut dyn Builder, + attr: Box, + _ty: &Type, + span: SourceSpan, + ) -> Option { + if let Some(attr) = attr.downcast_ref::() { + let op_builder = builder.create::(span); + return op_builder(attr.clone()).ok().map(|op| op.as_operation_ref()); + } + None + } +} + +impl DialectRegistration for UndefinedBehaviorDialect { + const NAMESPACE: &'static str = "ub"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + } +} diff --git a/dialects/ub/src/ops.rs b/dialects/ub/src/ops.rs new file mode 100644 index 000000000..26fdaa5bf --- /dev/null +++ b/dialects/ub/src/ops.rs @@ -0,0 +1,72 @@ +use alloc::boxed::Box; + +use midenc_hir::{derive::operation, effects::*, smallvec, traits::*, *}; + +use crate::*; + +/// This operation represents a value produced by undefined behavior, i.e. control reaching a +/// program point that is not supposed to be reachable. +/// +/// Any operation performed on a poison value, itself produces poison, and can be folded as such. +#[operation( + dialect = UndefinedBehaviorDialect, + traits(ConstantLike), + implements(InferTypeOpInterface, MemoryEffectOpInterface, Foldable) +)] +pub struct Poison { + #[attr(hidden)] + value: PoisonAttr, + #[result] + result: AnyType, +} + +impl EffectOpInterface for Poison { + fn has_no_effect(&self) -> bool { + true + } + + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } +} + +impl Foldable for Poison { + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult { + results.push(OpFoldResult::Attribute(Box::new(self.value().clone()))); + FoldResult::Ok(()) + } + + fn fold_with( + &self, + _operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + results.push(OpFoldResult::Attribute(Box::new(self.value().clone()))); + FoldResult::Ok(()) + } +} + +impl InferTypeOpInterface for Poison { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let poison_ty = self.value().ty().clone(); + self.result_mut().set_type(poison_ty); + Ok(()) + } +} + +/// This operation represents an assertion that a specific program point should never be dynamically +/// reachable. +/// +/// The specific way this gets lowered is up to the codegen backend and optimization choices. +#[operation( + dialect = UndefinedBehaviorDialect, + traits(Terminator), + implements(MemoryEffectOpInterface) +)] +pub struct Unreachable {} + +impl EffectOpInterface for Unreachable { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new(MemoryEffect::Write)]) + } +} diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 5ceb3864c..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -venv diff --git a/docs/README.txt b/docs/README.txt deleted file mode 100644 index f01fe0afd..000000000 --- a/docs/README.txt +++ /dev/null @@ -1,38 +0,0 @@ -# Miden Compiler Docs - -This directory contains the sources for the [Miden Compiler Documentation](https://docs.polygon.technology/miden/compiler/).). - -All doc files are written in Markdown, and are converted into an online book using [mkdocs](https://squidfunk.github.io/mkdocs-material/). - -## Building the docs - -You can build and view the documentation using two different methods, depending on whether or not -you have `cargo` installed. - -**NOTE:** Both methods described below are expected to be run from the root of the compiler project. - -### Using `cargo` - -To build the docs, use `cargo make docs`. This will place the build output in the `target/docs/site` -directory. - -If you wish to build the docs _and_ run a server to view them, which will reload when changes are -made, use `cargo make serve-docs`. The hostname and port will be displayed in the terminal, but by -default the docs should be available at `http://127.0.0.1:8000`. - -### Using `mkdocs` script - -If you don't have `cargo` installed, you can use this method instead. Again, the commands below are -written with the current working directory being the root of the compiler project, _not_ this -directory. - -To build the docs, use `docs/mkdocs build -d target/docs/site`. This matches the behavior of the -`cargo make docs` command. - -To view the docs in your browser, with reload on change enabled, use `docs/mkdocs serve`. The -hostname and port where the docs can be viewed will be printed to the terminal, but by default this -is `http://127.0.0.1:8000`. - -## License - -MIT diff --git a/docs/design/frontends.md b/docs/design/frontends.md deleted file mode 100644 index e48dfbf47..000000000 --- a/docs/design/frontends.md +++ /dev/null @@ -1,8 +0,0 @@ -# Supported front ends - -## WebAssembly (Wasm) - -TODO - -For the list of the unsupported Wasm core types, instructions and features, see the -[README](https://github.com/0xPolygonMiden/compiler/frontend-wasm/README.md). diff --git a/docs/design/overview.md b/docs/design/overview.md deleted file mode 100644 index fc09cffc9..000000000 --- a/docs/design/overview.md +++ /dev/null @@ -1,18 +0,0 @@ -# Compiler architecture - -This is an index of various design documents for the compiler and its components. Some of these -are planned topics, and some have documentation that hasn't been polished up yet. We'll slowly -start to flesh out the documentation in this section as the compiler matures. - -* Driver -* [Frontends](frontends.md) -* Intermediate Representation (HIR) -* Data Layout -* Inline Assembly -* Analysis -* Rewrite Passes -* Code Generation - * Instruction Scheduling - * Instruction Selection - * Operand Stack Management -* Packaging diff --git a/docs/external/.gitignore b/docs/external/.gitignore new file mode 100644 index 000000000..e4068b5b4 --- /dev/null +++ b/docs/external/.gitignore @@ -0,0 +1,3 @@ +.docusaurus/ +build/ +node_modules/ diff --git a/docs/external/docusaurus.config.ts b/docs/external/docusaurus.config.ts new file mode 100644 index 000000000..ef8eee764 --- /dev/null +++ b/docs/external/docusaurus.config.ts @@ -0,0 +1,129 @@ +import type { Config } from "@docusaurus/types"; +import { themes as prismThemes } from "prism-react-renderer"; + +// If your content lives in docs/src, set DOCS_PATH='src'; else '.' +const DOCS_PATH = + process.env.DOCS_PATH || (require("fs").existsSync("src") ? "src" : "."); + +const config: Config = { + title: "Docs Dev Preview", + url: "http://localhost:3000", + baseUrl: "/", + trailingSlash: false, + + // Minimal classic preset: docs only, autogenerated sidebars, same math plugins as prod + presets: [ + [ + "classic", + { + docs: { + path: DOCS_PATH, // '../docs' is implied because we are already inside docs/ + routeBasePath: "/", // mount docs at root for quick preview + sidebarPath: "./sidebars.ts", + remarkPlugins: [require("remark-math")], + rehypePlugins: [require("rehype-katex")], + versions: { + current: { + label: `unstable`, + }, + }, + }, + blog: false, + pages: false, + theme: { + customCss: "./styles.css", + }, + }, + ], + ], + + plugins: [ + [ + "@cmfcmf/docusaurus-search-local", + { + // whether to index docs pages + indexDocs: true, + + // whether to index blog pages + indexBlog: false, + + // whether to index static pages + indexPages: false, + + // language of your documentation, see next section + language: "en", + + // setting this to "none" will prevent the default CSS to be included. The default CSS + // comes from autocomplete-theme-classic, which you can read more about here: + // https://www.algolia.com/doc/ui-libraries/autocomplete/api-reference/autocomplete-theme-classic/ + style: undefined, + + // lunr.js-specific settings + lunr: { + // When indexing your documents, their content is split into "tokens". + // Text entered into the search box is also tokenized. + // This setting configures the separator used to determine where to split the text into tokens. + // By default, it splits the text at whitespace and dashes. + // + // Note: Does not work for "ja" and "th" languages, since these use a different tokenizer. + tokenizerSeparator: /[\s\-]+/, + // https://lunrjs.com/guides/customising.html#similarity-tuning + // + // This parameter controls the importance given to the length of a document and its fields. This + // value must be between 0 and 1, and by default it has a value of 0.75. Reducing this value + // reduces the effect of different length documents on a term's importance to that document. + b: 0.75, + // This controls how quickly the boost given by a common word reaches saturation. Increasing it + // will slow down the rate of saturation and lower values result in quicker saturation. The + // default value is 1.2. If the collection of documents being indexed have high occurrences + // of words that are not covered by a stop word filter, these words can quickly dominate any + // similarity calculation. In these cases, this value can be reduced to get more balanced results. + k1: 1.2, + // By default, we rank pages where the search term appears in the title higher than pages where + // the search term appears in just the text. This is done by "boosting" title matches with a + // higher value than content matches. The concrete boosting behavior can be controlled by changing + // the following settings. + titleBoost: 5, + contentBoost: 1, + tagsBoost: 3, + parentCategoriesBoost: 2, // Only used when indexing is enabled for categories + }, + }, + ], + ], + + themeConfig: + /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ + { + colorMode: { + defaultMode: "light", + disableSwitch: true, + }, + prism: { + theme: prismThemes.oneLight, + darkTheme: prismThemes.oneDark, + additionalLanguages: ["rust", "solidity", "toml", "yaml"], + }, + navbar: { + logo: { + src: "img/logo.png", + alt: "Miden Logo", + height: 240, + }, + title: "MIDEN", + items: [ + { + type: "docsVersionDropdown", + position: "left", + dropdownActiveClassDisabled: true, + }, + { + href: "https://github.com/0xMiden/", + label: "GitHub", + position: "right", + }, + ], + }, + }, +}; +export default config; diff --git a/docs/external/package-lock.json b/docs/external/package-lock.json new file mode 100644 index 000000000..cd68240f3 --- /dev/null +++ b/docs/external/package-lock.json @@ -0,0 +1,19971 @@ +{ + "name": "@miden/docs-dev", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@miden/docs-dev", + "devDependencies": { + "@cmfcmf/docusaurus-search-local": "^2.0.0", + "@docusaurus/core": "^3", + "@docusaurus/preset-classic": "^3", + "rehype-katex": "^7", + "remark-math": "^6" + } + }, + "node_modules/@ai-sdk/gateway": { + "version": "1.0.30", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-1.0.30.tgz", + "integrity": "sha512-QdrSUryr/CLcsCISokLHOImcHj1adGXk1yy4B3qipqLhcNc33Kj/O/3crI790Qp85oDx7sc4vm7R4raf9RA/kg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", + "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.10.tgz", + "integrity": "sha512-T1gZ76gEIwffep6MWI0QNy9jgoybUHE7TRaHB5k54K8mF91ciGFlbtCGxDYhMH3nCRergKwYFIDeFF0hJSIQHQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/react": { + "version": "2.0.54", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.54.tgz", + "integrity": "sha512-wbszephe+WR7y8DXwYN/SMr56pwU1N505/h3fOTz4NPHCW0sex6LzZ7DuhFR0J6ij3n3sLA11aEliFEa6B2FiA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "3.0.10", + "ai": "5.0.54", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.25.76 || ^4.1.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.4.0.tgz", + "integrity": "sha512-N0blWT/C0KOZ/OJ9GXBX66odJZlrYjMj3M+01y8ob1mjBFnBaBo7gOCyHBDQy60+H4pJXp3pSGlJOqJIueBH+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" + } + }, + "node_modules/@algolia/autocomplete-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-js/-/autocomplete-js-1.19.4.tgz", + "integrity": "sha512-ZkwsuTTIEuw+hbsIooMrNLvTVulUSSKqJT3ZeYYd//kA5fHaFf2/T0BDmd9qSGxZRhT5WS8AJYjFARLmj5x08g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.19.4", + "@algolia/autocomplete-preset-algolia": "1.19.4", + "@algolia/autocomplete-shared": "1.19.4", + "htm": "^3.1.1", + "preact": "^10.13.2" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.5.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-js/node_modules/@algolia/autocomplete-core": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.4.tgz", + "integrity": "sha512-yVwXLrfwQ3dAndY12j1pfa0oyC5hTDv+/dgwvVHj57dY3zN6PbAmcHdV5DOOdGJrCMXff+fsPr8G2Ik8zWOPTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.4", + "@algolia/autocomplete-shared": "1.19.4" + } + }, + "node_modules/@algolia/autocomplete-js/node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.4.tgz", + "integrity": "sha512-K6TQhTKxx0Es1ZbjlAQjgm/QLDOtKvw23MX0xmpvO7AwkmlmaEXo2PwHdVSs3Bquv28CkO2BYKks7jVSIdcXUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.4" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-js/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.4.tgz", + "integrity": "sha512-V7tYDgRXP0AqL4alwZBWNm1HPWjJvEU94Nr7Qa2cuPcIAbsTAj7M/F/+Pv/iwOWXl3N7tzVzNkOWm7sX6JT1SQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.2" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.19.4.tgz", + "integrity": "sha512-WhX4mYosy7yBDjkB6c/ag+WKICjvV2fqQv/+NWJlpvnk2JtMaZByi73F6svpQX945J+/PxpQe8YIRBZHuYsLAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.4" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.4.tgz", + "integrity": "sha512-V7tYDgRXP0AqL4alwZBWNm1HPWjJvEU94Nr7Qa2cuPcIAbsTAj7M/F/+Pv/iwOWXl3N7tzVzNkOWm7sX6JT1SQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-theme-classic": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-theme-classic/-/autocomplete-theme-classic-1.19.4.tgz", + "integrity": "sha512-/qE8BETNFbul4WrrUyBYgaaKcgFPk0Px9FDKADnr3HlIkXquRpcFHTxXK16jdwXb33yrcXaAVSQZRfUUSSnxVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.25.2.tgz", + "integrity": "sha512-tA1rqAafI+gUdewjZwyTsZVxesl22MTgLWRKt1+TBiL26NiKx7SjRqTI3pzm8ngx1ftM5LSgXkVIgk2+SRgPTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.25.2" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.25.2.tgz", + "integrity": "sha512-E+aZwwwmhvZXsRA1+8DhH2JJIwugBzHivASTnoq7bmv0nmForLyH7rMG5cOTiDK36DDLnKq1rMGzxWZZ70KZag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.25.2.tgz", + "integrity": "sha512-KYcenhfPKgR+WJ6IEwKVEFMKKCWLZdnYuw08+3Pn1cxAXbJcTIKjoYgEXzEW6gJmDaau2l55qNrZo6MBbX7+sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.25.2" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.38.0.tgz", + "integrity": "sha512-15d6zv8vtj2l9pnnp/EH7Rhq3/snCCHRz56NnX6xIUPrbJl5gCsIYXAz8C2IEkwOpoDb0r5G6ArY2gKdVMNezw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.25.2.tgz", + "integrity": "sha512-IfRGhBxvjli9mdexrCxX2N4XT9NBN3tvZK5zCaL8zkDcgsthiM9WPvGIZS/pl/FuXB7hA0lE5kqOzsQDP6OmGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.25.2.tgz", + "integrity": "sha512-HXX8vbJPYW29P18GxciiwaDpQid6UhpPP9nW9WE181uGUgFhyP5zaEkYWf9oYBrjMubrGwXi5YEzJOz6Oa4faA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@algolia/client-account/node_modules/@algolia/client-search": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.25.2.tgz", + "integrity": "sha512-pO/LpVnQlbJpcHRk+AroWyyFnh01eOlO6/uLZRUmYvr/hpKZKxI6n7ufgTawbo0KrAu2CePfiOkStYOmDuRjzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.38.0.tgz", + "integrity": "sha512-jJIbYAhYvTG3+gEAP5Q5Dp6PFJfUR+atz5rsqm5KjAKK+faLFdHJbM2IbOo0xdyGd+SH259MzfQKLJ9mZZ27dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.38.0.tgz", + "integrity": "sha512-aMCXzVPGJTeQnVU3Sdf30TfMN2+QyWcjfPTCCHyqVVgjPipb6RnK40aISGoO+rlYjh9LunDsNVFLwv+JEIF8bQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.38.0.tgz", + "integrity": "sha512-4c3FbpMiJX+VcaAj0rYaQdTLS/CkrdOn4hW+5y1plPov7KC7iSHai/VBbirmHuAfW1hVPCIh1w/4erKKTKuo+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.38.0.tgz", + "integrity": "sha512-FzLs6c8TBL4FSgNfnH2NL7O33ktecGiaKO4ZFG51QYORUzD5d6YwB9UBteaIYu/sgFoEdY57diYU4vyBH8R6iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.38.0.tgz", + "integrity": "sha512-7apiahlgZLvOqrh0+hAYAp/UWjqz6AfSJrCwnsoQNzgIT09dLSPIKREelkuQeUrKy38vHWWpSQE3M0zWSp/YrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.38.0.tgz", + "integrity": "sha512-PTAFMJOpVtJweExEYYgdmSCC6n4V/R+ctDL3fRQy77ulZM/p+zMLIQC9c7HCQE1zqpauvVck3f2zYSejaUTtrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/events": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@algolia/events/-/events-4.0.1.tgz", + "integrity": "sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/ingestion": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.38.0.tgz", + "integrity": "sha512-qGSUGgceJHGyJLZ06bFLwVe2Tpf9KwabmoBjFvFscVmMmU5scKya6voCYd9bdX7V0Xy1qya9MGbmTm4zlLuveQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.25.2.tgz", + "integrity": "sha512-aUXpcodoIpLPsnVc2OHgC9E156R7yXWLW2l+Zn24Cyepfq3IvmuVckBvJDpp7nPnXkEzeMuvnVxQfQsk+zP/BA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/logger-console": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.25.2.tgz", + "integrity": "sha512-H3Y+UB0Ty0htvMJ6zDSufhFTSDlg3Pyj3AXilfDdDRcvfhH4C/cJNVm+CTaGORxL5uKABGsBp+SZjsEMTyAunQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/logger-common": "4.25.2" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.38.0.tgz", + "integrity": "sha512-VnCtAUcHirvv/dDHg9jK1Z5oo4QOC5FKDxe40x8qloru2qDcjueT34jiAsB0gRos3VWf9v4iPSYTqMIFOcADpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.38.0.tgz", + "integrity": "sha512-fqgeU9GqxQorFUeGP4et1MyY28ccf9PCeciHwDPSbPYYiTqBItHdUIiytsNpjC5Dnc0RWtuXWCltLwSw9wN/bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.38.0.tgz", + "integrity": "sha512-nAUKbv4YQIXbpPi02AQvSPisD5FDDbT8XeYSh9HFoYP0Z3IpBLLDg7R4ahPvzd7gGsVKgEbXzRPWESXSji5yIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.25.2.tgz", + "integrity": "sha512-Q4wC3sgY0UFjV3Rb3icRLTpPB5/M44A8IxzJHM9PNeK1T3iX7X/fmz7ATUYQYZTpwHCYATlsQKWiTpql1hHjVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.38.0.tgz", + "integrity": "sha512-bkuAHaadC6OxJd3SVyQQnU1oJ9G/zdCqua7fwr1tJDrA/v7KzeS5np4/m6BuRUpTgVgFZHSewGnMcgj9DLBoaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.38.0.tgz", + "integrity": "sha512-yHDKZTnMPR3/4bY0CVC1/uRnnbAaJ+pctRuX7G/HflBkKOrnUBDEGtQQHzEfMz2FHZ/tbCL+Q9r6mvwTSGp8nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.25.2.tgz", + "integrity": "sha512-yw3RLHWc6V+pbdsFtq8b6T5bJqLDqnfKWS7nac1Vzcmgvs/V/Lfy7/6iOF9XRilu5aBDOBHoP1SOeIDghguzWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-common": "4.25.2", + "@algolia/logger-common": "4.25.2", + "@algolia/requester-common": "4.25.2" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", + "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "regexpu-core": "^6.2.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.4" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", + "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", + "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", + "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", + "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", + "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", + "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", + "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-constant-elements": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.27.1.tgz", + "integrity": "sha512-edoidOjl/ZxvYo4lSBOQGDSyToYVkTAwyVoa2tkuYTSmjrB1+uAedoL5iROVLXkxH+vRgA7uP4tMg2pUJpZ3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", + "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", + "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", + "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", + "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", + "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", + "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", + "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.0.tgz", + "integrity": "sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", + "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-react": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.27.1.tgz", + "integrity": "sha512-oJHWh2gLhU9dW9HHr42q0cI0/iHHXTLGe39qvpAZZzagHy0MzYLCnCVV0symeRvzmjHyVU7mw2K06E6u/JwbhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-react-display-name": "^7.27.1", + "@babel/plugin-transform-react-jsx": "^7.27.1", + "@babel/plugin-transform-react-jsx-development": "^7.27.1", + "@babel/plugin-transform-react-pure-annotations": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", + "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.43.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cmfcmf/docusaurus-search-local/-/docusaurus-search-local-2.0.0.tgz", + "integrity": "sha512-WfgqJN5VXeyhcAVTF4isXFNCvZXdOlQZIsXbgKPEmXcXykV+YfPX4UgKJ4J/MuKtaKQ8SGjfeXhg2clhdmwHVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-js": "^1.8.2", + "@algolia/autocomplete-theme-classic": "^1.8.2", + "@algolia/client-search": "^4.12.0", + "algoliasearch": "^4.12.0", + "cheerio": "^1.0.0", + "clsx": "^2.0.0", + "lunr-languages": "^1.4.0", + "mark.js": "^8.11.1", + "tslib": "^2.6.3" + }, + "peerDependencies": { + "@docusaurus/core": "^3.0.0", + "nodejieba": "^2.5.0 || ^3.0.0", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "nodejieba": { + "optional": true + } + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-analytics": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.25.2.tgz", + "integrity": "sha512-4Yxxhxh+XjXY8zPyo+h6tQuyoJWDBn8E3YLr8j+YAEy5p+r3/5Tp+ANvQ+hNaQXbwZpyf5d4ViYOBjJ8+bWNEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-common": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.25.2.tgz", + "integrity": "sha512-HXX8vbJPYW29P18GxciiwaDpQid6UhpPP9nW9WE181uGUgFhyP5zaEkYWf9oYBrjMubrGwXi5YEzJOz6Oa4faA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-personalization": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.25.2.tgz", + "integrity": "sha512-K81PRaHF77mHv2u8foWTHnIf5c+QNf/SnKNM7rB8JPi7TMYi4E5o2mFbgdU1ovd8eg9YMOEAuLkl1Nz1vbM3zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/client-search": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.25.2.tgz", + "integrity": "sha512-pO/LpVnQlbJpcHRk+AroWyyFnh01eOlO6/uLZRUmYvr/hpKZKxI6n7ufgTawbo0KrAu2CePfiOkStYOmDuRjzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/recommend": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.25.2.tgz", + "integrity": "sha512-puRrGeXwAuVa4mLdvXvmxHRFz9MkcCOLPcjz7MjU4NihlpIa+lZYgikJ7z0SUAaYgd6l5Bh00hXiU/OlX5ffXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.25.2", + "@algolia/cache-common": "4.25.2", + "@algolia/cache-in-memory": "4.25.2", + "@algolia/client-common": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/logger-common": "4.25.2", + "@algolia/logger-console": "4.25.2", + "@algolia/requester-browser-xhr": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/requester-node-http": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/requester-browser-xhr": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.25.2.tgz", + "integrity": "sha512-aAjfsI0AjWgXLh/xr9eoR8/9HekBkIER3bxGoBf9d1XWMMoTo/q92Da2fewkxwLE6mla95QJ9suJGOtMOewXXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/@algolia/requester-node-http": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.25.2.tgz", + "integrity": "sha512-Ja/FYB7W9ZM+m8UrMIlawNUAKpncvb9Mo+D8Jq5WepGTUyQ9CBYLsjwxv9O8wbj3TSWqTInf4uUBJ2FKR8G7xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/requester-common": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/algoliasearch": { + "version": "4.25.2", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.25.2.tgz", + "integrity": "sha512-lYx98L6kb1VvXypbPI7Z54C4BJB2VT5QvOYthvPq6/POufZj+YdyeZSKjoLBKHJgGmYWQTHOKtcCTdKf98WOCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.25.2", + "@algolia/cache-common": "4.25.2", + "@algolia/cache-in-memory": "4.25.2", + "@algolia/client-account": "4.25.2", + "@algolia/client-analytics": "4.25.2", + "@algolia/client-common": "4.25.2", + "@algolia/client-personalization": "4.25.2", + "@algolia/client-search": "4.25.2", + "@algolia/logger-common": "4.25.2", + "@algolia/logger-console": "4.25.2", + "@algolia/recommend": "4.25.2", + "@algolia/requester-browser-xhr": "4.25.2", + "@algolia/requester-common": "4.25.2", + "@algolia/requester-node-http": "4.25.2", + "@algolia/transporter": "4.25.2" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/cheerio": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.1.2.tgz", + "integrity": "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@cmfcmf/docusaurus-search-local/node_modules/htmlparser2": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz", + "integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.1", + "entities": "^6.0.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", + "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/postcss-alpha-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", + "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", + "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-color-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", + "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-function-display-p3-linear": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", + "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-function": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", + "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", + "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-content-alt-text": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", + "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-contrast-color-function": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", + "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-exponential-functions": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", + "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-font-format-keywords": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", + "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gamut-mapping": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", + "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-gradients-interpolation-method": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", + "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-hwb-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", + "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-ic-unit": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", + "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-initial": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", + "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", + "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-light-dark-function": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", + "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-float-and-clear": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", + "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overflow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", + "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-overscroll-behavior": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", + "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-resize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", + "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-logical-viewport-units": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", + "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-minmax": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", + "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", + "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-nested-calc": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", + "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-normalize-display-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", + "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-oklab-function": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", + "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-progressive-custom-properties": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", + "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-random-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", + "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-relative-color-syntax": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", + "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", + "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@csstools/postcss-sign-functions": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", + "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-stepped-value-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", + "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-text-decoration-shorthand": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", + "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-trigonometric-functions": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", + "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/postcss-unset-value": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", + "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@csstools/utilities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", + "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.1.0.tgz", + "integrity": "sha512-nuNKGjHj/FQeWgE9t+i83QD/V67QiaAmGY7xS9TVCRUiCqSljOgIKlsLoQZKKVwEG8f+OWKdznzZkJxGZ7d06A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/react": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.1.0.tgz", + "integrity": "sha512-4GHI7TT3sJZ2Vs4Kjadv7vAkMrTsJqHvzvxO3JA7UT8iPRKaDottG5o5uNshPWhVVaBYPC35Ukf8bfCotGpjSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ai-sdk/react": "^2.0.30", + "@algolia/autocomplete-core": "1.19.2", + "@docsearch/css": "4.1.0", + "ai": "^5.0.30", + "algoliasearch": "^5.28.0", + "marked": "^16.3.0", + "zod": "^4.1.8" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 20.0.0", + "react": ">= 16.8.0 < 20.0.0", + "react-dom": ">= 16.8.0 < 20.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docusaurus/babel": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.0.tgz", + "integrity": "sha512-QcZ+Rey0OvlLK9SPN4/+VWL+ut/tuADVdunA1fmC96fytdYjatdJrcw1koYdp/c+3k6lVYlwg9DDVNDecyLCAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-runtime": "^7.25.9", + "@babel/preset-env": "^7.25.9", + "@babel/preset-react": "^7.25.9", + "@babel/preset-typescript": "^7.25.9", + "@babel/runtime": "^7.25.9", + "@babel/runtime-corejs3": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@docusaurus/logger": "3.9.0", + "@docusaurus/utils": "3.9.0", + "babel-plugin-dynamic-import-node": "^2.3.3", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/bundler": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.0.tgz", + "integrity": "sha512-HaRLSmiwnJQ3uHBV3rd/BRDM9S/nHAshRk54djRZ+RX9ze4ONuFAovdD5es20ZDj7PRTjo38GVnBtHvuL/SwsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.25.9", + "@docusaurus/babel": "3.9.0", + "@docusaurus/cssnano-preset": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "babel-loader": "^9.2.1", + "clean-css": "^5.3.3", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.11.0", + "css-minimizer-webpack-plugin": "^5.0.1", + "cssnano": "^6.1.2", + "file-loader": "^6.2.0", + "html-minifier-terser": "^7.2.0", + "mini-css-extract-plugin": "^2.9.2", + "null-loader": "^4.0.1", + "postcss": "^8.5.4", + "postcss-loader": "^7.3.4", + "postcss-preset-env": "^10.2.1", + "terser-webpack-plugin": "^5.3.9", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "webpack": "^5.95.0", + "webpackbar": "^6.0.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/faster": "*" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } + } + }, + "node_modules/@docusaurus/core": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.0.tgz", + "integrity": "sha512-sEJ4MW/zuh1MfPORCRbSwnW/PjsVmOigWwBU6clcxm221/CNdnI/XqgfBrl2jj/zocSdNoQM8E3IP2W8dygi6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.9.0", + "@docusaurus/bundler": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "boxen": "^6.2.1", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cli-table3": "^0.6.3", + "combine-promises": "^1.1.0", + "commander": "^5.1.0", + "core-js": "^3.31.1", + "detect-port": "^1.5.1", + "escape-html": "^1.0.3", + "eta": "^2.2.0", + "eval": "^0.1.8", + "execa": "5.1.1", + "fs-extra": "^11.1.1", + "html-tags": "^3.3.1", + "html-webpack-plugin": "^5.6.0", + "leven": "^3.1.0", + "lodash": "^4.17.21", + "open": "^8.4.0", + "p-map": "^4.0.0", + "prompts": "^2.4.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", + "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-router": "^5.3.4", + "react-router-config": "^5.1.1", + "react-router-dom": "^5.3.4", + "semver": "^7.5.4", + "serve-handler": "^6.1.6", + "tinypool": "^1.0.2", + "tslib": "^2.6.0", + "update-notifier": "^6.0.2", + "webpack": "^5.95.0", + "webpack-bundle-analyzer": "^4.10.2", + "webpack-dev-server": "^5.2.2", + "webpack-merge": "^6.0.1" + }, + "bin": { + "docusaurus": "bin/docusaurus.mjs" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@mdx-js/react": "^3.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/cssnano-preset": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.0.tgz", + "integrity": "sha512-prCJXUcoJZBlovJzSFkfnfWr1gXd53VZfE+17fIpUWS6Zioc7WE4FPoXPi5ldAGZ8brhXre5xQ8NWDE90XP9yw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-advanced": "^6.1.2", + "postcss": "^8.5.4", + "postcss-sort-media-queries": "^5.2.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/logger": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.0.tgz", + "integrity": "sha512-lDtThsocWTF8ZrVF01ltfctA/xgtD/3oXWqEkKIDzF4fCWsWXH7hC4LCqT23xSuxZTIo8N+y02XSPvA/8DLInw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/mdx-loader": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.0.tgz", + "integrity": "sha512-9bfJYdkZFE+REwevkT4CYdTJ2f6ydgkbUFylkzTXrNGtBXtx25TRJGdn2cVzm3eVkeWdJrGkG/ypwrIWnbu5UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@mdx-js/mdx": "^3.0.0", + "@slorber/remark-comment": "^1.0.0", + "escape-html": "^1.0.3", + "estree-util-value-to-estree": "^3.0.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "image-size": "^2.0.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-raw": "^7.0.0", + "remark-directive": "^3.0.0", + "remark-emoji": "^4.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.0", + "stringify-object": "^3.3.0", + "tslib": "^2.6.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0", + "url-loader": "^4.1.1", + "vfile": "^6.0.1", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/module-type-aliases": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.0.tgz", + "integrity": "sha512-0ucYr79FpTCebN+l3ZlKqoW7HbMqSKT8JdsEg6QoUtxD3C7trF6KZiK/X6Yh+xekO1w3zzXYgPcIYTF2DV81tQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.9.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "@types/react-router-dom": "*", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/@docusaurus/plugin-content-blog": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.0.tgz", + "integrity": "sha512-XZXJ/rQgi2jT0XWNXOnSKooJgtGHPzkjaBjww6K9PD+YevNMTP9U8Y5sA7cLA5Bwuqrpee4i8NO3tSrjhhDW5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "cheerio": "1.0.0-rc.12", + "feed": "^4.2.2", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "srcset": "^4.0.0", + "tslib": "^2.6.0", + "unist-util-visit": "^5.0.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-docs": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.0.tgz", + "integrity": "sha512-PP+iDJg+lj4cn/7GbbmiguaQ8OX08YxnzQ17KqRC4ufJm11jdyXD33wA7vVtbeG/BkkgkiB/K7YyPHCPwmfVhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/module-type-aliases": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@types/react-router-config": "^5.0.7", + "combine-promises": "^1.1.0", + "fs-extra": "^11.1.1", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "schema-dts": "^1.1.2", + "tslib": "^2.6.0", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-content-pages": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.0.tgz", + "integrity": "sha512-ngetCpAZuivlaHC0l8a5KoK6PQWGuZ8742VwK7dbXeIW0Y70P4xwuocBdsCIQ9J6nB9rlTXRYMpNVyYyCpD7/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "fs-extra": "^11.1.1", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-css-cascade-layers": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.0.tgz", + "integrity": "sha512-giPTCjEzeaamMn8EHY/oDvsPDxF5ei1/q5lPUFQLldbc65jFQ1k6pPwKjtOznYy3TSfClCF1F1DNpYWIx7B5LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/plugin-debug": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.9.0.tgz", + "integrity": "sha512-DuFOZya+bcrYiL54qBEn2rdKuoWWNyOV5IoHI2MURLzwuYaKu/J9Gi618XUsj3N3qvqG2uxWQiXZcs9ecfMadA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "fs-extra": "^11.1.1", + "react-json-view-lite": "^2.3.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-analytics": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.0.tgz", + "integrity": "sha512-mUXvpasTDR2pXdnkkhGxEgB9frVAvLGc+T3fp6SGT2F+YoEQtjcmz9D43zubQawLn+W1KEhoj+vPusYe+HAl+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-gtag": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.0.tgz", + "integrity": "sha512-L4tCKYnmcyLV6VQs7XWQ3r7YSllagAU2GylZzdvz7NRMcXE12uSW5MCC2aSltbk09MYlqrYv1Ntp+ESsMvptYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@types/gtag.js": "^0.0.12", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-google-tag-manager": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.0.tgz", + "integrity": "sha512-+jWO3tkrvsMUKQ69KTIj9ZBf8sKY5kodLcP4yIaEkPzfWq9IEpE+ekQCtFWlrAmkJUtSxbjHK6HNZZkUNwwq7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-sitemap": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.0.tgz", + "integrity": "sha512-QOyLooWuF+On4q2RDGVZtKY0tlfdZwD9e/p7g1sJLUfOwN518V2Bo+kZtU82Or42SCKjyJ0lhSqAUOZfbeFhFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "fs-extra": "^11.1.1", + "sitemap": "^7.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/plugin-svgr": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.0.tgz", + "integrity": "sha512-pUZIfnhFtAYDmDwimFiBY+sxNUigyJnKbCwI9pTiXr3uGp43CsSsN8gwl/i8jBmqfsZzvNnGZNxc75wy9v6RXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@svgr/core": "8.1.0", + "@svgr/webpack": "^8.1.0", + "tslib": "^2.6.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/preset-classic": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.9.0.tgz", + "integrity": "sha512-nLoiDxf8bDNNxDSZ28+pFfSfT+QRi08Pn2K0zIvbjkM/X/otMs4ho0K8+2FpoLOoGApifaSuNfJXpGYnQV3rGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/plugin-content-blog": "3.9.0", + "@docusaurus/plugin-content-docs": "3.9.0", + "@docusaurus/plugin-content-pages": "3.9.0", + "@docusaurus/plugin-css-cascade-layers": "3.9.0", + "@docusaurus/plugin-debug": "3.9.0", + "@docusaurus/plugin-google-analytics": "3.9.0", + "@docusaurus/plugin-google-gtag": "3.9.0", + "@docusaurus/plugin-google-tag-manager": "3.9.0", + "@docusaurus/plugin-sitemap": "3.9.0", + "@docusaurus/plugin-svgr": "3.9.0", + "@docusaurus/theme-classic": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/theme-search-algolia": "3.9.0", + "@docusaurus/types": "3.9.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-classic": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.0.tgz", + "integrity": "sha512-RToUIabJOyX41nMIxkFn8LPeA+uHgySzyd6Ak/gsINqWHHTLDMoYPxBUmNm3S+okcfuMI54kNYvD6TY+6TIYDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/module-type-aliases": "3.9.0", + "@docusaurus/plugin-content-blog": "3.9.0", + "@docusaurus/plugin-content-docs": "3.9.0", + "@docusaurus/plugin-content-pages": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/theme-translations": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "@mdx-js/react": "^3.0.0", + "clsx": "^2.0.0", + "infima": "0.2.0-alpha.45", + "lodash": "^4.17.21", + "nprogress": "^0.2.0", + "postcss": "^8.5.4", + "prism-react-renderer": "^2.3.0", + "prismjs": "^1.29.0", + "react-router-dom": "^5.3.4", + "rtlcss": "^4.1.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-common": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.0.tgz", + "integrity": "sha512-pqNoQgttIpk7Ndm6N8OGbhi+1wBIQXQPYM7bPf1HDraXfvVpOzhcDty1yyK4coPWl0M7NxednZvKw4atfQ70Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/mdx-loader": "3.9.0", + "@docusaurus/module-type-aliases": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router-config": "*", + "clsx": "^2.0.0", + "parse-numeric-range": "^1.3.0", + "prism-react-renderer": "^2.3.0", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "@docusaurus/plugin-content-docs": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-search-algolia": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.0.tgz", + "integrity": "sha512-nbY7ZJVA10kTiBLJtscxK1aECeYvYFz+Sno9PkCE9KeFXqRDr6omtNmLVkbvyl4b6xgz+6yOIBdO/idLPVDpWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "^3.9.0 || ^4.1.0", + "@docusaurus/core": "3.9.0", + "@docusaurus/logger": "3.9.0", + "@docusaurus/plugin-content-docs": "3.9.0", + "@docusaurus/theme-common": "3.9.0", + "@docusaurus/theme-translations": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-validation": "3.9.0", + "algoliasearch": "^5.37.0", + "algoliasearch-helper": "^3.26.0", + "clsx": "^2.0.0", + "eta": "^2.2.0", + "fs-extra": "^11.1.1", + "lodash": "^4.17.21", + "tslib": "^2.6.0", + "utility-types": "^3.10.0" + }, + "engines": { + "node": ">=20.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/theme-translations": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.0.tgz", + "integrity": "sha512-4HUELBsE+rhtlnR1MsaNB9nJXPFZANeDQa5If1GfFVlis5mWUfdmXmbGangR7PfpK2tc56UETMtzjKrX5L5UWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-extra": "^11.1.1", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/types": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.0.tgz", + "integrity": "sha512-0klJLhHFHqkYoxIVp1LD7dnU1ASRTfSX+HFDiELOdz+YQUkOSfuU5hDa46zD8bLxrYffCb8FtJI7Z6BWAmVodg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mdx-js/mdx": "^3.0.0", + "@types/history": "^4.7.11", + "@types/mdast": "^4.0.2", + "@types/react": "*", + "commander": "^5.1.0", + "joi": "^17.9.2", + "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", + "utility-types": "^3.10.0", + "webpack": "^5.95.0", + "webpack-merge": "^5.9.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@docusaurus/types/node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@docusaurus/utils": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.0.tgz", + "integrity": "sha512-wpVRQbDhXxqbb1llhkpu++aD4UdHHQ5M7J8DmJELDphlwmpI44TdS5elQZOsjzPfGyITZyQLekcDXjyteJ0/bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.9.0", + "@docusaurus/types": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "escape-string-regexp": "^4.0.0", + "execa": "5.1.1", + "file-loader": "^6.2.0", + "fs-extra": "^11.1.1", + "github-slugger": "^1.5.0", + "globby": "^11.1.0", + "gray-matter": "^4.0.3", + "jiti": "^1.20.0", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "micromatch": "^4.0.5", + "p-queue": "^6.6.2", + "prompts": "^2.4.2", + "resolve-pathname": "^3.0.0", + "tslib": "^2.6.0", + "url-loader": "^4.1.1", + "utility-types": "^3.10.0", + "webpack": "^5.88.1" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-common": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.0.tgz", + "integrity": "sha512-zpmzRn2mniMnrx8ZEYyyDsr0/7EksVgUXL9IuODp0DSK+R19nDGCY7w2NaMGRmGnrQQKsT3t0NDZzBk0V6N9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/types": "3.9.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@docusaurus/utils-validation": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.0.tgz", + "integrity": "sha512-xpVLdFPpsE5dYuE7hOtghccCrRWRhM6tUQ4YpfSy5snCDWgROITG5Mj22fGstd/HBqTzKD8NFs7qPPs42qjgWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docusaurus/logger": "3.9.0", + "@docusaurus/utils": "3.9.0", + "@docusaurus/utils-common": "3.9.0", + "fs-extra": "^11.2.0", + "joi": "^17.9.2", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "tslib": "^2.6.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/buffers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.0.0.tgz", + "integrity": "sha512-NDigYR3PHqCnQLXYyoLbnEdzMMvzeiCWo1KOut7Q0CoIqg9tUAPKJ1iq/2nFhc5kZtexzutNY0LFjdwWL3Dw3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/codegen": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", + "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.14.0.tgz", + "integrity": "sha512-LpWbYgVnKzphN5S6uss4M25jJ/9+m6q6UJoeN6zTkK4xAGhKsiBRPVeF7OYMWonn5repMQbE5vieRXcMUrKDKw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/base64": "^1.1.2", + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/json-pointer": "^1.0.1", + "@jsonjoy.com/util": "^1.9.0", + "hyperdyperid": "^1.2.0", + "thingies": "^2.5.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pointer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", + "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/codegen": "^1.0.0", + "@jsonjoy.com/util": "^1.9.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", + "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/buffers": "^1.0.0", + "@jsonjoy.com/codegen": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", + "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "acorn": "^8.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", + "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true, + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", + "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@slorber/remark-comment": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", + "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^1.0.0", + "micromark-util-character": "^1.1.0", + "micromark-util-symbol": "^1.0.1" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/webpack": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/webpack/-/webpack-8.1.0.tgz", + "integrity": "sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@babel/plugin-transform-react-constant-elements": "^7.21.3", + "@babel/preset-env": "^7.20.2", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.21.0", + "@svgr/core": "8.1.0", + "@svgr/plugin-jsx": "8.1.0", + "@svgr/plugin-svgo": "8.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/gtag.js": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", + "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-proxy": { + "version": "1.17.16", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", + "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.12.0" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", + "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", + "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-config": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", + "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "^5.1.0" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/send": { + "version": "0.17.5", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/address": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", + "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ai": { + "version": "5.0.54", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.54.tgz", + "integrity": "sha512-eM3EH4VVCWRMfs17r8HF8RtCN/+vBdpWOQoHSVooIfB0BZerOHyrktrVoDP6G6xatUzGLTvJT3rMKLkbPTLPBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/gateway": "1.0.30", + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.10", + "@opentelemetry/api": "1.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/algoliasearch": { + "version": "5.38.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.38.0.tgz", + "integrity": "sha512-8VJKIzheeI9cjuVJhU1hYEVetOTe7LvA+CujAI7yqvYsPtZfVEvv1pg9AeFNtHBg/ZoSLGU5LPijhcY5l3Ea9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.4.0", + "@algolia/client-abtesting": "5.38.0", + "@algolia/client-analytics": "5.38.0", + "@algolia/client-common": "5.38.0", + "@algolia/client-insights": "5.38.0", + "@algolia/client-personalization": "5.38.0", + "@algolia/client-query-suggestions": "5.38.0", + "@algolia/client-search": "5.38.0", + "@algolia/ingestion": "1.38.0", + "@algolia/monitoring": "1.38.0", + "@algolia/recommend": "5.38.0", + "@algolia/requester-browser-xhr": "5.38.0", + "@algolia/requester-fetch": "5.38.0", + "@algolia/requester-node-http": "5.38.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/algoliasearch-helper": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.26.0.tgz", + "integrity": "sha512-Rv2x3GXleQ3ygwhkhJubhhYGsICmShLAiqtUuJTUkr9uOCOXyF2E71LVT4XDnVffbknv8XgScP4U0Oxtgm+hIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/events": "^4.0.1" + }, + "peerDependencies": { + "algoliasearch": ">= 3.1 < 6" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "license": "Apache-2.0", + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "dev": true, + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", + "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "object.assign": "^4.1.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.7.tgz", + "integrity": "sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true, + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/bonjour-service": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true, + "license": "ISC" + }, + "node_modules/boxen": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", + "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^6.2.0", + "chalk": "^4.1.2", + "cli-boxes": "^3.0.0", + "string-width": "^5.0.1", + "type-fest": "^2.5.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.26.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", + "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001745", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001745.tgz", + "integrity": "sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-table3/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/combine-promises": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", + "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true, + "license": "ISC" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compressible/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/configstore": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", + "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^6.0.1", + "graceful-fs": "^4.2.6", + "unique-string": "^3.0.0", + "write-file-atomic": "^3.0.3", + "xdg-basedir": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/yeoman/configstore?sponsor=1" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz", + "integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-compat": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", + "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.45.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.45.1.tgz", + "integrity": "sha512-OHnWFKgTUshEU8MK+lOs1H8kC8GkTi9Z1tvNkxrCcw9wl3MJIO7q2ld77wjWn4/xuGrVu2X+nME1iIIPBSdyEQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/crypto-random-string/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/css-blank-pseudo": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", + "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-declaration-sorter": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", + "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14 || ^16 || >=18" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-has-pseudo": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", + "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/css-loader": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", + "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", + "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "cssnano": "^6.0.1", + "jest-worker": "^29.4.3", + "postcss": "^8.4.24", + "schema-utils": "^4.0.1", + "serialize-javascript": "^6.0.1" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@parcel/css": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "lightningcss": { + "optional": true + } + } + }, + "node_modules/css-prefers-color-scheme": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", + "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssdb": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.4.2.tgz", + "integrity": "sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", + "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^6.1.2", + "lilconfig": "^3.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-advanced": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", + "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "autoprefixer": "^10.4.19", + "browserslist": "^4.23.0", + "cssnano-preset-default": "^6.1.2", + "postcss-discard-unused": "^6.0.5", + "postcss-merge-idents": "^6.0.3", + "postcss-reduce-idents": "^6.0.3", + "postcss-zindex": "^6.0.2" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-preset-default": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", + "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "css-declaration-sorter": "^7.2.0", + "cssnano-utils": "^4.0.2", + "postcss-calc": "^9.0.1", + "postcss-colormin": "^6.1.0", + "postcss-convert-values": "^6.1.0", + "postcss-discard-comments": "^6.0.2", + "postcss-discard-duplicates": "^6.0.3", + "postcss-discard-empty": "^6.0.3", + "postcss-discard-overridden": "^6.0.2", + "postcss-merge-longhand": "^6.0.5", + "postcss-merge-rules": "^6.1.1", + "postcss-minify-font-values": "^6.1.0", + "postcss-minify-gradients": "^6.0.3", + "postcss-minify-params": "^6.1.0", + "postcss-minify-selectors": "^6.0.4", + "postcss-normalize-charset": "^6.0.2", + "postcss-normalize-display-values": "^6.0.2", + "postcss-normalize-positions": "^6.0.2", + "postcss-normalize-repeat-style": "^6.0.2", + "postcss-normalize-string": "^6.0.2", + "postcss-normalize-timing-functions": "^6.0.2", + "postcss-normalize-unicode": "^6.1.0", + "postcss-normalize-url": "^6.0.2", + "postcss-normalize-whitespace": "^6.0.2", + "postcss-ordered-values": "^6.0.2", + "postcss-reduce-initial": "^6.1.0", + "postcss-reduce-transforms": "^6.0.2", + "postcss-svgo": "^6.0.3", + "postcss-unique-selectors": "^6.0.4" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/cssnano-utils": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", + "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-port": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", + "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "address": "^1.0.1", + "debug": "4" + }, + "bin": { + "detect": "bin/detect-port.js", + "detect-port": "bin/detect-port.js" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.224", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.224.tgz", + "integrity": "sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/emoticon": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", + "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", + "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", + "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", + "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-value-to-estree": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.4.0.tgz", + "integrity": "sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/remcohaszing" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eta": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", + "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "url": "https://github.com/eta-dev/eta?sponsor=1" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eval": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", + "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "require-like": ">= 0.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/express/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/feed": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.2.tgz", + "integrity": "sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-js": "^1.6.11" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/file-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/file-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-slugger": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", + "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regex.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.0.1.tgz", + "integrity": "sha512-CG/iEvgQqfzoVsMUbxSJcwbG2JwyZ3naEqPkeltwl0BSS8Bp83k3xlGms+0QdWFUAwV+uvo80wNswKF6FWEkKg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", + "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^3.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/htm": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/htm/-/htm-3.1.1.tgz", + "integrity": "sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-minifier-terser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", + "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "~5.3.2", + "commander": "^10.0.0", + "entities": "^4.4.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.15.1" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": "^14.13.1 || >=16.0.0" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.4.tgz", + "integrity": "sha512-V/PZeWsqhfpE27nKeX9EO2sbR+D17A+tLf6qU+ht66jdUsN0QLKJN27Z+1+gHrVMKgndBahes0PU6rRihDgHTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/html-webpack-plugin/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", + "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", + "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "dev": true, + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/infima": { + "version": "0.2.0-alpha.45", + "resolved": "https://registry.npmjs.org/infima/-/infima-0.2.0-alpha.45.tgz", + "integrity": "sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", + "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-yarn-global": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", + "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "dev": true, + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/launch-editor": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", + "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "picocolors": "^1.1.1", + "shell-quote": "^1.8.3" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lunr-languages": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/lunr-languages/-/lunr-languages-1.14.0.tgz", + "integrity": "sha512-hWUAb2KqM3L7J5bcrngszzISY4BxrXn/Xhbb9TTCJYEGqlR1nG67/M14sp09+PTIRklobrn57IAxcdcO/ZFyNA==", + "dev": true, + "license": "MPL-1.1" + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.3.0.tgz", + "integrity": "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.46.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.46.1.tgz", + "integrity": "sha512-2wjHDg7IjP+ufAqqqSxjiNePFDrvWviA79ajUwG9lkHhk3HzZOLBzzoUG8cx9vCagj6VvBQD7oXuLuFz5LaAOQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsonjoy.com/json-pack": "^1.11.0", + "@jsonjoy.com/util": "^1.9.0", + "glob-to-regex.js": "^1.0.1", + "thingies": "^2.5.0", + "tree-dump": "^1.0.3", + "tslib": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-math/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-space": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", + "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-factory-space/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-character": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", + "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^1.0.0", + "micromark-util-types": "^1.0.0" + } + }, + "node_modules/micromark-util-character/node_modules/micromark-util-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", + "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-symbol": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", + "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark/node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark/node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", + "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nprogress": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz", + "integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/null-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", + "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/null-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/null-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/null-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/null-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true, + "license": "MIT" + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", + "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-attribute-case-insensitive": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", + "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-calc": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", + "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-clamp": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", + "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=7.6.0" + }, + "peerDependencies": { + "postcss": "^8.4.6" + } + }, + "node_modules/postcss-color-functional-notation": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", + "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-hex-alpha": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", + "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-color-rebeccapurple": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", + "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-colormin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", + "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "colord": "^2.9.3", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-convert-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", + "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-custom-media": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", + "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-properties": { + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", + "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", + "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", + "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-discard-comments": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", + "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", + "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-empty": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", + "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", + "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-discard-unused": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", + "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-double-position-gradients": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", + "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", + "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-focus-within": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", + "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-font-variant": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", + "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-gap-properties": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", + "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-image-set-function": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", + "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/utilities": "^2.0.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-lab-function": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", + "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/utilities": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-loader": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", + "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^8.3.5", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-logical": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", + "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-merge-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", + "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", + "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^6.1.1" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-merge-rules": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", + "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^4.0.2", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", + "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", + "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "colord": "^2.9.3", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-params": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", + "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", + "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^7.0.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nesting": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", + "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/selector-resolve-nested": "^3.1.0", + "@csstools/selector-specificity": "^5.0.0", + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", + "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", + "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", + "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", + "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", + "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-string": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", + "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", + "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", + "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-url": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", + "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", + "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-opacity-percentage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", + "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", + "dev": true, + "funding": [ + { + "type": "kofi", + "url": "https://ko-fi.com/mrcgrtz" + }, + { + "type": "liberapay", + "url": "https://liberapay.com/mrcgrtz" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-ordered-values": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", + "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssnano-utils": "^4.0.2", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-overflow-shorthand": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", + "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-page-break": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", + "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8" + } + }, + "node_modules/postcss-place": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", + "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-preset-env": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz", + "integrity": "sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "@csstools/postcss-alpha-function": "^1.0.1", + "@csstools/postcss-cascade-layers": "^5.0.2", + "@csstools/postcss-color-function": "^4.0.12", + "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", + "@csstools/postcss-color-mix-function": "^3.0.12", + "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", + "@csstools/postcss-content-alt-text": "^2.0.8", + "@csstools/postcss-contrast-color-function": "^2.0.12", + "@csstools/postcss-exponential-functions": "^2.0.9", + "@csstools/postcss-font-format-keywords": "^4.0.0", + "@csstools/postcss-gamut-mapping": "^2.0.11", + "@csstools/postcss-gradients-interpolation-method": "^5.0.12", + "@csstools/postcss-hwb-function": "^4.0.12", + "@csstools/postcss-ic-unit": "^4.0.4", + "@csstools/postcss-initial": "^2.0.1", + "@csstools/postcss-is-pseudo-class": "^5.0.3", + "@csstools/postcss-light-dark-function": "^2.0.11", + "@csstools/postcss-logical-float-and-clear": "^3.0.0", + "@csstools/postcss-logical-overflow": "^2.0.0", + "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", + "@csstools/postcss-logical-resize": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.4", + "@csstools/postcss-media-minmax": "^2.0.9", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", + "@csstools/postcss-nested-calc": "^4.0.0", + "@csstools/postcss-normalize-display-values": "^4.0.0", + "@csstools/postcss-oklab-function": "^4.0.12", + "@csstools/postcss-progressive-custom-properties": "^4.2.1", + "@csstools/postcss-random-function": "^2.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.12", + "@csstools/postcss-scope-pseudo-class": "^4.0.1", + "@csstools/postcss-sign-functions": "^1.1.4", + "@csstools/postcss-stepped-value-functions": "^4.0.9", + "@csstools/postcss-text-decoration-shorthand": "^4.0.3", + "@csstools/postcss-trigonometric-functions": "^4.0.9", + "@csstools/postcss-unset-value": "^4.0.0", + "autoprefixer": "^10.4.21", + "browserslist": "^4.26.0", + "css-blank-pseudo": "^7.0.1", + "css-has-pseudo": "^7.0.3", + "css-prefers-color-scheme": "^10.0.0", + "cssdb": "^8.4.2", + "postcss-attribute-case-insensitive": "^7.0.1", + "postcss-clamp": "^4.1.0", + "postcss-color-functional-notation": "^7.0.12", + "postcss-color-hex-alpha": "^10.0.0", + "postcss-color-rebeccapurple": "^10.0.0", + "postcss-custom-media": "^11.0.6", + "postcss-custom-properties": "^14.0.6", + "postcss-custom-selectors": "^8.0.5", + "postcss-dir-pseudo-class": "^9.0.1", + "postcss-double-position-gradients": "^6.0.4", + "postcss-focus-visible": "^10.0.1", + "postcss-focus-within": "^9.0.1", + "postcss-font-variant": "^5.0.0", + "postcss-gap-properties": "^6.0.0", + "postcss-image-set-function": "^7.0.0", + "postcss-lab-function": "^7.0.12", + "postcss-logical": "^8.1.0", + "postcss-nesting": "^13.0.2", + "postcss-opacity-percentage": "^3.0.0", + "postcss-overflow-shorthand": "^6.0.0", + "postcss-page-break": "^3.0.4", + "postcss-place": "^10.0.0", + "postcss-pseudo-class-any-link": "^10.0.1", + "postcss-replace-overflow-wrap": "^4.0.0", + "postcss-selector-not": "^8.0.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", + "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-reduce-idents": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", + "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", + "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", + "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-replace-overflow-wrap": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", + "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.0.3" + } + }, + "node_modules/postcss-selector-not": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", + "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", + "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sort-media-queries": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", + "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "sort-css-media-queries": "2.2.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.4.23" + } + }, + "node_modules/postcss-svgo": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", + "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^3.2.0" + }, + "engines": { + "node": "^14 || ^16 || >= 18" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", + "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-zindex": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", + "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/preact": { + "version": "10.27.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", + "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.0" + } + }, + "node_modules/pretty-time": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", + "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", + "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-goat": "^4.0.0" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", + "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", + "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-helmet-async": { + "name": "@slorber/react-helmet-async", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", + "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "invariant": "^2.2.4", + "prop-types": "^15.7.2", + "react-fast-compare": "^3.2.0", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-json-view-lite": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz", + "integrity": "sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-loadable": { + "name": "@docusaurus/react-loadable", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", + "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + }, + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-loadable-ssr-addon-v5-slorber": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", + "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.10.3" + }, + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "react-loadable": "*", + "webpack": ">=4.41.1 || 5.x" + } + }, + "node_modules/react-router": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", + "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-config": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", + "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2" + }, + "peerDependencies": { + "react": ">=15", + "react-router": ">=5" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", + "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.4", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", + "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "license": "MIT" + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regexpu-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", + "dev": true, + "license": "MIT", + "dependencies": { + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.2.2", + "regjsgen": "^0.8.0", + "regjsparser": "^0.13.0", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.2.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", + "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/regjsparser": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", + "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "jsesc": "~3.1.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remark-directive": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", + "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-directive": "^3.0.0", + "micromark-extension-directive": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-emoji": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", + "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.2", + "emoticon": "^4.0.1", + "mdast-util-find-and-replace": "^3.0.1", + "node-emoji": "^2.1.0", + "unified": "^11.0.4" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", + "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "node_modules/renderkid/node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/renderkid/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/renderkid/node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-like": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rtlcss": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz", + "integrity": "sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==", + "dev": true, + "license": "MIT", + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0", + "postcss": "^8.4.21", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "rtlcss": "bin/rtlcss.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "dev": true, + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/schema-dts": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.5.tgz", + "integrity": "sha512-RJr9EaCmsLzBX2NDiO5Z3ux2BVosNZN5jo0gWgsyKvxKIUL5R3swNvoorulAeL9kLB0iTSX7V6aokhla2m7xbg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true, + "license": "MIT" + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sitemap": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", + "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=5.6.0" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-emoji-modifier-base": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/sort-css-media-queries": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", + "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.3.0" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/srcset": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz", + "integrity": "sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/stylehacks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", + "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.23.0", + "postcss-selector-parser": "^6.0.16" + }, + "engines": { + "node": "^14 || ^16 || >=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/svgo": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/swr": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tapable": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", + "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/thingies": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", + "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tree-dump": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", + "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/type-is/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", + "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/update-notifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", + "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^7.0.0", + "chalk": "^5.0.1", + "configstore": "^6.0.0", + "has-yarn": "^3.0.0", + "import-lazy": "^4.0.0", + "is-ci": "^3.0.1", + "is-installed-globally": "^0.4.0", + "is-npm": "^6.0.0", + "is-yarn-global": "^0.4.0", + "latest-version": "^7.0.0", + "pupa": "^3.1.0", + "semver": "^7.3.7", + "semver-diff": "^4.0.0", + "xdg-basedir": "^5.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/boxen": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", + "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/url-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/url-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/url-loader/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/watchpack": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", + "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpack": { + "version": "5.101.3", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", + "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.15.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.3", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.3.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", + "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.43.1", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-middleware/node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack-dev-server": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", + "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/express-serve-static-core": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "express": "^4.21.2", + "graceful-fs": "^4.2.6", + "http-proxy-middleware": "^2.0.9", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.4.2", + "ws": "^8.18.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", + "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpackbar": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", + "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "consola": "^3.2.3", + "figures": "^3.2.0", + "markdown-table": "^2.0.0", + "pretty-time": "^1.1.0", + "std-env": "^3.7.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "webpack": "3 || 4 || 5" + } + }, + "node_modules/webpackbar/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpackbar/node_modules/markdown-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", + "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "repeat-string": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webpackbar/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpackbar/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xdg-basedir": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", + "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz", + "integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/external/package.json b/docs/external/package.json new file mode 100644 index 000000000..a59fc51af --- /dev/null +++ b/docs/external/package.json @@ -0,0 +1,16 @@ +{ + "name": "@miden/docs-dev", + "private": true, + "scripts": { + "start:dev": "docusaurus start --port 3000 --host 0.0.0.0", + "build:dev": "docusaurus build", + "serve:dev": "docusaurus serve build" + }, + "devDependencies": { + "@cmfcmf/docusaurus-search-local": "^2.0.0", + "@docusaurus/core": "^3", + "@docusaurus/preset-classic": "^3", + "rehype-katex": "^7", + "remark-math": "^6" + } +} diff --git a/docs/external/sidebars.ts b/docs/external/sidebars.ts new file mode 100644 index 000000000..040e34de6 --- /dev/null +++ b/docs/external/sidebars.ts @@ -0,0 +1,7 @@ +import type { SidebarsConfig } from "@docusaurus/plugin-content-docs"; + +const sidebars: SidebarsConfig = { + docs: [{ type: "autogenerated", dirName: "." }], +}; + +export default sidebars; diff --git a/docs/external/src/_category_.yml b/docs/external/src/_category_.yml new file mode 100644 index 000000000..adabe4909 --- /dev/null +++ b/docs/external/src/_category_.yml @@ -0,0 +1,4 @@ +label: Compiler +# Determines where this documentation section appears relative to other sections on the main documentation page (which is the parent of this folder in the miden-docs repository) +position: 8 +collapsed: true diff --git a/docs/external/src/appendix/_category_.yml b/docs/external/src/appendix/_category_.yml new file mode 100644 index 000000000..ab3d9c721 --- /dev/null +++ b/docs/external/src/appendix/_category_.yml @@ -0,0 +1,4 @@ +label: Compiler +# Determines where this documentation section appears relative to other sections in the parent folder +position: 4 +collapsed: true diff --git a/docs/appendix/calling-conventions.md b/docs/external/src/appendix/calling-conventions.md similarity index 98% rename from docs/appendix/calling-conventions.md rename to docs/external/src/appendix/calling-conventions.md index 04baaa175..bee90c0ef 100644 --- a/docs/appendix/calling-conventions.md +++ b/docs/external/src/appendix/calling-conventions.md @@ -1,3 +1,8 @@ +--- +title: Calling Conventions +draft: true +--- + # Calling conventions This document describes the various calling conventions recognized/handled by the compiler, @@ -76,11 +81,13 @@ the section on the memory model below for more details. [^8]: An `enum` is `i32` if all members of the enumeration can be represented by an `int`/`unsigned int`, otherwise it uses i64. -!!! note +:::note + +The compiler does not support scalars larger than one word (128 bits) at this time. As a result, anything that is +larger than that must be allocated in linear memory, or in an automatic allocation (function-local memory), and passed +around by reference. - The compiler does not support scalars larger than one word (128 bits) at this time. As a result, anything that is - larger than that must be allocated in linear memory, or in an automatic allocation (function-local memory), and passed - around by reference. +::: The native scalar type for the Miden VM is a "field element", specifically a 64-bit value representing an integer in the "Goldilocks" field, i.e. `0..(2^64-2^32+1)`. A number of instructions in the VM operate on field elements directly. diff --git a/docs/appendix/canonabi-adhocabi-mismatch.md b/docs/external/src/appendix/canonabi-adhocabi-mismatch.md similarity index 94% rename from docs/appendix/canonabi-adhocabi-mismatch.md rename to docs/external/src/appendix/canonabi-adhocabi-mismatch.md index 17413987a..a83132fa8 100644 --- a/docs/appendix/canonabi-adhocabi-mismatch.md +++ b/docs/external/src/appendix/canonabi-adhocabi-mismatch.md @@ -1,11 +1,20 @@ +--- +title: Canonical ABI vs Miden ABI Incompatibility +draft: true +--- + # Canonical ABI vs Miden ABI incompatibility +:::note + This document describes an issue that arises when trying to map the ad-hoc calling convention/ABI used by various Miden Assembly procedures, such as those comprising the transaction kernel, and the "canonical" ABI(s) representable in Rust. It proposes a solution to this problem in the form of _adapter functions_, where the details of a given adapter are one of a closed set of known ABI _transformation strategies_. +::: + ## Summary The gist of the problem is that in Miden, the size and number of procedure results is only constrained @@ -41,17 +50,19 @@ above which inserts an implicit pointer to the caller's stack frame for the call value to, rather than doing so in a register (or in our case, on the operand stack). This seems to happen for any type that is larger than 8 bytes (i64). -!!! tip +:::tip - For a complete list of the transaction kernel functions, in WIT format, see - [miden.wit](https://github.com/0xPolygonMiden/compiler/blob/main/tests/rust-apps-wasm/wit-sdk/sdk/wit/miden.wit). +For a complete list of the transaction kernel functions, in WIT format, see +[miden.wit](https://github.com/0xMiden/compiler/blob/main/tests/rust-apps-wasm/wit-sdk/sdk/wit/miden.wit). + +::: For most transaction kernel functions, the adapter function can be generated automatically using the pattern recognition and adapter functions described below. ### Prerequisites -* The compiler must know the type signature for any function we wish to apply the adapter strategy to +- The compiler must know the type signature for any function we wish to apply the adapter strategy to ### Implementation @@ -165,7 +176,7 @@ adapter_function.build(); Here is how the adapter might look like in a pseudo-code for the `add_asset` function: -``` +```wat /// Takes an Asset as an argument and returns a new Asset func wasm_core_add_asset(v0: f64, v1: f64, v2: f64, v3: f64, ptr: i32) { v4 = call tx_kernel_add_asset(v0, v1, v2, v3); @@ -182,7 +193,6 @@ This Miden ABI pattern is selected if no other Miden ABI pattern is applicable a For example, the `get_id` function falls under this Miden ABI pattern and its calls will be translated to the tx kernel function calls without any modifications. - ## Transaction kernel functions that require manual adapter functions ### `get_assets` @@ -196,7 +206,7 @@ module. Here are the signatures of the `get_assets` function in the WIT, core Wasm, and the tx kernel ad-hoc ABI: Comment from the `miden-base` -``` +```text #! Writes the assets of the currently executing note into memory starting at the specified address. #! #! Inputs: [dest_ptr] @@ -240,13 +250,14 @@ func wasm_core_get_assets(asset_count: u32, ptr_ptr: i32) { } ``` -!!! note +:::note - Since the `get_assets` tx kernel function in the current form can trash the provided memory if - the actual assets count differs from the returned by `get_assets_count`, we can introduce the - asset count parameter to the `get_assets` tx kernel function and check that it the same as the - actual assets count written to memory. +Since the `get_assets` tx kernel function in the current form can trash the provided memory if +the actual assets count differs from the returned by `get_assets_count`, we can introduce the +asset count parameter to the `get_assets` tx kernel function and check that it the same as the +actual assets count written to memory. +::: ## The example of some functions signatures @@ -254,7 +265,7 @@ func wasm_core_get_assets(asset_count: u32, ptr_ptr: i32) { Comment from the `miden-base` -``` +```text #! Add the specified asset to the vault. #! #! Panics: @@ -281,11 +292,11 @@ The last `i32` is a pointer to a returned value (`word`) Tx kernel ad-hoc signature: `tx_kernel_add_asset(felt, felt, felt, felt) -> (felt, felt, felt, felt)` - ### `get_id` (no-adapter-needed Miden ABI pattern) Comment from the `miden-base` -``` + +```text #! Returns the account id. #! #! Stack: [] diff --git a/docs/external/src/appendix/index.md b/docs/external/src/appendix/index.md new file mode 100644 index 000000000..e2808319f --- /dev/null +++ b/docs/external/src/appendix/index.md @@ -0,0 +1,12 @@ +--- +title: Appendices +draft: true +--- + +# Appendices + +This section contains supplementary material providing deeper technical insights into the Miden compiler and related systems: + +- [Known Limitations](./known-limitations.md) - Details current constraints and unimplemented features. +- [Calling Conventions](./calling-conventions.md) - Explains function call mechanics and argument passing. +- [Canonical ABI vs Miden ABI](./canonabi-adhocabi-mismatch.md) - Analyzes cross-ABI interoperability challenges. diff --git a/docs/appendix/known-limitations.md b/docs/external/src/appendix/known-limitations.md similarity index 85% rename from docs/appendix/known-limitations.md rename to docs/external/src/appendix/known-limitations.md index 4cd203a45..215633b6e 100644 --- a/docs/appendix/known-limitations.md +++ b/docs/external/src/appendix/known-limitations.md @@ -1,9 +1,16 @@ +--- +title: Known Limitations +draft: true +--- + # Known limitations -!!! tip +:::tip + +See the [issue tracker](https://github.com/0xMiden/compiler/issues) for information +on known bugs. This document focuses on missing/incomplete features, rather than bugs. - See the [issue tracker](https://github.com/0xpolygonmiden/compiler/issues) for information - on known bugs. This document focuses on missing/incomplete features, rather than bugs. +::: The compiler is still in its early stages of development, so there are various features that are unimplemented, or only partially implemented, and the test suite is still limited in scope, so @@ -37,8 +44,8 @@ find a better/more natural representation for `Felt` in WebAssembly. ### Function call indirection - Status: **Unimplemented** -- Tracking Issue: [#32](https://github.com/0xPolygonMiden/compiler/issues/32) -- Release Milestone: [Beta 1](https://github.com/0xPolygonMiden/compiler/milestone/4) +- Tracking Issue: [#32](https://github.com/0xMiden/compiler/issues/32) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) This feature corresponds to `call_indirect` in WebAssembly, and is associated with Rust features such as trait objects (which use indirection to call trait methods), and closures. Note that the @@ -46,13 +53,15 @@ Rust compiler is able to erase the indirection associated with certain abstracti in some cases, shown below. If Rust is unable to statically resolve all call targets, then `midenc` will raise an error when it encounters any use of `call_indirect`. -!!! warning +:::warning + +The following examples rely on `rustc`/LLVM inlining enough code to be able to convert indirect +calls to direct calls. This may require you to enable link-time optimization with `lto = "fat"` +and compile all of the code in the crate together with `codegen-units = 1`, in order to maximize +the amount of inlining that can occur. Even then, it may not be possible to remove some forms of +indirection, in which case you will need to find another workaround. - The following examples rely on `rustc`/LLVM inlining enough code to be able to convert indirect - calls to direct calls. This may require you to enable link-time optimization with `lto = "fat"` - and compile all of the code in the crate together with `codegen-units = 1`, in order to maximize - the amount of inlining that can occur. Even then, it may not be possible to remove some forms of - indirection, in which case you will need to find another workaround. +::: #### Iterator lowered to loop @@ -111,8 +120,8 @@ fn main() -> u32 { ### Miden SDK - Status: **Incomplete** -- Tracking Issue: [#159](https://github.com/0xPolygonMiden/compiler/issues/159) and [#158](https://github.com/0xPolygonMiden/compiler/issues/158) -- Release Milestone: [Beta 1](https://github.com/0xPolygonMiden/compiler/milestone/4) +- Tracking Issue: [#159](https://github.com/0xMiden/compiler/issues/159) and [#158](https://github.com/0xMiden/compiler/issues/158) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) The Miden SDK for Rust, is a Rust crate that provides the implementation of native Miden types, as well as bindings to the Miden standard library and transaction kernel APIs. @@ -126,7 +135,7 @@ APIs which are lesser used. ### Rust/Miden FFI (foreign function interface) and interop - Status: **Internal Use Only** -- Tracking Issue: [#304](https://github.com/0xPolygonMiden/compiler/issues/304) +- Tracking Issue: [#304](https://github.com/0xMiden/compiler/issues/304) - Release Milestone: TBD While the compiler has functionality to link against native Miden Assembly libraries, binding @@ -152,8 +161,8 @@ and Miden packaging. Once present, we can open up the FFI for general use. ### Dynamic procedure invocation - Status: **Unimplemented** -- Tracking Issue: [#32](https://github.com/0xPolygonMiden/compiler/issues/32) -- Release Milestone: [Beta 1](https://github.com/0xPolygonMiden/compiler/milestone/4) +- Tracking Issue: [#32](https://github.com/0xMiden/compiler/issues/32) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) This is a dependency of [Function Call Indirection](#function-call-indirection) described above, and is the mechanism by which we can perform indirect calls in Miden. In order to implement support @@ -177,8 +186,8 @@ which has its "address" taken, and use the hash of the stub in place of the actu ### Cross-context procedure invocation - Status: **Unimplemented** -- Tracking Issue: [#303](https://github.com/0xPolygonMiden/compiler/issues/303) -- Release Milestone: [Beta 2](https://github.com/0xPolygonMiden/compiler/milestone/5) +- Tracking Issue: [#303](https://github.com/0xMiden/compiler/issues/303) +- Release Milestone: [Beta 2](https://github.com/0xMiden/compiler/milestone/5) This is required in order to support representing Miden accounts and note scripts in Rust, and compilation to Miden Assembly. @@ -197,7 +206,7 @@ The solution to this relies on compiling the Rust code for the `wasm32-wasip2` t a new kind of WebAssembly module, known as a _component_. These components adhere to the rules of the [WebAssembly Component Model](https://component-model.bytecodealliance.org/). Of primary interest to us, is the fact that components in this model are "shared-nothing", and the ABI used to -communicate across component boundaries, is specially designed to enforce shared-nothing semantics +communicate across component boundaries, is specially designed to enforce shared-nothing semantics on caller and callee. In addition to compiling for a specific Wasm target, we also rely on some additional tooling for describing component interfaces, types, and to generate Rust bindings for those descriptions, to ensure that calls across the boundary remain opaque, even to the linker, @@ -215,8 +224,8 @@ subfeatures being implemented first. ### Package format - Status: **Experimental** -- Tracking Issue: [#121](https://github.com/0xPolygonMiden/compiler/issues/121) -- Release Milestone: [Beta 1](https://github.com/0xPolygonMiden/compiler/milestone/4) +- Tracking Issue: [#121](https://github.com/0xMiden/compiler/issues/121) +- Release Milestone: [Beta 1](https://github.com/0xMiden/compiler/milestone/4) This feature represents the ability to compile and distribute a single artifact that contains the compiled MAST, and all required and optional metadata to make linking against, and executing @@ -229,8 +238,8 @@ that meets the minimum requirements to support libraries and programs compiled f - Content digest - The compiled MAST and metadata about the procedures exported from it - Read-only data segments and their hashes (if needed by the program, used to load data into the -advice provider when a program is loaded, and to write those segments into linear memory when the -program starts) + advice provider when a program is loaded, and to write those segments into linear memory when the + program starts) - Dependency information (optional, specifies what libraries were linked against during compilation) - Debug information (optional) @@ -239,7 +248,7 @@ currently, compile a package and then run it using `miden run` directly. Instead `midenc run` to load and run code from a package, as the compiler ships with the VM embedded for use with the interactive debugger, and provides native support for packaging on top of it. You can also use `midenc debug` to execute your program interactively in the debugger, depending on your -needs. See [Debugging Programs](../usage/debugger.md) for more information on how to use the +needs. See [Debugging Programs](../guides/debugger.md) for more information on how to use the debugger, and `midenc help run` for more information on executing programs with the `midenc run` command. diff --git a/docs/external/src/guides/_category_.yml b/docs/external/src/guides/_category_.yml new file mode 100644 index 000000000..9375afab9 --- /dev/null +++ b/docs/external/src/guides/_category_.yml @@ -0,0 +1,4 @@ +label: Guides +# Determines where this documentation section appears relative to other sections in the parent folder +position: 3 +collapsed: true diff --git a/docs/external/src/guides/debugger.md b/docs/external/src/guides/debugger.md new file mode 100644 index 000000000..9516fde2d --- /dev/null +++ b/docs/external/src/guides/debugger.md @@ -0,0 +1,230 @@ +--- +title: Debugging programs +sidebar_position: 5 +--- + +# Debugging programs + +A very useful tool in the Miden compiler suite, is its TUI-based interactive debugger, accessible +via the `midenc debug` command. + +:::warning + +The debugger is still quite new, and while very useful already, still has a fair number of +UX annoyances. Please report any bugs you encounter, and we'll try to get them patched ASAP! + +::: + +## Getting started + +The debugger is launched by executing `midenc debug`, and giving it a path to a program compiled +by `midenc compile`. See [Program Inputs](#program-inputs) for information on how to provide inputs +to the program you wish to debug. Run `midenc help debug` for more detailed usage documentation. + +The debugger may also be used as a library, but that is left as an exercise for the reader for now. + +## Example + +```shell +# Compile a program to MAST from a rustc-generated Wasm module +midenc compile foo.wasm -o foo.masl + +# Load that program into the debugger and start executing it +midenc debug foo.masl +``` + +## Program inputs + +To pass arguments to the program on the operand stack, or via the advice provider, you have two +options, depending on the needs of the program: + +1. Pass arguments to `midenc debug` in the same order you wish them to appear on the stack. That + is, the first argument you specify will be on top of the stack, and so on. +2. Specify a configuration file from which to load inputs for the program, via the `--inputs` option. + +### Via command line + +To specify the contents of the operand stack, you can do so following the raw arguments separator `--`. +Each operand must be a valid field element value, in either decimal or hexadecimal format. For example: + +```shell +midenc debug foo.masl -- 1 2 0xdeadbeef +``` + +If you pass arguments via the command line in conjunction with `--inputs`, then the command line arguments +will be used instead of the contents of the `inputs.stack` option (if set). This lets you specify a baseline +set of inputs, and then try out different arguments using the command line. + +### Via inputs config + +While simply passing operands to the `midenc debug` command is useful, it only allows you to specify +inputs to be passed via operand stack. To provide inputs via the advice provider, you will need to use +the `--inputs` option. The configuration file expected by `--inputs` also lets you tweak the execution +options for the VM, such as the maximum and expected cycle counts. + +An example configuration file looks like so: + +```toml +# This section is used for execution options +[options] +max_cycles = 5000 +expected_cycles = 4000 + +# This section is the root table for all inputs +[inputs] +# Specify elements to place on the operand stack, leftmost element will be on top of the stack +stack = [1, 2, 0xdeadbeef] + +# This section contains input options for the advice provider +[inputs.advice] +# Specify elements to place on the advice stack, leftmost element will be on top +stack = [1, 2, 3, 4] + +# The `inputs.advice.map` section is a list of advice map entries that should be +# placed in the advice map before the program is executed. Entries with duplicate +# keys are handled on a last-write-wins basis. +[[inputs.advice.map]] +# The key for this entry in the advice map +digest = '0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63' +# The values to be stored under this key +values = [1, 2, 3, 4] + +[[inputs.advice.map]] +digest = '0x20234ee941e53a15886e733cc8e041198c6e90d2a16ea18ce1030e8c3596dd38'' +values = [5, 6, 7, 8] +``` + +## Usage + +Once started, you will be dropped into the main debugger UI, stopped at the first cycle of +the program. The UI is organized into pages and panes, with the main/home page being the +one you get dropped into when the debugger starts. The home page contains the following panes: + +- Source Code - displays source code for the current instruction, if available, with + the relevant line and span highlighted, with syntax highlighting (when available) +- Disassembly - displays the 5 most recently executed VM instructions, and the current + cycle count +- Stack Trace - displays a stack trace for the current instruction, if the program was + compiled with tracing enabled. If frames are unavailable, this pane may be empty. +- Operand Stack - displays the contents of the operand stack and its current depth +- Breakpoints - displays the set of current breakpoints, along with how many were hit + at the current instruction, when relevant + +### Keyboard shortcuts + +On the home page, the following keyboard shortcuts are available: + +| Shortcut | Mnemonic | Description | +| -------- | -------------- | ------------------------------------------------------------------------------------------------------- | +| `q` | quit | exit the debugger | +| `h` | next pane | cycle focus to the next pane | +| `l` | prev pane | cycle focus to the previous pane | +| `s` | step | advance the VM one cycle | +| `n` | step next | advance the VM to the next instruction | +| `c` | continue | advance the VM to the next breakpoint, else to completion | +| `e` | exit frame | advance the VM until we exit the current call frame, a breakpoint is triggered, or execution terminates | +| `d` | delete | delete an item (where applicable, e.g. the breakpoints pane) | +| `:` | command prompt | bring up the command prompt (see below for details) | + +When various panes have focus, additional keyboard shortcuts are available, in any pane +with a list of items, or multiple lines (e.g. source code), `j` and `k` (or the up and +down arrows) will select the next item up and down, respectively. As more features are +added, I will document their keyboard shortcuts below. + +### Commands + +From the home page, typing `:` will bring up the command prompt in the footer pane. + +You will know the prompt is active because the keyboard shortcuts normally shown there will +no longer appear, and instead you will see the prompt, starting with `:`. It supports any +of the following commands: + +| Command | Aliases | Action | Description | +| ------------ | ------------ | ----------------- | --------------------------------------------------------------------- | +| `quit` | `q` | quit | exit the debugger | +| `debug` | | show debug log | display the internal debug log for the debugger itself | +| `reload` | | reload program | reloads the program from disk, and resets the UI (except breakpoints) | +| `breakpoint` | `break`, `b` | create breakpoint | see [Breakpoints](#breakpoints) | +| `read` | `r` | read memory | inspect linear memory (see [Reading Memory](#reading-memory) | + +## Breakpoints + +One of the most common things you will want to do with the debugger is set and manage breakpoints. +Using the command prompt, you can create breakpoints by typing `b` (or `break` or `breakpoint`), +followed by a space, and then the desired breakpoint expression to do any of the following: + +- Break at an instruction which corresponds to a source file (or file and line) whose name/path + matches a pattern +- Break at the first instruction which causes a call frame to be pushed for a procedure whose name + matches a pattern +- Break any time a specific opcode is executed +- Break at the next instruction +- Break after N cycles +- Break at CYCLE + +The syntax for each of these can be found below, in the same order (shown using `b` as the command): + +| Expression | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `b FILE[:LINE]` | Break when an instruction with a source location in `FILE` (a glob pattern)
_and_ that occur on `LINE` (literal, if provided) are hit. | +| `b in NAME` | Break when the glob pattern `NAME` matches the fully-qualified procedure name
containing the current instruction | +| `b for OPCODE` | Break when the an instruction with opcode `OPCODE` is exactly matched
(including immediate values) | +| `b next` | Break on the next instruction | +| `b after N` | Break after `N` cycles | +| `b at CYCLE` | Break when the cycle count reaches `CYCLE`.
If `CYCLE` has already occurred, this has no effect | + +When a breakpoint is hit, it will be highlighted, and the breakpoint window will display the number +of hit breakpoints in the lower right. + +After a breakpoint is hit, it expires if it is one of the following types: + +- Break after N +- Break at CYCLE +- Break next + +When a breakpoint expires, it is removed from the breakpoint list on the next cycle. + +## Reading memory + +Another useful diagnostic task is examining the contents of linear memory, to verify that expected +data has been written. You can do this via the command prompt, using `r` (or `read`), followed by +a space, and then the desired memory address and options: + +The format for read expressions is `:r ADDR [OPTIONS..]`, where `ADDR` is a memory address in +decimal or hexadecimal format (the latter requires the `0x` prefix). The `read` command supports +the following for `OPTIONS`: + +| Option | Alias | Values | Default | Description | +| ---------------- | ----- | --------------------------------------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `-mode MODE` | `-m` | `words` (`word`, `w`), `bytes` (`byte`, `b`) | `words` | Specify a memory addressing mode | +| `-format FORMAT` | `-f` | `decimal` (`d`), `hex` (`x`), `binary` (`bin`, `b`) | `decimal` | Specify the format used to print integral values | +| `-count N` | `-c` | | `1` | Specify the number of units to read | +| `-type TYPE` | `-t` | See [Types](#types) | `word` | Specify the type of value to read
This also has the effect of modifying the default `-format` and unit size for `-count` | + +Any invalid combination of options, or invalid syntax, will display an error in the status bar. + +### Types + +| Type | Description | +| ------------------ | -------------------------------------------------- | +| `iN` | A signed integer of `N` bits | +| `uN` | An unsigned integer of `N` bits | +| `felt` | A field element | +| `word` | A Miden word, i.e. an array of four field elements | +| `ptr` or `pointer` | A 32-bit memory address (implies `-format hex`) | + +## Roadmap + +The following are some features planned for the near future: + +- **Watchpoints**, i.e. cause execution to break when a memory store touches a specific address +- **Conditional breakpoints**, i.e. only trigger a breakpoint when an expression attached to it + evaluates to true +- More DYIM-style breakpoints, i.e. when breaking on first hitting a match for a file or + procedure, we probably shouldn't continue to break for every instruction to which that + breakpoint technically applies. Instead, it would make sense to break and then temporarily + disable that breakpoint until something changes that would make breaking again useful. + This will rely on the ability to disable breakpoints, not delete them, which we don't yet + support. +- More robust type support in the `read` command +- Display procedure locals and their contents in a dedicated pane diff --git a/docs/guides/develop_miden_in_rust.md b/docs/external/src/guides/develop_miden_in_rust.md similarity index 89% rename from docs/guides/develop_miden_in_rust.md rename to docs/external/src/guides/develop_miden_in_rust.md index 118653a41..2af020540 100644 --- a/docs/guides/develop_miden_in_rust.md +++ b/docs/external/src/guides/develop_miden_in_rust.md @@ -1,8 +1,13 @@ +--- +title: Developing Miden Programs in Rust +sidebar_position: 3 +--- + # Developing Miden programs in Rust This chapter will walk through how to develop Miden programs in Rust using the standard library provided by the `miden-stdlib-sys` crate (see the -[README](https://github.com/0xPolygonMiden/compiler/blob/main/sdk/stdlib-sys/README.md). +[README](https://github.com/0xMiden/compiler/blob/main/sdk/stdlib-sys/README.md). ## Getting started diff --git a/docs/external/src/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md b/docs/external/src/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md new file mode 100644 index 000000000..ca0e6324f --- /dev/null +++ b/docs/external/src/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md @@ -0,0 +1,8 @@ +--- +title: Developing Miden Rollup Accounts and Note Scripts in Rust +sidebar_position: 4 +--- + +# Developing Miden rollup accounts and note scripts in Rust + +This chapter walks you through how to develop Miden rollup accounts and note scripts in Rust using the Miden SDK crate. diff --git a/docs/external/src/guides/index.md b/docs/external/src/guides/index.md new file mode 100644 index 000000000..413bb0ac2 --- /dev/null +++ b/docs/external/src/guides/index.md @@ -0,0 +1,13 @@ +--- +title: Guides +sidebar_position: 1 +--- + +# Guides + +This section contains practical guides for working with the Miden compiler: + +* [Compiling Rust to WebAssembly](./rust_to_wasm.md) - Walks through compiling Rust code to WebAssembly modules +* [WebAssembly to Miden Assembly](./wasm_to_masm.md) - Explains compiling Wasm modules to Miden Assembly +* [Developing Miden Programs in Rust](./develop_miden_in_rust.md) - Demonstrates using Rust with Miden's standard library +* [Developing Miden Rollup Accounts and Note Scripts in Rust](./develop_miden_rollup_accounts_and_note_scripts_in_rust.md) - Shows Rust development for Miden rollup components diff --git a/docs/guides/rust_to_wasm.md b/docs/external/src/guides/rust_to_wasm.md similarity index 89% rename from docs/guides/rust_to_wasm.md rename to docs/external/src/guides/rust_to_wasm.md index 19ce9fdb5..bdc41e593 100644 --- a/docs/guides/rust_to_wasm.md +++ b/docs/external/src/guides/rust_to_wasm.md @@ -1,3 +1,8 @@ +--- +title: Rust to WebAssembly +sidebar_position: 1 +--- + # Compiling Rust To WebAssembly This chapter will walk you through compiling a Rust crate to a WebAssembly (Wasm) module @@ -18,7 +23,7 @@ To compile to WebAssembly, you must have the appropriate Rust toolchain installe a toolchain file to our project root so that `rustup` and `cargo` will know what we need, and use them by default: - cat < rust-toolchain.toml + cat <<EOF > rust-toolchain.toml [toolchain] channel = "stable" targets = ["wasm32-wasip1"] @@ -62,14 +67,16 @@ going to benefit from less code, even if conventionally that code would be less to the difference in proving time accumulated due to extra instructions. That said, there are no hard and fast rules, but these defaults are good ones to start with. -!!! tip +:::tip + +We reference a simple bump allocator provided by `miden-sdk-alloc` above, but any simple +allocator will do. The trade offs made by these small allocators are not generally suitable for +long-running, or allocation-heavy applications, as they "leak" memory (generally because they +make little to no attempt to recover freed allocations), however they are very useful for +one-shot programs that do minimal allocation, which is going to be the typical case for Miden +programs. - We reference a simple bump allocator provided by `miden-sdk-alloc` above, but any simple - allocator will do. The trade offs made by these small allocators are not generally suitable for - long-running, or allocation-heavy applications, as they "leak" memory (generally because they - make little to no attempt to recover freed allocations), however they are very useful for - one-shot programs that do minimal allocation, which is going to be the typical case for Miden - programs. +::: Next, edit `src/lib.rs` as shown below: diff --git a/docs/external/src/guides/wasm_to_masm.md b/docs/external/src/guides/wasm_to_masm.md new file mode 100644 index 000000000..8b6bb44f1 --- /dev/null +++ b/docs/external/src/guides/wasm_to_masm.md @@ -0,0 +1,143 @@ +--- +title: WebAssembly to Miden Assembly +sidebar_position: 2 +--- + +# Compiling WebAssembly to Miden Assembly + +This guide will walk you through compiling a WebAssembly (Wasm) module, in binary form +(i.e. a `.wasm` file), to Miden Assembly (Masm), both in its binary package form (a `.masp` file), +and in textual Miden Assembly syntax form (i.e. a `.masm` file). + +## Setup + +We will be making use of the example crate we created in [Compiling Rust to WebAssembly](rust_to_wasm.md), +which produces a small Wasm module that is easy to examine in Wasm text format, and demonstrates a +good set of default choices for a project compiling to Miden Assembly from Rust. + +In this chapter, we will be compiling Wasm to Masm using the `midenc` executable, so ensure that +you have followed the instructions in the [Getting Started with `midenc`](../usage/midenc.md) guide +and then return here. + +:::note + +While we are using `midenc` for this guide, the more common use case will be to use the +`cargo-miden` Cargo extension to handle the gritty details of compiling from Rust to Wasm +for you. However, the purpose of this guide is to show you what `cargo-miden` is handling +for you, and to give you a foundation for using `midenc` yourself if needed. + +::: + +## Compiling to Miden Assembly + +In the last chapter, we compiled a Rust crate to WebAssembly that contains an implementation +of the Fibonacci function called `fib`, that was emitted to +`target/wasm32-wasip1/release/wasm_fib.wasm`. All that remains is to tell `midenc` to compile this +module to Miden Assembly. + +Currently, by default, the compiler will emit an experimental package format that the Miden VM does +not yet support. We will instead use `midenc run` to execute the package using the VM for us, but +once the package format is stabilized, this same approach will work with `miden run` as well. + +We also want to examine the Miden Assembly generated by the compiler, so we're going to ask the +compiler to emit both types of artifacts: + +```bash +midenc compile --emit masm=wasm_fib.masm,masp target/wasm32-wasip1/release/wasm_fib.wasm +``` + +This will compile our Wasm module to a Miden package with the `.masp` extension, and also emit the +textual Masm to `wasm_fib.masm` so we can review it. The `wasm_fib.masp` file will be emitted in the +default output directory, which is the current working directory by default. + +If we dump the contents of `wasm_fib.masm`, we'll see the following generated code: + +```masm +export.fib + push.0 + push.1 + movup.2 + swap.1 + dup.1 + neq.0 + push.1 + while.true + if.true + push.4294967295 + movup.2 + swap.1 + u32wrapping_add + dup.1 + swap.1 + swap.3 + swap.1 + u32wrapping_add + movup.2 + swap.1 + dup.1 + neq.0 + push.1 + else + drop + drop + push.0 + end + end +end +``` + +If you compare this to the WebAssembly text format, you can see that this is a fairly +faithful translation, but there may be areas where we generate sub-optimal Miden Assembly. + +:::note + +At the moment the compiler does only minimal optimization, late in the pipeline during codegen, +and only in an effort to minimize operand stack management code. So if you see an instruction +sequence you think is bad, bring it to our attention, and if it is something that we can solve +as part of our overall optimization efforts, we will be sure to do so. There _are_ limits to +what we can generate compared to what one can write by hand, particularly because Rust's +memory model requires us to emulate byte-addressable memory on top of Miden's word-addressable +memory, however our goal is to keep this overhead within an acceptable bound in the general case, +and easily-recognized patterns that can be simplified using peephole optimization are precisely +the kind of thing we'd like to know about, as those kinds of optimizations are likely to produce +the most significant wins. + +::: + +## Testing with the Miden VM + +:::note + +Because the compiler ships with the VM embedded for `midenc debug`, you can run your program +without having to install the VM separately, though you should do that as well, as `midenc` only +exposes a limited set of commands for executing programs, intended for debugging. + +::: + +We can test our compiled program like so: + +```bash +$ midenc run --num-outputs 1 wasm_fib.masp -- 10 +============================================================ +Run program: wasm_fib.masp +============================================================ +Executed program with hash 0xe5ba88695040ec2477821b26190e9addbb1c9571ae30c564f5bbfd6cabf6c535 in 19 milliseconds +Output: [55] +VM cycles: 295 extended to 512 steps (42% padding). +├── Stack rows: 295 +├── Range checker rows: 67 +└── Chiplets rows: 250 +├── Hash chiplet rows: 248 +├── Bitwise chiplet rows: 0 +├── Memory chiplet rows: 1 +└── Kernel ROM rows: 0 +``` + +Success! We got the expected result of `55`. + +## Next steps + +This guide is not comprehensive, as we have not yet examined in detail the differences between +compiling libraries vs programs, linking together multiple libraries, packages, or discussed some of +the more esoteric compiler options. We will be updating this documentation with those details and +more in the coming weeks and months, so bear with us while we flesh out our guides! diff --git a/docs/external/src/index.md b/docs/external/src/index.md new file mode 100644 index 000000000..d30963961 --- /dev/null +++ b/docs/external/src/index.md @@ -0,0 +1,106 @@ +--- +title: Compiler +sidebar_position: 1 +--- + +# Getting Started + +Welcome to the documentation for the Miden compiler toolchain. + +:::caution + +The compiler is currently in an experimental state, and has known bugs and limitations, it is +not yet ready for production usage. However, we'd encourage you to start experimenting with it +yourself, and give us feedback on any issues or sharp edges you encounter. + +::: + +The documentation found here should provide a good starting point for the current capabilities of +the toolchain, however if you find something that is not covered, but is not listed as +unimplemented or a known limitation, please let us know by reporting an issue on the compiler +[issue tracker](https://github.com/0xMiden/compiler/issues). + +## What is provided? + +The compiler toolchain consists of the following primary components: + +- An intermediate representation (IR), which can be lowered to by compiler backends wishing to + support Miden as a target. The Miden IR is an SSA IR, much like Cranelift or LLVM, providing a + much simpler path from any given source language (e.g. Rust), to Miden Assembly. It is used + internally by the rest of the Miden compiler suite. +- A WebAssembly (Wasm) frontend for Miden IR. It can handle lowering both core Wasm modules, as + well as basic components using the experimental WebAssembly Component Model. Currently, the Wasm + frontend is known to work with Wasm modules produced by `rustc`, which is largely just what LLVM + produces, but with the shadow stack placed at the start of linear memory rather than after + read-only data. In the future we intend to support more variety in the structure of Wasm modules + we accept, but for the time being we're primarily focused on using this as the path for lowering + Rust to Miden. +- The compiler driver, in the form of the `midenc` executable, and a Rust crate, `midenc-compiler` + to allow integrating the compiler into other tools. This plays the same role as `rustc` does in + the Rust ecosystem. +- A Cargo extension, `cargo-miden`, that provides a convenient developer experience for creating + and compiling Rust projects targeting Miden. It contains a project template for a basic Rust crate, + and handles orchestrating `rustc` and `midenc` to compile the crate to WebAssembly, and then to + Miden Assembly. +- A terminal-based interactive debugger, available via `midenc debug`, which provides a UI very + similar to `lldb` or `gdb` when using the TUI mode. You can use this to run a program, or step + through it cycle-by-cycle. You can set various types of breakpoints; see the source code, call + stack, and contents of the operand stack at the current program point; as well as interatively + read memory and format it in various ways for display. +- A Miden SDK for Rust, which provides types and bindings to functionality exported from the Miden + standard library, as well as the Miden transaction kernel API. You can use this to access native + Miden features which are not provided by Rust out-of-the-box. The project template generated by + `cargo miden new` automatically adds this as a dependency. + +## What can I do with it? + +That all sounds great, but what can you do with the compiler today? The answer depends a bit on what +aspect of the compiler you are interested in: + +### Rust + +The most practically useful, and interesting capability provided by the compiler currently, is the +ability to compile arbitrary Rust programs to Miden Assembly. See the guides for more information +on setting up and compiling a Rust crate for execution via Miden. + +### WebAssembly + +More generally, the compiler frontend is capable of compiling WebAssembly modules, with some +constraints, to Miden Assembly. As a result, it is possible to compile a wider variety of languages +to Miden Assembly than just Rust, so long as the language can compile to WebAssembly. However, we +do not currently provide any of the language-level support for languages other than Rust, and +have limited ability to provide engineering support for languages other than Rust at this time. + +Our Wasm frontend does not support all of the extensions to the WebAssembly MVP, most notably the +reference types and GC proposals. + +### Miden IR + +If you are interested in compiling to Miden from your own compiler, you can target Miden IR, and +invoke the driver from your compiler to emit Miden artifacts. At this point in time, we don't have +the resources to provide much in the way of engineering support for this use case, but if you find +issues in your efforts to use the IR in your compiler, we would certainly like to know about them! + +We do not currently perform any optimizations on the IR, since we are primarily working with the +output of compiler backends which have already applied optimizations, at this time. This may change +in the future, but for now it is expected that you implement your own optimization passes as needed. + +## Known bugs and limitations + +For the latest information on known bugs and limitations, see the [issue tracker](https://github.com/0xMiden/compiler/issues). + +## Where to start? + +Provided here are a set of guides which are focused on documenting a couple of supported workflows +we expect will meet the needs of most users, within the constraints of the current feature set of +the compiler. If you find that there is something you wish to do that is not covered, and is not +one of our known limitations, please open an issue, and we will try to address the missing docs as +soon as possible. + +## Installation + +To get started, there are a few ways you might use the Miden compiler. Select the one that applies +to you, and the corresponding guide will walk you through getting up and running: + +1. [Using the Cargo extension](usage/cargo-miden.md) +2. [Using the `midenc` executable](usage/midenc.md) diff --git a/docs/external/src/theme/Admonition/Icon/Danger.tsx b/docs/external/src/theme/Admonition/Icon/Danger.tsx new file mode 100644 index 000000000..ab14d7dec --- /dev/null +++ b/docs/external/src/theme/Admonition/Icon/Danger.tsx @@ -0,0 +1,19 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Danger"; + +export default function AdmonitionIconDanger(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/external/src/theme/Admonition/Icon/Info.tsx b/docs/external/src/theme/Admonition/Icon/Info.tsx new file mode 100644 index 000000000..59e48a521 --- /dev/null +++ b/docs/external/src/theme/Admonition/Icon/Info.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Info"; + +export default function AdmonitionIconInfo(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/external/src/theme/Admonition/Icon/Note.tsx b/docs/external/src/theme/Admonition/Icon/Note.tsx new file mode 100644 index 000000000..d7c524b3a --- /dev/null +++ b/docs/external/src/theme/Admonition/Icon/Note.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Note"; + +export default function AdmonitionIconNote(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/external/src/theme/Admonition/Icon/Tip.tsx b/docs/external/src/theme/Admonition/Icon/Tip.tsx new file mode 100644 index 000000000..219bb8d0a --- /dev/null +++ b/docs/external/src/theme/Admonition/Icon/Tip.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Tip"; + +export default function AdmonitionIconTip(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/external/src/theme/Admonition/Icon/Warning.tsx b/docs/external/src/theme/Admonition/Icon/Warning.tsx new file mode 100644 index 000000000..f96398d11 --- /dev/null +++ b/docs/external/src/theme/Admonition/Icon/Warning.tsx @@ -0,0 +1,20 @@ +import React, { type ReactNode } from "react"; +import type { Props } from "@theme/Admonition/Icon/Warning"; + +export default function AdmonitionIconCaution(props: Props): ReactNode { + return ( + + + + ); +} diff --git a/docs/external/src/theme/Admonition/Layout/index.tsx b/docs/external/src/theme/Admonition/Layout/index.tsx new file mode 100644 index 000000000..7b2c170d8 --- /dev/null +++ b/docs/external/src/theme/Admonition/Layout/index.tsx @@ -0,0 +1,51 @@ +import React, { type ReactNode } from "react"; +import clsx from "clsx"; +import { ThemeClassNames } from "@docusaurus/theme-common"; + +import type { Props } from "@theme/Admonition/Layout"; + +import styles from "./styles.module.css"; + +function AdmonitionContainer({ + type, + className, + children, +}: Pick & { children: ReactNode }) { + return ( +

+ ); +} + +function AdmonitionHeading({ icon, title }: Pick) { + return ( +
+ {icon} + {/* {title} */} +
+ ); +} + +function AdmonitionContent({ children }: Pick) { + return children ? ( +
{children}
+ ) : null; +} + +export default function AdmonitionLayout(props: Props): ReactNode { + const { type, icon, title, children, className } = props; + return ( + + {title || icon ? : null} + {children} + + ); +} diff --git a/docs/external/src/theme/Admonition/Layout/styles.module.css b/docs/external/src/theme/Admonition/Layout/styles.module.css new file mode 100644 index 000000000..88df7e639 --- /dev/null +++ b/docs/external/src/theme/Admonition/Layout/styles.module.css @@ -0,0 +1,35 @@ +.admonition { + margin-bottom: 1em; +} + +.admonitionHeading { + font: var(--ifm-heading-font-weight) var(--ifm-h5-font-size) / + var(--ifm-heading-line-height) var(--ifm-heading-font-family); + text-transform: uppercase; +} + +/* Heading alone without content (does not handle fragment content) */ +.admonitionHeading:not(:last-child) { + margin-bottom: 0.3rem; +} + +.admonitionHeading code { + text-transform: none; +} + +.admonitionIcon { + display: inline-block; + vertical-align: middle; + margin-right: 0.4em; +} + +.admonitionIcon svg { + display: inline-block; + height: 1.6em; + width: 1.6em; + fill: var(--ifm-alert-foreground-color); +} + +.admonitionContent > :last-child { + margin-bottom: 0; +} diff --git a/docs/external/src/theme/Admonition/Type/Caution.tsx b/docs/external/src/theme/Admonition/Type/Caution.tsx new file mode 100644 index 000000000..b570a37a9 --- /dev/null +++ b/docs/external/src/theme/Admonition/Type/Caution.tsx @@ -0,0 +1,32 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Caution'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + caution + + ), +}; + +// TODO remove before v4: Caution replaced by Warning +// see https://github.com/facebook/docusaurus/issues/7558 +export default function AdmonitionTypeCaution(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/external/src/theme/Admonition/Type/Danger.tsx b/docs/external/src/theme/Admonition/Type/Danger.tsx new file mode 100644 index 000000000..49901fa91 --- /dev/null +++ b/docs/external/src/theme/Admonition/Type/Danger.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Danger'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconDanger from '@theme/Admonition/Icon/Danger'; + +const infimaClassName = 'alert alert--danger'; + +const defaultProps = { + icon: , + title: ( + + danger + + ), +}; + +export default function AdmonitionTypeDanger(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/external/src/theme/Admonition/Type/Info.tsx b/docs/external/src/theme/Admonition/Type/Info.tsx new file mode 100644 index 000000000..018e0a16d --- /dev/null +++ b/docs/external/src/theme/Admonition/Type/Info.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Info'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconInfo from '@theme/Admonition/Icon/Info'; + +const infimaClassName = 'alert alert--info'; + +const defaultProps = { + icon: , + title: ( + + info + + ), +}; + +export default function AdmonitionTypeInfo(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/external/src/theme/Admonition/Type/Note.tsx b/docs/external/src/theme/Admonition/Type/Note.tsx new file mode 100644 index 000000000..c99e03857 --- /dev/null +++ b/docs/external/src/theme/Admonition/Type/Note.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Note'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconNote from '@theme/Admonition/Icon/Note'; + +const infimaClassName = 'alert alert--secondary'; + +const defaultProps = { + icon: , + title: ( + + note + + ), +}; + +export default function AdmonitionTypeNote(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/external/src/theme/Admonition/Type/Tip.tsx b/docs/external/src/theme/Admonition/Type/Tip.tsx new file mode 100644 index 000000000..18604a5e9 --- /dev/null +++ b/docs/external/src/theme/Admonition/Type/Tip.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Tip'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconTip from '@theme/Admonition/Icon/Tip'; + +const infimaClassName = 'alert alert--success'; + +const defaultProps = { + icon: , + title: ( + + tip + + ), +}; + +export default function AdmonitionTypeTip(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/external/src/theme/Admonition/Type/Warning.tsx b/docs/external/src/theme/Admonition/Type/Warning.tsx new file mode 100644 index 000000000..61d9597b6 --- /dev/null +++ b/docs/external/src/theme/Admonition/Type/Warning.tsx @@ -0,0 +1,30 @@ +import React, {type ReactNode} from 'react'; +import clsx from 'clsx'; +import Translate from '@docusaurus/Translate'; +import type {Props} from '@theme/Admonition/Type/Warning'; +import AdmonitionLayout from '@theme/Admonition/Layout'; +import IconWarning from '@theme/Admonition/Icon/Warning'; + +const infimaClassName = 'alert alert--warning'; + +const defaultProps = { + icon: , + title: ( + + warning + + ), +}; + +export default function AdmonitionTypeWarning(props: Props): ReactNode { + return ( + + {props.children} + + ); +} diff --git a/docs/external/src/theme/Admonition/Types.tsx b/docs/external/src/theme/Admonition/Types.tsx new file mode 100644 index 000000000..2a1001900 --- /dev/null +++ b/docs/external/src/theme/Admonition/Types.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import AdmonitionTypeNote from '@theme/Admonition/Type/Note'; +import AdmonitionTypeTip from '@theme/Admonition/Type/Tip'; +import AdmonitionTypeInfo from '@theme/Admonition/Type/Info'; +import AdmonitionTypeWarning from '@theme/Admonition/Type/Warning'; +import AdmonitionTypeDanger from '@theme/Admonition/Type/Danger'; +import AdmonitionTypeCaution from '@theme/Admonition/Type/Caution'; +import type AdmonitionTypes from '@theme/Admonition/Types'; + +const admonitionTypes: typeof AdmonitionTypes = { + note: AdmonitionTypeNote, + tip: AdmonitionTypeTip, + info: AdmonitionTypeInfo, + warning: AdmonitionTypeWarning, + danger: AdmonitionTypeDanger, +}; + +// Undocumented legacy admonition type aliases +// Provide hardcoded/untranslated retrocompatible label +// See also https://github.com/facebook/docusaurus/issues/7767 +const admonitionAliases: typeof AdmonitionTypes = { + secondary: (props) => , + important: (props) => , + success: (props) => , + caution: AdmonitionTypeCaution, +}; + +export default { + ...admonitionTypes, + ...admonitionAliases, +}; diff --git a/docs/external/src/theme/Admonition/index.tsx b/docs/external/src/theme/Admonition/index.tsx new file mode 100644 index 000000000..8f4225da2 --- /dev/null +++ b/docs/external/src/theme/Admonition/index.tsx @@ -0,0 +1,21 @@ +import React, {type ComponentType, type ReactNode} from 'react'; +import {processAdmonitionProps} from '@docusaurus/theme-common'; +import type {Props} from '@theme/Admonition'; +import AdmonitionTypes from '@theme/Admonition/Types'; + +function getAdmonitionTypeComponent(type: string): ComponentType { + const component = AdmonitionTypes[type]; + if (component) { + return component; + } + console.warn( + `No admonition component found for admonition type "${type}". Using Info as fallback.`, + ); + return AdmonitionTypes.info!; +} + +export default function Admonition(unprocessedProps: Props): ReactNode { + const props = processAdmonitionProps(unprocessedProps); + const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type); + return ; +} diff --git a/docs/external/src/usage/_category_.yml b/docs/external/src/usage/_category_.yml new file mode 100644 index 000000000..bb98c44f6 --- /dev/null +++ b/docs/external/src/usage/_category_.yml @@ -0,0 +1,4 @@ +label: Usage +# Determines where this documentation section appears relative to other sections in the parent folder +position: 2 +collapsed: true diff --git a/docs/external/src/usage/cargo-miden.md b/docs/external/src/usage/cargo-miden.md new file mode 100644 index 000000000..944da6b7d --- /dev/null +++ b/docs/external/src/usage/cargo-miden.md @@ -0,0 +1,124 @@ +--- +title: As a Cargo extension +sidebar_position: 3 +--- + +# Getting started with Cargo + +As part of the Miden compiler toolchain, we provide a Cargo extension, `cargo-miden`, which provides +a template to spin up a new Miden project in Rust, and takes care of orchestrating `rustc` and +`midenc` to compile the Rust crate to a Miden package. + +## Installation + +:::warning + +Currently, `midenc` (and as a result, `cargo-miden`), requires the nightly Rust toolchain, so +make sure you have it installed first: + +```bash +rustup toolchain install nightly-2025-07-20 +``` + +NOTE: You can also use the latest nightly, but the specific nightly shown here is known to +work. + +::: + +To install the extension, clone the compiler repo first: + +```bash +git clone https://github.com/0xMiden/compiler +``` + +Then, run the following in your shell in the cloned repo folder: + +```bash +cargo install --path tools/cargo-miden --locked +``` + +This will take a minute to compile, but once complete, you can run `cargo help miden` or just +`cargo miden` to see the set of available commands and options. + +To get help for a specific command, use `cargo miden help ` or `cargo miden --help`. + +## Creating an example project + +If you're new to Miden and want to explore some example projects, you can use the `cargo miden example` command to create a project from one of our templates: + +```bash +cargo miden example +``` + +To see the list of available examples, run: + +```bash +cargo miden example --help +``` + +Available examples include: + +- **basic-wallet** - A basic wallet account implementation (creates both the wallet and a paired p2id-note) +- **p2id-note** - A pay-to-ID note script (creates both the note and a paired basic-wallet) +- **counter-contract** - A simple counter contract (creates both the contract and a paired counter-note) +- **counter-note** - A note script for interacting with the counter contract (creates both the note and paired counter-contract) +- **fibonacci** - A Fibonacci sequence calculator demonstrating basic computations +- **collatz** - An implementation of the Collatz conjecture +- **is-prime** - A prime number checker +- **storage-example** - Demonstrates storage operations in Miden + +Note that some examples create paired projects. For instance, running `cargo miden example basic-wallet` will create a directory containing both the `basic-wallet` account and the `p2id-note` script that interacts with it. + +## Creating a new project + +Your first step will be to create a new Rust project set up for compiling to Miden: + +```bash +cargo miden new foo +``` + +In this above example, this will create a new directory `foo`, containing a Cargo project for a +crate named `foo`, generated from our Miden project template. + +The template we use sets things up so that you can pretty much just build and run. Since the +toolchain depends on Rust's native WebAssembly target, it is set up just like a minimal WebAssembly +crate, with some additional tweaks for Miden specifically. + +Out of the box, you will get a Rust crate that depends on the Miden SDK, and sets the global +allocator to a simple bump allocator we provide as part of the SDK, and is well suited for most +Miden use cases, avoiding the overhead of more complex allocators. + +As there is no panic infrastructure, `panic = "abort"` is set, and the panic handler is configured +to use the native WebAssembly `unreachable` intrinsic, so the compiler will strip out all of the +usual panic formatting code. + +## Compiling to Miden package + +Now that you've created your project, compiling it to Miden package is as easy as running the +following command from the root of the project directory: + +```bash +cargo miden build --release +``` + +This will emit the compiled artifacts to `target/miden/release/foo.masp`. + +## Running a compiled Miden VM program + +:::warning + +To run the compiled Miden VM program you need to have `midenc` installed. See [`midenc` docs](./midenc.md) for the installation instructions. + +::: + +The compiled Miden VM program can be run from the Miden package with the following: + +```bash +midenc run target/miden/release/foo.masp --inputs some_inputs.toml +``` + +See `midenc run --help` for the inputs file format. + +## Examples + +Check out the [examples](https://github.com/0xMiden/compiler/tree/next/examples) for some `cargo-miden` project examples. diff --git a/docs/external/src/usage/index.md b/docs/external/src/usage/index.md new file mode 100644 index 000000000..10bbd762f --- /dev/null +++ b/docs/external/src/usage/index.md @@ -0,0 +1,13 @@ +--- +title: Usage +sidebar_position: 1 +--- + +# Usage + +The Usage section documents how to work with Miden's compiler tools. Key components include: + +- **Command-line interface** ([`midenc`](./midenc.md)) - A low-level compiler driver offering precise + control over compilation outputs and diagnostic information +- **Cargo extension** ([`cargo-miden`](./cargo-miden.md)) - Higher-level build tool integration for + managing Miden projects within Rust's package ecosystem diff --git a/docs/external/src/usage/midenc.md b/docs/external/src/usage/midenc.md new file mode 100644 index 000000000..3fdab5b19 --- /dev/null +++ b/docs/external/src/usage/midenc.md @@ -0,0 +1,134 @@ +--- +title: As an Executable +sidebar_position: 2 +--- + +# Getting started with `midenc` + +The `midenc` executable is the command-line interface for the compiler driver, as well as other +helpful tools, such as the interactive debugger. + +While it is a lower-level tool compared to `cargo-miden`, just like the difference between `rustc` +and `cargo`, it provides a lot of functionality for emitting diagnostic information, controlling +the output of the compiler, and configuring the compilation pipeline. Most users will want to use +`cargo-miden`, but understanding `midenc` is helpful for those times where you need to get your +hands dirty. + +## Installation + +:::warning + +Currently, `midenc` (and as a result, `cargo-miden`), requires the nightly Rust toolchain, so +make sure you have it installed first: + +```bash +rustup toolchain install nightly-2025-07-20 +``` + +NOTE: You can also use the latest nightly, but the specific nightly shown here is known to +work. + +::: + +To install the `midenc`, clone the compiler repo first: + +```bash +git clone https://github.com/0xMiden/compiler +``` + +Then, run the following in your shell in the cloned repo folder: + +```bash +cargo install --path midenc --locked +``` + +## Usage + +Once installed, you should be able to invoke the compiler, you should see output similar to this: + +```bash +midenc help compile +Usage: midenc compile [OPTIONS] [-- ...] + +Arguments: + [INPUTS]... + Path(s) to the source file(s) to compile. + + You may also use `-` as a file name to read a file from stdin. + +Options: + --output-dir
+ Write all compiler artifacts to DIR + + -W + Modify how warnings are treated by the compiler + + [default: auto] + + Possible values: + - none: Disable all warnings + - auto: Enable all warnings + - error: Promotes warnings to errors + + -v, --verbose + When set, produces more verbose output during compilation + + -h, --help + Print help (see a summary with '-h') +``` + +The actual help output covers quite a bit more than shown here, this is just for illustrative +purposes. + +The `midenc` executable supports two primary functions at this time: + +- `midenc compile` to compile one of our supported input formats to Miden Assembly +- `midenc debug` to run a Miden program attached to an interactive debugger +- `midenc run` to run a Miden program non-interactively, equivalent to `miden run` + +## Compilation + +See the help output for `midenc compile` for detailed information on its options and their +behavior. However, the following is an example of how one might use `midenc compile` in practice: + +```bash +midenc compile --target rollup \ + --entrypoint 'foo::main' \ + -lextra \ + -L ./masm \ + --emit=hir=-,masp \ + -o out.masp \ + target/wasm32-wasip1/release/foo.wasm +``` + +In this scenario, we are in the root of a Rust crate, named `foo`, which we have compiled for the +`wasm32-wasip1` target, which placed the resulting WebAssembly module in the +`target/wasm32-wasip1/release` directory. This crate exports a function named `main`, which we want +to use as the entrypoint of the program. + +Additionally, our Rust code links against some hand-written Miden Assembly code, namespaced under +`extra`, which can be found in `./masm/extra`. We are telling `midenc` to link the `extra` library, +and to add the `./masm` directory to the library search path. + +Lastly, we're configuring the output: + +- We're using `--emit` to request `midenc` to dump Miden IR (`hir`) to stdout (specified via the `-` + shorthand), in addition to the Miden package artifact (`masp`). +- We're telling `midenc` to write the compiled output to `out.masp` in the current directory, rather + than the default path that would have been used (`target/miden/foo.masp`). + +## Debugging + +See [Debugging Programs](../guides/debugger.md) for details on using `midenc debug` to debug Miden programs. + +## Next steps + +We have put together two useful guides to walk through more detail on compiling Rust to WebAssembly: + +1. To learn how to compile Rust to WebAssembly so that you can invoke `midenc compile` on the + resulting Wasm module, see [this guide](../guides/rust_to_wasm.md). +2. If you already have a WebAssembly module, or know how to produce one, and want to learn how to + compile it to Miden Assembly, see [this guide](../guides/wasm_to_masm.md). + +You may also be interested in our [basic account project template](https://github.com/0xMiden/rust-templates/tree/main/account/template), +as a starting point for your own Rust project. diff --git a/docs/external/static/img/custom_caret.svg b/docs/external/static/img/custom_caret.svg new file mode 100644 index 000000000..0480cf1c4 --- /dev/null +++ b/docs/external/static/img/custom_caret.svg @@ -0,0 +1,3 @@ + + + diff --git a/docs/external/static/img/favicon.ico b/docs/external/static/img/favicon.ico new file mode 100644 index 000000000..732d35222 Binary files /dev/null and b/docs/external/static/img/favicon.ico differ diff --git a/docs/external/static/img/logo.png b/docs/external/static/img/logo.png new file mode 100644 index 000000000..35e949756 Binary files /dev/null and b/docs/external/static/img/logo.png differ diff --git a/docs/external/styles.css b/docs/external/styles.css new file mode 100644 index 000000000..297366633 --- /dev/null +++ b/docs/external/styles.css @@ -0,0 +1,1046 @@ +/* Import Google Fonts */ +@import url("https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap"); + +/* ============================ + 0) SEMANTIC TOKENS (your brand) + ============================ */ + +:root { + /* ---- Brand palette (LIGHT) ---- */ + --color-brand: #ff5500; /* TODO: replace with your primary */ + --color-brand-600: #ff6a26; /* TODO */ + --color-brand-700: #e24c00; /* TODO */ + --color-brand-800: #b63c00; /* TODO */ + --color-secondary: #102445; + + --color-accent: #1764a8; /* TODO secondary/accent */ + --color-success: #00871d; + --color-warning: #ff5500; + --color-danger: #ff0000; + + /* ---- Neutral & Surfaces ---- */ + --color-bg: #ffffff; + --color-surface: #f9f9f9; + --color-elevated: color-mix(in oklab, #000 5%, transparent); + --color-border: color-mix(in oklab, #000 20%, transparent); + --color-text: #363636; + --color-muted: color-mix(in oklab, var(--color-text) 70%, transparent); + --color-link: var(--color-brand); + + /* ---- Typography ---- */ + --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, + "Liberation Mono", monospace; + --font-size-base: 14px; + --line-height-base: 150%; + --letter-spacing-base: 0; + + /* ---- Radii & Spacing ---- */ + --radius-xs: 5px; + --radius-md: 5px; + --radius-lg: 5px; + --radius-xl: 5px; + + /* Design system spacing - exact pixel values */ + --space-4px: 4px; /* Inline code padding */ + --space-16px: 16px; /* Standard paragraph, list, heading spacing */ + --space-24px: 24px; /* H1 to first content, H2 to large components, list indent */ + --space-32px: 32px; /* Section breaks, major structural spacing */ + --space-40px: 40px; /* Major structural reset (lower bound) */ + --space-48px: 48px; /* Major structural reset (upper bound) */ + + /* Legacy spacing variables for compatibility */ + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + + /* ---- Shadows ---- */ + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); + --shadow-md: 0 6px 16px rgba(0, 0, 0, 0.08); + --shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.12); + + /* ---- Container widths ---- */ + + /* Common layout sizes */ + --navbar-height: 64px; + + /* Docs sidebar width */ + --doc-sidebar-width: 250px !important; +} + +/* ========================================== + 1) MAP SEMANTIC → INFIMA (core variables) + ========================================== */ + +:root { + /* Brand */ + --ifm-color-primary: var(--color-brand); + --ifm-color-secondary: var(--color-secondary); + --ifm-color-primary-dark: var(--color-brand-700); + --ifm-color-primary-darker: var(--color-brand-800); + --ifm-color-primary-darkest: var(--color-brand-800); + --ifm-color-primary-light: var(--color-brand-600); + --ifm-color-primary-lighter: var(--color-brand-600); + --ifm-color-primary-lightest: var(--color-brand-600); + + /* Text & surfaces */ + --ifm-color-content: var(--color-text); + --ifm-color-content-secondary: var(--color-muted); + --ifm-link-color: var(--color-link); + --ifm-background-color: var(--color-bg); + --ifm-background-surface-color: var(--color-surface); + + /* Typography */ + --ifm-font-family-base: var(--font-sans); + --ifm-font-family-monospace: var(--font-mono); + --ifm-font-size-base: var(--font-size-base); + --ifm-line-height-base: var(--line-height-base); + --ifm-font-weight-base: 400; + --ifm-heading-font-weight: 700; + + /* Heading typography specifications */ + --ifm-h1-font-size: 28px; + --ifm-h1-line-height: 32px; + --ifm-h2-font-size: 24px; + --ifm-h2-line-height: 28px; + --ifm-h3-font-size: 20px; + --ifm-h3-line-height: 24px; + --ifm-h4-font-size: 16px; + --ifm-h4-line-height: 16px; + + /* Code typography specifications */ + --ifm-code-font-size: 14px; + --ifm-code-line-height: 150%; + --ifm-code-letter-spacing: -2%; + --ifm-code-padding-horizontal: 4px; + --ifm-code-padding-vertical: 4px; + + /* Layout & spacing */ + --ifm-spacing-horizontal: var(--space-4); + --ifm-global-radius: var(--radius-md); + + /* Component radii (Infima) */ + --ifm-breadcrumb-border-radius: var(--radius-md); + --ifm-button-border-radius: var(--radius-md); + --ifm-card-border-radius: var(--radius-md); + --ifm-alert-border-radius: var(--radius-md); + --ifm-badge-border-radius: var(--radius-md); + --ifm-tabs-border-radius: var(--radius-md); + --ifm-table-border-radius: var(--radius-md); + --ifm-code-border-radius: var(--radius-md); + + /* Sizes */ + --ifm-container-width: var(--container-width); + --ifm-navbar-height: var(--navbar-height); + --ifm-menu-link-padding-horizontal: 6px; + --ifm-menu-link-padding-vertical: 12px; + + /* Borders & shadows */ + --ifm-border-color: var(--color-border); + --ifm-card-box-shadow: var(--shadow-md); + --ifm-global-shadow-lw: var(--shadow-sm); + --ifm-global-shadow-md: var(--shadow-md); + --ifm-global-shadow-tl: var(--shadow-lg); + + /* Tables */ + --ifm-table-cell-padding: 0.75rem; + + /* Alerts */ + --ifm-alert-background-color: var(--color-elevated); + --ifm-alert-border-color: var(--color-border); + + /* Breadcrumbs */ + --ifm-breadcrumb-color-active: var(--color-text); + --ifm-breadcrumb-item-background-active: var(--color-elevated); +} + +/* ========================================== + 2) BASE ELEMENTS + ========================================== */ + +html, +body { + font-family: var(--ifm-font-family-base); + font-size: var(--ifm-font-size-base); + font-weight: var(--ifm-font-weight-base); + line-height: var(--ifm-line-height-base); + color: var(--ifm-color-content); + background: var(--ifm-background-color); + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; +} + +/* Remove underlines from all links by default */ +a { + color: var(--ifm-link-color); + text-decoration: none; +} + +/* Add underlines only to links within text content */ +p a, +li a, +blockquote a, +.theme-doc-markdown a, +.markdown a, +article a, +main a { + text-decoration: underline; + text-decoration-color: var(--ifm-link-color); + text-underline-offset: 0.2em; +} + +/* Hover effects for text links */ +p a:hover, +li a:hover, +blockquote a:hover, +.theme-doc-markdown a:hover, +.markdown a:hover, +article a:hover, +main a:hover { + text-decoration-color: currentColor; +} + +/* Ensure navigation links have no underlines */ +.navbar a, +.theme-doc-sidebar-container a, +.menu__link, +.breadcrumbs__link, +.pagination-nav__link, +.table-of-contents a { + text-decoration: none !important; +} + +/* Hover effects for navigation links */ +.navbar a:hover, +.theme-doc-sidebar-container a:hover, +.menu__link:hover, +.breadcrumbs__link:hover, +.pagination-nav__link:hover, +.table-of-contents a:hover { + text-decoration: none !important; +} + +/* Headings - exact design specifications */ +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-sans); + letter-spacing: 0em; + font-weight: bold; +} + +/* H1: 28px font, 32px line height */ +h1 { + font-size: var(--ifm-h1-font-size); + line-height: var(--ifm-h1-line-height); +} + +/* H2: 24px font, 28px line height */ +h2 { + font-size: var(--ifm-h2-font-size); + line-height: var(--ifm-h2-line-height); +} + +/* H3: 20px font, 24px line height */ +h3 { + font-size: var(--ifm-h3-font-size); + line-height: var(--ifm-h3-line-height); +} + +/* H4: 16px line height */ +h4 { + font-size: var(--ifm-h4-font-size); + line-height: var(--ifm-h4-line-height); +} + +h5 { + font-size: 1.125rem; + line-height: 20px; +} + +h6 { + font-size: 1rem; + text-transform: none; + line-height: 16px; +} + +/* Bold text styling */ +strong, +b { + font-weight: 600; + color: var(--ifm-color-content); +} + +/* Body/Paragraph: 14px, 130% line height, Regular */ +p { + font-size: var(--ifm-font-size-base); + line-height: var(--line-height-base); + font-weight: 400; + letter-spacing: 0em; +} + +/* Links (text): 14px, 130% line height, Semibold */ +a { + line-height: 130%; +} + +/* List/Bulletpoints: 14-16px, 150% line height, Regular */ +ul { + list-style-type: disc; + line-height: 150%; +} + +ul li::marker { + color: var(--color-border); +} + +/* Nested unordered lists */ +ul ul { + list-style-type: circle; +} + +ul ul ul { + list-style-type: square; +} + +/* Ordered lists: 14-16px, 150% line height, Regular */ +ol { + list-style-type: decimal; + line-height: 150%; + font-size: var(--font-size-base); + font-weight: 400; +} + +/* Nested ordered lists keep default styling */ +ol ol { + list-style-type: lower-alpha; +} + +ol ol ol { + list-style-type: lower-roman; +} + +/* Blockquote */ +blockquote { + border-left: 3px solid var(--ifm-color-primary); + background: var(--ifm-background-surface-color); + padding: var(--space-4); + border-radius: var(--radius-md); + color: var(--ifm-color-content); +} + +/* Images */ +img { + border-radius: var(--radius-md); +} + +/* ========================================== + 3) NAVBAR & FOOTER (scoped to stable classes) + ========================================== */ + +/* ThemeClassNames.layout.navbar.container */ +.theme-layout-navbar { + height: var(--ifm-navbar-height); + backdrop-filter: saturate(1.1) blur(8px); + border-bottom: 1px solid var(--ifm-border-color) !important; + background: color-mix(in oklab, var(--ifm-background-color) 75%, transparent); + backdrop-filter: saturate(1.1) blur(8px); + box-shadow: none; +} + +/* ThemeClassNames.layout.footer.container */ +.theme-layout-footer { + --ifm-footer-background-color: var(--color-elevated); + background: var(--ifm-footer-background-color); + color: var(--ifm-color-content); +} + +/* ========================================== + 4) SIDEBAR (Docs) + ========================================== */ + +/* ThemeClassNames.docs.docSidebarContainer */ +.theme-doc-sidebar-container { + border-right: 1px solid var(--ifm-border-color) !important; + padding: 5px; + font-size: 0.875rem; /* Sidebar-specific font size */ +} + +/* Main docs content area - wider to fill space */ +.theme-doc-markdown { + max-width: 100%; /* Use full available width */ +} + +/* ThemeClassNames.docs.docSidebarMenu */ +.theme-doc-sidebar-menu .menu__link { + font-family: var(--font-sans); + letter-spacing: -2%; + border-radius: var(--radius-md); + padding: var(--ifm-menu-link-padding-vertical) + var(--ifm-menu-link-padding-horizontal); +} + +.theme-doc-sidebar-menu .menu__link--active { + color: var(--ifm-color-primary); + background: color-mix( + in oklab, + var(--ifm-background-surface-color) 70%, + transparent + ); +} +.theme-doc-sidebar-menu .menu__link--active:hover { + background: var(--ifm-background-surface-color); +} + +/* Category collapsible */ +.menu__list-item-collapsible { + border-radius: var(--radius-md); +} + +/* ========================================== + 5) BREADCRUMBS + ========================================== */ + +/* ThemeClassNames.docs.docBreadcrumbs */ +.theme-doc-breadcrumbs .breadcrumbs__link { + line-height: 150%; + border-radius: var(--radius-md); + padding: var(--ifm-menu-link-padding-horizontal) + var(--ifm-menu-link-padding-vertical); +} + +.theme-doc-breadcrumbs .breadcrumbs__item--active .breadcrumbs__link { + color: var(--ifm-color-primary); +} + +/* ========================================== + 6) BUTTONS + ========================================== */ + +.button { + border-radius: var(--ifm-button-border-radius); + font-weight: 600; + letter-spacing: 0.01em; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + line-height: 150%; +} +.button--outline:not(.button--link) { + border-color: color-mix(in oklab, var(--ifm-color-primary) 70%, transparent); +} +.button--link { + color: var(--ifm-link-color); +} + +/* ========================================== + 7) BADGES + ========================================== */ + +.badge { + border-radius: var(--ifm-badge-border-radius); + border: 1px solid var(--ifm-border-color); + background: var(--ifm-background-surface-color); +} + +/* ========================================== + 8) ALERTS (Admonitions) + ========================================== */ + +.theme-admonition { + display: flex; + align-items: flex-start; /* icon aligns with text top */ + gap: 0.75rem; /* spacing between icon and text */ + padding: 1rem 1.25rem; +} + +/* ThemeClassNames.common.admonition */ +.theme-admonition.alert { + --ifm-alert-background-color: var(--color-elevated); + --ifm-alert-border-color: var(--ifm-color-primary); + --ifm-alert-background-color-highlight: #fff; + + font-family: var(--font-mono); + border: 1px solid var(--color-elevated); + border-top: 5px solid var(--ifm-alert-border-color); /* top border accent */ + border-left: none; /* disable default */ + border-radius: var(--ifm-alert-border-radius); + box-shadow: none; + background: var(--ifm-alert-background-color); + color: var(--ifm-color-content); + /* padding: 1rem 1.25rem; */ +} + +/* NOTE */ +.theme-admonition.alert--secondary { + --ifm-alert-border-color: #df9f26; + border-top-color: #df9f26; +} +/* TIP */ +.theme-admonition.alert--success { + --ifm-alert-background-color: var(--color-elevated); + border-top-color: var(--ifm-color-success) !important; +} +/* INFO / IMPORTANT */ +.theme-admonition.alert--info { + --ifm-alert-border-color: #1764a8; + border-top-color: #1764a8; +} +/* WARNING */ +.theme-admonition.alert--warning { + --ifm-alert-border-color: var(--color-warning); + border-top-color: var(--color-warning); +} + +/* DANGER */ +.theme-admonition.alert--danger { + --ifm-alert-border-color: var(--color-danger); + border-top-color: var(--color-danger); +} + +.theme-admonition ul li::marker { + color: var(--ifm-color-primary); +} + +/* Exclude list margins from alert blocks */ +.theme-admonition ul, +.theme-admonition ol { + margin-top: 0 !important; /* Smaller margins for lists in alert blocks */ + margin-bottom: 0 !important; + padding-left: 1.5rem !important; /* Standard padding for lists in alerts */ +} + +/* Nested lists in alert blocks should have no extra margin */ +.theme-admonition ul ul, +.theme-admonition ol ol, +.theme-admonition ul ol, +.theme-admonition ol ul { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +/* VERSION BANNER */ +.theme-doc-version-banner { + --ifm-alert-background-color: var(--color-elevated); + --ifm-alert-border-color: var(--color-warning); + + font-family: var(--font-mono); + border: 1px solid var(--color-elevated); + border-top: 6px solid var(--ifm-alert-border-color); + border-left: none; /* disable default */ + color: var(--ifm-color-content); +} + +/* ========================================== + 9) TABS + ========================================== */ + +/* ThemeClassNames.tabs.container */ +.theme-tabs-container .tabs { + --ifm-tabs-padding-vertical: 0.375rem; + --ifm-tabs-padding-horizontal: 0.75rem; +} +.theme-tabs-container .tabs__item { + border-radius: var(--ifm-tabs-border-radius); +} +.theme-tabs-container .tabs__item--active { + background: var(--ifm-background-surface-color); + box-shadow: inset 0 0 0 2px + color-mix(in oklab, var(--ifm-color-primary) 24%, transparent); +} + +/* ========================================== + 10) CARDS + ========================================== */ + +.card { + border-radius: var(--ifm-card-border-radius); + box-shadow: var(--ifm-card-box-shadow); + border: 1px solid var(--ifm-border-color); + background: var(--ifm-background-surface-color); +} + +/* ========================================== + 11) TABLES + ========================================== */ + +/* Apply border radius to the entire table */ +table { + border-radius: 5px; /* Adjust this value to change corner rounding */ +} + +tbody tr { + border-top: none; + background: var(--ifm-background-surface-color); +} + +/* ========================= + 12. Code block card + header + ========================= */ + +.theme-code-block { + border: 1px solid var(--ifm-border-color); + border-radius: 5px; + position: relative; +} + +/* Target CSS module class using attribute selector to handle hashed names */ +.theme-code-block [class*="codeBlockTitle"] { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + font-family: var(--font-sans) !important; + font-size: 0.875rem !important; + font-weight: 500 !important; + letter-spacing: 0.02rem !important; + color: var(--color-base) !important; + border-bottom: 1px solid var(--ifm-border-color) !important; +} + +/* Body */ +.theme-code-block pre { + margin: 0; + background: color-mix( + in oklab, + var(--color-surface) 96%, + var(--color-bg, #fff) + ); + padding: 16px 18px; +} + +/* Code text - design specifications */ +.theme-code-block code { + background: transparent; + border: 0; + border-radius: 0; + padding: 0; + font-family: var(--font-mono); + font-size: var(--ifm-code-font-size); /* 14px */ + line-height: var(--ifm-code-line-height); /* 24px */ + letter-spacing: var(--ifm-code-letter-spacing); /* -2% */ + color: var(--color-text); +} + +/* Inline code - design specifications */ +code { + font-family: var(--font-mono); + font-size: 12px; /* 14px */ + line-height: 24px; /* 24px */ + letter-spacing: var(--ifm-code-letter-spacing); /* -2% */ + font-weight: 400; +} + +/* Highlighted lines / magic comments */ +.token-line.highlighted, +.code-block-highlighted-line { + background: color-mix(in oklab, var(--ifm-color-primary) 12%, transparent); +} + +/* Optional: softer $ prompt tint in bash */ +.theme-code-block[data-language="bash"] pre code .token.operator { + opacity: 0.9; +} + +/* Dark tweaks */ +[data-theme="dark"] .theme-code-block .codeBlockTitle { + background: color-mix(in oklab, var(--color-surface) 85%, transparent); +} + +/* ========================================== + 12.1) DETAILS/SUMMARY (Expandable Sections) - styled like code blocks + ========================================== */ + +/* Scope to docs content only */ +.theme-doc-markdown details { + border: 1px solid var(--ifm-border-color); + border-radius: 5px; + background: var(--ifm-background-surface-color); + margin: 0.75rem 0 1rem; + overflow: hidden; /* crisp rounded corners */ + padding: 0; +} + +/* The clickable header row */ +.theme-doc-markdown details > summary { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 12px; + font-weight: 500; + text-transform: uppercase; + font-family: var(--font-sans); + line-height: 1.5; + cursor: pointer; + list-style: none; + user-select: none; + color: var(--color-text); + border-bottom: 1px solid transparent; +} + +/* Hide native disclosure marker */ +.theme-doc-markdown details > summary::marker, +.theme-doc-markdown details > summary::-webkit-details-marker { + display: none; + content: ""; +} + +/* Custom caret icon */ +.theme-doc-markdown details > summary::before { + content: ""; + width: 3px; + height: 3px; + flex-shrink: 0; + -webkit-mask: url("/img/custom_caret.svg") no-repeat center / contain; + mask: url("/img/custom_caret.svg") no-repeat center / contain; + background: currentColor; + transition: transform 0.15s ease; + transform: rotate(0deg); + transform-origin: center center; /* Rotate around its own center */ + opacity: 0.8; + position: relative; + top: 0; +} + +.theme-doc-markdown details[open] > summary { + border-bottom-color: var(--ifm-border-color); +} + +/* Rotate caret when details is open */ +.theme-doc-markdown details[open] > summary::before { + transform: rotate(90deg); + transform-origin: center center; /* Keep center rotation when open */ +} + +/* Body content area */ +.theme-doc-markdown details > :not(summary) { + padding: 1rem; +} + +/* Code block inside details (optional polish) */ +.theme-doc-markdown details pre { + margin: 0; + background: color-mix(in oklab, var(--color-surface) 96%, var(--color-bg)); + border-radius: 6px; +} + +.theme-doc-markdown details > summary { + border-top: none !important; + box-shadow: none !important; +} + +[class^="collapsibleContent_"], +[class*=" collapsibleContent_"] { + padding-top: 0 !important; + border-top: none !important; + box-shadow: none !important; +} + +[class^="collapsibleContent_"]::before, +[class*=" collapsibleContent_"]::before { + display: none !important; + content: none !important; +} + +/* ========================================== + 13) PAGINATION + ========================================== */ + +.pagination-nav__link { + border: 1px solid var(--ifm-border-color); + border-radius: var(--radius-md); + box-shadow: var(--ifm-global-shadow-lw); + background: var(--ifm-background-surface-color); + transition: all 0.2s ease; +} + +/* Hide "Previous" and "Next" labels */ +.pagination-nav__sublabel { + display: none; +} + +/* Main pagination link text - uppercase */ +.pagination-nav__label { + text-transform: uppercase; + font-weight: 500; + letter-spacing: 0.02em; + font-size: 1.2rem; + font-family: var(--font-mono); + color: color-mix(in oklab, #292929bf 75%, #000); +} + +/* Arrow text characters (>> and <<) */ +.pagination-nav__link--prev::before, +.pagination-nav__link--next::after { + color: var(--ifm-color-primary) !important; + font-weight: 700 !important; + font-size: 1.25rem !important; +} + +/* If arrows are in a separate element */ +.pagination-nav__link .pagination-nav__icon, +.pagination-nav__link--prev .pagination-nav__icon, +.pagination-nav__link--next .pagination-nav__icon { + color: var(--ifm-color-primary) !important; +} + +/* ========================================== + 14) TOC (right sidebar) + ========================================== */ + +/* TOC links styling */ +.table-of-contents { + font-family: var(--font-sans); + letter-spacing: 0.01rem; + border-left: 1px solid var(--ifm-border-color); + padding-left: var(--space-4); + font-size: 0.875rem; +} + +.table-of-contents__link { + color: var(--color-text); + display: block; + padding: 4px 0; +} + +.table-of-contents__link--active { + color: var(--ifm-color-primary); + font-weight: 500; +} + +/* ========================================== + 15) VERSION INDICATOR STYLING + ========================================== */ + +/* Style the version indicator that appears above page content */ +.theme-doc-version-badge { + font-size: 0.75rem !important; + font-weight: 600 !important; + color: var(--ifm-color-primary) !important; + background: color-mix( + in oklab, + var(--ifm-color-primary) 12%, + transparent + ) !important; + border: 1px solid + color-mix(in oklab, var(--ifm-color-primary) 30%, transparent) !important; + border-radius: var(--radius-md) !important; + /* line-height: 0 !important; */ + padding-top: 0.37rem !important; + display: inline-block !important; + text-transform: uppercase !important; + letter-spacing: 0.05em !important; + margin-top: 8px !important; + margin-bottom: 8px !important; +} + +/* ========================================== + 16) LOCAL SEARCH STYLING + ========================================== */ + +/* Local search modal styling - integrate with design system */ +.aa-Panel { + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + border: 1px solid var(--color-border); + background: var(--color-bg); +} + +.aa-Form { + border-radius: var(--radius-md); + background: var(--color-surface); + border: 1px solid var(--color-border); +} + +.aa-Input { + font-family: var(--font-sans); + font-size: var(--font-size-base); + color: var(--color-text); + background: transparent; +} + +.aa-Item { + border-radius: var(--radius-md); + color: var(--color-text); +} + +.aa-Item[aria-selected="true"] { + background: var(--color-surface); +} + +.aa-ItemContentTitle { + font-family: var(--font-sans); + font-weight: 600; + color: var(--color-text); +} + +.aa-ItemContentDescription { + font-family: var(--font-sans); + color: var(--color-muted); +} + +/* ========================================== + 17) NUMERED LISTS + ========================================== */ + +/* === DOCUSAURUS "VitePress-style" numbered steps === */ +.steps { + counter-reset: step-counter; + list-style: none; + padding-left: 1.5rem; + margin-left: 2.5rem; /* Space for number badges and line */ + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} +.steps > ol, +.steps > ul { + list-style: none; + margin-left: 0; + padding-left: 0; +} +.steps li { + counter-increment: step-counter; + position: relative; + margin-bottom: 2rem; + min-height: 2rem; +} + +/* Number badge - dynamically aligns with first element */ +.steps li::before { + content: counter(step-counter); + position: absolute; + left: -4rem; /* Align with .steps margin-left */ + top: 0.15rem; /* Slight offset for visual centering with text baseline */ + width: 2rem; + min-height: 2rem; + border-radius: 0px; + background: var(--ifm-background-surface-color); + color: var(--color-text); + font-size: 1rem; + font-family: var(--font-mono); + padding: 0.3rem 0.4rem; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid var(--ifm-border-color); + z-index: 2; + flex-shrink: 0; + line-height: 1; +} + +/* All headings inside steps get proper spacing */ +.steps li > h1, +.steps li > h2, +.steps li > h3, +.steps li > h4, +.steps li > h5, +.steps li > h6 { + margin-top: 0rem !important; + margin-bottom: 1.5rem !important; + line-height: 1.3 !important; + padding-top: 0.7rem !important; +} /* Specific h5 styling (most common) */ +.steps li > h5 { + font-size: 1.15rem !important; + font-weight: 600 !important; +} /* Style for all content inside step items */ +.steps li > * { + margin-bottom: 1rem; +} +.steps li > *:last-child { + margin-bottom: 0; +} + +/* Paragraphs right after headings - reduce space */ +.steps li > h1 + p, +.steps li > h2 + p, +.steps li > h3 + p, +.steps li > h4 + p, +.steps li > h5 + p, +.steps li > h6 + p { + margin-top: -0.75rem; +} + +/* First paragraph without heading */ +.steps li > p:first-child { + margin-top: 0; +} /* Code blocks within steps */ +.steps li pre, +.steps li .theme-code-block { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +/* Lists within steps */ +.steps li ul, +.steps li ol { + margin-top: 0.5rem; + margin-bottom: 0.5rem; + padding-left: 1.5rem; +} /* Admonitions within steps */ +.steps li .theme-admonition { + margin-top: 0.75rem; + margin-bottom: 0.75rem; +} + +/* ========================================== + 18) DESIGN SYSTEM SPACING SPECIFICATIONS + ========================================== */ + +/* Apply design system spacing to documentation content */ +.theme-doc-markdown h1 { + margin-bottom: var(--space-24px); /* H1 → First content block: 24px */ +} + +.theme-doc-markdown h2 { + margin-top: var( + --space-32px + ); /* H2 → previous section (major section break): 32px */ + margin-bottom: var(--space-16px); /* H2 → Paragraph below: 16px */ +} + +.theme-doc-markdown h3 { + margin-bottom: var(--space-16px); /* H3 → Paragraph below: 16px */ +} + +.theme-doc-markdown h4 { + margin-bottom: var(--space-16px); /* H4 → Paragraph below: 16px */ +} + +/* Paragraph spacing */ +.theme-doc-markdown p { + margin-bottom: var(--space-16px); /* Paragraph → Paragraph: 16px */ +} + +/* List spacing */ +.theme-doc-markdown ul, +.theme-doc-markdown ol { + margin-top: var(--space-16px); /* Paragraph → List: 16px */ + margin-bottom: var(--space-16px); /* List → Next Paragraph: 16px */ + padding-left: var(--space-24px); /* List indent: 24px */ +} + +/* H2 to large components (images, tables, diagrams) */ +.theme-doc-markdown h2 + img, +.theme-doc-markdown h2 + table, +.theme-doc-markdown h2 + .theme-admonition { + margin-top: var(--space-24px); /* H2 → large component: 24px */ +} + +/* Major structural reset for dramatic transitions */ +.theme-doc-markdown > *:first-child { + margin-top: 0; +} + +/* Section breaks - ensure consistent spacing between major sections */ +.theme-doc-markdown h1 + h2 { + margin-top: var(--space-32px); /* Major section break */ +} diff --git a/docs/external/tsconfig.json b/docs/external/tsconfig.json new file mode 100644 index 000000000..920d7a652 --- /dev/null +++ b/docs/external/tsconfig.json @@ -0,0 +1,8 @@ +{ + // This file is not used in compilation. It is here just for a nice editor experience. + "extends": "@docusaurus/tsconfig", + "compilerOptions": { + "baseUrl": "." + }, + "exclude": [".docusaurus", "build"] +} diff --git a/docs/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md b/docs/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md deleted file mode 100644 index 5e2145773..000000000 --- a/docs/guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md +++ /dev/null @@ -1,3 +0,0 @@ -# Developing Miden rollup accounts and note scripts in Rust - -This chapter walks you through how to develop Miden rollup accounts and note scripts in Rust using the Miden SDK crate. \ No newline at end of file diff --git a/docs/guides/wasm_to_masm.md b/docs/guides/wasm_to_masm.md deleted file mode 100644 index b9c993e57..000000000 --- a/docs/guides/wasm_to_masm.md +++ /dev/null @@ -1,130 +0,0 @@ -# Compiling WebAssembly to Miden Assembly - -This guide will walk you through compiling a WebAssembly (Wasm) module, in binary form -(i.e. a `.wasm` file), to Miden Assembly (Masm), both in its binary package form (a `.masp` file), -and in textual Miden Assembly syntax form (i.e. a `.masm` file). - -## Setup - -We will be making use of the example crate we created in [Compiling Rust to WebAssembly](rust_to_wasm.md), -which produces a small Wasm module that is easy to examine in Wasm text format, and demonstrates a -good set of default choices for a project compiling to Miden Assembly from Rust. - -In this chapter, we will be compiling Wasm to Masm using the `midenc` executable, so ensure that -you have followed the instructions in the [Getting Started with `midenc`](../usage/midenc.md) guide -and then return here. - -!!! note - - While we are using `midenc` for this guide, the more common use case will be to use the - `cargo-miden` Cargo extension to handle the gritty details of compiling from Rust to Wasm - for you. However, the purpose of this guide is to show you what `cargo-miden` is handling - for you, and to give you a foundation for using `midenc` yourself if needed. - -## Compiling to Miden Assembly - -In the last chapter, we compiled a Rust crate to WebAssembly that contains an implementation -of the Fibonacci function called `fib`, that was emitted to -`target/wasm32-wasip1/release/wasm_fib.wasm`. All that remains is to tell `midenc` to compile this -module to Miden Assembly. - -Currently, by default, the compiler will emit an experimental package format that the Miden VM does -not yet support. We will instead use `midenc run` to execute the package using the VM for us, but -once the package format is stabilized, this same approach will work with `miden run` as well. - -We also want to examine the Miden Assembly generated by the compiler, so we're going to ask the -compiler to emit both types of artifacts: - -```bash -midenc compile --emit masm=wasm_fib.masm,masp target/wasm32-wasip1/release/wasm_fib.wasm -``` - -This will compile our Wasm module to a Miden package with the `.masp` extension, and also emit the -textual Masm to `wasm_fib.masm` so we can review it. The `wasm_fib.masp` file will be emitted in the -default output directory, which is the current working directory by default. - -If we dump the contents of `wasm_fib.masm`, we'll see the following generated code: - -``` -export.fib - push.0 - push.1 - movup.2 - swap.1 - dup.1 - neq.0 - push.1 - while.true - if.true - push.4294967295 - movup.2 - swap.1 - u32wrapping_add - dup.1 - swap.1 - swap.3 - swap.1 - u32wrapping_add - movup.2 - swap.1 - dup.1 - neq.0 - push.1 - else - drop - drop - push.0 - end - end -end -``` - -If you compare this to the WebAssembly text format, you can see that this is a fairly -faithful translation, but there may be areas where we generate sub-optimal Miden Assembly. - -!!! note - - At the moment the compiler does only minimal optimization, late in the pipeline during codegen, - and only in an effort to minimize operand stack management code. So if you see an instruction - sequence you think is bad, bring it to our attention, and if it is something that we can solve - as part of our overall optimization efforts, we will be sure to do so. There _are_ limits to - what we can generate compared to what one can write by hand, particularly because Rust's - memory model requires us to emulate byte-addressable memory on top of Miden's word-addressable - memory, however our goal is to keep this overhead within an acceptable bound in the general case, - and easily-recognized patterns that can be simplified using peephole optimization are precisely - the kind of thing we'd like to know about, as those kinds of optimizations are likely to produce - the most significant wins. - -## Testing with the Miden VM - -!!! note - - Because the compiler ships with the VM embedded for `midenc debug`, you can run your program - without having to install the VM separately, though you should do that as well, as `midenc` only - exposes a limited set of commands for executing programs, intended for debugging. - -We can test our compiled program like so: - - $ midenc run --num-outputs 1 wasm_fib.masp -- 10 - ============================================================ - Run program: wasm_fib.masp - ============================================================ - Executed program with hash 0xe5ba88695040ec2477821b26190e9addbb1c9571ae30c564f5bbfd6cabf6c535 in 19 milliseconds - Output: [55] - VM cycles: 295 extended to 512 steps (42% padding). - ├── Stack rows: 295 - ├── Range checker rows: 67 - └── Chiplets rows: 250 - ├── Hash chiplet rows: 248 - ├── Bitwise chiplet rows: 0 - ├── Memory chiplet rows: 1 - └── Kernel ROM rows: 0 - -Success! We got the expected result of `55`. - -## Next steps - -This guide is not comprehensive, as we have not yet examined in detail the differences between -compiling libraries vs programs, linking together multiple libraries, packages, or discussed some of -the more esoteric compiler options. We will be updating this documentation with those details and -more in the coming weeks and months, so bear with us while we flesh out our guides! diff --git a/docs/index.md b/docs/index.md deleted file mode 100644 index aa212383a..000000000 --- a/docs/index.md +++ /dev/null @@ -1,103 +0,0 @@ -# Getting started - -Welcome to the documentation for the Miden compiler toolchain. - -!!! warning - - The compiler is currently in an experimental state, and has known bugs and limitations, it is - not yet ready for production usage. However, we'd encourage you to start experimenting with it - yourself, and give us feedback on any issues or sharp edges you encounter. - -The documentation found here should provide a good starting point for the current capabilities of -the toolchain, however if you find something that is not covered, but is not listed as -unimplemented or a known limitation, please let us know by reporting an issue on the compiler -[issue tracker](https://github.com/0xpolygonmiden/compiler/issues). - -## What is provided? - -The compiler toolchain consists of the following primary components: - -- An intermediate representation (IR), which can be lowered to by compiler backends wishing to -support Miden as a target. The Miden IR is an SSA IR, much like Cranelift or LLVM, providing a -much simpler path from any given source language (e.g. Rust), to Miden Assembly. It is used -internally by the rest of the Miden compiler suite. -- A WebAssembly (Wasm) frontend for Miden IR. It can handle lowering both core Wasm modules, as -well as basic components using the experimental WebAssembly Component Model. Currently, the Wasm -frontend is known to work with Wasm modules produced by `rustc`, which is largely just what LLVM -produces, but with the shadow stack placed at the start of linear memory rather than after -read-only data. In the future we intend to support more variety in the structure of Wasm modules -we accept, but for the time being we're primarily focused on using this as the path for lowering -Rust to Miden. -- The compiler driver, in the form of the `midenc` executable, and a Rust crate, `midenc-compiler` -to allow integrating the compiler into other tools. This plays the same role as `rustc` does in -the Rust ecosystem. -- A Cargo extension, `cargo-miden`, that provides a convenient developer experience for creating -and compiling Rust projects targeting Miden. It contains a project template for a basic Rust crate, -and handles orchestrating `rustc` and `midenc` to compile the crate to WebAssembly, and then to -Miden Assembly. -- A terminal-based interactive debugger, available via `midenc debug`, which provides a UI very -similar to `lldb` or `gdb` when using the TUI mode. You can use this to run a program, or step -through it cycle-by-cycle. You can set various types of breakpoints; see the source code, call -stack, and contents of the operand stack at the current program point; as well as interatively -read memory and format it in various ways for display. -- A Miden SDK for Rust, which provides types and bindings to functionality exported from the Miden -standard library, as well as the Miden transaction kernel API. You can use this to access native -Miden features which are not provided by Rust out-of-the-box. The project template generated by -`cargo miden new` automatically adds this as a dependency. - -## What can I do with it? - -That all sounds great, but what can you do with the compiler today? The answer depends a bit on what -aspect of the compiler you are interested in: - -### Rust - -The most practically useful, and interesting capability provided by the compiler currently, is the -ability to compile arbitrary Rust programs to Miden Assembly. See the guides for more information -on setting up and compiling a Rust crate for execution via Miden. - -### WebAssembly - -More generally, the compiler frontend is capable of compiling WebAssembly modules, with some -constraints, to Miden Assembly. As a result, it is possible to compile a wider variety of languages -to Miden Assembly than just Rust, so long as the language can compile to WebAssembly. However, we -do not currently provide any of the language-level support for languages other than Rust, and -have limited ability to provide engineering support for languages other than Rust at this time. - -Our Wasm frontend does not support all of the extensions to the WebAssembly MVP, most notably the -reference types and GC proposals. - -### Miden IR - -If you are interested in compiling to Miden from your own compiler, you can target Miden IR, and -invoke the driver from your compiler to emit Miden artifacts. At this point in time, we don't have -the resources to provide much in the way of engineering support for this use case, but if you find -issues in your efforts to use the IR in your compiler, we would certainly like to know about them! - -We do not currently perform any optimizations on the IR, since we are primarily working with the -output of compiler backends which have already applied optimizations, at this time. This may change -in the future, but for now it is expected that you implement your own optimization passes as needed. - -## Known bugs and limitations - -For the latest information on known bugs, see the [issue tracker](https://github.com/0xpolygonmiden/compiler/issues). - -See [Known Limitations](appendix/known-limitations.md) for details on what functionality is -missing or only partially implemented. - - -## Where to start? - -Provided here are a set of guides which are focused on documenting a couple of supported workflows -we expect will meet the needs of most users, within the constraints of the current feature set of -the compiler. If you find that there is something you wish to do that is not covered, and is not -one of our known limitations, please open an issue, and we will try to address the missing docs as -soon as possible. - -## Installation - -To get started, there are a few ways you might use the Miden compiler. Select the one that applies -to you, and the corresponding guide will walk you through getting up and running: - -1. [Using the Cargo extension](usage/cargo-miden.md) -2. [Using the `midenc` executable](usage/midenc.md) diff --git a/docs/internal/.gitignore b/docs/internal/.gitignore new file mode 100644 index 000000000..4e42a1bcd --- /dev/null +++ b/docs/internal/.gitignore @@ -0,0 +1 @@ +book/ \ No newline at end of file diff --git a/docs/internal/book.toml b/docs/internal/book.toml new file mode 100644 index 000000000..81af25ac5 --- /dev/null +++ b/docs/internal/book.toml @@ -0,0 +1,18 @@ +[book] +authors = ["Miden contributors"] +description = "Internal documentation for the Miden compiler." +language = "en" +multilingual = false +title = "Miden Compiler" + +[output.html] +git-repository-url = "https://github.com/0xMiden/compiler" + +[preprocessor.katex] +after = ["links"] + +[preprocessor.alerts] + +[output.linkcheck] +follow-web-links = false +traverse-parent-directories = true diff --git a/docs/internal/src/analyses.md b/docs/internal/src/analyses.md new file mode 100644 index 000000000..5b6e3eb53 --- /dev/null +++ b/docs/internal/src/analyses.md @@ -0,0 +1,111 @@ +# Analyses + +This document provides an overview of some of the current analysis passes the compiler uses when lowering from the frontend to Miden Assembly. This is not guaranteed to be comprehensive, but mostly meant as a high-level reference to what analyses exist and why they are being used. + +All analysis passes, at the time of writing, are maintained in the `midenc-hir-analysis` crate, with the exception of two, which are part of the core `midenc-hir` crate. + +- [Dominance](#dominance) +- [Loop Forest](#loop-forest) +- [Dead Code](#dead-code) +- [Sparse Constant Propagation](#sparse-constant-propagation) +- [Liveness](#liveness) +- [Spills](#spills) + +## Dominance + +Dominance analysis is responsible for computing three data structures commonly used in a compiler operating over a control flow graph in SSA form: + +- [Dominance tree](#dominance-tree) +- [Post-dominance tree](#post-dominance-tree) +- [Iterated dominance frontier](#iterated-dominance-frontier) + +What does dominance refer to in this context? Quite simply, it refers to the relationship between program points in a control flow graph. If all paths to a specific program point $B$, flow through a preceding program point $A$, then it can be said that $A$ _dominates_ $B$. Further, since any program point trivially dominates itself; when $A$ dominates $B$, and $A \neq B$, then $A$ is said to _properly dominate_ $B$. This distinction is occasionally important in the use of the computed dominance analysis. + +We're particularly interested in dominance as it pertains to uses and definitions of values of a program in SSA form. SSA, or _single-static assignment_ form, requires that programs adhere to the following properties: + +- Values, once defined, are immutable - hence "single-static assignment". To "reassign" a value, you must introduce a new definition representing the changed value. This doesn't account for mutating heap-allocated types where the "value" in SSA is the pointer to the data in memory, it is strictly in reference to what is encoded as an SSA value. +- Uses of a value must always be _properly dominated_ by the definition of that value, i.e. there cannot be any path through the control flow graph that reaches a use of a value, not preceded by its definition. + +> [!NOTE] +> Values correspond to registers in an abstract machine with infinitely many of them. Thus the type of a value must be something that has a defined lowering to whatever targets you wish to support. In practice, this is fixed-width integers up to 128 bits, single- or double-precision floating point numbers, pointers, and structs that can be represented as a very small set of scalar values. The set of allowed types of SSA values is not strictly speaking a property of SSA, but it is almost always the case that the types do not include values that require memory allocation (except as a pointer). + +Dominance analysis is critical for safe transformation of SSA-form programs. + +### Dominance tree + +A dominance tree represents blocks of the CFG as a tree, where the root of the tree is the entry block, and each block in the tree has a single parent, its _immediate dominator_, and zero or more children for which it is the immediate dominator. + +A reverse post-order traversal of the dominance tree is commonly used to visit nodes of the CFG such that each node is only seen once all of its predecessors have been seen, or if control must pass through the node before reaching a non-dominating predecessor (e.g. a loop header must always be passed through before any of its loopback edges). + +A dominance tree tells us what blocks control will always pass through to reach any other given block in the CFG. + +### Post-dominance tree + +A post-dominance tree is the inverse of a dominance tree, i.e. $B$ _post-dominates_ $A$, if all paths to the exit node of the CFG starting at $A$, must go through $B$. Accordingly, the _immediate post-dominator_ of $A$ is the post-dominator of $A$ that doesn't strictly post-dominate any other strict post-dominators of $A$. + +A post-dominance tree tells us what blocks control will always pass through to reach the exit. + +### Iterated dominance frontier + +The dominance frontier of some node $B$, is the set of all nodes $N$, such that $B$ dominates an immediate predecessor of $N$, but $B$ does not strictly dominate $N$. In other words, it is the set of nodes where $B$'s dominance stops. + +The _iterated_ dominance frontier of some node $B$, represents computing the dominance frontier $N$ of $B$, and then the dominance frontiers of all nodes in $N$, recursively. + +Iterated dominance frontiers are especially useful when one needs to introduce new definitions of a value, and ensure that all uses of the original value reachable from the new definition, are rewritten to use the new definition. Because uses must be dominated by defs, and the new definition may not strictly dominate all uses, but nevertheless be defined along _some_ path to a use, we may be required to introduce new _phi nodes_ (in HIR, block arguments) that join two or more definitions of a value together at join points in the CFG. The iterated dominance frontier of the new definition tells us all of the blocks where we would need to introduce a new block argument, if there are uses of the value in, or dominated by, that block. + +We currently use this in our spills analysis/transformation, in order to do the very thing described above for each reload of a spilled value (which represent new definitions of the spilled value). We want to ensure that uses of the original value reachable from the reload, use the reload instead, thus terminating the live range of the spilled value at the point it is spilled. + +## Loop Forest + +The loop forest represents the set of loops identified in a given CFG, as well as their components: + +- Entering blocks (loop predecessor blocks), i.e. non-loop nodes that are predecessors of the loop header. If only one such block exists, it is called the _loop pre-header_. +- Loop _header_, i.e. the block which dominates all other loop nodes. +- _Latch_ nodes, i.e. a loop node that has an edge back to the loop header +- _Exiting_ blocks, i.e. blocks which have a successor outside of the loop, but are inside the loop themselves. +- _Exit_ blocks, i.e. a non-loop block which is the successor of an exiting block. + +Each block may only be the header for a single loop, and thus you can identify a loop by the header block. + +See [LLVM Loop Terminology (and Canonical Forms)](https://llvm.org/docs/LoopTerminology.html) for a more comprehensive description of how loops are treated analyzed by the compiler, as we base our implementation on LLVM's. + +The loop forest can be queried for info about a particular loop, whether a block is part of a loop, and if it is a loop header. The information for a particular loop lets you query what blocks are part of the loop, what their role(s) in the loop are, and the relationship to other loops (i.e. whether the loop is a child of another loop). + +We currently do not make heavy use of this, except to attach some loop metadata to nodes during data flow analysis. Since we aim to lift unstructured control flow into structured control flow early during compilation, and this analysis is only defined for unstructured CFGs, it is only pertinent prior to control flow lifting. + +## Dead Code + +The dead code analysis computes the following information about the IR: + +- Whether a block is _executable_, or _live_. +- The set of known predecessors at certain program points (successors of a control flow op, entry points of a callable region, exit points of a call op), and whether those predecessors are executable. + +This is a fundamental analysis in our data flow analysis framework, and it coordinates with the _sparse constant propagation_ analysis to refine its results. We build on this to more precisely compute other analyses based on the liveness of a particular program point, or predecessors to that point. + +We do not yet perform _dead-code elimination_ based on this analysis, but likely will do so in the future. + +## Sparse Constant Propagation + +This analysis takes all constant values in a program, and uses that information to attempt determining whether or not uses of those constants that produce new values, may themselves be reduced to constants. + +This analysis does not transform the IR, that is done by the _sparse conditional constant propagation_ (SCCP) transform. Instead, it attaches data flow facts to each value derived from an operation that uses a value for which we know a constant value. + +This analysis feeds into the [_dead code_](#dead-code) analysis, by determining whether or not specific control flow paths are taken when operands to a control flow op have known constant values, e.g. if a `scf.if` selector is `true`, then the else region is statically dead. + +The SCCP transform mentioned above will actually take the results of the two analyses and rewrite the IR to remove statically unreachable paths, and to fold operations which can be reduced to constants. + +## Liveness + +This analysis computes the liveness, and live range, of every value in the program. This is of crucial importance during code generation, particularly as it relates to management of Miden's operand stack. + +Our specific implementation is based on the ideas and algorithms described in [_Register Spilling and Live-Range Splitting for SSA-form Programs_](https://pp.ipd.kit.edu/uploads/publikationen/braun09cc.pdf), by Matthias Braun and Sebastian Hack. Specifically, unlike many traditional liveness analysis implementations, we do not track liveness as a boolean state at each program point, but rather in terms of _next-use distances_. This seemingly small change has some significant benefits: we are able to reason more precisely about how important certain values are at each program point (e.g. should we keep a value in a register/on the operand stack, or can it be spilled to memory to make room for higher priority values); and we are able to quickly assess on entry to a loop, whether the next use of a value occurs within the loop, or after it. This choice of representation of liveness data plays a key role in our [_spills analysis_](#spills). + +Our liveness analysis also builds on top of the [_dead code_](#dead-code) and [_sparse constant propagation_](#sparse-constant-propagation) analyses, to avoid considering uses of values along code paths which are statically unreachable/dead. Like those two analyses, it is also implemented on top of our data flow analysis framework. + +## Spills + +The purpose of the spills analysis is to identify programs where we can statically determine that the number of simultaneously-live values would overflow the addressable Miden operand stack, thus requiring us to spill values to memory in order to access values that are in the overflow table. By doing this ahead of time during compilation, we can make smarter choices about what to spill, and when, such that the operand stack never overflows, and potentially expensive spill/reload operations are not emitted in hot code paths, such as loops. + +This analysis is tightly integrated with our [_liveness analysis_](#liveness) implementation, particularly the fact that our liveness information is based on next-use distances. Like liveness, it also builds on top of the [_dead code_](#dead-code) and [_sparse constant propagation_](#sparse-constant-propagation) analyses to avoid considering statically unreachable/dead code paths. + +The spills analysis also acts as a register allocator, in that part of how it determines what to spill and when, is by computing the live-in/live-out register sets at each block and operation, along with the set of values in those sets which have been spilled along code paths reaching each program point. We use this information to schedule operands at control flow join points, so that the state of the operand stack is kept consistent on exit from predecessors. diff --git a/docs/internal/src/codegen.md b/docs/internal/src/codegen.md new file mode 100644 index 000000000..1c342de9c --- /dev/null +++ b/docs/internal/src/codegen.md @@ -0,0 +1,3 @@ +# Code Generation + +Coming soon.. diff --git a/docs/internal/src/data_layout.md b/docs/internal/src/data_layout.md new file mode 100644 index 000000000..508030b92 --- /dev/null +++ b/docs/internal/src/data_layout.md @@ -0,0 +1,25 @@ +# Data layout + +This document describes how we map data/memory accesses from the byte-addressable address space asssumed by Rust and most (if not virtually all) other languages, to the element-addressable address space of the Miden VM. + +The details of this are abstracted away by HIR - so if you are working with Miden from Rust, or some other language that lowers to Miden Assembly via the Miden compiler's intermediate representation (HIR), it is essentially transparent. + +However, if you need to integrate handwritten Miden Assembly with, for example, Rust code that has been compiled by `midenc`, you will want to be aware of some of these details, as they are an intrinsic part of the _Application Binary Interface (ABI)_ of the compiled code. + +For most of this document, we'll be using Rust as the source language, and refer to specific details of how it is lowered into HIR via WebAssembly, and how data is laid out in memory from the perspective of Rust/Wasm, and then ultimately mapped to Miden. In general, these details are going to be very similar in other languages, particularly if going through the WebAssembly frontend, but once something is lowered into HIR, the way types are handled is shared across all languages. + +## Byte-addressable memory and type layout + +TODO: Describe how Rust lays out common types in memory, and some of the constraints one needs to be aware of when writing low-level code to access/manipulate those types. + +## Element-addressable memory and type layout + +TODO: Describe the specific methodology used to map the HIR type system to Miden's element-addressable memory. + +## WebAssembly + +TODO: This section will describe details of the core Wasm type system that are relevant here, particularly our reliance on a hack that uses the `float32` type to represent field elements efficiently in Rust/Wasm. + +## Canonical ABI + +TODO: This section will describe relevant aspects of the Canonical ABI type system that developers using the Wasm frontend should be aware of. diff --git a/docs/internal/src/frontends.md b/docs/internal/src/frontends.md new file mode 100644 index 000000000..90c02fbf1 --- /dev/null +++ b/docs/internal/src/frontends.md @@ -0,0 +1,8 @@ +# Supported front ends + +## WebAssembly (Wasm) + +TODO + +For the list of the unsupported Wasm core types, instructions and features, see the +[README](https://github.com/0xMiden/compiler/frontend-wasm/README.md). diff --git a/docs/internal/src/index.md b/docs/internal/src/index.md new file mode 100644 index 000000000..d127c4903 --- /dev/null +++ b/docs/internal/src/index.md @@ -0,0 +1,11 @@ +# Compiler architecture + +This document provides an index of the various design documents for the compiler itself, and its toolchain components. If you see something missing or lacking, please let us know what improvements to these documents you'd like to see! + +- [Frontends](frontends.md) +- [Intermediate Representation (HIR)](ir.md) +- [Data Layout](data_layout.md) +- [Analyses](analyses.md) +- [Rewrites](rewrites.md) +- [Code Generation](codegen.md) +- [Packaging](packaging.md) diff --git a/docs/internal/src/ir.md b/docs/internal/src/ir.md new file mode 100644 index 000000000..c07638f5f --- /dev/null +++ b/docs/internal/src/ir.md @@ -0,0 +1,807 @@ +# High-Level Intermediate Representation (HIR) + +This document describes the concepts, usage, and overall structure of the intermediate representation used by `midenc` and its various components. + +- [Core Concepts](#core-concepts) + - [Dialects](#dialects) + - [Operations](#operations) + - [Regions](#regions) + - [Blocks](#blocks) + - [Values](#values) + - [Operands](#operands) + - [Immediates](#immediates) + - [Attributes](#attributes) + - [Traits](#traits) + - [Interfaces](#interfaces) + - [Symbols](#symbols) + - [Symbol Tables](#symbol-tables) + - [Successors and Predecessors](#successors-and-predecessors) +- [High-Level Structure](#high-level-structure) +- [Pass Infrastructure](#pass-infrastructure) + - [Analysis](#analysis) + - [Pattern Rewrites](#pattern-rewrites) + - [Canonicalization](#canonicalization) + - [Folding](#folding) +- [Implementation Details](#implementation-details) + - [Session](#session) + - [Context](#context) + - [Entity References](#entity-references) + - [Entity Storage](#entity-storage) + - [StoreableEntity](#storeableentity) + - [ValueRange](#valuerange) + - [Entity Lists](#entity-lists) + - [Traversal](#traversal) + - [Program Points](#program-points) + - [Defining Dialects](#defining-dialects) + - [Dialect Registration](#dialect-registration) + - [Dialect Hooks](#dialect-hooks) + - [Defining Operations](#defining-operations) + - [Builders](#builders) + - [Validation](#validation) + - [Effects](#effects) + +## Core Concepts + +HIR is directly based on the design and implementation of [MLIR](https://mlir.llvm.org), in many cases, the documentation there can be a useful guide for HIR as well, in terms of concepts, etc. The actual implementation of HIR looks quite a bit different due to it being in Rust, rather than C++. + +MLIR, and by extension, HIR, are compiler intermediate representations based on a concept called the _Regionalized Value State Dependence Graph_ (commonly abbreviated as RVSDG), first introduced in [this paper](https://arxiv.org/pdf/1912.05036). The RVSDG representation, unlike other representations (e.g. LLVM IR), is oriented around data flow, rather than control flow, though it can represent both. Nodes in the data flow graph, which we call [_operations_](#operations), represent computations; while edges, which we call [_values_](#values), represent dependencies between computations. Regions represent the hierarchical structure of a program, both at a high level (e.g. the relationship between modules and functions), as well as at a low level (e.g. structured control flow, such as an if-else operation, or while loop. This representation allows for representing programs at a much higher level of abstraction, makes many data flow analyses and optimizations simpler and more effective, and naturally exposes parallelism inherent in the programs it represents. It is well worth reading the RVSDG paper if you are interested in learning more! + +More concretely, the above entities relate to each other as follows: + +- Operations can contain regions, operands which represent input values, and results which represent output values. +- Regions can contain [_basic blocks_](#blocks) +- Blocks can contain operations, and may introduce values in the form of block arguments. See the [Basic Blocks](#blocks) section for more details. +- Values from the edges of the data flow graph, i.e. operation A depends on B, if B produces a result that A consumes as an operand. + +As noted above, [operations](#operations) can represent both high-level and low-level concepts, e.g. both a function definition, and a function call. The semantics of an operation are encoded in the form of a wide variety of [_operation traits_](#traits), e.g. whether it is commutative, or idempotent; as well as a core set of [_operation interfaces_](#interfaces), e.g. there is an interface for side effects, unstructured/structured control flow operations, and more. This allows working with operations generically, i.e. you can write a control flow analysis without needing to handle every single control flow operation explicitly - instead, you can perform the analysis against a single interface (or set of interfaces that relate to each other), and any operation that implements the interface is automatically supported by the analysis. + +Operations are organized into [dialects](#dialects). A dialect can be used to represent some set of operations that are used in a specific phase of compilation, but may not be legal in later phases, and vice versa. For example, we have both `cf` (unstructured control flow) and `scf` (structured control flow) dialects. When lowering to Miden Assembly, we require all control flow to be represented using the `scf` dialect, but early in the pipeline, we receive programs with control flow in the `cf` dialect, which is then "lifted" into `scf` before code generation. + +See the following sections for more information on the concepts introduced above: + +- [Dialects](#dialects) +- [Operations](#operations) +- [Regions](#regions) +- [Blocks](#blocks) +- [Values](#values) +- [Traits](#traits) +- [Interfaces](#interfaces) + +### Dialects + +A _dialect_ is a logical grouping of operations, attributes, and associated analyses and transformations. It forms the basis on which the IR is modularized and extended. + +There are currently a limited set of dialects, comprising the set of operations we currently have defined lowerings for, or can be converted to another dialect that does: + +- `builtin`, which provides the `World`, `Component`, `Module`, `Function`, `GlobalVariable`, `Segment`, and `Ret` operations. +- `test`, used for testing within the compiler without external dependencies. +- `ub`, i.e. _undefined behavior_, which provides the `Poison` (and associated attribute type), and `Unreachable` operations. +- `arith`, i.e. _arithmetic_, which provides all of the mathematical operations we currently support lowerings for. This dialect also provides the `Constant` operation for all supported numeric types. +- `cf`, i.e. _control flow_, which provides all of the unstructured control flow or control flow-adjacent operations, i.e. `Br`, `CondBr`, `Switch`, and `Select`. The latter is not strictly speaking a control flow operation, but is semantically similar. This dialect is largely converted to the `scf` dialect before lowering, with the exception of `Select`, and limited support for `CondBr` (to handle a specific edge case of the control flow lifting transformation). +- `scf`, i.e. _structured control flow_, which provides structured equivalents of all the control flow we support, i.e. `If`, `While` (for both while/do and do/while loops), and `IndexSwitch` (essentially equivalent to `cf.switch`, but in structured form). The `Yield` and `Condition` operations are defined in this dialect to represent control flow within (or out of) a child region of one of the previous three ops. +- `hir` (likely to be renamed to `masm` or `vm` in the near future), which is currently used to represent the set of operations unique to the Miden VM, or correspond to compiler intrinsics implemented in Miden Assembly. + +See [_defining dialects_](#defining-dialects) for more information on what dialects are responsible for in HIR. + +### Operations + +An _operation_ represents a computation. Inputs to that computation are in the form of _operands_, and outputs of the computation are in the form of _results_. In practice, an operation may also have _effects_, such as reading/writing from memory, which also represent input/output of the operation, but not explicitly represented in an operation's operands and results. + +Operations can contain zero or more regions. An operation with no regions is also called a _primitive_ operation; while an operation with one or more regions is called a _structured_ operation. An example of the former is the `hir.call` operation, i.e. the function call instruction. An example of the latter is `scf.if`, which represents a structured conditional control flow operation, consisting of two regions, a "then" region, and an "else" region. + +Operations can implement any number of [_traits_](#traits) and [_interfaces_](#interfaces), so as to allow various pieces of IR infrastructure to operate over them generically based on those implementations. For example, the `arith.add` operation implements the `BinaryOp` and `Commutative` traits; the `scf.if` operation implements the `HasRecursiveSideEffects` trait, and the `RegionBranchOpInterface` interface. + +Operations that represent unstructured control flow may also have _successors_, i.e. the set of blocks which they transfer control to. Edges in the control flow graph are formed by "block operands" that act as the value type of a successor. Block operands are tracked in the use list of their associated blocks, allowing one to traverse up the CFG from successors to predecessors. + +Operations may also have associated [_attributes_](#attributes). Attributes represent metadata attached to an operation. Attributes are not guaranteed to be preserved during rewrites, except in certain specific cases. + +### Regions + +A _region_ encapsulates a control-flow graph (CFG) of one or more [_basic blocks_](#blocks). In HIR, the contents of a region are almost always in _single-static assignment_ (SSA) form, meaning that values may only be defined once, definitions must _dominate_ uses, and operations in the CFG described by the region are executed one-by-one, from the entry block of the region, until control exits the region (e.g. via `builtin.ret` or some other terminator instruction). + +The order of operations in the region closely corresponds to their scheduling order, though the code generator may reschedule operations when it is safe - and more efficient - to do so. + +Operations in a region may introduce nested regions. For example, the body of a function consists of a single region, and it might contain an `scf.if` operation that defines two nested regions, one for the true branch, and one for the false branch. Nested regions may access any [_values_](#values) in +an ancestor region, so long as those values dominate the operation that introduced the nested region. The exception to this are operations which are _isolated from above_. The regions of such an operation are not permitted to reference anything defined in an outer scope, except via +[_symbols_](#symbols). For example, _functions_ are an operation which is isolated from above. + +The purpose of regions, is to allow for hierarchical/structured control flow operations. Without them, representing structured control flow in the IR is difficult and error-prone, due to the semantics of SSA CFGs, particularly with regards to analyses like dominance and loops. It is also an important part of what makes [_operations_](#operations) such a powerful abstraction, as it provides a way to generically represent the concept of something like a function body, without needing to special-case them. + +A region must always consist of at least one block (the entry block), but not all regions allow multiple blocks. When multiple blocks are present, it implies the presence of unstructured control +flow, as the only way to transfer control between blocks is by using unstructured control flow operations, such as `cf.br`, `cf.cond_br`, or `cf.switch`. Structured control flow operations such as `scf.if`, introduce nested regions consisting of only a single block, as all control flow within a structured control flow op, must itself be structured. The specific rules for a region depend on the semantics of the containing operation. + +### Blocks + +A _block_, or _basic block_, is a set of one or more [_operations_](#operations) in which there is no control flow, except via the block _terminator_, i.e. the last operation in the block, which is responsible for transferring control to another block, exiting the current region (e.g. returning from a function body), or terminating program execution in some way (e.g. `ub.unreachable`). + +Blocks belong to [_regions_](#regions), and if a block has no parent region, it is considered _orphaned_. + +A block may declare _block arguments_, the only other way to introduce [_values_](#values) into the IR, aside from operation results. Predecessors of a block must ensure that they provide inputs for all block arguments when transfering control to the block. + +Blocks which are reachable as successors of some control flow operation, are said to be _used_ by that operation. These uses are represented in the form of the `BlockOperand` type, which specifies what block is used, what operation is the user, and the index of the successor in the operation's [_successor storage_](#entity-storage). The `BlockOperand` is linked into the [_use-list_](#entity-lists) of the referenced `Block`, and a `BlockOperandRef` is stored as part of the successor information in the using operation's successor storage. This is the means by which the control flow graph is traversed - you can navigate to predecessors of a block by visiting all of its "users", and you navigate to successors of a block by visiting all successors of the block terminator operation. + +### Values + +A _value_ represents terms in a program, temporaries created to store data as it flows through the program. In HIR, which is in SSA form, values are immutable - once created they cannot be changed nor destroyed. This property of values allows them to be reused, rather than recomputed, when the operation that produced them contains no side-effects, i.e. invoking the operation with the same inputs must produce the same outputs. This forms the basis of one of the ways in which SSA IRs can optimize programs. + +> [!NOTE] +> One way in which you can form an intuition for values in an SSA IR, is by thinking of them as registers in a virtual machine with no limit to the number of machine registers. This corresponds well to the fact that most values in an IR, are of a type which corresponds to something that can fit in a typical machine register (e.g. 32-bit or 64-bit values, sometimes larger). +> +> Values which cannot be held in actual machine registers, are usually managed in the form of heap or stack-allocated memory, with various operations used to allocate, copy/move, or extract smaller values from them. While not strictly required by the SSA representation, this is almost always effectively enforced by the instruction set, which will only consist of instructions whose operands and results are of a type that can be held in machine registers. + +Value _definitions_ (aka "defs") can be introduced in two ways: + +1. Block argument lists, i.e. the `BlockArgument` value kind. In general, block arguments are used as a more intuitive and ergonomic representation of SSA _phi nodes_, joining multiple definitions of a single value together at control flow join points. Block arguments are also used to represent _region arguments_, which correspond to the set of values that will be forward to that region by the parent operation (or from a sibling region). These arguments are defined as block arguments of the region's entry block. A prime example of this, is the `Function` op. The parameters expressed by the function signature are reflected in the entry block argument list of the function body region. +2. Operation results, i.e. the `OpResult` value kind. This is the primary way in which values are introduced. + +Both value kinds above implement the `Value` trait, which provides the set of metadata and behaviors that are common across all value kinds. In general, you will almost always be working with values in terms of this trait, rather than the concrete type. + +Values have _uses_ corresponding to usage as an operand of some operation. This is represented via the `OpOperand` type, which encodes the use of a specific value (i.e. its _user_, or owning operation; what value is used; its index in operand storage). The `OpOperand` is linked into the [_use list_](#entity-lists) of the value, and the `OpOperandRef` is stored in the [_entity storage_](#entity-storage) of the using operation. This allows navigating from an operation to all of the values it uses, as well from a value to all of its users. This makes replacing all uses of a value extremely efficient. + +As always, all _uses_ of a value must be dominated by its definition. The IR is invalid if this rule is ever violated. + +#### Operands + +An _operand_ is a [_value_](#values) used as an argument to an operation. + +Beyond the semantics of any given operation, operand ordering is only significant in so far as it is used as the order in which those items are expected to appear on the operand stack once lowered to Miden Assembly. The earlier an operand appears in the list of operands for an operation, the +closer to the top of the operand stack it will appear. + +Similarly, the ordering of operand results also correlates to the operand stack order after lowering. Specifically, the earlier a result appears in the result list, the closer to the top of the operand stack it will appear after the operation executes. + +#### Immediates + +Immediates are a built-in [_attribute_](#attributes) type, which we use to represent constants that are able to be used as "immediate" operands of machine instructions (e.g. a literal memory address, or integer value). + +The `Immediate` type provides a number of useful APIs for interacting with an immediate value, such as bitcasts, conversions, and common queries, e.g. "is this a signed integer". + +It should be noted, that this type is a convenience, it is entirely possible to represent the same information using other types, e.g. `u32` rather than `Immediate::U32`, and the IR makes no assumptions about what type is used for constants in general. We do, however, assume this type is used for constants in specific dialects of the IR, e.g. `hir`. + +### Attributes + +Attributes represent named metadata attached to an _operation_. + +Attributes can be used in two primary ways: + +- A name without a value, i.e. a "marker" attribute. In this case, the presence of the attribute is significant, e.g. `#[inline]`. +- A name with a value, i.e. a "key-value" attribute. This is the more common usage, e.g. `#[overflow = wrapping]`. + +Any type that implements the `AttributeValue` trait can be used as the value of a key/value-style attribute. This trait is implemented by default for all integral types, as well as a handful of IR types which have been used as attributes. There are also a few generic built-in attribute types that you may be interested in: + +- `ArrayAttr`, which can represent an array/vector-like collection of attribute values, e.g. `#[indices = [1, 2, 3]]`. +- `SetAttr`, which represents a set-like collection of attribute values. The primary difference between this and `ArrayAttr` is that the values are guaranteed to be unique. +- `DictAttr`, which represents a map-like collection of attribute values. + +It should be noted that there is no guarantee that attributes are preserved by transformations, i.e. if an operation is erased/replaced, attributes _may_ be lost in the process. As such, you must not assume that they will be preserved, unless made an intrinsic part of the operation definition. + +### Traits + +A _trait_ defines some property of an operation. This allows operations to be operated over generically based on those properties, e.g. in an analysis or rewrite, without having to handle the concrete operation type explicitly. + +Operations can always be cast to their implementing traits, as well as queried for if they implement a given trait. The set of traits attached to an operation can either be declared as part of the operation itself, or be attached to the operation at [dialect registration](#dialect-registration) time via [dialect hooks](#dialect-hooks). + +There are a number of predefined traits, found in `midenc_hir::traits`, e.g.: + +- `IsolatedFromAbove`, a marker trait that indicates that regions of the operation it is attached to cannot reference items from any parents, except via [_symbols_](#symbols). +- `Terminator`, a marker trait for operations which are valid block terminators +- `ReturnLike`, a trait that describes behavior shared by instructions that exit from an enclosing region, "returning" the results of executing that region. The most notable of these is `builtin.ret`, but `scf.yield` used by the structured control flow ops is also return-like in nature. +- `ConstantLike`, a marker trait for operations that produce a constant value +- `Commutative`, a marker trait for binary operations that exhibit commutativity, i.e. the order of the operands can be swapped without changing semantics. + +### Interfaces + +An _interface_, in contrast to a [_trait_](#traits), represents not only that an operation exhibits some property, but also provides a set of specialized APIs for working with them. + +Some key examples: + +- `EffectOpInterface`, operations whose side effects, or lack thereof, are well-specified. `MemoryEffectOpInterface` is a specialization of this interface specifically for operations with memory effects (e.g. read/write, alloc/free). This interface allows querying what effects an operation has, what resource the effect applies to (if known), or whether an operation affects a specific resource, and by what effect(s). +- `CallableOpInterface`, operations which are "callable", i.e. can be targets of a call-like operation. This allows querying information about the callable, such as its signature, whether it is a declaration or definition, etc. +- `CallOpInterface`, operations which can call a callable operation. This interface provides information about the call, and its callee. +- `SelectOpInterface`, operations which represent a selection between two values based on a boolean condition. This interface allows operating on all select-like operations without knowing what dialect they are from. +- `BranchOpInterface`, operations which implement an unstructured control flow branch from one block to one or more other blocks. This interface provides a generic means of accessing successors, successor operands, etc. +- `RegionBranchOpInterface`, operations which implement structured control flow from themselves (the parent), to one of their regions (the children). Much like `BranchOpInterface`, this interface provides a generic means of querying which regions are successors on entry, which regions are successors of their siblings, whether a region is "repetitive", i.e. loops, and more. +- `RegionBranchTerminatorOpInterface`, operations which represent control flow from some region of a `RegionBranchOpInterface` op, either to the parent op (e.g. returning/yielding), or to another region of that op (e.g. branching/yielding). Such operations are always children of a `RegionBranchOpInterface`, and conversely, the regions of a `RegionBranchOpInterface` must always terminate with an op that implements this interface. + +### Symbol Tables + +A _symbol table_ represents a namespace in which [_symbols_](#symbols) may be defined and resolved. + +Operations that represent a symbol table, must implement the `SymbolTable` trait. + +Symbol tables may be nested, so long as child symbol table operations are also valid symbols, so that the hierarchy of namespaces can be encoded as a _symbol path_ (see [Symbols](#symbols)). + +### Symbols + +A _symbol_ is a named operation, e.g. the function `foo` names that function so that it can be referenced and called from other operations. + +Symbols are only meaningful in the context of a _symbol table_, i.e. the namespace in which a symbol is registered. Symbols within a symbol table must be unique. + +A symbol is reified as a _symbol path_, i.e. `foo/bar` represents a symbol path consisting of two path components, `foo` and `bar`. Resolving that symbol path requires first resolving `foo` in the current symbol table, to an operation that is itself a symbol table, and then resolving `bar` there. + +Symbol paths can come in two forms: relative and absolute. Relative paths are resolved as described above, while absolute paths are resolved from the root symbol table, which is either the containing [_world_](#worlds), or the nearest symbol table which has no parent. + +Symbols, like the various forms of [_values_](#values), track their uses and definitions, i.e. when you reference a symbol from another operation, that reference is recorded in the use list of the referenced symbol. This allows us to trivially determine if a symbol is used, and visit all of those uses. + +### Successors and Predecessors + +The concept of _predecessor_ and _successor_ corresponds to a parent/child relationship between nodes in a control-flow graph (CFG), where edges in the graph are directed, and describe the order in which control flows through the program. If a node $A$ transfers control to a node $B$ after it is finished executing, then $A$ is a _predecessor_ of $B$, and $B$ is a _successor_ of $A$. + +Successors and predecessors can be looked at from a few similar, but unique, perspectives: + +#### Relating blocks + +We're generally interested in successors/predecessors as they relate to blocks in the CFG. This is of primary interest in dominance and loop analyses, as the operations belonging to a block inherit the interesting properties of those analyses from their parent block. + +In abstract, the predecessor of a block is the operation which transfers control to that block. When considering what blocks are predecessors of the current block, we're deriving that by mapping each predecessor operation to its parent block. + +We are often interested in specific edges of the CFG, and because it is possible for a predecessor operation to have multiple edges to the same successor block, it is insufficient to refer to these edges by predecessor op and target block alone, instead we also need to know the successor index in the predecessor op. + +Unique edges in the CFG are represented in the form of the `BlockOperand` type, which provides not only references to the predecessor operation and the successor block, but also the index of the successor in the predecessor's successor storage. + +#### Relating operations + +This perspective is less common, but useful to be aware of. + +Operations in a basic block are, generally, assumed to execute in order, top to bottom. Thus, the predecessor/successor terminology can also refer to the relationship between two consecutive operations in a basic block, i.e. if $A$ immediately precedes $B$ in a block, then $A$ is the predecessor of $B$, and $B$ is the successor of $A$. + +We do not generally refer to this relationship in the compiler, except in perhaps one or two places, so as to avoid confusion due to the overloaded terminology. + +#### Relating regions + +Another important place in which the predecessor/successor terminology applies, is in the relationship between a parent operation and its regions, specifically when the parent implements `RegionBranchOpInterface`. + +In this dynamic, the relationship exists between two points, which we represent via the `RegionBranchPoint` type, where the two points can be either the parent op itself, or any of its child regions. In practice, this produces three types of edges: + +1. From the parent op itself, to any of its child regions, i.e. "entering" the op and that specific region). In this case, the predecessor is the parent operation, and the successor is the child region (or more precisely, the entry block of that region). +2. From one of the child regions to one of its siblings, i.e. "yielding" to the sibling region. In this case, the predecessor is the terminator operation of the origin region, and the successor is the entry block of the sibling tregion. +3. From a child regions to the parent operation, i.e. "returning" from the op. In this case, the predecessor is the terminator operation of the child region, and the successor is the operation immediately succeeding the parent operation (not the parent operation itself). + +This relationship is important to understand when working with `RegionBranchOpInterface` and `RegionBranchTerminatorOpInterface` operations. + +#### Relating call and callable + +The last place where the predecessor/successor terminology is used, is in regards to inter-procedural analysis of call operations and their callees. + +In this situation, predecessors of a callable are the set of call sites which refer to it; while successors of a callable are the operations immediately succeeding the call site where control will resume when returning from the callable region. + +We care about this when performing inter-procedural analyses, as it dictates how the data flow analysis state is propagated from caller to callee, and back to the caller again. + +## High-Level Structure + +Beyond the core IR concepts introduced in the previous section, HIR also imposes some hierarchical structure to programs in form of builtin operations that are special-cased in certain respects: + +- [Worlds](#worlds) +- [Components](#components) +- [Modules](#modules) +- [Functions](#functions) + +In short, when compiling a program, the inputs (source program, dependencies, etc.) are represented in a single _world_ (i.e. everything we know about that program and what is needed to compile it). The input program is then translated into a single top-level _component_ of that world, and any of it's dependendencies are represented in the form of component _declarations_ (in HIR, a declaration - as opposed to a definition - consists of just the metadata about a thing, not its implementation, e.g. a function signature). + +A _component_ can contain one or more _modules_, and optionally, one or more _data segments_. Each module can contain any number of _functions_ and _global variables_. + +> [!NOTE] +> To understand how these relate to Miden Assembly, and Miden packages, see the [Packaging](packaging.md) document. + +The terminology and semantics of worlds and components, are based on the Web Assembly [Component Model](https://component-model.bytecodealliance.org). In particular, the following properties are key to understanding the relationships between these entities: + +- Worlds must encode everything needed by a component +- Components represent a shared-nothing boundary, i.e. nothing outside a component can access the resources of that component (e.g. memory). We rely on this property so that we can correctly represent the interaction between Miden _contexts_ (each of which has its own memory, with no way to access the memory of other contexts). +- Component-level exports represent the "interface" of a component, and are required to adhere to the [Canonical ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md). + +The following is a rough visual representation of the hierarchy and relationships between these concepts in HIR: + + World + | + v + ---- Component ----------- + | | + v | + Function (component export) | + | + ---------------- + | | + v v + Module Data Segment + | + |----------- + v v + Function Global Variable + | + v + ----- Region (a function has a single region, it's "body") + | | (the body region has a single block, it's "entry") + | v + | Block -> Block Argument (function parameters) + | | | + | | | + | | v + | | Operand + | v | ^ + ---> Operation <-- | + | | + v | + Result ------- + +A few notes: + +- Dependencies between components may only exist between component-level exported functions, i.e. it is not valid to depend on a function defined in a module of another component directly. +- Only component exports use the Canonical ABI, internally they handle lifting/lowering to the "core" ABI of the function which actually implements the behavior being exported. +- Data segments represent data that will be written into the shared memory of a component when the component is initialized. Thus, they must be specified at component level, and may not be shared between components. +- Global variables, representing some region of memory with a specified type, by definition cannot be shared between components, and are only visible within a component. We further restrict their definition to be within a module. Global variables _can_ be shared between modules, however. +- Worlds, components, and modules are single-region, single-block operations, with graph-like region semantics (i.e. their block does not adhere to SSA dominance rules). They all implement the `SymbolTable` trait, and all but World implements the `Symbol` trait. +- Functions are single-region, but that region can contain multiple blocks, and the body region is an SSA CFG region, i.e. it's blocks and operations must adhere to SSA dominance rules. The interaction with a function is determined by its _signature_, which dictates the types of its parameters and results, but these are not represented as operation operands/results, instead the function parameters are encoded as block parameters of its entry block, and function results are materialized at call sites based on the function signature. A validation rule ensures that the return-like operations in the function body return values that match the signature of the containing function. + +### Worlds + +A _world_ represents all available information about a program and its dependencies, required for compilation. It is unnamed, and so it is not possible to interact between worlds. + +Worlds may only contain components (possibly in the future we'll relax this to allow for non-component modules as well, but not today). Each world is a symbol table for the components it contains, facilitating inter-component dependencies. + +A world is by definition the root symbol table for everything it contains, i.e. an absolute symbol path is always resolved from the nearest world, or failing that, the nearest operation without a parent. + +### Components + +A _component_ is a named entity with an _interface_ comprised of it's exported functions. This implicit interface forms a signature that other components can use to provide for link-time virtualization of components, i.e. any component that can fulfill a given interface, can be used to satisfy that interface. + +Components may contain modules, as well as data segment definitions which will be visible to all code running within the component boundary. + +A component _declaration_, as opposed to a definition, consists strictly of its exported functions, all of which are declarations, not definitions. + +A component _instance_ refers to a component that has had all of its dependencies resolved concretely, and is thus fully-defined. + +The modules of a component provide the implementation of its exported interface, i.e. top-level component functions typically only handle lifting module exports into the Canonical ABI. + +### Modules + +A module is primarily two things: + +1. A named container for one or more functions belonging to a common namespace. +2. A concrete implementation of the functionality exported from a component. + +Functions within a module may be exported. Functions which are _not_ exported, are only visible within that module. + +A module defines a symbol table, whose entries are the functions and global variables defined in that module. Relative symbol paths used within the module are always resolved via this symbol table. + +### Functions + +A function is the highest-level unit of computation represented in HIR, and differs from the other container types (e.g. component, module), in that its body region is an SSA CFG region, i.e. its blocks and operations must adhere to the SSA dominance property. + +A function _declaration_ is represented as a function operation whose body region is empty, i.e. has no blocks. + +A function has a signature that encodes its parameters and results, as well as the calling convention it expects callers to use when calling it, and any special attributes that apply to it (i.e. whether it is inlineable, whether any of its parameters are special in some way, etc.). + +Function parameters are materialized as values in the form of entry block arguments, and always correspond 1:1. Function results are materialized as values only at call sites, not as operation results of the function op. + +Blocks in the function body must be terminated with one of two operations: + +- `builtin.ret`, which returns from the function to its caller. The set of operands passed to this operation must match the arity and types specified in the containing function's signature. +- `ub.unreachable`, representing some control flow path that should never be reachable at runtime. This is translated to an abort/trap during code generation. This operation is defined in the `ub` dialect as it corresponds to undefined behavior in a program. + +### Global Variables + +A global variable represents a named, typed region of memory, with a fixed address at runtime. + +Global variables may specify an optional _initializer_, which is a region consisting of operations that will be executed in order to initialize the state of the global variable prior to program start. Typically, the initializer should only consist of operations that can be executed at compile-time, not runtime, but because of how Miden memory is initialized, we can actually relax this rule. + +## Pass Infrastructure + +Compiler passes encode transformations of the IR from frontend to backend. In HIR, you define a pass over a concrete operation type, or over all operations and then filter on some criteria. + +The execution of passes is configured and run via a _pass manager_, which you construct and then add passes to, and then run once finalized. + +Passes typically make uses of [_analyses_](#analyses) in order to perform their specific transformations. In order to share the computation of analyses between passes, and to correctly know when those analyses can be preserved or recomputed, the pass manager will construct an _analysis manager_, which is then provided to passes during execution, so that they can query it for a specific analysis of the current operation. + +Passes can register statistics, which will then be tracked by the pass manager. + +The primary way you interact with the pass infrastructure is by: + +1. Construct a `PassManager` for whatever root operation type you plan to run the pass pipeline on. +2. Add one or more `Pass` implementations, nesting pass managers as needed in order to control which passes are applied at which level of the operation hierarchy. +3. Run the `PassManager` on the root operation you wish to transform. + +In HIR, there are three primary types of passes: + +- DIY, i.e. anything goes. What these do is completely up to the pass author. +- Pattern rewrites, which match against an operation by looking for some pattern, and then performing a rewrite of that operation based on that pattern. These are executed by the `GreedyPatternRewriteDriver`, and must adhere to a specific set of rules in order for the driver to be guaranteed to reach fixpoint. +- Canonicalizations, a special case of pattern rewrite which are orchestrated by the `Canonicalizer` rewrite pass. + +### Analyses + +An _analysis_ is responsible for computing some fact about the given IR entity it is given. Facts include things such as: the dominance tree for an SSA control flow graph; identifying loops and their various component parts such as the header, latches, and exits; reachability; liveness; identifying unused (i.e. dead) code, and much more. + +Analyses in the IR can be defined in one of two ways (and sometimes both): + +1. As an implementation of the `Analysis` trait. This is necessary for analyses which you wish to query from the `AnalysisManager` in a pass. +2. As an implementation of the `DataFlowAnalysis` trait, or one of its specializations, e.g. `DenseBackwardDataFlowAnalysis`. These are analyses which adhere to the classical data flow analysis rules, i.e. the analysis state represents a join/meet semi-lattice (depending on the type and direction of the analysis), and the transfer function ensures that the state always converges in a single direction. + +`Analysis` implementations get the current `AnalysisManager` instance in their `analyze` callback, and can use this to query other analyses that they depend on. It is important that implementations also implement `invalidate` if they should be invalidated based on dependent analyses (and whether those have been invalidated can be accessed via the provided `PreservedAnalyses` state in that callback). + +Analyses can be implemented for a specific concrete operation, or any operation. + +### Pattern Rewrites + +See the [Rewrites](rewrites.md) document for more information on rewrite passes in general, including the current set of transformation passes that build on the pattern rewrite infrastructure. + +Pattern rewrites are essentially transformation passes which are scheduled on a specific operation type, or any operation that implements some trait or interface; recognizes some pattern about the operation which we desire to rewrite/transform in some way, and then attempts to perform that rewrite. + +Pattern rewrites are applied using the `GreedyPatternRewriteDriver`, which coordinates the application of rewrites, reschedules operations affected by a rewrite to determine if the newly rewritten IR is now amenable to further rewrites, and attempts to fold operations and materialize constants, and if so configured, apply region simplification. + +These are the core means by which transformation of the IR is performed. + +### Canonicalization + +Canonicalization is a form of [_pattern rewrite_](#pattern-rewrites) that applies to a specific operation type that has a _canonical_ form, recognizes whether the operation is in that form, and if not, transforms it so that it is. + +What constitutes the _canonical form_ of an operation, depends on the operation itself: + +- In some cases, this might be ensuring that if an operation has a constant operand, that it is always in the same position - thus making pattern recognition at higher levels easier, as they only need to attempt to match a single pattern. +- In the case of control flow, the canonical form is often the simplest possible form that preserves the semantics. +- Some operations can be simplified based on known constant operands, or reduced to a constant themselves. This process is called [_constant folding_](#folding), and is an implicit canonicalization of all operations which support folding, via the `Foldable` trait. + +#### Folding + +Constant-folding is the process by which an operation is simplified, replaced with a simpler/less-expensive operation, or reduced to a constant value - when some or all of its operands are known constant values. + +The obvious example of this, is something like `v3 = arith.add v1, v2`, where both `v1` and `v2` are known to be constant values. This addition can be performed at compile-time, and the entire `arith.add` replaced with `arith.constant`, potentially enabling further folds of any operation using `v3`. + +What about when only some of the operands are constant? That depends on the operation in question. For example, something like `v4 = cf.select v1, v2, v3`, where `v1` is known to be the constant value `true`, would allow the entire `cf.select` to be erased, and all uses of `v4` replaced with `v2`. However, if only `v2` was constant, the attempt to fold the `cf.select` would fail, as no change can be made. + +A fold has three outcomes: + +- Success, i.e. the operation was able to be folded away; it can be erased and all uses of its results replaced with the fold outputs +- In-place, the operation was able to be simplified, but not folded away/replaced. In this case, there are no fold outputs, the original operation is simply updated. +- Failure, i.e. the operation could not be folded or simplified in any way + +Operation folding can be done manually, but is largely handled via the [_canonicalization_](#canonicalization) pass, which combines folding with other pattern rewrites, as well as region simplification. + +## Implementation Details + +The following sections get into certain low-level implementation details of the IR, which are important to be aware of when working with it. They are not ordered in any particular way, but are here for future reference. + +You should always refer to the documentation associated with the types mentioned here when working with them; however, this section is intended to provide an intro to the concepts and design decisions involved, so that you have the necessary context to understand how these things fit together and are used. + +### Session + +The `Session` type, provided by the `midenc-session` crate, represents all of the configuration for the current compilation _session_, i.e. invocation. + +A session begins by providing the compiler driver with some inputs, user-configurable flags/options, and intrumentation handler. A session ends when those inputs have been compiled to some output, and the driver exits. + +### Context + +The `Context` type, provided by the `midenc-hir` crate, encapsulates the current [_session_](#session), and provides all of the IR-specific storage and state required during compilation. + +In particular, a `Context` maintains the set of registered dialects, their hooks, the allocator for all IR entities produced with that context, and the uniquer for allocated value and block identifiers. All IR entities which are allocated using the `Context`, are referenced using [_entity references_](#entity-references). + +The `Context` itself is not commonly used directly, except in rare cases - primarily only when extending the context with dialect hooks, and when allocating values, operands, and blocks by hand. + +Every operation has access to the context which created it, making it easy to always access the context when needed. + +> [!WARNING] +> You _must_ ensure that the `Context` outlives any reference to an IR entity which is allocated with it. For this reason, we typically instantiate the `Context` at the same time as the `Session`, near the driver entrypoint, and either pass it by reference, or clone a reference-counted pointer to it; only dropping the original as the compiler is exiting. + +### Entity References + +All IR entities, e.g. values, operations, blocks, regions - are allocated via an arena allocator provided by the [`Context`](#context), along with any custom metadata relevant for the specific entity type. A custom smart pointer type, called `RawEntityRef`, is then used to reference those entities, while simultaneously providing access to the metadata, and enforcing Rust's aliasing rules via dynamic borrow checking. + +This approach is used due to the graph-like structure of the IR itself - the ergonomics we can provide this way far outweigh the cost of dynamic borrow checking. However, it does place the burden on the programmer to carefully manage the lifetime of borrowed entities, so as to avoid aliasing conflicts. To make such issues easy to troubleshoot and fix, the `RawEntityRef` type will track both the source location of a borrow, and the location where a conflicting borrow occurs, and print this information as part of the runtime error that occurs (when compiled with the `debug_refcell` feature enabled). + +There are two main "types" of `RawEntityRef` metadata: + +- `()`, aliased as `UnsafeEntityRef` +- `IntrusiveLink`, aliased as `UnsafeIntrusiveEntityRef`. + +In both cases, the type is aliased to reflect the underlying entity type being referenced, e.g. `BlockRef` is an `UnsafeIntrusiveEntityRef`, and `ValueRef` is an `UnsafeEntityRef`. + +The latter of the two is the most important, as it is used for all entity types which have a parent entity in which they are tracked by means of an intrusive doubly-linked list (called an [_entity list_](#entity-lists)), e.g. operations, blocks, regions, operands, etc. The key thing that the `UnsafeIntrusiveEntityRef` type provides, is access to the parent entity, if linked to one, and access to the previous and next sibling of the containing entity list, if present - without needing to borrow the underlying entity. This is critical for traversing the IR without needing to borrow each entity being traversed, unless one wants to explicitly visit it. Entities allocated into an `UnsafeIntrusiveEntityRef` must also implement the `EntityListItem` trait, which provides various callbacks that are invoked when inserting/removing/transferring them between the entity list of the parent entity type. + +The `UnsafeEntityRef` type, in contrast, is used for entities that are not tracked in an entity list, but in [_entity storage_](#entity-storage), i.e. a small, resizeable vector of references which are stored as part in the parent entity. Examples include block arguments, operation results, and operation successors. Entities allocated into an `UnsafeEntityRef` and stored in `EntityStorage`, must also implement the `StoreableEntity` trait, which provides a similar set of callbacks to `EntityListItem` for managing the lifecycle of an entity as it is inserted, removed, etc. + +#### Entity Storage + +This refers to the `EntityStorage` type, which abstracts over the storage of IR entities in a small, resizeable vector attached to a parent entity. + +The `EntityStorage` type provides the following: + +- Lifecycle management of stored entities, via the `StoreableEntity` trait +- Grouping of entities within storage, with relative indexing, support for insertion, removal, iteration, etc. This is used, for example, to use a single `EntityStorage` for all operands of an operation, while grouping operands semantically (e.g. group 0 are operands of the op itself, group 1 through N are operand groups for each successor of the operation). +- Conveniences for indexing, slicing, iterating, etc. + +##### StoreableEntity + +The `StoreableEntity` trait is used to ensure that, as entities are inserted/removed/transferred between instances of `EntityStorage`, that metadata about the relationship between the stored entity, and the parent, is updated accordingly. + +For example, this is used to ensure that when an `OpOperand` is inserted into the operand storage of an operation, that it is added to the use list of the referenced `Value`. Conversely, when that same operand is removed from the operand storage, the use of the referenced `Value` is removed. + +##### ValueRange + +The `ValueRange` type is intended to provide a uniform, efficient API over slices/ranges of value-like types, in the form of `ValueRef`. It can be directly constructed from any `EntityRange` or `EntityStorage` reference of `BlockArgumentRef`, `OpOperandRef`, or `OpResultRef` type; as well as raw slices of any of those types in addition to `ValueRef`. + +It supports borrowed and owned variants, iteration, indexing, and conversions. + +In general, you should prefer to use `ValueRange` when working with collections of value-like types, unless you have a specific reason otherwise. + +#### Entity Lists + +An entity list is simply a doubly-linked, intrusive list, owned by some entity, in which references to some specific child entity type are stored. + +Examples include: + +- The list of regions belonging to an operation +- The list of blocks belonging to a region +- The list of operations belonging to a block +- The list of operands using a block argument/op result +- The list of symbol users referencing a symbol + +In conjunction with the list itself, there are a set of traits which facilitate automatically maintaining the relationship between parent and child entity as items are inserted, removed, or transferred between parent lists: + +- `EntityParent`, implemented by any entity type which has some child entity of type `Child`. This provides us with the ability to map a parent/child relationship to the offset of the intrusive linked list in the parent entity, so that we can construct a reference to it. Entities can be the parent of multiple other entity types. +- `EntityWithParent`, implemented by the child entity which has some parent type `T`, this provides the inverse of `EntityParent`, i.e. the ability for the entity list infrastructure to resolve the parent type of a child entity it stores, and given a reference to the parent entity, get the relevant intrusive list for that child. Entities with a parent may only have a single parent entity type at this time. +- `EntityListItem`, implemented by any entity type which can be stored in an entity list. This trait provides the set of callbacks that are invoked by the entity list infrastructure when modifying the list (inserting, removing, and transferring items). This trait is public, but the entity list infra actually uses a different trait, called `EntityListTraits`, which is specialized based on whether the list items implement just `EntityListItem`, or both `EntityListItem` and `EntityWithParent`. The specialization for the latter ensures that the parent/child relationship is updated appropriately by the entity list itself, rather than requiring `EntityListItem` implementations to do so. + +We use intrusive linked lists for storing sets of entities that may be arbitrarily large, and where the O(1) insertion, removal, splicing and splitting makes up for the less cache-friendly iteration performance. Given a reference to an entity, we can always construct a cursor to that element of the list, and traverse the list from there, or modify the list there - this is a much more frequent operation than iterating these lists. + +### Traversal + +Before we can discuss the various ways of traversing the IR, we need to clarify what aspect of the IR we're interested in traversing: + +- The data flow graph, i.e. nodes are operations, edges are formed by operands referencing values. +- The control flow graph, i.e. nodes are either operations, blocks, or regions; and edges are formed by operations which transfer control (to the next op, to a specific set of successor blocks or regions). +- The call graph, i.e. nodes are symbols which implement `CallableOpInterface`, and edges are formed by operations which implement `CallOpInterface`. + +There are also a few traversal primitives which are commonly used: + +- Any `UnsafeIntrusiveEntityRef` for a type which implements `EntityListItem`, provides `next` and `prev` methods which allow navigating to the next/previous sibling item in the list, without borrowing the entity itself. In some cases, being siblings in an entity list does not mean that the items are near each other, e.g. the only thing shared in common between uses of a `Symbol`, is the symbol they refer to, but their order in the symbol use-list has no semantic meaning. In others, being siblings mean that the items are actually located next to each other in that order, e.g. operations in a block. +- Similarly, any `UnsafeIntrusiveEntityRef` for a type which implements `EntityWithParent`, provides a `parent` method which allow navigating to the parent entity from the child, without borrowing either of them. +- All child entities "owned" by a parent entity, are stored in either a [_entity list_](#entity-lists) or [_entity storage_](#entity-storage) attached to that entity. + +These three primitives provide the core means by which one can navigate the relevant graph in any direction. + +Another thing to be aware of, is that relationships between entities where there may be multiple edges between the same two entities, are typically represented using a special node type. For example: + +- `OpOperand` represents a use of a `Value` by an operation. In order to maintain the use-def graph of values, each value type, e.g. `BlockArgument`, has its own entity list for `OpOperand`s. What is stored in the relevant entity storage of the operation then, are `OpOperandRef`s. So while operands are intuitively something we think of as an intrinsic part of an operation, they are actually their own IR entity, which is then stored by reference both in the operation, and in the use-list of the value they reference. +- `BlockOperand` represents a "use" of a `Block` by an operation as a successor. This type is responsible for forming the edges of the CFG, and so much like `OpOperand`, the `Block` type has an entity list for `BlockOperand`s, effectively the set of that block's predecessors; while the operation has entity storage for `BlockOperandRefs` (or more precisely, `SuccessorInfo`, of which `BlockOperandRef` is one part). +- `SymbolUse` represents a use of a `Symbol` by an operation. This underpins the maintenance of the call graph. Unlike operands, symbol usage is not tracked as a fundamental part of every operation, i.e. there is no dedicated `symbols` field of the `Operation` type which provides the entity storage for `SymbolUseRef`s, nor is there a field which defines the entity list. Instead, the symbol use list of an op that implements `Symbol`, must be defined as part of the concrete operation type. Similarly, any concrete operation type that can use/reference a `Symbol` op, must determine for itself how it will store that use. For this reason, symbol maintenance is a bit less ergonomic than other entity types. + +We now can explore the different means by which the IR can be traversed: + +1. Using the raw traversal primitives described above. +2. The `Graph` trait +3. The `Walk` and `RawWalk` traits +4. `CallOpInterface` and `CallableOpInterface` (specifically for traversing the call graph) + +#### The `Graph` trait + +The `Graph` trait is an abstraction for directed graphs, and is intended for use when writing generic graph algorithms which can be reused across multiple graph types. For example, the implementation of pre-order and post-order visitors is implemented over any `Graph`. + +This trait is currently implemented for `Region`, `Block`/`BlockRef`, and `DomTreeNode`, as so far they are the only types we've needed to visit using this abstraction, primarily when performing pre-order/post-order visits of the CFG and/or dominance tree. + +#### The `Walk` and `RawWalk` traits + +The `Walk` trait defines how to walk all children of a given type, which are contained within the type for which `Walk` is being implemented. For example: + +- `Walk for Region` defines how to traverse a region to visit all of the operations it contains, recursively. +- `Walk for Operation` defines how to traverse an operation to visit all of the regions it contains, recursively. + +The difference between `Walk` and `RawWalk`, is that `Walk` requires borrowed references to the types it is implemented for, while `RawWalk` relies on the traversal primitives we introduced at the start of this section, to avoid borrowing any of the entities being traversed, with the sole exception being to access child entity lists long enough to get a reference to the head of the list. If we are ever mutating the IR as we visit it, then we use `RawWalk`, otherwise `Walk` tends to be more ergonomic. + +The `Walk` and `RawWalk` traits provide both pre- and post-order traversals, which dictates in what order the visit callback is invoked. You can further dictate the direction in which the children are visited, e.g. are operations of a block visited forward (top-down), or backward (bottom-up)? Lastly, if you wish to be able to break out of a traversal early, the traits provide variants of all functions which allow the visit callback to return a `WalkResult` that dictates whether to continue the traversal, skip the children of the current node, or abort the traversal with an error. + +#### `CallOpInterface` and `CallableOpInterface` + +These two interfaces provide the means for navigating the call graph, which is not explicitly maintained as its own data structure, but is rather implied by the connections between symbols and symbol uses which implement these interfaces, in a program. + +One is generally interested in the call graph for one of a couple reasons: + +1. Determine if a function/callable is used +2. Visit all callers of a function/callable +3. Visit the call graph reachable from a given call site as part of an analysis +4. Identify cycles in the call graph + +For 1 and 2, one can simply use the `Symbol` use-list: an empty use-list means the symbol is unused. For non-empty use-lists, one can visit every use, determine if that use is by a `CallOpInterface`, and take some action based on that. + +For 2 and 3, the mechanism is essentially identical: + +1. Assume that you are starting from a call site, i.e. an operation that implements `CallOpInterface`. Your first step is generally going to be to determine if the callable is a `SymbolPath`, or a `ValueRef` (i.e. an indirect call), using the `get_callable_for_callee` interface method. +2. If the callable is a `ValueRef`, you can try to trace that value back to an operation that materialized it from a `Symbol` (if that was the case), so as to make your analysis more precise; but in general there can be situations in which it is not possible to do so. What this means for your analysis depends on what that analysis is. +3. If the callable is a `SymbolPath`, then we need to try and resolve it. This can be done using the `resolve` or `resolve_in_symbol_table` interface methods. If successful, you will get a `SymbolRef` which represents the callable `Symbol`. If the symbol could not be resolved, `None` is returned, and you can traverse that edge of the call graph no further. +4. Once you've obtained the `SymbolRef` of the callable, you can borrow it, and then cast the `&dyn Symbol` reference to a `&dyn CallableOpInterface` reference using `symbol.as_symbol_operation().as_trait::()`. +5. With that reference, you call the `get_callable_region` interface method. If it returns `None`, then the callable represents a declaration, and so it is not possible to traverse the call graph further. If it returns a `RegionRef`, then you proceed by traversing all of the operations in that region, looking for more call sites to visit. + +### Program Points + +A _program point_ essentially represents a cursor in the CFG of a program. Specifically, program points are defined as a position before, or after, a block or operation. The type that represents this in the IR is `ProgramPoint`, which can be constructed from just about any type of block or operation reference. + +Program points are used in a few ways: + +- To specify where a block or operation should be inserted +- To specify at what point a block should be split into two +- To specify at what point a block should be merged into another +- To anchor data flow analysis state, e.g. the state before and after an operation, or the state on entry and exit from a block. + +Currently, we distinguish the points representing "before" a block (i.e. at the start of the block), and "after" a block (i.e. at the end of the block), from the first and last operations in the block, respectively. Thus, even though a point referring to the start of the block, and a point referring to "before" the first operation in that block, effectively refer to the same place, we currently treat them as distinct locations. This may change in the future, but for now, it is something to be aware of. + +The `ProgramPoint` type can be reified as a literal cursor into the operation list of a block, and then used to perform some action relative to that cursor. + +The key thing to understand about program points has to do with the relationship between before/after (or start/end) and what location that actually refers to. The gist, is that a program point, when materialized as a cursor into an operation list, will always have the cursor positioned such that if you inserted a new operation at that point, it would be placed where you expect it to be - i.e. if "before" an operation, the insertion will place the new item immediately preceding the operation referenced by the program point. This is of particular importance if inserting multiple operations using the same point, as the order in which operations will be inserted depends on whether the position is before or after the point. For example, inserting multiple items "before" an operation, will have them appear in that same order in the containing block. However, inserting multiple items "after" an operation, will have them appear in reverse order they were inserted (i.e. the last to be inserted will appear first in the block relative to the others). + +### Defining Dialects + +Defining a new dialect is as simple as defining a struct type which implements the `Dialect` trait. For example: + +```rust +use midenc_hir::{Dialect, DialectInfo}; + +#[derive(Debug)] +pub struct MyDialect { + info: DialectInfo, +} + +impl Dialect for MyDialect { + fn info(&self) -> &DialectInfo { + &self.info + } +} +``` + +One last thing remains before the dialect is ready to be used, and that is [_dialect registration_](#dialect-registration). + +#### Dialect Registration + +Dialect registration is the means by which a dialect and its operations are registered with the [`Context`](#context), such that operations of that dialect can be built. + +First, you must define the `DialectRegistration` implementation. To extend our example from above: + +```rust +impl DialectRegistration for MyDialect { + const NAMESPACE: &'static str = "my"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + } +} +``` + +This provides all of the information needed by the `Context` to register our dialect, i.e. what namespace the dialect uses, and what operations are registered with the dialect. + +The next step is to actually register the dialect with a specific `Context`. In general, this is automatically handled for you, i.e. whenever an operation of your dialect is being built, a call to `context.get_or_register_dialect::()` is made, so as to get a reference to the dialect. If the dialect has not yet been registered, a fresh instance will be constructed, all registered [_dialect hooks_](#dialect-hooks) will be invoked, and the initialized dialect registered with the context, before returning a reference to the registered instance. + +#### Dialect Hooks + +In some cases, it is necessary/desirable to extend a dialect with types/behaviors that we do not want (or cannot make) dependencies of the dialect itself. For example, extending the set of traits/interfaces implemented by operations of some dialect. + +The mechanism by which this is done, is in the form of _dialect hooks_, functions which are invoked when a dialect is being registered, before the main dialect registration callbacks (e.g. `register_operations`) are invoked. Hooks are provided a reference to the raw `DialectInfo`, which can be modified as if the hook is part of the `DialectRegistration` itself. + +Of particular use, is the `DialectInfo::register_operation_trait` method, which can be used to register a trait (or interface) to an operation before it is registered by the dialect. These "late-bound" traits are then added to the set of traits/interfaces defined as part of the operation itself, when the operation is registered with `DialectInfo::register_operation`. + +We currently use dialect hooks for: + +- Attaching the `midenc_codegen_masm::Lowering` trait to all operations for which we have defined its lowering to Miden Assembly. +- Attaching the `midenc_hir_eval::Eval` trait to all operations for which we have defined evaluation semantics, for use with the HIR evaluator. + +#### Defining Operations + +Defining operations involves a non-trivial amount of boilerplate if done by hand, so we have defined the `#[operation]` proc-macro attribute which takes care of all the boilerplate associated with defining a new operation. + +As a result, defining an operation looks like this (using an example from `midenc_dialect_arith`): + +```rust +use midenc_hir::{derive::operation, effects::*, traits::*, *}; + +use crate::ArithDialect; + +/// Two's complement sum +#[operation( + dialect = ArithDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Add { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, + #[attr] + overflow: Overflow, +} + +impl InferTypeOpInterface for Add { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let lhs = self.lhs().ty().clone(); + self.result_mut().set_type(lhs); + Ok(()) + } +} + +// MemoryEffectOpInterface is an alias for EffectOpInterface +impl EffectOpInterface for Add { + fn has_no_effect(&self) -> bool { + true + } + + fn effects( + &self, + ) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } +} +``` + +To summarize: + +- `dialect` specifies the dialect to which the operation will be registered +- `traits` specifies the set of derivable traits for this operation. +- `implements` specifies the set of traits/interfaces which will be manually implemented for this operation. If any of the listed traits/interfaces are _not_ implemented, a compiler error will be emitted. +- The fields of the `Add` struct represent various properties of the operation. Their meaning depends on what (if any) attributes they are decorated with: + - The `#[operand]` attribute represents an expected operand of this operation, and the field type represents the type constraint to apply to it. + - The `#[result]` attribute represents a result produced by this operation, and the field type represents the type constraint to apply to it. + - The `#[attr]` attribute represents a required attribute of this operation. If the `#[default]` attribute is present, it is treated as an optional attribute. + - If a field has no attributes, or only `#[default]`, it is defined as part of the concrete operation struct, and is considered an internal detail of the op. + - All other fields are not actually stored as part of the concrete operation type, but as part of the underlying `Operation` struct, and methods will be generated in an `impl Add` block that provide access to those fields in all the ways you'd expect. + - The `#[operand]`, `#[attr]`, and undecorated fields are all expected, in that order, as arguments to the op builder when constructing an instance of this op. + +There are a variety of field attributes and options for them that are not shown here. For now, the best reference for these is looking at the set of current dialects for an operation that is similar to what you want to define. You can also look at the implementation of the `#[operation]` proc-macro in `midenc_hir_macros` as the authoritative source on what is supported and how it affects the output. In the future we will provide more comprehensive documentation for it. + +### Builders + +Constructing the IR is generally done via implementations of the `Builder` trait, which includes implementations of the `Rewriter` trait, as the former is a super-trait of the latter. Typically, this means the `OpBuilder` type. + +Aside from a variety of useful APIs e.g. creating blocks, setting the insertion point of the builder, etc., most commonly you will be constructing specific operations, which is done in one of two ways: + +- The lowest level primitive, actually provided by the `BuilderExt` trait due to the need to keep `Builder` object-safe, is its `create` method. This method produces an implementation of `BuildableOp` for the specified operation type (i.e. the `T` type parameter), and signature (i.e. the `Args` type parameter, which must be a tuple type). The desired operation is then constructed by applying the necessary arguments to the `BuildableOp` as a function (because `BuildableOp` is an implementation of the `FnOnce` closure type). +- Much more commonly however, this boilerplate will be abstracted away for you by dialect-specific extensions of the `Builder` trait, e.g. the `BuiltinOpBuilder` trait extends all `Builder` implementations with methods for constructing any of the `builtin` dialect operations, such as the `ret` method, which constructs the `builtin.return` operation. All of the dialects used by the compiler define such a trait, all that is required is to bring it into scope in order to construct the specific operations you want. + +> [!NOTE] +> All of the boilerplate for constructing an operation from a `Builder` is generated for you when defining an operation type with the `#[operation]` proc-macro attribute. A key piece of this underlying infrastructure, is the `OperationBuilder` type, which is used to construct the `Operation` that underlies any concrete `Op` implementation. The `OperationBuilder` is also where new operations are verified and inserted into the underlying `Builder` during construction. + +If the builder has a valid insertion point set, then either of the above methods will also insert the constructed operation at that point. + +The primary difference between `Rewriter` and `Builder`, is that the `Rewriter` API surface is comprised primarily of methods that modify the IR in some way, e.g. moving things around, splitting and merging blocks, erasing operations, replacing ops with values, values with values, etc. Because `Builder` is a super-trait, it can be used both to construct new IR, and to rewrite existing IR. + +#### Validation + +A key aspect of the IR design, is to enable as much boilerplate as possible to be generated from the description of an operation, especially when it comes to verifying the validity of an operation using the type constraints of it's operands and results, and the traits/interfaces it implements. + +Operations are currently validated by the `PassManager`. Before executing the pass pipeline, the `PassManager` does a recursive validation of all the operations to make sure that operations have been built correctly according to their traits. Moreover, after executing each pass, the `PassManager` runs validation a second to make sure that the transformation hasn't broken any operation invariant. +Validation may also be triggered manually at any point by calling the `Operation::verify` method. + +There are two traits which underpin the verification infrastructure: + +- `Verify` which represents the verification of `Trait` against `Self`, which is an operation type. Typically, all traits with associated verification rules, implement this trait for all `T: Op + Trait`. +- `Verifier` which is a trait used to facilitate the generation of verification boilerplate specialized against a specific concrete operation type, without having to be aware of what operation traits/interfaces have associated `Verify` implementations. Instead, no-op verifiers are elided by the Rust compiler using `const { }` blocks. The method by which this is done is highly reliant on Rust's specialization infrastructure and type-level trickery. + +In the future, we will likely move verification out of the `OperationBuilder`, into a dedicated pass that is run as part of a pass pipeline, and invoked on each operation via `Operation::verify`. This will enable more robust verification than is currently possible (as operations are not yet inserted in the IR at the point verification is applied currently). + +### Effects + +Side effects are an important consideration during analysis and transformation of operations in the IR, particularly when evaluating whether operations can be reordered, re-materialized, spilled and reloaded, etc. + +The infrastructure underpinning the management and querying of effects is built on the following pieces: + +- The `Effect` trait, which is a marker trait for types which represent an effect, e.g. `MemoryEffect` which represents effects on memory, such as reading and writing, allocation and freeing. +- The `EffectOpInterface` interface, which is implemented for any operation for which the effect `T` is specified, and provides a number of useful methods for querying effects of a specific operation instance. +- The `Resource` trait, which represents a type of resource to which an effect can apply. In many cases, one will use `DefaultResource`, which is a catch-all resource that implies a global effect. However, it is also possible to scope effects to a specific resource, such as a specific address range in memory. This could permit operations with disjoint effects to be reordered relative to one another, when that would otherwise not be allowed if the effects were global. +- The `EffectInstance` type, which provides metadata about a specific effect, any attributes that apply to it, the resource affected, etc. + +It should be noted that the choice to implement `EffectOpInterface` for an operation is _not_ based on whether the operation _has_ the effect; but rather, it is based on whether the behavior of the operation with respect to that effect is _specified_ or not. + +For example, most operations will have a known effect (or lack thereof) on memory, e.g. `arith.add` will never have a memory effect, while `hir.load` by definition will read from memory. In some cases, whether an operation will have such an effect is not a property of the operation itself, but rather operations that may be nested in one of its child regions, e.g. `scf.if` has no memory effects in and of itself, but one of its regions might contain an operation which does, such as an `hir.store` in the "then" region. In this case, it does not make sense for `scf.if` to implement `EffectOpInterface`, because memory effects are not specified for `scf.if`, but are instead derived from its regions. + +When `EffectOpInterface` is not implemented for some operation, then one must treat the operation as conservatively as possible in regards to the specific effect. For example, `scf.call` does not implement this interface for `MemoryEffect`, because whether the call has any memory effects depends on the function being called. As a result, one must assume that the `scf.call` could have any possible memory effect, unless you are able to prove otherwise using inter-procedural analysis. + +#### Memory Effects + +The infrastructure described above can be used to represent any manner of side effect. However, the compiler is currently only largely concerned with effects on memory. For this, there are a few more specific pieces: + +- The `MemoryEffectOpInterface` trait alias, which is just an alias for `EffectOpInterface`. +- The `MemoryEffect` type, which represents the set of memory effects we care about. +- The `HasRecursiveMemoryEffects` trait, which should be implemented on any operation whose regions may contain operations that have memory effects. +- The `Operation::is_memory_effect_free` method, which returns a boolean indicating whether the operation is known not to have any memory effects. + +In most places, we're largely concerned with whether an operation is known to be memory effect free, thus allowing us to move that operation around freely. We have not started doing more sophisticated effect analysis and optimizations based on such analysis. diff --git a/docs/internal/src/packaging.md b/docs/internal/src/packaging.md new file mode 100644 index 000000000..7836d6017 --- /dev/null +++ b/docs/internal/src/packaging.md @@ -0,0 +1,3 @@ +# Packaging + +This document is coming soon! diff --git a/docs/internal/src/rewrites.md b/docs/internal/src/rewrites.md new file mode 100644 index 000000000..f3ea6e74a --- /dev/null +++ b/docs/internal/src/rewrites.md @@ -0,0 +1,111 @@ +# Rewrites + +This document provides an overview of some of the current transformation/rewrite passes the compiler uses when lowering from the frontend to Miden Assembly. This is not guaranteed to be comprehensive, but mostly meant as a high-level reference to what rewrites exist and what they acheive. + +Most rewrite passes, at the time of writing, are maintained in the `midenc-hir-transform` crate, with the exception of those which are either dialect-specific (i.e. canonicalization, or reliant on dialect-aware interfaces), or part of the core `midenc-hir` crate (i.e. region simplification, folding). + +- [Region Simplification](#region-simplification) +- [Folding](#folding) +- [Canonicalization](#canonicalization) +- [Sparse Conditional Constant Propagation](#sparse-conditional-constant-propagation) +- [Unstructured to Structured Control Flow Lifting](#control-flow-lifting) +- [Control Flow Sinking](#control-flow-sinking) +- [Spills](#spills) + +## Region Simplification + +Region simplification is a region-local transformation which: + +- Removes redundant block arguments +- Merges identical blocks +- Removes unused/dead code + +This transformation is exposed from the `Region` type, but is considered the responsibility of the `GreedyPatternRewriteDriver` to apply, based on the current compiler configuration. + +> [!NOTE] +> Currently, the block merging portion of region simplification is only stubbed out, and is not actually being performed. It will be incorporated in the future. + +## Folding + +Folding, or _constant folding_ to be more precise, is the process by which an operation is simplified, or even reduced to a constant, when some or all of its operands have known constant values. + +Operations which can be folded must implement the `Foldable` trait, and implement the folding logic there. Folds can produce three outcomes, represented via `FoldResult`: + +- `Ok`, indicating that the operation was able to be reduced to a (possibly constant) value or set of values. +- `InPlace`, indicating that the operation was able to rewrite itself into a simpler form, but could not be completely folded. This is commonly the case when only a subset of the operands are known-constant. +- `Failed`, indicating that the operation could not be folded or simplified at all. + +Folding can be done at any time, but similar to region simplification, is largely delegated to the `GreedyPatternRewriteDriver` to apply as part of canonicalization. + +## Canonicalization + +Canonicalization refers to rewriting an operation such that it is in its _canonical form_. An operation can have several canonicalization patterns that can be applied, some even stack. It must be the case that these canonicalizations _converge_, i.e. you must never define two separate canonicalizations that could try to rewrite an operation in opposing ways, they must either not overlap, or converge to a fixpoint. + +An operation that has at least one defined canonicalization pattern, must implement the `Canonicalizable` trait, and implement the `register_canonicalization_patterns` method to insert those rewrite patterns into the provided `RewriteSet`. + +Canonicalization is performed using the `Canonicalizer` pass, provided by the `midenc_hir_transform` crate. Internally, this configures and runs the `GreedyPatternRewriteDriver` to not only apply all canonicalization patterns until fixpoint, but also constant folding and region simplification (depending on configuration). + +This is the primary means by which the IR produced by a frontend is simplified and prepared for further transformation by the compiler. + +## Sparse Conditional Constant Propagation + +This pass applies the results of the _sparse constant propagation_ analysis described in [_Analyses_](analyses.md), by rewriting the IR to materialize constant values, replace operations that were reduced to constants, and of particular note, prune unreachable blocks/regions from the IR based on control flow that was resolved to a specific target or set of targets based on constant operands. + +## Control Flow Lifting + +This pass is responsible for converting unstructured control flow, represented via the `cf` dialect, into structured equivalents provided by the `scf` dialect. + +Because some forms of unstructured control flow cannot be fully converted into structured equivalents, this process is called "lifting" rather than "conversion". + +As an example, here's what it looks like to lift an unstructured conditional branch to a `scf.if`: + +- Before: + +``` +^block0(v0: i1, v1: u32, v2: u32): + cf.cond_br v0, ^block1(v1), ^block2(v2); + +^block1(v3: u32): + cf.br ^block3(v3); + +^block2(v4: u32): + v5 = arith.constant 1 : u32; + v6 = arith.add v4, v5; + cf.br ^block3(v6); + +^block3(v7: u32): + builtin.ret v7 +``` + +- After: + +``` +v8 = scf.if v0 { + scf.yield v1 +} else { + v5 = arith.constant 1 : u32; + v6 = arith.add v2, v5; + scf.yield v6 +}; +builtin.ret v8 +``` + +The above transformation is the simplest possible example. In practice, the transformation is much more involved, as it must handle control flow that exits from arbitrarily deep nesting (e.g. a `builtin.ret` within the body of a loop, guarded by a conditional branch). The specific details of the transformation in general are described in detail in the module documentation of `midenc_hir_transform::cfg_to_scf`. + +This transformation is a prerequisite for the generation of Miden Assembly, which provides only structured control flow primitives. + +## Control Flow Sinking + +This actually refers to two separate passes, but both are duals of the same goal, which is to move operations closer to their uses, thus reducing the amount of computation that is performed on code paths where the result of that computation is not used. + +The `ControlFlowSink` pass is generic, and will perform the transformation described above, so long as the operation has no side effects, is not a block terminator, and has no regions. + +The `SinkOperandDefs` pass (which will be renamed in the near future), is designed specifically to move constant-like operations directly before their uses, and materialize copies if necessary so that each user gets its own copy. We do this to counter the effect of the control flow lifting transform and the canonicalizer, which both materialize constants in the entry block of a region. These constants then have overly broad live ranges that introduce a high likelihood of needing to spill values to memory. Furthermore, because the Miden VM is a stack machine, not a register machine, there is very little benefit to sharing constant definitions. Instead, by materializing constants immediately before they are used, we produce much more efficient code (as we do not need to shuffle the operand stack to access constants previously defined), and we significantly reduce the chances that we will need to spill values to memory. + +## Spills + +The `TransformSpills` pass, implemented in `midenc_dialect_hir`, applies the results of the `Spills` analysis described in [_Analyses_](analyses.md). + +It inserts all of the computed spills and reloads, and fixes up the IR to ensure that all uses of a spilled value, use the closest dominating reload or definition of that value. + +The resulting IR is guaranteed to keep the maximum operand stack pressure to 16 elements or less. diff --git a/docs/mkdocs b/docs/mkdocs deleted file mode 100755 index 44f6c579e..000000000 --- a/docs/mkdocs +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -echo "${@}" - -python3 -m venv target/docs/venv -source target/docs/venv/bin/activate -pip3 install -r docs/requirements.txt -mkdocs "${@}" diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 23079102b..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -mkdocs-material==9.5.33 -markdown-include==0.8.1 -mkdocs-open-in-new-tab==1.0.3 diff --git a/docs/usage/cargo-miden.md b/docs/usage/cargo-miden.md deleted file mode 100644 index 6959fa76a..000000000 --- a/docs/usage/cargo-miden.md +++ /dev/null @@ -1,64 +0,0 @@ -# Getting started with Cargo - -As part of the Miden compiler toolchain, we provide a Cargo extension, `cargo-miden`, which provides -a template to spin up a new Miden project in Rust, and takes care of orchestrating `rustc` and -`midenc` to compile the Rust crate to a Miden package. - -## Installation - -!!! warning - - Currently, `midenc` (and as a result, `cargo-miden`), requires the nightly Rust toolchain, so - make sure you have it installed first: - - ```bash - rustup toolchain install nightly-2024-08-06 - ``` - - NOTE: You can also use the latest nightly, but the specific nightly shown here is known to - work. - -To install the extension, simply run the following in your shell: - -```bash -cargo +nightly-2024-08-06 install cargo-miden -``` - -This will take a minute to compile, but once complete, you can run `cargo help miden` or just -`cargo miden` to see the set of available commands and options. - -To get help for a specific command, use `cargo miden help ` or `cargo miden --help`. - -## Creating a new project - -Your first step will be to create a new Rust project set up for compiling to Miden: - -```bash -cargo miden new foo -``` - -In this above example, this will create a new directory `foo`, containing a Cargo project for a -crate named `foo`, generated from our Miden project template. - -The template we use sets things up so that you can pretty much just build and run. Since the -toolchain depends on Rust's native WebAssembly target, it is set up just like a minimal WebAssembly -crate, with some additional tweaks for Miden specfically. - -Out of the box, you will get a Rust crate that depends on the Miden SDK, and sets the global -allocator to a simple bump allocator we provide as part of the SDK, and is well suited for most -Miden use cases, avoiding the overhead of more complex allocators. - -As there is no panic infrastructure, `panic = "abort"` is set, and the panic handler is configured -to use the native WebAssembly `unreachable` intrinsic, so the compiler will strip out all of the -usual panic formatting code. - -### Compiling to Miden Assembly - -Now that you've created your project, compiling it to Miden Assembly is as easy as running the -following command from the root of the project directory: - -```bash -cargo miden build --release -``` - -This will emit the compiled artifacts to `target/miden`. diff --git a/docs/usage/debugger.md b/docs/usage/debugger.md deleted file mode 100644 index f79c60d93..000000000 --- a/docs/usage/debugger.md +++ /dev/null @@ -1,223 +0,0 @@ -# Debugging programs - -A very useful tool in the Miden compiler suite, is its TUI-based interactive debugger, accessible -via the `midenc debug` command. - -!!! warning - - The debugger is still quite new, and while very useful already, still has a fair number of - UX annoyances. Please report any bugs you encounter, and we'll try to get them patched ASAP! - -## Getting started - -The debugger is launched by executing `midenc debug`, and giving it a path to a program compiled -by `midenc compile`. See [Program Inputs](#program-inputs) for information on how to provide inputs -to the program you wish to debug. Run `midenc help debug` for more detailed usage documentation. - -The debugger may also be used as a library, but that is left as an exercise for the reader for now. - -## Example - -```shell -# Compile a program to MAST from a rustc-generated Wasm module -midenc compile foo.wasm -o foo.masl - -# Load that program into the debugger and start executing it -midenc debug foo.masl -``` - -## Program inputs - -To pass arguments to the program on the operand stack, or via the advice provider, you have two -options, depending on the needs of the program: - -1. Pass arguments to `midenc debug` in the same order you wish them to appear on the stack. That - is, the first argument you specify will be on top of the stack, and so on. -2. Specify a configuration file from which to load inputs for the program, via the `--inputs` option. - -### Via command line - -To specify the contents of the operand stack, you can do so following the raw arguments separator `--`. -Each operand must be a valid field element value, in either decimal or hexadecimal format. For example: - -```shell -midenc debug foo.masl -- 1 2 0xdeadbeef -``` - -If you pass arguments via the command line in conjunction with `--inputs`, then the command line arguments -will be used instead of the contents of the `inputs.stack` option (if set). This lets you specify a baseline -set of inputs, and then try out different arguments using the command line. - -### Via inputs config - -While simply passing operands to the `midenc debug` command is useful, it only allows you to specify -inputs to be passed via operand stack. To provide inputs via the advice provider, you will need to use -the `--inputs` option. The configuration file expected by `--inputs` also lets you tweak the execution -options for the VM, such as the maximum and expected cycle counts. - -An example configuration file looks like so: - -```toml -# This section is used for execution options -[options] -max_cycles = 5000 -expected_cycles = 4000 - -# This section is the root table for all inputs -[inputs] -# Specify elements to place on the operand stack, leftmost element will be on top of the stack -stack = [1, 2, 0xdeadbeef] - -# This section contains input options for the advice provider -[inputs.advice] -# Specify elements to place on the advice stack, leftmost element will be on top -stack = [1, 2, 3, 4] - -# The `inputs.advice.map` section is a list of advice map entries that should be -# placed in the advice map before the program is executed. Entries with duplicate -# keys are handled on a last-write-wins basis. -[[inputs.advice.map]] -# The key for this entry in the advice map -digest = '0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63' -# The values to be stored under this key -values = [1, 2, 3, 4] - -[[inputs.advice.map]] -digest = '0x20234ee941e53a15886e733cc8e041198c6e90d2a16ea18ce1030e8c3596dd38'' -values = [5, 6, 7, 8] -``` - -## Usage - -Once started, you will be dropped into the main debugger UI, stopped at the first cycle of -the program. The UI is organized into pages and panes, with the main/home page being the -one you get dropped into when the debugger starts. The home page contains the following panes: - -* Source Code - displays source code for the current instruction, if available, with - the relevant line and span highlighted, with syntax highlighting (when available) -* Disassembly - displays the 5 most recently executed VM instructions, and the current - cycle count -* Stack Trace - displays a stack trace for the current instruction, if the program was - compiled with tracing enabled. If frames are unavailable, this pane may be empty. -* Operand Stack - displays the contents of the operand stack and its current depth -* Breakpoints - displays the set of current breakpoints, along with how many were hit - at the current instruction, when relevant - -### Keyboard shortcuts - -On the home page, the following keyboard shortcuts are available: - -Shortcut | Mnemonic | Description | ----------|----------------|---------------| -`q` | quit | exit the debugger | -`h` | next pane | cycle focus to the next pane | -`l` | prev pane | cycle focus to the previous pane | -`s` | step | advance the VM one cycle | -`n` | step next | advance the VM to the next instruction | -`c` | continue | advance the VM to the next breakpoint, else to completion | -`e` | exit frame | advance the VM until we exit the current call frame, a breakpoint is triggered, or execution terminates | -`d` | delete | delete an item (where applicable, e.g. the breakpoints pane) | -`:` | command prompt | bring up the command prompt (see below for details) | - -When various panes have focus, additional keyboard shortcuts are available, in any pane -with a list of items, or multiple lines (e.g. source code), `j` and `k` (or the up and -down arrows) will select the next item up and down, respectively. As more features are -added, I will document their keyboard shortcuts below. - -### Commands - -From the home page, typing `:` will bring up the command prompt in the footer pane. - -You will know the prompt is active because the keyboard shortcuts normally shown there will -no longer appear, and instead you will see the prompt, starting with `:`. It supports any -of the following commands: - -Command | Aliases | Action | Description | --------------|--------------|-------------------|---------------| -`quit` | `q` | quit | exit the debugger | -`debug` | | show debug log | display the internal debug log for the debugger itself | -`reload` | | reload program | reloads the program from disk, and resets the UI (except breakpoints) | -`breakpoint` | `break`, `b` | create breakpoint | see [Breakpoints](#breakpoints) | -`read` | `r` | read memory | inspect linear memory (see [Reading Memory](#reading-memory) | - -## Breakpoints - -One of the most common things you will want to do with the debugger is set and manage breakpoints. -Using the command prompt, you can create breakpoints by typing `b` (or `break` or `breakpoint`), -followed by a space, and then the desired breakpoint expression to do any of the following: - -* Break at an instruction which corresponds to a source file (or file and line) whose name/path - matches a pattern -* Break at the first instruction which causes a call frame to be pushed for a procedure whose name - matches a pattern -* Break any time a specific opcode is executed -* Break at the next instruction -* Break after N cycles -* Break at CYCLE - -The syntax for each of these can be found below, in the same order (shown using `b` as the command): - -Expression | Description | ---------------------|---------------| -`b FILE[:LINE]` | Break when an instruction with a source location in `FILE` (a glob pattern)
_and_ that occur on `LINE` (literal, if provided) are hit. | -`b in NAME` | Break when the glob pattern `NAME` matches the fully-qualified procedure name
containing the current instruction | -`b for OPCODE` | Break when the an instruction with opcode `OPCODE` is exactly matched
(including immediate values) | -`b next` | Break on the next instruction | -`b after N` | Break after `N` cycles | -`b at CYCLE` | Break when the cycle count reaches `CYCLE`.
If `CYCLE` has already occurred, this has no effect | - -When a breakpoint is hit, it will be highlighted, and the breakpoint window will display the number -of hit breakpoints in the lower right. - -After a breakpoint is hit, it expires if it is one of the following types: - -* Break after N -* Break at CYCLE -* Break next - -When a breakpoint expires, it is removed from the breakpoint list on the next cycle. - -## Reading memory - -Another useful diagnostic task is examining the contents of linear memory, to verify that expected -data has been written. You can do this via the command prompt, using `r` (or `read`), followed by -a space, and then the desired memory address and options: - -The format for read expressions is `:r ADDR [OPTIONS..]`, where `ADDR` is a memory address in -decimal or hexadecimal format (the latter requires the `0x` prefix). The `read` command supports -the following for `OPTIONS`: - -Option | Alias | Values | Default | Description | -----------------|-------|-----------------|---------|--------------| -`-mode MODE` | `-m` |
  • `words` (`word` ,`w`)
  • `bytes` (`byte`, `b`)
| `words` | Specify a memory addressing mode | -`-format FORMAT`| `-f` |
  • `decimal` (`d`)
  • `hex` (`x`)
  • `binary` (`bin`, `b`)
| `decimal` | Specify the format used to print integral values | -`-count N` | `-c` | | `1` | Specify the number of units to read | -`-type TYPE` | `-t` | See [Types](#types) | `word` | Specify the type of value to read
This also has the effect of modifying the default `-format` and unit size for `-count` | - -Any invalid combination of options, or invalid syntax, will display an error in the status bar. - -### Types - -Type | Description | ---------|--------------| -`iN` | A signed integer of `N` bits | -`uN` | An unsigned integer of `N` bits | -`felt` | A field element | -`word` | A Miden word, i.e. an array of four field elements | -`ptr` or `pointer` | A 32-bit memory address (implies `-format hex`) | - -## Roadmap - -The following are some features planned for the near future: - -* **Watchpoints**, i.e. cause execution to break when a memory store touches a specific address -* **Conditional breakpoints**, i.e. only trigger a breakpoint when an expression attached to it - evaluates to true -* More DYIM-style breakpoints, i.e. when breaking on first hitting a match for a file or - procedure, we probably shouldn't continue to break for every instruction to which that - breakpoint technically applies. Instead, it would make sense to break and then temporarily - disable that breakpoint until something changes that would make breaking again useful. - This will rely on the ability to disable breakpoints, not delete them, which we don't yet - support. -* More robust type support in the `read` command -* Display procedure locals and their contents in a dedicated pane diff --git a/docs/usage/midenc.md b/docs/usage/midenc.md deleted file mode 100644 index 5e4a6a48a..000000000 --- a/docs/usage/midenc.md +++ /dev/null @@ -1,123 +0,0 @@ -# Getting started with `midenc` - -The `midenc` executable is the command-line interface for the compiler driver, as well as other -helpful tools, such as the interactive debugger. - -While it is a lower-level tool compared to `cargo-miden`, just like the difference between `rustc` -and `cargo`, it provides a lot of functionality for emitting diagnostic information, controlling -the output of the compiler, and configuring the compilation pipeline. Most users will want to use -`cargo-miden`, but understanding `midenc` is helpful for those times where you need to get your -hands dirty. - -## Installation - -First, you'll need to have Rust installed, with the nightly toolchain (currently we're building -against the `nightly-2024-08-06` toolchain, but we regularly update this). - -Then, simply install `midenc` using Cargo in one of the following ways: - - # From crates.io: - cargo +nightly install midenc - - # If you have cloned the git repo, and are in the project root: - cargo make install - - # If you have Rust installed, but have not cloned the git repo: - cargo install --git https://github.com/0xpolygonmiden/compiler midenc - - -!!! advice - - This installation method relies on Cargo-managed binaries being in your shell `PATH`, - which is almost always the case, but if you have disabled this functionality, you'll need - to add `midenc` to your `PATH` manually. - -## Usage - -Once installed, you should be able to invoke the compiler, you should see output similar to this: - - midenc help compile - Usage: midenc compile [OPTIONS] [-- ...] - - Arguments: - [INPUTS]... - Path(s) to the source file(s) to compile. - - You may also use `-` as a file name to read a file from stdin. - - Options: - --output-dir
- Write all compiler artifacts to DIR - - -W - Modify how warnings are treated by the compiler - - [default: auto] - - Possible values: - - none: Disable all warnings - - auto: Enable all warnings - - error: Promotes warnings to errors - - -v, --verbose - When set, produces more verbose output during compilation - - -h, --help - Print help (see a summary with '-h') - - -The actual help output covers quite a bit more than shown here, this is just for illustrative -purposes. - -The `midenc` executable supports two primary functions at this time: - -* `midenc compile` to compile one of our supported input formats to Miden Assembly -* `midenc debug` to run a Miden program attached to an interactive debugger -* `midenc run` to run a Miden program non-interactively, equivalent to `miden run` - -## Compilation - -See the help output for `midenc compile` for detailed information on its options and their -behavior. However, the following is an example of how one might use `midenc compile` in practice: - -```bash -midenc compile --target rollup \ - --entrypoint 'foo::main' \ - -lextra \ - -L ./masm \ - --emit=hir=-,masp \ - -o out.masp \ - target/wasm32-wasip1/release/foo.wasm -``` - -In this scenario, we are in the root of a Rust crate, named `foo`, which we have compiled for the -`wasm32-wasip1` target, which placed the resulting WebAssembly module in the -`target/wasm32-wasip1/release` directory. This crate exports a function named `main`, which we want -to use as the entrypoint of the program. - -Additionally, our Rust code links against some hand-written Miden Assembly code, namespaced under -`extra`, which can be found in `./masm/extra`. We are telling `midenc` to link the `extra` library, -and to add the `./masm` directory to the library search path. - -Lastly, we're configuring the output: - -* We're using `--emit` to request `midenc` to dump Miden IR (`hir`) to stdout (specified via the `-` -shorthand), in addition to the Miden package artifact (`masp`). -* We're telling `midenc` to write the compiled output to `out.masp` in the current directory, rather -than the default path that would have been used (`target/miden/foo.masp`). - -## Debugging - -See [Debugging Programs](debugger.md) for details on using `midenc debug` to debug Miden programs. - -## Next steps - -We have put together two useful guides to walk through more detail on compiling Rust to WebAssembly: - -1. To learn how to compile Rust to WebAssembly so that you can invoke `midenc compile` on the -resulting Wasm module, see [this guide](../guides/rust_to_wasm.md). -2. If you already have a WebAssembly module, or know how to produce one, and want to learn how to -compile it to Miden Assembly, see [this guide](../guides/wasm_to_masm.md). - -You may also be interested in our [basic account project template](https://github.com/0xpolygonmiden/rust-templates/tree/main/account/template), -as a starting point for your own Rust project. diff --git a/eval/CHANGELOG.md b/eval/CHANGELOG.md new file mode 100644 index 000000000..fb06cfde7 --- /dev/null +++ b/eval/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-hir-eval-v0.1.5...midenc-hir-eval-v0.4.0) - 2025-08-15 + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) diff --git a/eval/Cargo.toml b/eval/Cargo.toml new file mode 100644 index 000000000..8ebddca46 --- /dev/null +++ b/eval/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "midenc-hir-eval" +description = "An interpreter for Miden IR" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +homepage.workspace = true +documentation.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["midenc-hir/std"] + +[dependencies] +log.workspace = true +midenc-dialect-arith.workspace = true +midenc-dialect-cf.workspace = true +midenc-dialect-scf.workspace = true +midenc-dialect-hir.workspace = true +midenc-dialect-ub.workspace = true +midenc-hir.workspace = true +midenc-session.workspace = true +thiserror.workspace = true diff --git a/eval/README.md b/eval/README.md new file mode 100644 index 000000000..24eb78cbc --- /dev/null +++ b/eval/README.md @@ -0,0 +1,11 @@ +# Miden IR Interpreter + +This crate defines a lightweight interpreter for arbitrary HIR operations, primarily intended for use in verification and tests. + +It is implemented for all of the builtin dialects, but by implementing the `Eval` trait for operations in your dialect, they can be evaluated by the interpreter as well. + +The primary interface is the `HirEvaluator` struct, which is provided to implementations of `Eval::eval`, and contains a number of primitive helpers that one can use to interact with the current state of the evaluator, e.g. read/write memory or local variables, get/set the concrete values associated with SSA registers, and more. + +The evaluator works as a sort of coroutine evaluator, i.e. the core interpreter loop is managed by `HirEvaluator`, but the actual details of each operation are delegated to the `Eval` implemntation for that type. Operations interact with the evaluator for control flow by returning a `ControlFlowEffect` when returning from their `eval` implementation. The evaluator will use this to effect control transfers or other effects (e.g. traps). + +For example, a `Call`-like operation would return `ControlFlowEffect::Call`, and let the evaluator perform the actual control flow, but the `Call`-like op itself would handle the validation and details unique to its implementation. For example, a call that needs to switch into a new interpreter context would call `HirEvaluator::enter_context` before returning from its `eval` implementation. diff --git a/eval/src/eval.rs b/eval/src/eval.rs new file mode 100644 index 000000000..f274c98b5 --- /dev/null +++ b/eval/src/eval.rs @@ -0,0 +1,2038 @@ +use alloc::{ + boxed::Box, + format, + string::{String, ToString}, +}; + +use midenc_dialect_arith as arith; +use midenc_dialect_cf as cf; +use midenc_dialect_hir as hir; +use midenc_dialect_scf as scf; +use midenc_dialect_ub as ub; +use midenc_hir::{ + dialects::builtin, AttributeValue, Felt, Immediate, Op, OperationRef, Overflow, + RegionBranchPoint, RegionBranchTerminatorOpInterface, Report, SmallVec, SourceSpan, Spanned, + SuccessorInfo, Type, Value as _, ValueRange, +}; +use midenc_session::diagnostics::Severity; + +use crate::*; + +/// This trait is intended to be implemented by any [midenc_hir::Op] that we wish to be able to +/// evaluate via the [HirEvaluator]. +pub trait Eval { + /// Evaluate this operation, using the provided evaluator for any side effects/results, etc. + fn eval(&self, evaluator: &mut HirEvaluator) -> Result; +} + +/// This trait is intended to be implemented by any [midenc_hir::Op] that has associated one-time +/// initialization that it needs to perform prior to starting evaluation. +/// +/// Initialization is only performed when calling [HirEvaluator::eval] or one of its variants, on +/// an operation that implements this trait. If evaluation starts in an ancestor or descendant +/// operation, initialization is not performed unless explicitly implemented by the op at which +/// evaluation started. +pub trait Initialize { + /// Peform initialization, using the provided evaluator for any side effects. + fn initialize(&self, evaluator: &mut HirEvaluator) -> Result<(), Report>; +} + +/// Represents the action to take as the result of evaluating a given operation +#[derive(Default, Debug)] +pub enum ControlFlowEffect { + /// The current operation has no control effects + #[default] + None, + /// Execution should trap at the current instruction + Trap { span: SourceSpan, reason: String }, + /// Control is returning from the operation enclosing the current region + /// + /// It is expected that the operation being returned from implements CallableOpInterface, and + /// that the returning operation implements ReturnLike. + Return(Option), + /// Control is transferring unconditionally to another block in the current region + Jump(SuccessorInfo), + /// Control is transferring unconditionally to another region in the enclosing operation, or + /// returning from the enclosing operation itself. + Yield { + successor: RegionBranchPoint, + arguments: ValueRange<'static, 4>, + }, + /// Control should transfer to `callee`, with `arguments`, and return to the current operation + /// if control exits normally from the callee. + Call { + callee: OperationRef, + arguments: ValueRange<'static, 4>, + }, +} + +impl Initialize for builtin::Component { + fn initialize(&self, _evaluator: &mut HirEvaluator) -> Result<(), Report> { + todo!("visit all global variables in all modules of this component") + } +} +impl Initialize for builtin::Module { + fn initialize(&self, _evaluator: &mut HirEvaluator) -> Result<(), Report> { + todo!("visit all global variables in this module") + } +} + +impl Eval for builtin::Ret { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + // For now we only support single-return + assert!(self.num_operands() < 2, "multi-return functions are not yet supported"); + + if self.has_operands() { + let value = evaluator.get_value(&self.values()[0].borrow().as_value_ref())?; + Ok(ControlFlowEffect::Return(Some(value))) + } else { + Ok(ControlFlowEffect::Return(None)) + } + } +} + +impl Eval for builtin::RetImm { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + Ok(ControlFlowEffect::Return(Some((*self.value()).into()))) + } +} + +impl Eval for ub::Unreachable { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + Ok(ControlFlowEffect::Trap { + span: self.span(), + reason: "control reached an unreachable program point".to_string(), + }) + } +} + +impl Eval for ub::Poison { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let value = match self.value().as_immediate() { + Ok(imm) => Value::poison(self.span(), imm), + Err(ty) => { + return Err(self + .as_operation() + .context() + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid poison") + .with_primary_label(self.span(), format!("invalid poison type: {ty}")) + .into_report()); + } + }; + evaluator.set_value(self.result().as_value_ref(), value); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for cf::Br { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + Ok(ControlFlowEffect::Jump(self.successors()[0])) + } +} + +impl Eval for cf::CondBr { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let condition = evaluator.get_value(&self.condition().as_value_ref())?; + match condition { + Value::Immediate(Immediate::I1(condition)) => { + let successor = if condition { + self.successors()[0] + } else { + self.successors()[1] + }; + Ok(ControlFlowEffect::Jump(successor)) + } + Value::Immediate(_) => { + panic!("invalid immediate type for cf.cond_br condition: {condition:?}") + } + Value::Poison { .. } => { + panic!("invalid use of poison value for cf.cond_br condition: {condition:?}") + } + } + } +} + +impl Eval for cf::Switch { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let selector = evaluator.get_value(&self.selector().as_value_ref())?; + match selector { + Value::Immediate(Immediate::U32(selector)) => { + let successor = self + .cases() + .iter() + .find(|succ| succ.key().is_some_and(|k| *k == selector)) + .map(|succ| *succ.info()) + .unwrap_or_else(|| self.successors()[0]); + Ok(ControlFlowEffect::Jump(successor)) + } + Value::Immediate(_) => { + panic!("invalid immediate type for cf.switch selector: {selector:?}") + } + Value::Poison { .. } => { + panic!("invalid use of poison value for cf.switch selector: {selector:?}") + } + } + } +} + +impl Eval for cf::Select { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let condition = evaluator.get_value(&self.cond().as_value_ref())?; + match condition { + Value::Immediate(Immediate::I1(condition)) => { + let result = if condition { + evaluator.get_value(&self.first().as_value_ref()).unwrap() + } else { + evaluator.get_value(&self.second().as_value_ref()).unwrap() + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } + Value::Immediate(_) => { + panic!("invalid immediate type for cf.select condition: {condition:?}") + } + Value::Poison { .. } => { + panic!("invalid use of poison value for cf.select condition: {condition:?}") + } + } + } +} + +impl Eval for scf::If { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let condition = evaluator.use_value(&self.condition().as_value_ref())?; + match condition { + Immediate::I1(condition) => { + let successor = if condition { + self.then_body().as_region_ref() + } else { + self.else_body().as_region_ref() + }; + Ok(ControlFlowEffect::Yield { + successor: RegionBranchPoint::Child(successor), + arguments: ValueRange::Empty, + }) + } + _ => { + panic!("invalid immediate type for scf.if condition: {condition:?}") + } + } + } +} + +impl Eval for scf::While { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + let arguments = ValueRange::<4>::from(self.inits()).into_owned(); + + Ok(ControlFlowEffect::Yield { + successor: RegionBranchPoint::Child(self.before().as_region_ref()), + arguments, + }) + } +} + +impl Eval for scf::IndexSwitch { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let selector = evaluator.use_value(&self.selector().as_value_ref())?; + match selector { + Immediate::U32(selector) => { + let successor = self + .get_case_index_for_selector(selector) + .map(|index| self.get_case_region(index)) + .unwrap_or_else(|| self.default_region().as_region_ref()); + Ok(ControlFlowEffect::Yield { + successor: RegionBranchPoint::Child(successor), + arguments: ValueRange::Empty, + }) + } + _ => { + panic!("invalid immediate type for scf.index_switch selector: {selector:?}") + } + } + } +} + +impl Eval for scf::Yield { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let arguments = ValueRange::<4>::from(self.yielded()).into_owned(); + + // The following uses compiler infrastructure to determine where to yield to without + // hardcoding the list of known parent operations here and how to select the successor + // region, since that's already been done in the compiler. If this turns out to be a big + // perf bottleneck, we can implement something more efficient. + let this = self.as_operation().as_trait::().unwrap(); + let mut operands = SmallVec::<[_; 4]>::with_capacity(self.yielded().len()); + for yielded in self.yielded().iter() { + match evaluator.get_value(&yielded.borrow().as_value_ref())? { + Value::Immediate(value) | Value::Poison { value, .. } => { + operands.push(Some(Box::new(value) as Box)) + } + } + } + + // Because all of the operands are known constants, this should always select a single + // successor region + let succs = this.get_successor_regions(&operands); + assert_eq!(succs.len(), 1); + let successor = succs[0].successor(); + + Ok(ControlFlowEffect::Yield { + successor, + arguments, + }) + } +} + +impl Eval for scf::Condition { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let condition = evaluator.get_value(&self.condition().as_value_ref())?; + match condition { + Value::Immediate(Immediate::I1(condition)) => { + let parent_op = self.parent_op().unwrap(); + let parent_op = parent_op.borrow(); + let while_op = parent_op.downcast_ref::().unwrap(); + let arguments = ValueRange::<4>::from(self.forwarded()); + if condition { + Ok(ControlFlowEffect::Yield { + successor: RegionBranchPoint::Child(while_op.after().as_region_ref()), + arguments: arguments.into_owned(), + }) + } else { + Ok(ControlFlowEffect::Yield { + successor: RegionBranchPoint::Parent, + arguments: arguments.into_owned(), + }) + } + } + Value::Immediate(_) => { + panic!("invalid immediate type for scf.condition flag: {condition:?}") + } + Value::Poison { .. } => { + panic!("invalid use of poison value for scf.condition flag: {condition:?}") + } + } + } +} + +impl Eval for hir::Assert { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let input = self.value().as_value_ref(); + match evaluator.use_value(&input)? { + Immediate::I1(condition) => { + if condition { + Ok(ControlFlowEffect::None) + } else { + Ok(ControlFlowEffect::Trap { + span: self.span(), + reason: format!("assertion failed with code {}", self.code()), + }) + } + } + imm => Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected boolean value, got value of type {}: {imm}", imm.ty()), + )), + } + } +} + +impl Eval for hir::Assertz { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let input = self.value().as_value_ref(); + match evaluator.use_value(&input)? { + Immediate::I1(condition) => { + if condition { + Ok(ControlFlowEffect::Trap { + span: self.span(), + reason: format!("assertion failed with code {}", self.code()), + }) + } else { + Ok(ControlFlowEffect::None) + } + } + imm => Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected boolean value, got value of type {}: {imm}", imm.ty()), + )), + } + } +} + +impl Eval for hir::AssertEq { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.lhs().as_value_ref(); + let rhs = self.rhs().as_value_ref(); + let lhs_value = evaluator.use_value(&lhs)?; + let rhs_value = evaluator.use_value(&rhs)?; + if lhs_value != rhs_value { + Ok(ControlFlowEffect::Trap { + span: self.span(), + reason: format!("assertion failed: {lhs_value} != {rhs_value}"), + }) + } else { + Ok(ControlFlowEffect::None) + } + } +} + +impl Eval for hir::PtrToInt { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let input = evaluator.get_value(&self.operand().as_value_ref())?; + assert_eq!(input.ty(), Type::U32); + evaluator.set_value(self.result().as_value_ref(), input); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::IntToPtr { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let input = evaluator.get_value(&self.operand().as_value_ref())?; + match input { + Value::Poison { origin, value, .. } => { + if let Some(ptr) = value.as_u32() { + evaluator.set_value( + self.result().as_value_ref(), + Value::poison(origin, Immediate::U32(ptr)), + ); + return Ok(ControlFlowEffect::None); + } + } + Value::Immediate(value) => { + if let Some(ptr) = value.as_u32() { + evaluator.set_value( + self.result().as_value_ref(), + Value::Immediate(Immediate::U32(ptr)), + ); + return Ok(ControlFlowEffect::None); + } + } + } + + Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid value for int-to-ptr cast (from {}): {}", input.ty(), input), + )) + } +} + +impl Eval for hir::Cast { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + todo!() + } +} + +impl Eval for hir::Bitcast { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let input = evaluator.get_value(&self.operand().as_value_ref())?; + let result = self.result(); + let output_ty = result.ty(); + let output = input.map_ty(output_ty).map_err(|err| { + evaluator.report("evaluation failed", self.span(), format!("invalid bitcast: {err}")) + })?; + evaluator.set_value(result.as_value_ref(), output); + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::Exec { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let Some(symbol_table) = self.as_operation().nearest_symbol_table() else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + "cannot evaluate function calls without a symbol table in scope", + )); + }; + + let symbol_table = symbol_table.borrow(); + let symbol_table = symbol_table.as_symbol_table().unwrap(); + let symbol_path = &self.callee().path; + let Some(symbol) = symbol_table.resolve(symbol_path) else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("unable to resolve callee '{symbol_path}'"), + )); + }; + + let arguments = ValueRange::<4>::from(self.arguments()).into_owned(); + + Ok(ControlFlowEffect::Call { + callee: symbol.borrow().as_operation_ref(), + arguments, + }) + } +} + +impl Eval for hir::Store { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let addr = self.addr(); + let addr_value = evaluator.use_value(&addr.as_value_ref())?; + let Immediate::U32(addr_value) = addr_value else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected pointer to be a u32 immediate, got {}", addr_value.ty()), + )); + }; + + let value = evaluator.get_value(&self.value().as_value_ref())?; + let value_ty = value.ty(); + let pointer_ty = addr.ty(); + let expected_ty = pointer_ty + .pointee() + .expect("expected pointer type to have been verified already"); + if &value_ty != expected_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid store: value type is {value_ty}, but pointee type is {expected_ty}" + ), + )); + } + + evaluator.write_memory(addr_value, value)?; + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::StoreLocal { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let local = self.local(); + let value = evaluator.get_value(&self.value().as_value_ref())?; + let value_ty = value.ty(); + let local_ty = local.ty(); + if value_ty != local_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid store to local variable: value type is {value_ty}, but local type is \ + {local_ty}" + ), + )); + } + + evaluator.write_local(local, value)?; + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::Load { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let addr = self.addr(); + let addr_value = evaluator.use_value(&addr.as_value_ref())?; + let Immediate::U32(addr_value) = addr_value else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected pointer to be a u32 immediate, got {}", addr_value.ty()), + )); + }; + + let result = self.result(); + let ty = result.ty(); + let pointer_ty = addr.ty(); + let expected_ty = pointer_ty + .pointee() + .expect("expected pointer type to have been verified already"); + if ty != expected_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid load: value type is {ty}, but pointee type is {expected_ty}"), + )); + } + + let loaded = evaluator.read_memory(addr_value, ty)?; + + evaluator.set_value(result.as_value_ref(), loaded); + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::LoadLocal { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let local = self.local(); + let result = self.result(); + let ty = result.ty(); + let local_ty = local.ty(); + if ty != &local_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid load from local variable: value type is {ty}, but local type is \ + {local_ty}" + ), + )); + } + + let loaded = evaluator.read_local(local)?; + + evaluator.set_value(result.as_value_ref(), loaded); + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::MemGrow { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let pages = evaluator.use_value(&self.pages().as_value_ref())?; + let Immediate::U32(pages) = pages else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected u32 input, got {}: {pages}", pages.ty()), + )); + }; + + let current_size = { + let current_context = evaluator.current_context_mut(); + let current_size = current_context.memory_size(); + current_context.memory_grow(pages as usize); + current_size as u32 + }; + evaluator.set_value(self.result().as_value_ref(), Immediate::U32(current_size)); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::MemSize { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let current_size = evaluator.current_context().memory_size() as u32; + + evaluator.set_value(self.result().as_value_ref(), Immediate::U32(current_size)); + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::MemSet { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let addr = self.addr(); + let addr_value = evaluator.use_value(&addr.as_value_ref())?; + let Immediate::U32(addr_value) = addr_value else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected pointer to be a u32 immediate, got {}", addr_value.ty()), + )); + }; + + let count = evaluator.use_value(&self.count().as_value_ref())?; + let Immediate::U32(count) = count else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected count to be a u32 immediate, got {}", count.ty()), + )); + }; + + let value = evaluator.use_value(&self.value().as_value_ref())?; + + // Verify that element type matches pointee type + let value_ty = value.ty(); + let pointer_ty = addr.ty(); + let expected_ty = pointer_ty + .pointee() + .expect("expected pointer type to have been verified already"); + if &value_ty != expected_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid memset: element type is {value_ty}, but pointee type is {expected_ty}" + ), + )); + } + + // Perform memset + for offset in 0..count { + let addr = addr_value + offset; + evaluator.write_memory(addr, value)?; + } + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for hir::MemCpy { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let source = self.source(); + let source_value = evaluator.use_value(&source.as_value_ref())?; + let Immediate::U32(source_value) = source_value else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected source pointer to be a u32 immediate, got {}", source_value.ty()), + )); + }; + + let dest = self.destination(); + let dest_value = evaluator.use_value(&dest.as_value_ref())?; + let Immediate::U32(dest_value) = dest_value else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "expected destination pointer to be a u32 immediate, got {}", + dest_value.ty() + ), + )); + }; + + let count = evaluator.use_value(&self.count().as_value_ref())?; + let Immediate::U32(count) = count else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected count to be a u32 immediate, got {}", count.ty()), + )); + }; + + // Verify that source and destination pointer types match + let source_ty = source.ty(); + let dest_ty = dest.ty(); + if source_ty != dest_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid memcpy: source and destination types do not match: {source_ty} vs \ + {dest_ty}" + ), + )); + } + + // Perform memcpy + for offset in 0..count { + let src = source_value + offset; + let dst = dest_value + offset; + let value = evaluator.read_memory(src, &source_ty)?; + evaluator.write_memory(dst, value)?; + } + + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Constant { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + evaluator.set_value(self.result().as_value_ref(), *self.value()); + Ok(ControlFlowEffect::None) + } +} + +macro_rules! binop { + ($op:ident, $evaluator:ident, $operator:ident) => {{ + binop!($op, $evaluator, $operator, $operator) + }}; + + ($op:ident, $evaluator:ident, $operator:ident, $felt_operator:ident) => {{ + let lhs = $op.lhs(); + let lhs_value = $evaluator.use_value(&lhs.as_value_ref())?; + let rhs = $op.rhs(); + let rhs_value = $evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err($evaluator.report( + "evaluation failed", + $op.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::I8(y)) => Immediate::I8(x.$operator(y)), + (Immediate::U8(x), Immediate::U8(y)) => Immediate::U8(x.$operator(y)), + (Immediate::I16(x), Immediate::I16(y)) => Immediate::I16(x.$operator(y)), + (Immediate::U16(x), Immediate::U16(y)) => Immediate::U16(x.$operator(y)), + (Immediate::I32(x), Immediate::I32(y)) => Immediate::I32(x.$operator(y)), + (Immediate::U32(x), Immediate::U32(y)) => Immediate::U32(x.$operator(y)), + (Immediate::I64(x), Immediate::I64(y)) => Immediate::I64(x.$operator(y)), + (Immediate::U64(x), Immediate::U64(y)) => Immediate::U64(x.$operator(y)), + (Immediate::I128(x), Immediate::I128(y)) => Immediate::I128(x.$operator(y)), + (Immediate::U128(x), Immediate::U128(y)) => Immediate::U128(x.$operator(y)), + (Immediate::Felt(x), Immediate::Felt(y)) => Immediate::Felt(x.$felt_operator(y)), + _ => unreachable!(), + } + }}; +} + +macro_rules! binop_checked { + ($op:ident, $evaluator:ident, $operator:ident, $felt_operator:ident) => {{ + let lhs = $op.lhs(); + let lhs_value = $evaluator.use_value(&lhs.as_value_ref())?; + let rhs = $op.rhs(); + let rhs_value = $evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err($evaluator.report( + "evaluation failed", + $op.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::I8(y)) => x.$operator(y).map(Immediate::I8), + (Immediate::U8(x), Immediate::U8(y)) => x.$operator(y).map(Immediate::U8), + (Immediate::I16(x), Immediate::I16(y)) => x.$operator(y).map(Immediate::I16), + (Immediate::U16(x), Immediate::U16(y)) => x.$operator(y).map(Immediate::U16), + (Immediate::I32(x), Immediate::I32(y)) => x.$operator(y).map(Immediate::I32), + (Immediate::U32(x), Immediate::U32(y)) => x.$operator(y).map(Immediate::U32), + (Immediate::I64(x), Immediate::I64(y)) => x.$operator(y).map(Immediate::I64), + (Immediate::U64(x), Immediate::U64(y)) => x.$operator(y).map(Immediate::U64), + (Immediate::I128(x), Immediate::I128(y)) => x.$operator(y).map(Immediate::I128), + (Immediate::U128(x), Immediate::U128(y)) => x.$operator(y).map(Immediate::U128), + (Immediate::Felt(x), Immediate::Felt(y)) => Some(Immediate::Felt(x.$felt_operator(y))), + _ => unreachable!(), + } + }}; +} + +macro_rules! binop_overflowing { + ($op:ident, $evaluator:ident, $operator:ident, $felt_operator:ident) => {{ + let lhs = $op.lhs(); + let lhs_value = $evaluator.use_value(&lhs.as_value_ref())?; + let rhs = $op.rhs(); + let rhs_value = $evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err($evaluator.report( + "evaluation failed", + $op.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::I8(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::I8(value), flag) + } + (Immediate::U8(x), Immediate::U8(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::U8(value), flag) + } + (Immediate::I16(x), Immediate::I16(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::I16(value), flag) + } + (Immediate::U16(x), Immediate::U16(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::U16(value), flag) + } + (Immediate::I32(x), Immediate::I32(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::I32(value), flag) + } + (Immediate::U32(x), Immediate::U32(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::U32(value), flag) + } + (Immediate::I64(x), Immediate::I64(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::I64(value), flag) + } + (Immediate::U64(x), Immediate::U64(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::U64(value), flag) + } + (Immediate::I128(x), Immediate::I128(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::I128(value), flag) + } + (Immediate::U128(x), Immediate::U128(y)) => { + let (value, flag) = x.$operator(y); + (Immediate::U128(value), flag) + } + (Immediate::Felt(x), Immediate::Felt(y)) => { + let value = x.$felt_operator(y); + (Immediate::Felt(value), false) + } + _ => unreachable!(), + } + }}; +} + +macro_rules! logical_binop { + ($op:ident, $evaluator:ident, $operator:ident) => {{ + let lhs = $op.lhs(); + let lhs_value = $evaluator.use_value(&lhs.as_value_ref())?; + let rhs = $op.rhs(); + let rhs_value = $evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err($evaluator.report( + "evaluation failed", + $op.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + match (lhs_value, rhs_value) { + (Immediate::I1(x), Immediate::I1(y)) => x.$operator(y), + _ => { + return Err($evaluator.report( + "evaluation failed", + $op.span(), + format!("expected boolean operands, got {lhs_ty} and {rhs_ty}"), + )); + } + } + }}; +} + +trait InvalidFeltOperation: Sized { + fn invalid_binary_felt_op(self, _other: Self) -> Self { + panic!("unsupported felt operator") + } + + fn invalid_unary_felt_op(self) -> Self { + panic!("unsupported felt operator") + } +} +impl InvalidFeltOperation for Felt {} + +impl Eval for arith::Add { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Add; + + let result = match self.overflow() { + Overflow::Unchecked => binop!(self, evaluator, add), + Overflow::Checked => { + let result = binop_checked!(self, evaluator, checked_add, add); + let Some(result) = result else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + "arithmetic overflow", + )); + }; + result + } + Overflow::Wrapping => binop!(self, evaluator, wrapping_add, add), + Overflow::Overflowing => unreachable!(), + }; + + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::AddOverflowing { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Add; + + let (result, overflowed) = binop_overflowing!(self, evaluator, overflowing_add, add); + evaluator.set_value(self.result().as_value_ref(), result); + evaluator.set_value(self.overflowed().as_value_ref(), overflowed); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Sub { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Sub; + + let result = match self.overflow() { + Overflow::Unchecked => binop!(self, evaluator, sub), + Overflow::Checked => { + let result = binop_checked!(self, evaluator, checked_sub, sub); + let Some(result) = result else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + "arithmetic underflow", + )); + }; + result + } + Overflow::Wrapping => binop!(self, evaluator, wrapping_sub, sub), + Overflow::Overflowing => unreachable!(), + }; + + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::SubOverflowing { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Sub; + + let (result, overflowed) = binop_overflowing!(self, evaluator, overflowing_sub, sub); + evaluator.set_value(self.result().as_value_ref(), result); + evaluator.set_value(self.overflowed().as_value_ref(), overflowed); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Mul { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Mul; + + let result = match self.overflow() { + Overflow::Unchecked => binop!(self, evaluator, mul), + Overflow::Checked => { + let result = binop_checked!(self, evaluator, checked_sub, mul); + let Some(result) = result else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + "arithmetic overflow", + )); + }; + result + } + Overflow::Wrapping => binop!(self, evaluator, wrapping_mul, mul), + Overflow::Overflowing => unreachable!(), + }; + + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::MulOverflowing { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Mul; + + let (result, overflowed) = binop_overflowing!(self, evaluator, overflowing_mul, mul); + evaluator.set_value(self.result().as_value_ref(), result); + evaluator.set_value(self.overflowed().as_value_ref(), overflowed); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Exp { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + todo!() + } +} + +impl Eval for arith::Div { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Div; + + let result = binop!(self, evaluator, div); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Sdiv { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Div; + + let result = binop_checked!(self, evaluator, checked_div, div); + match result { + Some(result) => { + evaluator.set_value(self.result().as_value_ref(), result); + } + None => { + let divisor = evaluator.get_value(&self.rhs().as_value_ref()).unwrap(); + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("division by {divisor}"), + )); + } + } + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Mod { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = binop_checked!(self, evaluator, checked_rem_euclid, invalid_binary_felt_op); + match result { + Some(result) => { + evaluator.set_value(self.result().as_value_ref(), result); + } + None => { + return Err(evaluator.report("evaluation failed", self.span(), "division by zero")); + } + } + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Smod { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = binop_checked!(self, evaluator, checked_rem_euclid, invalid_binary_felt_op); + match result { + Some(result) => { + evaluator.set_value(self.result().as_value_ref(), result); + } + None => { + let divisor = evaluator.get_value(&self.rhs().as_value_ref()).unwrap(); + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("division by {divisor}"), + )); + } + } + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Divmod { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Div; + let quotient = binop_checked!(self, evaluator, checked_div_euclid, div); + let remainder = binop_checked!(self, evaluator, checked_rem_euclid, invalid_binary_felt_op); + + match (quotient, remainder) { + (Some(quotient), Some(remainder)) => { + evaluator.set_value(self.quotient().as_value_ref(), quotient); + evaluator.set_value(self.remainder().as_value_ref(), remainder); + } + _ => { + let divisor = evaluator.get_value(&self.rhs().as_value_ref()).unwrap(); + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("division by {divisor}"), + )); + } + } + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Sdivmod { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Div; + let quotient = binop_checked!(self, evaluator, checked_div_euclid, div); + let remainder = binop_checked!(self, evaluator, checked_rem_euclid, invalid_binary_felt_op); + + match (quotient, remainder) { + (Some(quotient), Some(remainder)) => { + evaluator.set_value(self.quotient().as_value_ref(), quotient); + evaluator.set_value(self.remainder().as_value_ref(), remainder); + } + _ => { + let divisor = evaluator.get_value(&self.rhs().as_value_ref()).unwrap(); + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("division by {divisor}"), + )); + } + } + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::And { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::BitAnd; + let result = logical_binop!(self, evaluator, bitand); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Or { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::BitOr; + let result = logical_binop!(self, evaluator, bitor); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Xor { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::BitXor; + let result = logical_binop!(self, evaluator, bitxor); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Band { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::BitAnd; + let result = binop!(self, evaluator, bitand, invalid_binary_felt_op); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Bor { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::BitOr; + let result = binop!(self, evaluator, bitor, invalid_binary_felt_op); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Bxor { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::BitXor; + let result = binop!(self, evaluator, bitxor, invalid_binary_felt_op); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} +impl Eval for arith::Shl { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.lhs(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + let rhs = self.shift(); + let rhs_value = evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + let result = match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::U32(y)) => Immediate::I8(x.wrapping_shl(y)), + (Immediate::U8(x), Immediate::U32(y)) => Immediate::U8(x.wrapping_shl(y)), + (Immediate::I16(x), Immediate::U32(y)) => Immediate::I16(x.wrapping_shl(y)), + (Immediate::U16(x), Immediate::U32(y)) => Immediate::U16(x.wrapping_shl(y)), + (Immediate::I32(x), Immediate::U32(y)) => Immediate::I32(x.wrapping_shl(y)), + (Immediate::U32(x), Immediate::U32(y)) => Immediate::U32(x.wrapping_shl(y)), + (Immediate::I64(x), Immediate::U32(y)) => Immediate::I64(x.wrapping_shl(y)), + (Immediate::U64(x), Immediate::U32(y)) => Immediate::U64(x.wrapping_shl(y)), + (Immediate::I128(x), Immediate::U32(y)) => Immediate::I128(x.wrapping_shl(y)), + (Immediate::U128(x), Immediate::U32(y)) => Immediate::U128(x.wrapping_shl(y)), + (_, Immediate::U32(_)) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("type does not support shl: {lhs_ty}"), + )); + } + (..) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid shift, expected u32, got {rhs_ty}"), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Shr { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.lhs(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + let rhs = self.shift(); + let rhs_value = evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + let result = match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::U32(y)) => Immediate::I8(x.wrapping_shr(y)), + (Immediate::U8(x), Immediate::U32(y)) => Immediate::U8(x.wrapping_shr(y)), + (Immediate::I16(x), Immediate::U32(y)) => Immediate::I16(x.wrapping_shr(y)), + (Immediate::U16(x), Immediate::U32(y)) => Immediate::U16(x.wrapping_shr(y)), + (Immediate::I32(x), Immediate::U32(y)) => Immediate::I32(x.wrapping_shr(y)), + (Immediate::U32(x), Immediate::U32(y)) => Immediate::U32(x.wrapping_shr(y)), + (Immediate::I64(x), Immediate::U32(y)) => Immediate::I64(x.wrapping_shr(y)), + (Immediate::U64(x), Immediate::U32(y)) => Immediate::U64(x.wrapping_shr(y)), + (Immediate::I128(x), Immediate::U32(y)) => Immediate::I128(x.wrapping_shr(y)), + (Immediate::U128(x), Immediate::U32(y)) => Immediate::U128(x.wrapping_shr(y)), + (_, Immediate::U32(_)) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("type does not support shr: {lhs_ty}"), + )); + } + (..) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid shift, expected u32, got {rhs_ty}"), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Ashr { + fn eval(&self, _evaluator: &mut HirEvaluator) -> Result { + todo!() + } +} + +impl Eval for arith::Rotl { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.lhs(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + let rhs = self.shift(); + let rhs_value = evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + let result = match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::U32(y)) => Immediate::I8(x.rotate_left(y)), + (Immediate::U8(x), Immediate::U32(y)) => Immediate::U8(x.rotate_left(y)), + (Immediate::I16(x), Immediate::U32(y)) => Immediate::I16(x.rotate_left(y)), + (Immediate::U16(x), Immediate::U32(y)) => Immediate::U16(x.rotate_left(y)), + (Immediate::I32(x), Immediate::U32(y)) => Immediate::I32(x.rotate_left(y)), + (Immediate::U32(x), Immediate::U32(y)) => Immediate::U32(x.rotate_left(y)), + (Immediate::I64(x), Immediate::U32(y)) => Immediate::I64(x.rotate_left(y)), + (Immediate::U64(x), Immediate::U32(y)) => Immediate::U64(x.rotate_left(y)), + (Immediate::I128(x), Immediate::U32(y)) => Immediate::I128(x.rotate_left(y)), + (Immediate::U128(x), Immediate::U32(y)) => Immediate::U128(x.rotate_left(y)), + (_, Immediate::U32(_)) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("type does not support rotl: {lhs_ty}"), + )); + } + (..) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid shift, expected u32, got {rhs_ty}"), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Rotr { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.lhs(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + let rhs = self.shift(); + let rhs_value = evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + let result = match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::U32(y)) => Immediate::I8(x.rotate_right(y)), + (Immediate::U8(x), Immediate::U32(y)) => Immediate::U8(x.rotate_right(y)), + (Immediate::I16(x), Immediate::U32(y)) => Immediate::I16(x.rotate_right(y)), + (Immediate::U16(x), Immediate::U32(y)) => Immediate::U16(x.rotate_right(y)), + (Immediate::I32(x), Immediate::U32(y)) => Immediate::I32(x.rotate_right(y)), + (Immediate::U32(x), Immediate::U32(y)) => Immediate::U32(x.rotate_right(y)), + (Immediate::I64(x), Immediate::U32(y)) => Immediate::I64(x.rotate_right(y)), + (Immediate::U64(x), Immediate::U32(y)) => Immediate::U64(x.rotate_right(y)), + (Immediate::I128(x), Immediate::U32(y)) => Immediate::I128(x.rotate_right(y)), + (Immediate::U128(x), Immediate::U32(y)) => Immediate::U128(x.rotate_right(y)), + (_, Immediate::U32(_)) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("type does not support rotr: {lhs_ty}"), + )); + } + (..) => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid shift, expected u32, got {rhs_ty}"), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +macro_rules! comparison { + ($op:ident, $evaluator:ident, $operator:ident) => {{ + let lhs = $op.lhs(); + let lhs_value = $evaluator.use_value(&lhs.as_value_ref())?; + let rhs = $op.rhs(); + let rhs_value = $evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err($evaluator.report( + "evaluation failed", + $op.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::I8(y)) => x.$operator(&y), + (Immediate::U8(x), Immediate::U8(y)) => x.$operator(&y), + (Immediate::I16(x), Immediate::I16(y)) => x.$operator(&y), + (Immediate::U16(x), Immediate::U16(y)) => x.$operator(&y), + (Immediate::I32(x), Immediate::I32(y)) => x.$operator(&y), + (Immediate::U32(x), Immediate::U32(y)) => x.$operator(&y), + (Immediate::I64(x), Immediate::I64(y)) => x.$operator(&y), + (Immediate::U64(x), Immediate::U64(y)) => x.$operator(&y), + (Immediate::I128(x), Immediate::I128(y)) => x.$operator(&y), + (Immediate::U128(x), Immediate::U128(y)) => x.$operator(&y), + (Immediate::Felt(x), Immediate::Felt(y)) => x.as_int().$operator(&y.as_int()), + _ => unreachable!(), + } + }}; +} + +macro_rules! comparison_with { + ($op:ident, $evaluator:ident, $comparator:path) => {{ + let lhs = $op.lhs(); + let lhs_value = $evaluator.use_value(&lhs.as_value_ref())?; + let rhs = $op.rhs(); + let rhs_value = $evaluator.use_value(&rhs.as_value_ref())?; + + let lhs_ty = lhs.ty(); + let rhs_ty = lhs.ty(); + if lhs_ty != rhs_ty { + return Err($evaluator.report( + "evaluation failed", + $op.span(), + format!("operand types do not match: {lhs_ty} vs {rhs_ty}"), + )); + } + + match (lhs_value, rhs_value) { + (Immediate::I8(x), Immediate::I8(y)) => Immediate::I8($comparator(x, y)), + (Immediate::U8(x), Immediate::U8(y)) => Immediate::U8($comparator(x, y)), + (Immediate::I16(x), Immediate::I16(y)) => Immediate::I16($comparator(x, y)), + (Immediate::U16(x), Immediate::U16(y)) => Immediate::U16($comparator(x, y)), + (Immediate::I32(x), Immediate::I32(y)) => Immediate::I32($comparator(x, y)), + (Immediate::U32(x), Immediate::U32(y)) => Immediate::U32($comparator(x, y)), + (Immediate::I64(x), Immediate::I64(y)) => Immediate::I64($comparator(x, y)), + (Immediate::U64(x), Immediate::U64(y)) => Immediate::U64($comparator(x, y)), + (Immediate::I128(x), Immediate::I128(y)) => Immediate::I128($comparator(x, y)), + (Immediate::U128(x), Immediate::U128(y)) => Immediate::U128($comparator(x, y)), + (Immediate::Felt(x), Immediate::Felt(y)) => { + Immediate::Felt(Felt::new($comparator(x.as_int(), y.as_int()))) + } + _ => unreachable!(), + } + }}; +} + +impl Eval for arith::Eq { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison!(self, evaluator, eq); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Neq { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison!(self, evaluator, ne); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Gt { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison!(self, evaluator, gt); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Gte { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison!(self, evaluator, ge); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Lt { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison!(self, evaluator, lt); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Lte { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison!(self, evaluator, le); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Min { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison_with!(self, evaluator, core::cmp::min); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Max { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let result = comparison_with!(self, evaluator, core::cmp::max); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Trunc { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let lhs_ty = lhs_value.ty(); + let result = self.result(); + let expected_ty = result.ty(); + if &lhs_ty == expected_ty { + evaluator.set_value(self.result().as_value_ref(), lhs_value); + return Ok(ControlFlowEffect::None); + } + + if lhs_ty.size_in_bits() < expected_ty.size_in_bits() { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid truncation: input type of {lhs_ty} is smaller than the target type \ + {expected_ty}" + ), + )); + } + + let result = match expected_ty { + Type::I1 => lhs_value.bitcast_u128().map(|x| Immediate::I1(!x.is_multiple_of(2))), + Type::I8 => lhs_value.bitcast_u128().map(|x| Immediate::I8(x as i8)), + Type::U8 => lhs_value.bitcast_u128().map(|x| Immediate::U8(x as u8)), + Type::I16 => lhs_value.bitcast_u128().map(|x| Immediate::I16(x as i16)), + Type::U16 => lhs_value.bitcast_u128().map(|x| Immediate::U16(x as u16)), + Type::I32 => lhs_value.bitcast_u128().map(|x| Immediate::I32(x as i32)), + Type::U32 => lhs_value.bitcast_u128().map(|x| Immediate::U32(x as u32)), + Type::I64 => lhs_value.bitcast_u128().map(|x| Immediate::I64(x as i64)), + Type::U64 => lhs_value.bitcast_u128().map(|x| Immediate::U64(x as u64)), + Type::I128 => lhs_value.bitcast_i128().map(Immediate::I128), + Type::U128 => lhs_value.bitcast_u128().map(Immediate::U128), + unsupported_ty => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid truncation: target type of {unsupported_ty} not supported"), + )); + } + } + .expect("expected infallable cast by this point"); + + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Zext { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let lhs_ty = lhs_value.ty(); + let result = self.result(); + let expected_ty = result.ty(); + if &lhs_ty == expected_ty { + evaluator.set_value(self.result().as_value_ref(), lhs_value); + return Ok(ControlFlowEffect::None); + } + + if lhs_ty.size_in_bits() > expected_ty.size_in_bits() { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid extension: input type of {lhs_ty} is larger than the target type \ + {expected_ty}" + ), + )); + } + + let result = match expected_ty { + Type::U8 => lhs_value.bitcast_u8().map(Immediate::U8), + Type::U16 => lhs_value.bitcast_u16().map(Immediate::U16), + Type::U32 => lhs_value.bitcast_u32().map(Immediate::U32), + Type::U64 => lhs_value.bitcast_u64().map(Immediate::U64), + Type::U128 => lhs_value.bitcast_u128().map(Immediate::U128), + unsupported_ty => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid zero-extension: target type of {unsupported_ty} not supported" + ), + )); + } + } + .expect("expected infallable cast by this point"); + + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Sext { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let lhs_ty = lhs_value.ty(); + let result = self.result(); + let expected_ty = result.ty(); + if &lhs_ty == expected_ty { + evaluator.set_value(self.result().as_value_ref(), lhs_value); + return Ok(ControlFlowEffect::None); + } + + if lhs_ty.size_in_bits() > expected_ty.size_in_bits() { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid extension: input type of {lhs_ty} is larger than the target type \ + {expected_ty}" + ), + )); + } + + let result = match expected_ty { + Type::I8 => lhs_value.bitcast_i8().map(Immediate::I8), + Type::I16 => lhs_value.bitcast_i16().map(Immediate::I16), + Type::I32 => lhs_value.bitcast_i32().map(Immediate::I32), + Type::I64 => lhs_value.bitcast_i64().map(Immediate::I64), + Type::I128 => lhs_value.bitcast_i128().map(Immediate::I128), + unsupported_ty => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!( + "invalid sign-extension: target type of {unsupported_ty} not supported" + ), + )); + } + } + .expect("expected infallable cast by this point"); + + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +macro_rules! unaryop { + ($op:ident, $evaluator:ident, $operator:ident) => {{ + unaryop!($op, $evaluator, $operator, $operator) + }}; + + ($op:ident, $evaluator:ident, $operator:ident, $felt_operator:ident) => {{ + let lhs = $op.operand(); + let lhs_value = $evaluator.use_value(&lhs.as_value_ref())?; + + match lhs_value { + Immediate::I8(x) => Immediate::I8(x.$operator()), + Immediate::U8(x) => Immediate::U8(x.$operator()), + Immediate::I16(x) => Immediate::I16(x.$operator()), + Immediate::U16(x) => Immediate::U16(x.$operator()), + Immediate::I32(x) => Immediate::I32(x.$operator()), + Immediate::U32(x) => Immediate::U32(x.$operator()), + Immediate::I64(x) => Immediate::I64(x.$operator()), + Immediate::U64(x) => Immediate::U64(x.$operator()), + Immediate::I128(x) => Immediate::I128(x.$operator()), + Immediate::U128(x) => Immediate::U128(x.$operator()), + Immediate::Felt(x) => Immediate::Felt(x.$felt_operator()), + _ => unreachable!(), + } + }}; +} + +impl Eval for arith::Incr { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use midenc_hir::FieldElement; + + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::I8(x.wrapping_add(1)), + Immediate::U8(x) => Immediate::U8(x.wrapping_add(1)), + Immediate::I16(x) => Immediate::I16(x.wrapping_add(1)), + Immediate::U16(x) => Immediate::U16(x.wrapping_add(1)), + Immediate::I32(x) => Immediate::I32(x.wrapping_add(1)), + Immediate::U32(x) => Immediate::U32(x.wrapping_add(1)), + Immediate::I64(x) => Immediate::I64(x.wrapping_add(1)), + Immediate::U64(x) => Immediate::U64(x.wrapping_add(1)), + Immediate::I128(x) => Immediate::I128(x.wrapping_add(1)), + Immediate::U128(x) => Immediate::U128(x.wrapping_add(1)), + Immediate::Felt(x) => Immediate::Felt(x + Felt::ONE), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("type does not support incr: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Neg { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::I8(-x), + Immediate::U8(x) => Immediate::U8(!x), + Immediate::I16(x) => Immediate::I16(-x), + Immediate::U16(x) => Immediate::U16(!x), + Immediate::I32(x) => Immediate::I32(-x), + Immediate::U32(x) => Immediate::U32(!x), + Immediate::I64(x) => Immediate::I64(-x), + Immediate::U64(x) => Immediate::U64(!x), + Immediate::I128(x) => Immediate::I128(-x), + Immediate::U128(x) => Immediate::U128(!x), + Immediate::Felt(x) => Immediate::Felt(Felt::new(!x.as_int())), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("negation is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Inv { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use midenc_hir::FieldElement; + + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::Felt(x) => Immediate::Felt(x.inv()), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("modular inverse is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Ilog2 { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::U32(x.ilog2()), + Immediate::U8(x) => Immediate::U32(x.ilog2()), + Immediate::I16(x) => Immediate::U32(x.ilog2()), + Immediate::U16(x) => Immediate::U32(x.ilog2()), + Immediate::I32(x) => Immediate::U32(x.ilog2()), + Immediate::U32(x) => Immediate::U32(x.ilog2()), + Immediate::I64(x) => Immediate::U32(x.ilog2()), + Immediate::U64(x) => Immediate::U32(x.ilog2()), + Immediate::I128(x) => Immediate::U32(x.ilog2()), + Immediate::U128(x) => Immediate::U32(x.ilog2()), + Immediate::Felt(x) => Immediate::U32(x.as_int().ilog2()), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("ilog2 is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Pow2 { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let Some(power) = lhs_value.as_u32() else { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("invalid power for pow2: {lhs_value} (type is {})", lhs.ty()), + )); + }; + let result = 2u32.pow(power); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Not { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I1(x) => !x, + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("expected boolean operand, got {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Bnot { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + use core::ops::Not; + + let result = unaryop!(self, evaluator, not, invalid_unary_felt_op); + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::IsOdd { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => x % 2 != 0, + Immediate::U8(x) => !x.is_multiple_of(2), + Immediate::I16(x) => x % 2 != 0, + Immediate::U16(x) => !x.is_multiple_of(2), + Immediate::I32(x) => x % 2 != 0, + Immediate::U32(x) => !x.is_multiple_of(2), + Immediate::I64(x) => x % 2 != 0, + Immediate::U64(x) => !x.is_multiple_of(2), + Immediate::I128(x) => x % 2 != 0, + Immediate::U128(x) => !x.is_multiple_of(2), + Immediate::Felt(x) => !x.as_int().is_multiple_of(2), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("is_odd is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Popcnt { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::U32(x.count_ones()), + Immediate::U8(x) => Immediate::U32(x.count_ones()), + Immediate::I16(x) => Immediate::U32(x.count_ones()), + Immediate::U16(x) => Immediate::U32(x.count_ones()), + Immediate::I32(x) => Immediate::U32(x.count_ones()), + Immediate::U32(x) => Immediate::U32(x.count_ones()), + Immediate::I64(x) => Immediate::U32(x.count_ones()), + Immediate::U64(x) => Immediate::U32(x.count_ones()), + Immediate::I128(x) => Immediate::U32(x.count_ones()), + Immediate::U128(x) => Immediate::U32(x.count_ones()), + Immediate::Felt(x) => Immediate::U32(x.as_int().count_ones()), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("popcnt is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Clz { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::U32(x.leading_zeros()), + Immediate::U8(x) => Immediate::U32(x.leading_zeros()), + Immediate::I16(x) => Immediate::U32(x.leading_zeros()), + Immediate::U16(x) => Immediate::U32(x.leading_zeros()), + Immediate::I32(x) => Immediate::U32(x.leading_zeros()), + Immediate::U32(x) => Immediate::U32(x.leading_zeros()), + Immediate::I64(x) => Immediate::U32(x.leading_zeros()), + Immediate::U64(x) => Immediate::U32(x.leading_zeros()), + Immediate::I128(x) => Immediate::U32(x.leading_zeros()), + Immediate::U128(x) => Immediate::U32(x.leading_zeros()), + Immediate::Felt(x) => Immediate::U32(x.as_int().leading_zeros()), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("clz is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Ctz { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::U32(x.trailing_zeros()), + Immediate::U8(x) => Immediate::U32(x.trailing_zeros()), + Immediate::I16(x) => Immediate::U32(x.trailing_zeros()), + Immediate::U16(x) => Immediate::U32(x.trailing_zeros()), + Immediate::I32(x) => Immediate::U32(x.trailing_zeros()), + Immediate::U32(x) => Immediate::U32(x.trailing_zeros()), + Immediate::I64(x) => Immediate::U32(x.trailing_zeros()), + Immediate::U64(x) => Immediate::U32(x.trailing_zeros()), + Immediate::I128(x) => Immediate::U32(x.trailing_zeros()), + Immediate::U128(x) => Immediate::U32(x.trailing_zeros()), + Immediate::Felt(x) => Immediate::U32(x.as_int().trailing_zeros()), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("ctz is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Clo { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::U32(x.leading_ones()), + Immediate::U8(x) => Immediate::U32(x.leading_ones()), + Immediate::I16(x) => Immediate::U32(x.leading_ones()), + Immediate::U16(x) => Immediate::U32(x.leading_ones()), + Immediate::I32(x) => Immediate::U32(x.leading_ones()), + Immediate::U32(x) => Immediate::U32(x.leading_ones()), + Immediate::I64(x) => Immediate::U32(x.leading_ones()), + Immediate::U64(x) => Immediate::U32(x.leading_ones()), + Immediate::I128(x) => Immediate::U32(x.leading_ones()), + Immediate::U128(x) => Immediate::U32(x.leading_ones()), + Immediate::Felt(x) => Immediate::U32(x.as_int().leading_ones()), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("clo is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} + +impl Eval for arith::Cto { + fn eval(&self, evaluator: &mut HirEvaluator) -> Result { + let lhs = self.operand(); + let lhs_value = evaluator.use_value(&lhs.as_value_ref())?; + + let result = match lhs_value { + Immediate::I8(x) => Immediate::U32(x.trailing_ones()), + Immediate::U8(x) => Immediate::U32(x.trailing_ones()), + Immediate::I16(x) => Immediate::U32(x.trailing_ones()), + Immediate::U16(x) => Immediate::U32(x.trailing_ones()), + Immediate::I32(x) => Immediate::U32(x.trailing_ones()), + Immediate::U32(x) => Immediate::U32(x.trailing_ones()), + Immediate::I64(x) => Immediate::U32(x.trailing_ones()), + Immediate::U64(x) => Immediate::U32(x.trailing_ones()), + Immediate::I128(x) => Immediate::U32(x.trailing_ones()), + Immediate::U128(x) => Immediate::U32(x.trailing_ones()), + Immediate::Felt(x) => Immediate::U32(x.as_int().trailing_ones()), + _ => { + return Err(evaluator.report( + "evaluation failed", + self.span(), + format!("cto is not supported for value type: {}", lhs.ty()), + )); + } + }; + evaluator.set_value(self.result().as_value_ref(), result); + Ok(ControlFlowEffect::None) + } +} diff --git a/eval/src/evaluator.rs b/eval/src/evaluator.rs new file mode 100644 index 000000000..ac807a4d3 --- /dev/null +++ b/eval/src/evaluator.rs @@ -0,0 +1,1044 @@ +mod context; +mod frame; +mod memory; + +use alloc::{format, rc::Rc, string::ToString, vec, vec::Vec}; + +use midenc_hir::{ + dialects::builtin::{ComponentId, LocalVariable}, + formatter::DisplayValues, + smallvec, CallableOpInterface, Context, Immediate, Operation, OperationRef, RegionBranchPoint, + RegionRef, Report, SmallVec, SourceSpan, Spanned, SymbolPath, Type, Value as _, ValueRange, + ValueRef, +}; +use midenc_session::diagnostics::{InFlightDiagnosticBuilder, Severity}; + +use self::{context::ExecutionContext, frame::CallFrame}; +use crate::{value::MaterializedValue, *}; + +pub struct HirEvaluator { + /// The context in which all IR objects are allocated + context: Rc, + /// The execution context stack + contexts: Vec, + /// The stack of call frames maintained during execution, for use in constructing stack traces. + call_stack: Vec, + /// An unspecified set of bit flags that can be manipulated by an operation when transferring + /// control to a successor operation. The semantics of the bits are dictated entirely by the + /// ops in question. + /// + /// It is expected that a protocol is adhered to in regards to setting/clearing condition flags. + /// Namely, the operation that sets condition flags should expect that the receiving operation + /// will clear them. Furthermore, if an operation observes a condition it doesn't understand, + /// it must assert. + condition: u8, + /// The last operation that set/reset the condition flags + condition_set_by: Option, + /// The current operation being executed + ip: Option, +} + +impl HirEvaluator { + /// Construct a new evaluator for `context` + pub fn new(context: Rc) -> Self { + Self { + context, + contexts: vec![ExecutionContext::default()], + call_stack: Default::default(), + condition: 0, + condition_set_by: None, + ip: None, + } + } + + /// Reset the evaluator state to start the next evaluation with a clean slate. + pub fn reset(&mut self) { + self.contexts.truncate(1); + self.current_context_mut().reset(); + self.call_stack.clear(); + self.condition = 0; + self.condition_set_by = None; + self.ip = None; + } + + /// The current frame of the call stack + fn current_frame(&self) -> &CallFrame { + self.call_stack.last().expect("cannot read current call frame") + } + + /// The current frame of the call stack + fn current_frame_mut(&mut self) -> &mut CallFrame { + self.call_stack.last_mut().expect("cannot read current call frame") + } + + /// The current execution context (i.e. memory, registers) + pub fn current_context(&self) -> &ExecutionContext { + self.contexts.last().unwrap() + } + + /// The current execution context (i.e. memory, registers) + pub fn current_context_mut(&mut self) -> &mut ExecutionContext { + self.contexts.last_mut().unwrap() + } + + /// Enter a fresh execution context (i.e. memory, registers), with an optional identifier + pub fn enter_context(&mut self, id: Option) { + self.contexts.push(id.map(ExecutionContext::new).unwrap_or_default()); + } + + /// Exit the current execution context, and return the previous context on the context stack. + pub fn exit_context(&mut self) { + assert!(self.contexts.len() > 1, "cannot exit the root context"); + self.contexts.pop().expect("attempted to exit a context that doesn't exist"); + } + + /// Evaluate `op` with `args`, returning the results, if any, produced by it. + /// + /// This will fail with an error if any of the following occur: + /// + /// * The number and type of arguments does not match the operands expected by `op` + /// * `op` implements `Initialize` and initialization fails + /// * An error occurs while evaluating `op` + pub fn eval(&mut self, op: &Operation, args: I) -> Result, Report> + where + I: IntoIterator, + { + // Handle evaluation of callable symbols specially + if let Some(callable) = op.as_trait::() { + return self.eval_callable(callable, args); + } + + self.reset(); + + let args = args.into_iter().collect::>(); + + // Check arity + if op.num_operands() != args.len() { + return Err(self.report( + "invalid evaluation", + op.span(), + format!("expected {} arguments, but {} were given", op.num_operands(), args.len()), + )); + } + + // Check types + for (expected, given) in op.operands().iter().zip(args.iter()) { + let given_ty = given.ty(); + let expected = expected.borrow(); + let expected_ty = expected.ty(); + if expected_ty != given_ty { + return Err(self + .error("invalid evaluation") + .with_primary_label( + op.span(), + format!("argument type mismatch: expected {expected_ty}, got {given_ty}",), + ) + .with_secondary_label( + expected.span(), + "argument provided for this operand is invalid", + ) + .into_report()); + } + } + + // If the root operation implements [Initialize], perform initialization now. + if let Some(initializable) = op.as_trait::() { + initializable.initialize(self)?; + } + + self.ip = Some(op.as_operation_ref()); + + // NOTE: This is a bit of a hack, because `op` may not actually be callable + let mut frame = CallFrame::new(op.as_operation_ref()); + + // Initialize operand registers with the given arguments + for (param, arg) in ValueRange::<2>::from(op.operands().all()).into_iter().zip(args) { + frame.set_value(param, arg); + } + + self.call_stack.push(frame); + + // Start evaluation + self.eval_op_and_gather_results(op) + } + + /// Evaluate `callable` with `args`, returning the results, if any, produced by it. + /// + /// This will fail with an error if any of the following occur: + /// + /// * The number and type of arguments does not match the callable signature. + /// * `callable` implements `Initialize` and initialization fails + /// * `callable` is a declaration + /// * An error occurs while evaluating the callable region + pub fn eval_callable( + &mut self, + callable: &dyn CallableOpInterface, + args: I, + ) -> Result, Report> + where + I: IntoIterator, + { + self.reset(); + + // Verify the callable symbol is defined, not just declared + let Some(callable_region) = callable.get_callable_region() else { + return Err(self.report( + "invalid entrypoint", + callable.as_operation().span(), + "symbol declarations are not valid callee targets", + )); + }; + + let signature = callable.signature(); + let args = args.into_iter().collect::>(); + + // Check arity + if signature.arity() != args.len() { + return Err(self.report( + "invalid call", + callable.as_operation().span(), + format!("expected {} arguments, but {} were given", signature.arity(), args.len()), + )); + } + + // Check types + for (index, (expected, given)) in signature.params().iter().zip(args.iter()).enumerate() { + let given_ty = given.ty(); + if expected.ty != given_ty { + let arg = callable_region.borrow().entry().arguments()[index]; + return Err(self + .error("invalid call") + .with_primary_label( + callable.as_operation().span(), + format!( + "argument type mismatch: expected {}, got {given_ty}", + &expected.ty + ), + ) + .with_secondary_label( + arg.span(), + "argument provided for this parameter is invalid", + ) + .into_report()); + } + } + + // If the callable also implements [Initialize], perform initialization now. + if let Some(initializable) = callable.as_operation().as_trait::() { + initializable.initialize(self)?; + } + + // Initialize the call stack + let mut frame = CallFrame::new(callable.as_operation().as_operation_ref()); + + // Initialize registers with the callee arguments + let region = callable_region.borrow(); + for (param, arg) in region.entry().argument_values().zip(args) { + frame.set_value(param, arg); + } + + self.call_stack.push(frame); + + // Evaluate the callable region + self.eval_region(callable.as_operation(), callable_region) + } + + /// Evaluate `symbol` in `symbol_table` with `args`, returning the results, if any, it produces. + /// + /// This will fail with an error if any of the following occur: + /// + /// * `symbol_table` does not implement `SymbolTable` + /// * `symbol_table` implements `Initialize` and initialization fails + /// * `symbol` is not resolvable via `op`'s symbol table + /// * `symbol` does not implement `CallableOpInterface` + /// * `symbol` is only a declaration + /// * The number and type of arguments does not match the symbol parameter list + /// * An error occurs while evaluating the given symbol + pub fn call( + &mut self, + symbol_table: &Operation, + symbol: &SymbolPath, + args: I, + ) -> Result, Report> + where + I: IntoIterator, + { + let Some(symbol_table) = symbol_table.as_symbol_table() else { + return Err(self.report( + "expected op to be a symbol table", + symbol_table.span(), + "this op does not implement the SymbolTable trait", + )); + }; + + // Resolve the symbol + let symbol_manager = symbol_table.symbol_manager(); + let Some(symbol) = symbol_manager.lookup_symbol_ref(symbol) else { + return Err(self.report( + "invalid entrypoint", + symbol_table.as_symbol_table_operation().span(), + format!("could not resolve '{symbol}' in this op"), + )); + }; + + let op = symbol.borrow(); + + // Verify the symbol is callable + let Some(callable) = op.as_trait::() else { + return Err(self.report( + "invalid entrypoint", + op.span(), + "this symbol does not implement CallableOpInterface", + )); + }; + + // Verify the callable symbol is defined, not just declared + let Some(callable_region) = callable.get_callable_region() else { + return Err(self.report( + "invalid entrypoint", + op.span(), + "symbol declarations are not valid callee targets", + )); + }; + + let signature = callable.signature(); + let args = args.into_iter().collect::>(); + + // Check arity + if signature.arity() != args.len() { + return Err(self.report( + "invalid call", + op.span(), + format!("expected {} arguments, but {} were given", signature.arity(), args.len()), + )); + } + + // Check types + for (index, (expected, given)) in signature.params().iter().zip(args.iter()).enumerate() { + let given_ty = given.ty(); + if expected.ty != given_ty { + let arg = callable_region.borrow().entry().arguments()[index]; + return Err(self + .error("invalid call") + .with_primary_label( + op.span(), + format!( + "argument type mismatch: expected {}, got {given_ty}", + &expected.ty + ), + ) + .with_secondary_label( + arg.span(), + "argument provided for this parameter is invalid", + ) + .into_report()); + } + } + + // If the root operation implements [Initialize], perform initialization now. + if let Some(initializable) = + symbol_table.as_symbol_table_operation().as_trait::() + { + initializable.initialize(self)?; + } + + // Initialize the call stack + let mut frame = CallFrame::new(symbol); + + // Initialize registers with the callee arguments + let region = callable_region.borrow(); + for (param, arg) in region.entry().argument_values().zip(args) { + frame.set_value(param, arg); + } + + self.call_stack.push(frame); + + // Evaluate the callable region + self.eval_region(&op, callable_region) + } + + /// Read a value of type `ty` from `addr` + /// + /// Returns an error if `addr` is invalid, `ty` is not a valid immediate type, or the specified + /// type could not be read from `addr` (either the encoding is invalid, or the read would be + /// out of bounds). + pub fn read_memory(&self, addr: u32, ty: &Type) -> Result { + self.current_context().read_memory(addr, ty, self.current_span()) + } + + /// Write `value` to `addr` in heap memory. + /// + /// Returns an error if `addr` is invalid, or `value` could not be written to `addr` (either the + /// value is poison, or the write would go out of bounds). + pub fn write_memory(&mut self, addr: u32, value: impl Into) -> Result<(), Report> { + let at = self.current_span(); + self.current_context_mut().write_memory(addr, value, at) + } + + /// Read the value of the given local variable in the current symbol, if present. + /// + /// See [CallFrame::read_local] for details on correct usage. + /// + /// # Panics + /// + /// This function will panic if the call stack is empty. + pub fn read_local(&self, local: &LocalVariable) -> Result { + self.call_stack + .last() + .expect("cannot read local variables outside of a function") + .read_local(local, self.current_span(), &self.context) + } + + /// Write `value` to `local`. + /// + /// See [CallFrame::write_local] for details on correct usage. + /// + /// # Panics + /// + /// This function will panic if the call stack is empty. + pub fn write_local( + &mut self, + local: &LocalVariable, + value: impl Into, + ) -> Result<(), Report> { + let span = self.current_span(); + self.call_stack + .last_mut() + .expect("cannot write local variables outside of a function") + .write_local(local, value.into(), span, &self.context) + } + + /// Read the concrete value assigned to `value` at the current program point + pub fn get_value(&self, value: &ValueRef) -> Result { + self.current_frame().get_value(value, self.current_span()) + } + + /// Read the concrete value assigned to `value` at the current program point, and verify that + /// its usage is valid, i.e. not poison. + pub fn use_value(&self, value: &ValueRef) -> Result { + self.current_frame().use_value(value, self.current_span()) + } + + /// Set the concrete value assigned to `id` + pub fn set_value(&mut self, id: ValueRef, value: impl Into) { + self.current_frame_mut().set_value(id, value); + } + + /// Start building an error diagnostic + pub fn error(&self, message: impl ToString) -> InFlightDiagnosticBuilder<'_> { + self.context + .session() + .diagnostics + .diagnostic(Severity::Error) + .with_message(message) + } + + /// Construct a [Report] from an error diagnostic consisting of a simple message and label. + pub fn report(&self, message: impl ToString, _at: SourceSpan, label: impl ToString) -> Report { + panic!("{}: {}", message.to_string(), label.to_string()) + /* + self.context + .session() + .diagnostics + .diagnostic(Severity::Error) + .with_message(message) + .with_primary_label(at, label) + .into_report() + */ + } + + pub fn current_span(&self) -> SourceSpan { + self.ip.map(|ip| ip.span()).unwrap_or_default() + } +} + +impl HirEvaluator { + /// This function implements the core interpreter loop. + /// + /// Fundamentally, the way it works is by evaluating `region` of `op` starting in its entry + /// block. Each operation in the block is evaluated using the `Eval` implementation for that + /// op, and the control flow effect produced by evaluation is used to drive the core loop: + /// + /// * For primitive operations that produce no control flow effects, evaluation proceeds to + /// the next operation in the block, unless a trap or error occurred to stop evaluation. + /// * For unstructured control flow branches, evaluation proceeds to the first operation in + /// the given destination block, and any provided successor arguments are written to the + /// registers of the current frame using the block parameter value ids. + /// * For return-like operations, the call frame for the containing callable operation is + /// popped from the call stack, and one of two paths is taken: + /// 1. If the top of the call stack has been reached, evaluation terminates and the given + /// return values are returned as the result of `eval_region` itself. + /// 2. Otherwise, control is transferred to the next operation after the caller op, by + /// updating the next region, block, and op, and continuing the loop there. The return + /// values are stored in the registers of the caller's frame as the results of the call. + /// * For yield-like operations: + /// 1. If the yield is returning to the parent operation, then much like return-like ops, + /// control is tranferred to the next operation after the parent op, and the yielded values + /// are stored in the registers of the current frame as the results of the parent op. + /// 2. If the yield is entering another region, then the yielded values are written to the + /// registers of the current frame using the value ids of the destination region's entry + /// block arguments. Control is transferred by updating the next region of the interpreter + /// loop, and resuming that loop, which will then start evaluating operations in the + /// entry block of that region. + /// * Lastly, call operations push a new frame on the call stack, and its registers are + /// initialized with the provided arguments, under the entry block arguments of the callable + /// region. Control is transferred by updating the next region of the interpreter loop, and + /// resuming that loop, which will then start evaluating operations in the entry block of + /// the callable region. + fn eval_region( + &mut self, + op: &Operation, + region: RegionRef, + ) -> Result, Report> { + log::debug!(target: "eval", "evaluating {} of {}", RegionBranchPoint::Child(region), op); + + self.ip = Some(op.as_operation_ref()); + + let mut next_region = Some(region); + 'region: while let Some(mut region) = next_region.take() { + let mut next_block = region.borrow().entry_block_ref(); + 'block: while let Some(mut block) = next_block.take() { + let mut next_op = block.borrow().body().front().as_pointer(); + 'op: while let Some(op) = next_op.take() { + next_op = op.next(); + let op = op.borrow(); + match self.eval_op(&op)? { + ControlFlowEffect::None => continue, + ControlFlowEffect::Trap { span, reason } => { + return Err(self + .error("evaluation failed") + .with_primary_label( + op.span(), + "execution trapped due to this operation", + ) + .with_secondary_label(span, reason) + .into_report()); + } + ControlFlowEffect::Return(returned) => { + // If this is the end of the call stack, we're returning from the + // top-level operation + let is_final_return = self.call_stack.len() == 1; + let frame = self.call_stack.pop().unwrap(); + + // Set up the resumption point if we're resuming execution after the + // caller + if !is_final_return { + // Restore the instruction pointer to the point where control was + // transferred to the callee + let caller_block = frame.caller_block().unwrap(); + next_op = frame.return_to(); + // NOTE: We change `block` here, rather than updating `next_block`, + // because we're resuming control with `next_op`, which doesn't + // revisit the outer `'block` loop. + block = caller_block; + next_region = caller_block.parent(); + } + + // Verify the results that were returned + let callee = frame.callee(); + let callable = callee.as_trait::().unwrap(); + let signature = callable.signature(); + let call = frame.caller(); + let results_returned = returned.is_some() as usize; + let results_expected = signature.results().len(); + if let Some(call) = call.as_ref() { + assert_eq!( + call.num_results(), + results_expected, + "expected to have caught call/callee signature mismatch \ + during verification" + ); + } + if results_returned != results_expected { + return Err(self + .error("evaluation failed") + .with_primary_label( + callee.span(), + format!( + "callee returned {results_returned} results, but \ + {results_expected} were expected" + ), + ) + .with_secondary_label( + callee.span(), + "this callable returned incorrect number of results", + ) + .into_report()); + } + if let Some(return_ty) = returned.as_ref().map(|v| v.ty()) { + let expected_ty = &signature.results[0].ty; + if &return_ty != expected_ty { + return Err(self + .error("evaluation failed") + .with_primary_label( + callee.span(), + format!( + "callee returned result type that does not match \ + its signature, got {return_ty} but signature \ + requires {expected_ty}" + ), + ) + .with_secondary_label( + callee.span(), + "this callable returned a value that does not match \ + its signature", + ) + .into_report()); + } + } + + if is_final_return { + // We're done executing the top-level operation, return to the + // evaluator directly to terminate. + return Ok(SmallVec::from_iter(returned)); + } else if let Some(value) = returned { + // Make sure we bind the result values of the call op before + // resuming execution after the call + let call = call.unwrap(); + let result = call.results()[0] as ValueRef; + self.set_value(result, value); + } + + // Return to after the call + continue 'op; + } + ControlFlowEffect::Jump(successor) => { + let dest = successor.successor(); + + // Check that arguments match successor block signature + let arguments = successor.successor_operands(); + let block = dest.borrow(); + if block.num_arguments() != arguments.len() { + return Err(self + .error("evaluation failed") + .with_primary_label( + op.span(), + format!( + "attempted to branch to {dest} with {} arguments, but \ + {} were expected", + block.num_arguments(), + arguments.len() + ), + ) + .into_report()); + } + for (param, arg) in block.arguments().iter().zip(arguments.iter()) { + let expected = param.borrow(); + let expected_ty = expected.ty(); + let given = arg.borrow(); + let given_ty = given.ty(); + if expected_ty != given_ty { + return Err(self + .error("evaluation failed") + .with_primary_label( + op.span(), + format!( + "attempted to branch to {dest} with mismatched \ + argument types" + ), + ) + .with_secondary_label( + expected.span(), + format!("expected {expected_ty}, got {given_ty}"), + ) + .into_report()); + } + let value = self.get_value(&arg)?; + self.set_value(*param as ValueRef, value); + } + + // Jump + next_block = Some(dest); + continue 'block; + } + ControlFlowEffect::Yield { + successor: RegionBranchPoint::Parent, + arguments, + } => { + // We're returning to the parent operation from a child region + // + // Check that arguments match parent op results + let parent = region.parent().unwrap(); + let parent_op = parent.borrow(); + if parent_op.num_results() != arguments.len() { + return Err(self + .error("evaluation failed") + .with_primary_label( + op.span(), + format!( + "attempted to yield to parent with {} arguments, but \ + {} were expected", + parent_op.num_results(), + arguments.len() + ), + ) + .with_secondary_label( + parent_op.span(), + "this is the parent operation", + ) + .into_report()); + } + log::debug!(target: "eval", " <= {}", + DisplayValues::new(parent_op.results().iter().zip(arguments.iter()).map(|(result, arg)| { + MaterializedValue { + id: *result as ValueRef, + value: self.get_value(&arg).unwrap(), + } + }))); + for (result, arg) in parent_op.results().iter().zip(arguments) { + let expected = result.borrow(); + let expected_ty = expected.ty(); + let given = arg.borrow(); + let given_ty = given.ty(); + if expected_ty != given_ty { + return Err(self + .error("evaluation failed") + .with_primary_label( + op.span(), + "attempted to yield to parent with mismatched \ + argument types", + ) + .with_secondary_label( + expected.span(), + format!("expected {expected_ty}, got {given_ty}"), + ) + .into_report()); + } + + let value = self.get_value(&arg)?; + self.set_value(*result as ValueRef, value); + } + + // Yield + next_op = parent.next(); + next_block = parent.parent(); + if let Some(parent_region) = next_block.and_then(|block| block.parent()) + { + region = parent_region; + next_region = Some(parent_region); + } else { + next_region = None; + } + + // If we're yielding from a standalone op, terminate evaluation + if next_op.is_none() { + break 'region; + } + continue 'op; + } + ControlFlowEffect::Yield { + successor: RegionBranchPoint::Child(successor), + arguments, + } => { + // Check that arguments match successor region entry block signature + let successor_region = successor.borrow(); + let dest = successor_region.entry(); + if dest.num_arguments() != arguments.len() { + return Err(self + .error("evaluation failed") + .with_primary_label( + op.span(), + format!( + "attempted to yield to {dest} with {} arguments, but \ + {} were expected", + dest.num_arguments(), + arguments.len() + ), + ) + .into_report()); + } + for (param, arg) in dest.arguments().iter().zip(arguments.iter()) { + let expected = param.borrow(); + let expected_ty = expected.ty(); + let given = arg.borrow(); + let given_ty = given.ty(); + if expected_ty != given_ty { + return Err(self + .error("evaluation failed") + .with_primary_label( + op.span(), + format!( + "attempted to yield to {dest} with mismatched \ + argument types" + ), + ) + .with_secondary_label( + expected.span(), + format!("expected {expected_ty}, got {given_ty}"), + ) + .into_report()); + } + + let value = self.get_value(&arg)?; + self.set_value(*param as ValueRef, value); + } + + // Yield + next_region = Some(successor); + continue 'region; + } + ControlFlowEffect::Call { callee, arguments } => { + let callable_region = self.prepare_call(&op, callee, arguments)?; + // Yield control to the callee + next_region = Some(callable_region); + continue 'region; + } + } + } + + return Err(self.report( + "evaluation failed", + block.grandparent().unwrap().span(), + format!( + "execution reached end of {block}, but no terminating control flow \ + effects were emitted" + ), + )); + } + } + + self.ip = Some(op.as_operation_ref()); + + // Obtain any results this operation produced + let mut results = SmallVec::with_capacity(op.num_results()); + let current_frame = self.current_frame(); + for result in ValueRange::<2>::from(op.results().all()) { + if let Some(value) = current_frame.try_get_value(&result) { + results.push(value); + continue; + } + return Err(self + .error("evaluation invariant violated") + .with_primary_label( + self.current_span(), + format!("{result} was not properly set in the evaluator state"), + ) + .with_help(format!( + "the implementation of Eval for '{}' is not updating the register state for \ + its results", + op.name() + )) + .into_report()); + } + + Ok(results) + } + + /// Evaluate `op` and verify it's results (if applicable). + /// + /// This is intended to be called from `eval_region`. See `eval_op_and_gather_results` if you + /// want something to evaluate a single operation and handle its control flow effects at the + /// same time. + fn eval_op(&mut self, op: &Operation) -> Result { + self.ip = Some(op.as_operation_ref()); + + // Ensure the op is evaluatable + let Some(evaluatable) = op.as_trait::() else { + return Err(self.report( + "evaluation failed", + self.current_span(), + format!("'{}' does not implement Eval", op.name()), + )); + }; + + log::debug!(target: "eval", "evaluating: {} {}", op.name(), DisplayValues::new(ValueRange::<2>::from(op.operands().all()).into_iter().map(|v| MaterializedValue { + id: v, + value: self.get_value(&v).unwrap(), + }))); + + // Evaluate it + let effect = evaluatable.eval(self)?; + + // Do not check results if control flow effect does not support results + match effect { + effect @ (ControlFlowEffect::Jump(_) + | ControlFlowEffect::Trap { .. } + | ControlFlowEffect::Call { .. } + | ControlFlowEffect::Yield { + successor: RegionBranchPoint::Parent, + .. + }) => return Ok(effect), + ControlFlowEffect::Yield { + successor, + ref arguments, + .. + } => { + log::debug!(target: "eval", " => {successor} {}", + DisplayValues::new(arguments.iter().map(|v| { + MaterializedValue { + id: v, + value: self.get_value(&v).unwrap(), + } + }))); + return Ok(effect); + } + ControlFlowEffect::Return(returning) => { + match returning { + Some(value) => log::debug!(target: "eval", " <= {value}"), + None => log::debug!(target: "eval", " <= ()"), + } + return Ok(effect); + } + ControlFlowEffect::None => (), + } + + // Obtain any results this operation produced + let current_frame = self.current_frame(); + log::debug!(target: "eval", " <= {}", DisplayValues::new(ValueRange::<2>::from(op.results().all()).into_iter().map(|v| { + MaterializedValue { + id: v, + value: self.get_value(&v).unwrap(), + } + }))); + for result in ValueRange::<2>::from(op.results().all()) { + if current_frame.is_defined(&result) { + continue; + } + return Err(self + .error("evaluation invariant violated") + .with_primary_label( + self.current_span(), + format!("{result} was not properly set in the evaluator state"), + ) + .with_help(format!( + "the implementation of Eval for '{}' is not updating the register state for \ + its results", + op.name() + )) + .into_report()); + } + + Ok(effect) + } + + /// This helper is designed to evaluate a single operation and return any results it produces. + /// + /// For control flow operations that enter another block or region, no results are gathered. + /// For control flow operations that return from a region, the successor arguments are gathered + /// as results. + /// + /// If the op being evaluated represents a call to a callable operation, the callable will be + /// evaluated, and the results gathered for the call will be determined by the control flow + /// effect produced by the call. + fn eval_op_and_gather_results( + &mut self, + op: &Operation, + ) -> Result, Report> { + match self.eval_op(op)? { + ControlFlowEffect::None => { + // Obtain any results this operation produced + let current_frame = self.current_frame(); + Ok(ValueRange::<2>::from(op.results().all()) + .into_iter() + .map(|result| current_frame.get_value_unchecked(&result)) + .collect()) + } + ControlFlowEffect::Jump(_) => Ok(smallvec![]), + ControlFlowEffect::Trap { span, reason } => Err(self + .error("evaluation failed") + .with_primary_label(op.span(), "execution trapped due to this operation") + .with_secondary_label(span, reason) + .into_report()), + ControlFlowEffect::Return(value) => Ok(SmallVec::from_iter(value)), + ControlFlowEffect::Call { callee, arguments } => { + let callable_region = self.prepare_call(op, callee, arguments)?; + return self.eval_region(&callee.borrow(), callable_region); + } + ControlFlowEffect::Yield { + successor, + arguments, + } => match successor { + RegionBranchPoint::Parent => { + let current_frame = self.current_frame(); + Ok(arguments + .into_iter() + .map(|result| current_frame.get_value_unchecked(&result)) + .collect()) + } + RegionBranchPoint::Child(region) => self.eval_region(op, region), + }, + } + } + + /// Validate a call to `callee` with `arguments`, and prepare the evaluator for execution of + /// the callable region. + /// + /// If successful, returns the callable region to evaluate, otherwise returns `Err`. + fn prepare_call( + &mut self, + caller: &Operation, + callee: OperationRef, + arguments: ValueRange<'static, 4>, + ) -> Result { + let callee_op = callee.borrow(); + let Some(callable) = callee_op.as_trait::() else { + return Err(self + .error("evaluation failed") + .with_primary_label(caller.span(), "invalid callee") + .with_secondary_label( + callee_op.span(), + "this operation does not implement CallableOpInterface", + ) + .into_report()); + }; + + let Some(callable_region) = callable.get_callable_region() else { + return Err(self + .error("evaluation failed") + .with_primary_label(caller.span(), "invalid callee") + .with_secondary_label( + callee_op.span(), + "there is no definition for this callable, only this declaration", + ) + .into_report()); + }; + + let signature = callable.signature(); + if arguments.len() != signature.arity() { + return Err(self + .error("evaluation failed") + .with_primary_label(caller.span(), "invalid number of arguments for callee") + .with_secondary_label( + callee_op.span(), + format!( + "this callable expected {} arguments, got {}", + signature.arity(), + arguments.len() + ), + ) + .into_report()); + } + + let mut frame = CallFrame::new(callee).with_caller(caller.as_operation_ref()); + + for (index, (param, arg)) in signature.params().iter().zip(arguments).enumerate() { + let argument = arg.borrow(); + let expected_ty = ¶m.ty; + let given_ty = argument.ty(); + let callable_region = callable_region.borrow(); + let param_value = callable_region.entry().arguments()[index]; + if given_ty != expected_ty { + return Err(self + .error("evaluation failed") + .with_primary_label(caller.span(), "invalid argument for callee") + .with_secondary_label( + callee_op.span(), + "argument types do not match this signature", + ) + .with_secondary_label( + param_value.span(), + format!("expected value of type {expected_ty}, got {given_ty}"), + ) + .into_report()); + } else { + let value = self.get_value(&arg)?; + frame.set_value(param_value as ValueRef, value); + } + } + + // Push new call frame + self.call_stack.push(frame); + + Ok(callable_region) + } +} diff --git a/eval/src/evaluator/context.rs b/eval/src/evaluator/context.rs new file mode 100644 index 000000000..a7bd8bffe --- /dev/null +++ b/eval/src/evaluator/context.rs @@ -0,0 +1,122 @@ +use alloc::vec::Vec; + +use midenc_hir::{dialects::builtin::ComponentId, Report, SourceSpan, Type}; +use midenc_session::diagnostics::WrapErr; + +use super::memory::{self, ReadFailed, WriteFailed}; +use crate::Value; + +const PAGE_SIZE: usize = 64 * 1024; +const MAX_ADDRESSABLE_HEAP: usize = 2usize.pow(30) - 1; + +/// The execution context associated with Miden context boundaries +pub struct ExecutionContext { + /// The identifier for this context, if known + /// + /// The root context never has an identifier + #[allow(unused)] + id: Option, + /// Heap memory + memory: Vec, +} + +impl ExecutionContext { + pub fn new(id: ComponentId) -> Self { + Self { + id: Some(id), + ..Default::default() + } + } + + /// Grow the heap of this context to be at least `n` pages + pub fn memory_grow(&mut self, n: usize) { + assert!(((n * PAGE_SIZE) as u32) < u32::MAX, "cannot grow heap larger than u32::MAX"); + self.memory.resize(n * PAGE_SIZE, 0); + } + + /// Return the size of this context's heap in pages + pub fn memory_size(&self) -> usize { + self.memory.len() / PAGE_SIZE + } + + /// Reset the memory of this context to its initial state + pub fn reset(&mut self) { + self.memory.truncate(0); + self.memory.resize(4 * PAGE_SIZE, 0); + } + + /// Read a value of type `ty` from `addr` + /// + /// Returns an error if `addr` is invalid, `ty` is not a valid immediate type, or the specified + /// type could not be read from `addr` (either the encoding is invalid, or the read would be + /// out of bounds). + pub fn read_memory(&self, addr: u32, ty: &Type, at: SourceSpan) -> Result { + let addr = addr as usize; + if addr > MAX_ADDRESSABLE_HEAP { + return Err(ReadFailed::AddressOutOfBounds { + addr: addr as u32, + at, + }) + .wrap_err("invalid memory read"); + } + + let size = ty.size_in_bytes(); + let end_addr = addr.checked_add(size); + if end_addr.is_none_or(|addr| addr > MAX_ADDRESSABLE_HEAP) { + return Err(ReadFailed::SizeOutOfBounds { + addr: addr as u32, + size: size as u32, + at, + }) + .wrap_err("invalid memory read"); + } + + memory::read_value(addr, ty, &self.memory).wrap_err("invalid memory read") + } + + /// Write `value` to `addr` in heap memory. + /// + /// Returns an error if `addr` is invalid, or `value` could not be written to `addr` (either the + /// value is poison, or the write would go out of bounds). + pub fn write_memory( + &mut self, + addr: u32, + value: impl Into, + at: SourceSpan, + ) -> Result<(), Report> { + let addr = addr as usize; + if addr > MAX_ADDRESSABLE_HEAP { + return Err(WriteFailed::AddressOutOfBounds { + addr: addr as u32, + at, + }) + .wrap_err("invalid memory write"); + } + + let value = value.into(); + let ty = value.ty(); + let size = ty.size_in_bytes(); + let end_addr = addr.checked_add(size); + if end_addr.is_none_or(|addr| addr > MAX_ADDRESSABLE_HEAP) { + return Err(WriteFailed::SizeOutOfBounds { + addr: addr as u32, + size: size as u32, + at, + }) + .wrap_err("invalid memory write"); + } + + memory::write_value(addr, value, &mut self.memory); + + Ok(()) + } +} + +impl Default for ExecutionContext { + fn default() -> Self { + Self { + id: None, + memory: Vec::with_capacity(4 * PAGE_SIZE), + } + } +} diff --git a/eval/src/evaluator/frame.rs b/eval/src/evaluator/frame.rs new file mode 100644 index 000000000..d9dff2d70 --- /dev/null +++ b/eval/src/evaluator/frame.rs @@ -0,0 +1,229 @@ +use alloc::{format, vec}; + +use midenc_hir::{ + dialects::builtin::{self, LocalVariable}, + formatter::DisplayHex, + BlockRef, Context, EntityRef, Felt, FxHashMap, Immediate, Operation, OperationRef, Report, + SmallVec, SourceSpan, SymbolPath, ValueId, ValueRef, +}; +use midenc_session::diagnostics::{miette, Diagnostic, Severity, WrapErr}; + +use super::memory; +use crate::Value; + +#[derive(Debug, thiserror::Error, Diagnostic)] +#[error("evaluation failed: concrete use of poison value {value}")] +#[diagnostic()] +pub struct InvalidPoisonUseError { + pub value: ValueId, + #[label(primary)] + pub at: SourceSpan, + #[label("poison originally produced here")] + pub origin: SourceSpan, +} + +#[derive(Debug, thiserror::Error, Diagnostic)] +#[error("evaluation failed: {value} is undefined")] +#[diagnostic()] +pub struct UndefinedValueError { + pub value: ValueId, + #[label()] + pub at: SourceSpan, +} + +/// Information about the current symbol being executed +pub struct CallFrame { + /// The callee corresponding to this frame + callee: OperationRef, + /// The operation that called `callee`, if called by an operation, not the evaluator itself + caller: Option, + /// Virtual registers used to map SSA values to their runtime value + registers: FxHashMap, + /// Function-local memory reserved as scratch space for local variables + locals: SmallVec<[u8; 64]>, +} + +impl CallFrame { + pub fn new(callee: OperationRef) -> Self { + let callee_op = callee.borrow(); + let locals = match callee_op.downcast_ref::() { + Some(function) => { + let capacity = function.num_locals() * core::mem::size_of::(); + let mut buf = SmallVec::with_capacity(capacity); + buf.resize(capacity, 0); + buf + } + None => Default::default(), + }; + + Self { + callee, + caller: None, + registers: Default::default(), + locals, + } + } + + pub fn with_caller(mut self, caller: OperationRef) -> Self { + self.caller = Some(caller); + self + } + + pub fn caller(&self) -> Option> { + self.caller.as_ref().map(|caller| caller.borrow()) + } + + pub fn return_to(&self) -> Option { + self.caller.as_ref().and_then(|caller| caller.next()) + } + + pub fn caller_block(&self) -> Option { + self.caller.as_ref().and_then(|caller| caller.parent()) + } + + pub fn callee(&self) -> EntityRef<'_, Operation> { + self.callee.borrow() + } + + pub fn symbol_path(&self) -> Option { + self.callee.borrow().as_symbol().map(|symbol| symbol.path()) + } + + pub fn is_defined(&self, value: &ValueRef) -> bool { + self.registers.contains_key(value) + } + + pub fn try_get_value(&self, value: &ValueRef) -> Option { + self.registers.get(value).copied() + } + + pub fn get_value(&self, value: &ValueRef, at: SourceSpan) -> Result { + self.registers.get(value).copied().ok_or_else(|| { + Report::new(UndefinedValueError { + value: value.borrow().id(), + at, + }) + }) + } + + #[inline(always)] + #[track_caller] + pub fn get_value_unchecked(&self, value: &ValueRef) -> Value { + self.registers[value] + } + + pub fn use_value(&self, value: &ValueRef, at: SourceSpan) -> Result { + match self.get_value(value, at)? { + Value::Poison { origin, .. } => Err(Report::new(InvalidPoisonUseError { + value: value.borrow().id(), + at, + origin, + })), + Value::Immediate(imm) => Ok(imm), + } + } + + pub fn set_value(&mut self, id: ValueRef, value: impl Into) { + self.registers.insert(id, value.into()); + } + + /// Read the value of the given local variable + /// + /// Returns an error if `local` is invalid, or a value of the defined type could not be read + /// from it (e.g. the encoding is not valid for the type). + pub fn read_local( + &self, + local: &LocalVariable, + span: SourceSpan, + context: &Context, + ) -> Result { + let offset = local.absolute_offset() * core::mem::size_of::(); + let ty = local.ty(); + let size = ty.size_in_bytes(); + if offset >= self.locals.len() || (offset + size) >= self.locals.len() { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid read of local variable") + .with_primary_label( + span, + format!( + "attempted to read value of size {size} from offset {offset}, but only {} \ + are allocated", + self.locals.len() + ), + ) + .into_report()); + } + + memory::read_value(offset, &ty, &self.locals).wrap_err("invalid memory read") + } + + /// Write `value` to `local`. + /// + /// Returns an error if `local` is invalid, or `value` could not be written to `local` (e.g. + /// the write would go out of bounds, or `value` is not a valid instance of the type associated + /// with `local`). + pub fn write_local( + &mut self, + local: &LocalVariable, + value: Value, + span: SourceSpan, + context: &Context, + ) -> Result<(), Report> { + let offset = local.absolute_offset() * core::mem::size_of::(); + let ty = local.ty(); + let size = ty.size_in_bytes(); + if offset >= self.locals.len() || (offset + size) >= self.locals.len() { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid write of local variable") + .with_primary_label( + span, + format!( + "attempted to write value of size {size} to offset {offset}, but only {} \ + are allocated", + self.locals.len() + ), + ) + .into_report()); + } + + memory::write_value(offset, value, &mut self.locals); + + Ok(()) + } +} + +impl core::fmt::Debug for CallFrame { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("CallFrame") + .field_with("callee", |f| match self.symbol_path() { + Some(path) => write!(f, "{path}"), + None => f.write_str(""), + }) + .field_with("caller", |f| match self.caller { + Some(caller) => write!(f, "{}", caller.borrow()), + None => f.write_str(""), + }) + .field_with("registers", |f| { + let mut builder = f.debug_map(); + for (k, v) in self.registers.iter() { + builder.key(k).value_with(|f| write!(f, "{v}")).finish()?; + } + builder.finish() + }) + .field_with("locals", |f| write!(f, "{:0x}", DisplayHex::new(&self.locals))) + .finish() + } +} + +impl core::fmt::Display for CallFrame { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.symbol_path() { + Some(path) => write!(f, "{path}"), + None => f.write_str(""), + } + } +} diff --git a/eval/src/evaluator/memory.rs b/eval/src/evaluator/memory.rs new file mode 100644 index 000000000..b74e0f83a --- /dev/null +++ b/eval/src/evaluator/memory.rs @@ -0,0 +1,266 @@ +use alloc::{format, string::String, vec, vec::Vec}; +use core::ops::{Index, IndexMut, Range}; + +use midenc_hir::{Felt, FieldElement, Immediate, SmallVec, SourceSpan, Type}; +use midenc_session::{ + diagnostics::{miette, Diagnostic}, + miden_assembly::utils::Deserializable, +}; + +use crate::Value; + +/// An error occurred while reading a value from memory +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum ReadFailed { + #[error("attempted to read memory beyond addressable heap at {addr}")] + #[diagnostic()] + AddressOutOfBounds { + addr: u32, + #[label] + at: SourceSpan, + }, + #[error("attempted to read value of size {size} beyond addressable heap at {addr}")] + #[diagnostic()] + SizeOutOfBounds { + addr: u32, + size: u32, + #[label] + at: SourceSpan, + }, + #[error("unsupported type")] + #[diagnostic()] + UnsupportedType, + #[error("invalid field element: {0}")] + #[diagnostic()] + InvalidFelt(String), +} + +/// An error occurred while writing a value to memory +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum WriteFailed { + #[error("attempted to write memory beyond addressable heap at {addr}")] + #[diagnostic()] + AddressOutOfBounds { + addr: u32, + #[label] + at: SourceSpan, + }, + #[error("attempted to write value of size {size} beyond addressable heap at {addr}")] + #[diagnostic()] + SizeOutOfBounds { + addr: u32, + size: u32, + #[label] + at: SourceSpan, + }, +} + +/// Read a value of type `ty`, starting from offset `addr` in `memory` +/// +/// This operation can fail if `ty` is not a supported immediate type, or if the bytes in memory +/// are not valid for that type. +/// +/// NOTE: If `memory` is smaller than implied by `addr`, it is presumed to be zeroed. +pub fn read_value(addr: usize, ty: &Type, memory: &[u8]) -> Result { + let imm = match ty { + Type::I1 => { + let byte = read_byte(addr, memory); + Immediate::I1((byte & 0x1) == 1) + } + Type::I8 => { + let value = read_byte(addr, memory) as i8; + Immediate::I8(value) + } + Type::U8 => { + let value = read_byte(addr, memory); + Immediate::U8(value) + } + Type::I16 => { + let value = i16::from_be_bytes(read_bytes(addr, memory)); + Immediate::I16(value) + } + Type::U16 => { + let value = u16::from_be_bytes(read_bytes(addr, memory)); + Immediate::U16(value) + } + Type::I32 => { + let value = i32::from_be_bytes(read_bytes(addr, memory)); + Immediate::I32(value) + } + Type::U32 => { + let value = u32::from_be_bytes(read_bytes(addr, memory)); + Immediate::U32(value) + } + Type::I64 => { + let value = i64::from_be_bytes(read_bytes(addr, memory)); + Immediate::I64(value) + } + Type::U64 => { + let value = u64::from_be_bytes(read_bytes(addr, memory)); + Immediate::U64(value) + } + Type::I128 => { + let value = i128::from_be_bytes(read_bytes(addr, memory)); + Immediate::I128(value) + } + Type::U128 => { + let value = u128::from_be_bytes(read_bytes(addr, memory)); + Immediate::U128(value) + } + Type::F64 => { + let value = f64::from_be_bytes(read_bytes(addr, memory)); + Immediate::F64(value) + } + Type::Felt => { + const FELT_SIZE: usize = Felt::ELEMENT_BYTES; + let bytes = read_bytes::(addr, memory); + Felt::read_from_bytes(&bytes).map(Immediate::Felt).map_err(|err| { + ReadFailed::InvalidFelt(format!("failed to decode felt at {addr}: {err}")) + })? + } + Type::Ptr(_) => { + let value = u32::from_be_bytes(read_bytes(addr, memory)); + Immediate::U32(value) + } + _ => { + return Err(ReadFailed::UnsupportedType); + } + }; + + Ok(Value::Immediate(imm)) +} + +/// Read a single byte from `addr` in `memory`. +/// +/// Returns a zero byte if `addr` is not in bounds of `memory`. +#[inline] +pub fn read_byte(addr: usize, memory: &[u8]) -> u8 { + memory.get(addr).copied().unwrap_or_default() +} + +/// Read `N` bytes starting from `addr` in `memory`. +/// +/// Any bytes that are out of bounds of `memory` are presumed to be zeroed. +pub fn read_bytes(addr: usize, memory: &[u8]) -> [u8; N] { + match memory.get(addr..(addr + N)) { + Some(bytes) => <[u8; N]>::try_from(bytes).unwrap(), + None if memory.len() <= addr => { + // No memory at `addr` has been written yet, return all zeros + [0; N] + } + None => { + // Some bytes are available, but not all, read them individually + let mut buf = [0; N]; + for (byte, addr) in (addr..(addr + N)).enumerate() { + buf[byte] = read_byte(addr, memory); + } + buf + } + } +} + +/// This trait exists so as to abstract over the buffer type being used to represent memory. +/// +/// For now, it is implemented for `Vec` and `SmallVec`. +pub trait Buffer: + Index + + Index, Output = [u8]> + + IndexMut + + IndexMut, Output = [u8]> +{ + fn get_mut(&mut self, index: usize) -> Option<&mut u8>; + fn get_slice_mut(&mut self, index: Range) -> Option<&mut [u8]>; + fn resize(&mut self, len: usize, value: u8); +} + +impl Buffer for Vec { + #[inline(always)] + fn get_mut(&mut self, index: usize) -> Option<&mut u8> { + self.as_mut_slice().get_mut(index) + } + + #[inline(always)] + fn get_slice_mut(&mut self, index: Range) -> Option<&mut [u8]> { + self.as_mut_slice().get_mut(index) + } + + #[inline(always)] + fn resize(&mut self, len: usize, value: u8) { + self.resize(len, value) + } +} + +impl Buffer for SmallVec<[u8; N]> { + #[inline(always)] + fn get_mut(&mut self, index: usize) -> Option<&mut u8> { + self.as_mut_slice().get_mut(index) + } + + #[inline(always)] + fn get_slice_mut(&mut self, index: Range) -> Option<&mut [u8]> { + self.as_mut_slice().get_mut(index) + } + + #[inline(always)] + fn resize(&mut self, len: usize, value: u8) { + self.resize(len, value) + } +} + +/// Write `value` to `memory` starting at offset `addr`. +/// +/// If `addr`, or the resulting write, would go out of bounds of `memory`, it is resized such that +/// there is sufficient space for the write, i.e. a write never fails unless allocating the +/// underlying storage would fail. +pub fn write_value(addr: usize, value: Value, memory: &mut B) { + let imm = match value { + Value::Poison { value, .. } | Value::Immediate(value) => value, + }; + + match imm { + Immediate::I1(value) => write_byte(addr, value as u8, memory), + Immediate::I8(value) => write_byte(addr, value as u8, memory), + Immediate::U8(value) => write_byte(addr, value, memory), + Immediate::I16(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::U16(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::I32(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::U32(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::I64(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::U64(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::I128(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::U128(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::F64(value) => write_bytes(addr, &value.to_be_bytes(), memory), + Immediate::Felt(value) => write_bytes(addr, Felt::elements_as_bytes(&[value]), memory), + } +} + +/// Write `byte` to `memory` at offset `addr`. +/// +/// If `addr` is out of bounds of `memory`, it is resized such that there is sufficient space for +/// the write, i.e. a write never fails unless allocating the underlying storage would fail. +pub fn write_byte(addr: usize, byte: u8, memory: &mut B) { + match memory.get_mut(addr) { + Some(slot) => *slot = byte, + None => { + memory.resize(addr + 8, 0); + memory[addr] = byte; + } + } +} + +/// Write `bytes` to `memory` at offset `addr`. +/// +/// If `addr`, or the resulting write, would go out of bounds of `memory`, it is resized such that +/// there is sufficient space for the write, i.e. a write never fails unless allocating the +/// underlying storage would fail. +pub fn write_bytes(addr: usize, bytes: &[u8], memory: &mut B) { + match memory.get_slice_mut(addr..(addr + bytes.len())) { + Some(target_bytes) => { + target_bytes.copy_from_slice(bytes); + } + None => { + memory.resize(addr + bytes.len() + 8, 0); + memory[addr..(addr + bytes.len())].copy_from_slice(bytes); + } + } +} diff --git a/eval/src/lib.rs b/eval/src/lib.rs new file mode 100644 index 000000000..bb8313c91 --- /dev/null +++ b/eval/src/lib.rs @@ -0,0 +1,121 @@ +#![no_std] +#![feature(debug_closure_helpers)] +#![deny(warnings)] + +#[cfg(any(feature = "std", test))] +extern crate std; + +extern crate alloc; + +mod eval; +mod evaluator; +#[cfg(test)] +mod tests; +mod value; + +pub use self::{ + eval::{ControlFlowEffect, Eval, Initialize}, + evaluator::HirEvaluator, + value::Value, +}; + +pub fn register_dialect_hooks(context: &midenc_hir::Context) { + use midenc_dialect_arith as arith; + use midenc_dialect_cf as cf; + use midenc_dialect_hir as hir; + use midenc_dialect_scf as scf; + use midenc_dialect_ub as ub; + use midenc_hir::dialects::builtin; + + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + }); + context.register_dialect_hook::(|info, _context| { + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + //info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + info.register_operation_trait::(); + }); +} diff --git a/eval/src/tests.rs b/eval/src/tests.rs new file mode 100644 index 000000000..845e380fd --- /dev/null +++ b/eval/src/tests.rs @@ -0,0 +1,188 @@ +use alloc::rc::Rc; + +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_dialect_scf::StructuredControlFlowOpBuilder; +use midenc_hir::{ + dialects::builtin::{BuiltinOpBuilder, FunctionBuilder}, + AbiParam, Builder, Context, Ident, Op, OpBuilder, ProgramPoint, Report, Signature, SourceSpan, + SymbolTable, Type, ValueRef, +}; + +use crate::*; + +struct TestContext { + context: Rc, + evaluator: HirEvaluator, +} + +fn setup() -> TestContext { + let context = Rc::new(Context::default()); + register_dialect_hooks(&context); + let evaluator = HirEvaluator::new(context.clone()); + + TestContext { context, evaluator } +} + +/// Test that we can evaluate a standalone operation, not just callables +/// +/// This verifies ControlFlowEffect::None and ControlFlowEffect::Yield. +#[test] +fn eval_test() -> Result<(), Report> { + let mut test_context = setup(); + + let mut builder = OpBuilder::new(test_context.context.clone()); + + let op = { + let block = builder.context().create_block_with_params([Type::I1]); + let cond = block.borrow().arguments()[0] as ValueRef; + let conditional = builder.r#if(cond, &[Type::U32], SourceSpan::default())?; + + let then_region = conditional.borrow().then_body().as_region_ref(); + builder.create_block(then_region, None, &[]); + let is_true = builder.u32(1, SourceSpan::default()); + builder.r#yield([is_true], SourceSpan::default())?; + + let else_region = conditional.borrow().else_body().as_region_ref(); + builder.create_block(else_region, None, &[]); + let is_false = builder.u32(0, SourceSpan::default()); + builder.r#yield([is_false], SourceSpan::default())?; + conditional.as_operation_ref() + }; + + let op = op.borrow(); + let results = test_context.evaluator.eval(&op, [true.into()])?; + assert_eq!(results.len(), 1); + assert_eq!(results[0], Value::Immediate(1u32.into())); + + let results = test_context.evaluator.eval(&op, [false.into()])?; + assert_eq!(results.len(), 1); + assert_eq!(results[0], Value::Immediate(0u32.into())); + + Ok(()) +} + +/// Test evaluation of a callable operation +/// +/// This verifies the interaction between ControlFlowEffect::Yield and ControlFlowEffect::Return +#[test] +fn eval_callable_test() -> Result<(), Report> { + let mut test_context = setup(); + + let mut builder = OpBuilder::new(test_context.context.clone()); + + let function = builder.create_function( + Ident::with_empty_span("test".into()), + Signature::new([AbiParam::new(Type::I1)], [AbiParam::new(Type::U32)]), + )?; + + { + let mut builder = FunctionBuilder::new(function, &mut builder); + let cond = builder.current_block().borrow().arguments()[0] as ValueRef; + let conditional = builder.r#if(cond, &[Type::U32], SourceSpan::default())?; + let result = conditional.borrow().results()[0] as ValueRef; + builder.ret(Some(result), SourceSpan::default())?; + + let then_region = conditional.borrow().then_body().as_region_ref(); + let then_block = builder.create_block_in_region(then_region); + builder.switch_to_block(then_block); + let is_true = builder.u32(1, SourceSpan::default()); + builder.r#yield([is_true], SourceSpan::default())?; + + let else_region = conditional.borrow().else_body().as_region_ref(); + let else_block = builder.create_block_in_region(else_region); + builder.switch_to_block(else_block); + let is_false = builder.u32(0, SourceSpan::default()); + builder.r#yield([is_false], SourceSpan::default())?; + } + + let callable = function.borrow(); + let results = test_context.evaluator.eval_callable(&*callable, [true.into()])?; + assert_eq!(results.len(), 1); + assert_eq!(results[0], Value::Immediate(1u32.into())); + + let results = test_context.evaluator.eval_callable(&*callable, [false.into()])?; + assert_eq!(results.len(), 1); + assert_eq!(results[0], Value::Immediate(0u32.into())); + + Ok(()) +} + +/// Test evaluation of a callable that calls another callable. +/// +/// This verifies the handling of ControlFlowEffect::Call and ControlFlowEffect::Return, and their +/// interaction with ControlFlowEffect::Yield +#[test] +fn call_handling_test() -> Result<(), Report> { + let mut test_context = setup(); + + let mut builder = OpBuilder::new(test_context.context.clone()); + + let mut module = builder.create_module(Ident::with_empty_span("test".into()))?; + + let module_body = module.borrow().body().as_region_ref(); + builder.create_block(module_body, None, &[]); + + // Define entry + let entry = builder.create_function( + Ident::with_empty_span("entrypoint".into()), + Signature::new([AbiParam::new(Type::I1)], [AbiParam::new(Type::U32)]), + )?; + module + .borrow_mut() + .symbol_manager_mut() + .insert_new(entry, ProgramPoint::Invalid); + + // Define callee + let callee_signature = Signature::new([AbiParam::new(Type::I1)], [AbiParam::new(Type::I1)]); + let callee = builder + .create_function(Ident::with_empty_span("callee".into()), callee_signature.clone())?; + module + .borrow_mut() + .symbol_manager_mut() + .insert_new(callee, ProgramPoint::Invalid); + + { + let mut builder = FunctionBuilder::new(entry, &mut builder); + let input = builder.current_block().borrow().arguments()[0] as ValueRef; + let call = builder.exec(callee, callee_signature, [input], SourceSpan::default())?; + let cond = call.borrow().results()[0] as ValueRef; + let conditional = builder.r#if(cond, &[Type::U32], SourceSpan::default())?; + let result = conditional.borrow().results()[0] as ValueRef; + builder.ret(Some(result), SourceSpan::default())?; + + let then_region = conditional.borrow().then_body().as_region_ref(); + let then_block = builder.create_block_in_region(then_region); + builder.switch_to_block(then_block); + let is_true = builder.u32(1, SourceSpan::default()); + builder.r#yield([is_true], SourceSpan::default())?; + + let else_region = conditional.borrow().else_body().as_region_ref(); + let else_block = builder.create_block_in_region(else_region); + builder.switch_to_block(else_block); + let is_false = builder.u32(0, SourceSpan::default()); + builder.r#yield([is_false], SourceSpan::default())?; + } + + // This function inverts the boolean value it receives and returns it + { + let mut builder = FunctionBuilder::new(callee, &mut builder); + let cond = builder.current_block().borrow().arguments()[0] as ValueRef; + let truthy = builder.i1(true, SourceSpan::default()); + let falsey = builder.i1(false, SourceSpan::default()); + let result = builder.select(cond, falsey, truthy, SourceSpan::default())?; + builder.ret(Some(result), SourceSpan::default())?; + } + + let callable = entry.borrow(); + let results = test_context.evaluator.eval_callable(&*callable, [true.into()])?; + assert_eq!(results.len(), 1); + assert_eq!(results[0], Value::Immediate(0u32.into())); + + let results = test_context.evaluator.eval_callable(&*callable, [false.into()])?; + assert_eq!(results.len(), 1); + assert_eq!(results[0], Value::Immediate(1u32.into())); + + Ok(()) +} diff --git a/eval/src/value.rs b/eval/src/value.rs new file mode 100644 index 000000000..b48f7b4c3 --- /dev/null +++ b/eval/src/value.rs @@ -0,0 +1,127 @@ +use midenc_hir::{Immediate, SourceSpan, Type, ValueRef}; +use midenc_session::diagnostics::{miette, Diagnostic}; + +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum InvalidCastError { + #[error("cannot cast an immediate to a value of type {0}")] + #[diagnostic()] + UnsupportedType(Type), + #[error("failed to cast {value} to type {ty}: value is out of range for type")] + #[diagnostic()] + InvalidBitcast { value: Immediate, ty: Type }, +} + +/// The runtime value representation for the IR +/// +/// Only immediates and poison values are explicitly represented, as heap-allocated values can +/// only ever be accessed in terms of immediate values. +#[derive(Debug, Copy, Clone)] +pub enum Value { + /// The value is invalid, and if we ever attempt to use it as an actual operand for anything + /// other than control flow, we will raise a report with the span of the source code where + /// the poison was generated, and the span where it was used. + Poison { + origin: SourceSpan, + used: SourceSpan, + /// The value assigned to the poison, also used to derive its type + value: Immediate, + }, + /// An immediate value + Immediate(Immediate), +} + +impl Value { + pub fn poison(span: SourceSpan, value: impl Into) -> Self { + Self::Poison { + origin: span, + used: SourceSpan::UNKNOWN, + value: value.into(), + } + } + + pub fn ty(&self) -> Type { + match self { + Self::Poison { value, .. } | Self::Immediate(value) => value.ty(), + } + } + + pub fn map_ty(self, ty: &Type) -> Result { + match self { + Self::Poison { + origin, + used, + value, + } => { + let value = Self::cast_immediate(value, ty)?; + Ok(Self::Poison { + origin, + used, + value, + }) + } + Self::Immediate(value) => Self::cast_immediate(value, ty).map(Self::Immediate), + } + } + + pub fn cast_immediate(value: Immediate, ty: &Type) -> Result { + let result = match ty { + Type::I1 => value.as_bool().map(Immediate::I1), + Type::I8 => value.bitcast_i8().map(Immediate::I8), + Type::U8 => value.bitcast_u8().map(Immediate::U8), + Type::I16 => value.bitcast_i16().map(Immediate::I16), + Type::U16 => value.bitcast_u16().map(Immediate::U16), + Type::I32 => value.bitcast_i32().map(Immediate::I32), + Type::U32 => value.bitcast_u32().map(Immediate::U32), + Type::I64 => value.bitcast_i64().map(Immediate::I64), + Type::U64 => value.bitcast_u64().map(Immediate::U64), + Type::I128 => value.bitcast_i128().map(Immediate::I128), + Type::U128 => value.bitcast_u128().map(Immediate::U128), + Type::Felt => value.bitcast_felt().map(Immediate::Felt), + Type::F64 => value.bitcast_f64().map(Immediate::F64), + Type::Ptr(_) => value.bitcast_u32().map(Immediate::U32), + ty => return Err(InvalidCastError::UnsupportedType(ty.clone())), + }; + + result.ok_or_else(|| InvalidCastError::InvalidBitcast { + value, + ty: ty.clone(), + }) + } +} + +impl Eq for Value {} +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Poison { value: x, .. }, Self::Poison { value: y, .. }) => x == y, + (Self::Poison { .. }, _) | (_, Self::Poison { .. }) => false, + (Self::Immediate(x), Self::Immediate(y)) => x == y, + } + } +} + +impl core::fmt::Display for Value { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Poison { .. } => f.write_str("poison"), + Self::Immediate(imm) => write!(f, "{imm}"), + } + } +} + +impl> From for Value { + fn from(value: T) -> Self { + Self::Immediate(value.into()) + } +} + +/// A utility type for displaying value assignments in debug tracing +pub struct MaterializedValue { + pub id: ValueRef, + pub value: Value, +} +impl core::fmt::Display for MaterializedValue { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{} = {}", &self.id, &self.value) + } +} diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..4e68c3ae9 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,6 @@ +# Rust program examples + +These examples are built using `cargo-miden`. + +`cargo miden` is a `cargo` cargo extension. Check out its [documentation](https://0xMiden.github.io/compiler/usage/cargo-miden/#compiling-to-miden-assembly) +for more details on how to build and run the compiled programs. diff --git a/examples/auth-component-no-auth/.gitignore b/examples/auth-component-no-auth/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/examples/auth-component-no-auth/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/auth-component-no-auth/Cargo.lock b/examples/auth-component-no-auth/Cargo.lock new file mode 100644 index 000000000..010f3420c --- /dev/null +++ b/examples/auth-component-no-auth/Cargo.lock @@ -0,0 +1,2040 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "auth-component-no-auth" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5252b3d2648e5eedbc1a6f501e3c795e07025c1e93bbf8bbdd6eef7f447a6d54" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen 0.46.0", +] + +[[package]] +name = "miden-air" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2871bc4f392053cd115d4532e4b0fb164791829cc94b35641ed72480547dfd1" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345ae47710bbb4c6956dcc669a537c5cf2081879b6238c0df6da50e84a77ea3f" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab186ac7061b47415b923cf2da88df505f25c333f7caace80fb7760cf9c0590" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ad0b07592486e02de3ff7f3bff1d7fa81b1a7120f7f0b1027d650d810bbab" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84e5b15ea6fe0f80688fde2d6e4f5a3b66417d2541388b7a6cf967c6a8a2bc0" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9d87c128f467874b272fec318e792385e35b14d200408a10d30909228db864" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bdb0ef7d8a30213d991bbe44ef3f64dbb9caeebb89cfd996565a9a7a18dab5" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64304339292cc4e50a7b534197326824eeb21f3ffaadd955be63cc57093854ed" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bf9a2c1cf3e3694d0eb347695a291602a57f817dd001ac838f300799576a69" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb026e69ae937b2a83bf69ea86577e0ec2450cb22cce163190b9fd42f2972b63" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a305e5e2c68d10bcb8e2ed3dc38e54bc3a538acc9eadc154b713d5bb47af22" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys 0.61.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e257d7246e7a9fd015fb0b28b330a8d4142151a33f03e6a497754f4b1f6a8e" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.5", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.4+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a5f4a424faf49c3c2c344f166f0662341d470ea185e939657aaff130f0ec4a" +dependencies = [ + "wit-bindgen 0.45.1", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.0", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/auth-component-no-auth/Cargo.toml b/examples/auth-component-no-auth/Cargo.toml new file mode 100644 index 000000000..574279dfa --- /dev/null +++ b/examples/auth-component-no-auth/Cargo.toml @@ -0,0 +1,28 @@ +cargo-features = ["trim-paths"] + +[package] +name = "auth-component-no-auth" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../sdk/sdk" } + +[package.metadata.component] +package = "miden:auth-component-no-auth" + +[package.metadata.miden] +project-kind = "authentication-component" + +[profile.release] +trim-paths = ["diagnostics", "object"] + +[profile.dev] +trim-paths = ["diagnostics", "object"] diff --git a/examples/auth-component-no-auth/rust-toolchain.toml b/examples/auth-component-no-auth/rust-toolchain.toml new file mode 100644 index 000000000..dd49b3008 --- /dev/null +++ b/examples/auth-component-no-auth/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" diff --git a/examples/auth-component-no-auth/src/lib.rs b/examples/auth-component-no-auth/src/lib.rs new file mode 100644 index 000000000..2d21cf624 --- /dev/null +++ b/examples/auth-component-no-auth/src/lib.rs @@ -0,0 +1,43 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// +// extern crate alloc; +// use alloc::vec::Vec; + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +miden::generate!(); +bindings::export!(AuthComponent); + +use miden::{account, *}; + +use crate::bindings::exports::miden::base::authentication_component::Guest; + +struct AuthComponent; + +impl Guest for AuthComponent { + fn auth_procedure(_arg: Word) { + // translated from MASM at + // https://github.com/0xMiden/miden-base/blob/e4912663276ab8eebb24b84d318417cb4ea0bba3/crates/miden-lib/asm/account_components/no_auth.masm?plain=1 + let init_comm = account::get_initial_commitment(); + let curr_comm = account::compute_current_commitment(); + // check if the account state has changed by comparing initial and final commitments + if curr_comm != init_comm { + // if the account has been updated, increment the nonce + account::incr_nonce(); + } + } +} diff --git a/examples/auth-component-no-auth/wit/interface.wit b/examples/auth-component-no-auth/wit/interface.wit new file mode 100644 index 000000000..6022bbc99 --- /dev/null +++ b/examples/auth-component-no-auth/wit/interface.wit @@ -0,0 +1,5 @@ +package miden:auth-component-no-auth@1.0.0; + +world auth-component-no-auth-world { + export miden:base/authentication-component@1.0.0; +} diff --git a/examples/auth-component-rpo-falcon512/Cargo.lock b/examples/auth-component-rpo-falcon512/Cargo.lock new file mode 100644 index 000000000..a428e9a92 --- /dev/null +++ b/examples/auth-component-rpo-falcon512/Cargo.lock @@ -0,0 +1,2055 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "auth-component-rpo-falcon512" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92119844f513ffa41556430369ab02c295a3578af21cf945caa3e9e0c2481ac3" +dependencies = [ + "equivalent", + "hashbrown", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2871bc4f392053cd115d4532e4b0fb164791829cc94b35641ed72480547dfd1" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345ae47710bbb4c6956dcc669a537c5cf2081879b6238c0df6da50e84a77ea3f" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab186ac7061b47415b923cf2da88df505f25c333f7caace80fb7760cf9c0590" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.27", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.27", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ad0b07592486e02de3ff7f3bff1d7fa81b1a7120f7f0b1027d650d810bbab" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84e5b15ea6fe0f80688fde2d6e4f5a3b66417d2541388b7a6cf967c6a8a2bc0" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9d87c128f467874b272fec318e792385e35b14d200408a10d30909228db864" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bdb0ef7d8a30213d991bbe44ef3f64dbb9caeebb89cfd996565a9a7a18dab5" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.27", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64304339292cc4e50a7b534197326824eeb21f3ffaadd955be63cc57093854ed" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bf9a2c1cf3e3694d0eb347695a291602a57f817dd001ac838f300799576a69" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb026e69ae937b2a83bf69ea86577e0ec2450cb22cce163190b9fd42f2972b63" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a305e5e2c68d10bcb8e2ed3dc38e54bc3a538acc9eadc154b713d5bb47af22" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.225" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2789234a13a53fc4be1b51ea1bab45a3c338bdb884862a257d10e5a74ae009e6" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys 0.61.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae2a4cf385da23d1d53bc15cdfa5c2109e93d8d362393c801e87da2f72f0e201" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.1", + "toml_datetime 0.7.1", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a197c0ec7d131bfc6f7e82c8442ba1595aeab35da7adbf05b6b73cd06a16b6be" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ded9fdb81f30a5708920310bfcd9ea7482ff9cba5f54601f7a19a877d5c2392" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.6", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.7+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c" +dependencies = [ + "wasip2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.27", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.0", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/auth-component-rpo-falcon512/Cargo.toml b/examples/auth-component-rpo-falcon512/Cargo.toml new file mode 100644 index 000000000..02098c8af --- /dev/null +++ b/examples/auth-component-rpo-falcon512/Cargo.toml @@ -0,0 +1,26 @@ +cargo-features = ["trim-paths"] + +[package] +name = "auth-component-rpo-falcon512" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +miden = { path = "../../sdk/sdk" } + +[package.metadata.component] +package = "miden:auth-component-rpo-falcon512" + +[package.metadata.miden] +project-kind = "authentication-component" + +[profile.release] +trim-paths = ["diagnostics", "object"] + +[profile.dev] +trim-paths = ["diagnostics", "object"] diff --git a/examples/auth-component-rpo-falcon512/rust-toolchain.toml b/examples/auth-component-rpo-falcon512/rust-toolchain.toml new file mode 100644 index 000000000..8e58f5be6 --- /dev/null +++ b/examples/auth-component-rpo-falcon512/rust-toolchain.toml @@ -0,0 +1,6 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" + diff --git a/examples/auth-component-rpo-falcon512/src/lib.rs b/examples/auth-component-rpo-falcon512/src/lib.rs new file mode 100644 index 000000000..fc97ee6df --- /dev/null +++ b/examples/auth-component-rpo-falcon512/src/lib.rs @@ -0,0 +1,59 @@ +#![no_std] + +extern crate alloc; + +use miden::{ + account, component, felt, hash_words, intrinsics::advice::adv_insert, tx, Felt, Value, + ValueAccess, Word, +}; + +use crate::bindings::exports::miden::base::authentication_component::Guest; + +miden::generate!(); +bindings::export!(AuthComponent); + +/// Authentication component storage/layout. +/// +/// Public key is expected to be in the slot 0. Matches MASM constant `PUBLIC_KEY_SLOT=0` in +/// ../base/crates/miden-lib/asm/account_components/rpo_falcon_512.masm +#[component] +struct AuthComponent { + /// The account owner's public key (RPO-Falcon512 public key hash). + #[storage( + slot(0), + description = "owner public key", + type = "auth::rpo_falcon512::pub_key" + )] + owner_public_key: Value, +} + +impl Guest for AuthComponent { + fn auth_procedure(_arg: Word) { + let ref_block_num = tx::get_block_number(); + let final_nonce = account::incr_nonce(); + + // Gather tx summary parts + let acct_delta_commit = account::compute_delta_commitment(); + let input_notes_commit = tx::get_input_notes_commitment(); + let output_notes_commit = tx::get_output_notes_commitment(); + + let salt = Word::from([felt!(0), felt!(0), ref_block_num, final_nonce]); + + let mut tx_summary = [acct_delta_commit, input_notes_commit, output_notes_commit, salt]; + let msg: Word = hash_words(&tx_summary).into(); + // On the advice stack the words are expected to be in the reverse order + tx_summary.reverse(); + // Insert tx summary into advice map under key `msg` + adv_insert(msg.clone(), &tx_summary); + + // Load public key from storage slot 0 + let storage = Self::default(); + let pub_key: Word = storage.owner_public_key.read(); + + // Emit signature request event to advice stack, + miden::emit_falcon_sig_to_stack(msg.clone(), pub_key.clone()); + + // Verify the signature loaded on the advice stack. + miden::rpo_falcon512_verify(pub_key, msg); + } +} diff --git a/examples/auth-component-rpo-falcon512/wit/interface.wit b/examples/auth-component-rpo-falcon512/wit/interface.wit new file mode 100644 index 000000000..5c9cdaa25 --- /dev/null +++ b/examples/auth-component-rpo-falcon512/wit/interface.wit @@ -0,0 +1,6 @@ +package miden:auth-component-rpo-falcon512@1.0.0; + +world auth-component-rpo-falcon512-world { + export miden:base/authentication-component@1.0.0; +} + diff --git a/examples/basic-wallet-tx-script/.gitignore b/examples/basic-wallet-tx-script/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/examples/basic-wallet-tx-script/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/basic-wallet-tx-script/Cargo.lock b/examples/basic-wallet-tx-script/Cargo.lock new file mode 100644 index 000000000..d10c6834b --- /dev/null +++ b/examples/basic-wallet-tx-script/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "basic-wallet-tx-script" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/basic-wallet-tx-script/Cargo.toml b/examples/basic-wallet-tx-script/Cargo.toml new file mode 100644 index 000000000..2dbe90edf --- /dev/null +++ b/examples/basic-wallet-tx-script/Cargo.toml @@ -0,0 +1,36 @@ +cargo-features = ["trim-paths"] + +[package] +name = "basic-wallet-tx-script" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../sdk/sdk" } + +[package.metadata.component] +package = "miden:basic-wallet-tx-script" + +[package.metadata.miden] +project-kind = "transaction-script" + +# Miden dependencies for cargo-miden build/linking +[package.metadata.miden.dependencies] +"miden:basic-wallet" = { path = "../basic-wallet" } + + +[package.metadata.component.target.dependencies] +"miden:basic-wallet" = { path = "../basic-wallet/target/generated-wit/" } + +[profile.release] +trim-paths = ["diagnostics", "object"] + +[profile.dev] +trim-paths = ["diagnostics", "object"] diff --git a/examples/basic-wallet-tx-script/cargo-generate.toml b/examples/basic-wallet-tx-script/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/basic-wallet-tx-script/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/basic-wallet-tx-script/rust-toolchain.toml b/examples/basic-wallet-tx-script/rust-toolchain.toml new file mode 100644 index 000000000..dd49b3008 --- /dev/null +++ b/examples/basic-wallet-tx-script/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" diff --git a/examples/basic-wallet-tx-script/src/lib.rs b/examples/basic-wallet-tx-script/src/lib.rs new file mode 100644 index 000000000..f5d0fbfbe --- /dev/null +++ b/examples/basic-wallet-tx-script/src/lib.rs @@ -0,0 +1,42 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// +// extern crate alloc; +// use alloc::vec::Vec; + +use miden::{intrinsics::advice::adv_push_mapvaln, *}; + +use crate::bindings::miden::basic_wallet::basic_wallet; + +// Input layout constants +const TAG_INDEX: usize = 0; +const AUX_INDEX: usize = 1; +const NOTE_TYPE_INDEX: usize = 2; +const EXECUTION_HINT_INDEX: usize = 3; +const RECIPIENT_START: usize = 4; +const RECIPIENT_END: usize = 8; +const ASSET_START: usize = 8; +const ASSET_END: usize = 12; + +#[tx_script] +fn run(arg: Word) { + let num_felts = adv_push_mapvaln(arg.clone()); + let num_felts_u64 = num_felts.as_u64(); + assert_eq(Felt::from_u32((num_felts_u64 % 4) as u32), felt!(0)); + let num_words = Felt::from_u64_unchecked(num_felts_u64 / 4); + let commitment = arg; + let input = adv_load_preimage(num_words, commitment); + let tag = input[TAG_INDEX]; + let aux = input[AUX_INDEX]; + let note_type = input[NOTE_TYPE_INDEX]; + let execution_hint = input[EXECUTION_HINT_INDEX]; + let recipient: [Felt; 4] = input[RECIPIENT_START..RECIPIENT_END].try_into().unwrap(); + let note_idx = + tx::create_note(tag.into(), aux, note_type.into(), execution_hint, recipient.into()); + let asset: [Felt; 4] = input[ASSET_START..ASSET_END].try_into().unwrap(); + basic_wallet::move_asset_to_note(asset.into(), note_idx); +} diff --git a/examples/basic-wallet-tx-script/wit/.gitkeep b/examples/basic-wallet-tx-script/wit/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/examples/basic-wallet/.gitignore b/examples/basic-wallet/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/examples/basic-wallet/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/basic-wallet/Cargo.lock b/examples/basic-wallet/Cargo.lock new file mode 100644 index 000000000..d19933301 --- /dev/null +++ b/examples/basic-wallet/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "basic_wallet" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/basic-wallet/Cargo.toml b/examples/basic-wallet/Cargo.toml new file mode 100644 index 000000000..ad6ca95be --- /dev/null +++ b/examples/basic-wallet/Cargo.toml @@ -0,0 +1,29 @@ +cargo-features = ["trim-paths"] + +[package] +name = "basic_wallet" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../sdk/sdk" } + +[package.metadata.component] +package = "miden:basic-wallet" + +[package.metadata.miden] +project-kind = "account" +supported-types = ["RegularAccountUpdatableCode"] + +[profile.release] +trim-paths = ["diagnostics", "object"] + +[profile.dev] +trim-paths = ["diagnostics", "object"] diff --git a/examples/basic-wallet/cargo-generate.toml b/examples/basic-wallet/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/basic-wallet/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/basic-wallet/rust-toolchain.toml b/examples/basic-wallet/rust-toolchain.toml new file mode 100644 index 000000000..dd49b3008 --- /dev/null +++ b/examples/basic-wallet/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" diff --git a/examples/basic-wallet/src/lib.rs b/examples/basic-wallet/src/lib.rs new file mode 100644 index 000000000..1f20806c1 --- /dev/null +++ b/examples/basic-wallet/src/lib.rs @@ -0,0 +1,38 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; + +use miden::{account, component, tx, Asset, NoteIdx}; + +#[component] +struct MyAccount; + +#[component] +impl MyAccount { + /// Adds an asset to the account. + /// + /// This function adds the specified asset to the account's asset list. + /// + /// # Arguments + /// * `asset` - The asset to be added to the account + pub fn receive_asset(&self, asset: Asset) { + account::add_asset(asset); + } + + /// Moves an asset from the account to a note. + /// + /// This function removes the specified asset from the account and adds it to + /// the note identified by the given index. + /// + /// # Arguments + /// * `asset` - The asset to move from the account to the note + /// * `note_idx` - The index of the note to receive the asset + pub fn move_asset_to_note(&self, asset: Asset, note_idx: NoteIdx) { + let asset = account::remove_asset(asset); + tx::add_asset_to_note(asset, note_idx); + } +} diff --git a/examples/collatz/Cargo.lock b/examples/collatz/Cargo.lock new file mode 100644 index 000000000..e238fea73 --- /dev/null +++ b/examples/collatz/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "collatz" +version = "0.1.0" diff --git a/examples/collatz/Cargo.toml b/examples/collatz/Cargo.toml new file mode 100644 index 000000000..23d5f3730 --- /dev/null +++ b/examples/collatz/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "collatz" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +#miden = { git = "https://github.com/0xMiden/compiler" } + + diff --git a/examples/collatz/README.md b/examples/collatz/README.md new file mode 100644 index 000000000..f6264a05e --- /dev/null +++ b/examples/collatz/README.md @@ -0,0 +1,21 @@ +# collatz + +## Useful commands + +`collatz` is built using the [Miden compiler](https://github.com/0xMiden/compiler). + +`cargo miden` is a `cargo` cargo extension. Check out its [documentation](https://0xMiden.github.io/compiler/usage/cargo-miden/#compiling-to-miden-assembly) +for more details on how to build and run the compiled programs. + +## Compile + +```bash +cargo miden build --release +``` + +## Run + +```bash +midenc run target/miden/release/collatz.masp --inputs inputs.toml +``` + diff --git a/examples/collatz/cargo-generate.toml b/examples/collatz/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/collatz/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/collatz/inputs.toml b/examples/collatz/inputs.toml new file mode 100644 index 000000000..07b3e8cfd --- /dev/null +++ b/examples/collatz/inputs.toml @@ -0,0 +1,2 @@ +[inputs] +stack = [25] diff --git a/examples/collatz/rust-toolchain.toml b/examples/collatz/rust-toolchain.toml new file mode 100644 index 000000000..fcfe53298 --- /dev/null +++ b/examples/collatz/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src"] +targets = ["wasm32-wasip1"] +profile = "minimal" diff --git a/examples/collatz/src/lib.rs b/examples/collatz/src/lib.rs new file mode 100644 index 000000000..9d1d14883 --- /dev/null +++ b/examples/collatz/src/lib.rs @@ -0,0 +1,40 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// // Global allocator to use heap memory in no-std environment +// #[global_allocator] +// static ALLOC: BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +// Pass up to 16 u32 inputs as entrypoint function parameters. +// The output is temporarely limited to 1 u32 value +// +// NOTE: +// The name of the entrypoint function is expected to be `entrypoint`. Do not remove the +// `#[no_mangle]` attribute, otherwise, the rustc will mangle the name and it'll not be recognized +// by the Miden compiler. +#[no_mangle] +fn entrypoint(mut n: u32) -> u32 { + let mut steps = 0; + while n != 1 { + if n % 2 == 0 { + n /= 2; + } else { + n = 3 * n + 1; + } + steps += 1; + } + steps +} diff --git a/examples/counter-contract/Cargo.lock b/examples/counter-contract/Cargo.lock new file mode 100644 index 000000000..004f5e4a7 --- /dev/null +++ b/examples/counter-contract/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "counter-contract" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/counter-contract/Cargo.toml b/examples/counter-contract/Cargo.toml new file mode 100644 index 000000000..cc30f4df6 --- /dev/null +++ b/examples/counter-contract/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "counter-contract" +description = "A simple example of a Miden counter contract using the Account Storage API" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +miden = { path = "../../sdk/sdk" } + +# [package.metadata.component] +# package = "miden:counter-contract" + +[package.metadata.miden] +project-kind = "account" +supported-types = ["RegularAccountUpdatableCode"] diff --git a/examples/counter-contract/README.md b/examples/counter-contract/README.md new file mode 100644 index 000000000..e60ed2d98 --- /dev/null +++ b/examples/counter-contract/README.md @@ -0,0 +1,7 @@ +# Counter Contract + +## Build + +```bash +cargo miden build --release +``` diff --git a/examples/counter-contract/cargo-generate.toml b/examples/counter-contract/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/counter-contract/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/counter-contract/rust-toolchain.toml b/examples/counter-contract/rust-toolchain.toml new file mode 100644 index 000000000..dd49b3008 --- /dev/null +++ b/examples/counter-contract/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" diff --git a/examples/counter-contract/src/lib.rs b/examples/counter-contract/src/lib.rs new file mode 100644 index 000000000..0e2c188ce --- /dev/null +++ b/examples/counter-contract/src/lib.rs @@ -0,0 +1,49 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; + +use miden::{component, felt, Felt, StorageMap, StorageMapAccess, Word}; + +use crate::bindings::exports::miden::counter_contract::counter::Guest; + +miden::generate!(); +bindings::export!(CounterContract); + +/// Main contract structure for the counter example. +#[component] +struct CounterContract { + /// Storage map holding the counter value. + #[storage(slot(0), description = "counter contract storage map")] + count_map: StorageMap, +} + +impl Guest for CounterContract { + /// Returns the current counter value stored in the contract's storage map. + fn get_count() -> Felt { + // Get the instance of the contract + let contract = CounterContract::default(); + // Define a fixed key for the counter value within the map + let key = Word::from([felt!(0), felt!(0), felt!(0), felt!(1)]); + // Read the value associated with the key from the storage map + contract.count_map.get(&key) + } + + /// Increments the counter value stored in the contract's storage map by one. + fn increment_count() -> Felt { + // Get the instance of the contract + let contract = CounterContract::default(); + // Define the same fixed key + let key = Word::from([felt!(0), felt!(0), felt!(0), felt!(1)]); + // Read the current value + let current_value: Felt = contract.count_map.get(&key); + // Increment the value by one + let new_value = current_value + felt!(1); + // Write the new value back to the storage map + contract.count_map.set(key, new_value); + new_value + } +} diff --git a/examples/counter-contract/wit/counter.wit b/examples/counter-contract/wit/counter.wit new file mode 100644 index 000000000..214ba623d --- /dev/null +++ b/examples/counter-contract/wit/counter.wit @@ -0,0 +1,17 @@ +package miden:counter-contract@0.1.0; + +use miden:base/core-types@1.0.0; + +interface counter { + use core-types.{felt}; + + /// Returns the current counter value stored in the contract's storage. + get-count: func() -> felt; + /// Increments the counter value stored in the contract's storage by one + //and return the new counter value + increment-count: func() -> felt; +} + +world counter-world { + export counter; +} diff --git a/examples/counter-note/Cargo.lock b/examples/counter-note/Cargo.lock new file mode 100644 index 000000000..e144cf9a7 --- /dev/null +++ b/examples/counter-note/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "counter-note" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/counter-note/Cargo.toml b/examples/counter-note/Cargo.toml new file mode 100644 index 000000000..33aa1aed5 --- /dev/null +++ b/examples/counter-note/Cargo.toml @@ -0,0 +1,36 @@ +cargo-features = ["trim-paths"] + +[package] +name = "counter-note" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../sdk/sdk" } + +[package.metadata.miden] +project-kind = "note-script" + +# TODO: switch to miden table +[package.metadata.component] +package = "miden:counter-note" + +# Miden dependencies for cargo-miden build/linking +[package.metadata.miden.dependencies] +"miden:counter-contract" = { path = "../counter-contract" } + +[package.metadata.component.target.dependencies] +"miden:counter-contract" = { path = "../counter-contract/wit/counter.wit" } + +[profile.release] +trim-paths = ["diagnostics", "object"] + +[profile.dev] +trim-paths = ["diagnostics", "object"] diff --git a/examples/counter-note/cargo-generate.toml b/examples/counter-note/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/counter-note/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/counter-note/rust-toolchain.toml b/examples/counter-note/rust-toolchain.toml new file mode 100644 index 000000000..dd49b3008 --- /dev/null +++ b/examples/counter-note/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" diff --git a/examples/counter-note/src/lib.rs b/examples/counter-note/src/lib.rs new file mode 100644 index 000000000..ca8c973df --- /dev/null +++ b/examples/counter-note/src/lib.rs @@ -0,0 +1,21 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +use miden::*; + +use crate::bindings::miden::counter_contract::counter; + +#[note_script] +fn run(_arg: Word) { + let initial_value = counter::get_count(); + counter::increment_count(); + let expected_value = initial_value + Felt::from_u32(1); + let final_value = counter::get_count(); + assert_eq(final_value, expected_value); +} diff --git a/examples/counter-note/wit/.gitkeep b/examples/counter-note/wit/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/examples/fibonacci/.gitignore b/examples/fibonacci/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/examples/fibonacci/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/fibonacci/Cargo.lock b/examples/fibonacci/Cargo.lock new file mode 100644 index 000000000..05862eb6c --- /dev/null +++ b/examples/fibonacci/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "fibonacci" +version = "0.1.0" diff --git a/examples/fibonacci/Cargo.toml b/examples/fibonacci/Cargo.toml new file mode 100644 index 000000000..21fb11c85 --- /dev/null +++ b/examples/fibonacci/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "fibonacci" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +#miden = { git = "https://github.com/0xMiden/compiler" } + + diff --git a/examples/fibonacci/README.md b/examples/fibonacci/README.md new file mode 100644 index 000000000..be6e4d7e0 --- /dev/null +++ b/examples/fibonacci/README.md @@ -0,0 +1,21 @@ +# fibonacci + +## Useful commands + +`fibonacci` is built using the [Miden compiler](https://github.com/0xMiden/compiler). + +`cargo miden` is a `cargo` cargo extension. Check out its [documentation](https://0xMiden.github.io/compiler/usage/cargo-miden/#compiling-to-miden-assembly) +for more details on how to build and run the compiled programs. + +## Compile + +```bash +cargo miden build --release +``` + +## Run + +```bash +midenc run target/miden/release/fibonacci.masp --inputs inputs.toml +``` + diff --git a/examples/fibonacci/cargo-generate.toml b/examples/fibonacci/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/fibonacci/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/fibonacci/inputs.toml b/examples/fibonacci/inputs.toml new file mode 100644 index 000000000..07b3e8cfd --- /dev/null +++ b/examples/fibonacci/inputs.toml @@ -0,0 +1,2 @@ +[inputs] +stack = [25] diff --git a/examples/fibonacci/rust-toolchain.toml b/examples/fibonacci/rust-toolchain.toml new file mode 100644 index 000000000..fcfe53298 --- /dev/null +++ b/examples/fibonacci/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src"] +targets = ["wasm32-wasip1"] +profile = "minimal" diff --git a/examples/fibonacci/src/lib.rs b/examples/fibonacci/src/lib.rs new file mode 100644 index 000000000..55a9dbe79 --- /dev/null +++ b/examples/fibonacci/src/lib.rs @@ -0,0 +1,38 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// // Global allocator to use heap memory in no-std environment +// #[global_allocator] +// static ALLOC: BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[panic_handler] +#[cfg(not(test))] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +// Pass up to 16 u32 inputs as entrypoint function parameters. +// The output is temporarely limited to 1 u32 value +// +// NOTE: +// The name of the entrypoint function is expected to be `entrypoint`. Do not remove the +// `#[no_mangle]` attribute, otherwise, the rustc will mangle the name and it'll not be recognized +// by the Miden compiler. +#[no_mangle] +pub fn entrypoint(n: u32) -> u32 { + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let c = a + b; + a = b; + b = c; + } + a +} diff --git a/examples/is-prime/Cargo.lock b/examples/is-prime/Cargo.lock new file mode 100644 index 000000000..5717c401b --- /dev/null +++ b/examples/is-prime/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "is_prime" +version = "0.1.0" diff --git a/examples/is-prime/Cargo.toml b/examples/is-prime/Cargo.toml new file mode 100644 index 000000000..6783d56d4 --- /dev/null +++ b/examples/is-prime/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "is_prime" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +#miden = { git = "https://github.com/0xMiden/compiler" } + + diff --git a/examples/is-prime/README.md b/examples/is-prime/README.md new file mode 100644 index 000000000..9706c575a --- /dev/null +++ b/examples/is-prime/README.md @@ -0,0 +1,14 @@ +# is-prime + +## Compile + +```bash +cargo miden build --release +``` + +## Run + +```bash +midenc run target/miden/release/is_prime.masp --inputs inputs.toml +``` + diff --git a/examples/is-prime/cargo-generate.toml b/examples/is-prime/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/is-prime/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/is-prime/inputs.toml b/examples/is-prime/inputs.toml new file mode 100644 index 000000000..6c687e873 --- /dev/null +++ b/examples/is-prime/inputs.toml @@ -0,0 +1,2 @@ +[inputs] +stack = [2147482583] diff --git a/examples/is-prime/rust-toolchain.toml b/examples/is-prime/rust-toolchain.toml new file mode 100644 index 000000000..fcfe53298 --- /dev/null +++ b/examples/is-prime/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src"] +targets = ["wasm32-wasip1"] +profile = "minimal" diff --git a/examples/is-prime/src/lib.rs b/examples/is-prime/src/lib.rs new file mode 100644 index 000000000..f3d922353 --- /dev/null +++ b/examples/is-prime/src/lib.rs @@ -0,0 +1,34 @@ +#![no_std] + +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +/// Returns 1 if integer is prime +fn is_prime(n: u32) -> bool { + if n <= 1 { + return false; + } + if n <= 3 { + return true; + } + if n % 2 == 0 || n % 3 == 0 { + return false; + } + let mut i = 5; + while i * i <= n { + if n % i == 0 || n % (i + 2) == 0 { + return false; + } + i += 6; + } + true +} + +/// https://www.math.utah.edu/~pa/MDS/primes.html +#[no_mangle] +fn entrypoint(n: u32) -> bool { + return is_prime(n); +} diff --git a/examples/p2id-note/.gitignore b/examples/p2id-note/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/examples/p2id-note/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/p2id-note/Cargo.lock b/examples/p2id-note/Cargo.lock new file mode 100644 index 000000000..b79abeaee --- /dev/null +++ b/examples/p2id-note/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "p2id" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/p2id-note/Cargo.toml b/examples/p2id-note/Cargo.toml new file mode 100644 index 000000000..c1e47a173 --- /dev/null +++ b/examples/p2id-note/Cargo.toml @@ -0,0 +1,36 @@ +cargo-features = ["trim-paths"] + +[package] +name = "p2id" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../sdk/sdk" } + +[package.metadata.component] +package = "miden:p2id" + +[package.metadata.miden] +project-kind = "note-script" + +# Miden dependencies for cargo-miden build/linking +[package.metadata.miden.dependencies] +"miden:basic-wallet" = { path = "../basic-wallet" } + + +[package.metadata.component.target.dependencies] +"miden:basic-wallet" = { path = "../basic-wallet/target/generated-wit/" } + +[profile.release] +trim-paths = ["diagnostics", "object"] + +[profile.dev] +trim-paths = ["diagnostics", "object"] diff --git a/examples/p2id-note/cargo-generate.toml b/examples/p2id-note/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/p2id-note/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/p2id-note/rust-toolchain.toml b/examples/p2id-note/rust-toolchain.toml new file mode 100644 index 000000000..dd49b3008 --- /dev/null +++ b/examples/p2id-note/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" diff --git a/examples/p2id-note/src/lib.rs b/examples/p2id-note/src/lib.rs new file mode 100644 index 000000000..0d6886ab9 --- /dev/null +++ b/examples/p2id-note/src/lib.rs @@ -0,0 +1,28 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +use miden::*; + +use crate::bindings::miden::basic_wallet::basic_wallet::receive_asset; + +#[note_script] +fn run(_arg: Word) { + let inputs = note::get_inputs(); + let target_account_id_prefix = inputs[0]; + let target_account_id_suffix = inputs[1]; + + let target_account = AccountId::from(target_account_id_prefix, target_account_id_suffix); + let current_account = account::get_id(); + assert_eq!(current_account, target_account); + + let assets = note::get_assets(); + for asset in assets { + receive_asset(asset); + } +} diff --git a/examples/p2id-note/wit/.gitkeep b/examples/p2id-note/wit/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/examples/storage-example/Cargo.lock b/examples/storage-example/Cargo.lock new file mode 100644 index 000000000..491a2c1f6 --- /dev/null +++ b/examples/storage-example/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "storage-example" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/examples/storage-example/Cargo.toml b/examples/storage-example/Cargo.toml new file mode 100644 index 000000000..745ffd63b --- /dev/null +++ b/examples/storage-example/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "storage-example" +description = "A simple example of a Miden account storage API" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +miden = { path = "../../sdk/sdk" } + +[package.metadata.component] +package = "miden:storage-example" + +[package.metadata.miden] +project-kind = "account" +supported-types = ["RegularAccountUpdatableCode"] diff --git a/examples/storage-example/cargo-generate.toml b/examples/storage-example/cargo-generate.toml new file mode 100644 index 000000000..26029f3e7 --- /dev/null +++ b/examples/storage-example/cargo-generate.toml @@ -0,0 +1,2 @@ +[template] +ignore = ["target"] diff --git a/examples/storage-example/rust-toolchain.toml b/examples/storage-example/rust-toolchain.toml new file mode 100644 index 000000000..dd49b3008 --- /dev/null +++ b/examples/storage-example/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "nightly-2025-07-20" +components = ["rustfmt", "rust-src", "clippy"] +targets = ["wasm32-wasip2"] +profile = "minimal" diff --git a/examples/storage-example/src/lib.rs b/examples/storage-example/src/lib.rs new file mode 100644 index 000000000..0ad1a9ee4 --- /dev/null +++ b/examples/storage-example/src/lib.rs @@ -0,0 +1,52 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; + +use miden::{component, Asset, Felt, StorageMap, StorageMapAccess, Value, ValueAccess, Word}; + +use crate::bindings::exports::miden::storage_example::*; + +miden::generate!(); +bindings::export!(MyAccount); + +#[component] +struct MyAccount { + #[storage( + slot(0), + description = "test value", + type = "auth::rpo_falcon512::pub_key" + )] + owner_public_key: Value, + + #[storage(slot(1), description = "test map")] + asset_qty_map: StorageMap, +} + +// // generated by the `component` and `storage` attribute macros +// impl Default for MyAccount { +// fn default() -> Self { +// Self { +// owner_public_key: Value { slot: 0 }, +// asset_qty_map: StorageMap { slot: 1 }, +// } +// } +// } + +impl foo::Guest for MyAccount { + fn set_asset_qty(pub_key: Word, asset: Asset, qty: Felt) { + let my_account = MyAccount::default(); + let owner_key: Word = my_account.owner_public_key.read(); + if pub_key == owner_key { + my_account.asset_qty_map.set(asset, qty); + } + } + + fn get_asset_qty(asset: Asset) -> Felt { + let my_account = MyAccount::default(); + my_account.asset_qty_map.get(&asset) + } +} diff --git a/examples/storage-example/wit/storage-example.wit b/examples/storage-example/wit/storage-example.wit new file mode 100644 index 000000000..e6abc48db --- /dev/null +++ b/examples/storage-example/wit/storage-example.wit @@ -0,0 +1,14 @@ +package miden:storage-example@1.0.0; + +use miden:base/core-types@1.0.0; + +interface foo { + use core-types.{felt, word, asset}; + + set-asset-qty: func(pub-key: word, asset: asset, qty: felt); + get-asset-qty: func(asset: asset) -> felt; +} + +world foo-world { + export foo; +} diff --git a/frontend-wasm/CHANGELOG.md b/frontend-wasm/CHANGELOG.md deleted file mode 100644 index 04f2b8e8b..000000000 --- a/frontend-wasm/CHANGELOG.md +++ /dev/null @@ -1,230 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-frontend-wasm-v0.0.6...midenc-frontend-wasm-v0.0.7) - 2024-09-17 - -### Other -- *(rustfmt)* disable wrap_comments due to broken behavior - -## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/midenc-frontend-wasm-v0.0.5...midenc-frontend-wasm-v0.0.6) - 2024-09-06 - -### Other -- switch all crates to a single workspace version (0.0.5) - -## [0.0.2](https://github.com/0xPolygonMiden/compiler/compare/midenc-frontend-wasm-v0.0.1...midenc-frontend-wasm-v0.0.2) - 2024-08-30 - -### Fixed -- *(codegen)* broken return via pointer transformation -- *(frontend-wasm)* do not apply redundant casts -- *(frontend-wasm)* incorrect types applied to certain primops - -### Other -- Merge pull request [#284](https://github.com/0xPolygonMiden/compiler/pull/284) from 0xPolygonMiden/bitwalker/abi-transform-test-fixes -- update expect tests due to codegen changes - -## [0.0.1](https://github.com/0xPolygonMiden/compiler/compare/midenc-frontend-wasm-v0.0.0...midenc-frontend-wasm-v0.0.1) - 2024-07-18 - -### Added -- implement support for wasm typed select -- implement memset, memcpy, mem_grow, mem_size, and bitcast ops -- add workaround for wasm `memory.grow` op translation -- introduce marker attribute for ABI in text representation of -- cut the Miden function digest from the Miden SDK function names -- introduce `import_miden` component import directive to represent -- employ module names in Wasm module imports in Rust bindings for -- add NoTransform strategy for Miden ABI call transformation -- introduce TransformStrategy and add the "return-via-pointer" -- draft the Miden ABI adaptor generation in Wasm frontend -- parse function digest from the Wasm module import -- draft Miden ABI function types encoding and retrieval -- introduce Miden ABI component import -- lay out the Rust Miden SDK structure, the first integration test -- introduce `CanonicalOptions` in IR and translate Wasm -- `Component::modules` are topologically sorted -- introduce module instantiation arguments -- translate Wasm component instance exports; -- Initial Wasm component translation. -- translate Wasm memory.copy op -- Wasm module data segment transtation -- parse Wasm components -- implement compiler driver, update midenc -- translate Wasm `memory.grow` op -- declare data segments in module, remove ptr type casting for `global_set`, -- Wasm data section parsing -- Wasm globals (`global.get`,`set`) translation -- Wasm `i32.wrap_i64` translation -- cast arguments for unsigned Wasm ops, implement signed Wasm ops translation -- Wasm br_table translation -- Wasm `unreachable` op translation -- Wasm `select` op translation -- Wasm `eqz`, `eq`, `ne`, `lt`, `gt`, `le`, `ge` integer and f64 operators -- Wasm integer `lt_u`, `le_u`, `ge_u` and `gt_u` operators translation -- Wasm integer `mul`, `div_u`, `rem_u` and f64 `mul`, `div`, `min`, `max` -- Wasm `f64.add, sub` and integer `sub` translation -- Wasm `shr_u`, `rotl`, `rotr` i32 and i64 instructions translation -- i32 and i64 variants of `shl` and `xor` Wasm ops translation -- Wasm i32.and, i32.or, i64.and, i64.or translation -- add i32.popcnt, i64.extend_i32_s, extend_i32_u Wasm ops translation -- run wasmparser's validator when parsing function bodies -- Wasm memory.grow and memory.size translation -- Wasm memory store/load ops translation -- handle BrTable and CallIndirect usupported Wasm instructions -- add Rust -> Wasm -> Miden IR test pipeline with a simple function call test; -- draft Wasm -> Miden IR translator, handling control flow ops and SSA construction - -### Fixed -- improve codegen quality using more precise casts -- properly handle shift operand for bitwise shift/rotate ops -- felt representation mismatch between rust and miden -- use the MASM module paths for the tx kernel module names -- change the `tx_kernel::get_inputs` low-level function signature -- query `ModuleImportInfo::aliases` with module id alias -- strip .wasm extension from parsed wasm binaries -- fix value type in store op in `return_via_pointer` transformation, -- fix build after cherry-pick into temp branch of off main; -- after rebase, add Wasm CM `record` type conversion, -- find the correct core module function for the IR component import -- tweak wasm frontend and related test infra -- swap panics with errors -- parsing Wasm module `memory` section, error handling in `emit_zero` -- improper handling of inlined blocks in inline-blocks transform -- always create the destination branch argument for the sentinel value -- be more explicit about overflow, address some bugs found while testing -- set reduced load/store mem ops ptr type to unsigned, -- cast pointer in memory access ops and br_table selector to U32 -- handle missing `Instruction::Switch` in jump destination changes -- cast i64 comparison ops result to i32 to preserve Wasm op semantics -- Cast i1 back to i32/i64 expected by Wasm ops after comparison ops -- cast u32/u64 back to Wasm ops expected i32/i64 after `shr_u`, `div_u`, `rem_u` ops -- skip Wasm element section instead of failing -- set `state.reachable = false` for `Operator::Unreachable` -- make `add`, `mul` and `sub` to use wrapping Miden operations -- handle InvalidFunctionError in ModuleEnviromnment::build -- fix build and tests after rebasing on top of bitwalker/wip branch -- pass SourceSpan in translate_operator - -### Other -- fix typos ([#243](https://github.com/0xPolygonMiden/compiler/pull/243)) -- extend and update integration tests -- Fix descriptions for crates -- set crates versions to 0.0.0, and `publish = false` for tests -- add missing descriptions to all crates -- rename `miden-prelude` to `miden-stdlib-sys` in SDK -- ensure all relevant crates are prefixed with `midenc-` -- Merge pull request [#187](https://github.com/0xPolygonMiden/compiler/pull/187) from 0xPolygonMiden/bitwalker/account-compilation-fixes -- check rustfmt on CI, format code with rustfmt -- run clippy on CI, fix all clippy warnings -- use midenc driver for non-cargo-based fixtures in -- use midenc driver to compile cargo-based fixtures -- handle assembler refactoring changes -- Merge pull request [#170](https://github.com/0xPolygonMiden/compiler/pull/170) from 0xPolygonMiden/greenhat/i159-tx-kernel-func-11apr -- Merge pull request [#155](https://github.com/0xPolygonMiden/compiler/pull/155) from 0xPolygonMiden/greenhat/i144-stdlib -- remove repetitive words -- Merge pull request [#151](https://github.com/0xPolygonMiden/compiler/pull/151) from 0xPolygonMiden/greenhat/i144-native-felt -- Merge pull request [#140](https://github.com/0xPolygonMiden/compiler/pull/140) from 0xPolygonMiden/greenhat/i138-rust-miden-sdk -- remove `dylib` from `crate-type` in Miden SDK crates -- do not inline `miden_sdk_function_type` function -- add `FunctionType::abi` and ditch redundant `*FunctionType` -- remove `miden-abi-conversion` crate and move its code to -- assert Miden ABI function result types after the transformation -- add doc comments to the Miden ABI transformation API -- assert that function call results are the same after transformation -- cache parsed digest and stable import names; -- introduce ModuleTranslationState to hold resolved function -- ditch module and function names for import in favor of -- intern module name and all names used in the module -- remove invocation method from component imports/exports -- clean up todos, add comments -- ensure Wasm module name fallback is set as early as possible -- introduce `ComponentTranslator` -- draft basic wallet translation -- add Wasm component translation support to the integration tests; -- update frontend expect tests with format changes -- add formatter config, format most crates -- update rust toolchain to latest nightly -- Merge pull request [#100](https://github.com/0xPolygonMiden/compiler/pull/100) from 0xPolygonMiden/greenhat/i89-translate-wasm-cm -- move `LiftedFunctionType` to `miden-hir-type` crate -- use `digest` name for MAST root hashes; -- remove `MastRootHash` in favor of `RpoDigest`; -- use FxHashMap instead of HashMap in frontend-wasm; -- remove handling of Wasm 64-bit memory -- remove `indices` macro definition duplication; -- add README for Wasm frontend -- fix a comment, comment out debug prints; -- move `MastRootHash` and `Interface*` types to their modules; -- add missing doc comments -- remove `BuildIrComponentInput`; -- handle errors on Wasm component translation; -- rename `mod_info` parameter to `module` in Wasm module -- introduce `Module::global_name`, and make `Module::name_section` private; -- code clean up; -- rename `WasmTranslationConfig::module_name_fallback` to `sourse_name`; -- set up mdbook deploy -- add guides for compiling rust->masm -- extract Wasm component section parsing into separate methods -- convert various `Translator` methods to functions -- extract Wasm core module sections parsing to a separate methods -- remove unused variables -- update wasmparser to v0.118.1 -- enable `rust_array` frontend test after block inline pass fixed -- lazy IR compilation in integration tests -- move module specific code to separate module in frontend-wasm -- remove unused dependencies -- Merge pull request [#61](https://github.com/0xPolygonMiden/compiler/pull/61) from 0xPolygonMiden/greenhat/cargo-ext-i60 -- make `WasmTranslationConfig::module_name_fallback` non-optional -- switch from emiting MASM in CodegenStage, and switch to output folder in cargo extension -- remove `miden_frontend_wasm::translate_program` -- add integration tests for comparison instructions -- fix build after rebase -- implement `gt_u` instruction semantic test, add `translate_program` to Wasm frontend -- compile rust app (cargo project) to masm, run both and compare results; -- temporarily disable dlmalloc test -- finalize pass refactoring, implement driver -- update tests broken due to formatter changes -- initial parser implementation -- demangle function names in Rust compilation tests -- update expected IR for dlmalloc (switch to *_imm ops) -- add a debug assert to not allow declaring a block predecessor twice -- more readable `mem_total_pages` implementation -- use `*_imm` op variant where possible -- code cleanup -- remove `ValueData::Alias` -- update expected IR for dlmalloc test -- use `ModuleFunctionBuilder` instead of `FunctionBuilder` in `FunctionBuilderExt` -- ignore dlmalloc test because hash part in mangled function names is not stable enough -- fix build after rebase -- use `panic_immediate_abort` std feature to avoid core::fmt (uses `call_indirect`) -- Rust code with `div`, `rem`, `shr` signed and unsigned ops -- Rust code using dlmalloc in no_std -- remove float point ops translation; -- make `translation_utils::block_with_params` a `FunctionBuilderExt` method -- move `module_env` to the root of the crate, remove `environ` module -- move module_translator tests to code_translator test, -- `check_ir_files` variant with expected wat and mir files; -- print types on type mismatch error when defining a variable; -- avoid unnecessary allocation by making `DataSegment::data` a reference -- remove caching for Rust -> Wasm compilation artifacts in tests -- skip `@producers` Wasm section when printing wasm in Rust compilation tests; -- remove `FuncEnvironment` and use `ModuleInfo` directly -- fix build after rebase -- fix the test for unsupported ops -- add BrTable(commented) to unsupported instructions test -- remove todo! for Wasm `select` and `unreachable` -- import `Type::*` and remove repetitive `Type::`; -- add test for unsupported Wasm spec v1 instructions -- re-word unsupported Wasm feature error message; -- silence diagnostic output in tests -- add per-instruction test for every implemented Wasm instruction -- move all unsupported Wasm ops to catch-all case -- cleanup rust compilation test; -- make a temp dir for Rust->Wasm compilation tests artifacts -- add Rust->Wasm->IR Fibonacci example -- update expected module name in tests -- better var names, cleanup comments -- provide some initial usage instructions -- Initial commit diff --git a/frontend-wasm/Cargo.toml b/frontend-wasm/Cargo.toml deleted file mode 100644 index dfa63d375..000000000 --- a/frontend-wasm/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "midenc-frontend-wasm" -description = "Wasm frontend for the Miden compiler" -version.workspace = true -rust-version.workspace = true -authors.workspace = true -repository.workspace = true -categories.workspace = true -keywords.workspace = true -license.workspace = true -readme.workspace = true -edition.workspace = true - -[dependencies] -anyhow.workspace = true -addr2line = "0.24" -derive_more.workspace = true -gimli = { version = "0.31", default-features = false, features = [ - 'read', - 'std', -] } -indexmap.workspace = true -log.workspace = true -miden-core.workspace = true -midenc-hir.workspace = true -midenc-hir-type.workspace = true -midenc-session.workspace = true -rustc-hash.workspace = true -smallvec.workspace = true -thiserror.workspace = true -wasmparser = "0.214" - -[dev-dependencies] -wat.workspace = true -expect-test.workspace = true diff --git a/frontend-wasm/src/code_translator/mod.rs b/frontend-wasm/src/code_translator/mod.rs deleted file mode 100644 index a77d294ba..000000000 --- a/frontend-wasm/src/code_translator/mod.rs +++ /dev/null @@ -1,1200 +0,0 @@ -//! This module contains the bulk of the code performing the translation between -//! WebAssembly and Miden IR. -//! -//! The translation is done in one pass, opcode by opcode. Two main data structures are used during -//! code translations: the value stack and the control stack. The value stack mimics the execution -//! of the WebAssembly stack machine: each instruction result is pushed onto the stack and -//! instruction arguments are popped off the stack. Similarly, when encountering a control flow -//! block, it is pushed onto the control stack and popped off when encountering the corresponding -//! `End`. -//! -//! Another data structure, the translation state, records information concerning unreachable code -//! status and about if inserting a return at the end of the function is necessary. -//! -//! Based on Cranelift's Wasm -> CLIF translator v11.0.0 - -use midenc_hir::{ - cranelift_entity::packed_option::ReservedValue, - diagnostics::{DiagnosticsHandler, IntoDiagnostic, Report, Severity, SourceSpan}, - Block, FieldElement, Immediate, Inst, InstBuilder, Type, - Type::*, - Value, -}; -use wasmparser::{MemArg, Operator}; - -use crate::{ - error::WasmResult, - intrinsics::{convert_intrinsics_call, is_miden_intrinsics_module}, - miden_abi::{is_miden_abi_module, transform::transform_miden_abi_call}, - module::{ - func_translation_state::{ControlStackFrame, ElseData, FuncTranslationState}, - function_builder_ext::FunctionBuilderExt, - module_translation_state::ModuleTranslationState, - types::{ir_type, BlockType, FuncIndex, GlobalIndex, ModuleTypes}, - Module, - }, - ssa::Variable, - unsupported_diag, -}; - -#[cfg(test)] -mod tests; - -#[cfg(test)] -mod tests_unsupported; - -/// Translates wasm operators into Miden IR instructions. -#[allow(clippy::too_many_arguments)] -pub fn translate_operator( - op: &Operator, - builder: &mut FunctionBuilderExt, - state: &mut FuncTranslationState, - module_state: &mut ModuleTranslationState, - module: &Module, - mod_types: &ModuleTypes, - diagnostics: &DiagnosticsHandler, - span: SourceSpan, -) -> WasmResult<()> { - if !state.reachable { - translate_unreachable_operator(op, builder, state, mod_types, diagnostics, span)?; - return Ok(()); - } - - // Given that we believe the current block is reachable, the FunctionBuilderExt ought to agree. - debug_assert!(!builder.is_unreachable()); - - match op { - /********************************** Locals **************************************** - * `get_local` and `set_local` are treated as non-SSA variables and will completely - * disappear in the Miden IR - ***********************************************************************************/ - Operator::LocalGet { local_index } => { - let val = builder.use_var(Variable::from_u32(*local_index)); - state.push1(val); - } - Operator::LocalSet { local_index } => { - let val = state.pop1(); - let var = Variable::from_u32(*local_index); - let expected_ty = builder.variable_type(var); - let value_ty = builder.data_flow_graph().value_type(val); - let val = if expected_ty != value_ty { - if expected_ty == &I32 && value_ty == &U32 { - builder.ins().bitcast(val, I32, span) - } else if expected_ty == &I64 && value_ty == &U64 { - builder.ins().bitcast(val, I64, span) - } else { - let expected_ty = expected_ty.clone(); - builder.ins().cast(val, expected_ty, span) - } - } else { - val - }; - builder.def_var(var, val); - } - Operator::LocalTee { local_index } => { - let val = state.peek1(); - builder.def_var(Variable::from_u32(*local_index), val); - } - /********************************** Globals ****************************************/ - Operator::GlobalGet { global_index } => { - let global_index = GlobalIndex::from_u32(*global_index); - let name = module.global_name(global_index); - let ty = ir_type(module.globals[global_index].ty, diagnostics)?; - state.push1(builder.ins().load_symbol(name.as_str(), ty, span)); - } - Operator::GlobalSet { global_index } => { - let global_index = GlobalIndex::from_u32(*global_index); - let name = module.global_name(global_index); - let ty = ir_type(module.globals[global_index].ty, diagnostics)?; - let ptr = builder.ins().symbol_addr(name.as_str(), Ptr(ty.clone().into()), span); - let val = state.pop1(); - builder.ins().store(ptr, val, span); - } - /********************************* Stack misc **************************************/ - Operator::Drop => _ = state.pop1(), - Operator::Select => { - let (arg1, arg2, cond) = state.pop3(); - // if cond is not 0, return arg1, else return arg2 - // https://www.w3.org/TR/wasm-core-1/#-hrefsyntax-instr-parametricmathsfselect%E2%91%A0 - // cond is expected to be an i32 - let cond_i1 = builder.ins().neq_imm(cond, Immediate::I32(0), span); - state.push1(builder.ins().select(cond_i1, arg1, arg2, span)); - } - Operator::TypedSelect { ty } => { - let (arg1, arg2, cond) = state.pop3(); - match ty { - wasmparser::ValType::F32 => { - let cond = - builder.ins().gt_imm(cond, Immediate::Felt(midenc_hir::Felt::ZERO), span); - state.push1(builder.ins().select(cond, arg1, arg2, span)); - } - wasmparser::ValType::I32 => { - let cond = builder.ins().neq_imm(cond, Immediate::I32(0), span); - state.push1(builder.ins().select(cond, arg1, arg2, span)); - } - wasmparser::ValType::I64 => { - let cond = builder.ins().neq_imm(cond, Immediate::I64(0), span); - state.push1(builder.ins().select(cond, arg1, arg2, span)); - } - ty => panic!("unsupported value type for 'select': {ty}"), - } - } - Operator::Unreachable => { - builder.ins().unreachable(span); - state.reachable = false; - } - Operator::Nop => {} - /***************************** Control flow blocks *********************************/ - Operator::Block { blockty } => { - translate_block(blockty, builder, state, mod_types, diagnostics, span)? - } - Operator::Loop { blockty } => { - translate_loop(blockty, builder, state, mod_types, diagnostics, span)? - } - Operator::If { blockty } => { - translate_if(blockty, state, builder, mod_types, diagnostics, span)? - } - Operator::Else => translate_else(state, builder, span)?, - Operator::End => translate_end(state, builder, span), - - /**************************** Branch instructions *********************************/ - Operator::Br { relative_depth } => translate_br(state, relative_depth, builder, span), - Operator::BrIf { relative_depth } => { - translate_br_if(*relative_depth, builder, state, span)? - } - Operator::BrTable { targets } => translate_br_table(targets, state, builder, span)?, - Operator::Return => translate_return(state, builder, diagnostics, span)?, - /************************************ Calls ****************************************/ - Operator::Call { function_index } => { - translate_call( - state, - module_state, - builder, - FuncIndex::from_u32(*function_index), - span, - diagnostics, - )?; - } - Operator::CallIndirect { - type_index: _, - table_index: _, - } => { - // TODO: - } - /******************************* Memory management *********************************/ - Operator::MemoryGrow { .. } => { - let arg = state.pop1_bitcasted(U32, builder, span); - state.push1(builder.ins().mem_grow(arg, span)); - } - Operator::MemorySize { .. } => { - // Return total Miden memory size - state.push1(builder.ins().mem_size(span)); - } - /******************************* Bulk memory operations *********************************/ - Operator::MemoryCopy { dst_mem, src_mem } => { - // See semantics at https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#memorycopy-instruction - if *src_mem == 0 && src_mem == dst_mem { - let len = state.pop1(); - let src_i32 = state.pop1(); - let dst_i32 = state.pop1(); - let dst = prepare_addr(dst_i32, &U8, None, builder, span); - let src = prepare_addr(src_i32, &U8, None, builder, span); - builder.ins().memcpy(src, dst, len, span); - } else { - unsupported_diag!(diagnostics, "MemoryCopy: only single memory is supported"); - } - } - Operator::MemoryFill { mem } => { - // See semantics at https://webassembly.github.io/spec/core/exec/instructions.html#exec-memory-fill - if *mem != 0 { - unsupported_diag!(diagnostics, "MemoryFill: only single memory is supported"); - } - let num_bytes = state.pop1(); - let value = state.pop1(); - let dst_i32 = state.pop1(); - let value = builder.ins().trunc(value, Type::U8, span); - let num_bytes = builder.ins().bitcast(num_bytes, Type::U32, span); - let dst = prepare_addr(dst_i32, &U8, None, builder, span); - builder.ins().memset(dst, num_bytes, value, span); - } - /******************************* Load instructions ***********************************/ - Operator::I32Load8U { memarg } => { - translate_load_zext(U8, I32, memarg, state, builder, span) - } - Operator::I32Load16U { memarg } => { - translate_load_zext(U16, I32, memarg, state, builder, span) - } - Operator::I32Load8S { memarg } => { - translate_load_sext(I8, I32, memarg, state, builder, span); - } - Operator::I32Load16S { memarg } => { - translate_load_sext(I16, I32, memarg, state, builder, span); - } - Operator::I64Load8U { memarg } => { - translate_load_zext(U8, I64, memarg, state, builder, span) - } - Operator::I64Load16U { memarg } => { - translate_load_zext(U16, I64, memarg, state, builder, span) - } - Operator::I64Load8S { memarg } => { - translate_load_sext(I8, I64, memarg, state, builder, span); - } - Operator::I64Load16S { memarg } => { - translate_load_sext(I16, I64, memarg, state, builder, span); - } - Operator::I64Load32S { memarg } => { - translate_load_sext(I32, I64, memarg, state, builder, span) - } - Operator::I64Load32U { memarg } => { - translate_load_zext(U32, I64, memarg, state, builder, span) - } - Operator::I32Load { memarg } => translate_load(I32, memarg, state, builder, span), - Operator::I64Load { memarg } => translate_load(I64, memarg, state, builder, span), - Operator::F32Load { memarg } => translate_load(Felt, memarg, state, builder, span), - /****************************** Store instructions ***********************************/ - Operator::I32Store { memarg } => translate_store(I32, memarg, state, builder, span), - Operator::I64Store { memarg } => translate_store(I64, memarg, state, builder, span), - Operator::F32Store { memarg } => translate_store(Felt, memarg, state, builder, span), - Operator::I32Store8 { memarg } | Operator::I64Store8 { memarg } => { - translate_store(U8, memarg, state, builder, span); - } - Operator::I32Store16 { memarg } | Operator::I64Store16 { memarg } => { - translate_store(U16, memarg, state, builder, span); - } - Operator::I64Store32 { memarg } => translate_store(U32, memarg, state, builder, span), - /****************************** Nullary Operators **********************************/ - Operator::I32Const { value } => state.push1(builder.ins().i32(*value, span)), - Operator::I64Const { value } => state.push1(builder.ins().i64(*value, span)), - - /******************************* Unary Operators *************************************/ - Operator::I32Clz | Operator::I64Clz => { - let val = state.pop1(); - let count = builder.ins().clz(val, span); - // To ensure we match the Wasm semantics, treat the output of clz as an i32 - state.push1(builder.ins().bitcast(count, Type::I32, span)); - } - Operator::I32Ctz | Operator::I64Ctz => { - let val = state.pop1(); - let count = builder.ins().ctz(val, span); - // To ensure we match the Wasm semantics, treat the output of ctz as an i32 - state.push1(builder.ins().bitcast(count, Type::I32, span)); - } - Operator::I32Popcnt | Operator::I64Popcnt => { - let val = state.pop1(); - let count = builder.ins().popcnt(val, span); - // To ensure we match the Wasm semantics, treat the output of popcnt as an i32 - state.push1(builder.ins().bitcast(count, Type::I32, span)); - } - Operator::I32Extend8S | Operator::I32Extend16S => { - let val = state.pop1(); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I64ExtendI32S => { - let val = state.pop1(); - state.push1(builder.ins().sext(val, I64, span)); - } - Operator::I64ExtendI32U => { - let val = state.pop1(); - let u32_val = builder.ins().bitcast(val, U32, span); - let u64_val = builder.ins().zext(u32_val, U64, span); - let i64_val = builder.ins().bitcast(u64_val, I64, span); - state.push1(i64_val); - } - Operator::I32WrapI64 => { - let val = state.pop1(); - state.push1(builder.ins().trunc(val, I32, span)); - } - /****************************** Binary Operators ************************************/ - Operator::I32Add | Operator::I64Add => { - let (arg1, arg2) = state.pop2(); - // wrapping because the result is mod 2^N - // https://www.w3.org/TR/wasm-core-1/#op-iadd - let value_type = builder.data_flow_graph().value_type(arg1); - let arg2 = if value_type != builder.data_flow_graph().value_type(arg2) { - let value_type = value_type.clone(); - builder.ins().bitcast(arg2, value_type, span) - } else { - arg2 - }; - state.push1(builder.ins().add_wrapping(arg1, arg2, span)); - } - Operator::I32And | Operator::I64And => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().band(arg1, arg2, span)); - } - Operator::I32Or | Operator::I64Or => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().bor(arg1, arg2, span)); - } - Operator::I32Xor | Operator::I64Xor => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().bxor(arg1, arg2, span)); - } - Operator::I32Shl => { - let (arg1, arg2) = state.pop2(); - // wrapping shift semantics drop any bits that would cause - // the shift to exceed the bitwidth of the type - let arg2 = builder.ins().bitcast(arg2, U32, span); - state.push1(builder.ins().shl(arg1, arg2, span)); - } - Operator::I64Shl => { - let (arg1, arg2) = state.pop2(); - // wrapping shift semantics drop any bits that would cause - // the shift to exceed the bitwidth of the type - let arg2 = builder.ins().cast(arg2, U32, span); - state.push1(builder.ins().shl(arg1, arg2, span)); - } - Operator::I32ShrU => { - let (arg1, arg2) = state.pop2_bitcasted(U32, builder, span); - // wrapping shift semantics drop any bits that would cause - // the shift to exceed the bitwidth of the type - let val = builder.ins().shr(arg1, arg2, span); - state.push1(builder.ins().bitcast(val, I32, span)); - } - Operator::I64ShrU => { - let (arg1, arg2) = state.pop2(); - let arg1 = builder.ins().bitcast(arg1, U64, span); - let arg2 = builder.ins().cast(arg2, U32, span); - // wrapping shift semantics drop any bits that would cause - // the shift to exceed the bitwidth of the type - let val = builder.ins().shr(arg1, arg2, span); - state.push1(builder.ins().bitcast(val, I64, span)); - } - Operator::I32ShrS => { - let (arg1, arg2) = state.pop2(); - // wrapping shift semantics drop any bits that would cause - // the shift to exceed the bitwidth of the type - let arg2 = builder.ins().bitcast(arg2, Type::U32, span); - state.push1(builder.ins().shr(arg1, arg2, span)); - } - Operator::I64ShrS => { - let (arg1, arg2) = state.pop2(); - // wrapping shift semantics drop any bits that would cause - // the shift to exceed the bitwidth of the type - let arg2 = builder.ins().cast(arg2, Type::U32, span); - state.push1(builder.ins().shr(arg1, arg2, span)); - } - Operator::I32Rotl => { - let (arg1, arg2) = state.pop2(); - let arg2 = builder.ins().bitcast(arg2, Type::U32, span); - state.push1(builder.ins().rotl(arg1, arg2, span)); - } - Operator::I64Rotl => { - let (arg1, arg2) = state.pop2(); - let arg2 = builder.ins().cast(arg2, Type::U32, span); - state.push1(builder.ins().rotl(arg1, arg2, span)); - } - Operator::I32Rotr => { - let (arg1, arg2) = state.pop2(); - let arg2 = builder.ins().bitcast(arg2, Type::U32, span); - state.push1(builder.ins().rotr(arg1, arg2, span)); - } - Operator::I64Rotr => { - let (arg1, arg2) = state.pop2(); - let arg2 = builder.ins().cast(arg2, Type::U32, span); - state.push1(builder.ins().rotr(arg1, arg2, span)); - } - Operator::I32Sub | Operator::I64Sub => { - let (arg1, arg2) = state.pop2(); - // wrapping because the result is mod 2^N - // https://www.w3.org/TR/wasm-core-1/#op-isub - state.push1(builder.ins().sub_wrapping(arg1, arg2, span)); - } - Operator::I32Mul | Operator::I64Mul => { - let (arg1, arg2) = state.pop2(); - // wrapping because the result is mod 2^N - // https://www.w3.org/TR/wasm-core-1/#op-imul - state.push1(builder.ins().mul_wrapping(arg1, arg2, span)); - } - Operator::I32DivS | Operator::I64DivS => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().div_unchecked(arg1, arg2, span)); - } - Operator::I32DivU => { - let (arg1, arg2) = state.pop2_bitcasted(U32, builder, span); - let val = builder.ins().div_unchecked(arg1, arg2, span); - state.push1(builder.ins().bitcast(val, I32, span)); - } - Operator::I64DivU => { - let (arg1, arg2) = state.pop2_bitcasted(U64, builder, span); - let val = builder.ins().div_unchecked(arg1, arg2, span); - state.push1(builder.ins().bitcast(val, I64, span)); - } - Operator::I32RemU => { - let (arg1, arg2) = state.pop2_bitcasted(U32, builder, span); - let val = builder.ins().r#mod_checked(arg1, arg2, span); - state.push1(builder.ins().bitcast(val, I32, span)); - } - Operator::I64RemU => { - let (arg1, arg2) = state.pop2_bitcasted(U64, builder, span); - let val = builder.ins().r#mod_checked(arg1, arg2, span); - state.push1(builder.ins().bitcast(val, I64, span)); - } - Operator::I32RemS | Operator::I64RemS => { - let (arg1, arg2) = state.pop2(); - state.push1(builder.ins().r#mod_checked(arg1, arg2, span)); - } - /**************************** Comparison Operators **********************************/ - Operator::I32LtU => { - let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span); - let val = builder.ins().lt(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I64LtU => { - let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span); - let val = builder.ins().lt(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I32LtS => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().lt(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I64LtS => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().lt(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I32LeU => { - let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span); - let val = builder.ins().lte(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I64LeU => { - let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span); - let val = builder.ins().lte(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I32LeS => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().lte(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I64LeS => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().lte(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I32GtU => { - let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span); - let val = builder.ins().gt(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I64GtU => { - let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span); - let val = builder.ins().gt(arg0, arg1, span); - state.push1(builder.ins().sext(val, I32, span)); - } - Operator::I32GtS | Operator::I64GtS => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().gt(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I32GeU => { - let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span); - let val = builder.ins().gte(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I64GeU => { - let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span); - let val = builder.ins().gte(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I32GeS => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().gte(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I64GeS => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().gte(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I32Eqz => { - let arg = state.pop1(); - let val = builder.ins().eq_imm(arg, Immediate::I32(0), span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I64Eqz => { - let arg = state.pop1(); - let val = builder.ins().eq_imm(arg, Immediate::I64(0), span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I32Eq => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().eq(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I64Eq => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().eq(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I32Ne => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().neq(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - Operator::I64Ne => { - let (arg0, arg1) = state.pop2(); - let val = builder.ins().neq(arg0, arg1, span); - state.push1(builder.ins().zext(val, I32, span)); - } - op => { - unsupported_diag!(diagnostics, "Wasm op {:?} is not supported", op); - } - }; - Ok(()) -} - -fn translate_load( - ptr_ty: Type, - memarg: &MemArg, - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) { - let addr_int = state.pop1(); - let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span); - state.push1(builder.ins().load(addr, span)); -} - -fn translate_load_sext( - ptr_ty: Type, - sext_ty: Type, - memarg: &MemArg, - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) { - let addr_int = state.pop1(); - let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span); - let val = builder.ins().load(addr, span); - let sext_val = builder.ins().sext(val, sext_ty, span); - state.push1(sext_val); -} - -fn translate_load_zext( - ptr_ty: Type, - zext_ty: Type, - memarg: &MemArg, - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) { - assert!(ptr_ty.is_unsigned_integer()); - let addr_int = state.pop1(); - let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span); - let val = builder.ins().load(addr, span); - let sext_val = builder.ins().zext(val, zext_ty, span); - state.push1(sext_val); -} - -fn translate_store( - ptr_ty: Type, - memarg: &MemArg, - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) { - let (addr_int, val) = state.pop2(); - let val_ty = builder.data_flow_graph().value_type(val); - let arg = if &ptr_ty != val_ty { - if ptr_ty.size_in_bits() == val_ty.size_in_bits() { - builder.ins().bitcast(val, ptr_ty.clone(), span) - } else if ptr_ty.is_unsigned_integer() && val_ty.is_signed_integer() { - let unsigned_val_ty = val_ty.as_unsigned(); - let uval = builder.ins().bitcast(val, unsigned_val_ty, span); - builder.ins().trunc(uval, ptr_ty.clone(), span) - } else { - builder.ins().trunc(val, ptr_ty.clone(), span) - } - } else { - val - }; - let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span); - builder.ins().store(addr, arg, span); -} - -fn prepare_addr( - addr_int: Value, - ptr_ty: &Type, - memarg: Option<&MemArg>, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) -> Value { - let addr_int_ty = builder.data_flow_graph().value_type(addr_int); - let addr_u32 = if addr_int_ty == &U32 { - addr_int - } else if addr_int_ty == &I32 { - builder.ins().bitcast(addr_int, U32, span) - } else if matches!(addr_int_ty, Ptr(_)) { - builder.ins().ptrtoint(addr_int, U32, span) - } else { - panic!("unexpected type used as pointer value: {addr_int_ty}"); - }; - let mut full_addr_int = addr_u32; - if let Some(memarg) = memarg { - if memarg.offset != 0 { - full_addr_int = - builder - .ins() - .add_imm_checked(addr_u32, Immediate::U32(memarg.offset as u32), span); - } - // TODO(pauls): For now, asserting alignment helps us catch mistakes/bugs, but we should - // probably make this something that can be disabled to avoid the overhead in release builds - if memarg.align > 0 { - // Generate alignment assertion - aligned addresses should always produce 0 here - let align_offset = builder.ins().mod_imm_unchecked( - full_addr_int, - Immediate::U32(2u32.pow(memarg.align as u32)), - span, - ); - builder.ins().assertz_with_error( - align_offset, - midenc_hir::ASSERT_FAILED_ALIGNMENT, - span, - ); - } - }; - builder.ins().inttoptr(full_addr_int, Type::Ptr(ptr_ty.clone().into()), span) -} - -fn translate_call( - func_state: &mut FuncTranslationState, - module_state: &mut ModuleTranslationState, - builder: &mut FunctionBuilderExt, - function_index: FuncIndex, - span: SourceSpan, - diagnostics: &DiagnosticsHandler, -) -> WasmResult<()> { - let func_id = - module_state.get_direct_func(builder.data_flow_graph_mut(), function_index, diagnostics)?; - let wasm_sig = module_state.signature(function_index); - let num_wasm_args = wasm_sig.params().len(); - let args = func_state.peekn(num_wasm_args); - if is_miden_intrinsics_module(func_id.module.as_symbol()) { - let results = convert_intrinsics_call(func_id, args, builder, span); - func_state.popn(num_wasm_args); - func_state.pushn(&results); - } else if is_miden_abi_module(func_id.module.as_symbol()) { - // Miden SDK function call, transform the call to the Miden ABI if needed - let results = transform_miden_abi_call(func_id, args, builder, span, diagnostics); - assert_eq!( - wasm_sig.results().len(), - results.len(), - "Adapted function call results quantity are not the same as the original Wasm \ - function results quantity for function {}", - func_id - ); - assert_eq!( - wasm_sig.results().iter().map(|p| &p.ty).collect::>(), - results - .iter() - .map(|r| builder.data_flow_graph().value_type(*r)) - .collect::>(), - "Adapted function call result types are not the same as the original Wasm function \ - result types for function {}", - func_id - ); - func_state.popn(num_wasm_args); - func_state.pushn(&results); - } else { - // no transformation needed - let call = builder.ins().call(func_id, args, span); - let results = builder.inst_results(call); - func_state.popn(num_wasm_args); - func_state.pushn(results); - }; - Ok(()) -} - -fn translate_return( - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - diagnostics: &DiagnosticsHandler, - span: SourceSpan, -) -> WasmResult<()> { - let return_count = { - let frame = &mut state.control_stack[0]; - frame.num_return_values() - }; - { - let return_args = match return_count { - 0 => None, - 1 => Some(*state.peekn_mut(return_count).first().unwrap()), - _ => { - unsupported_diag!(diagnostics, "Multiple values are not supported"); - } - }; - - builder.ins().ret(return_args, span); - } - state.popn(return_count); - state.reachable = false; - Ok(()) -} - -fn translate_br( - state: &mut FuncTranslationState, - relative_depth: &u32, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) { - let i = state.control_stack.len() - 1 - (*relative_depth as usize); - let (return_count, br_destination) = { - let frame = &mut state.control_stack[i]; - // We signal that all the code that follows until the next End is unreachable - frame.set_branched_to_exit(); - let return_count = if frame.is_loop() { - frame.num_param_values() - } else { - frame.num_return_values() - }; - (return_count, frame.br_destination()) - }; - let destination_args = state.peekn_mut(return_count); - builder.ins().br(br_destination, destination_args, span); - state.popn(return_count); - state.reachable = false; -} - -fn translate_br_if( - relative_depth: u32, - builder: &mut FunctionBuilderExt, - state: &mut FuncTranslationState, - span: SourceSpan, -) -> WasmResult<()> { - let cond = state.pop1_bitcasted(Type::I32, builder, span); - let (br_destination, inputs) = translate_br_if_args(relative_depth, state); - let next_block = builder.create_block(); - let then_dest = br_destination; - let then_args = inputs; - let else_dest = next_block; - let else_args = &[]; - // cond is expected to be a i32 value - let cond_i1 = builder.ins().neq_imm(cond, Immediate::I32(0), span); - builder.ins().cond_br(cond_i1, then_dest, then_args, else_dest, else_args, span); - builder.seal_block(next_block); // The only predecessor is the current block. - builder.switch_to_block(next_block); - Ok(()) -} - -fn translate_br_if_args( - relative_depth: u32, - state: &mut FuncTranslationState, -) -> (Block, &mut [Value]) { - let i = state.control_stack.len() - 1 - (relative_depth as usize); - let (return_count, br_destination) = { - let frame = &mut state.control_stack[i]; - // The values returned by the branch are still available for the reachable - // code that comes after it - frame.set_branched_to_exit(); - let return_count = if frame.is_loop() { - frame.num_param_values() - } else { - frame.num_return_values() - }; - (return_count, frame.br_destination()) - }; - let inputs = state.peekn_mut(return_count); - (br_destination, inputs) -} - -fn translate_br_table( - br_targets: &wasmparser::BrTable<'_>, - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) -> Result<(), Report> { - let mut targets = Vec::default(); - for depth in br_targets.targets() { - let depth = depth.into_diagnostic()?; - - targets.push(depth); - } - targets.sort(); - - let default_depth = br_targets.default(); - let min_depth = - core::cmp::min(targets.iter().copied().min().unwrap_or(default_depth), default_depth); - - let argc = { - let i = state.control_stack.len() - 1 - (min_depth as usize); - let min_depth_frame = &state.control_stack[i]; - if min_depth_frame.is_loop() { - min_depth_frame.num_param_values() - } else { - min_depth_frame.num_return_values() - } - }; - - let default_block = { - let i = state.control_stack.len() - 1 - (default_depth as usize); - let frame = &mut state.control_stack[i]; - frame.set_branched_to_exit(); - frame.br_destination() - }; - - let val = state.pop1(); - let val = if builder.data_flow_graph().value_type(val) != &U32 { - builder.ins().cast(val, U32, span) - } else { - val - }; - - let switch_builder = builder.ins().switch(val, span); - let switch_builder = - targets.into_iter().enumerate().fold(switch_builder, |acc, (label_idx, depth)| { - let block = { - let i = state.control_stack.len() - 1 - (depth as usize); - let frame = &mut state.control_stack[i]; - frame.set_branched_to_exit(); - frame.br_destination() - }; - let args = state.peekn_mut(argc); - acc.case(label_idx as u32, block, args) - }); - switch_builder.or_else(default_block, state.peekn_mut(argc)); - - state.popn(argc); - state.reachable = false; - Ok(()) -} - -fn translate_block( - blockty: &wasmparser::BlockType, - builder: &mut FunctionBuilderExt, - state: &mut FuncTranslationState, - mod_types: &ModuleTypes, - diagnostics: &DiagnosticsHandler, - span: SourceSpan, -) -> WasmResult<()> { - let blockty = BlockType::from_wasm(blockty, mod_types, diagnostics)?; - let next = builder.create_block_with_params(blockty.results.clone(), span); - state.push_block(next, blockty.params.len(), blockty.results.len()); - Ok(()) -} - -fn translate_end( - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) { - // The `End` instruction pops the last control frame from the control stack, seals - // the destination block (since `br` instructions targeting it only appear inside the - // block and have already been translated) and modify the value stack to use the - // possible `Block`'s arguments values. - let frame = state.control_stack.pop().unwrap(); - let next_block = frame.following_code(); - let return_count = frame.num_return_values(); - let return_args = state.peekn_mut(return_count); - - builder.ins().br(next_block, return_args, span); - - // You might expect that if we just finished an `if` block that - // didn't have a corresponding `else` block, then we would clean - // up our duplicate set of parameters that we pushed earlier - // right here. However, we don't have to explicitly do that, - // since we truncate the stack back to the original height - // below. - - builder.switch_to_block(next_block); - builder.seal_block(next_block); - - // If it is a loop we also have to seal the body loop block - if let ControlStackFrame::Loop { header, .. } = frame { - builder.seal_block(header) - } - - frame.truncate_value_stack_to_original_size(&mut state.stack); - state.stack.extend_from_slice(builder.block_params(next_block)); -} - -fn translate_else( - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) -> WasmResult<()> { - let i = state.control_stack.len() - 1; - match state.control_stack[i] { - ControlStackFrame::If { - ref else_data, - head_is_reachable, - ref mut consequent_ends_reachable, - num_return_values, - ref blocktype, - destination, - .. - } => { - // We finished the consequent, so record its final - // reachability state. - debug_assert!(consequent_ends_reachable.is_none()); - *consequent_ends_reachable = Some(state.reachable); - - if head_is_reachable { - // We have a branch from the head of the `if` to the `else`. - state.reachable = true; - - // Ensure we have a block for the `else` block (it may have - // already been pre-allocated, see `ElseData` for details). - let else_block = match *else_data { - ElseData::NoElse { - branch_inst, - placeholder, - } => { - debug_assert_eq!(blocktype.params.len(), num_return_values); - let else_block = - builder.create_block_with_params(blocktype.params.clone(), span); - let params_len = blocktype.params.len(); - builder.ins().br(destination, state.peekn(params_len), span); - state.popn(params_len); - - builder.change_jump_destination(branch_inst, placeholder, else_block); - builder.seal_block(else_block); - else_block - } - ElseData::WithElse { else_block } => { - builder.ins().br(destination, state.peekn(num_return_values), span); - state.popn(num_return_values); - else_block - } - }; - - // You might be expecting that we push the parameters for this - // `else` block here, something like this: - // - // state.pushn(&control_stack_frame.params); - // - // We don't do that because they are already on the top of the stack - // for us: we pushed the parameters twice when we saw the initial - // `if` so that we wouldn't have to save the parameters in the - // `ControlStackFrame` as another `Vec` allocation. - - builder.switch_to_block(else_block); - - // We don't bother updating the control frame's `ElseData` - // to `WithElse` because nothing else will read it. - } - } - _ => unreachable!(), - }; - Ok(()) -} - -fn translate_if( - blockty: &wasmparser::BlockType, - state: &mut FuncTranslationState, - builder: &mut FunctionBuilderExt, - mod_types: &ModuleTypes, - diagnostics: &DiagnosticsHandler, - span: SourceSpan, -) -> WasmResult<()> { - let blockty = BlockType::from_wasm(blockty, mod_types, diagnostics)?; - let cond = state.pop1(); - // cond is expected to be a i32 value - let cond_i1 = builder.ins().neq_imm(cond, Immediate::I32(0), span); - let next_block = builder.create_block(); - let (destination, else_data) = if blockty.params.eq(&blockty.results) { - // It is possible there is no `else` block, so we will only - // allocate a block for it if/when we find the `else`. For now, - // we if the condition isn't true, then we jump directly to the - // destination block following the whole `if...end`. If we do end - // up discovering an `else`, then we will allocate a block for it - // and go back and patch the jump. - let destination = builder.create_block_with_params(blockty.results.clone(), span); - let branch_inst = builder.ins().cond_br( - cond_i1, - next_block, - &[], - destination, - state.peekn(blockty.params.len()), - span, - ); - ( - destination, - ElseData::NoElse { - branch_inst, - placeholder: destination, - }, - ) - } else { - // The `if` type signature is not valid without an `else` block, - // so we eagerly allocate the `else` block here. - let destination = builder.create_block_with_params(blockty.results.clone(), span); - let else_block = builder.create_block_with_params(blockty.params.clone(), span); - builder.ins().cond_br( - cond_i1, - next_block, - &[], - else_block, - state.peekn(blockty.params.len()), - span, - ); - builder.seal_block(else_block); - (destination, ElseData::WithElse { else_block }) - }; - builder.seal_block(next_block); - builder.switch_to_block(next_block); - state.push_if(destination, else_data, blockty.params.len(), blockty.results.len(), blockty); - Ok(()) -} - -fn translate_loop( - blockty: &wasmparser::BlockType, - builder: &mut FunctionBuilderExt, - state: &mut FuncTranslationState, - mod_types: &ModuleTypes, - diagnostics: &DiagnosticsHandler, - span: SourceSpan, -) -> WasmResult<()> { - let blockty = BlockType::from_wasm(blockty, mod_types, diagnostics)?; - let loop_body = builder.create_block_with_params(blockty.params.clone(), span); - let next = builder.create_block_with_params(blockty.results.clone(), span); - builder.ins().br(loop_body, state.peekn(blockty.params.len()), span); - state.push_loop(loop_body, next, blockty.params.len(), blockty.results.len()); - state.popn(blockty.params.len()); - state.stack.extend_from_slice(builder.block_params(loop_body)); - builder.switch_to_block(loop_body); - Ok(()) -} - -/// Deals with a Wasm instruction located in an unreachable portion of the code. Most of them -/// are dropped but special ones like `End` or `Else` signal the potential end of the unreachable -/// portion so the translation state must be updated accordingly. -fn translate_unreachable_operator( - op: &Operator, - builder: &mut FunctionBuilderExt, - state: &mut FuncTranslationState, - mod_types: &ModuleTypes, - diagnostics: &DiagnosticsHandler, - span: SourceSpan, -) -> WasmResult<()> { - debug_assert!(!state.reachable); - match *op { - Operator::If { blockty } => { - // Push a placeholder control stack entry. The if isn't reachable, - // so we don't have any branches anywhere. - let blockty = BlockType::from_wasm(&blockty, mod_types, diagnostics)?; - state.push_if( - Block::reserved_value(), - ElseData::NoElse { - branch_inst: Inst::reserved_value(), - placeholder: Block::reserved_value(), - }, - 0, - 0, - blockty, - ); - } - Operator::Loop { blockty: _ } | Operator::Block { blockty: _ } => { - state.push_block(Block::reserved_value(), 0, 0); - } - Operator::Else => { - let i = state.control_stack.len() - 1; - match state.control_stack[i] { - ControlStackFrame::If { - ref else_data, - head_is_reachable, - ref mut consequent_ends_reachable, - ref blocktype, - .. - } => { - debug_assert!(consequent_ends_reachable.is_none()); - *consequent_ends_reachable = Some(state.reachable); - - if head_is_reachable { - // We have a branch from the head of the `if` to the `else`. - state.reachable = true; - - let else_block = match *else_data { - ElseData::NoElse { - branch_inst, - placeholder, - } => { - let else_block = builder - .create_block_with_params(blocktype.params.clone(), span); - let frame = state.control_stack.last().unwrap(); - frame.truncate_value_stack_to_else_params(&mut state.stack); - - // We change the target of the branch instruction. - builder.change_jump_destination( - branch_inst, - placeholder, - else_block, - ); - builder.seal_block(else_block); - else_block - } - ElseData::WithElse { else_block } => { - let frame = state.control_stack.last().unwrap(); - frame.truncate_value_stack_to_else_params(&mut state.stack); - else_block - } - }; - - builder.switch_to_block(else_block); - - // Again, no need to push the parameters for the `else`, - // since we already did when we saw the original `if`. See - // the comment for translating `Operator::Else` in - // `translate_operator` for details. - } - } - _ => unreachable!(), - } - } - Operator::End => { - let stack = &mut state.stack; - let control_stack = &mut state.control_stack; - let frame = control_stack.pop().unwrap(); - - // Pop unused parameters from stack. - frame.truncate_value_stack_to_original_size(stack); - - let reachable_anyway = match frame { - // If it is a loop we also have to seal the body loop block - ControlStackFrame::Loop { header, .. } => { - builder.seal_block(header); - // And loops can't have branches to the end. - false - } - // If we never set `consequent_ends_reachable` then that means - // we are finishing the consequent now, and there was no - // `else`. Whether the following block is reachable depends only - // on if the head was reachable. - ControlStackFrame::If { - head_is_reachable, - consequent_ends_reachable: None, - .. - } => head_is_reachable, - // Since we are only in this function when in unreachable code, - // we know that the alternative just ended unreachable. Whether - // the following block is reachable depends on if the consequent - // ended reachable or not. - ControlStackFrame::If { - head_is_reachable, - consequent_ends_reachable: Some(consequent_ends_reachable), - .. - } => head_is_reachable && consequent_ends_reachable, - // All other control constructs are already handled. - _ => false, - }; - - if frame.exit_is_branched_to() || reachable_anyway { - builder.switch_to_block(frame.following_code()); - builder.seal_block(frame.following_code()); - - // And add the return values of the block but only if the next block is reachable - // (which corresponds to testing if the stack depth is 1) - stack.extend_from_slice(builder.block_params(frame.following_code())); - state.reachable = true; - } - } - _ => { - // We don't translate because this is unreachable code - } - } - - Ok(()) -} diff --git a/frontend-wasm/src/code_translator/tests.rs b/frontend-wasm/src/code_translator/tests.rs deleted file mode 100644 index 8fa4ff9ca..000000000 --- a/frontend-wasm/src/code_translator/tests.rs +++ /dev/null @@ -1,1548 +0,0 @@ -use core::fmt::Write; - -use expect_test::expect; -use midenc_hir::Ident; - -use crate::{test_utils::test_context, translate, WasmTranslationConfig}; - -/// Check IR generated for a Wasm op(s). -/// Wrap Wasm ops in a function and check the IR generated for the entry block of that function. -fn check_op(wat_op: &str, expected_ir: expect_test::Expect) { - let context = test_context(); - - let wat = format!( - r#" - (module - (memory (;0;) 16384) - (func $test_wrapper - {wat_op} - ) - )"#, - ); - let wasm = wat::parse_str(wat).unwrap(); - let module = translate(&wasm, &WasmTranslationConfig::default(), &context.session) - .unwrap() - .unwrap_one_module(); - let func = module.function(Ident::from("test_wrapper")).unwrap(); - // let fref = module.get_funcref_by_name("test_wrapper").unwrap(); - // let func = module.get_function(fref).unwrap(); - let entry_block = func.dfg.entry_block(); - // let entry_block_data = func.dfg.block_data(entry_block); - let entry_block_data = func.dfg.block(entry_block); - let mut w = String::new(); - // print instructions up to the branch to the exit block - for inst in entry_block_data - .insts() - .take_while(|inst| !func.dfg[*inst].opcode().is_branch()) - { - let inst_printer = midenc_hir::InstPrettyPrinter { - current_function: func.id, - id: inst, - dfg: &func.dfg, - }; - writeln!(&mut w, "{inst_printer}").unwrap(); - } - expected_ir.assert_eq(&w); -} - -#[test] -fn memory_grow() { - check_op( - r#" - i32.const 1 - memory.grow - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1)) - (let (v1 u32) (bitcast v0)) - (let (v2 i32) (memory.grow v1)) - "#]], - ) -} - -#[test] -fn memory_size() { - check_op( - r#" - memory.size - drop - "#, - expect![[r#" - (let (v0 u32) (memory.size)) - "#]], - ) -} - -#[test] -fn memory_copy() { - check_op( - r#" - i32.const 20 ;; dst - i32.const 10 ;; src - i32.const 1 ;; len - memory.copy - "#, - expect![[r#" - (let (v0 i32) (const.i32 20)) - (let (v1 i32) (const.i32 10)) - (let (v2 i32) (const.i32 1)) - (let (v3 u32) (bitcast v0)) - (let (v4 (ptr u8)) (inttoptr v3)) - (let (v5 u32) (bitcast v1)) - (let (v6 (ptr u8)) (inttoptr v5)) - (memcpy v6 v4 v2) - "#]], - ) -} - -#[test] -fn i32_load8_u() { - check_op( - r#" - i32.const 1024 - i32.load8_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 (ptr u8)) (inttoptr v1)) - (let (v3 u8) (load v2)) - (let (v4 i32) (zext v3)) - "#]], - ) -} - -#[test] -fn i32_load16_u() { - check_op( - r#" - i32.const 1024 - i32.load16_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 2)) - (assertz 250 v2) - (let (v3 (ptr u16)) (inttoptr v1)) - (let (v4 u16) (load v3)) - (let (v5 i32) (zext v4)) - "#]], - ) -} - -#[test] -fn i32_load8_s() { - check_op( - r#" - i32.const 1024 - i32.load8_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 (ptr i8)) (inttoptr v1)) - (let (v3 i8) (load v2)) - (let (v4 i32) (sext v3)) - "#]], - ) -} - -#[test] -fn i32_load16_s() { - check_op( - r#" - i32.const 1024 - i32.load16_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 2)) - (assertz 250 v2) - (let (v3 (ptr i16)) (inttoptr v1)) - (let (v4 i16) (load v3)) - (let (v5 i32) (sext v4)) - "#]], - ) -} - -#[test] -fn i64_load8_u() { - check_op( - r#" - i32.const 1024 - i64.load8_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 (ptr u8)) (inttoptr v1)) - (let (v3 u8) (load v2)) - (let (v4 i64) (zext v3)) - "#]], - ) -} - -#[test] -fn i64_load16_u() { - check_op( - r#" - i32.const 1024 - i64.load16_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 2)) - (assertz 250 v2) - (let (v3 (ptr u16)) (inttoptr v1)) - (let (v4 u16) (load v3)) - (let (v5 i64) (zext v4)) - "#]], - ) -} - -#[test] -fn i64_load8_s() { - check_op( - r#" - i32.const 1024 - i64.load8_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 (ptr i8)) (inttoptr v1)) - (let (v3 i8) (load v2)) - (let (v4 i64) (sext v3)) - "#]], - ) -} - -#[test] -fn i64_load16_s() { - check_op( - r#" - i32.const 1024 - i64.load16_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 2)) - (assertz 250 v2) - (let (v3 (ptr i16)) (inttoptr v1)) - (let (v4 i16) (load v3)) - (let (v5 i64) (sext v4)) - "#]], - ) -} - -#[test] -fn i64_load32_s() { - check_op( - r#" - i32.const 1024 - i64.load32_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 4)) - (assertz 250 v2) - (let (v3 (ptr i32)) (inttoptr v1)) - (let (v4 i32) (load v3)) - (let (v5 i64) (sext v4)) - "#]], - ) -} - -#[test] -fn i64_load32_u() { - check_op( - r#" - i32.const 1024 - i64.load32_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 4)) - (assertz 250 v2) - (let (v3 (ptr u32)) (inttoptr v1)) - (let (v4 u32) (load v3)) - (let (v5 i64) (zext v4)) - "#]], - ) -} - -#[test] -fn i32_load() { - check_op( - r#" - i32.const 1024 - i32.load - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 4)) - (assertz 250 v2) - (let (v3 (ptr i32)) (inttoptr v1)) - (let (v4 i32) (load v3)) - "#]], - ) -} - -#[test] -fn i64_load() { - check_op( - r#" - i32.const 1024 - i64.load - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (mod.unchecked v1 8)) - (assertz 250 v2) - (let (v3 (ptr i64)) (inttoptr v1)) - (let (v4 i64) (load v3)) - "#]], - ) -} - -#[test] -fn i32_store() { - check_op( - r#" - i32.const 1024 - i32.const 1 - i32.store - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (mod.unchecked v2 4)) - (assertz 250 v3) - (let (v4 (ptr i32)) (inttoptr v2)) - (store v4 v1) - "#]], - ) -} - -#[test] -fn i64_store() { - check_op( - r#" - i32.const 1024 - i64.const 1 - i64.store - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 i64) (const.i64 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (mod.unchecked v2 8)) - (assertz 250 v3) - (let (v4 (ptr i64)) (inttoptr v2)) - (store v4 v1) - "#]], - ) -} - -#[test] -fn i32_store8() { - check_op( - r#" - i32.const 1024 - i32.const 1 - i32.store8 - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v1)) - (let (v3 u8) (trunc v2)) - (let (v4 u32) (bitcast v0)) - (let (v5 (ptr u8)) (inttoptr v4)) - (store v5 v3) - "#]], - ) -} - -#[test] -fn i32_store16() { - check_op( - r#" - i32.const 1024 - i32.const 1 - i32.store16 - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v1)) - (let (v3 u16) (trunc v2)) - (let (v4 u32) (bitcast v0)) - (let (v5 u32) (mod.unchecked v4 2)) - (assertz 250 v5) - (let (v6 (ptr u16)) (inttoptr v4)) - (store v6 v3) - "#]], - ) -} - -#[test] -fn i64_store32() { - check_op( - r#" - i32.const 1024 - i64.const 1 - i64.store32 - "#, - expect![[r#" - (let (v0 i32) (const.i32 1024)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v1)) - (let (v3 u32) (trunc v2)) - (let (v4 u32) (bitcast v0)) - (let (v5 u32) (mod.unchecked v4 4)) - (assertz 250 v5) - (let (v6 (ptr u32)) (inttoptr v4)) - (store v6 v3) - "#]], - ) -} - -#[test] -fn i32_const() { - check_op( - r#" - i32.const 1 - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1)) - "#]], - ) -} - -#[test] -fn i64_const() { - check_op( - r#" - i64.const 1 - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 1)) - "#]], - ) -} - -#[test] -fn i32_popcnt() { - check_op( - r#" - i32.const 1 - i32.popcnt - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1)) - (let (v1 u32) (popcnt v0)) - (let (v2 i32) (bitcast v1)) - "#]], - ) -} - -#[test] -fn i32_clz() { - check_op( - r#" - i32.const 1 - i32.clz - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1)) - (let (v1 u32) (clz v0)) - (let (v2 i32) (bitcast v1)) - "#]], - ) -} - -#[test] -fn i64_clz() { - check_op( - r#" - i64.const 1 - i64.clz - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 1)) - (let (v1 u32) (clz v0)) - (let (v2 i32) (bitcast v1)) - "#]], - ) -} - -#[test] -fn i32_ctz() { - check_op( - r#" - i32.const 1 - i32.ctz - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1)) - (let (v1 u32) (ctz v0)) - (let (v2 i32) (bitcast v1)) - "#]], - ) -} - -#[test] -fn i64_ctz() { - check_op( - r#" - i64.const 1 - i64.ctz - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 1)) - (let (v1 u32) (ctz v0)) - (let (v2 i32) (bitcast v1)) - "#]], - ) -} - -#[test] -fn i64_extend_i32_s() { - check_op( - r#" - i32.const 1 - i64.extend_i32_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1)) - (let (v1 i64) (sext v0)) - "#]], - ) -} - -#[test] -fn i64_extend_i32_u() { - check_op( - r#" - i32.const 1 - i64.extend_i32_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 1)) - (let (v1 u32) (bitcast v0)) - (let (v2 u64) (zext v1)) - (let (v3 i64) (bitcast v2)) - "#]], - ) -} - -#[test] -fn i32_wrap_i64() { - check_op( - r#" - i64.const 1 - i32.wrap_i64 - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 1)) - (let (v1 i32) (trunc v0)) - "#]], - ) -} - -#[test] -fn i32_add() { - check_op( - r#" - i32.const 3 - i32.const 1 - i32.add - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 3)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (add.wrapping v0 v1)) - "#]], - ) -} - -#[test] -fn i64_add() { - check_op( - r#" - i64.const 3 - i64.const 1 - i64.add - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 3)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (add.wrapping v0 v1)) - "#]], - ) -} - -#[test] -fn i32_and() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.and - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (band v0 v1)) - "#]], - ) -} - -#[test] -fn i64_and() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.and - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (band v0 v1)) - "#]], - ) -} - -#[test] -fn i32_or() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.or - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (bor v0 v1)) - "#]], - ) -} - -#[test] -fn i64_or() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.or - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (bor v0 v1)) - "#]], - ) -} - -#[test] -fn i32_sub() { - check_op( - r#" - i32.const 3 - i32.const 1 - i32.sub - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 3)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (sub.wrapping v0 v1)) - "#]], - ) -} - -#[test] -fn i64_sub() { - check_op( - r#" - i64.const 3 - i64.const 1 - i64.sub - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 3)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (sub.wrapping v0 v1)) - "#]], - ) -} - -#[test] -fn i32_xor() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.xor - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (bxor v0 v1)) - "#]], - ) -} - -#[test] -fn i64_xor() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.xor - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (bxor v0 v1)) - "#]], - ) -} - -#[test] -fn i32_shl() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.shl - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v1)) - (let (v3 i32) (shl.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i64_shl() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.shl - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u32) (cast v1)) - (let (v3 i64) (shl.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i32_shr_u() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.shr_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (bitcast v1)) - (let (v4 u32) (shr.wrapping v2 v3)) - (let (v5 i32) (bitcast v4)) - "#]], - ) -} - -#[test] -fn i64_shr_u() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.shr_u - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v0)) - (let (v3 u32) (cast v1)) - (let (v4 u64) (shr.wrapping v2 v3)) - (let (v5 i64) (bitcast v4)) - "#]], - ) -} - -#[test] -fn i32_shr_s() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.shr_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v1)) - (let (v3 i32) (shr.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i64_shr_s() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.shr_s - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u32) (cast v1)) - (let (v3 i64) (shr.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i32_rotl() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.rotl - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v1)) - (let (v3 i32) (rotl.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i64_rotl() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.rotl - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u32) (cast v1)) - (let (v3 i64) (rotl.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i32_rotr() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.rotr - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v1)) - (let (v3 i32) (rotr.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i64_rotr() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.rotr - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u32) (cast v1)) - (let (v3 i64) (rotr.wrapping v0 v2)) - "#]], - ) -} - -#[test] -fn i32_mul() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.mul - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (mul.wrapping v0 v1)) - "#]], - ) -} - -#[test] -fn i64_mul() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.mul - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (mul.wrapping v0 v1)) - "#]], - ) -} - -#[test] -fn i32_div_u() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.div_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (bitcast v1)) - (let (v4 u32) (div.unchecked v2 v3)) - (let (v5 i32) (bitcast v4)) - "#]], - ) -} - -#[test] -fn i64_div_u() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.div_u - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v0)) - (let (v3 u64) (bitcast v1)) - (let (v4 u64) (div.unchecked v2 v3)) - (let (v5 i64) (bitcast v4)) - "#]], - ) -} - -#[test] -fn i32_div_s() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.div_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (div.unchecked v0 v1)) - "#]], - ) -} - -#[test] -fn i64_div_s() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.div_s - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (div.unchecked v0 v1)) - "#]], - ) -} - -#[test] -fn i32_rem_u() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.rem_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (bitcast v1)) - (let (v4 u32) (mod.checked v2 v3)) - (let (v5 i32) (bitcast v4)) - "#]], - ) -} - -#[test] -fn i64_rem_u() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.rem_u - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v0)) - (let (v3 u64) (bitcast v1)) - (let (v4 u64) (mod.checked v2 v3)) - (let (v5 i64) (bitcast v4)) - "#]], - ) -} - -#[test] -fn i32_rem_s() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.rem_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i32) (mod.checked v0 v1)) - "#]], - ) -} - -#[test] -fn i64_rem_s() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.rem_s - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i64) (mod.checked v0 v1)) - "#]], - ) -} - -#[test] -fn i32_lt_u() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.lt_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (bitcast v1)) - (let (v4 i1) (lt v2 v3)) - (let (v5 i32) (sext v4)) - "#]], - ) -} - -#[test] -fn i64_lt_u() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.lt_u - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v0)) - (let (v3 u64) (bitcast v1)) - (let (v4 i1) (lt v2 v3)) - (let (v5 i32) (sext v4)) - "#]], - ) -} - -#[test] -fn i32_lt_s() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.lt_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i1) (lt v0 v1)) - (let (v3 i32) (sext v2)) - "#]], - ) -} - -#[test] -fn i64_lt_s() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.lt_s - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i1) (lt v0 v1)) - (let (v3 i32) (sext v2)) - "#]], - ) -} - -#[test] -fn i32_le_u() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.le_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (bitcast v1)) - (let (v4 i1) (lte v2 v3)) - (let (v5 i32) (sext v4)) - "#]], - ) -} - -#[test] -fn i64_le_u() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.le_u - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v0)) - (let (v3 u64) (bitcast v1)) - (let (v4 i1) (lte v2 v3)) - (let (v5 i32) (sext v4)) - "#]], - ) -} - -#[test] -fn i32_le_s() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.le_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i1) (lte v0 v1)) - (let (v3 i32) (sext v2)) - "#]], - ) -} - -#[test] -fn i64_le_s() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.le_s - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i1) (lte v0 v1)) - (let (v3 i32) (sext v2)) - "#]], - ) -} - -#[test] -fn i32_gt_u() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.gt_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (bitcast v1)) - (let (v4 i1) (gt v2 v3)) - (let (v5 i32) (sext v4)) - "#]], - ) -} - -#[test] -fn i64_gt_u() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.gt_u - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v0)) - (let (v3 u64) (bitcast v1)) - (let (v4 i1) (gt v2 v3)) - (let (v5 i32) (sext v4)) - "#]], - ) -} - -#[test] -fn i32_gt_s() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.gt_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i1) (gt v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn i64_gt_s() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.gt_s - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i1) (gt v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn i32_ge_u() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.ge_u - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (bitcast v1)) - (let (v4 i1) (gte v2 v3)) - (let (v5 i32) (zext v4)) - "#]], - ) -} - -#[test] -fn i64_ge_u() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.ge_u - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 u64) (bitcast v0)) - (let (v3 u64) (bitcast v1)) - (let (v4 i1) (gte v2 v3)) - (let (v5 i32) (zext v4)) - "#]], - ) -} - -#[test] -fn i32_ge_s() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.ge_s - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i1) (gte v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn i64_ge_s() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.ge_s - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i1) (gte v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn i32_eqz() { - check_op( - r#" - i32.const 2 - i32.eqz - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i1) (eq v0 0)) - (let (v2 i32) (zext v1)) - "#]], - ) -} - -#[test] -fn i64_eqz() { - check_op( - r#" - i64.const 2 - i64.eqz - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i1) (eq v0 0)) - (let (v2 i32) (zext v1)) - "#]], - ) -} - -#[test] -fn i32_eq() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.eq - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i1) (eq v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn i64_eq() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.eq - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i1) (eq v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn i32_ne() { - check_op( - r#" - i32.const 2 - i32.const 1 - i32.ne - drop - "#, - expect![[r#" - (let (v0 i32) (const.i32 2)) - (let (v1 i32) (const.i32 1)) - (let (v2 i1) (neq v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn i64_ne() { - check_op( - r#" - i64.const 2 - i64.const 1 - i64.ne - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 2)) - (let (v1 i64) (const.i64 1)) - (let (v2 i1) (neq v0 v1)) - (let (v3 i32) (zext v2)) - "#]], - ) -} - -#[test] -fn select_i32() { - check_op( - r#" - i64.const 3 - i64.const 7 - i32.const 1 - select - drop - "#, - expect![[r#" - (let (v0 i64) (const.i64 3)) - (let (v1 i64) (const.i64 7)) - (let (v2 i32) (const.i32 1)) - (let (v3 i1) (neq v2 0)) - (let (v4 i64) (select v3 v0 v1)) - "#]], - ) -} diff --git a/frontend-wasm/src/code_translator/tests_unsupported.rs b/frontend-wasm/src/code_translator/tests_unsupported.rs deleted file mode 100644 index 8e981fb83..000000000 --- a/frontend-wasm/src/code_translator/tests_unsupported.rs +++ /dev/null @@ -1,156 +0,0 @@ -use midenc_hir::{CallConv, Linkage, ModuleBuilder, Signature, SourceSpan}; -use wasmparser::{MemArg, Operator, Operator::*}; - -use super::translate_operator; -use crate::{ - module::{ - func_translation_state::FuncTranslationState, - function_builder_ext::{FunctionBuilderContext, FunctionBuilderExt}, - module_translation_state::ModuleTranslationState, - Module, - }, - test_utils::test_context, -}; - -fn check_unsupported(op: &Operator) { - let context = test_context(); - let mod_name = "noname"; - let module_info = Module::default(); - let mut module_builder = ModuleBuilder::new(mod_name); - let sig = Signature { - params: vec![], - results: vec![], - cc: CallConv::SystemV, - linkage: Linkage::External, - }; - let mut module_func_builder = module_builder.function("func_name", sig.clone()).unwrap(); - let mut fb_ctx = FunctionBuilderContext::new(); - let mod_types = Default::default(); - let mut state = FuncTranslationState::new(); - let mut builder_ext = FunctionBuilderExt::new(&mut module_func_builder, &mut fb_ctx); - let mut module_state = - ModuleTranslationState::new(&module_info, &mod_types, vec![], &context.session.diagnostics); - let result = translate_operator( - op, - &mut builder_ext, - &mut state, - &mut module_state, - &module_info, - &mod_types, - &context.session.diagnostics, - SourceSpan::default(), - ); - assert!(result.is_err(), "Expected unsupported op error for {:?}", op); - assert_eq!(result.unwrap_err().to_string(), format!("Wasm op {:?} is not supported", op)); -} - -// Wasm Spec v1.0 -const UNSUPPORTED_WASM_V1_OPS: &[Operator] = &[ - /****************************** Memory Operators *********************************** */ - F64Load { - memarg: MemArg { - align: 0, - max_align: 0, - offset: 0, - memory: 0, - }, - }, - F64Store { - memarg: MemArg { - align: 0, - max_align: 0, - offset: 0, - memory: 0, - }, - }, - /****************************** Nullary Operators ************************************/ - - // Cannot construct since Ieee32 fields are private - // F32Const { - // value: Ieee32(0), - // }, - // F64Const { - // value: Ieee32(0), - // }, - - /****************************** Unary Operators ************************************/ - F32Sqrt, - F64Sqrt, - F32Ceil, - F64Ceil, - F32Floor, - F64Floor, - F32Trunc, - F64Trunc, - F32Nearest, - F64Nearest, - F32Abs, - F64Abs, - F32Neg, - F64Neg, - F64ConvertI64U, - F64ConvertI32U, - F64ConvertI64S, - F64ConvertI32S, - F32ConvertI64S, - F32ConvertI32S, - F32ConvertI64U, - F32ConvertI32U, - F64PromoteF32, - F32DemoteF64, - I64TruncF64S, - I64TruncF32S, - I32TruncF64S, - I32TruncF32S, - I64TruncF64U, - I64TruncF32U, - I32TruncF64U, - I32TruncF32U, - I64TruncSatF64S, - I64TruncSatF32S, - I32TruncSatF64S, - I32TruncSatF32S, - I64TruncSatF64U, - I64TruncSatF32U, - I32TruncSatF64U, - I32TruncSatF32U, - F32ReinterpretI32, - F64ReinterpretI64, - I32ReinterpretF32, - I64ReinterpretF64, - /****************************** Binary Operators *********************************** */ - F32Add, - F32Sub, - F32Mul, - F32Div, - F32Min, - F32Max, - F32Copysign, - F64Copysign, - F64Add, - F64Sub, - F64Mul, - F64Div, - F64Min, - F64Max, - /**************************** Comparison Operators ********************************* */ - F32Eq, - F32Ne, - F32Gt, - F32Ge, - F32Le, - F32Lt, - F64Eq, - F64Ne, - F64Gt, - F64Ge, - F64Le, - F64Lt, -]; - -#[test] -fn error_for_unsupported_wasm_v1_ops() { - for op in UNSUPPORTED_WASM_V1_OPS.iter() { - check_unsupported(op); - } -} diff --git a/frontend-wasm/src/component/build_ir.rs b/frontend-wasm/src/component/build_ir.rs deleted file mode 100644 index 03f8dfbe6..000000000 --- a/frontend-wasm/src/component/build_ir.rs +++ /dev/null @@ -1,262 +0,0 @@ -use midenc_hir::diagnostics::Report; -use midenc_session::Session; - -use super::{ - inline, translator::ComponentTranslator, ComponentTypesBuilder, LinearComponentTranslation, - ParsedRootComponent, -}; -use crate::{ - component::ComponentParser, error::WasmResult, supported_component_model_features, - WasmTranslationConfig, -}; - -/// Translate a Wasm component binary into Miden IR component -pub fn translate_component( - wasm: &[u8], - config: &WasmTranslationConfig, - session: &Session, -) -> WasmResult { - let (mut component_types_builder, parsed_component) = parse(config, wasm, session)?; - let linearized_component_translation = inline(&mut component_types_builder, &parsed_component)?; - let component_types = component_types_builder.finish(); - let parsed_modules = parsed_component.static_modules; - let translator = ComponentTranslator::new(component_types, parsed_modules, config, session); - translator.translate(linearized_component_translation) -} - -fn parse<'data>( - config: &WasmTranslationConfig, - wasm: &'data [u8], - session: &Session, -) -> Result<(ComponentTypesBuilder, ParsedRootComponent<'data>), Report> { - let mut validator = - wasmparser::Validator::new_with_features(supported_component_model_features()); - let mut component_types_builder = Default::default(); - let component_parser = - ComponentParser::new(config, session, &mut validator, &mut component_types_builder); - let parsed_component = component_parser.parse(wasm)?; - Ok((component_types_builder, parsed_component)) -} - -fn inline( - component_types_builder: &mut ComponentTypesBuilder, - parsed_component: &ParsedRootComponent<'_>, -) -> WasmResult { - // ... after translation initially finishes the next pass is performed - // which we're calling "inlining". This will "instantiate" the root - // component, following nested component instantiations, creating a - // global list of initializers along the way. This phase uses the simple - // initializers in each component to track dataflow of host imports and - // internal references to items throughout a component at translation time. - // The produce initializers in the final `LinearComponent` are intended to be - // much simpler than the original component and more efficient for - // us to process (e.g. no string lookups as - // most everything is done through indices instead). - let component_dfg = inline::run( - component_types_builder, - &parsed_component.root_component, - &parsed_component.static_modules, - &parsed_component.static_components, - ) - .map_err(Report::msg)?; - Ok(component_dfg.finish()) -} - -#[cfg(test)] -mod tests { - use miden_core::crypto::hash::RpoDigest; - use midenc_hir::{FunctionType, Ident, InterfaceFunctionIdent, InterfaceIdent, Symbol}; - use midenc_hir_type::Type; - - use super::*; - use crate::{component::StaticModuleIndex, config::ImportMetadata, test_utils::test_context}; - - #[test] - fn translate_simple() { - let wat = r#" - (component - (core module (;0;) - (type (;0;) (func)) - (type (;1;) (func (param i32 i32) (result i32))) - (func $add (;0;) (type 1) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.add - ) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "add" (func $add)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (type (;0;) (func (param "a" u32) (param "b" u32) (result u32))) - (alias core export 0 "add" (core func (;0;))) - (func (;0;) (type 0) (canon lift (core func 0))) - (export (;1;) "add" (func 0)) - ) - "# - .to_string(); - let wasm = wat::parse_str(wat).unwrap(); - let context = test_context(); - let config = Default::default(); - let (mut component_types_builder, parsed_component) = - parse(&config, &wasm, &context.session).unwrap(); - let component_translation = - inline(&mut component_types_builder, &parsed_component).unwrap(); - - assert_eq!(parsed_component.static_modules.len(), 1); - // dbg!(&component_translation.component); - let module = &parsed_component.static_modules[StaticModuleIndex::from_u32(0)].module; - // dbg!(module); - assert_eq!(module.imports.len(), 0); - assert_eq!(component_translation.trampolines.len(), 0); - // dbg!(&component_translation.component.initializers); - assert_eq!(component_translation.component.initializers.len(), 1); - // dbg!(&component_translation.component.exports); - assert_eq!(component_translation.component.exports.len(), 1); - let component_types = component_types_builder.finish(); - let translator = ComponentTranslator::new( - component_types, - parsed_component.static_modules, - &config, - &context.session, - ); - let ir = translator.translate(component_translation).unwrap(); - - // dbg!(&ir.exports()); - assert!(!ir.modules().is_empty()); - assert!(!ir.exports().is_empty()); - let export_name_sym = Symbol::intern("add"); - let export = ir.exports().get(&export_name_sym.into()).unwrap(); - assert_eq!(export.function.function.as_symbol(), export_name_sym); - let expected_export_func_ty = FunctionType::new_wasm([Type::U32, Type::U32], [Type::U32]); - assert_eq!(export.function_ty, expected_export_func_ty); - } - - #[test] - fn translate_simple_import() { - let wat = r#" - (component - (type (;0;) - (instance - (type (;0;) (func (param "a" u32) (param "b" u32) (result u32))) - (export (;0;) "add" (func (type 0))) - ) - ) - (import "miden:add/add@1.0.0" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32) (result i32))) - (type (;1;) (func)) - (type (;2;) (func (param i32) (result i32))) - (import "miden:add/add@1.0.0" "add" (func $inc_wasm_component::bindings::miden::add::add::add::wit_import (;0;) (type 0))) - (func $inc (;1;) (type 2) (param i32) (result i32) - local.get 0 - i32.const 1 - call $inc_wasm_component::bindings::miden::add::add::add::wit_import - ) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "inc" (func $inc)) - ) - (alias export 0 "add" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (core instance (;0;) - (export "add" (func 0)) - ) - (core instance (;1;) (instantiate 0 - (with "miden:add/add@1.0.0" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (type (;1;) (func (param "a" u32) (result u32))) - (alias core export 1 "inc" (core func (;1;))) - (func (;1;) (type 1) (canon lift (core func 1))) - (export (;1;) "inc" (func 1)) - ) - "#.to_string(); - let wasm = wat::parse_str(wat).unwrap(); - let context = test_context(); - let interface_function_ident = InterfaceFunctionIdent { - interface: InterfaceIdent::from_full_ident("miden:add/add@1.0.0".to_string()), - function: Symbol::intern("add"), - }; - let import_metadata = [( - interface_function_ident, - ImportMetadata { - digest: RpoDigest::default(), - }, - )] - .into_iter() - .collect(); - - let config = WasmTranslationConfig { - import_metadata, - ..Default::default() - }; - let (mut component_types_builder, parsed_component) = - parse(&config, &wasm, &context.session).unwrap(); - let component_translation = - inline(&mut component_types_builder, &parsed_component).unwrap(); - assert_eq!(parsed_component.static_modules.len(), 1); - let module = &parsed_component.static_modules[StaticModuleIndex::from_u32(0)].module; - - // dbg!(&module.imports); - assert_eq!(module.imports.len(), 1); - - // dbg!(&component_translation.trampolines); - assert_eq!(component_translation.trampolines.len(), 1); - - // dbg!(&component_translation.component.initializers); - assert_eq!(component_translation.component.initializers.len(), 2); - - // dbg!(&component_translation.component.imports); - assert_eq!(component_translation.component.imports.len(), 1); - // dbg!(&component_translation.component.import_types); - assert_eq!(component_translation.component.import_types.len(), 1); - - // dbg!(&component_translation.component.exports); - assert_eq!(component_translation.component.exports.len(), 1); - - let component_types = component_types_builder.finish(); - - let translator = ComponentTranslator::new( - component_types, - parsed_component.static_modules, - &config, - &context.session, - ); - let ir = translator.translate(component_translation).unwrap(); - - // dbg!(&ir.exports()); - assert!(!ir.modules().is_empty()); - assert!(!ir.exports().is_empty()); - assert!(!ir.imports().is_empty()); - let export_name_sym = Symbol::intern("inc"); - let export = ir.exports().get(&export_name_sym.into()).unwrap(); - assert_eq!(export.function.function.as_symbol(), export_name_sym); - let expected_export_func_ty = FunctionType::new_wasm(vec![Type::U32], vec![Type::U32]); - assert_eq!(export.function_ty, expected_export_func_ty); - let module = ir.modules().first().unwrap().1; - // dbg!(&module.imports()); - let import_info = module.imports(); - dbg!(&import_info); - let function_id = *import_info - .imported(&Ident::from("miden:add/add@1.0.0")) - .unwrap() - .iter() - .collect::>() - .first() - .cloned() - .unwrap(); - dbg!(&function_id); - dbg!(ir.imports()); - let component_import = ir.imports().get(&function_id).unwrap().unwrap_canon_abi_import(); - assert_eq!(component_import.interface_function, interface_function_ident); - assert!(!component_import.function_ty.params.is_empty()); - let expected_import_func_ty = - FunctionType::new_wasm(vec![Type::U32, Type::U32], vec![Type::U32]); - assert_eq!(component_import.function_ty, expected_import_func_ty); - } -} diff --git a/frontend-wasm/src/component/dfg.rs b/frontend-wasm/src/component/dfg.rs deleted file mode 100644 index 8375b1552..000000000 --- a/frontend-wasm/src/component/dfg.rs +++ /dev/null @@ -1,606 +0,0 @@ -//! A dataflow-graph-like intermediate representation of a component -//! -//! This module contains `ComponentDfg` which is an intermediate step towards -//! becoming a full-fledged `LinearComponent`. The main purpose for the existence of -//! this representation of a component is to track dataflow between various -//! items within a component and support edits to them after the initial inlined -//! translation of a component. -//! -//! Currently fused adapters are represented with a core WebAssembly module -//! which gets "injected" into the final component as-if the component already -//! bundled it. In doing so the adapter modules need to be partitioned and -//! inserted into the final sequence of modules to instantiate. While this is -//! possible to do with a flat `GlobalInitializer` list it gets unwieldy really -//! quickly especially when other translation features are added. -//! -//! This module is largely a duplicate of the `component::info` module in this -//! crate. The hierarchy here uses `*Id` types instead of `*Index` types to -//! represent that they don't have any necessary implicit ordering. Additionally -//! nothing is kept in an ordered list and instead this is worked with in a -//! general dataflow fashion where dependencies are walked during processing. -//! -//! The `ComponentDfg::finish` method will convert the dataflow graph to a -//! linearized `GlobalInitializer` list which is intended to not be edited after -//! it's created. -//! -//! The `ComponentDfg` is created as part of the `component::inline` phase of -//! translation where the dataflow performed there allows identification of -//! fused adapters, what arguments make their way to core wasm modules, etc. - -// Based on wasmtime v16.0 Wasm component translation - -// TODO: remove this once Wasm CM support is complete -#![allow(dead_code)] - -use std::{hash::Hash, ops::Index}; - -use indexmap::IndexMap; -use midenc_hir::cranelift_entity::{EntityRef, PrimaryMap}; -use rustc_hash::FxHashMap; - -use crate::{ - component::*, - module::types::{EntityIndex, MemoryIndex, WasmType}, -}; - -#[derive(Default)] -pub struct ComponentDfg { - /// Same as `Component::import_types` - pub import_types: PrimaryMap, - - /// Same as `Component::imports` - pub imports: PrimaryMap)>, - - /// Same as `Component::exports` - pub exports: IndexMap, - - /// All trampolines and their type signature - pub trampolines: Intern, - - /// Know reallocation functions which are used by `lowerings` (e.g. will be - /// used by the host) - pub reallocs: Intern, - - /// Same as `reallocs`, but for post-return. - pub post_returns: Intern, - - /// Same as `reallocs`, but for post-return. - pub memories: Intern>, - - /// Metadata about all known core wasm instances created. - /// - /// This is mostly an ordered list and is not deduplicated based on contents - /// unlike the items above. Creation of an `Instance` is side-effectful and - /// all instances here are always required to be created. These are - /// considered "roots" in dataflow. - pub instances: PrimaryMap, - - /// Number of component instances that were created during the inlining - /// phase (this is not edited after creation). - pub num_runtime_component_instances: u32, - - /// Known adapter modules and how they are instantiated. - /// - /// This map is not filled in on the initial creation of a `ComponentDfg`. - /// Instead these modules are filled in by the `inline::adapt` phase where - /// adapter modules are identifed and filled in here. - /// - /// The payload here is the static module index representing the core wasm - /// adapter module that was generated as well as the arguments to the - /// instantiation of the adapter module. - pub adapter_modules: PrimaryMap)>, - - /// Metadata about where adapters can be found within their respective - /// adapter modules. - /// - /// Like `adapter_modules` this is not filled on the initial creation of - /// `ComponentDfg` but rather is created alongside `adapter_modules` during - /// the `inline::adapt` phase of translation. - /// - /// The values here are the module that the adapter is present within along - /// as the core wasm index of the export corresponding to the lowered - /// version of the adapter. - pub adapter_paritionings: PrimaryMap, - - /// Defined resources in this component sorted by index with metadata about - /// each resource. - /// - /// Note that each index here is a unique resource, and that may mean it was - /// the same component instantiated twice for example. - pub resources: PrimaryMap, - - /// Metadata about all imported resources into this component. This records - /// both how many imported resources there are (the size of this map) along - /// with what the corresponding runtime import is. - pub imported_resources: PrimaryMap, - - /// The total number of resource tables that will be used by this component, - /// currently the number of unique `TypeResourceTableIndex` allocations for - /// this component. - pub num_resource_tables: usize, - - /// An ordered list of side effects induced by instantiating this component. - /// - /// Currently all side effects are either instantiating core wasm modules or - /// declaring a resource. These side effects affect the dataflow processing - /// of this component by idnicating what order operations should be - /// performed during instantiation. - pub side_effects: Vec, -} - -/// Possible side effects that are possible with instantiating this component. -pub enum SideEffect { - /// A core wasm instance was created. - /// - /// Instantiation is side-effectful due to the presence of constructs such - /// as traps and the core wasm `start` function which may call component - /// imports. Instantiation order from the original component must be done in - /// the same order. - Instance(InstanceId), - - /// A resource was declared in this component. - /// - /// This is a bit less side-effectful than instantiation but this serves as - /// the order in which resources are initialized in a component with their - /// destructors. Destructors are loaded from core wasm instances (or - /// lowerings) which are produced by prior side-effectful operations. - Resource(DefinedResourceIndex), -} - -macro_rules! id { - ($(pub struct $name:ident(u32);)*) => ($( - #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)] - #[allow(missing_docs)] - pub struct $name(u32); - midenc_hir::cranelift_entity::entity_impl!($name); - )*) -} - -id! { - pub struct InstanceId(u32); - pub struct MemoryId(u32); - pub struct ReallocId(u32); - pub struct AdapterId(u32); - pub struct PostReturnId(u32); - pub struct AdapterModuleId(u32); -} - -/// Same as `info::InstantiateModule` -#[allow(missing_docs)] -pub enum Instance { - Static(StaticModuleIndex, Box<[CoreDef]>), - Import(RuntimeImportIndex, IndexMap>), -} - -/// Same as `info::Export` -#[allow(missing_docs)] -pub enum Export { - LiftedFunction { - ty: TypeFuncIndex, - func: CoreDef, - options: CanonicalOptions, - }, - ModuleStatic(StaticModuleIndex), - ModuleImport(RuntimeImportIndex), - Instance(IndexMap), - Type(TypeDef), -} - -/// Same as `info::CoreDef`, except has an extra `Adapter` variant. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -#[allow(missing_docs)] -pub enum CoreDef { - Export(CoreExport), - InstanceFlags(RuntimeComponentInstanceIndex), - Trampoline(TrampolineIndex), -} - -impl From> for CoreDef -where - EntityIndex: From, -{ - fn from(export: CoreExport) -> CoreDef { - CoreDef::Export(export.map_index(|i| i.into())) - } -} - -/// Same as `info::CoreExport` -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -#[allow(missing_docs)] -pub struct CoreExport { - pub instance: InstanceId, - pub item: ExportItem, -} - -impl CoreExport { - #[allow(missing_docs)] - pub fn map_index(self, f: impl FnOnce(T) -> U) -> CoreExport { - CoreExport { - instance: self.instance, - item: match self.item { - ExportItem::Index(i) => ExportItem::Index(f(i)), - ExportItem::Name(s) => ExportItem::Name(s), - }, - } - } -} - -/// Same as `info::Trampoline` -#[derive(Clone, PartialEq, Eq, Hash)] -#[allow(missing_docs)] -pub enum Trampoline { - LowerImport { - import: RuntimeImportIndex, - options: CanonicalOptions, - lower_ty: TypeFuncIndex, - }, - AlwaysTrap, - ResourceNew(TypeResourceTableIndex), - ResourceRep(TypeResourceTableIndex), - ResourceDrop(TypeResourceTableIndex), - ResourceTransferOwn, - ResourceTransferBorrow, - ResourceEnterCall, - ResourceExitCall, -} - -/// Same as `info::CanonicalOptions` -#[derive(Clone, Hash, Eq, PartialEq)] -#[allow(missing_docs)] -pub struct CanonicalOptions { - pub instance: RuntimeComponentInstanceIndex, - pub string_encoding: StringEncoding, - pub memory: Option, - pub realloc: Option, - pub post_return: Option, -} - -/// Same as `info::Resource` -#[allow(missing_docs)] -pub struct Resource { - pub rep: WasmType, - pub dtor: Option, - pub instance: RuntimeComponentInstanceIndex, -} - -/// A helper structure to "intern" and deduplicate values of type `V` with an -/// identifying key `K`. -/// -/// Note that this can also be used where `V` can't be intern'd to represent a -/// flat list of items. -pub struct Intern { - intern_map: FxHashMap, - key_map: PrimaryMap, -} - -impl Intern -where - K: EntityRef, -{ - /// Inserts the `value` specified into this set, returning either a fresh - /// key `K` if this value hasn't been seen before or otherwise returning the - /// previous `K` used to represent value. - /// - /// Note that this should only be used for component model items where the - /// creation of `value` is not side-effectful. - pub fn push(&mut self, value: V) -> K - where - V: Hash + Eq + Clone, - { - *self.intern_map.entry(value.clone()).or_insert_with(|| self.key_map.push(value)) - } - - /// Returns an iterator of all the values contained within this set. - pub fn iter(&self) -> impl Iterator { - self.key_map.iter() - } -} - -impl Index for Intern { - type Output = V; - - fn index(&self, key: K) -> &V { - &self.key_map[key] - } -} - -impl Default for Intern { - fn default() -> Intern { - Intern { - intern_map: FxHashMap::default(), - key_map: PrimaryMap::new(), - } - } -} - -impl ComponentDfg { - /// Consumes the intermediate `ComponentDfg` to produce a final `LinearComponent` - /// with a linear innitializer list. - pub fn finish(self) -> LinearComponentTranslation { - let mut linearize = LinearizeDfg { - dfg: &self, - initializers: Vec::new(), - runtime_memories: Default::default(), - runtime_post_return: Default::default(), - runtime_reallocs: Default::default(), - runtime_instances: Default::default(), - num_lowerings: 0, - trampolines: Default::default(), - trampoline_defs: Default::default(), - trampoline_map: Default::default(), - }; - - // Handle all side effects of this component in the order that they're - // defined. This will, for example, process all instantiations necessary - // of core wasm modules. - for item in linearize.dfg.side_effects.iter() { - linearize.side_effect(item); - } - - // Next the exports of the instance are handled which will likely end up - // creating some lowered imports, perhaps some saved modules, etc. - let exports = self - .exports - .iter() - .map(|(name, export)| (name.clone(), linearize.export(export))) - .collect(); - - // With all those pieces done the results of the dataflow-based - // linearization are recorded into the `LinearComponent`. The number of - // runtime values used for each index space is used from the `linearize` - // result. - LinearComponentTranslation { - trampolines: linearize.trampoline_defs, - component: LinearComponent { - exports, - initializers: linearize.initializers, - trampolines: linearize.trampolines, - num_lowerings: linearize.num_lowerings, - - num_runtime_memories: linearize.runtime_memories.len() as u32, - num_runtime_post_returns: linearize.runtime_post_return.len() as u32, - num_runtime_reallocs: linearize.runtime_reallocs.len() as u32, - num_runtime_instances: linearize.runtime_instances.len() as u32, - imports: self.imports, - import_types: self.import_types, - num_runtime_component_instances: self.num_runtime_component_instances, - num_resource_tables: self.num_resource_tables, - num_resources: (self.resources.len() + self.imported_resources.len()) as u32, - imported_resources: self.imported_resources, - defined_resource_instances: self - .resources - .iter() - .map(|(_, r)| r.instance) - .collect(), - }, - } - } - - /// Converts the provided defined index into a normal index, adding in the - /// number of imported resources. - pub fn resource_index(&self, defined: DefinedResourceIndex) -> ResourceIndex { - ResourceIndex::from_u32(defined.as_u32() + (self.imported_resources.len() as u32)) - } -} - -struct LinearizeDfg<'a> { - dfg: &'a ComponentDfg, - initializers: Vec, - trampolines: PrimaryMap, - trampoline_defs: PrimaryMap, - trampoline_map: FxHashMap, - runtime_memories: FxHashMap, - runtime_reallocs: FxHashMap, - runtime_post_return: FxHashMap, - runtime_instances: FxHashMap, - num_lowerings: u32, -} - -#[derive(Copy, Clone, Hash, Eq, PartialEq)] -enum RuntimeInstance { - Normal(InstanceId), -} - -impl LinearizeDfg<'_> { - fn side_effect(&mut self, effect: &SideEffect) { - match effect { - SideEffect::Instance(i) => { - self.instantiate(*i, &self.dfg.instances[*i]); - } - SideEffect::Resource(i) => { - self.resource(*i, &self.dfg.resources[*i]); - } - } - } - - fn instantiate(&mut self, instance: InstanceId, args: &Instance) { - log::trace!("creating instance {instance:?}"); - let instantiation = match args { - Instance::Static(index, args) => InstantiateModule::Static( - *index, - args.iter().map(|def| self.core_def(def)).collect(), - ), - Instance::Import(index, args) => InstantiateModule::Import( - *index, - args.iter() - .map(|(module, values)| { - let values = values - .iter() - .map(|(name, def)| (name.clone(), self.core_def(def))) - .collect(); - (module.clone(), values) - }) - .collect(), - ), - }; - let index = RuntimeInstanceIndex::new(self.runtime_instances.len()); - self.initializers.push(GlobalInitializer::InstantiateModule(instantiation)); - let prev = self.runtime_instances.insert(RuntimeInstance::Normal(instance), index); - assert!(prev.is_none()); - } - - fn resource(&mut self, index: DefinedResourceIndex, resource: &Resource) { - let dtor = resource.dtor.as_ref().map(|dtor| self.core_def(dtor)); - self.initializers.push(GlobalInitializer::Resource(info::Resource { - dtor, - index, - rep: resource.rep, - instance: resource.instance, - })); - } - - fn export(&mut self, export: &Export) -> info::Export { - match export { - Export::LiftedFunction { ty, func, options } => { - let func = self.core_def(func); - let options = self.options(options); - info::Export::LiftedFunction { - ty: *ty, - func, - options, - } - } - Export::ModuleStatic(i) => info::Export::ModuleStatic(*i), - Export::ModuleImport(i) => info::Export::ModuleImport(*i), - Export::Instance(map) => info::Export::Instance( - map.iter().map(|(name, export)| (name.clone(), self.export(export))).collect(), - ), - Export::Type(def) => info::Export::Type(*def), - } - } - - fn options(&mut self, options: &CanonicalOptions) -> info::CanonicalOptions { - let memory = options.memory.map(|mem| self.runtime_memory(mem)); - let realloc = options.realloc.map(|mem| self.runtime_realloc(mem)); - let post_return = options.post_return.map(|mem| self.runtime_post_return(mem)); - info::CanonicalOptions { - instance: options.instance, - string_encoding: options.string_encoding, - memory, - realloc, - post_return, - } - } - - fn runtime_memory(&mut self, mem: MemoryId) -> RuntimeMemoryIndex { - self.intern( - mem, - |me| &mut me.runtime_memories, - |me, mem| me.core_export(&me.dfg.memories[mem]), - |index, export| GlobalInitializer::ExtractMemory(ExtractMemory { index, export }), - ) - } - - fn runtime_realloc(&mut self, realloc: ReallocId) -> RuntimeReallocIndex { - self.intern( - realloc, - |me| &mut me.runtime_reallocs, - |me, realloc| me.core_def(&me.dfg.reallocs[realloc]), - |index, def| GlobalInitializer::ExtractRealloc(ExtractRealloc { index, def }), - ) - } - - fn runtime_post_return(&mut self, post_return: PostReturnId) -> RuntimePostReturnIndex { - self.intern( - post_return, - |me| &mut me.runtime_post_return, - |me, post_return| me.core_def(&me.dfg.post_returns[post_return]), - |index, def| GlobalInitializer::ExtractPostReturn(ExtractPostReturn { index, def }), - ) - } - - fn core_def(&mut self, def: &CoreDef) -> info::CoreDef { - match def { - CoreDef::Export(e) => info::CoreDef::Export(self.core_export(e)), - CoreDef::InstanceFlags(i) => info::CoreDef::InstanceFlags(*i), - CoreDef::Trampoline(index) => info::CoreDef::Trampoline(self.trampoline(*index)), - } - } - - fn trampoline(&mut self, index: TrampolineIndex) -> TrampolineIndex { - if let Some(idx) = self.trampoline_map.get(&index) { - return *idx; - } - let (signature, trampoline) = &self.dfg.trampolines[index]; - let trampoline = match trampoline { - Trampoline::LowerImport { - import, - options, - lower_ty, - } => { - let index = LoweredIndex::from_u32(self.num_lowerings); - self.num_lowerings += 1; - self.initializers.push(GlobalInitializer::LowerImport { - index, - import: *import, - }); - info::Trampoline::LowerImport { - index, - options: self.options(options), - lower_ty: *lower_ty, - } - } - Trampoline::AlwaysTrap => info::Trampoline::AlwaysTrap, - Trampoline::ResourceNew(ty) => info::Trampoline::ResourceNew(*ty), - Trampoline::ResourceDrop(ty) => info::Trampoline::ResourceDrop(*ty), - Trampoline::ResourceRep(ty) => info::Trampoline::ResourceRep(*ty), - Trampoline::ResourceTransferOwn => info::Trampoline::ResourceTransferOwn, - Trampoline::ResourceTransferBorrow => info::Trampoline::ResourceTransferBorrow, - Trampoline::ResourceEnterCall => info::Trampoline::ResourceEnterCall, - Trampoline::ResourceExitCall => info::Trampoline::ResourceExitCall, - }; - let i1 = self.trampolines.push(*signature); - let i2 = self.trampoline_defs.push(trampoline); - assert_eq!(i1, i2); - self.trampoline_map.insert(index, i1); - i1 - } - - fn core_export(&mut self, export: &CoreExport) -> info::CoreExport - where - T: Clone, - { - let instance = export.instance; - log::trace!("referencing export of {instance:?}"); - info::CoreExport { - instance: self.runtime_instances[&RuntimeInstance::Normal(instance)], - item: export.item.clone(), - } - } - - /// Helper function to manage interning of results to avoid duplicate - /// initializers being inserted into the final list. - /// - /// * `key` - the key being referenced which is used to deduplicate. - /// * `map` - a closure to access the interning map on `Self` - /// * `gen` - a closure to generate an intermediate value with `Self` from `K`. This is only - /// used if `key` hasn't previously been seen. This closure can recursively intern other - /// values possibly. - /// * `init` - a closure to use the result of `gen` to create the final initializer now that the - /// index `V` of the runtime item is known. - /// - /// This is used by all the other interning methods above to lazily append - /// initializers on-demand and avoid pushing more than one initializer at a - /// time. - fn intern( - &mut self, - key: K, - map: impl Fn(&mut Self) -> &mut FxHashMap, - gen: impl FnOnce(&mut Self, K) -> T, - init: impl FnOnce(V, T) -> GlobalInitializer, - ) -> V - where - K: Hash + Eq + Copy, - V: EntityRef, - { - if let Some(val) = map(self).get(&key) { - return *val; - } - let tmp = gen(self, key); - let index = V::new(map(self).len()); - self.initializers.push(init(index, tmp)); - let prev = map(self).insert(key, index); - assert!(prev.is_none()); - index - } -} diff --git a/frontend-wasm/src/component/info.rs b/frontend-wasm/src/component/info.rs deleted file mode 100644 index 5cf0e4c53..000000000 --- a/frontend-wasm/src/component/info.rs +++ /dev/null @@ -1,483 +0,0 @@ -// General runtime type-information about a component. -// -// Compared to the `Module` structure for core wasm this type is pretty -// significantly different. The core wasm `Module` corresponds roughly 1-to-1 -// with the structure of the wasm module itself, but instead a `LinearComponent` is -// more of a "translated" representation where the original structure is thrown -// away in favor of a more optimized representation. The considerations for this -// are: -// -// * This representation of a `LinearComponent` avoids the need to create a `PrimaryMap` of some -// form for each of the index spaces within a component. This is less so an issue about -// allocations and moreso that this information generally just isn't needed any time after -// instantiation. Avoiding creating these altogether helps components be lighter weight at runtime -// and additionally accelerates instantiation. -// -// * Finally by performing this sort of dataflow analysis we are capable of identifying what -// adapters need trampolines. For example this tracks when host functions are lowered which -// enables us to enumerate what trampolines are required to enter into a component. Additionally -// (eventually) this will track all of the "fused" adapter functions where a function from one -// component instance is lifted and then lowered into another component instance. -// -// Note, however, that the current design of `LinearComponent` has fundamental -// limitations which it was not designed for. For example there is no feasible -// way to implement either importing or exporting a component itself from the -// root component. Currently we rely on the ability to have static knowledge of -// what's coming from the host which at this point can only be either functions -// or core wasm modules. Additionally one flat list of initializers for a -// component are produced instead of initializers-per-component which would -// otherwise be required to export a component from a component. -// -// For now this tradeoff is made as it aligns well with the intended use case -// for components in an embedding. This may need to be revisited though if the -// requirements of embeddings change over time. - -// Based on wasmtime v16.0 Wasm component translation - -use indexmap::IndexMap; -use midenc_hir::cranelift_entity::PrimaryMap; - -use crate::{ - component::*, - module::types::{EntityIndex, MemoryIndex, WasmType}, -}; - -/// Metadata as a result of translating a component. -pub struct LinearComponentTranslation { - /// Serializable information that will be emitted into the final artifact. - pub component: LinearComponent, - - /// Metadata about required trampolines and what they're supposed to do. - pub trampolines: PrimaryMap, -} - -/// Run-time-type-information about a `LinearComponent`, its structure, and how to -/// instantiate it. -/// -/// This type is intended to mirror the `Module` type in this crate which -/// provides all the runtime information about the structure of a module and -/// how it works. -/// -/// NB: Lots of the component model is not yet implemented in the runtime so -/// this is going to undergo a lot of churn. -#[derive(Default, Debug)] -#[allow(dead_code)] -pub struct LinearComponent { - /// A list of typed values that this component imports. - /// - /// Note that each name is given an `ImportIndex` here for the next map to - /// refer back to. - pub import_types: PrimaryMap, - - /// A list of "flattened" imports that are used by this instance. - /// - /// This import map represents extracting imports, as necessary, from the - /// general imported types by this component. The flattening here refers to - /// extracting items from instances. Currently the flat imports are either a - /// host function or a core wasm module. - /// - /// For example if `ImportIndex(0)` pointed to an instance then this import - /// map represent extracting names from that map, for example extracting an - /// exported module or an exported function. - /// - /// Each import item is keyed by a `RuntimeImportIndex` which is referred to - /// by types below whenever something refers to an import. The value for - /// each `RuntimeImportIndex` in this map is the `ImportIndex` for where - /// this items comes from (which can be associated with a name above in the - /// `import_types` array) as well as the list of export names if - /// `ImportIndex` refers to an instance. The export names array represents - /// recursively fetching names within an instance. - pub imports: PrimaryMap)>, - - /// A list of this component's exports, indexed by either position or name. - pub exports: IndexMap, - - /// Initializers that must be processed when instantiating this component. - /// - /// This list of initializers does not correspond directly to the component - /// itself. The general goal with this is that the recursive nature of - /// components is "flattened" with an array like this which is a linear - /// sequence of instructions of how to instantiate a component. - pub initializers: Vec, - - /// The number of runtime instances (maximum `RuntimeInstanceIndex`) created - /// when instantiating this component. - pub num_runtime_instances: u32, - - /// Same as `num_runtime_instances`, but for `RuntimeComponentInstanceIndex` - /// instead. - pub num_runtime_component_instances: u32, - - /// The number of runtime memories (maximum `RuntimeMemoryIndex`) needed to - /// instantiate this component. - pub num_runtime_memories: u32, - - /// The number of runtime reallocs (maximum `RuntimeReallocIndex`) needed to - /// instantiate this component. - pub num_runtime_reallocs: u32, - - /// Same as `num_runtime_reallocs`, but for post-return functions. - pub num_runtime_post_returns: u32, - - /// WebAssembly type signature of all trampolines. - pub trampolines: PrimaryMap, - - /// The number of lowered host functions (maximum `LoweredIndex`) needed to - /// instantiate this component. - pub num_lowerings: u32, - - /// Maximal number of tables that required at runtime for resource-related - /// information in this component. - pub num_resource_tables: usize, - - /// Total number of resources both imported and defined within this - /// component. - pub num_resources: u32, - - /// Metadata about imported resources and where they are within the runtime - /// imports array. - /// - /// This map is only as large as the number of imported resources. - pub imported_resources: PrimaryMap, - - /// Metadata about which component instances defined each resource within - /// this component. - /// - /// This is used to determine which set of instance flags are inspected when - /// testing reentrance. - pub defined_resource_instances: PrimaryMap, -} - -#[allow(dead_code)] -impl LinearComponent { - /// Attempts to convert a resource index into a defined index. - /// - /// Returns `None` if `idx` is for an imported resource in this component or - /// `Some` if it's a locally defined resource. - pub fn defined_resource_index(&self, idx: ResourceIndex) -> Option { - let idx = idx.as_u32().checked_sub(self.imported_resources.len() as u32)?; - Some(DefinedResourceIndex::from_u32(idx)) - } - - /// Converts a defined resource index to a component-local resource index - /// which includes all imports. - pub fn resource_index(&self, idx: DefinedResourceIndex) -> ResourceIndex { - ResourceIndex::from_u32(self.imported_resources.len() as u32 + idx.as_u32()) - } -} - -/// GlobalInitializer instructions to get processed when instantiating a component -/// -/// The variants of this enum are processed during the instantiation phase of -/// a component in-order from front-to-back. These are otherwise emitted as a -/// component is parsed and read and translated. -#[derive(Debug)] -pub enum GlobalInitializer { - /// A core wasm module is being instantiated. - /// - /// This will result in a new core wasm instance being created, which may - /// involve running the `start` function of the instance as well if it's - /// specified. This largely delegates to the same standard instantiation - /// process as the rest of the core wasm machinery already uses. - InstantiateModule(InstantiateModule), - - /// A host function is being lowered, creating a core wasm function. - LowerImport { - /// The index of the lowered function that's being created. - /// - /// This is guaranteed to be the `n`th `LowerImport` instruction - /// if the index is `n`. - index: LoweredIndex, - - /// The index of the imported host function that is being lowered. - /// - /// It's guaranteed that this `RuntimeImportIndex` points to a function. - import: RuntimeImportIndex, - }, - - /// A core wasm linear memory - /// - /// This instruction indicates that the `index`th core wasm linear memory - /// needs to be extracted from the `export` specified, a pointer to a - /// previously created module instance. This lowering is then used in the - /// future by pointers from `CanonicalOptions`. - ExtractMemory(ExtractMemory), - - /// Same as `ExtractMemory`, except it's extracting a function pointer to be - /// used as a `realloc` function. - ExtractRealloc(ExtractRealloc), - - /// Same as `ExtractMemory`, except it's extracting a function pointer to be - /// used as a `post-return` function. - ExtractPostReturn(ExtractPostReturn), - - /// Declares a new defined resource within this component. - /// - /// Contains information about the destructor, for example. - #[allow(dead_code)] - Resource(Resource), -} - -/// Metadata for extraction of a memory of what's being extracted and where it's -/// going. -#[derive(Debug)] -pub struct ExtractMemory { - /// The index of the memory being defined. - pub index: RuntimeMemoryIndex, - /// Where this memory is being extracted from. - #[allow(dead_code)] - pub export: CoreExport, -} - -/// Same as `ExtractMemory` but for the `realloc` canonical option. -#[derive(Debug)] -pub struct ExtractRealloc { - /// The index of the realloc being defined. - pub index: RuntimeReallocIndex, - /// Where this realloc is being extracted from. - pub def: CoreDef, -} - -/// Same as `ExtractMemory` but for the `post-return` canonical option. -#[derive(Debug)] -pub struct ExtractPostReturn { - /// The index of the post-return being defined. - pub index: RuntimePostReturnIndex, - /// Where this post-return is being extracted from. - pub def: CoreDef, -} - -/// Different methods of instantiating a core wasm module. -#[derive(Debug)] -pub enum InstantiateModule { - /// A module defined within this component is being instantiated. - /// - /// Note that this is distinct from the case of imported modules because the - /// order of imports required is statically known and can be pre-calculated - /// to avoid string lookups related to names at runtime, represented by the - /// flat list of arguments here. - Static(StaticModuleIndex, Box<[CoreDef]>), - - /// An imported module is being instantiated. - /// - /// This is similar to `Upvar` but notably the imports are provided as a - /// two-level named map since import resolution order needs to happen at - /// runtime. - #[allow(dead_code)] - Import(RuntimeImportIndex, IndexMap>), -} - -/// Definition of a core wasm item and where it can come from within a -/// component. -/// -/// Note that this is sort of a result of data-flow-like analysis on a component -/// during translation of the component itself. References to core wasm items -/// are "translated" to either referring to a previous instance or to some sort of -/// lowered host import. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum CoreDef { - /// This item refers to an export of a previously instantiated core wasm - /// instance. - Export(CoreExport), - /// This is a reference to a wasm global which represents the - /// runtime-managed flags for a wasm instance. - InstanceFlags(RuntimeComponentInstanceIndex), - /// This is a reference to a trampoline which is described in the - /// `trampolines` array. - Trampoline(TrampolineIndex), -} - -impl From> for CoreDef -where - EntityIndex: From, -{ - fn from(export: CoreExport) -> CoreDef { - CoreDef::Export(export.map_index(|i| i.into())) - } -} - -/// Identifier of an exported item from a core WebAssembly module instance. -/// -/// Note that the `T` here is the index type for exports which can be -/// identified by index. The `T` is monomorphized with types like -/// [`EntityIndex`] or [`FuncIndex`]. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct CoreExport { - /// The instance that this item is located within. - /// - /// Note that this is intended to index the `instances` map within a - /// component. It's validated ahead of time that all instance pointers - /// refer only to previously-created instances. - pub instance: RuntimeInstanceIndex, - - /// The item that this export is referencing, either by name or by index. - pub item: ExportItem, -} - -impl CoreExport { - /// Maps the index type `T` to another type `U` if this export item indeed - /// refers to an index `T`. - pub fn map_index(self, f: impl FnOnce(T) -> U) -> CoreExport { - CoreExport { - instance: self.instance, - item: match self.item { - ExportItem::Index(i) => ExportItem::Index(f(i)), - ExportItem::Name(s) => ExportItem::Name(s), - }, - } - } -} - -/// An index at which to find an item within a runtime instance. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum ExportItem { - /// An exact index that the target can be found at. - /// - /// This is used where possible to avoid name lookups at runtime during the - /// instantiation process. This can only be used on instances where the - /// module was statically known at translation time, however. - Index(T), - - /// An item which is identified by a name, so at runtime we need to - /// perform a name lookup to determine the index that the item is located - /// at. - /// - /// This is used for instantiations of imported modules, for example, since - /// the precise shape of the module is not known. - Name(String), -} - -/// Possible exports from a component. -#[derive(Debug, Clone)] -pub enum Export { - /// A lifted function being exported which is an adaptation of a core wasm - /// function. - LiftedFunction { - /// The component function type of the function being created. - ty: TypeFuncIndex, - /// Which core WebAssembly export is being lifted. - func: CoreDef, - /// Any options, if present, associated with this lifting. - options: CanonicalOptions, - }, - /// A module defined within this component is exported. - #[allow(dead_code)] - ModuleStatic(StaticModuleIndex), - /// A module imported into this component is exported. - #[allow(dead_code)] - ModuleImport(RuntimeImportIndex), - /// A nested instance is being exported which has recursively defined - /// `Export` items. - Instance(IndexMap), - /// An exported type from a component or instance, currently only - /// informational. - #[allow(dead_code)] - Type(TypeDef), -} - -/// Canonical ABI options associated with a lifted or lowered function. -#[derive(Debug, Clone)] -pub struct CanonicalOptions { - /// The component instance that this bundle was associated with. - #[allow(dead_code)] - pub instance: RuntimeComponentInstanceIndex, - - /// The encoding used for strings. - pub string_encoding: StringEncoding, - - /// The memory used by these options, if specified. - #[allow(dead_code)] - pub memory: Option, - - /// The realloc function used by these options, if specified. - pub realloc: Option, - - /// The post-return function used by these options, if specified. - pub post_return: Option, -} - -/// Possible encodings of strings within the component model. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[allow(missing_docs)] -#[repr(u8)] -pub enum StringEncoding { - Utf8, - Utf16, - CompactUtf16, -} - -/// Description of a new resource declared in a `GlobalInitializer::Resource` -/// variant. -/// -/// This will have the effect of initializing runtime state for this resource, -/// namely the destructor is fetched and stored. -#[derive(Debug)] -#[allow(dead_code)] -pub struct Resource { - /// The local index of the resource being defined. - pub index: DefinedResourceIndex, - /// Core wasm representation of this resource. - pub rep: WasmType, - /// Optionally-specified destructor and where it comes from. - pub dtor: Option, - /// Which component instance this resource logically belongs to. - pub instance: RuntimeComponentInstanceIndex, -} - -/// A list of all possible trampolines that may be required to translate a -/// component completely. -/// -/// All trampolines have a core wasm function signature associated with them -/// which is stored in the `Component::trampolines` array. -#[derive(Debug)] -pub enum Trampoline { - /// Description of a lowered import used in conjunction with - /// `GlobalInitializer::LowerImport`. - LowerImport { - /// The runtime lowering state that this trampoline will access. - index: LoweredIndex, - - /// The type of the function that is being lowered, as perceived by the - /// component doing the lowering. - lower_ty: TypeFuncIndex, - - /// The canonical ABI options used when lowering this function specified - /// in the original component. - options: CanonicalOptions, - }, - - /// A small adapter which simply traps, used for degenerate lift/lower - /// combinations. - AlwaysTrap, - - /// A `resource.new` intrinsic which will inject a new resource into the - /// table specified. - #[allow(dead_code)] - ResourceNew(TypeResourceTableIndex), - - /// Same as `ResourceNew`, but for the `resource.rep` intrinsic. - #[allow(dead_code)] - ResourceRep(TypeResourceTableIndex), - - /// Same as `ResourceNew`, but for the `resource.drop` intrinsic. - #[allow(dead_code)] - ResourceDrop(TypeResourceTableIndex), - - /// An intrinsic used by FACT-generated modules which will transfer an owned - /// resource from one table to another. Used in component-to-component - /// adapter trampolines. - ResourceTransferOwn, - - /// Same as `ResourceTransferOwn` but for borrows. - ResourceTransferBorrow, - - /// An intrinsic used by FACT-generated modules which indicates that a call - /// is being entered and resource-related metadata needs to be configured. - /// - /// Note that this is currently only invoked when borrowed resources are - /// detected, otherwise this is "optimized out". - ResourceEnterCall, - - /// Same as `ResourceEnterCall` except for when exiting a call. - ResourceExitCall, -} diff --git a/frontend-wasm/src/component/inline.rs b/frontend-wasm/src/component/inline.rs deleted file mode 100644 index c89cbb498..000000000 --- a/frontend-wasm/src/component/inline.rs +++ /dev/null @@ -1,1253 +0,0 @@ -//! Implementation of "inlining" a component into a flat list of initializers. -//! -//! After the first phase of translating a component we're left with a single -//! root `ParsedComponent` for the original component along with a "static" list of -//! child components. Each `ParsedComponent` has a list of `LocalInitializer` items -//! inside of it which is a primitive representation of how the component -//! should be constructed with effectively one initializer per item in the -//! index space of a component. This "local initializer" list would be -//! relatively inefficient to process at runtime and more importantly doesn't -//! convey enough information to understand what trampolines need to be -//! translated. This consequently is the motivation for this file. -//! -//! The second phase of translation, inlining here, will in a sense interpret -//! the initializers, into a new list of `GlobalInitializer` entries -//! which are a sort of "global initializer". The generated `GlobalInitializer` is -//! much more specific than the `LocalInitializer` and additionally far fewer -//! `GlobalInitializer` structures are generated (in theory) than there are local -//! initializers. -//! -//! The "inlining" portion of the name of this module indicates how the -//! instantiation of a component is interpreted as calling a function. The -//! function's arguments are the imports provided to the instantiation of a -//! component, and further nested function calls happen on a stack when a -//! nested component is instantiated. The inlining then refers to how this -//! stack of instantiations is flattened to one list of `GlobalInitializer` -//! entries to represent the process of instantiating a component graph, -//! similar to how function inlining removes call instructions and creates one -//! giant function for a call graph. Here there are no inlining heuristics or -//! anything like that, we simply inline everything into the root component's -//! list of initializers. -//! -//! Another primary task this module performs is a form of dataflow analysis -//! to represent items in each index space with their definition rather than -//! references of relative indices. These definitions (all the `*Def` types in -//! this module) are not local to any one nested component and instead -//! represent state available at runtime tracked in the final `LinearComponent` -//! produced. -//! -//! With all this pieced together the general idea is relatively -//! straightforward. All of a component's initializers are processed in sequence -//! where instantiating a nested component pushes a "frame" onto a stack to -//! start executing and we resume at the old one when we're done. Items are -//! tracked where they come from and at the end after processing only the -//! side-effectful initializers are emitted to the `GlobalInitializer` list in the -//! final `LinearComponent`. - -// Based on wasmtime v16.0 Wasm component translation - -use std::{borrow::Cow, collections::HashMap}; - -use anyhow::{bail, Result}; -use indexmap::IndexMap; -use midenc_hir::cranelift_entity::PrimaryMap; -use rustc_hash::FxHashMap; -use wasmparser::types::{ComponentAnyTypeId, ComponentEntityType, ComponentInstanceTypeId}; - -use super::{ - resources::ResourcesBuilder, types::*, ClosedOverComponent, ClosedOverModule, ExportItem, - LocalCanonicalOptions, ParsedComponent, StringEncoding, -}; -use crate::{ - component::{dfg, LocalInitializer}, - module::{module_env::ParsedModule, types::*, ModuleImport}, - translation_utils::BuildFxHasher, -}; - -pub fn run( - types: &mut ComponentTypesBuilder, - root_component: &ParsedComponent<'_>, - nested_modules: &PrimaryMap>, - nested_components: &PrimaryMap>, -) -> Result { - let mut inliner = Inliner { - nested_modules, - nested_components, - result: Default::default(), - import_path_interner: Default::default(), - runtime_instances: PrimaryMap::default(), - }; - - let index = RuntimeComponentInstanceIndex::from_u32(0); - - // The initial arguments to the root component are all host imports. This - // means that they're all using the `ComponentItemDef::Host` variant. Here - // an `ImportIndex` is allocated for each item and then the argument is - // recorded. - // - // Note that this is represents the abstract state of a host import of an - // item since we don't know the precise structure of the host import. - let mut args = - HashMap::with_capacity_and_hasher(root_component.exports.len(), BuildFxHasher::default()); - let mut path = Vec::new(); - types.resources_mut().set_current_instance(index); - let types_ref = root_component.types_ref(); - for init in root_component.initializers.iter() { - let (name, ty) = match *init { - LocalInitializer::Import(name, ty) => (name, ty), - _ => continue, - }; - - // Before `convert_component_entity_type` below all resource types - // introduced by this import need to be registered and have indexes - // assigned to them. Any fresh new resource type referred to by imports - // is a brand new introduction of a resource which needs to have a type - // allocated to it, so new runtime imports are injected for each - // resource along with updating the `imported_resources` map. - let index = inliner.result.import_types.next_key(); - types.resources_mut().register_component_entity_type( - &types_ref, - ty, - &mut path, - &mut |path| { - let index = inliner.runtime_import(&ImportPath { - index, - path: path.iter().copied().map(Into::into).collect(), - }); - inliner.result.imported_resources.push(index) - }, - ); - - // With resources all taken care of it's now possible to convert this - // into our type system. - let ty = types.convert_component_entity_type(types_ref, ty)?; - - // Imports of types that aren't resources are not required to be - // specified by the host since it's just for type information within - // the component. - if let TypeDef::Interface(_) = ty { - continue; - } - let index = inliner.result.import_types.push((name.0.to_string(), ty)); - let path = ImportPath::root(index); - args.insert(name.0, ComponentItemDef::from_import(path, ty)?); - } - - // This will run the inliner to completion after being seeded with the - // initial frame. When the inliner finishes it will return the exports of - // the root frame which are then used for recording the exports of the - // component. - inliner.result.num_runtime_component_instances += 1; - let frame = InlinerFrame::new(index, root_component, ComponentClosure::default(), args, None); - let resources_snapshot = types.resources_mut().clone(); - let mut frames = vec![(frame, resources_snapshot)]; - let exports = inliner.run(types, &mut frames)?; - assert!(frames.is_empty()); - - let mut export_map = Default::default(); - for (name, def) in exports { - inliner.record_export(name, def, types, &mut export_map)?; - } - inliner.result.exports = export_map; - inliner.result.num_resource_tables = types.num_resource_tables(); - - Ok(inliner.result) -} - -struct Inliner<'a> { - /// The list of static modules that were found during initial translation of - /// the component. - /// - /// This is used during the instantiation of these modules to ahead-of-time - /// order the arguments precisely according to what the module is defined as - /// needing which avoids the need to do string lookups or permute arguments - /// at runtime. - nested_modules: &'a PrimaryMap>, - - /// The list of static components that were found during initial translation of - /// the component. - /// - /// This is used when instantiating nested components to push a new - /// `InlinerFrame` with the `ParsedComponent`s here. - nested_components: &'a PrimaryMap>, - - /// The final `LinearComponent` that is being constructed and returned from this - /// inliner. - result: dfg::ComponentDfg, - - // Maps used to "intern" various runtime items to only save them once at - // runtime instead of multiple times. - import_path_interner: FxHashMap, RuntimeImportIndex>, - - /// Origin information about where each runtime instance came from - runtime_instances: PrimaryMap, -} - -/// A "stack frame" as part of the inlining process, or the progress through -/// instantiating a component. -/// -/// All instantiations of a component will create an `InlinerFrame` and are -/// incrementally processed via the `initializers` list here. Note that the -/// inliner frames are stored on the heap to avoid recursion based on user -/// input. -struct InlinerFrame<'a> { - instance: RuntimeComponentInstanceIndex, - - /// The remaining initializers to process when instantiating this component. - initializers: std::slice::Iter<'a, LocalInitializer<'a>>, - - /// The component being instantiated. - translation: &'a ParsedComponent<'a>, - - /// The "closure arguments" to this component, or otherwise the maps indexed - /// by `ModuleUpvarIndex` and `ComponentUpvarIndex`. This is created when - /// a component is created and stored as part of a component's state during - /// inlining. - closure: ComponentClosure<'a>, - - /// The arguments to the creation of this component. - /// - /// At the root level these are all imports from the host and between - /// components this otherwise tracks how all the arguments are defined. - args: FxHashMap<&'a str, ComponentItemDef<'a>>, - - // core wasm index spaces - funcs: PrimaryMap, - memories: PrimaryMap>, - tables: PrimaryMap>, - globals: PrimaryMap>, - modules: PrimaryMap>, - - // component model index spaces - component_funcs: PrimaryMap>, - module_instances: PrimaryMap>, - component_instances: PrimaryMap>, - components: PrimaryMap>, - - /// The type of instance produced by completing the instantiation of this - /// frame. - /// - /// This is a wasmparser-relative piece of type information which is used to - /// register resource types after instantiation has completed. - /// - /// This is `Some` for all subcomponents and `None` for the root component. - instance_ty: Option, -} - -/// "Closure state" for a component which is resolved from the `ClosedOverVars` -/// state that was calculated during translation. -#[derive(Default, Clone)] -struct ComponentClosure<'a> { - modules: PrimaryMap>, - components: PrimaryMap>, -} - -/// Representation of a "path" into an import. -/// -/// Imports from the host at this time are one of three things: -/// -/// * Functions -/// * Core wasm modules -/// * "Instances" of these three items -/// -/// The "base" values are functions and core wasm modules, but the abstraction -/// of an instance allows embedding functions/modules deeply within other -/// instances. This "path" represents optionally walking through a host instance -/// to get to the final desired item. At runtime instances are just maps of -/// values and so this is used to ensure that we primarily only deal with -/// individual functions and modules instead of synthetic instances. -#[derive(Clone, PartialEq, Hash, Eq)] -struct ImportPath<'a> { - index: ImportIndex, - path: Vec>, -} - -/// Representation of all items which can be defined within a component. -/// -/// This is the "value" of an item defined within a component and is used to -/// represent both imports and exports. -#[derive(Clone)] -enum ComponentItemDef<'a> { - Component(ComponentDef<'a>), - Instance(ComponentInstanceDef<'a>), - Func(ComponentFuncDef<'a>), - Module(ModuleDef<'a>), - Type(TypeDef), -} - -#[derive(Clone)] -enum ModuleDef<'a> { - /// A core wasm module statically defined within the original component. - /// - /// The `StaticModuleIndex` indexes into the `static_modules` map in the - /// `Inliner`. - Static(StaticModuleIndex), - - /// A core wasm module that was imported from the host. - Import(ImportPath<'a>, TypeModuleIndex), -} - -// Note that unlike all other `*Def` types which are not allowed to have local -// indices this type does indeed have local indices. That is represented with -// the lack of a `Clone` here where once this is created it's never moved across -// components because module instances always stick within one component. -enum ModuleInstanceDef<'a> { - /// A core wasm module instance was created through the instantiation of a - /// module. - /// - /// The `RuntimeInstanceIndex` was the index allocated as this was the - /// `n`th instantiation and the `ModuleIndex` points into an - /// `InlinerFrame`'s local index space. - Instantiated(dfg::InstanceId, ModuleIndex), - - /// A "synthetic" core wasm module which is just a bag of named indices. - /// - /// Note that this can really only be used for passing as an argument to - /// another module's instantiation and is used to rename arguments locally. - Synthetic(&'a FxHashMap<&'a str, EntityIndex>), -} - -/// Configuration options which can be specified as part of the canonical ABI -/// in the component model. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct AdapterOptions { - /// The component instance index where the options were - /// originally specified. - pub instance: RuntimeComponentInstanceIndex, - /// How strings are encoded. - pub string_encoding: StringEncoding, - /// An optional memory definition supplied. - pub memory: Option>, - /// An optional definition of `realloc` to used. - pub realloc: Option, - /// An optional definition of a `post-return` to use. - pub post_return: Option, -} - -#[derive(Clone)] -enum ComponentFuncDef<'a> { - /// A host-imported component function. - Import(ImportPath<'a>), - - /// A core wasm function was lifted into a component function. - Lifted { - ty: TypeFuncIndex, - func: dfg::CoreDef, - options: AdapterOptions, - }, -} - -#[derive(Clone)] -enum ComponentInstanceDef<'a> { - /// A host-imported instance. - /// - /// This typically means that it's "just" a map of named values. - Import(ImportPath<'a>, TypeComponentInstanceIndex), - - /// A concrete map of values. - /// - /// This is used for both instantiated components as well as "synthetic" - /// components. This variant can be used for both because both are - /// represented by simply a bag of items within the entire component - /// instantiation process. - Items(IndexMap<&'a str, ComponentItemDef<'a>>), -} - -#[derive(Clone)] -struct ComponentDef<'a> { - index: StaticComponentIndex, - closure: ComponentClosure<'a>, -} - -impl<'a> Inliner<'a> { - /// Symbolically instantiates a component using the type information and - /// `frames` provided. - /// - /// The `types` provided is the type information for the entire component - /// translation process. This is a distinct output artifact separate from - /// the component metadata. - /// - /// The `frames` argument is storage to handle a "call stack" of components - /// instantiating one another. The youngest frame (last element) of the - /// frames list is a component that's currently having its initializers - /// processed. The second element of each frame is a snapshot of the - /// resource-related information just before the frame was translated. For - /// more information on this snapshotting see the documentation on - /// `ResourcesBuilder`. - fn run( - &mut self, - types: &mut ComponentTypesBuilder, - frames: &mut Vec<(InlinerFrame<'a>, ResourcesBuilder)>, - ) -> Result>> { - // This loop represents the execution of the instantiation of a - // component. This is an iterative process which is finished once all - // initializers are processed. Currently this is modeled as an infinite - // loop which drives the top-most iterator of the `frames` stack - // provided as an argument to this function. - loop { - let (frame, _) = frames.last_mut().unwrap(); - types.resources_mut().set_current_instance(frame.instance); - match frame.initializers.next() { - // Process the initializer and if it started the instantiation - // of another component then we push that frame on the stack to - // continue onwards. - Some(init) => { - if let Some(new_frame) = self.initializer(frame, types, init)? { - frames.push((new_frame, types.resources_mut().clone())); - } - } - - // If there are no more initializers for this frame then the - // component it represents has finished instantiation. The - // exports of the component are collected and then the entire - // frame is discarded. The exports are then either pushed in the - // parent frame, if any, as a new component instance or they're - // returned from this function for the root set of exports. - None => { - let exports = frame - .translation - .exports - .iter() - .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) - .collect::>()?; - let instance_ty = frame.instance_ty; - let (_, snapshot) = frames.pop().unwrap(); - *types.resources_mut() = snapshot; - match frames.last_mut() { - Some((parent, _)) => { - parent.finish_instantiate( - ComponentInstanceDef::Items(exports), - instance_ty.unwrap(), - types, - ); - } - None => break Ok(exports), - } - } - } - } - } - - fn initializer( - &mut self, - frame: &mut InlinerFrame<'a>, - types: &mut ComponentTypesBuilder, - initializer: &'a LocalInitializer, - ) -> Result>> { - use LocalInitializer::*; - - match initializer { - // When a component imports an item the actual definition of the - // item is looked up here (not at runtime) via its name. The - // arguments provided in our `InlinerFrame` describe how each - // argument was defined, so we simply move it from there into the - // correct index space. - // - // Note that for the root component this will add `*::Import` items - // but for sub-components this will do resolution to connect what - // was provided as an import at the instantiation-site to what was - // needed during the component's instantiation. - Import(name, ty) => { - let arg = match frame.args.get(name.0) { - Some(arg) => arg, - - // Not all arguments need to be provided for instantiation, - // namely the root component doesn't require - // structural type imports to be satisfied. These type - // imports are relevant for bindings generators and such but - // as a runtime there's not really a definition to fit in. - // - // If no argument was provided for `name` then it's asserted - // that this is a type import and additionally it's not a - // resource type import (which indeed must be provided). If - // all that passes then this initializer is effectively - // skipped. - None => { - match ty { - ComponentEntityType::Type { - created: ComponentAnyTypeId::Resource(_), - .. - } => unreachable!(), - ComponentEntityType::Type { .. } => {} - _ => unreachable!(), - } - return Ok(None); - } - }; - - // Next resource types need to be handled. For example if a - // resource is imported into this component then it needs to be - // assigned a unique table to provide the isolation guarantees - // of resources (this component's table is shared with no - // others). Here `register_component_entity_type` will find - // imported resources and then `lookup_resource` will find the - // resource within `arg` as necessary to lookup the original - // true definition of this resource. - // - // This is what enables tracking true resource origins - // throughout component translation while simultaneously also - // tracking unique tables for each resource in each component. - let mut path = Vec::new(); - let (resources, types) = types.resources_mut_and_types(); - resources.register_component_entity_type( - &frame.translation.types_ref(), - *ty, - &mut path, - &mut |path| arg.lookup_resource(path, types), - ); - - // And now with all the type information out of the way the - // `arg` definition is moved into its corresponding index space. - frame.push_item(arg.clone()); - } - - // Lowering a component function to a core wasm function. Here - // various metadata is recorded and then the final component gets an - // initializer recording the lowering. - Lower { - func, - options, - canonical_abi, - lower_ty, - } => { - let lower_ty = - types.convert_component_func_type(frame.translation.types_ref(), *lower_ty)?; - let options_lower = self.adapter_options(frame, options); - let func = match &frame.component_funcs[*func] { - // If this component function was originally a host import - // then this is a lowered host function which needs a - // trampoline to enter WebAssembly. That's recorded here - // with all relevant information. - ComponentFuncDef::Import(path) => { - let import = self.runtime_import(path); - let options = self.canonical_options(options_lower); - let index = self.result.trampolines.push(( - *canonical_abi, - dfg::Trampoline::LowerImport { - import, - options, - lower_ty, - }, - )); - dfg::CoreDef::Trampoline(index) - } - - // This case handles when a lifted function is later - // lowered, and both the lowering and the lifting are - // happening within the same component instance. - // - // In this situation if the `canon.lower`'d function is - // called then it immediately sets `may_enter` to `false`. - // When calling the callee, however, that's `canon.lift` - // which immediately traps if `may_enter` is `false`. That - // means that this pairing of functions creates a function - // that always traps. - // - // When closely reading the spec though the precise trap - // that comes out can be somewhat variable. Technically the - // function yielded here is one that should validate the - // arguments by lifting them, and then trap. This means that - // the trap could be different depending on whether all - // arguments are valid for now. This was discussed in - // WebAssembly/component-model#51 somewhat and the - // conclusion was that we can probably get away with "always - // trap" here. - // - // The `CoreDef::AlwaysTrap` variant here is used to - // indicate that this function is valid but if something - // actually calls it then it just generates a trap - // immediately. - ComponentFuncDef::Lifted { - options: options_lift, - .. - } if options_lift.instance == options_lower.instance => { - let index = self - .result - .trampolines - .push((*canonical_abi, dfg::Trampoline::AlwaysTrap)); - dfg::CoreDef::Trampoline(index) - } - - // Lowering a lifted function where the destination - // component is different than the source component - ComponentFuncDef::Lifted { .. } => { - bail!( - "Lowering a lifted function where the destination component is \ - different than the source component is not supported" - ); - } - }; - frame.funcs.push(func); - } - - // Lifting a core wasm function is relatively easy for now in that - // some metadata about the lifting is simply recorded. This'll get - // plumbed through to exports or a fused adapter later on. - Lift(ty, func, options) => { - let ty = types.convert_component_func_type(frame.translation.types_ref(), *ty)?; - let options = self.adapter_options(frame, options); - frame.component_funcs.push(ComponentFuncDef::Lifted { - ty, - func: frame.funcs[*func].clone(), - options, - }); - } - - // A new resource type is being introduced, so it's recorded as a - // brand new resource in the final `resources` array. Additionally - // for now resource introductions are considered side effects to - // know when to register their destructors so that's recorded as - // well. - // - // Note that this has the effect of when a component is instantiated - // twice it will produce unique types for the resources from each - // instantiation. That's the intended runtime semantics and - // implementation here, however. - Resource(ty, rep, dtor) => { - let idx = self.result.resources.push(dfg::Resource { - rep: *rep, - dtor: dtor.map(|i| frame.funcs[i].clone()), - instance: frame.instance, - }); - self.result.side_effects.push(dfg::SideEffect::Resource(idx)); - - // Register with type translation that all future references to - // `ty` will refer to `idx`. - // - // Note that this registration information is lost when this - // component finishes instantiation due to the snapshotting - // behavior in the frame processing loop above. This is also - // intended, though, since `ty` can't be referred to outside of - // this component. - let idx = self.result.resource_index(idx); - types.resources_mut().register_resource(ty.resource(), idx); - } - - // Resource-related intrinsics are generally all the same. - // Wasmparser type information is converted to our type - // information and then new entries for each intrinsic are recorded. - ResourceNew(id, ty) => { - let id = types.resource_id(id.resource()); - let index = self.result.trampolines.push((*ty, dfg::Trampoline::ResourceNew(id))); - frame.funcs.push(dfg::CoreDef::Trampoline(index)); - } - ResourceRep(id, ty) => { - let id = types.resource_id(id.resource()); - let index = self.result.trampolines.push((*ty, dfg::Trampoline::ResourceRep(id))); - frame.funcs.push(dfg::CoreDef::Trampoline(index)); - } - ResourceDrop(id, ty) => { - let id = types.resource_id(id.resource()); - let index = self.result.trampolines.push((*ty, dfg::Trampoline::ResourceDrop(id))); - frame.funcs.push(dfg::CoreDef::Trampoline(index)); - } - - ModuleStatic(idx) => { - frame.modules.push(ModuleDef::Static(*idx)); - } - - // Instantiation of a module is one of the meatier initializers that - // we'll generate. The main magic here is that for a statically - // known module we can order the imports as a list to exactly what - // the static module needs to be instantiated. For imported modules, - // however, the runtime string resolution must happen at runtime so - // that is deferred here by organizing the arguments as a two-layer - // `IndexMap` of what we're providing. - // - // In both cases though a new `RuntimeInstanceIndex` is allocated - // and an initializer is recorded to indicate that it's being - // instantiated. - ModuleInstantiate(module, args) => { - let instance_module; - let init = match &frame.modules[*module] { - ModuleDef::Static(idx) => { - let mut defs = Vec::new(); - for ModuleImport { - module: module_name, - field, - index: _, - } in &self.nested_modules[*idx].module.imports - { - let instance = args[module_name.as_str()]; - defs.push( - self.core_def_of_module_instance_export(frame, instance, field), - ); - } - instance_module = InstanceModule::Static(*idx); - dfg::Instance::Static(*idx, defs.into()) - } - ModuleDef::Import(path, ty) => { - let mut defs = IndexMap::new(); - for ((module, name), _) in types[*ty].imports.iter() { - let instance = args[module.as_str()]; - let def = - self.core_def_of_module_instance_export(frame, instance, name); - defs.entry(module.to_string()) - .or_insert(IndexMap::new()) - .insert(name.to_string(), def); - } - let index = self.runtime_import(path); - instance_module = InstanceModule::Import(*ty); - dfg::Instance::Import(index, defs) - } - }; - - let idx = self.result.instances.push(init); - self.result.side_effects.push(dfg::SideEffect::Instance(idx)); - let idx2 = self.runtime_instances.push(instance_module); - assert_eq!(idx, idx2); - frame.module_instances.push(ModuleInstanceDef::Instantiated(idx, *module)); - } - - ModuleSynthetic(map) => { - frame.module_instances.push(ModuleInstanceDef::Synthetic(map)); - } - - // This is one of the stages of the "magic" of implementing outer - // aliases to components and modules. For more information on this - // see the documentation on `LexicalScope`. This stage of the - // implementation of outer aliases is where the `ClosedOverVars` is - // transformed into a `ComponentClosure` state using the current - // `InlinerFrame`'s state. This will capture the "runtime" state of - // outer components and upvars and such naturally as part of the - // inlining process. - ComponentStatic(index, vars) => { - frame.components.push(ComponentDef { - index: *index, - closure: ComponentClosure { - modules: vars - .modules - .iter() - .map(|(_, m)| frame.closed_over_module(m)) - .collect(), - components: vars - .components - .iter() - .map(|(_, m)| frame.closed_over_component(m)) - .collect(), - }, - }); - } - - // Like module instantiation is this is a "meaty" part, and don't be - // fooled by the relative simplicity of this case. This is - // implemented primarily by the `Inliner` structure and the design - // of this entire module, so the "easy" step here is to simply - // create a new inliner frame and return it to get pushed onto the - // stack. - ComponentInstantiate(component, args, ty) => { - let component: &ComponentDef<'a> = &frame.components[*component]; - let index = RuntimeComponentInstanceIndex::from_u32( - self.result.num_runtime_component_instances, - ); - self.result.num_runtime_component_instances += 1; - let frame = InlinerFrame::new( - index, - &self.nested_components[component.index], - component.closure.clone(), - args.iter() - .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) - .collect::>()?, - Some(*ty), - ); - return Ok(Some(frame)); - } - - ComponentSynthetic(map) => { - let items = map - .iter() - .map(|(name, index)| Ok((*name, frame.item(*index, types)?))) - .collect::>()?; - frame.component_instances.push(ComponentInstanceDef::Items(items)); - } - - // Core wasm aliases, this and the cases below, are creating - // `CoreExport` items primarily to insert into the index space so we - // can create a unique identifier pointing to each core wasm export - // with the instance and relevant index/name as necessary. - AliasExportFunc(instance, name) => { - frame - .funcs - .push(self.core_def_of_module_instance_export(frame, *instance, name)); - } - - AliasExportTable(instance, name) => { - frame.tables.push( - match self.core_def_of_module_instance_export(frame, *instance, name) { - dfg::CoreDef::Export(e) => e, - _ => unreachable!(), - }, - ); - } - - AliasExportGlobal(instance, name) => { - frame.globals.push( - match self.core_def_of_module_instance_export(frame, *instance, name) { - dfg::CoreDef::Export(e) => e, - _ => unreachable!(), - }, - ); - } - - AliasExportMemory(instance, name) => { - frame.memories.push( - match self.core_def_of_module_instance_export(frame, *instance, name) { - dfg::CoreDef::Export(e) => e, - _ => unreachable!(), - }, - ); - } - - AliasComponentExport(instance, name) => { - match &frame.component_instances[*instance] { - // Aliasing an export from an imported instance means that - // we're extending the `ImportPath` by one name, represented - // with the clone + push here. Afterwards an appropriate - // item is then pushed in the relevant index space. - ComponentInstanceDef::Import(path, ty) => { - let path = path.push(*name); - let def = ComponentItemDef::from_import(path, types[*ty].exports[*name])?; - frame.push_item(def); - } - - // Given a component instance which was either created - // through instantiation of a component or through a - // synthetic renaming of items we just schlep around the - // definitions of various items here. - ComponentInstanceDef::Items(map) => frame.push_item(map[*name].clone()), - } - } - - // For more information on these see `LexicalScope` but otherwise - // this is just taking a closed over variable and inserting the - // actual definition into the local index space since this - // represents an outer alias to a module/component - AliasModule(idx) => { - frame.modules.push(frame.closed_over_module(idx)); - } - AliasComponent(idx) => { - frame.components.push(frame.closed_over_component(idx)); - } - - Export(item) => match item { - ComponentItem::Func(i) => { - frame.component_funcs.push(frame.component_funcs[*i].clone()); - } - ComponentItem::Module(i) => { - frame.modules.push(frame.modules[*i].clone()); - } - ComponentItem::Component(i) => { - frame.components.push(frame.components[*i].clone()); - } - ComponentItem::ComponentInstance(i) => { - frame.component_instances.push(frame.component_instances[*i].clone()); - } - - // Type index spaces aren't maintained during this inlining pass - // so ignore this. - ComponentItem::Type(_) => {} - }, - } - - Ok(None) - } - - /// "Commits" a path of an import to an actual index which is something that - /// will be calculated at runtime. - /// - /// Note that the cost of calculating an item for a `RuntimeImportIndex` at - /// runtime is amortized with an `InstancePre` which represents "all the - /// runtime imports are lined up" and after that no more name resolution is - /// necessary. - fn runtime_import(&mut self, path: &ImportPath<'a>) -> RuntimeImportIndex { - *self.import_path_interner.entry(path.clone()).or_insert_with(|| { - self.result - .imports - .push((path.index, path.path.iter().map(|s| s.to_string()).collect())) - }) - } - - /// Returns the `CoreDef`, the canonical definition for a core wasm item, - /// for the export `name` of `instance` within `frame`. - fn core_def_of_module_instance_export( - &self, - frame: &InlinerFrame<'a>, - instance: ModuleInstanceIndex, - name: &'a str, - ) -> dfg::CoreDef { - match &frame.module_instances[instance] { - // Instantiations of a statically known module means that we can - // refer to the exported item by a precise index, skipping name - // lookups at runtime. - // - // Instantiations of an imported module, however, must do name - // lookups at runtime since we don't know the structure ahead of - // time here. - ModuleInstanceDef::Instantiated(instance, module) => { - let item = match frame.modules[*module] { - ModuleDef::Static(idx) => { - let entity = self.nested_modules[idx].module.exports[name]; - ExportItem::Index(entity) - } - ModuleDef::Import(..) => ExportItem::Name(name.to_string()), - }; - dfg::CoreExport { - instance: *instance, - item, - } - .into() - } - - // This is a synthetic instance so the canonical definition of the - // original item is returned. - ModuleInstanceDef::Synthetic(instance) => match instance[name] { - EntityIndex::Function(i) => frame.funcs[i].clone(), - EntityIndex::Table(i) => frame.tables[i].clone().into(), - EntityIndex::Global(i) => frame.globals[i].clone().into(), - EntityIndex::Memory(i) => frame.memories[i].clone().into(), - }, - } - } - - /// Translates a `LocalCanonicalOptions` which indexes into the `frame` - /// specified into a runtime representation. - fn adapter_options( - &mut self, - frame: &InlinerFrame<'a>, - options: &LocalCanonicalOptions, - ) -> AdapterOptions { - let memory = options.memory.map(|i| { - frame.memories[i].clone().map_index(|i| match i { - EntityIndex::Memory(i) => i, - _ => unreachable!(), - }) - }); - let realloc = options.realloc.map(|i| frame.funcs[i].clone()); - let post_return = options.post_return.map(|i| frame.funcs[i].clone()); - AdapterOptions { - instance: frame.instance, - string_encoding: options.string_encoding, - memory, - realloc, - post_return, - } - } - - /// Translatees an `AdapterOptions` into a `CanonicalOptions` where - /// memories/functions are inserted into the global initializer list for - /// use at runtime. This is only used for lowered host functions and lifted - /// functions exported to the host. - fn canonical_options(&mut self, options: AdapterOptions) -> dfg::CanonicalOptions { - let memory = options.memory.map(|export| self.result.memories.push(export)); - let realloc = options.realloc.map(|def| self.result.reallocs.push(def)); - let post_return = options.post_return.map(|def| self.result.post_returns.push(def)); - dfg::CanonicalOptions { - instance: options.instance, - string_encoding: options.string_encoding, - memory, - realloc, - post_return, - } - } - - fn record_export( - &mut self, - name: &str, - def: ComponentItemDef<'a>, - types: &'a ComponentTypesBuilder, - map: &mut IndexMap, - ) -> Result<()> { - let export = match def { - // Exported modules are currently saved in a `PrimaryMap`, at - // runtime, so an index (`RuntimeModuleIndex`) is assigned here and - // then an initializer is recorded about where the module comes - // from. - ComponentItemDef::Module(module) => match module { - ModuleDef::Static(idx) => dfg::Export::ModuleStatic(idx), - ModuleDef::Import(path, _) => dfg::Export::ModuleImport(self.runtime_import(&path)), - }, - - ComponentItemDef::Func(func) => match func { - // If this is a lifted function from something lowered in this - // component then the configured options are plumbed through - // here. - ComponentFuncDef::Lifted { ty, func, options } => { - let options = self.canonical_options(options); - dfg::Export::LiftedFunction { ty, func, options } - } - - // Currently reexported functions from an import are not - // supported. Being able to actually call these functions is - // somewhat tricky and needs something like temporary scratch - // space that isn't implemented. - ComponentFuncDef::Import(_) => { - bail!( - "component export `{name}` is a reexport of an imported function which is \ - not implemented" - ) - } - }, - - ComponentItemDef::Instance(instance) => { - let mut result = IndexMap::new(); - match instance { - // If this instance is one that was originally imported by - // the component itself then the imports are translated here - // by converting to a `ComponentItemDef` and then - // recursively recording the export as a reexport. - // - // Note that for now this would only work with - // module-exporting instances. - ComponentInstanceDef::Import(path, ty) => { - for (name, ty) in types[ty].exports.iter() { - let path = path.push(name); - let def = ComponentItemDef::from_import(path, *ty)?; - self.record_export(name, def, types, &mut result)?; - } - } - - // An exported instance which is itself a bag of items is - // translated recursively here to our `result` map which is - // the bag of items we're exporting. - ComponentInstanceDef::Items(map) => { - for (name, def) in map { - self.record_export(name, def, types, &mut result)?; - } - } - } - dfg::Export::Instance(result) - } - - ComponentItemDef::Component(_) => { - bail!("exporting a component from the root component is not supported") - } - - ComponentItemDef::Type(def) => dfg::Export::Type(def), - }; - - map.insert(name.to_string(), export); - Ok(()) - } -} - -impl<'a> InlinerFrame<'a> { - fn new( - instance: RuntimeComponentInstanceIndex, - translation: &'a ParsedComponent<'a>, - closure: ComponentClosure<'a>, - args: FxHashMap<&'a str, ComponentItemDef<'a>>, - instance_ty: Option, - ) -> Self { - InlinerFrame { - instance, - translation, - closure, - args, - instance_ty, - initializers: translation.initializers.iter(), - - funcs: Default::default(), - memories: Default::default(), - tables: Default::default(), - globals: Default::default(), - - component_instances: Default::default(), - component_funcs: Default::default(), - module_instances: Default::default(), - components: Default::default(), - modules: Default::default(), - } - } - - fn item( - &self, - index: ComponentItem, - types: &mut ComponentTypesBuilder, - ) -> Result> { - Ok(match index { - ComponentItem::Func(i) => ComponentItemDef::Func(self.component_funcs[i].clone()), - ComponentItem::Component(i) => ComponentItemDef::Component(self.components[i].clone()), - ComponentItem::ComponentInstance(i) => { - ComponentItemDef::Instance(self.component_instances[i].clone()) - } - ComponentItem::Module(i) => ComponentItemDef::Module(self.modules[i].clone()), - ComponentItem::Type(t) => { - let types_ref = self.translation.types_ref(); - ComponentItemDef::Type(types.convert_type(types_ref, t)?) - } - }) - } - - /// Pushes the component `item` definition provided into the appropriate - /// index space within this component. - fn push_item(&mut self, item: ComponentItemDef<'a>) { - match item { - ComponentItemDef::Func(i) => { - self.component_funcs.push(i); - } - ComponentItemDef::Module(i) => { - self.modules.push(i); - } - ComponentItemDef::Component(i) => { - self.components.push(i); - } - ComponentItemDef::Instance(i) => { - self.component_instances.push(i); - } - - // In short, type definitions aren't tracked here. - // - // The longer form explanation for this is that structural types - // like lists and records don't need to be tracked at all and the - // only significant type which needs tracking is resource types - // themselves. Resource types, however, are tracked within the - // `ResourcesBuilder` state rather than an `InlinerFrame` so they're - // ignored here as well. The general reason for that is that type - // information is everywhere and this `InlinerFrame` is not - // everywhere so it seemed like it would make sense to split the - // two. - // - // Note though that this case is actually frequently hit, so it - // can't be `unreachable!()`. Instead callers are responsible for - // handling this appropriately with respect to resources. - ComponentItemDef::Type(_ty) => {} - } - } - - fn closed_over_module(&self, index: &ClosedOverModule) -> ModuleDef<'a> { - match *index { - ClosedOverModule::Local(i) => self.modules[i].clone(), - ClosedOverModule::Upvar(i) => self.closure.modules[i].clone(), - } - } - - fn closed_over_component(&self, index: &ClosedOverComponent) -> ComponentDef<'a> { - match *index { - ClosedOverComponent::Local(i) => self.components[i].clone(), - ClosedOverComponent::Upvar(i) => self.closure.components[i].clone(), - } - } - - /// Completes the instantiation of a subcomponent and records type - /// information for the instance that was produced. - /// - /// This method is invoked when an `InlinerFrame` finishes for a - /// subcomponent. The `def` provided represents the instance that was - /// produced from instantiation, and `ty` is the wasmparser-defined type of - /// the instance produced. - /// - /// The purpose of this method is to record type information about resources - /// in the instance produced. In the component model all instantiations of a - /// component produce fresh new types for all resources which are unequal to - /// all prior resources. This means that if wasmparser's `ty` type - /// information references a unique resource within `def` that has never - /// been registered before then that means it's a defined resource within - /// the component that was just instantiated (as opposed to an imported - /// resource which was reexported). - /// - /// Further type translation after this instantiation can refer to these - /// resource types and a mapping from those types to the our internal - /// types is required, so this method builds up those mappings. - /// - /// Essentially what happens here is that the `ty` type is registered and - /// any new unique resources are registered so new tables can be introduced - /// along with origin information about the actual underlying resource type - /// and which component instantiated it. - fn finish_instantiate( - &mut self, - def: ComponentInstanceDef<'a>, - ty: ComponentInstanceTypeId, - types: &mut ComponentTypesBuilder, - ) { - let (resources, types) = types.resources_mut_and_types(); - let mut path = Vec::new(); - let arg = ComponentItemDef::Instance(def); - resources.register_component_entity_type( - &self.translation.types_ref(), - ComponentEntityType::Instance(ty), - &mut path, - &mut |path| arg.lookup_resource(path, types), - ); - self.push_item(arg); - } -} - -impl<'a> ImportPath<'a> { - fn root(index: ImportIndex) -> ImportPath<'a> { - ImportPath { - index, - path: Vec::new(), - } - } - - fn push(&self, s: impl Into>) -> ImportPath<'a> { - let mut new = self.clone(); - new.path.push(s.into()); - new - } -} - -impl<'a> ComponentItemDef<'a> { - fn from_import(path: ImportPath<'a>, ty: TypeDef) -> Result> { - let item = match ty { - TypeDef::Module(ty) => ComponentItemDef::Module(ModuleDef::Import(path, ty)), - TypeDef::ComponentInstance(ty) => { - ComponentItemDef::Instance(ComponentInstanceDef::Import(path, ty)) - } - TypeDef::ComponentFunc(_ty) => ComponentItemDef::Func(ComponentFuncDef::Import(path)), - TypeDef::Component(_ty) => bail!("root-level component imports are not supported"), - TypeDef::Interface(_) | TypeDef::Resource(_) => ComponentItemDef::Type(ty), - }; - Ok(item) - } - - /// Walks the `path` within `self` to find a resource at that path. - /// - /// This method is used when resources are found within wasmparser's type - /// information and they need to be correlated with actual concrete - /// definitions from this inlining pass. The `path` here is a list of - /// instance export names (or empty) to walk to reach down into the final - /// definition which should refer to a resource itself. - fn lookup_resource(&self, path: &[&str], types: &ComponentTypes) -> ResourceIndex { - let mut cur = self.clone(); - - // Each element of `path` represents unwrapping a layer of an instance - // type, so handle those here by updating `cur` iteratively. - for element in path.iter().copied() { - let instance = match cur { - ComponentItemDef::Instance(def) => def, - _ => unreachable!(), - }; - cur = match instance { - // If this instance is a "bag of things" then this is as easy as - // looking up the name in the bag of names. - ComponentInstanceDef::Items(names) => names[element].clone(), - - // If, however, this instance is an imported instance then this - // is a further projection within the import with one more path - // element. The `types` type information is used to lookup the - // type of `element` within the instance type, and that's used - // in conjunction with a one-longer `path` to produce a new item - // definition. - ComponentInstanceDef::Import(path, ty) => { - ComponentItemDef::from_import(path.push(element), types[ty].exports[element]) - .unwrap() - } - }; - } - - // Once `path` has been iterated over it must be the case that the final - // item is a resource type, in which case a lookup can be performed. - match cur { - ComponentItemDef::Type(TypeDef::Resource(idx)) => types[idx].ty, - _ => unreachable!(), - } - } -} - -enum InstanceModule { - #[allow(dead_code)] - Static(StaticModuleIndex), - #[allow(dead_code)] - Import(TypeModuleIndex), -} diff --git a/frontend-wasm/src/component/mod.rs b/frontend-wasm/src/component/mod.rs deleted file mode 100644 index 8b237754f..000000000 --- a/frontend-wasm/src/component/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Support for the Wasm component model translation -//! -//! This module contains all of the internal type definitions to parse and -//! translate the component model. - -pub mod build_ir; -mod dfg; -pub mod info; -mod inline; -mod parser; -mod translator; -mod types; - -pub use self::{info::*, parser::*, types::*}; diff --git a/frontend-wasm/src/component/translator.rs b/frontend-wasm/src/component/translator.rs deleted file mode 100644 index e28ad0580..000000000 --- a/frontend-wasm/src/component/translator.rs +++ /dev/null @@ -1,455 +0,0 @@ -use midenc_hir::{ - cranelift_entity::PrimaryMap, diagnostics::Severity, CanonAbiImport, ComponentBuilder, - ComponentExport, FunctionIdent, FunctionType, Ident, InterfaceFunctionIdent, InterfaceIdent, - Symbol, -}; -use midenc_hir_type::Abi; -use midenc_session::Session; -use rustc_hash::FxHashMap; - -use super::{ - interface_type_to_ir, CanonicalOptions, ComponentTypes, CoreDef, CoreExport, Export, - ExportItem, GlobalInitializer, InstantiateModule, LinearComponent, LinearComponentTranslation, - LoweredIndex, RuntimeImportIndex, RuntimeInstanceIndex, RuntimePostReturnIndex, - RuntimeReallocIndex, StaticModuleIndex, Trampoline, TypeFuncIndex, -}; -use crate::{ - component::StringEncoding, - error::WasmResult, - module::{ - build_ir::build_ir_module, - instance::ModuleArgument, - module_env::ParsedModule, - module_translation_state::ModuleTranslationState, - types::{EntityIndex, FuncIndex}, - Module, ModuleImport, - }, - unsupported_diag, WasmTranslationConfig, -}; - -/// A translator from the linearized Wasm component model to the Miden IR component -pub struct ComponentTranslator<'a, 'data> { - /// The Wasm component types - component_types: ComponentTypes, - /// The parsed static modules of the Wasm component - parsed_modules: PrimaryMap>, - /// The translation configuration - config: &'a WasmTranslationConfig, - /// The runtime module instances index mapped to the static module index - module_instances_source: PrimaryMap, - /// The lower imports index mapped to the runtime import index - lower_imports: FxHashMap, - /// The realloc functions used in CanonicalOptions in this component - reallocs: FxHashMap, - /// The post return functions used in CanonicalOptions in this component - post_returns: FxHashMap, - session: &'a Session, -} - -impl<'a, 'data> ComponentTranslator<'a, 'data> { - pub fn new( - component_types: ComponentTypes, - parsed_modules: PrimaryMap>, - config: &'a WasmTranslationConfig, - session: &'a Session, - ) -> Self { - Self { - component_types, - parsed_modules, - config, - session, - module_instances_source: PrimaryMap::new(), - lower_imports: FxHashMap::default(), - reallocs: FxHashMap::default(), - post_returns: FxHashMap::default(), - } - } - - /// Translate the given linearized Wasm component to the Miden IR component - pub fn translate( - mut self, - wasm_translation: LinearComponentTranslation, - ) -> WasmResult { - let mut component_builder: midenc_hir::ComponentBuilder<'a> = - midenc_hir::ComponentBuilder::new(&self.session.diagnostics); - dbg!(&wasm_translation.component.initializers); - for initializer in &wasm_translation.component.initializers { - match initializer { - GlobalInitializer::InstantiateModule(instantiate_module) => { - self.translate_module_instance( - instantiate_module, - &mut component_builder, - &wasm_translation, - )?; - } - GlobalInitializer::LowerImport { - index: init_lowered_idx, - import, - } => { - self.lower_imports.insert(*init_lowered_idx, *import); - } - GlobalInitializer::ExtractMemory(mem) => { - if mem.index.as_u32() > 0 { - unsupported_diag!( - &self.session.diagnostics, - "only one memory is supported in the component" - ); - } - } - GlobalInitializer::ExtractRealloc(realloc) => { - let func_id = self.func_id_from_core_def(&realloc.def)?; - self.reallocs.insert(realloc.index, func_id); - } - GlobalInitializer::ExtractPostReturn(post_return) => { - let func_id = self.func_id_from_core_def(&post_return.def)?; - self.post_returns.insert(post_return.index, func_id); - } - GlobalInitializer::Resource(_) => { - unsupported_diag!( - &self.session.diagnostics, - "resource global initializers are not yet supported" - ); - } - } - } - for (name, export) in &wasm_translation.component.exports { - self.build_export(export, name, &mut component_builder)?; - } - Ok(component_builder.build()) - } - - /// Translate the given Wasm core module instantiotion to the Miden IR component - fn translate_module_instance( - &mut self, - instantiate_module: &InstantiateModule, - component_builder: &mut ComponentBuilder<'_>, - wasm_translation: &LinearComponentTranslation, - ) -> WasmResult<()> { - match instantiate_module { - InstantiateModule::Static(static_module_idx, args) => { - if self.module_instances_source.values().any(|idx| *idx == *static_module_idx) { - unsupported_diag!( - &self.session.diagnostics, - "A module with a static index {} is already instantiated. We don't \ - support multiple instantiations of the same module.", - static_module_idx.as_u32() - ); - } - self.module_instances_source.push(*static_module_idx); - // TODO: create and init module instance tables - // see https://github.com/0xPolygonMiden/compiler/issues/133 - let module = &self.parsed_modules[*static_module_idx].module; - let mut module_args: Vec = Vec::new(); - for (idx, arg) in args.iter().enumerate() { - match arg { - CoreDef::Export(export) => { - module_args.push(self.module_arg_from_export(export)?); - } - CoreDef::InstanceFlags(_) => { - unsupported_diag!( - &self.session.diagnostics, - "Wasm component instance flags are not supported" - ); - } - CoreDef::Trampoline(trampoline_idx) => { - let trampoline = &wasm_translation.trampolines[*trampoline_idx]; - let arg = self.module_arg_from_trampoline( - trampoline, - module, - idx, - &wasm_translation.component, - component_builder, - )?; - module_args.push(arg); - } - } - } - let module_types = self.component_types.module_types(); - let mut module_state = ModuleTranslationState::new( - module, - module_types, - module_args, - &self.session.diagnostics, - ); - let ir_module = build_ir_module( - self.parsed_modules.get_mut(*static_module_idx).unwrap(), - module_types, - &mut module_state, - self.config, - self.session, - )?; - component_builder.add_module(ir_module.into()).expect("module is already added"); - } - InstantiateModule::Import(..) => { - unsupported_diag!( - &self.session.diagnostics, - "Imported Wasm core module instantiation is not supported" - ); - } - }; - Ok(()) - } - - /// Build a Wasm core module argument from the given trampoline (component import) - fn module_arg_from_trampoline( - &self, - trampoline: &Trampoline, - module: &Module, - idx: usize, - wasm_component: &LinearComponent, - component_builder: &mut ComponentBuilder<'_>, - ) -> WasmResult { - match trampoline { - Trampoline::LowerImport { - index, - lower_ty, - options, - } => { - let module_import = module.imports.get(idx).expect("module import not found"); - let runtime_import_idx = self.lower_imports[index]; - let function_id = function_id_from_import(module, module_import); - let component_import = - self.translate_import(runtime_import_idx, *lower_ty, options, wasm_component)?; - component_builder.add_import(function_id, component_import.clone()); - Ok(ModuleArgument::ComponentImport(component_import)) - } - _ => unsupported_diag!( - &self.session.diagnostics, - "Not yet implemented trampoline type {:?}", - trampoline - ), - } - } - - /// Build a module argument from the given module export - fn module_arg_from_export( - &self, - export: &CoreExport, - ) -> WasmResult { - match export.item { - ExportItem::Index(entity_idx) => match entity_idx { - EntityIndex::Function(func_idx) => { - let exporting_module_id = self.module_instances_source[export.instance]; - let function_id = function_id_from_export( - &self.parsed_modules[exporting_module_id].module, - func_idx, - ); - Ok(ModuleArgument::Function(function_id)) - } - EntityIndex::Table(_idx) => { - // TODO: init the exported table with this module's table initialization values - // see https://github.com/0xPolygonMiden/compiler/issues/133 - Ok(ModuleArgument::Table) - } - EntityIndex::Memory(_) => { - unreachable!("Attempt to export memory from a module instance. ") - } - EntityIndex::Global(_) => unsupported_diag!( - &self.session.diagnostics, - "Exporting of core module globals are not yet supported" - ), - }, - ExportItem::Name(_) => unsupported_diag!( - &self.session.diagnostics, - "Named core module exports are not yet supported" - ), - } - } - - /// Translate the given runtime import to the Miden IR component import - fn translate_import( - &self, - runtime_import_index: RuntimeImportIndex, - signature: TypeFuncIndex, - options: &CanonicalOptions, - wasm_component: &LinearComponent, - ) -> WasmResult { - let (import_idx, import_names) = &wasm_component.imports[runtime_import_index]; - if import_names.len() != 1 { - unsupported_diag!(&self.session.diagnostics, "multi-name imports not supported"); - } - let import_func_name = import_names.first().unwrap(); - let (full_interface_name, _) = wasm_component.import_types[*import_idx].clone(); - let interface_function = InterfaceFunctionIdent { - interface: InterfaceIdent::from_full_ident(full_interface_name.clone()), - function: Symbol::intern(import_func_name), - }; - let Some(import_metadata) = self.config.import_metadata.get(&interface_function) else { - return Err(self - .session - .diagnostics - .diagnostic(Severity::Error) - .with_message(format!( - "wasm error: import metadata for interface function {interface_function:?} \ - not found" - )) - .into_report()); - }; - let lifted_func_ty = convert_lifted_func_ty(&signature, &self.component_types); - - let component_import = midenc_hir::ComponentImport::CanonAbiImport(CanonAbiImport { - function_ty: lifted_func_ty, - interface_function, - digest: import_metadata.digest, - options: self.translate_canonical_options(options)?, - }); - Ok(component_import) - } - - /// Build an IR Component export from the given Wasm component export - fn build_export( - &self, - export: &Export, - name: &String, - component_builder: &mut ComponentBuilder, - ) -> WasmResult<()> { - match export { - Export::LiftedFunction { ty, func, options } => { - let export_name = Symbol::intern(name).into(); - let export = self.build_export_lifted_function(func, ty, options)?; - component_builder.add_export(export_name, export); - Ok(()) - } - Export::Instance(exports) => { - // Flatten any(nested) interface instance exports into the IR `Component` exports - for (name, export) in exports { - self.build_export(export, name, component_builder)?; - } - Ok(()) - } - Export::ModuleStatic(_) => { - unsupported_diag!( - &self.session.diagnostics, - "Static module exports are not supported" - ); - } - Export::ModuleImport(_) => unsupported_diag!( - &self.session.diagnostics, - "Exporting of an imported module is not supported" - ), - Export::Type(_) => { - // Besides the function exports the individual type are also exported from the - // component We can ignore them for now - Ok(()) - } - } - } - - /// Build an IR Component export from the given lifted Wasm core module function export - fn build_export_lifted_function( - &self, - func: &CoreDef, - ty: &TypeFuncIndex, - options: &CanonicalOptions, - ) -> WasmResult { - let func_ident = self.func_id_from_core_def(func)?; - let lifted_func_ty = convert_lifted_func_ty(ty, &self.component_types); - let export = midenc_hir::ComponentExport { - function: func_ident, - function_ty: lifted_func_ty, - options: self.translate_canonical_options(options)?, - }; - Ok(export) - } - - fn func_id_from_core_def(&self, func: &CoreDef) -> WasmResult { - Ok(match func { - CoreDef::Export(core_export) => { - let module = - &self.parsed_modules[self.module_instances_source[core_export.instance]].module; - let from = Ident::from(module.name().as_str()); - let module_name = from; - let func_name = match core_export.item { - ExportItem::Index(idx) => match idx { - EntityIndex::Function(func_idx) => module.func_name(func_idx), - EntityIndex::Table(_) | EntityIndex::Memory(_) | EntityIndex::Global(_) => { - unsupported_diag!( - &self.session.diagnostics, - "Exporting of non-function entity {:?} is not supported", - core_export - ); - } - }, - ExportItem::Name(_) => { - unsupported_diag!( - &self.session.diagnostics, - "Named exports are not yet supported" - ); - } - }; - - midenc_hir::FunctionIdent { - module: module_name, - function: midenc_hir::Ident::with_empty_span(func_name), - } - } - CoreDef::InstanceFlags(_) => { - unsupported_diag!( - &self.session.diagnostics, - "Component instance flags exports are not supported" - ); - } - CoreDef::Trampoline(_) => { - unsupported_diag!( - &self.session.diagnostics, - "Trampoline core module exports are not supported" - ); - } - }) - } - - fn translate_canonical_options( - &self, - options: &CanonicalOptions, - ) -> WasmResult { - if options.string_encoding != StringEncoding::Utf8 { - unsupported_diag!( - &self.session.diagnostics, - "UTF-8 is expected in CanonicalOptions, string transcoding is not yet supported" - ); - } - Ok(midenc_hir::CanonicalOptions { - realloc: options.realloc.map(|idx| self.reallocs[&idx]), - post_return: options.post_return.map(|idx| self.post_returns[&idx]), - }) - } -} - -/// Get the function id from the given Wasm core module import -fn function_id_from_import(_module: &Module, module_import: &ModuleImport) -> FunctionIdent { - let function_id = FunctionIdent { - module: Ident::from(module_import.module.as_str()), - function: Ident::from(module_import.field.as_str()), - }; - function_id -} - -/// Get the function id from the given Wasm func_idx in the given Wasm core exporting_module -fn function_id_from_export(exporting_module: &Module, func_idx: FuncIndex) -> FunctionIdent { - let func_name = exporting_module.func_name(func_idx); - - FunctionIdent { - module: exporting_module.name(), - function: Ident::with_empty_span(func_name), - } -} - -/// Convert the given Wasm component function type to the Miden IR lifted function type -fn convert_lifted_func_ty(ty: &TypeFuncIndex, component_types: &ComponentTypes) -> FunctionType { - let type_func = component_types[*ty].clone(); - let params_types = component_types[type_func.params].clone().types; - let results_types = component_types[type_func.results].clone().types; - let params = params_types - .iter() - .map(|ty| interface_type_to_ir(ty, component_types)) - .collect(); - let results = results_types - .iter() - .map(|ty| interface_type_to_ir(ty, component_types)) - .collect(); - FunctionType { - params, - results, - abi: Abi::Wasm, - } -} diff --git a/frontend-wasm/src/component/types/mod.rs b/frontend-wasm/src/component/types/mod.rs deleted file mode 100644 index 008608e5a..000000000 --- a/frontend-wasm/src/component/types/mod.rs +++ /dev/null @@ -1,1770 +0,0 @@ -//! Component level types for the Wasm component model. - -// Based on wasmtime v16.0 Wasm component translation - -#![allow(dead_code)] - -pub mod resources; - -use core::{hash::Hash, ops::Index}; - -use anyhow::{bail, Result}; -use midenc_hir::cranelift_entity::{EntityRef, PrimaryMap}; -use rustc_hash::FxHashMap; -use wasmparser::{collections::IndexSet, names::KebabString, types}; - -use self::resources::ResourcesBuilder; -use crate::{ - indices, - module::types::{ - convert_func_type, convert_global_type, convert_table_type, EntityType, ModuleTypes, - ModuleTypesBuilder, - }, - translation_utils::{DiscriminantSize, FlagsSize}, -}; - -/// Maximum nesting depth of a type allowed -/// -/// This constant isn't chosen via any scientific means and its main purpose is -/// to handle types via recursion without worrying about stack overflow. -const MAX_TYPE_DEPTH: u32 = 100; - -/// Canonical ABI-defined constant for the maximum number of "flat" parameters -/// to a wasm function, or the maximum number of parameters a core wasm function -/// will take for just the parameters used. Over this number the heap is used -/// for transferring parameters. -pub const MAX_FLAT_PARAMS: usize = 16; - -/// Canonical ABI-defined constant for the maximum number of "flat" results. -/// This number of results are returned directly from wasm and otherwise results -/// are transferred through memory. -pub const MAX_FLAT_RESULTS: usize = 1; - -indices! { - // ======================================================================== - // Like Core WebAssembly, the Component Model places each definition into - // one of a fixed set of index spaces, allowing the definition to be - // referred to by subsequent definitions (in the text and binary format) via - // a nonnegative integral index. When defining, validating and executing a - // component, there are 5 component-level index spaces: - - // (component) functions - // (component) values - // (component) types - // component instances - // components - - // and 2 additional core index spaces that contain core definition - // introduced by the Component Model that are not in WebAssembly 1.0 (yet: - // the module-linking proposal would add them): - - // module instances - // modules - - // for a total of 12 index spaces that need to be maintained by an implementation when, e.g., validating a component. - - // These indices are used during translation time only when we're translating a - // component at this time. - - /// Index within a component's component type index space. - pub struct ComponentTypeIndex(u32); - - /// Index within a component's module index space. - pub struct ModuleIndex(u32); - - /// Index within a component's component index space. - pub struct ComponentIndex(u32); - - /// Index within a component's module instance index space. - pub struct ModuleInstanceIndex(u32); - - /// Index within a component's component instance index space. - pub struct ComponentInstanceIndex(u32); - - /// Index within a component's component function index space. - pub struct ComponentFuncIndex(u32); - - - /// Index into the global list of modules found within an entire component. - /// - /// Parsed modulu are saved on the side to get fully translated after - /// the original component has finished being translated. - pub struct StaticModuleIndex(u32); - - // ======================================================================== - // These indices are used to lookup type information within a `TypeTables` - // structure. These represent generally deduplicated type information across - // an entire component and are a form of RTTI in a sense. - - /// Index pointing to a component's type (exports/imports with - /// component-model types) - pub struct TypeComponentIndex(u32); - - /// Index pointing to a component instance's type (exports with - /// component-model types, no imports) - pub struct TypeComponentInstanceIndex(u32); - - /// Index pointing to a core wasm module's type (exports/imports with - /// core wasm types) - pub struct TypeModuleIndex(u32); - - /// Index pointing to a component model function type with arguments/result - /// as interface types. - pub struct TypeFuncIndex(u32); - - /// Index pointing to a record type in the component model (aka a struct). - pub struct TypeRecordIndex(u32); - /// Index pointing to a variant type in the component model (aka an enum). - pub struct TypeVariantIndex(u32); - /// Index pointing to a tuple type in the component model. - pub struct TypeTupleIndex(u32); - /// Index pointing to a flags type in the component model. - pub struct TypeFlagsIndex(u32); - /// Index pointing to an enum type in the component model. - pub struct TypeEnumIndex(u32); - /// Index pointing to an option type in the component model (aka a - /// `Option`) - pub struct TypeOptionIndex(u32); - /// Index pointing to an result type in the component model (aka a - /// `Result`) - pub struct TypeResultIndex(u32); - /// Index pointing to a list type in the component model. - pub struct TypeListIndex(u32); - - /// Index pointing to a resource table within a component. - /// - /// This is a type index which isn't part of the component - /// model per-se (or at least not the binary format). This index represents - /// a pointer to a table of runtime information tracking state for resources - /// within a component. Tables are generated per-resource-per-component - /// meaning that if the exact same resource is imported into 4 subcomponents - /// then that's 5 tables: one for the defining component and one for each - /// subcomponent. - /// - /// All resource-related intrinsics operate on table-local indices which - /// indicate which table the intrinsic is modifying. Each resource table has - /// an origin resource type (defined by `ResourceIndex`) along with a - /// component instance that it's recorded for. - pub struct TypeResourceTableIndex(u32); - - /// Index pointing to a resource within a component. - /// - /// This index space covers all unique resource type definitions. For - /// example all unique imports come first and then all locally-defined - /// resources come next. Note that this does not count the number of runtime - /// tables required to track resources (that's `TypeResourceTableIndex` - /// instead). Instead this is a count of the number of unique - /// `(type (resource (rep ..)))` declarations within a component, plus - /// imports. - /// - /// This is then used for correlating various information such as - /// destructors, origin information, etc. - pub struct ResourceIndex(u32); - - /// Index pointing to a local resource defined within a component. - /// - /// This is similar to `FooIndex` and `DefinedFooIndex` for core wasm and - /// the idea here is that this is guaranteed to be a wasm-defined resource - /// which is connected to a component instance for example. - pub struct DefinedResourceIndex(u32); - - // ======================================================================== - // Index types used to identify modules and components during translation. - - /// Index into a "closed over variables" list for components used to - /// implement outer aliases. For more information on this see the - /// documentation for the `LexicalScope` structure. - pub struct ModuleUpvarIndex(u32); - - /// Same as `ModuleUpvarIndex` but for components. - pub struct ComponentUpvarIndex(u32); - - /// Same as `StaticModuleIndex` but for components. - pub struct StaticComponentIndex(u32); - - // ======================================================================== - // These indices are actually used at runtime when managing a component at - // this time. - - /// Index that represents a core wasm instance created at runtime. - /// - /// This is used to keep track of when instances are created and is able to - /// refer back to previously created instances for exports and such. - pub struct RuntimeInstanceIndex(u32); - - /// Same as `RuntimeInstanceIndex` but tracks component instances instead. - pub struct RuntimeComponentInstanceIndex(u32); - - /// Used to index imports into a `LinearComponent` - /// - /// This does not correspond to anything in the binary format for the - /// component model. - pub struct ImportIndex(u32); - - /// Index that represents a leaf item imported into a component where a - /// "leaf" means "not an instance". - /// - /// This does not correspond to anything in the binary format for the - /// component model. - pub struct RuntimeImportIndex(u32); - - /// Index that represents a lowered host function and is used to represent - /// host function lowerings with options and such. - /// - /// This does not correspond to anything in the binary format for the - /// component model. - pub struct LoweredIndex(u32); - - /// Index representing a linear memory extracted from a wasm instance. - /// - /// This does not correspond to anything in the binary format for the - /// component model. - pub struct RuntimeMemoryIndex(u32); - - /// Same as `RuntimeMemoryIndex` except for the `realloc` function. - pub struct RuntimeReallocIndex(u32); - - /// Same as `RuntimeMemoryIndex` except for the `post-return` function. - pub struct RuntimePostReturnIndex(u32); - - /// Index for all trampolines that are defined for a - /// component. - pub struct TrampolineIndex(u32); - - /// Index type of a signature (imported or defined) inside all of the core modules of the flattened root component. - pub struct SignatureIndex(u32); - -} - -/// Equivalent of `EntityIndex` but for the component model instead of core -/// wasm. -#[derive(Debug, Clone, Copy)] -pub enum ComponentItem { - Func(ComponentFuncIndex), - Module(ModuleIndex), - Component(ComponentIndex), - ComponentInstance(ComponentInstanceIndex), - Type(types::ComponentAnyTypeId), -} - -/// Runtime information about the type information contained within a component. -/// -/// One of these is created per top-level component which describes all of the -/// types contained within the top-level component itself. Each sub-component -/// will have a pointer to this value as well. -#[derive(Default)] -pub struct ComponentTypes { - modules: PrimaryMap, - components: PrimaryMap, - component_instances: PrimaryMap, - functions: PrimaryMap, - lists: PrimaryMap, - records: PrimaryMap, - variants: PrimaryMap, - tuples: PrimaryMap, - enums: PrimaryMap, - flags: PrimaryMap, - options: PrimaryMap, - results: PrimaryMap, - resource_tables: PrimaryMap, - - module_types: ModuleTypes, -} - -impl ComponentTypes { - /// Returns the core wasm module types known within this component. - pub fn module_types(&self) -> &ModuleTypes { - &self.module_types - } - - /// Returns the canonical ABI information about the specified type. - pub fn canonical_abi(&self, ty: &InterfaceType) -> &CanonicalAbiInfo { - match ty { - InterfaceType::U8 | InterfaceType::S8 | InterfaceType::Bool => { - &CanonicalAbiInfo::SCALAR1 - } - - InterfaceType::U16 | InterfaceType::S16 => &CanonicalAbiInfo::SCALAR2, - - InterfaceType::U32 - | InterfaceType::S32 - | InterfaceType::Float32 - | InterfaceType::Char - | InterfaceType::Own(_) - | InterfaceType::Borrow(_) => &CanonicalAbiInfo::SCALAR4, - - InterfaceType::U64 | InterfaceType::S64 | InterfaceType::Float64 => { - &CanonicalAbiInfo::SCALAR8 - } - - InterfaceType::String | InterfaceType::List(_) => &CanonicalAbiInfo::POINTER_PAIR, - - InterfaceType::Record(i) => &self[*i].abi, - InterfaceType::Variant(i) => &self[*i].abi, - InterfaceType::Tuple(i) => &self[*i].abi, - InterfaceType::Flags(i) => &self[*i].abi, - InterfaceType::Enum(i) => &self[*i].abi, - InterfaceType::Option(i) => &self[*i].abi, - InterfaceType::Result(i) => &self[*i].abi, - } - } -} - -macro_rules! impl_index { - ($(impl Index<$ty:ident> for ComponentTypes { $output:ident => $field:ident })*) => ($( - impl std::ops::Index<$ty> for ComponentTypes { - type Output = $output; - #[inline] - fn index(&self, idx: $ty) -> &$output { - &self.$field[idx] - } - } - - impl std::ops::Index<$ty> for ComponentTypesBuilder { - type Output = $output; - #[inline] - fn index(&self, idx: $ty) -> &$output { - &self.component_types[idx] - } - } - )*) -} - -impl_index! { - impl Index for ComponentTypes { TypeModule => modules } - impl Index for ComponentTypes { TypeComponent => components } - impl Index for ComponentTypes { TypeComponentInstance => component_instances } - impl Index for ComponentTypes { TypeFunc => functions } - impl Index for ComponentTypes { TypeRecord => records } - impl Index for ComponentTypes { TypeVariant => variants } - impl Index for ComponentTypes { TypeTuple => tuples } - impl Index for ComponentTypes { TypeEnum => enums } - impl Index for ComponentTypes { TypeFlags => flags } - impl Index for ComponentTypes { TypeOption => options } - impl Index for ComponentTypes { TypeResult => results } - impl Index for ComponentTypes { TypeList => lists } - impl Index for ComponentTypes { TypeResourceTable => resource_tables } -} - -// Additionally forward anything that can index `ModuleTypes` to `ModuleTypes` -// (aka `SignatureIndex`) -impl Index for ComponentTypes -where - ModuleTypes: Index, -{ - type Output = >::Output; - - fn index(&self, idx: T) -> &Self::Output { - self.module_types.index(idx) - } -} - -impl Index for ComponentTypesBuilder -where - ModuleTypes: Index, -{ - type Output = >::Output; - - fn index(&self, idx: T) -> &Self::Output { - self.module_types.index(idx) - } -} - -/// Structured used to build a [`ComponentTypes`] during translation. -/// -/// This contains tables to intern any component types found as well as -/// managing building up core wasm [`ModuleTypes`] as well. -#[derive(Default)] -pub struct ComponentTypesBuilder { - functions: FxHashMap, - lists: FxHashMap, - records: FxHashMap, - variants: FxHashMap, - tuples: FxHashMap, - enums: FxHashMap, - flags: FxHashMap, - options: FxHashMap, - results: FxHashMap, - - component_types: ComponentTypes, - module_types: ModuleTypesBuilder, - - // Cache of what the "flat" representation of all types are which is only - // used at translation. - type_info: TypeInformationCache, - - resources: ResourcesBuilder, -} - -macro_rules! intern_and_fill_flat_types { - ($me:ident, $name:ident, $val:ident) => {{ - if let Some(idx) = $me.$name.get(&$val) { - return *idx; - } - let idx = $me.component_types.$name.push($val.clone()); - let mut info = TypeInformation::new(); - info.$name($me, &$val); - let idx2 = $me.type_info.$name.push(info); - assert_eq!(idx, idx2); - $me.$name.insert($val, idx); - return idx; - }}; -} - -impl ComponentTypesBuilder { - /// Finishes this list of component types and returns the finished - /// structure. - pub fn finish(mut self) -> ComponentTypes { - self.component_types.module_types = self.module_types.finish(); - self.component_types - } - - /// Returns the underlying builder used to build up core wasm module types. - /// - /// Note that this is shared across all modules found within a component to - /// improve the wins from deduplicating function signatures. - pub fn module_types_builder(&self) -> &ModuleTypesBuilder { - &self.module_types - } - - /// Same as `module_types_builder`, but `mut`. - pub fn module_types_builder_mut(&mut self) -> &mut ModuleTypesBuilder { - &mut self.module_types - } - - /// Returns the number of resource tables allocated so far, or the maximum - /// `TypeResourceTableIndex`. - pub fn num_resource_tables(&self) -> usize { - self.component_types.resource_tables.len() - } - - /// Returns a mutable reference to the underlying `ResourcesBuilder`. - pub fn resources_mut(&mut self) -> &mut ResourcesBuilder { - &mut self.resources - } - - /// Work around the borrow checker to borrow two sub-fields simultaneously - /// externally. - pub fn resources_mut_and_types(&mut self) -> (&mut ResourcesBuilder, &ComponentTypes) { - (&mut self.resources, &self.component_types) - } - - /// Converts a wasmparser `ComponentFuncType` - pub fn convert_component_func_type( - &mut self, - types: types::TypesRef<'_>, - id: types::ComponentFuncTypeId, - ) -> Result { - let ty = &types[id]; - let params = ty - .params - .iter() - .map(|(_name, ty)| self.valtype(types, ty)) - .collect::>()?; - let results = ty - .results - .iter() - .map(|(_name, ty)| self.valtype(types, ty)) - .collect::>()?; - let ty = TypeFunc { - params: self.new_tuple_type(params), - results: self.new_tuple_type(results), - }; - Ok(self.add_func_type(ty)) - } - - /// Converts a wasmparser `ComponentEntityType` - pub fn convert_component_entity_type( - &mut self, - types: types::TypesRef<'_>, - ty: types::ComponentEntityType, - ) -> Result { - Ok(match ty { - types::ComponentEntityType::Module(id) => { - TypeDef::Module(self.convert_module(types, id)?) - } - types::ComponentEntityType::Component(id) => { - TypeDef::Component(self.convert_component(types, id)?) - } - types::ComponentEntityType::Instance(id) => { - TypeDef::ComponentInstance(self.convert_instance(types, id)?) - } - types::ComponentEntityType::Func(id) => { - TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) - } - types::ComponentEntityType::Type { created, .. } => match created { - types::ComponentAnyTypeId::Defined(id) => { - TypeDef::Interface(self.defined_type(types, id)?) - } - types::ComponentAnyTypeId::Resource(id) => { - TypeDef::Resource(self.resource_id(id.resource())) - } - _ => bail!("unsupported type export"), - }, - types::ComponentEntityType::Value(_) => bail!("values not supported"), - }) - } - - /// Converts a wasmparser `Type` - pub fn convert_type( - &mut self, - types: types::TypesRef<'_>, - id: types::ComponentAnyTypeId, - ) -> Result { - Ok(match id { - types::ComponentAnyTypeId::Defined(id) => { - TypeDef::Interface(self.defined_type(types, id)?) - } - types::ComponentAnyTypeId::Component(id) => { - TypeDef::Component(self.convert_component(types, id)?) - } - types::ComponentAnyTypeId::Instance(id) => { - TypeDef::ComponentInstance(self.convert_instance(types, id)?) - } - types::ComponentAnyTypeId::Func(id) => { - TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) - } - types::ComponentAnyTypeId::Resource(id) => { - TypeDef::Resource(self.resource_id(id.resource())) - } - }) - } - - fn convert_component( - &mut self, - types: types::TypesRef<'_>, - id: types::ComponentTypeId, - ) -> Result { - let ty = &types[id]; - let mut result = TypeComponent::default(); - for (name, ty) in ty.imports.iter() { - result - .imports - .insert(name.clone(), self.convert_component_entity_type(types, *ty)?); - } - for (name, ty) in ty.exports.iter() { - result - .exports - .insert(name.clone(), self.convert_component_entity_type(types, *ty)?); - } - Ok(self.component_types.components.push(result)) - } - - fn convert_instance( - &mut self, - types: types::TypesRef<'_>, - id: types::ComponentInstanceTypeId, - ) -> Result { - let ty = &types[id]; - let mut result = TypeComponentInstance::default(); - for (name, ty) in ty.exports.iter() { - result - .exports - .insert(name.clone(), self.convert_component_entity_type(types, *ty)?); - } - Ok(self.component_types.component_instances.push(result)) - } - - fn convert_module( - &mut self, - types: types::TypesRef<'_>, - id: types::ComponentCoreModuleTypeId, - ) -> Result { - let ty = &types[id]; - let mut result = TypeModule::default(); - for ((module, field), ty) in ty.imports.iter() { - result - .imports - .insert((module.clone(), field.clone()), self.entity_type(types, ty)?); - } - for (name, ty) in ty.exports.iter() { - result.exports.insert(name.clone(), self.entity_type(types, ty)?); - } - Ok(self.component_types.modules.push(result)) - } - - fn entity_type( - &mut self, - types: types::TypesRef<'_>, - ty: &types::EntityType, - ) -> Result { - Ok(match ty { - types::EntityType::Func(idx) => { - let ty = types[*idx].unwrap_func(); - let ty = convert_func_type(ty); - EntityType::Function(self.module_types_builder_mut().wasm_func_type(*idx, ty)) - } - types::EntityType::Table(ty) => EntityType::Table(convert_table_type(ty)), - types::EntityType::Memory(ty) => EntityType::Memory((*ty).into()), - types::EntityType::Global(ty) => EntityType::Global(convert_global_type(ty)), - types::EntityType::Tag(_) => bail!("exceptions proposal not implemented"), - }) - } - - fn defined_type( - &mut self, - types: types::TypesRef<'_>, - id: types::ComponentDefinedTypeId, - ) -> Result { - let ret = match &types[id] { - types::ComponentDefinedType::Primitive(ty) => ty.into(), - types::ComponentDefinedType::Record(e) => { - InterfaceType::Record(self.record_type(types, e)?) - } - types::ComponentDefinedType::Variant(e) => { - InterfaceType::Variant(self.variant_type(types, e)?) - } - types::ComponentDefinedType::List(e) => InterfaceType::List(self.list_type(types, e)?), - types::ComponentDefinedType::Tuple(e) => { - InterfaceType::Tuple(self.tuple_type(types, e)?) - } - types::ComponentDefinedType::Flags(e) => InterfaceType::Flags(self.flags_type(e)), - types::ComponentDefinedType::Enum(e) => InterfaceType::Enum(self.enum_type(e)), - types::ComponentDefinedType::Option(e) => { - InterfaceType::Option(self.option_type(types, e)?) - } - types::ComponentDefinedType::Result { ok, err } => { - InterfaceType::Result(self.result_type(types, ok, err)?) - } - types::ComponentDefinedType::Own(r) => { - InterfaceType::Own(self.resource_id(r.resource())) - } - types::ComponentDefinedType::Borrow(r) => { - InterfaceType::Borrow(self.resource_id(r.resource())) - } - }; - let info = self.type_information(&ret); - if info.depth > MAX_TYPE_DEPTH { - bail!("type nesting is too deep"); - } - Ok(ret) - } - - fn valtype( - &mut self, - types: types::TypesRef<'_>, - ty: &types::ComponentValType, - ) -> Result { - match ty { - types::ComponentValType::Primitive(p) => Ok(p.into()), - types::ComponentValType::Type(id) => self.defined_type(types, *id), - } - } - - fn record_type( - &mut self, - types: types::TypesRef<'_>, - ty: &types::RecordType, - ) -> Result { - let fields = ty - .fields - .iter() - .map(|(name, ty)| { - Ok(RecordField { - name: name.to_string(), - ty: self.valtype(types, ty)?, - }) - }) - .collect::>>()?; - let abi = CanonicalAbiInfo::record( - fields.iter().map(|field| self.component_types.canonical_abi(&field.ty)), - ); - Ok(self.add_record_type(TypeRecord { fields, abi })) - } - - fn variant_type( - &mut self, - types: types::TypesRef<'_>, - ty: &types::VariantType, - ) -> Result { - let cases = ty - .cases - .iter() - .map(|(name, case)| { - if case.refines.is_some() { - bail!("refines is not supported at this time"); - } - Ok(VariantCase { - name: name.to_string(), - ty: match &case.ty.as_ref() { - Some(ty) => Some(self.valtype(types, ty)?), - None => None, - }, - }) - }) - .collect::>>()?; - let (info, abi) = VariantInfo::new( - cases - .iter() - .map(|c| c.ty.as_ref().map(|ty| self.component_types.canonical_abi(ty))), - ); - Ok(self.add_variant_type(TypeVariant { cases, abi, info })) - } - - fn tuple_type( - &mut self, - types: types::TypesRef<'_>, - ty: &types::TupleType, - ) -> Result { - let types = ty - .types - .iter() - .map(|ty| self.valtype(types, ty)) - .collect::>>()?; - Ok(self.new_tuple_type(types)) - } - - fn new_tuple_type(&mut self, types: Box<[InterfaceType]>) -> TypeTupleIndex { - let abi = - CanonicalAbiInfo::record(types.iter().map(|ty| self.component_types.canonical_abi(ty))); - self.add_tuple_type(TypeTuple { types, abi }) - } - - fn flags_type(&mut self, flags: &IndexSet) -> TypeFlagsIndex { - let flags = TypeFlags { - names: flags.iter().map(|s| s.to_string()).collect(), - abi: CanonicalAbiInfo::flags(flags.len()), - }; - self.add_flags_type(flags) - } - - fn enum_type(&mut self, variants: &IndexSet) -> TypeEnumIndex { - let names = variants.iter().map(|s| s.to_string()).collect::>(); - let (info, abi) = VariantInfo::new(names.iter().map(|_| None)); - self.add_enum_type(TypeEnum { names, abi, info }) - } - - fn option_type( - &mut self, - types: types::TypesRef<'_>, - ty: &types::ComponentValType, - ) -> Result { - let ty = self.valtype(types, ty)?; - let (info, abi) = VariantInfo::new([None, Some(self.component_types.canonical_abi(&ty))]); - Ok(self.add_option_type(TypeOption { ty, abi, info })) - } - - fn result_type( - &mut self, - types: types::TypesRef<'_>, - ok: &Option, - err: &Option, - ) -> Result { - let ok = match ok { - Some(ty) => Some(self.valtype(types, ty)?), - None => None, - }; - let err = match err { - Some(ty) => Some(self.valtype(types, ty)?), - None => None, - }; - let (info, abi) = VariantInfo::new([ - ok.as_ref().map(|t| self.component_types.canonical_abi(t)), - err.as_ref().map(|t| self.component_types.canonical_abi(t)), - ]); - Ok(self.add_result_type(TypeResult { ok, err, abi, info })) - } - - fn list_type( - &mut self, - types: types::TypesRef<'_>, - ty: &types::ComponentValType, - ) -> Result { - let element = self.valtype(types, ty)?; - Ok(self.add_list_type(TypeList { element })) - } - - /// Converts a wasmparser `id`, which must point to a resource, to its - /// corresponding `TypeResourceTableIndex`. - pub fn resource_id(&mut self, id: types::ResourceId) -> TypeResourceTableIndex { - self.resources.convert(id, &mut self.component_types) - } - - /// Interns a new function type within this type information. - pub fn add_func_type(&mut self, ty: TypeFunc) -> TypeFuncIndex { - intern(&mut self.functions, &mut self.component_types.functions, ty) - } - - /// Interns a new record type within this type information. - pub fn add_record_type(&mut self, ty: TypeRecord) -> TypeRecordIndex { - intern_and_fill_flat_types!(self, records, ty) - } - - /// Interns a new flags type within this type information. - pub fn add_flags_type(&mut self, ty: TypeFlags) -> TypeFlagsIndex { - intern_and_fill_flat_types!(self, flags, ty) - } - - /// Interns a new tuple type within this type information. - pub fn add_tuple_type(&mut self, ty: TypeTuple) -> TypeTupleIndex { - intern_and_fill_flat_types!(self, tuples, ty) - } - - /// Interns a new variant type within this type information. - pub fn add_variant_type(&mut self, ty: TypeVariant) -> TypeVariantIndex { - intern_and_fill_flat_types!(self, variants, ty) - } - - /// Interns a new enum type within this type information. - pub fn add_enum_type(&mut self, ty: TypeEnum) -> TypeEnumIndex { - intern_and_fill_flat_types!(self, enums, ty) - } - - /// Interns a new option type within this type information. - pub fn add_option_type(&mut self, ty: TypeOption) -> TypeOptionIndex { - intern_and_fill_flat_types!(self, options, ty) - } - - /// Interns a new result type within this type information. - pub fn add_result_type(&mut self, ty: TypeResult) -> TypeResultIndex { - intern_and_fill_flat_types!(self, results, ty) - } - - /// Interns a new type within this type information. - pub fn add_list_type(&mut self, ty: TypeList) -> TypeListIndex { - intern_and_fill_flat_types!(self, lists, ty) - } - - /// Returns the canonical ABI information about the specified type. - pub fn canonical_abi(&self, ty: &InterfaceType) -> &CanonicalAbiInfo { - self.component_types.canonical_abi(ty) - } - - /// Returns the "flat types" for the given interface type used in the - /// canonical ABI. - /// - /// Returns `None` if the type is too large to be represented via flat types - /// in the canonical abi. - pub fn flat_types(&self, ty: &InterfaceType) -> Option> { - self.type_information(ty).flat.as_flat_types() - } - - /// Returns whether the type specified contains any borrowed resources - /// within it. - pub fn ty_contains_borrow_resource(&self, ty: &InterfaceType) -> bool { - self.type_information(ty).has_borrow - } - - fn type_information(&self, ty: &InterfaceType) -> &TypeInformation { - match ty { - InterfaceType::U8 - | InterfaceType::S8 - | InterfaceType::Bool - | InterfaceType::U16 - | InterfaceType::S16 - | InterfaceType::U32 - | InterfaceType::S32 - | InterfaceType::Char - | InterfaceType::Own(_) => { - static INFO: TypeInformation = TypeInformation::primitive(FlatType::I32); - &INFO - } - InterfaceType::Borrow(_) => { - static INFO: TypeInformation = { - let mut info = TypeInformation::primitive(FlatType::I32); - info.has_borrow = true; - info - }; - &INFO - } - InterfaceType::U64 | InterfaceType::S64 => { - static INFO: TypeInformation = TypeInformation::primitive(FlatType::I64); - &INFO - } - InterfaceType::Float32 => { - static INFO: TypeInformation = TypeInformation::primitive(FlatType::F32); - &INFO - } - InterfaceType::Float64 => { - static INFO: TypeInformation = TypeInformation::primitive(FlatType::F64); - &INFO - } - InterfaceType::String => { - static INFO: TypeInformation = TypeInformation::string(); - &INFO - } - - InterfaceType::List(i) => &self.type_info.lists[*i], - InterfaceType::Record(i) => &self.type_info.records[*i], - InterfaceType::Variant(i) => &self.type_info.variants[*i], - InterfaceType::Tuple(i) => &self.type_info.tuples[*i], - InterfaceType::Flags(i) => &self.type_info.flags[*i], - InterfaceType::Enum(i) => &self.type_info.enums[*i], - InterfaceType::Option(i) => &self.type_info.options[*i], - InterfaceType::Result(i) => &self.type_info.results[*i], - } - } -} - -fn intern(map: &mut FxHashMap, list: &mut PrimaryMap, item: T) -> U -where - T: Hash + Clone + Eq, - U: Copy + EntityRef, -{ - if let Some(idx) = map.get(&item) { - return *idx; - } - let idx = list.push(item.clone()); - map.insert(item, idx); - idx -} - -/// Types of imports and exports in the component model. -/// -/// These types are what's available for import and export in components. Note -/// that all indirect indices contained here are intended to be looked up -/// through a sibling `ComponentTypes` structure. -#[derive(Copy, Clone, Debug)] -pub enum TypeDef { - /// A component and its type. - Component(TypeComponentIndex), - /// An instance of a component. - ComponentInstance(TypeComponentInstanceIndex), - /// A component function, not to be confused with a core wasm function. - ComponentFunc(TypeFuncIndex), - /// An interface type. - Interface(InterfaceType), - /// A core wasm module and its type. - Module(TypeModuleIndex), - /// A resource type which operates on the specified resource table. - /// - /// Note that different resource tables may point to the same underlying - /// actual resource type, but that's a private detail. - Resource(TypeResourceTableIndex), -} - -/// The type of a module in the component model. -/// -/// Note that this is not to be confused with `TypeComponent` below. This is -/// intended only for core wasm modules, not for components. -#[derive(Default)] -pub struct TypeModule { - /// The values that this module imports. - /// - /// Note that the value of this map is a core wasm `EntityType`, not a - /// component model `TypeRef`. Additionally note that this reflects the - /// two-level namespace of core WebAssembly, but unlike core wasm all import - /// names are required to be unique to describe a module in the component - /// model. - pub imports: FxHashMap<(String, String), EntityType>, - - /// The values that this module exports. - /// - /// Note that the value of this map is the core wasm `EntityType` to - /// represent that core wasm items are being exported. - pub exports: FxHashMap, -} - -/// The type of a component in the component model. -#[derive(Default)] -pub struct TypeComponent { - /// The named values that this component imports. - pub imports: FxHashMap, - /// The named values that this component exports. - pub exports: FxHashMap, -} - -/// The type of a component instance in the component model, or an instantiated -/// component. -/// -/// Component instances only have exports of types in the component model. -#[derive(Default)] -pub struct TypeComponentInstance { - /// The list of exports that this component has along with their types. - pub exports: FxHashMap, -} - -/// A component function type in the component model. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeFunc { - /// Parameters to the function represented as a tuple. - pub params: TypeTupleIndex, - /// Results of the function represented as a tuple. - pub results: TypeTupleIndex, -} - -/// All possible interface types that values can have. -/// -/// This list represents an exhaustive listing of interface types and the -/// shapes that they can take. Note that this enum is considered an "index" of -/// forms where for non-primitive types a `ComponentTypes` structure is used to -/// lookup further information based on the index found here. -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -#[allow(missing_docs)] -pub enum InterfaceType { - Bool, - S8, - U8, - S16, - U16, - S32, - U32, - S64, - U64, - Float32, - Float64, - Char, - String, - Record(TypeRecordIndex), - Variant(TypeVariantIndex), - List(TypeListIndex), - Tuple(TypeTupleIndex), - Flags(TypeFlagsIndex), - Enum(TypeEnumIndex), - Option(TypeOptionIndex), - Result(TypeResultIndex), - Own(TypeResourceTableIndex), - Borrow(TypeResourceTableIndex), -} - -impl From<&wasmparser::PrimitiveValType> for InterfaceType { - fn from(ty: &wasmparser::PrimitiveValType) -> InterfaceType { - match ty { - wasmparser::PrimitiveValType::Bool => InterfaceType::Bool, - wasmparser::PrimitiveValType::S8 => InterfaceType::S8, - wasmparser::PrimitiveValType::U8 => InterfaceType::U8, - wasmparser::PrimitiveValType::S16 => InterfaceType::S16, - wasmparser::PrimitiveValType::U16 => InterfaceType::U16, - wasmparser::PrimitiveValType::S32 => InterfaceType::S32, - wasmparser::PrimitiveValType::U32 => InterfaceType::U32, - wasmparser::PrimitiveValType::S64 => InterfaceType::S64, - wasmparser::PrimitiveValType::U64 => InterfaceType::U64, - wasmparser::PrimitiveValType::F32 => InterfaceType::Float32, - wasmparser::PrimitiveValType::F64 => InterfaceType::Float64, - wasmparser::PrimitiveValType::Char => InterfaceType::Char, - wasmparser::PrimitiveValType::String => InterfaceType::String, - } - } -} - -/// Bye information about a type in the canonical ABI -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct CanonicalAbiInfo { - /// The byte-size of this type in a 32-bit memory. - pub size32: u32, - /// The byte-alignment of this type in a 32-bit memory. - pub align32: u32, - /// The number of types it takes to represents this type in the "flat" - /// representation of the canonical abi where everything is passed as - /// immediate arguments or results. - /// - /// If this is `None` then this type is not representable in the flat ABI - /// because it is too large. - pub flat_count: Option, -} - -impl Default for CanonicalAbiInfo { - fn default() -> CanonicalAbiInfo { - CanonicalAbiInfo { - size32: 0, - align32: 1, - flat_count: Some(0), - } - } -} - -const fn align_to(a: u32, b: u32) -> u32 { - assert!(b.is_power_of_two()); - (a + (b - 1)) & !(b - 1) -} - -const fn max(a: u32, b: u32) -> u32 { - if a > b { - a - } else { - b - } -} - -impl CanonicalAbiInfo { - /// ABI information for lists/strings which are "pointer pairs" - pub const POINTER_PAIR: CanonicalAbiInfo = CanonicalAbiInfo { - size32: 8, - align32: 4, - flat_count: Some(2), - }; - /// ABI information for one-byte scalars. - pub const SCALAR1: CanonicalAbiInfo = CanonicalAbiInfo::scalar(1); - /// ABI information for two-byte scalars. - pub const SCALAR2: CanonicalAbiInfo = CanonicalAbiInfo::scalar(2); - /// ABI information for four-byte scalars. - pub const SCALAR4: CanonicalAbiInfo = CanonicalAbiInfo::scalar(4); - /// ABI information for eight-byte scalars. - pub const SCALAR8: CanonicalAbiInfo = CanonicalAbiInfo::scalar(8); - /// ABI information for zero-sized types. - const ZERO: CanonicalAbiInfo = CanonicalAbiInfo { - size32: 0, - align32: 1, - flat_count: Some(0), - }; - - const fn scalar(size: u32) -> CanonicalAbiInfo { - CanonicalAbiInfo { - size32: size, - align32: size, - flat_count: Some(1), - } - } - - /// Returns the abi for a record represented by the specified fields. - pub fn record<'a>(fields: impl Iterator) -> CanonicalAbiInfo { - // NB: this is basically a duplicate copy of - // `CanonicalAbiInfo::record_static` and the two should be kept in sync. - - let mut ret = CanonicalAbiInfo::default(); - for field in fields { - ret.size32 = align_to(ret.size32, field.align32) + field.size32; - ret.align32 = ret.align32.max(field.align32); - ret.flat_count = add_flat(ret.flat_count, field.flat_count); - } - ret.size32 = align_to(ret.size32, ret.align32); - ret - } - - /// Same as `CanonicalAbiInfo::record` but in a `const`-friendly context. - pub const fn record_static(fields: &[CanonicalAbiInfo]) -> CanonicalAbiInfo { - // NB: this is basically a duplicate copy of `CanonicalAbiInfo::record` - // and the two should be kept in sync. - - let mut ret = CanonicalAbiInfo::ZERO; - let mut i = 0; - while i < fields.len() { - let field = &fields[i]; - ret.size32 = align_to(ret.size32, field.align32) + field.size32; - ret.align32 = max(ret.align32, field.align32); - ret.flat_count = add_flat(ret.flat_count, field.flat_count); - i += 1; - } - ret.size32 = align_to(ret.size32, ret.align32); - ret - } - - /// Returns the delta from the current value of `offset` to align properly - /// and read the next record field of type `abi` for 32-bit memories. - pub fn next_field32(&self, offset: &mut u32) -> u32 { - *offset = align_to(*offset, self.align32) + self.size32; - *offset - self.size32 - } - - /// Same as `next_field32`, but bumps a usize pointer - pub fn next_field32_size(&self, offset: &mut usize) -> usize { - let cur = u32::try_from(*offset).unwrap(); - let cur = align_to(cur, self.align32) + self.size32; - *offset = usize::try_from(cur).unwrap(); - usize::try_from(cur - self.size32).unwrap() - } - - /// Returns ABI information for a structure which contains `count` flags. - pub const fn flags(count: usize) -> CanonicalAbiInfo { - let (size, align, flat_count) = match FlagsSize::from_count(count) { - FlagsSize::Size0 => (0, 1, 0), - FlagsSize::Size1 => (1, 1, 1), - FlagsSize::Size2 => (2, 2, 1), - FlagsSize::Size4Plus(n) => ((n as u32) * 4, 4, n), - }; - CanonicalAbiInfo { - size32: size, - align32: align, - flat_count: Some(flat_count), - } - } - - fn variant<'a, I>(cases: I) -> CanonicalAbiInfo - where - I: IntoIterator>, - I::IntoIter: ExactSizeIterator, - { - // NB: this is basically a duplicate definition of - // `CanonicalAbiInfo::variant_static`, these should be kept in sync. - - let cases = cases.into_iter(); - let discrim_size = u32::from(DiscriminantSize::from_count(cases.len()).unwrap()); - let mut max_size32 = 0; - let mut max_align32 = discrim_size; - let mut max_case_count = Some(0); - for case in cases.flatten() { - max_size32 = max_size32.max(case.size32); - max_align32 = max_align32.max(case.align32); - max_case_count = max_flat(max_case_count, case.flat_count); - } - CanonicalAbiInfo { - size32: align_to(align_to(discrim_size, max_align32) + max_size32, max_align32), - align32: max_align32, - flat_count: add_flat(max_case_count, Some(1)), - } - } - - /// Same as `CanonicalAbiInfo::variant` but `const`-safe - pub const fn variant_static(cases: &[Option]) -> CanonicalAbiInfo { - // NB: this is basically a duplicate definition of - // `CanonicalAbiInfo::variant`, these should be kept in sync. - - let discrim_size = match DiscriminantSize::from_count(cases.len()) { - Some(size) => size.byte_size(), - None => unreachable!(), - }; - let mut max_size32 = 0; - let mut max_align32 = discrim_size; - let mut max_case_count = Some(0); - let mut i = 0; - while i < cases.len() { - let case = &cases[i]; - if let Some(case) = case { - max_size32 = max(max_size32, case.size32); - max_align32 = max(max_align32, case.align32); - max_case_count = max_flat(max_case_count, case.flat_count); - } - i += 1; - } - CanonicalAbiInfo { - size32: align_to(align_to(discrim_size, max_align32) + max_size32, max_align32), - align32: max_align32, - flat_count: add_flat(max_case_count, Some(1)), - } - } - - /// Returns the flat count of this ABI information so long as the count - /// doesn't exceed the `max` specified. - pub fn flat_count(&self, max: usize) -> Option { - let flat = usize::from(self.flat_count?); - if flat > max { - None - } else { - Some(flat) - } - } -} - -/// ABI information about the representation of a variant. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct VariantInfo { - /// The size of the discriminant used. - pub size: DiscriminantSize, - /// The offset of the payload from the start of the variant in 32-bit - /// memories. - pub payload_offset32: u32, -} - -impl VariantInfo { - /// Returns the abi information for a variant represented by the specified - /// cases. - pub fn new<'a, I>(cases: I) -> (VariantInfo, CanonicalAbiInfo) - where - I: IntoIterator>, - I::IntoIter: ExactSizeIterator, - { - let cases = cases.into_iter(); - let size = DiscriminantSize::from_count(cases.len()).unwrap(); - let abi = CanonicalAbiInfo::variant(cases); - ( - VariantInfo { - size, - payload_offset32: align_to(u32::from(size), abi.align32), - }, - abi, - ) - } - - pub const fn new_static(cases: &[Option]) -> VariantInfo { - let size = match DiscriminantSize::from_count(cases.len()) { - Some(size) => size, - None => unreachable!(), - }; - let abi = CanonicalAbiInfo::variant_static(cases); - VariantInfo { - size, - payload_offset32: align_to(size.byte_size(), abi.align32), - } - } -} - -/// Shape of a "record" type in interface types. -/// -/// This is equivalent to a `struct` in Rust. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeRecord { - /// The fields that are contained within this struct type. - pub fields: Box<[RecordField]>, - /// Byte information about this type in the canonical ABI. - pub abi: CanonicalAbiInfo, -} - -/// One field within a record. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct RecordField { - /// The name of the field, unique amongst all fields in a record. - pub name: String, - /// The type that this field contains. - pub ty: InterfaceType, -} - -/// Shape of a "variant" type in interface types. -/// -/// Variants are close to Rust `enum` declarations where a value is one of many -/// cases and each case has a unique name and an optional payload associated -/// with it. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeVariant { - /// The list of cases that this variant can take. - pub cases: Box<[VariantCase]>, - /// Byte information about this type in the canonical ABI. - pub abi: CanonicalAbiInfo, - /// Byte information about this variant type. - pub info: VariantInfo, -} - -/// One case of a `variant` type which contains the name of the variant as well -/// as the payload. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct VariantCase { - /// Name of the variant, unique amongst all cases in a variant. - pub name: String, - /// Optional type associated with this payload. - pub ty: Option, -} - -/// Shape of a "tuple" type in interface types. -/// -/// This is largely the same as a tuple in Rust, basically a record with -/// unnamed fields. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeTuple { - /// The types that are contained within this tuple. - pub types: Box<[InterfaceType]>, - /// Byte information about this type in the canonical ABI. - pub abi: CanonicalAbiInfo, -} - -/// Shape of a "flags" type in interface types. -/// -/// This can be thought of as a record-of-bools, although the representation is -/// more efficient as bitflags. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeFlags { - /// The names of all flags, all of which are unique. - pub names: Box<[String]>, - /// Byte information about this type in the canonical ABI. - pub abi: CanonicalAbiInfo, -} - -/// Shape of an "enum" type in interface types, not to be confused with a Rust -/// `enum` type. -/// -/// In interface types enums are simply a bag of names, and can be seen as a -/// variant where all payloads are `Unit`. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeEnum { - /// The names of this enum, all of which are unique. - pub names: Box<[String]>, - /// Byte information about this type in the canonical ABI. - pub abi: CanonicalAbiInfo, - /// Byte information about this variant type. - pub info: VariantInfo, -} - -/// Shape of an "option" interface type. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeOption { - /// The `T` in `Result` - pub ty: InterfaceType, - /// Byte information about this type in the canonical ABI. - pub abi: CanonicalAbiInfo, - /// Byte information about this variant type. - pub info: VariantInfo, -} - -/// Shape of a "result" interface type. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeResult { - /// The `T` in `Result` - pub ok: Option, - /// The `E` in `Result` - pub err: Option, - /// Byte information about this type in the canonical ABI. - pub abi: CanonicalAbiInfo, - /// Byte information about this variant type. - pub info: VariantInfo, -} - -/// Metadata about a resource table added to a component. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeResourceTable { - /// The original resource that this table contains. - /// - /// This is used when destroying resources within this table since this - /// original definition will know how to execute destructors. - pub ty: ResourceIndex, - - /// The component instance that contains this resource table. - pub instance: RuntimeComponentInstanceIndex, -} - -/// Shape of a "list" interface type. -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -pub struct TypeList { - /// The element type of the list. - pub element: InterfaceType, -} - -const MAX_FLAT_TYPES: usize = if MAX_FLAT_PARAMS > MAX_FLAT_RESULTS { - MAX_FLAT_PARAMS -} else { - MAX_FLAT_RESULTS -}; - -const fn add_flat(a: Option, b: Option) -> Option { - const MAX: u8 = MAX_FLAT_TYPES as u8; - let sum = match (a, b) { - (Some(a), Some(b)) => match a.checked_add(b) { - Some(c) => c, - None => return None, - }, - _ => return None, - }; - if sum > MAX { - None - } else { - Some(sum) - } -} - -const fn max_flat(a: Option, b: Option) -> Option { - match (a, b) { - (Some(a), Some(b)) => { - if a > b { - Some(a) - } else { - Some(b) - } - } - _ => None, - } -} - -/// Flat representation of a type in just core wasm types. -pub struct FlatTypes<'a> { - /// The flat representation of this type in 32-bit memories. - pub memory32: &'a [FlatType], -} - -impl FlatTypes<'_> { - /// Returns the number of flat types used to represent this type. - /// - /// Note that this length is the same regardless to the size of memory. - pub fn len(&self) -> usize { - self.memory32.len() - } -} - -// Note that this is intentionally duplicated here to keep the size to 1 byte -// irregardless to changes in the core wasm type system since this will only -// ever use integers/floats for the forseeable future. -#[derive(PartialEq, Eq, Copy, Clone)] -#[allow(missing_docs)] -pub enum FlatType { - I32, - I64, - F32, - F64, -} - -struct FlatTypesStorage { - // This could be represented as `Vec` but on 64-bit architectures - // that's 24 bytes. Otherwise `FlatType` is 1 byte large and - // `MAX_FLAT_TYPES` is 16, so it should ideally be more space-efficient to - // use a flat array instead of a heap-based vector. - memory32: [FlatType; MAX_FLAT_TYPES], - - // Tracks the number of flat types pushed into this storage. If this is - // `MAX_FLAT_TYPES + 1` then this storage represents an un-reprsentable - // type in flat types. - len: u8, -} - -impl FlatTypesStorage { - const fn new() -> FlatTypesStorage { - FlatTypesStorage { - memory32: [FlatType::I32; MAX_FLAT_TYPES], - len: 0, - } - } - - fn as_flat_types(&self) -> Option> { - let len = usize::from(self.len); - if len > MAX_FLAT_TYPES { - assert_eq!(len, MAX_FLAT_TYPES + 1); - None - } else { - Some(FlatTypes { - memory32: &self.memory32[..len], - }) - } - } - - /// Pushes a new flat type into this list using `t32` for 32-bit memories - /// - /// Returns whether the type was actually pushed or whether this list of - /// flat types just exceeded the maximum meaning that it is now - /// unrepresentable with a flat list of types. - fn push(&mut self, t32: FlatType) -> bool { - let len = usize::from(self.len); - if len < MAX_FLAT_TYPES { - self.memory32[len] = t32; - self.len += 1; - true - } else { - // If this was the first one to go over then flag the length as - // being incompatible with a flat representation. - if len == MAX_FLAT_TYPES { - self.len += 1; - } - false - } - } -} - -impl FlatType { - fn join(&mut self, other: FlatType) { - if *self == other { - return; - } - *self = match (*self, other) { - (FlatType::I32, FlatType::F32) | (FlatType::F32, FlatType::I32) => FlatType::I32, - _ => FlatType::I64, - }; - } -} - -#[derive(Default)] -struct TypeInformationCache { - records: PrimaryMap, - variants: PrimaryMap, - tuples: PrimaryMap, - enums: PrimaryMap, - flags: PrimaryMap, - options: PrimaryMap, - results: PrimaryMap, - lists: PrimaryMap, -} - -struct TypeInformation { - depth: u32, - flat: FlatTypesStorage, - has_borrow: bool, -} - -impl TypeInformation { - const fn new() -> TypeInformation { - TypeInformation { - depth: 0, - flat: FlatTypesStorage::new(), - has_borrow: false, - } - } - - const fn primitive(flat: FlatType) -> TypeInformation { - let mut info = TypeInformation::new(); - info.depth = 1; - info.flat.memory32[0] = flat; - info.flat.len = 1; - info - } - - const fn string() -> TypeInformation { - let mut info = TypeInformation::new(); - info.depth = 1; - info.flat.memory32[0] = FlatType::I32; - info.flat.memory32[1] = FlatType::I32; - info.flat.len = 2; - info - } - - /// Builds up all flat types internally using the specified representation - /// for all of the component fields of the record. - fn build_record<'a>(&mut self, types: impl Iterator) { - self.depth = 1; - for info in types { - self.depth = self.depth.max(1 + info.depth); - self.has_borrow = self.has_borrow || info.has_borrow; - match info.flat.as_flat_types() { - Some(types) => { - for t32 in types.memory32.iter() { - if !self.flat.push(*t32) { - break; - } - } - } - None => { - self.flat.len = u8::try_from(MAX_FLAT_TYPES + 1).unwrap(); - } - } - } - } - - /// Builds up the flat types used to represent a `variant` which notably - /// handles "join"ing types together so each case is representable as a - /// single flat list of types. - /// - /// The iterator item is: - /// - /// * `None` - no payload for this case - /// * `Some(None)` - this case has a payload but can't be represented with flat types - /// * `Some(Some(types))` - this case has a payload and is represented with the types specified - /// in the flat representation. - fn build_variant<'a, I>(&mut self, cases: I) - where - I: IntoIterator>, - { - let cases = cases.into_iter(); - self.flat.push(FlatType::I32); - self.depth = 1; - - for info in cases { - let info = match info { - Some(info) => info, - // If this case doesn't have a payload then it doesn't change - // the depth/flat representation - None => continue, - }; - self.depth = self.depth.max(1 + info.depth); - self.has_borrow = self.has_borrow || info.has_borrow; - - // If this variant is already unrepresentable in a flat - // representation then this can be skipped. - if usize::from(self.flat.len) > MAX_FLAT_TYPES { - continue; - } - - let types = match info.flat.as_flat_types() { - Some(types) => types, - // If this case isn't representable with a flat list of types - // then this variant also isn't representable. - None => { - self.flat.len = u8::try_from(MAX_FLAT_TYPES + 1).unwrap(); - continue; - } - }; - // If the case used all of the flat types then the discriminant - // added for this variant means that this variant is no longer - // representable. - if types.memory32.len() >= MAX_FLAT_TYPES { - self.flat.len = u8::try_from(MAX_FLAT_TYPES + 1).unwrap(); - continue; - } - let dst = self.flat.memory32.iter_mut().skip(1); - for (i, (t32, dst32)) in types.memory32.iter().zip(dst).enumerate() { - if i + 1 < usize::from(self.flat.len) { - // If this index hs already been set by some previous case - // then the types are joined together. - dst32.join(*t32); - } else { - // Otherwise if this is the first time that the - // representation has gotten this large then the destination - // is simply whatever the type is. The length is also - // increased here to indicate this. - self.flat.len += 1; - *dst32 = *t32; - } - } - } - } - - fn records(&mut self, types: &ComponentTypesBuilder, ty: &TypeRecord) { - self.build_record(ty.fields.iter().map(|f| types.type_information(&f.ty))); - } - - fn tuples(&mut self, types: &ComponentTypesBuilder, ty: &TypeTuple) { - self.build_record(ty.types.iter().map(|t| types.type_information(t))); - } - - fn enums(&mut self, _types: &ComponentTypesBuilder, _ty: &TypeEnum) { - self.depth = 1; - self.flat.push(FlatType::I32); - } - - fn flags(&mut self, _types: &ComponentTypesBuilder, ty: &TypeFlags) { - self.depth = 1; - match FlagsSize::from_count(ty.names.len()) { - FlagsSize::Size0 => {} - FlagsSize::Size1 | FlagsSize::Size2 => { - self.flat.push(FlatType::I32); - } - FlagsSize::Size4Plus(n) => { - for _ in 0..n { - self.flat.push(FlatType::I32); - } - } - } - } - - fn variants(&mut self, types: &ComponentTypesBuilder, ty: &TypeVariant) { - self.build_variant( - ty.cases.iter().map(|c| c.ty.as_ref().map(|ty| types.type_information(ty))), - ) - } - - fn results(&mut self, types: &ComponentTypesBuilder, ty: &TypeResult) { - self.build_variant([ - ty.ok.as_ref().map(|ty| types.type_information(ty)), - ty.err.as_ref().map(|ty| types.type_information(ty)), - ]) - } - - fn options(&mut self, types: &ComponentTypesBuilder, ty: &TypeOption) { - self.build_variant([None, Some(types.type_information(&ty.ty))]); - } - - fn lists(&mut self, types: &ComponentTypesBuilder, ty: &TypeList) { - *self = TypeInformation::string(); - let info = types.type_information(&ty.element); - self.depth += info.depth; - self.has_borrow = info.has_borrow; - } -} - -pub fn interface_type_to_ir( - ty: &InterfaceType, - component_types: &ComponentTypes, -) -> midenc_hir_type::Type { - match ty { - InterfaceType::Bool => midenc_hir_type::Type::I1, - InterfaceType::S8 => midenc_hir_type::Type::I8, - InterfaceType::U8 => midenc_hir_type::Type::U8, - InterfaceType::S16 => midenc_hir_type::Type::I16, - InterfaceType::U16 => midenc_hir_type::Type::U16, - InterfaceType::S32 => midenc_hir_type::Type::I32, - InterfaceType::U32 => midenc_hir_type::Type::U32, - InterfaceType::S64 => midenc_hir_type::Type::I64, - InterfaceType::U64 => midenc_hir_type::Type::U64, - InterfaceType::Float32 => todo!(), - InterfaceType::Float64 => todo!(), - InterfaceType::Char => todo!(), - InterfaceType::String => todo!(), - InterfaceType::Record(idx) => { - let tys = component_types.records[*idx] - .fields - .iter() - .map(|f| interface_type_to_ir(&f.ty, component_types)); - midenc_hir_type::Type::Struct(midenc_hir_type::StructType::new(tys)) - } - InterfaceType::Variant(_) => todo!(), - InterfaceType::List(idx) => { - let element_ty = - interface_type_to_ir(&component_types.lists[*idx].element, component_types); - midenc_hir_type::Type::List(Box::new(element_ty)) - } - InterfaceType::Tuple(tuple_idx) => { - let tys = component_types.tuples[*tuple_idx] - .types - .iter() - .map(|t| interface_type_to_ir(t, component_types)); - midenc_hir_type::Type::Struct(midenc_hir_type::StructType::new(tys)) - } - InterfaceType::Flags(_) => todo!(), - InterfaceType::Enum(_) => todo!(), - InterfaceType::Option(_) => todo!(), - InterfaceType::Result(_) => todo!(), - InterfaceType::Own(_) => todo!(), - InterfaceType::Borrow(_) => todo!(), - } -} diff --git a/frontend-wasm/src/config.rs b/frontend-wasm/src/config.rs deleted file mode 100644 index 62e6c171a..000000000 --- a/frontend-wasm/src/config.rs +++ /dev/null @@ -1,53 +0,0 @@ -use alloc::{borrow::Cow, collections::BTreeMap, fmt}; - -use miden_core::crypto::hash::RpoDigest; -use midenc_hir::InterfaceFunctionIdent; - -/// Represents Miden VM codegen metadata for a function import. -/// This struct will have more fields in the future e.g. where the function -/// for this MAST hash is located (to be loaded by the VM) -#[derive(Clone)] -pub struct ImportMetadata { - /// The MAST root hash of the function to be used in codegen - pub digest: RpoDigest, -} -impl fmt::Debug for ImportMetadata { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map().entry(&"digest", &self.digest.to_hex()).finish() - } -} - -/// Configuration for the WASM translation. -#[derive(Debug)] -pub struct WasmTranslationConfig { - /// The source file name. - /// This is used as a fallback for module/component name if it's not parsed from the Wasm - /// binary, and an override name is not specified - pub source_name: Cow<'static, str>, - - /// If specified, overrides the module/component name with the one specified - pub override_name: Option>, - - /// Whether or not to generate native DWARF debug information. - pub generate_native_debuginfo: bool, - - /// Whether or not to retain DWARF sections in compiled modules. - pub parse_wasm_debuginfo: bool, - - /// Import metadata for MAST hashes, calling convention, of - /// each imported function. Having it here might be a temporary solution, - /// later we might want to move it to Wasm custom section. - pub import_metadata: BTreeMap, -} - -impl Default for WasmTranslationConfig { - fn default() -> Self { - Self { - source_name: Cow::Borrowed("noname"), - override_name: None, - generate_native_debuginfo: false, - parse_wasm_debuginfo: true, - import_metadata: Default::default(), - } - } -} diff --git a/frontend-wasm/src/error.rs b/frontend-wasm/src/error.rs deleted file mode 100644 index a42b399cf..000000000 --- a/frontend-wasm/src/error.rs +++ /dev/null @@ -1,79 +0,0 @@ -use midenc_hir::{ - diagnostics::{miette, Diagnostic, Report}, - SymbolConflictError, -}; -use thiserror::Error; - -/// A WebAssembly translation error. -/// -/// When a WebAssembly function can't be translated, one of these error codes will be returned -/// to describe the failure. -#[allow(missing_docs)] -#[derive(Error, Debug, Diagnostic)] -pub enum WasmError { - /// The input WebAssembly code is invalid. - /// - /// This error code is used by a WebAssembly translator when it encounters invalid WebAssembly - /// code. This should never happen for validated WebAssembly code. - #[error("invalid input WebAssembly code at offset {offset}: {message}")] - #[diagnostic()] - InvalidWebAssembly { - /// A string describing the validation error. - message: String, - /// The bytecode offset where the error occurred. - offset: usize, - }, - - /// A feature used by the WebAssembly code is not supported by the Miden IR. - #[error("unsupported WebAssembly code: {0}")] - #[diagnostic()] - Unsupported(String), - - /// Too many functions were declared in a module - #[error("Too many declared functions in the module")] - #[diagnostic()] - FuncNumLimitExceeded, - - /// Duplicate symbol names were found in a module - #[error(transparent)] - #[diagnostic(transparent)] - SymbolConflictError(#[from] SymbolConflictError), - - #[error("import metadata is missing: {0}")] - #[diagnostic()] - MissingImportMetadata(String), - - #[error("export metadata is missing: {0}")] - #[diagnostic()] - MissingExportMetadata(String), - - #[error(transparent)] - DwarfError(#[from] gimli::Error), - - #[error(transparent)] - Io(#[from] std::io::Error), -} - -impl From for WasmError { - fn from(e: wasmparser::BinaryReaderError) -> Self { - Self::InvalidWebAssembly { - message: e.message().into(), - offset: e.offset(), - } - } -} - -/// A convenient alias for a `Result` that uses `WasmError` as the error type. -pub type WasmResult = Result; - -/// Emit diagnostics and return an `Err(WasmError::Unsupported(msg))` where `msg` the string built -/// by calling `format!` on the arguments to this macro. -#[macro_export] -macro_rules! unsupported_diag { - ($diagnostics:expr, $($arg:tt)*) => {{ - return Err($diagnostics - .diagnostic(Severity::Error) - .with_message(format!($($arg)*)) - .into_report()); - }} -} diff --git a/frontend-wasm/src/intrinsics/felt.rs b/frontend-wasm/src/intrinsics/felt.rs deleted file mode 100644 index 01e8a76ec..000000000 --- a/frontend-wasm/src/intrinsics/felt.rs +++ /dev/null @@ -1,126 +0,0 @@ -use std::vec; - -use midenc_hir::{FunctionIdent, InstBuilder, SourceSpan, Type::*, Value}; - -use crate::module::function_builder_ext::FunctionBuilderExt; - -pub(crate) const INTRINSICS_FELT_MODULE_NAME: &str = "miden:stdlib/intrinsics_felt"; - -/// Convert a call to a felt op intrinsic function into instruction(s) -pub(crate) fn convert_felt_intrinsics( - func_id: FunctionIdent, - args: &[Value], - builder: &mut FunctionBuilderExt<'_, '_, '_>, - span: SourceSpan, -) -> Vec { - match func_id.function.as_symbol().as_str() { - // Conversion operations - "from_u64_unchecked" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - let inst = builder.ins().cast(args[0], Felt, span); - vec![inst] - } - "as_u64" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - // we're casting to i64 instead of u64 because Wasm doesn't have u64 - // and this value will be used in Wasm ops or local vars that expect i64 - let inst = builder.ins().cast(args[0], I64, span); - vec![inst] - } - // Arithmetic operations - "add" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().add_unchecked(args[0], args[1], span); - vec![inst] - } - "sub" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().sub_unchecked(args[0], args[1], span); - vec![inst] - } - "mul" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().mul_unchecked(args[0], args[1], span); - vec![inst] - } - "div" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().div_unchecked(args[0], args[1], span); - vec![inst] - } - "neg" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - let inst = builder.ins().neg(args[0], span); - vec![inst] - } - "inv" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - let inst = builder.ins().inv(args[0], span); - vec![inst] - } - "pow2" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - let inst = builder.ins().pow2(args[0], span); - vec![inst] - } - "exp" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().exp(args[0], args[1], span); - vec![inst] - } - // Comparison operations - "eq" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().eq(args[0], args[1], span); - let cast = builder.ins().cast(inst, I32, span); - vec![cast] - } - "gt" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().gt(args[0], args[1], span); - let cast = builder.ins().cast(inst, I32, span); - vec![cast] - } - "ge" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().gte(args[0], args[1], span); - let cast = builder.ins().cast(inst, I32, span); - vec![cast] - } - "lt" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().lt(args[0], args[1], span); - let cast = builder.ins().cast(inst, I32, span); - vec![cast] - } - "le" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - let inst = builder.ins().lte(args[0], args[1], span); - let cast = builder.ins().cast(inst, I32, span); - vec![cast] - } - "is_odd" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - let inst = builder.ins().is_odd(args[0], span); - let cast = builder.ins().cast(inst, I32, span); - vec![cast] - } - // Assert operations - "assert" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - builder.ins().assert(args[0], span); - vec![] - } - "assertz" => { - assert_eq!(args.len(), 1, "{} takes exactly one argument", func_id); - builder.ins().assertz(args[0], span); - vec![] - } - "assert_eq" => { - assert_eq!(args.len(), 2, "{} takes exactly two arguments", func_id); - builder.ins().assert_eq(args[0], args[1], span); - vec![] - } - _ => panic!("No felt op intrinsics found for {}", func_id), - } -} diff --git a/frontend-wasm/src/intrinsics/mem.rs b/frontend-wasm/src/intrinsics/mem.rs deleted file mode 100644 index de6b66362..000000000 --- a/frontend-wasm/src/intrinsics/mem.rs +++ /dev/null @@ -1,33 +0,0 @@ -use midenc_hir::{AbiParam, FunctionIdent, InstBuilder, Signature, SourceSpan, Type, Value}; - -use crate::module::function_builder_ext::FunctionBuilderExt; - -/// Convert a call to a memory intrinsic function -pub(crate) fn convert_mem_intrinsics( - func_id: FunctionIdent, - args: &[Value], - builder: &mut FunctionBuilderExt<'_, '_, '_>, - span: SourceSpan, -) -> Vec { - match func_id.function.as_symbol().as_str() { - "heap_base" => { - assert_eq!(args.len(), 0, "{} takes no arguments", func_id); - if builder - .data_flow_graph() - .get_import_by_name(func_id.module, func_id.function) - .is_none() - { - let signature = Signature::new([], [AbiParam::new(Type::U32)]); - let _ = builder.data_flow_graph_mut().import_function( - func_id.module, - func_id.function, - signature, - ); - } - let call = builder.ins().call(func_id, &[], span); - let value = builder.data_flow_graph().first_result(call); - vec![value] - } - _ => panic!("No allowed memory intrinsics found for {}", func_id), - } -} diff --git a/frontend-wasm/src/intrinsics/mod.rs b/frontend-wasm/src/intrinsics/mod.rs deleted file mode 100644 index 73abc88ba..000000000 --- a/frontend-wasm/src/intrinsics/mod.rs +++ /dev/null @@ -1,39 +0,0 @@ -mod felt; -mod mem; - -use std::{collections::HashSet, sync::OnceLock}; - -use midenc_hir::{FunctionIdent, SourceSpan, Symbol, Value}; - -use crate::module::function_builder_ext::FunctionBuilderExt; - -/// Check if the given module is a Miden module that contains intrinsics -pub fn is_miden_intrinsics_module(module_id: Symbol) -> bool { - modules().contains(module_id.as_str()) -} - -fn modules() -> &'static HashSet<&'static str> { - static MODULES: OnceLock> = OnceLock::new(); - MODULES.get_or_init(|| { - let mut s = HashSet::default(); - s.insert("intrinsics::mem"); - s.insert(felt::INTRINSICS_FELT_MODULE_NAME); - s - }) -} - -/// Convert a call to a Miden intrinsic function into instruction(s) -pub fn convert_intrinsics_call( - func_id: FunctionIdent, - args: &[Value], - builder: &mut FunctionBuilderExt, - span: SourceSpan, -) -> Vec { - match func_id.module.as_symbol().as_str() { - "intrinsics::mem" => mem::convert_mem_intrinsics(func_id, args, builder, span), - felt::INTRINSICS_FELT_MODULE_NAME => { - felt::convert_felt_intrinsics(func_id, args, builder, span) - } - _ => panic!("No intrinsics found for {}", func_id), - } -} diff --git a/frontend-wasm/src/lib.rs b/frontend-wasm/src/lib.rs deleted file mode 100644 index 58c311f64..000000000 --- a/frontend-wasm/src/lib.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Performs translation from Wasm to MidenIR - -// Coding conventions -#![deny(warnings)] -#![deny(missing_docs)] -#![deny(rustdoc::broken_intra_doc_links)] - -extern crate alloc; - -mod code_translator; -mod component; -mod config; -mod error; -mod intrinsics; -mod miden_abi; -mod module; -mod ssa; -mod translation_utils; - -#[cfg(test)] -mod test_utils; - -use component::build_ir::translate_component; -use error::WasmResult; -use midenc_session::Session; -use module::build_ir::translate_module_as_component; -use wasmparser::WasmFeatures; - -pub use self::{config::*, error::WasmError}; - -/// Translate a valid Wasm core module or Wasm Component Model binary into Miden -/// IR Component -pub fn translate( - wasm: &[u8], - config: &WasmTranslationConfig, - session: &Session, -) -> WasmResult { - if wasm[4..8] == [0x01, 0x00, 0x00, 0x00] { - // Wasm core module - // see https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md#component-definitions - translate_module_as_component(wasm, config, session) - } else { - translate_component(wasm, config, session) - } -} - -/// The set of core WebAssembly features which we need to or wish to support -pub(crate) fn supported_features() -> WasmFeatures { - WasmFeatures::BULK_MEMORY - | WasmFeatures::FLOATS - | WasmFeatures::FUNCTION_REFERENCES - | WasmFeatures::MULTI_VALUE - | WasmFeatures::MUTABLE_GLOBAL - | WasmFeatures::SATURATING_FLOAT_TO_INT - | WasmFeatures::SIGN_EXTENSION - | WasmFeatures::TAIL_CALL -} - -/// The extended set of WebAssembly features which are enabled when working with the Wasm Component -/// Model -pub(crate) fn supported_component_model_features() -> WasmFeatures { - supported_features() | WasmFeatures::COMPONENT_MODEL -} diff --git a/frontend-wasm/src/miden_abi/mod.rs b/frontend-wasm/src/miden_abi/mod.rs deleted file mode 100644 index 3eafa8f0b..000000000 --- a/frontend-wasm/src/miden_abi/mod.rs +++ /dev/null @@ -1,74 +0,0 @@ -pub(crate) mod stdlib; -pub(crate) mod transform; -pub(crate) mod tx_kernel; - -use miden_core::crypto::hash::RpoDigest; -use midenc_hir::{FunctionType, Symbol}; -use rustc_hash::FxHashMap; - -pub(crate) type FunctionTypeMap = FxHashMap<&'static str, FunctionType>; -pub(crate) type ModuleFunctionTypeMap = FxHashMap<&'static str, FunctionTypeMap>; - -/// Parse the stable import function name and the hex encoded digest from the function name -pub fn parse_import_function_digest(import_name: &str) -> Result<(String, RpoDigest), String> { - // parse the hex encoded digest from the function name in the angle brackets - // and the function name (before the angle brackets) example: - // "miden:tx_kernel/note.get_inputs" - let mut parts = import_name.split('<'); - let function_name = parts.next().unwrap(); - let digest = parts - .next() - .and_then(|s| s.strip_suffix('>')) - .ok_or("Import name parsing error: missing closing angle bracket in import name")?; - Ok(( - function_name.to_string(), - RpoDigest::try_from(digest).map_err(|e| e.to_string())?, - )) -} - -pub fn is_miden_abi_module(module_id: Symbol) -> bool { - is_miden_stdlib_module(module_id) || is_miden_sdk_module(module_id) -} - -pub fn miden_abi_function_type(module_id: Symbol, function_id: Symbol) -> FunctionType { - if is_miden_stdlib_module(module_id) { - miden_stdlib_function_type(module_id, function_id) - } else { - miden_sdk_function_type(module_id, function_id) - } -} - -fn is_miden_sdk_module(module_id: Symbol) -> bool { - tx_kernel::signatures().contains_key(module_id.as_str()) -} - -/// Get the target Miden ABI tx kernel function type for the given module and function id -pub fn miden_sdk_function_type(module_id: Symbol, function_id: Symbol) -> FunctionType { - let funcs = tx_kernel::signatures() - .get(module_id.as_str()) - .unwrap_or_else(|| panic!("No Miden ABI function types found for module {}", module_id)); - funcs.get(function_id.as_str()).cloned().unwrap_or_else(|| { - panic!( - "No Miden ABI function type found for function {} in module {}", - function_id, module_id - ) - }) -} - -fn is_miden_stdlib_module(module_id: Symbol) -> bool { - stdlib::signatures().contains_key(module_id.as_str()) -} - -/// Get the target Miden ABI stdlib function type for the given module and function id -#[inline(always)] -fn miden_stdlib_function_type(module_id: Symbol, function_id: Symbol) -> FunctionType { - let funcs = stdlib::signatures() - .get(module_id.as_str()) - .unwrap_or_else(|| panic!("No Miden ABI function types found for module {}", module_id)); - funcs.get(function_id.as_str()).cloned().unwrap_or_else(|| { - panic!( - "No Miden ABI function type found for function {} in module {}", - function_id, module_id - ) - }) -} diff --git a/frontend-wasm/src/miden_abi/stdlib.rs b/frontend-wasm/src/miden_abi/stdlib.rs deleted file mode 100644 index 0c66da303..000000000 --- a/frontend-wasm/src/miden_abi/stdlib.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Function types and lowered signatures for the Miden stdlib API functions - -use std::sync::OnceLock; - -use super::ModuleFunctionTypeMap; - -pub(crate) mod crypto; -pub(crate) mod mem; - -pub(crate) fn signatures() -> &'static ModuleFunctionTypeMap { - static TYPES: OnceLock = OnceLock::new(); - TYPES.get_or_init(|| { - let mut m: ModuleFunctionTypeMap = Default::default(); - m.extend(crypto::hashes::signatures()); - m.extend(crypto::dsa::signatures()); - m.extend(mem::signatures()); - m - }) -} diff --git a/frontend-wasm/src/miden_abi/stdlib/crypto/dsa.rs b/frontend-wasm/src/miden_abi/stdlib/crypto/dsa.rs deleted file mode 100644 index d786cae2e..000000000 --- a/frontend-wasm/src/miden_abi/stdlib/crypto/dsa.rs +++ /dev/null @@ -1,17 +0,0 @@ -use midenc_hir::FunctionType; -use midenc_hir_type::Type::*; - -use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; - -pub(crate) const RPO_FALCON512_VERIFY: &str = "rpo_falcon512_verify"; - -pub(crate) fn signatures() -> ModuleFunctionTypeMap { - let mut m: ModuleFunctionTypeMap = Default::default(); - let mut funcs: FunctionTypeMap = Default::default(); - funcs.insert( - RPO_FALCON512_VERIFY, - FunctionType::new([Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt], []), - ); - m.insert("std::crypto::dsa::rpo_falcon512", funcs); - m -} diff --git a/frontend-wasm/src/miden_abi/stdlib/crypto/hashes.rs b/frontend-wasm/src/miden_abi/stdlib/crypto/hashes.rs deleted file mode 100644 index 83b1dd781..000000000 --- a/frontend-wasm/src/miden_abi/stdlib/crypto/hashes.rs +++ /dev/null @@ -1,30 +0,0 @@ -use midenc_hir::FunctionType; -use midenc_hir_type::Type::*; - -use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; - -pub(crate) const BLAKE3_HASH_1TO1: &str = "hash_1to1"; -pub(crate) const BLAKE3_HASH_2TO1: &str = "hash_2to1"; - -pub(crate) fn signatures() -> ModuleFunctionTypeMap { - let mut m: ModuleFunctionTypeMap = Default::default(); - let mut blake3: FunctionTypeMap = Default::default(); - blake3.insert( - BLAKE3_HASH_1TO1, - //Accepts and returns a 8 Felt elements - FunctionType::new( - [I32, I32, I32, I32, I32, I32, I32, I32], - [I32, I32, I32, I32, I32, I32, I32, I32], - ), - ); - blake3.insert( - BLAKE3_HASH_2TO1, - // Accepts 16 and returns a 8 Felt elements - FunctionType::new( - [I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32], - [I32, I32, I32, I32, I32, I32, I32, I32], - ), - ); - m.insert("std::crypto::hashes::blake3", blake3); - m -} diff --git a/frontend-wasm/src/miden_abi/stdlib/mem.rs b/frontend-wasm/src/miden_abi/stdlib/mem.rs deleted file mode 100644 index 71c0dc71c..000000000 --- a/frontend-wasm/src/miden_abi/stdlib/mem.rs +++ /dev/null @@ -1,45 +0,0 @@ -use midenc_hir::FunctionType; -use midenc_hir_type::Type::*; - -use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; - -pub(crate) const PIPE_WORDS_TO_MEMORY: &str = "pipe_words_to_memory"; -pub(crate) const PIPE_DOUBLE_WORDS_TO_MEMORY: &str = "pipe_double_words_to_memory"; - -pub(crate) fn signatures() -> ModuleFunctionTypeMap { - let mut m: ModuleFunctionTypeMap = Default::default(); - let mut funcs: FunctionTypeMap = Default::default(); - funcs.insert( - PIPE_WORDS_TO_MEMORY, - FunctionType::new( - [ - Felt, // num_words - I32, // write_ptr - ], - [ - Felt, Felt, Felt, Felt, // HASH - I32, // write_ptr' - ], - ), - ); - funcs.insert( - PIPE_DOUBLE_WORDS_TO_MEMORY, - FunctionType::new( - [ - Felt, Felt, Felt, Felt, // C - Felt, Felt, Felt, Felt, // B - Felt, Felt, Felt, Felt, // A - I32, // write_ptr - I32, // end_ptr - ], - [ - Felt, Felt, Felt, Felt, // C - Felt, Felt, Felt, Felt, // B - Felt, Felt, Felt, Felt, // A - I32, // write_ptr - ], - ), - ); - m.insert("std::mem", funcs); - m -} diff --git a/frontend-wasm/src/miden_abi/transform.rs b/frontend-wasm/src/miden_abi/transform.rs deleted file mode 100644 index d8f4dfd9d..000000000 --- a/frontend-wasm/src/miden_abi/transform.rs +++ /dev/null @@ -1,135 +0,0 @@ -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, SourceSpan}, - FunctionIdent, Immediate, InstBuilder, - Type::*, - Value, -}; - -use super::{stdlib, tx_kernel}; -use crate::module::function_builder_ext::FunctionBuilderExt; - -/// The strategy to use for transforming a function call -enum TransformStrategy { - /// The Miden ABI function returns a length and a pointer and we only want the length - ListReturn, - /// The Miden ABI function returns on the stack and we want to return via a pointer argument - ReturnViaPointer, - /// No transformation needed - NoTransform, -} - -/// Get the transformation strategy for a function name -fn get_transform_strategy(module_id: &str, function_id: &str) -> TransformStrategy { - #[allow(clippy::single_match)] - match module_id { - "std::mem" => match function_id { - stdlib::mem::PIPE_WORDS_TO_MEMORY => return TransformStrategy::ReturnViaPointer, - stdlib::mem::PIPE_DOUBLE_WORDS_TO_MEMORY => return TransformStrategy::ReturnViaPointer, - _ => (), - }, - "std::crypto::hashes::blake3" => match function_id { - stdlib::crypto::hashes::BLAKE3_HASH_1TO1 => return TransformStrategy::ReturnViaPointer, - stdlib::crypto::hashes::BLAKE3_HASH_2TO1 => return TransformStrategy::ReturnViaPointer, - _ => (), - }, - "std::crypto::dsa::rpo_falcon512" => match function_id { - stdlib::crypto::dsa::RPO_FALCON512_VERIFY => return TransformStrategy::NoTransform, - _ => (), - }, - "miden::note" => match function_id { - tx_kernel::note::GET_INPUTS => return TransformStrategy::ListReturn, - _ => (), - }, - "miden::account" => match function_id { - tx_kernel::account::ADD_ASSET => return TransformStrategy::ReturnViaPointer, - tx_kernel::account::REMOVE_ASSET => return TransformStrategy::ReturnViaPointer, - tx_kernel::account::GET_ID => return TransformStrategy::NoTransform, - _ => (), - }, - "miden::tx" => match function_id { - tx_kernel::tx::CREATE_NOTE => return TransformStrategy::NoTransform, - _ => (), - }, - _ => (), - } - panic!("No transform strategy found for function '{function_id}' in module '{module_id}'"); -} - -/// Transform a function call based on the transformation strategy -pub fn transform_miden_abi_call( - func_id: FunctionIdent, - args: &[Value], - builder: &mut FunctionBuilderExt, - span: SourceSpan, - diagnostics: &DiagnosticsHandler, -) -> Vec { - use TransformStrategy::*; - match get_transform_strategy(func_id.module.as_str(), func_id.function.as_str()) { - ListReturn => list_return(func_id, args, builder, span, diagnostics), - ReturnViaPointer => return_via_pointer(func_id, args, builder, span, diagnostics), - NoTransform => no_transform(func_id, args, builder, span, diagnostics), - } -} - -/// No transformation needed -#[inline(always)] -pub fn no_transform( - func_id: FunctionIdent, - args: &[Value], - builder: &mut FunctionBuilderExt, - span: SourceSpan, - _diagnostics: &DiagnosticsHandler, -) -> Vec { - let call = builder.ins().call(func_id, args, span); - let results = builder.inst_results(call); - results.to_vec() -} - -/// The Miden ABI function returns a length and a pointer and we only want the length -pub fn list_return( - func_id: FunctionIdent, - args: &[Value], - builder: &mut FunctionBuilderExt, - span: SourceSpan, - _diagnostics: &DiagnosticsHandler, -) -> Vec { - let call = builder.ins().call(func_id, args, span); - let results = builder.inst_results(call); - assert_eq!(results.len(), 2, "List return strategy expects 2 results: length and pointer"); - // Return the first result (length) only - results[0..1].to_vec() -} - -/// The Miden ABI function returns felts on the stack and we want to return via a pointer argument -pub fn return_via_pointer( - func_id: FunctionIdent, - args: &[Value], - builder: &mut FunctionBuilderExt, - span: SourceSpan, - _diagnostics: &DiagnosticsHandler, -) -> Vec { - // Omit the last argument (pointer) - let args_wo_pointer = &args[0..args.len() - 1]; - let call = builder.ins().call(func_id, args_wo_pointer, span); - let results = builder.inst_results(call).to_vec(); - let ptr_arg = *args.last().unwrap(); - let ptr_arg_ty = builder.data_flow_graph().value_type(ptr_arg).clone(); - assert_eq!(ptr_arg_ty, I32); - let ptr_u32 = builder.ins().bitcast(ptr_arg, U32, span); - let result_ty = midenc_hir::StructType::new( - results.iter().map(|v| builder.data_flow_graph().value_type(*v).clone()), - ); - for (idx, value) in results.iter().enumerate() { - let value_ty = builder.data_flow_graph().value_type(*value).clone(); - let eff_ptr = if idx == 0 { - // We're assuming here that the base pointer is of the correct alignment - ptr_u32 - } else { - let imm = Immediate::U32(result_ty.get(idx).offset); - builder.ins().add_imm_checked(ptr_u32, imm, span) - }; - let addr = builder.ins().inttoptr(eff_ptr, Ptr(value_ty.into()), span); - builder.ins().store(addr, *value, span); - } - Vec::new() -} diff --git a/frontend-wasm/src/miden_abi/tx_kernel/account.rs b/frontend-wasm/src/miden_abi/tx_kernel/account.rs deleted file mode 100644 index 97cea3277..000000000 --- a/frontend-wasm/src/miden_abi/tx_kernel/account.rs +++ /dev/null @@ -1,22 +0,0 @@ -use midenc_hir::FunctionType; -use midenc_hir_type::Type::*; - -use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; - -pub const ADD_ASSET: &str = "add_asset"; -pub const REMOVE_ASSET: &str = "remove_asset"; -pub const GET_ID: &str = "get_id"; - -pub(crate) fn signatures() -> ModuleFunctionTypeMap { - let mut m: ModuleFunctionTypeMap = Default::default(); - let mut account: FunctionTypeMap = Default::default(); - account - .insert(ADD_ASSET, FunctionType::new([Felt, Felt, Felt, Felt], [Felt, Felt, Felt, Felt])); - account.insert( - REMOVE_ASSET, - FunctionType::new([Felt, Felt, Felt, Felt], [Felt, Felt, Felt, Felt]), - ); - account.insert(GET_ID, FunctionType::new([], [Felt])); - m.insert("miden::account", account); - m -} diff --git a/frontend-wasm/src/miden_abi/tx_kernel/note.rs b/frontend-wasm/src/miden_abi/tx_kernel/note.rs deleted file mode 100644 index bf2b73129..000000000 --- a/frontend-wasm/src/miden_abi/tx_kernel/note.rs +++ /dev/null @@ -1,14 +0,0 @@ -use midenc_hir::FunctionType; -use midenc_hir_type::Type::*; - -use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; - -pub const GET_INPUTS: &str = "get_inputs"; - -pub(crate) fn signatures() -> ModuleFunctionTypeMap { - let mut m: ModuleFunctionTypeMap = Default::default(); - let mut note: FunctionTypeMap = Default::default(); - note.insert(GET_INPUTS, FunctionType::new([I32], [I32, I32])); - m.insert("miden::note", note); - m -} diff --git a/frontend-wasm/src/miden_abi/tx_kernel/tx.rs b/frontend-wasm/src/miden_abi/tx_kernel/tx.rs deleted file mode 100644 index c1b63815c..000000000 --- a/frontend-wasm/src/miden_abi/tx_kernel/tx.rs +++ /dev/null @@ -1,17 +0,0 @@ -use midenc_hir::FunctionType; -use midenc_hir_type::Type::*; - -use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; - -pub const CREATE_NOTE: &str = "create_note"; - -pub(crate) fn signatures() -> ModuleFunctionTypeMap { - let mut m: ModuleFunctionTypeMap = Default::default(); - let mut note: FunctionTypeMap = Default::default(); - note.insert( - CREATE_NOTE, - FunctionType::new([Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt], [Felt]), - ); - m.insert("miden::tx", note); - m -} diff --git a/frontend-wasm/src/module/build_ir.rs b/frontend-wasm/src/module/build_ir.rs deleted file mode 100644 index 675ee472c..000000000 --- a/frontend-wasm/src/module/build_ir.rs +++ /dev/null @@ -1,213 +0,0 @@ -use core::mem; - -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, IntoDiagnostic, Severity, SourceSpan}, - CallConv, ConstantData, Linkage, MidenAbiImport, ModuleBuilder, Symbol, -}; -use midenc_session::Session; -use wasmparser::Validator; - -use super::{module_translation_state::ModuleTranslationState, MemoryIndex, Module}; -use crate::{ - error::WasmResult, - intrinsics::is_miden_intrinsics_module, - miden_abi::miden_abi_function_type, - module::{ - func_translator::FuncTranslator, - module_env::{FunctionBodyData, ModuleEnvironment, ParsedModule}, - types::{ir_func_sig, ir_func_type, ir_type, ModuleTypes}, - }, - WasmTranslationConfig, -}; - -/// Translate a valid Wasm core module binary into Miden IR component building -/// component imports for well-known Miden ABI functions -/// -/// This is a temporary solution until we compile an account code as Wasm -/// component. To be able to do it we need wit-bindgen type re-mapping implemented first (see -/// https://github.com/0xPolygonMiden/compiler/issues/116) -pub fn translate_module_as_component( - wasm: &[u8], - config: &WasmTranslationConfig, - session: &Session, -) -> WasmResult { - let mut validator = Validator::new_with_features(crate::supported_features()); - let parser = wasmparser::Parser::new(0); - let mut module_types_builder = Default::default(); - let mut parsed_module = ModuleEnvironment::new( - config, - &mut validator, - &mut module_types_builder, - ) - .parse(parser, wasm, &session.diagnostics)?; - parsed_module.module.set_name_fallback(config.source_name.clone()); - if let Some(name_override) = config.override_name.as_ref() { - parsed_module.module.set_name_override(name_override.clone()); - } - let module_types = module_types_builder.finish(); - - let mut module_state = ModuleTranslationState::new( - &parsed_module.module, - &module_types, - vec![], - &session.diagnostics, - ); - let module = - build_ir_module(&mut parsed_module, &module_types, &mut module_state, config, session)?; - let mut cb = midenc_hir::ComponentBuilder::new(&session.diagnostics); - let module_imports = module.imports(); - for import_module_id in module_imports.iter_module_names() { - if let Some(imports) = module_imports.imported(import_module_id) { - for ext_func in imports { - if is_miden_intrinsics_module(ext_func.module.as_symbol()) { - // ignore intrinsics imports - continue; - } - let function_ty = miden_abi_function_type( - ext_func.module.as_symbol(), - ext_func.function.as_symbol(), - ); - let digest = *module_state.digest(ext_func).unwrap_or_else(|| { - panic!("failed to find MAST root hash for function {}", ext_func.function) - }); - let component_import = - midenc_hir::ComponentImport::MidenAbiImport(MidenAbiImport { - function_ty, - digest, - }); - cb.add_import(*ext_func, component_import); - } - } - } - cb.add_module(module.into()).expect("module is already added"); - Ok(cb.build()) -} - -pub fn build_ir_module( - parsed_module: &mut ParsedModule, - module_types: &ModuleTypes, - module_state: &mut ModuleTranslationState, - _config: &WasmTranslationConfig, - session: &Session, -) -> WasmResult { - let name = parsed_module.module.name(); - let memory_size = parsed_module - .module - .memories - .get(MemoryIndex::from_u32(0)) - .map(|mem| mem.minimum as u32); - let mut module_builder = ModuleBuilder::new(name.as_str()); - if let Some(memory_size) = memory_size { - module_builder.with_reserved_memory_pages(memory_size); - } - build_globals(&parsed_module.module, &mut module_builder, &session.diagnostics)?; - build_data_segments(parsed_module, &mut module_builder, &session.diagnostics)?; - let addr2line = addr2line::Context::from_dwarf(gimli::Dwarf { - debug_abbrev: parsed_module.debuginfo.dwarf.debug_abbrev, - debug_addr: parsed_module.debuginfo.dwarf.debug_addr, - debug_aranges: parsed_module.debuginfo.dwarf.debug_aranges, - debug_info: parsed_module.debuginfo.dwarf.debug_info, - debug_line: parsed_module.debuginfo.dwarf.debug_line, - debug_line_str: parsed_module.debuginfo.dwarf.debug_line_str, - debug_str: parsed_module.debuginfo.dwarf.debug_str, - debug_str_offsets: parsed_module.debuginfo.dwarf.debug_str_offsets, - debug_types: parsed_module.debuginfo.dwarf.debug_types, - locations: parsed_module.debuginfo.dwarf.locations, - ranges: parsed_module.debuginfo.dwarf.ranges, - file_type: parsed_module.debuginfo.dwarf.file_type, - sup: parsed_module.debuginfo.dwarf.sup.clone(), - ..Default::default() - }) - .into_diagnostic()?; - let mut func_translator = FuncTranslator::new(); - // Although this renders this parsed module invalid(without functiong - // bodies), we don't support multiple module instances. Thus, this - // ParseModule will not be used again to make another module instance. - let func_body_inputs = mem::take(&mut parsed_module.function_body_inputs); - for (defined_func_idx, body_data) in func_body_inputs { - let func_index = &parsed_module.module.func_index(defined_func_idx); - let func_type = &parsed_module.module.functions[*func_index]; - let func_name = &parsed_module.module.func_name(*func_index); - let wasm_func_type = module_types[func_type.signature].clone(); - let ir_func_type = ir_func_type(&wasm_func_type, &session.diagnostics)?; - let sig = ir_func_sig(&ir_func_type, CallConv::SystemV, Linkage::External); - let mut module_func_builder = module_builder.function(func_name.as_str(), sig.clone())?; - let FunctionBodyData { validator, body } = body_data; - let mut func_validator = validator.into_validator(Default::default()); - func_translator.translate_body( - &body, - &mut module_func_builder, - module_state, - parsed_module, - module_types, - &addr2line, - session, - &mut func_validator, - )?; - module_func_builder.build(&session.diagnostics)?; - } - let module = module_builder.build(); - Ok(*module) -} - -fn build_globals( - wasm_module: &Module, - module_builder: &mut ModuleBuilder, - diagnostics: &DiagnosticsHandler, -) -> WasmResult<()> { - for (global_idx, global) in &wasm_module.globals { - let global_name = wasm_module - .name_section - .globals_names - .get(&global_idx) - .cloned() - .unwrap_or(Symbol::intern(format!("gv{}", global_idx.as_u32()))); - let global_init = wasm_module.try_global_initializer(global_idx, diagnostics)?; - let init = ConstantData::from(global_init.to_le_bytes(wasm_module, diagnostics)?); - if let Err(e) = module_builder.declare_global_variable( - global_name.as_str(), - ir_type(global.ty, diagnostics)?, - Linkage::External, - Some(init.clone()), - SourceSpan::default(), - ) { - let message = format!( - "Failed to declare global variable '{global_name}' with initializer '{init}' with \ - error: {:?}", - e - ); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message(message.clone()) - .into_report()); - } - } - Ok(()) -} - -fn build_data_segments( - translation: &ParsedModule, - module_builder: &mut ModuleBuilder, - diagnostics: &DiagnosticsHandler, -) -> WasmResult<()> { - for (data_segment_idx, data_segment) in &translation.data_segments { - let data_segment_name = - translation.module.name_section.data_segment_names[&data_segment_idx]; - let readonly = data_segment_name.as_str().contains(".rodata"); - let init = ConstantData::from(data_segment.data); - let offset = data_segment.offset.as_i32(&translation.module, diagnostics)? as u32; - let size = init.len() as u32; - if let Err(e) = module_builder.declare_data_segment(offset, size, init, readonly) { - let message = format!( - "Failed to declare data segment '{data_segment_name}' with size '{size}' at \ - '{offset}' with error: {:?}", - e - ); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message(message.clone()) - .into_report()); - } - } - Ok(()) -} diff --git a/frontend-wasm/src/module/function_builder_ext.rs b/frontend-wasm/src/module/function_builder_ext.rs deleted file mode 100644 index 36fdc0678..000000000 --- a/frontend-wasm/src/module/function_builder_ext.rs +++ /dev/null @@ -1,505 +0,0 @@ -use midenc_hir::{ - cranelift_entity::{EntitySet, SecondaryMap}, - diagnostics::SourceSpan, - Block, Br, CondBr, DataFlowGraph, InsertionPoint, Inst, InstBuilderBase, Instruction, - ModuleFunctionBuilder, ProgramPoint, Switch, Value, -}; -use midenc_hir_type::Type; - -use crate::ssa::{SSABuilder, SideEffects, Variable}; - -/// Tracking variables and blocks for SSA construction. -pub struct FunctionBuilderContext { - ssa: SSABuilder, - status: SecondaryMap, - types: SecondaryMap, -} - -impl FunctionBuilderContext { - pub fn new() -> Self { - Self { - ssa: SSABuilder::default(), - status: SecondaryMap::new(), - types: SecondaryMap::with_default(Type::Unknown), - } - } - - fn is_empty(&self) -> bool { - self.ssa.is_empty() && self.status.is_empty() && self.types.is_empty() - } - - fn clear(&mut self) { - self.ssa.clear(); - self.status.clear(); - self.types.clear(); - } -} - -#[derive(Clone, Default, Eq, PartialEq)] -enum BlockStatus { - /// No instructions have been added. - #[default] - Empty, - /// Some instructions have been added, but no terminator. - Partial, - /// A terminator has been added; no further instructions may be added. - Filled, -} - -/// A wrapper around Miden's `FunctionBuilder` and `SSABuilder` which provides -/// additional API for dealing with variables and SSA construction. -pub struct FunctionBuilderExt<'a, 'b, 'c: 'b> { - inner: &'b mut ModuleFunctionBuilder<'c>, - func_ctx: &'a mut FunctionBuilderContext, -} - -impl<'a, 'b, 'c> FunctionBuilderExt<'a, 'b, 'c> { - pub fn new( - inner: &'b mut ModuleFunctionBuilder<'c>, - func_ctx: &'a mut FunctionBuilderContext, - ) -> Self { - debug_assert!(func_ctx.is_empty()); - Self { inner, func_ctx } - } - - pub fn data_flow_graph(&self) -> &DataFlowGraph { - self.inner.data_flow_graph() - } - - pub fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { - self.inner.data_flow_graph_mut() - } - - pub fn id(&self) -> midenc_hir::FunctionIdent { - self.inner.id() - } - - pub fn signature(&self) -> &midenc_hir::Signature { - self.inner.signature() - } - - pub fn ins<'short>(&'short mut self) -> FuncInstBuilderExt<'short, 'a, 'b, 'c> { - let block = self.inner.current_block(); - FuncInstBuilderExt::new(self, block) - } - - #[inline] - pub fn current_block(&self) -> Block { - self.inner.current_block() - } - - pub fn inst_results(&self, inst: Inst) -> &[Value] { - self.inner.inst_results(inst) - } - - pub fn create_block(&mut self) -> Block { - let block = self.inner.create_block(); - self.func_ctx.ssa.declare_block(block); - block - } - - /// Create a `Block` with the given parameters. - pub fn create_block_with_params( - &mut self, - params: impl IntoIterator, - span: SourceSpan, - ) -> Block { - let block = self.create_block(); - for ty in params { - self.inner.append_block_param(block, ty, span); - } - block - } - - /// Append parameters to the given `Block` corresponding to the function - /// return values. This can be used to set up the block parameters for a - /// function exit block. - pub fn append_block_params_for_function_returns(&mut self, block: Block) { - // These parameters count as "user" parameters here because they aren't - // inserted by the SSABuilder. - debug_assert!( - self.is_pristine(block), - "You can't add block parameters after adding any instruction" - ); - - #[allow(clippy::unnecessary_to_owned)] - for argtyp in self.signature().results().to_vec() { - self.inner.append_block_param(block, argtyp.ty.clone(), SourceSpan::default()); - } - } - - /// After the call to this function, new instructions will be inserted into the designated - /// block, in the order they are declared. You must declare the types of the Block arguments - /// you will use here. - /// - /// When inserting the terminator instruction (which doesn't have a fallthrough to its immediate - /// successor), the block will be declared filled and it will not be possible to append - /// instructions to it. - pub fn switch_to_block(&mut self, block: Block) { - // First we check that the previous block has been filled. - debug_assert!( - self.is_unreachable() - || self.is_pristine(self.inner.current_block()) - || self.is_filled(self.inner.current_block()), - "you have to fill your block before switching" - ); - // We cannot switch to a filled block - debug_assert!( - !self.is_filled(block), - "you cannot switch to a block which is already filled" - ); - // Then we change the cursor position. - self.inner.switch_to_block(block); - } - - /// Retrieves all the parameters for a `Block` currently inferred from the jump instructions - /// inserted that target it and the SSA construction. - pub fn block_params(&self, block: Block) -> &[Value] { - self.inner.block_params(block) - } - - /// Declares that all the predecessors of this block are known. - /// - /// Function to call with `block` as soon as the last branch instruction to `block` has been - /// created. Forgetting to call this method on every block will cause inconsistencies in the - /// produced functions. - pub fn seal_block(&mut self, block: Block) { - let side_effects = self.func_ctx.ssa.seal_block(block, self.inner.data_flow_graph_mut()); - self.handle_ssa_side_effects(side_effects); - } - - /// A Block is 'filled' when a terminator instruction is present. - fn fill_current_block(&mut self) { - self.func_ctx.status[self.inner.current_block()] = BlockStatus::Filled; - } - - fn handle_ssa_side_effects(&mut self, side_effects: SideEffects) { - for modified_block in side_effects.instructions_added_to_blocks { - if self.is_pristine(modified_block) { - self.func_ctx.status[modified_block] = BlockStatus::Partial; - } - } - } - - /// Make sure that the current block is inserted in the layout. - pub fn ensure_inserted_block(&mut self) { - let block = self.inner.current_block(); - if self.is_pristine(block) { - self.func_ctx.status[block] = BlockStatus::Partial; - } else { - debug_assert!( - !self.is_filled(block), - "you cannot add an instruction to a block already filled" - ); - } - } - - /// Declare that translation of the current function is complete. - /// - /// This resets the state of the `FunctionBuilderContext` in preparation to - /// be used for another function. - pub fn finalize(self) { - // Check that all the `Block`s are filled and sealed. - #[cfg(debug_assertions)] - { - for block in self.func_ctx.status.keys() { - if !self.is_pristine(block) { - assert!( - self.func_ctx.ssa.is_sealed(block), - "FunctionBuilderExt finalized, but block {} is not sealed", - block, - ); - assert!( - self.is_filled(block), - "FunctionBuilderExt finalized, but block {} is not filled", - block, - ); - } - } - } - - // Clear the state (but preserve the allocated buffers) in preparation - // for translation another function. - self.func_ctx.clear(); - } - - #[inline] - pub fn variable_type(&self, var: Variable) -> &Type { - &self.func_ctx.types[var] - } - - /// Declares the type of a variable, so that it can be used later (by calling - /// [`FunctionBuilderExt::use_var`]). This function will return an error if the variable - /// has been previously declared. - pub fn try_declare_var(&mut self, var: Variable, ty: Type) -> Result<(), DeclareVariableError> { - if self.func_ctx.types[var] != Type::Unknown { - return Err(DeclareVariableError::DeclaredMultipleTimes(var)); - } - self.func_ctx.types[var] = ty; - Ok(()) - } - - /// In order to use a variable (by calling [`FunctionBuilderExt::use_var`]), you need - /// to first declare its type with this method. - pub fn declare_var(&mut self, var: Variable, ty: Type) { - self.try_declare_var(var, ty) - .unwrap_or_else(|_| panic!("the variable {:?} has been declared multiple times", var)) - } - - /// Returns the Miden IR necessary to use a previously defined user - /// variable, returning an error if this is not possible. - pub fn try_use_var(&mut self, var: Variable) -> Result { - // Assert that we're about to add instructions to this block using the definition of the - // given variable. ssa.use_var is the only part of this crate which can add block parameters - // behind the caller's back. If we disallow calling append_block_param as soon as use_var is - // called, then we enforce a strict separation between user parameters and SSA parameters. - self.ensure_inserted_block(); - - let (val, side_effects) = { - let ty = self - .func_ctx - .types - .get(var) - .cloned() - .ok_or(UseVariableError::UsedBeforeDeclared(var))?; - debug_assert_ne!( - ty, - Type::Unknown, - "variable {:?} is used but its type has not been declared", - var - ); - let current_block = self.inner.current_block(); - self.func_ctx - .ssa - .use_var(self.inner.data_flow_graph_mut(), var, ty, current_block) - }; - self.handle_ssa_side_effects(side_effects); - Ok(val) - } - - /// Returns the Miden IR value corresponding to the utilization at the current program - /// position of a previously defined user variable. - pub fn use_var(&mut self, var: Variable) -> Value { - self.try_use_var(var).unwrap_or_else(|_| { - panic!("variable {:?} is used but its type has not been declared", var) - }) - } - - /// Registers a new definition of a user variable. This function will return - /// an error if the value supplied does not match the type the variable was - /// declared to have. - pub fn try_def_var(&mut self, var: Variable, val: Value) -> Result<(), DefVariableError> { - let var_ty = self - .func_ctx - .types - .get(var) - .ok_or(DefVariableError::DefinedBeforeDeclared(var))?; - if var_ty != self.data_flow_graph().value_type(val) { - return Err(DefVariableError::TypeMismatch(var, val)); - } - - self.func_ctx.ssa.def_var(var, val, self.inner.current_block()); - Ok(()) - } - - /// Register a new definition of a user variable. The type of the value must be - /// the same as the type registered for the variable. - pub fn def_var(&mut self, var: Variable, val: Value) { - self.try_def_var(var, val).unwrap_or_else(|error| match error { - DefVariableError::TypeMismatch(var, val) => { - assert_eq!( - &self.func_ctx.types[var], - self.data_flow_graph().value_type(val), - "declared type of variable {:?} doesn't match type of value {}", - var, - val - ); - } - DefVariableError::DefinedBeforeDeclared(var) => { - panic!("variable {:?} is used but its type has not been declared", var); - } - }) - } - - /// Returns `true` if and only if no instructions have been added since the last call to - /// `switch_to_block`. - fn is_pristine(&self, block: Block) -> bool { - self.func_ctx.status[block] == BlockStatus::Empty - } - - /// Returns `true` if and only if a terminator instruction has been inserted since the - /// last call to `switch_to_block`. - fn is_filled(&self, block: Block) -> bool { - self.func_ctx.status[block] == BlockStatus::Filled - } - - /// Returns `true` if and only if the current `Block` is sealed and has no predecessors - /// declared. - /// - /// The entry block of a function is never unreachable. - pub fn is_unreachable(&self) -> bool { - let is_entry = self.inner.current_block() == self.data_flow_graph().entry_block(); - !is_entry - && self.func_ctx.ssa.is_sealed(self.inner.current_block()) - && !self.func_ctx.ssa.has_any_predecessors(self.inner.current_block()) - } - - /// Changes the destination of a jump instruction after creation. - /// - /// **Note:** You are responsible for maintaining the coherence with the arguments of - /// other jump instructions. - pub fn change_jump_destination(&mut self, inst: Inst, old_block: Block, new_block: Block) { - self.func_ctx.ssa.remove_block_predecessor(old_block, inst); - match &mut *self.data_flow_graph_mut().insts[inst].data { - Instruction::Br(Br { - ref mut successor, .. - }) if successor.destination == old_block => { - successor.destination = new_block; - } - Instruction::CondBr(CondBr { - ref mut then_dest, - ref mut else_dest, - .. - }) => { - if then_dest.destination == old_block { - then_dest.destination = new_block; - } else if else_dest.destination == old_block { - else_dest.destination = new_block; - } - } - Instruction::Switch(Switch { - op: _, - arg: _, - ref mut arms, - ref mut default, - }) => { - for arm in arms.iter_mut() { - if arm.successor.destination == old_block { - arm.successor.destination = new_block; - } - } - if default.destination == old_block { - default.destination = new_block; - } - } - _ => panic!("{} must be a branch instruction", inst), - } - self.func_ctx.ssa.declare_block_predecessor(new_block, inst); - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] -/// An error encountered when calling [`FunctionBuilderExt::try_use_var`]. -pub enum UseVariableError { - #[error("variable {0} is used before the declaration")] - UsedBeforeDeclared(Variable), -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, thiserror::Error)] -/// An error encountered when calling [`FunctionBuilderExt::try_declare_var`]. -pub enum DeclareVariableError { - #[error("variable {0} is already declared")] - DeclaredMultipleTimes(Variable), -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq, thiserror::Error)] -/// An error encountered when defining the initial value of a variable. -pub enum DefVariableError { - #[error( - "the types of variable {0} and value {1} are not the same. The `Value` supplied to \ - `def_var` must be of the same type as the variable was declared to be of in \ - `declare_var`." - )] - TypeMismatch(Variable, Value), - #[error( - "the value of variable {0} was defined (in call `def_val`) before it was declared (in \ - call `declare_var`)" - )] - DefinedBeforeDeclared(Variable), -} - -pub struct FuncInstBuilderExt<'a, 'b: 'a, 'c, 'd: 'c> { - builder: &'a mut FunctionBuilderExt<'b, 'c, 'd>, - ip: InsertionPoint, -} -impl<'a, 'b, 'c, 'd> FuncInstBuilderExt<'a, 'b, 'c, 'd> { - fn new(builder: &'a mut FunctionBuilderExt<'b, 'c, 'd>, block: Block) -> Self { - assert!(builder.data_flow_graph().is_block_linked(block)); - Self { - builder, - ip: InsertionPoint::after(ProgramPoint::Block(block)), - } - } -} -impl<'a, 'b, 'c, 'd> InstBuilderBase<'a> for FuncInstBuilderExt<'a, 'b, 'c, 'd> { - fn data_flow_graph(&self) -> &DataFlowGraph { - self.builder.data_flow_graph() - } - - fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { - self.builder.data_flow_graph_mut() - } - - fn insertion_point(&self) -> InsertionPoint { - self.ip - } - - // This implementation is richer than `InsertBuilder` because we use the data of the - // instruction being inserted to add related info to the DFG and the SSA building system, - // and perform debug sanity checks. - fn build(self, data: Instruction, ty: Type, span: SourceSpan) -> (Inst, &'a mut DataFlowGraph) { - // We only insert the Block in the layout when an instruction is added to it - self.builder.ensure_inserted_block(); - let opcode = data.opcode(); - let inst = self.builder.data_flow_graph_mut().insert_inst(self.ip, data, ty, span); - - match self.builder.inner.data_flow_graph().insts[inst].data.inner() { - Instruction::Br(Br { successor, .. }) => { - // If the user has supplied jump arguments we must adapt the arguments of - // the destination block - self.builder.func_ctx.ssa.declare_block_predecessor(successor.destination, inst); - } - - Instruction::CondBr(CondBr { - then_dest, - else_dest, - .. - }) => { - self.builder.func_ctx.ssa.declare_block_predecessor(then_dest.destination, inst); - if then_dest.destination != else_dest.destination { - self.builder - .func_ctx - .ssa - .declare_block_predecessor(else_dest.destination, inst); - } - } - Instruction::Switch(Switch { - op: _, - arg: _, - ref arms, - default: default_successor, - }) => { - // Unlike all other jumps/branches, arms are - // capable of having the same successor appear - // multiple times, so we must deduplicate. - let mut unique = EntitySet::::new(); - let blocks = arms - .iter() - .map(|arm| arm.successor.destination) - .chain([default_successor.destination]); - for block in blocks { - if !unique.insert(block) { - continue; - } - self.builder.func_ctx.ssa.declare_block_predecessor(block, inst); - } - } - inst => debug_assert!(!inst.opcode().is_branch()), - } - - if opcode.is_terminator() { - self.builder.fill_current_block() - } - (inst, self.builder.data_flow_graph_mut()) - } -} diff --git a/frontend-wasm/src/module/instance.rs b/frontend-wasm/src/module/instance.rs deleted file mode 100644 index 588c3a235..000000000 --- a/frontend-wasm/src/module/instance.rs +++ /dev/null @@ -1,12 +0,0 @@ -use midenc_hir::{ComponentImport, FunctionIdent}; - -/// Represents module argument that is used to instantiate a module. -#[derive(Debug, Clone)] -pub enum ModuleArgument { - /// Represents function that is exported from another module. - Function(FunctionIdent), - /// Represents component import that is lowered to a module import. - ComponentImport(ComponentImport), - /// Represents table exported from another module. - Table, -} diff --git a/frontend-wasm/src/module/mod.rs b/frontend-wasm/src/module/mod.rs deleted file mode 100644 index 58952595c..000000000 --- a/frontend-wasm/src/module/mod.rs +++ /dev/null @@ -1,368 +0,0 @@ -//! Data structures for representing parsed Wasm modules. - -// TODO: remove this once Wasm CM support is complete -#![allow(dead_code)] - -use std::{borrow::Cow, collections::BTreeMap, ops::Range}; - -use indexmap::IndexMap; -use midenc_hir::{ - cranelift_entity::{packed_option::ReservedValue, EntityRef, PrimaryMap}, - diagnostics::{DiagnosticsHandler, Severity}, - Ident, Symbol, -}; -use rustc_hash::FxHashMap; - -use self::types::*; -use crate::{component::SignatureIndex, error::WasmResult, unsupported_diag}; - -pub mod build_ir; -pub mod func_translation_state; -pub mod func_translator; -pub mod function_builder_ext; -pub mod instance; -pub mod module_env; -pub mod module_translation_state; -pub mod types; - -/// Table initialization data for all tables in the module. -#[derive(Debug, Default)] -pub struct TableInitialization { - /// Initial values for tables defined within the module itself. - /// - /// This contains the initial values and initializers for tables defined - /// within a wasm, so excluding imported tables. This initializer can - /// represent null-initialized tables, element-initialized tables (e.g. with - /// the function-references proposal), or precomputed images of table - /// initialization. For example table initializers to a table that are all - /// in-bounds will get removed from `segment` and moved into - /// `initial_values` here. - pub initial_values: PrimaryMap, - - /// Element segments present in the initial wasm module which are executed - /// at instantiation time. - /// - /// These element segments are iterated over during instantiation to apply - /// any segments that weren't already moved into `initial_values` above. - pub segments: Vec, -} - -/// Initial value for all elements in a table. -#[derive(Clone, Debug)] -pub enum TableInitialValue { - /// Initialize each table element to null, optionally setting some elements - /// to non-null given the precomputed image. - Null { - /// A precomputed image of table initializers for this table. - precomputed: Vec, - }, - - /// Initialize each table element to the function reference given - /// by the `FuncIndex`. - FuncRef(FuncIndex), -} - -/// A WebAssembly table initializer segment. -#[derive(Clone, Debug)] -pub struct TableSegment { - /// The index of a table to initialize. - pub table_index: TableIndex, - /// Optionally, a global variable giving a base index. - pub base: Option, - /// The offset to add to the base. - pub offset: u32, - /// The values to write into the table elements. - pub elements: Box<[FuncIndex]>, -} - -/// Different types that can appear in a module. -/// -/// Note that each of these variants are intended to index further into a -/// separate table. -#[derive(Debug, Copy, Clone)] -pub enum ModuleType { - Function(SignatureIndex), -} - -impl ModuleType { - /// Asserts this is a `ModuleType::Function`, returning the underlying - /// `SignatureIndex`. - pub fn unwrap_function(&self) -> SignatureIndex { - match self { - ModuleType::Function(f) => *f, - } - } -} - -/// A translated WebAssembly module, excluding the function bodies -#[derive(Default, Debug)] -pub struct Module { - /// All import records, in the order they are declared in the module. - pub imports: Vec, - - /// Exported entities. - pub exports: IndexMap, - - /// The module "start" function, if present. - pub start_func: Option, - - /// WebAssembly table initialization data, per table. - pub table_initialization: TableInitialization, - - /// WebAssembly passive elements. - pub passive_elements: Vec>, - - /// The map from passive element index (element segment index space) to index in - /// `passive_elements`. - pub passive_elements_map: BTreeMap, - - /// The map from passive data index (data segment index space) to index in `passive_data`. - pub passive_data_map: BTreeMap>, - - /// Types declared in the wasm module. - pub types: PrimaryMap, - - /// Number of imported or aliased functions in the module. - pub num_imported_funcs: usize, - - /// Number of imported or aliased tables in the module. - pub num_imported_tables: usize, - - /// Number of imported or aliased globals in the module. - pub num_imported_globals: usize, - - /// Number of functions that "escape" from this module - /// - /// This is also the number of functions in the `functions` array below with - /// an `func_ref` index (and is the maximum func_ref index). - pub num_escaped_funcs: usize, - - /// Types of functions, imported and local. - pub functions: PrimaryMap, - - /// WebAssembly tables. - pub tables: PrimaryMap, - - /// WebAssembly global variables. - pub globals: PrimaryMap, - - /// WebAssembly global initializers for locally-defined globals. - pub global_initializers: PrimaryMap, - - /// WebAssembly module memories. - pub memories: PrimaryMap, - - /// Parsed names section. - name_section: NameSection, - - /// The fallback name of this module, used if there is no module name in the name section, - /// and there is no override specified - name_fallback: Option, - - /// If specified, overrides the name of the module regardless of what is in the name section - name_override: Option, -} - -/// Module imports -#[derive(Debug, Clone)] -pub struct ModuleImport { - /// Name of this import - pub module: String, - /// The field name projection of this import - pub field: String, - /// Where this import will be placed, which also has type information - /// about the import. - pub index: EntityIndex, -} - -impl Module { - /// Convert a `DefinedFuncIndex` into a `FuncIndex`. - #[inline] - pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex { - FuncIndex::new(self.num_imported_funcs + defined_func.index()) - } - - /// Convert a `FuncIndex` into a `DefinedFuncIndex`. Returns None if the - /// index is an imported function. - #[inline] - pub fn defined_func_index(&self, func: FuncIndex) -> Option { - if func.index() < self.num_imported_funcs { - None - } else { - Some(DefinedFuncIndex::new(func.index() - self.num_imported_funcs)) - } - } - - /// Test whether the given function index is for an imported function. - #[inline] - pub fn is_imported_function(&self, index: FuncIndex) -> bool { - index.index() < self.num_imported_funcs - } - - /// Convert a `DefinedTableIndex` into a `TableIndex`. - #[inline] - pub fn table_index(&self, defined_table: DefinedTableIndex) -> TableIndex { - TableIndex::new(self.num_imported_tables + defined_table.index()) - } - - /// Convert a `TableIndex` into a `DefinedTableIndex`. Returns None if the - /// index is an imported table. - #[inline] - pub fn defined_table_index(&self, table: TableIndex) -> Option { - if table.index() < self.num_imported_tables { - None - } else { - Some(DefinedTableIndex::new(table.index() - self.num_imported_tables)) - } - } - - /// Test whether the given table index is for an imported table. - #[inline] - pub fn is_imported_table(&self, index: TableIndex) -> bool { - index.index() < self.num_imported_tables - } - - /// Test whether the given memory index is for an imported memory. - #[inline] - pub fn is_imported_memory(&self, index: MemoryIndex) -> bool { - self.memories[index].imported - } - - /// Convert a `DefinedGlobalIndex` into a `GlobalIndex`. - #[inline] - pub fn global_index(&self, defined_global: DefinedGlobalIndex) -> GlobalIndex { - GlobalIndex::new(self.num_imported_globals + defined_global.index()) - } - - /// Convert a `GlobalIndex` into a `DefinedGlobalIndex`. Returns None if the - /// index is an imported global. - #[inline] - pub fn defined_global_index(&self, global: GlobalIndex) -> Option { - if global.index() < self.num_imported_globals { - None - } else { - Some(DefinedGlobalIndex::new(global.index() - self.num_imported_globals)) - } - } - - /// Test whether the given global index is for an imported global. - #[inline] - pub fn is_imported_global(&self, index: GlobalIndex) -> bool { - index.index() < self.num_imported_globals - } - - pub fn global_name(&self, index: GlobalIndex) -> Symbol { - self.name_section - .globals_names - .get(&index) - .cloned() - .unwrap_or(Symbol::intern(format!("global{}", index.as_u32()).as_str())) - } - - /// Returns the type of an item based on its index - pub fn type_of(&self, index: EntityIndex) -> EntityType { - match index { - EntityIndex::Global(i) => EntityType::Global(self.globals[i].clone()), - EntityIndex::Table(i) => EntityType::Table(self.tables[i]), - EntityIndex::Memory(i) => EntityType::Memory(self.memories[i]), - EntityIndex::Function(i) => EntityType::Function(self.functions[i].signature), - } - } - - /// Appends a new function to this module with the given type information, - /// used for functions that either don't escape or aren't certain whether - /// they escape yet. - pub fn push_function(&mut self, signature: SignatureIndex) -> FuncIndex { - self.functions.push(FunctionTypeInfo { - signature, - func_ref: FuncRefIndex::reserved_value(), - }) - } - - /// Appends a new function to this module with the given type information. - pub fn push_escaped_function( - &mut self, - signature: SignatureIndex, - func_ref: FuncRefIndex, - ) -> FuncIndex { - self.functions.push(FunctionTypeInfo { - signature, - func_ref, - }) - } - - /// Returns the global initializer for the given index, or `Unsupported` error if the global is - /// imported. - pub fn try_global_initializer( - &self, - index: GlobalIndex, - diagnostics: &DiagnosticsHandler, - ) -> WasmResult<&GlobalInit> { - if let Some(defined_index) = self.defined_global_index(index) { - Ok(&self.global_initializers[defined_index]) - } else { - unsupported_diag!(diagnostics, "Imported globals are not supported yet"); - } - } - - /// Returns the name of this module - pub fn name(&self) -> Ident { - self.name_override - .or(self.name_section.module_name) - .or(self.name_fallback) - .expect("No module name in the name section and no fallback name is set") - } - - /// Returns the name of the given function - pub fn func_name(&self, index: FuncIndex) -> Symbol { - self.name_section - .func_names - .get(&index) - .cloned() - .unwrap_or(Symbol::intern(format!("func{}", index.as_u32()))) - } - - /// Sets the fallback name of this module, used if there is no module name in the name section - pub fn set_name_fallback(&mut self, name_fallback: Cow<'static, str>) { - self.name_fallback = Some(Ident::from(name_fallback.as_ref())); - } - - /// Sets the name of this module, discarding whatever is in the name section - pub fn set_name_override(&mut self, name_override: Cow<'static, str>) { - self.name_override = Some(Ident::from(name_override.as_ref())); - } -} - -/// Type information about functions in a wasm module. -#[derive(Debug, Clone, Copy)] -pub struct FunctionTypeInfo { - /// The type of this function, indexed into the module-wide type tables for - /// a module compilation. - pub signature: SignatureIndex, - /// The index into the funcref table, if present. Note that this is - /// `reserved_value()` if the function does not escape from a module. - pub func_ref: FuncRefIndex, -} - -impl FunctionTypeInfo { - /// Returns whether this function's type is one that "escapes" the current - /// module, meaning that the function is exported, used in `ref.func`, used - /// in a table, etc. - pub fn is_escaping(&self) -> bool { - !self.func_ref.is_reserved_value() - } -} - -/// Index into the funcref table -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -pub struct FuncRefIndex(u32); -midenc_hir::cranelift_entity::entity_impl!(FuncRefIndex); - -#[derive(Debug, Default)] -pub struct NameSection { - pub module_name: Option, - pub func_names: FxHashMap, - pub locals_names: FxHashMap>, - pub globals_names: FxHashMap, - pub data_segment_names: FxHashMap, -} diff --git a/frontend-wasm/src/module/module_translation_state.rs b/frontend-wasm/src/module/module_translation_state.rs deleted file mode 100644 index ac26f6854..000000000 --- a/frontend-wasm/src/module/module_translation_state.rs +++ /dev/null @@ -1,149 +0,0 @@ -use miden_core::crypto::hash::RpoDigest; -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, Severity}, - AbiParam, CallConv, DataFlowGraph, FunctionIdent, Ident, Linkage, Signature, -}; -use rustc_hash::FxHashMap; - -use super::{instance::ModuleArgument, ir_func_type, EntityIndex, FuncIndex, Module, ModuleTypes}; -use crate::{ - error::WasmResult, - intrinsics::is_miden_intrinsics_module, - miden_abi::{is_miden_abi_module, miden_abi_function_type, parse_import_function_digest}, - translation_utils::sig_from_func_type, -}; - -pub struct ModuleTranslationState { - /// Imported and local functions - /// Stores both the function reference and its signature - functions: FxHashMap, - /// Parsed MAST root hash for imported functions for Miden SDK - digests: FxHashMap, - /// Number of imported or aliased functions in the module. - pub num_imported_funcs: usize, - // stable_imported_miden_abi_functions: FxHashMap, -} - -impl ModuleTranslationState { - pub fn new( - module: &Module, - mod_types: &ModuleTypes, - module_args: Vec, - diagnostics: &DiagnosticsHandler, - ) -> Self { - let mut function_import_subst = FxHashMap::default(); - if module.imports.len() == module_args.len() { - for (import, arg) in module.imports.iter().zip(module_args) { - match (import.index, arg) { - (EntityIndex::Function(func_idx), ModuleArgument::Function(func_id)) => { - // Substitutes the function import with concrete function exported from - // another module - function_import_subst.insert(func_idx, func_id); - } - (EntityIndex::Function(_), ModuleArgument::ComponentImport(_)) => { - // Do nothing, the local function id will be used - } - (EntityIndex::Function(_), module_arg) => { - panic!( - "Unexpected {module_arg:?} module argument for function import \ - {import:?}" - ) - } - (..) => (), // Do nothing, we interested only in function imports - } - } - } - let mut functions = FxHashMap::default(); - let mut digests = FxHashMap::default(); - for (index, func_type) in &module.functions { - let wasm_func_type = mod_types[func_type.signature].clone(); - let ir_func_type = ir_func_type(&wasm_func_type, diagnostics).unwrap(); - let sig = sig_from_func_type(&ir_func_type, CallConv::SystemV, Linkage::External); - if let Some(subst) = function_import_subst.get(&index) { - functions.insert(index, (*subst, sig)); - } else if module.is_imported_function(index) { - assert!((index.as_u32() as usize) < module.num_imported_funcs); - let import = &module.imports[index.as_u32() as usize]; - if let Ok((func_stable_name, digest)) = parse_import_function_digest(&import.field) - { - let func_id = FunctionIdent { - module: Ident::from(import.module.as_str()), - function: Ident::from(func_stable_name.as_str()), - }; - functions.insert(index, (func_id, sig)); - digests.insert(func_id, digest); - } else { - let func_id = FunctionIdent { - module: Ident::from(import.module.as_str()), - function: Ident::from(import.field.as_str()), - }; - functions.insert(index, (func_id, sig)); - }; - } else { - let func_name = module.func_name(index); - let func_id = FunctionIdent { - module: module.name(), - function: Ident::from(func_name.as_str()), - }; - functions.insert(index, (func_id, sig)); - }; - } - Self { - functions, - digests, - num_imported_funcs: module.num_imported_funcs, - } - } - - /// Returns an IR function signature converted from Wasm function signature - /// for the given function index. - pub fn signature(&self, index: FuncIndex) -> &Signature { - &self.functions[&index].1 - } - - /// Returns parsed MAST root hash for the given function id (if it is imported and has one) - pub fn digest(&self, func_id: &FunctionIdent) -> Option<&RpoDigest> { - self.digests.get(func_id) - } - - /// Get the `FunctionIdent` that should be used to make a direct call to function - /// `index`. - /// - /// Import the callee into `func`'s DFG if it is not already present. - pub(crate) fn get_direct_func( - &mut self, - dfg: &mut DataFlowGraph, - index: FuncIndex, - diagnostics: &DiagnosticsHandler, - ) -> WasmResult { - let (func_id, wasm_sig) = self.functions[&index].clone(); - let sig: Signature = if is_miden_abi_module(func_id.module.as_symbol()) { - let ft = - miden_abi_function_type(func_id.module.as_symbol(), func_id.function.as_symbol()); - Signature::new( - ft.params.into_iter().map(AbiParam::new), - ft.results.into_iter().map(AbiParam::new), - ) - } else { - wasm_sig.clone() - }; - - if is_miden_intrinsics_module(func_id.module.as_symbol()) { - // Exit and do not import intrinsics functions into the DFG - return Ok(func_id); - } - - if dfg.get_import(&func_id).is_none() { - dfg.import_function(func_id.module, func_id.function, sig.clone()) - .map_err(|_e| { - let message = format!( - "Function with name {} in module {} with signature {sig:?} is already \ - imported (function call) with a different signature", - func_id.function, func_id.module - ); - diagnostics.diagnostic(Severity::Error).with_message(message).into_report() - })?; - } - Ok(func_id) - } -} diff --git a/frontend-wasm/src/module/types.rs b/frontend-wasm/src/module/types.rs deleted file mode 100644 index fa83cbb7e..000000000 --- a/frontend-wasm/src/module/types.rs +++ /dev/null @@ -1,666 +0,0 @@ -//! Types for parsed core WebAssembly modules. - -use core::fmt; -use std::{collections::HashMap, ops::Index}; - -use midenc_hir::{ - cranelift_entity::PrimaryMap, - diagnostics::{DiagnosticsHandler, Severity}, - AbiParam, CallConv, Linkage, Signature, -}; -use midenc_hir_type::{self as hir, Abi}; -use wasmparser::types::CoreTypeId; - -use crate::{component::SignatureIndex, error::WasmResult, module::Module, unsupported_diag}; - -/// Generates a new index type for each entity. -#[macro_export] -macro_rules! indices { - ($( - $(#[$a:meta])* - pub struct $name:ident(u32); - )*) => ($( - $(#[$a])* - #[derive( - Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug - )] - #[repr(transparent)] - pub struct $name(u32); - midenc_hir::cranelift_entity::entity_impl!($name); - )*); -} - -indices! { -/// Index type of a function (imported or defined) inside the WebAssembly module. -pub struct FuncIndex(u32); - -/// Index type of a defined function inside the WebAssembly module. -pub struct DefinedFuncIndex(u32); - -/// Index type of a defined table inside the WebAssembly module. -pub struct DefinedTableIndex(u32); - -/// Index type of a defined memory inside the WebAssembly module. -pub struct DefinedMemoryIndex(u32); - -/// Index type of a defined memory inside the WebAssembly module. -pub struct OwnedMemoryIndex(u32); - -/// Index type of a defined global inside the WebAssembly module. -pub struct DefinedGlobalIndex(u32); - -/// Index type of a table (imported or defined) inside the WebAssembly module. -pub struct TableIndex(u32); - -/// Index type of a global variable (imported or defined) inside the WebAssembly module. -pub struct GlobalIndex(u32); - -/// Index type of a linear memory (imported or defined) inside the WebAssembly module. -pub struct MemoryIndex(u32); - -/// Index type of a passive data segment inside the WebAssembly module. -pub struct DataIndex(u32); - -/// Index type of a passive element segment inside the WebAssembly module. -pub struct ElemIndex(u32); - -/// Index type of a type inside the WebAssembly module. -pub struct TypeIndex(u32); - -/// Index type of a data segment inside the WebAssembly module. -pub struct DataSegmentIndex(u32); - -} - -/// WebAssembly value type -- equivalent of `wasmparser`'s Type. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum WasmType { - /// I32 type - I32, - /// I64 type - I64, - /// F32 type - F32, - /// F64 type - F64, - /// V128 type - V128, - /// Reference type - Ref(WasmRefType), -} - -impl fmt::Display for WasmType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - WasmType::I32 => write!(f, "i32"), - WasmType::I64 => write!(f, "i64"), - WasmType::F32 => write!(f, "f32"), - WasmType::F64 => write!(f, "f64"), - WasmType::V128 => write!(f, "v128"), - WasmType::Ref(rt) => write!(f, "{rt}"), - } - } -} - -/// WebAssembly reference type -- equivalent of `wasmparser`'s RefType -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct WasmRefType { - pub nullable: bool, - pub heap_type: WasmHeapType, -} - -impl WasmRefType { - pub const EXTERNREF: WasmRefType = WasmRefType { - nullable: true, - heap_type: WasmHeapType::Extern, - }; - pub const FUNCREF: WasmRefType = WasmRefType { - nullable: true, - heap_type: WasmHeapType::Func, - }; -} - -impl fmt::Display for WasmRefType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Self::FUNCREF => write!(f, "funcref"), - Self::EXTERNREF => write!(f, "externref"), - _ => { - if self.nullable { - write!(f, "(ref null {})", self.heap_type) - } else { - write!(f, "(ref {})", self.heap_type) - } - } - } - } -} - -/// WebAssembly heap type -- equivalent of `wasmparser`'s HeapType -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum WasmHeapType { - /// The abstract, untyped (any) function. - /// - /// Introduced in the references-types proposal. - Func, - /// The abstract, external heap type. - /// - /// Introduced in the references-types proposal. - Extern, -} - -impl fmt::Display for WasmHeapType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Func => write!(f, "func"), - Self::Extern => write!(f, "extern"), - } - } -} - -/// WebAssembly function type -- equivalent of `wasmparser`'s FuncType. -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct WasmFuncType { - params: Box<[WasmType]>, - externref_params_count: usize, - returns: Box<[WasmType]>, - externref_returns_count: usize, -} - -impl WasmFuncType { - #[inline] - pub fn new(params: Box<[WasmType]>, returns: Box<[WasmType]>) -> Self { - let externref_params_count = params - .iter() - .filter(|p| match **p { - WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, - _ => false, - }) - .count(); - let externref_returns_count = returns - .iter() - .filter(|r| match **r { - WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, - _ => false, - }) - .count(); - WasmFuncType { - params, - externref_params_count, - returns, - externref_returns_count, - } - } - - /// Function params types. - #[inline] - pub fn params(&self) -> &[WasmType] { - &self.params - } - - /// How many `externref`s are in this function's params? - #[inline] - pub fn externref_params_count(&self) -> usize { - self.externref_params_count - } - - /// Returns params types. - #[inline] - pub fn returns(&self) -> &[WasmType] { - &self.returns - } - - /// How many `externref`s are in this function's returns? - #[inline] - pub fn externref_returns_count(&self) -> usize { - self.externref_returns_count - } -} - -/// An index of an entity. -#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] -pub enum EntityIndex { - /// Function index. - Function(FuncIndex), - /// Table index. - Table(TableIndex), - /// Memory index. - Memory(MemoryIndex), - /// Global index. - Global(GlobalIndex), -} - -impl EntityIndex { - pub fn unwrap_func(&self) -> FuncIndex { - match self { - EntityIndex::Function(f) => *f, - eidx => panic!("not a func, but {eidx:?}"), - } - } -} - -/// A type of an item in a wasm module where an item is typically something that -/// can be exported. -#[derive(Clone, Debug)] -pub enum EntityType { - /// A global variable with the specified content type - Global(Global), - /// A linear memory with the specified limits - Memory(Memory), - /// A table with the specified element type and limits - Table(Table), - /// A function type where the index points to the type section and records a - /// function signature. - Function(SignatureIndex), -} - -impl EntityType { - /// Assert that this entity is a global - pub fn unwrap_global(&self) -> &Global { - match self { - EntityType::Global(g) => g, - _ => panic!("not a global"), - } - } - - /// Assert that this entity is a memory - pub fn unwrap_memory(&self) -> &Memory { - match self { - EntityType::Memory(g) => g, - _ => panic!("not a memory"), - } - } - - /// Assert that this entity is a table - pub fn unwrap_table(&self) -> &Table { - match self { - EntityType::Table(g) => g, - _ => panic!("not a table"), - } - } - - /// Assert that this entity is a function - pub fn unwrap_func(&self) -> SignatureIndex { - match self { - EntityType::Function(g) => *g, - _ => panic!("not a func"), - } - } -} - -/// A WebAssembly global. -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct Global { - /// The Wasm type of the value stored in the global. - pub ty: WasmType, - /// A flag indicating whether the value may change at runtime. - pub mutability: bool, -} - -/// Globals are initialized via the `const` operators or by referring to another import. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub enum GlobalInit { - /// An `i32.const`. - I32Const(i32), - /// An `i64.const`. - I64Const(i64), - /// An `f32.const`. - F32Const(u32), - /// An `f64.const`. - F64Const(u64), - /// A `vconst`. - V128Const(u128), - /// A `global.get` of another global. - GetGlobal(GlobalIndex), -} - -impl GlobalInit { - /// Serialize the initializer constant expression into bytes (little-endian order). - pub fn to_le_bytes( - self, - module: &Module, - diagnostics: &DiagnosticsHandler, - ) -> WasmResult> { - Ok(match self { - GlobalInit::I32Const(x) => x.to_le_bytes().to_vec(), - GlobalInit::I64Const(x) => x.to_le_bytes().to_vec(), - GlobalInit::F32Const(x) => x.to_le_bytes().to_vec(), - GlobalInit::F64Const(x) => x.to_le_bytes().to_vec(), - GlobalInit::V128Const(x) => x.to_le_bytes().to_vec(), - GlobalInit::GetGlobal(global_idx) => { - let global_init = module.try_global_initializer(global_idx, diagnostics)?; - global_init.to_le_bytes(module, diagnostics)? - } - }) - } - - pub fn as_i32(&self, module: &Module, diagnostics: &DiagnosticsHandler) -> WasmResult { - Ok(match self { - GlobalInit::I32Const(x) => *x, - GlobalInit::GetGlobal(global_idx) => { - let global_init = module.try_global_initializer(*global_idx, diagnostics)?; - global_init.as_i32(module, diagnostics)? - } - g => { - unsupported_diag!(diagnostics, "Expected global init to be i32, got: {:?}", g); - } - }) - } -} - -/// WebAssembly table. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub struct Table { - /// The table elements' Wasm type. - pub wasm_ty: WasmRefType, - /// The minimum number of elements in the table. - pub minimum: u32, - /// The maximum number of elements in the table. - pub maximum: Option, -} - -/// WebAssembly linear memory. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub struct Memory { - /// The minimum number of pages in the memory. - pub minimum: u64, - /// The maximum number of pages in the memory. - pub maximum: Option, - /// Is this memory imported in the current [Module] - pub imported: bool, -} - -impl From for Memory { - fn from(ty: wasmparser::MemoryType) -> Memory { - Memory { - minimum: ty.initial, - maximum: ty.maximum, - imported: false, - } - } -} - -/// WebAssembly event. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub struct Tag { - /// The event signature type. - pub ty: TypeIndex, -} - -impl From for Tag { - fn from(ty: wasmparser::TagType) -> Tag { - match ty.kind { - wasmparser::TagKind::Exception => Tag { - ty: TypeIndex::from_u32(ty.func_type_idx), - }, - } - } -} -/// Offset of a data segment inside a linear memory. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] -pub enum DataSegmentOffset { - /// An `i32.const` offset. - I32Const(i32), - /// An offset as a `global.get` of another global. - GetGlobal(GlobalIndex), -} - -impl DataSegmentOffset { - /// Returns the offset as a i32, resolving the global if necessary. - pub fn as_i32(&self, module: &Module, diagnostics: &DiagnosticsHandler) -> WasmResult { - Ok(match self { - DataSegmentOffset::I32Const(x) => *x, - DataSegmentOffset::GetGlobal(global_idx) => { - let global_init = &module.try_global_initializer(*global_idx, diagnostics)?; - match global_init.as_i32(module, diagnostics) { - Err(_) => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message(format!( - "Failed to get data segment offset from global init {:?} with \ - global index {global_idx:?}", - global_init, - )) - .into_report()); - } - Ok(v) => v, - } - } - }) - } -} - -/// A WebAssembly data segment. -/// https://www.w3.org/TR/wasm-core-1/#data-segments%E2%91%A0 -#[derive(Debug)] -pub struct DataSegment<'a> { - /// The offset of the data segment inside the linear memory. - pub offset: DataSegmentOffset, - /// The initialization data. - pub data: &'a [u8], -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] -pub struct BlockType { - pub params: Vec, - pub results: Vec, -} - -impl BlockType { - pub fn from_wasm( - block_ty: &wasmparser::BlockType, - mod_types: &ModuleTypes, - diagnostics: &DiagnosticsHandler, - ) -> WasmResult { - Ok(match block_ty { - wasmparser::BlockType::Empty => Self::default(), - wasmparser::BlockType::Type(ty) => Self { - params: vec![], - results: vec![ir_type(convert_valtype(*ty), diagnostics)?], - }, - wasmparser::BlockType::FuncType(ty_index) => { - let func_type = &mod_types[SignatureIndex::from_u32(*ty_index)]; - let params = func_type - .params() - .iter() - .map(|t| ir_type(*t, diagnostics)) - .collect::>>()?; - let results = func_type - .returns() - .iter() - .map(|t| ir_type(*t, diagnostics)) - .collect::>>()?; - Self { params, results } - } - }) - } -} - -/// Note that accesing this type is primarily done through the `Index` -/// implementations for this type. -#[derive(Default)] -pub struct ModuleTypes { - wasm_signatures: PrimaryMap, -} - -impl ModuleTypes { - /// Returns an iterator over all the wasm function signatures found within - /// this module. - pub fn wasm_signatures(&self) -> impl Iterator { - self.wasm_signatures.iter() - } -} - -impl Index for ModuleTypes { - type Output = WasmFuncType; - - fn index(&self, sig: SignatureIndex) -> &WasmFuncType { - &self.wasm_signatures[sig] - } -} - -/// A builder for [`ModuleTypes`]. -#[derive(Default)] -pub struct ModuleTypesBuilder { - types: ModuleTypes, - interned_func_types: HashMap, - wasmparser_to_our: HashMap, -} - -impl ModuleTypesBuilder { - /// Reserves space for `amt` more type signatures. - pub fn reserve_wasm_signatures(&mut self, amt: usize) { - self.types.wasm_signatures.reserve(amt); - } - - /// Interns the `sig` specified and returns a unique `SignatureIndex` that - /// can be looked up within [`ModuleTypes`] to recover the [`WasmFuncType`] - /// at runtime. - pub fn wasm_func_type(&mut self, id: CoreTypeId, sig: WasmFuncType) -> SignatureIndex { - let sig = self.intern_func_type(sig); - self.wasmparser_to_our.insert(id, sig); - sig - } - - fn intern_func_type(&mut self, sig: WasmFuncType) -> SignatureIndex { - if let Some(idx) = self.interned_func_types.get(&sig) { - return *idx; - } - - let idx = self.types.wasm_signatures.push(sig.clone()); - self.interned_func_types.insert(sig, idx); - idx - } - - /// Returns the result [`ModuleTypes`] of this builder. - pub fn finish(self) -> ModuleTypes { - self.types - } - - /// Returns an iterator over all the wasm function signatures found within - /// this module. - pub fn wasm_signatures(&self) -> impl Iterator { - self.types.wasm_signatures() - } -} - -// Forward the indexing impl to the internal `ModuleTypes` -impl Index for ModuleTypesBuilder -where - ModuleTypes: Index, -{ - type Output = >::Output; - - fn index(&self, sig: T) -> &Self::Output { - &self.types[sig] - } -} - -/// Converts a Wasm function type into a Miden IR function type -pub fn ir_func_type( - ty: &WasmFuncType, - diagnostics: &DiagnosticsHandler, -) -> WasmResult { - let params = ty - .params() - .iter() - .map(|t| ir_type(*t, diagnostics)) - .collect::>>()?; - let results = ty - .returns() - .iter() - .map(|t| ir_type(*t, diagnostics)) - .collect::>>()?; - Ok(hir::FunctionType { - abi: Abi::Canonical, - results, - params, - }) -} - -/// Converts a Wasm type into a Miden IR type -pub fn ir_type(ty: WasmType, diagnostics: &DiagnosticsHandler) -> WasmResult { - Ok(match ty { - WasmType::I32 => hir::Type::I32, - WasmType::I64 => hir::Type::I64, - WasmType::F32 => hir::Type::Felt, - ty @ (WasmType::F64 | WasmType::V128 | WasmType::Ref(_)) => { - unsupported_diag!(diagnostics, "wasm error: unsupported type '{}'", ty) - } - }) -} - -/// Makes an IR function signature from a Wasm function type -pub fn ir_func_sig( - func_type: &hir::FunctionType, - call_conv: CallConv, - linkage: Linkage, -) -> Signature { - Signature { - params: func_type.params.iter().map(|ty| AbiParam::new(ty.clone())).collect(), - results: func_type.results.iter().map(|ty| AbiParam::new(ty.clone())).collect(), - cc: call_conv, - linkage, - } -} - -/// Converts a wasmparser table type -pub fn convert_global_type(ty: &wasmparser::GlobalType) -> Global { - Global { - ty: convert_valtype(ty.content_type), - mutability: ty.mutable, - } -} - -/// Converts a wasmparser table type -pub fn convert_table_type(ty: &wasmparser::TableType) -> Table { - assert!(!ty.table64, "64-bit tables are not supported"); - - Table { - wasm_ty: convert_ref_type(ty.element_type), - minimum: ty.initial as u32, - maximum: ty.maximum.map(|n| n as u32), - } -} - -/// Converts a wasmparser function type -pub fn convert_func_type(ty: &wasmparser::FuncType) -> WasmFuncType { - let params = ty.params().iter().map(|t| convert_valtype(*t)).collect(); - let results = ty.results().iter().map(|t| convert_valtype(*t)).collect(); - WasmFuncType::new(params, results) -} - -/// Converts a wasmparser value type -pub fn convert_valtype(ty: wasmparser::ValType) -> WasmType { - match ty { - wasmparser::ValType::I32 => WasmType::I32, - wasmparser::ValType::I64 => WasmType::I64, - wasmparser::ValType::F32 => WasmType::F32, - wasmparser::ValType::F64 => WasmType::F64, - wasmparser::ValType::V128 => WasmType::V128, - wasmparser::ValType::Ref(t) => WasmType::Ref(convert_ref_type(t)), - } -} - -/// Converts a wasmparser reference type -pub fn convert_ref_type(ty: wasmparser::RefType) -> WasmRefType { - WasmRefType { - nullable: ty.is_nullable(), - heap_type: convert_heap_type(ty.heap_type()), - } -} - -/// Converts a wasmparser heap type -pub fn convert_heap_type(ty: wasmparser::HeapType) -> WasmHeapType { - use wasmparser::AbstractHeapType; - match ty { - wasmparser::HeapType::Abstract { ty, shared: _ } => match ty { - AbstractHeapType::Func => WasmHeapType::Func, - AbstractHeapType::Extern => WasmHeapType::Extern, - ty => unimplemented!("unsupported abstract heap type {ty:?}"), - }, - wasmparser::HeapType::Concrete(_) => { - unimplemented!("user-defined types are not supported yet") - } - } -} diff --git a/frontend-wasm/src/ssa.rs b/frontend-wasm/src/ssa.rs deleted file mode 100644 index 7c1a27a76..000000000 --- a/frontend-wasm/src/ssa.rs +++ /dev/null @@ -1,506 +0,0 @@ -//! A SSA-building API that handles incomplete CFGs. -//! -//! The algorithm is based upon Braun M., Buchwald S., Hack S., Leißa R., Mallon C., -//! Zwinkau A. (2013) Simple and Efficient Construction of Static Single Assignment Form. -//! In: Jhala R., De Bosschere K. (eds) Compiler Construction. CC 2013. -//! Lecture Notes in Computer Science, vol 7791. Springer, Berlin, Heidelberg -//! -//! -//! -//! Based on Cranelift's Wasm -> CLIF translator v11.0.0 - -use core::mem; - -use midenc_hir::{ - cranelift_entity::{ - entity_impl, packed_option::PackedOption, EntityList, EntitySet, ListPool, SecondaryMap, - }, - diagnostics::SourceSpan, - Block, DataFlowGraph, Inst, Value, -}; -use midenc_hir_type::Type; - -/// Structure containing the data relevant the construction of SSA for a given function. -/// -/// The parameter struct `Variable` corresponds to the way variables are represented in the -/// non-SSA language you're translating from. -/// -/// The SSA building relies on information about the variables used and defined. -/// -/// This SSA building module allows you to def and use variables on the fly while you are -/// constructing the CFG, no need for a separate SSA pass after the CFG is completed. -/// -/// A basic block is said _filled_ if all the instruction that it contains have been translated, -/// and it is said _sealed_ if all of its predecessors have been declared. Only filled predecessors -/// can be declared. -#[derive(Default)] -pub struct SSABuilder { - /// Records for every variable and for every relevant block, the last definition of - /// the variable in the block. - variables: SecondaryMap>>, - - /// Records the position of the basic blocks and the list of values used but not defined in the - /// block. - ssa_blocks: SecondaryMap, - - /// Call stack for use in the `use_var`/`predecessors_lookup` state machine. - calls: Vec, - /// Result stack for use in the `use_var`/`predecessors_lookup` state machine. - results: Vec, - - /// Side effects accumulated in the `use_var`/`predecessors_lookup` state machine. - side_effects: SideEffects, - - /// Reused storage for cycle-detection. - visited: EntitySet, - - /// Storage for pending variable definitions. - variable_pool: ListPool, - - /// Storage for predecessor definitions. - inst_pool: ListPool, -} - -/// An opaque reference to a mutable variable. -#[derive(Copy, Clone, PartialEq, Eq)] -pub struct Variable(u32); -entity_impl!(Variable, "var"); - -/// Side effects of a `use_var` or a `seal_block` method call. -#[derive(Default)] -pub struct SideEffects { - /// When a variable is used but has never been defined before (this happens in the case of - /// unreachable code), a placeholder `iconst` or `fconst` value is added to the right `Block`. - /// This field signals if it is the case and return the `Block` to which the initialization has - /// been added. - pub instructions_added_to_blocks: Vec, -} - -impl SideEffects { - fn is_empty(&self) -> bool { - self.instructions_added_to_blocks.is_empty() - } -} - -#[derive(Clone)] -enum Sealed { - No { - // List of current Block arguments for which an earlier def has not been found yet. - undef_variables: EntityList, - }, - Yes, -} - -impl Default for Sealed { - fn default() -> Self { - Sealed::No { - undef_variables: EntityList::new(), - } - } -} - -#[derive(Clone, Default)] -struct SSABlockData { - // The predecessors of the Block with the block and branch instruction. - predecessors: EntityList, - // A block is sealed if all of its predecessors have been declared. - sealed: Sealed, - // If this block is sealed and it has exactly one predecessor, this is that predecessor. - single_predecessor: PackedOption, -} - -impl SSABuilder { - /// Clears a `SSABuilder` from all its data, letting it in a pristine state without - /// deallocating memory. - pub fn clear(&mut self) { - self.variables.clear(); - self.ssa_blocks.clear(); - self.variable_pool.clear(); - self.inst_pool.clear(); - debug_assert!(self.calls.is_empty()); - debug_assert!(self.results.is_empty()); - debug_assert!(self.side_effects.is_empty()); - } - - /// Tests whether an `SSABuilder` is in a cleared state. - pub fn is_empty(&self) -> bool { - self.variables.is_empty() - && self.ssa_blocks.is_empty() - && self.calls.is_empty() - && self.results.is_empty() - && self.side_effects.is_empty() - } -} - -/// States for the `use_var`/`predecessors_lookup` state machine. -enum Call { - UseVar(Inst), - FinishPredecessorsLookup(Value, Block), -} - -/// The following methods are the API of the SSA builder. Here is how it should be used when -/// translating to Miden IR: -/// -/// - for each basic block, create a corresponding data for SSA construction with `declare_block`; -/// -/// - while traversing a basic block and translating instruction, use `def_var` and `use_var` to -/// record definitions and uses of variables, these methods will give you the corresponding SSA -/// values; -/// -/// - when all the instructions in a basic block have translated, the block is said _filled_ and -/// only then you can add it as a predecessor to other blocks with `declare_block_predecessor`; -/// -/// - when you have constructed all the predecessor to a basic block, call `seal_block` on it with -/// the `Function` that you are building. -/// -/// This API will give you the correct SSA values to use as arguments of your instructions, -/// as well as modify the jump instruction and `Block` parameters to account for the SSA -/// Phi functions. -impl SSABuilder { - /// Declares a new definition of a variable in a given basic block. - pub fn def_var(&mut self, var: Variable, val: Value, block: Block) { - self.variables[var][block] = PackedOption::from(val); - } - - /// Declares a use of a variable in a given basic block. Returns the SSA value corresponding - /// to the current SSA definition of this variable and a list of newly created Blocks - /// - /// If the variable has never been defined in this blocks or recursively in its predecessors, - /// this method will silently create an initializer. You are - /// responsible for making sure that you initialize your variables. - pub fn use_var( - &mut self, - dfg: &mut DataFlowGraph, - var: Variable, - ty: Type, - block: Block, - ) -> (Value, SideEffects) { - debug_assert!(self.calls.is_empty()); - debug_assert!(self.results.is_empty()); - debug_assert!(self.side_effects.is_empty()); - - // Prepare the 'calls' and 'results' stacks for the state machine. - self.use_var_nonlocal(dfg, var, ty.clone(), block); - let value = self.run_state_machine(dfg, var, ty); - - let side_effects = mem::take(&mut self.side_effects); - (value, side_effects) - } - - /// Resolve the minimal SSA Value of `var` in `block` by traversing predecessors. - /// - /// This function sets up state for `run_state_machine()` but does not execute it. - fn use_var_nonlocal( - &mut self, - dfg: &mut DataFlowGraph, - var: Variable, - ty: Type, - mut block: Block, - ) { - // First, try Local Value Numbering (Algorithm 1 in the paper). - // If the variable already has a known Value in this block, use that. - if let Some(val) = self.variables[var][block].expand() { - self.results.push(val); - return; - } - - // Otherwise, use Global Value Numbering (Algorithm 2 in the paper). - // This resolves the Value with respect to its predecessors. - // Find the most recent definition of `var`, and the block the definition comes from. - let (val, from) = self.find_var(dfg, var, ty, block); - - // The `from` block returned from `find_var` is guaranteed to be on the path we follow by - // traversing only single-predecessor edges. It might be equal to `block` if there is no - // such path, but in that case `find_var` ensures that the variable is defined in this block - // by a new block parameter. It also might be somewhere in a cycle, but even then this loop - // will terminate the first time it encounters that block, rather than continuing around the - // cycle forever. - // - // Why is it okay to copy the definition to all intervening blocks? For the initial block, - // this may not be the final definition of this variable within this block, but if we've - // gotten here then we know there is no earlier definition in the block already. - // - // For the remaining blocks: Recall that a block is only allowed to be set as a predecessor - // after all its instructions have already been filled in, so when we follow a predecessor - // edge to a block, we know there will never be any more local variable definitions added to - // that block. We also know that `find_var` didn't find a definition for this variable in - // any of the blocks before `from`. - // - // So in either case there is no definition in these blocks yet and we can blindly set one. - let var_defs = &mut self.variables[var]; - while block != from { - debug_assert!(var_defs[block].is_none()); - var_defs[block] = PackedOption::from(val); - block = self.ssa_blocks[block].single_predecessor.unwrap(); - } - } - - /// Find the most recent definition of this variable, returning both the definition and the - /// block in which it was found. If we can't find a definition that's provably the right one for - /// all paths to the current block, then append a block parameter to some block and use that as - /// the definition. Either way, also arrange that the definition will be on the `results` stack - /// when `run_state_machine` is done processing the current step. - /// - /// If a block has exactly one predecessor, and the block is sealed so we know its predecessors - /// will never change, then its definition for this variable is the same as the definition from - /// that one predecessor. In this case it's easy to see that no block parameter is necessary, - /// but we need to look at the predecessor to see if a block parameter might be needed there. - /// That holds transitively across any chain of sealed blocks with exactly one predecessor each. - /// - /// This runs into a problem, though, if such a chain has a cycle: Blindly following a cyclic - /// chain that never defines this variable would lead to an infinite loop in the compiler. It - /// doesn't really matter what code we generate in that case. Since each block in the cycle has - /// exactly one predecessor, there's no way to enter the cycle from the function's entry block; - /// and since all blocks in the cycle are sealed, the entire cycle is permanently dead code. But - /// we still have to prevent the possibility of an infinite loop. - /// - /// To break cycles, we can pick any block within the cycle as the one where we'll add a block - /// parameter. It's convenient to pick the block at which we entered the cycle, because that's - /// the first place where we can detect that we just followed a cycle. Adding a block parameter - /// gives us a definition we can reuse throughout the rest of the cycle. - fn find_var( - &mut self, - dfg: &mut DataFlowGraph, - var: Variable, - ty: Type, - mut block: Block, - ) -> (Value, Block) { - // Try to find an existing definition along single-predecessor edges first. - self.visited.clear(); - let var_defs = &mut self.variables[var]; - while let Some(pred) = self.ssa_blocks[block].single_predecessor.expand() { - if !self.visited.insert(block) { - break; - } - block = pred; - if let Some(val) = var_defs[block].expand() { - self.results.push(val); - return (val, block); - } - } - - // We've promised to return the most recent block where `var` was defined, but we didn't - // find a usable definition. So create one. - let val = dfg.append_block_param(block, ty, SourceSpan::default()); - var_defs[block] = PackedOption::from(val); - - // Now every predecessor needs to pass its definition of this variable to the newly added - // block parameter. To do that we have to "recursively" call `use_var`, but there are two - // problems with doing that. First, we need to keep a fixed bound on stack depth, so we - // can't actually recurse; instead we defer to `run_state_machine`. Second, if we don't - // know all our predecessors yet, we have to defer this work until the block gets sealed. - match &mut self.ssa_blocks[block].sealed { - // Once all the `calls` added here complete, this leaves either `val` or an equivalent - // definition on the `results` stack. - Sealed::Yes => self.begin_predecessors_lookup(val, block), - Sealed::No { undef_variables } => { - undef_variables.push(var, &mut self.variable_pool); - self.results.push(val); - } - } - (val, block) - } - - /// Declares a new basic block to construct corresponding data for SSA construction. - /// No predecessors are declared here and the block is not sealed. - /// Predecessors have to be added with `declare_block_predecessor`. - pub fn declare_block(&mut self, block: Block) { - // Ensure the block exists so seal_one_block will see it even if no predecessors or - // variables get declared for this block. But don't assign anything to it: - // SecondaryMap automatically sets all blocks to `default()`. - let _ = &mut self.ssa_blocks[block]; - } - - /// Declares a new predecessor for a `Block` and record the branch instruction - /// of the predecessor that leads to it. - /// - /// The precedent `Block` must be filled before added as predecessor. - /// Note that you must provide no jump arguments to the branch - /// instruction when you create it since `SSABuilder` will fill them for you. - /// - /// Callers are expected to avoid adding the same predecessor more than once in the case - /// of a jump table. - pub fn declare_block_predecessor(&mut self, block: Block, inst: Inst) { - debug_assert!(!self.is_sealed(block), "you cannot add a predecessor to a sealed block"); - debug_assert!( - self.ssa_blocks[block] - .predecessors - .as_slice(&self.inst_pool) - .iter() - .all(|&branch| branch != inst), - "you have declared the same predecessor twice!" - ); - self.ssa_blocks[block].predecessors.push(inst, &mut self.inst_pool); - } - - /// Remove a previously declared Block predecessor by giving a reference to the jump - /// instruction. Returns the basic block containing the instruction. - /// - /// Note: use only when you know what you are doing, this might break the SSA building problem - pub fn remove_block_predecessor(&mut self, block: Block, inst: Inst) { - debug_assert!(!self.is_sealed(block)); - let data = &mut self.ssa_blocks[block]; - let pred = data - .predecessors - .as_slice(&self.inst_pool) - .iter() - .position(|&branch| branch == inst) - .expect("the predecessor you are trying to remove is not declared"); - data.predecessors.swap_remove(pred, &mut self.inst_pool); - } - - /// Completes the global value numbering for a `Block`, all of its predecessors having been - /// already sealed. - /// - /// This method modifies the function's `Layout` by adding arguments to the `Block`s to - /// take into account the Phi function placed by the SSA algorithm. - /// - /// Returns the list of newly created blocks for critical edge splitting. - pub fn seal_block(&mut self, block: Block, dfg: &mut DataFlowGraph) -> SideEffects { - debug_assert!( - !self.is_sealed(block), - "Attempting to seal {} which is already sealed.", - block - ); - self.seal_one_block(block, dfg); - mem::take(&mut self.side_effects) - } - - /// Helper function for `seal_block` - fn seal_one_block(&mut self, block: Block, dfg: &mut DataFlowGraph) { - // For each undef var we look up values in the predecessors and create a block parameter - // only if necessary. - let mut undef_variables = - match mem::replace(&mut self.ssa_blocks[block].sealed, Sealed::Yes) { - Sealed::No { undef_variables } => undef_variables, - Sealed::Yes => return, - }; - let ssa_params = undef_variables.len(&self.variable_pool); - - let predecessors = self.predecessors(block); - if predecessors.len() == 1 { - let pred = dfg.insts[predecessors[0]].block; - self.ssa_blocks[block].single_predecessor = PackedOption::from(pred); - } - - // Note that begin_predecessors_lookup requires visiting these variables in the same order - // that they were defined by find_var, because it appends arguments to the jump instructions - // in all the predecessor blocks one variable at a time. - for idx in 0..ssa_params { - let var = undef_variables.get(idx, &self.variable_pool).unwrap(); - - // We need the temporary Value that was assigned to this Variable. If that Value shows - // up as a result from any of our predecessors, then it never got assigned on the loop - // through that block. We get the value from the next block param, where it was first - // allocated in find_var. - let block_params = dfg.block_params(block); - - // On each iteration through this loop, there are (ssa_params - idx) undefined variables - // left to process. Previous iterations through the loop may have removed earlier block - // parameters, but the last (ssa_params - idx) block parameters always correspond to the - // remaining undefined variables. So index from the end of the current block params. - let val = block_params[block_params.len() - (ssa_params - idx)]; - - debug_assert!(self.calls.is_empty()); - debug_assert!(self.results.is_empty()); - // self.side_effects may be non-empty here so that callers can - // accumulate side effects over multiple calls. - self.begin_predecessors_lookup(val, block); - self.run_state_machine(dfg, var, dfg.value_type(val).clone()); - } - - undef_variables.clear(&mut self.variable_pool); - } - - /// Given the local SSA Value of a Variable in a Block, perform a recursive lookup on - /// predecessors to determine if it is redundant with another Value earlier in the CFG. - /// - /// If such a Value exists and is redundant, the local Value is replaced by the - /// corresponding non-local Value. If the original Value was a Block parameter, - /// the parameter may be removed if redundant. Parameters are placed eagerly by callers - /// to avoid infinite loops when looking up a Value for a Block that is in a CFG loop. - /// - /// Doing this lookup for each Value in each Block preserves SSA form during construction. - /// - /// ## Arguments - /// - /// `sentinel` is a dummy Block parameter inserted by `use_var_nonlocal()`. - /// Its purpose is to allow detection of CFG cycles while traversing predecessors. - fn begin_predecessors_lookup(&mut self, sentinel: Value, dest_block: Block) { - self.calls.push(Call::FinishPredecessorsLookup(sentinel, dest_block)); - // Iterate over the predecessors. - self.calls.extend( - self.ssa_blocks[dest_block] - .predecessors - .as_slice(&self.inst_pool) - .iter() - .rev() - .copied() - .map(Call::UseVar), - ); - } - - /// Examine the values from the predecessors and compute a result value, creating - /// block parameters as needed. - fn finish_predecessors_lookup( - &mut self, - dfg: &mut DataFlowGraph, - sentinel: Value, - dest_block: Block, - ) -> Value { - // Determine how many predecessors are yielding unique, non-temporary Values. - let num_predecessors = self.predecessors(dest_block).len(); - // When this `Drain` is dropped, these elements will get truncated. - let results = self.results.drain(self.results.len() - num_predecessors..); - // Keep the block argument. - let mut preds = self.ssa_blocks[dest_block].predecessors; - for (idx, &val) in results.as_slice().iter().enumerate() { - let pred = preds.get_mut(idx, &mut self.inst_pool).unwrap(); - let branch = *pred; - assert!( - dfg.insts[branch].opcode().is_branch(), - "you have declared a non-branch instruction as a predecessor to a block!" - ); - dfg.append_branch_destination_argument(branch, dest_block, val); - } - sentinel - } - - /// Returns the list of `Block`s that have been declared as predecessors of the argument. - fn predecessors(&self, block: Block) -> &[Inst] { - self.ssa_blocks[block].predecessors.as_slice(&self.inst_pool) - } - - /// Returns whether the given Block has any predecessor or not. - pub fn has_any_predecessors(&self, block: Block) -> bool { - !self.predecessors(block).is_empty() - } - - /// Returns `true` if and only if `seal_block` has been called on the argument. - pub fn is_sealed(&self, block: Block) -> bool { - matches!(self.ssa_blocks[block].sealed, Sealed::Yes) - } - - /// The main algorithm is naturally recursive: when there's a `use_var` in a - /// block with no corresponding local defs, it recurses and performs a - /// `use_var` in each predecessor. To avoid risking running out of callstack - /// space, we keep an explicit stack and use a small state machine rather - /// than literal recursion. - fn run_state_machine(&mut self, func: &mut DataFlowGraph, var: Variable, ty: Type) -> Value { - // Process the calls scheduled in `self.calls` until it is empty. - while let Some(call) = self.calls.pop() { - match call { - Call::UseVar(branch) => { - let block = func.insts[branch].block; - self.use_var_nonlocal(func, var, ty.clone(), block); - } - Call::FinishPredecessorsLookup(sentinel, dest_block) => { - let val = self.finish_predecessors_lookup(func, sentinel, dest_block); - self.results.push(val); - } - } - } - debug_assert_eq!(self.results.len(), 1); - self.results.pop().unwrap() - } -} diff --git a/frontend-wasm/src/test_utils.rs b/frontend-wasm/src/test_utils.rs deleted file mode 100644 index c08915429..000000000 --- a/frontend-wasm/src/test_utils.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::sync::Arc; - -use midenc_hir::{diagnostics::NullEmitter, testing::TestContext}; -use midenc_session::{ColorChoice, Options}; - -pub fn test_context() -> TestContext { - let options = Options::default().with_verbosity(midenc_session::Verbosity::Debug); - let emitter = Arc::new(NullEmitter::new(ColorChoice::Auto)); - TestContext::default_with_opts_and_emitter(options, Some(emitter)) -} diff --git a/frontend-wasm/src/translation_utils.rs b/frontend-wasm/src/translation_utils.rs deleted file mode 100644 index 067ee278d..000000000 --- a/frontend-wasm/src/translation_utils.rs +++ /dev/null @@ -1,152 +0,0 @@ -//! Helper functions and structures for the translation. - -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, Severity, SourceSpan}, - AbiParam, CallConv, Felt, FieldElement, InstBuilder, Linkage, Signature, Value, -}; -use midenc_hir_type::{FunctionType, Type}; -use rustc_hash::FxHasher; - -use crate::{ - error::WasmResult, module::function_builder_ext::FunctionBuilderExt, unsupported_diag, -}; - -pub type BuildFxHasher = std::hash::BuildHasherDefault; - -/// Represents the possible sizes in bytes of the discriminant of a variant type in the component -/// model -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum DiscriminantSize { - /// 8-bit discriminant - Size1, - /// 16-bit discriminant - Size2, - /// 32-bit discriminant - Size4, -} - -impl DiscriminantSize { - /// Calculate the size of discriminant needed to represent a variant with the specified number - /// of cases. - pub const fn from_count(count: usize) -> Option { - if count <= 0xff { - Some(Self::Size1) - } else if count <= 0xffff { - Some(Self::Size2) - } else if count <= 0xffff_ffff { - Some(Self::Size4) - } else { - None - } - } - - /// Returns the size, in bytes, of this discriminant - pub const fn byte_size(&self) -> u32 { - match self { - DiscriminantSize::Size1 => 1, - DiscriminantSize::Size2 => 2, - DiscriminantSize::Size4 => 4, - } - } -} - -impl From for u32 { - /// Size of the discriminant as a `u32` - fn from(size: DiscriminantSize) -> u32 { - size.byte_size() - } -} - -impl From for usize { - /// Size of the discriminant as a `usize` - fn from(size: DiscriminantSize) -> usize { - match size { - DiscriminantSize::Size1 => 1, - DiscriminantSize::Size2 => 2, - DiscriminantSize::Size4 => 4, - } - } -} - -/// Represents the number of bytes required to store a flags value in the component model -pub enum FlagsSize { - /// There are no flags - Size0, - /// Flags can fit in a u8 - Size1, - /// Flags can fit in a u16 - Size2, - /// Flags can fit in a specified number of u32 fields - Size4Plus(u8), -} - -impl FlagsSize { - /// Calculate the size needed to represent a value with the specified number of flags. - pub const fn from_count(count: usize) -> FlagsSize { - if count == 0 { - FlagsSize::Size0 - } else if count <= 8 { - FlagsSize::Size1 - } else if count <= 16 { - FlagsSize::Size2 - } else { - let amt = ceiling_divide(count, 32); - if amt > (u8::MAX as usize) { - panic!("too many flags"); - } - FlagsSize::Size4Plus(amt as u8) - } - } -} - -/// Divide `n` by `d`, rounding up in the case of a non-zero remainder. -const fn ceiling_divide(n: usize, d: usize) -> usize { - (n + d - 1) / d -} - -/// Emit instructions to produce a zero value in the given type. -pub fn emit_zero( - ty: &Type, - builder: &mut FunctionBuilderExt, - diagnostics: &DiagnosticsHandler, -) -> WasmResult { - Ok(match ty { - Type::I1 => builder.ins().i1(false, SourceSpan::default()), - Type::I8 => builder.ins().i8(0, SourceSpan::default()), - Type::I16 => builder.ins().i16(0, SourceSpan::default()), - Type::I32 => builder.ins().i32(0, SourceSpan::default()), - Type::I64 => builder.ins().i64(0, SourceSpan::default()), - Type::U8 => builder.ins().u8(0, SourceSpan::default()), - Type::U16 => builder.ins().u16(0, SourceSpan::default()), - Type::U32 => builder.ins().u32(0, SourceSpan::default()), - Type::U64 => builder.ins().u64(0, SourceSpan::default()), - Type::F64 => builder.ins().f64(0.0, SourceSpan::default()), - Type::Felt => builder.ins().felt(Felt::ZERO, SourceSpan::default()), - Type::I128 - | Type::U128 - | Type::U256 - | Type::Ptr(_) - | Type::NativePtr(..) - | Type::Struct(_) - | Type::Array(..) - | Type::List(_) - | Type::Unknown - | Type::Unit - | Type::Never => { - unsupported_diag!(diagnostics, "cannot emit zero value for type: {:?}", ty); - } - }) -} - -pub fn sig_from_func_type( - func_type: &FunctionType, - call_conv: CallConv, - linkage: Linkage, -) -> Signature { - Signature { - params: func_type.params.iter().map(|ty| AbiParam::new(ty.clone())).collect(), - results: func_type.results.iter().map(|ty| AbiParam::new(ty.clone())).collect(), - cc: call_conv, - linkage, - } -} diff --git a/frontend/wasm/CHANGELOG.md b/frontend/wasm/CHANGELOG.md new file mode 100644 index 000000000..970e7a275 --- /dev/null +++ b/frontend/wasm/CHANGELOG.md @@ -0,0 +1,312 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.4.0...midenc-frontend-wasm-v0.4.1) - 2025-09-03 + +### Other + +- Address PR comments with some extra comments. +- Add 128-bit wide arithmetic support to the compiler. + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.1.5...midenc-frontend-wasm-v0.4.0) - 2025-08-15 + +### Added + +- add missing and fix existing tx kernel function bindings +- implement advice map API in Miden SDK +- expose Miden stdlib `hash_elements` procedure in the Miden SDK +- add `crypto::hmerge()` in Miden SDK (`hmerge` VM intruction); + +### Fixed + +- `tx:add_asset_to_note` Miden SDK bindings +- #631 Aligned data segments overlap the `BumpAlloc` instance +- update Miden SDK `AccountId` type and `account::get_id()` for two + +### Other + +- use advice map API in the basic wallet tx script +- add custom `Debug` implementation for `ResolvedDataSegment` +- rename `io` to `advice`, export modules in stdlib SDK +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) +- `hmerge` intrinsic to accept digests as a pointer and load +- rename for readability, add comments + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.1.0...midenc-frontend-wasm-v0.1.5) - 2025-07-01 + +### Added + +- small integers in `struct` support in result in cross-context calls +- *(frontend)* add the arbitrary shape `struct` support in +- implement Wasm CM indirect lowering shim+fixup module bypass +- add Miden SDK `note::get_assets` Rust bindings + +### Fixed + +- `ret` returns the exit block args +- `ResolvedDataSegment::padding_needed()` to calculate an adjustment DOWN (not UP) +- *(frontend)* ensure data segments are word-aligned for Miden VM #551 +- wasm import module names to be in sync with WIT files (Miden SDK) +- consider function parameters size in felts (>16) when deciding if + +### Other + +- clean up debugging asserts +- add type signature conversion tests for Wasm CM type flattening +- use `DisplayValues` to avoid materiazing the `Vec` in the frontend +- clean up commented out `dbg!` in the frontend +- use `DisplayValues` to avoid materializing the `Vec` +- add `target` in all `log` messages in the frontend +- use `ValueRange` instead of `Vec` +- add more info to the assertions +- use `assert!` instead of `if` +- use `[0]` to get the first element +- remove the `offset` in `load/store` in CanonABI +- lift/lower `call` +- use `SmallVec` for ABI transformation results +- avoid extra allocation +- use `SmallVec` for data segments +- remove `expect-test` in favor of `midenc-expect-test` + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.0.7...midenc-frontend-wasm-v0.0.8) - 2025-04-24 + +### Added +- *(types)* clean up hir-type for use outside the compiler + +### Fixed +- *(codegen)* incomplete global/data segment lowering + +### Other +- update Miden VM to v0.13.2 and uncomment the Miden package +- update expect tests with type printer changes +- update expect tests +- *(frontend)* rework handling of symbols in frontend +- Move the new Wasm frontend to `frontend/wasm` and remove the old + +## [0.0.7](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.0.6...midenc-frontend-wasm-v0.0.7) - 2024-09-17 + +### Other +- *(rustfmt)* disable wrap_comments due to broken behavior + +## [0.0.6](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.0.5...midenc-frontend-wasm-v0.0.6) - 2024-09-06 + +### Other +- switch all crates to a single workspace version (0.0.5) + +## [0.0.2](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.0.1...midenc-frontend-wasm-v0.0.2) - 2024-08-30 + +### Fixed +- *(codegen)* broken return via pointer transformation +- *(frontend-wasm)* do not apply redundant casts +- *(frontend-wasm)* incorrect types applied to certain primops + +### Other +- Merge pull request [#284](https://github.com/0xMiden/compiler/pull/284) from 0xMiden/bitwalker/abi-transform-test-fixes +- update expect tests due to codegen changes + +## [0.0.1](https://github.com/0xMiden/compiler/compare/midenc-frontend-wasm-v0.0.0...midenc-frontend-wasm-v0.0.1) - 2024-07-18 + +### Added +- implement support for wasm typed select +- implement memset, memcpy, mem_grow, mem_size, and bitcast ops +- add workaround for wasm `memory.grow` op translation +- introduce marker attribute for ABI in text representation of +- cut the Miden function digest from the Miden SDK function names +- introduce `import_miden` component import directive to represent +- employ module names in Wasm module imports in Rust bindings for +- add NoTransform strategy for Miden ABI call transformation +- introduce TransformStrategy and add the "return-via-pointer" +- draft the Miden ABI adaptor generation in Wasm frontend +- parse function digest from the Wasm module import +- draft Miden ABI function types encoding and retrieval +- introduce Miden ABI component import +- lay out the Rust Miden SDK structure, the first integration test +- introduce `CanonicalOptions` in IR and translate Wasm +- `Component::modules` are topologically sorted +- introduce module instantiation arguments +- translate Wasm component instance exports; +- Initial Wasm component translation. +- translate Wasm memory.copy op +- Wasm module data segment transtation +- parse Wasm components +- implement compiler driver, update midenc +- translate Wasm `memory.grow` op +- declare data segments in module, remove ptr type casting for `global_set`, +- Wasm data section parsing +- Wasm globals (`global.get`,`set`) translation +- Wasm `i32.wrap_i64` translation +- cast arguments for unsigned Wasm ops, implement signed Wasm ops translation +- Wasm br_table translation +- Wasm `unreachable` op translation +- Wasm `select` op translation +- Wasm `eqz`, `eq`, `ne`, `lt`, `gt`, `le`, `ge` integer and f64 operators +- Wasm integer `lt_u`, `le_u`, `ge_u` and `gt_u` operators translation +- Wasm integer `mul`, `div_u`, `rem_u` and f64 `mul`, `div`, `min`, `max` +- Wasm `f64.add, sub` and integer `sub` translation +- Wasm `shr_u`, `rotl`, `rotr` i32 and i64 instructions translation +- i32 and i64 variants of `shl` and `xor` Wasm ops translation +- Wasm i32.and, i32.or, i64.and, i64.or translation +- add i32.popcnt, i64.extend_i32_s, extend_i32_u Wasm ops translation +- run wasmparser's validator when parsing function bodies +- Wasm memory.grow and memory.size translation +- Wasm memory store/load ops translation +- handle BrTable and CallIndirect usupported Wasm instructions +- add Rust -> Wasm -> Miden IR test pipeline with a simple function call test; +- draft Wasm -> Miden IR translator, handling control flow ops and SSA construction + +### Fixed +- improve codegen quality using more precise casts +- properly handle shift operand for bitwise shift/rotate ops +- felt representation mismatch between rust and miden +- use the MASM module paths for the tx kernel module names +- change the `tx_kernel::get_inputs` low-level function signature +- query `ModuleImportInfo::aliases` with module id alias +- strip .wasm extension from parsed wasm binaries +- fix value type in store op in `return_via_pointer` transformation, +- fix build after cherry-pick into temp branch of off main; +- after rebase, add Wasm CM `record` type conversion, +- find the correct core module function for the IR component import +- tweak wasm frontend and related test infra +- swap panics with errors +- parsing Wasm module `memory` section, error handling in `emit_zero` +- improper handling of inlined blocks in inline-blocks transform +- always create the destination branch argument for the sentinel value +- be more explicit about overflow, address some bugs found while testing +- set reduced load/store mem ops ptr type to unsigned, +- cast pointer in memory access ops and br_table selector to U32 +- handle missing `Instruction::Switch` in jump destination changes +- cast i64 comparison ops result to i32 to preserve Wasm op semantics +- Cast i1 back to i32/i64 expected by Wasm ops after comparison ops +- cast u32/u64 back to Wasm ops expected i32/i64 after `shr_u`, `div_u`, `rem_u` ops +- skip Wasm element section instead of failing +- set `state.reachable = false` for `Operator::Unreachable` +- make `add`, `mul` and `sub` to use wrapping Miden operations +- handle InvalidFunctionError in ModuleEnviromnment::build +- fix build and tests after rebasing on top of bitwalker/wip branch +- pass SourceSpan in translate_operator + +### Other +- fix typos ([#243](https://github.com/0xMiden/compiler/pull/243)) +- extend and update integration tests +- Fix descriptions for crates +- set crates versions to 0.0.0, and `publish = false` for tests +- add missing descriptions to all crates +- rename `miden-prelude` to `miden-stdlib-sys` in SDK +- ensure all relevant crates are prefixed with `midenc-` +- Merge pull request [#187](https://github.com/0xMiden/compiler/pull/187) from 0xMiden/bitwalker/account-compilation-fixes +- check rustfmt on CI, format code with rustfmt +- run clippy on CI, fix all clippy warnings +- use midenc driver for non-cargo-based fixtures in +- use midenc driver to compile cargo-based fixtures +- handle assembler refactoring changes +- Merge pull request [#170](https://github.com/0xMiden/compiler/pull/170) from 0xMiden/greenhat/i159-tx-kernel-func-11apr +- Merge pull request [#155](https://github.com/0xMiden/compiler/pull/155) from 0xMiden/greenhat/i144-stdlib +- remove repetitive words +- Merge pull request [#151](https://github.com/0xMiden/compiler/pull/151) from 0xMiden/greenhat/i144-native-felt +- Merge pull request [#140](https://github.com/0xMiden/compiler/pull/140) from 0xMiden/greenhat/i138-rust-miden-sdk +- remove `dylib` from `crate-type` in Miden SDK crates +- do not inline `miden_sdk_function_type` function +- add `FunctionType::abi` and ditch redundant `*FunctionType` +- remove `miden-abi-conversion` crate and move its code to +- assert Miden ABI function result types after the transformation +- add doc comments to the Miden ABI transformation API +- assert that function call results are the same after transformation +- cache parsed digest and stable import names; +- introduce ModuleTranslationState to hold resolved function +- ditch module and function names for import in favor of +- intern module name and all names used in the module +- remove invocation method from component imports/exports +- clean up todos, add comments +- ensure Wasm module name fallback is set as early as possible +- introduce `ComponentTranslator` +- draft basic wallet translation +- add Wasm component translation support to the integration tests; +- update frontend expect tests with format changes +- add formatter config, format most crates +- update rust toolchain to latest nightly +- Merge pull request [#100](https://github.com/0xMiden/compiler/pull/100) from 0xMiden/greenhat/i89-translate-wasm-cm +- move `LiftedFunctionType` to `miden-hir-type` crate +- use `digest` name for MAST root hashes; +- remove `MastRootHash` in favor of `RpoDigest`; +- use FxHashMap instead of HashMap in frontend-wasm; +- remove handling of Wasm 64-bit memory +- remove `indices` macro definition duplication; +- add README for Wasm frontend +- fix a comment, comment out debug prints; +- move `MastRootHash` and `Interface*` types to their modules; +- add missing doc comments +- remove `BuildIrComponentInput`; +- handle errors on Wasm component translation; +- rename `mod_info` parameter to `module` in Wasm module +- introduce `Module::global_name`, and make `Module::name_section` private; +- code clean up; +- rename `WasmTranslationConfig::module_name_fallback` to `sourse_name`; +- set up mdbook deploy +- add guides for compiling rust->masm +- extract Wasm component section parsing into separate methods +- convert various `Translator` methods to functions +- extract Wasm core module sections parsing to a separate methods +- remove unused variables +- update wasmparser to v0.118.1 +- enable `rust_array` frontend test after block inline pass fixed +- lazy IR compilation in integration tests +- move module specific code to separate module in frontend-wasm +- remove unused dependencies +- Merge pull request [#61](https://github.com/0xMiden/compiler/pull/61) from 0xMiden/greenhat/cargo-ext-i60 +- make `WasmTranslationConfig::module_name_fallback` non-optional +- switch from emiting MASM in CodegenStage, and switch to output folder in cargo extension +- remove `miden_frontend_wasm::translate_program` +- add integration tests for comparison instructions +- fix build after rebase +- implement `gt_u` instruction semantic test, add `translate_program` to Wasm frontend +- compile rust app (cargo project) to masm, run both and compare results; +- temporarily disable dlmalloc test +- finalize pass refactoring, implement driver +- update tests broken due to formatter changes +- initial parser implementation +- demangle function names in Rust compilation tests +- update expected IR for dlmalloc (switch to *_imm ops) +- add a debug assert to not allow declaring a block predecessor twice +- more readable `mem_total_pages` implementation +- use `*_imm` op variant where possible +- code cleanup +- remove `ValueData::Alias` +- update expected IR for dlmalloc test +- use `ModuleFunctionBuilder` instead of `FunctionBuilder` in `FunctionBuilderExt` +- ignore dlmalloc test because hash part in mangled function names is not stable enough +- fix build after rebase +- use `panic_immediate_abort` std feature to avoid core::fmt (uses `call_indirect`) +- Rust code with `div`, `rem`, `shr` signed and unsigned ops +- Rust code using dlmalloc in no_std +- remove float point ops translation; +- make `translation_utils::block_with_params` a `FunctionBuilderExt` method +- move `module_env` to the root of the crate, remove `environ` module +- move module_translator tests to code_translator test, +- `check_ir_files` variant with expected wat and mir files; +- print types on type mismatch error when defining a variable; +- avoid unnecessary allocation by making `DataSegment::data` a reference +- remove caching for Rust -> Wasm compilation artifacts in tests +- skip `@producers` Wasm section when printing wasm in Rust compilation tests; +- remove `FuncEnvironment` and use `ModuleInfo` directly +- fix build after rebase +- fix the test for unsupported ops +- add BrTable(commented) to unsupported instructions test +- remove todo! for Wasm `select` and `unreachable` +- import `Type::*` and remove repetitive `Type::`; +- add test for unsupported Wasm spec v1 instructions +- re-word unsupported Wasm feature error message; +- silence diagnostic output in tests +- add per-instruction test for every implemented Wasm instruction +- move all unsupported Wasm ops to catch-all case +- cleanup rust compilation test; +- make a temp dir for Rust->Wasm compilation tests artifacts +- add Rust->Wasm->IR Fibonacci example +- update expected module name in tests +- better var names, cleanup comments +- provide some initial usage instructions +- Initial commit diff --git a/frontend/wasm/Cargo.toml b/frontend/wasm/Cargo.toml new file mode 100644 index 000000000..271b39d9e --- /dev/null +++ b/frontend/wasm/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "midenc-frontend-wasm" +description = "Wasm frontend for the Miden compiler" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[features] +default = ["std"] +std = ["wasmparser/std", "gimli/std", "midenc-hir-symbol/std"] + +[dependencies] +anyhow.workspace = true +addr2line = "0.24" +cranelift-entity.workspace = true +gimli = { version = "0.31", default-features = false, features = ['read'] } +indexmap = "2.7" +log.workspace = true +miden-core.workspace = true +midenc-hir.workspace = true +midenc-dialect-hir.workspace = true +midenc-dialect-cf.workspace = true +midenc-dialect-ub.workspace = true +midenc-dialect-arith.workspace = true +midenc-hir-symbol.workspace = true +midenc-session.workspace = true +thiserror.workspace = true +wasmparser.workspace = true + +[dev-dependencies] +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging +wat.workspace = true +midenc-expect-test = { path = "../../tools/expect-test" } diff --git a/frontend-wasm/README.md b/frontend/wasm/README.md similarity index 100% rename from frontend-wasm/README.md rename to frontend/wasm/README.md diff --git a/frontend/wasm/src/callable.rs b/frontend/wasm/src/callable.rs new file mode 100644 index 000000000..670133805 --- /dev/null +++ b/frontend/wasm/src/callable.rs @@ -0,0 +1,75 @@ +use midenc_hir::{dialects::builtin::FunctionRef, interner::Symbol, Signature, SymbolPath}; + +use crate::intrinsics::Intrinsic; + +/// Local core Wasm module functon or processed module import to be used for the translation of the +/// Wasm `call` op. +#[derive(Clone)] +pub enum CallableFunction { + /// An intrinsic function for which calls will be lowered to a VM instruction + Instruction { + /// The recovered intrinsic + intrinsic: Intrinsic, + /// Function signature parsed from the core Wasm module + signature: Signature, + }, + /// An intrinsic function implemented in Miden Assembly + Intrinsic { + /// The recovered intrinsic + intrinsic: Intrinsic, + /// Reference to the function declaration or definition + function_ref: FunctionRef, + /// Function signature parsed from the core Wasm module + signature: Signature, + }, + /// All other functions + Function { + /// Module and function name parsed from the core Wasm module + wasm_id: SymbolPath, + /// Reference to the function declaration or definition + function_ref: FunctionRef, + /// Function signature parsed from the core Wasm module + signature: Signature, + }, +} + +impl CallableFunction { + pub fn is_intrinsic(&self) -> bool { + matches!(self, Self::Intrinsic { .. }) + } + + pub fn function_ref(&self) -> Option { + match self { + Self::Instruction { .. } => None, + Self::Intrinsic { function_ref, .. } | Self::Function { function_ref, .. } => { + Some(*function_ref) + } + } + } + + pub fn function_name(&self) -> Symbol { + match self { + Self::Instruction { intrinsic, .. } | Self::Intrinsic { intrinsic, .. } => { + intrinsic.function_name() + } + Self::Function { wasm_id, .. } => wasm_id.name(), + } + } + + pub fn signature(&self) -> &Signature { + match self { + Self::Instruction { signature, .. } + | Self::Intrinsic { signature, .. } + | Self::Function { signature, .. } => signature, + } + } + + pub fn symbol_path(&self) -> SymbolPath { + match self { + Self::Instruction { intrinsic, .. } | Self::Intrinsic { intrinsic, .. } => { + intrinsic.into_symbol_path() + } + Self::Function { wasm_id, .. } => wasm_id.clone(), + } + } +} diff --git a/frontend/wasm/src/code_translator/expected/globals.hir b/frontend/wasm/src/code_translator/expected/globals.hir new file mode 100644 index 000000000..fbc692da0 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/globals.hir @@ -0,0 +1,14 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = builtin.global_symbol @root_ns:root@1.0.0/noname/MyGlobalVal : ptr + v1 = hir.bitcast v0 : ptr; + v2 = hir.load v1 : i32; + v3 = arith.constant 9 : i32; + v4 = arith.add v2, v3 : i32 #[overflow = wrapping]; + v5 = builtin.global_symbol @root_ns:root@1.0.0/noname/MyGlobalVal : ptr + v6 = hir.bitcast v5 : ptr; + hir.store v6, v4; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_add.hir b/frontend/wasm/src/code_translator/expected/i32_add.hir new file mode 100644 index 000000000..7f8312ff5 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_add.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 3 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.add v0, v1 : i32 #[overflow = wrapping]; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_and.hir b/frontend/wasm/src/code_translator/expected/i32_and.hir new file mode 100644 index 000000000..28e28a78b --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_and.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.band v0, v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_clz.hir b/frontend/wasm/src/code_translator/expected/i32_clz.hir new file mode 100644 index 000000000..81507257f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_clz.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i32; + v1 = arith.clz v0 : u32; + v2 = hir.bitcast v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_const.hir b/frontend/wasm/src/code_translator/expected/i32_const.hir new file mode 100644 index 000000000..099033cae --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_const.hir @@ -0,0 +1,7 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_ctz.hir b/frontend/wasm/src/code_translator/expected/i32_ctz.hir new file mode 100644 index 000000000..224b45721 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_ctz.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i32; + v1 = arith.ctz v0 : u32; + v2 = hir.bitcast v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_div_s.hir b/frontend/wasm/src/code_translator/expected/i32_div_s.hir new file mode 100644 index 000000000..c6c031400 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_div_s.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.div v0, v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_div_u.hir b/frontend/wasm/src/code_translator/expected/i32_div_u.hir new file mode 100644 index 000000000..512f5c3ed --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_div_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v4 = arith.div v2, v3 : u32; + v5 = hir.bitcast v4 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_eq.hir b/frontend/wasm/src/code_translator/expected/i32_eq.hir new file mode 100644 index 000000000..9ca28f713 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_eq.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.eq v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_eqz.hir b/frontend/wasm/src/code_translator/expected/i32_eqz.hir new file mode 100644 index 000000000..04b65ebc0 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_eqz.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 0 : i32; + v2 = arith.eq v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_ge_s.hir b/frontend/wasm/src/code_translator/expected/i32_ge_s.hir new file mode 100644 index 000000000..8297642cc --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_ge_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.gte v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_ge_u.hir b/frontend/wasm/src/code_translator/expected/i32_ge_u.hir new file mode 100644 index 000000000..047373260 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_ge_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v4 = arith.gte v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_gt_s.hir b/frontend/wasm/src/code_translator/expected/i32_gt_s.hir new file mode 100644 index 000000000..4aa9ad949 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_gt_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.gt v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_gt_u.hir b/frontend/wasm/src/code_translator/expected/i32_gt_u.hir new file mode 100644 index 000000000..9e6708296 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_gt_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v4 = arith.gt v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_le_s.hir b/frontend/wasm/src/code_translator/expected/i32_le_s.hir new file mode 100644 index 000000000..f313804bb --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_le_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.lte v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_le_u.hir b/frontend/wasm/src/code_translator/expected/i32_le_u.hir new file mode 100644 index 000000000..d97041e92 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_le_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v4 = arith.lte v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_load.hir b/frontend/wasm/src/code_translator/expected/i32_load.hir new file mode 100644 index 000000000..32b8ab640 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_load.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 4 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_load16_s.hir b/frontend/wasm/src/code_translator/expected/i32_load16_s.hir new file mode 100644 index 000000000..da4ffaf8d --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_load16_s.hir @@ -0,0 +1,14 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 2 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : i16; + v6 = arith.sext v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_load16_u.hir b/frontend/wasm/src/code_translator/expected/i32_load16_u.hir new file mode 100644 index 000000000..6e8c02e91 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_load16_u.hir @@ -0,0 +1,15 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 2 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : u16; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_load8_s.hir b/frontend/wasm/src/code_translator/expected/i32_load8_s.hir new file mode 100644 index 000000000..c8d0f09d4 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_load8_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = hir.int_to_ptr v1 : ptr; + v3 = hir.load v2 : i8; + v4 = arith.sext v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_load8_u.hir b/frontend/wasm/src/code_translator/expected/i32_load8_u.hir new file mode 100644 index 000000000..5237740ff --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_load8_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = hir.int_to_ptr v1 : ptr; + v3 = hir.load v2 : u8; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_lt_s.hir b/frontend/wasm/src/code_translator/expected/i32_lt_s.hir new file mode 100644 index 000000000..b4a29f4fc --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_lt_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.lt v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_lt_u.hir b/frontend/wasm/src/code_translator/expected/i32_lt_u.hir new file mode 100644 index 000000000..382d1ff6c --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_lt_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v4 = arith.lt v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_mul.hir b/frontend/wasm/src/code_translator/expected/i32_mul.hir new file mode 100644 index 000000000..8c5070f7d --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_mul.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.mul v0, v1 : i32 #[overflow = wrapping]; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_ne.hir b/frontend/wasm/src/code_translator/expected/i32_ne.hir new file mode 100644 index 000000000..345fb8147 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_ne.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.neq v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_or.hir b/frontend/wasm/src/code_translator/expected/i32_or.hir new file mode 100644 index 000000000..414bf49b1 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_or.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.bor v0, v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_popcnt.hir b/frontend/wasm/src/code_translator/expected/i32_popcnt.hir new file mode 100644 index 000000000..58a4f390c --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_popcnt.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i32; + v1 = arith.popcnt v0 : u32; + v2 = hir.bitcast v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_rem_s.hir b/frontend/wasm/src/code_translator/expected/i32_rem_s.hir new file mode 100644 index 000000000..ee86cb67a --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_rem_s.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.mod v0, v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_rem_u.hir b/frontend/wasm/src/code_translator/expected/i32_rem_u.hir new file mode 100644 index 000000000..f5d91b0fe --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_rem_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v4 = arith.mod v2, v3 : u32; + v5 = hir.bitcast v4 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_rotl.hir b/frontend/wasm/src/code_translator/expected/i32_rotl.hir new file mode 100644 index 000000000..e1014a710 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_rotl.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v1 : u32; + v3 = arith.rotl v0, v2 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_rotr.hir b/frontend/wasm/src/code_translator/expected/i32_rotr.hir new file mode 100644 index 000000000..68547f67c --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_rotr.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v1 : u32; + v3 = arith.rotr v0, v2 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_shl.hir b/frontend/wasm/src/code_translator/expected/i32_shl.hir new file mode 100644 index 000000000..4187dc706 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_shl.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v1 : u32; + v3 = arith.shl v0, v2 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_shr_s.hir b/frontend/wasm/src/code_translator/expected/i32_shr_s.hir new file mode 100644 index 000000000..a48946b3b --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_shr_s.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v1 : u32; + v3 = arith.shr v0, v2 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_shr_u.hir b/frontend/wasm/src/code_translator/expected/i32_shr_u.hir new file mode 100644 index 000000000..c33fbc4f3 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_shr_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v4 = arith.shr v2, v3 : u32; + v5 = hir.bitcast v4 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_store.hir b/frontend/wasm/src/code_translator/expected/i32_store.hir new file mode 100644 index 000000000..6abc234b3 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_store.hir @@ -0,0 +1,14 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v0 : u32; + v3 = arith.constant 4 : u32; + v4 = arith.mod v2, v3 : u32; + hir.assertz v4 #[code = 250]; + v5 = hir.int_to_ptr v2 : ptr; + hir.store v5, v1; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_store16.hir b/frontend/wasm/src/code_translator/expected/i32_store16.hir new file mode 100644 index 000000000..330db042b --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_store16.hir @@ -0,0 +1,16 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v1 : u32; + v3 = arith.trunc v2 : u16; + v4 = hir.bitcast v0 : u32; + v5 = arith.constant 2 : u32; + v6 = arith.mod v4, v5 : u32; + hir.assertz v6 #[code = 250]; + v7 = hir.int_to_ptr v4 : ptr; + hir.store v7, v3; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_store8.hir b/frontend/wasm/src/code_translator/expected/i32_store8.hir new file mode 100644 index 000000000..613d686bc --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_store8.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = arith.constant 1 : i32; + v2 = hir.bitcast v1 : u32; + v3 = arith.trunc v2 : u8; + v4 = hir.bitcast v0 : u32; + v5 = hir.int_to_ptr v4 : ptr; + hir.store v5, v3; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_sub.hir b/frontend/wasm/src/code_translator/expected/i32_sub.hir new file mode 100644 index 000000000..3c508cdf8 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_sub.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 3 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_wrap_i64.hir b/frontend/wasm/src/code_translator/expected/i32_wrap_i64.hir new file mode 100644 index 000000000..e914f4d2f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_wrap_i64.hir @@ -0,0 +1,8 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i64; + v1 = arith.trunc v0 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i32_xor.hir b/frontend/wasm/src/code_translator/expected/i32_xor.hir new file mode 100644 index 000000000..5da8367d7 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i32_xor.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 1 : i32; + v2 = arith.bxor v0, v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_add.hir b/frontend/wasm/src/code_translator/expected/i64_add.hir new file mode 100644 index 000000000..da762016f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_add.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 3 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.add v0, v1 : i64 #[overflow = wrapping]; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_and.hir b/frontend/wasm/src/code_translator/expected/i64_and.hir new file mode 100644 index 000000000..5382ea768 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_and.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.band v0, v1 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_clz.hir b/frontend/wasm/src/code_translator/expected/i64_clz.hir new file mode 100644 index 000000000..1e2a25c81 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_clz.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i64; + v1 = arith.clz v0 : u32; + v2 = hir.bitcast v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_const.hir b/frontend/wasm/src/code_translator/expected/i64_const.hir new file mode 100644 index 000000000..94d0535c7 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_const.hir @@ -0,0 +1,7 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_ctz.hir b/frontend/wasm/src/code_translator/expected/i64_ctz.hir new file mode 100644 index 000000000..a349429f2 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_ctz.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i64; + v1 = arith.ctz v0 : u32; + v2 = hir.bitcast v1 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_div_s.hir b/frontend/wasm/src/code_translator/expected/i64_div_s.hir new file mode 100644 index 000000000..4dc9c3f41 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_div_s.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.div v0, v1 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_div_u.hir b/frontend/wasm/src/code_translator/expected/i64_div_u.hir new file mode 100644 index 000000000..365ab890f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_div_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u64; + v3 = hir.bitcast v1 : u64; + v4 = arith.div v2, v3 : u64; + v5 = hir.bitcast v4 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_eq.hir b/frontend/wasm/src/code_translator/expected/i64_eq.hir new file mode 100644 index 000000000..9084b4bf4 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_eq.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.eq v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_eqz.hir b/frontend/wasm/src/code_translator/expected/i64_eqz.hir new file mode 100644 index 000000000..13357d32e --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_eqz.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 0 : i64; + v2 = arith.eq v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_extend_i32_s.hir b/frontend/wasm/src/code_translator/expected/i64_extend_i32_s.hir new file mode 100644 index 000000000..b559f930c --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_extend_i32_s.hir @@ -0,0 +1,8 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i32; + v1 = arith.sext v0 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_extend_i32_u.hir b/frontend/wasm/src/code_translator/expected/i64_extend_i32_u.hir new file mode 100644 index 000000000..da792a882 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_extend_i32_u.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.zext v1 : u64; + v3 = hir.bitcast v2 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_ge_s.hir b/frontend/wasm/src/code_translator/expected/i64_ge_s.hir new file mode 100644 index 000000000..bb451645f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_ge_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.gte v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_ge_u.hir b/frontend/wasm/src/code_translator/expected/i64_ge_u.hir new file mode 100644 index 000000000..a0fae5449 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_ge_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u64; + v3 = hir.bitcast v1 : u64; + v4 = arith.gte v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_gt_s.hir b/frontend/wasm/src/code_translator/expected/i64_gt_s.hir new file mode 100644 index 000000000..3aed5ff0e --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_gt_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.gt v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_gt_u.hir b/frontend/wasm/src/code_translator/expected/i64_gt_u.hir new file mode 100644 index 000000000..e9b59e797 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_gt_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u64; + v3 = hir.bitcast v1 : u64; + v4 = arith.gt v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_le_s.hir b/frontend/wasm/src/code_translator/expected/i64_le_s.hir new file mode 100644 index 000000000..533b3a451 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_le_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.lte v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_le_u.hir b/frontend/wasm/src/code_translator/expected/i64_le_u.hir new file mode 100644 index 000000000..498d08c13 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_le_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u64; + v3 = hir.bitcast v1 : u64; + v4 = arith.lte v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_load.hir b/frontend/wasm/src/code_translator/expected/i64_load.hir new file mode 100644 index 000000000..410266bdf --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_load.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 8 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_load16_s.hir b/frontend/wasm/src/code_translator/expected/i64_load16_s.hir new file mode 100644 index 000000000..cf3eb280f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_load16_s.hir @@ -0,0 +1,14 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 2 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : i16; + v6 = arith.sext v5 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_load16_u.hir b/frontend/wasm/src/code_translator/expected/i64_load16_u.hir new file mode 100644 index 000000000..e1f780057 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_load16_u.hir @@ -0,0 +1,15 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 2 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : u16; + v6 = arith.zext v5 : u64; + v7 = hir.bitcast v6 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_load32_s.hir b/frontend/wasm/src/code_translator/expected/i64_load32_s.hir new file mode 100644 index 000000000..dfd2e4a53 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_load32_s.hir @@ -0,0 +1,14 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 4 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : i32; + v6 = arith.sext v5 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_load32_u.hir b/frontend/wasm/src/code_translator/expected/i64_load32_u.hir new file mode 100644 index 000000000..c7f8d68dc --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_load32_u.hir @@ -0,0 +1,15 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = arith.constant 4 : u32; + v3 = arith.mod v1, v2 : u32; + hir.assertz v3 #[code = 250]; + v4 = hir.int_to_ptr v1 : ptr; + v5 = hir.load v4 : u32; + v6 = arith.zext v5 : u64; + v7 = hir.bitcast v6 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_load8_s.hir b/frontend/wasm/src/code_translator/expected/i64_load8_s.hir new file mode 100644 index 000000000..018716acb --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_load8_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = hir.int_to_ptr v1 : ptr; + v3 = hir.load v2 : i8; + v4 = arith.sext v3 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_load8_u.hir b/frontend/wasm/src/code_translator/expected/i64_load8_u.hir new file mode 100644 index 000000000..e0221ac67 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_load8_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = hir.bitcast v0 : u32; + v2 = hir.int_to_ptr v1 : ptr; + v3 = hir.load v2 : u8; + v4 = arith.zext v3 : u64; + v5 = hir.bitcast v4 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_lt_s.hir b/frontend/wasm/src/code_translator/expected/i64_lt_s.hir new file mode 100644 index 000000000..3bfaea991 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_lt_s.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.lt v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_lt_u.hir b/frontend/wasm/src/code_translator/expected/i64_lt_u.hir new file mode 100644 index 000000000..f560fc49f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_lt_u.hir @@ -0,0 +1,13 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u64; + v3 = hir.bitcast v1 : u64; + v4 = arith.lt v2, v3 : i1; + v5 = arith.zext v4 : u32; + v6 = hir.bitcast v5 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_mul.hir b/frontend/wasm/src/code_translator/expected/i64_mul.hir new file mode 100644 index 000000000..a2c8b624f --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_mul.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.mul v0, v1 : i64 #[overflow = wrapping]; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_ne.hir b/frontend/wasm/src/code_translator/expected/i64_ne.hir new file mode 100644 index 000000000..bb854087d --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_ne.hir @@ -0,0 +1,11 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.neq v0, v1 : i1; + v3 = arith.zext v2 : u32; + v4 = hir.bitcast v3 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_or.hir b/frontend/wasm/src/code_translator/expected/i64_or.hir new file mode 100644 index 000000000..6d7b732ac --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_or.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.bor v0, v1 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_rem_s.hir b/frontend/wasm/src/code_translator/expected/i64_rem_s.hir new file mode 100644 index 000000000..5ce9697d8 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_rem_s.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.mod v0, v1 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_rem_u.hir b/frontend/wasm/src/code_translator/expected/i64_rem_u.hir new file mode 100644 index 000000000..09a02962c --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_rem_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u64; + v3 = hir.bitcast v1 : u64; + v4 = arith.mod v2, v3 : u64; + v5 = hir.bitcast v4 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_rotl.hir b/frontend/wasm/src/code_translator/expected/i64_rotl.hir new file mode 100644 index 000000000..060516858 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_rotl.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.cast v1 : u32; + v3 = arith.rotl v0, v2 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_rotr.hir b/frontend/wasm/src/code_translator/expected/i64_rotr.hir new file mode 100644 index 000000000..ca967d90e --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_rotr.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.cast v1 : u32; + v3 = arith.rotr v0, v2 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_shl.hir b/frontend/wasm/src/code_translator/expected/i64_shl.hir new file mode 100644 index 000000000..ee148f7d2 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_shl.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.cast v1 : u32; + v3 = arith.shl v0, v2 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_shr_s.hir b/frontend/wasm/src/code_translator/expected/i64_shr_s.hir new file mode 100644 index 000000000..be1cc0536 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_shr_s.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.cast v1 : u32; + v3 = arith.shr v0, v2 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_shr_u.hir b/frontend/wasm/src/code_translator/expected/i64_shr_u.hir new file mode 100644 index 000000000..9fb57eeeb --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_shr_u.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u64; + v3 = hir.cast v1 : u32; + v4 = arith.shr v2, v3 : u64; + v5 = hir.bitcast v4 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_store.hir b/frontend/wasm/src/code_translator/expected/i64_store.hir new file mode 100644 index 000000000..5a8002d57 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_store.hir @@ -0,0 +1,14 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v0 : u32; + v3 = arith.constant 8 : u32; + v4 = arith.mod v2, v3 : u32; + hir.assertz v4 #[code = 250]; + v5 = hir.int_to_ptr v2 : ptr; + hir.store v5, v1; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_store32.hir b/frontend/wasm/src/code_translator/expected/i64_store32.hir new file mode 100644 index 000000000..ec10fea3d --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_store32.hir @@ -0,0 +1,16 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1024 : i32; + v1 = arith.constant 1 : i64; + v2 = hir.bitcast v1 : u64; + v3 = arith.trunc v2 : u32; + v4 = hir.bitcast v0 : u32; + v5 = arith.constant 4 : u32; + v6 = arith.mod v4, v5 : u32; + hir.assertz v6 #[code = 250]; + v7 = hir.int_to_ptr v4 : ptr; + hir.store v7, v3; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_sub.hir b/frontend/wasm/src/code_translator/expected/i64_sub.hir new file mode 100644 index 000000000..eb2632b8e --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_sub.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 3 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.sub v0, v1 : i64 #[overflow = wrapping]; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/i64_xor.hir b/frontend/wasm/src/code_translator/expected/i64_xor.hir new file mode 100644 index 000000000..fd3d7874e --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/i64_xor.hir @@ -0,0 +1,9 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i64; + v1 = arith.constant 1 : i64; + v2 = arith.bxor v0, v1 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/if_else.hir b/frontend/wasm/src/code_translator/expected/if_else.hir new file mode 100644 index 000000000..32ddce1f0 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/if_else.hir @@ -0,0 +1,17 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 2 : i32; + v1 = arith.constant 0 : i32; + v2 = arith.neq v0, v1 : i1; + cf.cond_br v2 ^block6, ^block8; +^block5: + builtin.ret ; +^block6: + v4 = arith.constant 3 : i32; + cf.br ^block7(v4); +^block7(v3: i32): + cf.br ^block5; +^block8: + v5 = arith.constant 5 : i32; + cf.br ^block7(v5); +}; diff --git a/frontend/wasm/src/code_translator/expected/memory_copy.hir b/frontend/wasm/src/code_translator/expected/memory_copy.hir new file mode 100644 index 000000000..c0bb90f35 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/memory_copy.hir @@ -0,0 +1,15 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 20 : i32; + v1 = arith.constant 10 : i32; + v2 = arith.constant 1 : i32; + v3 = hir.bitcast v2 : u32; + v4 = hir.bitcast v0 : u32; + v5 = hir.int_to_ptr v4 : ptr; + v6 = hir.bitcast v1 : u32; + v7 = hir.int_to_ptr v6 : ptr; + hir.mem_cpy v7, v5, v3; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/memory_grow.hir b/frontend/wasm/src/code_translator/expected/memory_grow.hir new file mode 100644 index 000000000..2f67f8126 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/memory_grow.hir @@ -0,0 +1,10 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 1 : i32; + v1 = hir.bitcast v0 : u32; + v2 = hir.mem_grow v1 : u32; + v3 = hir.bitcast v2 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/memory_size.hir b/frontend/wasm/src/code_translator/expected/memory_size.hir new file mode 100644 index 000000000..2314494eb --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/memory_size.hir @@ -0,0 +1,8 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = hir.mem_size : u32; + v1 = hir.bitcast v0 : i32; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/expected/select_i32.hir b/frontend/wasm/src/code_translator/expected/select_i32.hir new file mode 100644 index 000000000..a4aa38c72 --- /dev/null +++ b/frontend/wasm/src/code_translator/expected/select_i32.hir @@ -0,0 +1,12 @@ +public builtin.function @test_wrapper() { +^block4: + v0 = arith.constant 3 : i64; + v1 = arith.constant 7 : i64; + v2 = arith.constant 1 : i32; + v3 = arith.constant 0 : i32; + v4 = arith.neq v2, v3 : i1; + v5 = cf.select v4, v0, v1 : i64; + cf.br ^block5; +^block5: + builtin.ret ; +}; diff --git a/frontend/wasm/src/code_translator/mod.rs b/frontend/wasm/src/code_translator/mod.rs new file mode 100644 index 000000000..233420af4 --- /dev/null +++ b/frontend/wasm/src/code_translator/mod.rs @@ -0,0 +1,1331 @@ +//! This module contains the bulk of the code performing the translation between +//! WebAssembly and Miden IR. +//! +//! The translation is done in one pass, opcode by opcode. Two main data structures are used during +//! code translations: the value stack and the control stack. The value stack mimics the execution +//! of the WebAssembly stack machine: each instruction result is pushed onto the stack and +//! instruction arguments are popped off the stack. Similarly, when encountering a control flow +//! block, it is pushed onto the control stack and popped off when encountering the corresponding +//! `End`. +//! +//! Another data structure, the translation state, records information concerning unreachable code +//! status and about if inserting a return at the end of the function is necessary. +//! +//! Based on Cranelift's Wasm -> CLIF translator v11.0.0 + +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_cf::{ControlFlowOpBuilder, SwitchCase}; +use midenc_dialect_hir::{assertions, HirOpBuilder}; +use midenc_dialect_ub::UndefinedBehaviorOpBuilder; +use midenc_hir::{ + dialects::builtin::BuiltinOpBuilder, + BlockRef, Builder, Felt, FieldElement, Immediate, PointerType, + Type::{self, *}, + ValueRef, +}; +use midenc_session::diagnostics::{DiagnosticsHandler, IntoDiagnostic, Report, SourceSpan}; +use wasmparser::{MemArg, Operator}; + +use crate::{ + callable::CallableFunction, + error::WasmResult, + intrinsics::convert_intrinsics_call, + module::{ + func_translation_state::{ControlStackFrame, ElseData, FuncTranslationState}, + function_builder_ext::FunctionBuilderExt, + module_translation_state::ModuleTranslationState, + types::{BlockType, FuncIndex, GlobalIndex, ModuleTypesBuilder}, + Module, + }, + ssa::Variable, + unsupported_diag, +}; + +#[cfg(test)] +mod tests; + +/// Translates wasm operators into Miden IR instructions. +#[allow(clippy::too_many_arguments)] +pub fn translate_operator( + op: &Operator, + builder: &mut FunctionBuilderExt<'_, B>, + state: &mut FuncTranslationState, + module_state: &mut ModuleTranslationState, + module: &Module, + mod_types: &ModuleTypesBuilder, + diagnostics: &DiagnosticsHandler, + span: SourceSpan, +) -> WasmResult<()> { + if !state.reachable { + translate_unreachable_operator(op, builder, state, mod_types, diagnostics, span)?; + return Ok(()); + } + + // Given that we believe the current block is reachable, the FunctionBuilderExt ought to agree. + debug_assert!(!builder.is_unreachable()); + + match op { + /********************************** Locals **************************************** + * `get_local` and `set_local` are treated as non-SSA variables and will completely + * disappear in the Miden IR + ***********************************************************************************/ + Operator::LocalGet { local_index } => { + let val = builder.use_var(Variable::from_u32(*local_index)); + state.push1(val); + } + Operator::LocalSet { local_index } => { + let val = state.pop1(); + let var = Variable::from_u32(*local_index); + let expected_ty = builder.variable_type(var).clone(); + let value_ty = val.borrow().ty().clone(); + let val = if expected_ty != value_ty { + if expected_ty == I32 && value_ty == U32 { + builder.bitcast(val, I32, span)? + } else if expected_ty == I64 && value_ty == U64 { + builder.bitcast(val, I64, span)? + } else { + let expected_ty = expected_ty.clone(); + builder.cast(val, expected_ty, span)? + } + } else { + val + }; + builder.def_var(var, val); + } + Operator::LocalTee { local_index } => { + let val = state.peek1(); + builder.def_var(Variable::from_u32(*local_index), val); + } + /********************************** Globals ****************************************/ + Operator::GlobalGet { global_index } => { + let global_index = GlobalIndex::from_u32(*global_index); + let name = module.global_name(global_index); + let gv = module_state.module_builder.get_global_var(name).unwrap_or_else(|| { + panic!("global var not found: index={}, name={}", global_index.as_u32(), name) + }); + let val = builder.load_global(gv, span)?; + state.push1(val); + } + Operator::GlobalSet { global_index } => { + let global_index = GlobalIndex::from_u32(*global_index); + let name = module.global_name(global_index); + let gv = module_state.module_builder.get_global_var(name).unwrap_or_else(|| { + panic!("global var not found: index={}, name={}", global_index.as_u32(), name) + }); + let arg = state.pop1(); + builder.store_global(gv, arg, span)?; + } + /********************************* Stack misc **************************************/ + Operator::Drop => _ = state.pop1(), + Operator::Select => { + let (arg1, arg2, cond) = state.pop3(); + // if cond is not 0, return arg1, else return arg2 + // https://www.w3.org/TR/wasm-core-1/#-hrefsyntax-instr-parametricmathsfselect%E2%91%A0 + // cond is expected to be an i32 + let imm = builder.imm(Immediate::I32(0), span); + let cond_i1 = builder.neq(cond, imm, span)?; + state.push1(builder.select(cond_i1, arg1, arg2, span)?); + } + Operator::TypedSelect { ty } => { + let (arg1, arg2, cond) = state.pop3(); + match ty { + wasmparser::ValType::F32 => { + let imm = builder.felt(Felt::ZERO, span); + let cond = builder.gt(cond, imm, span)?; + state.push1(builder.select(cond, arg1, arg2, span)?); + } + wasmparser::ValType::I32 => { + let imm = builder.imm(Immediate::I32(0), span); + let cond = builder.neq(cond, imm, span)?; + state.push1(builder.select(cond, arg1, arg2, span)?); + } + wasmparser::ValType::I64 => { + let imm = builder.imm(Immediate::I64(0), span); + let cond = builder.neq(cond, imm, span)?; + state.push1(builder.select(cond, arg1, arg2, span)?); + } + ty => panic!("unsupported value type for 'select': {ty}"), + }; + } + Operator::Unreachable => { + builder.unreachable(span); + state.reachable = false; + } + Operator::Nop => {} + /***************************** Control flow blocks *********************************/ + Operator::Block { blockty } => { + translate_block(blockty, builder, state, mod_types, diagnostics, span)?; + } + Operator::Loop { blockty } => { + translate_loop(blockty, builder, state, mod_types, diagnostics, span)?; + } + Operator::If { blockty } => { + translate_if(blockty, state, builder, mod_types, diagnostics, span)?; + } + Operator::Else => translate_else(state, builder, span)?, + Operator::End => translate_end(state, builder, span)?, + + /**************************** Branch instructions *********************************/ + Operator::Br { relative_depth } => translate_br(state, relative_depth, builder, span)?, + Operator::BrIf { relative_depth } => { + translate_br_if(*relative_depth, builder, state, span)? + } + Operator::BrTable { targets } => translate_br_table(targets, state, builder, span)?, + Operator::Return => translate_return(state, builder, diagnostics, span)?, + /************************************ Calls ****************************************/ + Operator::Call { function_index } => { + translate_call( + state, + module_state, + builder, + FuncIndex::from_u32(*function_index), + span, + diagnostics, + )?; + } + Operator::CallIndirect { + type_index: _, + table_index: _, + } => { + todo!("CallIndirect is not supported yet"); + } + /******************************* Memory management *********************************/ + Operator::MemoryGrow { .. } => { + let arg = state.pop1_bitcasted(U32, builder, span); + let result = builder.mem_grow(arg, span)?; + // WASM memory.grow returns i32, so bitcast from U32 to I32 + state.push1(builder.bitcast(result, I32, span)?); + } + Operator::MemorySize { .. } => { + // Return total Miden memory size + let result = builder.mem_size(span)?; + // WASM memory.size returns i32, so bitcast from U32 to I32 + state.push1(builder.bitcast(result, I32, span)?); + } + /******************************* Bulk memory operations *********************************/ + Operator::MemoryCopy { dst_mem, src_mem } => { + // See semantics at https://github.com/WebAssembly/bulk-memory-operations/blob/master/proposals/bulk-memory-operations/Overview.md#memorycopy-instruction + if *src_mem == 0 && src_mem == dst_mem { + let count_i32 = state.pop1(); + let src_i32 = state.pop1(); + let dst_i32 = state.pop1(); + let count = builder.bitcast(count_i32, Type::U32, span)?; + let dst = prepare_addr(dst_i32, &U8, None, builder, span)?; + let src = prepare_addr(src_i32, &U8, None, builder, span)?; + builder.memcpy(src, dst, count, span)?; + } else { + unsupported_diag!(diagnostics, "MemoryCopy: only single memory is supported"); + } + } + Operator::MemoryFill { mem } => { + // See semantics at https://webassembly.github.io/spec/core/exec/instructions.html#exec-memory-fill + if *mem != 0 { + unsupported_diag!(diagnostics, "MemoryFill: only single memory is supported"); + } + let num_bytes = state.pop1(); + let value = state.pop1(); + let dst_i32 = state.pop1(); + let value = builder.trunc(value, Type::U8, span)?; + let num_bytes = builder.bitcast(num_bytes, Type::U32, span)?; + let dst = prepare_addr(dst_i32, &U8, None, builder, span)?; + builder.memset(dst, num_bytes, value, span)?; + } + /******************************* Load instructions ***********************************/ + Operator::I32Load8U { memarg } => { + translate_load_zext(U8, U32, memarg, state, builder, span)?; + } + Operator::I32Load16U { memarg } => { + translate_load_zext(U16, U32, memarg, state, builder, span)?; + } + Operator::I32Load8S { memarg } => { + translate_load_sext(I8, I32, memarg, state, builder, span)?; + } + Operator::I32Load16S { memarg } => { + translate_load_sext(I16, I32, memarg, state, builder, span)?; + } + Operator::I64Load8U { memarg } => { + translate_load_zext(U8, U64, memarg, state, builder, span)?; + } + Operator::I64Load16U { memarg } => { + translate_load_zext(U16, U64, memarg, state, builder, span)?; + } + Operator::I64Load8S { memarg } => { + translate_load_sext(I8, I64, memarg, state, builder, span)?; + } + Operator::I64Load16S { memarg } => { + translate_load_sext(I16, I64, memarg, state, builder, span)?; + } + Operator::I64Load32S { memarg } => { + translate_load_sext(I32, I64, memarg, state, builder, span)?; + } + Operator::I64Load32U { memarg } => { + translate_load_zext(U32, U64, memarg, state, builder, span)?; + } + Operator::I32Load { memarg } => translate_load(I32, memarg, state, builder, span)?, + Operator::I64Load { memarg } => translate_load(I64, memarg, state, builder, span)?, + Operator::F32Load { memarg } => translate_load(Felt, memarg, state, builder, span)?, + /****************************** Store instructions ***********************************/ + Operator::I32Store { memarg } => translate_store(I32, memarg, state, builder, span)?, + Operator::I64Store { memarg } => translate_store(I64, memarg, state, builder, span)?, + Operator::F32Store { memarg } => translate_store(Felt, memarg, state, builder, span)?, + Operator::I32Store8 { memarg } | Operator::I64Store8 { memarg } => { + translate_store(U8, memarg, state, builder, span)?; + } + Operator::I32Store16 { memarg } | Operator::I64Store16 { memarg } => { + translate_store(U16, memarg, state, builder, span)?; + } + Operator::I64Store32 { memarg } => translate_store(U32, memarg, state, builder, span)?, + /****************************** Nullary Operators **********************************/ + Operator::I32Const { value } => state.push1(builder.i32(*value, span)), + Operator::I64Const { value } => state.push1(builder.i64(*value, span)), + + /******************************* Unary Operators *************************************/ + Operator::I32Clz | Operator::I64Clz => { + let val = state.pop1(); + let count = builder.clz(val, span)?; + // To ensure we match the Wasm semantics, treat the output of clz as an i32 + state.push1(builder.bitcast(count, Type::I32, span)?); + } + Operator::I32Ctz | Operator::I64Ctz => { + let val = state.pop1(); + let count = builder.ctz(val, span)?; + // To ensure we match the Wasm semantics, treat the output of ctz as an i32 + state.push1(builder.bitcast(count, Type::I32, span)?); + } + Operator::I32Popcnt | Operator::I64Popcnt => { + let val = state.pop1(); + let count = builder.popcnt(val, span)?; + // To ensure we match the Wasm semantics, treat the output of popcnt as an i32 + state.push1(builder.bitcast(count, Type::I32, span)?); + } + Operator::I32Extend8S | Operator::I32Extend16S => { + let val = state.pop1(); + state.push1(builder.sext(val, I32, span)?); + } + Operator::I64ExtendI32S => { + let val = state.pop1(); + state.push1(builder.sext(val, I64, span)?); + } + Operator::I64ExtendI32U => { + let val = state.pop1(); + let u32_val = builder.bitcast(val, U32, span)?; + let u64_val = builder.zext(u32_val, U64, span)?; + let i64_val = builder.bitcast(u64_val, I64, span)?; + state.push1(i64_val); + } + Operator::I32WrapI64 => { + let val = state.pop1(); + state.push1(builder.trunc(val, I32, span)?); + } + Operator::F32ReinterpretI32 => { + let val = state.pop1_bitcasted(Felt, builder, span); + state.push1(val); + } + /****************************** Binary Operators ************************************/ + Operator::I32Add | Operator::I64Add => { + let (arg1, arg2) = state.pop2(); + // wrapping because the result is mod 2^N + // https://www.w3.org/TR/wasm-core-1/#op-iadd + + let value_type = arg1.borrow().ty().clone(); + let arg2 = if &value_type != arg2.borrow().ty() { + let value_type = value_type.clone(); + builder.bitcast(arg2, value_type, span)? + } else { + arg2 + }; + state.push1(builder.add_wrapping(arg1, arg2, span)?); + } + Operator::I64Add128 => { + let (rhs_hi, rhs_lo) = state.pop2(); + let (lhs_hi, lhs_lo) = state.pop2(); + + let lhs = builder.join(lhs_hi, lhs_lo, span)?; + let rhs = builder.join(rhs_hi, rhs_lo, span)?; + + let res = builder.add_wrapping(lhs, rhs, span)?; + + // Ensure the high limb is left on the top of the value stack. + let (res_hi, res_lo) = builder.split(res, span)?; + state.pushn(&[res_lo, res_hi]); + } + Operator::I32And | Operator::I64And => { + let (arg1, arg2) = state.pop2(); + state.push1(builder.band(arg1, arg2, span)?); + } + Operator::I32Or | Operator::I64Or => { + let (arg1, arg2) = state.pop2(); + state.push1(builder.bor(arg1, arg2, span)?); + } + Operator::I32Xor | Operator::I64Xor => { + let (arg1, arg2) = state.pop2(); + state.push1(builder.bxor(arg1, arg2, span)?); + } + Operator::I32Shl => { + let (arg1, arg2) = state.pop2(); + // wrapping shift semantics drop any bits that would cause + // the shift to exceed the bitwidth of the type + let arg2 = builder.bitcast(arg2, U32, span)?; + state.push1(builder.shl(arg1, arg2, span)?); + } + Operator::I64Shl => { + let (arg1, arg2) = state.pop2(); + // wrapping shift semantics drop any bits that would cause + // the shift to exceed the bitwidth of the type + let arg2 = builder.cast(arg2, U32, span)?; + state.push1(builder.shl(arg1, arg2, span)?); + } + Operator::I32ShrU => { + let (arg1, arg2) = state.pop2_bitcasted(U32, builder, span)?; + // wrapping shift semantics drop any bits that would cause + // the shift to exceed the bitwidth of the type + let val = builder.shr(arg1, arg2, span)?; + state.push1(builder.bitcast(val, I32, span)?); + } + Operator::I64ShrU => { + let (arg1, arg2) = state.pop2(); + let arg1 = builder.bitcast(arg1, U64, span)?; + let arg2 = builder.cast(arg2, U32, span)?; + // wrapping shift semantics drop any bits that would cause + // the shift to exceed the bitwidth of the type + let val = builder.shr(arg1, arg2, span)?; + state.push1(builder.bitcast(val, I64, span)?); + } + Operator::I32ShrS => { + let (arg1, arg2) = state.pop2(); + // wrapping shift semantics drop any bits that would cause + // the shift to exceed the bitwidth of the type + let arg2 = builder.bitcast(arg2, Type::U32, span)?; + state.push1(builder.shr(arg1, arg2, span)?); + } + Operator::I64ShrS => { + let (arg1, arg2) = state.pop2(); + // wrapping shift semantics drop any bits that would cause + // the shift to exceed the bitwidth of the type + let arg2 = builder.cast(arg2, Type::U32, span)?; + state.push1(builder.shr(arg1, arg2, span)?); + } + Operator::I32Rotl => { + let (arg1, arg2) = state.pop2(); + let arg2 = builder.bitcast(arg2, Type::U32, span)?; + state.push1(builder.rotl(arg1, arg2, span)?); + } + Operator::I64Rotl => { + let (arg1, arg2) = state.pop2(); + let arg2 = builder.cast(arg2, Type::U32, span)?; + state.push1(builder.rotl(arg1, arg2, span)?); + } + Operator::I32Rotr => { + let (arg1, arg2) = state.pop2(); + let arg2 = builder.bitcast(arg2, Type::U32, span)?; + state.push1(builder.rotr(arg1, arg2, span)?); + } + Operator::I64Rotr => { + let (arg1, arg2) = state.pop2(); + let arg2 = builder.cast(arg2, Type::U32, span)?; + state.push1(builder.rotr(arg1, arg2, span)?); + } + Operator::I32Sub | Operator::I64Sub => { + let (arg1, arg2) = state.pop2(); + // wrapping because the result is mod 2^N + // https://www.w3.org/TR/wasm-core-1/#op-isub + state.push1(builder.sub_wrapping(arg1, arg2, span)?); + } + Operator::I64Sub128 => { + let (rhs_hi, rhs_lo) = state.pop2(); + let (lhs_hi, lhs_lo) = state.pop2(); + + let lhs = builder.join(lhs_hi, lhs_lo, span)?; + let rhs = builder.join(rhs_hi, rhs_lo, span)?; + + let res = builder.sub_wrapping(lhs, rhs, span)?; + + // Ensure the high limb is left on the top of the value stack. + let (res_hi, res_lo) = builder.split(res, span)?; + state.pushn(&[res_lo, res_hi]); + } + Operator::I32Mul | Operator::I64Mul => { + let (arg1, arg2) = state.pop2(); + // wrapping because the result is mod 2^N + // https://www.w3.org/TR/wasm-core-1/#op-imul + state.push1(builder.mul_wrapping(arg1, arg2, span)?); + } + Operator::I64MulWideU => { + let (arg1, arg2) = state.pop2(); + + let lhs_u = builder.bitcast(arg1, Type::U64, span)?; + let lhs = builder.zext(lhs_u, Type::U128, span)?; + + let rhs_u = builder.bitcast(arg2, Type::U64, span)?; + let rhs = builder.zext(rhs_u, Type::U128, span)?; + + let res = builder.mul_wrapping(lhs, rhs, span)?; + + // Ensure the high limb is left on the top of the value stack. + let (res_hi, res_lo) = builder.split(res, span)?; + state.pushn(&[res_lo, res_hi]); + } + Operator::I64MulWideS => { + let (arg1, arg2) = state.pop2(); + + let lhs = builder.sext(arg1, Type::I128, span)?; + let rhs = builder.sext(arg2, Type::I128, span)?; + + let res = builder.mul_wrapping(lhs, rhs, span)?; + + // Ensure the high limb is left on the top of the value stack. + let (res_hi, res_lo) = builder.split(res, span)?; + state.pushn(&[res_lo, res_hi]); + } + Operator::I32DivS | Operator::I64DivS => { + let (arg1, arg2) = state.pop2(); + state.push1(builder.div(arg1, arg2, span)?); + } + Operator::I32DivU => { + let (arg1, arg2) = state.pop2_bitcasted(U32, builder, span)?; + let val = builder.div(arg1, arg2, span)?; + state.push1(builder.bitcast(val, I32, span)?); + } + Operator::I64DivU => { + let (arg1, arg2) = state.pop2_bitcasted(U64, builder, span)?; + let val = builder.div(arg1, arg2, span)?; + state.push1(builder.bitcast(val, I64, span)?); + } + Operator::I32RemU => { + let (arg1, arg2) = state.pop2_bitcasted(U32, builder, span)?; + let val = builder.r#mod(arg1, arg2, span)?; + state.push1(builder.bitcast(val, I32, span)?); + } + Operator::I64RemU => { + let (arg1, arg2) = state.pop2_bitcasted(U64, builder, span)?; + let val = builder.r#mod(arg1, arg2, span)?; + state.push1(builder.bitcast(val, I64, span)?); + } + Operator::I32RemS | Operator::I64RemS => { + let (arg1, arg2) = state.pop2(); + state.push1(builder.r#mod(arg1, arg2, span)?); + } + /**************************** Comparison Operators **********************************/ + Operator::I32LtU => { + let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span)?; + let val = builder.lt(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64LtU => { + let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span)?; + let val = builder.lt(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32LtS => { + let (arg0, arg1) = state.pop2(); + let val = builder.lt(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64LtS => { + let (arg0, arg1) = state.pop2(); + let val = builder.lt(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32LeU => { + let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span)?; + let val = builder.lte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64LeU => { + let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span)?; + let val = builder.lte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32LeS => { + let (arg0, arg1) = state.pop2(); + let val = builder.lte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64LeS => { + let (arg0, arg1) = state.pop2(); + let val = builder.lte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32GtU => { + let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span)?; + let val = builder.gt(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64GtU => { + let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span)?; + let val = builder.gt(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32GtS | Operator::I64GtS => { + let (arg0, arg1) = state.pop2(); + let val = builder.gt(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32GeU => { + let (arg0, arg1) = state.pop2_bitcasted(U32, builder, span)?; + let val = builder.gte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64GeU => { + let (arg0, arg1) = state.pop2_bitcasted(U64, builder, span)?; + let val = builder.gte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32GeS => { + let (arg0, arg1) = state.pop2(); + let val = builder.gte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64GeS => { + let (arg0, arg1) = state.pop2(); + let val = builder.gte(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32Eqz => { + let arg = state.pop1(); + let imm = builder.imm(Immediate::I32(0), span); + let val = builder.eq(arg, imm, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64Eqz => { + let arg = state.pop1(); + let imm = builder.imm(Immediate::I64(0), span); + let val = builder.eq(arg, imm, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32Eq => { + let (arg0, arg1) = state.pop2(); + let val = builder.eq(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64Eq => { + let (arg0, arg1) = state.pop2(); + let val = builder.eq(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I32Ne => { + let (arg0, arg1) = state.pop2(); + let val = builder.neq(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + Operator::I64Ne => { + let (arg0, arg1) = state.pop2(); + let val = builder.neq(arg0, arg1, span)?; + let extended = builder.zext(val, U32, span)?; + state.push1(builder.bitcast(extended, I32, span)?); + } + op => { + unsupported_diag!(diagnostics, "Wasm op {:?} is not supported", op); + } + }; + Ok(()) +} + +fn translate_load( + ptr_ty: Type, + memarg: &MemArg, + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult<()> { + let addr_int = state.pop1(); + let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span)?; + state.push1(builder.load(addr, span)?); + Ok(()) +} + +fn translate_load_sext( + ptr_ty: Type, + sext_ty: Type, + memarg: &MemArg, + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult<()> { + let addr_int = state.pop1(); + let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span)?; + let val = builder.load(addr, span)?; + let sext_val = builder.sext(val, sext_ty, span)?; + state.push1(sext_val); + Ok(()) +} + +fn translate_load_zext( + ptr_ty: Type, + zext_ty: Type, + memarg: &MemArg, + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult<()> { + assert!(ptr_ty.is_unsigned_integer()); + let addr_int = state.pop1(); + let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span)?; + let val = builder.load(addr, span)?; + let zext_val = builder.zext(val, zext_ty.clone(), span)?; + let bitcast_val = match zext_ty { + Type::U32 => builder.bitcast(zext_val, Type::I32, span), + Type::U64 => builder.bitcast(zext_val, Type::I64, span), + _ => Ok(zext_val), + }?; + state.push1(bitcast_val); + Ok(()) +} + +fn translate_store( + ptr_ty: Type, + memarg: &MemArg, + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult<()> { + let (addr_int, val) = state.pop2(); + let val_ty = val.borrow().ty().clone(); + let arg = if ptr_ty != val_ty { + if ptr_ty.size_in_bits() == val_ty.size_in_bits() { + builder.bitcast(val, ptr_ty.clone(), span)? + } else if ptr_ty.is_unsigned_integer() && val_ty.is_signed_integer() { + let unsigned_val_ty = val_ty.as_unsigned(); + let uval = builder.bitcast(val, unsigned_val_ty, span)?; + builder.trunc(uval, ptr_ty.clone(), span)? + } else { + builder.trunc(val, ptr_ty.clone(), span)? + } + } else { + val + }; + let addr = prepare_addr(addr_int, &ptr_ty, Some(memarg), builder, span)?; + builder.store(addr, arg, span)?; + Ok(()) +} + +fn prepare_addr( + addr_int: ValueRef, + ptr_ty: &Type, + memarg: Option<&MemArg>, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult { + let addr_int_ty = addr_int.borrow().ty().clone(); + let addr_u32 = if addr_int_ty == U32 { + addr_int + } else if addr_int_ty == I32 { + builder.bitcast(addr_int, U32, span)? + } else if matches!(addr_int_ty, Ptr(_)) { + builder.ptrtoint(addr_int, U32, span)? + } else { + panic!("unexpected type used as pointer value: {addr_int_ty}"); + }; + let mut full_addr_int = addr_u32; + if let Some(memarg) = memarg { + if memarg.offset != 0 { + let imm = builder.imm(Immediate::U32(memarg.offset as u32), span); + full_addr_int = builder.add(addr_u32, imm, span)?; + } + // TODO(pauls): For now, asserting alignment helps us catch mistakes/bugs, but we should + // probably make this something that can be disabled to avoid the overhead in release builds + if memarg.align > 0 { + // Generate alignment assertion - aligned addresses should always produce 0 here + let imm = builder.imm(Immediate::U32(2u32.pow(memarg.align as u32)), span); + let align_offset = builder.r#mod(full_addr_int, imm, span)?; + builder.assertz_with_error(align_offset, assertions::ASSERT_FAILED_ALIGNMENT, span)?; + } + }; + builder.inttoptr(full_addr_int, Type::from(PointerType::new(ptr_ty.clone())), span) +} + +fn translate_call( + func_state: &mut FuncTranslationState, + module_state: &mut ModuleTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + function_index: FuncIndex, + span: SourceSpan, + _diagnostics: &DiagnosticsHandler, +) -> WasmResult<()> { + match module_state.get_direct_func(function_index)? { + CallableFunction::Instruction { + intrinsic, + signature, + } => { + let arity = signature.arity(); + let args = func_state.peekn(arity); + let results = convert_intrinsics_call(intrinsic, None, args, builder, span)?; + func_state.popn(arity); + func_state.pushn(&results); + } + CallableFunction::Intrinsic { + intrinsic, + function_ref, + signature, + } => { + let arity = signature.arity(); + let args = func_state.peekn(arity); + let results = + convert_intrinsics_call(intrinsic, Some(function_ref), args, builder, span)?; + func_state.popn(arity); + func_state.pushn(&results); + } + CallableFunction::Function { + function_ref, + signature, + .. + } => { + let arity = signature.arity(); + let args = func_state.peekn(arity); + let exec = builder.exec(function_ref, signature, args.iter().copied(), span)?; + let borrow = exec.borrow(); + let results = borrow.as_ref().results(); + func_state.popn(arity); + let result_vals: Vec = + results.iter().map(|op_res| op_res.borrow().as_value_ref()).collect(); + func_state.pushn(&result_vals); + } + } + Ok(()) +} + +fn translate_return( + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + diagnostics: &DiagnosticsHandler, + span: SourceSpan, +) -> WasmResult<()> { + let return_count = { + let frame = &mut state.control_stack[0]; + frame.num_return_values() + }; + { + let return_args = match return_count { + 0 => None, + 1 => Some(*state.peekn_mut(return_count).first().unwrap()), + _ => { + unsupported_diag!(diagnostics, "Multiple values are not supported"); + } + }; + + builder.ret(return_args, span)?; + } + state.popn(return_count); + state.reachable = false; + Ok(()) +} + +fn translate_br( + state: &mut FuncTranslationState, + relative_depth: &u32, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult<()> { + let i = state.control_stack.len() - 1 - (*relative_depth as usize); + let (return_count, br_destination) = { + let frame = &mut state.control_stack[i]; + // We signal that all the code that follows until the next End is unreachable + frame.set_branched_to_exit(); + let return_count = if frame.is_loop() { + frame.num_param_values() + } else { + frame.num_return_values() + }; + (return_count, frame.br_destination()) + }; + let destination_args = state.peekn_mut(return_count).to_vec(); + builder.br(br_destination, destination_args, span)?; + state.popn(return_count); + state.reachable = false; + Ok(()) +} + +fn translate_br_if( + relative_depth: u32, + builder: &mut FunctionBuilderExt<'_, B>, + state: &mut FuncTranslationState, + span: SourceSpan, +) -> WasmResult<()> { + let cond = state.pop1_bitcasted(Type::I32, builder, span); + let (br_destination, inputs) = translate_br_if_args(relative_depth, state); + let next_block = builder.create_block(); + let then_dest = br_destination; + let then_args = inputs.to_vec(); + let else_dest = next_block; + let else_args = vec![]; + // cond is expected to be a i32 value + let imm = builder.imm(Immediate::I32(0), span); + let cond_i1 = builder.neq(cond, imm, span)?; + builder.cond_br(cond_i1, then_dest, then_args, else_dest, else_args, span)?; + builder.seal_block(next_block); // The only predecessor is the current block. + builder.switch_to_block(next_block); + Ok(()) +} + +fn translate_br_if_args( + relative_depth: u32, + state: &mut FuncTranslationState, +) -> (BlockRef, &mut [ValueRef]) { + let i = state.control_stack.len() - 1 - (relative_depth as usize); + let (return_count, br_destination) = { + let frame = &mut state.control_stack[i]; + // The values returned by the branch are still available for the reachable + // code that comes after it + frame.set_branched_to_exit(); + let return_count = if frame.is_loop() { + frame.num_param_values() + } else { + frame.num_return_values() + }; + (return_count, frame.br_destination()) + }; + let inputs = state.peekn_mut(return_count); + (br_destination, inputs) +} + +fn translate_br_table( + br_targets: &wasmparser::BrTable<'_>, + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> Result<(), Report> { + let mut targets = Vec::default(); + for depth in br_targets.targets() { + let depth = depth.into_diagnostic()?; + + targets.push(depth); + } + targets.sort(); + + let default_depth = br_targets.default(); + let min_depth = + core::cmp::min(targets.iter().copied().min().unwrap_or(default_depth), default_depth); + + let argc = { + let i = state.control_stack.len() - 1 - (min_depth as usize); + let min_depth_frame = &state.control_stack[i]; + if min_depth_frame.is_loop() { + min_depth_frame.num_param_values() + } else { + min_depth_frame.num_return_values() + } + }; + + let default_block = { + let i = state.control_stack.len() - 1 - (default_depth as usize); + let frame = &mut state.control_stack[i]; + frame.set_branched_to_exit(); + frame.br_destination() + }; + + let selector = state.pop1(); + let selector = if selector.borrow().ty().clone() != U32 { + builder.cast(selector, U32, span)? + } else { + selector + }; + + let mut cases = Vec::new(); + for (label_idx, depth) in targets.into_iter().enumerate() { + let block = { + let i = state.control_stack.len() - 1 - (depth as usize); + let frame = &mut state.control_stack[i]; + frame.set_branched_to_exit(); + frame.br_destination() + }; + let args = state.peekn_mut(argc).to_vec(); + let case = SwitchCase { + value: label_idx as u32, + successor: block, + arguments: args, + }; + cases.push(case); + } + + let default_args = state.peekn_mut(argc).to_vec(); + state.popn(argc); + builder.switch(selector, cases, default_block, default_args, span)?; + state.reachable = false; + Ok(()) +} + +fn translate_block( + blockty: &wasmparser::BlockType, + builder: &mut FunctionBuilderExt<'_, B>, + state: &mut FuncTranslationState, + mod_types: &ModuleTypesBuilder, + diagnostics: &DiagnosticsHandler, + span: SourceSpan, +) -> WasmResult<()> { + let blockty = BlockType::from_wasm(blockty, mod_types, diagnostics)?; + let next = builder.create_block_with_params(blockty.results.clone(), span); + state.push_block(next, blockty.params.len(), blockty.results.len()); + Ok(()) +} + +fn translate_end( + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult<()> { + // The `End` instruction pops the last control frame from the control stack, seals + // the destination block (since `br` instructions targeting it only appear inside the + // block and have already been translated) and modify the value stack to use the + // possible `Block`'s arguments values. + let frame = state.control_stack.pop().unwrap(); + let next_block = frame.following_code(); + let return_count = frame.num_return_values(); + let return_args = state.peekn_mut(return_count); + + builder.br(next_block, return_args.iter().cloned(), span)?; + + // You might expect that if we just finished an `if` block that + // didn't have a corresponding `else` block, then we would clean + // up our duplicate set of parameters that we pushed earlier + // right here. However, we don't have to explicitly do that, + // since we truncate the stack back to the original height + // below. + + builder.switch_to_block(next_block); + builder.seal_block(next_block); + + // If it is a loop we also have to seal the body loop block + if let ControlStackFrame::Loop { header, .. } = frame { + builder.seal_block(header) + } + + frame.truncate_value_stack_to_original_size(&mut state.stack); + let next_block_args: Vec = next_block + .borrow() + .arguments() + .iter() + .map(|ba| ba.borrow().as_value_ref()) + .collect(); + state.stack.extend_from_slice(&next_block_args); + Ok(()) +} + +fn translate_else( + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult<()> { + let i = state.control_stack.len() - 1; + match state.control_stack[i] { + ControlStackFrame::If { + ref else_data, + head_is_reachable, + ref mut consequent_ends_reachable, + num_return_values, + ref blocktype, + destination, + .. + } => { + // We finished the consequent, so record its final + // reachability state. + debug_assert!(consequent_ends_reachable.is_none()); + *consequent_ends_reachable = Some(state.reachable); + + if head_is_reachable { + // We have a branch from the head of the `if` to the `else`. + state.reachable = true; + + // Ensure we have a block for the `else` block (it may have + // already been pre-allocated, see `ElseData` for details). + let else_block = match *else_data { + ElseData::NoElse { + branch_inst, + placeholder, + } => { + debug_assert_eq!(blocktype.params.len(), num_return_values); + let else_block = + builder.create_block_with_params(blocktype.params.clone(), span); + let params_len = blocktype.params.len(); + builder.br(destination, state.peekn(params_len).iter().copied(), span)?; + state.popn(params_len); + + builder.change_jump_destination(branch_inst, placeholder, else_block); + builder.seal_block(else_block); + else_block + } + ElseData::WithElse { else_block } => { + builder.br( + destination, + state.peekn(num_return_values).iter().copied(), + span, + )?; + state.popn(num_return_values); + else_block + } + }; + + // You might be expecting that we push the parameters for this + // `else` block here, something like this: + // + // state.pushn(&control_stack_frame.params); + // + // We don't do that because they are already on the top of the stack + // for us: we pushed the parameters twice when we saw the initial + // `if` so that we wouldn't have to save the parameters in the + // `ControlStackFrame` as another `Vec` allocation. + + builder.switch_to_block(else_block); + + // We don't bother updating the control frame's `ElseData` + // to `WithElse` because nothing else will read it. + } + } + _ => unreachable!(), + }; + Ok(()) +} + +fn translate_if( + blockty: &wasmparser::BlockType, + state: &mut FuncTranslationState, + builder: &mut FunctionBuilderExt<'_, B>, + mod_types: &ModuleTypesBuilder, + diagnostics: &DiagnosticsHandler, + span: SourceSpan, +) -> WasmResult<()> { + let blockty = BlockType::from_wasm(blockty, mod_types, diagnostics)?; + let cond = state.pop1(); + // cond is expected to be a i32 value + let imm = builder.imm(Immediate::I32(0), span); + let cond_i1 = builder.neq(cond, imm, span)?; + let next_block = builder.create_block(); + let (destination, else_data) = if blockty.params.eq(&blockty.results) { + // It is possible there is no `else` block, so we will only + // allocate a block for it if/when we find the `else`. For now, + // we if the condition isn't true, then we jump directly to the + // destination block following the whole `if...end`. If we do end + // up discovering an `else`, then we will allocate a block for it + // and go back and patch the jump. + let destination = builder.create_block_with_params(blockty.results.clone(), span); + let branch_inst = builder + .cond_br( + cond_i1, + next_block, + [], + destination, + state.peekn(blockty.params.len()).iter().copied(), + span, + )? + .as_operation_ref(); + ( + destination, + ElseData::NoElse { + branch_inst, + placeholder: destination, + }, + ) + } else { + // The `if` type signature is not valid without an `else` block, + // so we eagerly allocate the `else` block here. + let destination = builder.create_block_with_params(blockty.results.clone(), span); + let else_block = builder.create_block_with_params(blockty.params.clone(), span); + builder.cond_br( + cond_i1, + next_block, + [], + else_block, + state.peekn(blockty.params.len()).iter().copied(), + span, + )?; + builder.seal_block(else_block); + (destination, ElseData::WithElse { else_block }) + }; + builder.seal_block(next_block); + builder.switch_to_block(next_block); + state.push_if(destination, else_data, blockty.params.len(), blockty.results.len(), blockty); + Ok(()) +} + +fn translate_loop( + blockty: &wasmparser::BlockType, + builder: &mut FunctionBuilderExt<'_, B>, + state: &mut FuncTranslationState, + mod_types: &ModuleTypesBuilder, + diagnostics: &DiagnosticsHandler, + span: SourceSpan, +) -> WasmResult<()> { + let blockty = BlockType::from_wasm(blockty, mod_types, diagnostics)?; + let loop_body = builder.create_block_with_params(blockty.params.clone(), span); + let next = builder.create_block_with_params(blockty.results.clone(), span); + let args = state.peekn(blockty.params.len()).to_vec(); + builder.br(loop_body, args, span)?; + state.push_loop(loop_body, next, blockty.params.len(), blockty.results.len()); + state.popn(blockty.params.len()); + let loop_body_args: Vec = loop_body + .borrow() + .arguments() + .iter() + .map(|ba| ba.borrow().as_value_ref()) + .collect(); + state.stack.extend_from_slice(&loop_body_args); + builder.switch_to_block(loop_body); + Ok(()) +} + +/// Deals with a Wasm instruction located in an unreachable portion of the code. Most of them +/// are dropped but special ones like `End` or `Else` signal the potential end of the unreachable +/// portion so the translation state must be updated accordingly. +fn translate_unreachable_operator( + op: &Operator, + builder: &mut FunctionBuilderExt<'_, B>, + state: &mut FuncTranslationState, + mod_types: &ModuleTypesBuilder, + diagnostics: &DiagnosticsHandler, + span: SourceSpan, +) -> WasmResult<()> { + debug_assert!(!state.reachable); + match *op { + Operator::If { blockty } => { + // Push a placeholder control stack entry. The if isn't reachable, + // so we don't have any branches anywhere. + let blockty = BlockType::from_wasm(&blockty, mod_types, diagnostics)?; + let detached_block = builder.create_detached_block(); + state.push_if( + detached_block, + ElseData::NoElse { + branch_inst: builder.unreachable(span).as_operation_ref(), + placeholder: detached_block, + }, + 0, + 0, + blockty, + ); + } + Operator::Loop { blockty: _ } | Operator::Block { blockty: _ } => { + state.push_block(builder.create_detached_block(), 0, 0); + } + Operator::Else => { + let i = state.control_stack.len() - 1; + match state.control_stack[i] { + ControlStackFrame::If { + ref else_data, + head_is_reachable, + ref mut consequent_ends_reachable, + ref blocktype, + .. + } => { + debug_assert!(consequent_ends_reachable.is_none()); + *consequent_ends_reachable = Some(state.reachable); + + if head_is_reachable { + // We have a branch from the head of the `if` to the `else`. + state.reachable = true; + + let else_block = match *else_data { + ElseData::NoElse { + branch_inst, + placeholder, + } => { + let else_block = builder + .create_block_with_params(blocktype.params.clone(), span); + let frame = state.control_stack.last().unwrap(); + frame.truncate_value_stack_to_else_params(&mut state.stack); + + // We change the target of the branch instruction. + builder.change_jump_destination( + branch_inst, + placeholder, + else_block, + ); + builder.seal_block(else_block); + else_block + } + ElseData::WithElse { else_block } => { + let frame = state.control_stack.last().unwrap(); + frame.truncate_value_stack_to_else_params(&mut state.stack); + else_block + } + }; + + builder.switch_to_block(else_block); + + // Again, no need to push the parameters for the `else`, + // since we already did when we saw the original `if`. See + // the comment for translating `Operator::Else` in + // `translate_operator` for details. + } + } + _ => unreachable!(), + } + } + Operator::End => { + let stack = &mut state.stack; + let control_stack = &mut state.control_stack; + let frame = control_stack.pop().unwrap(); + + // Pop unused parameters from stack. + frame.truncate_value_stack_to_original_size(stack); + + let reachable_anyway = match frame { + // If it is a loop we also have to seal the body loop block + ControlStackFrame::Loop { header, .. } => { + builder.seal_block(header); + // And loops can't have branches to the end. + false + } + // If we never set `consequent_ends_reachable` then that means + // we are finishing the consequent now, and there was no + // `else`. Whether the following block is reachable depends only + // on if the head was reachable. + ControlStackFrame::If { + head_is_reachable, + consequent_ends_reachable: None, + .. + } => head_is_reachable, + // Since we are only in this function when in unreachable code, + // we know that the alternative just ended unreachable. Whether + // the following block is reachable depends on if the consequent + // ended reachable or not. + ControlStackFrame::If { + head_is_reachable, + consequent_ends_reachable: Some(consequent_ends_reachable), + .. + } => head_is_reachable && consequent_ends_reachable, + // All other control constructs are already handled. + _ => false, + }; + + if frame.exit_is_branched_to() || reachable_anyway { + builder.switch_to_block(frame.following_code()); + builder.seal_block(frame.following_code()); + + // And add the return values of the block but only if the next block is reachable + // (which corresponds to testing if the stack depth is 1) + let next_block_args: Vec = frame + .following_code() + .borrow() + .arguments() + .iter() + .map(|ba| ba.borrow().as_value_ref()) + .collect(); + stack.extend_from_slice(&next_block_args); + state.reachable = true; + } + } + _ => { + // We don't translate because this is unreachable code + } + } + + Ok(()) +} diff --git a/frontend/wasm/src/code_translator/tests.rs b/frontend/wasm/src/code_translator/tests.rs new file mode 100644 index 000000000..c24e006c1 --- /dev/null +++ b/frontend/wasm/src/code_translator/tests.rs @@ -0,0 +1,1133 @@ +use core::fmt::Write; +use std::rc::Rc; + +use midenc_expect_test::expect_file; +use midenc_hir::{dialects::builtin, Op, Operation, WalkResult}; + +use crate::{translate, WasmTranslationConfig}; + +/// Check IR generated for a Wasm op(s). +/// Wrap Wasm ops in a function and check the IR generated for the entry block of that function. +fn check_op(wat_op: &str, expected_ir: midenc_expect_test::ExpectFile) { + let ctx = midenc_hir::Context::default(); + let context = Rc::new(ctx); + + let wat = format!( + r#" + (module + (memory (;0;) 16384) + (global $MyGlobalVal (mut i32) i32.const 42) + (func $test_wrapper + {wat_op} + ) + (export "test_wrapper" (func $test_wrapper)) + )"#, + ); + let wasm = wat::parse_str(wat).unwrap(); + let output = translate(&wasm, &WasmTranslationConfig::default(), context.clone()) + .map_err(|e| { + if let Some(labels) = e.labels() { + for label in labels { + eprintln!("{}", label.label().unwrap()); + } + } + let report = midenc_session::diagnostics::PrintDiagnostic::new(e).to_string(); + eprintln!("{report}"); + }) + .unwrap(); + + let component = output.component.borrow(); + let mut w = String::new(); + component + .as_operation() + .prewalk(|op: &Operation| { + if let Some(_function) = op.downcast_ref::() { + match writeln!(&mut w, "{op}") { + Ok(_) => WalkResult::Skip, + Err(err) => WalkResult::Break(err), + } + } else { + WalkResult::Continue(()) + } + }) + .into_result() + .unwrap(); + + expected_ir.assert_eq(&w); +} + +#[test] +fn memory_grow() { + check_op( + r#" + i32.const 1 + memory.grow + drop + "#, + expect_file!["expected/memory_grow.hir"], + ) +} + +#[test] +fn memory_size() { + check_op( + r#" + memory.size + drop + "#, + expect_file!["./expected/memory_size.hir"], + ) +} + +#[test] +fn memory_copy() { + check_op( + r#" + i32.const 20 ;; dst + i32.const 10 ;; src + i32.const 1 ;; len + memory.copy + "#, + expect_file!["./expected/memory_copy.hir"], + ) +} + +#[test] +fn i32_load8_u() { + check_op( + r#" + i32.const 1024 + i32.load8_u + drop + "#, + expect_file!["./expected/i32_load8_u.hir"], + ) +} + +#[test] +fn i32_load16_u() { + check_op( + r#" + i32.const 1024 + i32.load16_u + drop + "#, + expect_file!["./expected/i32_load16_u.hir"], + ) +} + +#[test] +fn i32_load8_s() { + check_op( + r#" + i32.const 1024 + i32.load8_s + drop + "#, + expect_file!["./expected/i32_load8_s.hir"], + ) +} + +#[test] +fn i32_load16_s() { + check_op( + r#" + i32.const 1024 + i32.load16_s + drop + "#, + expect_file!["./expected/i32_load16_s.hir"], + ) +} + +#[test] +fn i64_load8_u() { + check_op( + r#" + i32.const 1024 + i64.load8_u + drop + "#, + expect_file!["./expected/i64_load8_u.hir"], + ) +} + +#[test] +fn i64_load16_u() { + check_op( + r#" + i32.const 1024 + i64.load16_u + drop + "#, + expect_file!["./expected/i64_load16_u.hir"], + ) +} + +#[test] +fn i64_load8_s() { + check_op( + r#" + i32.const 1024 + i64.load8_s + drop + "#, + expect_file!["./expected/i64_load8_s.hir"], + ) +} + +#[test] +fn i64_load16_s() { + check_op( + r#" + i32.const 1024 + i64.load16_s + drop + "#, + expect_file!["./expected/i64_load16_s.hir"], + ) +} + +#[test] +fn i64_load32_s() { + check_op( + r#" + i32.const 1024 + i64.load32_s + drop + "#, + expect_file!["./expected/i64_load32_s.hir"], + ) +} + +#[test] +fn i64_load32_u() { + check_op( + r#" + i32.const 1024 + i64.load32_u + drop + "#, + expect_file!["./expected/i64_load32_u.hir"], + ) +} + +#[test] +fn i32_load() { + check_op( + r#" + i32.const 1024 + i32.load + drop + "#, + expect_file!["./expected/i32_load.hir"], + ) +} + +#[test] +fn i64_load() { + check_op( + r#" + i32.const 1024 + i64.load + drop + "#, + expect_file!["./expected/i64_load.hir"], + ) +} + +#[test] +fn i32_store() { + check_op( + r#" + i32.const 1024 + i32.const 1 + i32.store + "#, + expect_file!["./expected/i32_store.hir"], + ) +} + +#[test] +fn i64_store() { + check_op( + r#" + i32.const 1024 + i64.const 1 + i64.store + "#, + expect_file!["./expected/i64_store.hir"], + ) +} + +#[test] +fn i32_store8() { + check_op( + r#" + i32.const 1024 + i32.const 1 + i32.store8 + "#, + expect_file!["./expected/i32_store8.hir"], + ) +} + +#[test] +fn i32_store16() { + check_op( + r#" + i32.const 1024 + i32.const 1 + i32.store16 + "#, + expect_file!["./expected/i32_store16.hir"], + ) +} + +#[test] +fn i64_store32() { + check_op( + r#" + i32.const 1024 + i64.const 1 + i64.store32 + "#, + expect_file!["./expected/i64_store32.hir"], + ) +} + +#[test] +fn i32_const() { + check_op( + r#" + i32.const 1 + drop + "#, + expect_file!["./expected/i32_const.hir"], + ) +} + +#[test] +fn i64_const() { + check_op( + r#" + i64.const 1 + drop + "#, + expect_file!["./expected/i64_const.hir"], + ) +} + +#[test] +fn i32_popcnt() { + check_op( + r#" + i32.const 1 + i32.popcnt + drop + "#, + expect_file!["./expected/i32_popcnt.hir"], + ) +} + +#[test] +fn i32_clz() { + check_op( + r#" + i32.const 1 + i32.clz + drop + "#, + expect_file!["./expected/i32_clz.hir"], + ) +} + +#[test] +fn i64_clz() { + check_op( + r#" + i64.const 1 + i64.clz + drop + "#, + expect_file!["./expected/i64_clz.hir"], + ) +} + +#[test] +fn i32_ctz() { + check_op( + r#" + i32.const 1 + i32.ctz + drop + "#, + expect_file!["./expected/i32_ctz.hir"], + ) +} + +#[test] +fn i64_ctz() { + check_op( + r#" + i64.const 1 + i64.ctz + drop + "#, + expect_file!["./expected/i64_ctz.hir"], + ) +} + +#[test] +fn i64_extend_i32_s() { + check_op( + r#" + i32.const 1 + i64.extend_i32_s + drop + "#, + expect_file!["./expected/i64_extend_i32_s.hir"], + ) +} + +#[test] +fn i64_extend_i32_u() { + check_op( + r#" + i32.const 1 + i64.extend_i32_u + drop + "#, + expect_file!["./expected/i64_extend_i32_u.hir"], + ) +} + +#[test] +fn i32_wrap_i64() { + check_op( + r#" + i64.const 1 + i32.wrap_i64 + drop + "#, + expect_file!["./expected/i32_wrap_i64.hir"], + ) +} + +#[test] +fn i32_add() { + check_op( + r#" + i32.const 3 + i32.const 1 + i32.add + drop + "#, + expect_file!["./expected/i32_add.hir"], + ) +} + +#[test] +fn i64_add() { + check_op( + r#" + i64.const 3 + i64.const 1 + i64.add + drop + "#, + expect_file!["./expected/i64_add.hir"], + ) +} + +#[test] +fn i32_and() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.and + drop + "#, + expect_file!["./expected/i32_and.hir"], + ) +} + +#[test] +fn i64_and() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.and + drop + "#, + expect_file!["./expected/i64_and.hir"], + ) +} + +#[test] +fn i32_or() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.or + drop + "#, + expect_file!["./expected/i32_or.hir"], + ) +} + +#[test] +fn i64_or() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.or + drop + "#, + expect_file!["./expected/i64_or.hir"], + ) +} + +#[test] +fn i32_sub() { + check_op( + r#" + i32.const 3 + i32.const 1 + i32.sub + drop + "#, + expect_file!["./expected/i32_sub.hir"], + ) +} + +#[test] +fn i64_sub() { + check_op( + r#" + i64.const 3 + i64.const 1 + i64.sub + drop + "#, + expect_file!["./expected/i64_sub.hir"], + ) +} + +#[test] +fn i32_xor() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.xor + drop + "#, + expect_file!["./expected/i32_xor.hir"], + ) +} + +#[test] +fn i64_xor() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.xor + drop + "#, + expect_file!["./expected/i64_xor.hir"], + ) +} + +#[test] +fn i32_shl() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.shl + drop + "#, + expect_file!["./expected/i32_shl.hir"], + ) +} + +#[test] +fn i64_shl() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.shl + drop + "#, + expect_file!["./expected/i64_shl.hir"], + ) +} + +#[test] +fn i32_shr_u() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.shr_u + drop + "#, + expect_file!["./expected/i32_shr_u.hir"], + ) +} + +#[test] +fn i64_shr_u() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.shr_u + drop + "#, + expect_file!["./expected/i64_shr_u.hir"], + ) +} + +#[test] +fn i32_shr_s() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.shr_s + drop + "#, + expect_file!["./expected/i32_shr_s.hir"], + ) +} + +#[test] +fn i64_shr_s() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.shr_s + drop + "#, + expect_file!["./expected/i64_shr_s.hir"], + ) +} + +#[test] +fn i32_rotl() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.rotl + drop + "#, + expect_file!["./expected/i32_rotl.hir"], + ) +} + +#[test] +fn i64_rotl() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.rotl + drop + "#, + expect_file!["./expected/i64_rotl.hir"], + ) +} + +#[test] +fn i32_rotr() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.rotr + drop + "#, + expect_file!["./expected/i32_rotr.hir"], + ) +} + +#[test] +fn i64_rotr() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.rotr + drop + "#, + expect_file!["./expected/i64_rotr.hir"], + ) +} + +#[test] +fn i32_mul() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.mul + drop + "#, + expect_file!["./expected/i32_mul.hir"], + ) +} + +#[test] +fn i64_mul() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.mul + drop + "#, + expect_file!["./expected/i64_mul.hir"], + ) +} + +#[test] +fn i32_div_u() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.div_u + drop + "#, + expect_file!["./expected/i32_div_u.hir"], + ) +} + +#[test] +fn i64_div_u() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.div_u + drop + "#, + expect_file!["./expected/i64_div_u.hir"], + ) +} + +#[test] +fn i32_div_s() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.div_s + drop + "#, + expect_file!["./expected/i32_div_s.hir"], + ) +} + +#[test] +fn i64_div_s() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.div_s + drop + "#, + expect_file!["./expected/i64_div_s.hir"], + ) +} + +#[test] +fn i32_rem_u() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.rem_u + drop + "#, + expect_file!["./expected/i32_rem_u.hir"], + ) +} + +#[test] +fn i64_rem_u() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.rem_u + drop + "#, + expect_file!["./expected/i64_rem_u.hir"], + ) +} + +#[test] +fn i32_rem_s() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.rem_s + drop + "#, + expect_file!["./expected/i32_rem_s.hir"], + ) +} + +#[test] +fn i64_rem_s() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.rem_s + drop + "#, + expect_file!["./expected/i64_rem_s.hir"], + ) +} + +#[test] +fn i32_lt_u() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.lt_u + drop + "#, + expect_file!["./expected/i32_lt_u.hir"], + ) +} + +#[test] +fn i64_lt_u() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.lt_u + drop + "#, + expect_file!("./expected/i64_lt_u.hir"), + ) +} + +#[test] +fn i32_lt_s() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.lt_s + drop + "#, + expect_file!("./expected/i32_lt_s.hir"), + ) +} + +#[test] +fn i64_lt_s() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.lt_s + drop + "#, + expect_file!("./expected/i64_lt_s.hir"), + ) +} + +#[test] +fn i32_le_u() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.le_u + drop + "#, + expect_file!("./expected/i32_le_u.hir"), + ) +} + +#[test] +fn i64_le_u() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.le_u + drop + "#, + expect_file!("./expected/i64_le_u.hir"), + ) +} + +#[test] +fn i32_le_s() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.le_s + drop + "#, + expect_file!("./expected/i32_le_s.hir"), + ) +} + +#[test] +fn i64_le_s() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.le_s + drop + "#, + expect_file!("./expected/i64_le_s.hir"), + ) +} + +#[test] +fn i32_gt_u() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.gt_u + drop + "#, + expect_file!("./expected/i32_gt_u.hir"), + ) +} + +#[test] +fn i64_gt_u() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.gt_u + drop + "#, + expect_file!("./expected/i64_gt_u.hir"), + ) +} + +#[test] +fn i32_gt_s() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.gt_s + drop + "#, + expect_file!("./expected/i32_gt_s.hir"), + ) +} + +#[test] +fn i64_gt_s() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.gt_s + drop + "#, + expect_file!("./expected/i64_gt_s.hir"), + ) +} + +#[test] +fn i32_ge_u() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.ge_u + drop + "#, + expect_file!("./expected/i32_ge_u.hir"), + ) +} + +#[test] +fn i64_ge_u() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.ge_u + drop + "#, + expect_file!("./expected/i64_ge_u.hir"), + ) +} + +#[test] +fn i32_ge_s() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.ge_s + drop + "#, + expect_file!("./expected/i32_ge_s.hir"), + ) +} + +#[test] +fn i64_ge_s() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.ge_s + drop + "#, + expect_file!("./expected/i64_ge_s.hir"), + ) +} + +#[test] +fn i32_eqz() { + check_op( + r#" + i32.const 2 + i32.eqz + drop + "#, + expect_file!("./expected/i32_eqz.hir"), + ) +} + +#[test] +fn i64_eqz() { + check_op( + r#" + i64.const 2 + i64.eqz + drop + "#, + expect_file!("./expected/i64_eqz.hir"), + ) +} + +#[test] +fn i32_eq() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.eq + drop + "#, + expect_file!("./expected/i32_eq.hir"), + ) +} + +#[test] +fn i64_eq() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.eq + drop + "#, + expect_file!("./expected/i64_eq.hir"), + ) +} + +#[test] +fn i32_ne() { + check_op( + r#" + i32.const 2 + i32.const 1 + i32.ne + drop + "#, + expect_file!("./expected/i32_ne.hir"), + ) +} + +#[test] +fn i64_ne() { + check_op( + r#" + i64.const 2 + i64.const 1 + i64.ne + drop + "#, + expect_file!("./expected/i64_ne.hir"), + ) +} + +#[test] +fn select_i32() { + check_op( + r#" + i64.const 3 + i64.const 7 + i32.const 1 + select + drop + "#, + expect_file!("./expected/select_i32.hir"), + ) +} + +#[test] +fn if_else() { + check_op( + r#" + i32.const 2 + if (result i32) + i32.const 3 + else + i32.const 5 + end + drop + "#, + expect_file!("./expected/if_else.hir"), + ) +} + +#[test] +fn globals() { + check_op( + r#" + + global.get $MyGlobalVal + i32.const 9 + i32.add + global.set $MyGlobalVal + "#, + expect_file!("./expected/globals.hir"), + ) +} diff --git a/frontend/wasm/src/component/build_ir.rs b/frontend/wasm/src/component/build_ir.rs new file mode 100644 index 000000000..67704d841 --- /dev/null +++ b/frontend/wasm/src/component/build_ir.rs @@ -0,0 +1,60 @@ +use std::rc::Rc; + +use midenc_hir::{dialects::builtin::BuiltinDialect, Context}; +use midenc_session::{diagnostics::Report, Session}; + +use super::{translator::ComponentTranslator, ComponentTypesBuilder, ParsedRootComponent}; +use crate::{ + component::ComponentParser, error::WasmResult, supported_component_model_features, + FrontendOutput, WasmTranslationConfig, +}; + +fn parse<'data>( + config: &WasmTranslationConfig, + wasm: &'data [u8], + session: &Session, +) -> Result<(ComponentTypesBuilder, ParsedRootComponent<'data>), Report> { + let mut validator = + wasmparser::Validator::new_with_features(supported_component_model_features()); + let mut component_types_builder = Default::default(); + let component_parser = + ComponentParser::new(config, session, &mut validator, &mut component_types_builder); + let parsed_component = component_parser.parse(wasm)?; + Ok((component_types_builder, parsed_component)) +} + +/// Translate a Wasm component binary into Miden IR component +pub fn translate_component( + wasm: &[u8], + config: &WasmTranslationConfig, + context: Rc, +) -> WasmResult { + let (mut component_types_builder, mut parsed_root_component) = + parse(config, wasm, context.session())?; + let dialect = context.get_or_register_dialect::(); + dialect.expect_registered_name::(); + // Extract component name from exported component instance + let id = { + let instance = parsed_root_component + .root_component + .exports + .iter() + .find_map(|(name, c)| match c { + super::ComponentItem::ComponentInstance(_) => Some((*name).to_string()), + _ => None, + }) + .expect("expected at least one component instance to be exported"); + + instance + .parse() + .expect("failed to parse ComponentId from Wasm component instance name") + }; + let translator = ComponentTranslator::new( + id, + &mut parsed_root_component.static_modules, + &parsed_root_component.static_components, + config, + context, + ); + translator.translate2(&parsed_root_component.root_component, &mut component_types_builder) +} diff --git a/frontend/wasm/src/component/canon_abi_utils.rs b/frontend/wasm/src/component/canon_abi_utils.rs new file mode 100644 index 000000000..4cde87b35 --- /dev/null +++ b/frontend/wasm/src/component/canon_abi_utils.rs @@ -0,0 +1,120 @@ +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{AddressSpace, Builder, PointerType, SmallVec, SourceSpan, Type, ValueRef}; + +use crate::{error::WasmResult, module::function_builder_ext::FunctionBuilderExt, WasmError}; + +/// Recursively loads primitive values from memory based on the component-level type following the +/// canonical ABI loading algorithm from +/// https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#loading +pub fn load( + fb: &mut FunctionBuilderExt, + ptr: ValueRef, + ty: &Type, + values: &mut SmallVec<[ValueRef; 8]>, + span: SourceSpan, +) -> WasmResult<()> { + match ty { + // Primitive types are loaded directly + Type::I1 + | Type::I8 + | Type::U8 + | Type::I16 + | Type::U16 + | Type::I32 + | Type::U32 + | Type::I64 + | Type::U64 + | Type::Felt => { + let ptr_type = + Type::from(PointerType::new_with_address_space(ty.clone(), AddressSpace::Byte)); + let typed_ptr = fb.inttoptr(ptr, ptr_type, span)?; + let value = fb.load(typed_ptr, span)?; + values.push(value); + } + + // Struct types are loaded field by field + Type::Struct(struct_ty) => { + // For each field in the struct, use the pre-calculated field offset + for field in struct_ty.fields() { + let field_offset = fb.i32(field.offset as i32, span); + let fielt_addr = fb.add_unchecked(ptr, field_offset, span)?; + // Recursively load the field + load(fb, fielt_addr, &field.ty, values, span)?; + } + } + + Type::List(_) => { + unimplemented!("List types are not yet supported in cross-context calls") + } + + _ => { + return Err(WasmError::Unsupported(format!( + "Unsupported type in canonical ABI loading: {ty:?}" + )) + .into()); + } + } + + Ok(()) +} + +/// Recursively stores primitive values to memory based on the component-level type following the +/// canonical ABI storing algorithm from +/// https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#storing +pub fn store( + fb: &mut FunctionBuilderExt, + ptr: ValueRef, + ty: &Type, + values: &mut impl Iterator, + span: SourceSpan, +) -> WasmResult<()> { + match ty { + // Primitive types are stored directly + Type::I1 + | Type::I8 + | Type::U8 + | Type::I16 + | Type::U16 + | Type::I32 + | Type::U32 + | Type::I64 + | Type::U64 + | Type::Felt => { + let ptr_type = + Type::from(PointerType::new_with_address_space(ty.clone(), AddressSpace::Byte)); + let src_ptr = fb.inttoptr(ptr, ptr_type, span)?; + let value_to_store = values.next().expect("Not enough values to store"); + let value = if value_to_store.borrow().ty() != ty { + fb.bitcast(value_to_store, ty.clone(), span)? + } else { + value_to_store + }; + fb.store(src_ptr, value, span)?; + } + + // Struct types are stored field by field + Type::Struct(struct_ty) => { + // For each field in the struct, use the pre-calculated field offset + for field in struct_ty.fields() { + let field_offset = fb.i32(field.offset as i32, span); + let field_addr = fb.add_unchecked(ptr, field_offset, span)?; + // Recursively store the field + store(fb, field_addr, &field.ty, values, span)?; + } + } + + Type::List(_) => { + unimplemented!("List types are not yet supported in cross-context calls") + } + + _ => { + return Err(WasmError::Unsupported(format!( + "Unsupported type in canonical ABI storing: {ty:?}" + )) + .into()); + } + } + + Ok(()) +} diff --git a/frontend/wasm/src/component/flat.rs b/frontend/wasm/src/component/flat.rs new file mode 100644 index 000000000..e4af415e5 --- /dev/null +++ b/frontend/wasm/src/component/flat.rs @@ -0,0 +1,504 @@ +//! Convertion between the Wasm CM types and the Miden cross-context ABI types. +//! +//! See https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening +//! and https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md +//! for the Wasm CM <-> core Wasm types conversion rules. + +use midenc_hir::{ + diagnostics::{miette, Diagnostic}, + AbiParam, ArgumentExtension, ArgumentPurpose, CallConv, FunctionType, PointerType, Signature, + StructType, Type, Visibility, +}; + +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum CanonicalTypeError { + #[error("unexpected use of reserved canonical abi type: {0}")] + #[diagnostic()] + Reserved(Type), + #[error("type '{0}' is not supported by the canonical abi")] + #[diagnostic()] + Unsupported(Type), +} + +/// Flattens the given CanonABI type into a list of ABI parameters. +pub fn flatten_type(ty: &Type) -> Result, CanonicalTypeError> { + // see https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening + Ok(match ty { + Type::I1 => vec![AbiParam { + ty: Type::I32, + purpose: ArgumentPurpose::Default, + extension: ArgumentExtension::Zext, + }], + Type::I8 => vec![AbiParam { + ty: Type::I32, + purpose: ArgumentPurpose::Default, + extension: ArgumentExtension::Sext, + }], + Type::U8 => vec![AbiParam { + ty: Type::I32, + purpose: ArgumentPurpose::Default, + extension: ArgumentExtension::Zext, + }], + Type::I16 => vec![AbiParam { + ty: Type::I32, + purpose: ArgumentPurpose::Default, + extension: ArgumentExtension::Sext, + }], + Type::U16 => vec![AbiParam { + ty: Type::I32, + purpose: ArgumentPurpose::Default, + extension: ArgumentExtension::Zext, + }], + Type::I32 => vec![AbiParam::new(Type::I32)], + Type::U32 => vec![AbiParam::new(Type::I32)], + Type::I64 => vec![AbiParam::new(Type::I64)], + Type::U64 => vec![AbiParam::new(Type::I64)], + Type::I128 | Type::U128 | Type::U256 => { + unimplemented!("flattening of {ty} in canonical abi") + } + Type::F64 => return Err(CanonicalTypeError::Reserved(ty.clone())), + Type::Felt => vec![AbiParam::new(Type::Felt)], + Type::Struct(struct_ty) => struct_ty + .fields() + .iter() + .map(|field| flatten_type(&field.ty)) + .try_collect::>>()? + .into_iter() + .flatten() + .collect(), + Type::Array(array_ty) => { + vec![AbiParam::new(array_ty.element_type().clone()); array_ty.len()] + } + Type::List(elem_ty) => vec![ + // pointer to the list element type + AbiParam::sret(Type::from(PointerType::new(elem_ty.as_ref().clone()))), + // length of the list + AbiParam::new(Type::I32), + ], + Type::Unknown | Type::Never | Type::Ptr(_) | Type::Function(_) => { + return Err(CanonicalTypeError::Unsupported(ty.clone())); + } + }) +} + +/// Flattens the given list of CanonABI types into a list of ABI parameters. +pub fn flatten_types(tys: &[Type]) -> Result, CanonicalTypeError> { + Ok(tys + .iter() + .map(flatten_type) + .try_collect::>>()? + .into_iter() + .flatten() + .collect()) +} + +/// Flattens the given CanonABI function type +pub fn flatten_function_type( + func_ty: &FunctionType, + cc: CallConv, +) -> Result { + // from https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening + // + // For a variety of practical reasons, we need to limit the total number of flattened + // parameters and results, falling back to storing everything in linear memory. The number of + // flattened results is currently limited to 1 due to various parts of the toolchain (notably + // the C ABI) not yet being able to express multi-value returns. Hopefully this limitation is + // temporary and can be lifted before the Component Model is fully standardized. + assert!( + func_ty.abi.is_wasm_canonical_abi(), + "unexpected function abi: {:?}", + &func_ty.abi + ); + const MAX_FLAT_PARAMS: usize = 16; + const MAX_FLAT_RESULTS: usize = 1; + let mut flat_params = flatten_types(&func_ty.params)?; + let mut flat_results = flatten_types(&func_ty.results)?; + if flat_params.len() > MAX_FLAT_PARAMS { + // from https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening + // + // When there are too many flat values, in general, a single `i32` pointer can be passed instead + // (pointing to a tuple in linear memory). When lowering into linear memory, this requires the + // Canonical ABI to call `realloc` to allocate space to put the tuple. + let tuple = Type::from(StructType::new(func_ty.params.clone())); + flat_params = vec![AbiParam::sret(Type::from(PointerType::new(tuple)))]; + } + if flat_results.len() > MAX_FLAT_RESULTS { + // from https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#flattening + // + // As an optimization, when lowering the return value of an imported function (via `canon + // lower`), the caller can have already allocated space for the return value (e.g., + // efficiently on the stack), passing in an `i32` pointer as an parameter instead of + // returning an `i32` as a return value. + assert_eq!(func_ty.results.len(), 1, "expected a single result"); + let result = func_ty.results.first().expect("unexpected empty results").clone(); + match cc { + CallConv::CanonLift => { + flat_results = vec![AbiParam::sret(Type::from(PointerType::new(result)))]; + } + CallConv::CanonLower => { + flat_params.push(AbiParam::sret(Type::from(PointerType::new(result)))); + flat_results = vec![]; + } + _ => panic!("unexpected call convention, only CanonLift and CanonLower are supported"), + } + } + Ok(Signature { + params: flat_params, + results: flat_results, + cc, + visibility: Visibility::Public, + }) +} + +/// Checks if the given function signature needs to be transformed, i.e., if it contains a pointer +pub fn needs_transformation(sig: &Signature) -> bool { + let pointer_in_params = + sig.params().iter().any(|param| param.purpose == ArgumentPurpose::StructReturn); + let pointer_in_results = sig + .results() + .iter() + .any(|result| result.purpose == ArgumentPurpose::StructReturn); + + // Check if the total size of parameters exceeds 16 felts (the maximum stack elements + // accessible to the callee using the `call` instruction) + let params_size_in_felts: usize = + sig.params().iter().map(|param| param.ty.size_in_felts()).sum(); + let exceeds_felt_limit = params_size_in_felts > 16; + + pointer_in_params || pointer_in_results || exceeds_felt_limit +} + +/// Asserts that the given core Wasm signature is equivalent to the given flattened signature +/// This checks that we flattened the Wasm CM function type correctly. +pub fn assert_core_wasm_signature_equivalence( + wasm_core_sig: &Signature, + flattened_sig: &Signature, +) { + assert_eq!( + wasm_core_sig.params().len(), + flattened_sig.params().len(), + "expected the same number of params" + ); + assert_eq!( + wasm_core_sig.results().len(), + flattened_sig.results().len(), + "expected the same number of results" + ); + for (wasm_core_param, flattened_param) in + wasm_core_sig.params().iter().zip(flattened_sig.params()) + { + assert_eq!(wasm_core_param.ty, flattened_param.ty, "expected the same param type"); + } +} + +#[cfg(test)] +mod tests { + use std::sync::Arc; + + use midenc_hir::ArrayType; + + use super::*; + + #[test] + fn test_flatten_type_integers() { + // Test I1 (bool) + let result = flatten_type(&Type::I1).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[0].extension, ArgumentExtension::Zext); + + // Test I8 + let result = flatten_type(&Type::I8).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[0].extension, ArgumentExtension::Sext); + + // Test U8 + let result = flatten_type(&Type::U8).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[0].extension, ArgumentExtension::Zext); + + // Test I16 + let result = flatten_type(&Type::I16).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[0].extension, ArgumentExtension::Sext); + + // Test U16 + let result = flatten_type(&Type::U16).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[0].extension, ArgumentExtension::Zext); + + // Test I32 + let result = flatten_type(&Type::I32).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[0].extension, ArgumentExtension::None); + + // Test U32 + let result = flatten_type(&Type::U32).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[0].extension, ArgumentExtension::None); + + // Test I64 + let result = flatten_type(&Type::I64).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I64); + assert_eq!(result[0].extension, ArgumentExtension::None); + + // Test U64 + let result = flatten_type(&Type::U64).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I64); + assert_eq!(result[0].extension, ArgumentExtension::None); + } + + #[test] + fn test_flatten_type_felt() { + let result = flatten_type(&Type::Felt).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::Felt); + assert_eq!(result[0].extension, ArgumentExtension::None); + } + + #[test] + fn test_flatten_type_struct() { + // Empty struct + let empty_struct = Type::from(StructType::new(vec![])); + let result = flatten_type(&empty_struct).unwrap(); + assert_eq!(result.len(), 0); + + // Simple struct with two fields + let struct_ty = Type::from(StructType::new(vec![Type::I32, Type::Felt])); + let result = flatten_type(&struct_ty).unwrap(); + assert_eq!(result.len(), 2); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[1].ty, Type::Felt); + + // Nested struct + let inner_struct = Type::from(StructType::new(vec![Type::I8, Type::U16])); + let outer_struct = Type::from(StructType::new(vec![Type::I32, inner_struct])); + let result = flatten_type(&outer_struct).unwrap(); + assert_eq!(result.len(), 3); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[1].ty, Type::I32); // I8 flattened to I32 + assert_eq!(result[1].extension, ArgumentExtension::Sext); + assert_eq!(result[2].ty, Type::I32); // U16 flattened to I32 + assert_eq!(result[2].extension, ArgumentExtension::Zext); + } + + #[test] + fn test_flatten_type_array() { + // Array of 3 I32s + let array_ty = Type::from(ArrayType::new(Type::I32, 3)); + let result = flatten_type(&array_ty).unwrap(); + assert_eq!(result.len(), 3); + assert!(result.iter().all(|param| param.ty == Type::I32)); + + // Array of 5 Felts + let array_ty = Type::from(ArrayType::new(Type::Felt, 5)); + let result = flatten_type(&array_ty).unwrap(); + assert_eq!(result.len(), 5); + assert!(result.iter().all(|param| param.ty == Type::Felt)); + + // Empty array + let array_ty = Type::from(ArrayType::new(Type::I32, 0)); + let result = flatten_type(&array_ty).unwrap(); + assert_eq!(result.len(), 0); + } + + #[test] + fn test_flatten_type_list() { + // List of I32s + let list_ty = Type::List(Arc::new(Type::I32)); + let result = flatten_type(&list_ty).unwrap(); + assert_eq!(result.len(), 2); + assert!(matches!(result[0].ty, Type::Ptr(_))); + assert_eq!(result[0].purpose, ArgumentPurpose::StructReturn); + assert_eq!(result[1].ty, Type::I32); // length + + // List of structs + let struct_ty = Type::from(StructType::new(vec![Type::I32, Type::Felt])); + let list_ty = Type::List(Arc::new(struct_ty)); + let result = flatten_type(&list_ty).unwrap(); + assert_eq!(result.len(), 2); + assert!(matches!(result[0].ty, Type::Ptr(_))); + assert_eq!(result[0].purpose, ArgumentPurpose::StructReturn); + assert_eq!(result[1].ty, Type::I32); // length + } + + #[test] + fn test_flatten_types() { + // Empty types + let result = flatten_types(&[]).unwrap(); + assert_eq!(result.len(), 0); + + // Single type + let result = flatten_types(&[Type::I32]).unwrap(); + assert_eq!(result.len(), 1); + assert_eq!(result[0].ty, Type::I32); + + // Multiple types + let result = flatten_types(&[Type::I32, Type::Felt, Type::I8]).unwrap(); + assert_eq!(result.len(), 3); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[1].ty, Type::Felt); + assert_eq!(result[2].ty, Type::I32); // I8 flattened to I32 + + // Types that expand (struct) + let struct_ty = Type::from(StructType::new(vec![Type::I32, Type::Felt])); + let result = flatten_types(&[Type::I32, struct_ty]).unwrap(); + assert_eq!(result.len(), 3); + assert_eq!(result[0].ty, Type::I32); + assert_eq!(result[1].ty, Type::I32); + assert_eq!(result[2].ty, Type::Felt); + } + + #[test] + fn test_flatten_function_type_simple() { + let mut func_ty = + FunctionType::new(CallConv::Fast, vec![Type::I32, Type::Felt], vec![Type::I32]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLift).unwrap(); + + assert_eq!(sig.params().len(), 2); + assert_eq!(sig.params()[0].ty, Type::I32); + assert_eq!(sig.params()[1].ty, Type::Felt); + + assert_eq!(sig.results().len(), 1); + assert_eq!(sig.results()[0].ty, Type::I32); + + assert_eq!(sig.cc, CallConv::CanonLift); + } + + #[test] + fn test_flatten_function_type_max_params() { + // Exactly 16 params - should not be transformed + let params = vec![Type::I32; 16]; + let mut func_ty = FunctionType::new(CallConv::Fast, params, vec![Type::I32]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLift).unwrap(); + + assert_eq!(sig.params().len(), 16); + assert!(sig.params().iter().all(|p| p.ty == Type::I32)); + + // 17 params - should be transformed to pointer + let params = vec![Type::I32; 17]; + let mut func_ty = FunctionType::new(CallConv::Fast, params, vec![Type::I32]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLift).unwrap(); + + assert_eq!(sig.params().len(), 1); + assert!(matches!(sig.params()[0].ty, Type::Ptr(_))); + assert_eq!(sig.params()[0].purpose, ArgumentPurpose::StructReturn); + } + + #[test] + fn test_flatten_function_type_max_results_canon_lift() { + // Single result - should not be transformed + let mut func_ty = FunctionType::new(CallConv::Fast, vec![Type::I32], vec![Type::Felt]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLift).unwrap(); + + assert_eq!(sig.results().len(), 1); + assert_eq!(sig.results()[0].ty, Type::Felt); + + // Multiple results with struct - should be transformed for CanonLift + let struct_ty = Type::from(StructType::new(vec![Type::I32, Type::Felt])); + let mut func_ty = FunctionType::new(CallConv::Fast, vec![Type::I32], vec![struct_ty]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLift).unwrap(); + + assert_eq!(sig.params().len(), 1); + assert_eq!(sig.params()[0].ty, Type::I32); + + assert_eq!(sig.results().len(), 1); + assert!(matches!(sig.results()[0].ty, Type::Ptr(_))); + assert_eq!(sig.results()[0].purpose, ArgumentPurpose::StructReturn); + } + + #[test] + fn test_flatten_function_type_max_results_canon_lower() { + // Multiple results with struct - should be transformed differently for CanonLower + let struct_ty = Type::from(StructType::new(vec![Type::I32, Type::Felt])); + let mut func_ty = FunctionType::new(CallConv::Fast, vec![Type::I32], vec![struct_ty]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLower).unwrap(); + + assert_eq!(sig.params().len(), 2); // original param + return pointer + assert_eq!(sig.params()[0].ty, Type::I32); + assert!(matches!(sig.params()[1].ty, Type::Ptr(_))); + assert_eq!(sig.params()[1].purpose, ArgumentPurpose::StructReturn); + + assert_eq!(sig.results().len(), 0); // no results for CanonLower + } + + #[test] + fn test_flatten_function_type_edge_cases() { + // Empty function + let mut func_ty = FunctionType::new(CallConv::Fast, vec![], vec![]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLift).unwrap(); + assert_eq!(sig.params().len(), 0); + assert_eq!(sig.results().len(), 0); + + // Many params that expand (structs) + let struct_ty = Type::from(StructType::new(vec![Type::I32; 10])); + let params = vec![struct_ty.clone(), struct_ty]; // 20 total params when flattened + let mut func_ty = FunctionType::new(CallConv::Fast, params, vec![]); + func_ty.abi = CallConv::CanonLift; + let sig = flatten_function_type(&func_ty, CallConv::CanonLift).unwrap(); + + assert_eq!(sig.params().len(), 1); // transformed to pointer + assert!(matches!(sig.params()[0].ty, Type::Ptr(_))); + } + + #[test] + fn test_needs_transformation() { + // No transformation needed - simple types + let sig = Signature { + params: vec![AbiParam::new(Type::I32), AbiParam::new(Type::Felt)], + results: vec![AbiParam::new(Type::I32)], + cc: CallConv::CanonLift, + visibility: Visibility::Public, + }; + assert!(!needs_transformation(&sig)); + + // Transformation needed - pointer in params + let mut sig_with_ptr = sig.clone(); + sig_with_ptr.params[0].purpose = ArgumentPurpose::StructReturn; + assert!(needs_transformation(&sig_with_ptr)); + + // Transformation needed - pointer in results + let sig = Signature { + params: vec![AbiParam::new(Type::I32)], + results: vec![AbiParam::sret(Type::from(PointerType::new(Type::I32)))], + cc: CallConv::CanonLift, + visibility: Visibility::Public, + }; + assert!(needs_transformation(&sig)); + + // Transformation needed - exceeds 16 felts + let params = vec![AbiParam::new(Type::Felt); 17]; + let sig = Signature { + params, + results: vec![], + cc: CallConv::CanonLift, + visibility: Visibility::Public, + }; + assert!(needs_transformation(&sig)); + + // Edge case - exactly 16 felts + let params = vec![AbiParam::new(Type::Felt); 16]; + let sig = Signature { + params, + results: vec![], + cc: CallConv::CanonLift, + visibility: Visibility::Public, + }; + assert!(!needs_transformation(&sig)); + } +} diff --git a/frontend/wasm/src/component/lift_exports.rs b/frontend/wasm/src/component/lift_exports.rs new file mode 100644 index 000000000..fdfe20c70 --- /dev/null +++ b/frontend/wasm/src/component/lift_exports.rs @@ -0,0 +1,315 @@ +use alloc::rc::Rc; +use core::cell::RefCell; + +use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::{BuiltinOpBuilder, ComponentBuilder, ModuleBuilder}, + CallConv, FunctionType, Ident, Op, Signature, SmallVec, SourceSpan, SymbolPath, ValueRange, + ValueRef, Visibility, +}; +use midenc_session::{diagnostics::Severity, DiagnosticsHandler}; + +use super::{ + canon_abi_utils::load, + flat::{flatten_function_type, flatten_types, needs_transformation}, +}; +use crate::{ + error::WasmResult, + module::function_builder_ext::{ + FunctionBuilderContext, FunctionBuilderExt, SSABuilderListener, + }, +}; + +pub fn generate_export_lifting_function( + component_builder: &mut ComponentBuilder, + export_func_name: &str, + export_func_ty: FunctionType, + core_export_func_path: SymbolPath, + diagnostics: &DiagnosticsHandler, +) -> WasmResult<()> { + let cross_ctx_export_sig_flat = flatten_function_type(&export_func_ty, CallConv::CanonLift) + .map_err(|e| { + let message = format!( + "Component export lifting generation. Signature for exported function \ + {core_export_func_path} requires flattening. Error: {e}" + ); + diagnostics.diagnostic(Severity::Error).with_message(message).into_report() + })?; + + // Miden Base expects the authentication component to export a single + // procedure whose name matches `auth_*` (underscore). The base WIT + // defines this function as `auth-procedure` (kebab-case). Until + // https://github.com/0xMiden/miden-base/issues/1861 lands, we map the + // authentication procedure name from `auth-procedure` to + // `auth__procedure` (double underscore) to match the current miden-base + // expectation. + // + // IMPORTANT: Restrict this rename to the authentication interface only. + // We do this by matching the exact WIT name `auth-procedure` instead of + // rewriting arbitrary names that merely start with `auth-`. + let export_func_ident = if export_func_name == "auth-procedure" { + Ident::new("auth__procedure".into(), SourceSpan::default()) + } else { + Ident::new(export_func_name.to_string().into(), SourceSpan::default()) + }; + + let core_export_module_path = core_export_func_path.without_leaf(); + let core_module_ref = component_builder + .resolve_module(&core_export_module_path) + .expect("failed to find the core module"); + + let mut core_module_builder = ModuleBuilder::new(core_module_ref); + let core_export_func_ref = core_module_builder + .get_function(core_export_func_path.name().as_str()) + .expect("failed to find the core module export function"); + // Make the lowered core WASM export private so only the lifted wrapper is + // publicly exported from the component. This prevents double-exports and + // ensures all external callers go through the Canonical ABI–correct + // wrapper generated here. + core_module_builder + .set_function_visibility(core_export_func_path.name().as_str(), Visibility::Private); + let core_export_func_sig = core_export_func_ref.borrow().signature().clone(); + + if needs_transformation(&cross_ctx_export_sig_flat) { + generate_lifting_with_transformation( + component_builder, + export_func_ident, + &export_func_ty, + cross_ctx_export_sig_flat, + core_export_func_ref, + core_export_func_sig, + &core_export_func_path, + diagnostics, + )?; + } else { + generate_direct_lifting( + component_builder, + export_func_ident, + core_export_func_ref, + core_export_func_sig, + cross_ctx_export_sig_flat, + )?; + } + + Ok(()) +} + +/// Generates a lifting function for component exports that require transformation. +/// +/// This function handles the case where a core WebAssembly export needs to be "lifted" to match +/// Component Model conventions, specifically when the function returns complex types that exceed +/// the canonical ABI limits (e.g., structs with more than one field, or types larger than 64 bits). +/// +/// In the transformation case, the core WASM function returns a pointer to the result data, +/// while the lifted component function returns the actual structured data as a tuple. +/// +/// # Arguments +/// +/// * `export_func_ident` - The identifier (name) for the exported function in the component interface. +/// This is the name that external callers will use. +/// +/// * `export_func_ty` - The original function type from the component model perspective, containing +/// the high-level types (e.g., structs, records) before flattening. +/// +/// * `cross_ctx_export_sig_flat` - The flattened component level export function signature after +/// applying canonical ABI transformations. This signature represents how the function appears in +/// cross-context calls. +/// +/// * `core_export_func_ref` - Reference to the lowered core WebAssembly function that implements the actual +/// logic. This function follows core WASM conventions (returns pointer for complex types). +/// +/// * `core_export_func_sig` - The signature of the lowered core WASM function, which may use pointer +/// returns for complex types according to canonical ABI rules. +/// +/// * `core_export_func_path` - The symbol path to the core function, used for debugging and +/// error reporting purposes. +#[allow(clippy::too_many_arguments)] +fn generate_lifting_with_transformation( + component_builder: &mut ComponentBuilder, + export_func_ident: Ident, + export_func_ty: &FunctionType, + cross_ctx_export_sig_flat: Signature, + core_export_func_ref: midenc_hir::dialects::builtin::FunctionRef, + core_export_func_sig: Signature, + core_export_func_path: &SymbolPath, + diagnostics: &DiagnosticsHandler, +) -> WasmResult<()> { + assert_eq!( + cross_ctx_export_sig_flat.results().len(), + 1, + "The flattened signature for {export_func_ident} component export function is expected to \ + have only one result", + ); + assert!( + cross_ctx_export_sig_flat.results()[0].purpose == midenc_hir::ArgumentPurpose::StructReturn, + "The flattened signature for {export_func_ident} component export function is expected to \ + have a pointer in the result", + ); + + // Extract flattened result types from the exported component-level function type + let flattened_results = flatten_types(&export_func_ty.results).map_err(|e| { + let message = format!( + "Failed to flatten result types for exported function {core_export_func_path}: {e}" + ); + diagnostics.diagnostic(Severity::Error).with_message(message).into_report() + })?; + + assert!( + cross_ctx_export_sig_flat.params().len() <= 16, + "Too many parameters in the flattened signature of {export_func_ident} component export \ + function. For cross-context calls only up to 16 felt flattened params supported (advice \ + provider is not yet supported). Try passing less data as a temporary workaround.", + ); + + // Create the signature with the flattened result types + let new_func_sig = Signature { + params: cross_ctx_export_sig_flat.params, + results: flattened_results.clone(), + cc: cross_ctx_export_sig_flat.cc, + visibility: cross_ctx_export_sig_flat.visibility, + }; + let export_func_ref = component_builder.define_function(export_func_ident, new_func_sig)?; + + let (span, context) = { + let export_func = export_func_ref.borrow(); + (export_func.name().span, export_func.as_operation().context_rc()) + }; + let func_ctx = Rc::new(RefCell::new(FunctionBuilderContext::new(context.clone()))); + let mut op_builder = + midenc_hir::OpBuilder::new(context).with_listener(SSABuilderListener::new(func_ctx)); + let mut fb = FunctionBuilderExt::new(export_func_ref, &mut op_builder); + + let entry_block = fb.current_block(); + fb.seal_block(entry_block); + let args: Vec = entry_block + .borrow() + .arguments() + .iter() + .copied() + .map(|ba| ba as ValueRef) + .collect(); + + // Export lifting: The core exported function returns a pointer to the result + // We need to: + // 1. Load the data from that pointer into the "flattened" representation (primitive types) + // 2. Return it as individual values (tuple) + + let exec = fb.exec(core_export_func_ref, core_export_func_sig, args, span)?; + + let borrow = exec.borrow(); + let results = borrow.results().all(); + + // The core function should return a single pointer (as i32) + assert_eq!(results.len(), 1, "expected single result"); + let result_ptr = results[0].borrow().as_value_ref(); + + // Load values from the core function's result pointer using recursive loading + let mut return_values = SmallVec::<[ValueRef; 8]>::new(); + + // Load results using the recursive function from canon_abi_utils + assert_eq!( + export_func_ty.results.len(), + 1, + "expected a single result in the component-level export function" + ); + let result_type = &export_func_ty.results[0]; + + load(&mut fb, result_ptr, result_type, &mut return_values, span)?; + + assert!( + return_values.len() <= 16, + "Too many return values to pass on the stack for lifted {export_func_ident} component \ + export function. The advice provider is not supported. Try return less data as a \ + temporary workaround." + ); + + // Return the loaded values + let exit_block = fb.create_block(); + fb.br(exit_block, return_values.clone(), span)?; + fb.append_block_params_for_function_returns(exit_block); + fb.seal_block(exit_block); + fb.switch_to_block(exit_block); + fb.ret(return_values, span)?; + + Ok(()) +} + +/// Generates a lifting function for component exports that don't require transformation. +/// +/// This function handles the simple case where a core WebAssembly export can be directly +/// lifted to a component export without any signature transformation. This occurs when: +/// - The function returns a single primitive value (fits in 64 bits) +/// - The function returns nothing (void) +/// - The types are already compatible between core WASM and Component Model +/// +/// # Arguments +/// +/// * `export_func_ident` - The identifier (name) for the exported function. This name will be +/// used by external callers to invoke the function. +/// +/// * `core_export_func_ref` - Reference to the underlying lowered core WebAssembly function that provides +/// the actual implementation. This function is called directly without transformation. +/// +/// * `core_export_func_sig` - The signature of the lowered core function, which is compatible with the +/// component model signature (no transformation needed). +/// +/// * `cross_ctx_export_sig_flat` - The flattened component level export function signature after +/// applying canonical ABI transformations. This signature represents how the function appears in +/// cross-context calls. +/// +/// The generated lifting function is essentially a simple wrapper that: +/// 1. Receives arguments from the component model caller +/// 2. Directly calls the core WASM function with the same arguments +/// 3. Returns the result unchanged +/// +fn generate_direct_lifting( + component_builder: &mut ComponentBuilder, + export_func_ident: Ident, + core_export_func_ref: midenc_hir::dialects::builtin::FunctionRef, + core_export_func_sig: Signature, + cross_ctx_export_sig_flat: Signature, +) -> WasmResult<()> { + let export_func_ref = + component_builder.define_function(export_func_ident, cross_ctx_export_sig_flat.clone())?; + + let (span, context) = { + let export_func = export_func_ref.borrow(); + (export_func.name().span, export_func.as_operation().context_rc()) + }; + let func_ctx = Rc::new(RefCell::new(FunctionBuilderContext::new(context.clone()))); + let mut op_builder = + midenc_hir::OpBuilder::new(context).with_listener(SSABuilderListener::new(func_ctx)); + let mut fb = FunctionBuilderExt::new(export_func_ref, &mut op_builder); + + let entry_block = fb.current_block(); + fb.seal_block(entry_block); + let args: Vec = entry_block + .borrow() + .arguments() + .iter() + .copied() + .map(|ba| ba as ValueRef) + .collect(); + + let exec = fb + .exec(core_export_func_ref, core_export_func_sig, args, span) + .expect("failed to build an exec op"); + + let borrow = exec.borrow(); + let results = ValueRange::<2>::from(borrow.results().all()); + assert!( + results.len() <= 1, + "For direct lifting of the component export function {export_func_ident} expected a \ + single result or none" + ); + + let exit_block = fb.create_block(); + fb.br(exit_block, vec![], span).expect("failed br"); + fb.seal_block(exit_block); + fb.switch_to_block(exit_block); + let returning_onty_first = results.iter().take(1); + fb.ret(returning_onty_first, span).expect("failed ret"); + + Ok(()) +} diff --git a/frontend/wasm/src/component/lower_imports.rs b/frontend/wasm/src/component/lower_imports.rs new file mode 100644 index 000000000..1433d186a --- /dev/null +++ b/frontend/wasm/src/component/lower_imports.rs @@ -0,0 +1,327 @@ +//! lowering the imports into the Miden ABI for the cross-context calls + +use alloc::rc::Rc; +use core::cell::RefCell; + +use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + diagnostics::WrapErr, + dialects::builtin::{ + BuiltinOpBuilder, ComponentBuilder, ComponentId, ModuleBuilder, WorldBuilder, + }, + ArgumentPurpose, AsValueRange, CallConv, FunctionType, Op, Signature, SourceSpan, SymbolPath, + ValueRef, +}; + +use super::{ + canon_abi_utils::store, + flat::{flatten_function_type, flatten_types, needs_transformation}, +}; +use crate::{ + callable::CallableFunction, + error::WasmResult, + module::function_builder_ext::{ + FunctionBuilderContext, FunctionBuilderExt, SSABuilderListener, + }, +}; + +/// Generates the lowering function (cross-context Miden ABI -> Wasm CABI) for the given import function. +pub fn generate_import_lowering_function( + world_builder: &mut WorldBuilder, + module_builder: &mut ModuleBuilder, + import_func_path: SymbolPath, + import_func_ty: &FunctionType, + core_func_path: SymbolPath, + core_func_sig: Signature, +) -> WasmResult { + let import_lowered_sig = flatten_function_type(import_func_ty, CallConv::CanonLower) + .wrap_err_with(|| { + format!( + "failed to generate component import lowering: signature of '{import_func_path}' \ + requires flattening" + ) + })?; + + let core_func_ref = module_builder + .define_function(core_func_path.name().into(), core_func_sig.clone()) + .expect("failed to define the core function"); + + let (span, context) = { + let core_func = core_func_ref.borrow(); + (core_func.name().span, core_func.as_operation().context_rc()) + }; + let func_ctx = Rc::new(RefCell::new(FunctionBuilderContext::new(context.clone()))); + let mut op_builder = + midenc_hir::OpBuilder::new(context).with_listener(SSABuilderListener::new(func_ctx)); + let mut fb = FunctionBuilderExt::new(core_func_ref, &mut op_builder); + + let entry_block = fb.current_block(); + fb.seal_block(entry_block); + let args: Vec = entry_block + .borrow() + .arguments() + .iter() + .copied() + .map(|ba| ba as ValueRef) + .collect(); + + if needs_transformation(&import_lowered_sig) { + generate_lowering_with_transformation( + world_builder, + &import_func_path, + import_func_ty, + core_func_path, + core_func_sig, + import_lowered_sig, + core_func_ref, + &mut fb, + &args, + span, + ) + } else { + generate_direct_lowering( + world_builder, + &import_func_path, + import_func_ty, + core_func_path, + core_func_sig, + core_func_ref, + &mut fb, + &args, + span, + ) + } +} + +/// Generates a lowering function for component imports that require transformation. +/// +/// This function handles the case where a Component Model import needs to be "lowered" to match +/// core WebAssembly conventions. This is necessary when importing functions that return complex +/// types (structs, records, tuples) which must be transformed to use pointer-based returns in +/// core WASM due to canonical ABI limitations. +/// +/// The transformation converts from Component Model style (returning structured data) to core +/// WASM style (storing results via an output pointer parameter). +/// +/// # Arguments +/// +/// * `import_func_path` - The full symbol path to the imported function, including namespace, +/// component name, and function name (e.g., "miden:component/interface@1.0.0#function"). +/// +/// * `import_func_ty` - The original Component Model function type with high-level types +/// (structs, records) before any flattening or transformation. +/// +/// * `core_func_path` - The symbol path for the core WASM function being generated. This is +/// the lowered function that will be called from core WASM code. +/// +/// * `core_func_sig` - The signature of the generated lowered core function, which includes a pointer +/// parameter for returning complex results according to canonical ABI rules. +/// +/// * `import_func_sig_flat` - The flattened signature after applying canonical lowering. Contains +/// the pointer parameter for struct returns when needed. +/// +/// * `core_func_ref` - Reference to the core function being built. This is the function that +/// will contain the lowering logic. +/// +/// * `args` - The arguments passed to the core function, including the output pointer as the +/// last argument for storing results. +/// +#[allow(clippy::too_many_arguments)] +fn generate_lowering_with_transformation( + world_builder: &mut WorldBuilder, + import_func_path: &SymbolPath, + import_func_ty: &FunctionType, + core_func_path: SymbolPath, + core_func_sig: Signature, + import_func_sig_flat: Signature, + core_func_ref: midenc_hir::dialects::builtin::FunctionRef, + fb: &mut FunctionBuilderExt<'_, impl midenc_hir::Builder>, + args: &[ValueRef], + span: SourceSpan, +) -> WasmResult { + assert!( + import_func_sig_flat.params().last().unwrap().purpose == ArgumentPurpose::StructReturn, + "The flattened component import function {import_func_path} signature should have the \ + last parameter a pointer" + ); + + assert!( + core_func_sig.results().is_empty(), + "The lowered core function {core_func_path} should not have results when using \ + out-pointer pattern" + ); + + let id = ComponentId::try_from(import_func_path) + .wrap_err("path does not start with a valid component id")?; + let component_ref = if let Some(component_ref) = world_builder.find_component(&id) { + component_ref + } else { + world_builder + .define_component(id.namespace.into(), id.name.into(), id.version) + .expect("failed to define the component") + }; + + let mut component_builder = ComponentBuilder::new(component_ref); + + // The import function's results are passed via a pointer parameter. + // This happens when the result type would flatten to more than 1 value + + // The import function should have the lifted signature (returns tuple) + // not the lowered signature with pointer parameter + let import_func_sig = flatten_function_type(import_func_ty, CallConv::CanonLower) + .wrap_err_with(|| { + format!("failed to flatten import function signature for '{import_func_path}'") + })?; + + // Extract the actual result types from the import function type + let flattened_results = flatten_types(&import_func_ty.results).wrap_err_with(|| { + format!("failed to flatten result types for import function '{import_func_path}'") + })?; + + // Remove the pointer parameter that was added for the flattened signature + let params_without_ptr = import_func_sig.params[..import_func_sig.params.len() - 1].to_vec(); + let new_import_func_sig = Signature { + params: params_without_ptr, + results: flattened_results.clone(), + cc: import_func_sig.cc, + visibility: import_func_sig.visibility, + }; + let import_func_ref = component_builder + .define_function(import_func_path.name().into(), new_import_func_sig.clone()) + .expect("failed to define the import function"); + + // Import lowering: The lowered function takes a pointer as the last parameter + // where results should be stored. The import function returns a pointer to the result. + // We need to: + // 1. Call the import function (it returns a tuple to the flattened result) + // 2. Store the data from the tuple to the output pointer which expect to hold + // flattened result + + // Get the pointer argument (last argument) where we need to store results + let output_ptr = args.last().expect("expected pointer argument"); + let args_without_ptr: Vec<_> = args[..args.len() - 1].to_vec(); + + // Call the import function - it will return a tuple to the flattened result + let call = fb.call(import_func_ref, new_import_func_sig, args_without_ptr, span)?; + + let borrow = call.borrow(); + let results = borrow.as_ref().results().as_value_range().into_owned(); + + // Store values recursively based on the component-level type + // This follows the canonical ABI store algorithm from: + // https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#storing + assert_eq!(import_func_ty.results.len(), 1, "expected a single result type"); + let result_type = &import_func_ty.results[0]; + let mut results_iter = results.into_iter(); + + store(fb, *output_ptr, result_type, &mut results_iter, span)?; + + let exit_block = fb.create_block(); + fb.br(exit_block, [], span)?; + fb.seal_block(exit_block); + fb.switch_to_block(exit_block); + fb.ret([], span)?; + + Ok(CallableFunction::Function { + wasm_id: core_func_path, + function_ref: core_func_ref, + signature: core_func_sig, + }) +} + +/// Generates a lowering function for component imports that don't require transformation. +/// +/// This function handles the simple case where a Component Model import can be directly +/// called from core WebAssembly without signature transformation. This occurs when: +/// - The function returns a single primitive value (fits in 64 bits) +/// - The function returns nothing (void) +/// - All parameters are simple types that don't need flattening +/// +/// No pointer-based parameter passing or result storing is needed in this case. +/// +/// # Arguments +/// +/// * `import_func_path` - The full symbol path to the imported function in Component Model +/// format (e.g., "miden:component/interface@1.0.0#function"). +/// +/// * `import_func_ty` - The Component Model function type. In this case, it should be simple +/// enough to not require transformation. +/// +/// * `core_func_path` - The symbol path for the generated core WASM function that performs +/// the lowering. +/// +/// * `core_func_sig` - The lowered signature of the core function, which should be compatible with +/// the component import (no transformation needed). +/// +/// * `core_func_ref` - Reference to the core function being built. +/// +/// * `args` - The arguments to pass directly to the component import function. +/// +/// # Implementation Details +/// +/// The generated lowering function is a simple pass-through that: +/// 1. Receives arguments from core WASM caller +/// 2. Directly calls the component import with the same arguments +/// 3. Returns the result unchanged (at most one simple value) +/// +#[allow(clippy::too_many_arguments)] +fn generate_direct_lowering( + world_builder: &mut WorldBuilder, + import_func_path: &SymbolPath, + import_func_ty: &FunctionType, + core_func_path: SymbolPath, + core_func_sig: Signature, + core_func_ref: midenc_hir::dialects::builtin::FunctionRef, + fb: &mut FunctionBuilderExt<'_, impl midenc_hir::Builder>, + args: &[ValueRef], + span: SourceSpan, +) -> WasmResult { + let id = ComponentId::try_from(import_func_path) + .wrap_err("path does not start with a valid component id")?; + + let component_ref = if let Some(component_ref) = world_builder.find_component(&id) { + component_ref + } else { + world_builder + .define_component(id.namespace.into(), id.name.into(), id.version) + .expect("failed to define the component") + }; + + let mut component_builder = ComponentBuilder::new(component_ref); + + let import_func_sig = flatten_function_type(import_func_ty, CallConv::CanonLift) + .wrap_err_with(|| { + format!("failed to flatten import function signature for '{import_func_path}'") + })?; + let import_func_ref = component_builder + .define_function(import_func_path.name().into(), import_func_sig.clone()) + .expect("failed to define the import function"); + + let call = fb + .call(import_func_ref, core_func_sig.clone(), args.to_vec(), span) + .expect("failed to build an exec op"); + + let borrow = call.borrow(); + let results_storage = borrow.as_ref().results(); + let results: Vec = + results_storage.iter().map(|op_res| op_res.borrow().as_value_ref()).collect(); + assert!( + results.len() <= 1, + "For direct lowering the component import function {import_func_path} expected a single \ + result or none" + ); + + let exit_block = fb.create_block(); + fb.br(exit_block, vec![], span)?; + fb.seal_block(exit_block); + fb.switch_to_block(exit_block); + let returning = results.first().cloned(); + fb.ret(returning, span).expect("failed ret"); + + Ok(CallableFunction::Function { + wasm_id: core_func_path, + function_ref: core_func_ref, + signature: core_func_sig, + }) +} diff --git a/frontend/wasm/src/component/mod.rs b/frontend/wasm/src/component/mod.rs new file mode 100644 index 000000000..4a90cdc75 --- /dev/null +++ b/frontend/wasm/src/component/mod.rs @@ -0,0 +1,16 @@ +//! Support for the Wasm component model translation +//! +//! This module contains all of the internal type definitions to parse and +//! translate the component model. + +pub(crate) mod build_ir; +mod canon_abi_utils; +mod flat; +mod lift_exports; +pub(crate) mod lower_imports; +mod parser; +mod shim_bypass; +mod translator; +mod types; + +pub use self::{parser::*, types::*}; diff --git a/frontend-wasm/src/component/parser.rs b/frontend/wasm/src/component/parser.rs similarity index 87% rename from frontend-wasm/src/component/parser.rs rename to frontend/wasm/src/component/parser.rs index a06002497..d2e039761 100644 --- a/frontend-wasm/src/component/parser.rs +++ b/frontend/wasm/src/component/parser.rs @@ -3,20 +3,17 @@ // Based on wasmtime v16.0 Wasm component translation -use std::{collections::HashMap, mem}; +use std::mem; +use cranelift_entity::PrimaryMap; use indexmap::IndexMap; -use midenc_hir::{ - cranelift_entity::PrimaryMap, - diagnostics::{IntoDiagnostic, Severity}, -}; -use midenc_session::Session; -use rustc_hash::FxHashMap; +use midenc_hir::{FxBuildHasher, FxHashMap}; +use midenc_session::{diagnostics::IntoDiagnostic, Session}; use wasmparser::{ - types::{ + component_types::{ AliasableResourceId, ComponentEntityType, ComponentFuncTypeId, ComponentInstanceTypeId, - Types, }, + types::Types, Chunk, ComponentImportName, Encoding, Parser, Payload, Validator, }; @@ -30,7 +27,6 @@ use crate::{ TableIndex, WasmType, }, }, - translation_utils::BuildFxHasher, unsupported_diag, WasmTranslationConfig, }; @@ -184,29 +180,49 @@ pub struct ParsedComponent<'data> { pub types: Option, } +#[derive(Debug, Clone)] +pub struct CanonLower { + pub func: ComponentFuncIndex, + pub lower_ty: ComponentFuncTypeId, + pub canonical_abi: SignatureIndex, + pub options: LocalCanonicalOptions, +} + +#[derive(Debug, Clone)] +pub struct CanonLift { + pub ty: ComponentFuncTypeId, + pub func: FuncIndex, + pub options: LocalCanonicalOptions, +} + +#[derive(Debug, Clone)] +// component instances +pub struct ComponentInstantiation<'data> { + pub component: ComponentIndex, + pub args: FxHashMap<&'data str, ComponentItem>, + pub ty: ComponentInstanceTypeId, +} + // NB: the type information contained in `LocalInitializer` should always point // to `wasmparser`'s type information, not ours. Component types cannot be // fully determined due to resources until instantiations are known which is // tracked during the inlining phase. This means that all type information below // is straight from `wasmparser`'s passes. +#[derive(Debug)] pub enum LocalInitializer<'data> { // imports Import(ComponentImportName<'data>, ComponentEntityType), // canonical function sections - Lower { - func: ComponentFuncIndex, - lower_ty: ComponentFuncTypeId, - canonical_abi: SignatureIndex, - options: LocalCanonicalOptions, - }, - Lift(ComponentFuncTypeId, FuncIndex, LocalCanonicalOptions), + Lower(CanonLower), + Lift(CanonLift), // resources Resource(AliasableResourceId, WasmType, Option), ResourceNew(AliasableResourceId, SignatureIndex), ResourceRep(AliasableResourceId, SignatureIndex), ResourceDrop(AliasableResourceId, SignatureIndex), + ResourceDropAsync(AliasableResourceId, SignatureIndex), // core wasm modules ModuleStatic(StaticModuleIndex), @@ -218,12 +234,7 @@ pub enum LocalInitializer<'data> { // components ComponentStatic(StaticComponentIndex, ClosedOverVars), - // component instances - ComponentInstantiate( - ComponentIndex, - FxHashMap<&'data str, ComponentItem>, - ComponentInstanceTypeId, - ), + ComponentInstantiate(ComponentInstantiation<'data>), ComponentSynthetic(FxHashMap<&'data str, ComponentItem>), // alias section @@ -236,13 +247,13 @@ pub enum LocalInitializer<'data> { AliasComponent(ClosedOverComponent), // export section - Export(ComponentItem), + Export(&'data str, ComponentItem), } /// The "closure environment" of components themselves. /// /// For more information see `LexicalScope`. -#[derive(Default)] +#[derive(Default, Debug)] pub struct ClosedOverVars { pub components: PrimaryMap, pub modules: PrimaryMap, @@ -252,6 +263,7 @@ pub struct ClosedOverVars { /// a component are being created. /// /// For more information see `LexicalScope`. +#[derive(Debug)] pub enum ClosedOverComponent { /// A closed over component is coming from the local component's index /// space, meaning a previously defined component is being captured. @@ -264,17 +276,21 @@ pub enum ClosedOverComponent { } /// Same as `ClosedOverComponent`, but for modules. +#[derive(Debug)] pub enum ClosedOverModule { Local(ModuleIndex), Upvar(ModuleUpvarIndex), } /// Representation of canonical ABI options. +#[derive(Debug, Clone)] pub struct LocalCanonicalOptions { pub string_encoding: StringEncoding, pub memory: Option, pub realloc: Option, pub post_return: Option, + pub is_async: bool, + pub async_callback: Option, } /// Action to take after parsing a payload. @@ -417,7 +433,10 @@ impl<'a, 'data> ComponentParser<'a, 'data> { // debug. other => { self.validator.payload(&other).into_diagnostic()?; - unsupported_diag!(&self.session.diagnostics, "unsupported section {other:?}"); + unsupported_diag!( + &self.session.diagnostics, + "unsupported component section {other:?}" + ); } } @@ -486,7 +505,9 @@ impl<'a, 'data> ComponentParser<'a, 'data> { self.validator.component_canonical_section(&s).into_diagnostic()?; for func in s { let types = self.validator.types(0).unwrap(); - let init = match func.into_diagnostic()? { + let canonical_func = func.into_diagnostic()?; + log::debug!(target: "component-parser","Processing canonical function: {canonical_func:?}"); + let init = match canonical_func { wasmparser::CanonicalFunction::Lift { type_index, core_func_index, @@ -495,7 +516,7 @@ impl<'a, 'data> ComponentParser<'a, 'data> { let ty = types.component_any_type_at(type_index).unwrap_func(); let func = FuncIndex::from_u32(core_func_index); let options = canonical_options(&options); - LocalInitializer::Lift(ty, func, options) + LocalInitializer::Lift(CanonLift { ty, func, options }) } wasmparser::CanonicalFunction::Lower { func_index, @@ -507,12 +528,12 @@ impl<'a, 'data> ComponentParser<'a, 'data> { let canonical_abi = self.core_func_signature(core_func_index); core_func_index += 1; - LocalInitializer::Lower { + LocalInitializer::Lower(CanonLower { func, options, canonical_abi, lower_ty, - } + }) } wasmparser::CanonicalFunction::ResourceNew { resource } => { let resource = types.component_any_type_at(resource).unwrap_resource(); @@ -526,13 +547,48 @@ impl<'a, 'data> ComponentParser<'a, 'data> { core_func_index += 1; LocalInitializer::ResourceDrop(resource, ty) } + wasmparser::CanonicalFunction::ResourceDropAsync { resource } => { + let resource = types.component_any_type_at(resource).unwrap_resource(); + let ty = self.core_func_signature(core_func_index); + core_func_index += 1; + LocalInitializer::ResourceDropAsync(resource, ty) + } wasmparser::CanonicalFunction::ResourceRep { resource } => { let resource = types.component_any_type_at(resource).unwrap_resource(); let ty = self.core_func_signature(core_func_index); core_func_index += 1; LocalInitializer::ResourceRep(resource, ty) } + wasmparser::CanonicalFunction::ErrorContextNew { .. } + | wasmparser::CanonicalFunction::ErrorContextDrop + | wasmparser::CanonicalFunction::ErrorContextDebugMessage { .. } + | wasmparser::CanonicalFunction::ThreadSpawn { .. } + | wasmparser::CanonicalFunction::ThreadAvailableParallelism + | wasmparser::CanonicalFunction::Yield { .. } + | wasmparser::CanonicalFunction::BackpressureSet + | wasmparser::CanonicalFunction::WaitableJoin + | wasmparser::CanonicalFunction::WaitableSetNew + | wasmparser::CanonicalFunction::WaitableSetDrop + | wasmparser::CanonicalFunction::WaitableSetPoll { .. } + | wasmparser::CanonicalFunction::WaitableSetWait { .. } + | wasmparser::CanonicalFunction::FutureNew { .. } + | wasmparser::CanonicalFunction::FutureRead { .. } + | wasmparser::CanonicalFunction::FutureWrite { .. } + | wasmparser::CanonicalFunction::FutureCancelRead { .. } + | wasmparser::CanonicalFunction::FutureCancelWrite { .. } + | wasmparser::CanonicalFunction::FutureCloseWritable { .. } + | wasmparser::CanonicalFunction::FutureCloseReadable { .. } + | wasmparser::CanonicalFunction::SubtaskDrop + | wasmparser::CanonicalFunction::TaskReturn { .. } + | wasmparser::CanonicalFunction::StreamNew { .. } + | wasmparser::CanonicalFunction::StreamRead { .. } + | wasmparser::CanonicalFunction::StreamWrite { .. } + | wasmparser::CanonicalFunction::StreamCancelRead { .. } + | wasmparser::CanonicalFunction::StreamCancelWrite { .. } + | wasmparser::CanonicalFunction::StreamCloseWritable { .. } + | wasmparser::CanonicalFunction::StreamCloseReadable { .. } => unimplemented!(), }; + log::debug!(target: "component-parser", "Adding canonical initializer: {init:?}"); self.result.initializers.push(init); } Ok(()) @@ -553,12 +609,16 @@ impl<'a, 'data> ComponentParser<'a, 'data> { // module and actual function translation is deferred until this // entire process has completed. self.validator.module_section(&range).into_diagnostic()?; - let parsed_module = ModuleEnvironment::new( + let module_environment = ModuleEnvironment::new( self.config, self.validator, self.types.module_types_builder_mut(), - ) - .parse(parser, &component[range.start..range.end], &self.session.diagnostics)?; + ); + let parsed_module = module_environment.parse( + parser, + &component[range.start..range.end], + &self.session.diagnostics, + )?; let static_idx = self.static_modules.push(parsed_module); self.result.initializers.push(LocalInitializer::ModuleStatic(static_idx)); // Set a fallback name for the newly added parsed module to be used if @@ -659,7 +719,7 @@ impl<'a, 'data> ComponentParser<'a, 'data> { let item = self.kind_to_item(export.kind, export.index)?; let prev = self.result.exports.insert(export.name.0, item); assert!(prev.is_none()); - self.result.initializers.push(LocalInitializer::Export(item)); + self.result.initializers.push(LocalInitializer::Export(export.name.0, item)); } Ok(()) } @@ -707,13 +767,17 @@ impl<'a, 'data> ComponentParser<'a, 'data> { raw_args: &[wasmparser::ComponentInstantiationArg<'data>], ty: ComponentInstanceTypeId, ) -> WasmResult> { - let mut args = HashMap::with_capacity_and_hasher(raw_args.len(), BuildFxHasher::default()); + let mut args = FxHashMap::with_capacity_and_hasher(raw_args.len(), FxBuildHasher); for arg in raw_args { let idx = self.kind_to_item(arg.kind, arg.index)?; args.insert(arg.name, idx); } - Ok(LocalInitializer::ComponentInstantiate(component, args, ty)) + Ok(LocalInitializer::ComponentInstantiate(ComponentInstantiation { + component, + args, + ty, + })) } /// Creates a synthetic module from the list of items currently in the @@ -722,7 +786,7 @@ impl<'a, 'data> ComponentParser<'a, 'data> { &mut self, exports: &[wasmparser::ComponentExport<'data>], ) -> WasmResult> { - let mut map = HashMap::with_capacity_and_hasher(exports.len(), BuildFxHasher::default()); + let mut map = FxHashMap::with_capacity_and_hasher(exports.len(), FxBuildHasher); for export in exports { let idx = self.kind_to_item(export.kind, export.index)?; map.insert(export.name.0, idx); @@ -825,7 +889,7 @@ fn instantiate_module<'data>( module: ModuleIndex, raw_args: &[wasmparser::InstantiationArg<'data>], ) -> LocalInitializer<'data> { - let mut args = HashMap::with_capacity_and_hasher(raw_args.len(), BuildFxHasher::default()); + let mut args = FxHashMap::with_capacity_and_hasher(raw_args.len(), FxBuildHasher); for arg in raw_args { match arg.kind { wasmparser::InstantiationArgKind::Instance => { @@ -842,7 +906,7 @@ fn instantiate_module<'data>( fn instantiate_module_from_exports<'data>( exports: &[wasmparser::Export<'data>], ) -> LocalInitializer<'data> { - let mut map = HashMap::with_capacity_and_hasher(exports.len(), BuildFxHasher::default()); + let mut map = FxHashMap::with_capacity_and_hasher(exports.len(), FxBuildHasher); for export in exports { let idx = match export.kind { wasmparser::ExternalKind::Func => { @@ -877,6 +941,8 @@ fn canonical_options(opts: &[wasmparser::CanonicalOption]) -> LocalCanonicalOpti memory: None, realloc: None, post_return: None, + is_async: false, + async_callback: None, }; for opt in opts { match opt { @@ -901,6 +967,12 @@ fn canonical_options(opts: &[wasmparser::CanonicalOption]) -> LocalCanonicalOpti let idx = FuncIndex::from_u32(*idx); ret.post_return = Some(idx); } + wasmparser::CanonicalOption::Async => { + ret.is_async = true; + } + wasmparser::CanonicalOption::Callback(idx) => { + ret.async_callback = Some(FuncIndex::from_u32(*idx)); + } } } ret @@ -928,3 +1000,13 @@ impl ParsedComponent<'_> { self.types.as_ref().unwrap().as_ref() } } + +/// Possible encodings of strings within the component model. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[allow(missing_docs)] +#[repr(u8)] +pub enum StringEncoding { + Utf8, + Utf16, + CompactUtf16, +} diff --git a/frontend/wasm/src/component/shim_bypass.rs b/frontend/wasm/src/component/shim_bypass.rs new file mode 100644 index 000000000..16b5825c5 --- /dev/null +++ b/frontend/wasm/src/component/shim_bypass.rs @@ -0,0 +1,127 @@ +use midenc_hir::{formatter::DisplayValues, FxHashMap}; + +use crate::module::module_env::ParsedModule; + +/// Checks if a module is a shim module generated by wit-component for indirect lowering. +/// +/// Shim modules have these characteristics: +/// 1. Export a table named "$imports" +/// 2. Export functions (may have numeric names or other patterns) +/// 3. Functions contain only call_indirect instructions +/// 4. Usually have minimal imports +pub fn is_shim_module(module: &ParsedModule) -> bool { + log::trace!(target: "shim-bypass", + "Checking if module is shim module. Exports: {}", + DisplayValues::new(module.module.exports.keys()) + ); + + // Check for table export named "$imports" + let has_imports_table = module.module.exports.iter().any(|(name, _)| *name == "$imports"); + log::trace!(target: "shim-bypass", "Has $imports table: {has_imports_table}"); + + // If it has the $imports table, it's likely a shim module + if has_imports_table { + // Additional checks to confirm it's a shim module + let has_function_exports = + module.module.exports.iter().any(|(_, entity)| { + matches!(entity, crate::module::types::EntityIndex::Function(_)) + }); + + // Shim modules typically have few imports (or none) + let has_few_imports = module.module.imports.len() <= 2; + + let is_shim = has_function_exports && has_few_imports; + log::debug!(target: "shim-bypass", + "Module is shim: {is_shim} (has_imports_table: {has_imports_table}, has_function_exports: {has_function_exports}, \ + has_few_imports: {has_few_imports})" + ); + + return is_shim; + } + + // Fallback: check for minimal shim module characteristics + // A shim module typically has a table export and minimal other structure + let has_table_export = module + .module + .exports + .iter() + .any(|(_, entity)| matches!(entity, crate::module::types::EntityIndex::Table(_))); + + let has_minimal_structure = + module.module.exports.len() <= 3 && module.module.imports.len() <= 2; + + let is_shim = has_table_export && has_minimal_structure; + log::debug!(target: "shim-bypass", + "Module is shim (fallback): {is_shim} (has_table_export: {has_table_export}, has_minimal_structure: {has_minimal_structure})" + ); + + is_shim +} + +/// Checks if a module is a fixup module that wires up the shim module's table. +/// +/// Fixup modules have these characteristics: +/// 1. Import a table named "$imports" +/// 2. Import functions (the actual canon lower functions) +/// 3. Have element sections that populate the table +pub fn is_fixup_module(module: &ParsedModule) -> bool { + log::trace!(target: "shim-bypass", "Checking if module is fixup module. Imports: {}", + DisplayValues::new(module.module.imports.iter())); + + // Check for table import named "$imports" + let has_imports_table = module.module.imports.iter().any(|import| { + let is_imports_table = import.module.is_empty() && import.field == "$imports"; + if is_imports_table { + log::trace!(target: "shim-bypass", "Found $imports table import"); + } + is_imports_table + }); + + log::debug!(target: "shim-bypass", "Module is fixup: {has_imports_table}"); + has_imports_table +} + +/// Extracts the mapping from shim function names to their indices in the indirect call table +pub fn extract_shim_mappings(shim_module: &ParsedModule) -> FxHashMap { + let mut mappings = FxHashMap::default(); + + // The shim module exports functions like "indirect-miden:cross-ctx-account-word/foo@1.0.0-process-word" + // Each function does a call_indirect with a specific table index + for (export_name, entity) in &shim_module.module.exports { + if export_name.starts_with("indirect-") + && matches!(entity, crate::module::types::EntityIndex::Function(_)) + { + // Extract the original import name from the shim function name + // "indirect-miden:cross-ctx-account-word/foo@1.0.0-process-word" -> "miden:cross-ctx-account-word/foo@1.0.0" + let import_name = export_name + .strip_prefix("indirect-") + .and_then(|s| { + // Find the last '-' which separates the module name from the function name + s.rfind('-').map(|pos| &s[..pos]) + }) + .unwrap_or(export_name); + + // Map the import name to its function index + // This assumes the shim module's function indices correspond directly to the imports + let func_idx = entity.unwrap_func(); + mappings.insert(import_name.to_string(), func_idx.as_u32()); + } + } + + mappings +} + +/// Information about how to bypass shim modules +#[derive(Debug, Default)] +pub struct ShimBypassInfo { + /// Indices of modules that are shim modules and should be skipped (using ModuleIndex values) + pub shim_module_indices: Vec, + /// Indices of modules that are fixup modules and should be skipped (using ModuleIndex values) + pub fixup_module_indices: Vec, + /// Static module indices that are shim modules (using StaticModuleIndex values) + pub shim_static_modules: Vec, + /// Static module indices that are fixup modules (using StaticModuleIndex values) + pub fixup_static_modules: Vec, + /// Set of module instance indices that are shim instances + pub shim_instance_indices: Vec, +} diff --git a/frontend/wasm/src/component/translator.rs b/frontend/wasm/src/component/translator.rs new file mode 100644 index 000000000..140e05d85 --- /dev/null +++ b/frontend/wasm/src/component/translator.rs @@ -0,0 +1,1063 @@ +use std::rc::Rc; + +use cranelift_entity::PrimaryMap; +use midenc_hir::{ + self as hir2, + diagnostics::Report, + dialects::builtin::{self, ComponentBuilder, ModuleBuilder, World, WorldBuilder}, + formatter::DisplayValues, + interner::Symbol, + smallvec, BuilderExt, CallConv, Context, FunctionType, FxHashMap, Ident, SymbolNameComponent, + SymbolPath, +}; +use wasmparser::{component_types::ComponentEntityType, types::TypesRef}; + +use super::{ + interface_type_to_ir, + shim_bypass::{self, ShimBypassInfo}, + CanonLift, CanonLower, ClosedOverComponent, ClosedOverModule, ComponentFuncIndex, + ComponentIndex, ComponentInstanceIndex, ComponentInstantiation, ComponentTypesBuilder, + ComponentUpvarIndex, ModuleIndex, ModuleInstanceIndex, ModuleUpvarIndex, ParsedComponent, + StaticModuleIndex, TypeComponentInstanceIndex, TypeDef, TypeFuncIndex, TypeModuleIndex, +}; +use crate::{ + component::{ + lift_exports::generate_export_lifting_function, ComponentItem, LocalInitializer, + StaticComponentIndex, + }, + error::WasmResult, + module::{ + build_ir::build_ir_module, + instance::ModuleArgument, + module_env::ParsedModule, + module_translation_state::ModuleTranslationState, + types::{EntityIndex, FuncIndex}, + }, + unsupported_diag, FrontendOutput, WasmTranslationConfig, +}; + +/// A translator from the linearized Wasm component model to the Miden IR component +pub struct ComponentTranslator<'a> { + /// The translation configuration + config: &'a WasmTranslationConfig, + + /// The list of static modules that were found during initial translation of + /// the component. + /// + /// This is used during the instantiation of these modules to ahead-of-time + /// order the arguments precisely according to what the module is defined as + /// needing which avoids the need to do string lookups or permute arguments + /// at runtime. + nested_modules: &'a mut PrimaryMap>, + + /// The list of static components that were found during initial translation of + /// the component. + /// + /// This is used when instantiating nested components to push a new + /// `ComponentFrame` with the `ParsedComponent`s here. + nested_components: &'a PrimaryMap>, + + world_builder: WorldBuilder, + result: ComponentBuilder, + + context: Rc, + + /// Information about shim modules to bypass + shim_bypass_info: ShimBypassInfo, +} + +impl<'a> ComponentTranslator<'a> { + /// Detect shim and fixup modules in the component + fn detect_shim_modules(&mut self, root_component: &ParsedComponent) { + log::debug!(target: "component-translator", + "Component has {} initializers and {} static modules", + root_component.initializers.len(), + self.nested_modules.len() + ); + + // First, check all static modules + for (static_idx, module) in self.nested_modules.iter() { + log::debug!(target: "component-translator", + "Static module {}: exports={}, imports={}", + static_idx.as_u32(), + DisplayValues::new(module.module.exports.keys()), + DisplayValues::new(module.module.imports.iter()), + ); + + if shim_bypass::is_shim_module(module) { + log::info!(target: "component-translator", "Detected shim module at static index {}", static_idx.as_u32()); + self.shim_bypass_info.shim_static_modules.push(static_idx.as_u32()); + } else if shim_bypass::is_fixup_module(module) { + log::info!(target: "component-translator", "Detected fixup module at static index {}", static_idx.as_u32()); + self.shim_bypass_info.fixup_static_modules.push(static_idx.as_u32()); + } + } + + for (i, init) in root_component.initializers.iter().enumerate() { + log::trace!(target: "component-translator", "Initializer {}: {:?}", i, std::mem::discriminant(init)); + } + + log::debug!(target: "component-translator", + "Shim bypass info: shim_static_modules={:?}, fixup_static_modules={:?}", + self.shim_bypass_info.shim_static_modules, + self.shim_bypass_info.fixup_static_modules + ); + } + + pub fn new( + id: builtin::ComponentId, + nested_modules: &'a mut PrimaryMap>, + nested_components: &'a PrimaryMap>, + config: &'a WasmTranslationConfig, + context: Rc, + ) -> Self { + let ns = hir2::Ident::with_empty_span(id.namespace); + let name = hir2::Ident::with_empty_span(id.name); + + // If a world wasn't provided to us, create one + let world_ref = match config.world { + Some(world) => world, + None => context.clone().builder().create::(Default::default())() + .expect("failed to create world"), + }; + let mut world_builder = WorldBuilder::new(world_ref); + + let raw_entity_ref = world_builder + .define_component(ns, name, id.version) + .expect("failed to define component"); + let result = ComponentBuilder::new(raw_entity_ref); + + Self { + config, + context, + nested_modules, + nested_components, + world_builder, + result, + shim_bypass_info: ShimBypassInfo::default(), + } + } + + pub fn translate2( + mut self, + root_component: &'a ParsedComponent, + types: &mut ComponentTypesBuilder, + ) -> WasmResult { + self.detect_shim_modules(root_component); + + let mut frame = ComponentFrame::new(root_component.types_ref(), FxHashMap::default()); + + for init in &root_component.initializers { + self.initializer(&mut frame, types, init)?; + } + + let account_component_metadata_bytes_vec: Vec> = self + .nested_modules + .into_iter() + .flat_map(|t| t.1.account_component_metadata_bytes.map(|slice| slice.to_vec())) + .collect(); + assert!( + account_component_metadata_bytes_vec.len() <= 1, + "unexpected multiple core Wasm module to have account component metadata section", + ); + let account_component_metadata_bytes = + account_component_metadata_bytes_vec.first().map(ToOwned::to_owned); + + let output = FrontendOutput { + component: self.result.component, + account_component_metadata_bytes, + }; + Ok(output) + } + + fn initializer( + &mut self, + frame: &mut ComponentFrame<'a>, + types: &mut ComponentTypesBuilder, + init: &'a LocalInitializer<'a>, + ) -> WasmResult<()> { + log::trace!(target: "component-translator", "init: {init:?}"); + match init { + LocalInitializer::Import(name, ty) => { + match frame.args.get(name.0) { + Some(arg) => { + frame.push_item(arg.clone()); + } + + // Not all arguments need to be provided for instantiation, namely the root + // component doesn't require structural type imports to be satisfied. + None => { + match ty { + ComponentEntityType::Instance(_) => { + self.component_import(frame, types, name, ty)?; + } + _ => { + unsupported_diag!( + self.context.diagnostics(), + "Importing of {:?} is not yet supported", + ty + ) + } + }; + } + }; + } + LocalInitializer::Lower(lower) => { + log::debug!(target: "component-translator", "Adding canon lower function: {lower:?}"); + frame.funcs.push(CoreDef::Lower(lower.clone())); + } + LocalInitializer::Lift(lift) => { + frame.component_funcs.push(ComponentFuncDef::Lifted(lift.clone())); + } + LocalInitializer::Resource(..) => { + unsupported_diag!( + self.context.diagnostics(), + "Resource initializers are not supported" + ) + } + LocalInitializer::ResourceNew(..) => { + unsupported_diag!(self.context.diagnostics(), "Resource creation is not supported") + } + LocalInitializer::ResourceRep(..) => { + unsupported_diag!( + self.context.diagnostics(), + "Resource representation is not supported" + ) + } + LocalInitializer::ResourceDrop(..) | LocalInitializer::ResourceDropAsync(..) => { + unsupported_diag!(self.context.diagnostics(), "Resource dropping is not supported") + } + LocalInitializer::ModuleStatic(static_module_idx) => { + let module_idx = frame.modules.len() as u32; + frame.modules.push(ModuleDef::Static(*static_module_idx)); + + // Track the mapping from frame module index to static module index for shim/fixup modules + if self.shim_bypass_info.shim_static_modules.contains(&static_module_idx.as_u32()) { + log::warn!(target: "component-translator", + "Marking frame module {} as shim module (static {})", + module_idx, + static_module_idx.as_u32() + ); + self.shim_bypass_info.shim_module_indices.push(module_idx); + } else if self + .shim_bypass_info + .fixup_static_modules + .contains(&static_module_idx.as_u32()) + { + log::warn!(target: "component-translator", + "Marking frame module {} as fixup module (static {})", + module_idx, + static_module_idx.as_u32() + ); + self.shim_bypass_info.fixup_module_indices.push(module_idx); + } + } + LocalInitializer::ModuleInstantiate(module_idx, ref args) => { + self.module_instantiation(frame, types, module_idx, args)?; + } + LocalInitializer::ModuleSynthetic(entities) => { + // Check if this synthetic module contains shim exports + // If so, we need to track this as a shim-related instance + let mut is_shim_related = false; + for (name, _) in entities.iter() { + if frame.funcs.iter().any(|(_, f)| { + if let CoreDef::Export(inst_idx, export_name) = f { + // Check if this export is from a shim instance + self.shim_bypass_info.shim_instance_indices.contains(&inst_idx.as_u32()) + && export_name == name + } else { + false + } + }) { + is_shim_related = true; + break; + } + } + + let instance_idx = frame.module_instances.len() as u32; + frame.module_instances.push(ModuleInstanceDef::Synthetic(entities)); + + if is_shim_related { + log::trace!(target: "component-translator", + "Detected shim-related synthetic instance at index {instance_idx}" + ); + // This synthetic instance contains shim exports, mark it for bypass + self.shim_bypass_info.shim_instance_indices.push(instance_idx); + } + } + LocalInitializer::ComponentStatic(idx, ref vars) => { + frame.components.push(ComponentDef { + index: *idx, + closure: ComponentClosure { + modules: vars + .modules + .iter() + .map(|(_, m)| frame.closed_over_module(m)) + .collect(), + components: vars + .components + .iter() + .map(|(_, m)| frame.closed_over_component(m)) + .collect(), + }, + }); + } + LocalInitializer::ComponentInstantiate( + instance @ ComponentInstantiation { + component, + ref args, + ty: _, + }, + ) => { + let component: &ComponentDef = &frame.components[*component]; + + let translation = &self.nested_components[component.index]; + let mut new_frame = ComponentFrame::new( + translation.types_ref(), + args.iter() + .map(|(name, item)| Ok((*name, frame.item(*item, types)?))) + .collect::>()?, + ); + log::debug!(target: "component-translator", + "Processing {} nested component initializers for instance", + translation.initializers.len() + ); + for (i, init) in translation.initializers.iter().enumerate() { + log::trace!(target: "component-translator", "Processing nested initializer {i}: {init:?}"); + self.initializer(&mut new_frame, types, init)?; + } + let instance_idx = frame + .component_instances + .push(ComponentInstanceDef::Instantiated(instance.clone())); + frame.frames.insert(instance_idx, new_frame); + } + LocalInitializer::ComponentSynthetic(_) => { + unsupported_diag!( + self.context.diagnostics(), + "Synthetic components are not yet supported" + ) + } + LocalInitializer::AliasExportFunc(module_instance_idx, name) => { + log::debug!(target: "component-translator", + "Pushing alias export to frame.funcs at index {} (module_instance: {}, name: \ + '{}')", + frame.funcs.len(), + module_instance_idx.as_u32(), + name + ); + frame.funcs.push(CoreDef::Export(*module_instance_idx, name)); + } + LocalInitializer::AliasExportTable(module_instance_idx, name) => { + // Check if this table alias is from a shim module that should be bypassed + if self + .shim_bypass_info + .shim_instance_indices + .contains(&module_instance_idx.as_u32()) + { + log::trace!(target: "component-translator", + "Skipping table alias from shim instance {} (table: {})", + module_instance_idx.as_u32(), + name + ); + // Skip table aliases from shim modules + } else { + unsupported_diag!( + self.context.diagnostics(), + "Table exports are not yet supported" + ) + } + } + LocalInitializer::AliasExportGlobal(..) => { + unsupported_diag!( + self.context.diagnostics(), + "Global exports are not yet supported" + ) + } + LocalInitializer::AliasExportMemory(..) => { + // Do nothing, assuming Rust compiled code having one memory instance. + } + LocalInitializer::AliasComponentExport(component_instance_idx, name) => { + let import = &frame.component_instances[*component_instance_idx].unwrap_import(); + let def = ComponentItemDef::from_import( + name, + types[import.ty].exports[*name], + *component_instance_idx, + ); + frame.push_item(def); + } + LocalInitializer::AliasModule(_) => { + unsupported_diag!( + self.context.diagnostics(), + "Module aliases are not yet supported" + ) + } + LocalInitializer::AliasComponent(_) => { + unsupported_diag!( + self.context.diagnostics(), + "Component aliases are not yet supported" + ) + } + LocalInitializer::Export(_name, component_item) => { + match component_item { + ComponentItem::Func(i) => { + frame.component_funcs.push(frame.component_funcs[*i].clone()); + } + ComponentItem::ComponentInstance(_) => { + let unwrap_instance = component_item.unwrap_instance(); + self.component_export(frame, types, unwrap_instance)?; + } + ComponentItem::Type(_) => { + // do nothing + } + _ => unsupported_diag!( + self.context.diagnostics(), + "Exporting of {:?} is not yet supported", + component_item + ), + } + } + } + Ok(()) + } + + fn component_export( + &mut self, + frame: &mut ComponentFrame<'a>, + types: &mut ComponentTypesBuilder, + component_instance_idx: ComponentInstanceIndex, + ) -> WasmResult<()> { + let instance = &frame.component_instances[component_instance_idx].unwrap_instantiated(); + let static_component_idx = frame.components[instance.component].index; + let parsed_component = &self.nested_components[static_component_idx]; + for (name, item) in parsed_component.exports.iter() { + if let ComponentItem::Func(f) = item { + self.define_component_export_lift_func( + frame, + types, + component_instance_idx, + name, + f, + )?; + } else { + // we're only interested in exported functions + } + } + frame.component_instances.push(ComponentInstanceDef::Export); + Ok(()) + } + + fn define_component_export_lift_func( + &mut self, + frame: &ComponentFrame<'a>, + types: &mut ComponentTypesBuilder, + component_instance_idx: ComponentInstanceIndex, + name: &str, + f: &ComponentFuncIndex, + ) -> WasmResult<()> { + let nested_frame = &frame.frames[&component_instance_idx]; + let canon_lift = nested_frame.component_funcs[*f].unwrap_canon_lift(); + let type_func_idx = types.convert_component_func_type(frame.types, canon_lift.ty).unwrap(); + + let component_types = types.resources_mut_and_types().1; + let func_ty = convert_lifted_func_ty(CallConv::CanonLift, &type_func_idx, component_types); + let core_export_func_path = self.core_module_export_func_path(frame, canon_lift); + generate_export_lifting_function( + &mut self.result, + name, + func_ty, + core_export_func_path, + self.context.diagnostics(), + )?; + Ok(()) + } + + fn core_module_export_func_path( + &self, + frame: &ComponentFrame<'a>, + canon_lift: &CanonLift, + ) -> SymbolPath { + match &frame.funcs[canon_lift.func] { + CoreDef::Export(module_instance_idx, name) => { + match &frame.module_instances[*module_instance_idx] { + ModuleInstanceDef::Instantiated { + module_idx, + args: _, + } => match frame.modules[*module_idx] { + ModuleDef::Static(static_module_idx) => { + let parsed_module = &self.nested_modules[static_module_idx]; + let func_idx = parsed_module.module.exports[*name].unwrap_func(); + let func_name = parsed_module.module.func_name(func_idx); + let module_ident = parsed_module.module.name(); + SymbolPath { + path: smallvec![ + SymbolNameComponent::Component(module_ident.as_symbol()), + SymbolNameComponent::Leaf(func_name) + ], + } + } + ModuleDef::Import(_) => { + panic!("expected static module") + } + }, + ModuleInstanceDef::Synthetic(_hash_map) => { + panic!("expected instantiated module") + } + } + } + CoreDef::Lower(canon_lower) => { + panic!("expected export, got {canon_lower:?}") + } + } + } + + fn module_instantiation( + &mut self, + frame: &mut ComponentFrame<'a>, + types: &mut ComponentTypesBuilder, + module_idx: &ModuleIndex, + args: &'a FxHashMap<&str, ModuleInstanceIndex>, + ) -> Result<(), Report> { + let instance_idx = frame.module_instances.len() as u32; + let current_module_idx = module_idx.as_u32(); + + log::debug!(target: "component-translator", + "Module instantiation: instance {} -> module {} (args: {})", + instance_idx, + current_module_idx, + DisplayValues::new(args.keys()) + ); + + // Check if this module instantiation should be skipped (shim or fixup) + if self.shim_bypass_info.shim_module_indices.contains(¤t_module_idx) { + log::warn!(target: "component-translator", + "SKIPPING translation of shim module instance {instance_idx} (module {current_module_idx})" + ); + // Push a placeholder instance but don't do any translation + frame.module_instances.push(ModuleInstanceDef::Instantiated { + module_idx: *module_idx, + args: args.clone(), + }); + // Mark this instance as a shim instance for later redirection + self.shim_bypass_info.shim_instance_indices.push(instance_idx); + return Ok(()); + } else if self.shim_bypass_info.fixup_module_indices.contains(¤t_module_idx) { + log::warn!(target: "component-translator", + "SKIPPING translation of fixup module instance {instance_idx} (module {current_module_idx})" + ); + // Push a placeholder instance but don't do any translation + frame.module_instances.push(ModuleInstanceDef::Instantiated { + module_idx: *module_idx, + args: args.clone(), + }); + return Ok(()); + } + + log::debug!(target: "component-translator", + "Proceeding with normal translation of module instance {instance_idx} (module {current_module_idx})" + ); + frame.module_instances.push(ModuleInstanceDef::Instantiated { + module_idx: *module_idx, + args: args.clone(), + }); + + let mut import_canon_lower_args: FxHashMap = + FxHashMap::default(); + match &frame.modules[*module_idx] { + ModuleDef::Static(static_module_idx) => { + let parsed_module = self.nested_modules.get_mut(*static_module_idx).unwrap(); + for module_arg in args { + let arg_module_name = module_arg.0; + let module_path = SymbolPath { + path: smallvec![ + SymbolNameComponent::Root, + SymbolNameComponent::Component(Symbol::intern(*arg_module_name)) + ], + }; + + // Check if this argument references a shim instance + let actual_instance_idx = *module_arg.1; + + let arg_module = &frame.module_instances[actual_instance_idx]; + match arg_module { + ModuleInstanceDef::Instantiated { + module_idx: _, + args: _, + } => { + unsupported_diag!( + self.context.diagnostics(), + "Instantiated module as another module instantiation argument is \ + not supported yet" + ) + } + ModuleInstanceDef::Synthetic(entities) => { + // module with CanonLower synthetic functions + for (func_name, entity) in entities.iter() { + log::trace!(target: "component-translator", + "Processing synthetic function '{func_name}' with entity {entity:?}" + ); + + let (signature, path) = canon_lower_func( + frame, + types, + &module_path, + func_name, + entity, + &self.shim_bypass_info, + )?; + log::trace!(target: "component-translator", + "canon_lower_func returned signature '{signature}' for function '{func_name}' \ + at path '{path}'" + ); + import_canon_lower_args + .insert(path, ModuleArgument::ComponentImport(signature)); + } + } + } + } + + let module_types = types.module_types_builder(); + parsed_module.module.set_name_fallback(self.config.source_name.clone()); + if let Some(name_override) = self.config.override_name.as_ref() { + parsed_module.module.set_name_override(name_override.clone()); + } + + let module_name = parsed_module.module.name().as_str(); + let module_ref = self.result.define_module(Ident::from(module_name)).unwrap(); + let mut module_builder = ModuleBuilder::new(module_ref); + let mut module_state = ModuleTranslationState::new( + &parsed_module.module, + &mut module_builder, + &mut self.world_builder, + module_types, + import_canon_lower_args, + self.context.diagnostics(), + )?; + + build_ir_module( + parsed_module, + module_types, + &mut module_state, + self.config, + self.context.clone(), + )?; + } + ModuleDef::Import(_) => { + panic!("Module import instantiation is not supported yet") + } + }; + Ok(()) + } + + fn component_import( + &mut self, + frame: &mut ComponentFrame<'a>, + types: &mut ComponentTypesBuilder, + name: &wasmparser::ComponentImportName<'_>, + ty: &ComponentEntityType, + ) -> Result<(), Report> { + let ty = types.convert_component_entity_type(frame.types, *ty).map_err(Report::msg)?; + let ty = match ty { + TypeDef::ComponentInstance(type_component_instance_idx) => type_component_instance_idx, + _ => panic!("expected component instance"), + }; + frame + .component_instances + .push(ComponentInstanceDef::Import(ComponentInstanceImport { + name: name.0.to_string(), + ty, + })); + + Ok(()) + } +} + +fn convert_lifted_func_ty( + abi: CallConv, + ty: &TypeFuncIndex, + component_types: &super::ComponentTypes, +) -> FunctionType { + let type_func = component_types[*ty].clone(); + let params_types = component_types[type_func.params].clone().types; + let results_types = component_types[type_func.results].clone().types; + let params = params_types + .iter() + .map(|ty| interface_type_to_ir(ty, component_types)) + .collect(); + let results = results_types + .iter() + .map(|ty| interface_type_to_ir(ty, component_types)) + .collect(); + FunctionType { + params, + results, + abi, + } +} + +fn canon_lower_func( + frame: &mut ComponentFrame, + types: &mut ComponentTypesBuilder, + module_path: &SymbolPath, + func_name: &str, + entity: &EntityIndex, + shim_bypass_info: &ShimBypassInfo, +) -> WasmResult<(FunctionType, SymbolPath)> { + let func_id = entity.unwrap_func(); + log::debug!(target: "component-translator", "canon_lower_func: function '{}', func_id: {}", func_name, func_id.as_u32()); + + let func_def = &frame.funcs[func_id]; + log::debug!(target: "component-translator", "canon_lower_func: func_def at index {}: {:?}", func_id.as_u32(), func_def); + + // Check if the function at this index is an alias export instead of a canon lower + match func_def { + CoreDef::Lower(lower) => { + let type_func_idx = types + .convert_component_func_type(frame.types, lower.lower_ty) + .map_err(Report::msg)?; + + let component_types = types.resources_mut_and_types().1; + let func_ty = + convert_lifted_func_ty(CallConv::CanonLower, &type_func_idx, component_types); + + let mut path = module_path.clone(); + path.path.push(SymbolNameComponent::Leaf(Symbol::intern(func_name))); + + Ok((func_ty, path)) + } + CoreDef::Export(module_instance_idx, export_name) => canon_lower_from_alias_export( + frame, + types, + module_path, + func_name, + module_instance_idx, + export_name, + shim_bypass_info, + ), + } +} + +/// Handles the case where a function is an alias export from a module instance +/// instead of a direct canon lower definition. +fn canon_lower_from_alias_export( + frame: &ComponentFrame, + types: &mut ComponentTypesBuilder, + module_path: &SymbolPath, + func_name: &str, + module_instance_idx: &ModuleInstanceIndex, + export_name: &str, + shim_bypass_info: &ShimBypassInfo, +) -> WasmResult<(FunctionType, SymbolPath)> { + log::debug!(target: "component-translator", + "Function {} is an alias export from module instance {} export '{}'", + func_name, + module_instance_idx.as_u32(), + export_name + ); + + // Check if this is an alias export from a bypassed shim module + if shim_bypass_info.shim_instance_indices.contains(&module_instance_idx.as_u32()) { + log::debug!(target: "component-translator", + "Alias export is from bypassed shim module instance {}", + module_instance_idx.as_u32() + ); + + // This alias export is from a bypassed shim module. The canon lower function + // that should have been provided by this shim module was lost during bypass. + // We need to reconstruct the missing canon lower function. + + // Try to find the function type by looking for it in component instance exports + let mut func_type_idx = None; + + // Search through component instances to find the export with this function name + for (idx, inst_def) in frame.component_instances.iter() { + if let ComponentInstanceDef::Import(import) = inst_def { + // Get the component instance type + let inst_ty = &types[import.ty]; + + log::debug!(target: "component-translator", + "Checking component instance {} (import '{}') for function '{}'", + idx.as_u32(), + import.name, + func_name + ); + + // Check if this instance exports the function we're looking for + if let Some(TypeDef::ComponentFunc(ty_idx)) = inst_ty.exports.get(func_name) { + func_type_idx = Some(*ty_idx); + log::debug!(target: "component-translator", + "Found function '{}' type in component instance '{}' exports: \ + TypeFuncIndex({})", + func_name, + import.name, + ty_idx.as_u32() + ); + break; + } + } + } + + if let Some(type_func_idx) = func_type_idx { + // We found the type information, use it to create the correct signature + let component_types = types.resources_mut_and_types().1; + let func_ty = + convert_lifted_func_ty(CallConv::CanonLower, &type_func_idx, component_types); + + let mut path = module_path.clone(); + path.path.push(SymbolNameComponent::Leaf(Symbol::intern(func_name))); + + log::debug!(target: "component-translator", "Created signature for '{func_name}' from type information: {func_ty}"); + + Ok((func_ty, path)) + } else { + Err(Report::msg(format!( + "Could not find type information for function '{func_name}' in component exports" + ))) + } + } else { + log::error!(target: "component-translator", + "Alias export from non-bypassed module instance {} - this should not happen", + module_instance_idx.as_u32() + ); + Err(Report::msg("Unexpected alias export from non-bypassed module")) + } +} + +#[derive(Clone, Debug)] +enum ComponentInstanceDef<'a> { + Import(ComponentInstanceImport), + Instantiated(ComponentInstantiation<'a>), + Export, +} +impl ComponentInstanceDef<'_> { + fn unwrap_import(&self) -> ComponentInstanceImport { + match self { + ComponentInstanceDef::Import(import) => import.clone(), + _ => panic!("expected import"), + } + } + + fn unwrap_instantiated(&self) -> &ComponentInstantiation<'_> { + match self { + ComponentInstanceDef::Instantiated(i) => i, + _ => panic!("expected instantiated"), + } + } +} + +#[derive(Debug, Clone)] +struct ComponentInstanceImport { + name: String, + ty: TypeComponentInstanceIndex, +} + +#[derive(Clone, Debug)] +enum ComponentFuncDef<'a> { + /// A host-imported component function. + Import(ComponentInstanceIndex, &'a str, Option), + + /// A core wasm function was lifted into a component function. + Lifted(CanonLift), +} +impl ComponentFuncDef<'_> { + fn unwrap_canon_lift(&self) -> &CanonLift { + match self { + ComponentFuncDef::Lifted(lift) => lift, + _ => panic!("expected lift, got {self:?}"), + } + } +} + +#[derive(Clone)] +enum ModuleDef { + /// A core wasm module statically defined within the original component. + /// + /// The `StaticModuleIndex` indexes into the `static_modules` map in the + /// `Inliner`. + Static(StaticModuleIndex), + + /// A core wasm module that was imported from the host. + Import(TypeModuleIndex), +} + +/// "Closure state" for a component which is resolved from the `ClosedOverVars` +/// state that was calculated during translation. +#[derive(Default, Clone)] +struct ComponentClosure { + modules: PrimaryMap, + components: PrimaryMap, +} + +#[derive(Clone)] +struct ComponentDef { + index: StaticComponentIndex, + closure: ComponentClosure, +} + +/// Definition of a core wasm item and where it can come from within a +/// component. +#[derive(Debug, Clone)] +pub enum CoreDef<'a> { + /// This item refers to an export of a previously instantiated core wasm + /// instance. + Export(ModuleInstanceIndex, &'a str), + Lower(CanonLower), +} + +impl CoreDef<'_> { + pub fn unwrap_canon_lower(&self) -> &CanonLower { + match self { + CoreDef::Lower(lower) => lower, + _ => panic!("expected lower"), + } + } +} + +enum ModuleInstanceDef<'a> { + /// A core wasm module instance was created through the instantiation of a + /// module. + Instantiated { + module_idx: ModuleIndex, + args: FxHashMap<&'a str, ModuleInstanceIndex>, + }, + + /// A "synthetic" core wasm module which is just a bag of named indices. + Synthetic(&'a FxHashMap<&'a str, EntityIndex>), +} + +/// Representation of all items which can be defined within a component. +/// +/// This is the "value" of an item defined within a component and is used to +/// represent both imports and exports. +#[derive(Clone)] +enum ComponentItemDef<'a> { + Component(ComponentDef), + Instance(ComponentInstanceDef<'a>), + Func(ComponentFuncDef<'a>), + Module(ModuleDef), + Type(TypeDef), +} + +impl<'a> ComponentItemDef<'a> { + fn from_import( + name: &'a str, + ty: TypeDef, + component_instance_idx: ComponentInstanceIndex, + ) -> ComponentItemDef<'a> { + let item = match ty { + TypeDef::Module(ty) => ComponentItemDef::Module(ModuleDef::Import(ty)), + TypeDef::ComponentInstance(ty) => { + ComponentItemDef::Instance(ComponentInstanceDef::Import(ComponentInstanceImport { + name: name.to_string(), + ty, + })) + } + TypeDef::ComponentFunc(ty) => ComponentItemDef::Func(ComponentFuncDef::Import( + component_instance_idx, + name, + Some(ty), + )), + TypeDef::Component(_ty) => panic!("root-level component imports are not supported"), + TypeDef::Interface(_) | TypeDef::Resource(_) => ComponentItemDef::Type(ty), + }; + item + } +} + +struct ComponentFrame<'a> { + types: TypesRef<'a>, + + /// The "closure arguments" to this component, or otherwise the maps indexed + /// by `ModuleUpvarIndex` and `ComponentUpvarIndex`. This is created when + /// a component is created and stored as part of a component's state during + /// inlining. + closure: ComponentClosure, + + /// The arguments to the creation of this component. + /// + /// At the root level these are all imports from the host and between + /// components this otherwise tracks how all the arguments are defined. + args: FxHashMap<&'a str, ComponentItemDef<'a>>, + + // core wasm index spaces + funcs: PrimaryMap>, + // memories: PrimaryMap>, + // tables: PrimaryMap>, + // globals: PrimaryMap>, + modules: PrimaryMap, + + // component model index spaces + component_funcs: PrimaryMap>, + module_instances: PrimaryMap>, + component_instances: PrimaryMap>, + frames: FxHashMap>, + components: PrimaryMap, +} + +impl<'a> ComponentFrame<'a> { + fn new(types: TypesRef<'a>, args: FxHashMap<&'a str, ComponentItemDef<'a>>) -> Self { + Self { + types, + funcs: PrimaryMap::new(), + component_funcs: PrimaryMap::new(), + component_instances: PrimaryMap::new(), + components: PrimaryMap::new(), + modules: PrimaryMap::new(), + closure: Default::default(), + module_instances: Default::default(), + args, + frames: Default::default(), + } + } + + fn closed_over_module(&self, index: &ClosedOverModule) -> ModuleDef { + match *index { + ClosedOverModule::Local(i) => self.modules[i].clone(), + ClosedOverModule::Upvar(i) => self.closure.modules[i].clone(), + } + } + + fn closed_over_component(&self, index: &ClosedOverComponent) -> ComponentDef { + match *index { + ClosedOverComponent::Local(i) => self.components[i].clone(), + ClosedOverComponent::Upvar(i) => self.closure.components[i].clone(), + } + } + + fn item( + &self, + index: ComponentItem, + types: &mut ComponentTypesBuilder, + ) -> WasmResult> { + Ok(match index { + ComponentItem::Func(i) => ComponentItemDef::Func(self.component_funcs[i].clone()), + ComponentItem::Component(i) => ComponentItemDef::Component(self.components[i].clone()), + ComponentItem::ComponentInstance(i) => { + ComponentItemDef::Instance(self.component_instances[i].clone()) + } + ComponentItem::Module(i) => ComponentItemDef::Module(self.modules[i].clone()), + ComponentItem::Type(t) => { + ComponentItemDef::Type(types.convert_type(self.types, t).map_err(Report::msg)?) + } + }) + } + + /// Pushes the component `item` definition provided into the appropriate + /// index space within this component. + fn push_item(&mut self, item: ComponentItemDef<'a>) { + match item { + ComponentItemDef::Func(i) => { + self.component_funcs.push(i); + } + ComponentItemDef::Module(i) => { + self.modules.push(i); + } + ComponentItemDef::Component(i) => { + self.components.push(i); + } + ComponentItemDef::Instance(i) => { + self.component_instances.push(i); + } + ComponentItemDef::Type(_ty) => {} + } + } +} diff --git a/frontend/wasm/src/component/types/mod.rs b/frontend/wasm/src/component/types/mod.rs new file mode 100644 index 000000000..561e01955 --- /dev/null +++ b/frontend/wasm/src/component/types/mod.rs @@ -0,0 +1,1797 @@ +//! Component level types for the Wasm component model. + +// Based on wasmtime v16.0 Wasm component translation + +#![allow(dead_code)] + +pub mod resources; + +use alloc::sync::Arc; +use core::{hash::Hash, ops::Index}; + +use anyhow::{bail, Result}; +use cranelift_entity::{EntityRef, PrimaryMap}; +use indexmap::IndexMap; +use midenc_hir::{FxHashMap, SmallVec}; +use wasmparser::{ + collections::IndexSet, + component_types, + names::KebabString, + types::{self, TypesRef}, +}; + +use self::resources::ResourcesBuilder; +use crate::{ + indices, + module::types::{ + convert_func_type, convert_global_type, convert_table_type, EntityType, ModuleTypes, + ModuleTypesBuilder, + }, + translation_utils::{DiscriminantSize, FlagsSize}, +}; + +/// Maximum nesting depth of a type allowed +/// +/// This constant isn't chosen via any scientific means and its main purpose is +/// to handle types via recursion without worrying about stack overflow. +const MAX_TYPE_DEPTH: u32 = 100; + +/// Canonical ABI-defined constant for the maximum number of "flat" parameters +/// to a wasm function, or the maximum number of parameters a core wasm function +/// will take for just the parameters used. Over this number the heap is used +/// for transferring parameters. +pub const MAX_FLAT_PARAMS: usize = 16; + +/// Canonical ABI-defined constant for the maximum number of "flat" results. +/// This number of results are returned directly from wasm and otherwise results +/// are transferred through memory. +pub const MAX_FLAT_RESULTS: usize = 1; + +indices! { + // ======================================================================== + // Like Core WebAssembly, the Component Model places each definition into + // one of a fixed set of index spaces, allowing the definition to be + // referred to by subsequent definitions (in the text and binary format) via + // a nonnegative integral index. When defining, validating and executing a + // component, there are 5 component-level index spaces: + + // (component) functions + // (component) values + // (component) types + // component instances + // components + + // and 2 additional core index spaces that contain core definition + // introduced by the Component Model that are not in WebAssembly 1.0 (yet: + // the module-linking proposal would add them): + + // module instances + // modules + + // for a total of 12 index spaces that need to be maintained by an implementation when, e.g., validating a component. + + // These indices are used during translation time only when we're translating a + // component at this time. + + /// Index within a component's component type index space. + pub struct ComponentTypeIndex(u32); + + /// Index within a component's module index space. + pub struct ModuleIndex(u32); + + /// Index within a component's component index space. + pub struct ComponentIndex(u32); + + /// Index within a component's module instance index space. + pub struct ModuleInstanceIndex(u32); + + /// Index within a component's component instance index space. + pub struct ComponentInstanceIndex(u32); + + /// Index within a component's component function index space. + pub struct ComponentFuncIndex(u32); + + + /// Index into the global list of modules found within an entire component. + /// + /// Parsed modulu are saved on the side to get fully translated after + /// the original component has finished being translated. + pub struct StaticModuleIndex(u32); + + // ======================================================================== + // These indices are used to lookup type information within a `TypeTables` + // structure. These represent generally deduplicated type information across + // an entire component and are a form of RTTI in a sense. + + /// Index pointing to a component's type (exports/imports with + /// component-model types) + pub struct TypeComponentIndex(u32); + + /// Index pointing to a component instance's type (exports with + /// component-model types, no imports) + pub struct TypeComponentInstanceIndex(u32); + + /// Index pointing to a core wasm module's type (exports/imports with + /// core wasm types) + pub struct TypeModuleIndex(u32); + + /// Index pointing to a component model function type with arguments/result + /// as interface types. + pub struct TypeFuncIndex(u32); + + /// Index pointing to a record type in the component model (aka a struct). + pub struct TypeRecordIndex(u32); + /// Index pointing to a variant type in the component model (aka an enum). + pub struct TypeVariantIndex(u32); + /// Index pointing to a tuple type in the component model. + pub struct TypeTupleIndex(u32); + /// Index pointing to a flags type in the component model. + pub struct TypeFlagsIndex(u32); + /// Index pointing to an enum type in the component model. + pub struct TypeEnumIndex(u32); + /// Index pointing to an option type in the component model (aka a + /// `Option`) + pub struct TypeOptionIndex(u32); + /// Index pointing to an result type in the component model (aka a + /// `Result`) + pub struct TypeResultIndex(u32); + /// Index pointing to a list type in the component model. + pub struct TypeListIndex(u32); + + /// Index pointing to a resource table within a component. + /// + /// This is a type index which isn't part of the component + /// model per-se (or at least not the binary format). This index represents + /// a pointer to a table of runtime information tracking state for resources + /// within a component. Tables are generated per-resource-per-component + /// meaning that if the exact same resource is imported into 4 subcomponents + /// then that's 5 tables: one for the defining component and one for each + /// subcomponent. + /// + /// All resource-related intrinsics operate on table-local indices which + /// indicate which table the intrinsic is modifying. Each resource table has + /// an origin resource type (defined by `ResourceIndex`) along with a + /// component instance that it's recorded for. + pub struct TypeResourceTableIndex(u32); + + /// Index pointing to a resource within a component. + /// + /// This index space covers all unique resource type definitions. For + /// example all unique imports come first and then all locally-defined + /// resources come next. Note that this does not count the number of runtime + /// tables required to track resources (that's `TypeResourceTableIndex` + /// instead). Instead this is a count of the number of unique + /// `(type (resource (rep ..)))` declarations within a component, plus + /// imports. + /// + /// This is then used for correlating various information such as + /// destructors, origin information, etc. + pub struct ResourceIndex(u32); + + /// Index pointing to a local resource defined within a component. + /// + /// This is similar to `FooIndex` and `DefinedFooIndex` for core wasm and + /// the idea here is that this is guaranteed to be a wasm-defined resource + /// which is connected to a component instance for example. + pub struct DefinedResourceIndex(u32); + + // ======================================================================== + // Index types used to identify modules and components during translation. + + /// Index into a "closed over variables" list for components used to + /// implement outer aliases. For more information on this see the + /// documentation for the `LexicalScope` structure. + pub struct ModuleUpvarIndex(u32); + + /// Same as `ModuleUpvarIndex` but for components. + pub struct ComponentUpvarIndex(u32); + + /// Same as `StaticModuleIndex` but for components. + pub struct StaticComponentIndex(u32); + + // ======================================================================== + // These indices are actually used at runtime when managing a component at + // this time. + + /// Index that represents a core wasm instance created at runtime. + /// + /// This is used to keep track of when instances are created and is able to + /// refer back to previously created instances for exports and such. + pub struct RuntimeInstanceIndex(u32); + + /// Same as `RuntimeInstanceIndex` but tracks component instances instead. + pub struct RuntimeComponentInstanceIndex(u32); + + /// Used to index imports into a `LinearComponent` + /// + /// This does not correspond to anything in the binary format for the + /// component model. + pub struct ImportIndex(u32); + + /// Index that represents a leaf item imported into a component where a + /// "leaf" means "not an instance". + /// + /// This does not correspond to anything in the binary format for the + /// component model. + pub struct RuntimeImportIndex(u32); + + /// Index that represents a lowered host function and is used to represent + /// host function lowerings with options and such. + /// + /// This does not correspond to anything in the binary format for the + /// component model. + pub struct LoweredIndex(u32); + + /// Index representing a linear memory extracted from a wasm instance. + /// + /// This does not correspond to anything in the binary format for the + /// component model. + pub struct RuntimeMemoryIndex(u32); + + /// Same as `RuntimeMemoryIndex` except for the `realloc` function. + pub struct RuntimeReallocIndex(u32); + + /// Same as `RuntimeMemoryIndex` except for the `post-return` function. + pub struct RuntimePostReturnIndex(u32); + + /// Index for all trampolines that are defined for a + /// component. + pub struct TrampolineIndex(u32); + + /// Index type of a signature (imported or defined) inside all of the core modules of the flattened root component. + pub struct SignatureIndex(u32); + +} + +/// Equivalent of `EntityIndex` but for the component model instead of core +/// wasm. +#[derive(Debug, Clone, Copy)] +pub enum ComponentItem { + Func(ComponentFuncIndex), + Module(ModuleIndex), + Component(ComponentIndex), + ComponentInstance(ComponentInstanceIndex), + Type(component_types::ComponentAnyTypeId), +} +impl ComponentItem { + pub(crate) fn unwrap_instance(&self) -> ComponentInstanceIndex { + match self { + ComponentItem::ComponentInstance(i) => *i, + _ => panic!("expected a component instance, got {self:?}"), + } + } +} + +/// Runtime information about the type information contained within a component. +/// +/// One of these is created per top-level component which describes all of the +/// types contained within the top-level component itself. Each sub-component +/// will have a pointer to this value as well. +#[derive(Default)] +pub struct ComponentTypes { + modules: PrimaryMap, + components: PrimaryMap, + component_instances: PrimaryMap, + functions: PrimaryMap, + lists: PrimaryMap, + records: PrimaryMap, + variants: PrimaryMap, + tuples: PrimaryMap, + enums: PrimaryMap, + flags: PrimaryMap, + options: PrimaryMap, + results: PrimaryMap, + resource_tables: PrimaryMap, + + module_types: ModuleTypes, +} + +impl ComponentTypes { + /// Returns the core wasm module types known within this component. + pub fn module_types(&self) -> &ModuleTypes { + &self.module_types + } + + /// Returns the canonical ABI information about the specified type. + pub fn canonical_abi(&self, ty: &InterfaceType) -> &CanonicalAbiInfo { + match ty { + InterfaceType::U8 | InterfaceType::S8 | InterfaceType::Bool => { + &CanonicalAbiInfo::SCALAR1 + } + + InterfaceType::U16 | InterfaceType::S16 => &CanonicalAbiInfo::SCALAR2, + + InterfaceType::U32 + | InterfaceType::S32 + | InterfaceType::Float32 + | InterfaceType::Char + | InterfaceType::Own(_) + | InterfaceType::Borrow(_) => &CanonicalAbiInfo::SCALAR4, + + InterfaceType::U64 | InterfaceType::S64 | InterfaceType::Float64 => { + &CanonicalAbiInfo::SCALAR8 + } + + InterfaceType::String | InterfaceType::List(_) | InterfaceType::ErrorContext => { + &CanonicalAbiInfo::POINTER_PAIR + } + + InterfaceType::Record(i) => &self[*i].abi, + InterfaceType::Variant(i) => &self[*i].abi, + InterfaceType::Tuple(i) => &self[*i].abi, + InterfaceType::Flags(i) => &self[*i].abi, + InterfaceType::Enum(i) => &self[*i].abi, + InterfaceType::Option(i) => &self[*i].abi, + InterfaceType::Result(i) => &self[*i].abi, + } + } +} + +macro_rules! impl_index { + ($(impl Index<$ty:ident> for ComponentTypes { $output:ident => $field:ident })*) => ($( + impl std::ops::Index<$ty> for ComponentTypes { + type Output = $output; + #[inline] + fn index(&self, idx: $ty) -> &$output { + &self.$field[idx] + } + } + + impl std::ops::Index<$ty> for ComponentTypesBuilder { + type Output = $output; + #[inline] + fn index(&self, idx: $ty) -> &$output { + &self.component_types[idx] + } + } + )*) +} + +impl_index! { + impl Index for ComponentTypes { TypeModule => modules } + impl Index for ComponentTypes { TypeComponent => components } + impl Index for ComponentTypes { TypeComponentInstance => component_instances } + impl Index for ComponentTypes { TypeFunc => functions } + impl Index for ComponentTypes { TypeRecord => records } + impl Index for ComponentTypes { TypeVariant => variants } + impl Index for ComponentTypes { TypeTuple => tuples } + impl Index for ComponentTypes { TypeEnum => enums } + impl Index for ComponentTypes { TypeFlags => flags } + impl Index for ComponentTypes { TypeOption => options } + impl Index for ComponentTypes { TypeResult => results } + impl Index for ComponentTypes { TypeList => lists } + impl Index for ComponentTypes { TypeResourceTable => resource_tables } +} + +// Additionally forward anything that can index `ModuleTypes` to `ModuleTypes` +// (aka `SignatureIndex`) +impl Index for ComponentTypes +where + ModuleTypes: Index, +{ + type Output = >::Output; + + fn index(&self, idx: T) -> &Self::Output { + self.module_types.index(idx) + } +} + +impl Index for ComponentTypesBuilder +where + ModuleTypes: Index, +{ + type Output = >::Output; + + fn index(&self, idx: T) -> &Self::Output { + self.module_types.index(idx) + } +} + +/// Structured used to build a [`ComponentTypes`] during translation. +/// +/// This contains tables to intern any component types found as well as +/// managing building up core wasm [`ModuleTypes`] as well. +#[derive(Default)] +pub struct ComponentTypesBuilder { + functions: FxHashMap, + lists: FxHashMap, + records: FxHashMap, + variants: FxHashMap, + tuples: FxHashMap, + enums: FxHashMap, + flags: FxHashMap, + options: FxHashMap, + results: FxHashMap, + + component_types: ComponentTypes, + module_types: ModuleTypesBuilder, + + // Cache of what the "flat" representation of all types are which is only + // used at translation. + type_info: TypeInformationCache, + + resources: ResourcesBuilder, +} + +macro_rules! intern_and_fill_flat_types { + ($me:ident, $name:ident, $val:ident) => {{ + if let Some(idx) = $me.$name.get(&$val) { + return *idx; + } + let idx = $me.component_types.$name.push($val.clone()); + let mut info = TypeInformation::new(); + info.$name($me, &$val); + let idx2 = $me.type_info.$name.push(info); + assert_eq!(idx, idx2); + $me.$name.insert($val, idx); + return idx; + }}; +} + +impl ComponentTypesBuilder { + /// Finishes this list of component types and returns the finished + /// structure. + pub fn finish(mut self) -> ComponentTypes { + self.component_types.module_types = self.module_types.finish(); + self.component_types + } + + /// Returns the underlying builder used to build up core wasm module types. + /// + /// Note that this is shared across all modules found within a component to + /// improve the wins from deduplicating function signatures. + pub fn module_types_builder(&self) -> &ModuleTypesBuilder { + &self.module_types + } + + /// Same as `module_types_builder`, but `mut`. + pub fn module_types_builder_mut(&mut self) -> &mut ModuleTypesBuilder { + &mut self.module_types + } + + /// Returns the number of resource tables allocated so far, or the maximum + /// `TypeResourceTableIndex`. + pub fn num_resource_tables(&self) -> usize { + self.component_types.resource_tables.len() + } + + /// Returns a mutable reference to the underlying `ResourcesBuilder`. + pub fn resources_mut(&mut self) -> &mut ResourcesBuilder { + &mut self.resources + } + + /// Work around the borrow checker to borrow two sub-fields simultaneously + /// externally. + pub fn resources_mut_and_types(&mut self) -> (&mut ResourcesBuilder, &ComponentTypes) { + (&mut self.resources, &self.component_types) + } + + /// Converts a wasmparser `ComponentFuncType` + pub fn convert_component_func_type( + &mut self, + types: TypesRef<'_>, + id: component_types::ComponentFuncTypeId, + ) -> Result { + let ty = &types[id]; + let params = ty + .params + .iter() + .map(|(_name, ty)| self.valtype(types, ty)) + .collect::>()?; + let results = match ty.result.as_ref() { + Some(ty) => SmallVec::from_buf([self.valtype(types, ty)?]).into_boxed_slice(), + None => Box::<[_]>::default(), + }; + let ty = TypeFunc { + params: self.new_tuple_type(params), + results: self.new_tuple_type(results), + }; + Ok(self.add_func_type(ty)) + } + + /// Converts a wasmparser `ComponentEntityType` + pub fn convert_component_entity_type( + &mut self, + types: TypesRef<'_>, + ty: component_types::ComponentEntityType, + ) -> Result { + Ok(match ty { + component_types::ComponentEntityType::Module(id) => { + TypeDef::Module(self.convert_module(types, id)?) + } + component_types::ComponentEntityType::Component(id) => { + TypeDef::Component(self.convert_component(types, id)?) + } + component_types::ComponentEntityType::Instance(id) => { + TypeDef::ComponentInstance(self.convert_instance(types, id)?) + } + component_types::ComponentEntityType::Func(id) => { + TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) + } + component_types::ComponentEntityType::Type { created, .. } => match created { + component_types::ComponentAnyTypeId::Defined(id) => { + TypeDef::Interface(self.defined_type(types, id)?) + } + component_types::ComponentAnyTypeId::Resource(id) => { + TypeDef::Resource(self.resource_id(id.resource())) + } + _ => bail!("unsupported type export"), + }, + component_types::ComponentEntityType::Value(_) => bail!("values not supported"), + }) + } + + /// Converts a wasmparser `Type` + pub fn convert_type( + &mut self, + types: TypesRef<'_>, + id: component_types::ComponentAnyTypeId, + ) -> Result { + Ok(match id { + component_types::ComponentAnyTypeId::Defined(id) => { + TypeDef::Interface(self.defined_type(types, id)?) + } + component_types::ComponentAnyTypeId::Component(id) => { + TypeDef::Component(self.convert_component(types, id)?) + } + component_types::ComponentAnyTypeId::Instance(id) => { + TypeDef::ComponentInstance(self.convert_instance(types, id)?) + } + component_types::ComponentAnyTypeId::Func(id) => { + TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?) + } + component_types::ComponentAnyTypeId::Resource(id) => { + TypeDef::Resource(self.resource_id(id.resource())) + } + }) + } + + fn convert_component( + &mut self, + types: TypesRef<'_>, + id: component_types::ComponentTypeId, + ) -> Result { + let ty = &types[id]; + let mut result = TypeComponent::default(); + for (name, ty) in ty.imports.iter() { + result + .imports + .insert(name.clone(), self.convert_component_entity_type(types, *ty)?); + } + for (name, ty) in ty.exports.iter() { + result + .exports + .insert(name.clone(), self.convert_component_entity_type(types, *ty)?); + } + Ok(self.component_types.components.push(result)) + } + + fn convert_instance( + &mut self, + types: TypesRef<'_>, + id: component_types::ComponentInstanceTypeId, + ) -> Result { + let ty = &types[id]; + let mut result = TypeComponentInstance::default(); + for (name, ty) in ty.exports.iter() { + result + .exports + .insert(name.clone(), self.convert_component_entity_type(types, *ty)?); + } + Ok(self.component_types.component_instances.push(result)) + } + + fn convert_module( + &mut self, + types: TypesRef<'_>, + id: component_types::ComponentCoreModuleTypeId, + ) -> Result { + let ty = &types[id]; + let mut result = TypeModule::default(); + for ((module, field), ty) in ty.imports.iter() { + result + .imports + .insert((module.clone(), field.clone()), self.entity_type(types, ty)?); + } + for (name, ty) in ty.exports.iter() { + result.exports.insert(name.clone(), self.entity_type(types, ty)?); + } + Ok(self.component_types.modules.push(result)) + } + + fn entity_type(&mut self, types: TypesRef<'_>, ty: &types::EntityType) -> Result { + Ok(match ty { + types::EntityType::Func(idx) => { + let ty = types[*idx].unwrap_func(); + let ty = convert_func_type(ty); + EntityType::Function(self.module_types_builder_mut().wasm_func_type(*idx, ty)) + } + types::EntityType::Table(ty) => EntityType::Table(convert_table_type(ty)), + types::EntityType::Memory(ty) => EntityType::Memory((*ty).into()), + types::EntityType::Global(ty) => EntityType::Global(convert_global_type(ty)), + types::EntityType::Tag(_) => bail!("exceptions proposal not implemented"), + }) + } + + fn defined_type( + &mut self, + types: TypesRef<'_>, + id: component_types::ComponentDefinedTypeId, + ) -> Result { + let ret = match &types[id] { + component_types::ComponentDefinedType::Primitive(ty) => ty.into(), + component_types::ComponentDefinedType::Record(e) => { + InterfaceType::Record(self.record_type(types, e)?) + } + component_types::ComponentDefinedType::Variant(e) => { + InterfaceType::Variant(self.variant_type(types, e)?) + } + component_types::ComponentDefinedType::List(e) => { + InterfaceType::List(self.list_type(types, e)?) + } + component_types::ComponentDefinedType::Tuple(e) => { + InterfaceType::Tuple(self.tuple_type(types, e)?) + } + component_types::ComponentDefinedType::Flags(e) => { + InterfaceType::Flags(self.flags_type(e)) + } + component_types::ComponentDefinedType::Enum(e) => { + InterfaceType::Enum(self.enum_type(e)) + } + component_types::ComponentDefinedType::Option(e) => { + InterfaceType::Option(self.option_type(types, e)?) + } + component_types::ComponentDefinedType::Result { ok, err } => { + InterfaceType::Result(self.result_type(types, ok, err)?) + } + component_types::ComponentDefinedType::Own(r) => { + InterfaceType::Own(self.resource_id(r.resource())) + } + component_types::ComponentDefinedType::Borrow(r) => { + InterfaceType::Borrow(self.resource_id(r.resource())) + } + component_types::ComponentDefinedType::Stream(_) + | component_types::ComponentDefinedType::Future(_) => { + unimplemented!("support for the async proposal is not implemented") + } + }; + let info = self.type_information(&ret); + if info.depth > MAX_TYPE_DEPTH { + bail!("type nesting is too deep"); + } + Ok(ret) + } + + fn valtype( + &mut self, + types: TypesRef<'_>, + ty: &component_types::ComponentValType, + ) -> Result { + match ty { + component_types::ComponentValType::Primitive(p) => Ok(p.into()), + component_types::ComponentValType::Type(id) => self.defined_type(types, *id), + } + } + + fn record_type( + &mut self, + types: TypesRef<'_>, + ty: &component_types::RecordType, + ) -> Result { + let fields = ty + .fields + .iter() + .map(|(name, ty)| { + Ok(RecordField { + name: name.to_string(), + ty: self.valtype(types, ty)?, + }) + }) + .collect::>>()?; + let abi = CanonicalAbiInfo::record( + fields.iter().map(|field| self.component_types.canonical_abi(&field.ty)), + ); + Ok(self.add_record_type(TypeRecord { fields, abi })) + } + + fn variant_type( + &mut self, + types: TypesRef<'_>, + ty: &component_types::VariantType, + ) -> Result { + let cases = ty + .cases + .iter() + .map(|(name, case)| { + if case.refines.is_some() { + bail!("refines is not supported at this time"); + } + Ok(VariantCase { + name: name.to_string(), + ty: match &case.ty.as_ref() { + Some(ty) => Some(self.valtype(types, ty)?), + None => None, + }, + }) + }) + .collect::>>()?; + let (info, abi) = VariantInfo::new( + cases + .iter() + .map(|c| c.ty.as_ref().map(|ty| self.component_types.canonical_abi(ty))), + ); + Ok(self.add_variant_type(TypeVariant { cases, abi, info })) + } + + fn tuple_type( + &mut self, + types: TypesRef<'_>, + ty: &component_types::TupleType, + ) -> Result { + let types = ty + .types + .iter() + .map(|ty| self.valtype(types, ty)) + .collect::>>()?; + Ok(self.new_tuple_type(types)) + } + + fn new_tuple_type(&mut self, types: Box<[InterfaceType]>) -> TypeTupleIndex { + let abi = + CanonicalAbiInfo::record(types.iter().map(|ty| self.component_types.canonical_abi(ty))); + self.add_tuple_type(TypeTuple { types, abi }) + } + + fn flags_type(&mut self, flags: &IndexSet) -> TypeFlagsIndex { + let flags = TypeFlags { + names: flags.iter().map(|s| s.to_string()).collect(), + abi: CanonicalAbiInfo::flags(flags.len()), + }; + self.add_flags_type(flags) + } + + fn enum_type(&mut self, variants: &IndexSet) -> TypeEnumIndex { + let names = variants.iter().map(|s| s.to_string()).collect::>(); + let (info, abi) = VariantInfo::new(names.iter().map(|_| None)); + self.add_enum_type(TypeEnum { names, abi, info }) + } + + fn option_type( + &mut self, + types: TypesRef<'_>, + ty: &component_types::ComponentValType, + ) -> Result { + let ty = self.valtype(types, ty)?; + let (info, abi) = VariantInfo::new([None, Some(self.component_types.canonical_abi(&ty))]); + Ok(self.add_option_type(TypeOption { ty, abi, info })) + } + + fn result_type( + &mut self, + types: TypesRef<'_>, + ok: &Option, + err: &Option, + ) -> Result { + let ok = match ok { + Some(ty) => Some(self.valtype(types, ty)?), + None => None, + }; + let err = match err { + Some(ty) => Some(self.valtype(types, ty)?), + None => None, + }; + let (info, abi) = VariantInfo::new([ + ok.as_ref().map(|t| self.component_types.canonical_abi(t)), + err.as_ref().map(|t| self.component_types.canonical_abi(t)), + ]); + Ok(self.add_result_type(TypeResult { ok, err, abi, info })) + } + + fn list_type( + &mut self, + types: TypesRef<'_>, + ty: &component_types::ComponentValType, + ) -> Result { + let element = self.valtype(types, ty)?; + Ok(self.add_list_type(TypeList { element })) + } + + /// Converts a wasmparser `id`, which must point to a resource, to its + /// corresponding `TypeResourceTableIndex`. + pub fn resource_id(&mut self, id: component_types::ResourceId) -> TypeResourceTableIndex { + self.resources.convert(id, &mut self.component_types) + } + + /// Interns a new function type within this type information. + pub fn add_func_type(&mut self, ty: TypeFunc) -> TypeFuncIndex { + intern(&mut self.functions, &mut self.component_types.functions, ty) + } + + /// Interns a new record type within this type information. + pub fn add_record_type(&mut self, ty: TypeRecord) -> TypeRecordIndex { + intern_and_fill_flat_types!(self, records, ty) + } + + /// Interns a new flags type within this type information. + pub fn add_flags_type(&mut self, ty: TypeFlags) -> TypeFlagsIndex { + intern_and_fill_flat_types!(self, flags, ty) + } + + /// Interns a new tuple type within this type information. + pub fn add_tuple_type(&mut self, ty: TypeTuple) -> TypeTupleIndex { + intern_and_fill_flat_types!(self, tuples, ty) + } + + /// Interns a new variant type within this type information. + pub fn add_variant_type(&mut self, ty: TypeVariant) -> TypeVariantIndex { + intern_and_fill_flat_types!(self, variants, ty) + } + + /// Interns a new enum type within this type information. + pub fn add_enum_type(&mut self, ty: TypeEnum) -> TypeEnumIndex { + intern_and_fill_flat_types!(self, enums, ty) + } + + /// Interns a new option type within this type information. + pub fn add_option_type(&mut self, ty: TypeOption) -> TypeOptionIndex { + intern_and_fill_flat_types!(self, options, ty) + } + + /// Interns a new result type within this type information. + pub fn add_result_type(&mut self, ty: TypeResult) -> TypeResultIndex { + intern_and_fill_flat_types!(self, results, ty) + } + + /// Interns a new type within this type information. + pub fn add_list_type(&mut self, ty: TypeList) -> TypeListIndex { + intern_and_fill_flat_types!(self, lists, ty) + } + + /// Returns the canonical ABI information about the specified type. + pub fn canonical_abi(&self, ty: &InterfaceType) -> &CanonicalAbiInfo { + self.component_types.canonical_abi(ty) + } + + /// Returns the "flat types" for the given interface type used in the + /// canonical ABI. + /// + /// Returns `None` if the type is too large to be represented via flat types + /// in the canonical abi. + pub fn flat_types(&self, ty: &InterfaceType) -> Option> { + self.type_information(ty).flat.as_flat_types() + } + + /// Returns whether the type specified contains any borrowed resources + /// within it. + pub fn ty_contains_borrow_resource(&self, ty: &InterfaceType) -> bool { + self.type_information(ty).has_borrow + } + + fn type_information(&self, ty: &InterfaceType) -> &TypeInformation { + match ty { + InterfaceType::U8 + | InterfaceType::S8 + | InterfaceType::Bool + | InterfaceType::U16 + | InterfaceType::S16 + | InterfaceType::U32 + | InterfaceType::S32 + | InterfaceType::Char + | InterfaceType::ErrorContext + | InterfaceType::Own(_) => { + static INFO: TypeInformation = TypeInformation::primitive(FlatType::I32); + &INFO + } + InterfaceType::Borrow(_) => { + static INFO: TypeInformation = { + let mut info = TypeInformation::primitive(FlatType::I32); + info.has_borrow = true; + info + }; + &INFO + } + InterfaceType::U64 | InterfaceType::S64 => { + static INFO: TypeInformation = TypeInformation::primitive(FlatType::I64); + &INFO + } + InterfaceType::Float32 => { + static INFO: TypeInformation = TypeInformation::primitive(FlatType::F32); + &INFO + } + InterfaceType::Float64 => { + static INFO: TypeInformation = TypeInformation::primitive(FlatType::F64); + &INFO + } + InterfaceType::String => { + static INFO: TypeInformation = TypeInformation::string(); + &INFO + } + + InterfaceType::List(i) => &self.type_info.lists[*i], + InterfaceType::Record(i) => &self.type_info.records[*i], + InterfaceType::Variant(i) => &self.type_info.variants[*i], + InterfaceType::Tuple(i) => &self.type_info.tuples[*i], + InterfaceType::Flags(i) => &self.type_info.flags[*i], + InterfaceType::Enum(i) => &self.type_info.enums[*i], + InterfaceType::Option(i) => &self.type_info.options[*i], + InterfaceType::Result(i) => &self.type_info.results[*i], + } + } +} + +fn intern(map: &mut FxHashMap, list: &mut PrimaryMap, item: T) -> U +where + T: Hash + Clone + Eq, + U: Copy + EntityRef, +{ + if let Some(idx) = map.get(&item) { + return *idx; + } + let idx = list.push(item.clone()); + map.insert(item, idx); + idx +} + +/// Types of imports and exports in the component model. +/// +/// These types are what's available for import and export in components. Note +/// that all indirect indices contained here are intended to be looked up +/// through a sibling `ComponentTypes` structure. +#[derive(Copy, Clone, Debug)] +pub enum TypeDef { + /// A component and its type. + Component(TypeComponentIndex), + /// An instance of a component. + ComponentInstance(TypeComponentInstanceIndex), + /// A component function, not to be confused with a core wasm function. + ComponentFunc(TypeFuncIndex), + /// An interface type. + Interface(InterfaceType), + /// A core wasm module and its type. + Module(TypeModuleIndex), + /// A resource type which operates on the specified resource table. + /// + /// Note that different resource tables may point to the same underlying + /// actual resource type, but that's a private detail. + Resource(TypeResourceTableIndex), +} + +/// The type of a module in the component model. +/// +/// Note that this is not to be confused with `TypeComponent` below. This is +/// intended only for core wasm modules, not for components. +#[derive(Default)] +pub struct TypeModule { + /// The values that this module imports. + /// + /// Note that the value of this map is a core wasm `EntityType`, not a + /// component model `TypeRef`. Additionally note that this reflects the + /// two-level namespace of core WebAssembly, but unlike core wasm all import + /// names are required to be unique to describe a module in the component + /// model. + pub imports: FxHashMap<(String, String), EntityType>, + + /// The values that this module exports. + /// + /// Note that the value of this map is the core wasm `EntityType` to + /// represent that core wasm items are being exported. + pub exports: FxHashMap, +} + +/// The type of a component in the component model. +#[derive(Default)] +pub struct TypeComponent { + /// The named values that this component imports. + pub imports: FxHashMap, + /// The named values that this component exports. + pub exports: FxHashMap, +} + +/// The type of a component instance in the component model, or an instantiated +/// component. +/// +/// Component instances only have exports of types in the component model. +#[derive(Default)] +pub struct TypeComponentInstance { + /// The list of exports that this component has along with their types. + pub exports: IndexMap, +} + +/// A component function type in the component model. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeFunc { + /// Parameters to the function represented as a tuple. + pub params: TypeTupleIndex, + /// Results of the function represented as a tuple. + pub results: TypeTupleIndex, +} + +/// All possible interface types that values can have. +/// +/// This list represents an exhaustive listing of interface types and the +/// shapes that they can take. Note that this enum is considered an "index" of +/// forms where for non-primitive types a `ComponentTypes` structure is used to +/// lookup further information based on the index found here. +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum InterfaceType { + Bool, + S8, + U8, + S16, + U16, + S32, + U32, + S64, + U64, + Float32, + Float64, + Char, + String, + ErrorContext, + Record(TypeRecordIndex), + Variant(TypeVariantIndex), + List(TypeListIndex), + Tuple(TypeTupleIndex), + Flags(TypeFlagsIndex), + Enum(TypeEnumIndex), + Option(TypeOptionIndex), + Result(TypeResultIndex), + Own(TypeResourceTableIndex), + Borrow(TypeResourceTableIndex), +} + +impl From<&wasmparser::PrimitiveValType> for InterfaceType { + fn from(ty: &wasmparser::PrimitiveValType) -> InterfaceType { + match ty { + wasmparser::PrimitiveValType::Bool => InterfaceType::Bool, + wasmparser::PrimitiveValType::S8 => InterfaceType::S8, + wasmparser::PrimitiveValType::U8 => InterfaceType::U8, + wasmparser::PrimitiveValType::S16 => InterfaceType::S16, + wasmparser::PrimitiveValType::U16 => InterfaceType::U16, + wasmparser::PrimitiveValType::S32 => InterfaceType::S32, + wasmparser::PrimitiveValType::U32 => InterfaceType::U32, + wasmparser::PrimitiveValType::S64 => InterfaceType::S64, + wasmparser::PrimitiveValType::U64 => InterfaceType::U64, + wasmparser::PrimitiveValType::F32 => InterfaceType::Float32, + wasmparser::PrimitiveValType::F64 => InterfaceType::Float64, + wasmparser::PrimitiveValType::Char => InterfaceType::Char, + wasmparser::PrimitiveValType::String => InterfaceType::String, + wasmparser::PrimitiveValType::ErrorContext => InterfaceType::ErrorContext, + } + } +} + +/// Bye information about a type in the canonical ABI +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct CanonicalAbiInfo { + /// The byte-size of this type in a 32-bit memory. + pub size32: u32, + /// The byte-alignment of this type in a 32-bit memory. + pub align32: u32, + /// The number of types it takes to represents this type in the "flat" + /// representation of the canonical abi where everything is passed as + /// immediate arguments or results. + /// + /// If this is `None` then this type is not representable in the flat ABI + /// because it is too large. + pub flat_count: Option, +} + +impl Default for CanonicalAbiInfo { + fn default() -> CanonicalAbiInfo { + CanonicalAbiInfo { + size32: 0, + align32: 1, + flat_count: Some(0), + } + } +} + +const fn align_to(a: u32, b: u32) -> u32 { + assert!(b.is_power_of_two()); + (a + (b - 1)) & !(b - 1) +} + +const fn max(a: u32, b: u32) -> u32 { + if a > b { + a + } else { + b + } +} + +impl CanonicalAbiInfo { + /// ABI information for lists/strings which are "pointer pairs" + pub const POINTER_PAIR: CanonicalAbiInfo = CanonicalAbiInfo { + size32: 8, + align32: 4, + flat_count: Some(2), + }; + /// ABI information for one-byte scalars. + pub const SCALAR1: CanonicalAbiInfo = CanonicalAbiInfo::scalar(1); + /// ABI information for two-byte scalars. + pub const SCALAR2: CanonicalAbiInfo = CanonicalAbiInfo::scalar(2); + /// ABI information for four-byte scalars. + pub const SCALAR4: CanonicalAbiInfo = CanonicalAbiInfo::scalar(4); + /// ABI information for eight-byte scalars. + pub const SCALAR8: CanonicalAbiInfo = CanonicalAbiInfo::scalar(8); + /// ABI information for zero-sized types. + const ZERO: CanonicalAbiInfo = CanonicalAbiInfo { + size32: 0, + align32: 1, + flat_count: Some(0), + }; + + const fn scalar(size: u32) -> CanonicalAbiInfo { + CanonicalAbiInfo { + size32: size, + align32: size, + flat_count: Some(1), + } + } + + /// Returns the abi for a record represented by the specified fields. + pub fn record<'a>(fields: impl Iterator) -> CanonicalAbiInfo { + // NB: this is basically a duplicate copy of + // `CanonicalAbiInfo::record_static` and the two should be kept in sync. + + let mut ret = CanonicalAbiInfo::default(); + for field in fields { + ret.size32 = align_to(ret.size32, field.align32) + field.size32; + ret.align32 = ret.align32.max(field.align32); + ret.flat_count = add_flat(ret.flat_count, field.flat_count); + } + ret.size32 = align_to(ret.size32, ret.align32); + ret + } + + /// Same as `CanonicalAbiInfo::record` but in a `const`-friendly context. + pub const fn record_static(fields: &[CanonicalAbiInfo]) -> CanonicalAbiInfo { + // NB: this is basically a duplicate copy of `CanonicalAbiInfo::record` + // and the two should be kept in sync. + + let mut ret = CanonicalAbiInfo::ZERO; + let mut i = 0; + while i < fields.len() { + let field = &fields[i]; + ret.size32 = align_to(ret.size32, field.align32) + field.size32; + ret.align32 = max(ret.align32, field.align32); + ret.flat_count = add_flat(ret.flat_count, field.flat_count); + i += 1; + } + ret.size32 = align_to(ret.size32, ret.align32); + ret + } + + /// Returns the delta from the current value of `offset` to align properly + /// and read the next record field of type `abi` for 32-bit memories. + pub fn next_field32(&self, offset: &mut u32) -> u32 { + *offset = align_to(*offset, self.align32) + self.size32; + *offset - self.size32 + } + + /// Same as `next_field32`, but bumps a usize pointer + pub fn next_field32_size(&self, offset: &mut usize) -> usize { + let cur = u32::try_from(*offset).unwrap(); + let cur = align_to(cur, self.align32) + self.size32; + *offset = usize::try_from(cur).unwrap(); + usize::try_from(cur - self.size32).unwrap() + } + + /// Returns ABI information for a structure which contains `count` flags. + pub const fn flags(count: usize) -> CanonicalAbiInfo { + let (size, align, flat_count) = match FlagsSize::from_count(count) { + FlagsSize::Size0 => (0, 1, 0), + FlagsSize::Size1 => (1, 1, 1), + FlagsSize::Size2 => (2, 2, 1), + FlagsSize::Size4Plus(n) => ((n as u32) * 4, 4, n), + }; + CanonicalAbiInfo { + size32: size, + align32: align, + flat_count: Some(flat_count), + } + } + + fn variant<'a, I>(cases: I) -> CanonicalAbiInfo + where + I: IntoIterator>, + I::IntoIter: ExactSizeIterator, + { + // NB: this is basically a duplicate definition of + // `CanonicalAbiInfo::variant_static`, these should be kept in sync. + + let cases = cases.into_iter(); + let discrim_size = u32::from(DiscriminantSize::from_count(cases.len()).unwrap()); + let mut max_size32 = 0; + let mut max_align32 = discrim_size; + let mut max_case_count = Some(0); + for case in cases.flatten() { + max_size32 = max_size32.max(case.size32); + max_align32 = max_align32.max(case.align32); + max_case_count = max_flat(max_case_count, case.flat_count); + } + CanonicalAbiInfo { + size32: align_to(align_to(discrim_size, max_align32) + max_size32, max_align32), + align32: max_align32, + flat_count: add_flat(max_case_count, Some(1)), + } + } + + /// Same as `CanonicalAbiInfo::variant` but `const`-safe + pub const fn variant_static(cases: &[Option]) -> CanonicalAbiInfo { + // NB: this is basically a duplicate definition of + // `CanonicalAbiInfo::variant`, these should be kept in sync. + + let discrim_size = match DiscriminantSize::from_count(cases.len()) { + Some(size) => size.byte_size(), + None => unreachable!(), + }; + let mut max_size32 = 0; + let mut max_align32 = discrim_size; + let mut max_case_count = Some(0); + let mut i = 0; + while i < cases.len() { + let case = &cases[i]; + if let Some(case) = case { + max_size32 = max(max_size32, case.size32); + max_align32 = max(max_align32, case.align32); + max_case_count = max_flat(max_case_count, case.flat_count); + } + i += 1; + } + CanonicalAbiInfo { + size32: align_to(align_to(discrim_size, max_align32) + max_size32, max_align32), + align32: max_align32, + flat_count: add_flat(max_case_count, Some(1)), + } + } + + /// Returns the flat count of this ABI information so long as the count + /// doesn't exceed the `max` specified. + pub fn flat_count(&self, max: usize) -> Option { + let flat = usize::from(self.flat_count?); + if flat > max { + None + } else { + Some(flat) + } + } +} + +/// ABI information about the representation of a variant. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct VariantInfo { + /// The size of the discriminant used. + pub size: DiscriminantSize, + /// The offset of the payload from the start of the variant in 32-bit + /// memories. + pub payload_offset32: u32, +} + +impl VariantInfo { + /// Returns the abi information for a variant represented by the specified + /// cases. + pub fn new<'a, I>(cases: I) -> (VariantInfo, CanonicalAbiInfo) + where + I: IntoIterator>, + I::IntoIter: ExactSizeIterator, + { + let cases = cases.into_iter(); + let size = DiscriminantSize::from_count(cases.len()).unwrap(); + let abi = CanonicalAbiInfo::variant(cases); + ( + VariantInfo { + size, + payload_offset32: align_to(u32::from(size), abi.align32), + }, + abi, + ) + } + + pub const fn new_static(cases: &[Option]) -> VariantInfo { + let size = match DiscriminantSize::from_count(cases.len()) { + Some(size) => size, + None => unreachable!(), + }; + let abi = CanonicalAbiInfo::variant_static(cases); + VariantInfo { + size, + payload_offset32: align_to(size.byte_size(), abi.align32), + } + } +} + +/// Shape of a "record" type in interface types. +/// +/// This is equivalent to a `struct` in Rust. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeRecord { + /// The fields that are contained within this struct type. + pub fields: Box<[RecordField]>, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, +} + +/// One field within a record. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct RecordField { + /// The name of the field, unique amongst all fields in a record. + pub name: String, + /// The type that this field contains. + pub ty: InterfaceType, +} + +/// Shape of a "variant" type in interface types. +/// +/// Variants are close to Rust `enum` declarations where a value is one of many +/// cases and each case has a unique name and an optional payload associated +/// with it. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeVariant { + /// The list of cases that this variant can take. + pub cases: Box<[VariantCase]>, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, + /// Byte information about this variant type. + pub info: VariantInfo, +} + +/// One case of a `variant` type which contains the name of the variant as well +/// as the payload. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct VariantCase { + /// Name of the variant, unique amongst all cases in a variant. + pub name: String, + /// Optional type associated with this payload. + pub ty: Option, +} + +/// Shape of a "tuple" type in interface types. +/// +/// This is largely the same as a tuple in Rust, basically a record with +/// unnamed fields. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeTuple { + /// The types that are contained within this tuple. + pub types: Box<[InterfaceType]>, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, +} + +/// Shape of a "flags" type in interface types. +/// +/// This can be thought of as a record-of-bools, although the representation is +/// more efficient as bitflags. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeFlags { + /// The names of all flags, all of which are unique. + pub names: Box<[String]>, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, +} + +/// Shape of an "enum" type in interface types, not to be confused with a Rust +/// `enum` type. +/// +/// In interface types enums are simply a bag of names, and can be seen as a +/// variant where all payloads are `Unit`. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeEnum { + /// The names of this enum, all of which are unique. + pub names: Box<[String]>, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, + /// Byte information about this variant type. + pub info: VariantInfo, +} + +/// Shape of an "option" interface type. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeOption { + /// The `T` in `Result` + pub ty: InterfaceType, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, + /// Byte information about this variant type. + pub info: VariantInfo, +} + +/// Shape of a "result" interface type. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeResult { + /// The `T` in `Result` + pub ok: Option, + /// The `E` in `Result` + pub err: Option, + /// Byte information about this type in the canonical ABI. + pub abi: CanonicalAbiInfo, + /// Byte information about this variant type. + pub info: VariantInfo, +} + +/// Metadata about a resource table added to a component. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeResourceTable { + /// The original resource that this table contains. + /// + /// This is used when destroying resources within this table since this + /// original definition will know how to execute destructors. + pub ty: ResourceIndex, + + /// The component instance that contains this resource table. + pub instance: RuntimeComponentInstanceIndex, +} + +/// Shape of a "list" interface type. +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +pub struct TypeList { + /// The element type of the list. + pub element: InterfaceType, +} + +const MAX_FLAT_TYPES: usize = if MAX_FLAT_PARAMS > MAX_FLAT_RESULTS { + MAX_FLAT_PARAMS +} else { + MAX_FLAT_RESULTS +}; + +const fn add_flat(a: Option, b: Option) -> Option { + const MAX: u8 = MAX_FLAT_TYPES as u8; + let sum = match (a, b) { + (Some(a), Some(b)) => match a.checked_add(b) { + Some(c) => c, + None => return None, + }, + _ => return None, + }; + if sum > MAX { + None + } else { + Some(sum) + } +} + +const fn max_flat(a: Option, b: Option) -> Option { + match (a, b) { + (Some(a), Some(b)) => { + if a > b { + Some(a) + } else { + Some(b) + } + } + _ => None, + } +} + +/// Flat representation of a type in just core wasm types. +pub struct FlatTypes<'a> { + /// The flat representation of this type in 32-bit memories. + pub memory32: &'a [FlatType], +} + +impl FlatTypes<'_> { + /// Returns the number of flat types used to represent this type. + /// + /// Note that this length is the same regardless to the size of memory. + pub fn len(&self) -> usize { + self.memory32.len() + } +} + +// Note that this is intentionally duplicated here to keep the size to 1 byte +// irregardless to changes in the core wasm type system since this will only +// ever use integers/floats for the forseeable future. +#[derive(PartialEq, Eq, Copy, Clone)] +#[allow(missing_docs)] +pub enum FlatType { + I32, + I64, + F32, + F64, +} + +struct FlatTypesStorage { + // This could be represented as `Vec` but on 64-bit architectures + // that's 24 bytes. Otherwise `FlatType` is 1 byte large and + // `MAX_FLAT_TYPES` is 16, so it should ideally be more space-efficient to + // use a flat array instead of a heap-based vector. + memory32: [FlatType; MAX_FLAT_TYPES], + + // Tracks the number of flat types pushed into this storage. If this is + // `MAX_FLAT_TYPES + 1` then this storage represents an un-reprsentable + // type in flat types. + len: u8, +} + +impl FlatTypesStorage { + const fn new() -> FlatTypesStorage { + FlatTypesStorage { + memory32: [FlatType::I32; MAX_FLAT_TYPES], + len: 0, + } + } + + fn as_flat_types(&self) -> Option> { + let len = usize::from(self.len); + if len > MAX_FLAT_TYPES { + assert_eq!(len, MAX_FLAT_TYPES + 1); + None + } else { + Some(FlatTypes { + memory32: &self.memory32[..len], + }) + } + } + + /// Pushes a new flat type into this list using `t32` for 32-bit memories + /// + /// Returns whether the type was actually pushed or whether this list of + /// flat types just exceeded the maximum meaning that it is now + /// unrepresentable with a flat list of types. + fn push(&mut self, t32: FlatType) -> bool { + let len = usize::from(self.len); + if len < MAX_FLAT_TYPES { + self.memory32[len] = t32; + self.len += 1; + true + } else { + // If this was the first one to go over then flag the length as + // being incompatible with a flat representation. + if len == MAX_FLAT_TYPES { + self.len += 1; + } + false + } + } +} + +impl FlatType { + fn join(&mut self, other: FlatType) { + if *self == other { + return; + } + *self = match (*self, other) { + (FlatType::I32, FlatType::F32) | (FlatType::F32, FlatType::I32) => FlatType::I32, + _ => FlatType::I64, + }; + } +} + +#[derive(Default)] +struct TypeInformationCache { + records: PrimaryMap, + variants: PrimaryMap, + tuples: PrimaryMap, + enums: PrimaryMap, + flags: PrimaryMap, + options: PrimaryMap, + results: PrimaryMap, + lists: PrimaryMap, +} + +struct TypeInformation { + depth: u32, + flat: FlatTypesStorage, + has_borrow: bool, +} + +impl TypeInformation { + const fn new() -> TypeInformation { + TypeInformation { + depth: 0, + flat: FlatTypesStorage::new(), + has_borrow: false, + } + } + + const fn primitive(flat: FlatType) -> TypeInformation { + let mut info = TypeInformation::new(); + info.depth = 1; + info.flat.memory32[0] = flat; + info.flat.len = 1; + info + } + + const fn string() -> TypeInformation { + let mut info = TypeInformation::new(); + info.depth = 1; + info.flat.memory32[0] = FlatType::I32; + info.flat.memory32[1] = FlatType::I32; + info.flat.len = 2; + info + } + + /// Builds up all flat types internally using the specified representation + /// for all of the component fields of the record. + fn build_record<'a>(&mut self, types: impl Iterator) { + self.depth = 1; + for info in types { + self.depth = self.depth.max(1 + info.depth); + self.has_borrow = self.has_borrow || info.has_borrow; + match info.flat.as_flat_types() { + Some(types) => { + for t32 in types.memory32.iter() { + if !self.flat.push(*t32) { + break; + } + } + } + None => { + self.flat.len = u8::try_from(MAX_FLAT_TYPES + 1).unwrap(); + } + } + } + } + + /// Builds up the flat types used to represent a `variant` which notably + /// handles "join"ing types together so each case is representable as a + /// single flat list of types. + /// + /// The iterator item is: + /// + /// * `None` - no payload for this case + /// * `Some(None)` - this case has a payload but can't be represented with flat types + /// * `Some(Some(types))` - this case has a payload and is represented with the types specified + /// in the flat representation. + fn build_variant<'a, I>(&mut self, cases: I) + where + I: IntoIterator>, + { + let cases = cases.into_iter(); + self.flat.push(FlatType::I32); + self.depth = 1; + + for info in cases { + let info = match info { + Some(info) => info, + // If this case doesn't have a payload then it doesn't change + // the depth/flat representation + None => continue, + }; + self.depth = self.depth.max(1 + info.depth); + self.has_borrow = self.has_borrow || info.has_borrow; + + // If this variant is already unrepresentable in a flat + // representation then this can be skipped. + if usize::from(self.flat.len) > MAX_FLAT_TYPES { + continue; + } + + let types = match info.flat.as_flat_types() { + Some(types) => types, + // If this case isn't representable with a flat list of types + // then this variant also isn't representable. + None => { + self.flat.len = u8::try_from(MAX_FLAT_TYPES + 1).unwrap(); + continue; + } + }; + // If the case used all of the flat types then the discriminant + // added for this variant means that this variant is no longer + // representable. + if types.memory32.len() >= MAX_FLAT_TYPES { + self.flat.len = u8::try_from(MAX_FLAT_TYPES + 1).unwrap(); + continue; + } + let dst = self.flat.memory32.iter_mut().skip(1); + for (i, (t32, dst32)) in types.memory32.iter().zip(dst).enumerate() { + if i + 1 < usize::from(self.flat.len) { + // If this index hs already been set by some previous case + // then the types are joined together. + dst32.join(*t32); + } else { + // Otherwise if this is the first time that the + // representation has gotten this large then the destination + // is simply whatever the type is. The length is also + // increased here to indicate this. + self.flat.len += 1; + *dst32 = *t32; + } + } + } + } + + fn records(&mut self, types: &ComponentTypesBuilder, ty: &TypeRecord) { + self.build_record(ty.fields.iter().map(|f| types.type_information(&f.ty))); + } + + fn tuples(&mut self, types: &ComponentTypesBuilder, ty: &TypeTuple) { + self.build_record(ty.types.iter().map(|t| types.type_information(t))); + } + + fn enums(&mut self, _types: &ComponentTypesBuilder, _ty: &TypeEnum) { + self.depth = 1; + self.flat.push(FlatType::I32); + } + + fn flags(&mut self, _types: &ComponentTypesBuilder, ty: &TypeFlags) { + self.depth = 1; + match FlagsSize::from_count(ty.names.len()) { + FlagsSize::Size0 => {} + FlagsSize::Size1 | FlagsSize::Size2 => { + self.flat.push(FlatType::I32); + } + FlagsSize::Size4Plus(n) => { + for _ in 0..n { + self.flat.push(FlatType::I32); + } + } + } + } + + fn variants(&mut self, types: &ComponentTypesBuilder, ty: &TypeVariant) { + self.build_variant( + ty.cases.iter().map(|c| c.ty.as_ref().map(|ty| types.type_information(ty))), + ) + } + + fn results(&mut self, types: &ComponentTypesBuilder, ty: &TypeResult) { + self.build_variant([ + ty.ok.as_ref().map(|ty| types.type_information(ty)), + ty.err.as_ref().map(|ty| types.type_information(ty)), + ]) + } + + fn options(&mut self, types: &ComponentTypesBuilder, ty: &TypeOption) { + self.build_variant([None, Some(types.type_information(&ty.ty))]); + } + + fn lists(&mut self, types: &ComponentTypesBuilder, ty: &TypeList) { + *self = TypeInformation::string(); + let info = types.type_information(&ty.element); + self.depth += info.depth; + self.has_borrow = info.has_borrow; + } +} + +pub fn interface_type_to_ir( + ty: &InterfaceType, + component_types: &ComponentTypes, +) -> midenc_hir::Type { + match ty { + InterfaceType::Bool => midenc_hir::Type::I1, + InterfaceType::S8 => midenc_hir::Type::I8, + InterfaceType::U8 => midenc_hir::Type::U8, + InterfaceType::S16 => midenc_hir::Type::I16, + InterfaceType::U16 => midenc_hir::Type::U16, + InterfaceType::S32 => midenc_hir::Type::I32, + InterfaceType::U32 => midenc_hir::Type::U32, + InterfaceType::S64 => midenc_hir::Type::I64, + InterfaceType::U64 => midenc_hir::Type::U64, + InterfaceType::Float32 => midenc_hir::Type::Felt, + InterfaceType::Float64 => todo!(), + InterfaceType::Char => todo!(), + InterfaceType::String => todo!(), + InterfaceType::ErrorContext => todo!("the async proposal is not currently supported"), + InterfaceType::Record(idx) => { + let tys = component_types.records[*idx] + .fields + .iter() + .map(|f| interface_type_to_ir(&f.ty, component_types)); + midenc_hir::Type::from(midenc_hir::StructType::new(tys)) + } + // TODO: This is a stub to make `enum` in WIT generation work. Use proper type when ready. + InterfaceType::Variant(_) => midenc_hir::Type::U32, + InterfaceType::List(idx) => { + let element_ty = + interface_type_to_ir(&component_types.lists[*idx].element, component_types); + midenc_hir::Type::List(Arc::new(element_ty)) + } + InterfaceType::Tuple(tuple_idx) => { + let tys = component_types.tuples[*tuple_idx] + .types + .iter() + .map(|t| interface_type_to_ir(t, component_types)); + midenc_hir::Type::from(midenc_hir::StructType::new(tys)) + } + InterfaceType::Flags(_) => todo!(), + InterfaceType::Enum(_) => todo!(), + InterfaceType::Option(_) => todo!(), + InterfaceType::Result(_) => todo!(), + InterfaceType::Own(_) => todo!(), + InterfaceType::Borrow(_) => todo!(), + } +} diff --git a/frontend-wasm/src/component/types/resources.rs b/frontend/wasm/src/component/types/resources.rs similarity index 91% rename from frontend-wasm/src/component/types/resources.rs rename to frontend/wasm/src/component/types/resources.rs index 4d85f15e4..b49faa1ea 100644 --- a/frontend-wasm/src/component/types/resources.rs +++ b/frontend/wasm/src/component/types/resources.rs @@ -66,8 +66,8 @@ // Based on wasmtime v16.0 Wasm component translation -use rustc_hash::FxHashMap; -use wasmparser::types; +use midenc_hir::FxHashMap; +use wasmparser::{component_types, types}; use crate::component::{ ComponentTypes, ResourceIndex, RuntimeComponentInstanceIndex, TypeResourceTable, @@ -110,7 +110,7 @@ pub struct ResourcesBuilder { /// A cache of previously visited `ResourceId` items and which table they /// correspond to. This is lazily populated as resources are visited and is /// exclusively used by the `convert` function below. - resource_id_to_table_index: FxHashMap, + resource_id_to_table_index: FxHashMap, /// A cache of the origin resource type behind a `ResourceId`. /// @@ -120,7 +120,7 @@ pub struct ResourcesBuilder { /// phase. This is used to record the actual underlying type of a resource /// and where it originally comes from. When a resource is later referred to /// then a table is injected to be referred to. - resource_id_to_resource_index: FxHashMap, + resource_id_to_resource_index: FxHashMap, /// The current instance index that's being visited. This is updated as /// inliner frames are processed and components are instantiated. @@ -143,7 +143,7 @@ impl ResourcesBuilder { /// any component it's assigned a new table, which is exactly what we want. pub fn convert( &mut self, - id: types::ResourceId, + id: component_types::ResourceId, types: &mut ComponentTypes, ) -> TypeResourceTableIndex { *self.resource_id_to_table_index.entry(id).or_insert_with(|| { @@ -171,7 +171,7 @@ impl ResourcesBuilder { pub fn register_component_entity_type<'a>( &mut self, types: &'a types::TypesRef<'_>, - ty: types::ComponentEntityType, + ty: component_types::ComponentEntityType, path: &mut Vec<&'a str>, register: &mut dyn FnMut(&[&'a str]) -> ResourceIndex, ) { @@ -181,8 +181,8 @@ impl ResourcesBuilder { // with the current path and that's inserted in to // `resource_id_to_resource_index` if the resource hasn't been seen // yet. - types::ComponentEntityType::Type { - created: types::ComponentAnyTypeId::Resource(id), + component_types::ComponentEntityType::Type { + created: component_types::ComponentAnyTypeId::Resource(id), .. } => { self.resource_id_to_resource_index @@ -193,7 +193,7 @@ impl ResourcesBuilder { // Resources can be imported/defined through exports of instances so // all instance exports are walked here. Note the management of // `path` which is used for the recursive invocation of this method. - types::ComponentEntityType::Instance(id) => { + component_types::ComponentEntityType::Instance(id) => { let ty = &types[id]; for (name, ty) in ty.exports.iter() { path.push(name); @@ -204,11 +204,11 @@ impl ResourcesBuilder { // None of these items can introduce a new component type, so // there's no need to recurse over these. - types::ComponentEntityType::Func(_) - | types::ComponentEntityType::Type { .. } - | types::ComponentEntityType::Module(_) - | types::ComponentEntityType::Component(_) - | types::ComponentEntityType::Value(_) => {} + component_types::ComponentEntityType::Func(_) + | component_types::ComponentEntityType::Type { .. } + | component_types::ComponentEntityType::Module(_) + | component_types::ComponentEntityType::Component(_) + | component_types::ComponentEntityType::Value(_) => {} } } @@ -216,7 +216,7 @@ impl ResourcesBuilder { /// defined by the `ty` provided. /// /// This is used when a local resource is defined within a component for example. - pub fn register_resource(&mut self, id: types::ResourceId, ty: ResourceIndex) { + pub fn register_resource(&mut self, id: component_types::ResourceId, ty: ResourceIndex) { let prev = self.resource_id_to_resource_index.insert(id, ty); assert!(prev.is_none()); } diff --git a/frontend/wasm/src/config.rs b/frontend/wasm/src/config.rs new file mode 100644 index 000000000..ea0078b75 --- /dev/null +++ b/frontend/wasm/src/config.rs @@ -0,0 +1,47 @@ +use alloc::borrow::Cow; + +/// Configuration for the WASM translation. +#[derive(Clone)] +pub struct WasmTranslationConfig { + /// The source file name. + /// This is used as a fallback for module/component name if it's not parsed from the Wasm + /// binary, and an override name is not specified + pub source_name: Cow<'static, str>, + + /// If specified, overrides the module/component name with the one specified + pub override_name: Option>, + + /// The HIR world in which to translate any components/modules + pub world: Option, + + /// Whether or not to generate native DWARF debug information. + pub generate_native_debuginfo: bool, + + /// Whether or not to retain DWARF sections in compiled modules. + pub parse_wasm_debuginfo: bool, +} + +impl core::fmt::Debug for WasmTranslationConfig { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let world = if self.world.is_some() { "Some" } else { "None" }; + f.debug_struct("WasmTranslationConfig") + .field("source_name", &self.source_name) + .field("override_name", &self.override_name) + .field("world", &world) + .field("generate_native_debuginfo", &self.generate_native_debuginfo) + .field("parse_wasm_debuginfo", &self.parse_wasm_debuginfo) + .finish() + } +} + +impl Default for WasmTranslationConfig { + fn default() -> Self { + Self { + source_name: Cow::Borrowed("noname"), + override_name: None, + world: None, + generate_native_debuginfo: false, + parse_wasm_debuginfo: true, + } + } +} diff --git a/frontend/wasm/src/error.rs b/frontend/wasm/src/error.rs new file mode 100644 index 000000000..803814c73 --- /dev/null +++ b/frontend/wasm/src/error.rs @@ -0,0 +1,71 @@ +use midenc_session::diagnostics::{miette, Diagnostic, Report}; +use thiserror::Error; + +/// A WebAssembly translation error. +/// +/// When a WebAssembly function can't be translated, one of these error codes will be returned +/// to describe the failure. +#[allow(missing_docs)] +#[derive(Error, Debug, Diagnostic)] +pub enum WasmError { + /// The input WebAssembly code is invalid. + /// + /// This error code is used by a WebAssembly translator when it encounters invalid WebAssembly + /// code. This should never happen for validated WebAssembly code. + #[error("invalid input WebAssembly code at offset {offset}: {message}")] + #[diagnostic()] + InvalidWebAssembly { + /// A string describing the validation error. + message: String, + /// The bytecode offset where the error occurred. + offset: usize, + }, + + /// A feature used by the WebAssembly code is not supported by the Miden IR. + #[error("unsupported WebAssembly code: {0}")] + #[diagnostic()] + Unsupported(String), + + /// Too many functions were declared in a module + #[error("Too many declared functions in the module")] + #[diagnostic()] + FuncNumLimitExceeded, + + #[error("import metadata is missing: {0}")] + #[diagnostic()] + MissingImportMetadata(String), + + #[error("export metadata is missing: {0}")] + #[diagnostic()] + MissingExportMetadata(String), + + #[error(transparent)] + DwarfError(#[from] gimli::Error), + + #[error(transparent)] + Io(#[from] std::io::Error), +} + +impl From for WasmError { + fn from(e: wasmparser::BinaryReaderError) -> Self { + Self::InvalidWebAssembly { + message: e.message().into(), + offset: e.offset(), + } + } +} + +/// A convenient alias for a `Result` that uses `WasmError` as the error type. +pub type WasmResult = Result; + +/// Emit diagnostics and return an `Err(WasmError::Unsupported(msg))` where `msg` the string built +/// by calling `format!` on the arguments to this macro. +#[macro_export] +macro_rules! unsupported_diag { + ($diagnostics:expr, $($arg:tt)*) => {{ + return Err($diagnostics + .diagnostic(midenc_session::diagnostics::Severity::Error) + .with_message(format!($($arg)*)) + .into_report()); + }} +} diff --git a/frontend/wasm/src/intrinsics/advice.rs b/frontend/wasm/src/intrinsics/advice.rs new file mode 100644 index 000000000..ce0950ffd --- /dev/null +++ b/frontend/wasm/src/intrinsics/advice.rs @@ -0,0 +1,120 @@ +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::FunctionRef, + interner::{symbols, Symbol}, + Builder, FunctionType, SmallVec, SourceSpan, SymbolNameComponent, Type, ValueRef, +}; + +use crate::{error::WasmResult, module::function_builder_ext::FunctionBuilderExt}; + +pub(crate) const MODULE_ID: &str = "intrinsics::advice"; +/// The module path prefix for advice intrinsics, not including the function name +pub const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Intrinsics), + SymbolNameComponent::Component(symbols::Advice), +]; + +/// Get the [FunctionType] of an advice intrinsic, if it is implemented as a function. +/// +/// Returns `None` for intrinsics which are unknown, or correspond to native instructions. +pub fn function_type(function: Symbol) -> Option { + match function.as_str() { + "adv_push_mapvaln" => { + // The WASM import signature: takes 4 f32 values (Word) and returns 1 f32 + let sig = FunctionType::new( + midenc_hir::CallConv::Wasm, + vec![ + Type::Felt, // key0 + Type::Felt, // key1 + Type::Felt, // key2 + Type::Felt, // key3 + ], + vec![Type::Felt], // Returns number of elements pushed + ); + Some(sig) + } + "adv_insert_mem" => { + // Signature: (key0..key3, start_ptr, end_ptr) -> () + Some(FunctionType::new( + midenc_hir::CallConv::Wasm, + vec![Type::Felt, Type::Felt, Type::Felt, Type::Felt, Type::Felt, Type::Felt], + vec![], + )) + } + "emit_falcon_sig_to_stack" => { + // (msg0..msg3, pk0..pk3) -> () + Some(FunctionType::new( + midenc_hir::CallConv::Wasm, + vec![ + Type::Felt, + Type::Felt, + Type::Felt, + Type::Felt, + Type::Felt, + Type::Felt, + Type::Felt, + Type::Felt, + ], + vec![], + )) + } + _ => None, + } +} + +/// Convert a call to an advice intrinsic function into instruction(s) +pub fn convert_advice_intrinsics( + function: Symbol, + function_ref: Option, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult> { + let function_ref = + function_ref.unwrap_or_else(|| panic!("expected '{function}' to have been declared")); + + match function.as_str() { + "adv_push_mapvaln" => { + // The WASM import has 4 parameters (key0-3) and returns 1 f32 + assert_eq!(args.len(), 4, "{function} takes exactly four arguments (key0-3)"); + + let func = function_ref.borrow(); + let signature = func.signature().clone(); + drop(func); + + // Call the function with all arguments + // The intrinsics::advice::adv_push_mapvaln function will be mapped to the MASM adv_push_mapvaln + let exec = builder.exec(function_ref, signature, args.iter().copied(), span)?; + + // Extract the return value from the exec operation + let borrow = exec.borrow(); + let results = borrow.as_ref().results(); + let result_vals: SmallVec<[ValueRef; 1]> = + results.iter().map(|op_res| op_res.borrow().as_value_ref()).collect(); + + // The function returns the number of elements pushed as i32 + Ok(result_vals) + } + "emit_falcon_sig_to_stack" => { + assert_eq!(args.len(), 8, "{function} takes exactly eight arguments"); + let func = function_ref.borrow(); + let signature = func.signature().clone(); + drop(func); + let _ = builder.exec(function_ref, signature, args.iter().copied(), span)?; + Ok(SmallVec::new()) + } + "adv_insert_mem" => { + // Lower to MASM intrinsic call: intrinsics::advice::insert_mem + assert_eq!(args.len(), 6, "insert_mem takes exactly six arguments"); + let func = function_ref.borrow(); + let signature = func.signature().clone(); + drop(func); + let _ = builder.exec(function_ref, signature, args.iter().copied(), span)?; + Ok(SmallVec::new()) + } + _ => { + panic!("unsupported io intrinsic: '{function}'") + } + } +} diff --git a/frontend/wasm/src/intrinsics/crypto.rs b/frontend/wasm/src/intrinsics/crypto.rs new file mode 100644 index 000000000..1486a2f33 --- /dev/null +++ b/frontend/wasm/src/intrinsics/crypto.rs @@ -0,0 +1,56 @@ +//! Cryptographic intrinsics conversion module for WebAssembly to Miden IR. +//! +//! This module handles the conversion of cryptographic operations from Wasm imports +//! to their corresponding Miden VM instructions. + +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::FunctionRef, + interner::{symbols, Symbol}, + Builder, SmallVec, SourceSpan, SymbolNameComponent, ValueRef, +}; + +use crate::{error::WasmResult, module::function_builder_ext::FunctionBuilderExt}; + +pub(crate) const MODULE_ID: &str = "intrinsics::crypto"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Intrinsics), + SymbolNameComponent::Component(symbols::Crypto), +]; + +/// Convert a call to a crypto intrinsic function into instruction(s) +pub(crate) fn convert_crypto_intrinsics( + function: Symbol, + function_ref: Option, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult> { + let function_ref = + function_ref.unwrap_or_else(|| panic!("expected '{function}' to have been declared")); + + match function.as_str() { + "hmerge" => { + // The WASM import has 2 parameters (digests pointer + result pointer) + assert_eq!( + args.len(), + 2, + "{function} takes exactly two arguments (digests pointer + result pointer)" + ); + + let func = function_ref.borrow(); + let signature = func.signature().clone(); + drop(func); + + // Call the function with both arguments + // The intrinsics::crypto::hmerge function will be mapped to the MASM hmerge + let _exec = builder.exec(function_ref, signature, args.iter().copied(), span)?; + + // Since the WASM signature has the result pointer as the last parameter, + // the function doesn't return anything - it writes to memory + Ok(SmallVec::new()) + } + unknown => panic!("unknown crypto intrinsic: {unknown}"), + } +} diff --git a/frontend/wasm/src/intrinsics/debug.rs b/frontend/wasm/src/intrinsics/debug.rs new file mode 100644 index 000000000..e49e7220b --- /dev/null +++ b/frontend/wasm/src/intrinsics/debug.rs @@ -0,0 +1,33 @@ +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::FunctionRef, + interner::{symbols, Symbol}, + smallvec, Builder, SmallVec, SourceSpan, SymbolNameComponent, ValueRef, +}; + +use crate::{error::WasmResult, module::function_builder_ext::FunctionBuilderExt}; + +pub(crate) const MODULE_ID: &str = "intrinsics::debug"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Intrinsics), + SymbolNameComponent::Component(symbols::Debug), +]; + +/// Convert a call to a debugging intrinsic function into instruction(s) +pub(crate) fn convert_debug_intrinsics( + function: Symbol, + _function_ref: Option, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult> { + match function.as_str() { + "break" => { + assert_eq!(args.len(), 0, "{function} takes exactly one argument"); + builder.breakpoint(span)?; + Ok(smallvec![]) + } + _ => panic!("no debug intrinsics found named '{function}'"), + } +} diff --git a/frontend/wasm/src/intrinsics/felt.rs b/frontend/wasm/src/intrinsics/felt.rs new file mode 100644 index 000000000..858a970ea --- /dev/null +++ b/frontend/wasm/src/intrinsics/felt.rs @@ -0,0 +1,141 @@ +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::FunctionRef, + interner::{symbols, Symbol}, + smallvec, Builder, SmallVec, SourceSpan, SymbolNameComponent, Type, ValueRef, +}; + +use crate::{error::WasmResult, module::function_builder_ext::FunctionBuilderExt}; + +pub(crate) const MODULE_ID: &str = "intrinsics::felt"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Intrinsics), + SymbolNameComponent::Component(symbols::FeltModule), +]; + +/// Convert a call to a felt op intrinsic function into instruction(s) +pub(crate) fn convert_felt_intrinsics( + function: Symbol, + _function_ref: Option, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult> { + match function.as_str() { + // Conversion operations + "from_u64_unchecked" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + let inst = builder.cast(args[0], Type::Felt, span)?; + Ok(smallvec![inst]) + } + "from_u32" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + let inst = builder.bitcast(args[0], Type::Felt, span)?; + Ok(smallvec![inst]) + } + "as_u64" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + // we're casting to i64 instead of u64 because Wasm doesn't have u64 + // and this value will be used in Wasm ops or local vars that expect i64 + let inst = builder.cast(args[0], Type::I64, span)?; + Ok(smallvec![inst]) + } + // Arithmetic operations + "add" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.add_unchecked(args[0], args[1], span)?; + Ok(smallvec![inst]) + } + "sub" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.sub_unchecked(args[0], args[1], span)?; + Ok(smallvec![inst]) + } + "mul" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.mul_unchecked(args[0], args[1], span)?; + Ok(smallvec![inst]) + } + "div" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.div(args[0], args[1], span)?; + Ok(smallvec![inst]) + } + "neg" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + let inst = builder.neg(args[0], span)?; + Ok(smallvec![inst]) + } + "inv" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + let inst = builder.inv(args[0], span)?; + Ok(smallvec![inst]) + } + "pow2" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + let inst = builder.pow2(args[0], span)?; + Ok(smallvec![inst]) + } + "exp" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.exp(args[0], args[1], span)?; + Ok(smallvec![inst]) + } + // Comparison operations + "eq" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.eq(args[0], args[1], span)?; + let cast = builder.cast(inst, Type::I32, span)?; + Ok(smallvec![cast]) + } + "gt" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.gt(args[0], args[1], span)?; + let cast = builder.cast(inst, Type::I32, span)?; + Ok(smallvec![cast]) + } + "ge" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.gte(args[0], args[1], span)?; + let cast = builder.cast(inst, Type::I32, span)?; + Ok(smallvec![cast]) + } + "lt" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.lt(args[0], args[1], span)?; + let cast = builder.cast(inst, Type::I32, span)?; + Ok(smallvec![cast]) + } + "le" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + let inst = builder.lte(args[0], args[1], span)?; + let cast = builder.cast(inst, Type::I32, span)?; + Ok(smallvec![cast]) + } + "is_odd" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + let inst = builder.is_odd(args[0], span)?; + let cast = builder.cast(inst, Type::I32, span)?; + Ok(smallvec![cast]) + } + // Assert operations + "assert" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + builder.assert(args[0], span)?; + Ok(smallvec![]) + } + "assertz" => { + assert_eq!(args.len(), 1, "{function} takes exactly one argument"); + builder.assertz(args[0], span)?; + Ok(smallvec![]) + } + "assert_eq" => { + assert_eq!(args.len(), 2, "{function} takes exactly two arguments"); + builder.assert_eq(args[0], args[1], span)?; + Ok(smallvec![]) + } + _ => panic!("no felt intrinsics found named '{function}'"), + } +} diff --git a/frontend/wasm/src/intrinsics/intrinsic.rs b/frontend/wasm/src/intrinsics/intrinsic.rs new file mode 100644 index 000000000..364c475fa --- /dev/null +++ b/frontend/wasm/src/intrinsics/intrinsic.rs @@ -0,0 +1,189 @@ +use midenc_hir::{ + diagnostics::{miette, Diagnostic}, + interner::{symbols, Symbol}, + FunctionType, SymbolNameComponent, SymbolPath, Type, +}; + +use super::{advice, crypto, debug, felt, mem}; + +/// Error raised when an attempt is made to use or load an unrecognized intrinsic +#[derive(Debug, thiserror::Error, Diagnostic)] +#[error("unrecognized intrinsic: '{0}'")] +#[diagnostic()] +pub struct UnknownIntrinsicError(SymbolPath); + +/// An intrinsic function, of a known kind. +/// +/// This is used instead of [SymbolPath] as it encodes information known/validated about the +/// intrinsic up to the point it was encoded in this type. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Intrinsic { + /// A debugging intrinsic + Debug(Symbol), + /// A memory intrinsic + Mem(Symbol), + /// A field element intrinsic + Felt(Symbol), + /// A cryptographic intrinsic + Crypto(Symbol), + /// An advice intrinsic + Advice(Symbol), +} + +/// Attempt to recognize an intrinsic function from the given [SymbolPath]. +/// +/// The path must be a valid absolute path to a function in a known intrinsic module +/// +/// NOTE: This does not validate that the intrinsic function is known. +impl TryFrom<&SymbolPath> for Intrinsic { + type Error = UnknownIntrinsicError; + + fn try_from(path: &SymbolPath) -> Result { + let mut components = path.components().peekable(); + + // Ignore the root component if present + components.next_if_eq(&SymbolNameComponent::Root); + + // Must be in the 'intrinsics' namespace + components + .next_if_eq(&SymbolNameComponent::Component(symbols::Intrinsics)) + .ok_or_else(|| UnknownIntrinsicError(path.clone()))?; + + // Must be a known 'intrinsics' module (handled last) + let kind = components + .next() + .map(|c| c.as_symbol_name()) + .ok_or_else(|| UnknownIntrinsicError(path.clone()))?; + + // The last component, if present, must be a leaf, i.e. function name + let function = components + .next_if(|c| c.is_leaf()) + .map(|c| c.as_symbol_name()) + .ok_or_else(|| UnknownIntrinsicError(path.clone()))?; + + match kind { + symbols::Debug => Ok(Self::Debug(function)), + symbols::Mem => Ok(Self::Mem(function)), + symbols::FeltModule => Ok(Self::Felt(function)), + symbols::Crypto => Ok(Self::Crypto(function)), + symbols::Advice => Ok(Self::Advice(function)), + _ => Err(UnknownIntrinsicError(path.clone())), + } + } +} + +impl Intrinsic { + /// Get a [SymbolPath] corresponding to this intrinsic + pub fn into_symbol_path(self) -> SymbolPath { + let mut path = self.module_path(); + path.set_name(self.function_name()); + path + } + + /// Get a [Symbol] corresponding to the module in the `intrinsics` namespace where this + /// intrinsic is defined. + pub fn module_name(&self) -> Symbol { + match self { + Self::Debug(_) => symbols::Debug, + Self::Mem(_) => symbols::Mem, + Self::Felt(_) => symbols::FeltModule, + Self::Crypto(_) => symbols::Crypto, + Self::Advice(_) => symbols::Advice, + } + } + + /// Get a [SymbolPath] corresponding to the module containing this intrinsic + pub fn module_path(&self) -> SymbolPath { + match self { + Self::Debug(_) => SymbolPath::from_iter(debug::MODULE_PREFIX.iter().copied()), + Self::Mem(_) => SymbolPath::from_iter(mem::MODULE_PREFIX.iter().copied()), + Self::Felt(_) => SymbolPath::from_iter(felt::MODULE_PREFIX.iter().copied()), + Self::Crypto(_) => SymbolPath::from_iter(crypto::MODULE_PREFIX.iter().copied()), + Self::Advice(_) => SymbolPath::from_iter(advice::MODULE_PREFIX.iter().copied()), + } + } + + /// Get the name of the intrinsic function as a [Symbol] + pub fn function_name(&self) -> Symbol { + match self { + Self::Debug(function) + | Self::Mem(function) + | Self::Felt(function) + | Self::Crypto(function) + | Self::Advice(function) => *function, + } + } + + /// Get the [FunctionType] of this intrinsic, if it is implemented as a function. + /// + /// Returns `None` for intrinsics which are unknown, or correspond to native instructions. + pub fn function_type(&self) -> Option { + match self { + Self::Mem(function) => mem::function_type(*function), + // All debugging intrinsics are currently implemented as native instructions + Self::Debug(_) => None, + // All field element intrinsics are currently implemented as native instructions + Self::Felt(_) => None, + // Crypto intrinsics are converted to function calls + Self::Crypto(function) => { + match function.as_str() { + "hmerge" => { + // The WASM import signature: takes 2 i32 pointers (digests array pointer + result pointer) + let sig = midenc_hir::FunctionType::new( + midenc_hir::CallConv::Wasm, + vec![ + // Pointer to array of two digests + Type::I32, + // Result pointer + Type::I32, + ], + vec![], // No returns - writes to the result pointer + ); + Some(sig) + } + _ => None, + } + } + Self::Advice(function) => advice::function_type(*function), + } + } + + /// Get the [IntrinsicsConversionResult] representing how this intrinsic will be lowered. + /// + /// Returns `None` for intrinsics which are unknown. + pub fn conversion_result(&self) -> Option { + match self { + Self::Mem(function) => { + mem::function_type(*function).map(IntrinsicsConversionResult::FunctionType) + } + Self::Debug(_) | Self::Felt(_) => Some(IntrinsicsConversionResult::MidenVmOp), + Self::Advice(_function) => self + .function_type() + .map(IntrinsicsConversionResult::FunctionType) + .or(Some(IntrinsicsConversionResult::MidenVmOp)), + // Crypto intrinsics are converted to function calls + Self::Crypto(_function) => self + .function_type() + .map(IntrinsicsConversionResult::FunctionType) + .or(Some(IntrinsicsConversionResult::MidenVmOp)), + } + } +} + +/// Represents how an intrinsic will be converted to IR +pub enum IntrinsicsConversionResult { + /// As a function + FunctionType(FunctionType), + /// As a native instruction + MidenVmOp, +} + +impl IntrinsicsConversionResult { + pub fn is_function(&self) -> bool { + matches!(self, IntrinsicsConversionResult::FunctionType(_)) + } + + pub fn is_operation(&self) -> bool { + matches!(self, IntrinsicsConversionResult::MidenVmOp) + } +} diff --git a/frontend/wasm/src/intrinsics/mem.rs b/frontend/wasm/src/intrinsics/mem.rs new file mode 100644 index 000000000..97d081447 --- /dev/null +++ b/frontend/wasm/src/intrinsics/mem.rs @@ -0,0 +1,62 @@ +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::FunctionRef, + interner::{symbols, Symbol}, + AbiParam, Builder, CallConv, FunctionType, Signature, SmallVec, SourceSpan, + SymbolNameComponent, Type, ValueRef, +}; + +use crate::{error::WasmResult, module::function_builder_ext::FunctionBuilderExt}; + +pub(crate) const MODULE_ID: &str = "intrinsics::mem"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Intrinsics), + SymbolNameComponent::Component(symbols::Mem), +]; + +pub const HEAP_BASE: &str = "heap_base"; + +const HEAP_BASE_FUNC: ([Type; 0], [Type; 1]) = ([], [Type::U32]); + +pub fn function_type(function: Symbol) -> Option { + match function.as_str() { + HEAP_BASE => Some(FunctionType::new(CallConv::Wasm, HEAP_BASE_FUNC.0, HEAP_BASE_FUNC.1)), + _ => None, + } +} + +fn signature(function: Symbol) -> Signature { + match function.as_str() { + HEAP_BASE => { + Signature::new(HEAP_BASE_FUNC.0.map(AbiParam::new), HEAP_BASE_FUNC.1.map(AbiParam::new)) + } + _ => panic!("No memory intrinsics Signature found for {function}"), + } +} + +/// Convert a call to a memory intrinsic function +pub(crate) fn convert_mem_intrinsics( + function: Symbol, + function_ref: Option, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult> { + let function_ref = + function_ref.unwrap_or_else(|| panic!("expected '{function}' to have been declared")); + match function.as_str() { + HEAP_BASE => { + let func = function_ref.borrow(); + assert_eq!(args.len(), 0, "{} takes no arguments", &func.name()); + + let signature = func.signature().clone(); + drop(func); + let exec = builder.exec(function_ref, signature, args.iter().copied(), span)?; + let borrow = exec.borrow(); + let results = borrow.as_ref().results(); + Ok(results.iter().map(|op_res| op_res.borrow().as_value_ref()).collect()) + } + _ => panic!("no memory intrinsics found with name '{function}'"), + } +} diff --git a/frontend/wasm/src/intrinsics/mod.rs b/frontend/wasm/src/intrinsics/mod.rs new file mode 100644 index 000000000..ce8e6bf10 --- /dev/null +++ b/frontend/wasm/src/intrinsics/mod.rs @@ -0,0 +1,40 @@ +mod intrinsic; + +pub use self::intrinsic::*; + +pub mod advice; +pub mod crypto; +pub mod debug; +pub mod felt; +pub mod mem; + +use midenc_hir::{dialects::builtin::FunctionRef, Builder, SmallVec, SourceSpan, ValueRef}; + +use crate::{error::WasmResult, module::function_builder_ext::FunctionBuilderExt}; + +/// Convert a call to a Miden intrinsic function into instruction(s) +pub fn convert_intrinsics_call( + intrinsic: Intrinsic, + function_ref: Option, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, + span: SourceSpan, +) -> WasmResult> { + match intrinsic { + Intrinsic::Debug(function) => { + debug::convert_debug_intrinsics(function, function_ref, args, builder, span) + } + Intrinsic::Mem(function) => { + mem::convert_mem_intrinsics(function, function_ref, args, builder, span) + } + Intrinsic::Felt(function) => { + felt::convert_felt_intrinsics(function, function_ref, args, builder, span) + } + Intrinsic::Crypto(function) => { + crypto::convert_crypto_intrinsics(function, function_ref, args, builder, span) + } + Intrinsic::Advice(function) => { + advice::convert_advice_intrinsics(function, function_ref, args, builder, span) + } + } +} diff --git a/frontend/wasm/src/lib.rs b/frontend/wasm/src/lib.rs new file mode 100644 index 000000000..9cae525cf --- /dev/null +++ b/frontend/wasm/src/lib.rs @@ -0,0 +1,79 @@ +//! Performs translation from Wasm to MidenIR + +// Coding conventions +#![deny(warnings)] +#![deny(missing_docs)] +#![deny(rustdoc::broken_intra_doc_links)] +// Allow unused code that we're going to need for implementing the missing Wasm features (call_direct, tables, etc.) +#![allow(dead_code)] +#![feature(iterator_try_collect)] + +extern crate alloc; + +mod callable; +mod code_translator; +mod component; +mod config; +mod error; +mod intrinsics; +mod miden_abi; +mod module; +mod ssa; +mod translation_utils; + +use alloc::rc::Rc; + +use component::build_ir::translate_component; +use error::WasmResult; +use midenc_hir::{dialects::builtin, Context}; +use module::build_ir::translate_module_as_component; +use wasmparser::WasmFeatures; + +pub use self::{config::*, error::WasmError}; + +/// The output of the frontend Wasm translation stage +pub struct FrontendOutput { + /// The IR component translated from the Wasm + pub component: builtin::ComponentRef, + /// The serialized AccountComponentMetadata (name, description, storage layout, etc.) + pub account_component_metadata_bytes: Option>, +} + +/// Translate a valid Wasm core module or Wasm Component Model binary into Miden +/// IR Component +pub fn translate( + wasm: &[u8], + config: &WasmTranslationConfig, + context: Rc, +) -> WasmResult { + if wasm[4..8] == [0x01, 0x00, 0x00, 0x00] { + // Wasm core module + // see https://github.com/WebAssembly/component-model/blob/main/design/mvp/Binary.md#component-definitions + let component = translate_module_as_component(wasm, config, context)?; + Ok(FrontendOutput { + component, + account_component_metadata_bytes: None, + }) + } else { + translate_component(wasm, config, context) + } +} + +/// The set of core WebAssembly features which we need to or wish to support +pub(crate) fn supported_features() -> WasmFeatures { + WasmFeatures::BULK_MEMORY + | WasmFeatures::FLOATS + | WasmFeatures::FUNCTION_REFERENCES + | WasmFeatures::MULTI_VALUE + | WasmFeatures::MUTABLE_GLOBAL + | WasmFeatures::SATURATING_FLOAT_TO_INT + | WasmFeatures::SIGN_EXTENSION + | WasmFeatures::TAIL_CALL + | WasmFeatures::WIDE_ARITHMETIC +} + +/// The extended set of WebAssembly features which are enabled when working with the Wasm Component +/// Model +pub(crate) fn supported_component_model_features() -> WasmFeatures { + supported_features() | WasmFeatures::COMPONENT_MODEL +} diff --git a/frontend/wasm/src/miden_abi/mod.rs b/frontend/wasm/src/miden_abi/mod.rs new file mode 100644 index 000000000..d99329c86 --- /dev/null +++ b/frontend/wasm/src/miden_abi/mod.rs @@ -0,0 +1,57 @@ +pub(crate) mod stdlib; +pub(crate) mod transform; +pub(crate) mod tx_kernel; + +use midenc_hir::{interner::Symbol, FunctionType, FxHashMap, SymbolNameComponent, SymbolPath}; +use midenc_hir_symbol::symbols; + +pub(crate) type FunctionTypeMap = FxHashMap; +pub(crate) type ModuleFunctionTypeMap = FxHashMap; + +pub fn is_miden_abi_module(path: &SymbolPath) -> bool { + let module_path = path.without_leaf(); + is_miden_stdlib_module(&module_path) || is_miden_sdk_module(&module_path) +} + +fn is_miden_sdk_module(module_path: &SymbolPath) -> bool { + tx_kernel::signatures().contains_key(module_path) +} + +fn is_miden_stdlib_module(module_path: &SymbolPath) -> bool { + stdlib::signatures().contains_key(module_path) +} + +pub fn miden_abi_function_type(path: &SymbolPath) -> FunctionType { + const STD: &[SymbolNameComponent] = + &[SymbolNameComponent::Root, SymbolNameComponent::Component(symbols::Std)]; + + if path.is_prefixed_by(STD) { + miden_stdlib_function_type(path) + } else { + miden_sdk_function_type(path) + } +} + +/// Get the target Miden ABI tx kernel function type for the given module and function id +pub fn miden_sdk_function_type(path: &SymbolPath) -> FunctionType { + let module_path = path.without_leaf(); + let funcs = tx_kernel::signatures() + .get(module_path.as_ref()) + .unwrap_or_else(|| panic!("No Miden ABI function types found for module {module_path}")); + funcs + .get(&path.name()) + .cloned() + .unwrap_or_else(|| panic!("No Miden ABI function type found for function {path}")) +} + +/// Get the target Miden ABI stdlib function type for the given module and function id +fn miden_stdlib_function_type(path: &SymbolPath) -> FunctionType { + let module_path = path.without_leaf(); + let funcs = stdlib::signatures() + .get(module_path.as_ref()) + .unwrap_or_else(|| panic!("No Miden ABI function types found for module {module_path}")); + funcs + .get(&path.name()) + .cloned() + .unwrap_or_else(|| panic!("No Miden ABI function type found for function {path}")) +} diff --git a/frontend/wasm/src/miden_abi/stdlib.rs b/frontend/wasm/src/miden_abi/stdlib.rs new file mode 100644 index 000000000..91ed49a1a --- /dev/null +++ b/frontend/wasm/src/miden_abi/stdlib.rs @@ -0,0 +1,20 @@ +//! Function types and lowered signatures for the Miden stdlib API functions + +use midenc_hir_symbol::sync::LazyLock; + +use super::ModuleFunctionTypeMap; + +pub(crate) mod crypto; +pub(crate) mod mem; + +pub(crate) fn signatures() -> &'static ModuleFunctionTypeMap { + static TYPES: LazyLock = LazyLock::new(|| { + let mut m: ModuleFunctionTypeMap = Default::default(); + m.extend(crypto::hashes::blake3::signatures()); + m.extend(crypto::hashes::rpo::signatures()); + m.extend(crypto::dsa::rpo_falcon512::signatures()); + m.extend(mem::signatures()); + m + }); + &TYPES +} diff --git a/frontend-wasm/src/miden_abi/stdlib/crypto.rs b/frontend/wasm/src/miden_abi/stdlib/crypto.rs similarity index 100% rename from frontend-wasm/src/miden_abi/stdlib/crypto.rs rename to frontend/wasm/src/miden_abi/stdlib/crypto.rs diff --git a/frontend/wasm/src/miden_abi/stdlib/crypto/dsa/mod.rs b/frontend/wasm/src/miden_abi/stdlib/crypto/dsa/mod.rs new file mode 100644 index 000000000..80fb8a8ff --- /dev/null +++ b/frontend/wasm/src/miden_abi/stdlib/crypto/dsa/mod.rs @@ -0,0 +1 @@ +pub mod rpo_falcon512; diff --git a/frontend/wasm/src/miden_abi/stdlib/crypto/dsa/rpo_falcon512.rs b/frontend/wasm/src/miden_abi/stdlib/crypto/dsa/rpo_falcon512.rs new file mode 100644 index 000000000..24e901668 --- /dev/null +++ b/frontend/wasm/src/miden_abi/stdlib/crypto/dsa/rpo_falcon512.rs @@ -0,0 +1,34 @@ +use midenc_hir::{ + interner::{symbols, Symbol}, + CallConv, FunctionType, SymbolNameComponent, SymbolPath, + Type::*, +}; + +use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; + +pub(crate) const MODULE_ID: &str = "std::crypto::dsa::rpo_falcon512"; + +pub(crate) const RPO_FALCON512_VERIFY: &str = "verify"; + +fn module_path() -> SymbolPath { + // Build 'std::crypto::dsa::rpo_falcon512' using interned symbol components + let parts = [ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Std), + SymbolNameComponent::Component(symbols::Crypto), + SymbolNameComponent::Component(symbols::Dsa), + SymbolNameComponent::Component(symbols::RpoFalcon512), + ]; + SymbolPath::from_iter(parts) +} + +pub(crate) fn signatures() -> ModuleFunctionTypeMap { + let mut m: ModuleFunctionTypeMap = Default::default(); + let mut funcs: FunctionTypeMap = Default::default(); + funcs.insert( + Symbol::from(RPO_FALCON512_VERIFY), + FunctionType::new(CallConv::Wasm, [Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt], []), + ); + m.insert(module_path(), funcs); + m +} diff --git a/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/blake3.rs b/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/blake3.rs new file mode 100644 index 000000000..588d322d7 --- /dev/null +++ b/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/blake3.rs @@ -0,0 +1,42 @@ +use midenc_hir::{ + interner::{symbols, Symbol}, + CallConv, FunctionType, SymbolNameComponent, SymbolPath, + Type::*, +}; + +use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; + +pub const MODULE_ID: &str = "std::crypto::hashes::blake3"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Std), + SymbolNameComponent::Component(symbols::Crypto), + SymbolNameComponent::Component(symbols::Hashes), + SymbolNameComponent::Component(symbols::Blake3), +]; + +pub(crate) const HASH_1TO1: &str = "hash_1to1"; +pub(crate) const HASH_2TO1: &str = "hash_2to1"; + +pub(crate) fn signatures() -> ModuleFunctionTypeMap { + let mut m: ModuleFunctionTypeMap = Default::default(); + let mut blake3: FunctionTypeMap = Default::default(); + blake3.insert( + Symbol::from(HASH_1TO1), + FunctionType::new( + CallConv::Wasm, + [I32, I32, I32, I32, I32, I32, I32, I32], + [I32, I32, I32, I32, I32, I32, I32, I32], + ), + ); + blake3.insert( + Symbol::from(HASH_2TO1), + FunctionType::new( + CallConv::Wasm, + [I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32, I32], + [I32, I32, I32, I32, I32, I32, I32, I32], + ), + ); + m.insert(SymbolPath::from_iter(MODULE_PREFIX.iter().copied()), blake3); + m +} diff --git a/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/mod.rs b/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/mod.rs new file mode 100644 index 000000000..2e2b08344 --- /dev/null +++ b/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/mod.rs @@ -0,0 +1,2 @@ +pub mod blake3; +pub mod rpo; diff --git a/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/rpo.rs b/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/rpo.rs new file mode 100644 index 000000000..5e636b30d --- /dev/null +++ b/frontend/wasm/src/miden_abi/stdlib/crypto/hashes/rpo.rs @@ -0,0 +1,37 @@ +use midenc_hir::{ + interner::{symbols, Symbol}, + CallConv, FunctionType, SymbolNameComponent, SymbolPath, + Type::{Felt, I32}, +}; + +use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; + +pub const MODULE_ID: &str = "std::crypto::hashes::rpo"; + +pub const HASH_MEMORY: &str = "hash_memory"; +pub const HASH_MEMORY_WORDS: &str = "hash_memory_words"; + +pub(crate) fn signatures() -> ModuleFunctionTypeMap { + let mut m: ModuleFunctionTypeMap = Default::default(); + let mut rpo: FunctionTypeMap = Default::default(); + // hash_memory takes (ptr: u32, num_elements: u32) and returns 4 Felt values on the stack + rpo.insert( + Symbol::from(HASH_MEMORY), + FunctionType::new(CallConv::Wasm, [I32, I32], [Felt, Felt, Felt, Felt]), + ); + // hash_memory_words takes (start_addr: u32, end_addr: u32) and returns 4 Felt values on the stack + rpo.insert( + Symbol::from(HASH_MEMORY_WORDS), + FunctionType::new(CallConv::Wasm, [I32, I32], [Felt, Felt, Felt, Felt]), + ); + + let module_path = SymbolPath::from_iter([ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Std), + SymbolNameComponent::Component(symbols::Crypto), + SymbolNameComponent::Component(symbols::Hashes), + SymbolNameComponent::Component(Symbol::intern("rpo")), + ]); + m.insert(module_path, rpo); + m +} diff --git a/frontend/wasm/src/miden_abi/stdlib/mem.rs b/frontend/wasm/src/miden_abi/stdlib/mem.rs new file mode 100644 index 000000000..b3aafacfd --- /dev/null +++ b/frontend/wasm/src/miden_abi/stdlib/mem.rs @@ -0,0 +1,72 @@ +use midenc_hir::{ + interner::{symbols, Symbol}, + CallConv, FunctionType, SymbolNameComponent, SymbolPath, + Type::*, +}; + +use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; + +pub(crate) const MODULE_ID: &str = "std::mem"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Std), + SymbolNameComponent::Component(symbols::Mem), +]; + +pub(crate) const PIPE_WORDS_TO_MEMORY: &str = "pipe_words_to_memory"; +pub(crate) const PIPE_DOUBLE_WORDS_TO_MEMORY: &str = "pipe_double_words_to_memory"; +pub(crate) const PIPE_PREIMAGE_TO_MEMORY: &str = "pipe_preimage_to_memory"; + +pub(crate) fn signatures() -> ModuleFunctionTypeMap { + let mut m: ModuleFunctionTypeMap = Default::default(); + let mut funcs: FunctionTypeMap = Default::default(); + funcs.insert( + Symbol::from(PIPE_WORDS_TO_MEMORY), + FunctionType::new( + CallConv::Wasm, + [ + Felt, // num_words + I32, // write_ptr + ], + [ + Felt, Felt, Felt, Felt, // HASH + I32, // write_ptr' + ], + ), + ); + funcs.insert( + Symbol::from(PIPE_DOUBLE_WORDS_TO_MEMORY), + FunctionType::new( + CallConv::Wasm, + [ + Felt, Felt, Felt, Felt, // C + Felt, Felt, Felt, Felt, // B + Felt, Felt, Felt, Felt, // A + I32, // write_ptr + I32, // end_ptr + ], + [ + Felt, Felt, Felt, Felt, // C + Felt, Felt, Felt, Felt, // B + Felt, Felt, Felt, Felt, // A + I32, // write_ptr + ], + ), + ); + funcs.insert( + Symbol::from(PIPE_PREIMAGE_TO_MEMORY), + FunctionType::new( + CallConv::Wasm, + [ + Felt, // num_words + I32, // write_ptr + Felt, Felt, Felt, Felt, // COM (commitment) + ], + [ + I32, // write_ptr' + ], + ), + ); + m.insert(SymbolPath::from_iter(MODULE_PREFIX.iter().copied()), funcs); + m +} diff --git a/frontend/wasm/src/miden_abi/transform.rs b/frontend/wasm/src/miden_abi/transform.rs new file mode 100644 index 000000000..e5b5d857f --- /dev/null +++ b/frontend/wasm/src/miden_abi/transform.rs @@ -0,0 +1,221 @@ +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::FunctionRef, + interner::{symbols, Symbol}, + Builder, Immediate, PointerType, SymbolNameComponent, SymbolPath, Type, ValueRef, +}; + +use super::{stdlib, tx_kernel}; +use crate::module::function_builder_ext::FunctionBuilderExt; + +/// The strategy to use for transforming a function call +enum TransformStrategy { + /// The Miden ABI function returns a length and a pointer and we only want the length + ListReturn, + /// The Miden ABI function returns on the stack and we want to return via a pointer argument + ReturnViaPointer, + /// No transformation needed + NoTransform, +} + +/// Get the transformation strategy for a function name +fn get_transform_strategy(path: &SymbolPath) -> Option { + let mut components = path.components().peekable(); + components.next_if_eq(&SymbolNameComponent::Root); + + match components.next()?.as_symbol_name() { + symbols::Std => match components.next()?.as_symbol_name() { + symbols::Mem => match components.next_if(|c| c.is_leaf())?.as_symbol_name().as_str() { + stdlib::mem::PIPE_WORDS_TO_MEMORY | stdlib::mem::PIPE_DOUBLE_WORDS_TO_MEMORY => { + Some(TransformStrategy::ReturnViaPointer) + } + stdlib::mem::PIPE_PREIMAGE_TO_MEMORY => Some(TransformStrategy::NoTransform), + _ => None, + }, + symbols::Crypto => match components.next()?.as_symbol_name() { + symbols::Hashes => match components.next()?.as_symbol_name() { + symbols::Blake3 => { + match components.next_if(|c| c.is_leaf())?.as_symbol_name().as_str() { + stdlib::crypto::hashes::blake3::HASH_1TO1 + | stdlib::crypto::hashes::blake3::HASH_2TO1 => { + Some(TransformStrategy::ReturnViaPointer) + } + _ => None, + } + } + name if name == Symbol::intern("rpo") => { + match components.next_if(|c| c.is_leaf())?.as_symbol_name().as_str() { + stdlib::crypto::hashes::rpo::HASH_MEMORY + | stdlib::crypto::hashes::rpo::HASH_MEMORY_WORDS => { + Some(TransformStrategy::ReturnViaPointer) + } + _ => None, + } + } + _ => None, + }, + symbols::Dsa => match components.next()?.as_symbol_name() { + symbols::RpoFalcon512 => { + match components.next_if(|c| c.is_leaf())?.as_symbol_name().as_str() { + stdlib::crypto::dsa::rpo_falcon512::RPO_FALCON512_VERIFY => { + Some(TransformStrategy::NoTransform) + } + _ => None, + } + } + _ => None, + }, + _ => None, + }, + _ => None, + }, + symbols::Miden => match components.next()?.as_symbol_name() { + symbols::Account => { + match components.next_if(|c| c.is_leaf())?.as_symbol_name().as_str() { + tx_kernel::account::INCR_NONCE + | tx_kernel::account::GET_NONCE + | tx_kernel::account::GET_BALANCE => Some(TransformStrategy::NoTransform), + tx_kernel::account::ADD_ASSET + | tx_kernel::account::GET_ID + | tx_kernel::account::REMOVE_ASSET + | tx_kernel::account::GET_INITIAL_COMMITMENT + | tx_kernel::account::COMPUTE_CURRENT_COMMITMENT + | tx_kernel::account::COMPUTE_DELTA_COMMITMENT + | tx_kernel::account::GET_STORAGE_ITEM + | tx_kernel::account::SET_STORAGE_ITEM + | tx_kernel::account::GET_STORAGE_MAP_ITEM + | tx_kernel::account::SET_STORAGE_MAP_ITEM => { + Some(TransformStrategy::ReturnViaPointer) + } + _ => None, + } + } + symbols::Note => match components.next_if(|c| c.is_leaf())?.as_symbol_name().as_str() { + tx_kernel::note::GET_INPUTS => Some(TransformStrategy::ListReturn), + tx_kernel::note::GET_ASSETS => Some(TransformStrategy::ListReturn), + tx_kernel::note::GET_SENDER + | tx_kernel::note::GET_SCRIPT_ROOT + | tx_kernel::note::GET_SERIAL_NUMBER => Some(TransformStrategy::ReturnViaPointer), + _ => None, + }, + symbols::Tx => match components.next_if(|c| c.is_leaf())?.as_symbol_name().as_str() { + tx_kernel::tx::CREATE_NOTE => Some(TransformStrategy::NoTransform), + tx_kernel::tx::ADD_ASSET_TO_NOTE => Some(TransformStrategy::ReturnViaPointer), + tx_kernel::tx::GET_BLOCK_NUMBER => Some(TransformStrategy::NoTransform), + tx_kernel::tx::GET_INPUT_NOTES_COMMITMENT + | tx_kernel::tx::GET_OUTPUT_NOTES_COMMITMENT => { + Some(TransformStrategy::ReturnViaPointer) + } + _ => None, + }, + _ => None, + }, + _ => None, + } +} + +/// Transform a Miden ABI function call based on the transformation strategy +/// +/// `import_func` - import function that we're transforming a call to (think of a MASM function) +/// `args` - arguments to the generated synthetic function +/// Returns results that will be returned from the synthetic function +pub fn transform_miden_abi_call( + import_func_ref: FunctionRef, + import_path: &SymbolPath, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, +) -> Vec { + use TransformStrategy::*; + match get_transform_strategy(import_path) { + Some(ListReturn) => list_return(import_func_ref, args, builder), + Some(ReturnViaPointer) => return_via_pointer(import_func_ref, args, builder), + Some(NoTransform) => no_transform(import_func_ref, args, builder), + None => panic!("no transform strategy implemented for '{import_path}'"), + } +} + +/// No transformation needed +#[inline(always)] +pub fn no_transform( + import_func_ref: FunctionRef, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, +) -> Vec { + let span = import_func_ref.borrow().name().span; + let signature = import_func_ref.borrow().signature().clone(); + let exec = builder + .exec(import_func_ref, signature, args.to_vec(), span) + .expect("failed to build an exec op in no_transform strategy"); + + let borrow = exec.borrow(); + let results_storage = borrow.as_ref().results(); + let results: Vec = + results_storage.iter().map(|op_res| op_res.borrow().as_value_ref()).collect(); + results +} + +/// The Miden ABI function returns a length and a pointer and we only want the length +pub fn list_return( + import_func_ref: FunctionRef, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, +) -> Vec { + let span = import_func_ref.borrow().name().span; + let signature = import_func_ref.borrow().signature().clone(); + let exec = builder + .exec(import_func_ref, signature, args.to_vec(), span) + .expect("failed to build an exec op in list_return strategy"); + + let borrow = exec.borrow(); + let results_storage = borrow.as_ref().results(); + let results: Vec = + results_storage.iter().map(|op_res| op_res.borrow().as_value_ref()).collect(); + + assert_eq!(results.len(), 2, "List return strategy expects 2 results: length and pointer"); + // Return the first result (length) only + results[0..1].to_vec() +} + +/// The Miden ABI function returns felts on the stack and we want to return via a pointer argument +pub fn return_via_pointer( + import_func_ref: FunctionRef, + args: &[ValueRef], + builder: &mut FunctionBuilderExt<'_, B>, +) -> Vec { + let span = import_func_ref.borrow().name().span; + // Omit the last argument (pointer) + let args_wo_pointer = &args[0..args.len() - 1]; + let signature = import_func_ref.borrow().signature().clone(); + let exec = builder + .exec(import_func_ref, signature, args_wo_pointer.to_vec(), span) + .expect("failed to build an exec op in return_via_pointer strategy"); + + let borrow = exec.borrow(); + let results_storage = borrow.as_ref().results(); + let results: Vec = + results_storage.iter().map(|op_res| op_res.borrow().as_value_ref()).collect(); + + let ptr_arg = *args.last().expect("empty args"); + let ptr_arg_ty = ptr_arg.borrow().ty().clone(); + assert_eq!(ptr_arg_ty, Type::I32); + let ptr_u32 = builder.bitcast(ptr_arg, Type::U32, span).expect("failed bitcast to U32"); + + let result_ty = midenc_hir::StructType::new(results.iter().map(|v| (*v).borrow().ty().clone())); + for (idx, value) in results.iter().enumerate() { + let value_ty = (*value).borrow().ty().clone().clone(); + let eff_ptr = if idx == 0 { + // We're assuming here that the base pointer is of the correct alignment + ptr_u32 + } else { + let imm = Immediate::U32(result_ty.get(idx).offset); + let imm_val = builder.imm(imm, span); + builder.add(ptr_u32, imm_val, span).expect("failed add") + }; + let addr = builder + .inttoptr(eff_ptr, Type::from(PointerType::new(value_ty)), span) + .expect("failed inttoptr"); + builder.store(addr, *value, span).expect("failed store"); + } + Vec::new() +} diff --git a/frontend-wasm/src/miden_abi/tx_kernel.rs b/frontend/wasm/src/miden_abi/tx_kernel.rs similarity index 76% rename from frontend-wasm/src/miden_abi/tx_kernel.rs rename to frontend/wasm/src/miden_abi/tx_kernel.rs index df92dd5be..6d92e1109 100644 --- a/frontend-wasm/src/miden_abi/tx_kernel.rs +++ b/frontend/wasm/src/miden_abi/tx_kernel.rs @@ -4,17 +4,17 @@ pub(crate) mod account; pub(crate) mod note; pub(crate) mod tx; -use std::sync::OnceLock; +use midenc_hir_symbol::sync::LazyLock; use super::ModuleFunctionTypeMap; pub(crate) fn signatures() -> &'static ModuleFunctionTypeMap { - static TYPES: OnceLock = OnceLock::new(); - TYPES.get_or_init(|| { + static TYPES: LazyLock = LazyLock::new(|| { let mut m: ModuleFunctionTypeMap = Default::default(); m.extend(account::signatures()); m.extend(note::signatures()); m.extend(tx::signatures()); m - }) + }); + &TYPES } diff --git a/frontend/wasm/src/miden_abi/tx_kernel/account.rs b/frontend/wasm/src/miden_abi/tx_kernel/account.rs new file mode 100644 index 000000000..40012c6e6 --- /dev/null +++ b/frontend/wasm/src/miden_abi/tx_kernel/account.rs @@ -0,0 +1,86 @@ +use midenc_hir::{ + interner::{symbols, Symbol}, + CallConv, FunctionType, SymbolNameComponent, SymbolPath, + Type::*, +}; + +use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; + +pub const MODULE_ID: &str = "miden::account"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Miden), + SymbolNameComponent::Component(symbols::Account), +]; + +pub const ADD_ASSET: &str = "add_asset"; +pub const REMOVE_ASSET: &str = "remove_asset"; +pub const GET_ID: &str = "get_id"; +pub const GET_NONCE: &str = "get_nonce"; +pub const GET_INITIAL_COMMITMENT: &str = "get_initial_commitment"; +pub const COMPUTE_CURRENT_COMMITMENT: &str = "compute_current_commitment"; +pub const COMPUTE_DELTA_COMMITMENT: &str = "compute_delta_commitment"; +pub const GET_STORAGE_ITEM: &str = "get_item"; +pub const SET_STORAGE_ITEM: &str = "set_item"; +pub const GET_STORAGE_MAP_ITEM: &str = "get_map_item"; +pub const SET_STORAGE_MAP_ITEM: &str = "set_map_item"; +pub const INCR_NONCE: &str = "incr_nonce"; +pub const GET_BALANCE: &str = "get_balance"; + +pub(crate) fn signatures() -> ModuleFunctionTypeMap { + let mut m: ModuleFunctionTypeMap = Default::default(); + let mut account: FunctionTypeMap = Default::default(); + account.insert( + Symbol::from(ADD_ASSET), + FunctionType::new(CallConv::Wasm, [Felt, Felt, Felt, Felt], [Felt, Felt, Felt, Felt]), + ); + account.insert( + Symbol::from(REMOVE_ASSET), + FunctionType::new(CallConv::Wasm, [Felt, Felt, Felt, Felt], [Felt, Felt, Felt, Felt]), + ); + account.insert(Symbol::from(GET_ID), FunctionType::new(CallConv::Wasm, [], [Felt, Felt])); + account.insert(Symbol::from(GET_NONCE), FunctionType::new(CallConv::Wasm, [], [Felt])); + account.insert( + Symbol::from(GET_INITIAL_COMMITMENT), + FunctionType::new(CallConv::Wasm, [], [Felt, Felt, Felt, Felt]), + ); + account.insert( + Symbol::from(COMPUTE_CURRENT_COMMITMENT), + FunctionType::new(CallConv::Wasm, [], [Felt, Felt, Felt, Felt]), + ); + account.insert( + Symbol::from(COMPUTE_DELTA_COMMITMENT), + FunctionType::new(CallConv::Wasm, [], [Felt, Felt, Felt, Felt]), + ); + account.insert( + Symbol::from(GET_STORAGE_ITEM), + FunctionType::new(CallConv::Wasm, [Felt], [Felt, Felt, Felt, Felt]), + ); + account.insert( + Symbol::from(SET_STORAGE_ITEM), + FunctionType::new( + CallConv::Wasm, + [Felt, Felt, Felt, Felt, Felt], + [Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt], + ), + ); + account.insert( + Symbol::from(GET_STORAGE_MAP_ITEM), + FunctionType::new(CallConv::Wasm, [Felt, Felt, Felt, Felt, Felt], [Felt, Felt, Felt, Felt]), + ); + account.insert( + Symbol::from(SET_STORAGE_MAP_ITEM), + FunctionType::new( + CallConv::Wasm, + [Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt], + [Felt, Felt, Felt, Felt, Felt, Felt, Felt, Felt], + ), + ); + account.insert(Symbol::from(INCR_NONCE), FunctionType::new(CallConv::Wasm, [], [Felt])); + account.insert( + Symbol::from(GET_BALANCE), + FunctionType::new(CallConv::Wasm, [Felt, Felt], [Felt]), + ); + m.insert(SymbolPath::from_iter(MODULE_PREFIX.iter().copied()), account); + m +} diff --git a/frontend/wasm/src/miden_abi/tx_kernel/note.rs b/frontend/wasm/src/miden_abi/tx_kernel/note.rs new file mode 100644 index 000000000..75347508f --- /dev/null +++ b/frontend/wasm/src/miden_abi/tx_kernel/note.rs @@ -0,0 +1,38 @@ +use midenc_hir::{ + interner::{symbols, Symbol}, + CallConv, FunctionType, SymbolNameComponent, SymbolPath, + Type::*, +}; + +use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; + +pub const MODULE_ID: &str = "miden::note"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Miden), + SymbolNameComponent::Component(symbols::Note), +]; + +pub const GET_INPUTS: &str = "get_inputs"; +pub const GET_ASSETS: &str = "get_assets"; +pub const GET_SENDER: &str = "get_sender"; +pub const GET_SCRIPT_ROOT: &str = "get_script_root"; +pub const GET_SERIAL_NUMBER: &str = "get_serial_number"; + +pub(crate) fn signatures() -> ModuleFunctionTypeMap { + let mut m: ModuleFunctionTypeMap = Default::default(); + let mut note: FunctionTypeMap = Default::default(); + note.insert(Symbol::from(GET_INPUTS), FunctionType::new(CallConv::Wasm, [I32], [I32, I32])); + note.insert(Symbol::from(GET_ASSETS), FunctionType::new(CallConv::Wasm, [I32], [I32, I32])); + note.insert(Symbol::from(GET_SENDER), FunctionType::new(CallConv::Wasm, [], [Felt, Felt])); + note.insert( + Symbol::from(GET_SCRIPT_ROOT), + FunctionType::new(CallConv::Wasm, [], [Felt, Felt, Felt, Felt]), + ); + note.insert( + Symbol::from(GET_SERIAL_NUMBER), + FunctionType::new(CallConv::Wasm, [], [Felt, Felt, Felt, Felt]), + ); + m.insert(SymbolPath::from_iter(MODULE_PREFIX.iter().copied()), note); + m +} diff --git a/frontend/wasm/src/miden_abi/tx_kernel/tx.rs b/frontend/wasm/src/miden_abi/tx_kernel/tx.rs new file mode 100644 index 000000000..6fbc0605e --- /dev/null +++ b/frontend/wasm/src/miden_abi/tx_kernel/tx.rs @@ -0,0 +1,65 @@ +use midenc_hir::{ + interner::{symbols, Symbol}, + CallConv, FunctionType, SymbolNameComponent, SymbolPath, + Type::*, +}; + +use crate::miden_abi::{FunctionTypeMap, ModuleFunctionTypeMap}; + +pub const MODULE_ID: &str = "miden::tx"; +pub(crate) const MODULE_PREFIX: &[SymbolNameComponent] = &[ + SymbolNameComponent::Root, + SymbolNameComponent::Component(symbols::Miden), + SymbolNameComponent::Component(symbols::Tx), +]; + +pub const CREATE_NOTE: &str = "create_note"; +pub const ADD_ASSET_TO_NOTE: &str = "add_asset_to_note"; +pub const GET_BLOCK_NUMBER: &str = "get_block_number"; +pub const GET_INPUT_NOTES_COMMITMENT: &str = "get_input_notes_commitment"; +pub const GET_OUTPUT_NOTES_COMMITMENT: &str = "get_output_notes_commitment"; + +pub(crate) fn signatures() -> ModuleFunctionTypeMap { + let mut m: ModuleFunctionTypeMap = Default::default(); + let mut note: FunctionTypeMap = Default::default(); + note.insert( + Symbol::from(CREATE_NOTE), + FunctionType::new( + CallConv::Wasm, + [ + Felt, // tag + Felt, // aux + Felt, // note_type + Felt, // execution-hint + // recipient (4 felts) + Felt, Felt, Felt, Felt, + ], + [Felt], + ), + ); + note.insert( + Symbol::from(ADD_ASSET_TO_NOTE), + FunctionType::new( + CallConv::Wasm, + [ + Felt, Felt, Felt, Felt, // asset (4 felts) + Felt, // note_idx + ], + [ + Felt, Felt, Felt, Felt, // asset (4 felts) + Felt, // note_idx + ], + ), + ); + note.insert(Symbol::from(GET_BLOCK_NUMBER), FunctionType::new(CallConv::Wasm, [], [Felt])); + note.insert( + Symbol::from(GET_INPUT_NOTES_COMMITMENT), + FunctionType::new(CallConv::Wasm, [], [Felt, Felt, Felt, Felt]), + ); + note.insert( + Symbol::from(GET_OUTPUT_NOTES_COMMITMENT), + FunctionType::new(CallConv::Wasm, [], [Felt, Felt, Felt, Felt]), + ); + m.insert(SymbolPath::from_iter(MODULE_PREFIX.iter().copied()), note); + m +} diff --git a/frontend/wasm/src/module/build_ir.rs b/frontend/wasm/src/module/build_ir.rs new file mode 100644 index 000000000..9f7f6c536 --- /dev/null +++ b/frontend/wasm/src/module/build_ir.rs @@ -0,0 +1,224 @@ +use core::mem; +use std::rc::Rc; + +use midenc_hir::{ + constants::ConstantData, + dialects::builtin::{ + self, BuiltinOpBuilder, ComponentBuilder, ModuleBuilder, World, WorldBuilder, + }, + interner::Symbol, + version::Version, + Builder, BuilderExt, Context, FxHashMap, Ident, Op, OpBuilder, Visibility, +}; +use midenc_session::diagnostics::{DiagnosticsHandler, IntoDiagnostic, Severity, SourceSpan}; +use wasmparser::Validator; + +use super::{ + module_translation_state::ModuleTranslationState, types::ModuleTypesBuilder, MemoryIndex, +}; +use crate::{ + error::WasmResult, + module::{ + func_translator::FuncTranslator, + linker_stubs::maybe_lower_linker_stub, + module_env::{FunctionBodyData, ModuleEnvironment, ParsedModule}, + types::ir_type, + }, + WasmTranslationConfig, +}; + +/// Translate a valid Wasm core module binary into Miden IR component building +/// component imports for well-known Miden ABI functions +/// +/// This is a temporary solution until we compile an account code as Wasm +/// component. To be able to do it we need wit-bindgen type re-mapping implemented first (see +/// https://github.com/0xMiden/compiler/issues/116) +pub fn translate_module_as_component( + wasm: &[u8], + config: &WasmTranslationConfig, + context: Rc, +) -> WasmResult { + let mut validator = Validator::new_with_features(crate::supported_features()); + let parser = wasmparser::Parser::new(0); + let mut module_types_builder = Default::default(); + let mut parsed_module = ModuleEnvironment::new( + config, + &mut validator, + &mut module_types_builder, + ) + .parse(parser, wasm, context.diagnostics())?; + parsed_module.module.set_name_fallback(config.source_name.clone()); + if let Some(name_override) = config.override_name.as_ref() { + parsed_module.module.set_name_override(name_override.clone()); + } + let module_types = module_types_builder; + + // If a world wasn't provided to us, create one + let world_ref = match config.world { + Some(world) => world, + None => context.clone().builder().create::(Default::default())()?, + }; + let mut world_builder = WorldBuilder::new(world_ref); + + let ns = Ident::from("root_ns"); + let name = Ident::from("root"); + let ver = Version::parse("1.0.0").unwrap(); + let component_ref = world_builder.define_component(ns, name, ver)?; + let mut cb = ComponentBuilder::new(component_ref); + let module_name = parsed_module.module.name().as_str(); + let module_ref = cb.define_module(Ident::from(module_name)).unwrap(); + + let mut module_builder = ModuleBuilder::new(module_ref); + let mut module_state = ModuleTranslationState::new( + &parsed_module.module, + &mut module_builder, + &mut world_builder, + &module_types, + FxHashMap::default(), + context.diagnostics(), + )?; + build_ir_module(&mut parsed_module, &module_types, &mut module_state, config, context)?; + + Ok(component_ref) +} + +pub fn build_ir_module( + parsed_module: &mut ParsedModule, + module_types: &ModuleTypesBuilder, + module_state: &mut ModuleTranslationState, + _config: &WasmTranslationConfig, + context: Rc, +) -> WasmResult<()> { + let _memory_size = parsed_module + .module + .memories + .get(MemoryIndex::from_u32(0)) + .map(|mem| mem.minimum as u32); + + build_globals(&parsed_module.module, module_state.module_builder, context.diagnostics())?; + build_data_segments(parsed_module, module_state.module_builder, context.diagnostics())?; + let addr2line = addr2line::Context::from_dwarf(gimli::Dwarf { + debug_abbrev: parsed_module.debuginfo.dwarf.debug_abbrev, + debug_addr: parsed_module.debuginfo.dwarf.debug_addr, + debug_aranges: parsed_module.debuginfo.dwarf.debug_aranges, + debug_info: parsed_module.debuginfo.dwarf.debug_info, + debug_line: parsed_module.debuginfo.dwarf.debug_line, + debug_line_str: parsed_module.debuginfo.dwarf.debug_line_str, + debug_str: parsed_module.debuginfo.dwarf.debug_str, + debug_str_offsets: parsed_module.debuginfo.dwarf.debug_str_offsets, + debug_types: parsed_module.debuginfo.dwarf.debug_types, + locations: parsed_module.debuginfo.dwarf.locations, + ranges: parsed_module.debuginfo.dwarf.ranges, + file_type: parsed_module.debuginfo.dwarf.file_type, + sup: parsed_module.debuginfo.dwarf.sup.clone(), + ..Default::default() + }) + .into_diagnostic()?; + let mut func_translator = FuncTranslator::new(context.clone()); + // Although this renders this parsed module invalid(without functiong + // bodies), we don't support multiple module instances. Thus, this + // ParseModule will not be used again to make another module instance. + let func_body_inputs = mem::take(&mut parsed_module.function_body_inputs); + for (defined_func_idx, body_data) in func_body_inputs { + let func_index = parsed_module.module.func_index(defined_func_idx); + let func_name = parsed_module.module.func_name(func_index).as_str(); + + let function_ref = + module_state.module_builder.get_function(func_name).unwrap_or_else(|| { + panic!("cannot build {func_name} function, since it is not defined in the module.") + }); + // If this is a linker stub, synthesize its body to exec the MASM callee. + // Note: Intrinsics and Miden ABI (SDK/stdlib) calls are expected to be + // surfaced via such linker stubs rather than as core-wasm imports. + if maybe_lower_linker_stub(function_ref, &body_data.body, module_state)? { + continue; + } + + let FunctionBodyData { validator, body } = body_data; + let mut func_validator = validator.into_validator(Default::default()); + func_translator.translate_body( + &body, + function_ref, + module_state, + parsed_module, + module_types, + &addr2line, + context.session(), + &mut func_validator, + )?; + } + Ok(()) +} + +fn build_globals( + wasm_module: &crate::module::Module, + module_builder: &mut ModuleBuilder, + diagnostics: &DiagnosticsHandler, +) -> WasmResult<()> { + let span = SourceSpan::default(); + for (global_idx, global) in &wasm_module.globals { + let global_name = wasm_module + .name_section + .globals_names + .get(&global_idx) + .cloned() + .unwrap_or(Symbol::intern(format!("gv{}", global_idx.as_u32()))); + let global_init = wasm_module.try_global_initializer(global_idx, diagnostics)?; + let visibility = if wasm_module.is_exported(global_idx.into()) { + Visibility::Public + } else { + Visibility::Private + }; + let mut global_var_ref = module_builder + .define_global_variable( + global_name.into(), + visibility, + ir_type(global.ty, diagnostics)?, + ) + .map_err(|e| { + diagnostics + .diagnostic(Severity::Error) + .with_message( + (format!( + "Failed to declare global variable '{global_name}' with error: {e:?}" + )) + .clone(), + ) + .into_report() + })?; + let context = global_var_ref.borrow().as_operation().context_rc().clone(); + let init_region_ref = { + let mut global_var = global_var_ref.borrow_mut(); + let region_ref = global_var.initializer_mut().as_region_ref(); + region_ref + }; + let mut op_builder = OpBuilder::new(context); + op_builder.create_block(init_region_ref, None, &[]); + op_builder.ret_imm(global_init.to_imm(wasm_module, diagnostics)?, span)?; + } + Ok(()) +} + +fn build_data_segments( + translation: &ParsedModule, + module_builder: &mut ModuleBuilder, + diagnostics: &DiagnosticsHandler, +) -> WasmResult<()> { + for (data_segment_idx, data_segment) in &translation.data_segments { + let data_segment_name = + translation.module.name_section.data_segment_names[&data_segment_idx]; + let readonly = data_segment_name.as_str().contains(".rodata"); + let offset = data_segment.offset.as_i32(&translation.module, diagnostics)? as u32; + let init = ConstantData::from(data_segment.data.to_vec()); + let size = init.len() as u32; + if let Err(e) = + module_builder.define_data_segment(offset, init, readonly, SourceSpan::default()) + { + return Err(e.wrap_err(format!( + "Failed to declare data segment '{data_segment_name}' with size '{size}' at \ + '{offset:#x}'" + ))); + } + } + Ok(()) +} diff --git a/frontend-wasm/src/module/func_translation_state.rs b/frontend/wasm/src/module/func_translation_state.rs similarity index 81% rename from frontend-wasm/src/module/func_translation_state.rs rename to frontend/wasm/src/module/func_translation_state.rs index a4b39c9e4..154278798 100644 --- a/frontend-wasm/src/module/func_translation_state.rs +++ b/frontend/wasm/src/module/func_translation_state.rs @@ -5,11 +5,11 @@ //! //! Based on Cranelift's Wasm -> CLIF translator v11.0.0 -use midenc_hir::{diagnostics::SourceSpan, Block, Inst, InstBuilder, Signature, Value}; -use midenc_hir_type::Type; +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{BlockRef, Builder, OperationRef, Signature, SourceSpan, Type, ValueRef}; use super::function_builder_ext::FunctionBuilderExt; -use crate::module::types::BlockType; +use crate::{error::WasmResult, module::types::BlockType}; /// Information about the presence of an associated `else` for an `if`, or the /// lack thereof. @@ -23,10 +23,10 @@ pub enum ElseData { /// If we discover that we need an `else` block, this is the jump /// instruction that needs to be fixed up to point to the new `else` /// block rather than the destination block after the `if...end`. - branch_inst: Inst, + branch_inst: OperationRef, /// The placeholder block we're replacing. - placeholder: Block, + placeholder: BlockRef, }, /// We have already allocated an `else` block. @@ -37,23 +37,23 @@ pub enum ElseData { /// these cases, we pre-allocate the `else` block. WithElse { /// This is the `else` block. - else_block: Block, + else_block: BlockRef, }, } /// A control stack frame can be an `if`, a `block` or a `loop`, each one having the following /// fields: /// -/// - `destination`: reference to the `Block` that will hold the code after the control block; +/// - `destination`: reference to the `BlockRef` that will hold the code after the control block; /// - `num_return_values`: number of values returned by the control block; /// - `original_stack_size`: size of the value stack at the beginning of the control block. /// -/// The `loop` frame has a `header` field that references the `Block` that contains the beginning +/// The `loop` frame has a `header` field that references the `BlockRef` that contains the beginning /// of the body of the loop. #[derive(Debug)] pub enum ControlStackFrame { If { - destination: Block, + destination: BlockRef, else_data: ElseData, num_param_values: usize, num_return_values: usize, @@ -73,15 +73,15 @@ pub enum ControlStackFrame { // `state.reachable` when we hit the `end` in the `if .. else .. end`. }, Block { - destination: Block, + destination: BlockRef, num_param_values: usize, num_return_values: usize, original_stack_size: usize, exit_is_branched_to: bool, }, Loop { - destination: Block, - header: Block, + destination: BlockRef, + header: BlockRef, num_param_values: usize, num_return_values: usize, original_stack_size: usize, @@ -118,7 +118,7 @@ impl ControlStackFrame { } } - pub fn following_code(&self) -> Block { + pub fn following_code(&self) -> BlockRef { match *self { Self::If { destination, .. } | Self::Block { destination, .. } @@ -126,7 +126,7 @@ impl ControlStackFrame { } } - pub fn br_destination(&self) -> Block { + pub fn br_destination(&self) -> BlockRef { match *self { Self::If { destination, .. } | Self::Block { destination, .. } => destination, Self::Loop { header, .. } => header, @@ -189,14 +189,14 @@ impl ControlStackFrame { /// Pop values from the value stack so that it is left at the /// input-parameters to an else-block. - pub fn truncate_value_stack_to_else_params(&self, stack: &mut Vec) { + pub fn truncate_value_stack_to_else_params(&self, stack: &mut Vec) { debug_assert!(matches!(self, &ControlStackFrame::If { .. })); stack.truncate(self.original_stack_size()); } /// Pop values from the value stack so that it is left at the state it was /// before this control-flow frame. - pub fn truncate_value_stack_to_original_size(&self, stack: &mut Vec) { + pub fn truncate_value_stack_to_original_size(&self, stack: &mut Vec) { // The "If" frame pushes its parameters twice, so they're available to the else block // (see also `FuncTranslationState::push_if`). // Yet, the original_stack_size member accounts for them only once, so that the else @@ -223,7 +223,7 @@ impl ControlStackFrame { pub struct FuncTranslationState { /// A stack of values corresponding to the active values in the input wasm function at this /// point. - pub(crate) stack: Vec, + pub(crate) stack: Vec, /// A stack of active control flow operations at this point in the input wasm function. pub(crate) control_stack: Vec, /// Is the current translation state still reachable? This is false when translating operators @@ -251,115 +251,80 @@ impl FuncTranslationState { /// /// This resets the state to containing only a single block representing the whole function. /// The exit block is the last block in the function which will contain the return instruction. - pub(crate) fn initialize(&mut self, sig: &Signature, exit_block: Block) { + pub(crate) fn initialize(&mut self, sig: &Signature, exit_block: BlockRef) { self.clear(); self.push_block(exit_block, 0, sig.results().len()); } /// Push a value. - pub(crate) fn push1(&mut self, val: Value) { + pub(crate) fn push1(&mut self, val: ValueRef) { self.stack.push(val); } /// Push multiple values. - pub(crate) fn pushn(&mut self, vals: &[Value]) { + pub(crate) fn pushn(&mut self, vals: &[ValueRef]) { self.stack.extend_from_slice(vals); } /// Pop one value. - pub(crate) fn pop1(&mut self) -> Value { + pub(crate) fn pop1(&mut self) -> ValueRef { self.stack.pop().expect("attempted to pop a value from an empty stack") } - /// Pop one value and cast it to the specified type. - pub(crate) fn pop1_casted( - &mut self, - ty: Type, - builder: &mut FunctionBuilderExt, - span: SourceSpan, - ) -> Value { - let val = self.stack.pop().expect("attempted to pop a value from an empty stack"); - if builder.data_flow_graph().value_type(val) != &ty { - builder.ins().cast(val, ty, span) - } else { - val - } - } - /// Pop one value and bitcast it to the specified type. - pub(crate) fn pop1_bitcasted( + pub(crate) fn pop1_bitcasted( &mut self, ty: Type, - builder: &mut FunctionBuilderExt, + builder: &mut FunctionBuilderExt<'_, B>, span: SourceSpan, - ) -> Value { + ) -> ValueRef { let val = self.stack.pop().expect("attempted to pop a value from an empty stack"); - if builder.data_flow_graph().value_type(val) != &ty { - builder.ins().bitcast(val, ty, span) + if val.borrow().ty() != &ty { + builder + .bitcast(val, ty.clone(), span) + .unwrap_or_else(|_| panic!("failed to bitcast {val:?} to {ty:?}")) } else { val } } /// Peek at the top of the stack without popping it. - pub(crate) fn peek1(&self) -> Value { + pub(crate) fn peek1(&self) -> ValueRef { *self.stack.last().expect("attempted to peek at a value on an empty stack") } /// Pop two values. Return them in the order they were pushed. - pub(crate) fn pop2(&mut self) -> (Value, Value) { - let v2 = self.stack.pop().unwrap(); - let v1 = self.stack.pop().unwrap(); - (v1, v2) - } - - /// Pop two values. Cast them to the specified type. Return them in the order they were pushed. - pub(crate) fn pop2_casted( - &mut self, - ty: Type, - builder: &mut FunctionBuilderExt, - span: SourceSpan, - ) -> (Value, Value) { + pub(crate) fn pop2(&mut self) -> (ValueRef, ValueRef) { let v2 = self.stack.pop().unwrap(); let v1 = self.stack.pop().unwrap(); - let v1 = if builder.data_flow_graph().value_type(v1) != &ty { - builder.ins().cast(v1, ty.clone(), span) - } else { - v1 - }; - let v2 = if builder.data_flow_graph().value_type(v2) != &ty { - builder.ins().cast(v2, ty, span) - } else { - v2 - }; (v1, v2) } /// Pop two values. Bitcast them to the specified type. Return them in the order they were /// pushed. - pub(crate) fn pop2_bitcasted( + pub(crate) fn pop2_bitcasted( &mut self, ty: Type, - builder: &mut FunctionBuilderExt, + builder: &mut FunctionBuilderExt<'_, B>, span: SourceSpan, - ) -> (Value, Value) { + ) -> WasmResult<(ValueRef, ValueRef)> { let v2 = self.stack.pop().unwrap(); let v1 = self.stack.pop().unwrap(); - let v1 = if builder.data_flow_graph().value_type(v1) != &ty { - builder.ins().bitcast(v1, ty.clone(), span) + let v1 = if v1.borrow().ty() != &ty { + builder.bitcast(v1, ty.clone(), span)? } else { v1 }; - let v2 = if builder.data_flow_graph().value_type(v2) != &ty { - builder.ins().bitcast(v2, ty, span) + let v2 = if v2.borrow().ty() != &ty { + builder.bitcast(v2, ty, span)? } else { v2 }; - (v1, v2) + Ok((v1, v2)) } /// Pop three values. Return them in the order they were pushed. - pub(crate) fn pop3(&mut self) -> (Value, Value, Value) { + pub(crate) fn pop3(&mut self) -> (ValueRef, ValueRef, ValueRef) { let v3 = self.stack.pop().unwrap(); let v2 = self.stack.pop().unwrap(); let v1 = self.stack.pop().unwrap(); @@ -388,13 +353,13 @@ impl FuncTranslationState { } /// Peek at the top `n` values on the stack in the order they were pushed. - pub(crate) fn peekn(&self, n: usize) -> &[Value] { + pub(crate) fn peekn(&self, n: usize) -> &[ValueRef] { self.ensure_length_is_at_least(n); &self.stack[self.stack.len() - n..] } /// Peek at the top `n` values on the stack in the order they were pushed. - pub(crate) fn peekn_mut(&mut self, n: usize) -> &mut [Value] { + pub(crate) fn peekn_mut(&mut self, n: usize) -> &mut [ValueRef] { self.ensure_length_is_at_least(n); let len = self.stack.len(); &mut self.stack[len - n..] @@ -403,7 +368,7 @@ impl FuncTranslationState { /// Push a block on the control stack. pub(crate) fn push_block( &mut self, - following_code: Block, + following_code: BlockRef, num_param_types: usize, num_result_types: usize, ) { @@ -420,8 +385,8 @@ impl FuncTranslationState { /// Push a loop on the control stack. pub(crate) fn push_loop( &mut self, - header: Block, - following_code: Block, + header: BlockRef, + following_code: BlockRef, num_param_types: usize, num_result_types: usize, ) { @@ -438,7 +403,7 @@ impl FuncTranslationState { /// Push an if on the control stack. pub(crate) fn push_if( &mut self, - destination: Block, + destination: BlockRef, else_data: ElseData, num_param_types: usize, num_result_types: usize, diff --git a/frontend-wasm/src/module/func_translator.rs b/frontend/wasm/src/module/func_translator.rs similarity index 77% rename from frontend-wasm/src/module/func_translator.rs rename to frontend/wasm/src/module/func_translator.rs index 1f9177f38..8ec7e0f77 100644 --- a/frontend-wasm/src/module/func_translator.rs +++ b/frontend/wasm/src/module/func_translator.rs @@ -6,15 +6,24 @@ //! //! Based on Cranelift's Wasm -> CLIF translator v11.0.0 +use std::{cell::RefCell, rc::Rc}; + +use cranelift_entity::EntityRef; use midenc_hir::{ - cranelift_entity::EntityRef, + diagnostics::{ColumnNumber, LineNumber}, + dialects::builtin::{BuiltinOpBuilder, FunctionRef}, + BlockRef, Builder, Context, Op, +}; +use midenc_session::{ diagnostics::{DiagnosticsHandler, IntoDiagnostic, SourceManagerExt, SourceSpan}, - Block, InstBuilder, ModuleFunctionBuilder, + Session, }; -use midenc_session::Session; use wasmparser::{FuncValidator, FunctionBody, WasmModuleResources}; -use super::{module_env::ParsedModule, module_translation_state::ModuleTranslationState}; +use super::{ + function_builder_ext::SSABuilderListener, module_env::ParsedModule, + module_translation_state::ModuleTranslationState, types::ModuleTypesBuilder, +}; use crate::{ code_translator::translate_operator, error::WasmResult, @@ -22,7 +31,7 @@ use crate::{ func_translation_state::FuncTranslationState, function_builder_ext::{FunctionBuilderContext, FunctionBuilderExt}, module_env::DwarfReader, - types::{convert_valtype, ir_type, ModuleTypes}, + types::{convert_valtype, ir_type}, }, ssa::Variable, translation_utils::emit_zero, @@ -34,15 +43,15 @@ use crate::{ /// by a `FuncEnvironment` object. A single translator instance can be reused to translate multiple /// functions which will reduce heap allocation traffic. pub struct FuncTranslator { - func_ctx: FunctionBuilderContext, + func_ctx: Rc>, state: FuncTranslationState, } impl FuncTranslator { /// Create a new translator. - pub fn new() -> Self { + pub fn new(context: Rc) -> Self { Self { - func_ctx: FunctionBuilderContext::new(), + func_ctx: Rc::new(RefCell::new(FunctionBuilderContext::new(context))), state: FuncTranslationState::new(), } } @@ -52,15 +61,20 @@ impl FuncTranslator { pub fn translate_body( &mut self, body: &FunctionBody<'_>, - mod_func_builder: &mut ModuleFunctionBuilder, + // mod_func_builder: &mut FunctionBuilder<'_>, + func: FunctionRef, module_state: &mut ModuleTranslationState, module: &ParsedModule<'_>, - mod_types: &ModuleTypes, + mod_types: &ModuleTypesBuilder, addr2line: &addr2line::Context>, session: &Session, func_validator: &mut FuncValidator, ) -> WasmResult<()> { - let mut builder = FunctionBuilderExt::new(mod_func_builder, &mut self.func_ctx); + let context = func.borrow().as_operation().context_rc(); + let mut op_builder = midenc_hir::OpBuilder::new(context) + .with_listener(SSABuilderListener::new(self.func_ctx.clone())); + let mut builder = FunctionBuilderExt::new(func, &mut op_builder); + let entry_block = builder.current_block(); builder.seal_block(entry_block); // Declare all predecessors known. @@ -70,7 +84,10 @@ impl FuncTranslator { // function and its return values. let exit_block = builder.create_block(); builder.append_block_params_for_function_returns(exit_block); - self.state.initialize(builder.signature(), exit_block); + { + let signature = builder.signature(); + self.state.initialize(&signature, exit_block); + } let mut reader = body.get_locals_reader().into_diagnostic()?; @@ -103,16 +120,19 @@ impl FuncTranslator { /// Declare local variables for the signature parameters that correspond to WebAssembly locals. /// /// Return the number of local variables declared. -fn declare_parameters(builder: &mut FunctionBuilderExt, entry_block: Block) -> usize { +fn declare_parameters( + builder: &mut FunctionBuilderExt<'_, B>, + entry_block: BlockRef, +) -> usize { let sig_len = builder.signature().params().len(); let mut next_local = 0; for i in 0..sig_len { - let abi_param = &builder.signature().params()[i]; + let abi_param = builder.signature().params()[i].clone(); let local = Variable::new(next_local); - builder.declare_var(local, abi_param.ty.clone()); + builder.declare_var(local, abi_param.ty); next_local += 1; - let param_value = builder.block_params(entry_block)[i]; + let param_value = entry_block.borrow().arguments()[i]; builder.def_var(local, param_value); } next_local @@ -121,9 +141,9 @@ fn declare_parameters(builder: &mut FunctionBuilderExt, entry_block: Block) -> u /// Parse the local variable declarations that precede the function body. /// /// Declare local variables, starting from `num_params`. -fn parse_local_decls( +fn parse_local_decls( reader: &mut wasmparser::LocalsReader<'_>, - builder: &mut FunctionBuilderExt, + builder: &mut FunctionBuilderExt<'_, B>, num_params: usize, validator: &mut FuncValidator, diagnostics: &DiagnosticsHandler, @@ -144,8 +164,8 @@ fn parse_local_decls( /// Declare `count` local variables of the same type, starting from `next_local`. /// /// Fail if too many locals are declared in the function, or if the type is not valid for a local. -fn declare_locals( - builder: &mut FunctionBuilderExt, +fn declare_locals( + builder: &mut FunctionBuilderExt<'_, B>, count: u32, wasm_type: wasmparser::ValType, next_local: &mut usize, @@ -168,13 +188,13 @@ fn declare_locals( /// This assumes that the local variable declarations have already been parsed and function /// arguments and locals are declared in the builder. #[allow(clippy::too_many_arguments)] -fn parse_function_body( +fn parse_function_body( reader: &mut wasmparser::OperatorsReader<'_>, - builder: &mut FunctionBuilderExt, + builder: &mut FunctionBuilderExt<'_, B>, state: &mut FuncTranslationState, module_state: &mut ModuleTranslationState, module: &ParsedModule<'_>, - mod_types: &ModuleTypes, + mod_types: &ModuleTypesBuilder, addr2line: &addr2line::Context>, session: &Session, func_validator: &mut FuncValidator, @@ -182,6 +202,7 @@ fn parse_function_body( // The control stack is initialized with a single block representing the whole function. debug_assert_eq!(state.control_stack.len(), 1, "State not initialized"); + let func_name = builder.name(); let mut end_span = SourceSpan::default(); while !reader.eof() { let pos = reader.original_position(); @@ -198,20 +219,18 @@ fn parse_function_body( let path = path.canonicalize().unwrap_or_else(|_| path.to_path_buf()); if path.exists() { let source_file = session.source_manager.load_file(&path).into_diagnostic()?; - let line = loc.line.and_then(|line| line.checked_sub(1)).unwrap_or(0); - let column = loc.column.and_then(|col| col.checked_sub(1)).unwrap_or(0); + let line = loc.line.and_then(LineNumber::new).unwrap_or_default(); + let column = loc.column.and_then(ColumnNumber::new).unwrap_or_default(); span = source_file.line_column_to_span(line, column).unwrap_or_default(); } else { - log::debug!( - "failed to locate span for instruction at offset {offset} in function {}", - builder.id() + log::debug!(target: "module-parser", + "failed to locate span for instruction at offset {offset} in function {func_name}" ); } } } else { - log::debug!( - "failed to locate span for instruction at offset {offset} in function {}", - builder.id() + log::debug!(target: "module-parser", + "failed to locate span for instruction at offset {offset} in function {func_name}" ); } @@ -241,7 +260,7 @@ fn parse_function_body( // If the exit block is unreachable, it may not have the correct arguments, so we would // generate a return instruction that doesn't match the signature. if state.reachable && !builder.is_unreachable() { - builder.ins().ret(state.stack.first().cloned(), end_span); + builder.ret(state.stack.first().cloned(), end_span)?; } // Discard any remaining values on the stack. Either we just returned them, diff --git a/frontend/wasm/src/module/function_builder_ext.rs b/frontend/wasm/src/module/function_builder_ext.rs new file mode 100644 index 000000000..a45311443 --- /dev/null +++ b/frontend/wasm/src/module/function_builder_ext.rs @@ -0,0 +1,507 @@ +use alloc::rc::Rc; +use core::cell::RefCell; + +use cranelift_entity::SecondaryMap; +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_dialect_ub::UndefinedBehaviorOpBuilder; +use midenc_hir::{ + dialects::builtin::{BuiltinOpBuilder, FunctionBuilder, FunctionRef}, + traits::{BranchOpInterface, Terminator}, + BlockRef, Builder, Context, EntityRef, FxHashMap, FxHashSet, Ident, Listener, ListenerType, + OpBuilder, OperationRef, ProgramPoint, RegionRef, Signature, SmallVec, SourceSpan, Type, + ValueRef, +}; + +use crate::ssa::{SSABuilder, SideEffects, Variable}; + +/// Tracking variables and blocks for SSA construction. +pub struct FunctionBuilderContext { + ssa: SSABuilder, + status: FxHashMap, + types: SecondaryMap, +} + +impl FunctionBuilderContext { + pub fn new(context: Rc) -> Self { + Self { + ssa: SSABuilder::new(context), + status: Default::default(), + types: SecondaryMap::with_default(Type::Unknown), + } + } + + fn is_empty(&self) -> bool { + self.ssa.is_empty() && self.status.is_empty() && self.types.is_empty() + } + + fn clear(&mut self) { + self.ssa.clear(); + self.status.clear(); + self.types.clear(); + } + + /// Returns `true` if and only if no instructions have been added and the block is empty. + fn is_pristine(&mut self, block: &BlockRef) -> bool { + self.status.entry(*block).or_default() == &BlockStatus::Empty + } + + /// Returns `true` if and the block has been filled. + fn is_filled(&mut self, block: &BlockRef) -> bool { + self.status.entry(*block).or_default() == &BlockStatus::Filled + } +} + +#[derive(Clone, Default, Eq, PartialEq, Debug)] +enum BlockStatus { + /// No instructions have been added. + #[default] + Empty, + /// Some instructions have been added, but no terminator. + Partial, + /// A terminator has been added; no further instructions may be added. + Filled, +} + +pub struct SSABuilderListener { + builder: Rc>, +} + +impl SSABuilderListener { + pub const fn new(builder: Rc>) -> Self { + Self { builder } + } +} + +impl Listener for SSABuilderListener { + fn kind(&self) -> ListenerType { + ListenerType::Builder + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + let borrow = op.borrow(); + let op = borrow.as_ref().as_operation(); + let mut builder = self.builder.borrow_mut(); + + let block = prev.block().expect("invalid program point"); + if builder.is_pristine(&block) { + builder.status.insert(block, BlockStatus::Partial); + } else { + let is_filled = builder.is_filled(&block); + debug_assert!(!is_filled, "you cannot add an instruction to a block already filled"); + } + + if op.implements::() { + let mut unique: FxHashSet = FxHashSet::default(); + for succ in op.successors().iter() { + let successor = succ.block.borrow().successor(); + if !unique.insert(successor) { + continue; + } + builder.ssa.declare_block_predecessor(successor, op.as_operation_ref()); + } + } + + if op.implements::() { + builder.status.insert(block, BlockStatus::Filled); + } + } + + fn notify_block_inserted( + &self, + _block: BlockRef, + _prev: Option, + _ip: Option, + ) { + } +} + +/// A wrapper around Miden's `FunctionBuilder` and `SSABuilder` which provides +/// additional API for dealing with variables and SSA construction. +pub struct FunctionBuilderExt<'c, B: ?Sized + Builder> { + inner: FunctionBuilder<'c, B>, + func_ctx: Rc>, +} + +impl<'c> FunctionBuilderExt<'c, OpBuilder> { + pub fn new(func: FunctionRef, builder: &'c mut OpBuilder) -> Self { + let func_ctx = builder.listener().map(|l| l.builder.clone()).unwrap(); + debug_assert!(func_ctx.borrow().is_empty()); + + let inner = FunctionBuilder::new(func, builder); + + Self { inner, func_ctx } + } +} + +impl FunctionBuilderExt<'_, B> { + pub fn name(&self) -> Ident { + *self.inner.func.borrow().name() + } + + pub fn signature(&self) -> EntityRef<'_, Signature> { + EntityRef::map(self.inner.func.borrow(), |f| f.signature()) + } + + #[inline] + pub fn current_block(&self) -> BlockRef { + self.inner.current_block() + } + + /// Create a new `Block` in the function preserving the current insertion point and declare it + /// in the SSA context. + pub fn create_block(&mut self) -> BlockRef { + // save the current insertion point + let old_ip = *self.inner.builder().insertion_point(); + let region = self.inner.body_region(); + let block = self.inner.builder_mut().create_block(region, None, &[]); + // restore the insertion point to the previous block + self.inner.builder_mut().set_insertion_point(old_ip); + self.func_ctx.borrow_mut().ssa.declare_block(block); + block + } + + /// Create a `Block` with the given parameters. + pub fn create_block_with_params( + &mut self, + params: impl IntoIterator, + span: SourceSpan, + ) -> BlockRef { + let block = self.create_block(); + for ty in params { + self.inner.append_block_param(block, ty, span); + } + block + } + + pub fn create_detached_block(&mut self) -> BlockRef { + self.inner.builder().context().create_block() + } + + /// Append parameters to the given `Block` corresponding to the function + /// return values. This can be used to set up the block parameters for a + /// function exit block. + pub fn append_block_params_for_function_returns(&mut self, block: BlockRef) { + // These parameters count as "user" parameters here because they aren't + // inserted by the SSABuilder. + debug_assert!( + self.is_pristine(&block), + "You can't add block parameters after adding any instruction" + ); + + let results = SmallVec::<[_; 2]>::from_iter(self.signature().results().iter().cloned()); + for argtyp in results { + self.inner.append_block_param(block, argtyp.ty.clone(), SourceSpan::default()); + } + } + + /// After the call to this function, new instructions will be inserted into the designated + /// block, in the order they are declared. You must declare the types of the Block arguments + /// you will use here. + /// + /// When inserting the terminator instruction (which doesn't have a fallthrough to its immediate + /// successor), the block will be declared filled and it will not be possible to append + /// instructions to it. + pub fn switch_to_block(&mut self, block: BlockRef) { + // First we check that the previous block has been filled. + let is_unreachable = self.is_unreachable(); + debug_assert!( + is_unreachable + || self.is_pristine(&self.inner.current_block()) + || self.is_filled(&self.inner.current_block()), + "you have to fill your block before switching" + ); + // We cannot switch to a filled block + debug_assert!( + !self.is_filled(&block), + "you cannot switch to a block which is already filled" + ); + // Then we change the cursor position. + self.inner.switch_to_block(block); + } + + /// Declares that all the predecessors of this block are known. + /// + /// Function to call with `block` as soon as the last branch instruction to `block` has been + /// created. Forgetting to call this method on every block will cause inconsistencies in the + /// produced functions. + pub fn seal_block(&mut self, block: BlockRef) { + let side_effects = self.func_ctx.borrow_mut().ssa.seal_block(block); + self.handle_ssa_side_effects(side_effects); + } + + fn handle_ssa_side_effects(&mut self, side_effects: SideEffects) { + for modified_block in side_effects.instructions_added_to_blocks { + if self.is_pristine(&modified_block) { + self.func_ctx.borrow_mut().status.insert(modified_block, BlockStatus::Partial); + } + } + } + + /// Make sure that the current block is inserted in the layout. + pub fn ensure_inserted_block(&mut self) { + let block = self.inner.current_block(); + if self.is_pristine(&block) { + self.func_ctx.borrow_mut().status.insert(block, BlockStatus::Partial); + } else { + debug_assert!( + !self.is_filled(&block), + "you cannot add an instruction to a block already filled" + ); + } + } + + /// Declare that translation of the current function is complete. + /// + /// This resets the state of the `FunctionBuilderContext` in preparation to + /// be used for another function. + pub fn finalize(self) { + // Check that all the `Block`s are filled and sealed. + #[cfg(debug_assertions)] + { + let keys: Vec = self.func_ctx.borrow().status.keys().cloned().collect(); + for block in keys { + if !self.is_pristine(&block) { + assert!( + self.func_ctx.borrow().ssa.is_sealed(block), + "FunctionBuilderExt finalized, but block {block} is not sealed", + ); + assert!( + self.is_filled(&block), + "FunctionBuilderExt finalized, but block {block} is not filled", + ); + } + } + } + + // Clear the state (but preserve the allocated buffers) in preparation + // for translation another function. + self.func_ctx.borrow_mut().clear(); + } + + #[inline] + pub fn variable_type(&self, var: Variable) -> Type { + self.func_ctx.borrow().types[var].clone() + } + + /// Declares the type of a variable, so that it can be used later (by calling + /// [`FunctionBuilderExt::use_var`]). This function will return an error if the variable + /// has been previously declared. + pub fn try_declare_var(&mut self, var: Variable, ty: Type) -> Result<(), DeclareVariableError> { + if self.func_ctx.borrow().types[var] != Type::Unknown { + return Err(DeclareVariableError::DeclaredMultipleTimes(var)); + } + self.func_ctx.borrow_mut().types[var] = ty; + Ok(()) + } + + /// In order to use a variable (by calling [`FunctionBuilderExt::use_var`]), you need + /// to first declare its type with this method. + pub fn declare_var(&mut self, var: Variable, ty: Type) { + self.try_declare_var(var, ty) + .unwrap_or_else(|_| panic!("the variable {var:?} has been declared multiple times")) + } + + /// Returns the Miden IR necessary to use a previously defined user + /// variable, returning an error if this is not possible. + pub fn try_use_var(&mut self, var: Variable) -> Result { + // Assert that we're about to add instructions to this block using the definition of the + // given variable. ssa.use_var is the only part of this crate which can add block parameters + // behind the caller's back. If we disallow calling append_block_param as soon as use_var is + // called, then we enforce a strict separation between user parameters and SSA parameters. + self.ensure_inserted_block(); + + let (val, side_effects) = { + let ty = self + .func_ctx + .borrow() + .types + .get(var) + .cloned() + .ok_or(UseVariableError::UsedBeforeDeclared(var))?; + debug_assert_ne!( + ty, + Type::Unknown, + "variable {var:?} is used but its type has not been declared" + ); + let current_block = self.inner.current_block(); + self.func_ctx.borrow_mut().ssa.use_var(var, ty, current_block) + }; + self.handle_ssa_side_effects(side_effects); + Ok(val) + } + + /// Returns the Miden IR value corresponding to the utilization at the current program + /// position of a previously defined user variable. + pub fn use_var(&mut self, var: Variable) -> ValueRef { + self.try_use_var(var).unwrap_or_else(|_| { + panic!("variable {var:?} is used but its type has not been declared") + }) + } + + /// Registers a new definition of a user variable. This function will return + /// an error if the value supplied does not match the type the variable was + /// declared to have. + pub fn try_def_var(&mut self, var: Variable, val: ValueRef) -> Result<(), DefVariableError> { + let mut func_ctx = self.func_ctx.borrow_mut(); + let var_ty = func_ctx.types.get(var).ok_or(DefVariableError::DefinedBeforeDeclared(var))?; + if var_ty != val.borrow().ty() { + return Err(DefVariableError::TypeMismatch(var, val)); + } + func_ctx.ssa.def_var(var, val, self.inner.current_block()); + Ok(()) + } + + /// Register a new definition of a user variable. The type of the value must be + /// the same as the type registered for the variable. + pub fn def_var(&mut self, var: Variable, val: ValueRef) { + self.try_def_var(var, val).unwrap_or_else(|error| match error { + DefVariableError::TypeMismatch(var, val) => { + assert_eq!( + &self.func_ctx.borrow().types[var], + val.borrow().ty(), + "declared type of variable {var:?} doesn't match type of value {val}" + ); + } + DefVariableError::DefinedBeforeDeclared(var) => { + panic!("variable {var:?} is used but its type has not been declared"); + } + }) + } + + /// Returns `true` if and only if no instructions have been added since the last call to + /// `switch_to_block`. + fn is_pristine(&self, block: &BlockRef) -> bool { + self.func_ctx.borrow_mut().is_pristine(block) + } + + /// Returns `true` if and only if a terminator instruction has been inserted since the + /// last call to `switch_to_block`. + fn is_filled(&self, block: &BlockRef) -> bool { + self.func_ctx.borrow_mut().is_filled(block) + } + + /// Returns `true` if and only if the current `Block` is sealed and has no predecessors + /// declared. + /// + /// The entry block of a function is never unreachable. + pub fn is_unreachable(&self) -> bool { + let is_entry = self.inner.current_block() == self.inner.entry_block(); + let func_ctx = self.func_ctx.borrow(); + let is_sealed = func_ctx.ssa.is_sealed(self.inner.current_block()); + let has_no_predecessors = !func_ctx.ssa.has_any_predecessors(self.inner.current_block()); + !is_entry && is_sealed && has_no_predecessors + } + + /// Changes the destination of a jump instruction after creation. + /// + /// **Note:** You are responsible for maintaining the coherence with the arguments of + /// other jump instructions. + /// + /// NOTE: Panics if `branch_inst` is not a branch instruction. + pub fn change_jump_destination( + &mut self, + mut branch_inst: OperationRef, + old_block: BlockRef, + new_block: BlockRef, + ) { + self.func_ctx.borrow_mut().ssa.remove_block_predecessor(old_block, branch_inst); + let mut borrow_mut = branch_inst.borrow_mut(); + let Some(inst_branch) = borrow_mut.as_trait_mut::() else { + panic!("expected branch instruction, got {branch_inst:?}"); + }; + inst_branch.change_branch_destination(old_block, new_block); + self.func_ctx.borrow_mut().ssa.declare_block_predecessor(new_block, branch_inst); + } +} + +impl<'f, B: ?Sized + Builder> ArithOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + self.inner.builder() + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self.inner.builder_mut() + } +} + +impl<'f, B: ?Sized + Builder> ControlFlowOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + self.inner.builder() + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self.inner.builder_mut() + } +} + +impl<'f, B: ?Sized + Builder> UndefinedBehaviorOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + self.inner.builder() + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self.inner.builder_mut() + } +} + +impl<'f, B: ?Sized + Builder> BuiltinOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + self.inner.builder() + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self.inner.builder_mut() + } +} + +impl<'f, B: ?Sized + Builder> HirOpBuilder<'f, B> for FunctionBuilderExt<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + self.inner.builder() + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self.inner.builder_mut() + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, thiserror::Error)] +/// An error encountered when calling [`FunctionBuilderExt::try_use_var`]. +pub enum UseVariableError { + #[error("variable {0} is used before the declaration")] + UsedBeforeDeclared(Variable), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, thiserror::Error)] +/// An error encountered when calling [`FunctionBuilderExt::try_declare_var`]. +pub enum DeclareVariableError { + #[error("variable {0} is already declared")] + DeclaredMultipleTimes(Variable), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, thiserror::Error)] +/// An error encountered when defining the initial value of a variable. +pub enum DefVariableError { + #[error( + "the types of variable {0} and value {1} are not the same. The `Value` supplied to \ + `def_var` must be of the same type as the variable was declared to be of in \ + `declare_var`." + )] + TypeMismatch(Variable, ValueRef), + #[error( + "the value of variable {0} was defined (in call `def_val`) before it was declared (in \ + call `declare_var`)" + )] + DefinedBeforeDeclared(Variable), +} diff --git a/frontend/wasm/src/module/instance.rs b/frontend/wasm/src/module/instance.rs new file mode 100644 index 000000000..610bd844d --- /dev/null +++ b/frontend/wasm/src/module/instance.rs @@ -0,0 +1,21 @@ +use midenc_hir::{FunctionType, SymbolPath}; + +/// Represents module argument that is used to instantiate a module. +#[derive(Debug, Clone)] +pub enum ModuleArgument { + /// Represents function that is exported from another module. + Function(SymbolPath), + /// Represents component import (component level type signature) that is lowered to a module import. + ComponentImport(FunctionType), + /// Represents table exported from another module. + Table, +} + +/// Canonical ABI options associated with a lifted or lowered function. +#[derive(Debug, Clone)] +pub struct CanonicalOptions { + /// The realloc function used by these options, if specified. + pub realloc: Option, + /// The post-return function used by these options, if specified. + pub post_return: Option, +} diff --git a/frontend/wasm/src/module/linker_stubs.rs b/frontend/wasm/src/module/linker_stubs.rs new file mode 100644 index 000000000..b42cda743 --- /dev/null +++ b/frontend/wasm/src/module/linker_stubs.rs @@ -0,0 +1,167 @@ +//! Generic lowering for Rust linker stubs to MASM procedure calls. +//! A linker stub is detected as a function whose body consists solely of a +//! single `unreachable` instruction (plus the implicit `end`). The stub +//! function name is expected to be a fully-qualified MASM function path like +//! `miden::account::add_asset` and is used to locate the MASM callee. + +use core::str::FromStr; +use std::cell::RefCell; + +use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_hir::{ + diagnostics::WrapErr, + dialects::builtin::{BuiltinOpBuilder, FunctionRef, ModuleBuilder}, + AbiParam, FunctionType, Op, Signature, SmallVec, SymbolPath, ValueRef, +}; +use wasmparser::{FunctionBody, Operator}; + +use crate::{ + error::WasmResult, + intrinsics::{convert_intrinsics_call, Intrinsic}, + miden_abi::{ + is_miden_abi_module, miden_abi_function_type, transform::transform_miden_abi_call, + }, + module::{ + function_builder_ext::{FunctionBuilderContext, FunctionBuilderExt, SSABuilderListener}, + module_translation_state::ModuleTranslationState, + }, +}; + +/// Returns true if the given Wasm function body consists only of an +/// `unreachable` operator (ignoring `end`/`nop`). +pub fn is_unreachable_stub(body: &FunctionBody<'_>) -> bool { + let mut reader = match body.get_operators_reader() { + Ok(r) => r, + Err(_) => return false, + }; + let mut saw_unreachable = false; + while !reader.eof() { + let Ok((op, _)) = reader.read_with_offset() else { + return false; + }; + match op { + Operator::Unreachable => { + saw_unreachable = true; + } + Operator::End | Operator::Nop => { + // ignore + } + _ => return false, + } + } + saw_unreachable +} + +/// If `body` looks like a linker stub, lowers `function_ref` to a call to the +/// MASM callee derived from the function name and applies the appropriate +/// TransformStrategy. Returns `true` if handled, `false` otherwise. +pub fn maybe_lower_linker_stub( + function_ref: FunctionRef, + body: &FunctionBody<'_>, + module_state: &mut ModuleTranslationState, +) -> WasmResult { + if !is_unreachable_stub(body) { + return Ok(false); + } + + // Parse function name as MASM function ident: "ns::...::func" + let name_string = { + let borrowed = function_ref.borrow(); + borrowed.name().as_str().to_string() + }; + // Expect stub export names to be fully-qualified MASM paths already (e.g. "intrinsics::felt::add"). + let func_ident = match midenc_hir::FunctionIdent::from_str(&name_string) { + Ok(id) => id, + Err(_) => return Ok(false), + }; + let import_path: SymbolPath = SymbolPath::from_masm_function_id(func_ident); + // Ensure the stub targets a known Miden ABI module or a recognized intrinsic. + let is_intrinsic = Intrinsic::try_from(&import_path).is_ok(); + if !is_miden_abi_module(&import_path) && !is_intrinsic { + return Ok(false); + } + + // Classify intrinsics and obtain signature when needed + let (import_sig, intrinsic): (Signature, Option) = + match Intrinsic::try_from(&import_path) { + Ok(intr) => (function_ref.borrow().signature().clone(), Some(intr)), + Err(_) => { + let import_ft: FunctionType = miden_abi_function_type(&import_path); + ( + Signature::new( + import_ft.params.into_iter().map(AbiParam::new), + import_ft.results.into_iter().map(AbiParam::new), + ), + None, + ) + } + }; + + // Build the function body for the stub and replace it with an exec to MASM + let span = function_ref.borrow().name().span; + let context = function_ref.borrow().as_operation().context_rc(); + let func_builder_ctx = + std::rc::Rc::new(RefCell::new(FunctionBuilderContext::new(context.clone()))); + let mut op_builder = midenc_hir::OpBuilder::new(context) + .with_listener(SSABuilderListener::new(func_builder_ctx)); + let mut fb = FunctionBuilderExt::new(function_ref, &mut op_builder); + + // Entry/args + let entry_block = fb.current_block(); + fb.seal_block(entry_block); + let args: Vec = entry_block + .borrow() + .arguments() + .iter() + .copied() + .map(|ba| ba as ValueRef) + .collect(); + + // Declare MASM import callee in world and exec via TransformStrategy + let results: Vec = if let Some(intr) = intrinsic { + // Decide whether the intrinsic is implemented as a function or an operation + let Some(conv) = intr.conversion_result() else { + return Ok(false); + }; + if conv.is_function() { + // Declare callee and call via convert_intrinsics_call with function_ref + let import_module_ref = module_state + .world_builder + .declare_module_tree(&import_path.without_leaf()) + .wrap_err("failed to create module for intrinsics imports")?; + let mut import_module_builder = ModuleBuilder::new(import_module_ref); + let intrinsic_func_ref = import_module_builder + .define_function(import_path.name().into(), import_sig.clone()) + .wrap_err("failed to create intrinsic function ref")?; + convert_intrinsics_call(intr, Some(intrinsic_func_ref), &args, &mut fb, span)?.to_vec() + } else { + // Inline conversion of intrinsic operation + convert_intrinsics_call(intr, None, &args, &mut fb, span)?.to_vec() + } + } else { + // Miden ABI path: exec import with TransformStrategy + let import_module_ref = module_state + .world_builder + .declare_module_tree(&import_path.without_leaf()) + .wrap_err("failed to create module for MASM imports")?; + let mut import_module_builder = ModuleBuilder::new(import_module_ref); + let import_func_ref = import_module_builder + .define_function(import_path.name().into(), import_sig) + .wrap_err("failed to create MASM import function ref")?; + transform_miden_abi_call(import_func_ref, &import_path, &args, &mut fb) + }; + + // Return + let exit_block = fb.create_block(); + fb.append_block_params_for_function_returns(exit_block); + fb.br(exit_block, results, span)?; + fb.seal_block(exit_block); + fb.switch_to_block(exit_block); + let ret_vals: SmallVec<[ValueRef; 1]> = { + let borrow = exit_block.borrow(); + borrow.argument_values().collect() + }; + fb.ret(ret_vals, span)?; + + Ok(true) +} diff --git a/frontend/wasm/src/module/mod.rs b/frontend/wasm/src/module/mod.rs new file mode 100644 index 000000000..c0c73f209 --- /dev/null +++ b/frontend/wasm/src/module/mod.rs @@ -0,0 +1,374 @@ +//! Data structures for representing parsed Wasm modules. + +use alloc::{borrow::Cow, collections::BTreeMap}; +use core::{fmt, ops::Range}; + +use cranelift_entity::{packed_option::ReservedValue, EntityRef, PrimaryMap}; +use indexmap::IndexMap; +use midenc_hir::{interner::Symbol, FxHashMap, Ident}; +use midenc_session::DiagnosticsHandler; + +use self::types::*; +use crate::{component::SignatureIndex, error::WasmResult, unsupported_diag}; + +pub mod build_ir; +pub mod func_translation_state; +pub mod func_translator; +pub mod function_builder_ext; +pub mod instance; +pub mod linker_stubs; +pub mod module_env; +pub mod module_translation_state; +pub mod types; + +/// Table initialization data for all tables in the module. +#[derive(Debug, Default)] +pub struct TableInitialization { + /// Initial values for tables defined within the module itself. + /// + /// This contains the initial values and initializers for tables defined + /// within a wasm, so excluding imported tables. This initializer can + /// represent null-initialized tables, element-initialized tables (e.g. with + /// the function-references proposal), or precomputed images of table + /// initialization. For example table initializers to a table that are all + /// in-bounds will get removed from `segment` and moved into + /// `initial_values` here. + pub initial_values: PrimaryMap, + + /// Element segments present in the initial wasm module which are executed + /// at instantiation time. + /// + /// These element segments are iterated over during instantiation to apply + /// any segments that weren't already moved into `initial_values` above. + pub segments: Vec, +} + +/// Initial value for all elements in a table. +#[derive(Clone, Debug)] +pub enum TableInitialValue { + /// Initialize each table element to null, optionally setting some elements + /// to non-null given the precomputed image. + Null { + /// A precomputed image of table initializers for this table. + precomputed: Vec, + }, + + /// Initialize each table element to the function reference given + /// by the `FuncIndex`. + FuncRef(FuncIndex), +} + +/// A WebAssembly table initializer segment. +#[derive(Clone, Debug)] +pub struct TableSegment { + /// The index of a table to initialize. + pub table_index: TableIndex, + /// Optionally, a global variable giving a base index. + pub base: Option, + /// The offset to add to the base. + pub offset: u32, + /// The values to write into the table elements. + pub elements: Box<[FuncIndex]>, +} + +/// Different types that can appear in a module. +/// +/// Note that each of these variants are intended to index further into a +/// separate table. +#[derive(Debug, Copy, Clone)] +pub enum ModuleType { + Function(SignatureIndex), +} + +impl ModuleType { + /// Asserts this is a `ModuleType::Function`, returning the underlying + /// `SignatureIndex`. + pub fn unwrap_function(&self) -> SignatureIndex { + match self { + ModuleType::Function(f) => *f, + } + } +} + +/// A translated WebAssembly module, excluding the function bodies +#[derive(Default, Debug)] +pub struct Module { + /// All import records, in the order they are declared in the module. + pub imports: Vec, + + /// Exported entities. + pub exports: IndexMap, + + /// The module "start" function, if present. + pub start_func: Option, + + /// WebAssembly table initialization data, per table. + pub table_initialization: TableInitialization, + + /// WebAssembly passive elements. + pub passive_elements: Vec>, + + /// The map from passive element index (element segment index space) to index in + /// `passive_elements`. + pub passive_elements_map: BTreeMap, + + /// The map from passive data index (data segment index space) to index in `passive_data`. + pub passive_data_map: BTreeMap>, + + /// Types declared in the wasm module. + pub types: PrimaryMap, + + /// Number of imported or aliased functions in the module. + pub num_imported_funcs: usize, + + /// Number of imported or aliased tables in the module. + pub num_imported_tables: usize, + + /// Number of imported or aliased globals in the module. + pub num_imported_globals: usize, + + /// Number of functions that "escape" from this module + /// + /// This is also the number of functions in the `functions` array below with + /// an `func_ref` index (and is the maximum func_ref index). + pub num_escaped_funcs: usize, + + /// Types of functions, imported and local. + pub functions: PrimaryMap, + + /// WebAssembly tables. + pub tables: PrimaryMap, + + /// WebAssembly global variables. + pub globals: PrimaryMap, + + /// WebAssembly global initializers for locally-defined globals. + pub global_initializers: PrimaryMap, + + /// WebAssembly module memories. + pub memories: PrimaryMap, + + /// Parsed names section. + name_section: NameSection, + + /// The fallback name of this module, used if there is no module name in the name section, + /// and there is no override specified + name_fallback: Option, + + /// If specified, overrides the name of the module regardless of what is in the name section + name_override: Option, +} + +/// Module imports +#[derive(Debug, Clone)] +pub struct ModuleImport { + /// Name of this import + pub module: String, + /// The field name projection of this import + pub field: String, + /// Where this import will be placed, which also has type information + /// about the import. + pub index: EntityIndex, +} + +impl Module { + /// Convert a `DefinedFuncIndex` into a `FuncIndex`. + #[inline] + pub fn func_index(&self, defined_func: DefinedFuncIndex) -> FuncIndex { + FuncIndex::new(self.num_imported_funcs + defined_func.index()) + } + + /// Convert a `FuncIndex` into a `DefinedFuncIndex`. Returns None if the + /// index is an imported function. + #[inline] + pub fn defined_func_index(&self, func: FuncIndex) -> Option { + if func.index() < self.num_imported_funcs { + None + } else { + Some(DefinedFuncIndex::new(func.index() - self.num_imported_funcs)) + } + } + + /// Test whether the given function index is for an imported function. + #[inline] + pub fn is_imported_function(&self, index: FuncIndex) -> bool { + index.index() < self.num_imported_funcs + } + + pub fn is_exported(&self, entity: EntityIndex) -> bool { + self.exports.values().any(|export| *export == entity) + } + + /// Convert a `DefinedTableIndex` into a `TableIndex`. + #[inline] + pub fn table_index(&self, defined_table: DefinedTableIndex) -> TableIndex { + TableIndex::new(self.num_imported_tables + defined_table.index()) + } + + /// Convert a `TableIndex` into a `DefinedTableIndex`. Returns None if the + /// index is an imported table. + #[inline] + pub fn defined_table_index(&self, table: TableIndex) -> Option { + if table.index() < self.num_imported_tables { + None + } else { + Some(DefinedTableIndex::new(table.index() - self.num_imported_tables)) + } + } + + /// Test whether the given table index is for an imported table. + #[inline] + pub fn is_imported_table(&self, index: TableIndex) -> bool { + index.index() < self.num_imported_tables + } + + /// Test whether the given memory index is for an imported memory. + #[inline] + pub fn is_imported_memory(&self, index: MemoryIndex) -> bool { + self.memories[index].imported + } + + /// Convert a `DefinedGlobalIndex` into a `GlobalIndex`. + #[inline] + pub fn global_index(&self, defined_global: DefinedGlobalIndex) -> GlobalIndex { + GlobalIndex::new(self.num_imported_globals + defined_global.index()) + } + + /// Convert a `GlobalIndex` into a `DefinedGlobalIndex`. Returns None if the + /// index is an imported global. + #[inline] + pub fn defined_global_index(&self, global: GlobalIndex) -> Option { + if global.index() < self.num_imported_globals { + None + } else { + Some(DefinedGlobalIndex::new(global.index() - self.num_imported_globals)) + } + } + + /// Test whether the given global index is for an imported global. + #[inline] + pub fn is_imported_global(&self, index: GlobalIndex) -> bool { + index.index() < self.num_imported_globals + } + + pub fn global_name(&self, index: GlobalIndex) -> Symbol { + self.name_section + .globals_names + .get(&index) + .cloned() + .unwrap_or(Symbol::intern(format!("global{}", index.as_u32()).as_str())) + } + + /// Returns the type of an item based on its index + pub fn type_of(&self, index: EntityIndex) -> EntityType { + match index { + EntityIndex::Global(i) => EntityType::Global(self.globals[i].clone()), + EntityIndex::Table(i) => EntityType::Table(self.tables[i]), + EntityIndex::Memory(i) => EntityType::Memory(self.memories[i]), + EntityIndex::Function(i) => EntityType::Function(self.functions[i].signature), + } + } + + /// Appends a new function to this module with the given type information, + /// used for functions that either don't escape or aren't certain whether + /// they escape yet. + pub fn push_function(&mut self, signature: SignatureIndex) -> FuncIndex { + self.functions.push(FunctionTypeInfo { + signature, + func_ref: FuncRefIndex::reserved_value(), + }) + } + + /// Appends a new function to this module with the given type information. + pub fn push_escaped_function( + &mut self, + signature: SignatureIndex, + func_ref: FuncRefIndex, + ) -> FuncIndex { + self.functions.push(FunctionTypeInfo { + signature, + func_ref, + }) + } + + /// Returns the global initializer for the given index, or `Unsupported` error if the global is + /// imported. + pub fn try_global_initializer( + &self, + index: GlobalIndex, + diagnostics: &DiagnosticsHandler, + ) -> WasmResult<&GlobalInit> { + if let Some(defined_index) = self.defined_global_index(index) { + Ok(&self.global_initializers[defined_index]) + } else { + unsupported_diag!(diagnostics, "Imported globals are not supported yet"); + } + } + + /// Returns the name of this module + pub fn name(&self) -> Ident { + self.name_override + .or(self.name_section.module_name) + .or(self.name_fallback) + .expect("No module name in the name section and no fallback name is set") + } + + /// Returns the name of the given function + pub fn func_name(&self, index: FuncIndex) -> Symbol { + self.name_section + .func_names + .get(&index) + .cloned() + .unwrap_or(Symbol::intern(format!("func{}", index.as_u32()))) + } + + /// Sets the fallback name of this module, used if there is no module name in the name section + pub fn set_name_fallback(&mut self, name_fallback: Cow<'static, str>) { + self.name_fallback = Some(Ident::from(name_fallback.as_ref())); + } + + /// Sets the name of this module, discarding whatever is in the name section + pub fn set_name_override(&mut self, name_override: Cow<'static, str>) { + self.name_override = Some(Ident::from(name_override.as_ref())); + } +} + +impl fmt::Display for ModuleImport { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}.{}({:?})", self.module, self.field, self.index) + } +} + +/// Type information about functions in a wasm module. +#[derive(Debug, Clone, Copy)] +pub struct FunctionTypeInfo { + /// The type of this function, indexed into the module-wide type tables for + /// a module compilation. + pub signature: SignatureIndex, + /// The index into the funcref table, if present. Note that this is + /// `reserved_value()` if the function does not escape from a module. + pub func_ref: FuncRefIndex, +} + +impl FunctionTypeInfo { + /// Returns whether this function's type is one that "escapes" the current + /// module, meaning that the function is exported, used in `ref.func`, used + /// in a table, etc. + pub fn is_escaping(&self) -> bool { + !self.func_ref.is_reserved_value() + } +} + +/// Index into the funcref table +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub struct FuncRefIndex(u32); +cranelift_entity::entity_impl!(FuncRefIndex); + +#[derive(Debug, Default)] +pub struct NameSection { + pub module_name: Option, + pub func_names: FxHashMap, + pub locals_names: FxHashMap>, + pub globals_names: FxHashMap, + pub data_segment_names: FxHashMap, +} diff --git a/frontend-wasm/src/module/module_env.rs b/frontend/wasm/src/module/module_env.rs similarity index 95% rename from frontend-wasm/src/module/module_env.rs rename to frontend/wasm/src/module/module_env.rs index bffbc1daa..7a598ade4 100644 --- a/frontend-wasm/src/module/module_env.rs +++ b/frontend/wasm/src/module/module_env.rs @@ -1,10 +1,10 @@ -use std::{ops::Range, path::PathBuf, sync::Arc}; +use alloc::sync::Arc; +use core::ops::Range; +use std::path::PathBuf; -use midenc_hir::{ - cranelift_entity::{packed_option::ReservedValue, PrimaryMap}, - diagnostics::{DiagnosticsHandler, IntoDiagnostic, Report, Severity}, - Ident, Symbol, -}; +use cranelift_entity::{packed_option::ReservedValue, PrimaryMap}; +use midenc_hir::{interner::Symbol, Ident}; +use midenc_session::diagnostics::{DiagnosticsHandler, IntoDiagnostic, Report, Severity}; use wasmparser::{ types::CoreTypeId, CustomSectionReader, DataKind, ElementItems, ElementKind, Encoding, ExternalKind, FuncToValidate, FunctionBody, NameSectionReader, Naming, Operator, Parser, @@ -76,6 +76,9 @@ pub struct ParsedModule<'data> { /// When we're parsing the code section this will be incremented so we know /// which function is currently being defined. code_index: u32, + + /// The serialized AccountComponentMetadata (name, description, storage layout, etc.) + pub account_component_metadata_bytes: Option<&'data [u8]>, } /// Contains function data: byte code and its offset in the module. @@ -200,23 +203,34 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { // the passive count, do not reserve anything here. } Payload::CustomSection(s) if s.name() == "name" => { - let reader = wasmparser::BinaryReader::new( + let reader = wasmparser::BinaryReader::new_features( s.data(), s.data_offset(), *self.validator.features(), ); let result = self.name_section(NameSectionReader::new(reader)); if let Err(e) = result { - log::warn!("failed to parse name section {:?}", e); + log::warn!(target: "module-parser", "failed to parse name section {e:?}"); } } - Payload::CustomSection(s) => self.dwarf_section(&s), + Payload::CustomSection(s) if s.name().starts_with(".debug_") => self.dwarf_section(&s), + Payload::CustomSection(s) if s.name() == "rodata,miden_account" => { + self.result.account_component_metadata_bytes = Some(s.data()); + } + Payload::CustomSection { .. } => { + // ignore any other custom sections + } + // It's expected that validation will probably reject other // payloads such as `UnknownSection` or those related to the // component model. other => { self.validator.payload(&other).into_diagnostic()?; - unsupported_diag!(diagnostics, "wasm error: unsupported section {:?}", other); + unsupported_diag!( + diagnostics, + "wasm error: unsupported module section {:?}", + other + ); } } Ok(()) @@ -249,8 +263,8 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { self.types.reserve_wasm_signatures(num); for i in 0..types.count() { let types = self.validator.types(0).unwrap(); - let ty = types.core_type_at(i); - self.declare_type(ty.unwrap_sub())?; + let ty = types.core_type_at_in_module(i); + self.declare_type(ty)?; } Ok(()) } @@ -718,9 +732,6 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { fn dwarf_section(&mut self, section: &CustomSectionReader<'data>) { let name = section.name(); - if !name.starts_with(".debug_") { - return; - } if !self.config.generate_native_debuginfo && !self.config.parse_wasm_debuginfo { self.result.has_unparsed_debuginfo = true; return; @@ -759,7 +770,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { ".debug_aranges" | ".debug_pubnames" | ".debug_pubtypes" => return, other => { - log::warn!("unknown debug section `{}`", other); + log::warn!(target: "module-parser", "unknown debug section `{other}`"); return; } } @@ -812,7 +823,9 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { let sig_index = self.types.wasm_func_type(id, wasm); self.result.module.types.push(ModuleType::Function(sig_index)); } - CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => unimplemented!(), + CompositeInnerType::Array(_) + | CompositeInnerType::Struct(_) + | CompositeInnerType::Cont(_) => unimplemented!(), } Ok(()) } diff --git a/frontend/wasm/src/module/module_translation_state.rs b/frontend/wasm/src/module/module_translation_state.rs new file mode 100644 index 000000000..473980186 --- /dev/null +++ b/frontend/wasm/src/module/module_translation_state.rs @@ -0,0 +1,158 @@ +use midenc_hir::{ + dialects::builtin::{ModuleBuilder, WorldBuilder}, + interner::Symbol, + smallvec, CallConv, FxHashMap, Signature, SymbolNameComponent, SymbolPath, Visibility, +}; +use midenc_session::diagnostics::{DiagnosticsHandler, Severity}; + +use super::{instance::ModuleArgument, ir_func_type, types::ModuleTypesBuilder, FuncIndex, Module}; +use crate::{ + callable::CallableFunction, component::lower_imports::generate_import_lowering_function, + error::WasmResult, translation_utils::sig_from_func_type, +}; + +pub struct ModuleTranslationState<'a> { + /// Imported and local functions + functions: FxHashMap, + pub module_builder: &'a mut ModuleBuilder, + pub world_builder: &'a mut WorldBuilder, +} + +impl<'a> ModuleTranslationState<'a> { + /// Create a new `ModuleTranslationState` for the core Wasm module translation + /// + /// Parameters: + /// `module` - the core Wasm module + /// `module_builder` - the Miden IR Module builder + /// `world_builder` - the Miden IR World builder + /// `mod_types` - the Miden IR module types builder + /// `module_args` - the module instantiation arguments, i.e. entities to "fill" module imports + pub fn new( + module: &Module, + module_builder: &'a mut ModuleBuilder, + world_builder: &'a mut WorldBuilder, + mod_types: &ModuleTypesBuilder, + module_args: FxHashMap, + diagnostics: &DiagnosticsHandler, + ) -> WasmResult { + let mut functions = FxHashMap::default(); + for (index, func_type) in &module.functions { + let wasm_func_type = mod_types[func_type.signature].clone(); + let ir_func_type = ir_func_type(&wasm_func_type, diagnostics)?; + let func_name = module.func_name(index); + let path = SymbolPath { + path: smallvec![ + SymbolNameComponent::Root, + SymbolNameComponent::Component(module.name().as_symbol()), + SymbolNameComponent::Leaf(func_name) + ], + }; + let visibility = if module.is_exported(index.into()) { + Visibility::Public + } else { + Visibility::Private + }; + let sig = sig_from_func_type(&ir_func_type, CallConv::SystemV, visibility); + if module.is_imported_function(index) { + assert!((index.as_u32() as usize) < module.num_imported_funcs); + let import = &module.imports[index.as_u32() as usize]; + let func = process_import( + module_builder, + world_builder, + &module_args, + path, + sig, + import, + diagnostics, + )?; + functions.insert(index, func); + } else { + let function_ref = module_builder + .define_function(path.name().into(), sig.clone()) + .map_err(|e| { + diagnostics + .diagnostic(Severity::Error) + .with_message(format!( + "Failed to add new function '{}' to module: {e:?}", + path.name() + )) + .into_report() + })?; + let defined_function = CallableFunction::Function { + wasm_id: path, + function_ref, + signature: sig.clone(), + }; + functions.insert(index, defined_function); + }; + } + Ok(Self { + functions, + module_builder, + world_builder, + }) + } + + /// Get the `CallableFunction` that should be used to make a direct call to function `index`. + pub(crate) fn get_direct_func(&mut self, index: FuncIndex) -> WasmResult { + let defined_func = self.functions[&index].clone(); + Ok(defined_func) + } +} + +/// Returns [`CallableFunction`] translated from the core Wasm module import +fn process_import( + module_builder: &mut ModuleBuilder, + world_builder: &mut WorldBuilder, + module_args: &FxHashMap, + core_func_id: SymbolPath, + core_func_sig: Signature, + import: &super::ModuleImport, + diagnostics: &DiagnosticsHandler, +) -> WasmResult { + let import_path = SymbolPath { + path: smallvec![ + SymbolNameComponent::Root, + SymbolNameComponent::Component(Symbol::intern(&import.module)), + SymbolNameComponent::Leaf(Symbol::intern(&import.field)) + ], + }; + let Some(module_arg) = module_args.get(&import_path) else { + crate::unsupported_diag!(diagnostics, "unexpected import '{import_path:?}'"); + }; + process_module_arg( + module_builder, + world_builder, + core_func_id, + core_func_sig, + import_path, + module_arg, + ) +} + +fn process_module_arg( + module_builder: &mut ModuleBuilder, + world_builder: &mut WorldBuilder, + path: SymbolPath, + sig: Signature, + wasm_import_path: SymbolPath, + module_arg: &ModuleArgument, +) -> WasmResult { + Ok(match module_arg { + ModuleArgument::Function(_) => { + todo!("core Wasm function import is not implemented yet"); + //generate the internal function and call the import argument function" + } + ModuleArgument::ComponentImport(signature) => generate_import_lowering_function( + world_builder, + module_builder, + wasm_import_path, + signature, + path, + sig, + )?, + ModuleArgument::Table => { + todo!("implement the table import module arguments") + } + }) +} diff --git a/frontend/wasm/src/module/types.rs b/frontend/wasm/src/module/types.rs new file mode 100644 index 000000000..e026f5cd3 --- /dev/null +++ b/frontend/wasm/src/module/types.rs @@ -0,0 +1,687 @@ +//! Types for parsed core WebAssembly modules. + +use core::{fmt, ops::Index}; + +use cranelift_entity::PrimaryMap; +use midenc_hir::{ + self as hir, AbiParam, CallConv, FxHashMap, Immediate, Signature, SmallVec, Visibility, +}; +use midenc_session::diagnostics::{DiagnosticsHandler, Severity}; +use wasmparser::types::CoreTypeId; + +use crate::{component::SignatureIndex, error::WasmResult, module::Module, unsupported_diag}; + +/// Generates a new index type for each entity. +#[macro_export] +macro_rules! indices { + ($( + $(#[$a:meta])* + pub struct $name:ident(u32); + )*) => ($( + $(#[$a])* + #[derive( + Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug + )] + #[repr(transparent)] + pub struct $name(u32); + ::cranelift_entity::entity_impl!($name); + )*); +} + +indices! { +/// Index type of a function (imported or defined) inside the WebAssembly module. +pub struct FuncIndex(u32); + +/// Index type of a defined function inside the WebAssembly module. +pub struct DefinedFuncIndex(u32); + +/// Index type of a defined table inside the WebAssembly module. +pub struct DefinedTableIndex(u32); + +/// Index type of a defined memory inside the WebAssembly module. +pub struct DefinedMemoryIndex(u32); + +/// Index type of a defined memory inside the WebAssembly module. +pub struct OwnedMemoryIndex(u32); + +/// Index type of a defined global inside the WebAssembly module. +pub struct DefinedGlobalIndex(u32); + +/// Index type of a table (imported or defined) inside the WebAssembly module. +pub struct TableIndex(u32); + +/// Index type of a global variable (imported or defined) inside the WebAssembly module. +pub struct GlobalIndex(u32); + +/// Index type of a linear memory (imported or defined) inside the WebAssembly module. +pub struct MemoryIndex(u32); + +/// Index type of a passive data segment inside the WebAssembly module. +pub struct DataIndex(u32); + +/// Index type of a passive element segment inside the WebAssembly module. +pub struct ElemIndex(u32); + +/// Index type of a type inside the WebAssembly module. +pub struct TypeIndex(u32); + +/// Index type of a data segment inside the WebAssembly module. +pub struct DataSegmentIndex(u32); + +} + +/// WebAssembly value type -- equivalent of `wasmparser`'s Type. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum WasmType { + /// I32 type + I32, + /// I64 type + I64, + /// F32 type + F32, + /// F64 type + F64, + /// V128 type + V128, + /// Reference type + Ref(WasmRefType), +} + +impl fmt::Display for WasmType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WasmType::I32 => write!(f, "i32"), + WasmType::I64 => write!(f, "i64"), + WasmType::F32 => write!(f, "f32"), + WasmType::F64 => write!(f, "f64"), + WasmType::V128 => write!(f, "v128"), + WasmType::Ref(rt) => write!(f, "{rt}"), + } + } +} + +/// WebAssembly reference type -- equivalent of `wasmparser`'s RefType +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct WasmRefType { + pub nullable: bool, + pub heap_type: WasmHeapType, +} + +impl WasmRefType { + pub const EXTERNREF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Extern, + }; + pub const FUNCREF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Func, + }; +} + +impl fmt::Display for WasmRefType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::FUNCREF => write!(f, "funcref"), + Self::EXTERNREF => write!(f, "externref"), + _ => { + if self.nullable { + write!(f, "(ref null {})", self.heap_type) + } else { + write!(f, "(ref {})", self.heap_type) + } + } + } + } +} + +/// WebAssembly heap type -- equivalent of `wasmparser`'s HeapType +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum WasmHeapType { + /// The abstract, untyped (any) function. + /// + /// Introduced in the references-types proposal. + Func, + /// The abstract, external heap type. + /// + /// Introduced in the references-types proposal. + Extern, +} + +impl fmt::Display for WasmHeapType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Func => write!(f, "func"), + Self::Extern => write!(f, "extern"), + } + } +} + +/// WebAssembly function type -- equivalent of `wasmparser`'s FuncType. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct WasmFuncType { + params: Box<[WasmType]>, + externref_params_count: usize, + returns: Box<[WasmType]>, + externref_returns_count: usize, +} + +impl WasmFuncType { + #[inline] + pub fn new(params: Box<[WasmType]>, returns: Box<[WasmType]>) -> Self { + let externref_params_count = params + .iter() + .filter(|p| match **p { + WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, + _ => false, + }) + .count(); + let externref_returns_count = returns + .iter() + .filter(|r| match **r { + WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, + _ => false, + }) + .count(); + WasmFuncType { + params, + externref_params_count, + returns, + externref_returns_count, + } + } + + /// Function params types. + #[inline] + pub fn params(&self) -> &[WasmType] { + &self.params + } + + /// How many `externref`s are in this function's params? + #[inline] + pub fn externref_params_count(&self) -> usize { + self.externref_params_count + } + + /// Returns params types. + #[inline] + pub fn returns(&self) -> &[WasmType] { + &self.returns + } + + /// How many `externref`s are in this function's returns? + #[inline] + pub fn externref_returns_count(&self) -> usize { + self.externref_returns_count + } +} + +/// An index of an entity. +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] +pub enum EntityIndex { + /// Function index. + Function(FuncIndex), + /// Table index. + Table(TableIndex), + /// Memory index. + Memory(MemoryIndex), + /// Global index. + Global(GlobalIndex), +} + +impl EntityIndex { + pub fn unwrap_func(&self) -> FuncIndex { + match self { + EntityIndex::Function(f) => *f, + eidx => panic!("not a func, but {eidx:?}"), + } + } +} + +impl From for EntityIndex { + fn from(value: FuncIndex) -> Self { + Self::Function(value) + } +} + +impl From for EntityIndex { + fn from(value: TableIndex) -> Self { + Self::Table(value) + } +} + +impl From for EntityIndex { + fn from(value: MemoryIndex) -> Self { + Self::Memory(value) + } +} + +impl From for EntityIndex { + fn from(value: GlobalIndex) -> Self { + Self::Global(value) + } +} + +/// A type of an item in a wasm module where an item is typically something that +/// can be exported. +#[derive(Clone, Debug)] +pub enum EntityType { + /// A global variable with the specified content type + Global(Global), + /// A linear memory with the specified limits + Memory(Memory), + /// A table with the specified element type and limits + Table(Table), + /// A function type where the index points to the type section and records a + /// function signature. + Function(SignatureIndex), +} + +impl EntityType { + /// Assert that this entity is a global + pub fn unwrap_global(&self) -> &Global { + match self { + EntityType::Global(g) => g, + _ => panic!("not a global"), + } + } + + /// Assert that this entity is a memory + pub fn unwrap_memory(&self) -> &Memory { + match self { + EntityType::Memory(g) => g, + _ => panic!("not a memory"), + } + } + + /// Assert that this entity is a table + pub fn unwrap_table(&self) -> &Table { + match self { + EntityType::Table(g) => g, + _ => panic!("not a table"), + } + } + + /// Assert that this entity is a function + pub fn unwrap_func(&self) -> SignatureIndex { + match self { + EntityType::Function(g) => *g, + _ => panic!("not a func"), + } + } +} + +/// A WebAssembly global. +#[derive(Debug, Clone, Hash, Eq, PartialEq)] +pub struct Global { + /// The Wasm type of the value stored in the global. + pub ty: WasmType, + /// A flag indicating whether the value may change at runtime. + pub mutability: bool, +} + +/// Globals are initialized via the `const` operators or by referring to another import. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum GlobalInit { + /// An `i32.const`. + I32Const(i32), + /// An `i64.const`. + I64Const(i64), + /// An `f32.const`. + F32Const(u32), + /// An `f64.const`. + F64Const(u64), + /// A `vconst`. + V128Const(u128), + /// A `global.get` of another global. + GetGlobal(GlobalIndex), +} + +impl GlobalInit { + /// Serialize the initializer constant expression into bytes (little-endian order). + pub fn to_imm( + self, + module: &Module, + diagnostics: &DiagnosticsHandler, + ) -> WasmResult { + Ok(match self { + GlobalInit::I32Const(x) => x.into(), + GlobalInit::I64Const(x) => x.into(), + GlobalInit::F32Const(x) => x.into(), + GlobalInit::F64Const(x) => x.into(), + GlobalInit::V128Const(x) => x.into(), + GlobalInit::GetGlobal(global_idx) => { + let global_init = module.try_global_initializer(global_idx, diagnostics)?; + global_init.to_imm(module, diagnostics)? + } + }) + } + + pub fn as_i32(&self, module: &Module, diagnostics: &DiagnosticsHandler) -> WasmResult { + Ok(match self { + GlobalInit::I32Const(x) => *x, + GlobalInit::GetGlobal(global_idx) => { + let global_init = module.try_global_initializer(*global_idx, diagnostics)?; + global_init.as_i32(module, diagnostics)? + } + g => { + unsupported_diag!(diagnostics, "Expected global init to be i32, got: {:?}", g); + } + }) + } +} + +/// WebAssembly table. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub struct Table { + /// The table elements' Wasm type. + pub wasm_ty: WasmRefType, + /// The minimum number of elements in the table. + pub minimum: u32, + /// The maximum number of elements in the table. + pub maximum: Option, +} + +/// WebAssembly linear memory. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub struct Memory { + /// The minimum number of pages in the memory. + pub minimum: u64, + /// The maximum number of pages in the memory. + pub maximum: Option, + /// Is this memory imported in the current [Module] + pub imported: bool, +} + +impl From for Memory { + fn from(ty: wasmparser::MemoryType) -> Memory { + Memory { + minimum: ty.initial, + maximum: ty.maximum, + imported: false, + } + } +} + +/// WebAssembly event. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub struct Tag { + /// The event signature type. + pub ty: TypeIndex, +} + +impl From for Tag { + fn from(ty: wasmparser::TagType) -> Tag { + match ty.kind { + wasmparser::TagKind::Exception => Tag { + ty: TypeIndex::from_u32(ty.func_type_idx), + }, + } + } +} +/// Offset of a data segment inside a linear memory. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum DataSegmentOffset { + /// An `i32.const` offset. + I32Const(i32), + /// An offset as a `global.get` of another global. + GetGlobal(GlobalIndex), +} + +impl DataSegmentOffset { + /// Returns the offset as a i32, resolving the global if necessary. + pub fn as_i32(&self, module: &Module, diagnostics: &DiagnosticsHandler) -> WasmResult { + Ok(match self { + DataSegmentOffset::I32Const(x) => *x, + DataSegmentOffset::GetGlobal(global_idx) => { + let global_init = &module.try_global_initializer(*global_idx, diagnostics)?; + match global_init.as_i32(module, diagnostics) { + Err(_) => { + return Err(diagnostics + .diagnostic(Severity::Error) + .with_message(format!( + "Failed to get data segment offset from global init \ + {global_init:?} with global index {global_idx:?}", + )) + .into_report()); + } + Ok(v) => v, + } + } + }) + } +} + +/// A WebAssembly data segment. +/// https://www.w3.org/TR/wasm-core-1/#data-segments%E2%91%A0 +#[derive(Debug)] +pub struct DataSegment<'a> { + /// The offset of the data segment inside the linear memory. + pub offset: DataSegmentOffset, + /// The initialization data. + pub data: &'a [u8], +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)] +pub struct BlockType { + pub params: Vec, + pub results: Vec, +} + +impl BlockType { + pub fn from_wasm( + block_ty: &wasmparser::BlockType, + mod_types: &ModuleTypesBuilder, + diagnostics: &DiagnosticsHandler, + ) -> WasmResult { + Ok(match block_ty { + wasmparser::BlockType::Empty => Self::default(), + wasmparser::BlockType::Type(ty) => Self { + params: vec![], + results: vec![ir_type(convert_valtype(*ty), diagnostics)?], + }, + wasmparser::BlockType::FuncType(ty_index) => { + let func_type = &mod_types[SignatureIndex::from_u32(*ty_index)]; + let params = func_type + .params() + .iter() + .map(|t| ir_type(*t, diagnostics)) + .collect::>>()?; + let results = func_type + .returns() + .iter() + .map(|t| ir_type(*t, diagnostics)) + .collect::>>()?; + Self { params, results } + } + }) + } +} + +/// Note that accesing this type is primarily done through the `Index` +/// implementations for this type. +#[derive(Default)] +pub struct ModuleTypes { + wasm_signatures: PrimaryMap, +} + +impl ModuleTypes { + /// Returns an iterator over all the wasm function signatures found within + /// this module. + pub fn wasm_signatures(&self) -> impl Iterator { + self.wasm_signatures.iter() + } +} + +impl Index for ModuleTypes { + type Output = WasmFuncType; + + fn index(&self, sig: SignatureIndex) -> &WasmFuncType { + &self.wasm_signatures[sig] + } +} + +/// A builder for [`ModuleTypes`]. +#[derive(Default)] +pub struct ModuleTypesBuilder { + types: ModuleTypes, + interned_func_types: FxHashMap, + wasmparser_to_our: FxHashMap, +} + +impl ModuleTypesBuilder { + /// Reserves space for `amt` more type signatures. + pub fn reserve_wasm_signatures(&mut self, amt: usize) { + self.types.wasm_signatures.reserve(amt); + } + + /// Interns the `sig` specified and returns a unique `SignatureIndex` that + /// can be looked up within [`ModuleTypes`] to recover the [`WasmFuncType`] + /// at runtime. + pub fn wasm_func_type(&mut self, id: CoreTypeId, sig: WasmFuncType) -> SignatureIndex { + let sig = self.intern_func_type(sig); + self.wasmparser_to_our.insert(id, sig); + sig + } + + fn intern_func_type(&mut self, sig: WasmFuncType) -> SignatureIndex { + if let Some(idx) = self.interned_func_types.get(&sig) { + return *idx; + } + + let idx = self.types.wasm_signatures.push(sig.clone()); + self.interned_func_types.insert(sig, idx); + idx + } + + /// Returns the result [`ModuleTypes`] of this builder. + pub fn finish(self) -> ModuleTypes { + self.types + } + + /// Returns an iterator over all the wasm function signatures found within + /// this module. + pub fn wasm_signatures(&self) -> impl Iterator { + self.types.wasm_signatures() + } +} + +// Forward the indexing impl to the internal `ModuleTypes` +impl Index for ModuleTypesBuilder +where + ModuleTypes: Index, +{ + type Output = >::Output; + + fn index(&self, sig: T) -> &Self::Output { + &self.types[sig] + } +} + +/// Converts a Wasm function type into a Miden IR function type +pub fn ir_func_type( + ty: &WasmFuncType, + diagnostics: &DiagnosticsHandler, +) -> WasmResult { + let params = ty + .params() + .iter() + .map(|t| ir_type(*t, diagnostics)) + .collect::>>()?; + let results = ty + .returns() + .iter() + .map(|t| ir_type(*t, diagnostics)) + .collect::>>()?; + Ok(hir::FunctionType { + abi: CallConv::Wasm, + results, + params, + }) +} + +/// Converts a Wasm type into a Miden IR type +pub fn ir_type(ty: WasmType, diagnostics: &DiagnosticsHandler) -> WasmResult { + Ok(match ty { + WasmType::I32 => hir::Type::I32, + WasmType::I64 => hir::Type::I64, + WasmType::F32 => hir::Type::Felt, + ty @ (WasmType::F64 | WasmType::V128 | WasmType::Ref(_)) => { + unsupported_diag!(diagnostics, "wasm error: unsupported type '{}'", ty) + } + }) +} + +/// Makes an IR function signature from a Wasm function type +pub fn ir_func_sig( + func_type: &hir::FunctionType, + call_conv: CallConv, + visibility: Visibility, +) -> Signature { + Signature { + params: func_type.params.iter().map(|ty| AbiParam::new(ty.clone())).collect(), + results: func_type.results.iter().map(|ty| AbiParam::new(ty.clone())).collect(), + cc: call_conv, + visibility, + } +} + +/// Converts a wasmparser table type +pub fn convert_global_type(ty: &wasmparser::GlobalType) -> Global { + Global { + ty: convert_valtype(ty.content_type), + mutability: ty.mutable, + } +} + +/// Converts a wasmparser table type +pub fn convert_table_type(ty: &wasmparser::TableType) -> Table { + assert!(!ty.table64, "64-bit tables are not supported"); + + Table { + wasm_ty: convert_ref_type(ty.element_type), + minimum: ty.initial as u32, + maximum: ty.maximum.map(|n| n as u32), + } +} + +/// Converts a wasmparser function type +pub fn convert_func_type(ty: &wasmparser::FuncType) -> WasmFuncType { + let params = ty.params().iter().map(|t| convert_valtype(*t)).collect(); + let results = ty.results().iter().map(|t| convert_valtype(*t)).collect(); + WasmFuncType::new(params, results) +} + +/// Converts a wasmparser value type +pub fn convert_valtype(ty: wasmparser::ValType) -> WasmType { + match ty { + wasmparser::ValType::I32 => WasmType::I32, + wasmparser::ValType::I64 => WasmType::I64, + wasmparser::ValType::F32 => WasmType::F32, + wasmparser::ValType::F64 => WasmType::F64, + wasmparser::ValType::V128 => WasmType::V128, + wasmparser::ValType::Ref(t) => WasmType::Ref(convert_ref_type(t)), + } +} + +/// Converts a wasmparser reference type +pub fn convert_ref_type(ty: wasmparser::RefType) -> WasmRefType { + WasmRefType { + nullable: ty.is_nullable(), + heap_type: convert_heap_type(ty.heap_type()), + } +} + +/// Converts a wasmparser heap type +pub fn convert_heap_type(ty: wasmparser::HeapType) -> WasmHeapType { + use wasmparser::AbstractHeapType; + match ty { + wasmparser::HeapType::Abstract { ty, shared: _ } => match ty { + AbstractHeapType::Func => WasmHeapType::Func, + AbstractHeapType::Extern => WasmHeapType::Extern, + ty => unimplemented!("unsupported abstract heap type {ty:?}"), + }, + wasmparser::HeapType::Concrete(_) => { + unimplemented!("user-defined types are not supported yet") + } + } +} diff --git a/frontend/wasm/src/ssa.rs b/frontend/wasm/src/ssa.rs new file mode 100644 index 000000000..4289f67ce --- /dev/null +++ b/frontend/wasm/src/ssa.rs @@ -0,0 +1,523 @@ +//! A SSA-building API that handles incomplete CFGs. +//! +//! The algorithm is based upon Braun M., Buchwald S., Hack S., Leißa R., Mallon C., +//! Zwinkau A. (2013) Simple and Efficient Construction of Static Single Assignment Form. +//! In: Jhala R., De Bosschere K. (eds) Compiler Construction. CC 2013. +//! Lecture Notes in Computer Science, vol 7791. Springer, Berlin, Heidelberg +//! +//! +//! +//! Based on Cranelift's Wasm -> CLIF translator v11.0.0 + +use alloc::rc::Rc; +use core::mem; + +use cranelift_entity::{entity_impl, EntityList, ListPool}; +use midenc_hir::{ + traits::BranchOpInterface, BlockRef, Context, FxHashMap, FxHashSet, OperationRef, SourceSpan, + Type, ValueRef, +}; + +/// Structure containing the data relevant the construction of SSA for a given function. +/// +/// The parameter struct `Variable` corresponds to the way variables are represented in the +/// non-SSA language you're translating from. +/// +/// The SSA building relies on information about the variables used and defined. +/// +/// This SSA building module allows you to def and use variables on the fly while you are +/// constructing the CFG, no need for a separate SSA pass after the CFG is completed. +/// +/// A basic block is said _filled_ if all the instruction that it contains have been translated, +/// and it is said _sealed_ if all of its predecessors have been declared. Only filled predecessors +/// can be declared. +pub struct SSABuilder { + context: Rc, + + /// Records for every variable and for every relevant block, the last definition of + /// the variable in the block. + variables: FxHashMap>>, + + /// Records the position of the basic blocks and the list of values used but not defined in the + /// block. + ssa_blocks: FxHashMap, + + /// Call stack for use in the `use_var`/`predecessors_lookup` state machine. + calls: Vec, + /// Result stack for use in the `use_var`/`predecessors_lookup` state machine. + results: Vec, + + /// Side effects accumulated in the `use_var`/`predecessors_lookup` state machine. + side_effects: SideEffects, + + /// Reused storage for cycle-detection. + visited: FxHashSet, + + /// Storage for pending variable definitions. + variable_pool: ListPool, + // /// Storage for predecessor definitions. + // inst_pool: ListPool, +} + +/// An opaque reference to a mutable variable. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct Variable(u32); +entity_impl!(Variable, "var"); + +/// Side effects of a `use_var` or a `seal_block` method call. +#[derive(Default)] +pub struct SideEffects { + /// When a variable is used but has never been defined before (this happens in the case of + /// unreachable code), a placeholder `iconst` or `fconst` value is added to the right `Block`. + /// This field signals if it is the case and return the `Block` to which the initialization has + /// been added. + pub instructions_added_to_blocks: Vec, +} + +impl SideEffects { + fn is_empty(&self) -> bool { + self.instructions_added_to_blocks.is_empty() + } +} + +#[derive(Clone, Debug)] +enum Sealed { + No { + // List of current Block arguments for which an earlier def has not been found yet. + undef_variables: EntityList, + }, + Yes, +} + +impl Default for Sealed { + fn default() -> Self { + Sealed::No { + undef_variables: EntityList::new(), + } + } +} + +#[derive(Clone, Default, Debug)] +struct SSABlockData { + // The predecessors of the Block with the block and branch instruction. + // predecessors: EntityList, + predecessors: Vec, + // A block is sealed if all of its predecessors have been declared. + sealed: Sealed, + // If this block is sealed and it has exactly one predecessor, this is that predecessor. + single_predecessor: Option, +} + +impl SSABuilder { + pub fn new(context: Rc) -> Self { + Self { + context, + variables: FxHashMap::default(), + ssa_blocks: FxHashMap::default(), + calls: Vec::new(), + results: Vec::new(), + side_effects: SideEffects::default(), + visited: FxHashSet::default(), + variable_pool: ListPool::new(), + } + } + + /// Clears a `SSABuilder` from all its data, letting it in a pristine state without + /// deallocating memory. + pub fn clear(&mut self) { + self.variables.clear(); + self.ssa_blocks.clear(); + self.variable_pool.clear(); + // self.inst_pool.clear(); + debug_assert!(self.calls.is_empty()); + debug_assert!(self.results.is_empty()); + debug_assert!(self.side_effects.is_empty()); + } + + /// Tests whether an `SSABuilder` is in a cleared state. + pub fn is_empty(&self) -> bool { + self.variables.is_empty() + && self.ssa_blocks.is_empty() + && self.calls.is_empty() + && self.results.is_empty() + && self.side_effects.is_empty() + } +} + +/// States for the `use_var`/`predecessors_lookup` state machine. +enum Call { + UseVar(OperationRef), + FinishPredecessorsLookup(ValueRef, BlockRef), +} + +/// The following methods are the API of the SSA builder. Here is how it should be used when +/// translating to Miden IR: +/// +/// - for each basic block, create a corresponding data for SSA construction with `declare_block`; +/// +/// - while traversing a basic block and translating instruction, use `def_var` and `use_var` to +/// record definitions and uses of variables, these methods will give you the corresponding SSA +/// values; +/// +/// - when all the instructions in a basic block have translated, the block is said _filled_ and +/// only then you can add it as a predecessor to other blocks with `declare_block_predecessor`; +/// +/// - when you have constructed all the predecessor to a basic block, call `seal_block` on it with +/// the `Function` that you are building. +/// +/// This API will give you the correct SSA values to use as arguments of your instructions, +/// as well as modify the jump instruction and `Block` parameters to account for the SSA +/// Phi functions. +impl SSABuilder { + /// Declares a new definition of a variable in a given basic block. + pub fn def_var(&mut self, var: Variable, val: ValueRef, block: BlockRef) { + match self.variables.get_mut(&var) { + Some(var_defs) => { + var_defs.insert(block, Some(val)); + } + None => { + let mut var_defs = FxHashMap::default(); + var_defs.insert(block, Some(val)); + self.variables.insert(var, var_defs); + } + } + } + + /// Declares a use of a variable in a given basic block. Returns the SSA value corresponding + /// to the current SSA definition of this variable and a list of newly created Blocks + /// + /// If the variable has never been defined in this blocks or recursively in its predecessors, + /// this method will silently create an initializer. You are + /// responsible for making sure that you initialize your variables. + pub fn use_var(&mut self, var: Variable, ty: Type, block: BlockRef) -> (ValueRef, SideEffects) { + debug_assert!(self.calls.is_empty()); + debug_assert!(self.results.is_empty()); + debug_assert!(self.side_effects.is_empty()); + + // Prepare the 'calls' and 'results' stacks for the state machine. + self.use_var_nonlocal(var, ty.clone(), block); + let value = self.run_state_machine(var, ty); + + let side_effects = mem::take(&mut self.side_effects); + (value, side_effects) + } + + /// Resolve the minimal SSA Value of `var` in `block` by traversing predecessors. + /// + /// This function sets up state for `run_state_machine()` but does not execute it. + fn use_var_nonlocal(&mut self, var: Variable, ty: Type, mut block: BlockRef) { + // First, try Local Value Numbering (Algorithm 1 in the paper). + // If the variable already has a known Value in this block, use that. + if let Some(val) = self.variables.entry(var).or_default().entry(block).or_default() { + self.results.push(*val); + return; + } + + // Otherwise, use Global Value Numbering (Algorithm 2 in the paper). + // This resolves the Value with respect to its predecessors. + // Find the most recent definition of `var`, and the block the definition comes from. + let (val, from) = self.find_var(var, ty, block); + + // The `from` block returned from `find_var` is guaranteed to be on the path we follow by + // traversing only single-predecessor edges. It might be equal to `block` if there is no + // such path, but in that case `find_var` ensures that the variable is defined in this block + // by a new block parameter. It also might be somewhere in a cycle, but even then this loop + // will terminate the first time it encounters that block, rather than continuing around the + // cycle forever. + // + // Why is it okay to copy the definition to all intervening blocks? For the initial block, + // this may not be the final definition of this variable within this block, but if we've + // gotten here then we know there is no earlier definition in the block already. + // + // For the remaining blocks: Recall that a block is only allowed to be set as a predecessor + // after all its instructions have already been filled in, so when we follow a predecessor + // edge to a block, we know there will never be any more local variable definitions added to + // that block. We also know that `find_var` didn't find a definition for this variable in + // any of the blocks before `from`. + // + // So in either case there is no definition in these blocks yet and we can blindly set one. + let var_defs = self.variables.entry(var).or_default(); + while block != from { + debug_assert!(var_defs[&block].is_none()); + var_defs.insert(block, Some(val)); + block = self.ssa_blocks[&block].single_predecessor.unwrap(); + } + } + + /// Find the most recent definition of this variable, returning both the definition and the + /// block in which it was found. If we can't find a definition that's provably the right one for + /// all paths to the current block, then append a block parameter to some block and use that as + /// the definition. Either way, also arrange that the definition will be on the `results` stack + /// when `run_state_machine` is done processing the current step. + /// + /// If a block has exactly one predecessor, and the block is sealed so we know its predecessors + /// will never change, then its definition for this variable is the same as the definition from + /// that one predecessor. In this case it's easy to see that no block parameter is necessary, + /// but we need to look at the predecessor to see if a block parameter might be needed there. + /// That holds transitively across any chain of sealed blocks with exactly one predecessor each. + /// + /// This runs into a problem, though, if such a chain has a cycle: Blindly following a cyclic + /// chain that never defines this variable would lead to an infinite loop in the compiler. It + /// doesn't really matter what code we generate in that case. Since each block in the cycle has + /// exactly one predecessor, there's no way to enter the cycle from the function's entry block; + /// and since all blocks in the cycle are sealed, the entire cycle is permanently dead code. But + /// we still have to prevent the possibility of an infinite loop. + /// + /// To break cycles, we can pick any block within the cycle as the one where we'll add a block + /// parameter. It's convenient to pick the block at which we entered the cycle, because that's + /// the first place where we can detect that we just followed a cycle. Adding a block parameter + /// gives us a definition we can reuse throughout the rest of the cycle. + fn find_var(&mut self, var: Variable, ty: Type, mut block: BlockRef) -> (ValueRef, BlockRef) { + // Try to find an existing definition along single-predecessor edges first. + self.visited.clear(); + let var_defs = self.variables.get_mut(&var).unwrap(); + while let Some(pred) = self.ssa_blocks[&block].single_predecessor { + if !self.visited.insert(block) { + break; + } + block = pred; + if let Some(val) = var_defs.entry(block).or_default() { + self.results.push(*val); + return (*val, block); + } + } + + // We've promised to return the most recent block where `var` was defined, but we didn't + // find a usable definition. So create one. + + let val = self.context.append_block_argument(block, ty, SourceSpan::default()); + var_defs.insert(block, Some(val)); + + // Now every predecessor needs to pass its definition of this variable to the newly added + // block parameter. To do that we have to "recursively" call `use_var`, but there are two + // problems with doing that. First, we need to keep a fixed bound on stack depth, so we + // can't actually recurse; instead we defer to `run_state_machine`. Second, if we don't + // know all our predecessors yet, we have to defer this work until the block gets sealed. + match &mut self.ssa_blocks.get_mut(&block).unwrap().sealed { + // Once all the `calls` added here complete, this leaves either `val` or an equivalent + // definition on the `results` stack. + Sealed::Yes => self.begin_predecessors_lookup(val, block), + Sealed::No { undef_variables } => { + undef_variables.push(var, &mut self.variable_pool); + self.results.push(val); + } + } + (val, block) + } + + /// Declares a new basic block to construct corresponding data for SSA construction. + /// No predecessors are declared here and the block is not sealed. + /// Predecessors have to be added with `declare_block_predecessor`. + pub fn declare_block(&mut self, block: BlockRef) { + // Ensure the block exists so seal_one_block will see it even if no predecessors or + // variables get declared for this block. + self.ssa_blocks.insert(block, SSABlockData::default()); + } + + /// Declares a new predecessor for a `Block` and record the branch instruction + /// of the predecessor that leads to it. + /// + /// The precedent `Block` must be filled before added as predecessor. + /// Note that you must provide no jump arguments to the branch + /// instruction when you create it since `SSABuilder` will fill them for you. + /// + /// Callers are expected to avoid adding the same predecessor more than once in the case + /// of a jump table. + pub fn declare_block_predecessor(&mut self, block: BlockRef, inst: OperationRef) { + debug_assert!( + !self.is_sealed(block), + "you cannot add a predecessor {inst} to a sealed block {block}" + ); + debug_assert!( + self.ssa_blocks[&block].predecessors.iter().all(|&branch| branch != inst), + "you have declared the same predecessor twice!" + ); + self.ssa_blocks.get_mut(&block).unwrap().predecessors.push(inst); + } + + /// Remove a previously declared Block predecessor by giving a reference to the jump + /// instruction. Returns the basic block containing the instruction. + /// + /// Note: use only when you know what you are doing, this might break the SSA building problem + pub fn remove_block_predecessor(&mut self, block: BlockRef, inst: OperationRef) { + debug_assert!(!self.is_sealed(block)); + let data = self.ssa_blocks.get_mut(&block).unwrap(); + let pred = data + .predecessors + .iter() + .position(|&branch| branch == inst) + .expect("the predecessor you are trying to remove is not declared"); + data.predecessors.swap_remove(pred); + } + + /// Completes the global value numbering for a `Block`, all of its predecessors having been + /// already sealed. + /// + /// This method modifies the function's `Layout` by adding arguments to the `Block`s to + /// take into account the Phi function placed by the SSA algorithm. + /// + /// Returns the list of newly created blocks for critical edge splitting. + pub fn seal_block(&mut self, block: BlockRef) -> SideEffects { + debug_assert!( + !self.is_sealed(block), + "Attempting to seal {block} which is already sealed." + ); + self.seal_one_block(block); + mem::take(&mut self.side_effects) + } + + /// Helper function for `seal_block` + fn seal_one_block(&mut self, block: BlockRef) { + use midenc_hir::hashbrown::hash_map::EntryRef; + + // For each undef var we look up values in the predecessors and create a block parameter + // only if necessary. + let mut undef_variables = match self.ssa_blocks.entry_ref(&block) { + EntryRef::Occupied(mut entry) => { + let old_sealed = entry.get().sealed.clone(); + entry.get_mut().sealed = Sealed::Yes; + match old_sealed { + Sealed::No { undef_variables } => undef_variables, + Sealed::Yes => return, + } + } + EntryRef::Vacant(_) => { + self.ssa_blocks.insert( + block, + SSABlockData { + sealed: Sealed::Yes, + ..Default::default() + }, + ); + EntityList::new() + } + }; + + let ssa_params = undef_variables.len(&self.variable_pool); + + let predecessors = self.predecessors(block); + if predecessors.len() == 1 { + let pred = predecessors[0].parent().expect("no containing block found"); + self.ssa_blocks.get_mut(&block).unwrap().single_predecessor = Some(pred); + } + + // Note that begin_predecessors_lookup requires visiting these variables in the same order + // that they were defined by find_var, because it appends arguments to the jump instructions + // in all the predecessor blocks one variable at a time. + for idx in 0..ssa_params { + let var = undef_variables.get(idx, &self.variable_pool).unwrap(); + + // We need the temporary Value that was assigned to this Variable. If that Value shows + // up as a result from any of our predecessors, then it never got assigned on the loop + // through that block. We get the value from the next block param, where it was first + // allocated in find_var. + // + // On each iteration through this loop, there are (ssa_params - idx) undefined variables + // left to process. Previous iterations through the loop may have removed earlier block + // parameters, but the last (ssa_params - idx) block parameters always correspond to the + // remaining undefined variables. So index from the end of the current block params. + let (val, val_ty) = { + let block = block.borrow(); + let block_args = block.arguments(); + let val = block_args[block_args.len() - (ssa_params - idx)] as ValueRef; + (val, val.borrow().ty().clone()) + }; + + debug_assert!(self.calls.is_empty()); + debug_assert!(self.results.is_empty()); + // self.side_effects may be non-empty here so that callers can + // accumulate side effects over multiple calls. + self.begin_predecessors_lookup(val, block); + self.run_state_machine(var, val_ty); + } + + undef_variables.clear(&mut self.variable_pool); + } + + /// Given the local SSA Value of a Variable in a Block, perform a recursive lookup on + /// predecessors to determine if it is redundant with another Value earlier in the CFG. + /// + /// If such a Value exists and is redundant, the local Value is replaced by the + /// corresponding non-local Value. If the original Value was a Block parameter, + /// the parameter may be removed if redundant. Parameters are placed eagerly by callers + /// to avoid infinite loops when looking up a Value for a Block that is in a CFG loop. + /// + /// Doing this lookup for each Value in each Block preserves SSA form during construction. + /// + /// ## Arguments + /// + /// `sentinel` is a dummy Block parameter inserted by `use_var_nonlocal()`. + /// Its purpose is to allow detection of CFG cycles while traversing predecessors. + fn begin_predecessors_lookup(&mut self, sentinel: ValueRef, dest_block: BlockRef) { + self.calls.push(Call::FinishPredecessorsLookup(sentinel, dest_block)); + // Iterate over the predecessors. + self.calls.extend( + self.ssa_blocks[&dest_block] + .predecessors + .iter() + .rev() + .copied() + .map(Call::UseVar), + ); + } + + /// Examine the values from the predecessors and compute a result value, creating + /// block parameters as needed. + fn finish_predecessors_lookup(&mut self, sentinel: ValueRef, dest_block: BlockRef) -> ValueRef { + // Determine how many predecessors are yielding unique, non-temporary Values. + let num_predecessors = self.predecessors(dest_block).len(); + // When this `Drain` is dropped, these elements will get truncated. + let results = self.results.drain(self.results.len() - num_predecessors..); + // Keep the block argument. + let preds = &mut self.ssa_blocks.get_mut(&dest_block).unwrap().predecessors; + for (idx, &val) in results.as_slice().iter().enumerate() { + let pred = preds.get_mut(idx).unwrap(); + let branch = *pred; + assert!( + branch.borrow().implements::(), + "you have declared a non-branch instruction as a predecessor to a block!" + ); + + self.context.append_branch_destination_argument(branch, dest_block, val); + } + sentinel + } + + /// Returns the list of `Block`s that have been declared as predecessors of the argument. + fn predecessors(&self, block: BlockRef) -> &[OperationRef] { + &self.ssa_blocks[&block].predecessors + } + + /// Returns whether the given Block has any predecessor or not. + pub fn has_any_predecessors(&self, block: BlockRef) -> bool { + !self.predecessors(block).is_empty() + } + + /// Returns `true` if and only if `seal_block` has been called on the argument. + pub fn is_sealed(&self, block: BlockRef) -> bool { + matches!(self.ssa_blocks.get(&block).cloned().unwrap_or_default().sealed, Sealed::Yes) + } + + /// The main algorithm is naturally recursive: when there's a `use_var` in a + /// block with no corresponding local defs, it recurses and performs a + /// `use_var` in each predecessor. To avoid risking running out of callstack + /// space, we keep an explicit stack and use a small state machine rather + /// than literal recursion. + fn run_state_machine(&mut self, var: Variable, ty: Type) -> ValueRef { + // Process the calls scheduled in `self.calls` until it is empty. + while let Some(call) = self.calls.pop() { + match call { + Call::UseVar(branch) => { + let block = branch.parent().expect("no containing block is found"); + self.use_var_nonlocal(var, ty.clone(), block); + } + Call::FinishPredecessorsLookup(sentinel, dest_block) => { + let val = self.finish_predecessors_lookup(sentinel, dest_block); + self.results.push(val); + } + } + } + debug_assert_eq!(self.results.len(), 1); + self.results.pop().unwrap() + } +} diff --git a/frontend/wasm/src/test_utils.rs b/frontend/wasm/src/test_utils.rs new file mode 100644 index 000000000..f60a18ed4 --- /dev/null +++ b/frontend/wasm/src/test_utils.rs @@ -0,0 +1,3 @@ +use std::rc::Rc; + +use midenc_hir::Context; diff --git a/frontend/wasm/src/translation_utils.rs b/frontend/wasm/src/translation_utils.rs new file mode 100644 index 000000000..353cf3d0f --- /dev/null +++ b/frontend/wasm/src/translation_utils.rs @@ -0,0 +1,149 @@ +//! Helper functions and structures for the translation. + +use miden_core::{Felt, FieldElement}; +use midenc_dialect_arith::ArithOpBuilder; +use midenc_hir::{ + AbiParam, Builder, CallConv, FunctionType, Signature, SourceSpan, Type, ValueRef, Visibility, +}; +use midenc_session::DiagnosticsHandler; + +use crate::{ + error::WasmResult, module::function_builder_ext::FunctionBuilderExt, unsupported_diag, +}; + +/// Represents the possible sizes in bytes of the discriminant of a variant type in the component +/// model +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum DiscriminantSize { + /// 8-bit discriminant + Size1, + /// 16-bit discriminant + Size2, + /// 32-bit discriminant + Size4, +} + +impl DiscriminantSize { + /// Calculate the size of discriminant needed to represent a variant with the specified number + /// of cases. + pub const fn from_count(count: usize) -> Option { + if count <= 0xff { + Some(Self::Size1) + } else if count <= 0xffff { + Some(Self::Size2) + } else if count <= 0xffff_ffff { + Some(Self::Size4) + } else { + None + } + } + + /// Returns the size, in bytes, of this discriminant + pub const fn byte_size(&self) -> u32 { + match self { + DiscriminantSize::Size1 => 1, + DiscriminantSize::Size2 => 2, + DiscriminantSize::Size4 => 4, + } + } +} + +impl From for u32 { + /// Size of the discriminant as a `u32` + fn from(size: DiscriminantSize) -> u32 { + size.byte_size() + } +} + +impl From for usize { + /// Size of the discriminant as a `usize` + fn from(size: DiscriminantSize) -> usize { + match size { + DiscriminantSize::Size1 => 1, + DiscriminantSize::Size2 => 2, + DiscriminantSize::Size4 => 4, + } + } +} + +/// Represents the number of bytes required to store a flags value in the component model +pub enum FlagsSize { + /// There are no flags + Size0, + /// Flags can fit in a u8 + Size1, + /// Flags can fit in a u16 + Size2, + /// Flags can fit in a specified number of u32 fields + Size4Plus(u8), +} + +impl FlagsSize { + /// Calculate the size needed to represent a value with the specified number of flags. + pub const fn from_count(count: usize) -> FlagsSize { + if count == 0 { + FlagsSize::Size0 + } else if count <= 8 { + FlagsSize::Size1 + } else if count <= 16 { + FlagsSize::Size2 + } else { + let amt = ceiling_divide(count, 32); + if amt > (u8::MAX as usize) { + panic!("too many flags"); + } + FlagsSize::Size4Plus(amt as u8) + } + } +} + +/// Divide `n` by `d`, rounding up in the case of a non-zero remainder. +const fn ceiling_divide(n: usize, d: usize) -> usize { + n.div_ceil(d) +} + +/// Emit instructions to produce a zero value in the given type. +pub fn emit_zero( + ty: &Type, + builder: &mut FunctionBuilderExt<'_, B>, + diagnostics: &DiagnosticsHandler, +) -> WasmResult { + Ok(match ty { + Type::I1 => builder.i1(false, SourceSpan::default()), + Type::I8 => builder.i8(0, SourceSpan::default()), + Type::I16 => builder.i16(0, SourceSpan::default()), + Type::I32 => builder.i32(0, SourceSpan::default()), + Type::I64 => builder.i64(0, SourceSpan::default()), + Type::U8 => builder.u8(0, SourceSpan::default()), + Type::U16 => builder.u16(0, SourceSpan::default()), + Type::U32 => builder.u32(0, SourceSpan::default()), + Type::U64 => builder.u64(0, SourceSpan::default()), + Type::F64 => builder.f64(0.0, SourceSpan::default()), + Type::Felt => builder.felt(Felt::ZERO, SourceSpan::default()), + Type::I128 + | Type::U128 + | Type::U256 + | Type::Ptr(_) + | Type::Struct(_) + | Type::Array(..) + | Type::List(_) + | Type::Function(_) + | Type::Unknown + | Type::Never => { + unsupported_diag!(diagnostics, "cannot emit zero value for type: {:?}", ty); + } + }) +} + +pub fn sig_from_func_type( + func_type: &FunctionType, + call_conv: CallConv, + visibility: Visibility, +) -> Signature { + Signature { + params: func_type.params.iter().map(|ty| AbiParam::new(ty.clone())).collect(), + results: func_type.results.iter().map(|ty| AbiParam::new(ty.clone())).collect(), + cc: call_conv, + visibility, + } +} diff --git a/hir-analysis/CHANGELOG.md b/hir-analysis/CHANGELOG.md index 525b46715..3646d965c 100644 --- a/hir-analysis/CHANGELOG.md +++ b/hir-analysis/CHANGELOG.md @@ -6,6 +6,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-hir-analysis-v0.4.0...midenc-hir-analysis-v0.4.1) - 2025-09-03 + +### Other + +- switch to `expect_file!` in spills tests +- increase stack pressure in the `spills_loop_nest` test and make it spill +- move `tests` module in spill analysis into a separate file +- rewrite spill analysis tests, add logging + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-hir-analysis-v0.1.5...midenc-hir-analysis-v0.4.0) - 2025-08-15 + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-hir-analysis-v0.0.7...midenc-hir-analysis-v0.0.8) - 2025-04-24 + +### Added +- implement cross-context Call op, rename IR Exec back to Call + +### Fixed +- *(codegen)* incomplete global/data segment lowering + +### Other +- treat warnings as compiler errors, +- update rust toolchain, clean up deps +- rename hir2 crates +- remove old contents of hir, hir-analysis, hir-transform +- update rust toolchain to 1-16 nightly @ 1.86.0 +- normalize use of fxhash-based hash maps +- rename Call IR op to Exec + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-hir-analysis-v0.0.6...midenc-hir-analysis-v0.0.7) - 2024-09-17 ### Other diff --git a/hir-analysis/Cargo.toml b/hir-analysis/Cargo.toml index 41aa1444c..242667006 100644 --- a/hir-analysis/Cargo.toml +++ b/hir-analysis/Cargo.toml @@ -12,16 +12,14 @@ readme.workspace = true edition.workspace = true [dependencies] -anyhow.workspace = true -cranelift-entity.workspace = true -cranelift-bforest.workspace = true -inventory.workspace = true -intrusive-collections.workspace = true +blink-alloc.workspace = true +bitvec.workspace = true +log.workspace = true midenc-hir.workspace = true -midenc-session.workspace = true -rustc-hash.workspace = true -smallvec.workspace = true -thiserror.workspace = true [dev-dependencies] -pretty_assertions.workspace = true +midenc-dialect-hir = { path = "../dialects/hir" } +midenc-dialect-arith = { path = "../dialects/arith" } +midenc-dialect-cf = { path = "../dialects/cf" } +midenc-expect-test = { path = "../tools/expect-test" } +env_logger.workspace = true diff --git a/hir-analysis/src/analyses.rs b/hir-analysis/src/analyses.rs new file mode 100644 index 000000000..2b4fd0166 --- /dev/null +++ b/hir-analysis/src/analyses.rs @@ -0,0 +1,13 @@ +pub mod constant_propagation; +pub mod dce; +pub mod liveness; +mod loops; +pub mod spills; + +pub use self::{ + constant_propagation::SparseConstantPropagation, + dce::DeadCodeAnalysis, + liveness::LivenessAnalysis, + loops::{LoopAction, LoopState}, + spills::SpillAnalysis, +}; diff --git a/hir-analysis/src/analyses/constant_propagation.rs b/hir-analysis/src/analyses/constant_propagation.rs new file mode 100644 index 000000000..c1f2cffaf --- /dev/null +++ b/hir-analysis/src/analyses/constant_propagation.rs @@ -0,0 +1,246 @@ +use alloc::{boxed::Box, rc::Rc}; +use core::fmt; + +use midenc_hir::{ + traits::Foldable, AttributeValue, Dialect, FoldResult, Forward, OpFoldResult, Operation, + Report, SmallVec, +}; + +use crate::{ + sparse::{self, SparseDataFlowAnalysis}, + AnalysisState, AnalysisStateGuard, AnalysisStateGuardMut, BuildableDataFlowAnalysis, + DataFlowSolver, Lattice, LatticeLike, SparseForwardDataFlowAnalysis, SparseLattice, +}; + +/// This lattice value represents a known constant value of a lattice. +#[derive(Default)] +pub struct ConstantValue { + /// The constant value + constant: Option>>, + /// The dialect that can be used to materialize this constant + dialect: Option>, +} +impl fmt::Display for ConstantValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.constant.as_ref() { + None => f.write_str("uninitialized"), + Some(None) => f.write_str("unknown"), + Some(Some(value)) => fmt::Debug::fmt(value, f), + } + } +} +impl fmt::Debug for ConstantValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl Clone for ConstantValue { + fn clone(&self) -> Self { + let constant = self.constant.as_ref().map(|c| c.as_deref().map(|c| c.clone_value())); + Self { + constant, + dialect: self.dialect.clone(), + } + } +} + +#[allow(unused)] +impl ConstantValue { + pub fn new(constant: Box, dialect: Rc) -> Self { + Self { + constant: Some(Some(constant)), + dialect: Some(dialect), + } + } + + pub fn unknown() -> Self { + Self { + constant: Some(None), + ..Default::default() + } + } + + #[inline] + pub fn uninitialized() -> Self { + Self::default() + } + + #[inline] + pub const fn is_uninitialized(&self) -> bool { + self.constant.is_none() + } + + pub fn constant_value(&self) -> Option> { + self.constant + .as_ref() + .expect("expected constant value to be initialized") + .as_deref() + .map(|c| c.clone_value()) + } + + pub fn constant_dialect(&self) -> Option> { + self.dialect.clone() + } +} + +impl Eq for ConstantValue {} +impl PartialEq for ConstantValue { + fn eq(&self, other: &Self) -> bool { + self.constant == other.constant + } +} + +impl LatticeLike for ConstantValue { + fn join(&self, rhs: &Self) -> Self { + // The join of two constant values is: + // + // * `unknown` if they represent different values + // * The identity function if they represent the same value + // * The more defined value if one of the two is uninitialized + match (self.is_uninitialized(), rhs.is_uninitialized()) { + (false, false) => { + if self == rhs { + self.clone() + } else { + Self::unknown() + } + } + (true, true) | (false, true) => self.clone(), + (true, false) => rhs.clone(), + } + } + + fn meet(&self, rhs: &Self) -> Self { + if self.is_uninitialized() || rhs.is_uninitialized() { + Self::uninitialized() + } else if self == rhs { + self.clone() + } else { + Self::unknown() + } + } +} + +/// This analysis implements sparse constant propagation, which attempts to determine constant- +/// valued results for operations using constant-valued operands, by speculatively folding +/// operations. +/// +/// When combined with dead-code analysis, this becomes sparse conditional constant propagation, +/// commonly abbreviated as _SCCP_. +#[derive(Default)] +pub struct SparseConstantPropagation; + +impl BuildableDataFlowAnalysis for SparseConstantPropagation { + type Strategy = SparseDataFlowAnalysis; + + #[inline(always)] + fn new(_solver: &mut DataFlowSolver) -> Self { + Self + } +} + +impl SparseForwardDataFlowAnalysis for SparseConstantPropagation { + type Lattice = Lattice; + + fn debug_name(&self) -> &'static str { + "sparse-constant-propagation" + } + + fn visit_operation( + &self, + op: &Operation, + operands: &[AnalysisStateGuard<'_, Self::Lattice>], + results: &mut [AnalysisStateGuardMut<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) -> Result<(), Report> { + log::debug!("visiting operation {op}"); + + // Don't try to simulate the results of a region operation as we can't guarantee that + // folding will be out-of-place. We don't allow in-place folds as the desire here is for + // simulated execution, and not general folding. + if op.has_regions() { + log::trace!("op has regions so conservatively setting results to entry state"); + sparse::set_all_to_entry_states(self, results); + return Ok(()); + } + + let mut constant_operands = + SmallVec::<[Option>; 8]>::with_capacity(op.num_operands()); + for (index, operand_lattice) in operands.iter().enumerate() { + log::trace!( + "operand lattice for {} is {}", + op.operands()[index].borrow().as_value_ref(), + operand_lattice.value() + ); + if operand_lattice.value().is_uninitialized() { + return Ok(()); + } + constant_operands.push(operand_lattice.value().constant_value()); + } + + // Save the original operands and attributes just in case the operation folds in-place. + // The constant passed in may not correspond to the real runtime value, so in-place updates + // are not allowed. + // + // Simulate the result of folding this operation to a constant. If folding fails or would be + // an in-place fold, mark the results as overdefined. + let mut fold_results = SmallVec::with_capacity(op.num_results()); + let fold_result = op.fold(&mut fold_results); + if matches!(fold_result, FoldResult::Failed | FoldResult::InPlace) { + sparse::set_all_to_entry_states(self, results); + return Ok(()); + } + + // Merge the fold results into the lattice for this operation. + assert_eq!(fold_results.len(), op.num_results()); + for (lattice, fold_result) in results.iter_mut().zip(fold_results.into_iter()) { + // Merge in the result of the fold, either a constant or a value. + match fold_result { + OpFoldResult::Attribute(value) => { + let new_lattice = ConstantValue::new(value, op.dialect()); + log::trace!( + "setting lattice for {} to {new_lattice} from {}", + lattice.anchor(), + lattice.value() + ); + let change_result = lattice.join(&new_lattice); + log::debug!( + "setting constant value for {} to {new_lattice}: {change_result} as {}", + lattice.anchor(), + lattice.value() + ); + } + OpFoldResult::Value(value) => { + let new_lattice = solver.get_or_create_mut::, _>(value); + log::trace!( + "setting lattice for {} to {} from {}", + lattice.anchor(), + new_lattice.value(), + lattice.value() + ); + let change_result = lattice.join(new_lattice.value()); + log::debug!( + "setting constant value for {} to {}: {change_result} as {}", + lattice.anchor(), + new_lattice.value(), + lattice.value() + ); + } + } + } + + Ok(()) + } + + fn set_to_entry_state(&self, lattice: &mut AnalysisStateGuardMut<'_, Self::Lattice>) { + log::trace!("setting lattice to entry state from {}", lattice.value()); + let entry_state = ConstantValue::unknown(); + let change_result = lattice.join(&entry_state); + log::debug!( + "setting constant value for {} to {entry_state}: {change_result} as {}", + lattice.anchor(), + lattice.value() + ); + } +} diff --git a/hir-analysis/src/analyses/dce.rs b/hir-analysis/src/analyses/dce.rs new file mode 100644 index 000000000..361023de1 --- /dev/null +++ b/hir-analysis/src/analyses/dce.rs @@ -0,0 +1,953 @@ +use alloc::boxed::Box; +use core::{ + any::Any, + cell::{Cell, RefCell}, + fmt, + ptr::NonNull, +}; + +use midenc_hir::{ + adt::{SmallDenseMap, SmallSet}, + pass::AnalysisManager, + traits::{BranchOpInterface, ReturnLike}, + AttributeValue, Block, BlockRef, CallOpInterface, CallableOpInterface, EntityWithId, Forward, + Operation, OperationRef, ProgramPoint, RegionBranchOpInterface, RegionBranchPoint, + RegionBranchTerminatorOpInterface, RegionSuccessorIter, Report, SmallVec, SourceSpan, Spanned, + Symbol, SymbolManager, SymbolMap, SymbolTable, ValueRef, +}; + +use super::constant_propagation::ConstantValue; +use crate::{ + AnalysisQueue, AnalysisState, AnalysisStateGuardMut, AnalysisStateInfo, + AnalysisStateSubscription, AnalysisStateSubscriptionBehavior, AnalysisStrategy, + BuildableAnalysisState, BuildableDataFlowAnalysis, ChangeResult, DataFlowAnalysis, + DataFlowSolver, Dense, Lattice, LatticeAnchor, LatticeAnchorRef, +}; + +/// This is a simple analysis state that represents whether the associated lattice anchor +/// (either a block or a control-flow edge) is live. +#[derive(Debug)] +pub struct Executable { + anchor: LatticeAnchorRef, + is_live: bool, +} +impl core::fmt::Display for Executable { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if self.is_live { + f.write_str("live") + } else { + f.write_str("dead") + } + } +} +impl Executable { + #[inline(always)] + pub const fn is_live(&self) -> bool { + self.is_live + } + + #[inline(always)] + pub fn mark_live(&mut self) -> ChangeResult { + if core::mem::replace(&mut self.is_live, true) { + ChangeResult::Unchanged + } else { + ChangeResult::Changed + } + } + + #[allow(unused)] + #[inline(always)] + pub fn mark_dead(&mut self) -> ChangeResult { + if core::mem::replace(&mut self.is_live, false) { + ChangeResult::Changed + } else { + ChangeResult::Unchanged + } + } +} +impl BuildableAnalysisState for Executable { + fn create(anchor: LatticeAnchorRef) -> Self { + Self { + anchor, + // Optimistically assume the anchor is dead + is_live: false, + } + } +} +impl AnalysisState for Executable { + fn as_any(&self) -> &dyn Any { + self + } + + fn anchor(&self) -> &dyn LatticeAnchor { + &self.anchor + } +} +impl AnalysisStateSubscriptionBehavior for Executable { + fn on_require_analysis( + &self, + info: &mut AnalysisStateInfo, + current_analysis: core::ptr::NonNull, + dependent: ProgramPoint, + ) { + // Ensure we re-run at the dependent point + info.subscribe(AnalysisStateSubscription::AtPoint { + analysis: current_analysis, + point: dependent, + }); + } + + fn on_subscribe(&self, subscriber: NonNull, info: &AnalysisStateInfo) { + info.subscribe(AnalysisStateSubscription::OnUpdate { + analysis: subscriber, + }); + } + + fn on_update(&self, info: &mut AnalysisStateInfo, worklist: &mut AnalysisQueue) { + use crate::solver::QueuedAnalysis; + + // If there are no on-update subscribers, we have nothing to do + let no_update_subscriptions = info.on_update_subscribers_count() == 0; + if no_update_subscriptions { + return; + } + + // When the executable state changes, re-enqueue any of the on-update subscribers + let anchor = info.anchor(); + if let Some(point) = anchor.as_program_point() { + if point.is_at_block_start() { + // Re-invoke analyses on the block itself + for analysis in info.on_update_subscribers() { + worklist.push_back(QueuedAnalysis { point, analysis }); + } + // Re-invoke analyses on all operations in the block + let block = point.block().unwrap(); + for op in block.borrow().body() { + let point = ProgramPoint::after(&*op); + for analysis in info.on_update_subscribers() { + worklist.push_back(QueuedAnalysis { point, analysis }); + } + } + } + } else if let Some(edge) = (anchor as &dyn Any).downcast_ref::() { + // Re-invoke the analysis on the successor block + let point = ProgramPoint::before(edge.to()); + for analysis in info.on_update_subscribers() { + worklist.push_back(QueuedAnalysis { point, analysis }); + } + } + } +} + +/// This analysis state represents a set of live control-flow "predecessors" of a program point +/// (either an operation or a block), which are the last operations along all execution paths that +/// pass through this point. +/// +/// For example, in dead-code analysis, an operation with region control-flow can be the predecessor +/// of a region's entry block or itself, the exiting terminator of a region can be the predecessor +/// of the parent operation or another region's entry block, the callsite of a callable operation +/// can be the predecessor to its entry block, and the exiting terminator of a callable operation +/// can be the predecessor of the call operation. +/// +/// The state can optionally contain information about which values are propagated from each +/// predecessor to the successor point. +/// +/// The state can indicate that it is underdefined, meaning that not all live control-flow +/// predecessors can be known. +pub struct PredecessorState { + anchor: LatticeAnchorRef, + /// The known control-flow predecessors of this program point. + known_predecessors: SmallSet, + /// The successor inputs when branching from a given predecessor. + successor_inputs: SmallDenseMap>, + /// Whether all predecessors are known. Optimistically assume that we know all predecessors. + all_known: bool, +} + +impl fmt::Debug for PredecessorState { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("PredecessorState") + .field_with("anchor", |f| fmt::Display::fmt(&self.anchor, f)) + .field_with("known_predecessors", |f| { + let mut builder = f.debug_list(); + for pred in self.known_predecessors.iter() { + let pred = pred.borrow(); + builder + .entry_with(|f| write!(f, "{} in {}", pred.name(), pred.parent().unwrap())); + } + builder.finish() + }) + .field_with("successor_inputs", |f| { + let mut builder = f.debug_list(); + for (op, inputs) in self.successor_inputs.iter() { + let op = op.borrow(); + builder.entry_with(|f| { + f.debug_map() + .key_with(|f| write!(f, "{} in {}", op.name(), op.parent().unwrap())) + .value(inputs) + .finish() + }); + } + builder.finish() + }) + .field("all_known", &self.all_known) + .finish() + } +} + +impl PredecessorState { + #[inline(always)] + pub const fn all_predecessors_known(&self) -> bool { + self.all_known + } + + #[inline(always)] + pub fn known_predecessors(&self) -> &[OperationRef] { + self.known_predecessors.as_slice() + } + + /// Indicate that there are potentially unknown predecessors. + pub fn set_has_unknown_predecessors(&mut self) -> ChangeResult { + if core::mem::replace(&mut self.all_known, false) { + ChangeResult::Changed + } else { + ChangeResult::Unchanged + } + } + + #[allow(unused)] + #[inline] + pub fn successor_inputs(&self, predecessor: &OperationRef) -> &[ValueRef] { + &self.successor_inputs[predecessor] + } + + pub fn join(&mut self, predecessor: OperationRef) -> ChangeResult { + if self.known_predecessors.insert(predecessor) { + self.successor_inputs.insert(predecessor, Default::default()); + ChangeResult::Changed + } else { + ChangeResult::Unchanged + } + } + + pub fn join_with_inputs( + &mut self, + predecessor: OperationRef, + inputs: impl IntoIterator, + ) -> ChangeResult { + let mut result = self.join(predecessor); + let prev_inputs = self.successor_inputs.get_mut(&predecessor).unwrap(); + let inputs = inputs.into_iter().collect::>(); + if prev_inputs != &inputs { + *prev_inputs = inputs; + result |= ChangeResult::Changed; + } + result + } +} + +impl BuildableAnalysisState for PredecessorState { + fn create(anchor: LatticeAnchorRef) -> Self { + Self { + anchor, + known_predecessors: Default::default(), + successor_inputs: Default::default(), + all_known: true, + } + } +} + +impl AnalysisState for PredecessorState { + fn as_any(&self) -> &dyn Any { + self + } + + fn anchor(&self) -> &dyn LatticeAnchor { + &self.anchor + } +} + +#[derive(Copy, Clone, Debug, Spanned)] +pub struct CfgEdge { + #[span] + span: SourceSpan, + from: BlockRef, + to: BlockRef, +} +impl CfgEdge { + pub fn new(from: BlockRef, to: BlockRef, span: SourceSpan) -> Self { + Self { span, from, to } + } + + #[allow(unused)] + #[inline(always)] + pub const fn from(&self) -> BlockRef { + self.from + } + + #[inline(always)] + pub const fn to(&self) -> BlockRef { + self.to + } +} +impl Eq for CfgEdge {} +impl PartialEq for CfgEdge { + fn eq(&self, other: &Self) -> bool { + self.from == other.from && self.to == other.to + } +} +impl core::hash::Hash for CfgEdge { + fn hash(&self, state: &mut H) { + self.from.hash(state); + self.to.hash(state); + } +} +impl fmt::Display for CfgEdge { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use midenc_hir::EntityWithId; + let from = self.from.borrow().id(); + let to = self.to.borrow().id(); + write!(f, "{from} -> {to}") + } +} +impl LatticeAnchor for CfgEdge {} + +/// Dead code analysis analyzes control-flow, as understood by [RegionBranchOpInterface] and +/// [BranchOpInterface], and the callgraph, as understood by [CallableOpInterface] and +/// [CallOpInterface]. +/// +/// This analysis uses known constant values of operands to determine the liveness of each block and +/// each edge between a block and its predecessors. For region control-flow, this analysis +/// determines the predecessor operations for region entry blocks and region control-flow +/// operations. For the callgraph, this analysis determines the callsites and live returns of every +/// function. +pub struct DeadCodeAnalysis { + /// The top-level operation the analysis is running on. This is used to detect + /// if a callable is outside the scope of the analysis and thus must be + /// considered an external callable. + analysis_scope: Cell>, + /// A symbol table used for O(1) symbol lookups during simplification. + #[allow(unused)] + symbol_table: RefCell, +} + +impl BuildableDataFlowAnalysis for DeadCodeAnalysis { + type Strategy = Self; + + #[inline(always)] + fn new(_solver: &mut crate::DataFlowSolver) -> Self { + Self { + analysis_scope: Cell::new(None), + symbol_table: Default::default(), + } + } +} + +impl AnalysisStrategy for DeadCodeAnalysis { + type Direction = Forward; + type Kind = Dense; + + #[inline(always)] + fn build(analysis: Self, _solver: &mut crate::DataFlowSolver) -> Self { + analysis + } +} + +impl DataFlowAnalysis for DeadCodeAnalysis { + fn analysis_id(&self) -> core::any::TypeId { + core::any::TypeId::of::() + } + + fn debug_name(&self) -> &'static str { + "dead-code" + } + + fn initialize( + &self, + top: &Operation, + solver: &mut crate::DataFlowSolver, + _analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + // Mark the top-level blocks as executable. + log::trace!(target: self.debug_name(), "marking all non-empty region entry blocks as executable"); + for region in top.regions() { + if region.is_empty() { + continue; + } + + let entry = ProgramPoint::at_start_of(region.entry_block_ref().unwrap()); + let mut state = solver.get_or_create_mut::(entry); + let change_result = state.change(|exec| exec.mark_live()); + log::debug!( + target: self.debug_name(), + "marking region {} at {entry} as executable: {change_result}", + region.region_number() + ); + } + + // Mark as overdefined the predecessors of callable symbols with potentially unknown + // predecessors. + self.initialize_callable_symbols(top, solver); + + self.initialize_recursively(top, solver)?; + + Ok(()) + } + + fn visit( + &self, + point: &ProgramPoint, + solver: &mut crate::DataFlowSolver, + ) -> Result<(), Report> { + if point.is_at_block_start() { + log::debug!(target: self.debug_name(), "not visiting {point} as it is at block start"); + return Ok(()); + } + + let operation = point.prev_operation().unwrap(); + let op = operation.borrow(); + + log::debug!(target: self.debug_name(), "analyzing op preceding program point {point}: {op}"); + + // If the parent block is not executable, there is nothing to do. + if operation.parent().is_none_or(|block| { + !solver + .get_or_create_mut::(ProgramPoint::at_start_of(block)) + .is_live() + }) { + log::debug!(target: self.debug_name(), "skipping analysis at {point} as parent block is dead/non-executable"); + return Ok(()); + } + + if let Some(call) = op.as_trait::() { + // We have a live call op. Add this as a live predecessor of the callee. + self.visit_call_operation(call, solver); + } + + // Visit the regions. + if op.has_regions() { + // Check if we can reason about the region control-flow. + if let Some(branch) = op.as_trait::() { + self.visit_region_branch_operation(branch, solver); + } else if op.implements::() { + log::debug!( + target: self.debug_name(), + "{} is a callable operation: resolving call site predecessors..", + op.name() + ); + let callsites = solver.require::( + ProgramPoint::after(&*op), + ProgramPoint::after(&*op), + ); + log::trace!(target: self.debug_name(), "found {} call sites", callsites.known_predecessors().len()); + + // If the callsites could not be resolved or are known to be non-empty, mark the + // callable as executable. + if !callsites.all_predecessors_known() || !callsites.known_predecessors().is_empty() + { + log::trace!( + target: self.debug_name(), + "not all call site predecessors are known - marking callable entry blocks \ + as live" + ); + self.mark_entry_blocks_live(&op, solver); + } + } else { + // Otherwise, conservatively mark all entry blocks as executable. + log::debug!( + target: self.debug_name(), + "op has regions, but is not a call or region control flow op: conservatively \ + marking entry blocks live" + ); + self.mark_entry_blocks_live(&op, solver); + } + } + + if is_region_or_callable_return(&op) { + log::debug!(target: self.debug_name(), "op is a return-like operation from a region or callable"); + let parent_op = op.parent_op().unwrap(); + let parent_op = parent_op.borrow(); + if let Some(branch) = parent_op.as_trait::() { + // Visit the exiting terminator of a region. + self.visit_region_terminator(&op, branch, solver); + } else if let Some(callable) = parent_op.as_trait::() { + // Visit the exiting terminator of a callable. + self.visit_callable_terminator(&op, callable, solver); + } + } + + // Visit the successors. + if op.has_successors() { + log::debug!(target: self.debug_name(), "visiting successors of {}", op.name()); + // Check if we can reason about the control-flow. + // + // Otherwise, conservatively mark all successors as exectuable. + if let Some(branch) = op.as_trait::() { + log::trace!( + target: self.debug_name(), "we can reason about op's successors as it implements BranchOpInterface" + ); + self.visit_branch_operation(branch, solver); + } else { + log::trace!( + target: self.debug_name(), "we can't reason about op's successors, so conservatively marking them live" + ); + for successor in op.successors().all() { + let succ = successor.successor(); + let successor_block = succ.borrow(); + let op_block = operation.parent().unwrap(); + self.mark_edge_live(&op_block.borrow(), &successor_block, solver); + } + } + } + + log::debug!(target: self.debug_name(), "finished analysis for {}", op.name()); + + Ok(()) + } +} + +type MaybeConstOperands = SmallVec<[Option>; 2]>; + +impl DeadCodeAnalysis { + /// Find and mark callable symbols with potentially unknown callsites as having overdefined + /// predecessors. `top` is the top-level operation that the analysis is operating on. + fn initialize_callable_symbols(&self, top: &Operation, solver: &mut DataFlowSolver) { + log::trace!(target: self.debug_name(), "initializing callable symbols in '{}'", top.name()); + + self.analysis_scope.set(Some(top.as_operation_ref())); + + let walk_fn = |sym_table: &dyn SymbolTable, all_uses_visible: bool| { + let symbol_table_op = sym_table.as_symbol_table_operation(); + log::trace!(target: self.debug_name(), "analyzing symbol table '{}'", symbol_table_op.name()); + let symbol_table_region = symbol_table_op.region(0); + let symbol_table_block = symbol_table_region.entry(); + + let mut found_callable_symbol = false; + for candidate in symbol_table_block.body().iter() { + let Some(callable) = candidate.as_trait::() else { + continue; + }; + + // We're only interested in callables with definitions, not declarations + if callable.get_callable_region().is_none() { + continue; + } + + // We're also only interested in callable symbols + let Some(symbol) = callable.as_operation().as_trait::() else { + continue; + }; + + // If a callable symbol has public visibility, or we are unable see all uses (for + // example the address of a function is taken, but not called), then we have + // potentially unknown callsites. + let visibility = symbol.visibility(); + log::trace!( + target: self.debug_name(), "found callable symbol '{}' with visibility {visibility}", + symbol.name() + ); + if visibility.is_public() || (!all_uses_visible && visibility.is_internal()) { + log::trace!(target: self.debug_name(), "marking symbol as having unknown predecessors"); + let mut state = solver.get_or_create_mut::( + ProgramPoint::after(callable.as_operation()), + ); + state.set_has_unknown_predecessors(); + } + found_callable_symbol = true; + } + + // Exit early if no eligible callable symbols were found in the table. + if !found_callable_symbol { + log::trace!(target: self.debug_name(), "no callable symbols found in this symbol table"); + return; + } + + // Walk the symbol table to check for non-call uses of symbols. + log::trace!(target: self.debug_name(), "looking for non-call uses of symbols in the symbol table region"); + let uses = Operation::all_symbol_uses_in_region(&symbol_table_region); + let top_symbol_table = SymbolManager::from(top); + for symbol_use in uses { + let symbol_use = symbol_use.borrow(); + if symbol_use.owner.borrow().implements::() { + continue; + } + + // If a callable symbol has a non-call use, then we can't be guaranteed to know all + // callsites. + let symbol_attr = symbol_use.symbol(); + log::trace!( + target: self.debug_name(), "found symbol use whose user does not implement CallOpInterface - marking \ + symbol as having unknown predecessors" + ); + if let Some(symbol) = top_symbol_table.lookup_symbol_ref(&symbol_attr.path) { + let mut state = solver + .get_or_create_mut::(ProgramPoint::after(symbol)); + state.set_has_unknown_predecessors(); + } + } + }; + + top.walk_symbol_tables(/*all_symbol_uses_visible=*/ top.parent().is_none(), walk_fn); + } + + /// Recursively Initialize the analysis on nested regions. + fn initialize_recursively( + &self, + op: &Operation, + solver: &mut DataFlowSolver, + ) -> Result<(), Report> { + // Initialize the analysis by visiting every op with control-flow semantics. + if op.has_regions() + || op.has_successors() + || is_region_or_callable_return(op) + || op.implements::() + { + // When the liveness of the parent block changes, make sure to re-invoke the analysis on + // the op. + if let Some(block) = op.parent() { + let exec = + solver.get_or_create_mut::(ProgramPoint::at_start_of(block)); + log::trace!( + target: self.debug_name(), "subscribing {} to changes in liveness of {block} (currently={})", + self.debug_name(), + exec.is_live() + ); + AnalysisStateGuardMut::subscribe(&exec, self); + } + + // Visit the op. + let point = ProgramPoint::after(op); + self.visit(&point, solver)?; + } + + // Recurse on nested operations. + let regions = op.regions(); + if !regions.is_empty() { + log::trace!(target: self.debug_name(), "visiting regions of '{}'", op.name()); + for region in regions { + if region.is_empty() { + continue; + } + for block in region.body() { + log::trace!(target: self.debug_name(), "visiting body of {} top-down", block.id()); + for op in block.body() { + self.initialize_recursively(&op, solver)?; + } + } + } + } + + Ok(()) + } + + /// Mark the edge between `from` and `to` as executable. + fn mark_edge_live(&self, from: &Block, to: &Block, solver: &mut DataFlowSolver) { + let mut state = solver.get_or_create_mut::(ProgramPoint::at_start_of(to)); + let change_result = state.change(|exec| exec.mark_live()); + log::debug!(target: self.debug_name(), "marking control flow edge successor {} live: {change_result}", to.id()); + + // Ensure change notifications for the block are flushed first + drop(state); + + let mut edge_state = solver.get_or_create_mut::(CfgEdge::new( + from.as_block_ref(), + to.as_block_ref(), + from.span(), + )); + let change_result = edge_state.change(|exec| exec.mark_live()); + log::debug!( + target: self.debug_name(), "marking control flow edge live: {} -> {}: {change_result}", + from.id(), + to.id() + ); + } + + /// Mark the entry blocks of the operation as executable. + fn mark_entry_blocks_live(&self, op: &Operation, solver: &mut DataFlowSolver) { + for region in op.regions() { + if let Some(entry) = region.entry_block_ref() { + let mut state = + solver.get_or_create_mut::(ProgramPoint::at_start_of(entry)); + let change_result = state.change(|exec| exec.mark_live()); + log::trace!(target: self.debug_name(), "marking entry block {entry} live: {change_result}"); + } + } + } + + /// Visit the given call operation and compute any necessary lattice state. + fn visit_call_operation(&self, call: &dyn CallOpInterface, solver: &mut DataFlowSolver) { + log::debug!(target: self.debug_name(), "visiting call operation: {}", call.as_operation().name()); + + // TODO: Update this when symbol table changes are complete, e.g. call.resolve_in_symbol_table(&self.symbol_table_collection) + let callable = call.resolve(); + + // A call to a externally-defined callable has unknown predecessors. + let is_external_callable = |op: &Operation| -> bool { + // A callable outside the analysis scope is an external callable. + if !self.with_analysis_scope(|scope| scope.is_ancestor_of(op)) { + return true; + } + // Otherwise, check if the callable region is defined. + if let Some(callable) = op.as_trait::() { + callable.get_callable_region().is_none() + } else { + false + } + }; + + // If the callable is unresolvable, mark the call ops predecessors as overdefined/unknown + if callable.is_none() { + let mut predecessors = solver + .get_or_create_mut::(ProgramPoint::after(call.as_operation())); + let change_result = predecessors.set_has_unknown_predecessors(); + log::debug!( + target: self.debug_name(), "marking call-site return at {} as having unknown predecessors: {change_result}", + call.as_operation() + ); + return; + } + + // TODO: Add support for non-symbol callables when necessary. + // + // If the callable has non-call uses we would mark as having reached pessimistic fixpoint, + // otherwise allow for propagating the return values out. + let callable = callable.unwrap(); + let callable = callable.borrow(); + // It the callable can have external callers we don't know about, we have to be conservative + // about the set of possible predecessors. + if !is_external_callable(callable.as_symbol_operation()) { + // Add the live callsite + let mut callsites = solver.get_or_create_mut::( + ProgramPoint::after(callable.as_symbol_operation()), + ); + let change_result = callsites.change(|ps| ps.join(call.as_operation_ref())); + log::debug!( + target: self.debug_name(), "adding call-site {} to predecessor state for its callee: {change_result}", + call.as_operation() + ); + } else { + // Mark this call op's predecessors as overdefined + let mut predecessors = solver + .get_or_create_mut::(ProgramPoint::after(call.as_operation())); + let change_result = predecessors.change(|ps| ps.set_has_unknown_predecessors()); + log::debug!( + target: self.debug_name(), "marking call-site return for external callable at {} as having unknown \ + predecessors: {change_result}", + call.as_operation() + ); + } + } + + /// Visit the given branch operation with successors and try to determine + /// which are live from the current block. + fn visit_branch_operation(&self, branch: &dyn BranchOpInterface, solver: &mut DataFlowSolver) { + // Try to deduce a single successor for the branch. + let Some(operands) = self.get_operand_values(branch.as_operation(), solver) else { + log::trace!(target: self.debug_name(), "unable to prove liveness of successor blocks"); + return; + }; + + if let Some(successor) = branch.get_successor_for_operands(&operands) { + let (from, to) = { + let succ = successor.block.borrow(); + (succ.predecessor(), succ.successor()) + }; + self.mark_edge_live(&from.borrow(), &to.borrow(), solver); + } else { + // Otherwise, mark all successors as executable and outgoing edges. + for successor in branch.successors().all() { + let block_operand = successor.block.borrow(); + let from = block_operand.predecessor(); + let to = block_operand.successor(); + self.mark_edge_live(&from.borrow(), &to.borrow(), solver); + } + } + } + + /// Visit the given region branch operation, which defines regions, and + /// compute any necessary lattice state. This also resolves the lattice state + /// of both the operation results and any nested regions. + fn visit_region_branch_operation( + &self, + branch: &dyn RegionBranchOpInterface, + solver: &mut DataFlowSolver, + ) { + log::trace!(target: self.debug_name(), "visiting region branch operation: {}", branch.as_operation().name()); + + // Try to deduce which regions are executable. + let Some(operands) = self.get_operand_values(branch.as_operation(), solver) else { + log::debug!(target: self.debug_name(), "unable to prove liveness of entry successor regions"); + return; + }; + + log::trace!(target: self.debug_name(), "processing entry successor regions"); + for successor in branch.get_entry_successor_regions(&operands) { + // The successor can be either an entry block or the parent operation. + let point = if let Some(succ) = successor.successor() { + ProgramPoint::at_start_of(succ.borrow().entry_block_ref().unwrap()) + } else { + ProgramPoint::after(branch.as_operation()) + }; + // Mark the entry block as executable. + let mut state = solver.get_or_create_mut::(point); + let change_result = state.change(|exec| exec.mark_live()); + log::debug!(target: self.debug_name(), "marking region successor {point} live: {change_result}"); + // Add the parent op as a predecessor + let mut predecessors = solver.get_or_create_mut::(point); + let change_result = predecessors.change(|ps| { + ps.join_with_inputs(branch.as_operation_ref(), successor.successor_inputs().iter()) + }); + log::debug!( + target: self.debug_name(), "adding {} as predecessor for {point}: {change_result}", + branch.as_operation().name() + ); + } + } + + /// Visit the given terminator operation that exits a region under an + /// operation with control-flow semantics. These are terminators with no CFG + /// successors. + fn visit_region_terminator( + &self, + op: &Operation, + branch: &dyn RegionBranchOpInterface, + solver: &mut DataFlowSolver, + ) { + log::debug!(target: self.debug_name(), "visiting region terminator: {op}"); + let Some(operands) = self.get_operand_values(op, solver) else { + log::debug!(target: self.debug_name(), "unable to prove liveness of region terminator successors"); + return; + }; + + let successors = + if let Some(terminator) = op.as_trait::() { + let successors = terminator.get_successor_regions(&operands); + RegionSuccessorIter::new(op, successors) + } else { + branch.get_successor_regions(RegionBranchPoint::Child(op.parent_region().unwrap())) + }; + + // Mark successor region entry blocks as executable and add this op to the list of + // predecessors. + for successor in successors { + let (mut predecessors, point) = if let Some(region) = successor.successor() { + let entry = region.borrow().entry_block_ref().unwrap(); + let point = ProgramPoint::at_start_of(entry); + let mut state = solver.get_or_create_mut::(point); + let change_result = state.change(|exec| exec.mark_live()); + log::debug!( + target: self.debug_name(), "marking region successor {} entry {point} as live: {change_result}", + successor.branch_point() + ); + (solver.get_or_create_mut::(point), point) + } else { + // Add this terminator as a predecessor to the parent op. + let point = ProgramPoint::after(branch.as_operation()); + (solver.get_or_create_mut::(point), point) + }; + let change_result = predecessors.change(|ps| { + ps.join_with_inputs(op.as_operation_ref(), successor.successor_inputs().iter()) + }); + log::debug!(target: self.debug_name(), "adding {} as predecessor for {point}: {change_result}", op.name()); + } + } + + /// Visit the given terminator operation that exits a callable region. These + /// are terminators with no CFG successors. + fn visit_callable_terminator( + &self, + op: &Operation, + callable: &dyn CallableOpInterface, + solver: &mut DataFlowSolver, + ) { + log::debug!(target: self.debug_name(), "visiting callable op terminator: {op}"); + // Add as predecessors to all callsites this return op. + let callsites = solver.require::( + ProgramPoint::after(callable.as_operation()), + ProgramPoint::after(op), + ); + let can_resolve = op.implements::(); + for predecessor in callsites.known_predecessors().iter() { + let predecessor = predecessor.borrow(); + assert!(predecessor.implements::()); + let point = ProgramPoint::after(&*predecessor); + let mut predecessors = solver.get_or_create_mut::(point); + if can_resolve { + let change_result = predecessors.change(|ps| ps.join(op.as_operation_ref())); + log::debug!(target: self.debug_name(), "adding {} as predecessor for {point}: {change_result}", op.name()) + } else { + // If the terminator is not a return-like, then conservatively assume we can't + // resolve the predecessor. + let change_result = predecessors.change(|ps| ps.set_has_unknown_predecessors()); + log::debug!(target: self.debug_name(), "marking {point} as having unknown predecessors: {change_result}") + } + } + } + + /// Get the constant values of the operands of the operation. + /// + /// Returns `None` if any of the operand lattices are uninitialized. + fn get_operand_values( + &self, + op: &Operation, + solver: &mut DataFlowSolver, + ) -> Option { + get_operand_values(op, |value: &ValueRef| { + let lattice = solver.get_or_create_mut::, _>(*value); + log::trace!( + target: self.debug_name(), "subscribing to constant propagation changes of operand {value} (current={})", + lattice.value() + ); + AnalysisStateGuardMut::subscribe(&lattice, self); + lattice + }) + } + + /// Invoke a closure with the current analysis scope operation, or panic if no scope was set. + #[inline] + fn with_analysis_scope(&self, mut callback: F) -> T + where + F: FnMut(&Operation) -> T, + { + let scope = self.analysis_scope.get().expect("expected analysis scope to be set"); + callback(&scope.borrow()) + } +} + +/// Returns true if `op` is a returning terminator in a inter-region control flow op, or of a +/// callable region (i.e. return from a function). +fn is_region_or_callable_return(op: &Operation) -> bool { + let block = op.parent(); + !op.has_successors() + && block.is_some_and(|block| { + let is_region_or_callable_op = block.grandparent().is_some_and(|parent_op| { + let parent_op = parent_op.borrow(); + parent_op.implements::() + || parent_op.implements::() + }); + is_region_or_callable_op && block.borrow().terminator() == Some(op.as_operation_ref()) + }) +} + +/// Get the constant values of the operands of an operation. +/// +/// If any of the constant value lattices are uninitialized, return None to indicate the analysis +/// should bail out. +fn get_operand_values(op: &Operation, mut get_lattice: F) -> Option +where + F: FnMut(&ValueRef) -> AnalysisStateGuardMut<'_, Lattice>, +{ + let mut operands = + SmallVec::<[Option>; 2]>::with_capacity(op.num_operands()); + for operand in op.operands().all() { + let operand = operand.borrow(); + let value = operand.as_value_ref(); + let lattice = get_lattice(&value); + // If any of the operand's values are uninitialized, bail out. + if lattice.value().is_uninitialized() { + return None; + } + operands.push(lattice.value().constant_value()); + } + Some(operands) +} diff --git a/hir-analysis/src/analyses/liveness.rs b/hir-analysis/src/analyses/liveness.rs new file mode 100644 index 000000000..29c46c6ff --- /dev/null +++ b/hir-analysis/src/analyses/liveness.rs @@ -0,0 +1,745 @@ +mod next_use_set; + +use core::borrow::Borrow; + +use midenc_hir::{ + dominance::DominanceInfo, + pass::{Analysis, AnalysisManager, PreservedAnalyses}, + Backward, Block, BlockRef, CallOpInterface, EntityRef, Operation, ProgramPoint, + RegionBranchOpInterface, RegionBranchPoint, RegionRef, Report, Spanned, SymbolTable, ValueRef, +}; + +pub use self::next_use_set::NextUseSet; +use super::{dce::Executable, DeadCodeAnalysis, SparseConstantPropagation}; +use crate::{ + analyses::{dce::CfgEdge, LoopState}, + dense::DenseDataFlowAnalysis, + AnalysisState, AnalysisStateGuardMut, BuildableDataFlowAnalysis, CallControlFlowAction, + DataFlowSolver, DenseBackwardDataFlowAnalysis, DenseLattice, Lattice, LatticeLike, +}; + +/// The distance penalty applied to an edge which exits a loop +pub const LOOP_EXIT_DISTANCE: u32 = 100_000; + +/// This analysis computes what values are live, and the distance to next use, for all program +/// points in the given operation. It computes both live-in and live-out sets, in order to answer +/// liveness questions about the state of the program at an operation, as well as questions about +/// the state of the program immediately after an operation. +/// +/// This analysis is a bit different than "standard" liveness analysis, in a few ways: +/// +/// * It is not sparse, i.e. it attaches liveness information to program points, rather than values +/// * Liveness is not just a boolean state, it also represents how long the value must live until +/// its next use. This is invaluable for instruction scheduling and resource allocation (registers, +/// operand stack space). +/// +/// The design for this is based on the description in _Register Spilling and Live-Range Splitting +/// for SSA-form Programs_, by Mattias Braun and Sebastian Hack. The paper also is used in our +/// algorithm for computing spills (and splitting live-ranges), as you might expect from its title. +/// +/// ## The Basic Algorithm +/// +/// 1. Start at the end of a block, with an empty `live_out` set +/// 2. If the block has any successors, take the `meet` of the `live_in` sets across all successors, +/// after incrementing the next-use distances in each set by 1 (for normal blocks) or by 10,000 +/// (if the edge from the current block to the successor is exiting a loop). Additionally, all +/// block parameters of each successor are removed from its `live_in` set (since by definition +/// those values cannot be live before they are defined). The `meet` of these sets then becomes +/// the initial `live_out` set for the current block. +/// 3. Start visiting ops in the block bottom-up. The `live_out` set for the block terminator +/// inherits the `live_out` set computed in steps 1 and 2. +/// 4. At the block terminator, the `live_out` set is inherited from the block `live_out` set. The +/// `live_in` set is then initialized empty, and then the following steps are performed: +/// a. Any operands of the terminator are added/updated in the set with a next-use distance of 0 +/// 5. Move to the previous operation in the block from the current operation, then: +/// * The `live_out` set is inherited from the successor op's `live_in` set, all results of the +/// op that are missing from the `live_out` set, are added with a distance of `u32::MAX`. +/// * The `live_in` set is populated by taking the `live_out` set, removing all of the op +/// results, incrementing the next-use distance of all values in the set by 1, and then +/// setting/adding all of the op operands with a distance of `0`. +/// 6. Repeat until we reach the top of the block. The `live_in` set for the block inherits the +/// `live_in` set of the first op in the block, but adds in any of the block parameters which +/// are missing from the set with a distance of `u32::MAX` +/// +/// In essence, we work backwards from the bottom of a block to the top, propagating next-use info +/// up the CFG (and ideally visiting each block in reverse post-order, to reach fixpoint +/// efficiently). +/// +/// To aid the solver in efficiently reaching fixpoint, the following are done: +/// +/// 1. We drive the dense analysis using the [DominanceInfo] analysis computed for the given op, +/// this reduces the amount of extra work that needs to be done by the solver, as most blocks +/// will not change their liveness info after it is initially computed. +/// 2. Unless a block is marked live by dead code analysis, we do not compute liveness for it, and +/// we will ignore any block successors which are not marked live by dead code analysis as well. +/// 3. If the analysis is run on an operation, and it is determined that the liveness information +/// used to derive its `live_in` set has not changed since we last saw it, we do not proceed +/// with the analysis to avoid propagating changes unnecessarily +#[derive(Default)] +pub struct Liveness; + +/// This type is used to compute the [LivenessAnalysis] results for an entire [Function]. +/// +/// Internally, it instantiates a [DataFlowSolver] with [DeadCodeAnalysis], +/// [SparseConstantPropagation], and [LivenessAnalysis], and runs them to fixpint. It additionally +/// relies on the [DominanceInfo] and [LoopForest] analyses to provide us with details about the +/// CFG structure that we then use both to optimize the work done by the solver, as well as feed +/// into the actual liveness information itself (i.e. by specifying how much distance a given +/// control flow edge adds). +#[derive(Default)] +pub struct LivenessAnalysis { + solver: DataFlowSolver, +} + +impl LivenessAnalysis { + #[inline] + pub fn solver(&self) -> &DataFlowSolver { + &self.solver + } + + /// Returns true if `value` is live on entry to `block` + #[inline] + pub fn is_live_at_start(&self, value: V, block: BlockRef) -> bool + where + V: Borrow, + { + let next_uses = self.next_uses_at(&ProgramPoint::at_start_of(block)); + next_uses.is_some_and(|nu| nu.is_live(value)) + } + + /// Returns true if `value` is live at the block terminator of `block` + #[inline] + pub fn is_live_at_end(&self, value: V, block: BlockRef) -> bool + where + V: Borrow, + { + let next_uses = self.next_uses_at(&ProgramPoint::at_end_of(block)); + next_uses.is_some_and(|nu| nu.is_live(value)) + } + + /// Returns true if `value` is live at the entry of `op` + #[inline] + pub fn is_live_before(&self, value: V, op: &Operation) -> bool + where + V: Borrow, + { + let next_uses = self.next_uses_at(&ProgramPoint::before(op)); + next_uses.is_some_and(|nu| nu.is_live(value)) + } + + /// Returns true if `value` is live on exit from `op` + #[inline] + pub fn is_live_after(&self, value: V, op: &Operation) -> bool + where + V: Borrow, + { + let next_uses = self.next_uses_at(&ProgramPoint::after(op)); + next_uses.is_some_and(|nu| nu.is_live(value)) + } + + /// Returns true if `value` is live after entering `op`, i.e. when executing any of its child + /// regions. + /// + /// This will return true if `value` is live after exiting from any of `op`'s regions, as well + /// as in the case where none of `op`'s regions are executed and control is transferred to the + /// next op in the containing block. + #[inline] + pub fn is_live_after_entry(&self, value: V, op: &Operation) -> bool + where + V: Borrow, + { + let value = value.borrow(); + if self.is_live_after(value, op) { + return true; + } + + if let Some(br_op) = op.as_trait::() { + for succ in br_op.get_successor_regions(RegionBranchPoint::Parent) { + if let Some(region) = succ.into_successor() { + let entry = region.borrow().entry_block_ref().unwrap(); + if !self.is_block_executable(entry) { + // Ignore dead regions + continue; + } + + if self.is_live_at_start(value, entry) { + return true; + } + } + } + } + + false + } + + #[inline] + pub fn next_use_after(&self, value: V, op: &Operation) -> u32 + where + V: Borrow, + { + let next_uses = self.next_uses_at(&ProgramPoint::after(op)); + next_uses.map(|nu| nu.distance(value)).unwrap_or(u32::MAX) + } + + #[inline] + pub fn is_block_executable(&self, block: BlockRef) -> bool { + self.solver + .get::(&ProgramPoint::at_start_of(block)) + .is_none_or(|state| state.is_live()) + } + + #[inline] + pub fn next_uses_at(&self, anchor: &ProgramPoint) -> Option> { + self.solver + .get::, _>(anchor) + .map(|next_uses| EntityRef::map(next_uses, |nu| nu.value())) + } +} + +impl Analysis for LivenessAnalysis { + type Target = Operation; + + fn name(&self) -> &'static str { + "liveness" + } + + fn analyze( + &mut self, + op: &Self::Target, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + self.solver.load::(); + self.solver.load::(); + self.solver.load::(); + self.solver + .initialize_and_run(op, analysis_manager) + .expect("liveness analysis failed"); + + Ok(()) + } + + fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool { + !preserved_analyses.is_preserved::() + } +} + +impl BuildableDataFlowAnalysis for Liveness { + type Strategy = DenseDataFlowAnalysis; + + fn new(_solver: &mut DataFlowSolver) -> Self { + Self + } +} + +/// Liveness is computed as a dense, backward-propagating data-flow analysis: +/// +/// * Liveness information is attached to each operation, and the start and end of each block +/// * Liveness is computed by visiting the CFG of an operation in postorder, this ensures +/// that next-use information is propagated upwards in the CFG. +/// * Liveness is _not_ interprocedural +/// * Liveness _does_ take into account region control flow, i.e. a value which is used inside a +/// nested region of an operation will have a next-use distance that is either: +/// * Treated as the distance to the containing operation, if the containing op does not involve +/// region control flow (i.e. implements `RegionBranchOpInterface`) +/// * Computed as if the nested region was flattened into the current one, in cases where region +/// control flow is involved. This ensures that structured control flow ops have useful next- +/// use distances computed, e.g. values used after a `scf.while` are not considered "closer" +/// than values inside the loop. +impl DenseBackwardDataFlowAnalysis for Liveness { + type Lattice = Lattice; + + fn debug_name(&self) -> &'static str { + "liveness" + } + + fn symbol_table(&self) -> Option<&dyn SymbolTable> { + None + } + + /// This is invoked when visiting a block with successors, once for each successor. + /// + /// This is where we handle determining what information to propagate from the successor's + /// live-in set into the predecessor block's live-out set. + /// + /// * `from` is the block we're currently visiting + /// * `to` is the successor that this control-flow edge transfers control to + /// * `live_in_to` is the live-in state of the successor as computed by the analysis so far + /// * `live_out_from` is the live-out state of the current block that we've computed thus far, + /// and which we are extending with values used across the edge, and not defined by the + /// successor block's parameters. + /// + /// This is where we take into account loop state information, if available, to determine how + /// to increment next-use distances from the successor. + fn visit_branch_control_flow_transfer( + &self, + from: &Block, + to: BlockRef, + live_in_to: &Self::Lattice, + live_out_from: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + // Start with the live-in set + let mut live_out = live_in_to.value().clone(); + + // Remove successor params from the set + let succ = to.borrow(); + for param in succ.arguments() { + let param = param.borrow().as_value_ref(); + live_out.remove(param); + } + + // Increment the next-use distances by LOOP_EXIT_DISTANCE if this edge exits + // a loop + let edge = CfgEdge::new(from.as_block_ref(), to, from.span()); + let is_loop_exit = + solver.get::(&edge).is_some_and(|state| state.is_exiting_loop()); + if is_loop_exit { + for next_use in live_out.iter_mut() { + next_use.distance = next_use.distance.saturating_add(LOOP_EXIT_DISTANCE); + } + } + + // We use `join` here, not `meet`, because we want the union of the sets, albeit the minimum + // distances of values in both sets. The `meet` implementation for NextUseSet performs set + // intersection, which is not what we want here + live_out_from.join(&live_out); + } + + /// This will be called on each operation in a block, once the initial live-out state for the + /// block has been computed, either by default-initializing the state, or by invoking the + /// `visit_branch_control_flow_transfer` function above for each successor and meeting the + /// sets. + /// + /// * `live_out` - the live-out set of the operation, computed when the successor op in the same + /// block was visited, or by inheriting the live-out set of the block if it is the terminator + /// * `live_in` - the live-in set of the operation, must be recomputed here + /// + /// This function is responsible for computing the live-out set for the preceding operation in + /// the block OR the live-in set for the block itself if it is the first operation in the block. + fn visit_operation( + &self, + op: &Operation, + live_out: &Self::Lattice, + live_in: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) -> Result<(), Report> { + // If this op is orphaned, skip the analysis + let Some(parent_block) = op.parent() else { + return Ok(()); + }; + log::trace!( + target: self.debug_name(), + "deriving live-in for {op} from live-out at {}: {:#?}", + live_out.anchor(), + live_out.value() + ); + + // To compute the live-in set, we must start with a copy of the live-out set + let mut temp_live_in = live_out.value().clone(); + + // Increment all next-use distances by 1 + for next_use in temp_live_in.iter_mut() { + next_use.distance = next_use.distance.saturating_add(1); + } + + // Remove the op results from the set + for result in op.results().all().iter() { + let result = result.borrow().as_value_ref(); + temp_live_in.remove(result); + } + + // Set the next-use distance of any operands to 0 + for operand in op.operands().all().iter() { + temp_live_in.insert(operand.borrow().as_value_ref(), 0); + } + + // Determine if the state has changed, if so, then overwrite `live_in` with what we've + // computed. Otherwise, do nothing to avoid triggering re-analysis. + log::trace!(target: self.debug_name(), "computed live-in for {op}: {:#?}", &temp_live_in); + if live_in.value() == &temp_live_in { + return Ok(()); + } else { + *live_in.value_mut() = temp_live_in; + } + + self.propagate_live_in_to_prev_live_out(op, parent_block, live_in.value(), solver); + + Ok(()) + } + + /// Called to set the lattice state to its "overdefined" state, for our purposes, we use the + /// empty set, i.e. nothing is live. + fn set_to_exit_state( + &self, + lattice: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + _solver: &mut DataFlowSolver, + ) { + lattice.value_mut().clear(); + log::trace!(target: self.debug_name(), "set lattice for {} to exit state: {:#?}", lattice.anchor(), lattice.value()); + } + + /// This will be invoked differently depending on various situations in which call control-flow + /// occurs: + /// + /// * If the solver is configured for inter-procedural analysis, and the callable op definition + /// is resolvable, then `CallControlFlowAction::Enter` indicates that we are propagating + /// liveness information from the entry block of the callee (`after`) to before the call + /// operation (`before`). + /// * If the solver is not configured for inter-procedural analysis, or the callable op + /// is a unresolvable or resolves to a declaration, then `CallControlFlowAction::External` + /// will be passed, and it is up to us to decide how to handle the call op, `after` refers to + /// the liveness state after `call`, and `before` refers to the liveness state before `call`, + /// just like when [Self::visit_operation] is called. + /// * If the analysis is visiting a block of an operation with region control-flow, and that + /// block exits back to the parent operation, _and_ the parent operation implements + /// `CallableOpInterface`, then this function will be invoked for all callsites in order to + /// propagate liveness information from after the call to the end of the exit block. Thus: + /// * `action` will be set to `CallControlFlowAction::Exit` + /// * `after` refers to the liveness information after the call operation + /// * `before` refers to the liveness information at the end of an exit block in the callee, + /// the specific block can be obtained via the lattice anchor. + /// + fn visit_call_control_flow_transfer( + &self, + call: &dyn CallOpInterface, + action: CallControlFlowAction, + after: &Self::Lattice, + before: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + assert!( + !solver.config().is_interprocedural(), + "support for interprocedural liveness analysis is not yet complete" + ); + + match action { + CallControlFlowAction::Enter | CallControlFlowAction::Exit => { + unimplemented!("interprocedural liveness analysis") + } + CallControlFlowAction::External => { + self.visit_operation(call.as_operation(), after, before, solver) + .expect("unexpected failure computing liveness"); + } + } + } + + /// This is invoked in order to propagate liveness information across region control-flow + /// transfers of `branch`, and can be invoked differently depending on the source/target of the + /// branch itself: + /// + /// 1. If `region_from` is `None`, we're branching into a region of `branch`: + /// * `after` is the live-in set of the entry block of `region_to` + /// * `before` is the live-in set of the branch op itself + /// 2. If `region_from` is `Some`, but `region_to` is `None`, we're branching out of a region of + /// `branch`, to `branch` itself: + /// * `after` is the live-out set of `branch` + /// * `before` is the live-out set of the exit block of `region_from` + /// 3. If `region_from` and `region_to` are `Some`, we're branching between regions of `branch`: + /// * `after` is the live-in set of the entry block of `region_to` + /// * `before` is the live-out set of the exit block of `region_from` + /// 4. It should not be the case that both regions are `None`, however if this does occur, we + /// will just delegate to `visit_operation`, as it implies that the regions of `branch` are + /// not going to be entered. + /// + /// In short, each of the above corresponds to the following: + /// + /// 1. We're propagating liveness out of `region_to` to the `branch` op live-in set + /// 2. We're propagating liveness from the live-out set of `branch` op into the live-out set of + /// an exit from `region_from`. + /// 3. We're propagating liveness up the region tree of `branch`, from the live-in set of one + /// region to the live-out set of another, just like normal branching control flow. + /// 4. We're propagating liveness from the live-out set to the live-in set of op, the same as + /// is done in `visit_operation` + fn visit_region_branch_control_flow_transfer( + &self, + branch: &dyn RegionBranchOpInterface, + region_from: Option, + region_to: Option, + after: &Self::Lattice, + before: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + log::trace!(target: self.debug_name(), "visit region branch operation: {}", branch.as_operation()); + log::trace!( + target: self.debug_name(), + "propagating liveness backwards along control flow edge {} -> {}", + before.anchor(), + after.anchor() + ); + log::trace!(target: self.debug_name(), "source lattice: {:#?}", after.value()); + log::trace!(target: self.debug_name(), "target lattice: {:#?}", before.value()); + + match (region_from, region_to) { + // 4. + (None, None) => { + log::debug!( + "control flow does not visit any child regions, visiting like a regular op" + ); + self.visit_operation(branch.as_operation(), after, before, solver) + .expect("unexpected failure during liveness analysis"); + } + // 1. + (None, Some(region_to)) => { + log::debug!( + "propagating live-in set of region entry block to live-in of region branch op" + ); + // We're only interested in propagating liveness out of `region_to` for values that + // are defined in an ancestor region. We are guaranteed that removing the block + // parameters for the entry block of `region_to` from `after`, will give us only + // the set of values which are used in `region_to`, but not defined in `region_to`. + // + // This is sufficient for our needs, so the more expensive dominance check is not + // needed. + let mut live_in = after.value().clone(); + let region_to = region_to.borrow(); + let region_to_entry = region_to.entry(); + let op = branch.as_operation(); + + // Remove region entry arguments for `region_to` + for arg in region_to_entry.arguments() { + live_in.remove(*arg as ValueRef); + } + + // Remove operation results of `branch` + for result in op.results().iter() { + live_in.remove(*result as ValueRef); + } + + // Set next-use distance of all operands 0 + for operand in op.operands().iter() { + let operand = operand.borrow().as_value_ref(); + live_in.insert(operand, 0); + } + + // Join the before/after lattices to ensure we propagate liveness from multi-exit + // regions, e.g. `hir.if` + let before_live_in = before.value().join(&live_in); + + // Determine if the state has changed, if so, then overwrite `before` with what + // we've computed. Otherwise, do nothing to avoid triggering re-analysis. + log::trace!( + target: self.debug_name(), + "joined live-in lattice of {} with live-in of {}: {:#?}", + before.anchor(), + after.anchor(), + &before_live_in + ); + if before.value() == &before_live_in { + return; + } else { + *before.value_mut() = before_live_in; + } + + let parent_block = op.parent().unwrap(); + self.propagate_live_in_to_prev_live_out(op, parent_block, before.value(), solver); + } + // 2. + (Some(region_from), None) => { + log::debug!( + "propagating live-out set of region branch op to live-out set of region exit \ + block" + ); + // We're starting with the live-out set of `op`, which contains some/all of its + // results. We must do two things here to propagate liveness to the live-out set of + // the exit block (which is derived from its terminator op): + // + // 1. Remove `op`'s results from the set + // 2. If `region_from` is a repetitive region (i.e. part of a loop), we need to + // increment the remaining next-use distances by LOOP_EXIT_DISTANCE + //let op = branch.as_operation(); + let mut live_out = after.value().clone(); + let is_loop_exit = + branch.is_repetitive_region(region_from.borrow().region_number()); + log::debug!( + "exit region is part of a loop, so this control flow edge represents a loop \ + exit" + ); + if is_loop_exit { + for value in live_out.iter_mut() { + value.distance = value.distance.saturating_add(LOOP_EXIT_DISTANCE); + } + } + + // Remove results of branch op + for result in branch.as_operation().results().iter() { + live_out.remove(*result as ValueRef); + } + + // Take the join of before and after, so that we take the minimum distance across + // all successors of `branch_op` + let before_live_out = before.value().join(&live_out); + + // Determine if the state has changed, if so, then overwrite `before` with what + // we've computed. Otherwise, do nothing to avoid triggering re-analysis. + log::trace!( + target: self.debug_name(), + "joined live-out lattice of {} with live-out lattice of {}: {:#?}", + before.anchor(), + after.anchor(), + &before_live_out + ); + if before.value() != &before_live_out { + *before.value_mut() = before_live_out.clone(); + } + + // We need to attach the live-out information to the terminator of `region_from` + // that is exiting to the parent op so that it is picked up by the dense dataflow + // analysis framework prior to visit_operation being invoked + let pp = before.anchor().as_program_point().unwrap(); + let terminator = pp.operation().expect("expected block terminator"); + + // Ensure the analysis state for `terminator` is initialized with `before_live_out` + let point = ProgramPoint::after(terminator); + log::debug!("propagating live-out lattice of {pp} to live-out of {point}"); + let mut term_liveness = solver.get_or_create_mut::, _>(point); + if term_liveness.value() != &before_live_out { + *term_liveness.value_mut() = before_live_out; + } + } + // 3. + (Some(region_from), Some(region_to)) => { + log::trace!( + target: self.debug_name(), + "propagating live-in lattice to live-out lattice for cross-region control flow", + ); + // We're starting with the live-in set of `region_to`'s entry block, and propagating + // to the live-out set of `region_from`'s exit to `region_to`. We must do the + // following: + // + // 1. Remove the region parameters of `region_to` from the live-in set + // 2. If region_from is part of a loop, and region_to is not, increment the next-use + // distance of all live values by LOOP_EXIT_DISTANCE + let mut live_in = after.value().clone(); + let region_to = region_to.borrow(); + let region_to_entry = region_to.entry(); + let is_loop_exit = branch + .is_repetitive_region(region_from.borrow().region_number()) + && !branch.is_repetitive_region(region_to.region_number()); + for arg in region_to_entry.arguments() { + live_in.remove(*arg as ValueRef); + } + if is_loop_exit { + log::debug!( + "predecessor region is part of a loop, but successor is not, so this \ + control flow edge represents a loop exit" + ); + for value in live_in.iter_mut() { + value.distance = value.distance.saturating_add(LOOP_EXIT_DISTANCE); + } + } + + // Take the join of before and after, so that we take the minimum distance across + // all successors of `branch_op` + let before_live_out = before.value().join(&live_in); + + // Determine if the state has changed, if so, then overwrite `before` with what + // we've computed. Otherwise, do nothing to avoid triggering re-analysis. + log::trace!( + target: self.debug_name(), + "joined live-out lattice of {} with live-in lattice of {}: {:#?}", + before.anchor(), + after.anchor(), + &before_live_out + ); + if before.value() != &before_live_out { + *before.value_mut() = before_live_out.clone(); + } + + // We need to attach the live-out information to the terminator of `region_from` + // that is exiting to the parent op so that it is picked up by the dense dataflow + // analysis framework prior to visit_operation being invoked + let pp = before.anchor().as_program_point().unwrap(); + let terminator = pp.operation().expect("expected block terminator"); + + // Ensure the analysis state for `terminator` is initialized with `before_live_out` + let point = ProgramPoint::after(terminator); + log::debug!("propagating live-out lattice of {pp} to live-out of {point}"); + let mut term_liveness = solver.get_or_create_mut::, _>(point); + if term_liveness.value() != &before_live_out { + *term_liveness.value_mut() = before_live_out; + } + } + } + } +} + +impl Liveness { + // Propagate live-in from `op`, to the live-out of its predecessor op, or the live-in of the + // containing block if we've reached the start of the block. + // + // NOTE: `parent_block` must be the containing block of `op` + fn propagate_live_in_to_prev_live_out( + &self, + op: &Operation, + parent_block: BlockRef, + live_in: &NextUseSet, + solver: &mut DataFlowSolver, + ) { + // Is this the first op in the block? + if let Some(prev) = op.as_operation_ref().prev() { + log::debug!( + "propagating live-in of {} to live-out of {}", + ProgramPoint::before(op), + ProgramPoint::after(prev) + ); + // No, in which case we need to compute the live-out set for the preceding op in the + // block, by taking the live-in set for this op, and adding entries for all of the + // op results not yet in the set + let mut live_out_prev = live_in.clone(); + let prev_op = prev.borrow(); + for result in prev_op.results().iter() { + let result = result.borrow().as_value_ref(); + if live_out_prev.contains(result) { + continue; + } + // This op result has no known uses + live_out_prev.insert(result, u32::MAX); + } + + // Ensure the analysis state for `prev` is initialized with `live_out_prev` + let point = ProgramPoint::after(prev); + log::trace!( + target: self.debug_name(), + "joined live-out lattice of {} with live-in lattice of {}: {:#?}", + point, + ProgramPoint::before(op), + &live_out_prev + ); + let mut prev_liveness = solver.get_or_create_mut::, _>(point); + if prev_liveness.value() != &live_out_prev { + *prev_liveness.value_mut() = live_out_prev; + } + } else { + log::debug!( + "propagating live-in of {} to live-in of {}", + ProgramPoint::before(op), + ProgramPoint::at_start_of(parent_block) + ); + // Yes, in which case we need to compute the live-in set for the block by taking the + // live-in set for this op, and ensure entries for all of the block parameters + let mut live_in_block = live_in.clone(); + let block = parent_block.borrow(); + for arg in block.arguments().iter().copied() { + let arg = arg as ValueRef; + if live_in_block.contains(arg) { + continue; + } + // This block argument has no known uses + live_in_block.insert(arg, u32::MAX); + } + + let point = ProgramPoint::at_start_of(parent_block); + log::trace!( + target: self.debug_name(), + "joined live-in lattice of {} to live-in lattice of {}: {:#?}", + point, + ProgramPoint::before(op), + &live_in_block + ); + let mut block_liveness = solver.get_or_create_mut::, _>(point); + if block_liveness.value() != &live_in_block { + *block_liveness.value_mut() = live_in_block; + } + } + } +} diff --git a/hir-analysis/src/analyses/liveness/next_use_set.rs b/hir-analysis/src/analyses/liveness/next_use_set.rs new file mode 100644 index 000000000..1e02992b2 --- /dev/null +++ b/hir-analysis/src/analyses/liveness/next_use_set.rs @@ -0,0 +1,369 @@ +use core::borrow::Borrow; + +use midenc_hir::{SmallVec, ValueRef}; + +use crate::{ChangeResult, LatticeLike}; + +/// Represents a single value and its next use distance at some program point +#[derive(Debug, Copy, Clone)] +pub struct NextUse { + /// The value in question + pub value: ValueRef, + /// The distance to its next use. + /// + /// The distance is `u32::MAX` if unused/unknown, 0 if used at the current program point + pub distance: u32, +} + +impl NextUse { + #[inline] + pub const fn is_live(&self) -> bool { + self.distance < u32::MAX + } +} + +impl Eq for NextUse {} +impl PartialEq for NextUse { + fn eq(&self, other: &Self) -> bool { + self.value.eq(&other.value) + } +} +impl PartialOrd for NextUse { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for NextUse { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.distance + .cmp(&other.distance) + .then_with(|| self.value.borrow().id().cmp(&other.value.borrow().id())) + } +} + +/// The lattice representing global next-use information for a program point. +/// +/// The lattice maps zero or more SSA values to their global next-use distance at a given program +/// point. It has set-like semantics: a value only appears once in each set, and the union of two +/// sets takes the minimum distance for values present in both sets. +/// +/// From this, we derive the partial order required for a join semi-lattice: +/// +/// * The _bottom_ value of the lattice, which represents uninitialized or unknown state, is the +/// empty set. A set which does not contain a specific value, indicates that we have not observed +/// any uses of that value at that point, so we cannot reason about it until we do, or until we +/// have reached its definition. +/// * The _top_ value of the lattice, which represents that a value is never used, is given by a +/// next-use distance of `u32::MAX`. This value is only applied when we reach the definition of +/// a value and have observed no uses of it. +/// * All other values represent the fact that a value has a given _minimal_ next-use distance. If +/// we observe two different next-use distances at some join point in the program, the larger +/// distance is discarded in favor of the shorter distance. +/// +/// For known distances, there are a few distance-related properties that are worth keeping in mind: +/// +/// * A distance of `0` indicates that the next-use of a value is at the current operation/anchor +/// * A distance of `1` indicates that the next-use of a value is at the operation immediately +/// succeeding the current operation. +/// * Distances are incremented by 1 at each program point preceding a use. The exception to this +/// is when a loop exit is reached, in which case all next-use distances across that edge are +/// incremented by a large constant value, 10,000, to ensure that next-use distances reflect the +/// fact that a use may actually be much further away than it appears, depending on how many +/// iterations of the loop occur before exiting the loop. +/// +/// This lattice lets us answer the following questions: +/// +/// 1. Is a given value live at the current program point +/// 2. Given the set of live values at the current program point, which values have the closest +/// (or furthest) next use? +/// +/// The second question is of primary importance for spills analysis, register allocation and (in +/// the case of Miden) operand stack management. If we're going to choose what values to spill, so +/// as to keep the most important values available in registers (or the operand stack), then we +/// want to know when those values are needed. +#[derive(Default, Debug, Clone)] +pub struct NextUseSet(SmallVec<[NextUse; 4]>); + +impl Eq for NextUseSet {} +impl PartialEq for NextUseSet { + fn eq(&self, other: &Self) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + for next_use in self.0.iter() { + if other + .0 + .iter() + .find(|nu| nu.value == next_use.value) + .is_none_or(|nu| nu.distance != next_use.distance) + { + return false; + } + } + + true + } +} + +impl LatticeLike for NextUseSet { + #[inline] + fn join(&self, other: &Self) -> Self { + self.union(other) + } + + #[inline] + fn meet(&self, other: &Self) -> Self { + self.intersection(other) + } +} + +impl FromIterator for NextUseSet { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + let mut set = Self::default(); + for NextUse { value, distance } in iter.into_iter() { + set.insert(value, distance); + } + set + } +} + +impl NextUseSet { + /// Inserts `value` in this set with the given `distance`. + /// + /// A distance of `u32::MAX` signifies infinite distance, which is + /// equivalent to saying that `value` is not live. + /// + /// If `value` is already in this set, the distance is updated to be the + /// lesser of the two distances, e.g. if the previous distance was `u32::MAX`, + /// and `distance` was `1`, the entry is updated to have a distance of `1` after + /// this function returns. + pub fn insert(&mut self, value: ValueRef, distance: u32) -> ChangeResult { + if let Some(existing) = self.0.iter_mut().find(|next_use| next_use.value.eq(&value)) { + if existing.distance == distance { + ChangeResult::Unchanged + } else { + existing.distance = core::cmp::min(existing.distance, distance); + ChangeResult::Changed + } + } else { + self.0.push(NextUse { value, distance }); + ChangeResult::Changed + } + } + + /// Returns true if this set is empty/uninitialized + #[inline] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Returns the number of values recorded in this set + #[inline] + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns the number of live values in this set, i.e. values whose next-use distance is not + /// overdefined. + pub fn num_live(&self) -> usize { + self.0.iter().filter(|nu| nu.distance < u32::MAX).count() + } + + /// Returns `true` if `value` is live in this set + #[inline] + pub fn is_live(&self, value: V) -> bool + where + V: Borrow, + { + self.distance(value) < u32::MAX + } + + /// Returns the distance to the next use of `value` as an integer. + /// + /// If `value` is not live, or the distance is unknown, returns `u32::MAX` + pub fn distance(&self, value: V) -> u32 + where + V: Borrow, + { + self.get(value).map(|next_use| next_use.distance).unwrap_or(u32::MAX) + } + + /// Returns `true` if `value` is in this set + #[inline] + pub fn contains(&self, value: V) -> bool + where + V: Borrow, + { + self.get(value).is_none() + } + + /// Gets the [NextUse] associated with the given `value`, if known + #[inline] + pub fn get(&self, value: V) -> Option<&NextUse> + where + V: Borrow, + { + let value = value.borrow(); + self.0.iter().find(|next_use| &next_use.value == value) + } + + /// Gets a mutable reference to the distance associated with the given `value`, if known + #[inline] + pub fn get_mut(&mut self, value: V) -> Option<&mut NextUse> + where + V: Borrow, + { + let value = value.borrow(); + self.0.iter_mut().find(|next_use| &next_use.value == value) + } + + /// Removes the entry for `value` from this set + pub fn remove(&mut self, value: V) -> Option + where + V: Borrow, + { + let value = value.borrow(); + self.0 + .iter() + .position(|next_use| &next_use.value == value) + .map(|index| self.0.swap_remove(index).distance) + } + + /// Remove any entries for which `callback` returns `false` + pub fn retain(&mut self, callback: F) + where + F: FnMut(&mut NextUse) -> bool, + { + self.0.retain(callback); + } + + /// Remove all entries in the set + pub fn clear(&mut self) { + self.0.clear(); + } + + /// Returns a new set containing the union of `self` and `other`. + /// + /// The resulting set will preserve the minimum distances from both sets. + pub fn union(&self, other: &Self) -> Self { + let mut result = self.clone(); + for NextUse { value, distance } in other.iter().cloned() { + result.insert(value, distance); + } + result + } + + /// Returns a new set containing the intersection of `self` and `other`. + /// + /// The resulting set will preserve the minimum distances from both sets. + pub fn intersection(&self, other: &Self) -> Self { + let mut result = Self::default(); + for NextUse { + value, + distance: v1, + } in self.iter() + { + match other.get(value) { + None => continue, + Some(NextUse { distance: v2, .. }) => { + result.insert(*value, core::cmp::min(*v1, *v2)); + } + } + } + result + } + + /// Returns a new set containing the symmetric difference of `self` and `other`, + /// i.e. the values that are in `self` or `other` but not in both. + pub fn symmetric_difference(&self, other: &Self) -> Self { + let mut result = Self::default(); + for next_use in self.iter() { + if !other.contains(next_use.value) { + result.0.push(*next_use); + } + } + for next_use in other.iter() { + if !self.contains(next_use.value) { + result.0.push(*next_use); + } + } + result + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter() + } + + pub fn iter_mut(&mut self) -> impl Iterator { + self.0.iter_mut() + } + + pub fn keys(&self) -> impl Iterator + '_ { + self.0.iter().map(|next_use| next_use.value) + } + + /// Returns an iterator over the values in this set with a finite next-use distance + pub fn live(&self) -> impl Iterator + '_ { + self.0.iter().filter_map(|next_use| { + if next_use.distance < u32::MAX { + Some(next_use.value) + } else { + None + } + }) + } + + /// Remove the value in this set which is closest compared to the others + /// + /// If this set is empty, returns `None`. + /// + /// If more than one value have the same distance, this returns the value with + /// the lowest id first. + #[inline] + pub fn pop_first(&mut self) -> Option { + let index = + self.0.iter().enumerate().min_by(|a, b| a.1.cmp(b.1)).map(|(index, _)| index)?; + Some(self.0.swap_remove(index)) + } + + /// Remove the value in this set which is furthest away compared to the others + /// + /// If this set is empty, returns `None`. + /// + /// If more than one value have the same distance, this returns the value with + /// the highest id first. + #[inline] + pub fn pop_last(&mut self) -> Option { + let index = + self.0.iter().enumerate().max_by(|a, b| a.1.cmp(b.1)).map(|(index, _)| index)?; + Some(self.0.swap_remove(index)) + } +} +impl<'b> core::ops::BitOr<&'b NextUseSet> for &NextUseSet { + type Output = NextUseSet; + + #[inline] + fn bitor(self, rhs: &'b NextUseSet) -> Self::Output { + self.union(rhs) + } +} +impl<'b> core::ops::BitAnd<&'b NextUseSet> for &NextUseSet { + type Output = NextUseSet; + + #[inline] + fn bitand(self, rhs: &'b NextUseSet) -> Self::Output { + self.intersection(rhs) + } +} +impl<'b> core::ops::BitXor<&'b NextUseSet> for &NextUseSet { + type Output = NextUseSet; + + #[inline] + fn bitxor(self, rhs: &'b NextUseSet) -> Self::Output { + self.symmetric_difference(rhs) + } +} diff --git a/hir-analysis/src/analyses/loops.rs b/hir-analysis/src/analyses/loops.rs new file mode 100644 index 000000000..b05276c3f --- /dev/null +++ b/hir-analysis/src/analyses/loops.rs @@ -0,0 +1,146 @@ +use core::any::Any; + +use crate::{AnalysisState, BuildableAnalysisState, ChangeResult, LatticeAnchor, LatticeAnchorRef}; + +/// This enumeration represents a lattice of control flow edges that have loop effects. +/// +/// The lattice is as follows: +/// +/// * `Uninitialized` is the _bottom_ state, and the default state of the lattice +/// * `Unknown` is the _top_ or _overdefined_ state, and represents a state where we are unable to +/// conclude any facts about loop effects along the corresponding control flow edge. +/// * `None` is the "minimal" initialized state of the lattice, i.e. thus far, there are no loop +/// effects known to occur along the current control flow edge +/// * `Enter`, `Latch`, and `Exit` are "maximal" initialized states of the lattice, but are +/// mutually-exclusive determinations. A control flow edge cannot be more than one of these at the +/// same time. +/// +/// The partial order (and transitions) when joining states are: +/// +/// * `uninitialized -> none -> enter|latch|exit -> unknown` +/// +/// Put another way, we can join `None` with `Enter` and get `Enter`, but joining `Enter` with +/// `Exit` will produce `Unknown`. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +pub enum LoopAction { + /// We have no information about this edge yet + #[default] + Uninitialized, + /// We do not know what loop action might be taking place along this edge + Unknown, + /// No loop action is taking place along the associated control flow edge + None, + /// The associated control flow edge enters a loop + Enter, + /// The associated control flow edge loops back to the loop header + Latch, + /// The associated control flow edge exits the current loop + Exit, +} + +impl LoopAction { + #[inline] + pub fn is_uninitialized(&self) -> bool { + matches!(self, Self::Uninitialized) + } + + #[inline] + pub fn is_overdefined(&self) -> bool { + matches!(self, Self::Unknown) + } + + #[inline] + pub fn is_known_loop_effect(&self) -> bool { + matches!(self, Self::Enter | Self::Latch | Self::Exit) + } +} + +/// An [AnalyisState] that associates a [LoopAction] with some anchor ([CfgEdge] for now) +#[derive(Copy, Clone)] +pub struct LoopState { + anchor: LatticeAnchorRef, + action: LoopAction, +} + +impl LoopState { + /// What type of action is associated with the anchor + #[inline] + pub const fn action(&self) -> LoopAction { + self.action + } + + /// Returns true if this state indicates that the anchor is associated with entry to a loop + pub fn is_entering_loop(&self) -> bool { + matches!(self.action, LoopAction::Enter) + } + + /// Returns true if this state indicates that the anchor is associated with exiting a loop + pub fn is_exiting_loop(&self) -> bool { + matches!(self.action, LoopAction::Exit) + } + + /// Returns true if this state indicates that the anchor is associated with a loop latch, i.e. + /// a block in a loop which has a backedge to the loop header. + pub fn is_loop_latch(&self) -> bool { + matches!(self.action, LoopAction::Latch) + } + + /// Set the loop action for this state, returning whether or not that is a change from the + /// previous state. + /// + /// NOTE: This will set the state to overdefined if `action` conflicts with the current state. + pub fn set_action(&mut self, action: LoopAction) -> ChangeResult { + if action.is_uninitialized() { + if self.action.is_uninitialized() { + return ChangeResult::Unchanged; + } + self.action = action; + return ChangeResult::Changed; + } + + if self.action.is_overdefined() { + return ChangeResult::Unchanged; + } + + if self.action.is_known_loop_effect() { + if self.action != action { + self.action = LoopAction::Unknown; + return ChangeResult::Changed; + } + return ChangeResult::Unchanged; + } + + if core::mem::replace(&mut self.action, action) == action { + ChangeResult::Unchanged + } else { + ChangeResult::Changed + } + } + + /// Joining two loop states will do one of the following: + /// + /// * If `Uninitialized`, `action` is determined to be the new loop state + /// * If `Unknown`, the result is always `Unknown` + pub fn join(&mut self, action: LoopAction) -> ChangeResult { + self.set_action(action) + } +} + +impl BuildableAnalysisState for LoopState { + fn create(anchor: LatticeAnchorRef) -> Self { + Self { + anchor, + action: LoopAction::Unknown, + } + } +} + +impl AnalysisState for LoopState { + fn as_any(&self) -> &dyn Any { + self + } + + fn anchor(&self) -> &dyn LatticeAnchor { + &self.anchor + } +} diff --git a/hir-analysis/src/analyses/spills.rs b/hir-analysis/src/analyses/spills.rs new file mode 100644 index 000000000..aa9385ba9 --- /dev/null +++ b/hir-analysis/src/analyses/spills.rs @@ -0,0 +1,2399 @@ +use alloc::{boxed::Box, collections::VecDeque, vec::Vec}; + +use midenc_hir::{ + adt::{SmallOrdMap, SmallSet}, + cfg::Graph, + dialects::builtin::Function, + dominance::{DominanceInfo, DominanceTree}, + formatter::DisplayValues, + loops::{Loop, LoopForest, LoopInfo}, + pass::{Analysis, AnalysisManager, PreservedAnalyses}, + traits::{BranchOpInterface, IsolatedFromAbove, Terminator}, + AttributeValue, Block, BlockRef, FxHashMap, FxHashSet, LoopLikeOpInterface, Op, Operation, + OperationRef, ProgramPoint, Region, RegionBranchOpInterface, RegionBranchPoint, + RegionBranchTerminatorOpInterface, Report, SmallVec, SourceSpan, Spanned, SuccessorOperands, + Value, ValueOrAlias, ValueRange, ValueRef, +}; + +use super::dce::{CfgEdge, Executable}; +use crate::{ + analyses::{ + constant_propagation::ConstantValue, dce::PredecessorState, liveness::LOOP_EXIT_DISTANCE, + LivenessAnalysis, + }, + Lattice, +}; + +#[cfg(test)] +mod tests; + +/// This analysis is responsible for simulating the state of the operand stack at each program +/// point, taking into account the results of liveness analysis, and computing whether or not to +/// insert spills/reloads of values which would cause the operand stack depth to exceed 16 elements, +/// the maximum addressable depth. +/// +/// The algorithm here is based on the paper [_Register Spilling and Live-Range Splitting for +/// SSA-form Programs_ by Matthias Braun and Sebastian Hack](https://pp.ipd.kit.edu/uploads/publikationen/braun09cc.pdf), +/// which also happens to describe the algorithm we based our liveness analysis on. While the broad +/// strokes are the same, various modifications/tweaks to the algorithm they describe are needed in +/// order to be suitable for our use case. In particular, we must distinguish between the SSA values +/// which uniquely identify each operand, from the raw elements on the operand stack which represent +/// those values. The need for spills is determined solely on the low-level operand stack +/// representation, _not_ the number of live SSA values (although there can be a correspondence in +/// cases where each SSA value has an effective size of 1 stack element). As this is a type- +/// sensitive analysis, it differs from the algorithm in the paper, which is based on an assumption +/// that all operands are machine-word sized, and thus each value only requires a single register to +/// hold. +/// +/// Despite these differences, the overall approach is effectively identical. We still are largely +/// concerned with the SSA values, the primary difference being that we are computing spills based +/// on the raw operand stack state, rather than virtual register pressure as described in the paper. +/// As a result, the number of spills needed at a given program point are not necessarily 1:1, as +/// it may be necessary to spill multiple values in order to free sufficient capacity on the operand +/// stack to hold the required operands; or conversely, we may evict operands that free up more +/// operand stack space than is strictly needed due to the size of those values. +/// +/// The general algorithm, once liveness has been computed (see [LivenessAnalysis] for more +/// details), can be summarized as follows: +/// +/// In reverse CFG postorder, visit each block B, and: +/// +/// 1. Determine initialization of W at entry to B (W^entry). W is the set of operands on the +/// operand stack. From this we are able to determine what, if any, actions are required to +/// keep |W| <= K where K is the maximum allowed operand stack depth. +/// +/// 2. Determine initialization of S at entry to B (S^entry). S is the set of values which have +/// been spilled up to that point in the program. We can use S to determine whether or not +/// to actually emit a spill instruction when a spill is necessary, as due to the SSA form of +/// the program, every value has a single definition, so we need only emit a spill for a given +/// value once. +/// +/// 3. For each predecessor P of B, determine what, if any, spills and/or reloads are needed to +/// ensure that W and S are consistent regardless of what path is taken to reach B, and that +/// |W| <= K. Depending on whether P has multiple successors, it may be necessary to split the +/// edge between P and B, so that the emitted spills/reloads only apply along that edge. +/// +/// 4. Perform the MIN algorithm on B, which is used to determine spill/reloads at each instruction +/// in the block. MIN is designed to make optimal decisions about what to spill, so as to +/// minimize the number of spill/reload-related instructions executed by any given program +/// execution trace. It does this by using the next-use distance associated with values in W, +/// which is computed as part of our liveness analysis. Unlike traditional liveness analysis +/// which only tracks what is live at a given program point, next-use distances not only tell +/// you whether a value is live or dead, but how far away the next use of that value is. MIN +/// uses this information to select spill candidates from W furthest away from the current +/// instruction; and on top of this we also add an additional heuristic based on the size of +/// each candidate as represented on the operand stack. Given two values with equal next-use +/// distances, the largest candidates are spilled first, allowing us to free more operand stack +/// space with fewer spills. +/// +/// The MIN algorithm works as follows: +/// +/// 1. Starting at the top of the block, B, W is initialized with the set W^entry(B), and S with +/// S^entry(B) +/// +/// 2. For each instruction, I, in the block, update W and S according to the needs of I, while +/// attempting to preserve as many live values in W as possible. Each instruction fundamentally +/// requires that: On entry, W contains all the operands of I; on exit, W contains all of the +/// results of I; and that at all times, |W| <= K. This means that we may need to reload operands +/// of I that are not in W (because they were spilled), and we may need to spill values from W to +/// ensure that the stack depth <= K. The specific effects for I are computed as follows: +/// a. All operands of I not in W, must be reloaded in front of I, thus adding them to W. +/// This is also one means by which values are added to S, as by definition a reload implies that +/// the value must have been spilled, or it would still be in W. Thus, when we emit reloads, we +/// also ensure that the reloaded value is added to S. +/// b. If a reload would cause |W| to exceed K, we must select values in W to spill. Candidates +/// are selected from the set of values in W which are not operands of I, prioritized first by +/// greatest next-use distance, then by stack consumption, as determined by the representation of +/// the value type on the operand stack. +/// c. By definition, none of I's results can be in W directly in front of I, so we must always +/// ensure that W has sufficient capacity to hold all of I's results. The analysis of sufficient +/// capacity is somewhat subtle: +/// - Any of I's operands that are live-at I, but _not_ live-after I, do _not_ count towards +/// the operand stack usage when calculating available capacity for the results. This is +/// because those operands will be consumed, and their space can be re-used for results. +/// - Any of I's operands that are live-after I, however, _do_ count towards the stack usage +/// - If W still has insufficient capacity for all the results, we must select candidates +/// to spill. Candidates are the set of values in W which are either not operands of I, +/// or are operands of I which are live-after I. Selection criteria is the same as before. +/// +/// d. Operands of I which are _not_ live-after I, are removed from W on exit from I, thus W +/// reflects only those values which are live at the current program point. +/// e. Lastly, when we select a value to be spilled, we only emit spill instructions for those +/// values which are not yet in S, i.e. they have not yet been spilled; and which have a finite +/// next-use distance, i.e. the value is still live. If a value to be spilled _is_ in S and/or is +/// unused after that point in the program, we can elide the spill entirely. +/// +/// What we've described above represents both the analysis itself, as well as the effects of +/// applying that analysis to the actual control flow graph of the function. However, doing so +/// introduces a problem that must be addressed: SSA-form programs can only have a single definition +/// of each value, but by introducing spills (and consequently, reloads of the spilled values), we +/// have introduced new definitions of those values - each reload constitutes a new definition. +/// As a result, our program is no longer in SSA form, and we must restore that property in order +/// to proceed with compilation. +/// +/// **NOTE:** The way that we represent reloads doesn't _literally_ introduce multiple definitions +/// of a given [Value], our IR does not permit representing that. Instead, we represent reloads as +/// an instruction which takes the spilled SSA value we want to reload as an argument, and produces +/// a new SSA value representing the reloaded spill. As a result of this representation, our program +/// always remains technically in SSA form, but the essence of the problem remains the same: When a +/// value is spilled, its live range is terminated; a reload effectively brings the spilled value +/// back to life, starting a new live range. Thus references to the spilled value which are now +/// dominated by a reload in the control flow graph, are no longer semantically correct - they must +/// be rewritten to reference the nearest dominating definition. +/// +/// Restoring SSA form is not the responsibility of this analysis, however I will briefly describe +/// the method here, while you have the context at hand. The obvious assumption would be that we +/// simply treat each reload as a new SSA value, and update any uses of the original value with the +/// nearest dominating definition. The way we represent reloads in HIR already does the first step +/// for us, however there is a subtle problem with the second part: join points in the control flow +/// graph. Consider the following: +/// +/// ```text,ignore +/// (block 0 (param v0) (param v1) +/// (cond_br v1 (block 1) (block 2))) +/// +/// (block 1 +/// (spill v0) +/// ... +/// (let v2 (reload v0)) ; here we've assigned the reload of v0 a new SSA value +/// (br (block 3))) +/// +/// (block 2 +/// ... +/// (br (block 3))) +/// +/// (block 3 +/// (ret v2)) ; here we've updated a v0 reference to the nearest definition +/// ``` +/// +/// Above, control flow branches in one of two directions from the entry block, and along one of +/// those branches `v0` is spilled and later reloaded. Control flow joins again in the final block +/// where `v0` is returned. We attempted to restore the program to SSA form as described above, +/// first by assigning reloads a new SSA value, then by finding all uses of the spilled value and +/// rewriting those uses to reference the nearest dominating definition. +/// +/// Because the use of `v0` in block 3 is dominated by the reload in block 1, it is rewritten to +/// reference `v2` instead. The problem with that is obvious - the reload in block 1 does not +/// _strictly_ dominate the use in block 3, i.e. there are paths through the function which can +/// reach block 3 without passing through block 1 first, and `v2` will be undefined along those +/// paths! +/// +/// However this problem also has an obvious solution: introduce a new block parameter in block 3 +/// to represent the appropriate definition of `v0` that applies based on the predecessor used to +/// reach block 3. This ensures that the use in block 3 is strictly dominated by an appropriate +/// definition. +/// +/// So now that we've understood the problem with the naive approach, and the essence of the +/// solution to that particular problem, we can walk through the generalized solution that can be +/// used to reconstruct SSA form for any program we can represent in our IR. +/// +/// 1. Given the set of spilled values, S, visit the dominance tree in postorder (bottom-up) +/// 2. In each block, working towards the start of the block from the end, visit each instruction +/// until one of the following occurs: +/// a. We find a use of a value in S. We append the use to the list of other uses of that value +/// which are awaiting a rewrite while we search for the nearest dominating definition. +/// b. We find a reload of a value in S. This reload is, by construction, the nearest dominating +/// definition for all uses of the reloaded value that we have found so far. We rewrite all of +/// those uses to reference the reloaded value, and remove them from the list. +/// c. We find the original definition of a value in S. This is similar to what happens when we +/// find a reload, except no rewrite is needed, so we simply remove all pending uses of that +/// value from the list. +/// d. We reach the top of the block. Note that block parameters are treated as definitions, so +/// those are handled first as described in the previous point. However, an additional step is +/// required here: If the current block is in the iterated dominance frontier for S, i.e. for any +/// value in S, the current block is in the dominance frontier of the original definition of that +/// value - then for each such value for which we have found at least one use, we must add a new +/// block parameter representing that value; rewrite all uses we have found so far to use the +/// block parameter instead; remove those uses from the list; and lastly, rewrite the branch +/// instruction in each predecessor to pass the value as a new block argument when branching to +/// the current block. +/// 3. When we start processing a block, the union of the set of unresolved uses found in each +/// successor, forms the initial state of that set for the current block. If a block has no +/// successors, then the set is initially empty. This is how we propagate uses up the dominance +/// tree until we find an appropriate definition. Since we ensure that block parameters are added +/// along the dominance frontier for each spilled value, we guarantee that the first definition +/// we reach always strictly dominates the uses we have propagated to that point. +/// +/// NOTE: A nice side effect of this algorithm is that any reloads we reach for which we have +/// no uses, are dead and can be eliminated. Similarly, a reload we never reach must also be +/// dead code - but in practice that won't happen, since we do not visit unreachable blocks +/// during the spill analysis anyway. +#[derive(Debug, Default, Clone)] +pub struct SpillAnalysis { + // The set of control flow edges that must be split to accommodate spills/reloads. + pub splits: SmallVec<[SplitInfo; 1]>, + // The set of values that have been spilled + pub spilled: FxHashSet, + // The spills themselves + pub spills: SmallVec<[SpillInfo; 4]>, + // The set of instructions corresponding to the reload of a spilled value + pub reloads: SmallVec<[ReloadInfo; 4]>, + // The set of operands in registers on entry to a given program point + w_entries: FxHashMap>, + // The set of operands that have been spilled upon entry to a given program point + s_entries: FxHashMap>, + // The set of operands in registers on exit from a given program point + w_exits: FxHashMap>, + // The set of operands that have been spilled so far, on exit from a given program point + s_exits: FxHashMap>, +} + +/// Represents a single predecessor for some [ProgramPoint] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Predecessor { + /// The predecessor of the point, is one of the following: + /// + /// 1. For a point at the start of a block, the predecessor is the operation itself on entry + /// 2. For a point after an op, the predecessor is the entry of that op, i.e. control bypassed + /// all of the op's nested regions and skipped straight to after the op. + Parent, + /// The predecessor of the point is cross-region control flow + Region(OperationRef), + /// The predecessor of the point is unstructured control flow + Block { op: OperationRef, index: u8 }, +} + +impl Predecessor { + pub fn operation(&self, point: ProgramPoint) -> OperationRef { + match self { + Self::Parent => match point { + ProgramPoint::Block { block, .. } => block.grandparent().unwrap(), + ProgramPoint::Op { op, .. } => op, + _ => unreachable!(), + }, + Self::Region(op) | Self::Block { op, .. } => *op, + } + } + + pub fn block(&self) -> Option { + match self { + Self::Parent => None, + Self::Region(op) | Self::Block { op, .. } => op.parent(), + } + } + + pub fn arguments(&self, point: ProgramPoint) -> ValueRange<'static, 4> { + match self { + Self::Parent => match point { + ProgramPoint::Block { block, .. } => { + // We need to get the entry successor operands from the parent branch op to + // `block` + let op = block.grandparent().unwrap(); + let op = op.borrow(); + let branch = op.as_trait::().unwrap(); + let args = branch.get_entry_successor_operands(RegionBranchPoint::Child( + block.parent().unwrap(), + )); + ValueRange::<4>::from(args).into_owned() + } + ProgramPoint::Op { op, .. } => { + // There cannot be any successor arguments in this case, and the op itself + // cannot have any results + assert_eq!(op.borrow().num_results(), 0); + ValueRange::Empty + } + _ => unreachable!(), + }, + Self::Region(op) => { + let op = op.borrow(); + let terminator = op.as_trait::().unwrap(); + let branch_point = match point { + ProgramPoint::Block { block, .. } => { + // Transfer of control to another region of the parent op + RegionBranchPoint::Child(block.parent().unwrap()) + } + ProgramPoint::Op { .. } => { + // Returning from the predecessor region back to the parent op's exit + RegionBranchPoint::Parent + } + _ => unreachable!(), + }; + let args = terminator.get_successor_operands(branch_point); + ValueRange::<4>::from(args).into_owned() + } + Self::Block { op, index } => { + ValueRange::<4>::from(op.borrow().successor(*index as usize).arguments).into_owned() + } + } + } +} + +/// The state of the W and S sets on entry to a given block +#[derive(Debug)] +struct ProgramPointInfo { + point: ProgramPoint, + w_entry: SmallSet, + s_entry: SmallSet, + live_predecessors: SmallVec<[Predecessor; 2]>, +} + +/// Uniquely identifies a computed split control flow edge in a [SpillAnalysis] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Split(u32); +impl Split { + pub fn new(id: usize) -> Self { + Self(id.try_into().expect("invalid index")) + } + + #[inline(always)] + pub const fn as_usize(&self) -> usize { + self.0 as usize + } +} + +/// Metadata about a control flow edge which needs to be split in order to accommodate spills and/or +/// reloads along that edge. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct SplitInfo { + pub id: Split, + /// The destination program point for the control flow edge being split + pub point: ProgramPoint, + /// The predecessor, or origin, of the control flow edge being split + pub predecessor: Predecessor, + /// The block representing the split, if materialized + pub split: Option, +} + +/// Uniquely identifies a computed spill in a [SpillAnalysis] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Spill(u32); +impl Spill { + pub fn new(id: usize) -> Self { + Self(id.try_into().expect("invalid index")) + } + + #[inline(always)] + pub const fn as_usize(&self) -> usize { + self.0 as usize + } +} + +/// Metadata about a computed spill +#[derive(Debug, Clone)] +pub struct SpillInfo { + pub id: Spill, + /// The point in the program where this spill should be placed + pub place: Placement, + /// The value to be spilled + pub value: ValueRef, + /// The span associated with the source code that triggered the spill + pub span: SourceSpan, + /// The spill instruction, if materialized + pub inst: Option, +} + +impl SpillInfo { + pub fn stack_size(&self) -> usize { + self.value.borrow().ty().size_in_felts() + } +} + +/// Uniquely identifies a computed reload in a [SpillAnalysis] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Reload(u32); +impl Reload { + pub fn new(id: usize) -> Self { + Self(id.try_into().expect("invalid index")) + } + + #[inline(always)] + pub const fn as_usize(&self) -> usize { + self.0 as usize + } +} + +/// Metadata about a computed reload +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ReloadInfo { + pub id: Reload, + /// The point in the program where this spill should be placed + pub place: Placement, + /// The spilled value to be reloaded + pub value: ValueRef, + /// The span associated with the source code that triggered the spill + pub span: SourceSpan, + /// The reload instruction, if materialized + pub inst: Option, +} + +/// This enumeration represents a program location where a spill or reload operation should be +/// placed. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum Placement { + /// A concrete location in the current program. + /// + /// The operation will be placed according to the semantics of the given [InsertionPoint] + At(ProgramPoint), + /// A pseudo-location, corresponding to the end of the block that will be materialized + /// to split the control flow edge represented by [Split]. + Split(Split), +} + +/// The maximum number of operand stack slots which can be assigned without spills. +const K: usize = 16; + +impl Analysis for SpillAnalysis { + type Target = Function; + + fn name(&self) -> &'static str { + "spills" + } + + fn analyze( + &mut self, + op: &Self::Target, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::debug!(target: "spills", "running spills analysis for {}", op.as_operation()); + + let liveness = analysis_manager.get_analysis::()?; + self.compute(op, &liveness, analysis_manager) + } + + fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool { + !preserved_analyses.is_preserved::() + } +} + +/// Queries +impl SpillAnalysis { + /// Returns true if at least one value must be spilled + pub fn has_spills(&self) -> bool { + !self.spills.is_empty() + } + + /// Returns the set of control flow edges that must be split to accommodate spills/reloads + pub fn splits(&self) -> &[SplitInfo] { + self.splits.as_slice() + } + + /// Same as [SpillAnalysis::splits], but as a mutable reference + pub fn splits_mut(&mut self) -> &mut [SplitInfo] { + self.splits.as_mut_slice() + } + + pub fn get_split(&self, split: Split) -> &SplitInfo { + &self.splits[split.as_usize()] + } + + /// Returns the set of values which require spills + pub fn spilled(&self) -> &FxHashSet { + &self.spilled + } + + /// Returns true if `value` is spilled at some point + pub fn is_spilled(&self, value: &ValueRef) -> bool { + self.spilled.contains(value) + } + + /// Returns true if `value` is spilled at the given program point (i.e. inserted before) + pub fn is_spilled_at(&self, value: ValueRef, pp: ProgramPoint) -> bool { + let place = match pp { + ProgramPoint::Block { + block: split_block, .. + } => match self.splits.iter().find(|split| split.split == Some(split_block)) { + Some(split) => Placement::Split(split.id), + None => Placement::At(ProgramPoint::at_end_of(split_block)), + }, + point => Placement::At(point), + }; + self.spills.iter().any(|info| info.value == value && info.place == place) + } + + /// Returns true if `value` will be spilled in the given split + pub fn is_spilled_in_split(&self, value: ValueRef, split: Split) -> bool { + self.spills.iter().any(|info| { + info.value == value && matches!(info.place, Placement::Split(s) if s == split) + }) + } + + /// Returns the set of computed spills + pub fn spills(&self) -> &[SpillInfo] { + self.spills.as_slice() + } + + /// Same as [SpillAnalysis::spills], but as a mutable reference + pub fn spills_mut(&mut self) -> &mut [SpillInfo] { + self.spills.as_mut_slice() + } + + /// Returns true if `value` is reloaded at some point + pub fn is_reloaded(&self, value: &ValueRef) -> bool { + self.reloads.iter().any(|info| &info.value == value) + } + + /// Returns true if `value` is reloaded at the given program point (i.e. inserted before) + pub fn is_reloaded_at(&self, value: ValueRef, pp: ProgramPoint) -> bool { + let place = match pp { + ProgramPoint::Block { + block: split_block, .. + } => match self.splits.iter().find(|split| split.split == Some(split_block)) { + Some(split) => Placement::Split(split.id), + None => Placement::At(ProgramPoint::at_end_of(split_block)), + }, + point => Placement::At(point), + }; + self.reloads.iter().any(|info| info.value == value && info.place == place) + } + + /// Returns true if `value` will be reloaded in the given split + pub fn is_reloaded_in_split(&self, value: ValueRef, split: Split) -> bool { + self.reloads.iter().any(|info| { + info.value == value && matches!(info.place, Placement::Split(s) if s == split) + }) + } + + /// Returns the set of computed reloads + pub fn reloads(&self) -> &[ReloadInfo] { + self.reloads.as_slice() + } + + /// Same as [SpillAnalysis::reloads], but as a mutable reference + pub fn reloads_mut(&mut self) -> &mut [ReloadInfo] { + self.reloads.as_mut_slice() + } + + /// Returns the operands in W upon entry to `point` + pub fn w_entry(&self, point: &ProgramPoint) -> &[ValueOrAlias] { + self.w_entries[point].as_slice() + } + + /// Returns the operands in S upon entry to `point` + pub fn s_entry(&self, point: &ProgramPoint) -> &[ValueOrAlias] { + self.s_entries[point].as_slice() + } + + /// Returns the operands in W upon exit from `point` + pub fn w_exit(&self, point: &ProgramPoint) -> &[ValueOrAlias] { + self.w_exits[point].as_slice() + } + + /// Returns the operands in S upon exit from `point` + pub fn s_exit(&self, point: &ProgramPoint) -> &[ValueOrAlias] { + self.s_exits[point].as_slice() + } +} + +/// Analysis +impl SpillAnalysis { + fn compute( + &mut self, + function: &Function, + liveness: &LivenessAnalysis, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + if function.body().has_one_block() { + let mut _deferred = Vec::<(BlockRef, SmallVec<[BlockRef; 2]>)>::default(); + self.visit_single_block( + function.as_operation(), + &function.entry_block().borrow(), + None, + liveness, + analysis_manager, + &mut _deferred, + )?; + assert!(_deferred.is_empty()); + Ok(()) + } else { + // We generally expect that control flow lifting will have removed all but the entry + // block, but in some cases there can be some remaining unstructured control flow, so + // we handle that in the usual way here + let dominfo = analysis_manager.get_analysis::()?; + let loops = analysis_manager.get_analysis::()?; + let entry_region = function.body().as_region_ref(); + let domtree = dominfo.dominance(entry_region); + if let Some(loop_forest) = loops.get(&entry_region) { + self.visit_cfg( + function.as_operation(), + &domtree, + loop_forest, + liveness, + analysis_manager, + ) + } else { + let loop_forest = LoopForest::new(&domtree); + self.visit_cfg( + function.as_operation(), + &domtree, + &loop_forest, + liveness, + analysis_manager, + ) + } + } + } + + fn visit_cfg( + &mut self, + op: &Operation, + domtree: &DominanceTree, + loops: &LoopForest, + liveness: &LivenessAnalysis, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::trace!(target: "spills", "visiting cfg"); + + // Visit the blocks of the CFG in reverse postorder (top-down) + let mut block_q = VecDeque::from(domtree.reverse_postorder()); + + // If a block has a predecessor which it dominates (i.e. control flow always flows through + // the block in question before the given predecessor), then we must defer computing spills + // and reloads for that edge until we have visited the predecessor. This map is used to + // track deferred edges for each block. + let mut deferred = Vec::<(BlockRef, SmallVec<[BlockRef; 2]>)>::default(); + + while let Some(node) = block_q.pop_front() { + let Some(block_ref) = node.block() else { + continue; + }; + + self.visit_single_block( + op, + &block_ref.borrow(), + Some(loops), + liveness, + analysis_manager.clone(), + &mut deferred, + )?; + } + + // We've visited all blocks at least once, now we need to go back and insert + // spills/reloads along loopback edges, as we skipped those on the first pass + for (block_ref, preds) in deferred { + let block = block_ref.borrow(); + + // W^entry(B) + let w_entry = self.w_entries[&ProgramPoint::at_start_of(block_ref)].clone(); + + // Derive S^entry(B) and construct information about the program point at block start + let block_info = self.block_entry_info(op, &block, liveness, w_entry); + + // For each predecessor P of B, insert spills/reloads along the inbound control flow + // edge as follows: + // + // * All variables in W^entry(B) \ W^exit(P) need to be reloaded + // * All variables in (S^entry(B) \ S^exit(P)) ∩ W^exit(P) need to be spilled + // + // If a given predecessor has not been processed yet, skip P, and revisit the edge later + // after we have processed P. + let mut _defer = SmallVec::default(); + for pred in block_info.live_predecessors.iter() { + let predecessor = pred.block().unwrap(); + + // Only visit predecessors that were deferred + if !preds.contains(&predecessor) { + continue; + } + + self.compute_control_flow_edge_spills_and_reloads( + &block_info, + pred, + &mut _defer, + liveness, + ); + } + } + + Ok(()) + } + + fn visit_region_cfg( + &mut self, + op: &dyn RegionBranchOpInterface, + entry: &Block, + liveness: &LivenessAnalysis, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::trace!(target: "spills", "visiting region cfg"); + + // Visit the blocks of the CFG in reverse postorder (top-down) + let region = entry.parent().unwrap(); + let mut region_q = Region::postorder_region_graph(®ion.borrow()); + + // If a region has a predecessor which it dominates (i.e. control flow always flows through + // the region in question before the given predecessor), then we must defer computing spills + // and reloads for that edge until we have visited the predecessor. This map is used to + // track deferred edges for each block. + let mut deferred = Vec::<(BlockRef, SmallVec<[BlockRef; 2]>)>::default(); + + let operation = op.as_operation(); + while let Some(region) = region_q.pop() { + let region = region.borrow(); + let block = region.entry(); + + self.visit_single_block( + operation, + &block, + None, + liveness, + analysis_manager.clone(), + &mut deferred, + )?; + } + + // We've visited all blocks at least once, now we need to go back and insert + // spills/reloads along loopback edges, as we skipped those on the first pass + for (block_ref, preds) in deferred { + let block = block_ref.borrow(); + + // W^entry(B) + let w_entry = self.w_entries[&ProgramPoint::at_start_of(block_ref)].clone(); + + // Derive S^entry(B) and construct information about the program point at block start + let block_info = self.block_entry_info(operation, &block, liveness, w_entry); + + // For each predecessor P of B, insert spills/reloads along the inbound control flow + // edge as follows: + // + // * All variables in W^entry(B) \ W^exit(P) need to be reloaded + // * All variables in (S^entry(B) \ S^exit(P)) ∩ W^exit(P) need to be spilled + // + // If a given predecessor has not been processed yet, skip P, and revisit the edge later + // after we have processed P. + let mut _defer = SmallVec::default(); + for pred in block_info.live_predecessors.iter() { + let predecessor = pred.block(); + + // Only visit predecessors that were deferred + if predecessor.is_some_and(|p| !preds.contains(&p)) { + continue; + } + + self.compute_control_flow_edge_spills_and_reloads( + &block_info, + pred, + &mut _defer, + liveness, + ); + } + } + + Ok(()) + } + + fn visit_single_block( + &mut self, + op: &Operation, + block: &Block, + loops: Option<&LoopForest>, + liveness: &LivenessAnalysis, + analysis_manager: AnalysisManager, + deferred: &mut Vec<(BlockRef, SmallVec<[BlockRef; 2]>)>, + ) -> Result<(), Report> { + let block_ref = block.as_block_ref(); + + log::trace!(target: "spills", "visiting {block}"); + + // Compute W^entry(B) + self.compute_w_entry(op, block, loops, liveness); + + // Derive S^entry(B) from W^entry(B) and compute live predecessors at block start + let w_entry = self.w_entries[&ProgramPoint::at_start_of(block)].clone(); + log::trace!(target: "spills", "computing block information"); + let block_info = self.block_entry_info(op, block, liveness, w_entry); + log::trace!(target: "spills", " W^entry({block}) = {{{}}}", DisplayValues::new(block_info.w_entry.iter())); + log::trace!(target: "spills", " S^entry({block}) = {{{}}}", DisplayValues::new(block_info.s_entry.iter())); + + // For each predecessor P of B, insert spills/reloads along the inbound control flow + // edge as follows: + // + // * All variables in W^entry(B) \ W^exit(P) need to be reloaded + // * All variables in (S^entry(B) \ S^exit(P)) ∩ W^exit(P) need to be spilled + // + // If a given predecessor has not been processed yet, skip P, and revisit the edge later + // after we have processed P. + // + // NOTE: Because W^exit(P) does not contain the block parameters for any given + // successor, as those values are renamed predecessor operands, some work must be done + // to determine the true contents of W^exit(P) for each predecessor/successor edge, and + // only then insert spills/reloads as described above. + let mut deferred_preds = SmallVec::<[BlockRef; 2]>::default(); + for pred in block_info.live_predecessors.iter() { + // As soon as we need to start inserting spills/reloads, mark the function changed + self.compute_control_flow_edge_spills_and_reloads( + &block_info, + pred, + &mut deferred_preds, + liveness, + ); + } + if !deferred_preds.is_empty() { + deferred.push((block_ref, deferred_preds)); + } + + // We have our W and S sets for the entry of B, and we have inserted all spills/reloads + // needed on incoming control flow edges to ensure that the contents of W and S are the + // same regardless of which predecessor we reach B from. + // + // Now, we essentially repeat this process for each instruction I in B, i.e. we apply + // the MIN algorithm to B. As a result, we will also have computed the contents of W + // and S at the exit of B, which will be needed subsequently for the successors of B + // when we process them. + // + // The primary differences here, are that we: + // + // * Assume that if a reload is needed (not in W), that it was previously spilled (must + // be in S) + // * We do not issue spills for values that have already been spilled + // * We do not emit spill instructions for values which are dead, they are just dropped + // * We must spill from W to make room for operands and results of I, if there is + // insufficient space to hold the current contents of W + whatever operands of I we + // need to reload + the results of I that will be placed on the operand stack. We do + // so by spilling values with the greatest next-use distance first, preferring to + // spill larger values where we have an option. We also may factor in liveness - if an + // operand of I is dead after I, we do not need to count that operand when computing + // the operand stack usage for results (thus reusing the space of the operand for one + // or more results). + // * It is important to note that we must count _all_ uses of the same value towards the + // operand stack usage, unless the semantics of an instruction explicitly dictate that + // a specific operand pattern only requires a single copy on the operand stack. + // Currently that is not the case for any instructions, and we would prefer to be more + // conservative at this point anyway. + let mut w = block_info.w_entry; + let mut s = block_info.s_entry; + for op in block.body() { + if let Some(loop_like) = op.as_trait::() { + // If we hit a loop-like region branch operation, we need to process it much + // like how we do unstructured control flow loops. The primary difference is + // that we do not use the dominance tree to determine the order in which the + // loop is visited, and we must also take into account op results on exit + // from the op, unlike how "results" are represented in an unstructured loop + self.visit_loop_like( + loop_like, + &mut w, + &mut s, + liveness, + analysis_manager.nest(op.as_operation_ref()), + )?; + } else if let Some(branch) = op.as_trait::() { + // If we hit a region branch operation, we need to process it much like how + // we do unstructured control flow. The primary difference is that we do not + // use the dominance tree to determine the order in which the regions of the + // op are visited, and we must take into account op results on exit from the + // op. + self.visit_region_branch_operation( + branch, + &mut w, + &mut s, + liveness, + analysis_manager.nest(op.as_operation_ref()), + )?; + } else { + self.min(&op, &mut w, &mut s, liveness); + } + } + + let end_of_block = ProgramPoint::at_end_of(block_ref); + self.w_exits.insert(end_of_block, w); + self.s_exits.insert(end_of_block, s); + + Ok(()) + } + + fn visit_loop_like( + &mut self, + loop_like: &dyn LoopLikeOpInterface, + w: &mut SmallSet, + s: &mut SmallSet, + liveness: &LivenessAnalysis, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + // Compute W and S for entry into the entry successor regions of `branch`, according to + // the standard MIN rules up to the point where we have computed any necessary spills and + // reloads, _without_ considering operation results of `branch`, so long as `branch` cannot + // ever skip all of its nested regions (i.e. `branch` is not a predecessor of its own exit) + + // Compute W and S through the region graph of `branch` from the loop header, and + // then derive W and S for exit from `branch` using predecessors of the exit point. W and + // S at exit from `branch` are not computed using the standard MIN approach, as exiting + // from nested regions with results is akin to an unstructured branch with arguments, so + // we handle it as such. + // + // NOTE: This differs from visit_region_branch_operation in how we select spill/reload + // candidates, so as to avoid spilling in a loop, or reloading in a loop, when either of + // those could be lifted or pushed down to loop exits. + let branch = loop_like.as_operation().as_trait::().unwrap(); + self.visit_region_branch_operation(branch, w, s, liveness, analysis_manager) + } + + fn visit_region_branch_operation( + &mut self, + branch: &dyn RegionBranchOpInterface, + w: &mut SmallSet, + s: &mut SmallSet, + liveness: &LivenessAnalysis, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::trace!(target: "spills", "visiting region branch op '{}'", branch.as_operation()); + log::trace!(target: "spills", " W^in = {w:?}"); + log::trace!(target: "spills", " S^in = {s:?}"); + + // PHASE 1: + // + // Compute W and S at entry to `branch`, i.e. before any of its control flow is evaluated - + // purely what is needed to begin evaluating the op. This will be used to derive W and S + // within regions of the op. + // + // NOTE: This does not take into account results of `op` like MIN does for other types of + // operations, as in the case of region control flow, the "results" are akin to successor + // block arguments, rather than needing to compete for space with operands of the op itself. + // + // TODO(pauls): Make sure we properly handle cases where control can pass directly from op + // entry to exit, skipping nested regions. For now we have no such operations which also + // produce results, which is the only case that matters. + + let op = branch.as_operation(); + let before_op = ProgramPoint::before(op); + let place = Placement::At(before_op); + let span = op.span(); + + let args = ValueRange::<2>::from(op.operands().group(0)); + let mut to_reload = args.iter().map(ValueOrAlias::new).collect::>(); + + // Remove the first occurrance of any operand already in W, remaining uses + // must be considered against the stack usage calculation (but will not + // actually be reloaded) + for operand in w.iter() { + if let Some(pos) = to_reload.iter().position(|o| o == operand) { + to_reload.swap_remove(pos); + } + } + log::trace!(target: "spills", " require reloading = {to_reload:#?}"); + + // Precompute the starting stack usage of W + let w_used = w.iter().map(|o| o.stack_size()).sum::(); + log::trace!(target: "spills", " current stack usage = {w_used}"); + + // Compute the needed operand stack space for all operands not currently in W, i.e. those + // which must be reloaded from a spill slot + let in_needed = to_reload.iter().map(|o| o.stack_size()).sum::(); + log::trace!(target: "spills", " required by reloads = {in_needed}"); + + // If we have room for operands and results in W, then no spills are needed, + // otherwise we require two passes to compute the spills we will need to issue + let mut to_spill = SmallSet::<_, 4>::default(); + + // First pass: compute spills for entry to I (making room for operands) + // + // The max usage in is determined by the size of values currently in W, plus the size + // of any duplicate operands (i.e. values used as operands more than once), as well as + // the size of any operands which must be reloaded. + let max_usage_in = w_used + in_needed; + if max_usage_in > K { + log::trace!(target: "spills", " max usage on entry ({max_usage_in}) exceeds K ({K}), spills required"); + // We must spill enough capacity to keep K >= 16 + let mut must_spill = max_usage_in - K; + // Our initial set of candidates consists of values in W which are not operands + // of the current instruction. + let mut candidates = + w.iter().filter(|o| !args.contains(*o)).copied().collect::>(); + // We order the candidates such that those whose next-use distance is greatest, are + // placed last, and thus will be selected first. We further break ties between + // values with equal next-use distances by ordering them by the + // effective size on the operand stack, so that larger values are + // spilled first. + candidates.sort_by(|a, b| { + let a_dist = liveness.next_use_after(a, op); + let b_dist = liveness.next_use_after(b, op); + a_dist.cmp(&b_dist).then(a.stack_size().cmp(&b.stack_size())) + }); + // Spill until we have made enough room + while must_spill > 0 { + let candidate = candidates.pop().unwrap_or_else(|| { + panic!( + "unable to spill sufficient capacity to hold all operands on stack at one \ + time at {op}" + ) + }); + must_spill = must_spill.saturating_sub(candidate.stack_size()); + to_spill.insert(candidate); + } + } else { + log::trace!(target: "spills", " spills required on entry: no"); + } + + log::trace!(target: "spills", " spills = {to_spill:?}"); + + // Emit spills first, to make space for reloaded values on the operand stack + for spill in to_spill.iter() { + if s.insert(*spill) { + self.spill(place, spill.value(), span); + } + + // Remove spilled values from W + w.remove(spill); + } + + // Emit reloads for those operands of I not yet in W + for reload in to_reload { + // We only need to emit a reload for a given value once + if w.insert(reload) { + // By definition, if we are emitting a reload, the value must have been spilled + s.insert(reload); + self.reload(place, reload.value(), span); + } + } + + // At this point, we have our W^entry and S^entry for `branch` + self.w_entries.insert(before_op, w.clone()); + self.s_entries.insert(before_op, s.clone()); + + log::trace!(target: "spills", " W^entry = {w:?}"); + log::trace!(target: "spills", " S^entry = {s:?}"); + + // PHASE 2: + // + // For each entry successor region, we propagate W and S from `branch` entry to the start + // of each successor region's entry block, updating their contents to reflect the renaming + // of successor arguments now that we're in a new block. + // + // From each entry region, we then visit the region control graph in reverse post-order, + // just like we do unstructured CFGs, propgating W and S along the way. Once all regions + // have been visited, we can move on to the final phase. + + // Compute the constant values for operands of `branch`, in case it allows us to elide + // a subset of successor regions. + let mut operands = SmallVec::<[Option>; 4]>::with_capacity( + branch.operands().group(0).len(), + ); + for operand in branch.operands().group(0).iter() { + let value = operand.borrow().as_value_ref(); + let constant_prop_lattice = liveness.solver().get::, _>(&value); + if let Some(lattice) = constant_prop_lattice { + if lattice.value().is_uninitialized() { + operands.push(None); + continue; + } + operands.push(lattice.value().constant_value()); + } + } + + for successor in branch.get_entry_successor_regions(&operands) { + //let mut w_entry = w.clone(); + //let mut s_entry = s.clone(); + + // Fixup W and S based on successor operands + let branch_point = *successor.branch_point(); + let inputs = branch.get_entry_successor_operands(branch_point); + assert_eq!( + inputs.num_produced(), + 0, + "we don't currently support internally-produced successor operands" + ); + match successor.into_successor() { + Some(region) => { + let region = region.borrow(); + let block = region.entry(); + log::trace!(target: "spills", " processing successor {block}"); + + // Visit the contents of `region` + // + // After this, W and S will have been set/propagated from `block` entry through + // all of its operations, to `block` exit. + // + // TODO(pauls): For now we assume that all regions are single-block + assert!( + region.has_one_block(), + "support for multi-block regions in this pass has not been implemented" + ); + self.visit_region_cfg(branch, &block, liveness, analysis_manager.clone())?; + } + None => { + // TODO(pauls): Need to compute W and S on exit from `branch` as if the exit + // point of `branch` is the entry of a new block, i.e. as computed via + // `compute_w_entry_normal` + log::trace!(target: "spills", " processing self as successor"); + todo!() + } + } + } + + // PHASE 3: + // + // We must compute W and S on exit from `branch` by obtaining the W^exit and S^exit sets + // computed in Phase 2, and handle it much like we do join points in a unstructured CFG. + // + // In this case, the results of `branch` correspond to the arguments yielded from each + // predecessor. So to determine whether any spills/reloads are needed, we must first + // rename the yielded values to the result values, duplicating any of the arguments if the + // referenced value is still live after `branch`. Then, for any values remaining in W that + // are live after `branch`, but not live on exit from all predecessors, we must issue a + // reload for that value. Correspondingly, if there are any values in S which are live after + // `branch`, but not spilled in every predecessor, we must issue a spill for that value. + log::trace!(target: "spills", " computing W^exit for '{}'..", branch.as_operation().name()); + + // First, compute W^exit(branch) + self.compute_w_exit_region_branch_op(branch, liveness); + + // Then, derive S^exit(branch) + let ProgramPointInfo { + w_entry: w_exit, + s_entry: s_exit, + .. + } = self.op_exit_info( + branch, + liveness, + &self.w_exits[&ProgramPoint::after(branch.as_operation())], + ); + + *w = w_exit; + *s = s_exit; + + log::trace!(target: "spills", " W^exit = {w:?}"); + log::trace!(target: "spills", " S^exit = {s:?}"); + + Ok(()) + } + + pub fn set_materialized_split(&mut self, split: Split, block: BlockRef) { + self.splits[split.as_usize()].split = Some(block); + } + + pub fn set_materialized_spill(&mut self, spill: Spill, op: OperationRef) { + self.spills[spill.as_usize()].inst = Some(op); + } + + pub fn set_materialized_reload(&mut self, reload: Reload, op: OperationRef) { + self.reloads[reload.as_usize()].inst = Some(op); + } + + fn spill(&mut self, place: Placement, value: ValueRef, span: SourceSpan) -> Spill { + let id = Spill::new(self.spills.len()); + self.spilled.insert(value); + self.spills.push(SpillInfo { + id, + place, + value, + span, + inst: None, + }); + id + } + + fn reload(&mut self, place: Placement, value: ValueRef, span: SourceSpan) -> Reload { + let id = Reload::new(self.reloads.len()); + self.reloads.push(ReloadInfo { + id, + place, + value, + span, + inst: None, + }); + id + } + + fn split(&mut self, point: ProgramPoint, predecessor: Predecessor) -> Split { + let id = Split::new(self.splits.len()); + self.splits.push(SplitInfo { + id, + point, + predecessor, + split: None, + }); + id + } + + fn compute_w_entry( + &mut self, + op: &Operation, + block: &Block, + loops: Option<&LoopForest>, + liveness: &LivenessAnalysis, + ) { + let block_ref = block.as_block_ref(); + if let Some(loop_info) = + loops.and_then(|loops| loops.loop_for(block_ref).filter(|l| l.header() == block_ref)) + { + log::trace!(target: "spills", "computing W^entry for loop header {block}"); + return self.compute_w_entry_loop(block, &loop_info, liveness); + } else if let Some(loop_like) = op.as_trait::() { + let region = block.parent().unwrap(); + if loop_like.get_loop_header_region() == region && block.is_entry_block() { + log::trace!(target: "spills", "computing W^entry for loop-like header {block}"); + return self.compute_w_entry_loop_like(loop_like, block, liveness); + } + } + + log::trace!(target: "spills", "computing W^entry normally for {block}"); + self.compute_w_entry_normal(op, block, liveness); + } + + fn compute_w_entry_normal( + &mut self, + op: &Operation, + block: &Block, + liveness: &LivenessAnalysis, + ) { + let mut freq = SmallOrdMap::::default(); + let mut take = SmallSet::::default(); + let mut cand = SmallSet::::default(); + + // Block arguments are always in w_entry by definition + for arg in block.arguments().iter().copied() { + take.insert(ValueOrAlias::new(arg as ValueRef)); + } + + // TODO(pauls): We likely need to account for the implicit spilling that occurs when the + // operand stack space required by the function arguments exceeds K. In such cases, the W set + // contains the function parameters up to the first parameter that would cause the operand + // stack to overflow, all subsequent parameters are placed on the advice stack, and are assumed + // to be moved from the advice stack to locals in the same order as they appear in the function + // signature as part of the function prologue. Thus, the S set is preloaded with those values + // which were spilled in this manner. + // + // NOTE: It should never be the case that the set of block arguments consumes more than K + assert!( + take.iter().map(|o| o.stack_size()).sum::() <= K, + "unhandled spills implied by function/block parameter list" + ); + + // If this is the entry block to an IsolatedFromAbove region, the operands in w_entry are + // guaranteed to be equal to the set of region arguments, so we're done. + if block.is_entry_block() && op.implements::() { + self.w_entries.insert(ProgramPoint::at_start_of(block), take); + return; + } + + // If this block is the entry block of a RegionBranchOpInterface op, then we compute the + // set of predecessors differently than unstructured CFG ops. + let mut predecessor_count = 0; + if op.implements::() && block.is_entry_block() { + let predecessors = liveness + .solver() + .get::(&ProgramPoint::at_start_of(block)) + .expect("expected all predecessors of region block to be known"); + assert!( + predecessors.all_predecessors_known(), + "unexpected unresolved region successors" + ); + let operation = op.as_operation_ref(); + for predecessor in predecessors.known_predecessors().iter().copied() { + if predecessor == operation { + predecessor_count += 1; + let end_of_pred = ProgramPoint::before(operation); + for o in self.w_entries[&end_of_pred].iter().copied() { + // Do not add candidates which are not live-after the predecessor + if liveness.is_live_after_entry(o, op) { + *freq.entry(o).or_insert(0) += 1; + cand.insert(o); + } + } + continue; + } + + let predecessor_block = predecessor.parent().unwrap(); + if !liveness.is_block_executable(predecessor_block) { + continue; + } + + predecessor_count += 1; + let end_of_pred = ProgramPoint::at_end_of(predecessor_block); + for o in self.w_exits[&end_of_pred].iter().copied() { + // Do not add candidates which are not live-after the predecessor + if liveness.is_live_at_end(o, predecessor_block) { + *freq.entry(o).or_insert(0) += 1; + cand.insert(o); + } + } + } + } else { + for pred in block.predecessors() { + let predecessor = pred.predecessor(); + + // Skip control edges that aren't executable. + let edge = CfgEdge::new(predecessor, pred.successor(), block.span()); + if !liveness.solver().get::(&edge).is_none_or(|exe| exe.is_live()) { + continue; + } + + predecessor_count += 1; + let end_of_pred = ProgramPoint::at_end_of(predecessor); + for o in self.w_exits[&end_of_pred].iter().copied() { + // Do not add candidates which are not live-after the predecessor + if liveness.is_live_at_end(o, predecessor) { + *freq.entry(o).or_insert(0) += 1; + cand.insert(o); + } + } + } + } + + for (v, count) in freq.iter() { + if *count as usize == predecessor_count { + cand.remove(v); + take.insert(*v); + } + } + + // If there are paths to B containing > K values on the operand stack, this must be due to the + // successor arguments that are renamed on entry to B, remaining live in B, which implicitly + // requires copying so that both the block parameter and the source value are both live in B. + // + // However, in order to actually fail this assertion, it would have to be the case that all + // predecessors of this block are passing the same value as a successor argument, _and_ that the + // value is still live in this block. This would imply that the block parameter is unnecessary + // in the first place. + // + // Since that is extraordinarily unlikely to occur, and we want to catch any situations in which + // this assertion fails, we do not attempt to handle it automatically. + let taken = take.iter().map(|o| o.stack_size()).sum::(); + assert!( + taken <= K, + "implicit operand stack overflow along incoming control flow edges of {block}" + ); + + let entry = ProgramPoint::at_start_of(block); + let entry_next_uses = liveness.next_uses_at(&entry).unwrap(); + + // Prefer to select candidates with the smallest next-use distance, otherwise all else being + // equal, choose to keep smaller values on the operand stack, and spill larger values, thus + // freeing more space when spills are needed. + let mut cand = cand.into_vec(); + cand.sort_by(|a, b| { + entry_next_uses + .distance(a) + .cmp(&entry_next_uses.distance(b)) + .then(a.stack_size().cmp(&b.stack_size())) + }); + + let mut available = K - taken; + let mut cand = cand.into_iter(); + while available > 0 { + if let Some(candidate) = cand.next() { + let size = candidate.stack_size(); + if size <= available { + take.insert(candidate); + available -= size; + continue; + } + } + break; + } + + self.w_entries.insert(ProgramPoint::at_start_of(block), take); + } + + fn compute_w_exit_region_branch_op( + &mut self, + branch: &dyn RegionBranchOpInterface, + liveness: &LivenessAnalysis, + ) { + let mut freq = SmallOrdMap::::default(); + let mut take = SmallSet::::default(); + let mut cand = SmallSet::::default(); + + // Op results are always in W^exit by definition + for result in branch.results().iter().copied() { + take.insert(ValueOrAlias::new(result as ValueRef)); + } + + assert!( + take.iter().map(|o| o.stack_size()).sum::() <= K, + "unhandled spills implied by results of region branch op" + ); + + // If this block is the entry block of a RegionBranchOpInterface op, then we compute the + // set of predecessors differently than unstructured CFG ops. + let mut predecessor_count = 0; + let predecessors = liveness + .solver() + .get::(&ProgramPoint::after(branch.as_operation())) + .expect("expected all predecessors of region exit to be known"); + assert!( + predecessors.all_predecessors_known(), + "unexpected unresolved region predecessors" + ); + let operation = branch.as_operation_ref(); + for predecessor in predecessors.known_predecessors().iter().copied() { + if predecessor == operation { + predecessor_count += 1; + let end_of_pred = ProgramPoint::before(operation); + log::trace!(target: "spills", "examining exit predecessor {end_of_pred}"); + for o in self.w_entries[&end_of_pred].iter().copied() { + // Do not add candidates which are not live-after the predecessor + if liveness.is_live_after_entry(o, branch.as_operation()) { + *freq.entry(o).or_insert(0) += 1; + cand.insert(o); + } + } + continue; + } + + let predecessor_block = predecessor.parent().unwrap(); + if !liveness.is_block_executable(predecessor_block) { + continue; + } + + predecessor_count += 1; + let end_of_pred = ProgramPoint::at_end_of(predecessor_block); + log::trace!(target: "spills", "examining exit predecessor {end_of_pred}"); + for o in self.w_exits[&end_of_pred].iter().copied() { + // Do not add candidates which are not live-after the predecessor + if liveness.is_live_at_end(o, predecessor_block) { + *freq.entry(o).or_insert(0) += 1; + cand.insert(o); + } + } + } + + for (v, count) in freq.iter() { + if *count as usize == predecessor_count { + cand.remove(v); + take.insert(*v); + } + } + + let taken = take.iter().map(|o| o.stack_size()).sum::(); + assert!( + taken <= K, + "implicit operand stack overflow along incoming control flow edges of {}", + ProgramPoint::after(operation) + ); + + let entry = ProgramPoint::after(operation); + let entry_next_uses = liveness.next_uses_at(&entry).unwrap(); + + // Prefer to select candidates with the smallest next-use distance, otherwise all else being + // equal, choose to keep smaller values on the operand stack, and spill larger values, thus + // freeing more space when spills are needed. + let mut cand = cand.into_vec(); + cand.sort_by(|a, b| { + entry_next_uses + .distance(a) + .cmp(&entry_next_uses.distance(b)) + .then(a.stack_size().cmp(&b.stack_size())) + }); + + let mut available = K - taken; + let mut cand = cand.into_iter(); + while available > 0 { + if let Some(candidate) = cand.next() { + let size = candidate.stack_size(); + if size <= available { + take.insert(candidate); + available -= size; + continue; + } + } + break; + } + + self.w_exits.insert(entry, take); + } + + fn compute_w_entry_loop( + &mut self, + block: &Block, + loop_info: &Loop, + liveness: &LivenessAnalysis, + ) { + let entry = ProgramPoint::at_start_of(block); + + let params = block + .arguments() + .iter() + .copied() + .map(|v| v as ValueRef) + .collect::>(); + let mut alive = params.iter().copied().map(ValueOrAlias::new).collect::>(); + + let next_uses = liveness.next_uses_at(&entry).expect("missing liveness for block entry"); + alive.extend(next_uses.iter().filter_map(|v| { + if v.is_live() { + Some(ValueOrAlias::new(v.value)) + } else { + None + } + })); + + log::trace!(target: "spills", " alive at loop entry: {{{}}}", DisplayValues::new(alive.iter())); + + // Initial candidates are values live at block entry which are used in the loop body + let mut cand = alive + .iter() + .filter(|o| next_uses.distance(*o) < LOOP_EXIT_DISTANCE) + .copied() + .collect::>(); + + log::trace!(target: "spills", " initial candidates: {{{}}}", DisplayValues::new(cand.iter())); + + // Values which are "live through" the loop, are those which are live at entry, but not + // used within the body of the loop. If we have excess available operand stack capacity, + // then we can avoid issuing spills/reloads for at least some of these values. + let live_through = alive.difference(&cand); + + log::trace!(target: "spills", " live through loop: {{{}}}", DisplayValues::new(live_through.iter())); + + let w_used = cand.iter().map(|o| o.stack_size()).sum::(); + let max_loop_pressure = max_loop_pressure(loop_info, liveness); + log::trace!(target: "spills", " w_used={w_used}, K={K}, loop_pressure={max_loop_pressure}"); + + if w_used < K { + if let Some(mut free_in_loop) = K.checked_sub(max_loop_pressure) { + let mut live_through = live_through.into_vec(); + live_through.sort_by(|a, b| { + next_uses + .distance(a) + .cmp(&next_uses.distance(b)) + .then(a.stack_size().cmp(&b.stack_size())) + }); + + let mut live_through = live_through.into_iter(); + while free_in_loop > 0 { + if let Some(operand) = live_through.next() { + if let Some(new_free) = free_in_loop.checked_sub(operand.stack_size()) { + if cand.insert(operand) { + free_in_loop = new_free; + } + continue; + } + } + break; + } + } + + self.w_entries.insert(ProgramPoint::at_start_of(block), cand); + } else { + // We require the block parameters to be in W on entry + let mut take = + SmallSet::<_, 4>::from_iter(params.iter().copied().map(ValueOrAlias::new)); + + // So remove them from the set of candidates, then sort remaining by next-use and size + let mut cand = cand.into_vec(); + cand.retain(|o| !params.contains(&o.value())); + cand.sort_by(|a, b| { + next_uses + .distance(a) + .cmp(&next_uses.distance(b)) + .then(a.stack_size().cmp(&b.stack_size())) + }); + + // Fill `take` with as many of the candidates as we can + let mut taken = take.iter().map(|o| o.stack_size()).sum::(); + take.extend(cand.into_iter().take_while(|operand| { + let size = operand.stack_size(); + let new_size = taken + size; + if new_size <= K { + taken = new_size; + true + } else { + false + } + })); + self.w_entries.insert(ProgramPoint::at_start_of(block), take); + } + } + + fn compute_w_entry_loop_like( + &mut self, + loop_like: &dyn LoopLikeOpInterface, + block: &Block, + liveness: &LivenessAnalysis, + ) { + let entry = ProgramPoint::at_start_of(block); + + let params = ValueRange::<4>::from(block.arguments()); + let mut alive = params.iter().map(ValueOrAlias::new).collect::>(); + + let next_uses = liveness.next_uses_at(&entry).expect("missing liveness for block entry"); + alive.extend(next_uses.iter().filter_map(|v| { + if v.is_live() { + Some(ValueOrAlias::new(v.value)) + } else { + None + } + })); + + // Initial candidates are values live at block entry which are used in the loop body + let mut cand = alive + .iter() + .filter(|o| next_uses.distance(*o) < LOOP_EXIT_DISTANCE) + .copied() + .collect::>(); + + // Values which are "live through" the loop, are those which are live at entry, but not + // used within the body of the loop. If we have excess available operand stack capacity, + // then we can avoid issuing spills/reloads for at least some of these values. + let live_through = alive.difference(&cand); + + let w_used = cand.iter().map(|o| o.stack_size()).sum::(); + if w_used < K { + if let Some(mut free_in_loop) = + K.checked_sub(max_loop_pressure_loop_like(loop_like, liveness)) + { + let mut live_through = live_through.into_vec(); + live_through.sort_by(|a, b| { + next_uses + .distance(a) + .cmp(&next_uses.distance(b)) + .then(a.stack_size().cmp(&b.stack_size())) + }); + + let mut live_through = live_through.into_iter(); + while free_in_loop > 0 { + if let Some(operand) = live_through.next() { + if let Some(new_free) = free_in_loop.checked_sub(operand.stack_size()) { + if cand.insert(operand) { + free_in_loop = new_free; + } + continue; + } + } + break; + } + } + + self.w_entries.insert(ProgramPoint::at_start_of(block), cand); + } else { + // We require the block parameters to be in W on entry + let mut take = SmallSet::<_, 4>::from_iter(params.iter().map(ValueOrAlias::new)); + + // So remove them from the set of candidates, then sort remaining by next-use and size + let mut cand = cand.into_vec(); + cand.retain(|o| !params.contains(o)); + cand.sort_by(|a, b| { + next_uses + .distance(a) + .cmp(&next_uses.distance(b)) + .then(a.stack_size().cmp(&b.stack_size())) + }); + + // Fill `take` with as many of the candidates as we can + let mut taken = take.iter().map(|o| o.stack_size()).sum::(); + take.extend(cand.into_iter().take_while(|operand| { + let size = operand.stack_size(); + let new_size = taken + size; + if new_size <= K { + taken = new_size; + true + } else { + false + } + })); + self.w_entries.insert(ProgramPoint::at_start_of(block), take); + } + } + + fn block_entry_info( + &self, + op: &Operation, + block: &Block, + liveness: &LivenessAnalysis, + w_entry: SmallSet, + ) -> ProgramPointInfo { + let mut info = ProgramPointInfo { + point: ProgramPoint::at_start_of(block), + w_entry, + s_entry: Default::default(), + live_predecessors: Default::default(), + }; + + // Compute S^entry(B) and live predecessors + // + // NOTE: If `block` is the entry block of a nested region of `op`, and `op` implements + // RegionBranchOpInterface, then derive predecessor state using the information that we + // know is attached to live predecessor edges of `block` + if let Some(branch) = + op.as_trait::().filter(|_| block.is_entry_block()) + { + let predecessors = liveness + .solver() + .get::(&info.point) + .expect("expected all predecessors of region block to be known"); + assert!( + predecessors.all_predecessors_known(), + "unexpected unresolved region successors" + ); + let branch_op = branch.as_operation().as_operation_ref(); + for predecessor in predecessors.known_predecessors() { + // Is `predecessor` the operation itself? Fetch the computed S attached to before + // the branch op + if predecessor == &branch_op { + info.live_predecessors.push(Predecessor::Parent); + if let Some(s_in) = self.s_entries.get(&ProgramPoint::before(branch_op)) { + info.s_entry = info.s_entry.into_union(s_in); + } + continue; + } + + info.live_predecessors.push(Predecessor::Region(*predecessor)); + + // Merge in the state from the predecessor's terminator. + let pred_block = predecessor.parent().unwrap(); + if let Some(s_out) = self.s_exits.get(&ProgramPoint::at_end_of(pred_block)) { + info.s_entry = info.s_entry.into_union(s_out); + } + } + } else { + for pred in block.predecessors() { + let predecessor = pred.predecessor(); + + // Skip control edges that aren't executable. + let edge = CfgEdge::new(predecessor, pred.successor(), block.span()); + if !liveness.solver().get::(&edge).is_none_or(|exe| exe.is_live()) { + continue; + } + + info.live_predecessors.push(Predecessor::Block { + op: pred.owner, + index: pred.index, + }); + + if let Some(s_exitp) = self.s_exits.get(&ProgramPoint::at_end_of(predecessor)) { + info.s_entry = info.s_entry.into_union(s_exitp); + } + } + } + + info.s_entry = info.s_entry.into_intersection(&info.w_entry); + + info + } + + fn op_exit_info( + &self, + branch: &dyn RegionBranchOpInterface, + liveness: &LivenessAnalysis, + w_entry: &SmallSet, + ) -> ProgramPointInfo { + let mut info = ProgramPointInfo { + point: ProgramPoint::after(branch.as_operation()), + w_entry: w_entry.clone(), + s_entry: Default::default(), + live_predecessors: Default::default(), + }; + + // Compute S^entry(B) and live predecessors + // + // NOTE: If `block` is the entry block of a nested region of `op`, and `op` implements + // RegionBranchOpInterface, then derive predecessor state using the information that we + // know is attached to live predecessor edges of `block` + let predecessors = liveness + .solver() + .get::(&info.point) + .expect("expected all predecessors of region block to be known"); + assert!(predecessors.all_predecessors_known(), "unexpected unresolved region successors"); + let branch_op = branch.as_operation().as_operation_ref(); + for predecessor in predecessors.known_predecessors() { + // Is `predecessor` the operation itself? Fetch the computed S attached to before + // the branch op + if predecessor == &branch_op { + info.live_predecessors.push(Predecessor::Parent); + let s_in = &self.s_entries[&ProgramPoint::before(branch_op)]; + info.s_entry = info.s_entry.into_union(s_in); + continue; + } + + info.live_predecessors.push(Predecessor::Region(*predecessor)); + + // Merge in the state from the predecessor's terminator. + let pred_block = predecessor.parent().unwrap(); + let s_out = &self.s_exits[&ProgramPoint::at_end_of(pred_block)]; + + info.s_entry = info.s_entry.into_union(s_out); + } + + info.s_entry = info.s_entry.into_intersection(&info.w_entry); + + info + } +} + +/// Compute the maximum operand stack depth required within the body of the given loop. +/// +/// If the stack depth never reaches K, the excess capacity represents an opportunity to +/// avoid issuing spills/reloads for values which are live through the loop. +fn max_loop_pressure(loop_info: &Loop, liveness: &LivenessAnalysis) -> usize { + let header = loop_info.header(); + let mut max = max_block_pressure(&header.borrow(), liveness); + let mut block_q = VecDeque::from_iter([header]); + let mut visited = SmallSet::::default(); + + log::trace!(target: "spills", "computing max pressure for loop headed by {header}"); + + while let Some(block) = block_q.pop_front() { + if !visited.insert(block) { + continue; + } + + let children = BlockRef::children(block).collect::>(); + log::trace!(target: "spills", " children of {block}: {children:?}"); + let loop_children = children + .iter() + .filter(|b| loop_info.contains_block(**b)) + .copied() + .collect::>(); + log::trace!(target: "spills", " children in loop: {loop_children:?}"); + block_q.extend(loop_children); + + let max_block_pressure = max_block_pressure(&block.borrow(), liveness); + log::trace!(target: "spills", " block {block} pressure = {max_block_pressure}"); + max = core::cmp::max(max, max_block_pressure); + } + + log::trace!(target: "spills", " max loop pressure = {max}"); + max +} + +fn max_loop_pressure_loop_like( + loop_like: &dyn LoopLikeOpInterface, + liveness: &LivenessAnalysis, +) -> usize { + let mut max_pressure = 0; + let mut visited = SmallSet::<_, 4>::default(); + for region in loop_like.get_loop_regions() { + if !visited.insert(region) { + continue; + } + Region::traverse_region_graph(®ion.borrow(), |region, _| { + if !visited.insert(region.as_region_ref()) { + return true; + } + let region_entry = region.entry(); + max_pressure = + core::cmp::max(max_pressure, max_block_pressure(®ion_entry, liveness)); + false + }); + } + max_pressure +} + +/// Compute the maximum operand stack pressure for `block`, using `liveness` +fn max_block_pressure(block: &Block, liveness: &LivenessAnalysis) -> usize { + let mut max_pressure = 0; + + let live_in = liveness.next_uses_at(&ProgramPoint::at_start_of(block)); + if let Some(live_in) = live_in { + for v in live_in.live() { + max_pressure += v.borrow().ty().size_in_felts(); + } + } + + let mut operands = SmallVec::<[ValueRef; 8]>::default(); + for op in block.body() { + operands.clear(); + operands.extend(op.operands().all().iter().map(|v| v.borrow().as_value_ref())); + + let mut live_in_pressure = 0; + let mut relief = 0usize; + let live_in = liveness.next_uses_at(&ProgramPoint::before(&*op)); + let live_out = liveness.next_uses_at(&ProgramPoint::after(&*op)); + if let Some(live_in) = live_in { + for live in live_in.live() { + if operands.contains(&live) { + continue; + } + if live_out + .as_ref() + .is_none_or(|live_out| live_out.get(live).is_none_or(|v| !v.is_live())) + { + continue; + } + live_in_pressure += live.borrow().ty().size_in_felts(); + } + } + for operand in operands.iter() { + let size = operand.borrow().ty().size_in_felts(); + if live_out + .as_ref() + .is_none_or(|live_out| live_out.get(operand).is_none_or(|v| !v.is_live())) + { + relief += size; + } + live_in_pressure += size; + } + let mut result_pressure = 0usize; + for result in op.results().all() { + result_pressure += result.borrow().ty().size_in_felts(); + } + + live_in_pressure += result_pressure.saturating_sub(relief); + max_pressure = core::cmp::max(max_pressure, live_in_pressure); + + // Visit any nested regions and ensure that the maximum pressure in those regions is taken + // into account + if let Some(loop_like) = op.as_trait::() { + max_pressure = + core::cmp::max(max_pressure, max_loop_pressure_loop_like(loop_like, liveness)); + } else if let Some(branch_op) = op.as_trait::() { + let mut visited = SmallSet::<_, 4>::default(); + for region in branch_op.get_successor_regions(RegionBranchPoint::Parent) { + if let Some(region) = region.into_successor() { + if !visited.insert(region) { + continue; + } + Region::traverse_region_graph(®ion.borrow(), |region, _| { + if !visited.insert(region.as_region_ref()) { + return true; + } + let region_entry = region.entry(); + max_pressure = core::cmp::max( + max_pressure, + max_block_pressure(®ion_entry, liveness), + ); + false + }); + } + } + } + } + + max_pressure +} + +impl SpillAnalysis { + /// At join points in the control flow graph, the set of live and spilled values may, and likely + /// will, differ depending on which predecessor is taken to reach it. We must ensure that for + /// any given predecessor: + /// + /// * Spills are inserted for any values expected in S upon entry to the successor block, which have + /// not been spilled yet. This occurs when a spill is needed in some predecessor, but not in + /// another, thus we must make sure the spill slot is written to at join points. + /// * Reloads are inserted for any values expected in W upon entry to the successor block, which are + /// not in W yet. This occurs when a value is spilled on the path taken through a given + /// predecessor, and hasn't been reloaded again, thus we need to reload it now. + /// + /// NOTE: We are not actually mutating the function and inserting instructions here. Instead, we + /// are computing what instructions need to be inserted, and where, as part of the analysis. A + /// rewrite pass can then apply the analysis results to the function, if desired. + fn compute_control_flow_edge_spills_and_reloads( + &mut self, + info: &ProgramPointInfo, + pred: &Predecessor, + deferred: &mut SmallVec<[BlockRef; 2]>, + liveness: &LivenessAnalysis, + ) { + // Select the appropriate predecessor program point for the point represented by `info`, + // and then obtain W^exit(P). + // + // If we don't have W^exit(P) yet, then P hasn't been processed yet. This is permitted for + // intra-region control flow, but not inter-region control flow. + let (w_exitp, s_exitp) = match pred.block() { + // The predecessor is either another block in the same region, or cross-region control + // flow from a block in a sibling region. + Some(predecessor) => { + let end_of_pred = ProgramPoint::at_end_of(predecessor); + let Some(w_exitp) = self.w_exits.get(&end_of_pred) else { + // We expect both predecessor and successor to be in the same region if we do + // not yet have W^exit(P) available, in which case we defer processing of this + // program point. + /* + let successor = info.point.block().unwrap(); + assert_eq!( + successor.parent(), + predecessor.parent(), + "expected w_exitp to be computed already for cross-region control flow" + ); + */ + deferred.push(predecessor); + return; + }; + let s_exitp = &self.s_exits[&end_of_pred]; + (w_exitp, s_exitp) + } + // The predecessor is the operation itself, but whether the edge we're visiting is + // entering the operation, or exiting, is determined by `info` + None => { + let end_of_pred = match info.point { + // The predecessor is `op` itself in a scenario where control has skipped all of + // `op`'s nested regions. + ProgramPoint::Op { op, .. } => ProgramPoint::before(op), + // The predecessor is `op` itself on entry to `block` + ProgramPoint::Block { block, .. } => { + ProgramPoint::before(block.grandparent().unwrap()) + } + _ => unreachable!(), + }; + // By definition we must have already visited before(op) + let w_exitp = &self.w_entries[&end_of_pred]; + let s_exitp = &self.s_entries[&end_of_pred]; + (w_exitp, s_exitp) + } + }; + + let mut to_reload = info.w_entry.difference(w_exitp); + let mut to_spill = info.s_entry.difference(s_exitp).into_intersection(w_exitp); + + // We need to issue spills for any items in W^exit(P) / W^entry(B) that are not in S^exit(P), + // but are live-after P. + // + // This can occur when B is a loop header, and the computed W^entry(B) does not include values + // in W^exit(P) that are live-through the loop, typically because of loop pressure within the + // loop requiring us to place spills of those values outside the loop. + let must_spill = w_exitp.difference(&info.w_entry).into_difference(s_exitp); + let next_uses = liveness + .next_uses_at(&info.point) + .expect("missing liveness info for program point"); + to_spill.extend(must_spill.into_iter().filter(|o| next_uses.is_live(o))); + + // We expect any block parameters present to be in `to_reload` at this point, as they will never + // be in W^exit(P) (the parameters are not in scope at the end of P). The arguments provided in + // the predecessor corresponding to the block parameters may or may not be in W^exit(P), so we + // must determine which of those values need to be reloaded, and whether or not to spill any of + // them so that there is sufficient room in W to hold all the block parameters. Spills may be + // needed for two reasons: + // + // 1. There are multiple predecessors, and we need to spill a value to ensure it is spilled on + // all paths to the current block + // + // 2. An argument corresponding to a block parameter for this block is still live in/through + // this block. Due to values being renamed when used as block arguments, we must ensure there + // is a new copy of the argument so that the original value, and the renamed alias, are both + // live simultaneously. If there is insufficient operand stack space to accommodate both, + // then we must spill values from W to make room. + // + // So in short, we post-process `to_reload` by matching any values in the set which are block + // parameters, with the corresponding source values in W^exit(P) (issuing reloads if the value + // given as argument in the predecessor is not in W^exit(P)) + let pred_args = pred.arguments(info.point); + + match &info.point { + // Remove block params from `to_reload`, and replace them, as needed, with reloads of the value + // in the predecessor which was used as the successor argument + ProgramPoint::Block { block, .. } => { + for (i, param) in block.borrow().arguments().iter().enumerate() { + let param = *param as ValueRef; + to_reload.remove(¶m); + // Match up this parameter with its source argument, and if the source value is not in + // W^exit(P), then a reload is needed + let src = pred_args.get(i).unwrap_or_else(|| { + panic!("index {i} is out of bounds: len is {}", pred_args.len()) + }); + if !w_exitp.contains(&src) { + to_reload.insert(ValueOrAlias::new(src)); + } + } + } + // Remove op results from `to_reload`, and replace them, as needed, with reloads of the + // value in the predecessor which was used as the successor argument + ProgramPoint::Op { op: _, .. } => { + todo!() + } + _ => unreachable!(), + } + + // If there are no reloads or spills needed, we're done + if to_reload.is_empty() && to_spill.is_empty() { + return; + } + + // Otherwise, we need to split the edge from P to B, and place any spills/reloads in the split, + // S, moving any block arguments for B, to the unconditional branch in S. + let split = self.split(info.point, *pred); + let place = Placement::Split(split); + let span = pred.operation(info.point).span(); + + // Insert spills first, to end the live ranges of as many variables as possible + for spill in to_spill { + self.spill(place, spill.value(), span); + } + + // Then insert needed reloads + for reload in to_reload { + self.reload(place, reload.value(), span); + } + } + + /// The MIN algorithm is used to compute the spills and reloads to insert at each instruction in a + /// block, so as to ensure that there is sufficient space to hold all instruction operands and + /// results without exceeding K elements on the operand stack simultaneously, and allocating spills + /// so as to minimize the number of live ranges needing to be split. + /// + /// MIN will spill values with the greatest next-use distance first, using the size of the operand + /// as a tie-breaker for values with equidistant next uses (i.e. the larger values get spilled + /// first, thus making more room on the operand stack). + /// + /// It is expected that upon entry to a given block, that the W and S sets are accurate, regardless + /// of which predecessor edge was used to reach the block. This is handled earlier during analysis + /// by computing the necessary spills and reloads to be inserted along each control flow edge, as + /// required. + fn min( + &mut self, + op: &Operation, + w: &mut SmallSet, + s: &mut SmallSet, + liveness: &LivenessAnalysis, + ) { + let before_op = ProgramPoint::before(op); + let place = Placement::At(before_op); + let span = op.span(); + + log::trace!(target: "spills", "scheduling spills/reloads at {before_op}"); + log::trace!(target: "spills", " W^entry = {w:?}"); + log::trace!(target: "spills", " S^entry = {s:?}"); + + let is_terminator = op.implements::() + && !(op.implements::() + || op.implements::()); + + if is_terminator { + log::trace!(target: "spills", " terminator = true"); + // A non-branching terminator is either a return, or an unreachable. + // In the latter case, there are no operands or results, so there is no + // effect on W or S In the former case, the operands to the instruction are + // the "results" from the perspective of the operand stack, so we are simply + // ensuring that those values are in W by issuing reloads as necessary, all + // other values are dead, so we do not actually issue any spills. + w.retain(|o| liveness.is_live_before(o, op)); + let to_reload = ValueRange::<4>::from(op.operands().all()); + for reload in to_reload.into_iter().map(ValueOrAlias::new) { + if w.insert(reload) { + log::trace!(target: "spills", " emitting reload for {reload}"); + self.reload(place, reload.value(), span); + } + } + + log::trace!(target: "spills", " W^exit = {w:?}"); + log::trace!(target: "spills", " S^exit = {s:?}"); + return; + } + + // All other instructions are handled more or less identically according to the effects + // an instruction has, as described in the documentation of the MIN algorithm. + // + // In the case of branch instructions, successor arguments are not considered inputs to + // the instruction. Instead, we handle spills/reloads for each control flow edge + // independently, as if they occur on exit from this instruction. The result is that + // we may or may not have all successor arguments in W on exit from I, but by the time + // each successor block is reached, all block parameters are guaranteed to be in W + let args = ValueRange::<4>::from(op.operands().group(0)); + let mut to_reload = args.iter().map(ValueOrAlias::new).collect::>(); + + // Remove the first occurrance of any operand already in W, remaining uses + // must be considered against the stack usage calculation (but will not + // actually be reloaded) + for operand in w.iter() { + if let Some(pos) = to_reload.iter().position(|o| o == operand) { + to_reload.swap_remove(pos); + } + } + + // Precompute the starting stack usage of W + let w_used = w.iter().map(|o| o.stack_size()).sum::(); + + // Compute the needed operand stack space for all operands not currently + // in W, i.e. those which must be reloaded from a spill slot + let in_needed = to_reload.iter().map(|o| o.stack_size()).sum::(); + + // Compute the needed operand stack space for results of I + let results = ValueRange::<2>::from(op.results().all()); + let out_needed = results.iter().map(|v| v.borrow().ty().size_in_felts()).sum::(); + + // Compute the amount of operand stack space needed for operands which are + // not live across the instruction, i.e. which do not consume stack space + // concurrently with the results. + let in_consumed = args + .iter() + .filter_map(|v| { + if liveness.is_live_after(v, op) { + None + } else { + Some(v.borrow().ty().size_in_felts()) + } + }) + .sum::(); + + log::trace!(target: "spills", " results = {results}"); + log::trace!(target: "spills", " require copy/reload = {to_reload:?}"); + log::trace!(target: "spills", " current stack usage = {w_used}"); + log::trace!(target: "spills", " required by reloads = {in_needed}"); + log::trace!(target: "spills", " required by results = {out_needed}"); + log::trace!(target: "spills", " freed by op = {in_consumed}"); + + // If we have room for operands and results in W, then no spills are needed, + // otherwise we require two passes to compute the spills we will need to issue + let mut to_spill = SmallSet::<_, 4>::default(); + + // First pass: compute spills for entry to I (making room for operands) + // + // The max usage in is determined by the size of values currently in W, plus the size + // of any duplicate operands (i.e. values used as operands more than once), as well as + // the size of any operands which must be reloaded. + let max_usage_in = w_used + in_needed; + if max_usage_in > K { + log::trace!(target: "spills", "max usage on entry ({max_usage_in}) exceeds K ({K}), spills required"); + // We must spill enough capacity to keep K >= 16 + let mut must_spill = max_usage_in - K; + // Our initial set of candidates consists of values in W which are not operands + // of the current instruction. + let mut candidates = + w.iter().filter(|o| !args.contains(*o)).copied().collect::>(); + // We order the candidates such that those whose next-use distance is greatest, are + // placed last, and thus will be selected first. We further break ties between + // values with equal next-use distances by ordering them by the + // effective size on the operand stack, so that larger values are + // spilled first. + candidates.sort_by(|a, b| { + let a_dist = liveness.next_use_after(a, op); + let b_dist = liveness.next_use_after(b, op); + a_dist.cmp(&b_dist).then(a.stack_size().cmp(&b.stack_size())) + }); + // Spill until we have made enough room + while must_spill > 0 { + let candidate = candidates.pop().unwrap_or_else(|| { + panic!( + "unable to spill sufficient capacity to hold all operands on stack at one \ + time at {op}" + ) + }); + must_spill = must_spill.saturating_sub(candidate.stack_size()); + to_spill.insert(candidate); + } + } else { + log::trace!(target: "spills", " spills required on entry: no"); + } + + // Second pass: compute spills for exit from I (making room for results) + let spilled = to_spill.iter().map(|o| o.stack_size()).sum::(); + log::trace!(target: "spills", " freed by spills = {spilled}"); + // The max usage out is computed by adding the space required for all results of I, to + // the max usage in, then subtracting the size of all operands which are consumed by I, + // as well as the size of those values in W which we have spilled. + let max_usage_out = (max_usage_in + out_needed).saturating_sub(in_consumed + spilled); + if max_usage_out > K { + log::trace!(target: "spills", "max usage on exit ({max_usage_out}) exceeds K ({K}), additional spills required"); + // We must spill enough capacity to keep K >= 16 + let mut must_spill = max_usage_out - K; + // For this pass, the set of candidates consists of values in W which are not + // operands of I, and which have not been spilled yet, as well as values in W + // which are operands of I that are live-after I. The latter group may sound + // contradictory, how can you spill something before it is used? However, what + // is actually happening is that we spill those values before I, so that we + // can treat those values as being "consumed" by I, such that their space in W + // can be reused by the results of I. + let mut candidates = w + .iter() + .filter(|o| { + if !args.contains(*o) { + // Not an argument, not yet spilled + !to_spill.contains(*o) + } else { + // A spillable argument + liveness.is_live_after(*o, op) + } + }) + .cloned() + .collect::>(); + candidates.sort_by(|a, b| { + let a_dist = liveness.next_use_after(a, op); + let b_dist = liveness.next_use_after(b, op); + a_dist.cmp(&b_dist).then(a.stack_size().cmp(&b.stack_size())) + }); + while must_spill > 0 { + let candidate = candidates.pop().unwrap_or_else(|| { + panic!( + "unable to spill sufficient capacity to hold all operands on stack at one \ + time at {op}" + ) + }); + // If we're spilling an operand of I, we can multiple the amount of space + // freed by the spill by the number of uses of the spilled value in I + let num_uses = + core::cmp::max(1, args.iter().filter(|v| *v == candidate.value()).count()); + let freed = candidate.stack_size() * num_uses; + must_spill = must_spill.saturating_sub(freed); + to_spill.insert(candidate); + } + } else { + log::trace!(target: "spills", " spills required on exit: no"); + } + + // Emit spills first, to make space for reloaded values on the operand stack + for spill in to_spill.iter() { + if s.insert(*spill) { + log::trace!(target: "spills", "emitting spill for {spill}"); + self.spill(place, spill.value(), span); + } + + // Remove spilled values from W + w.remove(spill); + } + + // Emit reloads for those operands of I not yet in W + for reload in to_reload { + // We only need to emit a reload for a given value once + if w.insert(reload) { + log::trace!(target: "spills", "emitting reload for {reload}"); + // By definition, if we are emitting a reload, the value must have been spilled + s.insert(reload); + self.reload(place, reload.value(), span); + } + } + + // At this point, we've emitted our spills/reloads, so we need to prepare W for the next + // instruction by applying the effects of the instruction to W, i.e. consuming those + // operands which are consumed, and adding instruction results. + // + // First, we remove operands from W which are no longer live-after I, _except_ any + // which are used as successor arguments. This is because we must know which successor + // arguments are still in W at the block terminator when we are computing what to spill + // or reload along each control flow edge. + // + // Second, if applicable, we add in the instruction results + log::trace!(target: "spills", " applying effects of operation to W.."); + if let Some(branch) = op.as_trait::() { + log::trace!(target: "spills", " op is a control flow branch, attempting to resolve a single successor"); + // Try to determine if we can select a single successor here + let mut operands = SmallVec::<[Option>; 4]>::with_capacity( + op.operands().group(0).len(), + ); + for operand in op.operands().group(0).iter() { + let value = operand.borrow().as_value_ref(); + let constant_prop_lattice = + liveness.solver().get::, _>(&value); + if let Some(lattice) = constant_prop_lattice { + if lattice.value().is_uninitialized() { + operands.push(None); + continue; + } + operands.push(lattice.value().constant_value()); + } + } + + if let Some(succ) = branch.get_successor_for_operands(&operands) { + log::trace!(target: "spills", " resolved single succeessor {}", succ.successor()); + w.retain(|o| { + op.operands() + .group(succ.successor_operand_group()) + .iter() + .any(|arg| arg.borrow().as_value_ref() == o.value()) + || liveness.is_live_after(o, op) + }); + } else { + let successor_operand_groups = op + .successors() + .iter() + .filter_map(|s| { + let successor = s.successor(); + if liveness.is_block_executable(successor) { + Some(s.successor_operand_group()) + } else { + None + } + }) + .collect::>(); + log::trace!(target: "spills", " resolved {} successors", successor_operand_groups.len()); + w.retain(|o| { + let is_succ_arg = successor_operand_groups.iter().copied().any(|succ| { + op.operands() + .group(succ) + .iter() + .any(|arg| arg.borrow().as_value_ref() == o.value()) + }); + is_succ_arg || liveness.is_live_after(o, op) + }); + } + } else { + log::trace!(target: "spills", " '{}' is a primitive operation", op.name()); + + // This is a simple operation + log::trace!(target: "spills", " removing dead operands from W"); + w.retain(|o| liveness.is_live_after(o, op)); + log::trace!(target: "spills", " adding results to W"); + w.extend(results.iter().map(ValueOrAlias::new)); + } + + log::trace!(target: "spills", " W^exit = {w:?}"); + log::trace!(target: "spills", " S^exit = {s:?}"); + } +} diff --git a/hir-analysis/src/analyses/spills/expected/spills_branch_cfg.hir b/hir-analysis/src/analyses/spills/expected/spills_branch_cfg.hir new file mode 100644 index 000000000..3453dc4c8 --- /dev/null +++ b/hir-analysis/src/analyses/spills/expected/spills_branch_cfg.hir @@ -0,0 +1,31 @@ +public builtin.function @test::spill_branch(v0: ptr) -> u32 { +^block1(v0: ptr): + v1 = hir.ptr_to_int v0 : u32; + v2 = arith.constant 32 : u32; + v3 = arith.add v1, v2 : u32 #[overflow = unchecked]; + v4 = hir.int_to_ptr v3 : ptr; + v5 = hir.load v4 : u128; + v6 = arith.constant 64 : u32; + v7 = arith.add v1, v6 : u32 #[overflow = unchecked]; + v8 = hir.int_to_ptr v7 : ptr; + v9 = hir.load v8 : u128; + v10 = arith.constant 0 : u32; + v11 = arith.eq v1, v10 : i1; + cf.cond_br v11 ^block2, ^block3; +^block2: + v12 = arith.constant 1 : u64; + v13 = hir.exec @test/example(v8, v5, v9, v9, v12) : u32 + cf.br ^block4(v13); +^block3: + v14 = arith.constant 8 : u32; + v15 = arith.add v1, v14 : u32 #[overflow = unchecked]; + cf.br ^block4(v15); +^block4(v16: u32): + v17 = arith.constant 72 : u32; + v18 = arith.add v1, v17 : u32 #[overflow = unchecked]; + v19 = arith.add v18, v16 : u32 #[overflow = unchecked]; + v20 = hir.int_to_ptr v19 : ptr; + hir.store v4, v9; + v21 = hir.load v20 : u64; + builtin.ret v3; +}; \ No newline at end of file diff --git a/hir-analysis/src/analyses/spills/expected/spills_intra_block.hir b/hir-analysis/src/analyses/spills/expected/spills_intra_block.hir new file mode 100644 index 000000000..786ce807e --- /dev/null +++ b/hir-analysis/src/analyses/spills/expected/spills_intra_block.hir @@ -0,0 +1,20 @@ +public builtin.function @test::spill(v0: ptr) -> u32 { +^block1(v0: ptr): + v1 = hir.ptr_to_int v0 : u32; + v2 = arith.constant 32 : u32; + v3 = arith.add v1, v2 : u32 #[overflow = unchecked]; + v4 = hir.int_to_ptr v3 : ptr; + v5 = hir.load v4 : u128; + v6 = arith.constant 64 : u32; + v7 = arith.add v1, v6 : u32 #[overflow = unchecked]; + v8 = hir.int_to_ptr v7 : ptr; + v9 = hir.load v8 : u128; + v10 = arith.constant 1 : u64; + v11 = hir.exec @test/example(v8, v5, v9, v9, v10) : u32 + v12 = arith.constant 72 : u32; + v13 = arith.add v1, v12 : u32 #[overflow = unchecked]; + hir.store v4, v9; + v14 = hir.int_to_ptr v13 : ptr; + v15 = hir.load v14 : u64; + builtin.ret v3; +}; \ No newline at end of file diff --git a/hir-analysis/src/analyses/spills/expected/spills_loop_nest.hir b/hir-analysis/src/analyses/spills/expected/spills_loop_nest.hir new file mode 100644 index 000000000..2cb3b3d02 --- /dev/null +++ b/hir-analysis/src/analyses/spills/expected/spills_loop_nest.hir @@ -0,0 +1,42 @@ +public builtin.function @test::spill_loop(v0: ptr, v1: u32, v2: u32) -> u64 { +^block1(v0: ptr, v1: u32, v2: u32): + v3 = arith.constant 0 : u32; + v4 = arith.constant 0 : u32; + v5 = arith.constant 0 : u64; + cf.br ^block2(v3, v4, v5); +^block2(v6: u32, v7: u32, v8: u64): + v9 = arith.eq v6, v1 : i1; + cf.cond_br v9 ^block3, ^block4; +^block3: + builtin.ret v8; +^block4: + cf.br ^block5(v7, v8); +^block5(v10: u32, v11: u64): + v12 = arith.eq v10, v2 : i1; + cf.cond_br v12 ^block6(v10, v11), ^block7; +^block6(v13: u32, v14: u64): + v15 = arith.constant 1 : u32; + v16 = arith.add v6, v15 : u32 #[overflow = unchecked]; + cf.br ^block2(v16, v13, v14); +^block7: + v17 = arith.constant 1 : u32; + v18 = arith.sub v6, v17 : u32 #[overflow = unchecked]; + v19 = arith.mul v18, v2 : u32 #[overflow = unchecked]; + v20 = arith.add v10, v19 : u32 #[overflow = unchecked]; + v21 = hir.ptr_to_int v0 : u32; + v22 = arith.add v21, v20 : u32 #[overflow = unchecked]; + v23 = hir.int_to_ptr v22 : ptr; + v24 = hir.load v23 : u64; + v25 = arith.constant 1 : u64; + v26 = arith.constant 2 : u64; + v27 = arith.constant 3 : u64; + v28 = arith.constant 4 : u64; + v29 = arith.constant 5 : u64; + v30 = arith.constant 6 : u64; + v31 = arith.constant 7 : u64; + v32 = hir.exec @test/example(v23, v24, v25, v26, v27, v28, v29, v30) : u32 + v33 = arith.add v11, v24 : u64 #[overflow = unchecked]; + v34 = arith.constant 1 : u32; + v35 = arith.add v10, v34 : u32 #[overflow = unchecked]; + cf.br ^block5(v35, v33); +}; \ No newline at end of file diff --git a/hir-analysis/src/analyses/spills/tests.rs b/hir-analysis/src/analyses/spills/tests.rs new file mode 100644 index 000000000..6385c5265 --- /dev/null +++ b/hir-analysis/src/analyses/spills/tests.rs @@ -0,0 +1,610 @@ +use alloc::{rc::Rc, sync::Arc}; +use std::string::ToString; + +use midenc_dialect_arith::ArithOpBuilder as Arith; +use midenc_dialect_cf::ControlFlowOpBuilder as Cf; +use midenc_dialect_hir::HirOpBuilder; +use midenc_expect_test::expect_file; +use midenc_hir::{ + dialects::builtin::{BuiltinOpBuilder, Function, FunctionBuilder}, + pass::AnalysisManager, + AbiParam, AddressSpace, BlockRef, Builder, Context, Ident, Op, OpBuilder, PointerType, + ProgramPoint, Report, Signature, SourceSpan, SymbolTable, Type, ValueRef, +}; + +use crate::analyses::{ + spills::{Predecessor, Split}, + SpillAnalysis, +}; + +type AnalysisResult = Result; + +/// In this test, we force several values to be live simultaneously inside the same block, +/// of sufficient size on the operand stack so as to require some of them to be spilled +/// at least once, and later reloaded. +/// +/// The purpose here is to validate the MIN algorithm that determines whether or not we need +/// to spill operands at each program point, in the following ways: +/// +/// * Ensure that we spill values we expect to be spilled +/// * Ensure that spills are inserted at the appropriate locations +/// * Ensure that we reload values that were previously spilled +/// * Ensure that reloads are inserted at the appropriate locations +/// +/// The following HIR is constructed for this test: +/// +/// * `in` indicates the set of values in W at an instruction, with reloads included +/// * `out` indicates the set of values in W after an instruction, with spills excluded +/// * `spills` indicates the candidates from W which were selected to be spilled at the +/// instruction +/// * `reloads` indicates the set of values in S which must be reloaded at the instruction +/// +/// ```text,ignore +/// (func (export #spill) (param (ptr u8)) (result u32) +/// (block 0 (param v0 (ptr u8)) +/// (let (v1 u32) (ptrtoint v0)) ; in=[v0] out=[v1] +/// (let (v2 u32) (add v1 32)) ; in=[v1] out=[v1 v2] +/// (let (v3 (ptr u128)) (inttoptr v2)) ; in=[v1 v2] out=[v1 v2 v3] +/// (let (v4 u128) (load v3)) ; in=[v1 v2 v3] out=[v1 v2 v3 v4] +/// (let (v5 u32) (add v1 64)) ; in=[v1 v2 v3 v4] out=[v1 v2 v3 v4 v5] +/// (let (v6 (ptr u128)) (inttoptr v5)) ; in=[v1 v2 v3 v4 v5] out=[v1 v2 v3 v4 v6] +/// (let (v7 u128) (load v6)) ; in=[v1 v2 v3 v4 v6] out=[v1 v2 v3 v4 v6 v7] +/// (let (v8 u64) (const.u64 1)) ; in=[v1 v2 v3 v4 v6 v7] out=[v1 v2 v3 v4 v6 v7 v8] +/// (let (v9 u32) (call (#example) v6 v4 v7 v7 v8)) <-- operand stack pressure hits 18 here +/// ; in=[v1 v2 v3 v4 v6 v7 v7 v8] out=[v1 v7 v9] +/// ; spills=[v2 v3] (v2 is furthest next-use, followed by v3) +/// (let (v10 u32) (add v1 72)) ; in=[v1 v7] out=[v7 v10] +/// (let (v11 (ptr u64)) (inttoptr v10)) ; in=[v7 v10] out=[v7 v11] +/// (store v3 v7) ; reload=[v3] in=[v3 v7 v11] out=[v11] +/// (let (v12 u64) (load v11)) ; in=[v11] out=[v12] +/// (ret v2) ; reload=[v2] in=[v2] out=[v2] +/// ) +/// ``` +#[test] +fn spills_intra_block() -> AnalysisResult<()> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + let span = SourceSpan::UNKNOWN; + let context = Rc::new(Context::default()); + let mut ob = OpBuilder::new(context.clone()); + + // Module and test function + let mut module = ob.create_module(Ident::with_empty_span("test".into()))?; + let module_body = module.borrow().body().as_region_ref(); + ob.create_block(module_body, None, &[]); + let func = ob.create_function( + Ident::with_empty_span("test::spill".into()), + Signature::new( + [AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U8, + AddressSpace::Element, + ))))], + [AbiParam::new(Type::U32)], + ), + )?; + module.borrow_mut().symbol_manager_mut().insert_new(func, ProgramPoint::Invalid); + // Callee with signature: (ptr u128, u128, u128, u128, u64) -> u32 + let callee_sig = Signature::new( + [ + AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + )))), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U64), + ], + [AbiParam::new(Type::U32)], + ); + let callee = + ob.create_function(Ident::with_empty_span("example".into()), callee_sig.clone())?; + module + .borrow_mut() + .symbol_manager_mut() + .insert_new(callee, ProgramPoint::Invalid); + // Body + let call_op; + let store_op; + let ret_op; + let (v2, v3); + { + let mut b = FunctionBuilder::new(func, &mut ob); + let entry = b.current_block(); + let v0 = entry.borrow().arguments()[0] as ValueRef; + let v1 = b.ptrtoint(v0, Type::U32, span)?; + let k32 = b.u32(32, span); + let v2c = b.add_unchecked(v1, k32, span)?; + let v3c = b.inttoptr( + v2c, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v4 = b.load(v3c, span)?; + let k64 = b.u32(64, span); + let v5 = b.add_unchecked(v1, k64, span)?; + let v6 = b.inttoptr( + v5, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v7 = b.load(v6, span)?; + let v8 = b.u64(1, span); + let call = b.exec(callee, callee_sig.clone(), [v6, v4, v7, v7, v8], span)?; + call_op = call.as_operation_ref(); + let _v9 = call.borrow().results()[0] as ValueRef; + let k72 = b.u32(72, span); + let v10 = b.add_unchecked(v1, k72, span)?; + let store = b.store(v3c, v7, span)?; + store_op = store.as_operation_ref(); + let _v11 = b.inttoptr( + v10, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U64, + AddressSpace::Element, + ))), + span, + )?; + // Load from the computed u64 pointer before returning, as described in the test doc + let _v12 = b.load(_v11, span)?; + let ret = b.ret(Some(v2c), span)?; + ret_op = ret.as_operation_ref(); + v2 = v2c; + v3 = v3c; + } + + expect_file!["expected/spills_intra_block.hir"] + .assert_eq(&func.as_operation_ref().borrow().to_string()); + + let am = AnalysisManager::new(func.as_operation_ref(), None); + let spills = am.get_analysis_for::()?; + + assert!(spills.has_spills()); + assert_eq!(spills.spills().len(), 2); + assert!(spills.is_spilled(&v2)); + assert!(spills.is_spilled_at(v2, ProgramPoint::before(call_op))); + assert!(spills.is_spilled(&v3)); + assert!(spills.is_spilled_at(v3, ProgramPoint::before(call_op))); + assert_eq!(spills.reloads().len(), 2); + assert!(spills.is_reloaded_at(v3, ProgramPoint::before(store_op))); + assert!(spills.is_reloaded_at(v2, ProgramPoint::before(ret_op))); + Ok(()) +} + +/// In this test, we are verifying the behavior of the spill analysis when applied to a +/// control flow graph with branching control flow, where spills are required along one +/// branch and not the other. This verifies the following: +/// +/// * Control flow edges are split as necessary to insert required spills/reloads +/// * Propagation of the W and S sets from predecessors to successors is correct +/// * The W and S sets are properly computed at join points in the CFG +/// +/// The following HIR is constructed for this test (see the first test in this file for +/// a description of the notation used, if unclear): +/// +/// ```text,ignore +/// (func (export #spill) (param (ptr u8)) (result u32) +/// (block 0 (param v0 (ptr u8)) +/// (let (v1 u32) (ptrtoint v0)) ; in=[v0] out=[v1] +/// (let (v2 u32) (add v1 32)) ; in=[v1] out=[v1 v2] +/// (let (v3 (ptr u128)) (inttoptr v2)) ; in=[v1 v2] out=[v1 v2 v3] +/// (let (v4 u128) (load v3)) ; in=[v1 v2 v3] out=[v1 v2 v3 v4] +/// (let (v5 u32) (add v1 64)) ; in=[v1 v2 v3 v4] out=[v1 v2 v3 v4 v5] +/// (let (v6 (ptr u128)) (inttoptr v5)) ; in=[v1 v2 v3 v4 v5] out=[v1 v2 v3 v4 v6] +/// (let (v7 u128) (load v6)) ; in=[v1 v2 v3 v4 v6] out=[v1 v2 v3 v4 v6 v7] +/// (let (v8 i1) (eq v1 0)) ; in=[v1 v2 v3 v4 v6, v7] out=[v1 v2 v3 v4 v6 v7, v8] +/// (cond_br v8 (block 1) (block 2))) +/// +/// (block 1 +/// (let (v9 u64) (const.u64 1)) ; in=[v1 v2 v3 v4 v6 v7] out=[v1 v2 v3 v4 v6 v7 v9] +/// (let (v10 u32) (call (#example) v6 v4 v7 v7 v9)) <-- operand stack pressure hits 18 here +/// ; in=[v1 v2 v3 v4 v6 v7 v7 v9] out=[v1 v7 v10] +/// ; spills=[v2 v3] (v2 is furthest next-use, followed by v3) +/// (br (block 3 v10))) ; this edge will be split to reload v2/v3 as expected by block3 +/// +/// (block 2 +/// (let (v11 u32) (add v1 8)) ; in=[v1 v2 v3 v7] out=[v1 v2 v3 v7 v11] +/// (br (block 3 v11))) ; this edge will be split to spill v2/v3 to match the edge from block1 +/// +/// (block 3 (param v12 u32)) ; we expect that the edge between block 2 and 3 will be split, and spills of v2/v3 inserted +/// (let (v13 u32) (add v1 72)) ; in=[v1 v7 v12] out=[v7 v12 v13] +/// (let (v14 u32) (add v13 v12)) ; in=[v7 v12 v13] out=[v7 v14] +/// (let (v15 (ptr u64)) (inttoptr v14)) ; in=[v7 v14] out=[v7 v15] +/// (store v3 v7) ; reload=[v3] in=[v3 v7 v15] out=[v15] +/// (let (v16 u64) (load v15)) ; in=[v15] out=[v16] +/// (ret v2)) ; reload=[v2] in=[v2] out=[v2] +/// ) +/// ``` +#[test] +fn spills_branching_control_flow() -> AnalysisResult<()> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + let span = SourceSpan::UNKNOWN; + let context = Rc::new(Context::default()); + let mut ob = OpBuilder::new(context.clone()); + + // Module and test function + let mut module = ob.create_module(Ident::with_empty_span("test".into()))?; + let module_body = module.borrow().body().as_region_ref(); + ob.create_block(module_body, None, &[]); + let func = ob.create_function( + Ident::with_empty_span("test::spill_branch".into()), + Signature::new( + [AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U8, + AddressSpace::Element, + ))))], + [AbiParam::new(Type::U32)], + ), + )?; + module.borrow_mut().symbol_manager_mut().insert_new(func, ProgramPoint::Invalid); + // Callee with signature: (ptr u128, u128, u128, u128, u64) -> u32 + let callee_sig = Signature::new( + [ + AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + )))), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U128), + AbiParam::new(Type::U64), + ], + [AbiParam::new(Type::U32)], + ); + let callee = + ob.create_function(Ident::with_empty_span("example".into()), callee_sig.clone())?; + module + .borrow_mut() + .symbol_manager_mut() + .insert_new(callee, ProgramPoint::Invalid); + + let (block1, block2, block3); + let (v2, v3); + { + let mut b = FunctionBuilder::new(func, &mut ob); + let entry = b.current_block(); + let v0 = entry.borrow().arguments()[0] as ValueRef; + let v1 = b.ptrtoint(v0, Type::U32, span)?; + let k32 = b.u32(32, span); + let v2c = b.add_unchecked(v1, k32, span)?; + let v3c = b.inttoptr( + v2c, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v4 = b.load(v3c, span)?; + let k64 = b.u32(64, span); + let v5 = b.add_unchecked(v1, k64, span)?; + let v6 = b.inttoptr( + v5, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U128, + AddressSpace::Element, + ))), + span, + )?; + let v7 = b.load(v6, span)?; + let zero = b.u32(0, span); + let v8 = b.eq(v1, zero, span)?; + let t = b.create_block(); + let f = b.create_block(); + Cf::cond_br(&mut b, v8, t, [], f, [], span)?; + + // then + b.switch_to_block(t); + let v9 = b.u64(1, span); + let call = b.exec(callee, callee_sig.clone(), [v6, v4, v7, v7, v9], span)?; + let v10 = call.borrow().results()[0] as ValueRef; + let join = b.create_block(); + b.br(join, [v10], span)?; + + // else + b.switch_to_block(f); + let k8 = b.u32(8, span); + let v11 = b.add_unchecked(v1, k8, span)?; + b.br(join, [v11], span)?; + + // join + let v12 = b.append_block_param(join, Type::U32, span); + b.switch_to_block(join); + let k72 = b.u32(72, span); + let v13 = b.add_unchecked(v1, k72, span)?; + let v14 = b.add_unchecked(v13, v12, span)?; + let v15 = b.inttoptr( + v14, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U64, + AddressSpace::Element, + ))), + span, + )?; + b.store(v3c, v7, span)?; + let _v16 = b.load(v15, span)?; + b.ret(Some(v2c), span)?; + + block1 = t; + block2 = f; + block3 = join; + v2 = v2c; + v3 = v3c; + } + + expect_file!["expected/spills_branch_cfg.hir"] + .assert_eq(&func.as_operation_ref().borrow().to_string()); + + let am = AnalysisManager::new(func.as_operation_ref(), None); + let spills = am.get_analysis_for::()?; + + assert!(spills.has_spills()); + assert_eq!(spills.spills().len(), 4); + assert_eq!(spills.splits().len(), 2); + + let find_split = |to_block: BlockRef, from_block: BlockRef| -> Option { + spills.splits().iter().find_map(|s| match (s.point, s.predecessor) { + (ProgramPoint::Block { block, .. }, Predecessor::Block { op, .. }) + if block == to_block && op.parent() == Some(from_block) => + { + Some(s.id) + } + _ => None, + }) + }; + let split_blk1_blk3 = find_split(block3, block1).expect("missing split for block1->block3"); + let split_blk2_blk3 = find_split(block3, block2).expect("missing split for block2->block3"); + + // v2 should have a spill inserted from block2 to block3, as it is spilled in block1 + assert!(spills.is_spilled_in_split(v2, split_blk2_blk3)); + // v3 should have a spill inserted from block2 to block3, as it is spilled in block1 + assert!(spills.is_spilled_in_split(v3, split_blk2_blk3)); + // v2 and v3 should be reloaded on the edge from block1 to block3, since they were + // spilled previously, but must be in W on entry to block3 + assert_eq!(spills.reloads().len(), 2); + assert!(spills.is_reloaded_in_split(v2, split_blk1_blk3)); + assert!(spills.is_reloaded_in_split(v3, split_blk1_blk3)); + + Ok(()) +} + +/// In this test, we are verifying the behavior of the spill analysis when applied to a +/// control flow graph with cyclical control flow, i.e. loops. We're interested specifically in +/// the following properties: +/// +/// * W and S at entry to a loop are computed correctly +/// * Values live-through - but not live-in - a loop, which cannot survive the loop due to +/// operand stack pressure within the loop, are spilled outside of the loop, with reloads +/// placed on exit edges from the loop where needed +/// * W and S upon exit from a loop are computed correctly +/// +/// The following HIR is constructed for this test (see the first test in this file for +/// a description of the notation used, if unclear): +/// +/// ```text,ignore +/// (func (export #spill) (param (ptr u64)) (param u32) (param u32) (result u64) +/// (block 0 (param v0 (ptr u64)) (param v1 u32) (param v2 u32) +/// (let (v3 u32) (const.u32 0)) ; in=[v0, v1, v2] out=[v0, v1, v2, v3] +/// (let (v4 u32) (const.u32 0)) ; in=[v0, v1, v2, v3] out=[v0, v1, v2, v3, v4] +/// (let (v5 u64) (const.u64 0)) ; in=[v0, v1, v2, v3, v4] out=[v0, v1, v2, v3, v4, v5] +/// (br (block 1 v3 v4 v5))) +/// +/// (block 1 (param v6 u32) (param v7 u32) (param v8 u64)) ; outer loop +/// (let (v9 i1) (eq v6 v1)) ; in=[v0, v2, v6, v7, v8] out=[v0, v1, v2, v6, v7, v8, v9] +/// (cond_br v9 (block 2) (block 3))) ; in=[v0, v1, v2, v6, v7, v8, v9] out=[v0, v1, v2, v6, v7, v8] +/// +/// (block 2 ; exit outer loop, return from function +/// (ret v8)) ; in=[v0, v1, v2, v6, v7, v8] out=[v8] +/// +/// (block 3 ; split edge +/// (br (block 4 v7 v8))) ; in=[v0, v1, v2, v6, v7, v8] out=[v0, v1, v2, v6] +/// +/// (block 4 (param v10 u32) (param v11 u64) ; inner loop +/// (let (v12 i1) (eq v10 v2)) ; in=[v0, v1, v2, v6, v10, v11] out=[v0, v1, v2, v6, v10, v11, v12] +/// (cond_br v12 (block 5) (block 6))) ; in=[v0, v1, v2, v6, v10, v11, v12] out=[v0, v1, v2, v6, v10, v11] +/// +/// (block 5 ; increment row count, continue outer loop +/// (let (v13 u32) (add v6 1)) ; in=[v0, v1, v2, v6, v10, v11] out=[v0, v1, v2, v10, v11, v13] +/// (br (block 1 v13 v10 v11))) +/// +/// (block 6 ; load value at v0[row][col], add to sum, increment col, continue inner loop +/// (let (v14 u32) (sub.saturating v6 1)) ; row_offset := ROW_SIZE * row.saturating_sub(1) +/// ; in=[v0, v1, v2, v6, v10, v11] out=[v0, v1, v2, v6, v10, v11, 14] +/// (let (v15 u32) (mul v14 v2)) ; in=[v0, v1, v2, v6, v10, v11, 14] out=[v0, v1, v2, v6, v10, v11, 15] +/// (let (v16 u32) (add v10 v15)) ; offset := col + row_offset +/// ; in=[v0, v1, v2, v6, v10, v11, 15] out=[v0, v1, v2, v6, v10, v11, v16] +/// (let (v17 u32) (ptrtoint v0)) ; ptr := (v0 as u32 + offset) as *u64 +/// ; in=[v0, v1, v2, v6, v10, v11, v16] out=[v0, v1, v2, v6, v10, v11, v16, 17] +/// (let (v18 u32) (add v17 v16)) ; in=[v0, v1, v2, v6, v10, v11, v16, v17] out=[v0, v1, v2, v6, v10, v11, v18] +/// (let (v19 (ptr u64)) (ptrtoint v18)) ; in=[v0, v1, v2, v6, v10, v11, v18] out=[v0, v1, v2, v6, v10, v11, v19] +/// (let (v20 u64) (load v19)) ; sum += *ptr +/// ; in=[v0, v1, v2, v6, v10, v11, v19] out=[v0, v1, v2, v6, v10, v11, v20] +/// (let (v21 u64) (add v11 v20)) ; in=[v0, v1, v2, v6, v10, v11, v20] out=[v0, v1, v2, v6, v10, v21] +/// (let (v22 u32) (add v10 1)) ; col++ +/// ; in=[v0, v1, v2, v6, v10, v21] out=[v0, v1, v2, v6, v21, v22] +/// (br (block 4 v22 v21))) +/// ) +/// ``` +#[test] +fn spills_loop_nest() -> AnalysisResult<()> { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + let span = SourceSpan::UNKNOWN; + let context = Rc::new(Context::default()); + let mut ob = OpBuilder::new(context.clone()); + + let mut module = ob.create_module(Ident::with_empty_span("test".into()))?; + let module_body = module.borrow().body().as_region_ref(); + ob.create_block(module_body, None, &[]); + let func = ob.create_function( + Ident::with_empty_span("test::spill_loop".into()), + Signature::new( + [ + AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U64, + AddressSpace::Element, + )))), + AbiParam::new(Type::U32), + AbiParam::new(Type::U32), + ], + [AbiParam::new(Type::U64)], + ), + )?; + module.borrow_mut().symbol_manager_mut().insert_new(func, ProgramPoint::Invalid); + let callee_sig = Signature::new( + [ + AbiParam::new(Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U64, + AddressSpace::Element, + )))), + AbiParam::new(Type::U64), + AbiParam::new(Type::U64), + AbiParam::new(Type::U64), + AbiParam::new(Type::U64), + AbiParam::new(Type::U64), + AbiParam::new(Type::U64), + AbiParam::new(Type::U64), + ], + [AbiParam::new(Type::U32)], + ); + let callee = + ob.create_function(Ident::with_empty_span("example".into()), callee_sig.clone())?; + module + .borrow_mut() + .symbol_manager_mut() + .insert_new(callee, ProgramPoint::Invalid); + + let call_op6 = { + let mut b = FunctionBuilder::new(func, &mut ob); + let entry = b.current_block(); + let (v0, v1, v2) = { + let entry_b = entry.borrow(); + let args = entry_b.arguments(); + (args[0] as ValueRef, args[1] as ValueRef, args[2] as ValueRef) + }; + + let blk1 = b.create_block(); + let blk2 = b.create_block(); + let blk3 = b.create_block(); + let blk4 = b.create_block(); + let blk5 = b.create_block(); + let blk6 = b.create_block(); + + // entry -> block1(r, c, sum) + let r0 = b.u32(0, span); + let c0 = b.u32(0, span); + let s0 = b.u64(0, span); + b.br(blk1, [r0, c0, s0], span)?; + + // block1: outer loop header + let r = b.append_block_param(blk1, Type::U32, span); + let c = b.append_block_param(blk1, Type::U32, span); + let sum = b.append_block_param(blk1, Type::U64, span); + b.switch_to_block(blk1); + let cond_outer = b.eq(r, v1, span)?; + Cf::cond_br(&mut b, cond_outer, blk2, [], blk3, [], span)?; + + // block2: return sum + b.switch_to_block(blk2); + b.ret(Some(sum), span)?; + + // block3: split edge to inner loop + b.switch_to_block(blk3); + b.br(blk4, [c, sum], span)?; + + // block4: inner loop header + let col = b.append_block_param(blk4, Type::U32, span); + let acc = b.append_block_param(blk4, Type::U64, span); + b.switch_to_block(blk4); + let cond_inner = b.eq(col, v2, span)?; + // If inner loop finished (col == v2), forward state to blk5; otherwise run body + Cf::cond_br(&mut b, cond_inner, blk5, [col, acc], blk6, [], span)?; + + // block5: latch to outer loop; receive forwarded inner state + b.switch_to_block(blk5); + let pcol = b.append_block_param(blk5, Type::U32, span); + let pacc = b.append_block_param(blk5, Type::U64, span); + let one = b.u32(1, span); + let r1 = b.add_unchecked(r, one, span)?; + b.br(blk1, [r1, pcol, pacc], span)?; + + // block6: inner loop body + b.switch_to_block(blk6); + let one = b.u32(1, span); + let rowm1 = b.sub_unchecked(r, one, span)?; + let row_off = b.mul_unchecked(rowm1, v2, span)?; + let offset = b.add_unchecked(col, row_off, span)?; + let base = b.ptrtoint(v0, Type::U32, span)?; + let addr = b.add_unchecked(base, offset, span)?; + let v3c = b.inttoptr( + addr, + Type::Ptr(Arc::new(PointerType::new_with_address_space( + Type::U64, + AddressSpace::Element, + ))), + span, + )?; + let load = b.load(v3c, span)?; + // Create extra pressure by making multiple values live and passing them to a call + let k1 = b.u64(1, span); + let k2 = b.u64(2, span); + let k3 = b.u64(3, span); + let k4 = b.u64(4, span); + let k5 = b.u64(5, span); + let k6 = b.u64(6, span); + let _k7 = b.u64(7, span); + let call = b.exec(callee, callee_sig.clone(), [v3c, load, k1, k2, k3, k4, k5, k6], span)?; + let accn = b.add_unchecked(acc, load, span)?; + let one2 = b.u32(1, span); + let coln = b.add_unchecked(col, one2, span)?; + // Backedge: continue inner loop by jumping to its header + b.br(blk4, [coln, accn], span)?; + + call.as_operation_ref() + }; + + expect_file!["expected/spills_loop_nest.hir"] + .assert_eq(&func.as_operation_ref().borrow().to_string()); + + let am = AnalysisManager::new(func.as_operation_ref(), None); + let spills = am.get_analysis_for::()?; + + // With the added call in the inner loop body, stack pressure exceeds 16, + // so spills and reloads are expected. + assert!(spills.has_spills()); + assert!(!spills.spills().is_empty()); + assert!(!spills.reloads().is_empty()); + + // We expect that at least one of the key live values is spilled before the call in block6 + let spilled_at_call = spills + .spills() + .iter() + .filter(|s| matches!(s.place, crate::analyses::spills::Placement::At(pp) if pp == ProgramPoint::before(call_op6))) + .map(|s| s.value) + .collect::>(); + assert!(!spilled_at_call.is_empty()); + // There should be at least one split created due to differing W/S sets across edges + assert!(!spills.splits().is_empty()); + // And at least one reload must be placed on a split + let reloads_on_splits = spills + .reloads() + .iter() + .any(|r| matches!(r.place, crate::analyses::spills::Placement::Split(_))); + assert!(reloads_on_splits); + + Ok(()) +} diff --git a/hir-analysis/src/analysis.rs b/hir-analysis/src/analysis.rs new file mode 100644 index 000000000..74ca159d1 --- /dev/null +++ b/hir-analysis/src/analysis.rs @@ -0,0 +1,189 @@ +pub(super) mod state; + +use midenc_hir::{pass::AnalysisManager, Direction, Operation, ProgramPoint, Report}; + +pub use self::state::{ + AnalysisState, AnalysisStateGuard, AnalysisStateGuardMut, AnalysisStateInfo, + AnalysisStateSubscription, AnalysisStateSubscriptionBehavior, BuildableAnalysisState, Revision, +}; +use super::DataFlowSolver; + +/// Indicates whether the control enters, exits, or skips over the callee (in the case of +/// external functions). +#[derive(Debug, Copy, Clone)] +pub enum CallControlFlowAction { + Enter, + Exit, + External, +} + +/// [DataFlowAnalysis] is the base trait for all data-flow analyses. +/// +/// In general, a data-flow analysis, is expected to visit the IR rooted at some operation, and in +/// the process, define state that represents its results, invoking transfer functions on that state +/// at each program point. In addition to its own state, it may also consume the states of other +/// analyses which it either requires, or may optimistically benefit from. In the process, a +/// dependency graph is established between the analyses being applied. +/// +/// In classical data-flow analysis, the dependency graph is static and each analysis defines the +/// transfer functions between its input states and output states. In the data-flow framework +/// implemented here however, things work a bit differently: +/// +/// * The set of analyses, and the dependencies between them, is dynamic and implicit. +/// * Multiple analyses can share the same state, so the transfer functions are defined as part of +/// the [AnalysisState] implementation, not the [DataFlowAnalysis] that consumes it. The analysis +/// is responsible for invoking the appropriate transfer function at each program point, but it +/// does not need to know how it is implemented. +/// +/// Generally, when an analysis queries an uninitialized state, it is expected to "bail out", i.e., +/// not provide any updates. When the value is initialized, the solver will re-invoke the analysis. +/// However, if the solver exhausts its worklist, and there are still uninitialized states, the +/// solver "nudges" the analyses by default-initializing those states. +pub trait DataFlowAnalysis { + /// A friendly name for this analysis in diagnostics + fn debug_name(&self) -> &'static str { + core::any::type_name::() + } + /// The unique type identifier of the concrete analysis type. + fn analysis_id(&self) -> core::any::TypeId; + /// Initialize the analysis from the provided top-level operation by building an initial + /// dependency graph between all lattice anchors of interest. This can be implemented by calling + /// `visit` on all program points of interest below the top-level operation. + /// + /// An analysis can optionally provide initial values to certain analysis states to influence + /// the evolution of the analysis. + fn initialize( + &self, + op: &Operation, + solver: &mut DataFlowSolver, + analysis_manager: AnalysisManager, + ) -> Result<(), Report>; + + /// Visit the given program point. + /// + /// The solver will invoke this function when a state this analysis depends on has changed at + /// the given program point. + /// + /// This function is similar to a transfer function - it queries analysis states that it depends + /// on, and derives/computes other states. + /// + /// When an analysis state is queried (via [DataFlowSolver::require]), it establishes a + /// dependency between this analysis, and that state, at a specific program point. As a result, + /// this function will be invoked by the solver on that program point, if at any point in the + /// future, the state changes. + /// + /// While dependencies between analysis states are generally handled automatically by the solver, + /// implementations of an analysis may also explicitly add dependencies between some input state + /// and a specific program point, to ensure that the solver will invoke the analysis on that + /// program point if the input state changes. + fn visit(&self, point: &ProgramPoint, solver: &mut DataFlowSolver) -> Result<(), Report>; +} + +/// This trait represents a type which is can be constructed into an instance of [DataFlowAnalysis] +/// by the [DataFlowSolver], by constructing an instance of its corresponding [AnalysisStrategy] +/// with an instance of the type. The strategy is responsible for adapting the specific semantics +/// of the analysis to the abstract [DataFlowAnalysis] interface. +/// +/// There are two primary ways of categorizing analysis: +/// +/// * dense vs sparse - dictates whether analysis states are anchored to program points (dense) or +/// values (sparse). Sparse analyses are referred to as such because SSA values can only have a +/// single definition, so the analysis state only has to be anchored to a single location per +/// value. Dense analyses on the other hand, must attach analysis state to every program point +/// (operation and block) visited by the analysis. +/// +/// * forward vs backward - dictates whether analysis state is propagated forward (from the entry +/// point of a region to the exits of the region) or backward (from the exits of a region, to +/// the entry point). A forward analysis follows the CFG top-down, while a backward analysis +/// visits the CFG bottom-up. +/// +/// As a result, there are four unique permutations of analysis possible, and each have different +/// semantics from the others, requiring separate traits. This trait allows loading analyses into +/// the [DataFlowSolver] without having to know the specific details of how that analysis is +/// implemented. Instead, the author of the analysis implements the specific analysis trait that +/// corresponds to the semantics it wants, and then implements this trait to specify the concrete +/// type that understands how to run that type of analysis in the context of our data-flow analysis +/// framework. +/// +/// This must be separate from [DataFlowAnalysis] as the underlying type may not implement the +/// [DataFlowAnalysis] trait itself, only doing so once combined with the specified strategy. +/// However, it is expected that all concrete implementations of [DataFlowAnalysis] implement this +/// trait, enabling it to be loaded into the solver via [DataFlowSolver::load]. +pub trait BuildableDataFlowAnalysis { + /// The type which knows how to run `Self` as an instance of [DataFlowAnalysis]. + /// + /// The dense and sparse analysis kinds have concrete types which handle the details which are + /// universal to all such analyses. Those types would be specified as the strategy for a + /// specific analysis of the corresponding kind (e.g. `SparseDataFlowAnalysis` provides the + /// implementation of [DataFlowAnalysis] for all sparse-(forward|backward) analyses. + type Strategy: Sized + AnalysisStrategy; + + /// Construct a fresh instance of the underlying analysis type. + /// + /// The current [DataFlowSolver] instance is provided, allowing an implementation to access the + /// global [DataFlowConfig], as well as load any other analyses that it depends on. Any analysis + /// that has already been loaded prior to calling this function, will be ignored. + fn new(solver: &mut DataFlowSolver) -> Self; +} + +/// This trait represents a type that adapts some primitive analysis `T` to the abstract +/// [DataFlowAnalysis] interface. +/// +/// It is intended to be used in conjunction with [BuildableDataFlowAnalysis]. See the documentation +/// of that trait for more details on how these work together and why they exist. +pub trait AnalysisStrategy: DataFlowAnalysis { + /// The kind (dense vs sparse) of the analysis being performed + type Kind: AnalysisKind; + /// The direction in which analysis state is propagated (forward vs backward) by this analysis. + type Direction: Direction; + + /// Construct a valid [DataFlowAnalysis] instance using this strategy, by providing an instance + /// of the underlying analysis type. + /// + /// The current [DataFlowSolver] instance is provided, allowing an implementation to access the + /// global [DataFlowConfig], as well as load any analyses that it depends on. Any analysis that + /// has already been loaded prior to calling this function, will be ignored. + /// + /// The `analysis` instance is expected to have been constructed using + /// [BuildableDataFlowAnalysis::new], if `T` implements the trait. + fn build(analysis: T, solver: &mut DataFlowSolver) -> Self; +} + +/// A marker trait for abstracting/specializing over the abstract kind of an analysis: dense or +/// sparse. +/// +/// This trait is sealed as there are only two supported kinds. +#[allow(private_bounds)] +pub trait AnalysisKind: sealed::AnalysisKind { + fn is_dense() -> bool { + Self::IS_DENSE + } + fn is_sparse() -> bool { + Self::IS_SPARSE + } +} + +impl AnalysisKind for K {} + +mod sealed { + pub(super) trait AnalysisKind { + const IS_DENSE: bool; + const IS_SPARSE: bool; + } + + #[derive(Debug, Copy, Clone)] + pub struct Dense; + impl AnalysisKind for Dense { + const IS_DENSE: bool = true; + const IS_SPARSE: bool = false; + } + + #[derive(Debug, Copy, Clone)] + pub struct Sparse; + impl AnalysisKind for Sparse { + const IS_DENSE: bool = false; + const IS_SPARSE: bool = true; + } +} + +pub use self::sealed::{Dense, Sparse}; diff --git a/hir-analysis/src/analysis/state.rs b/hir-analysis/src/analysis/state.rs new file mode 100644 index 000000000..3ec8e1f88 --- /dev/null +++ b/hir-analysis/src/analysis/state.rs @@ -0,0 +1,85 @@ +mod guard; +mod info; +mod raw; + +use core::any::Any; + +use midenc_hir::ProgramPoint; + +pub(crate) use self::raw::{ + AnalysisStateDescriptor, RawAnalysisStateInfo, RawAnalysisStateInfoHandle, +}; +pub use self::{ + guard::{AnalysisStateGuard, AnalysisStateGuardMut}, + info::{ + AnalysisStateInfo, AnalysisStateSubscription, AnalysisStateSubscriptionBehavior, Revision, + }, +}; +use crate::{DataFlowAnalysis, LatticeAnchor, LatticeAnchorRef}; + +/// The identifier for a uniqued [AnalysisStateKeyImpl] +#[derive(Copy, Clone)] +pub struct AnalysisStateKey { + type_id: core::any::TypeId, + anchor: LatticeAnchorRef, +} +impl AnalysisStateKey { + pub fn new(anchor: LatticeAnchorRef) -> Self + where + T: BuildableAnalysisState, + { + Self { + type_id: core::any::TypeId::of::(), + anchor, + } + } +} +impl Eq for AnalysisStateKey {} +impl PartialEq for AnalysisStateKey { + fn eq(&self, other: &Self) -> bool { + self.type_id == other.type_id && self.anchor == other.anchor + } +} +impl core::hash::Hash for AnalysisStateKey { + fn hash(&self, state: &mut H) { + self.type_id.hash(state); + self.anchor.hash(state); + } +} +impl core::fmt::Debug for AnalysisStateKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("AnalysisStateKey") + .field("type_id", &self.type_id) + .field_with("anchor", |f| write!(f, "{}", &self.anchor)) + .finish() + } +} +impl core::fmt::Display for AnalysisStateKey { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", &self.anchor) + } +} + +pub trait BuildableAnalysisState: AnalysisState + Any { + fn create(anchor: LatticeAnchorRef) -> Self; +} + +pub trait AnalysisState { + fn as_any(&self) -> &dyn Any; + fn type_name(&self) -> &'static str { + core::any::type_name::() + } + fn anchor(&self) -> &dyn LatticeAnchor; +} + +impl dyn AnalysisState { + #[inline] + pub fn is(&self) -> bool { + self.as_any().is::() + } + + #[inline] + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any().downcast_ref::() + } +} diff --git a/hir-analysis/src/analysis/state/guard.rs b/hir-analysis/src/analysis/state/guard.rs new file mode 100644 index 000000000..0df4d6c4f --- /dev/null +++ b/hir-analysis/src/analysis/state/guard.rs @@ -0,0 +1,346 @@ +use alloc::rc::Rc; +use core::{cell::RefCell, ptr::NonNull}; + +use midenc_hir::{ + entity::{BorrowRef, BorrowRefMut}, + EntityRef, +}; + +use super::*; +use crate::{solver::AnalysisQueue, ChangeResult, DenseLattice, SparseLattice}; + +/// An immmutable handle/guard for some analysis state T +pub struct AnalysisStateGuard<'a, T: AnalysisState + 'static> { + #[allow(unused)] + info: NonNull, + state: NonNull, + _borrow: BorrowRef<'a>, +} +impl<'a, T: AnalysisState + 'static> AnalysisStateGuard<'a, T> { + pub(crate) unsafe fn new(info: NonNull) -> Self { + let handle = RawAnalysisStateInfoHandle::new(info); + let (state, _borrow) = handle.state_ref(); + Self { + info, + state, + _borrow, + } + } + + pub fn into_entity_ref(guard: Self) -> EntityRef<'a, T> { + let guard = core::mem::ManuallyDrop::new(guard); + let state = guard.state; + let borrow_ref = unsafe { core::ptr::read(&guard._borrow) }; + EntityRef::from_raw_parts(state, borrow_ref) + } + + /// Subscribe `analysis` to any changes of the lattice anchor. + /// + /// This is handled by invoking the [AnalysisStateSubscriptionBehavior::on_subscribe] callback, + /// leaving the handling for ad-hoc subscriptions of this kind to each [AnalysisState] + /// implementation. + /// + /// # Safety + /// + /// The caller must ensure that `analysis` is owned by the [DataFlowSolver], as that is the + /// only situation in which it is safe for us to take the address of the analysis for later use. + pub fn subscribe(guard: &Self, analysis: &A) + where + A: DataFlowAnalysis + 'static, + { + let analysis = analysis as *const dyn DataFlowAnalysis; + let analysis = unsafe { NonNull::new_unchecked(analysis.cast_mut()) }; + Self::subscribe_nonnull(guard, analysis); + } + + /// Subscribe `analysis` to any changes of the lattice anchor. + /// + /// This is handled by invoking the [AnalysisStateSubscriptionBehavior::on_subscribe] callback, + /// leaving the handling for ad-hoc subscriptions of this kind to each [AnalysisState] + /// implementation. + /// + /// # Safety + /// + /// The caller must ensure that `analysis` is owned by the [DataFlowSolver], as that is the + /// only situation in which it is safe for us to take the address of the analysis for later use. + fn subscribe_nonnull(guard: &Self, analysis: NonNull) { + let info = unsafe { guard.info.as_ref() }; + let state = unsafe { guard.state.as_ref() }; + ::on_subscribe(state, analysis, info); + } +} +impl AsRef for AnalysisStateGuard<'_, T> { + #[inline(always)] + fn as_ref(&self) -> &T { + unsafe { self.state.as_ref() } + } +} +impl core::ops::Deref for AnalysisStateGuard<'_, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} +impl core::fmt::Debug for AnalysisStateGuard<'_, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self.as_ref(), f) + } +} +impl core::fmt::Display for AnalysisStateGuard<'_, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self.as_ref(), f) + } +} +impl AnalysisState for AnalysisStateGuard<'_, T> { + fn as_any(&self) -> &dyn Any { + self.as_ref().as_any() + } + + fn anchor(&self) -> &dyn LatticeAnchor { + self.as_ref().anchor() + } +} + +/// A mutable handle/guard for some analysis state T +pub struct AnalysisStateGuardMut<'a, T: AnalysisState + 'static> { + worklist: Rc>, + info: NonNull, + state: NonNull, + _borrow: BorrowRefMut<'a>, + changed: ChangeResult, +} +impl<'a, T: AnalysisState + 'static> AnalysisStateGuardMut<'a, T> { + pub(crate) unsafe fn new( + info: NonNull, + worklist: Rc>, + ) -> Self { + let handle = RawAnalysisStateInfoHandle::new(info); + let (state, _borrow) = handle.state_mut(); + Self { + worklist, + info, + state, + _borrow, + changed: ChangeResult::Unchanged, + } + } + + pub fn freeze(mut guard: Self) -> AnalysisStateGuard<'a, T> { + guard.notify_if_changed(); + + let guard = core::mem::ManuallyDrop::new(guard); + let info = guard.info; + let state = guard.state; + let _worklist = unsafe { core::ptr::read(&guard.worklist) }; + let borrow_ref_mut = unsafe { core::ptr::read(&guard._borrow) }; + AnalysisStateGuard { + info, + state, + _borrow: borrow_ref_mut.into_borrow_ref(), + } + } + + /// Consume the guard and convert the underlying mutable borrow of the state into an immutable + /// borrow, after propagating any changes made to the state while mutating it. When this + /// function returns, the state can be safely aliased by immutable references, while the caller + /// retains the ability to interact with the state via the returned [crate::EntityRef]. + pub fn into_entity_ref(mut guard: Self) -> EntityRef<'a, T> { + guard.notify_if_changed(); + + let guard = core::mem::ManuallyDrop::new(guard); + let state = guard.state; + let borrow_ref_mut = unsafe { core::ptr::read(&guard._borrow) }; + EntityRef::from_raw_parts(state, borrow_ref_mut.into_borrow_ref()) + } + + /// Apply a function to the underlying [AnalysisState] that may or may not change it. + /// + /// The callback is expected to return a [ChangeResult] reflecting whether or not changes were + /// applied. If the callback fails to signal that a change was made, dependent analyses will + /// not be re-enqueued, and thus analyses may make incorrect assumptions. + /// + /// This should be used when you need a mutable reference to the state, but may not actually + /// end up mutating the state with it. The default [DerefMut] implementation will always + /// assume the underlying state was changed if invoked - this function lets you bypass that, + /// but with the requirement that you signal changes manually. + pub fn change(&mut self, callback: F) -> ChangeResult + where + F: FnOnce(&mut T) -> ChangeResult, + { + log::trace!("starting analysis state change of type {}", core::any::type_name::()); + let result = callback(unsafe { self.state.as_mut() }); + log::trace!("analysis state changed: {}", result.changed()); + self.changed |= result; + result + } + + /// Subscribe `analysis` to any changes of the lattice anchor. + /// + /// This is handled by invoking the [AnalysisStateSubscriptionBehavior::on_subscribe] callback, + /// leaving the handling for ad-hoc subscriptions of this kind to each [AnalysisState] + /// implementation. + /// + /// # Safety + /// + /// The caller must ensure that `analysis` is owned by the [DataFlowSolver], as that is the + /// only situation in which it is safe for us to take the address of the analysis for later use. + pub fn subscribe(guard: &Self, analysis: &A) + where + A: DataFlowAnalysis + 'static, + { + let analysis = analysis as *const dyn DataFlowAnalysis; + let analysis = unsafe { NonNull::new_unchecked(analysis.cast_mut()) }; + Self::subscribe_nonnull(guard, analysis); + } + + /// Subscribe `analysis` to any changes of the lattice anchor. + /// + /// This is handled by invoking the [AnalysisStateSubscriptionBehavior::on_subscribe] callback, + /// leaving the handling for ad-hoc subscriptions of this kind to each [AnalysisState] + /// implementation. + /// + /// # Safety + /// + /// The caller must ensure that `analysis` is owned by the [DataFlowSolver], as that is the + /// only situation in which it is safe for us to take the address of the analysis for later use. + fn subscribe_nonnull(guard: &Self, analysis: NonNull) { + let info = unsafe { guard.info.as_ref() }; + let state = unsafe { guard.state.as_ref() }; + ::on_subscribe(state, analysis, info); + } + + fn notify_if_changed(&mut self) { + if self.changed.changed() { + // propagting update to {state.debug_name()} of {state.anchor} value {state} + let mut info = self.info; + let info = unsafe { info.as_mut() }; + info.increment_revision(); + let mut worklist = self.worklist.borrow_mut(); + let anchor = info.anchor(); + log::trace!( + "committing changes to analysis state {} at {anchor}", + core::any::type_name::() + ); + let state = unsafe { self.state.as_ref() }; + // Handle the change for each subscriber to this analysis state + log::trace!( + "there are {} subscriptions to notify of this change", + info.subscriptions().len() + ); + { + let subscriptions = info.subscriptions(); + for subscription in subscriptions.iter() { + subscription.handle_state_change::(anchor, &mut *worklist); + } + } + // Invoke any custom on-update logic for this analysis state type + log::trace!("invoking on_update callback to notify user-defined subscriptions"); + ::on_update(state, info, &mut worklist); + } + } +} +impl AsRef for AnalysisStateGuardMut<'_, T> { + #[inline(always)] + fn as_ref(&self) -> &T { + unsafe { self.state.as_ref() } + } +} +impl AsMut for AnalysisStateGuardMut<'_, T> { + #[inline] + fn as_mut(&mut self) -> &mut T { + // This is overly conservative, but we assume that a mutable borrow of the underlying state + // changes that state, and thus must be notified. The problem is that just because you take + // a mutable reference and seemingly change the state, doesn't mean that the state actually + // changed. As a result, users of an AnalysisStateGuard are encouraged to either only take + // a mutable reference after checking that the state actually changed, or use the + // AnalysisStateGuard::change method. + self.changed = ChangeResult::Changed; + + unsafe { self.state.as_mut() } + } +} +impl core::ops::Deref for AnalysisStateGuardMut<'_, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} +impl core::ops::DerefMut for AnalysisStateGuardMut<'_, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut() + } +} +impl Drop for AnalysisStateGuardMut<'_, T> { + fn drop(&mut self) { + self.notify_if_changed(); + } +} +impl core::fmt::Debug for AnalysisStateGuardMut<'_, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self.as_ref(), f) + } +} +impl core::fmt::Display for AnalysisStateGuardMut<'_, T> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self.as_ref(), f) + } +} +impl AnalysisState for AnalysisStateGuardMut<'_, T> { + fn as_any(&self) -> &dyn Any { + self.as_ref().as_any() + } + + fn anchor(&self) -> &dyn LatticeAnchor { + self.as_ref().anchor() + } +} +impl DenseLattice for AnalysisStateGuardMut<'_, T> { + type Lattice = ::Lattice; + + #[inline] + fn lattice(&self) -> &Self::Lattice { + unsafe { self.state.as_ref() }.lattice() + } + + #[inline] + fn lattice_mut(&mut self) -> &mut Self::Lattice { + unsafe { self.state.as_mut() }.lattice_mut() + } + + fn join(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let result = unsafe { self.state.as_mut().join(rhs) }; + self.changed |= result; + result + } + + fn meet(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let result = unsafe { self.state.as_mut().meet(rhs) }; + self.changed |= result; + result + } +} +impl SparseLattice for AnalysisStateGuardMut<'_, T> { + type Lattice = ::Lattice; + + #[inline] + fn lattice(&self) -> &Self::Lattice { + unsafe { self.state.as_ref() }.lattice() + } + + fn join(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let result = unsafe { self.state.as_mut().join(rhs) }; + self.changed |= result; + result + } + + fn meet(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let result = unsafe { self.state.as_mut().join(rhs) }; + self.changed |= result; + result + } +} diff --git a/hir-analysis/src/analysis/state/info.rs b/hir-analysis/src/analysis/state/info.rs new file mode 100644 index 000000000..e98aa3065 --- /dev/null +++ b/hir-analysis/src/analysis/state/info.rs @@ -0,0 +1,318 @@ +use alloc::collections::VecDeque; +use core::{ + any::TypeId, + cell::{Ref, RefCell}, + ptr::NonNull, +}; + +use midenc_hir::{adt::SmallSet, entity::RawEntity, EntityRef, SmallVec}; + +use super::*; +use crate::{solver::QueuedAnalysis, AnalysisQueue, LatticeAnchor, LatticeAnchorRef}; + +pub type Revision = u32; + +pub struct AnalysisStateInfo { + /// The immortal characteristics of the underlying analysis state: + /// + /// * The type id of the concrete `AnalysisState` implementation + /// * `dyn AnalysisState` vtable + /// * Offset from start of `AnalysisStateInfo` struct to the actual state data when allocated + /// in a `RawAnalysisStateInfo` struct. + /// + /// This is shared across all instances of the same analysis state type + descriptor: NonNull, + /// The anchor to which the analysis state is attached + anchor: LatticeAnchorRef, + /// A monotonically-increasing integer representing the number of revisions to this state + revision: Revision, + /// The set of dependent program points and associated analyses which are to be re-analyzed + /// whenever the state changes. + /// + /// This is dynamically borrow-checked so that subscriptions can be recorded without requiring + /// a mutable borrow of the analysis state itself. + pub(super) subscriptions: RefCell>, +} +impl AnalysisStateInfo { + #[inline] + pub(super) fn new( + descriptor: NonNull, + anchor: LatticeAnchorRef, + ) -> Self { + Self { + descriptor, + anchor, + revision: 0, + subscriptions: Default::default(), + } + } + + #[inline] + pub fn anchor(&self) -> &dyn LatticeAnchor { + self.anchor.as_ref() + } + + #[inline(always)] + pub const fn anchor_ref(&self) -> LatticeAnchorRef { + self.anchor + } + + #[inline] + pub const fn revision(&self) -> Revision { + self.revision + } + + pub(crate) fn increment_revision(&mut self) { + self.revision += 1; + } + + #[inline] + pub fn debug_name(&self) -> &'static str { + self.descriptor().debug_name() + } + + #[inline(always)] + pub(super) fn descriptor(&self) -> &AnalysisStateDescriptor { + unsafe { self.descriptor.as_ref() } + } + + #[allow(unused)] + #[inline] + pub(super) fn state(&self) -> NonNull { + let descriptor = self.descriptor(); + unsafe { + let ptr = self as *const Self; + let ptr = + NonNull::new_unchecked(ptr.byte_add(descriptor.offset()).cast::<()>().cast_mut()); + NonNull::::from_raw_parts(ptr, descriptor.metadata()) + } + } + + pub(crate) fn borrow_state(&self) -> EntityRef<'_, T> { + let descriptor = self.descriptor(); + assert_eq!(descriptor.type_id(), &TypeId::of::()); + unsafe { + let ptr = self as *const Self; + let ptr = + NonNull::new_unchecked(ptr.byte_add(descriptor.offset()).cast::<()>().cast_mut()); + let raw_entity = ptr.cast::>(); + raw_entity.as_ref().borrow() + } + } + + #[inline] + pub fn subscriptions(&self) -> Ref<'_, [AnalysisStateSubscription]> { + Ref::map(self.subscriptions.borrow(), |subs| subs.as_slice()) + } + + pub fn on_update_subscribers_count(&self) -> usize { + self.subscriptions + .borrow() + .iter() + .filter(|sub| matches!(sub, AnalysisStateSubscription::OnUpdate { .. })) + .count() + } + + pub fn on_update_subscribers(&self) -> SmallVec<[NonNull; 4]> { + self.subscriptions + .borrow() + .iter() + .filter_map(|sub| match sub { + AnalysisStateSubscription::OnUpdate { analysis } => Some(*analysis), + _ => None, + }) + .collect::>() + } + + /// Add a subscription for `analysis` at `point` to this state + pub fn subscribe(&self, subscriber: AnalysisStateSubscription) { + self.subscriptions.borrow_mut().insert(subscriber); + } +} + +#[derive(PartialEq, Eq, Copy, Clone)] +pub enum AnalysisStateSubscription { + /// This subscribes `analysis` to state changes handled by the `on_update` callback of the + /// analysis state. + OnUpdate { + /// The analysis that subscribed to changes + analysis: NonNull, + }, + /// This subscribes `analysis` to state changes by re-running it on `point` + /// + /// Point might be the same as the analysis state anchor, but doesn't have to be. + AtPoint { + /// The analysis to run + analysis: NonNull, + /// The point at which to run it + point: ProgramPoint, + }, + /// This subscribes `analysis` to state changes by re-running it on all uses of the anchor value. + /// + /// NOTE: This subscription type is only valid for `ValueRef` anchors for the moment. In the + /// future, we might be able to expand this to other forms of usable entities, e.g. symbols. + Uses { + /// The analysis to run + analysis: NonNull, + }, +} + +impl AnalysisStateSubscription { + pub fn handle_state_change( + &self, + anchor: &dyn LatticeAnchor, + worklist: &mut VecDeque, + ) where + T: AnalysisState + 'static, + A: alloc::alloc::Allocator, + { + log::trace!( + "handling analysis state change to anchor '{anchor}' for {}", + core::any::type_name::() + ); + match *self { + // Delegated to [AnalysisStateSubscriptionBehavior::on_update] + Self::OnUpdate { analysis: _ } => (), + // Re-run `analysis` at `point` + Self::AtPoint { analysis, point } => { + log::trace!("enqueuing {} at {point}", unsafe { analysis.as_ref().debug_name() }); + worklist.push_back(QueuedAnalysis { point, analysis }); + } + // Re-run `analysis` for all uses of the current value + Self::Uses { analysis } => { + let value = anchor + .as_value() + .unwrap_or_else(|| panic!("expected value anchor, got: {:?}", anchor)); + for user in value.borrow().iter_uses() { + let user = user.owner; + let point = ProgramPoint::after(user); + worklist.push_back(QueuedAnalysis { point, analysis }); + } + } + } + } +} + +pub trait AnalysisStateSubscriptionBehavior: AnalysisState { + /// Called when an [AnalysisState] is being queried by an analysis via [DataFlowSolver::require] + /// + /// This invokes state-specific logic for how to subscribe the dependent analysis to changes + /// of that state. By default, one of two options behaviors is applied: + /// + /// * For program point anchors, the analysis is re-run at `dependent` on state changes + /// * For value anchors, the analysis is re-run at `dependent` _and_ at all uses of the value + /// + /// NOTE: Subscriptions are established when an analysis is queried, if you wish to execute + /// custom behavior when updates are being propagated, see `on_update`. + fn on_require_analysis( + &self, + info: &mut AnalysisStateInfo, + current_analysis: NonNull, + dependent: ProgramPoint, + ) { + on_require_analysis_fallback(info, current_analysis, dependent); + } + + /// Called when an analysis subscribes to any changes to the current [AnalysisState]. + fn on_subscribe(&self, subscriber: NonNull, info: &AnalysisStateInfo) { + log::trace!( + "subscribing {} to state updates for analysis state {} at {}", + unsafe { subscriber.as_ref().debug_name() }, + info.debug_name(), + info.anchor() + ); + info.subscribe(AnalysisStateSubscription::OnUpdate { + analysis: subscriber, + }); + } + + /// Called when changes to an [AnalysisState] are being propagated. This callback has visibility + /// into the modified state, and can use that information to modify other analysis states that + /// may be directly/indirectly affected by the changes. + #[allow(unused_variables)] + fn on_update(&self, info: &mut AnalysisStateInfo, worklist: &mut AnalysisQueue) {} +} + +impl AnalysisStateSubscriptionBehavior for T { + default fn on_require_analysis( + &self, + info: &mut AnalysisStateInfo, + current_analysis: NonNull, + dependent: ProgramPoint, + ) { + on_require_analysis_fallback(info, current_analysis, dependent); + } + + /// Called when an analysis subscribes to any changes to the current [AnalysisState]. + default fn on_subscribe( + &self, + subscriber: NonNull, + info: &AnalysisStateInfo, + ) { + log::trace!( + "subscribing {} to state updates for analysis state {} at {}", + unsafe { subscriber.as_ref().debug_name() }, + info.debug_name(), + info.anchor() + ); + info.subscribe(AnalysisStateSubscription::OnUpdate { + analysis: subscriber, + }); + } + + /// Called when changes to an [AnalysisState] are being propagated. This callback has visibility + /// into the modified state, and can use that information to modify other analysis states that + /// may be directly/indirectly affected by the changes. + default fn on_update(&self, info: &mut AnalysisStateInfo, worklist: &mut AnalysisQueue) { + log::trace!( + "notifying {} subscribers of update to sparse lattice at {}", + info.on_update_subscribers_count(), + info.anchor() + ); + if let Some(value) = info.anchor().as_value() { + for user in value.borrow().uses() { + let user = user.owner; + for subscriber in info.on_update_subscribers() { + worklist.push_back(QueuedAnalysis { + point: ProgramPoint::after(user), + analysis: subscriber, + }); + } + } + } else if let Some(point) = info.anchor().as_program_point() { + for subscriber in info.on_update_subscribers() { + worklist.push_back(QueuedAnalysis { + point, + analysis: subscriber, + }); + } + } + } +} + +pub fn on_require_analysis_fallback( + info: &mut AnalysisStateInfo, + current_analysis: NonNull, + dependent: ProgramPoint, +) { + log::trace!( + "applying default subscriptions for {} at {} for {dependent} for analysis state {}", + unsafe { current_analysis.as_ref().debug_name() }, + info.anchor(), + info.debug_name() + ); + if info.anchor().is_value() { + info.subscribe(AnalysisStateSubscription::AtPoint { + analysis: current_analysis, + point: dependent, + }); + info.subscribe(AnalysisStateSubscription::Uses { + analysis: current_analysis, + }); + } else { + info.subscribe(AnalysisStateSubscription::AtPoint { + analysis: current_analysis, + point: dependent, + }); + } +} diff --git a/hir-analysis/src/analysis/state/raw.rs b/hir-analysis/src/analysis/state/raw.rs new file mode 100644 index 000000000..605dcc7b2 --- /dev/null +++ b/hir-analysis/src/analysis/state/raw.rs @@ -0,0 +1,143 @@ +use core::{any::TypeId, ptr::NonNull}; + +use midenc_hir::{ + entity::{BorrowRef, BorrowRefMut, EntityRef, RawEntity}, + FxHashMap, +}; + +use super::*; +use crate::LatticeAnchorRef; + +pub struct AnalysisStateDescriptor { + type_name: &'static str, + /// The unique id of the concrete analysis state type + type_id: TypeId, + /// The vtable pointer for the analysis state + metadata: core::ptr::DynMetadata, + /// Offset from the start of [AnalysisStateInfo] to the start of the RawEntity for this + /// analsyis type + offset: u32, +} +impl AnalysisStateDescriptor { + pub fn new(alloc: &blink_alloc::Blink) -> NonNull { + let dyn_ptr = NonNull::::dangling() as NonNull; + let offset = (core::mem::offset_of!(RawAnalysisStateInfo, state) + - core::mem::offset_of!(RawAnalysisStateInfo, info)) as u32; + let desc = alloc.put(Self { + type_name: core::any::type_name::(), + type_id: TypeId::of::(), + metadata: dyn_ptr.to_raw_parts().1, + offset, + }); + unsafe { NonNull::new_unchecked(desc) } + } + + #[inline(always)] + pub const fn debug_name(&self) -> &'static str { + self.type_name + } + + #[inline(always)] + pub const fn type_id(&self) -> &TypeId { + &self.type_id + } + + #[inline(always)] + pub const fn offset(&self) -> usize { + self.offset as usize + } + + #[inline(always)] + pub const fn metadata(&self) -> core::ptr::DynMetadata { + self.metadata + } +} + +#[repr(C)] +pub struct RawAnalysisStateInfo { + info: AnalysisStateInfo, + state: RawEntity, +} +impl RawAnalysisStateInfo { + /// Allocate a new instance of the analysis state `T` attached to `anchor`, using `alloc`. + /// + /// Returns the [AnalysisStateKey] which uniquely identifies this state. + pub fn alloc( + alloc: &blink_alloc::Blink, + descriptors: &mut FxHashMap>, + key: AnalysisStateKey, + anchor: LatticeAnchorRef, + ) -> NonNull { + debug_assert_eq!(key, AnalysisStateKey::new::(anchor)); + + let type_id = TypeId::of::(); + let descriptor = *descriptors + .entry(type_id) + .or_insert_with(|| AnalysisStateDescriptor::new::(alloc)); + + let info = alloc.put(RawAnalysisStateInfo { + info: AnalysisStateInfo::new(descriptor, anchor), + state: RawEntity::new(::create(anchor)), + }); + unsafe { NonNull::new_unchecked(info) } + } + + #[inline] + pub fn as_info_ptr(raw_info: NonNull) -> NonNull { + unsafe { + let raw = raw_info.as_ptr(); + let info = core::ptr::addr_of_mut!((*raw).info); + NonNull::new_unchecked(info) + } + } +} + +pub struct RawAnalysisStateInfoHandle { + state: NonNull>, + offset: u32, +} +impl RawAnalysisStateInfoHandle { + pub unsafe fn new(info: NonNull) -> Self { + let offset = info.as_ref().descriptor().offset; + let state = info.byte_add(offset as usize).cast::>(); + Self { state, offset } + } + + #[track_caller] + #[inline] + #[allow(unused)] + pub fn into_entity_ref<'a>(self) -> EntityRef<'a, T> { + unsafe { self.state.as_ref().borrow() } + } + + #[track_caller] + #[inline] + pub(super) unsafe fn state_mut<'a>(self) -> (NonNull, BorrowRefMut<'a>) { + unsafe { self.state.as_ref().borrow_mut_unsafe() } + } + + #[track_caller] + #[inline] + pub(super) unsafe fn state_ref<'a>(self) -> (NonNull, BorrowRef<'a>) { + unsafe { self.state.as_ref().borrow_unsafe() } + } + + pub fn with(&mut self, callback: F) + where + F: FnOnce(&mut T, &mut AnalysisStateInfo), + { + let mut info = + unsafe { self.state.byte_sub(self.offset as usize).cast::() }; + let mut state = self.state; + let state = unsafe { state.as_mut() }; + let info = unsafe { info.as_mut() }; + callback(&mut state.borrow_mut(), info); + } +} + +impl core::ops::CoerceUnsized> for RawAnalysisStateInfoHandle +where + T: ?Sized + core::marker::Unsize, + U: ?Sized, +{ +} diff --git a/hir-analysis/src/anchor.rs b/hir-analysis/src/anchor.rs new file mode 100644 index 000000000..00d3d3b50 --- /dev/null +++ b/hir-analysis/src/anchor.rs @@ -0,0 +1,389 @@ +use core::{any::Any, fmt, hash::Hash, ptr::NonNull}; + +use midenc_hir::{ + Block, BlockArgument, BlockArgumentRef, BlockRef, DynHash, DynPartialEq, FxHashMap, FxHasher, + OpResult, OpResultRef, Operation, OperationRef, ProgramPoint, RawEntityRef, SourceSpan, + Spanned, Value, ValueRef, +}; + +/// This represents a pointer to a type-erased [LatticeAnchor] value. +/// +/// # Safety +/// +/// Anchors are immutable, so dereferencing these are always safe while the [DataFlowSolver] which +/// allocated them is still live. However, you must ensure that a reference never outlives the +/// parent [DataFlowSolver]. In practice, this is basically enforced in terms of API - you can't +/// do anything useful with one of these without the solver, however it is still incumbent on users +/// of this type to uphold this guarantee. +#[derive(Copy, Clone)] +pub struct LatticeAnchorRef(NonNull); + +impl LatticeAnchorRef { + /// Get a [LatticeAnchorRef] from a raw [LatticeAnchor] pointer. + #[inline] + fn new(raw: NonNull) -> Self { + Self(raw) + } + + fn compute_hash(anchor: &A) -> u64 + where + A: ?Sized + LatticeAnchor, + { + use core::hash::Hasher; + + let mut hasher = FxHasher::default(); + anchor.dyn_hash(&mut hasher); + hasher.finish() + } + + pub fn intern( + anchor: &A, + alloc: &blink_alloc::Blink, + interned: &mut FxHashMap, + ) -> LatticeAnchorRef + where + A: LatticeAnchorExt, + { + let hash = anchor.anchor_id(); + *interned + .entry(hash) + .or_insert_with(|| ::alloc(anchor, alloc)) + } +} + +impl core::ops::Deref for LatticeAnchorRef { + type Target = dyn LatticeAnchor; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + unsafe { self.0.as_ref() } + } +} + +impl core::convert::AsRef for LatticeAnchorRef { + #[inline(always)] + fn as_ref(&self) -> &dyn LatticeAnchor { + unsafe { self.0.as_ref() } + } +} + +impl Eq for LatticeAnchorRef {} + +impl PartialEq for LatticeAnchorRef { + fn eq(&self, other: &Self) -> bool { + unsafe { self.0.as_ref().dyn_eq(other.0.as_ref()) } + } +} + +impl Hash for LatticeAnchorRef { + fn hash(&self, state: &mut H) { + unsafe { + self.0.as_ref().dyn_hash(state); + } + } +} + +impl Spanned for LatticeAnchorRef { + fn span(&self) -> SourceSpan { + unsafe { self.0.as_ref().span() } + } +} + +impl fmt::Debug for LatticeAnchorRef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Debug::fmt(unsafe { self.0.as_ref() }, f) + } +} + +impl fmt::Display for LatticeAnchorRef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Display::fmt(unsafe { self.0.as_ref() }, f) + } +} + +/// An abstraction over lattice anchors. +/// +/// In classical data-flow analysis, lattice anchors represent positions in a program to which +/// lattice elements are attached. In sparse data-flow analysis, these can be SSA values, and in +/// dense data-flow analysis, these are the program points before and after every operation. +/// +/// [LatticeAnchor] provides the means to represent and work with any type of anchor. +pub trait LatticeAnchor: + Any + Spanned + fmt::Debug + fmt::Display + DynPartialEq + DynHash +{ + fn is_value(&self) -> bool { + false + } + + fn as_value(&self) -> Option { + None + } + + fn is_valid_program_point(&self) -> bool { + false + } + + fn as_program_point(&self) -> Option { + None + } +} + +impl LatticeAnchor for LatticeAnchorRef { + #[inline] + fn is_value(&self) -> bool { + self.as_ref().is_value() + } + + #[inline] + fn as_value(&self) -> Option { + self.as_ref().as_value() + } + + #[inline] + fn is_valid_program_point(&self) -> bool { + self.as_ref().is_valid_program_point() + } + + #[inline] + fn as_program_point(&self) -> Option { + self.as_ref().as_program_point() + } +} + +impl LatticeAnchor for ProgramPoint { + fn is_valid_program_point(&self) -> bool { + true + } + + fn as_program_point(&self) -> Option { + Some(*self) + } +} + +impl LatticeAnchor for Operation { + fn is_valid_program_point(&self) -> bool { + true + } + + fn as_program_point(&self) -> Option { + Some(ProgramPoint::before(self)) + } +} + +impl LatticeAnchor for Block { + fn is_valid_program_point(&self) -> bool { + true + } + + fn as_program_point(&self) -> Option { + Some(ProgramPoint::at_start_of(self)) + } +} + +impl LatticeAnchor for BlockArgument { + fn is_value(&self) -> bool { + true + } + + fn as_value(&self) -> Option { + Some(self.as_value_ref()) + } +} + +impl LatticeAnchor for OpResult { + fn is_value(&self) -> bool { + true + } + + fn as_value(&self) -> Option { + Some(self.as_value_ref()) + } +} + +impl LatticeAnchor for dyn Value { + fn is_value(&self) -> bool { + true + } + + fn as_value(&self) -> Option { + Some(unsafe { ValueRef::from_raw(self) }) + } +} + +impl LatticeAnchor for RawEntityRef { + default fn is_value(&self) -> bool { + false + } + + default fn as_value(&self) -> Option { + None + } + + default fn is_valid_program_point(&self) -> bool { + false + } + + default fn as_program_point(&self) -> Option { + None + } +} + +impl LatticeAnchor for ValueRef { + fn is_value(&self) -> bool { + true + } + + fn as_value(&self) -> Option { + Some(*self) + } +} + +impl LatticeAnchor for BlockArgumentRef { + fn is_value(&self) -> bool { + true + } + + fn as_value(&self) -> Option { + Some(*self) + } +} + +impl LatticeAnchor for OpResultRef { + fn is_value(&self) -> bool { + true + } + + fn as_value(&self) -> Option { + Some(*self) + } +} + +impl LatticeAnchor for OperationRef { + fn is_valid_program_point(&self) -> bool { + true + } + + fn as_program_point(&self) -> Option { + Some(ProgramPoint::before(*self)) + } +} + +impl LatticeAnchor for BlockRef { + fn is_valid_program_point(&self) -> bool { + true + } + + fn as_program_point(&self) -> Option { + Some(ProgramPoint::at_start_of(*self)) + } +} + +#[doc(hidden)] +pub trait LatticeAnchorExt: sealed::IsLatticeAnchor { + fn anchor_id(&self) -> u64; + + fn alloc(&self, alloc: &blink_alloc::Blink) -> LatticeAnchorRef; +} + +mod sealed { + use super::LatticeAnchor; + + pub trait IsLatticeAnchor: LatticeAnchor {} + impl IsLatticeAnchor for A {} +} + +impl LatticeAnchorExt for A { + default fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(self) + } + + default fn alloc(&self, alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + let ptr = alloc.put(self.clone()); + LatticeAnchorRef::new(unsafe { NonNull::new_unchecked(ptr) }) + } +} + +impl LatticeAnchorExt for LatticeAnchorRef { + fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(self.as_ref()) + } + + #[inline(always)] + fn alloc(&self, _alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + *self + } +} + +impl LatticeAnchorExt for ValueRef { + fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(&*self.borrow()) + } + + fn alloc(&self, _alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + // We do not need to allocate for IR entity refs, as by definition their context outlives + // the dataflow solver, so we only need to convert the reference to a &dyn LatticeAnchor. + let value = self.borrow(); + let ptr = if let Some(result) = value.downcast_ref::() { + result as &dyn LatticeAnchor as *const dyn LatticeAnchor + } else { + let arg = value.downcast_ref::().unwrap(); + arg as &dyn LatticeAnchor as *const dyn LatticeAnchor + }; + LatticeAnchorRef::new(unsafe { NonNull::new_unchecked(ptr.cast_mut()) }) + } +} + +impl LatticeAnchorExt for BlockArgumentRef { + fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(&*self.borrow()) + } + + fn alloc(&self, _alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + let ptr = &*self.borrow() as &dyn LatticeAnchor as *const dyn LatticeAnchor; + LatticeAnchorRef::new(unsafe { NonNull::new_unchecked(ptr.cast_mut()) }) + } +} + +impl LatticeAnchorExt for OpResultRef { + fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(&*self.borrow()) + } + + fn alloc(&self, _alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + let ptr = &*self.borrow() as &dyn LatticeAnchor as *const dyn LatticeAnchor; + LatticeAnchorRef::new(unsafe { NonNull::new_unchecked(ptr.cast_mut()) }) + } +} + +impl LatticeAnchorExt for BlockRef { + fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(&*self.borrow()) + } + + fn alloc(&self, _alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + let ptr = &*self.borrow() as &dyn LatticeAnchor as *const dyn LatticeAnchor; + LatticeAnchorRef::new(unsafe { NonNull::new_unchecked(ptr.cast_mut()) }) + } +} + +impl LatticeAnchorExt for OperationRef { + fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(&*self.borrow()) + } + + fn alloc(&self, _alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + let ptr = &*self.borrow() as &dyn LatticeAnchor as *const dyn LatticeAnchor; + LatticeAnchorRef::new(unsafe { NonNull::new_unchecked(ptr.cast_mut()) }) + } +} + +impl LatticeAnchorExt for ProgramPoint { + fn anchor_id(&self) -> u64 { + LatticeAnchorRef::compute_hash(self) + } + + fn alloc(&self, alloc: &blink_alloc::Blink) -> LatticeAnchorRef { + let ptr = alloc.put(*self); + LatticeAnchorRef::new(unsafe { NonNull::new_unchecked(ptr) }) + } +} diff --git a/hir-analysis/src/change_result.rs b/hir-analysis/src/change_result.rs new file mode 100644 index 000000000..5e38b7827 --- /dev/null +++ b/hir-analysis/src/change_result.rs @@ -0,0 +1,70 @@ +use core::fmt; + +/// A result type used to indicatee if a change happened. +/// +/// Supports boolean operations, with `Changed` representing a `true` value +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ChangeResult { + Unchanged, + Changed, +} + +impl ChangeResult { + #[inline] + pub fn changed(&self) -> bool { + matches!(self, Self::Changed) + } +} + +impl fmt::Display for ChangeResult { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + if self.changed() { + f.write_str("changed") + } else { + f.write_str("unchanged") + } + } +} + +impl core::ops::BitOr for ChangeResult { + type Output = ChangeResult; + + #[inline] + fn bitor(self, rhs: Self) -> Self::Output { + if matches!(self, Self::Changed) { + self + } else { + rhs + } + } +} +impl core::ops::BitAnd for ChangeResult { + type Output = ChangeResult; + + #[inline] + fn bitand(self, rhs: Self) -> Self::Output { + if matches!(self, Self::Unchanged) { + self + } else { + rhs + } + } +} +impl core::ops::BitOrAssign for ChangeResult { + #[inline] + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; + } +} +impl core::ops::BitAndAssign for ChangeResult { + #[inline] + fn bitand_assign(&mut self, rhs: Self) { + *self = *self & rhs; + } +} +impl From for bool { + #[inline] + fn from(value: ChangeResult) -> Self { + value.changed() + } +} diff --git a/hir-analysis/src/config.rs b/hir-analysis/src/config.rs new file mode 100644 index 000000000..c53ad7158 --- /dev/null +++ b/hir-analysis/src/config.rs @@ -0,0 +1,29 @@ +/// Configuration for the data flow solver and child analyses. +#[derive(Debug, Default, Clone)] +pub struct DataFlowConfig { + /// Indicates whether the solver should operation interprocedurally + interprocedural: bool, +} + +impl DataFlowConfig { + /// Get a new, default configuration + #[inline] + pub fn new() -> Self { + Self::default() + } + + #[inline(always)] + pub const fn is_interprocedural(&self) -> bool { + self.interprocedural + } + + /// Set whether the solver should operate interprocedurally, i.e. enter the callee body when + /// available. + /// + /// Interprocedural analyses may be more precise, but also more expensive as more states need to + /// be computed and the fixpoint convergence takes longer. + pub fn set_interprocedural(&mut self, yes: bool) -> &mut Self { + self.interprocedural = yes; + self + } +} diff --git a/hir-analysis/src/control_flow.rs b/hir-analysis/src/control_flow.rs deleted file mode 100644 index efc80b3e6..000000000 --- a/hir-analysis/src/control_flow.rs +++ /dev/null @@ -1,406 +0,0 @@ -use cranelift_bforest as bforest; -use cranelift_entity::SecondaryMap; -use midenc_hir::{ - pass::{Analysis, AnalysisManager, AnalysisResult}, - Block, DataFlowGraph, Function, Inst, Instruction, -}; -use midenc_session::Session; - -/// Represents the predecessor of the current basic block. -/// -/// A predecessor in this context is both the instruction which transfers control to -/// the current block, and the block which encloses that instruction. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct BlockPredecessor { - pub block: Block, - pub inst: Inst, -} -impl BlockPredecessor { - #[inline] - pub fn new(block: Block, inst: Inst) -> Self { - Self { block, inst } - } -} - -/// A node in the control flow graph, which contains the successors and predecessors of a given -/// `Block`. -#[derive(Clone, Default)] -struct Node { - /// Instructions which transfer control to this block - pub predecessors: bforest::Map, - /// Set of blocks that are targets of branches/jumps in this block. - pub successors: bforest::Set, -} - -/// The control flow graph maps all blocks in a function to their predecessor and successor blocks. -pub struct ControlFlowGraph { - data: SecondaryMap, - pred_forest: bforest::MapForest, - succ_forest: bforest::SetForest, - valid: bool, -} -impl Clone for ControlFlowGraph { - fn clone(&self) -> Self { - let mut data = SecondaryMap::::with_capacity(self.data.capacity()); - let mut pred_forest = bforest::MapForest::new(); - let mut succ_forest = bforest::SetForest::new(); - - for (k, v) in self.data.iter() { - let node = &mut data[k]; - for (pk, pv) in v.predecessors.iter(&self.pred_forest) { - node.predecessors.insert(pk, pv, &mut pred_forest, &()); - } - for succ in v.successors.iter(&self.succ_forest) { - node.successors.insert(succ, &mut succ_forest, &()); - } - } - - Self { - data, - pred_forest, - succ_forest, - valid: self.valid, - } - } -} -impl Default for ControlFlowGraph { - fn default() -> Self { - Self { - data: SecondaryMap::default(), - pred_forest: bforest::MapForest::new(), - succ_forest: bforest::SetForest::new(), - valid: false, - } - } -} -impl Analysis for ControlFlowGraph { - type Entity = Function; - - fn analyze( - function: &Self::Entity, - _analyses: &mut AnalysisManager, - _session: &Session, - ) -> AnalysisResult { - Ok(ControlFlowGraph::with_function(function)) - } -} -impl ControlFlowGraph { - pub fn new() -> Self { - Self::default() - } - - /// Reset this control flow graph to its initial state for reuse - pub fn clear(&mut self) { - self.data.clear(); - self.pred_forest.clear(); - self.succ_forest.clear(); - self.valid = false; - } - - /// Obtain a control flow graph computed over `func`. - pub fn with_function(func: &Function) -> Self { - let mut cfg = Self::new(); - cfg.compute(&func.dfg); - cfg - } - - /// Compute the control flow graph for `dfg`. - /// - /// NOTE: This will reset the current state of this graph. - pub fn compute(&mut self, dfg: &DataFlowGraph) { - self.clear(); - self.data.resize(dfg.num_blocks()); - - for (block, _) in dfg.blocks() { - self.compute_block(dfg, block); - } - - self.valid = true; - } - - /// Recompute the control flow graph of `block`. - /// - /// This is for use after modifying instructions within a block. It recomputes all edges - /// from `block` while leaving edges to `block` intact. It performs a restricted version of - /// `compute` which allows us to avoid recomputing the graph for all blocks, only those which - /// are modified by a specific set of changes. - pub fn recompute_block(&mut self, dfg: &DataFlowGraph, block: Block) { - debug_assert!(self.is_valid()); - self.invalidate_block_successors(block); - self.compute_block(dfg, block); - } - - /// Similar to `recompute_block`, this recomputes all edges from `block` as if they had been - /// removed, while leaving edges to `block` intact. It is expected that predecessor blocks - /// will have `recompute_block` subsequently called on them so that `block` is fully removed - /// from the CFG. - pub fn detach_block(&mut self, block: Block) { - debug_assert!(self.is_valid()); - self.invalidate_block_successors(block); - } - - /// Return the number of predecessors for `block` - pub fn num_predecessors(&self, block: Block) -> usize { - self.data[block].predecessors.iter(&self.pred_forest).count() - } - - /// Return the number of successors for `block` - pub fn num_successors(&self, block: Block) -> usize { - self.data[block].successors.iter(&self.succ_forest).count() - } - - /// Get an iterator over the CFG predecessors to `block`. - pub fn pred_iter(&self, block: Block) -> PredIter { - PredIter(self.data[block].predecessors.iter(&self.pred_forest)) - } - - /// Get an iterator over the CFG successors to `block`. - pub fn succ_iter(&self, block: Block) -> SuccIter { - debug_assert!(self.is_valid()); - self.data[block].successors.iter(&self.succ_forest) - } - - /// Check if the CFG is in a valid state. - /// - /// Note that this doesn't perform any kind of validity checks. It simply checks if the - /// `compute()` method has been called since the last `clear()`. It does not check that the - /// CFG is consistent with the function. - pub fn is_valid(&self) -> bool { - self.valid - } - - fn compute_block(&mut self, dfg: &DataFlowGraph, block: Block) { - visit_block_succs(dfg, block, |inst, dest, _| { - self.add_edge(block, inst, dest); - }); - } - - fn invalidate_block_successors(&mut self, block: Block) { - use core::mem; - - let mut successors = mem::take(&mut self.data[block].successors); - for succ in successors.iter(&self.succ_forest) { - self.data[succ] - .predecessors - .retain(&mut self.pred_forest, |_, &mut e| e != block); - } - successors.clear(&mut self.succ_forest); - } - - fn add_edge(&mut self, from: Block, from_inst: Inst, to: Block) { - self.data[from].successors.insert(to, &mut self.succ_forest, &()); - self.data[to].predecessors.insert(from_inst, from, &mut self.pred_forest, &()); - } -} - -/// An iterator over block predecessors. The iterator type is `BlockPredecessor`. -/// -/// Each predecessor is an instruction that branches to the block. -pub struct PredIter<'a>(bforest::MapIter<'a, Inst, Block>); - -impl<'a> Iterator for PredIter<'a> { - type Item = BlockPredecessor; - - fn next(&mut self) -> Option { - self.0.next().map(|(i, e)| BlockPredecessor::new(e, i)) - } -} - -/// An iterator over block successors. The iterator type is `Block`. -pub type SuccIter<'a> = bforest::SetIter<'a, Block>; - -/// Visit all successors of a block with a given visitor closure. The closure -/// arguments are the branch instruction that is used to reach the successor, -/// the successor block itself, and a flag indicating whether the block is -/// branched to via a table entry. -pub(crate) fn visit_block_succs( - dfg: &DataFlowGraph, - block: Block, - mut visit: F, -) { - use midenc_hir::{Br, CondBr, Switch}; - - if let Some(inst) = dfg.last_inst(block) { - match &dfg[inst] { - Instruction::Br(Br { successor, .. }) => { - visit(inst, successor.destination, false); - } - - Instruction::CondBr(CondBr { - then_dest, - else_dest, - .. - }) => { - visit(inst, then_dest.destination, false); - visit(inst, else_dest.destination, false); - } - - Instruction::Switch(Switch { - ref arms, - default: default_succ, - .. - }) => { - visit(inst, default_succ.destination, false); - - for arm in arms.as_slice() { - visit(inst, arm.successor.destination, true); - } - } - - inst => debug_assert!(!inst.opcode().is_branch()), - } - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::*; - - use super::*; - - #[test] - fn cfg_empty() { - let dfg = DataFlowGraph::default(); - - let mut cfg = ControlFlowGraph::new(); - cfg.compute(&dfg); - } - - #[test] - fn cfg_no_predecessors() { - let mut dfg = DataFlowGraph::default(); - - let _block0 = dfg.create_block(); - let _block1 = dfg.create_block(); - let _block2 = dfg.create_block(); - - let mut cfg = ControlFlowGraph::new(); - cfg.compute(&dfg); - - let mut blocks = dfg.blocks().map(|(blk, _)| blk); - for block in dfg.blocks().map(|(blk, _)| blk) { - assert_eq!(block, blocks.next().unwrap()); - assert_eq!(cfg.pred_iter(block).count(), 0); - assert_eq!(cfg.succ_iter(block).count(), 0); - } - } - - #[test] - fn cfg_branches_and_jumps() { - let diagnostics = diagnostics::DiagnosticsHandler::default(); - - // Define the 'test' module - let mut builder = ModuleBuilder::new("test"); - - // Declare the `fib` function, with the appropriate type signature - let sig = Signature { - params: vec![AbiParam::new(Type::I32)], - results: vec![AbiParam::new(Type::I32)], - cc: CallConv::SystemV, - linkage: Linkage::External, - }; - let mut fb = - builder.function("branches_and_jumps", sig).expect("unexpected symbol conflict"); - - let block0 = fb.entry_block(); - let cond = { - let args = fb.block_params(block0); - args[0] - }; - - let block1 = fb.create_block(); - let block2 = fb.create_block(); - - let cond = fb.ins().trunc(cond, Type::I1, SourceSpan::default()); - let br_block0_block2_block1 = - fb.ins().cond_br(cond, block2, &[], block1, &[], SourceSpan::default()); - fb.switch_to_block(block1); - let br_block1_block1_block2 = - fb.ins().cond_br(cond, block1, &[], block2, &[], SourceSpan::default()); - - let id = fb - .build(&diagnostics) - .expect("unexpected validation error, see diagnostics output"); - - let mut module = builder.build(); - let mut function = module.unlink(id.function); - - let mut cfg = ControlFlowGraph::with_function(&function); - - { - let block0_predecessors = cfg.pred_iter(block0).collect::>(); - let block1_predecessors = cfg.pred_iter(block1).collect::>(); - let block2_predecessors = cfg.pred_iter(block2).collect::>(); - - let block0_successors = cfg.succ_iter(block0).collect::>(); - let block1_successors = cfg.succ_iter(block1).collect::>(); - let block2_successors = cfg.succ_iter(block2).collect::>(); - - assert_eq!(block0_predecessors.len(), 0); - assert_eq!(block1_predecessors.len(), 2); - assert_eq!(block2_predecessors.len(), 2); - - assert!(block1_predecessors - .contains(&BlockPredecessor::new(block0, br_block0_block2_block1))); - assert!(block1_predecessors - .contains(&BlockPredecessor::new(block1, br_block1_block1_block2))); - assert!(block2_predecessors - .contains(&BlockPredecessor::new(block0, br_block0_block2_block1))); - assert!(block2_predecessors - .contains(&BlockPredecessor::new(block1, br_block1_block1_block2))); - - assert_eq!(block0_successors, [block1, block2]); - assert_eq!(block1_successors, [block1, block2]); - assert_eq!(block2_successors, []); - } - - // Add a new block to hold a return instruction - let ret_block; - { - let mut builder = FunctionBuilder::new(&mut function); - ret_block = builder.create_block(); - builder.switch_to_block(ret_block); - builder.ins().ret(None, SourceSpan::default()); - } - - // Change some instructions and recompute block0 and ret_block - function.dfg.replace(br_block0_block2_block1).cond_br( - cond, - block1, - &[], - ret_block, - &[], - SourceSpan::default(), - ); - cfg.recompute_block(&function.dfg, block0); - cfg.recompute_block(&function.dfg, ret_block); - let br_block0_block1_ret_block = br_block0_block2_block1; - - { - let block0_predecessors = cfg.pred_iter(block0).collect::>(); - let block1_predecessors = cfg.pred_iter(block1).collect::>(); - let block2_predecessors = cfg.pred_iter(block2).collect::>(); - - let block0_successors = cfg.succ_iter(block0); - let block1_successors = cfg.succ_iter(block1); - let block2_successors = cfg.succ_iter(block2); - - assert_eq!(block0_predecessors.len(), 0); - assert_eq!(block1_predecessors.len(), 2); - assert_eq!(block2_predecessors.len(), 1); - - assert!(block1_predecessors - .contains(&BlockPredecessor::new(block0, br_block0_block1_ret_block)),); - assert!(block1_predecessors - .contains(&BlockPredecessor::new(block1, br_block1_block1_block2)),); - assert!(!block2_predecessors - .contains(&BlockPredecessor::new(block0, br_block0_block1_ret_block)),); - assert!(block2_predecessors - .contains(&BlockPredecessor::new(block1, br_block1_block1_block2)),); - - assert_eq!(block0_successors.collect::>(), [block1, ret_block]); - assert_eq!(block1_successors.collect::>(), [block1, block2]); - assert_eq!(block2_successors.collect::>(), []); - } - } -} diff --git a/hir-analysis/src/data.rs b/hir-analysis/src/data.rs deleted file mode 100644 index ffa0ea8d2..000000000 --- a/hir-analysis/src/data.rs +++ /dev/null @@ -1,155 +0,0 @@ -use midenc_hir::{ - pass::{Analysis, AnalysisManager, AnalysisResult}, - Function, FunctionIdent, GlobalValue, GlobalValueData, GlobalVariableTable, Module, Program, -}; -use midenc_session::Session; -use rustc_hash::FxHashMap; - -/// This analysis calculates the addresses/offsets of all global variables in a [Program] or -/// [Module] -pub struct GlobalVariableAnalysis { - layout: GlobalVariableLayout, - _marker: core::marker::PhantomData, -} -impl Default for GlobalVariableAnalysis { - fn default() -> Self { - Self { - layout: Default::default(), - _marker: core::marker::PhantomData, - } - } -} -impl GlobalVariableAnalysis { - pub fn layout(&self) -> &GlobalVariableLayout { - &self.layout - } -} - -impl Analysis for GlobalVariableAnalysis { - type Entity = Program; - - fn analyze( - program: &Self::Entity, - _analyses: &mut AnalysisManager, - _session: &Session, - ) -> AnalysisResult { - let mut layout = GlobalVariableLayout { - global_table_offset: core::cmp::max( - program.reserved_memory_bytes().next_multiple_of(32), - program.segments().next_available_offset(), - ), - ..GlobalVariableLayout::default() - }; - - let globals = program.globals(); - for module in program.modules().iter() { - for function in module.functions() { - let mut function_offsets = FxHashMap::default(); - for gv in function.dfg.globals.keys() { - if let Some(addr) = - compute_global_value_addr(gv, layout.global_table_offset, function, globals) - { - function_offsets.insert(gv, addr); - } - } - layout.offsets.insert(function.id, function_offsets); - } - } - - Ok(Self { - layout, - _marker: core::marker::PhantomData, - }) - } -} - -impl Analysis for GlobalVariableAnalysis { - type Entity = Module; - - fn analyze( - module: &Self::Entity, - _analyses: &mut AnalysisManager, - _session: &Session, - ) -> AnalysisResult { - let mut layout = GlobalVariableLayout { - global_table_offset: core::cmp::max( - module.reserved_memory_bytes().next_multiple_of(32), - module.segments().next_available_offset(), - ), - ..GlobalVariableLayout::default() - }; - - let globals = module.globals(); - for function in module.functions() { - let mut function_offsets = FxHashMap::default(); - for gv in function.dfg.globals.keys() { - if let Some(addr) = - compute_global_value_addr(gv, layout.global_table_offset, function, globals) - { - function_offsets.insert(gv, addr); - } - } - layout.offsets.insert(function.id, function_offsets); - } - - Ok(Self { - layout, - _marker: core::marker::PhantomData, - }) - } -} - -/// This struct contains data about the layout of global variables in linear memory -#[derive(Default, Clone)] -pub struct GlobalVariableLayout { - global_table_offset: u32, - offsets: FxHashMap>, -} -impl GlobalVariableLayout { - /// Get the address/offset at which global variables will start being allocated - pub fn global_table_offset(&self) -> u32 { - self.global_table_offset - } - - /// Get the statically-allocated address at which the global value `gv` for `function` is - /// stored. - /// - /// This function returns `None` if the analysis does not know about `function`, `gv`, or if - /// the symbol which `gv` resolves to was undefined. - pub fn get_computed_addr(&self, function: &FunctionIdent, gv: GlobalValue) -> Option { - self.offsets.get(function).and_then(|offsets| offsets.get(&gv).copied()) - } -} - -/// Computes the absolute offset (address) represented by the given global value -fn compute_global_value_addr( - mut gv: GlobalValue, - global_table_offset: u32, - function: &Function, - globals: &GlobalVariableTable, -) -> Option { - let mut relative_offset = 0; - loop { - let gv_data = function.dfg.global_value(gv); - relative_offset += gv_data.offset(); - match gv_data { - GlobalValueData::Symbol { name, .. } => { - let var = globals.find(*name)?; - let base_offset = unsafe { globals.offset_of(var) }; - if relative_offset >= 0 { - return Some((global_table_offset + base_offset) + relative_offset as u32); - } else { - return Some( - (global_table_offset + base_offset) - relative_offset.unsigned_abs(), - ); - } - } - GlobalValueData::IAddImm { base, .. } => { - gv = *base; - } - GlobalValueData::Load { base, .. } => { - gv = *base; - } - } - } -} diff --git a/hir-analysis/src/def_use.rs b/hir-analysis/src/def_use.rs deleted file mode 100644 index 25fad3274..000000000 --- a/hir-analysis/src/def_use.rs +++ /dev/null @@ -1,428 +0,0 @@ -use cranelift_entity::SecondaryMap; -use intrusive_collections::{intrusive_adapter, LinkedListLink}; -use midenc_hir::{BranchInfo, DataFlowGraph, Inst, Instruction, Value, ValueData}; - -use crate::DominatorTree; - -/// The def-use graph provides us with three useful pieces of information: -/// -/// * The set of users for any value in a function -/// * Whether a value is ever used -/// * The nearest dominating definition for a specific use of a value, taking into account spills -/// -/// It is computed by visiting the reachable blocks of a function, and building a graph of all the -/// value definitions and their uses. -#[derive(Default)] -pub struct DefUseGraph { - values: SecondaryMap, -} -impl DefUseGraph { - /// Get the set of users for `value` - pub fn users(&self, value: Value) -> &Users { - &self.values[value] - } - - /// Get a mutable reference to the set of users for `value` - pub fn users_mut(&mut self, value: Value) -> &mut Users { - &mut self.values[value] - } - - /// Returns true if `value` has any reachable uses - pub fn is_used(&self, value: Value) -> bool { - !self.values[value].is_empty() - } - - /// This function will return the nearest definition of `value` which dominates `user` - /// in the CFG, taking into account the reload pseudo-instruction, such that reloads of - /// `value` are considered definitions. - /// - /// TODO(pauls): Look into treating aliases created via block arguments as valid definitions. - pub fn nearest_dominating_definition( - &self, - user: Inst, - value: Value, - dfg: &DataFlowGraph, - domtree: &DominatorTree, - ) -> Value { - // Check if `value` is defined by any instructions in the block containing `user`, - // between `user` and the block header. If no definition is found, check if the - // block itself defines the value via its parameter list. - if let Some(found) = dfg.nearest_definition_in_block(user, value) { - return found; - } - - // If we didn't find a definition in the current block, and the current block is - // in the iterated dominance frontier of the reloads of `value`, then we would - // expect there to be a block argument introduced in the current block which joins - // together multiple control-dependent definitions of `value` which dominate the - // uses in this block. - // - // In such cases, we return an error indicating that we have identified a missing phi. - // - // The caller must choose how to proceed, but the assumption is that the caller will - // insert a new block argument, wire up definitions of `value` to that phi, and use - // the new phi value as the nearest dominating definition. - - // If we reach here, then the containing block has no definition for `value`, so the next - // place where a valid definition can occur, is in the immediate dominator of `user_block`. - // - // The following process is repeated recursively until we reach a definition of `value`: - // - // 1. Find the immediate dominator of the current block, and get a cursor to the end of the - // instruction list of that block - // 2. Walk up the block from the end, looking for a valid definition of `value` - // 3. If we reach the block header, check if `value` is defined as a block parameter in that - // block, and if not, go back to step 1. - // - // TODO(pauls): If we observe that the value we're interested in, is passed as a block - // argument to a block that dominates (or contains) `user`, we can treat the block - // parameter as a valid definition of `value`, and rewrite uses of `value` to use - // the block parameter instead. This would have the effect of reducing the live - // range of `value`, reducing operand stack pressure, and potentially removing the - // need for some spills/reloads. - let mut current_block = dfg.inst_block(user).unwrap(); - while let Some(idom) = domtree.idom(current_block) { - current_block = dfg.inst_block(idom).unwrap(); - - if let Some(found) = dfg.nearest_definition_in_block(idom, value) { - return found; - } - } - - // If we reach here, then the current block must be the entry block or unreachable, and we - // did not find the definition in that block. In this case we must raise an - // assertion, because it should not be possible to reach this point with a valid - // dataflow graph. - unreachable!("expected to find a definition for {value}, but no valid definition exists"); - } - - pub fn replace_uses_in( - &mut self, - value: Value, - replacement: Value, - user: Inst, - dfg: &mut DataFlowGraph, - ) { - // Remove all users of `value` from `user` in the def/use graph - let mut replacing = UserList::default(); - { - let mut cursor = self.values[value].cursor_mut(); - while let Some(current_use) = cursor.get() { - if current_use.inst == user && current_use.value == value { - replacing.push_back(cursor.remove().unwrap()); - } - } - } - - // Rewrite the dataflow graph to effect the replacements - for current_use in replacing.iter() { - match current_use.ty { - Use::Operand { index } => { - let args = dfg.insts[current_use.inst].arguments_mut(&mut dfg.value_lists); - args[index as usize] = replacement; - } - Use::BlockArgument { succ, index } => match dfg.insts[current_use.inst].as_mut() { - Instruction::Br(midenc_hir::Br { - ref mut successor, .. - }) => { - assert_eq!(succ, 0); - let args = successor.args.as_mut_slice(&mut dfg.value_lists); - args[index as usize] = replacement; - } - Instruction::CondBr(midenc_hir::CondBr { - ref mut then_dest, - ref mut else_dest, - .. - }) => { - let args = match succ { - 0 => then_dest.args.as_mut_slice(&mut dfg.value_lists), - 1 => else_dest.args.as_mut_slice(&mut dfg.value_lists), - n => unreachable!( - "unexpected successor index {n} for conditional branch" - ), - }; - args[index as usize] = replacement; - } - Instruction::Switch(midenc_hir::Switch { - ref mut arms, - default: ref mut default_succ, - .. - }) => { - let succ = succ as usize; - assert!( - succ < arms.len() + 1, - "invalid successor index {succ}: but only {} arms plus fallback", - arms.len() - ); - let args = if arms.len() == succ { - default_succ.args.as_mut_slice(&mut dfg.value_lists) - } else { - arms[succ].successor.args.as_mut_slice(&mut dfg.value_lists) - }; - args[index as usize] = replacement; - } - _ => unreachable!(), - }, - } - } - - // Add new uses of `replacement` to `user` in the def/use graph - let replacement_users = &mut self.values[replacement]; - for mut current_use in replacing.into_iter() { - current_use.value = replacement; - replacement_users.push_back(current_use); - } - } - - pub fn compute(dfg: &DataFlowGraph, domtree: &DominatorTree) -> Self { - // For now, we're interested in computing a def/use graph that only contains definitions - // and uses actually in the function layout, and which are reachable from the entry. This - // means that some valid values which are in the function layout, but in an unreachable - // block, will be treated as undefined and/or as having no users. A more complete def/use - // graph can be computed directly from the `DataFlowGraph`, but would then require us to - // always validate that a given definition (or use) is one we are actually interested in, - // so in essence we are just pre-filtering the graph. - let mut graph = Self { - values: SecondaryMap::with_capacity(dfg.values.len()), - }; - for block in domtree.cfg_postorder().iter().rev().copied() { - for arg in dfg.block_args(block) { - graph.define(*arg); - } - for inst in dfg.block_insts(block) { - match dfg.analyze_branch(inst) { - BranchInfo::NotABranch => { - for result in dfg.inst_results(inst) { - graph.define(*result); - } - graph.insert_operand_uses(inst, dfg, domtree); - } - BranchInfo::SingleDest(successor) => { - debug_assert_eq!( - dfg.inst_results(inst), - &[], - "branch instructions cannot have results" - ); - graph.insert_operand_uses(inst, dfg, domtree); - for (index, value) in successor.args.iter().copied().enumerate() { - debug_assert!(def_dominates_use(value, inst, dfg, domtree)); - let user = Box::new(User { - link: Default::default(), - inst, - value, - ty: Use::BlockArgument { - succ: 0, - index: u16::try_from(index).expect("too many arguments"), - }, - }); - graph.insert_use(user); - } - } - BranchInfo::MultiDest(ref successors) => { - debug_assert_eq!( - dfg.inst_results(inst), - &[], - "branch instructions cannot have results" - ); - graph.insert_operand_uses(inst, dfg, domtree); - for (succ, successor) in successors.iter().enumerate() { - let succ = u16::try_from(succ).expect("too many successors"); - for (index, value) in successor.args.iter().copied().enumerate() { - debug_assert!(def_dominates_use(value, inst, dfg, domtree)); - let user = Box::new(User { - link: Default::default(), - inst, - value, - ty: Use::BlockArgument { - succ, - index: u16::try_from(index).expect("too many arguments"), - }, - }); - graph.insert_use(user); - } - } - } - } - } - } - - graph - } - - #[inline] - fn define(&mut self, value: Value) { - self.values[value] = Users::default(); - } - - #[inline] - fn insert_use(&mut self, user: Box) { - self.values[user.value].push_back(user); - } - - fn insert_operand_uses(&mut self, inst: Inst, dfg: &DataFlowGraph, domtree: &DominatorTree) { - for (index, value) in dfg.inst_args(inst).iter().copied().enumerate() { - debug_assert!(def_dominates_use(value, inst, dfg, domtree)); - let user = Box::new(User { - link: Default::default(), - inst, - value, - ty: Use::Operand { - index: u16::try_from(index).expect("too many arguments"), - }, - }); - self.insert_use(user); - } - } -} - -fn def_dominates_use( - value: Value, - user: Inst, - dfg: &DataFlowGraph, - domtree: &DominatorTree, -) -> bool { - match dfg.value_data(value) { - ValueData::Inst { inst, .. } => domtree.dominates(*inst, user, dfg), - ValueData::Param { block, .. } => { - if dfg.inst_block(user).unwrap() == *block { - true - } else { - domtree.dominates(*block, user, dfg) - } - } - } -} - -intrusive_adapter!(pub UserAdapter = Box: User { link: LinkedListLink }); - -pub type UserList = intrusive_collections::LinkedList; -pub type UserCursorMut<'a> = intrusive_collections::linked_list::CursorMut<'a, UserAdapter>; - -/// An intrusively-linked list of [User] records -#[derive(Default)] -pub struct Users { - list: intrusive_collections::LinkedList, -} -impl Clone for Users { - fn clone(&self) -> Self { - Self { - list: Default::default(), - } - } -} -impl Users { - /// Returns true if there are no users - pub fn is_empty(&self) -> bool { - self.list.is_empty() - } - - /// Append a [User] to the end of the list - pub fn push_back(&mut self, user: Box) { - self.list.push_back(user); - } - - /// Pop a [User] from the front of the list - pub fn pop_front(&mut self) -> Option> { - self.list.pop_front() - } - - /// Get an iterator over the [User]s in this list - pub fn iter(&self) -> impl Iterator + '_ { - self.list.iter() - } - - /// Get a cursor to the front of this list - pub fn cursor_mut(&mut self) -> UserCursorMut<'_> { - self.list.front_mut() - } - - /// Steal the [User]s from this list, as a new [Users] list - pub fn take(&mut self) -> Self { - Self { - list: self.list.take(), - } - } -} - -/// A [User] represents information about the source of a [Use] of a [Value]. -/// -/// More specifically, it tells us what instruction is the user, what value is used, and the type -/// of use. An instruction can be a [User] of multiple values, and can be a [User] of the same -/// value multiple times, once for each [Use]. -#[derive(Debug, Clone)] -pub struct User { - link: LinkedListLink, - /// The user is always an instruction - pub inst: Inst, - /// The value being used - pub value: Value, - /// The type of use - pub ty: Use, -} -impl User { - /// Construct a new [User] from its constituent parts - #[inline] - pub fn new(inst: Inst, value: Value, ty: Use) -> Self { - Self { - link: Default::default(), - inst, - value, - ty, - } - } -} -impl Eq for User {} -impl PartialEq for User { - fn eq(&self, other: &Self) -> bool { - self.inst == other.inst && self.value == other.value && self.ty == other.ty - } -} -impl core::hash::Hash for User { - fn hash(&self, state: &mut H) { - self.inst.hash(state); - self.value.hash(state); - self.ty.hash(state); - } -} - -/// A [Use] describes how a specific value is used within an [Instruction], i.e. what type of -/// argument and the index of that argument. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Use { - /// The value is used as an operand at `index` - Operand { index: u16 }, - /// The value is used as the block argument at `index`, for successor at index `succ` - BlockArgument { - /// The index of the successor, where successor indices are computed based on the - /// order in which they are referenced by the instruction, i.e. a `cond_br` instruction - /// always has two successors, the first would have index 0, the second index 1. - /// - /// We reference blocks this way, rather than using the block identifier, to handle - /// cases like the `switch` instruction, where a block can be referenced multiple times, - /// unlike `cond_br`, which requires that its two successors are unique. In order to - /// disambiguate block references for `switch`, we use the successor index, as it makes - /// for convenient access in conjunction with `analyze_branch`. - succ: u16, - /// The index of the block argument, when considering the argument list for the given - /// successor block identified by `succ` - index: u16, - }, -} -impl Use { - /// Returns true if the use is as an instruction operand - pub fn is_operand(&self) -> bool { - matches!(self, Use::Operand { .. }) - } - - /// Returns true if the use is as a successor block argument - pub fn is_block_argument(&self) -> bool { - matches!(self, Use::BlockArgument { .. }) - } - - /// Returns the index of the use in its respective argument list - pub fn index(&self) -> usize { - match self { - Self::Operand { index } | Self::BlockArgument { index, .. } => *index as usize, - } - } -} diff --git a/hir-analysis/src/dense.rs b/hir-analysis/src/dense.rs new file mode 100644 index 000000000..05637564b --- /dev/null +++ b/hir-analysis/src/dense.rs @@ -0,0 +1,569 @@ +mod backward; +mod forward; +mod lattice; + +use midenc_hir::{ + cfg::Graph, dominance::DominanceInfo, loops::LoopForest, pass::AnalysisManager, Backward, + Block, BlockRef, CallOpInterface, EntityWithId, Forward, Operation, ProgramPoint, + RegionBranchOpInterface, RegionKindInterface, RegionRef, Report, Spanned, SymbolTable, +}; + +pub use self::{ + backward::DenseBackwardDataFlowAnalysis, forward::DenseForwardDataFlowAnalysis, + lattice::DenseLattice, +}; +use super::{AnalysisStrategy, DataFlowAnalysis, DataFlowSolver, Dense}; +use crate::analyses::{dce::CfgEdge, LoopAction, LoopState}; + +/// This type provides an [AnalysisStrategy] for dense data-flow analyses. +/// +/// In short, it implements the [DataFlowAnalysis] trait, and handles all of the boilerplate that +/// any well-structured dense data-flow analysis requires. Analyses can make use of this strategy +/// by implementing one of the dense data-flow analysis traits, which [DenseDataFlowAnalysis] will +/// use to delegate analysis-specific details to the analysis implementation. The two traits are: +/// +/// * [DenseForwardDataFlowAnalysis], for forward-propagating dense analyses +/// * [DenseBackwardDataFlowAnalysis], for backward-propagating dense analyses +/// +/// ## What is a dense analysis? +/// +/// A dense data-flow analysis is one which associates analysis state with program points, in order +/// to represent the evolution of some state as the program executes (in the case of forward +/// analysis), or to derive facts about program points based on the possible paths that execution +/// might take (backward analysis). +/// +/// This is in contrast to sparse data-flow analysis, which associates state with SSA values at +/// their definition, and thus the state does not evolve as the program executes. This is also where +/// the distinction between _dense_ and _sparse_ comes from - program points are dense, while SSA +/// value definitions are sparse (insofar as the state associated with an SSA value only ever occurs +/// once, while states associated with program points are duplicated at each point). +/// +/// Some examples of dense analyses: +/// +/// * Dead code analysis - at the extreme, indicates whether every program point is executable, i.e. +/// "live", or not. In practice, dead code analysis tends to only associate its state with +/// specific control-flow edges, i.e. changes to the state only occur at block boundaries. +/// * Reaching definition analysis - tracks, for each program point, the set of values whose +/// definitions reach that point. +/// * Dead store analysis - determines for each store instruction in a function, whether or not the +/// stored value is ever read. For example, if you are initializing a struct, and set some field +/// `foo` to `1`, and then set it to `2`, the first store is never observable, and so the store +/// could be eliminated entirely. +/// +/// ## Usage +/// +/// This type is meant to be used indirectly, as an [AnalysisStrategy] implementation, rather than +/// directly as a [DataFlowAnalysis] implementation, as shown below: +/// +/// ```rust,ignore +/// use midenc_hir::dataflow::*; +/// +/// #[derive(Default)] +/// pub struct MyAnalysis; +/// impl BuildableDataFlowAnalysis for MyAnalysis { +/// type Strategy = DenseDataFlowAnalysis; +/// +/// fn new(_solver: &mut DataFlowSolver) -> Self { +/// Self +/// } +/// } +/// impl DenseForwardDataFlowAnalysis for MyAnalysis { +/// type Lattice = Lattice; +/// +/// //... +/// } +/// ``` +/// +/// The above permits us to load `MyAnalysis` into a `DataFlowSolver` without ever mentioning the +/// `DenseDataFlowAnalysis` type at all, like so: +/// +/// ```rust,ignore +/// let mut solver = DataFlowSolver::default(); +/// solver.load::(); +/// solver.initialize_and_run(&op, analysis_manager); +/// ``` +pub struct DenseDataFlowAnalysis { + analysis: T, + _direction: core::marker::PhantomData, +} + +impl AnalysisStrategy for DenseDataFlowAnalysis { + type Direction = Forward; + type Kind = Dense; + + fn build(analysis: A, _solver: &mut DataFlowSolver) -> Self { + Self { + analysis, + _direction: core::marker::PhantomData, + } + } +} + +impl AnalysisStrategy for DenseDataFlowAnalysis { + type Direction = Backward; + type Kind = Dense; + + fn build(analysis: A, _solver: &mut DataFlowSolver) -> Self { + Self { + analysis, + _direction: core::marker::PhantomData, + } + } +} + +impl DataFlowAnalysis for DenseDataFlowAnalysis { + #[inline] + fn debug_name(&self) -> &'static str { + self.analysis.debug_name() + } + + fn analysis_id(&self) -> core::any::TypeId { + core::any::TypeId::of::() + } + + /// Initialize the analysis by visiting every program point whose execution may modify the + /// program state; that is, every operation and block. + fn initialize( + &self, + top: &Operation, + solver: &mut DataFlowSolver, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::debug!( + target: self.analysis.debug_name(), + "initializing analysis for {top}", + ); + + forward::process_operation(self, top, solver)?; + + // If the op has SSACFG regions, use the dominator tree analysis, if available, to visit the + // CFG top-down. Otherwise, fall back to a naive iteration over the contents of each region. + // + // If we have a domtree, we don't bother visiting unreachable blocks (i.e. blocks that + // are not in the tree because they are unreachable via the CFG). If we don't have a domtree, + // then all blocks are visited, regardless of reachability. + if !top.has_regions() { + return Ok(()); + } + + let is_ssa_cfg = top + .as_trait::() + .is_none_or(|rki| rki.has_ssa_dominance()); + log::trace!(target: self.analysis.debug_name(), "visiting regions of op (is cfg={is_ssa_cfg})"); + if is_ssa_cfg { + let dominfo = analysis_manager.get_analysis::()?; + for (region_index, region) in top.regions().iter().enumerate() { + // Single-block regions do not require a dominance tree (and do not have one) + if region.has_one_block() { + let block = region.entry(); + log::trace!(target: self.analysis.debug_name(), "initializing single-block region {region_index} from entry: {block}"); + forward::visit_block(self, &block, solver); + log::trace!(target: self.analysis.debug_name(), "initializing {block} in pre-order"); + for op in block.body() { + let child_analysis_manager = analysis_manager.nest(op.as_operation_ref()); + self.initialize(&op, solver, child_analysis_manager)?; + } + } else { + let entry_block = region.entry_block_ref().unwrap(); + log::trace!(target: self.analysis.debug_name(), "initializing multi-block region {region_index} with entry: {entry_block}"); + log::trace!(target: self.analysis.debug_name(), "computing region dominance tree"); + let domtree = dominfo.dominance(region.as_region_ref()); + log::trace!(target: self.analysis.debug_name(), "computing region loop forest forward"); + let loop_forest = LoopForest::new(&domtree); + + // Visit blocks in CFG reverse post-order + log::trace!( + target: self.analysis.debug_name(), + "visiting region control flow graph in reverse post-order", + ); + for node in domtree.reverse_postorder() { + let Some(block) = node.block() else { + continue; + }; + log::trace!(target: self.analysis.debug_name(), "analyzing {block}"); + + // Anchor the fact that a loop is being exited to the CfgEdge of the exit, + // if applicable for this block + if let Some(loop_info) = loop_forest.loop_for(block) { + // This block can exit a loop + if loop_info.is_loop_exiting(block) { + log::trace!(target: self.analysis.debug_name(), "{block} is a loop exit"); + for succ in BlockRef::children(block) { + if !loop_info.contains_block(succ) { + log::trace!(target: self.analysis.debug_name(), "{block} can exit loop to {succ}"); + let mut guard = solver.get_or_create_mut::( + CfgEdge::new(block, succ, block.span()), + ); + guard.join(LoopAction::Exit); + } + } + } + } + + let block = block.borrow(); + forward::visit_block(self, &block, solver); + log::trace!(target: self.analysis.debug_name(), "initializing {block} in pre-order"); + for op in block.body() { + let child_analysis_manager = + analysis_manager.nest(op.as_operation_ref()); + self.initialize(&op, solver, child_analysis_manager)?; + } + } + } + } + } else { + for (region_index, region) in top.regions().iter().enumerate() { + for block in region.body() { + log::trace!(target: self.analysis.debug_name(), "initializing {block} of region {region_index}"); + forward::visit_block(self, &block, solver); + log::trace!(target: self.analysis.debug_name(), "initializing {block} in pre-order"); + for op in block.body() { + let child_analysis_manager = analysis_manager.nest(op.as_operation_ref()); + self.initialize(&op, solver, child_analysis_manager)?; + } + } + } + } + + Ok(()) + } + + /// Visit a program point that modifies the state of the program. + /// + /// If the program point is at the beginning of a block, then the state is propagated from + /// control-flow predecessors or callsites. If the operation before the program point is a call + /// operation or region control-flow operation, then the state after the execution of the + /// operation is set by control-flow or the callgraph. Otherwise, this function invokes the + /// operation transfer function before the program point iterator. + fn visit(&self, point: &ProgramPoint, solver: &mut DataFlowSolver) -> Result<(), Report> { + if point.is_at_block_start() { + let block = point.block().expect("expected block"); + forward::visit_block(self, &block.borrow(), solver); + } else { + let op = point.operation().expect("expected operation"); + forward::process_operation(self, &op.borrow(), solver)?; + } + + Ok(()) + } +} + +impl DataFlowAnalysis for DenseDataFlowAnalysis { + #[inline] + fn debug_name(&self) -> &'static str { + self.analysis.debug_name() + } + + fn analysis_id(&self) -> core::any::TypeId { + core::any::TypeId::of::() + } + + /// Initialize the analysis by visiting every program point whose execution may modify the + /// program state; that is, every operation and block. + fn initialize( + &self, + top: &Operation, + solver: &mut DataFlowSolver, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::trace!( + target: self.analysis.debug_name(), + "initializing analysis for {top}", + ); + + backward::process_operation(self, top, solver)?; + + // If the op has SSACFG regions, use the dominator tree analysis, if available, to visit the + // CFG in post-order. Otherwise, fall back to a naive iteration over the contents of each region. + // + // If we have a domtree, we don't bother visiting unreachable blocks (i.e. blocks that + // are not in the tree because they are unreachable via the CFG). If we don't have a domtree, + // then all blocks are visited, regardless of reachability. + if !top.has_regions() { + return Ok(()); + } + + let is_ssa_cfg = top + .as_trait::() + .is_none_or(|rki| rki.has_ssa_dominance()); + log::trace!(target: self.analysis.debug_name(), "visiting regions of op (is cfg={is_ssa_cfg})"); + if is_ssa_cfg { + let dominfo = analysis_manager.get_analysis::()?; + for (region_index, region) in top.regions().iter().enumerate() { + // Single-block regions do not require a dominance tree (and do not have one) + if region.has_one_block() { + let block = region.entry(); + log::trace!(target: self.analysis.debug_name(), "initializing single-block region {region_index} from entry: {block}"); + backward::visit_block(self, &block, solver); + log::trace!(target: self.analysis.debug_name(), "initializing {block} in post-order"); + for op in block.body().iter().rev() { + let child_analysis_manager = analysis_manager.nest(op.as_operation_ref()); + self.initialize(&op, solver, child_analysis_manager)?; + } + } else { + let entry_block = region.entry_block_ref().unwrap(); + log::trace!(target: self.analysis.debug_name(), "initializing multi-block region {region_index} with entry: {entry_block}"); + log::trace!(target: self.analysis.debug_name(), "computing region dominance tree"); + let domtree = dominfo.dominance(region.as_region_ref()); + log::trace!(target: self.analysis.debug_name(), "computing region loop forest backward"); + let loop_forest = LoopForest::new(&domtree); + + // Visit blocks in CFG postorder + log::trace!( + target: self.analysis.debug_name(), + "visiting region control flow graph in post-order", + ); + for node in domtree.postorder() { + let Some(block) = node.block() else { + continue; + }; + log::trace!(target: self.analysis.debug_name(), "analyzing {block}"); + + // Anchor the fact that a loop is being exited to the CfgEdge of the exit, + // if applicable for this block + if let Some(loop_info) = loop_forest.loop_for(block) { + // This block can exit a loop + if loop_info.is_loop_exiting(block) { + log::trace!(target: self.analysis.debug_name(), "{block} is a loop exit"); + for succ in BlockRef::children(block) { + if !loop_info.contains_block(succ) { + log::trace!(target: self.analysis.debug_name(), "{block} can exit loop to {succ}"); + let mut guard = solver.get_or_create_mut::( + CfgEdge::new(block, succ, block.span()), + ); + guard.join(LoopAction::Exit); + } + } + } + } + + let block = block.borrow(); + backward::visit_block(self, &block, solver); + log::trace!(target: self.analysis.debug_name(), "initializing {block} in post-order"); + for op in block.body().iter().rev() { + let child_analysis_manager = + analysis_manager.nest(op.as_operation_ref()); + self.initialize(&op, solver, child_analysis_manager)?; + } + } + } + } + } else { + for (region_index, region) in top.regions().iter().enumerate() { + for block in region.body().iter().rev() { + log::trace!(target: self.analysis.debug_name(), "initializing {block} of region {region_index}"); + backward::visit_block(self, &block, solver); + log::trace!(target: self.analysis.debug_name(), "initializing {block} in post-order"); + for op in block.body().iter().rev() { + let child_analysis_manager = analysis_manager.nest(op.as_operation_ref()); + self.initialize(&op, solver, child_analysis_manager)?; + } + } + } + } + + Ok(()) + } + + /// Visit a program point that modifies the state of the program. If the program point is at the + /// beginning of a block, then the state is propagated from control-flow predecessors or + /// callsites. If the operation before the program point is a call operation or region + /// control-flow operation, then the state after the execution of the operation is set by + /// control-flow or the callgraph. Otherwise, this function invokes the operation transfer + /// function before the program point. + fn visit(&self, point: &ProgramPoint, solver: &mut DataFlowSolver) -> Result<(), Report> { + if point.is_at_block_end() { + let block = point.block().expect("expected block"); + backward::visit_block(self, &block.borrow(), solver); + } else { + let op = point.next_operation().expect("expected operation"); + backward::process_operation(self, &op.borrow(), solver)?; + } + + Ok(()) + } +} + +impl DenseForwardDataFlowAnalysis + for DenseDataFlowAnalysis +{ + type Lattice = ::Lattice; + + fn debug_name(&self) -> &'static str { + ::debug_name(&self.analysis) + } + + fn visit_operation( + &self, + op: &Operation, + before: &Self::Lattice, + after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) -> Result<(), Report> { + ::visit_operation( + &self.analysis, + op, + before, + after, + solver, + ) + } + + fn set_to_entry_state( + &self, + lattice: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::set_to_entry_state(&self.analysis, lattice, solver); + } + + fn visit_branch_control_flow_transfer( + &self, + from: BlockRef, + to: &Block, + before: &Self::Lattice, + after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::visit_branch_control_flow_transfer( + &self.analysis, + from, + to, + before, + after, + solver, + ); + } + + fn visit_region_branch_control_flow_transfer( + &self, + branch: &dyn RegionBranchOpInterface, + region_from: Option, + region_to: Option, + before: &Self::Lattice, + after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::visit_region_branch_control_flow_transfer( + &self.analysis, + branch, + region_from, + region_to, + before, + after, + solver, + ); + } + + fn visit_call_control_flow_transfer( + &self, + call: &dyn CallOpInterface, + action: super::CallControlFlowAction, + before: &Self::Lattice, + after: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::visit_call_control_flow_transfer( + &self.analysis, + call, + action, + before, + after, + solver, + ); + } +} + +impl DenseBackwardDataFlowAnalysis + for DenseDataFlowAnalysis +{ + type Lattice = ::Lattice; + + fn debug_name(&self) -> &'static str { + ::debug_name(&self.analysis) + } + + fn symbol_table(&self) -> Option<&dyn SymbolTable> { + ::symbol_table(&self.analysis) + } + + fn set_to_exit_state( + &self, + lattice: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::set_to_exit_state(&self.analysis, lattice, solver) + } + + fn visit_operation( + &self, + op: &Operation, + after: &Self::Lattice, + before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) -> Result<(), Report> { + ::visit_operation( + &self.analysis, + op, + after, + before, + solver, + ) + } + + fn visit_branch_control_flow_transfer( + &self, + from: &Block, + to: BlockRef, + after: &Self::Lattice, + before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::visit_branch_control_flow_transfer( + &self.analysis, + from, + to, + after, + before, + solver, + ) + } + + fn visit_region_branch_control_flow_transfer( + &self, + branch: &dyn RegionBranchOpInterface, + region_from: Option, + region_to: Option, + after: &Self::Lattice, + before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::visit_region_branch_control_flow_transfer( + &self.analysis, + branch, + region_from, + region_to, + after, + before, + solver, + ) + } + + fn visit_call_control_flow_transfer( + &self, + call: &dyn CallOpInterface, + action: super::CallControlFlowAction, + after: &Self::Lattice, + before: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + ::visit_call_control_flow_transfer( + &self.analysis, + call, + action, + after, + before, + solver, + ) + } +} diff --git a/hir-analysis/src/dense/backward.rs b/hir-analysis/src/dense/backward.rs new file mode 100644 index 000000000..c8771c44f --- /dev/null +++ b/hir-analysis/src/dense/backward.rs @@ -0,0 +1,517 @@ +use midenc_hir::{ + cfg::Graph, Block, CallOpInterface, CallableOpInterface, Operation, ProgramPoint, + RegionBranchOpInterface, RegionBranchPoint, RegionBranchTerminatorOpInterface, RegionRef, + Report, Spanned, SymbolTable, +}; + +use super::*; +use crate::{ + analyses::dce::{CfgEdge, Executable, PredecessorState}, + AnalysisStateGuardMut, BuildableAnalysisState, CallControlFlowAction, DataFlowSolver, +}; + +/// The base trait for all dense backward data-flow analyses. +/// +/// This type of dense data-flow analysis attaches a lattice to program points and implements a +/// transfer function from the lattice after each operation to the lattice before it, thus +/// propagating backwards. +/// +/// Visiting a program point will invoke the transfer function of the operation following the +/// program point iterator. Visiting a program point at the end of a block will visit the block +/// itself. +/// +/// Implementations of this analysis are expected to be constructed with a symbol table collection +/// that is used to cache symbol resolution during interprocedural analysis. This table can be +/// empty. +#[allow(unused_variables)] +pub trait DenseBackwardDataFlowAnalysis: 'static { + type Lattice: BuildableAnalysisState + DenseLattice; + + fn debug_name(&self) -> &'static str { + core::any::type_name::() + } + + /// Get the symbol table to use for caching symbol resolution during interprocedural analysis. + /// + /// If `None`, no caching is performed, and the symbol table is dynamically looked up from + /// the current operation being analyzed. + fn symbol_table(&self) -> Option<&dyn SymbolTable>; + + /// The transfer function. + /// + /// Visits an operation with the dense lattice state as computed after it. Implementations are + /// expected to compute/update the state of the lattice before the op. + fn visit_operation( + &self, + op: &Operation, + after: &Self::Lattice, + before: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) -> Result<(), Report>; + + /// Set the dense lattice before the control flow exit point and propagate an update if it + /// changed. + fn set_to_exit_state( + &self, + lattice: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ); + + /// Propagate the dense lattice backward along the control flow edge represented by `from` and + /// `to`, which is known to be the result of intra-region control flow, i.e. via + /// [BranchOpInterface]. This is invoked when visiting blocks, rather than the terminators of + /// those blocks. + /// + /// The default implementation just invokes `meet` on the states, meaning that operations + /// implementing [BranchOpInterface] don't have any effect on the lattice that isn't already + /// expressed by the interface itself. + /// + /// The lattices are as follows: + /// + /// * `after` is the lattice at the beginning of `to` + /// * `before` is the lattice at the end of `from` + /// + /// By default, the `before` state is met with the `after` state. Implementations can override + /// this in certain cases. Specifically, if the edge itself should be taken into account in + /// some way, such as if there are subtleties in the transfer function due to edge weights or + /// other control flow considerations. For example, one might wish to take into account the + /// fact that an edge enters or exits a loop. + fn visit_branch_control_flow_transfer( + &self, + from: &Block, + to: BlockRef, + after: &Self::Lattice, + before: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + before.meet(after.lattice()); + } + + /// Propagate the dense lattice backwards along the control flow edge from `region_from` to + /// `region_to` regions of the `branch` operation. If set to `None`, this corresponds to control + /// flow branches originating at, or targeting, the `branch` operation itself. The default + /// implementation just invokes `meet` on the states, meaning that operations implementing + /// [RegionBranchOpInterface] don't have any effect on the lattice that isn't already expressed + /// by the interface itself. + /// + /// The lattices are as follows: + /// + /// * `after`: + /// - If `region_to` is set, this is the lattice at the beginning of the entry block of that + /// region. + /// - Otherwise, this is the lattice after the parent op. + /// * `before`: + /// - If `region_from` is set, this is the lattice at the end of the block that exits the + /// region. Note that for multi-exit regions, the lattices are equal at the end of all + /// exiting blocks, but they are associated with different program points. + /// - Otherwise, this is the lattice before the parent op. + /// + /// By default, the `before` state is met with the `after` state. Implementations can override + /// this in certain cases. Specifically, if the `branch` op may affect the lattice before + /// entering any region, the implementation can handle `region_from.is_none()`. If the `branch` + /// op may affect the lattice after all terminated, the implementation can handle + /// `region_to.is_none()`. Additional refinements are possible based on specific pairs of + /// `region_from` and `region_to`. + fn visit_region_branch_control_flow_transfer( + &self, + branch: &dyn RegionBranchOpInterface, + region_from: Option, + region_to: Option, + after: &Self::Lattice, + before: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + before.meet(after.lattice()); + } + + /// Propagate the dense lattice backwards along the call control flow edge, which can be either + /// entering or exiting the callee. + /// + /// The default implementation for enter and exit callee action just invokes `meet` on the + /// states, meaning that operations implementing [CallOpInterface] don't have any effect on the + /// lattice that isn't already expressed by the interface itself. The default implementation for + /// external callee action additionally sets the result to the exit (fixpoint) state. + /// + /// Three types of back-propagation are possible here: + /// + /// * `CallControlFlowAction::External` indicates that: + /// - `after` is the state following the call operation + /// - `before` is the state before the call operation + /// * `CallControlFlowAction::Enter` indicates that: + /// - `after` is the state at the top of the callee entry block + /// - `before` is the state before the call operation + /// * `CallControlFlowAction::Exit` indicates that: + /// - `after` is the state after the call operation + /// - `before` is the state of exit blocks of the callee + /// + /// By default, the `before` state is simply met with the `after` state. Implementations may + /// be interested in overriding this in some circumstances. Specifically, if the `call` op + /// may affect the lattice prior to entering the callee, a custom implementation can be added + /// for `CallControlFlowAction::Enter`. If the `call` op may affect the lattice post-exiting + /// the callee, the implementation can handle `CallControlFlowAction::Exit` + fn visit_call_control_flow_transfer( + &self, + call: &dyn CallOpInterface, + action: CallControlFlowAction, + after: &Self::Lattice, + before: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + before.meet(after.lattice()); + // Note that `set_to_exit_state` may be a "partial fixpoint" for some + // lattices, e.g., lattices that are lists of maps of other lattices will + // only set fixpoint for "known" lattices. + if matches!(action, CallControlFlowAction::External) { + self.set_to_exit_state(before, solver); + } + } +} + +/// Visit an operation. +/// +/// Dispatches to specialized methods for call or region control-flow operations. Otherwise, this +/// function invokes the operation transfer function. +pub fn process_operation( + analysis: &DenseDataFlowAnalysis, + op: &Operation, + solver: &mut DataFlowSolver, +) -> Result<(), Report> +where + A: DenseBackwardDataFlowAnalysis, +{ + log::trace!("processing op {}", op.name()); + let point = ProgramPoint::before(op); + // If the containing block is not executable, bail out. + if op.parent().is_none_or(|block| { + !solver + .require::(ProgramPoint::at_start_of(block), point) + .is_live() + }) { + log::trace!("skipping analysis for {}: containing block is dead/not executable", op.name()); + return Ok(()); + } + + // Get the dense lattice to update. + log::trace!("getting 'before' analysis state for {point}"); + let mut before = solver.get_or_create_mut(point); + + // Get the dense state after execution of this op. + log::trace!("getting 'after' analysis state for {}", ProgramPoint::after(op)); + // If this is the last operation in it's block, propagate the liveness of the block end to + // after this op + let after = if op.as_operation_ref().next().is_none() { + let after_block = solver.require::<::Lattice, _>( + ProgramPoint::at_end_of(op.parent().unwrap()), + point, + ); + let mut after = solver + .get_or_create_mut::<::Lattice, _>( + ProgramPoint::after(op), + ); + after.join(after_block.lattice()); + AnalysisStateGuardMut::subscribe(&after, analysis); + AnalysisStateGuardMut::freeze(after) + } else { + solver.require(ProgramPoint::after(op), point) + }; + + // Special cases where control flow may dictate data flow. + if let Some(branch) = op.as_trait::() { + log::trace!("op implements RegionBranchOpInterface, handling as special case"); + visit_region_branch_operation( + analysis, + point, + branch, + RegionBranchPoint::Parent, + &mut before, + solver, + ); + return Ok(()); + } + + if let Some(call) = op.as_trait::() { + log::trace!("op implements CallOpInterface, handling as special case"); + visit_call_operation(analysis, call, &after, &mut before, solver); + return Ok(()); + } + + // Invoke the operation transfer function. + log::trace!("invoking {}::visit_operation", core::any::type_name::()); + analysis.visit_operation(op, &after, &mut before, solver) +} + +/// Visit a block. The state at the end of the block is propagated from control-flow successors of +/// the block or callsites. +pub fn visit_block( + analysis: &DenseDataFlowAnalysis, + block: &Block, + solver: &mut DataFlowSolver, +) where + A: DenseBackwardDataFlowAnalysis, +{ + log::trace!("processing block {}", block.id()); + let point = ProgramPoint::at_end_of(block); + // If the block is not executable, bail out. + if !solver + .require::(ProgramPoint::at_start_of(block), point) + .is_live() + { + log::trace!("skipping analysis for {}: it is dead/not executable", block.id()); + return; + } + + log::trace!("getting 'before' analysis state for {point}"); + let mut before = solver.get_or_create_mut(point); + + // We need "exit" blocks, i.e. the blocks that may return control to the parent operation. + let is_region_exit_block = |block: &Block| { + match block.terminator() { + // Treat empty and terminator-less blocks as exit blocks. + None => true, + // There may be a weird case where a terminator may be transferring control either to + // the parent or to another block, so exit blocks and successors are not mutually + // exclusive. + Some(op) => op.borrow().implements::(), + } + }; + + if is_region_exit_block(block) { + log::trace!("{} is a region exit block", block.id()); + // If this block is exiting from a callable, the successors of exiting from a callable are + // the successors of all call sites. And the call sites themselves are predecessors of the + // callable. + let parent_op = block.parent_op().expect("orphaned block"); + let region = block.parent().unwrap(); + if let Some(callable) = parent_op.borrow().as_trait::() { + log::trace!("{}'s parent implements CallableOpInterface", block.id()); + let callable_region = callable.get_callable_region(); + if callable_region.is_some_and(|r| r == region) { + log::trace!( + "{}'s parent region is a callable region - getting analysis state for call \ + sites", + block.id() + ); + let callsites = solver.require::( + ProgramPoint::after(callable.as_operation()), + point, + ); + // If not all call sites are known, conservative mark all lattices as + // having reached their pessimistic fix points. + log::trace!("all predecessors known: {}", callsites.all_predecessors_known()); + log::trace!("solver is inter-procedural: {}", solver.config().is_interprocedural()); + if !callsites.all_predecessors_known() || !solver.config().is_interprocedural() { + return analysis.set_to_exit_state(&mut before, solver); + } + + for callsite in callsites.known_predecessors() { + let call = callsite.borrow(); + let call = call.as_trait::().expect("invalid callsite"); + log::trace!( + "visiting control flow transfer exit from call to {}", + call.callable_for_callee() + ); + let after = solver.require(ProgramPoint::after(*callsite), point); + analysis.visit_call_control_flow_transfer( + call, + CallControlFlowAction::Exit, + &after, + &mut before, + solver, + ); + } + + return; + } + } + + // If this block is exiting from an operation with region-based control flow, propagate the + // lattice back along the control flow edge. + if let Some(branch) = parent_op.borrow().as_trait::() { + log::trace!("{}'s parent implements RegionBranchOpInterface", block.id()); + return visit_region_branch_operation( + analysis, + point, + branch, + RegionBranchPoint::Child(region), + &mut before, + solver, + ); + } + + // Cannot reason about successors of an exit block, set the pessimistic fixpoint. + log::trace!("cannot reason about successors of {} - setting to exit state", block.id()); + return analysis.set_to_exit_state(&mut before, solver); + } + + // Meet the state with the state before block's successors. + log::trace!( + "meeting the before lattice with the after lattice of {}'s successors", + block.id() + ); + for successor in Block::children(block.as_block_ref()) { + if !solver + .require::( + CfgEdge::new(block.as_block_ref(), successor, block.span()), + point, + ) + .is_live() + { + log::trace!( + "skipping dead/non-executable control flow edge {} -> {successor}", + block.id() + ); + continue; + } + + // Merge in the state from the successor: either the first operation, or the block itself + // when empty. + log::trace!("meeting before lattice of {} and after lattice of {successor}", block.id()); + let after = solver.require(ProgramPoint::before(successor), point); + analysis.visit_branch_control_flow_transfer(block, successor, &after, &mut before, solver); + } +} + +/// Visit an operation for which the data flow is described by the `CallOpInterface`. Performs +/// inter-procedural data flow as follows: +/// +/// * Find the callable (resolve via the symbol table) +/// * If the solver is not configured for inter-procedural analysis, or the callable op is just a +/// declaration, then invoke the `visit_call_control_flow_transfer` callback of the analysis, to +/// let it decide how to proceed. This can work just like `visit_operation` for some analyses. +/// * If the solver is configured for inter-procedural analysis, and the callable op is a definition +/// then `after` is set to the lattice state of the entry block of the callable region, and then +/// the `visit_call_control_flow_transfer` callback is invoked. +/// * Lastly, if the callable op is not resolvable: +/// * If configured for inter-procedural analysis, then `set_to_exit_state` is called on the +/// `before` lattice. This is because we expected to perform an analyis taking into account +/// the state of the callee, but it was not available, so we cannot assume anything. +/// * If _not_ configured for inter-procedural analysis, then `visit_call_control_flow_transfer` +/// is invoked, so that the analysis implementation can decide how to proceed. +pub fn visit_call_operation( + analysis: &DenseDataFlowAnalysis, + call: &dyn CallOpInterface, + after: &::Lattice, + before: &mut AnalysisStateGuardMut<'_, ::Lattice>, + solver: &mut DataFlowSolver, +) where + A: DenseBackwardDataFlowAnalysis, +{ + // Find the callee. + let callee = match analysis.symbol_table() { + None => call.resolve(), + Some(cache) => call.resolve_in_symbol_table(cache), + }; + + let callee_ref = callee.as_ref().map(|callee| callee.borrow()); + let callable = match callee_ref.as_ref() { + None => None, + Some(callee) => callee.as_symbol_operation().as_trait::(), + }; + + // No region means the callee is only declared in this module. If that is the case or if the + // solver is not interprocedural, let the hook handle it. + let is_declaration = + callable.is_some_and(|c| c.get_callable_region().is_none_or(|cr| cr.borrow().is_empty())); + let is_interprocedural = solver.config().is_interprocedural(); + if is_declaration || !is_interprocedural { + return analysis.visit_call_control_flow_transfer( + call, + CallControlFlowAction::External, + after, + before, + solver, + ); + } + + if let Some(callable) = callable { + // Call-level control flow specifies the data flow here. + // + // func.func @callee() { + // ^calleeEntryBlock: + // // latticeAtCalleeEntry + // ... + // } + // func.func @caller() { + // ... + // // latticeBeforeCall + // call @callee + // ... + // } + let region = callable.get_callable_region().unwrap().borrow(); + let callee_entry_block = region.entry(); + let callee_entry = ProgramPoint::at_start_of(&*callee_entry_block); + let lattice_at_callee_entry = + solver.require(callee_entry, ProgramPoint::before(call.as_operation())); + let lattice_before_call = &mut *before; + analysis.visit_call_control_flow_transfer( + call, + CallControlFlowAction::Enter, + &lattice_at_callee_entry, + lattice_before_call, + solver, + ); + } else { + analysis.set_to_exit_state(before, solver); + } +} + +/// Visit a program point within a region branch operation with successors (from which the state is +/// propagated) in or after it. +pub fn visit_region_branch_operation( + analysis: &DenseDataFlowAnalysis, + point: ProgramPoint, + branch: &dyn RegionBranchOpInterface, + branch_point: RegionBranchPoint, + before: &mut AnalysisStateGuardMut<'_, ::Lattice>, + solver: &mut DataFlowSolver, +) where + A: DenseBackwardDataFlowAnalysis, +{ + log::trace!("visiting region branch operation from {point}"); + // The successors of the operation may be either the first operation of the entry block of each + // possible successor region, or the next operation when the branch is a successor of itself. + for successor in branch.get_successor_regions(branch_point) { + log::trace!("visiting region branch successor {}", successor.branch_point()); + let successor_region = successor.successor(); + let after = match successor_region.as_ref() { + None => { + // The successor is `branch` itself + log::trace!( + "getting 'after' analysis state for {}", + ProgramPoint::after(branch.as_operation()) + ); + solver.require(ProgramPoint::after(branch.as_operation()), point) + } + Some(region) => { + // The successor is a region of `branch` + let block = + region.borrow().entry_block_ref().expect("unexpected empty successor region"); + log::trace!( + "getting 'after' analysis state for {}", + ProgramPoint::at_start_of(block) + ); + if !solver + .require::(ProgramPoint::at_start_of(block), point) + .is_live() + { + log::trace!( + "skipping successor {region} because its entry block is \ + dead/non-executable", + ); + continue; + } + solver.require(ProgramPoint::at_start_of(block), point) + } + }; + + let region_from = branch_point.region(); + analysis.visit_region_branch_control_flow_transfer( + branch, + region_from, + successor_region, + &after, + before, + solver, + ); + } +} diff --git a/hir-analysis/src/dense/forward.rs b/hir-analysis/src/dense/forward.rs new file mode 100644 index 000000000..5f2fa9d58 --- /dev/null +++ b/hir-analysis/src/dense/forward.rs @@ -0,0 +1,440 @@ +use midenc_hir::{ + Block, CallOpInterface, CallableOpInterface, Operation, ProgramPoint, RegionBranchOpInterface, + RegionRef, Report, Spanned, +}; + +use super::*; +use crate::{ + analyses::dce::{CfgEdge, Executable, PredecessorState}, + AnalysisStateGuardMut, BuildableAnalysisState, CallControlFlowAction, DataFlowSolver, +}; + +/// The base trait for all dense forward data-flow analyses. +/// +/// This type of dense data-flow analysis attaches a lattice to program points and implements a +/// transfer function from the lattice before each operation to the lattice after, thus propagating +/// forwards. The lattice contains information about the state of the program at that program point. +/// +/// Visiting a program point will invoke the transfer function of the operation preceding the +/// program point iterator. Visiting a program point at the beginning of a block will visit the +/// block itself. +#[allow(unused_variables)] +pub trait DenseForwardDataFlowAnalysis: 'static { + type Lattice: BuildableAnalysisState + DenseLattice; + + fn debug_name(&self) -> &'static str { + core::any::type_name::() + } + + /// Propagate the dense lattice before the execution of an operation to the lattice after its + /// execution. + fn visit_operation( + &self, + op: &Operation, + before: &Self::Lattice, + after: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) -> Result<(), Report>; + + /// Set the dense lattice at control flow entry point and propagate an update if it changed. + /// + /// The lattice here may be anchored to one of the following points: + /// + /// 1. `ProgramPoint::at_start_of(block)` for the block being entered + /// 2. `ProgramPoint::before(op)` for the first op in a block being entered + /// 3. `ProgramPoint::after(call)` for propagating lattice state from the predecessor of a + /// call to a callable op (i.e. from return sites to after the call returns). + /// + /// In the case of 2 specifically, we distinguish the anchors "start of block" and "before op + /// at start of block", however in general these effectively refer to the same program point. + /// It is up to the implementation to decide how they wish to handle this case, but it is safe + /// to simply propagate the state from 1 to 2. + fn set_to_entry_state( + &self, + lattice: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ); + + /// Propagate the dense lattice forward along the control flow edge represented by `from` and + /// `to`, which is known to be the result of intra-region control flow, i.e. via + /// [BranchOpInterface]. This is invoked when visiting blocks, rather than the terminators of + /// those blocks. The block being visited when this function is called is `to`. + /// + /// The default implementation just invokes `join` on the states, meaning that operations + /// implementing [BranchOpInterface] don't have any effect on the lattice that isn't already + /// expressed by the interface itself. + /// + /// The lattices are as follows: + /// + /// * `before` is the lattice at the end of `from` + /// * `after` is the lattice at the beginning of `to` + /// + /// By default, the `after` state is joined with the `before` state. Implementations can + /// override this in certain cases. Specifically, if the edge itself should be taken into + /// account in some way, such as if there are subtleties in the transfer function due to edge + /// weights or other control flow considerations. For example, one might wish to take into + /// account the fact that an edge enters or exits a loop. + fn visit_branch_control_flow_transfer( + &self, + from: BlockRef, + to: &Block, + before: &Self::Lattice, + after: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + after.join(before.lattice()); + } + + /// Propagate the dense lattice forward along the control flow edge from `region_from` to + /// `region_to`, which must be regions of the `branch` operation, or `None`, which corresponds + /// to the branch op itself (i.e. control flow originating at, or targeting, the `branch` op). + /// + /// The default implementation just invokes `join` on the states, meaning that operations + /// implementing [RegionBranchOpInterface] don't have any effect on the lattice that isn't + /// already expressed by the interface itself. + /// + /// The lattices are as follows: + /// + /// * `before`: + /// - If `region_from` is set, this is the lattice at the end of the block that exits the + /// region. Note that for multi-exit regions, the lattices are equal at the end of all + /// exiting blocks, but they are associated with different program points. + /// - Otherwise, this is the lattice before the parent op + /// * `after`: + /// - If `region-to` is set, this is the lattice at the beginning of the entry block of that + /// region. + /// - Otherwise, this is the lattice after the parent op + /// + /// Implementations can implement additional custom behavior by handling specific cases manually. + /// For example, if the `branch` op may affect the lattice before entering any region, the impl + /// can handle `region_from.is_none()`. Similarly, if the `branch` op may affect the lattice + /// after all terminated, the implementation can handle `region_to.is_none()`. Additional + /// refinements are possible for specific pairs of regions. + fn visit_region_branch_control_flow_transfer( + &self, + branch: &dyn RegionBranchOpInterface, + region_from: Option, + region_to: Option, + before: &Self::Lattice, + after: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + after.join(before.lattice()); + } + + /// Propagate the dense lattice forward along the call control flow edge, which can be either + /// entering or exiting the callee. + /// + /// The default implementation for enter and exit callee actions invokes `join` on the states, + /// meaning that operations implementing [CallOpInterface] don't have any effect on the lattice + /// that isn't already expressed by the interface itself. The default handling for the external + /// callee action additionally sets the `after` lattice to the entry state. + /// + /// Two types of forward-propagation are possible here: + /// + /// * `CallControlFlowAction::Enter` indicates: + /// - `before` is the state before the call operation + /// - `after` is the state at the beginning of the callee entry block + /// * `CallControlFlowAction::Exit` indicates: + /// - `before` is the state at the end of a callee exit block + /// - `after` is the state after the call operation + /// + /// Implementations can implement additional custom behavior by handling specific cases manually. + /// For example, if `call` may affect the lattice prior to entering the callee, the impl can + /// handle `CallControlFlowAction::Enter`. Similarly, if `call` may affect the lattice post- + /// exiting the callee, the impl can handle `CallControlFlowAction::Exit`. + fn visit_call_control_flow_transfer( + &self, + call: &dyn CallOpInterface, + action: CallControlFlowAction, + before: &Self::Lattice, + after: &mut AnalysisStateGuardMut<'_, Self::Lattice>, + solver: &mut DataFlowSolver, + ) { + after.join(before.lattice()); + // Note that `set_to_entry_state` may be a "partial fixpoint" for some + // lattices, e.g., lattices that are lists of maps of other lattices will + // only set fixpoint for "known" lattices. + if matches!(action, CallControlFlowAction::External) { + self.set_to_entry_state(after, solver); + } + } +} + +/// Visit an operation. If this is a call operation or region control-flow +/// operation, then the state after the execution of the operation is set by +/// control-flow or the callgraph. Otherwise, this function invokes the +/// operation transfer function. +pub fn process_operation( + analysis: &DenseDataFlowAnalysis, + op: &Operation, + solver: &mut DataFlowSolver, +) -> Result<(), Report> +where + A: DenseForwardDataFlowAnalysis, +{ + let point: ProgramPoint = ProgramPoint::after(op); + + // If the containing block is not executable, bail out. + let not_executable = op.parent().is_some_and(|block| { + let block_start = ProgramPoint::at_start_of(block); + !solver.require::(block_start, point).is_live() + }); + if not_executable { + return Ok(()); + } + + // Get the dense lattice to update. + let mut after = solver.get_or_create_mut(point); + + // If this op implements region control-flow, then control-flow dictates its transfer + // function. + if let Some(branch) = op.as_trait::() { + visit_region_branch_operation(analysis, point, branch, &mut after, solver); + return Ok(()); + } + + // Get the dense state before the execution of the op. + let before = solver.require(ProgramPoint::before(op), point); + + // If this is a call operation, then join its lattices across known return sites. + if let Some(call) = op.as_trait::() { + visit_call_operation(analysis, call, &before, &mut after, solver); + return Ok(()); + } + + // Invoke the operation transfer function. + analysis.visit_operation(op, &before, &mut after, solver) +} + +/// Visit a block. The state at the start of the block is propagated from +/// control-flow predecessors or callsites. +pub fn visit_block( + analysis: &DenseDataFlowAnalysis, + block: &Block, + solver: &mut DataFlowSolver, +) where + A: DenseForwardDataFlowAnalysis, +{ + // If the block is not executable, bail out. + let point = ProgramPoint::at_start_of(block); + if !solver.require::(point, point).is_live() { + return; + } + + // Get the dense lattice to update. + let mut after = solver.get_or_create_mut(point); + + // The dense lattices of entry blocks are set by region control-flow or the callgraph. + if block.is_entry_block() { + // Check if this block is the entry block of a callable region. + let op = block.parent_op().expect("orphaned block"); + let operation = op.borrow(); + if let Some(callable) = operation.as_trait::() { + let region = block.parent().unwrap(); + let callable_region = callable.get_callable_region(); + if callable_region.is_some_and(|r| r == region) { + let callsites = solver.require::( + ProgramPoint::after(callable.as_operation()), + point, + ); + // If not all callsites are known, conservatively mark all lattices as having + // reached their pessimistic fixpoints. Do the same if interprocedural analysis + // is not enabled. + if !callsites.all_predecessors_known() || !solver.config().is_interprocedural() { + return analysis.set_to_entry_state(&mut after, solver); + } + + for callsite in callsites.known_predecessors() { + // Get the dense lattice before the callsite. + let before = solver.require(ProgramPoint::before(*callsite), point); + let call = callsite.borrow(); + let call = call.as_trait::().unwrap(); + analysis.visit_call_control_flow_transfer( + call, + CallControlFlowAction::Enter, + &before, + &mut after, + solver, + ); + } + return; + } + } + + // Check if we can reason about the control-flow. + if let Some(branch) = operation.as_trait::() { + return visit_region_branch_operation(analysis, point, branch, &mut after, solver); + } + + // Otherwise, we can't reason about the data-flow. + return analysis.set_to_entry_state(&mut after, solver); + } + + // Join the state with the state after the block's predecessors. + for pred in block.predecessors() { + // Skip control edges that aren't executable. + let predecessor = pred.predecessor(); + let anchor = CfgEdge::new(predecessor, pred.successor(), block.span()); + if !solver.require::(anchor, point).is_live() { + continue; + } + + // Merge in the state from the predecessor's terminator. + let before = solver.require::<::Lattice, _>( + ProgramPoint::after(pred.owner), + point, + ); + analysis.visit_branch_control_flow_transfer( + predecessor, + block, + &before, + &mut after, + solver, + ); + } +} + +/// Visit an operation for which the data flow is described by the +/// `CallOpInterface`. +pub fn visit_call_operation( + analysis: &DenseDataFlowAnalysis, + call: &dyn CallOpInterface, + before: &::Lattice, + after: &mut AnalysisStateGuardMut<'_, ::Lattice>, + solver: &mut DataFlowSolver, +) where + A: DenseForwardDataFlowAnalysis, +{ + // Allow for customizing the behavior of calls to external symbols, including when the + // analysis is explicitly marked as non-interprocedural. + let symbol = call.resolve(); + let symbol = symbol.as_ref().map(|s| s.borrow()); + let callable_op = symbol.as_ref().map(|s| s.as_symbol_operation()); + let callable = callable_op.and_then(|op| op.as_trait::()); + if !solver.config().is_interprocedural() + || callable.is_some_and(|callable| callable.get_callable_region().is_none()) + { + return analysis.visit_call_control_flow_transfer( + call, + CallControlFlowAction::External, + before, + after, + solver, + ); + } + + // Otherwise, if not all return sites are known, then conservatively assume we + // can't reason about the data-flow. + let call_op = call.as_operation().as_operation_ref(); + let after_call = ProgramPoint::after(call_op); + let predecessors = solver.require::(after_call, after_call); + if !predecessors.all_predecessors_known() { + return analysis.set_to_entry_state(after, solver); + } + + for predecessor in predecessors.known_predecessors() { + // Get the lattices at callee return: + // + // func.func @callee() { + // ... + // return // predecessor + // // latticeAtCalleeReturn + // } + // func.func @caller() { + // ... + // call @callee + // // latticeAfterCall + // ... + // } + let lattice_after_call = &mut *after; + let lattice_at_callee_return = + solver.require(ProgramPoint::after(*predecessor), ProgramPoint::after(call_op)); + analysis.visit_call_control_flow_transfer( + call, + CallControlFlowAction::Exit, + &lattice_at_callee_return, + lattice_after_call, + solver, + ); + } +} + +/// Visit a program point within a region branch operation with predecessors +/// in it. This can either be an entry block of one of the regions of the +/// parent operation itself. +pub fn visit_region_branch_operation( + analysis: &DenseDataFlowAnalysis, + point: ProgramPoint, + branch: &dyn RegionBranchOpInterface, + after: &mut AnalysisStateGuardMut<'_, ::Lattice>, + solver: &mut DataFlowSolver, +) where + A: DenseForwardDataFlowAnalysis, +{ + // Get the terminator predecessors. + let predecessors = solver.require::(point, point); + assert!(predecessors.all_predecessors_known(), "unexpected unresolved region successors"); + + let branch_op = branch.as_operation().as_operation_ref(); + for predecessor in predecessors.known_predecessors() { + let before = if &branch_op == predecessor { + // If the predecessor is the parent, get the state before the parent. + solver.require(ProgramPoint::before(*predecessor), point) + } else { + // Otherwise, get the state after the terminator. + solver.require(ProgramPoint::after(*predecessor), point) + }; + + // This function is called in two cases: + // 1. when visiting the block (point = block start); + // 2. when visiting the parent operation (point = iter after parent op). + // In both cases, we are looking for predecessor operations of the point, + // 1. predecessor may be the terminator of another block from another + // region (assuming that the block does belong to another region via an + // assertion) or the parent (when parent can transfer control to this + // region); + // 2. predecessor may be the terminator of a block that exits the + // region (when region transfers control to the parent) or the operation + // before the parent. + // In the latter case, just perform the join as it isn't the control flow + // affected by the region. + let region_from = if &branch_op == predecessor { + None + } else { + predecessor.borrow().parent_region() + }; + if point.is_at_block_start() { + let region_to = point.block().unwrap().parent().unwrap(); + analysis.visit_region_branch_control_flow_transfer( + branch, + region_from, + Some(region_to), + &before, + after, + solver, + ); + } else { + assert_eq!( + point.prev_operation().unwrap(), + branch_op, + "expected to be visiting the branch itself" + ); + // Only need to call the arc transfer when the predecessor is the region or the op + // itself, not the previous op. + let parent_op = predecessor.borrow().parent_op().unwrap(); + if parent_op == branch_op || predecessor == &branch_op { + analysis.visit_region_branch_control_flow_transfer( + branch, + region_from, + None, + &before, + after, + solver, + ); + } else { + after.join(before.lattice()); + } + } + } +} diff --git a/hir-analysis/src/dense/lattice.rs b/hir-analysis/src/dense/lattice.rs new file mode 100644 index 000000000..48f033c9d --- /dev/null +++ b/hir-analysis/src/dense/lattice.rs @@ -0,0 +1,19 @@ +use crate::{AnalysisState, ChangeResult}; + +/// A [DenseLattice] represents some program state at a specific program point. +/// +/// It is propagated through the IR by dense data-flow analysis. +#[allow(unused_variables)] +pub trait DenseLattice: AnalysisState + core::fmt::Debug { + type Lattice; + + fn lattice(&self) -> &Self::Lattice; + fn lattice_mut(&mut self) -> &mut Self::Lattice; + + fn join(&mut self, rhs: &Self::Lattice) -> ChangeResult { + ChangeResult::Unchanged + } + fn meet(&mut self, rhs: &Self::Lattice) -> ChangeResult { + ChangeResult::Unchanged + } +} diff --git a/hir-analysis/src/dependency_graph.rs b/hir-analysis/src/dependency_graph.rs deleted file mode 100644 index 1e09a2602..000000000 --- a/hir-analysis/src/dependency_graph.rs +++ /dev/null @@ -1,1490 +0,0 @@ -use std::{ - cmp::Ordering, - collections::{BTreeMap, BTreeSet}, - fmt, -}; - -use midenc_hir as hir; -use smallvec::SmallVec; - -/// This represents a node in a [DependencyGraph]. -/// -/// The node types here are carefully chosen to provide us with the following -/// properties once we've constructed a [DependencyGraph] from a block: -/// -/// * Distinguish between block-local operands and those which come from a dominating block. This -/// let's us reason globally about how function arguments and instruction results are used in -/// blocks of the program so that they can be moved/copied as appropriate to keep them live only -/// for as long as they are needed. -/// * Represent the dependencies of individual arguments, this ensures that dependencies between -/// expressions in a block are correctly represented when we compute a [TreeGraph], and that we -/// can determine exactly how many instances of a value are needed in a function. -/// * Represent usage of individual instruction results - both to ensure we make copies of those -/// results as needed, but to ensure we drop unused results immediately if they are not needed. -/// -/// Furthermore, the precise layout and ordering of this enum is intentional, -/// as it determines the order in which nodes are sorted, and thus the order -/// in which we visit them during certain operations. -/// -/// It is also essential that this is kept in sync with [NodeId], which is -/// a packed representation of [Node] designed to ensure that the order in -/// which [NodeId] is ordered is the same as the corresponding [Node]. Put -/// another way: [Node] is the unpacked form of [NodeId]. -/// -/// NOTE: Adding variants/fields to this type must be done carefully, to ensure -/// that we can encode a [Node] as a [NodeId], and to preserve the fact that -/// a [NodeId] fits in a `u64`. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Node { - /// This node type represents a value known to be on the - /// operand stack upon entry to the current block, i.e. - /// it's definition is external to this block, but available. - Stack(hir::Value), - /// This node represents an instruction argument. Only `Inst` may - /// depend on nodes of this type directly, and it may only depend - /// on `Result` or `Stack` nodes itself. - /// - /// There are different kinds of arguments though, see [ArgumentNode] for details - Argument(ArgumentNode), - /// This node acts as a join point for the remaining node types, - /// i.e. it is the predecessor for `Argument`, and the successor - /// for `Result` and is used to represent the fact that results - /// implicitly depend on all arguments to the instruction which - /// produced them. - Inst { - /// The unique id of this instruction - id: hir::Inst, - /// The position of this instruction in its containing block - pos: u16, - }, - /// This node represents an instruction result. `Result` may only have - /// `Argument` as predecessor (i.e. the argument depends on a result), - /// and may only have `Inst` as successor (i.e. the instruction which - /// produced the result is the only way a result can appear in the graph). - Result { - /// The id of the value represented by this result - value: hir::Value, - /// The index of this result in the instruction results list - index: u8, - }, -} -impl core::hash::Hash for Node { - fn hash(&self, hasher: &mut H) { - // Ensure that by hashing either NodeId or Node we get the same hash - self.id().hash(hasher); - } -} -impl Node { - /// Get the identifier corresponding to this node. - /// - /// A given [Node] will always have the same identifier, as [NodeId] is - /// derived from the content of a [Node] (it is in fact a packed representation - /// of the same data). - pub fn id(self) -> NodeId { - NodeId::from(self) - } - - /// Returns true if this node represents an item in the current block - /// - /// The only node type for which this returns false is `Stack`, as such - /// values are by definition not defined in the current block. - #[inline] - pub fn is_block_local(&self) -> bool { - !matches!(self, Self::Stack(_)) - } - - /// Fallibly converts this node to an instruction identifier - #[inline] - pub fn as_instruction(&self) -> Option { - match self { - Self::Inst { id, .. } => Some(*id), - Self::Argument(ref arg) => Some(arg.inst()), - _ => None, - } - } - - /// Unwraps this node as an instruction identifier, or panics - pub fn unwrap_inst(&self) -> hir::Inst { - match self { - Self::Inst { id, .. } => *id, - Self::Argument(ref arg) => arg.inst(), - node => panic!("cannot unwrap node as instruction: {node:?}"), - } - } - - /// Fallibly converts this node to a value identifier - #[inline] - pub fn as_value(&self) -> Option { - match self { - Self::Stack(value) | Self::Result { value, .. } => Some(*value), - _ => None, - } - } -} -impl From for Node { - fn from(id: NodeId) -> Self { - id.into_node() - .unwrap_or_else(|_| panic!("invalid tag for node id: {:064b}", id.0)) - } -} -impl fmt::Debug for Node { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} -impl fmt::Display for Node { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Stack(value) => write!(f, "{value}"), - Self::Inst { id, .. } => write!(f, "{id}"), - Self::Argument(ref arg) => write!(f, "{arg:?}"), - Self::Result { value, .. } => write!(f, "result({value})"), - } - } -} - -/// This is a subtype of [Node] which represents the various types of arguments -/// we want to represent in a [DependencyGraph]. -/// -/// As with [Node], the layout and representation of this type is carefully -/// chosen, and must be kept in sync with [Node] and [NodeId]. -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -#[repr(u8)] -pub enum ArgumentNode { - /// The argument is required by an instruction directly. - /// - /// For control-flow instructions, this argument type is used for - /// non-block arguments, e.g. in `cond_br v0, block1(v1)`, `v0` - /// would be of this type. - Direct { - /// The instruction to which this argument belongs - inst: hir::Inst, - /// The index of this argument in the instruction parameter list - index: u8, - }, - /// The argument is required by an instruction indirectly. - /// - /// This is only applicable to control-flow instructions, and indicates - /// that the argument is required along all control flow edges for which - /// the instruction is a predecessor. Each use of a value will get its - /// own node in the dependency graph to represent the specific position - /// of the argument in its respective block argument list. - /// - /// In the IR of `cond_br v0, block1(v1), block2(v0, v1)`, `v1` would be - /// of this type, and the dependency graph would have unique nodes for - /// both uses. - Indirect { - /// The instruction to which this argument belongs - inst: hir::Inst, - /// The index of this argument in the successor argument list - index: u8, - /// The index of the successor block to which this argument is bound - successor: u8, - }, - /// The argument is conditionally required by an instruction indirectly. - /// - /// This is a variation on `Indirect` which represents instructions such - /// as `cond_br` and `switch` where an argument is passed to a subset of - /// the successors for the instruction. In such cases, the argument may - /// not be used at all along the other edges, and if so, can be conditionally - /// materialized along the subset of edges which actually require it. - Conditional { - /// The instruction to which this argument belongs - inst: hir::Inst, - /// The index of this argument in the successor argument list - index: u8, - /// The successor block to which this argument is bound - successor: u8, - }, -} -impl ArgumentNode { - /// Return the instruction to which this argument belongs - #[inline] - pub fn inst(&self) -> hir::Inst { - match self { - Self::Direct { inst, .. } - | Self::Indirect { inst, .. } - | Self::Conditional { inst, .. } => *inst, - } - } - - /// Return the index of this argument in its corresponding argument list - /// - /// NOTE: Different argument types correspond to different argument lists, you - /// must make sure you are using the index returned here with the correct list. - #[inline] - pub fn index(&self) -> u8 { - match self { - Self::Direct { index, .. } - | Self::Indirect { index, .. } - | Self::Conditional { index, .. } => *index, - } - } - - /// For indirect/conditional arguments, returns the index of the successor in the - /// successor list of the instruction. - #[inline] - pub fn successor(&self) -> Option { - match self { - Self::Direct { .. } => None, - Self::Indirect { successor, .. } | Self::Conditional { successor, .. } => { - Some(*successor) - } - } - } -} -impl fmt::Debug for ArgumentNode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Direct { inst, index } => write!(f, "arg({index} of {inst})"), - Self::Indirect { - inst, - successor, - index, - } => write!(f, "block_arg({index} to {successor} of {inst})"), - Self::Conditional { - inst, - successor, - index, - } => write!(f, "conditional_block_arg({index} to {successor} of {inst})"), - } - } -} -impl Ord for ArgumentNode { - /// NOTE: This must match the ordering behavior of [NodeId] - fn cmp(&self, other: &Self) -> Ordering { - // Order by instruction, then by successor (if applicable), then by index - // - // After ordering in this way, Direct is always ordered before Indirect/Conditional, - // to account for the fact that an instruction's direct parameters always are needed - // before the successor arguments. However, Indirect/Conditional may never compare equal - // to each other after ordering based on the fields described above, because to do so - // would represent the same argument position being represented using two different, - // conflicting types. - match (self, other) { - ( - Self::Direct { - inst: x_inst, - index: xi, - }, - Self::Direct { - inst: y_inst, - index: yi, - }, - ) => x_inst.cmp(y_inst).then(xi.cmp(yi)), - ( - Self::Direct { inst: x_inst, .. }, - Self::Indirect { inst: y_inst, .. } | Self::Conditional { inst: y_inst, .. }, - ) => x_inst.cmp(y_inst).then(Ordering::Less), - ( - Self::Indirect { inst: x_inst, .. } | Self::Conditional { inst: x_inst, .. }, - Self::Direct { inst: y_inst, .. }, - ) => x_inst.cmp(y_inst).then(Ordering::Greater), - ( - Self::Indirect { - inst: x_inst, - successor: x_blk, - index: xi, - }, - Self::Indirect { - inst: y_inst, - successor: y_blk, - index: yi, - }, - ) => x_inst.cmp(y_inst).then(x_blk.cmp(y_blk)).then(xi.cmp(yi)), - ( - Self::Indirect { - inst: x_inst, - successor: x_blk, - index: xi, - }, - Self::Conditional { - inst: y_inst, - successor: y_blk, - index: yi, - }, - ) - | ( - Self::Conditional { - inst: x_inst, - successor: x_blk, - index: xi, - }, - Self::Indirect { - inst: y_inst, - successor: y_blk, - index: yi, - }, - ) => { - let result = x_inst.cmp(y_inst).then(x_blk.cmp(y_blk)).then(xi.cmp(yi)); - assert_ne!(result, Ordering::Equal, "argument node type conflict"); - result - } - ( - Self::Conditional { - inst: x_inst, - successor: x_blk, - index: xi, - }, - Self::Conditional { - inst: y_inst, - successor: y_blk, - index: yi, - }, - ) => x_inst.cmp(y_inst).then(x_blk.cmp(y_blk)).then(xi.cmp(yi)), - } - } -} -impl PartialOrd for ArgumentNode { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Debug, thiserror::Error)] -#[error("invalid node identifier")] -pub struct InvalidNodeIdError; - -/// Produce a bit-packed representation of [Node] which is naturally -/// sortable as if it was the expanded [Node] type. -/// -/// We currently only need 5 unique values for the node type, so we -/// use 3 bits, which gives us 7 unique values, thus we have 2 extra -/// tag values if we ever need them. This leaves us with 61 bits, of -/// which 32 is reserved for the instruction or value identifier, and -/// the remaining 29 are available for storing any type-specific data. -/// -/// We choose a layout that ensures that when compared as an integer, -/// the sort order of the corresponding [Node] would be identical. This -/// is a bit tricky, since [ArgumentNode] for example ignores the difference -/// between Indirect/Conditional argument types when sorted, so in that -/// case we use the same tag for those types, and differentiate them by -/// using one of the payload bits as a "conditional" marker. With this -/// modification, we can place the tag bits first, followed by a -/// type-specific layout which obeys the ordering rules for that type. -/// -/// The following is the key for any comments describing bit layout: -/// -/// * `t`: tag -/// * `i`: inst -/// * `v`: value -/// * `s`: successor -/// * `x`: index -/// * `c`: conditional marker -/// * `0`: unused/zero -/// -/// This is the layout used for argument nodes -/// -/// ```text,ignore -/// tttiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiissssssssxxxxxxxx000000000000c -/// |--tag (3)--|--inst (32)--|--successor (8)--|--index (8)--|--unused/zero (12)--|--conditional (1)--| -/// ``` -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(transparent)] -pub struct NodeId(u64); -impl NodeId { - const IS_CONDITIONAL_ARG: u64 = 1; - const TAG_ARG_DIRECT: u64 = 1 << 60; - const TAG_ARG_INDIRECT: u64 = 2 << 60; - const TAG_INST: u64 = 3 << 60; - const TAG_MASK: u64 = 0b111 << 60; - const TAG_RESULT: u64 = 4 << 60; - - /// Returns true if the [Node] corresponding to this identifier is of `Stack` type - #[inline] - pub fn is_stack(&self) -> bool { - self.0 & Self::TAG_MASK == 0 - } - - /// Returns true if the [Node] corresponding to this identifier is of `Result` type - #[inline] - pub fn is_result(&self) -> bool { - self.0 & Self::TAG_MASK == Self::TAG_RESULT - } - - /// Returns true if the [Node] corresponding to this identifier is of `Inst` type - #[inline] - pub fn is_instruction(&self) -> bool { - self.0 & Self::TAG_MASK == Self::TAG_INST - } - - /// Returns true if the [Node] corresponding to this identifier is of `Argument` type - #[inline] - pub fn is_argument(&self) -> bool { - matches!(self.0 & Self::TAG_MASK, Self::TAG_ARG_DIRECT | Self::TAG_ARG_INDIRECT) - } - - /// Decode this identifier into its corresponding [Node] - #[inline(always)] - pub fn expand(self) -> Node { - self.into() - } - - /// Extract the [midenc_hir::Inst] associated with the corresponding [Node], or panic - /// if the node type does not have an associated instruction identifier. - pub fn unwrap_inst(self) -> hir::Inst { - let tag = self.0 & Self::TAG_MASK; - match tag { - Self::TAG_ARG_DIRECT | Self::TAG_ARG_INDIRECT => { - hir::Inst::from_u32(((self.0 >> 28) & (u32::MAX as u64)) as u32) - } - Self::TAG_INST => hir::Inst::from_u32(((self.0 >> 16) & (u32::MAX as u64)) as u32), - 0 | Self::TAG_RESULT => panic!("cannot unwrap node id as instruction: {self:?}"), - _invalid => panic!("invalid node id: {:064b}", self.0), - } - } - - /// Safely convert this identifier into a [Node]. - /// - /// This can be used in cases where the source of the [NodeId] is untrusted. - pub fn into_node(self) -> Result { - let tag = self.0 & Self::TAG_MASK; - match tag { - 0 => { - let value = (self.0 & (u32::MAX as u64)) as u32; - Ok(Node::Stack(hir::Value::from_u32(value))) - } - Self::TAG_INST => { - let pos = (self.0 & (u16::MAX as u64)) as u16; - let id = hir::Inst::from_u32(((self.0 >> 16) & (u32::MAX as u64)) as u32); - Ok(Node::Inst { id, pos }) - } - Self::TAG_ARG_DIRECT => { - let mut shifted = self.0 >> 12; - let index = (shifted & (u8::MAX as u64)) as u8; - shifted >>= 16; - let inst = (shifted & (u32::MAX as u64)) as u32; - Ok(Node::Argument(ArgumentNode::Direct { - inst: hir::Inst::from_u32(inst), - index, - })) - } - Self::TAG_ARG_INDIRECT => { - let is_conditional = self.0 & Self::IS_CONDITIONAL_ARG == Self::IS_CONDITIONAL_ARG; - let mut shifted = self.0 >> 12; - let index = (shifted & (u8::MAX as u64)) as u8; - shifted >>= 8; - let successor = (shifted & (u8::MAX as u64)) as u8; - shifted >>= 8; - let inst = hir::Inst::from_u32((shifted & (u32::MAX as u64)) as u32); - Ok(Node::Argument(if is_conditional { - ArgumentNode::Conditional { - inst, - successor, - index, - } - } else { - ArgumentNode::Indirect { - inst, - successor, - index, - } - })) - } - Self::TAG_RESULT => { - let value = hir::Value::from_u32((self.0 & (u32::MAX as u64)) as u32); - let index = ((self.0 >> 52) & (u8::MAX as u64)) as u8; - Ok(Node::Result { value, index }) - } - _ => Err(InvalidNodeIdError), - } - } -} -impl fmt::Debug for NodeId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&Node::from(*self), f) - } -} -impl fmt::Display for NodeId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&Node::from(*self), f) - } -} -impl From for NodeId { - fn from(node: Node) -> Self { - use cranelift_entity::EntityRef; - match node { - // ttt00000000000000000000000000000vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - Node::Stack(value) => Self(value.index() as u64), - // ttt0000000000000iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiixxxxxxxxxxxxxxxx - Node::Inst { id, pos } => { - let inst = (id.index() as u64) << 16; - let index = pos as u64; - Self(Self::TAG_INST | inst | index) - } - // tttiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiissssssssxxxxxxxx000000000000c - Node::Argument(arg) => match arg { - ArgumentNode::Direct { inst, index } => { - let inst = (inst.index() as u64) << 28; - let index = (index as u64) << 12; - Self(Self::TAG_ARG_DIRECT | inst | index) - } - ArgumentNode::Indirect { - inst, - successor, - index, - } => { - let inst = (inst.index() as u64) << 28; - let successor = (successor as u64) << 20; - let index = (index as u64) << 12; - Self(Self::TAG_ARG_INDIRECT | inst | successor | index) - } - ArgumentNode::Conditional { - inst, - successor, - index, - } => { - let inst = (inst.index() as u64) << 28; - let successor = (successor as u64) << 20; - let index = (index as u64) << 12; - Self( - Self::TAG_ARG_INDIRECT - | inst - | successor - | index - | Self::IS_CONDITIONAL_ARG, - ) - } - }, - // tttdddddddd000000000000000000000vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv - Node::Result { value, index } => { - let value = value.index() as u64; - let index = (index as u64) << 52; - Self(Self::TAG_RESULT | index | value) - } - } - } -} -impl<'a> From<&'a Node> for NodeId { - #[inline] - fn from(node: &'a Node) -> Self { - (*node).into() - } -} - -/// This structure represents the relationship between dependent and -/// dependency in a [DependencyGraph]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Dependency { - /// The node which has the dependency. - pub dependent: NodeId, - /// The node which is being depended upon. - pub dependency: NodeId, -} -impl Dependency { - /// Construct a new [Dependency]. - /// - /// In debug builds this will raise an assertion if the dependency being described - /// has nonsensical semantics. In release builds this assertion is elided. - #[inline] - pub fn new(dependent: NodeId, dependency: NodeId) -> Self { - is_valid_dependency(dependent, dependency); - Self { - dependent, - dependency, - } - } -} -impl fmt::Display for Dependency { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} => {}", self.dependent, self.dependency) - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -struct Edge { - node: NodeId, - direction: Direction, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum Direction { - Dependent, - Dependency, -} - -#[derive(Debug, PartialEq, Eq)] -pub struct InvalidDependencyGraphQuery; - -/// This error type is returned by [DependencyGraph::toposort] -#[derive(Debug, thiserror::Error)] -#[error("an unexpected cycle was detected when attempting to topologically sort a treegraph")] -pub struct UnexpectedCycleError; - -/// [DependencyGraph] is a directed, acyclic graph used to represent control -/// and data dependencies in a single basic block of a function in Miden IR. -/// -/// Once constructed, we can use the graph to query information such as: -/// -/// * What is the source for each argument of an instruction -/// * Is a given instruction result used? How many times and by who? -/// * Can a given argument consume its source value, or must it be copied -/// * What node represents the last use of a value -/// * Is an instruction dead code? -/// -/// Most importantly however, a [DependencyGraph] is required in order to -/// compute a [TreeGraph] for the block in question, which is essential for -/// instruction scheduling and code generation. -#[derive(Default, Clone)] -pub struct DependencyGraph { - /// The set of nodes represented in the graph - nodes: BTreeSet, - /// A map of every node in the graph to other nodes in the graph with which it has - /// a relationship, and which dependencies describe that relationship. - edges: BTreeMap>, -} -impl DependencyGraph { - /// Create a new, empty [DependencyGraph] - #[inline] - pub fn new() -> Self { - Self::default() - } - - /// Add `node` to the dependency graph, if it is not already present - pub fn add_node(&mut self, node: Node) -> NodeId { - let id = node.id(); - if self.nodes.insert(id) { - self.edges.insert(id, Default::default()); - } - id - } - - /// Returns true if this graph contains `node` - #[inline] - pub fn contains(&self, node: &Node) -> bool { - let id = node.id(); - self.contains_id(&id) - } - - /// Returns true if this graph contains `node` - #[inline] - pub fn contains_id(&self, node: &NodeId) -> bool { - self.nodes.contains(node) - } - - /// Returns true if there is a path to `b` from `a` in the graph. - pub fn is_reachable_from(&self, a: NodeId, b: NodeId) -> bool { - if !self.nodes.contains(&a) || !self.nodes.contains(&b) { - return false; - } - - let mut visited = BTreeSet::default(); - let mut worklist = std::collections::VecDeque::from([a]); - while let Some(node_id) = worklist.pop_front() { - if !visited.insert(node_id) { - continue; - } - - if node_id == b { - return true; - } - - worklist.extend(self.successor_ids(node_id)); - } - - false - } - - /// Add a dependency from `a` to `b` - pub fn add_dependency(&mut self, a: NodeId, b: NodeId) { - assert_ne!(a, b, "cannot add a self-referential dependency"); - - let edge = Edge { - node: b, - direction: Direction::Dependent, - }; - let edges = self.edges.get_mut(&a).unwrap(); - if edges.contains(&edge) { - return; - } - edges.push(edge); - let edge = Edge { - node: a, - direction: Direction::Dependency, - }; - let edges = self.edges.get_mut(&b).unwrap(); - debug_assert!(!edges.contains(&edge)); - edges.push(edge); - } - - /// Get a [Dependency] corresponding to the edge from `from` to `to` - /// - /// This will panic if there is no edge between the two nodes given. - pub fn edge(&self, from: NodeId, to: NodeId) -> Dependency { - let edges = self.edges.get(&from).unwrap(); - let edge = Edge { - node: to, - direction: Direction::Dependent, - }; - assert!(self.nodes.contains(&from)); - assert!(self.nodes.contains(&to)); - if edges.contains(&edge) { - Dependency::new(from, to) - } else { - panic!( - "invalid edge: there is no dependency from {} to {}", - from.expand(), - to.expand(), - ); - } - } - - /// Removes `node` from the graph, along with all edges in which it appears - pub fn remove_node>(&mut self, node: N) { - let id = node.into(); - if self.nodes.remove(&id) { - let edges = self.edges.remove(&id).unwrap(); - for Edge { - node: other_node_id, - .. - } in edges.into_iter() - { - self.edges.get_mut(&other_node_id).unwrap().retain(|e| e.node != id); - } - } - } - - /// Removes an edge from `a` to `b`. - /// - /// If `value` is provided, the use corresponding to that value is removed, rather than - /// the entire edge from `a` to `b`. However, if removing `value` makes the edge dead, or - /// `value` is not provided, then the entire edge is removed. - pub fn remove_edge(&mut self, a: NodeId, b: NodeId) { - // Get the edge id that connects a <-> b - if let Some(edges) = self.edges.get_mut(&a) { - edges.retain(|e| e.node != b || e.direction == Direction::Dependency); - } - if let Some(edges) = self.edges.get_mut(&b) { - edges.retain(|e| e.node != a || e.direction == Direction::Dependent); - } - } - - /// Returns the number of predecessors, i.e. dependents, for `node` in the graph - pub fn num_predecessors>(&self, node: N) -> usize { - let id = node.into(); - self.edges - .get(&id) - .map(|es| es.iter().filter(|e| e.direction == Direction::Dependency).count()) - .unwrap_or_default() - } - - /// Returns an iterator over the nodes in this graph - pub fn nodes(&self) -> impl Iterator + '_ { - self.nodes.iter().copied().map(Node::from) - } - - /// Returns an iterator over the nodes in this graph - pub fn node_ids(&self) -> impl Iterator + '_ { - self.nodes.iter().copied() - } - - /// Return the sole predecessor of `node`, if `node` has any predecessors. - /// - /// Returns `Err` if `node` has multiple predecessors - pub fn parent( - &self, - node: impl Into, - ) -> Result, InvalidDependencyGraphQuery> { - let mut predecessors = self.predecessors(node); - match predecessors.next() { - None => Ok(None), - Some(parent) => { - if predecessors.next().is_some() { - Err(InvalidDependencyGraphQuery) - } else { - Ok(Some(parent.dependent)) - } - } - } - } - - /// Like `parent`, but panics if `node` does not have a single parent - pub fn unwrap_parent(&self, node: impl Into) -> NodeId { - let node = node.into(); - self.parent(node) - .unwrap_or_else(|_| { - panic!("expected {node} to have a single parent, but found multiple") - }) - .unwrap_or_else(|| panic!("expected {node} to have a parent, but it has none")) - } - - /// Return the sole successor of `node`, if `node` has any successors. - /// - /// Returns `Err` if `node` has multiple successors - pub fn child( - &self, - node: impl Into, - ) -> Result, InvalidDependencyGraphQuery> { - let mut successors = self.successors(node); - match successors.next() { - None => Ok(None), - Some(child) => { - if successors.next().is_some() { - Err(InvalidDependencyGraphQuery) - } else { - Ok(Some(child.dependency)) - } - } - } - } - - /// Like `child`, but panics if `node` does not have a single child - pub fn unwrap_child(&self, node: impl Into) -> NodeId { - let node = node.into(); - self.child(node) - .unwrap_or_else(|_| { - panic!("expected {node} to have a single child, but found multiple") - }) - .unwrap_or_else(|| panic!("expected {node} to have a child, but it has none")) - } - - /// Returns an iterator over the predecessors, or dependents, of `node` in the graph - pub fn predecessors<'a, 'b: 'a>(&'b self, node: impl Into) -> Predecessors<'a> { - let id = node.into(); - Predecessors { - node: id, - iter: self.edges[&id].iter(), - } - } - - /// Like `predecessors`, but avoids decoding [Node] values, instead producing the raw [NodeId] - /// values. - pub fn predecessor_ids(&self, node: impl Into) -> impl Iterator + '_ { - let id = node.into(); - self.edges[&id].iter().filter_map(|edge| { - if matches!(edge.direction, Direction::Dependency) { - Some(edge.node) - } else { - None - } - }) - } - - /// Returns an iterator over the successors, or dependencies, of `node` in the graph - pub fn successors<'a, 'b: 'a>(&'b self, node: impl Into) -> Successors<'a> { - let id = node.into(); - Successors { - node: id, - iter: self.edges[&id].iter(), - } - } - - /// Like `successors`, but avoids decoding [Node] values, instead producing the raw [NodeId] - /// values. - pub fn successor_ids(&self, node: impl Into) -> impl Iterator + '_ { - let id = node.into(); - self.edges[&id].iter().filter_map(|edge| { - if matches!(edge.direction, Direction::Dependent) { - Some(edge.node) - } else { - None - } - }) - } - - /// Returns a data structure which assigns an index to each node in the graph for which `root` - /// is an ancestor, including `root` itself. The assigned index indicates the order in which - /// nodes will be emitted during code generation - the lower the index, the earlier the node - /// is emitted. Conversely, a higher index indicates that a node will be scheduled later in - /// the program, so values will be materialized from lowest index to highest. - pub fn indexed( - &self, - root: impl Into, - ) -> Result { - let root = root.into(); - - let mut output = BTreeMap::::new(); - let mut stack = vec![root]; - let mut discovered = BTreeSet::::default(); - let mut finished = BTreeSet::::default(); - - while let Some(node) = stack.last().copied() { - if discovered.insert(node) { - if node.is_instruction() { - for arg in self.successors(node).filter(|succ| succ.dependency.is_argument()) { - let arg_source_id = self.unwrap_child(arg.dependency); - if !discovered.contains(&arg_source_id) { - stack.push(arg_source_id); - } - } - for other in self.successors(node).filter(|succ| !succ.dependency.is_argument()) - { - let succ_node_id = if other.dependency.is_instruction() { - other.dependency - } else { - assert!(other.dependency.is_result()); - self.unwrap_child(other.dependency) - }; - if !discovered.contains(&succ_node_id) { - stack.push(succ_node_id); - } - } - } else if node.is_result() { - let inst_node = self.unwrap_child(node); - if !discovered.contains(&inst_node) { - stack.push(inst_node); - } - } - } else { - stack.pop(); - if finished.insert(node) { - let index = output.len(); - output.insert(node, index); - } - } - } - - Ok(DependencyGraphIndices { sorted: output }) - } - - /// Get the topographically-sorted nodes of this graph for which `root` is an ancestor. - pub fn toposort(&self, root: impl Into) -> Result, UnexpectedCycleError> { - use std::collections::VecDeque; - - let root = root.into(); - let mut depgraph = self.clone(); - let mut output = Vec::::with_capacity(depgraph.nodes.len()); - - // Remove all predecessor edges to the root - if let Some(edges) = depgraph.edges.get_mut(&root) { - edges.retain(|e| e.direction == Direction::Dependent); - } - - let mut roots = VecDeque::from_iter([root]); - let mut successors = SmallVec::<[NodeId; 4]>::default(); - while let Some(nid) = roots.pop_front() { - output.push(nid); - successors.clear(); - successors.extend(depgraph.successor_ids(nid)); - for mid in successors.drain(..) { - depgraph.remove_edge(nid, mid); - if depgraph.num_predecessors(mid) == 0 { - roots.push_back(mid); - } - } - } - - let has_cycle = depgraph.edges.iter().any(|(n, es)| output.contains(n) && !es.is_empty()); - if has_cycle { - Err(UnexpectedCycleError) - } else { - Ok(output) - } - } - - /// This function is used to represent the dependency of an instruction on values - /// it uses as arguments. We do so by adding the appropriate argument node to the - /// graph, and adding edges between the instruction and the argument node, and the - /// argument node and the stack value or instruction result which it references. - pub fn add_data_dependency( - &mut self, - dependent_id: NodeId, - argument: ArgumentNode, - value: hir::Value, - pp: hir::ProgramPoint, - function: &hir::Function, - ) { - debug_assert!(dependent_id.is_instruction()); - - let dependency_id = self.add_node(Node::Argument(argument)); - match function.dfg.value_data(value) { - hir::ValueData::Inst { - inst: dep_inst, - num, - .. - } => { - let dep_inst = *dep_inst; - let block_id = function.dfg.pp_block(pp); - if function.dfg.insts[dep_inst].block == block_id { - let dep_inst_index = - function.dfg.block_insts(block_id).position(|id| id == dep_inst).unwrap(); - let result_inst_node_id = self.add_node(Node::Inst { - id: dep_inst, - pos: dep_inst_index as u16, - }); - let result_node_id = self.add_node(Node::Result { - value, - index: *num as u8, - }); - self.add_dependency(result_node_id, result_inst_node_id); - self.add_dependency(dependency_id, result_node_id); - } else { - let operand_node_id = self.add_node(Node::Stack(value)); - self.add_dependency(dependency_id, operand_node_id); - }; - } - hir::ValueData::Param { .. } => { - let operand_node_id = self.add_node(Node::Stack(value)); - self.add_dependency(dependency_id, operand_node_id); - } - } - self.add_dependency(dependent_id, dependency_id); - } -} -impl fmt::Debug for DependencyGraph { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("DependencyGraph") - .field("nodes", &DebugNodes(self)) - .field("edges", &DebugEdges(self)) - .finish() - } -} - -/// This structure is produced by [DependencyGraph::indexed], which assigns -/// an ordinal index to every [Node] in the graph based on the order in which it -/// is visited during code generation. The lower the index, the earlier it is -/// visited. -/// -/// This is used to compare nodes in the graph with a common dependency to see which -/// one is the last dependent, which allows us to be more precise when we manipulate -/// the operand stack. -#[derive(Default)] -pub struct DependencyGraphIndices { - /// The topographically sorted nodes for the component of the - /// dependency graph for which we have constructed this set. - sorted: BTreeMap, -} -impl DependencyGraphIndices { - /// Get the index of `node` - /// - /// NOTE: This function will panic if `node` was not in the corresponding dependency graph, or - /// is unresolved - #[inline] - pub fn get(&self, node: impl Into) -> Option { - let id = node.into(); - self.sorted.get(&id).copied() - } -} -impl fmt::Debug for DependencyGraphIndices { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_map().entries(self.sorted.iter()).finish() - } -} - -/// An iterator over each successor edge, or [Dependency], of a given node in a [DependencyGraph] -pub struct Successors<'a> { - node: NodeId, - iter: core::slice::Iter<'a, Edge>, -} -impl<'a> Iterator for Successors<'a> { - type Item = Dependency; - - fn next(&mut self) -> Option { - for Edge { node, direction } in &mut self.iter { - if matches!(direction, Direction::Dependent) { - return Some(Dependency::new(self.node, *node)); - } - } - - None - } -} -impl<'a> DoubleEndedIterator for Successors<'a> { - fn next_back(&mut self) -> Option { - while let Some(Edge { node, direction }) = self.iter.next_back() { - if matches!(direction, Direction::Dependent) { - return Some(Dependency::new(self.node, *node)); - } - } - - None - } -} -impl<'a> ExactSizeIterator for Successors<'a> { - #[inline] - fn len(&self) -> usize { - self.iter.len() - } -} - -/// An iterator over each predecessor edge, or [Dependency], of a given node in a [DependencyGraph] -pub struct Predecessors<'a> { - node: NodeId, - iter: core::slice::Iter<'a, Edge>, -} -impl<'a> Iterator for Predecessors<'a> { - type Item = Dependency; - - fn next(&mut self) -> Option { - for Edge { node, direction } in &mut self.iter { - if matches!(direction, Direction::Dependency) { - return Some(Dependency::new(*node, self.node)); - } - } - - None - } -} -impl<'a> DoubleEndedIterator for Predecessors<'a> { - fn next_back(&mut self) -> Option { - while let Some(Edge { node, direction }) = self.iter.next_back() { - if matches!(direction, Direction::Dependency) { - return Some(Dependency::new(*node, self.node)); - } - } - - None - } -} -impl<'a> ExactSizeIterator for Predecessors<'a> { - #[inline] - fn len(&self) -> usize { - self.iter.len() - } -} - -struct DebugNodes<'a>(&'a DependencyGraph); -impl<'a> fmt::Debug for DebugNodes<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list().entries(self.0.nodes.iter()).finish() - } -} - -struct DebugEdges<'a>(&'a DependencyGraph); -impl<'a> fmt::Debug for DebugEdges<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut edges = f.debug_list(); - for node in self.0.nodes.iter().copied() { - for edge in self.0.successors(node) { - edges.entry(&format_args!("{}", edge)); - } - } - edges.finish() - } -} - -#[cfg(debug_assertions)] -#[inline(never)] -fn is_valid_dependency(dependent: NodeId, dependency: NodeId) -> bool { - match (dependent.into(), dependency.into()) { - (Node::Argument(_), Node::Stack(_) | Node::Result { .. }) => true, - (Node::Argument(_), Node::Inst { .. } | Node::Argument(_)) => { - panic!( - "{dependent} -> {dependency} is invalid: arguments may only depend on results or \ - operands" - ); - } - (Node::Inst { .. }, Node::Inst { .. } | Node::Result { .. } | Node::Argument(_)) => true, - (Node::Inst { .. }, _) => panic!( - "{dependent} -> {dependency} is invalid: instruction nodes may only depend directly \ - on arguments" - ), - (Node::Result { .. }, Node::Inst { .. }) => true, - (Node::Result { .. }, _) => panic!( - "{dependent} -> {dependency} is invalid: result nodes may only depend directly on \ - instructions" - ), - (Node::Stack(_), _) => { - panic!("{dependent} -> {dependency} is invalid: stack nodes may not have dependencies") - } - } -} - -#[cfg(not(debug_assertions))] -#[inline(always)] -const fn is_valid_dependency(_dependent: NodeId, _dependency: NodeId) -> bool { - true -} - -/// Helper function to produce a graph for: -/// -/// ```text,ignore -/// block0(v0: i32): -/// v1 = inst0 v0 -/// v3 = inst3 -/// v2 = inst1 v1, v0 -/// inst2 v2, block1(v1), block2(v1, v0) -/// ``` -/// -/// This graph represents: -/// -/// * All node types -/// * All three argument types -/// * All types of result usage (unused, singly/multiply used) -/// * Instruction and value identifiers which are added out of order with respect to program order -#[cfg(test)] -pub(crate) fn simple_dependency_graph() -> DependencyGraph { - let mut graph = DependencyGraph::new(); - let v0 = hir::Value::from_u32(0); - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - let v3 = hir::Value::from_u32(3); - let inst0 = hir::Inst::from_u32(0); - let inst1 = hir::Inst::from_u32(1); - let inst2 = hir::Inst::from_u32(2); - let inst3 = hir::Inst::from_u32(3); - - let v0_node = graph.add_node(Node::Stack(v0)); - let v1_node = graph.add_node(Node::Result { - value: v1, - index: 0, - }); - let v2_node = graph.add_node(Node::Result { - value: v2, - index: 0, - }); - let v3_node = graph.add_node(Node::Result { - value: v3, - index: 0, - }); - let inst0_node = graph.add_node(Node::Inst { id: inst0, pos: 0 }); - let inst1_node = graph.add_node(Node::Inst { id: inst1, pos: 2 }); - let inst2_node = graph.add_node(Node::Inst { id: inst2, pos: 3 }); - let inst3_node = graph.add_node(Node::Inst { id: inst3, pos: 1 }); - let inst0_arg0_node = graph.add_node(Node::Argument(ArgumentNode::Direct { - inst: inst0, - index: 0, - })); - let inst1_arg0_node = graph.add_node(Node::Argument(ArgumentNode::Direct { - inst: inst1, - index: 0, - })); - let inst1_arg1_node = graph.add_node(Node::Argument(ArgumentNode::Direct { - inst: inst1, - index: 1, - })); - let inst2_arg0_node = graph.add_node(Node::Argument(ArgumentNode::Direct { - inst: inst2, - index: 0, - })); - let inst2_block1_arg0_node = graph.add_node(Node::Argument(ArgumentNode::Indirect { - inst: inst2, - index: 0, - successor: 0, - })); - let inst2_block2_arg0_node = graph.add_node(Node::Argument(ArgumentNode::Indirect { - inst: inst2, - index: 0, - successor: 1, - })); - let inst2_block2_arg1_node = graph.add_node(Node::Argument(ArgumentNode::Conditional { - inst: inst2, - index: 1, - successor: 1, - })); - graph.add_dependency(v1_node, inst0_node); - graph.add_dependency(inst0_node, inst0_arg0_node); - graph.add_dependency(inst0_arg0_node, v0_node); - graph.add_dependency(v2_node, inst1_node); - graph.add_dependency(inst1_node, inst1_arg0_node); - graph.add_dependency(inst1_node, inst1_arg1_node); - graph.add_dependency(inst1_arg0_node, v1_node); - graph.add_dependency(inst1_arg1_node, v0_node); - graph.add_dependency(inst2_node, inst2_arg0_node); - graph.add_dependency(inst2_node, inst2_block1_arg0_node); - graph.add_dependency(inst2_node, inst2_block2_arg0_node); - graph.add_dependency(inst2_node, inst2_block2_arg1_node); - graph.add_dependency(inst2_arg0_node, v2_node); - graph.add_dependency(inst2_block1_arg0_node, v1_node); - graph.add_dependency(inst2_block2_arg0_node, v1_node); - graph.add_dependency(inst2_block2_arg1_node, v0_node); - graph.add_dependency(v3_node, inst3_node); - graph -} - -#[cfg(test)] -mod tests { - use midenc_hir::{self as hir, assert_matches}; - - use super::*; - - #[test] - fn dependency_graph_construction() { - let graph = simple_dependency_graph(); - - let v0 = hir::Value::from_u32(0); - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - let v3 = hir::Value::from_u32(3); - let inst0 = hir::Inst::from_u32(0); - let inst1 = hir::Inst::from_u32(1); - let inst2 = hir::Inst::from_u32(2); - let inst3 = hir::Inst::from_u32(3); - let v0_node = Node::Stack(v0); - let v1_node = Node::Result { - value: v1, - index: 0, - }; - let v2_node = Node::Result { - value: v2, - index: 0, - }; - let v3_node = Node::Result { - value: v3, - index: 0, - }; - let inst0_node = Node::Inst { id: inst0, pos: 0 }; - let inst1_node = Node::Inst { id: inst1, pos: 2 }; - let inst2_node = Node::Inst { id: inst2, pos: 3 }; - let inst3_node = Node::Inst { id: inst3, pos: 1 }; - let inst0_arg0_node = Node::Argument(ArgumentNode::Direct { - inst: inst0, - index: 0, - }); - let inst1_arg0_node = Node::Argument(ArgumentNode::Direct { - inst: inst1, - index: 0, - }); - let inst1_arg1_node = Node::Argument(ArgumentNode::Direct { - inst: inst1, - index: 1, - }); - let inst2_arg0_node = Node::Argument(ArgumentNode::Direct { - inst: inst2, - index: 0, - }); - let inst2_block1_arg0_node = Node::Argument(ArgumentNode::Indirect { - inst: inst2, - index: 0, - successor: 0, - }); - let inst2_block2_arg0_node = Node::Argument(ArgumentNode::Indirect { - inst: inst2, - index: 0, - successor: 1, - }); - let inst2_block2_arg1_node = Node::Argument(ArgumentNode::Conditional { - inst: inst2, - index: 1, - successor: 1, - }); - - // Make sure all the nodes are in the graph - assert!(graph.contains(&v0_node)); - assert!(graph.contains(&v1_node)); - assert!(graph.contains(&v2_node)); - assert!(graph.contains(&v3_node)); - assert!(graph.contains(&inst0_node)); - assert!(graph.contains(&inst1_node)); - assert!(graph.contains(&inst2_node)); - assert!(graph.contains(&inst3_node)); - assert!(graph.contains(&inst0_arg0_node)); - assert!(graph.contains(&inst1_arg0_node)); - assert!(graph.contains(&inst1_arg1_node)); - assert!(graph.contains(&inst2_arg0_node)); - assert!(graph.contains(&inst2_block1_arg0_node)); - assert!(graph.contains(&inst2_block2_arg0_node)); - assert!(graph.contains(&inst2_block2_arg1_node)); - - // Results depend on the instructions which produce them - assert_eq!(graph.child(v1_node), Ok(Some(inst0_node.into()))); - assert_eq!(graph.child(v2_node), Ok(Some(inst1_node.into()))); - - // Instructions depend on their arguments - assert_eq!(graph.child(inst0_node), Ok(Some(inst0_arg0_node.into()))); - let mut inst1_successors = graph.successors(inst1_node).map(|s| s.dependency); - assert_eq!(inst1_successors.next(), Some(inst1_arg0_node.into())); - assert_eq!(inst1_successors.next(), Some(inst1_arg1_node.into())); - assert_eq!(inst1_successors.next(), None); - - // Arguments depend on stack values or instruction results - assert_eq!(graph.child(inst0_arg0_node), Ok(Some(v0_node.into()))); - assert_eq!(graph.child(inst1_arg0_node), Ok(Some(v1_node.into()))); - assert_eq!(graph.child(inst1_arg1_node), Ok(Some(v0_node.into()))); - assert_eq!(graph.child(inst2_arg0_node), Ok(Some(v2_node.into()))); - assert_eq!(graph.child(inst2_block1_arg0_node), Ok(Some(v1_node.into()))); - assert_eq!(graph.child(inst2_block2_arg0_node), Ok(Some(v1_node.into()))); - assert_eq!(graph.child(inst2_block2_arg1_node), Ok(Some(v0_node.into()))); - - // Arguments only have one dependent, the instruction they belong to - assert_eq!(graph.parent(inst0_arg0_node), Ok(Some(inst0_node.into()))); - assert_eq!(graph.parent(inst1_arg0_node), Ok(Some(inst1_node.into()))); - assert_eq!(graph.parent(inst1_arg1_node), Ok(Some(inst1_node.into()))); - assert_eq!(graph.parent(inst2_arg0_node), Ok(Some(inst2_node.into()))); - assert_eq!(graph.parent(inst2_block1_arg0_node), Ok(Some(inst2_node.into()))); - assert_eq!(graph.parent(inst2_block2_arg0_node), Ok(Some(inst2_node.into()))); - assert_eq!(graph.parent(inst2_block2_arg1_node), Ok(Some(inst2_node.into()))); - - // Results which are unused have no dependents - assert_eq!(graph.parent(v3_node), Ok(None)); - - // Results which are used have one or more dependents - assert_eq!(graph.parent(v2_node), Ok(Some(inst2_arg0_node.into()))); - assert_matches!(graph.parent(v1_node), Err(_)); - let mut v1_dependents = graph.predecessors(v1_node).map(|p| p.dependent); - assert_eq!(v1_dependents.next(), Some(inst1_arg0_node.into())); - assert_eq!(v1_dependents.next(), Some(inst2_block1_arg0_node.into())); - assert_eq!(v1_dependents.next(), Some(inst2_block2_arg0_node.into())); - assert_eq!(v1_dependents.next(), None); - - // Nodes with multiple dependents will raise an error if you ask for the parent - assert_matches!(graph.parent(v0_node), Err(_)); - // Stack nodes can have no dependencies - assert_eq!(graph.child(v0_node), Ok(None)); - } - - /// We're expecting the graph to correspond to the following expression graph - /// - /// ```text,ignore - /// inst2 - /// |- inst2_arg0 -> v2 -> inst1--------- - /// | | - /// | _____________ - /// | | | - /// | inst1_arg0 inst1_arg1 - /// | | | - /// | v | - /// |- inst2_block1_arg0 ------> v1 -> inst0 | - /// | ^ | | - /// | | v | - /// | | inst0_arg0 | - /// |- inst2_block2_arg0 -------- | | - /// | v | - /// |- inst2_block2_arg1 -------------> v0 <--- - /// ``` - /// - /// Which should correspond to the following index assignment: - /// - /// 0. v0 - /// 1. inst0 - /// 2. result(v1) - /// 3. inst1 - /// 4. result(v2) - /// 5. inst2 - /// - /// For reference, this is the IR we have a graph of: - /// - /// ```text,ignore - /// block0(v0: i32): - /// v1 = inst0 v0 - /// v3 = inst3 - /// v2 = inst1 v1, v0 - /// inst2 v2, block1(v1), block2(v1, v0) - /// ``` - #[test] - fn dependency_graph_indexed() { - let graph = simple_dependency_graph(); - - let v0 = hir::Value::from_u32(0); - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - let inst0 = hir::Inst::from_u32(0); - let inst1 = hir::Inst::from_u32(1); - let inst2 = hir::Inst::from_u32(2); - let inst3 = hir::Inst::from_u32(3); - let v0_node = Node::Stack(v0); - let v1_node = Node::Result { - value: v1, - index: 0, - }; - let v2_node = Node::Result { - value: v2, - index: 0, - }; - let inst0_node = Node::Inst { id: inst0, pos: 0 }; - let inst1_node = Node::Inst { id: inst1, pos: 2 }; - let inst2_node = Node::Inst { id: inst2, pos: 3 }; - let inst3_node = Node::Inst { id: inst3, pos: 1 }; - - let indices = graph.indexed(inst2_node).unwrap(); - - assert_eq!(indices.get(inst3_node), None); - assert_eq!(indices.get(inst2_node), Some(5)); - assert_eq!(indices.get(v2_node), Some(4)); - assert_eq!(indices.get(inst1_node), Some(3)); - assert_eq!(indices.get(v1_node), Some(2)); - assert_eq!(indices.get(inst0_node), Some(1)); - assert_eq!(indices.get(v0_node), Some(0)); - } -} diff --git a/hir-analysis/src/dominance.rs b/hir-analysis/src/dominance.rs deleted file mode 100644 index 4655fbd9d..000000000 --- a/hir-analysis/src/dominance.rs +++ /dev/null @@ -1,1032 +0,0 @@ -use std::{ - cmp::{self, Ordering}, - collections::{BTreeSet, VecDeque}, - mem, -}; - -use cranelift_entity::{packed_option::PackedOption, SecondaryMap}; -use midenc_hir::{ - pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}, - Block, BranchInfo, DataFlowGraph, Function, Inst, ProgramPoint, Value, -}; -use midenc_session::Session; -use smallvec::SmallVec; - -use super::{BlockPredecessor, ControlFlowGraph}; - -/// RPO numbers are assigned as multiples of STRIDE to leave room -/// for modifications to the dominator tree. -const STRIDE: u32 = 4; - -/// A special RPO number used during `compute_postorder`. -const SEEN: u32 = 1; - -/// A node in the dominator tree. Each block has one of these. -#[derive(Clone, Default)] -struct Node { - /// Number of this node in a reverse post-order traversal of the control-flow graph, starting - /// from 1. - /// - /// This number is monotonic in the reverse post-order but not contiguous, as we leave holes - /// for localized modifications of the dominator tree after it is initially computed. - /// - /// Unreachable nodes get number 0, all others are > 0. - rpo_number: u32, - /// The immediate dominator of this block, represented as the instruction at the end of the - /// dominating block which transfers control to this block. - /// - /// This is `None` for unreachable blocks, as well as the entry block, which has no dominators. - idom: PackedOption, -} - -/// DFT stack state marker for computing the cfg post-order. -enum Visit { - First, - Last, -} - -#[derive(Default)] -pub struct DominatorTree { - nodes: SecondaryMap, - /// Post-order of all reachable blocks in the control flow graph - postorder: Vec, - /// Scratch buffer used by `compute_postorder` - stack: Vec<(Visit, Block)>, - valid: bool, -} -impl Analysis for DominatorTree { - type Entity = Function; - - fn analyze( - function: &Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> AnalysisResult { - let cfg = analyses.get_or_compute(function, session)?; - Ok(DominatorTree::with_function(function, &cfg)) - } - - fn is_invalidated(&self, preserved: &PreservedAnalyses) -> bool { - !preserved.is_preserved::() - } -} -impl DominatorTree { - /// Allocate a new blank dominator tree. Use `compute` to compute the dominator tree for a - /// function. - pub fn new() -> Self { - Self::default() - } - - /// Allocate and compute a dominator tree. - pub fn with_function(func: &Function, cfg: &ControlFlowGraph) -> Self { - let block_capacity = func.dfg.num_blocks(); - let mut domtree = Self { - nodes: SecondaryMap::with_capacity(block_capacity), - postorder: Vec::with_capacity(block_capacity), - stack: Vec::new(), - valid: false, - }; - domtree.compute(func, cfg); - domtree - } - - /// Reset and compute a CFG post-order and dominator tree. - pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph) { - debug_assert!(cfg.is_valid()); - self.compute_postorder(func); - self.compute_domtree(func, cfg); - self.valid = true; - } - - /// Clear the data structures used to represent the dominator tree. This will leave the tree in - /// a state where `is_valid()` returns false. - pub fn clear(&mut self) { - self.nodes.clear(); - self.postorder.clear(); - debug_assert!(self.stack.is_empty()); - self.valid = false; - } - - /// Check if the dominator tree is in a valid state. - /// - /// Note that this doesn't perform any kind of validity checks. It simply checks if the - /// `compute()` method has been called since the last `clear()`. It does not check that the - /// dominator tree is consistent with the CFG. - pub fn is_valid(&self) -> bool { - self.valid - } - - /// Is `block` reachable from the entry block? - pub fn is_reachable(&self, block: Block) -> bool { - self.nodes[block].rpo_number != 0 - } - - /// Get the blocks in cfg post-order used to compute the dominator tree. - /// - /// NOTE: This order is not updated automatically when the control-flow graph is modified, - /// it is computed from scratch and cached by `compute`. - pub fn cfg_postorder(&self) -> &[Block] { - debug_assert!(self.is_valid()); - &self.postorder - } - - /// Returns the immediate dominator of `block`. - /// - /// The immediate dominator of a basic block is the instruction which transfers control to that - /// block (and implicitly, its enclosing block). This instruction does not have to be the - /// terminator of its block, though it typically is. - /// - /// An instruction "dominates" `block` if all control flow paths from the function entry to - /// `block` must go through that instruction. - /// - /// The "immediate dominator" is the dominator that is closest to `block`. All other dominators - /// also dominate the immediate dominator. - /// - /// This returns `None` if `block` is not reachable from the entry block, or if it is the entry - /// block which has no dominators. - pub fn idom(&self, block: Block) -> Option { - self.nodes[block].idom.into() - } - - /// Compare two blocks relative to the reverse post-order. - pub fn rpo_cmp_block(&self, a: Block, b: Block) -> Ordering { - self.nodes[a].rpo_number.cmp(&self.nodes[b].rpo_number) - } - - /// Compare two program points relative to a reverse post-order traversal of the control-flow - /// graph. - /// - /// Return `Ordering::Less` if `a` comes before `b` in the RPO. - /// - /// If `a` and `b` belong to the same block, compare their relative position in the block. - pub fn rpo_cmp(&self, a: A, b: B, dfg: &DataFlowGraph) -> Ordering - where - A: Into, - B: Into, - { - let a = a.into(); - let b = b.into(); - self.rpo_cmp_block(dfg.pp_block(a), dfg.pp_block(b)) - .then_with(|| dfg.pp_cmp(a, b)) - } - - /// Returns `true` if `a` dominates `b`. - /// - /// The dominance relation requires that every path from the entry block to `b` passes through - /// `a`. As this function determines _non-strict_ dominance, a block/instruction is considered - /// to dominate itself. See [DominatorTree::strictly_dominates] for the strict variant of this - /// relationship. - /// - /// Dominance is ill defined for unreachable blocks. If you happen to be querying dominance for - /// instructions in the same unreachable block, the result is always correct; but for other - /// pairings, the result will always be `false` if one of them is unreachable. - pub fn dominates(&self, a: A, b: B, dfg: &DataFlowGraph) -> bool - where - A: Into, - B: Into, - { - let a = a.into(); - let b = b.into(); - if a == b { - return true; - } - match a { - ProgramPoint::Block(block_a) => self.last_dominator(block_a, b, dfg).is_some(), - ProgramPoint::Inst(inst_a) => { - let block_a = - dfg.inst_block(inst_a).expect("instruction is not attached to a block"); - match self.last_dominator(block_a, b, dfg) { - Some(last) => dfg.pp_cmp(inst_a, last) != Ordering::Greater, - None => false, - } - } - } - } - - /// Returns `true` if `a` strictly dominates `b`. - /// - /// This dominance relation requires that `a != b`, and that every path from the entry block to - /// `b` passes through `a`. See [DominatorTree::dominates] for the non-strict variant of this - /// relationship. - /// - /// Dominance is ill defined for unreachable blocks. If you happen to be querying dominance for - /// instructions in the same unreachable block, the result is always correct; but for other - /// pairings, the result will always be `false` if one of them is unreachable. - pub fn strictly_dominates(&self, a: A, b: B, dfg: &DataFlowGraph) -> bool - where - A: Into, - B: Into, - { - let a = a.into(); - let b = b.into(); - if a == b { - return false; - } - match a { - ProgramPoint::Block(block_a) => self.last_dominator(block_a, b, dfg).is_some(), - ProgramPoint::Inst(inst_a) => { - let block_a = - dfg.inst_block(inst_a).expect("instruction is not attached to a block"); - match self.last_dominator(block_a, b, dfg) { - Some(last) => dfg.pp_cmp(inst_a, last) == Ordering::Less, - None => false, - } - } - } - } - - /// Find the last instruction in `a` that dominates `b`. - /// - /// If no instructions in `a` dominate `b`, return `None`. - pub fn last_dominator(&self, a: Block, b: B, dfg: &DataFlowGraph) -> Option - where - B: Into, - { - let (mut block_b, mut inst_b) = match b.into() { - ProgramPoint::Block(block) => (block, None), - ProgramPoint::Inst(inst) => ( - dfg.inst_block(inst).expect("instruction is not attached to a block"), - Some(inst), - ), - }; - let rpo_a = self.nodes[a].rpo_number; - - // Run a finger up the dominator tree from b until we see a. - // Do nothing if b is unreachable. - while rpo_a < self.nodes[block_b].rpo_number { - let idom = match self.idom(block_b) { - Some(idom) => idom, - None => return None, // a is unreachable, so we climbed past the entry - }; - block_b = dfg - .inst_block(idom) - .expect("control flow graph has been modified since dominator tree was computed"); - inst_b = Some(idom); - } - if a == block_b { - inst_b - } else { - None - } - } - - /// Compute the common dominator of two basic blocks. - /// - /// Both basic blocks are assumed to be reachable. - pub fn common_dominator( - &self, - mut a: BlockPredecessor, - mut b: BlockPredecessor, - dfg: &DataFlowGraph, - ) -> BlockPredecessor { - loop { - match self.rpo_cmp_block(a.block, b.block) { - Ordering::Less => { - // `a` comes before `b` in the RPO. Move `b` up. - let idom = self.nodes[b.block].idom.expect("Unreachable basic block?"); - b = BlockPredecessor::new( - dfg.inst_block(idom).expect("Dangling idom instruction"), - idom, - ); - } - Ordering::Greater => { - // `b` comes before `a` in the RPO. Move `a` up. - let idom = self.nodes[a.block].idom.expect("Unreachable basic block?"); - a = BlockPredecessor::new( - dfg.inst_block(idom).expect("Dangling idom instruction"), - idom, - ); - } - Ordering::Equal => break, - } - } - - debug_assert_eq!(a.block, b.block, "Unreachable block passed to common_dominator?"); - - // We're in the same block. The common dominator is the earlier instruction. - if dfg.pp_cmp(a.inst, b.inst) == Ordering::Less { - a - } else { - b - } - } - - /// Reset all internal data structures and compute a post-order of the control flow graph. - /// - /// This leaves `rpo_number == 1` for all reachable blocks, 0 for unreachable ones. - fn compute_postorder(&mut self, func: &Function) { - self.clear(); - self.nodes.resize(func.dfg.num_blocks()); - - // This algorithm is a depth first traversal (DFT) of the control flow graph, computing a - // post-order of the blocks that are reachable form the entry block. A DFT post-order is not - // unique. The specific order we get is controlled by the order each node's children are - // visited. - // - // We view the CFG as a graph where each `BlockCall` value of a terminating branch - // instruction is an edge. A consequence of this is that we visit successor nodes in the - // reverse order specified by the branch instruction that terminates the basic block. - // (Reversed because we are using a stack to control traversal, and push the successors in - // the order the branch instruction specifies -- there's no good reason for this particular - // order.) - // - // During this algorithm only, use `rpo_number` to hold the following state: - // - // 0: block has not yet had its first visit - // SEEN: block has been visited at least once, implying that all of its successors are on - // the stack - self.stack.push((Visit::First, func.dfg.entry_block())); - - while let Some((visit, block)) = self.stack.pop() { - match visit { - Visit::First => { - if self.nodes[block].rpo_number == 0 { - // This is the first time we pop the block, so we need to scan its - // successors and then revisit it. - self.nodes[block].rpo_number = SEEN; - self.stack.push((Visit::Last, block)); - if let Some(inst) = func.dfg.last_inst(block) { - // Heuristic: chase the children in reverse. This puts the first - // successor block first in the postorder, all other things being - // equal, which tends to prioritize loop backedges over out-edges, - // putting the edge-block closer to the loop body and minimizing - // live-ranges in linear instruction space. This heuristic doesn't have - // any effect on the computation of dominators, and is purely for other - // consumers of the postorder we cache here. - match func.dfg.analyze_branch(inst) { - BranchInfo::NotABranch => (), - BranchInfo::SingleDest(successor) => { - if self.nodes[successor.destination].rpo_number == 0 { - self.stack.push((Visit::First, successor.destination)); - } - } - BranchInfo::MultiDest(ref successors) => { - for successor in successors.iter().rev() { - if self.nodes[successor.destination].rpo_number == 0 { - self.stack.push((Visit::First, successor.destination)); - } - } - } - } - } - } - } - - Visit::Last => { - // We've finished all this node's successors. - self.postorder.push(block); - } - } - } - } - - /// Build a dominator tree from a control flow graph using Keith D. Cooper's - /// "Simple, Fast Dominator Algorithm." - fn compute_domtree(&mut self, func: &Function, cfg: &ControlFlowGraph) { - // During this algorithm, `rpo_number` has the following values: - // - // 0: block is not reachable. - // 1: block is reachable, but has not yet been visited during the first pass. This is set by - // `compute_postorder`. - // 2+: block is reachable and has an assigned RPO number. - - // We'll be iterating over a reverse post-order of the CFG, skipping the entry block. - let (entry_block, postorder) = match self.postorder.as_slice().split_last() { - Some((&eb, rest)) => (eb, rest), - None => return, - }; - - // Do a first pass where we assign RPO numbers to all reachable nodes. - self.nodes[entry_block].rpo_number = 2 * STRIDE; - for (rpo_idx, &block) in postorder.iter().rev().enumerate() { - // Update the current node and give it an RPO number. - // The entry block got 2, the rest start at 3 by multiples of STRIDE to leave - // room for future dominator tree modifications. - // - // Since `compute_idom` will only look at nodes with an assigned RPO number, the - // function will never see an uninitialized predecessor. - // - // Due to the nature of the post-order traversal, every node we visit will have at - // least one predecessor that has previously been visited during this RPO. - self.nodes[block] = Node { - idom: self.compute_idom(block, cfg, &func.dfg).into(), - rpo_number: (rpo_idx as u32 + 3) * STRIDE, - } - } - - // Now that we have RPO numbers for everything and initial immediate dominator estimates, - // iterate until convergence. - // - // If the function is free of irreducible control flow, this will exit after one iteration. - let mut changed = true; - while changed { - changed = false; - for &block in postorder.iter().rev() { - let idom = self.compute_idom(block, cfg, &func.dfg).into(); - if self.nodes[block].idom != idom { - self.nodes[block].idom = idom; - changed = true; - } - } - } - } - - // Compute the immediate dominator for `block` using the current `idom` states for the reachable - // nodes. - fn compute_idom(&self, block: Block, cfg: &ControlFlowGraph, dfg: &DataFlowGraph) -> Inst { - // Get an iterator with just the reachable, already visited predecessors to `block`. - // Note that during the first pass, `rpo_number` is 1 for reachable blocks that haven't - // been visited yet, 0 for unreachable blocks. - let mut reachable_preds = cfg - .pred_iter(block) - .filter(|&BlockPredecessor { block: pred, .. }| self.nodes[pred].rpo_number > 1); - - // The RPO must visit at least one predecessor before this node. - let mut idom = - reachable_preds.next().expect("block node must have one reachable predecessor"); - - for pred in reachable_preds { - idom = self.common_dominator(idom, pred, dfg); - } - - idom.inst - } -} - -/// Auxiliary structure for `DominatorTree` which provides: -/// -/// - Traversal of the dominator tree in pre-order -/// - Ordering of blocks in dominator tree pre-order -/// - Constant-time dominance checks per-block -#[derive(Default)] -pub struct DominatorTreePreorder { - nodes: SecondaryMap, - stack: Vec, -} - -#[derive(Default, Clone)] -struct PreorderNode { - /// First child node in the dominator tree - child: Option, - /// Next sibling node in the dominator tree, ordered - /// according to the control-flow graph reverse post-order. - sibling: Option, - /// Sequence number for this node in a pre-order traversal of the dominator tree - /// - /// Unreachable blocks are 0, entry block is 1 - pre_number: u32, - /// Maximum `pre_number` for the sub-tree of the dominator tree that is rooted at this node. - /// - /// This is always greater than or equal to `pre_number` - pre_max: u32, -} -impl DominatorTreePreorder { - pub fn new() -> Self { - Self::default() - } - - pub fn with_function(domtree: &DominatorTree, function: &Function) -> Self { - let mut this = Self::new(); - this.compute(domtree, function); - this - } - - pub fn compute(&mut self, domtree: &DominatorTree, function: &Function) { - self.nodes.clear(); - debug_assert_eq!(self.stack.len(), 0); - - // Step 1: Populate the child and sibling links. - // - // By following the CFG post-order and pushing to the front of the lists, we make sure that - // sibling lists are ordered according to the CFG reverse post-order. - for &block in domtree.cfg_postorder() { - if let Some(idom_inst) = domtree.idom(block) { - let idom = function.dfg.inst_block(idom_inst).unwrap(); - let sib = mem::replace(&mut self.nodes[idom].child, Some(block)); - self.nodes[block].sibling = sib; - } else { - // The only block without an immediate dominator is the entry. - self.stack.push(block); - } - } - - // Step 2. Assign pre-order numbers from a DFS of the dominator tree. - debug_assert!(self.stack.len() <= 1); - let mut n = 0; - while let Some(block) = self.stack.pop() { - n += 1; - let node = &mut self.nodes[block]; - node.pre_number = n; - node.pre_max = n; - if let Some(n) = node.sibling { - self.stack.push(n); - } - if let Some(n) = node.child { - self.stack.push(n); - } - } - - // Step 3. Propagate the `pre_max` numbers up the tree. - // The CFG post-order is topologically ordered w.r.t. dominance so a node comes after all - // its dominator tree children. - for &block in domtree.cfg_postorder() { - if let Some(idom_inst) = domtree.idom(block) { - let idom = function.dfg.inst_block(idom_inst).unwrap(); - let pre_max = cmp::max(self.nodes[block].pre_max, self.nodes[idom].pre_max); - self.nodes[idom].pre_max = pre_max; - } - } - } - - /// Get an iterator over the immediate children of `block` in the dominator tree. - /// - /// These are the blocks whose immediate dominator is an instruction in `block`, ordered - /// according to the CFG reverse post-order. - pub fn children(&self, block: Block) -> ChildIter { - ChildIter { - dtpo: self, - next: self.nodes[block].child, - } - } - - /// Fast, constant time dominance check with block granularity. - /// - /// This computes the same result as `domtree.dominates(a, b)`, but in guaranteed fast constant - /// time. This is less general than the `DominatorTree` method because it only works with block - /// program points. - /// - /// A block is considered to dominate itself. - pub fn dominates(&self, a: Block, b: Block) -> bool { - let na = &self.nodes[a]; - let nb = &self.nodes[b]; - na.pre_number <= nb.pre_number && na.pre_max >= nb.pre_max - } - - /// Compare two blocks according to the dominator pre-order. - pub fn pre_cmp_block(&self, a: Block, b: Block) -> Ordering { - self.nodes[a].pre_number.cmp(&self.nodes[b].pre_number) - } - - /// Compare two program points according to the dominator tree pre-order. - /// - /// This ordering of program points have the property that given a program point, pp, all the - /// program points dominated by pp follow immediately and contiguously after pp in the order. - pub fn pre_cmp(&self, a: A, b: B, function: &Function) -> Ordering - where - A: Into, - B: Into, - { - let a = a.into(); - let b = b.into(); - self.pre_cmp_block(function.dfg.pp_block(a), function.dfg.pp_block(b)) - .then_with(|| function.dfg.pp_cmp(a, b)) - } -} - -/// An iterator that enumerates the direct children of a block in the dominator tree. -pub struct ChildIter<'a> { - dtpo: &'a DominatorTreePreorder, - next: Option, -} - -impl<'a> Iterator for ChildIter<'a> { - type Item = Block; - - fn next(&mut self) -> Option { - let n = self.next; - if let Some(block) = n { - self.next = self.dtpo.nodes[block].sibling; - } - n - } -} - -/// Calculates the dominance frontier for every block in a given `DominatorTree` -/// -/// The dominance frontier of a block `B` is the set of blocks `DF` where for each block `Y` in `DF` -/// `B` dominates some predecessor of `Y`, but does not strictly dominate `Y`. -/// -/// Dominance frontiers are useful in the construction of SSA form, as well as identifying control -/// dependent dataflow (for example, a variable in a program that has a different value depending -/// on what branch of an `if` statement is taken). -/// -/// A dominance frontier can also be computed for a set of blocks, by taking the union of the -/// dominance frontiers of each block in the set. -/// -/// An iterated dominance frontier is given by computing the dominance frontier for some set `X`, -/// i.e. `DF(X)`, then computing the dominance frontier on that, i.e. `DF(DF(X))`, taking the union -/// of the results, and repeating this process until fixpoint is reached. This is often represented -/// in literature as `DF+(X)`. -/// -/// Iterated dominance frontiers are of particular usefulness to us, because they correspond to the -/// set of blocks in which we need to place phi nodes for some variable, in order to properly handle -/// control dependent dataflow for that variable. -/// -/// Consider the following example (not in SSA form): -/// -/// -/// ```text,ignore -/// block0(x): -/// v = 0 -/// cond_br x, block1, block2 -/// -/// block1(): -/// v = 1 -/// br block3 -/// -/// block2(): -/// v = 2 -/// br block3 -/// -/// block3: -/// ret v -/// ``` -/// -/// In this example, we have a variable, `v`, which is assigned new values later in the program -/// depending on which path through the program is taken. To transform this program into SSA form, -/// we take the set `V`, containing all of the assignments to `v`, and compute `DF+(V)`. Given -/// the program above, that would give us the set `{block3}`: -/// -/// * The dominance frontier of the assignment in `block0` is empty, because `block0` strictly -/// dominates all other blocks in the program. -/// * The dominance frontier of the assignment in `block1` contains `block3`, because `block1` -/// dominates a predecessor of `block3` (itself), but does not strictly dominate that predecessor, -/// because a node cannot strictly dominate itself. -/// * The dominance frontier of the assignment in `block2` contains `block3`, for the same reasons -/// as `block1`. -/// * The dominance frontier of `block3` is empty, because it has no successors and thus cannot -/// dominate any other blocks. -/// * The union of all the dominance frontiers is simply `{block3}` -/// -/// So this tells us that we need to place a phi node (a block parameter) at `block3`, and rewrite -/// all uses of `v` strictly dominated by the phi node to use the value associated with the phi -/// instead. In every predecessor of `block3`, we must pass `v` as a new block argument. Lastly, to -/// obtain SSA form, we rewrite assignments to `v` as defining new variables instead, and walk up -/// the dominance tree from each use of `v` until we find the nearest dominating definition for that -/// use, and rewrite the usage of `v` to use the value produced by that definition. Performing these -/// steps gives us the following program: -/// -/// ```text,ignore -/// block0(x): -/// v0 = 0 -/// cond_br x, block1, block2 -/// -/// block1(): -/// v2 = 1 -/// br block3(v2) -/// -/// block2(): -/// v3 = 2 -/// br block3(v3) -/// -/// block3(v1): -/// ret v1 -/// ``` -/// -/// This program is in SSA form, and the dataflow for `v` is now explicit. An interesting -/// consequence of the transformation we performed, is that we are able to trivially recognize -/// that the definition of `v` in `block0` is unused, allowing us to eliminate it entirely. -#[derive(Default)] -pub struct DominanceFrontier { - /// The dominance frontier for each block, as a set of blocks - dfs: SecondaryMap>, -} -impl DominanceFrontier { - pub fn compute(domtree: &DominatorTree, cfg: &ControlFlowGraph, function: &Function) -> Self { - let mut dfs = SecondaryMap::>::default(); - - for id in domtree.cfg_postorder() { - let id = *id; - if cfg.num_predecessors(id) < 2 { - continue; - } - let idom = domtree.idom(id).unwrap(); - for BlockPredecessor { block: p, inst: i } in cfg.pred_iter(id) { - let mut p = p; - let mut i = i; - while i != idom { - dfs[p].insert(id); - let Some(idom_p) = domtree.idom(p) else { - break; - }; - i = idom_p; - p = function.dfg.inst_block(idom_p).unwrap(); - } - } - } - - Self { dfs } - } - - /// Compute the iterated dominance frontier for `block` - pub fn iterate(&self, block: &Block) -> BTreeSet { - self.iterate_all([*block]) - } - - /// Compute the iterated dominance frontier for `blocks` - pub fn iterate_all(&self, blocks: I) -> BTreeSet - where - I: IntoIterator, - { - let mut block_q = VecDeque::from_iter(blocks); - let mut idf = BTreeSet::default(); - while let Some(block) = block_q.pop_front() { - let added = self.dfs[block].difference(&idf).copied().collect::>(); - if added.is_empty() { - continue; - } - - // Extend `df` and add the new blocks to the queue - for block in added { - idf.insert(block); - if !block_q.contains(&block) { - block_q.push_back(block); - } - } - } - - idf - } - - /// Get an iterator over the dominance frontier of `block` - pub fn iter(&self, block: &Block) -> impl Iterator + '_ { - DominanceFrontierIter { - df: self.dfs.get(*block).map(|set| set.iter().copied()), - } - } - - /// Get an iterator over the dominance frontier of `value` - pub fn iter_by_value( - &self, - value: Value, - function: &Function, - ) -> impl Iterator + '_ { - let defining_block = function.dfg.value_block(value); - DominanceFrontierIter { - df: self.dfs.get(defining_block).map(|set| set.iter().copied()), - } - } - - /// Get the set of blocks in the dominance frontier of `block`, or `None` if `block` has an - /// empty dominance frontier. - #[inline] - pub fn get(&self, block: &Block) -> Option<&BTreeSet> { - self.dfs.get(*block) - } - - /// Get the set of blocks in the dominance frontier of `value`, or `None` if `value` has an - /// empty dominance frontier. - pub fn get_by_value(&self, value: Value, function: &Function) -> Option<&BTreeSet> { - let defining_block = function.dfg.value_block(value); - self.dfs.get(defining_block) - } -} - -struct DominanceFrontierIter { - df: Option, -} -impl<'a, I> Iterator for DominanceFrontierIter -where - I: Iterator + 'a, -{ - type Item = Block; - - fn next(&mut self) -> Option { - if let Some(i) = self.df.as_mut() { - i.next() - } else { - None - } - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{ - AbiParam, FunctionBuilder, Immediate, InstBuilder, Signature, SourceSpan, Type, - }; - - use super::*; - - #[test] - fn domtree_empty() { - let id = "test::empty".parse().unwrap(); - let function = Function::new(id, Signature::new([], [])); - let entry = function.dfg.entry_block(); - - let cfg = ControlFlowGraph::with_function(&function); - assert!(cfg.is_valid()); - let domtree = DominatorTree::with_function(&function, &cfg); - - assert_eq!(1, domtree.nodes.keys().count()); - assert_eq!(domtree.cfg_postorder(), &[entry]); - - let mut dtpo = DominatorTreePreorder::new(); - dtpo.compute(&domtree, &function); - } - - #[test] - fn domtree_unreachable_node() { - let id = "test::unreachable_node".parse().unwrap(); - let mut function = Function::new(id, Signature::new([AbiParam::new(Type::I32)], [])); - let block0 = function.dfg.entry_block(); - let block1 = function.dfg.create_block(); - let block2 = function.dfg.create_block(); - let trap_block = function.dfg.create_block(); - let v2 = { - let mut builder = FunctionBuilder::new(&mut function); - let v0 = { - let args = builder.block_params(block0); - args[0] - }; - - builder.switch_to_block(block0); - let cond = builder.ins().neq_imm(v0, Immediate::I32(0), SourceSpan::UNKNOWN); - builder.ins().cond_br(cond, block2, &[], trap_block, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(trap_block); - builder.ins().unreachable(SourceSpan::UNKNOWN); - - builder.switch_to_block(block1); - let v1 = builder.ins().i32(1, SourceSpan::UNKNOWN); - let v2 = builder.ins().add_checked(v0, v1, SourceSpan::UNKNOWN); - builder.ins().br(block0, &[v2], SourceSpan::UNKNOWN); - - builder.switch_to_block(block2); - builder.ins().ret(Some(v0), SourceSpan::UNKNOWN); - v2 - }; - - let cfg = ControlFlowGraph::with_function(&function); - let domtree = DominatorTree::with_function(&function, &cfg); - - // Fall-through-first, prune-at-source DFT: - // - // block0 { - // brif block2 { - // trap - // block2 { - // return - // } block2 - // } block0 - assert_eq!(domtree.cfg_postorder(), &[block2, trap_block, block0]); - - let v2_inst = function.dfg.value_data(v2).unwrap_inst(); - assert!(!domtree.dominates(v2_inst, block0, &function.dfg)); - assert!(!domtree.dominates(block0, v2_inst, &function.dfg)); - - let mut dtpo = DominatorTreePreorder::new(); - dtpo.compute(&domtree, &function); - assert!(dtpo.dominates(block0, block0)); - assert!(!dtpo.dominates(block0, block1)); - assert!(dtpo.dominates(block0, block2)); - assert!(!dtpo.dominates(block1, block0)); - assert!(dtpo.dominates(block1, block1)); - assert!(!dtpo.dominates(block1, block2)); - assert!(!dtpo.dominates(block2, block0)); - assert!(!dtpo.dominates(block2, block1)); - assert!(dtpo.dominates(block2, block2)); - } - - #[test] - fn domtree_non_zero_entry_block() { - let id = "test::non_zero_entry".parse().unwrap(); - let mut function = Function::new(id, Signature::new([], [])); - let block0 = function.dfg.entry_block(); - let block1 = function.dfg.create_block(); - let block2 = function.dfg.create_block(); - let block3 = function.dfg.create_block(); - let cond = function.dfg.append_block_param(block3, Type::I1, SourceSpan::UNKNOWN); - function.dfg.entry = block3; - function.signature.params.push(AbiParam::new(Type::I1)); - let (br_block3_block1, br_block1_block0_block2) = { - let mut builder = FunctionBuilder::new(&mut function); - - builder.switch_to_block(block3); - let br_block3_block1 = builder.ins().br(block1, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block1); - let br_block1_block0_block2 = - builder.ins().cond_br(cond, block0, &[], block2, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block2); - builder.ins().br(block0, &[], SourceSpan::UNKNOWN); - - (br_block3_block1, br_block1_block0_block2) - }; - - let cfg = ControlFlowGraph::with_function(&function); - let domtree = DominatorTree::with_function(&function, &cfg); - - // Fall-through-first, prune-at-source DFT: - // - // block3 { - // block3:jump block1 { - // block1 { - // block1:brif block0 { - // block1:jump block2 { - // block2 { - // block2:jump block0 (seen) - // } block2 - // } block1:jump block2 - // block0 { - // } block0 - // } block1:brif block0 - // } block1 - // } block3:jump block1 - // } block3 - - assert_eq!(domtree.cfg_postorder(), &[block0, block2, block1, block3]); - - assert_eq!(function.dfg.entry_block(), block3); - assert_eq!(domtree.idom(block3), None); - assert_eq!(domtree.idom(block1).unwrap(), br_block3_block1); - assert_eq!(domtree.idom(block2).unwrap(), br_block1_block0_block2); - assert_eq!(domtree.idom(block0).unwrap(), br_block1_block0_block2); - - assert!(domtree.dominates(br_block1_block0_block2, br_block1_block0_block2, &function.dfg)); - assert!(!domtree.dominates(br_block1_block0_block2, br_block3_block1, &function.dfg)); - assert!(domtree.dominates(br_block3_block1, br_block1_block0_block2, &function.dfg)); - - assert_eq!(domtree.rpo_cmp(block3, block3, &function.dfg), Ordering::Equal); - assert_eq!(domtree.rpo_cmp(block3, block1, &function.dfg), Ordering::Less); - assert_eq!(domtree.rpo_cmp(block3, br_block3_block1, &function.dfg), Ordering::Less); - assert_eq!( - domtree.rpo_cmp(br_block3_block1, br_block1_block0_block2, &function.dfg), - Ordering::Less - ); - } - - #[test] - fn domtree_backwards_layout() { - let id = "test::backwards_layout".parse().unwrap(); - let mut function = Function::new(id, Signature::new([], [])); - let block0 = function.dfg.entry_block(); - let block1 = function.dfg.create_block(); - let block2 = function.dfg.create_block(); - let (jmp02, trap, jmp21) = { - let mut builder = FunctionBuilder::new(&mut function); - - builder.switch_to_block(block0); - let jmp02 = builder.ins().br(block2, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block1); - let trap = builder.ins().unreachable(SourceSpan::UNKNOWN); - - builder.switch_to_block(block2); - let jmp21 = builder.ins().br(block1, &[], SourceSpan::UNKNOWN); - - (jmp02, trap, jmp21) - }; - - let cfg = ControlFlowGraph::with_function(&function); - let domtree = DominatorTree::with_function(&function, &cfg); - - assert_eq!(function.dfg.entry_block(), block0); - assert_eq!(domtree.idom(block0), None); - assert_eq!(domtree.idom(block1), Some(jmp21)); - assert_eq!(domtree.idom(block2), Some(jmp02)); - - assert!(domtree.dominates(block0, block0, &function.dfg)); - assert!(domtree.dominates(block0, jmp02, &function.dfg)); - assert!(domtree.dominates(block0, block1, &function.dfg)); - assert!(domtree.dominates(block0, trap, &function.dfg)); - assert!(domtree.dominates(block0, block2, &function.dfg)); - assert!(domtree.dominates(block0, jmp21, &function.dfg)); - - assert!(!domtree.dominates(jmp02, block0, &function.dfg)); - assert!(domtree.dominates(jmp02, jmp02, &function.dfg)); - assert!(domtree.dominates(jmp02, block1, &function.dfg)); - assert!(domtree.dominates(jmp02, trap, &function.dfg)); - assert!(domtree.dominates(jmp02, block2, &function.dfg)); - assert!(domtree.dominates(jmp02, jmp21, &function.dfg)); - - assert!(!domtree.dominates(block1, block0, &function.dfg)); - assert!(!domtree.dominates(block1, jmp02, &function.dfg)); - assert!(domtree.dominates(block1, block1, &function.dfg)); - assert!(domtree.dominates(block1, trap, &function.dfg)); - assert!(!domtree.dominates(block1, block2, &function.dfg)); - assert!(!domtree.dominates(block1, jmp21, &function.dfg)); - - assert!(!domtree.dominates(trap, block0, &function.dfg)); - assert!(!domtree.dominates(trap, jmp02, &function.dfg)); - assert!(!domtree.dominates(trap, block1, &function.dfg)); - assert!(domtree.dominates(trap, trap, &function.dfg)); - assert!(!domtree.dominates(trap, block2, &function.dfg)); - assert!(!domtree.dominates(trap, jmp21, &function.dfg)); - - assert!(!domtree.dominates(block2, block0, &function.dfg)); - assert!(!domtree.dominates(block2, jmp02, &function.dfg)); - assert!(domtree.dominates(block2, block1, &function.dfg)); - assert!(domtree.dominates(block2, trap, &function.dfg)); - assert!(domtree.dominates(block2, block2, &function.dfg)); - assert!(domtree.dominates(block2, jmp21, &function.dfg)); - - assert!(!domtree.dominates(jmp21, block0, &function.dfg)); - assert!(!domtree.dominates(jmp21, jmp02, &function.dfg)); - assert!(domtree.dominates(jmp21, block1, &function.dfg)); - assert!(domtree.dominates(jmp21, trap, &function.dfg)); - assert!(!domtree.dominates(jmp21, block2, &function.dfg)); - assert!(domtree.dominates(jmp21, jmp21, &function.dfg)); - } -} diff --git a/hir-analysis/src/lattice.rs b/hir-analysis/src/lattice.rs new file mode 100644 index 000000000..aff2e2062 --- /dev/null +++ b/hir-analysis/src/lattice.rs @@ -0,0 +1,236 @@ +use core::{convert::AsRef, fmt}; + +use super::{ + AnalysisState, BuildableAnalysisState, ChangeResult, DenseLattice, LatticeAnchor, + LatticeAnchorRef, SparseLattice, +}; + +/// This trait must be implemented for any value that exhibits the properties of a +/// [Lattice](https://en.wikipedia.org/wiki/Lattice_(order)#Definition). +/// +/// Lattices can either be bounded or unbounded, however in the specific case of data-flow analysis, +/// virtually all lattices are going to be bounded, in order to represent two important states: +/// +/// * Undefined (i.e. not yet known, _bottom_). This state represents the initial state +/// of the lattice, as well as its minimum value. +/// * Overdefined (i.e. cannot be known, _top_). This state represents the "failure" state of +/// the lattice, as well as its maximum value. This is almost always used to signal that an +/// analysis has reached conflicting conclusions, or has discovered information that makes it +/// impossible to draw a conclusion at all. +/// +/// These "bound" the values of the lattice, and all valid values of the lattice are partially (or +/// totally) ordered with respect to these bounds. For example, integer range analysis will start +/// in the _undefined_ state, as it does not yet know anything about integer values in the program. +/// It will then start to visit integer values in the program, and will either identify a valid +/// range for the value, refine the range for a value (i.e. new information makes the range +/// narrower or wider), or determine that the range cannot be known (i.e. the _overdefined_ state, +/// but also conveniently, the "maximum" range of an finite integral value is equivalent to saying +/// that the range covers all valid values of that type). +/// +/// It is permitted to implement this trait for semi-lattices (i.e. values for which only `join` +/// or `meet` are well-defined), however the implementation of whichever method is _not_ well- +/// defined must assert/panic, to ensure the value is not improperly used in an analysis that relies +/// on the properties of a join (or meet) semi-lattice for correctness. +pub trait LatticeLike: Default + Clone + Eq + fmt::Debug + 'static { + /// Joins `self` with `other`, producing a new value that represents the least upper bound of + /// the two values in the join semi-lattice of the type. + /// + /// Formally, the join of two values is represented by the binary operation $\lor$, i.e. logical + /// disjunction, or more commonly, logical-OR. + /// + /// The following are some examples of what joins of non-boolean values look like in practice: + /// + /// * The least upper bound of a non-empty set of integers $I$, is the maximum value in $I$. + /// * The disjunction of two partially-ordered sets is the union of those sets + /// * The disjunction of two integral ranges is a range that includes the elements of both + fn join(&self, other: &Self) -> Self; + + /// Meets `self` with `other`, producing a new value that represents the greatest lower bound of + /// the two values in the meet semi-lattice of the type. + /// + /// Formally, the meet of two values is represented by the binary operation $\and$, i.e. logical + /// conjunction, or more commonly, logical-AND. + /// + /// The following are some examples of what meets of non-boolean values look like in practice: + /// + /// * The greatest lower bound of a non-empty set of integers $I$, is the minimum value in $I$. + /// * The conjunction of two partially-ordered sets is the intersection of those sets + /// * The conjunction of two non-overlapping integral ranges is an empty range + /// (i.e. _overdefined_). + /// * The conjunction of two overlapping integral ranges is the overlapping range. + fn meet(&self, other: &Self) -> Self; +} + +/// This type adapts a [LatticeLike] value for use as an [AnalysisState] by a [DataFlowAnalysis]. +pub struct Lattice { + anchor: LatticeAnchorRef, + value: T, +} + +impl Lattice { + /// Construct a new [Lattice] from the given anchor and [LatticeLike] value. + pub fn new(anchor: LatticeAnchorRef, value: T) -> Self { + Self { anchor, value } + } + + /// Get a reference to the underlying lattice value + pub fn value(&self) -> &T { + &self.value + } + + /// Get a mutable reference to the underlying lattice value + pub fn value_mut(&mut self) -> &mut T { + &mut self.value + } +} + +impl core::fmt::Debug for Lattice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.value, f) + } +} + +impl core::fmt::Display for Lattice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(&self.value, f) + } +} + +/// Any type that has a default value is buildable +/// +/// NOTE: The default value is expected to correspond to the _minimum_ state of the lattice, +/// typically a value that represents "unknown" or most conservative state. Otherwise, the +/// implementation of any analyses that depend on the type are likely to reach the overdefined +/// state when they otherwise would not (due to a non-minimal value "conflicting" with the value +/// concluded by the analysis). +impl BuildableAnalysisState for Lattice { + default fn create(anchor: LatticeAnchorRef) -> Self { + Self { + anchor, + value: Default::default(), + } + } +} + +impl AnalysisState for Lattice { + #[inline(always)] + fn as_any(&self) -> &dyn core::any::Any { + self as &dyn core::any::Any + } + + #[inline(always)] + fn anchor(&self) -> &dyn LatticeAnchor { + self.anchor.as_ref() + } +} + +impl SparseLattice for Lattice { + type Lattice = T; + + #[inline] + fn lattice(&self) -> &Self::Lattice { + &self.value + } + + fn join(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let new_value = ::join(&self.value, rhs); + debug_assert_eq!( + ::join(&new_value, &self.value), + new_value, + "expected `join` to be monotonic" + ); + debug_assert_eq!( + ::join(&new_value, rhs), + new_value, + "expected `join` to be monotonic" + ); + + // Update the current optimistic value if something changed + if new_value == self.value { + ChangeResult::Unchanged + } else { + self.value = new_value; + ChangeResult::Changed + } + } + + fn meet(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let new_value = ::meet(&self.value, rhs); + debug_assert_eq!( + ::meet(&new_value, &self.value), + new_value, + "expected `meet` to be monotonic" + ); + debug_assert_eq!( + ::meet(&new_value, rhs), + new_value, + "expected `meet` to be monotonic" + ); + + // Update the current optimistic value if something changed + if new_value == self.value { + ChangeResult::Unchanged + } else { + self.value = new_value; + ChangeResult::Changed + } + } +} + +impl DenseLattice for Lattice { + type Lattice = T; + + #[inline] + fn lattice(&self) -> &Self::Lattice { + &self.value + } + + #[inline] + fn lattice_mut(&mut self) -> &mut Self::Lattice { + &mut self.value + } + + fn join(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let new_value = ::join(&self.value, rhs); + debug_assert_eq!( + ::join(&new_value, &self.value), + new_value, + "expected `join` to be monotonic" + ); + debug_assert_eq!( + ::join(&new_value, rhs), + new_value, + "expected `join` to be monotonic" + ); + + // Update the current optimistic value if something changed + if new_value == self.value { + ChangeResult::Unchanged + } else { + self.value = new_value; + ChangeResult::Changed + } + } + + fn meet(&mut self, rhs: &Self::Lattice) -> ChangeResult { + let new_value = ::meet(&self.value, rhs); + debug_assert_eq!( + ::meet(&new_value, &self.value), + new_value, + "expected `meet` to be monotonic" + ); + debug_assert_eq!( + ::meet(&new_value, rhs), + new_value, + "expected `meet` to be monotonic" + ); + + // Update the current optimistic value if something changed + if new_value == self.value { + ChangeResult::Unchanged + } else { + self.value = new_value; + ChangeResult::Changed + } + } +} diff --git a/hir-analysis/src/lib.rs b/hir-analysis/src/lib.rs index 98c2af98a..269b32018 100644 --- a/hir-analysis/src/lib.rs +++ b/hir-analysis/src/lib.rs @@ -1,25 +1,41 @@ +#![no_std] +#![feature(allocator_api)] +#![feature(coerce_unsized)] +#![feature(debug_closure_helpers)] +#![feature(ptr_metadata)] +#![feature(specialization)] +#![feature(unsize)] +// Specialization +#![allow(incomplete_features)] +#![deny(warnings)] + extern crate alloc; +#[cfg(test)] +extern crate std; -mod control_flow; -mod data; -mod def_use; -pub mod dependency_graph; -mod dominance; -mod liveness; -mod loops; -pub mod spill; -mod treegraph; -mod validation; +pub mod analyses; +mod analysis; +mod anchor; +mod change_result; +mod config; +pub mod dense; +mod lattice; +mod solver; +pub mod sparse; +use self::anchor::LatticeAnchorExt; pub use self::{ - control_flow::{BlockPredecessor, ControlFlowGraph}, - data::{GlobalVariableAnalysis, GlobalVariableLayout}, - def_use::{DefUseGraph, Use, User, UserList, Users}, - dependency_graph::DependencyGraph, - dominance::{DominanceFrontier, DominatorTree, DominatorTreePreorder}, - liveness::LivenessAnalysis, - loops::{Loop, LoopAnalysis, LoopLevel}, - spill::{Reload, ReloadInfo, Spill, SpillAnalysis, SpillInfo}, - treegraph::{OrderedTreeGraph, TreeGraph}, - validation::{ModuleValidationAnalysis, Rule}, + analysis::{ + AnalysisKind, AnalysisState, AnalysisStateGuard, AnalysisStateGuardMut, AnalysisStateInfo, + AnalysisStateSubscription, AnalysisStateSubscriptionBehavior, AnalysisStrategy, + BuildableAnalysisState, BuildableDataFlowAnalysis, CallControlFlowAction, DataFlowAnalysis, + Dense, Revision, Sparse, + }, + anchor::{LatticeAnchor, LatticeAnchorRef}, + change_result::ChangeResult, + config::DataFlowConfig, + dense::{DenseBackwardDataFlowAnalysis, DenseForwardDataFlowAnalysis, DenseLattice}, + lattice::{Lattice, LatticeLike}, + solver::{AnalysisQueue, DataFlowSolver}, + sparse::{SparseBackwardDataFlowAnalysis, SparseForwardDataFlowAnalysis, SparseLattice}, }; diff --git a/hir-analysis/src/liveness.rs b/hir-analysis/src/liveness.rs deleted file mode 100644 index fcd69ed17..000000000 --- a/hir-analysis/src/liveness.rs +++ /dev/null @@ -1,1716 +0,0 @@ -use std::{ - cmp, - collections::{BTreeMap, VecDeque}, -}; - -use midenc_hir::{ - self as hir, - pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}, - Block as BlockId, Inst as InstId, Value as ValueId, *, -}; -use midenc_session::Session; -use rustc_hash::FxHashMap; - -use super::{ControlFlowGraph, DominatorTree, LoopAnalysis}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -struct BlockInfo { - max_register_pressure: u16, - max_operand_stack_pressure: u16, -} - -/// This data structure is the result of computing liveness information over the -/// control flow graph of an HIR function. It uses a somewhat novel approach to -/// liveness that is ideally suited to optimal register allocation over programs -/// in SSA form, as described in _Register Spilling and Live-Range Splitting for -/// SSA-Form Programs_, by Matthias Braun and Sebastian Hack. -/// -/// In short, rather than simply tracking what values are live at a particular -/// program point, we instead track the global next-use distance for all values -/// which are live at a given program point. Just like the more typical liveness -/// analysis, the next-use analysis computes next-use distances for a block by -/// taking the next-use distances at the exit of the block, and working backwards -/// to compute the next-use distances at the entry of the block. When computing -/// the next-use distances at the exit of a block with multiple successors, the -/// join function takes the minimum next-use distances of all variables live across -/// some edge. -/// -/// By default, variables introduced by an instruction (i.e. their definition) are -/// assigned the next-use distance at the next instruction (or the block exit). If -/// no use of the variable has been observed, it is assigned a next-use distance of -/// infinity (here represented as `u32::MAX`). Each time we step backwards up through -/// the block, we increment the distance of all values observed so far by 1. If we -/// encounter a use of a variable at the current instruction, its next-use distance -/// is set to 0. -/// -/// When we calculate the next-use distances for the exit of a block, the set is -/// initialized by taking the join of the next-use distances at the entry of all -/// successors of that block, or the empty set if there are no successors. However, -/// if the current block is inside a loop, and any successor is outside that loop, -/// then all next-use distances obtained from that successor are incremented by a -/// large constant (1000 in our case). -/// -/// The algorithm follows the standard dataflow analysis approach of working until -/// a fixpoint is reached. We start by visiting the control flow graph in reverse -/// postorder, and revisit any blocks whose results change since the last time we -/// saw that block. -/// -/// The resulting data structure is ideal for register allocation, but the real benefit -/// is that it allows us to be smart about how we spill values, since it can tell us -/// how "hot" a value is, allowing us to prioritize such values over those which may -/// not be used for awhile. -#[derive(Debug, Default)] -pub struct LivenessAnalysis { - // Liveness/global next-uses at a given program point - live_in: FxHashMap, - // Liveness/global next-uses after a given program point - live_out: FxHashMap, - // Maximum pressures for each block - per_block_info: FxHashMap, -} -impl Analysis for LivenessAnalysis { - type Entity = Function; - - fn analyze( - function: &Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> AnalysisResult { - let cfg = analyses.get_or_compute(function, session)?; - let domtree = analyses.get_or_compute(function, session)?; - let loops = analyses.get_or_compute(function, session)?; - Ok(LivenessAnalysis::compute(function, &cfg, &domtree, &loops)) - } - - fn is_invalidated(&self, preserved: &PreservedAnalyses) -> bool { - !preserved.is_preserved::() - || !preserved.is_preserved::() - || !preserved.is_preserved::() - } -} -impl LivenessAnalysis { - /// Computes liveness for the given function, using the provided analyses - pub fn compute( - function: &Function, - cfg: &ControlFlowGraph, - domtree: &DominatorTree, - loops: &LoopAnalysis, - ) -> Self { - let mut liveness = Self::default(); - liveness.recompute(function, cfg, domtree, loops); - liveness - } - - /// Recomputes liveness for the given function, using the provided analyses - pub fn recompute( - &mut self, - function: &Function, - cfg: &ControlFlowGraph, - domtree: &DominatorTree, - loops: &LoopAnalysis, - ) { - self.clear(); - compute_liveness(self, function, cfg, domtree, loops) - } - - /// Clear all computed liveness information, without releasing the memory we allocated - pub fn clear(&mut self) { - self.live_in.clear(); - self.live_out.clear(); - self.per_block_info.clear(); - } - - /// Returns true if `value` is live at the given program point. - /// - /// To be live "at" means that the value is live entering that point of - /// the program, but does not imply that it is live after that point. - pub fn is_live_at(&self, value: &ValueId, pp: ProgramPoint) -> bool { - self.live_in[&pp].is_live(value) - } - - /// Returns true if `value` is live after the given program point. - /// - /// To be live "after" means that the value is live exiting that point of - /// the program, but does not imply that it is live before that point. - pub fn is_live_after(&self, value: &ValueId, pp: ProgramPoint) -> bool { - self.live_out[&pp].is_live(value) - } - - /// Returns the next-use distance of `value` at the given program point. - pub fn next_use(&self, value: &ValueId, pp: ProgramPoint) -> u32 { - self.live_in[&pp].distance(value) - } - - /// Returns the next-use distance of `value` after the given program point. - pub fn next_use_after(&self, value: &ValueId, pp: ProgramPoint) -> u32 { - self.live_out[&pp].distance(value) - } - - /// Returns the global next-use distances at the given program point - pub fn next_uses(&self, pp: ProgramPoint) -> &NextUseSet { - &self.live_in[&pp] - } - - /// Returns the global next-use distances at the given program point - pub fn next_uses_after(&self, pp: ProgramPoint) -> &NextUseSet { - &self.live_out[&pp] - } - - /// Returns an iterator over values which are live at the given program point - #[inline] - pub fn live_at(&self, pp: ProgramPoint) -> impl Iterator + '_ { - self.live_in[&pp].live() - } - - /// Returns an iterator over values which are live after the given program point - #[inline] - pub fn live_after(&self, pp: ProgramPoint) -> impl Iterator + '_ { - self.live_out[&pp].live() - } - - /// Returns the maximum register pressure in the given block - pub fn max_register_pressure(&self, block: &BlockId) -> usize { - self.per_block_info[block].max_register_pressure as usize - } - - /// Returns the maximum estimated operand stack pressure in the given block - pub fn max_operand_stack_pressure(&self, block: &BlockId) -> usize { - self.per_block_info[block].max_operand_stack_pressure as usize - } - - /// Returns the chromatic number of the interference graph implicit in the - /// liveness data represented here, i.e. number of colors required to color - /// the graph such that no two adjacent nodes share the same color, i.e. the - /// minimum number of registers needed to perform register allocation over - /// the function without spills. - /// - /// In a more practical sense, this returns the maximum number of live values - /// at any one point in the analyzed function. - /// - /// # Explanation - /// - /// Because the interference graphs of SSA-form programs are chordal graphs, - /// and chordal graphs are "perfect", this makes certain properties of the interference - /// graph easy to derive. In particular, the chromatic number of a perfect graph is - /// equal to the size of its largest clique (group of nodes which form a complete graph, - /// i.e. have edges to each other). - pub fn chromatic_number(&self) -> usize { - let mut max = 0; - for (_pp, next_used) in self.live_in.iter() { - max = cmp::max(next_used.iter().filter(|(_, d)| *d < &u32::MAX).count(), max); - } - max - } -} - -/// This data structure is used to maintain a mapping from variables to -/// their next-use distance at a specific program point. -/// -/// If a value is not in the set, we have not observed its definition, or -/// any uses at the associated program point. -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct NextUseSet(BTreeMap); -impl FromIterator<(ValueId, u32)> for NextUseSet { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - let mut set = Self::default(); - for (k, v) in iter.into_iter() { - set.insert(k, v); - } - set - } -} -impl NextUseSet { - /// Inserts `value` in this set with the given `distance`. - /// - /// A distance of `u32::MAX` signifies infinite distance, which is - /// equivalent to saying that `value` is not live. - /// - /// If `value` is already in this set, the distance is updated to be the - /// lesser of the two distances, e.g. if the previous distance was `u32::MAX`, - /// and `distance` was `1`, the entry is updated to have a distance of `1` after - /// this function returns. - pub fn insert(&mut self, value: ValueId, distance: u32) { - use std::collections::btree_map::Entry; - match self.0.entry(value) { - Entry::Vacant(entry) => { - entry.insert(distance); - } - Entry::Occupied(mut entry) => { - let prev_distance = entry.get_mut(); - *prev_distance = std::cmp::min(*prev_distance, distance); - } - } - } - - /// Returns `true` if `value` is live in this set - #[inline] - pub fn is_live(&self, value: &ValueId) -> bool { - self.distance(value) < u32::MAX - } - - /// Returns the distance to the next use of `value` as an integer. - /// - /// If `value` is not live, or the distance is unknown, returns `u32::MAX` - pub fn distance(&self, value: &ValueId) -> u32 { - self.0.get(value).copied().unwrap_or(u32::MAX) - } - - /// Returns `true` if `value` is in this set - #[inline] - pub fn contains(&self, value: &ValueId) -> bool { - self.0.contains_key(value) - } - - /// Gets the distance associated with the given `value`, if known - #[inline] - pub fn get(&self, value: &ValueId) -> Option<&u32> { - self.0.get(value) - } - - /// Gets a mutable reference to the distance associated with the given `value`, if known - #[inline] - pub fn get_mut(&mut self, value: &ValueId) -> Option<&mut u32> { - self.0.get_mut(value) - } - - pub fn entry( - &mut self, - value: ValueId, - ) -> std::collections::btree_map::Entry<'_, ValueId, u32> { - self.0.entry(value) - } - - /// Removes the entry for `value` from this set - pub fn remove(&mut self, value: &ValueId) -> Option { - self.0.remove(value) - } - - /// Returns a new set containing the union of `self` and `other`. - /// - /// The resulting set will preserve the minimum distances from both sets. - pub fn union(&self, other: &Self) -> Self { - let mut result = self.clone(); - for (k, v) in other.iter() { - result.insert(*k, *v); - } - result - } - - /// Returns a new set containing the intersection of `self` and `other`. - /// - /// The resulting set will preserve the minimum distances from both sets. - pub fn intersection(&self, other: &Self) -> Self { - let mut result = Self::default(); - for (k, v1) in self.iter() { - match other.get(k) { - None => continue, - Some(v2) => { - result.0.insert(*k, core::cmp::min(*v1, *v2)); - } - } - } - result - } - - /// Returns a new set containing the symmetric difference of `self` and `other`, - /// i.e. the values that are in `self` or `other` but not in both. - pub fn symmetric_difference(&self, other: &Self) -> Self { - let mut result = Self::default(); - for (k, v) in self.iter() { - if !other.0.contains_key(k) { - result.0.insert(*k, *v); - } - } - for (k, v) in other.iter() { - if !self.0.contains_key(k) { - result.0.insert(*k, *v); - } - } - result - } - - pub fn iter(&self) -> impl Iterator { - self.0.iter() - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.0.iter_mut() - } - - pub fn keys(&self) -> impl Iterator + '_ { - self.0.keys().copied() - } - - /// Returns an iterator over the values in this set with a finite next-use distance - pub fn live(&self) -> impl Iterator + '_ { - self.0 - .iter() - .filter_map(|(v, dist)| if *dist < u32::MAX { Some(*v) } else { None }) - } - - /// Remove the value in this set which is closest compared to the others - /// - /// If this set is empty, returns `None`. - /// - /// If more than one value have the same distance, this returns the value with - /// the lowest id first. - #[inline] - pub fn pop_first(&mut self) -> Option<(ValueId, u32)> { - self.0.pop_first() - } - - /// Remove the value in this set which is furthest away compared to the others - /// - /// If this set is empty, returns `None`. - /// - /// If more than one value have the same distance, this returns the value with - /// the highest id first. - #[inline] - pub fn pop_last(&mut self) -> Option<(ValueId, u32)> { - self.0.pop_last() - } -} -impl<'a, 'b> std::ops::BitOr<&'b NextUseSet> for &'a NextUseSet { - type Output = NextUseSet; - - #[inline] - fn bitor(self, rhs: &'b NextUseSet) -> Self::Output { - self.union(rhs) - } -} -impl<'a, 'b> std::ops::BitAnd<&'b NextUseSet> for &'a NextUseSet { - type Output = NextUseSet; - - #[inline] - fn bitand(self, rhs: &'b NextUseSet) -> Self::Output { - self.intersection(rhs) - } -} -impl<'a, 'b> std::ops::BitXor<&'b NextUseSet> for &'a NextUseSet { - type Output = NextUseSet; - - #[inline] - fn bitxor(self, rhs: &'b NextUseSet) -> Self::Output { - self.symmetric_difference(rhs) - } -} - -// The distance penalty applied to an edge which exits a loop -pub const LOOP_EXIT_DISTANCE: u32 = 100_000; - -/// This function computes global next-use distances/liveness until a fixpoint is reached. -/// -/// The resulting data structure associates two [NextUseSet]s with every block and instruction in -/// `function`. One set provides next-use distances _at_ a given program point, the other _after_ a -/// given program point. Intuitively, these are basically what they sound like. If a value is used -/// "at" a given instruction, it's next-use distance will be 0, and if it is never used after that, -/// it won't be present in the "after" set. However, if it used again later, it's distance in the -/// "after" set will be the distance from the instruction following the current one (or the block -/// exit if the current one is a terminator). -fn compute_liveness( - liveness: &mut LivenessAnalysis, - function: &Function, - cfg: &ControlFlowGraph, - domtree: &DominatorTree, - loops: &LoopAnalysis, -) { - use std::collections::hash_map::Entry; - - let mut worklist = VecDeque::from_iter(domtree.cfg_postorder().iter().copied()); - - // Track the analysis sensitivity of each block - // - // The liveness analysis, being a dataflow analysis, is run to a fixpoint. However, the - // analysis results for linear control flow will never change, so recomputing liveness - // multiple times for those blocks is unnecessary. For large functions, this could add up to - // a non-trivial amount of work. - // - // Instead, we track whether or not the analysis of each block is flow-sensitive, and propagate - // that information to predecessors of that block - if a block has a successor which is - // flow-sensitive, then it is also by definition flow-sensitive, since the inputs to its - // liveness analysis is subject to change. We then only re-analyze flow-sensitive blocks. - let mut flow_sensitive = BTreeMap::::default(); - for block in worklist.iter().copied() { - let terminator = function.dfg.last_inst(block).unwrap(); - match function.dfg.analyze_branch(terminator) { - // This block terminates with either a return from the enclosing function, or - // with a trap of some kind, e.g. unreachable. By definition, liveness analysis - // of this block cannot be flow-sensitive, as liveness is computed bottom-up. - BranchInfo::NotABranch => { - flow_sensitive.insert(block, false); - } - BranchInfo::SingleDest(succ) => { - // If the successor is flow-sensitive, by definition so must the predecessor. - // - // If the successor's sensitivity is not yet known, then that means control can - // reach `succ` before it reaches `block`, which implies that both must be flow- - // sensitive, as the results of liveness analysis in `block` are dependent on - // `succ`, and vice versa. - // - // Blocks which are part of loops are presumed to be flow-sensitive, with the only - // exception being exit nodes of the loop whose successors are known to be flow- - // insensitive. This flow-insensitivity can propagate to successors of those blocks - // so long as they are exclusively predecessors of flow-insensitive blocks. - // - // Putting this all together - it must be the case that we either know that `succ` - // is flow-insensitive, or we know that it is flow-sensitive, either explicitly or - // by implication. - flow_sensitive - .insert(block, flow_sensitive.get(&succ.destination).copied().unwrap_or(true)); - } - BranchInfo::MultiDest(succs) => { - // Must like the single-successor case, we derive flow-sensitivity for predecessors - // from their successors. - // - // The primary difference in this situation, is that the only possible way for - // `block` to be flow-insensitive, is if all successors are explicitly flow- - // insensitive. - let is_flow_sensitive = succs - .iter() - .any(|succ| flow_sensitive.get(&succ.destination).copied().unwrap_or(true)); - flow_sensitive.insert(block, is_flow_sensitive); - } - } - } - - // Compute liveness and global next-uses for each block, bottom-up - // - // For each block, liveness is determined by the next-use distance for each variable, where a - // distance of `u32::MAX` is our representation of infinity, and infinity is the distance - // assigned a value which is dead. All values implicitly start with an infinite distance, and - // the distance to next use is calculated by taking the length of control flow edges (0 for - // regular edges, LOOP_EXIT_DISTANCE for edges which leave a loop) and adding that to the - // next-use distances of values which are live over the edge. - // - // The next-use distance for a value within a block, is the distance between that use and it's - // definition in terms of the number of instructions in the block which precede it. As such, - // we visit instructions in a block bottom-up, where at each program point, we record the - // following next-use information: - // - // * Values defined by the current instruction are given the next-use distances observed thus - // far at that instruction, e.g. if no use of a value is observed, that distance is `u32::MAX`. - // If the value is used by the next instruction, the distance is `1`, and so on. For all - // preceding instructions, the value is removed from the next-use set. - // * Values used by the current instruction are given a next-use distance of `0` - // * All other values known at the current program point have their next-use distances - // incremented by 1 - while let Some(block_id) = worklist.pop_front() { - let block = &function.dfg.blocks[block_id]; - let block_loop = loops.innermost_loop(block_id); - let mut max_register_pressure = 0; - let mut max_operand_stack_pressure = 0; - let mut inst_cursor = block.insts.back(); - while let Some(inst_data) = inst_cursor.get() { - let inst = inst_data.key; - let pp = ProgramPoint::Inst(inst); - let branch_info = inst_data.analyze_branch(&function.dfg.value_lists); - let mut operand_stack_pressure = 0; - - // Compute the initial next-use set of this program point - // - // * For terminators which are branches, next-use distances are given by the next-use - // distances live over the control-flow graph edges. Multiple successors are handled by - // joining the next-use sets of all edges. In both cases, block arguments of successors - // are removed from the set, unless the successor dominates the instruction. - // * For all other terminators (i.e. `ret`), the next-use set is empty. - // * For regular instructions, the next-use set is initialized using the next-use set of - // the succeeding instruction, with all distances incremented by 1, but with all values - // defined by the successor removed from the set. - // - let (inst_next_uses, inst_next_uses_after) = match branch_info { - // This is either a non-branching terminator, or a regular instruction - BranchInfo::NotABranch => { - let succ_cursor = inst_cursor.peek_next(); - if let Some(succ) = succ_cursor.get() { - // Start with the next-use set of the next instruction - let mut inst_next_uses = liveness - .live_in - .entry(ProgramPoint::Inst(succ.key)) - .or_default() - .clone(); - - // Increment the next-use distance for all inherited next uses - for (_value, dist) in inst_next_uses.iter_mut() { - *dist = dist.saturating_add(1); - } - - // The next uses up to this point forms the next-use set after `inst` - let mut inst_next_uses_after = inst_next_uses.clone(); - - // Add uses by this instruction to the live-in set, with a distance of 0 - let args = inst_data.arguments(&function.dfg.value_lists); - for arg in args.iter().copied() { - inst_next_uses.insert(arg, 0); - operand_stack_pressure += - function.dfg.value_type(arg).size_in_felts() as u16; - } - - // Remove all instruction results from the live-in set, and ensure they have - // a default distance set in the live-out set - let mut operand_stack_pressure_out = operand_stack_pressure; - let results = function.dfg.inst_results(inst); - for result in results { - inst_next_uses.remove(result); - inst_next_uses_after.entry(*result).or_insert(u32::MAX); - operand_stack_pressure_out += - function.dfg.value_type(*result).size_in_felts() as u16; - } - - // Compute operand stack pressure on entry and exit to instruction, and - // take the maximum of the two as the max operand stack pressure for this - // instruction. - // - // On entry, pressure is applied by the union of the sets of live-in values - // and instruction arguments. - // - // On exit, pressure is given by taking the entry pressure, and subtracting - // the size of values which are not live-across the instruction, i.e. are - // consumed by the instruction; and adding the size of the instruction - // results - for (value, next_use_dist) in inst_next_uses.iter() { - let is_argument = args.contains(value); - let live_in = *next_use_dist != u32::MAX; - // Values which are not live-in are ignored (includes inst results) - if !live_in { - continue; - } - let live_after = inst_next_uses_after.is_live(value); - let value_size = function.dfg.value_type(*value).size_in_felts() as u16; - // Arguments are already accounted for, but we want to deduct the size - // of arguments which are not live-after the instruction from the max - // pressure on exit. - if is_argument { - if !live_after { - operand_stack_pressure_out -= value_size; - } - } else { - // For live-in non-arguments, add them to the max pressure on entry - operand_stack_pressure += value_size; - // For live-in non-arguments which are also live-after, add them to - // the max pressure on exit - if live_after { - operand_stack_pressure_out += value_size; - } - } - } - operand_stack_pressure = - cmp::max(operand_stack_pressure, operand_stack_pressure_out); - - (inst_next_uses, inst_next_uses_after) - } else { - // This must be a non-branching terminator, i.e. the `ret` instruction - // Thus, the next-use set after `inst` is empty initially - assert!(inst_data.opcode().is_terminator()); - - // Add uses by this instruction to the live-in set, with a distance of 0 - let mut inst_next_uses = NextUseSet::default(); - for arg in inst_data.arguments(&function.dfg.value_lists).iter().copied() { - inst_next_uses.insert(arg, 0); - operand_stack_pressure += - function.dfg.value_type(arg).size_in_felts() as u16; - } - - (inst_next_uses, NextUseSet::default()) - } - } - // This is a branch instruction, so get the next-use set at the entry of each - // successor, increment the distances in those sets based on the distance of the - // edge, and then take the join of those sets as the initial next-use set for `inst` - BranchInfo::SingleDest(SuccessorInfo { - destination: succ, - args: succ_args, - }) => { - let mut inst_next_uses = liveness - .live_in - .get(&ProgramPoint::Block(succ)) - .cloned() - .unwrap_or_default(); - - // Increment the next-use distance for all inherited next uses - // - // If this block is in a loop, make sure we add the loop exit distance for - // edges leaving the loop - let use_loop_exit_distance = block_loop - .map(|block_loop| { - loops - .innermost_loop(succ) - .map(|l| block_loop != l && loops.is_child_loop(block_loop, l)) - .unwrap_or(true) - }) - .unwrap_or(false); - let distance = if use_loop_exit_distance { - LOOP_EXIT_DISTANCE - } else { - 1 - }; - for (_value, dist) in inst_next_uses.iter_mut() { - *dist = dist.saturating_add(distance); - } - - // The next uses up to this point forms the initial next-use set after `inst` - let mut inst_next_uses_after = inst_next_uses.clone(); - - // Add uses by this instruction to the live-in set, with a distance of 0 - let args = inst_data.arguments(&function.dfg.value_lists); - for arg in args.iter().copied().chain(succ_args.iter().copied()) { - inst_next_uses.insert(arg, 0); - operand_stack_pressure += - function.dfg.value_type(arg).size_in_felts() as u16; - } - - // Remove the successor block arguments from live-in/live-out, as those values - // cannot be live before they are defined, and even if the destination block - // dominates the current block (via loop), those values must be dead at this - // instruction as we're providing new definitions for them. - for value in function.dfg.block_args(succ) { - // Only remove them from live-in if they are not actually used though, - // since, for example, a loopback branch to a loop header could pass - // a value that was defined via that header's block parameters. - if !succ_args.contains(value) && !args.contains(value) { - inst_next_uses.remove(value); - } - inst_next_uses_after.remove(value); - } - - // Add all live-in values other than arguments to the operand stack pressure - for (value, next_use_dist) in inst_next_uses.iter() { - if args.contains(value) || succ_args.contains(value) { - continue; - } - if *next_use_dist != u32::MAX { - operand_stack_pressure += - function.dfg.value_type(*value).size_in_felts() as u16; - } - } - - (inst_next_uses, inst_next_uses_after) - } - // Same as above - // - // NOTE: We additionally assert here that all critical edges in the control flow - // graph have been split, as we cannot proceed correctly otherwise. It is expected - // that either no critical edges exist, or that they have been split by a prior - // transformation. - BranchInfo::MultiDest(succs) => { - let mut inst_next_uses = NextUseSet::default(); - let mut inst_next_uses_after = NextUseSet::default(); - - // Instruction arguments are shared across all successors - let args = inst_data.arguments(&function.dfg.value_lists); - for arg in args.iter().copied() { - inst_next_uses.insert(arg, 0); - operand_stack_pressure += - function.dfg.value_type(arg).size_in_felts() as u16; - } - - let mut max_branch_operand_stack_pressure = operand_stack_pressure; - for SuccessorInfo { - destination, - args: succ_args, - } in succs.iter() - { - let destination = *destination; - // If the successor block has multiple predecessors, this is a critical - // edge, as by definition this instruction means the - // current block has multiple successors - assert_eq!( - cfg.num_predecessors(destination), - 1, - "expected all critical edges of {} to have been split!", - destination, - ); - let mut jt_next_uses = liveness - .live_in - .get(&ProgramPoint::Block(destination)) - .cloned() - .unwrap_or_default(); - - // Increment the next-use distance for all inherited next uses - // - // If this block is in a loop, make sure we add the loop exit distance for - // edges leaving the loop - let use_loop_exit_distance = block_loop - .map(|block_loop| { - loops - .innermost_loop(destination) - .map(|l| block_loop != l && loops.is_child_loop(block_loop, l)) - .unwrap_or(true) - }) - .unwrap_or(false); - let distance = if use_loop_exit_distance { - LOOP_EXIT_DISTANCE - } else { - 1 - }; - for (_value, dist) in jt_next_uses.iter_mut() { - *dist = dist.saturating_add(distance); - } - - // The next uses up to this point forms the initial next-use set after - // `inst` - let mut jt_next_uses_after = jt_next_uses.clone(); - - // Add uses by this successor's arguments to the live-in set, with a - // distance of 0 - let mut jt_operand_stack_pressure = operand_stack_pressure; - for arg in succ_args.iter().copied() { - jt_next_uses.insert(arg, 0); - jt_operand_stack_pressure += - function.dfg.value_type(arg).size_in_felts() as u16; - } - - // Remove the successor block arguments from live-in/live-out, as those - // values cannot be live before they are defined, and even if the - // destination block dominates the current block (via loop), those values - // must be dead at this instruction as we're providing new definitions for - // them. - for value in function.dfg.block_args(destination) { - // Only remove them from live-in if they are not actually used though, - // since, for example, a loopback branch to a loop header could pass - // a value that was defined via that header's block parameters. - if !succ_args.contains(value) { - jt_next_uses.remove(value); - } - jt_next_uses_after.remove(value); - } - - // Add non-argument live-in values to the max operand stack pressure - for (value, next_use_dist) in jt_next_uses.iter() { - if args.contains(value) || succ_args.contains(value) { - continue; - } - if *next_use_dist != u32::MAX { - jt_operand_stack_pressure += - function.dfg.value_type(*value).size_in_felts() as u16; - } - } - - inst_next_uses = inst_next_uses.union(&jt_next_uses); - inst_next_uses_after = inst_next_uses_after.union(&jt_next_uses_after); - max_branch_operand_stack_pressure = - cmp::max(max_branch_operand_stack_pressure, jt_operand_stack_pressure); - } - - // The max operand stack pressure for this instruction is the greatest pressure - // across all successors - operand_stack_pressure = max_branch_operand_stack_pressure; - - (inst_next_uses, inst_next_uses_after) - } - }; - - // The maximum register pressure for this block is the greatest number of live-in values - // at any point within the block. - max_register_pressure = cmp::max( - max_register_pressure, - u16::try_from(inst_next_uses.iter().filter(|(_, d)| *d < &u32::MAX).count()) - .expect("too many live values"), - ); - - // The maximum operand stack pressure for this block is the greatest pressure at any - // point within the block. - max_operand_stack_pressure = - cmp::max(max_operand_stack_pressure, operand_stack_pressure); - - // Record the next-use distances for this program point - liveness.live_in.insert(pp, inst_next_uses); - liveness.live_out.insert(pp, inst_next_uses_after); - - // Move to the instruction preceding this one - inst_cursor.move_prev(); - } - - // Handle the block header - let pp = ProgramPoint::Block(block_id); - // The block header derives it's next-use distances from the live-in set of it's first - // instruction - let first_inst = block.insts.front().get().unwrap().key; - let mut block_next_uses = liveness.live_in[&ProgramPoint::Inst(first_inst)].clone(); - // For each block argument, make sure a default next-use distance (u32::MAX) is set - // if a distance is not found in the live-in set of the first instruction - for arg in block.params.as_slice(&function.dfg.value_lists).iter().copied() { - block_next_uses.entry(arg).or_insert(u32::MAX); - } - // For blocks, the "after" set corresponds to the next-use set "after" the block - // terminator. This makes it easy to query liveness at entry and exit to a block. - let last_inst = block.insts.back().get().unwrap().key; - let block_next_uses_after = liveness.live_out[&ProgramPoint::Inst(last_inst)].clone(); - // Run the analysis to a fixpoint - match liveness.live_in.entry(pp) { - Entry::Vacant(entry) => { - entry.insert(block_next_uses); - liveness.live_out.insert(pp, block_next_uses_after); - // Always revisit flow-sensitive blocks at least once - if flow_sensitive[&block_id] { - worklist.push_back(block_id); - } - } - Entry::Occupied(mut entry) => { - let prev = entry.get(); - let has_changed = prev != &block_next_uses; - entry.insert(block_next_uses); - liveness.live_out.insert(pp, block_next_uses_after); - // If this block has changed, make sure we revisit predecessors of this block, as - // their liveness inputs have changed as a result. - if !has_changed { - continue; - } - for pred in cfg.pred_iter(block_id) { - worklist.push_back(pred.block); - } - } - } - liveness.per_block_info.insert( - block_id, - BlockInfo { - max_register_pressure, - max_operand_stack_pressure, - }, - ); - } -} - -impl hir::Decorator for &LivenessAnalysis { - type Display<'a> = DisplayLiveness<'a> where Self: 'a; - - fn decorate_block<'a, 'l: 'a>(&'l self, block: BlockId) -> Self::Display<'a> { - DisplayLiveness { - pp: ProgramPoint::Block(block), - lr: self, - } - } - - fn decorate_inst<'a, 'l: 'a>(&'l self, inst: InstId) -> Self::Display<'a> { - DisplayLiveness { - pp: ProgramPoint::Inst(inst), - lr: self, - } - } -} - -struct NextUse<'a> { - value: &'a ValueId, - distance: &'a u32, -} -impl<'a> core::fmt::Display for NextUse<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - write!(f, "{}:{}", self.value, self.distance) - } -} - -#[doc(hidden)] -pub struct DisplayLiveness<'a> { - pp: ProgramPoint, - lr: &'a LivenessAnalysis, -} -impl<'a> core::fmt::Display for DisplayLiveness<'a> { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - let live = self - .lr - .next_uses(self.pp) - .iter() - .map(|(value, distance)| NextUse { value, distance }); - write!(f, " ; next_used=[{}]", DisplayValues::new(live),) - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::testing::TestContext; - use pretty_assertions::assert_eq; - - use super::*; - use crate::Loop; - - /// In this test, we're limiting the liveness analysis to a single basic block, and checking - /// for the following properties: - /// - /// * Values are only live up to and including their last use - /// * Next-use distances are 0 for instruction arguments on entry to the instruction - /// * Next-use distances are u32::MAX for unused instruction results on exit from the - /// instruction - /// * Next-use distances are accurate for used instruction results on exit from the instruction - /// * Instruction results are not present in the live-in set of the defining instruction - /// - /// The following HIR is constructed for this test: - /// - /// * `in=[v0:0,..]` indicates the set of live-in values and their next-use distance - /// * `out=[v0:0,..]` indicates the set of live-out values and their next-use distance - /// - /// ```text,ignore - /// (func (export #liveness) (param (ptr u8)) (result u32) - /// (block 0 (param v0 (ptr u8)) - /// (let (v1 u32) (ptrtoint v0)) - /// (let (v2 u32) (add v1 32)) - /// (let (v3 (ptr u128)) (inttoptr v2)) - /// (let (v4 u128) (load v3)) - /// (let (v5 u32) (add v1 64)) - /// (ret v5)) - /// ) - /// ``` - #[test] - fn liveness_intra_block() -> AnalysisResult<()> { - let context = TestContext::default(); - let id = "test::liveness".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - [AbiParam::new(Type::U32)], - ), - ); - - let v4; - let ret; - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let v0 = { - let args = builder.block_params(entry); - args[0] - }; - - // entry - let v1 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - let v2 = builder.ins().add_imm_unchecked(v1, Immediate::U32(32), SourceSpan::UNKNOWN); - let v3 = - builder.ins().inttoptr(v2, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - v4 = builder.ins().load(v3, SourceSpan::UNKNOWN); - let v5 = builder.ins().add_imm_unchecked(v1, Immediate::U32(64), SourceSpan::UNKNOWN); - ret = builder.ins().ret(Some(v5), SourceSpan::UNKNOWN); - } - - let mut analyses = AnalysisManager::default(); - let liveness = analyses.get_or_compute::(&function, &context.session)?; - - let block0 = Block::from_u32(0); - - // v0 should be live-in at the function entry, and since it is used by the first instruction - // in the block, it should have a next-use distance of 0 - let v0 = Value::from_u32(0); - assert!(liveness.is_live_at(&v0, ProgramPoint::Block(block0))); - assert_eq!(liveness.next_use(&v0, ProgramPoint::Block(block0)), 0); - let inst0 = Inst::from_u32(0); - assert!(liveness.is_live_at(&v0, ProgramPoint::Inst(inst0))); - assert_eq!(liveness.next_use(&v0, ProgramPoint::Inst(inst0)), 0); - - // The live range of v0 should end immediately after inst0 - assert!(!liveness.is_live_after(&v0, ProgramPoint::Inst(inst0))); - assert_eq!(liveness.next_use_after(&v0, ProgramPoint::Inst(inst0)), u32::MAX); - - // v1 is the result of inst0, but should not be live-in at inst0, only live-after, - // where its next-use distance, being the next instruction in the block, should be 1 - let v1 = Value::from_u32(1); - assert!(!liveness.is_live_at(&v1, ProgramPoint::Inst(inst0))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Inst(inst0)), u32::MAX); - assert!(liveness.is_live_after(&v1, ProgramPoint::Inst(inst0))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Inst(inst0)), 1); - - // v1 is also used later in the block, so ensure that the next-use distance after - // inst1 reflects that usage - let inst1 = Inst::from_u32(1); - assert!(liveness.is_live_at(&v1, ProgramPoint::Inst(inst1))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Inst(inst1)), 0); - assert!(liveness.is_live_after(&v1, ProgramPoint::Inst(inst1))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Inst(inst1)), 3); - - // v4 is never used after inst3, its defining instruction, ensure that the - // liveness analysis reflects that - let inst3 = Inst::from_u32(3); - assert!(!liveness.is_live_after(&v4, ProgramPoint::Inst(inst3))); - assert_eq!(liveness.next_use_after(&v4, ProgramPoint::Inst(inst3)), u32::MAX); - // It should obviously not appear live-in at inst4 - let inst4 = Inst::from_u32(4); - assert!(!liveness.is_live_at(&v4, ProgramPoint::Inst(inst4))); - assert_eq!(liveness.next_use(&v4, ProgramPoint::Inst(inst4)), u32::MAX); - - // Because this block terminates with a return from the function, the only value - // live-in at the return should be the returned value, and the block should have - // an empty live-after set - let v5 = Value::from_u32(5); - assert!(liveness.is_live_at(&v5, ProgramPoint::Inst(ret))); - assert_eq!(liveness.next_use(&v5, ProgramPoint::Inst(ret)), 0); - assert!(!liveness.is_live_after(&v5, ProgramPoint::Inst(ret))); - assert_eq!(liveness.next_use_after(&v5, ProgramPoint::Inst(ret)), u32::MAX); - assert!(!liveness.is_live_after(&v5, ProgramPoint::Block(block0))); - assert_eq!(liveness.next_use_after(&v5, ProgramPoint::Block(block0)), u32::MAX); - - Ok(()) - } - - /// In this test, we're extending the liveness analysis beyond a single basic block, to a set - /// of four blocks that represent a very common form of control flow: conditional branches, - /// specifically a typical if/else scenario. Here, we are concerned with the following - /// properties of liveness analysis: - /// - /// * Propagating liveness up the dominator tree works correctly - /// * Liveness of block arguments is not propagated into predecessor blocks - /// * When unioning liveness across successors of a conditional branch, the minimum next-use - /// distance always wins, i.e. if a value is live on one path, it is always live in the - /// predecessor - /// - /// The following HIR is constructed for this test: - /// - /// * `in=[v0:0,..]` indicates the set of live-in values and their next-use distance - /// * `out=[v0:0,..]` indicates the set of live-out values and their next-use distance - /// - /// ```text,ignore - /// (func (export #liveness) (param (ptr u8)) (result u32) - /// (block 0 (param v0 (ptr u8)) - /// (let (v1 u32) (ptrtoint v0)) - /// (let (v2 u32) (add v1 32)) - /// (let (v3 (ptr u128)) (inttoptr v2)) - /// (let (v4 u128) (load v3)) - /// (let (v5 u32) (add v1 64)) ;; v5 unused in this block, but used later; v1 used again later - /// (let (v6 i1) (eq v5 128)) - /// (cond_br v6 (block 1) (block 2))) - /// - /// (block 1 ; in this block, v4 is used first, v5 second - /// (let (v7 u128) (const.u128 1)) - /// (let (v8 u128) (add v4 v7)) ;; unused - /// (let (v9 u32) (add v5 8)) - /// (br (block 3 v9))) - /// - /// (block 2 ; in this block, v5 is used first, v4 second - /// (let (v10 u128) (const.u128 2)) - /// (let (v11 u32) (add v5 16)) - /// (let (v12 u128) (add v4 v10)) ;; unused - /// (br (block 3 v11))) - /// - /// (block 3 (param v13 u32) - /// (let (v14 u32) (add v1 v13)) - /// (ret v14)) - /// ) - /// ``` - #[test] - fn liveness_conditional_control_flow() -> AnalysisResult<()> { - let context = TestContext::default(); - let id = "test::liveness".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - [AbiParam::new(Type::U32)], - ), - ); - - let v4; - let v5; - let br0; - let br1; - let br2; - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let v0 = { - let args = builder.block_params(entry); - args[0] - }; - - let block1 = builder.create_block(); - let block2 = builder.create_block(); - let block3 = builder.create_block(); - - // entry - let v1 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - let v2 = builder.ins().add_imm_unchecked(v1, Immediate::U32(32), SourceSpan::UNKNOWN); - let v3 = - builder.ins().inttoptr(v2, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - v4 = builder.ins().load(v3, SourceSpan::UNKNOWN); - v5 = builder.ins().add_imm_unchecked(v1, Immediate::U32(64), SourceSpan::UNKNOWN); - let v6 = builder.ins().eq_imm(v5, Immediate::U32(128), SourceSpan::UNKNOWN); - br0 = builder.ins().cond_br(v6, block1, &[], block2, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block1); - let v7 = builder.ins().u128(1, SourceSpan::UNKNOWN); - let _v8 = builder.ins().add_unchecked(v4, v7, SourceSpan::UNKNOWN); - let v9 = builder.ins().add_imm_unchecked(v5, Immediate::U32(8), SourceSpan::UNKNOWN); - br1 = builder.ins().br(block3, &[v9], SourceSpan::UNKNOWN); - - builder.switch_to_block(block2); - let v10 = builder.ins().u128(2, SourceSpan::UNKNOWN); - let v11 = builder.ins().add_imm_unchecked(v5, Immediate::U32(16), SourceSpan::UNKNOWN); - let _v12 = builder.ins().add_unchecked(v4, v10, SourceSpan::UNKNOWN); - br2 = builder.ins().br(block3, &[v11], SourceSpan::UNKNOWN); - - let v13 = builder.append_block_param(block3, Type::U32, SourceSpan::UNKNOWN); - builder.switch_to_block(block3); - let v14 = builder.ins().add_unchecked(v1, v13, SourceSpan::UNKNOWN); - builder.ins().ret(Some(v14), SourceSpan::UNKNOWN); - } - - let mut analyses = AnalysisManager::default(); - let liveness = analyses.get_or_compute::(&function, &context.session)?; - - let block0 = Block::from_u32(0); - let block1 = Block::from_u32(1); - let block2 = Block::from_u32(2); - let block3 = Block::from_u32(3); - - // We expect v13 to be live-in at block3, with a next-use distance of 0 - let v13 = Value::from_u32(13); - assert!(liveness.is_live_at(&v13, ProgramPoint::Block(block3))); - assert_eq!(liveness.next_use(&v13, ProgramPoint::Block(block3)), 0); - - // However, it should _not_ be live-after block1 or block2, nor their terminators - assert!(!liveness.is_live_after(&v13, ProgramPoint::Block(block1))); - assert!(!liveness.is_live_after(&v13, ProgramPoint::Inst(br1))); - assert!(!liveness.is_live_after(&v13, ProgramPoint::Block(block2))); - assert!(!liveness.is_live_after(&v13, ProgramPoint::Inst(br2))); - // Also, definitely shouldn't be live-in at the terminators either - assert!(!liveness.is_live_at(&v13, ProgramPoint::Inst(br1))); - assert!(!liveness.is_live_at(&v13, ProgramPoint::Inst(br2))); - - // v1 should be live-in at block3 with a next-use distance of zero - let v1 = Value::from_u32(1); - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block3))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block3)), 0); - - // Both block1 and block2 should see v1 in their live-after set with a next-use distance of - // 1 - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block1))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Block(block1)), 1); - assert!(liveness.is_live_after(&v1, ProgramPoint::Inst(br1))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Inst(br1)), 1); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block2))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Block(block2)), 1); - assert!(liveness.is_live_after(&v1, ProgramPoint::Inst(br2))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Inst(br2)), 1); - - // Both block1 and block2 should see v1 in their live-in set with a next-use distance of 4 - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block1))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block1)), 4); - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block2))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block2)), 4); - - // The entry block should see v1, v4 and v5 in the live-after set with a next-use distance - // of 5 for v1, 2 for v4 and v5 (the min distance from the union of both block1 and block2) - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block0))); - assert!(liveness.is_live_after(&v4, ProgramPoint::Block(block0))); - assert!(liveness.is_live_after(&v5, ProgramPoint::Block(block0))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_after(&v4, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_after(&v5, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_at(&v1, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_at(&v4, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_at(&v5, ProgramPoint::Inst(br0))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Block(block0)), 5); - assert_eq!(liveness.next_use_after(&v4, ProgramPoint::Block(block0)), 2); - assert_eq!(liveness.next_use_after(&v5, ProgramPoint::Block(block0)), 2); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Inst(br0)), 5); - assert_eq!(liveness.next_use_after(&v4, ProgramPoint::Inst(br0)), 2); - assert_eq!(liveness.next_use_after(&v5, ProgramPoint::Inst(br0)), 2); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Inst(br0)), 5); - assert_eq!(liveness.next_use(&v4, ProgramPoint::Inst(br0)), 2); - assert_eq!(liveness.next_use(&v5, ProgramPoint::Inst(br0)), 2); - - // Ensure that the next-use distance for v1 and v5 is correct at the defining inst - let v5_inst = function.dfg.value_data(v5).unwrap_inst(); - assert!(liveness.is_live_at(&v1, ProgramPoint::Inst(v5_inst))); - assert!(!liveness.is_live_at(&v5, ProgramPoint::Inst(v5_inst))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Inst(v5_inst))); - assert!(liveness.is_live_after(&v5, ProgramPoint::Inst(v5_inst))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Inst(v5_inst)), 0); - assert_eq!(liveness.next_use(&v5, ProgramPoint::Inst(v5_inst)), u32::MAX); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Inst(v5_inst)), 7); - assert_eq!(liveness.next_use_after(&v5, ProgramPoint::Inst(v5_inst)), 1); - - Ok(()) - } - - /// In this test, we're verifying the behavior of liveness analysis when cycles in the control - /// flow graph are present, i.e. loops. Cycles aren't always representative of loops, but we're - /// primarily concerned with the following properties: - /// - /// * Values which are live across a loop have their liveness correctly propagated - /// * Values live-across a loop should have next-use distances reflecting the loop distance when - /// their next-use is outside the loop - /// * Values in the loop header have the correct liveness information, even when used as - /// arguments on a loopback edge to that same header - /// - /// The following HIR is constructed for this test: - /// - /// * `in=[v0:0,..]` indicates the set of live-in values and their next-use distance - /// * `out=[v0:0,..]` indicates the set of live-out values and their next-use distance - /// - /// ```text,ignore - /// (func (export #liveness) (param (ptr u8)) (result u32) - /// (block 0 (param v0 (ptr u8)) - /// (let (v1 u32) (ptrtoint v0)) - /// (let (v2 u32) (add v1 32)) - /// (let (v3 (ptr u128)) (inttoptr v2)) - /// (let (v4 u128) (load v3)) - /// (let (v5 u32) (add v1 64)) ;; v5 unused in this block, but used later; v1 used again later - /// (let (v6 i1) (eq v5 128)) - /// (cond_br v6 (block 1) (block 5))) - /// - /// (block 1 ; split edge - /// ; the natural way of expressing this loop would be to have block0 branch to the loop - /// ; header, but that results in a critical edge between block0 and the loop header, which - /// ; we are compelled to split. this block splits the edge - /// (br (block 2 v4 v5))) - /// - /// (block 2 (param v7 u128) (param v8 u32); loop header+body - /// (let (v9 u128) (const.u128 1)) - /// (let (v10 u128) (add v7 v9)) ;; unused - /// (let (v11 u32) (add v8 8)) - /// (let (v12 i1) (eq v11 128)) - /// (cond_br v12 (block 3) (block 4))) - /// - /// (block 3 ; split edge - /// ; the conditional branch at the end of block2, if routed directly to itself, introduces - /// ; a critical edge from block2 to itself. We split the edge using this block - /// (br (block 2 v7 v11))) - /// - /// (block 4 ; split edge - /// (br (block 6 v11))) - /// - /// (block 5 ; in this block, v5 is used first, v4 second - /// (let (v13 u128) (const.u128 2)) - /// (let (v14 u32) (add v5 16)) - /// (let (v15 u128) (add v4 v13)) ;; unused - /// (br (block 6 v14))) - /// - /// (block 6 (param v16 u32) - /// (let (v17 u32) (add v1 v16)) - /// (ret v17)) - /// ) - /// ``` - #[test] - fn liveness_loop_simple() -> AnalysisResult<()> { - let context = TestContext::default(); - let id = "test::liveness".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - [AbiParam::new(Type::U32)], - ), - ); - - let v4; - let v5; - let br0; - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let v0 = { - let args = builder.block_params(entry); - args[0] - }; - - let block1 = builder.create_block(); - let block2 = builder.create_block(); - let block3 = builder.create_block(); - let block4 = builder.create_block(); - let block5 = builder.create_block(); - let block6 = builder.create_block(); - - // entry - let v1 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - let v2 = builder.ins().add_imm_unchecked(v1, Immediate::U32(32), SourceSpan::UNKNOWN); - let v3 = - builder.ins().inttoptr(v2, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - v4 = builder.ins().load(v3, SourceSpan::UNKNOWN); - v5 = builder.ins().add_imm_unchecked(v1, Immediate::U32(64), SourceSpan::UNKNOWN); - let v6 = builder.ins().eq_imm(v5, Immediate::U32(128), SourceSpan::UNKNOWN); - br0 = builder.ins().cond_br(v6, block1, &[], block5, &[], SourceSpan::UNKNOWN); - - // block1 - split edge (loop path) - builder.switch_to_block(block1); - builder.ins().br(block2, &[v4, v5], SourceSpan::UNKNOWN); - - // block2 - loop header+body - let v7 = builder.append_block_param(block2, Type::U128, SourceSpan::UNKNOWN); - let v8 = builder.append_block_param(block2, Type::U32, SourceSpan::UNKNOWN); - builder.switch_to_block(block2); - let v9 = builder.ins().u128(1, SourceSpan::UNKNOWN); - let _v10 = builder.ins().add_unchecked(v7, v9, SourceSpan::UNKNOWN); - let v11 = builder.ins().add_imm_unchecked(v8, Immediate::U32(8), SourceSpan::UNKNOWN); - let v12 = builder.ins().eq_imm(v11, Immediate::U32(128), SourceSpan::UNKNOWN); - builder.ins().cond_br(v12, block3, &[], block4, &[v11], SourceSpan::UNKNOWN); - - // block3 - split edge - builder.switch_to_block(block3); - builder.ins().br(block2, &[v7, v11], SourceSpan::UNKNOWN); - - // block4 - split edge - builder.switch_to_block(block4); - builder.ins().br(block6, &[v11], SourceSpan::UNKNOWN); - - // block5 - non-loop path - builder.switch_to_block(block5); - let v13 = builder.ins().u128(2, SourceSpan::UNKNOWN); - let v14 = builder.ins().add_imm_unchecked(v5, Immediate::U32(16), SourceSpan::UNKNOWN); - let _v15 = builder.ins().add_unchecked(v4, v13, SourceSpan::UNKNOWN); - builder.ins().br(block6, &[v14], SourceSpan::UNKNOWN); - - // block6 - join point - let v16 = builder.append_block_param(block6, Type::U32, SourceSpan::UNKNOWN); - builder.switch_to_block(block6); - let v17 = builder.ins().add_unchecked(v1, v16, SourceSpan::UNKNOWN); - builder.ins().ret(Some(v17), SourceSpan::UNKNOWN); - } - - let mut analyses = AnalysisManager::default(); - let liveness = analyses.get_or_compute::(&function, &context.session)?; - - let block0 = Block::from_u32(0); - let block1 = Block::from_u32(1); - let block2 = Block::from_u32(2); - let block3 = Block::from_u32(3); - let block4 = Block::from_u32(4); - let block5 = Block::from_u32(5); - - // * v1 should be live-after block0 with normal distance (min distance) - let v1 = Value::from_u32(1); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block0))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Block(block0)), 5); - // * v1 should be live-in and live-after block1 with loop distance, as it is not used in the - // loop that it is the header of - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block1))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block1))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block1)), 6 + LOOP_EXIT_DISTANCE); - assert_eq!( - liveness.next_use_after(&v1, ProgramPoint::Block(block1)), - 6 + LOOP_EXIT_DISTANCE - ); - // * v1 should be live-in and live-after block2 with loop distance, as it is not used in the - // loop that it is the body of - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block2))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block2))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block2)), 5 + LOOP_EXIT_DISTANCE); - assert_eq!( - liveness.next_use_after(&v1, ProgramPoint::Block(block2)), - 1 + LOOP_EXIT_DISTANCE - ); - // * v1 should be live-in and live-after block3 with loop distance + distance through block2 - // as the next use requires another full iteration of the loop to reach the exit - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block3))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block3))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block3)), 6 + LOOP_EXIT_DISTANCE); - assert_eq!( - liveness.next_use_after(&v1, ProgramPoint::Block(block3)), - 6 + LOOP_EXIT_DISTANCE - ); - // * v1 should be live-in and live-after block4 with normal distance, as we have exited - // the loop along this edge - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block4))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block4))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block4)), 1); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Block(block4)), 1); - // * v1 should be live-in and live-after block5 with normal distance - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block5))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block5))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block5)), 4); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Block(block5)), 1); - - // v4 and v5 should have next-use distances at end of block0 of 1 (min distance - // across all successors of block0), - assert!(liveness.is_live_at(&v4, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_after(&v4, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_at(&v5, ProgramPoint::Inst(br0))); - assert!(liveness.is_live_after(&v5, ProgramPoint::Inst(br0))); - assert_eq!(liveness.next_use(&v4, ProgramPoint::Inst(br0)), 1); - assert_eq!(liveness.next_use_after(&v4, ProgramPoint::Inst(br0)), 1); - assert_eq!(liveness.next_use(&v5, ProgramPoint::Inst(br0)), 1); - assert_eq!(liveness.next_use_after(&v5, ProgramPoint::Inst(br0)), 1); - - // v7 and v8 should not have liveness information at end of block1 - let v7 = Value::from_u32(7); - let v8 = Value::from_u32(8); - let block1_term = function.dfg.last_inst(block1).unwrap(); - assert!(!liveness.is_live_after(&v7, ProgramPoint::Block(block1))); - assert!(!liveness.is_live_after(&v8, ProgramPoint::Block(block1))); - assert!(!liveness.is_live_after(&v7, ProgramPoint::Inst(block1_term))); - assert!(!liveness.is_live_after(&v8, ProgramPoint::Inst(block1_term))); - assert!(!liveness.is_live_at(&v7, ProgramPoint::Inst(block1_term))); - assert!(!liveness.is_live_at(&v8, ProgramPoint::Inst(block1_term))); - assert_eq!(liveness.next_use_after(&v7, ProgramPoint::Block(block1)), u32::MAX); - assert_eq!(liveness.next_use_after(&v8, ProgramPoint::Block(block1)), u32::MAX); - assert_eq!(liveness.next_use(&v7, ProgramPoint::Inst(block1_term)), u32::MAX); - assert_eq!(liveness.next_use(&v8, ProgramPoint::Inst(block1_term)), u32::MAX); - - // v7 should be live at end of block2 with next-use distance of 1 - let block2_term = function.dfg.last_inst(block2).unwrap(); - assert!(liveness.is_live_at(&v7, ProgramPoint::Inst(block2_term))); - assert!(liveness.is_live_after(&v7, ProgramPoint::Inst(block2_term))); - assert!(liveness.is_live_after(&v7, ProgramPoint::Block(block2))); - assert_eq!(liveness.next_use(&v7, ProgramPoint::Inst(block2_term)), 1); - assert_eq!(liveness.next_use_after(&v7, ProgramPoint::Inst(block2_term)), 1); - assert_eq!(liveness.next_use_after(&v7, ProgramPoint::Block(block2)), 1); - - // v8 should _not_ be live at end of block2 - assert!(!liveness.is_live_at(&v8, ProgramPoint::Inst(block2_term))); - assert_eq!(liveness.next_use(&v8, ProgramPoint::Inst(block2_term)), u32::MAX); - assert!(!liveness.is_live_after(&v8, ProgramPoint::Block(block2))); - assert_eq!(liveness.next_use_after(&v8, ProgramPoint::Block(block2)), u32::MAX); - - // v7 should be live-in at end of block3 with next-use distance of 0 - let block3_term = function.dfg.last_inst(block3).unwrap(); - assert!(liveness.is_live_at(&v7, ProgramPoint::Inst(block3_term))); - assert_eq!(liveness.next_use(&v7, ProgramPoint::Inst(block3_term)), 0); - - // v8 should not be live at entry of block3 - assert!(!liveness.is_live_at(&v8, ProgramPoint::Block(block3))); - assert_eq!(liveness.next_use(&v8, ProgramPoint::Block(block3)), u32::MAX); - - // neither v7 nor v8 should be live after end of block3 - assert!(!liveness.is_live_after(&v7, ProgramPoint::Block(block3))); - assert_eq!(liveness.next_use_after(&v7, ProgramPoint::Block(block3)), u32::MAX); - assert!(!liveness.is_live_after(&v8, ProgramPoint::Block(block3))); - assert_eq!(liveness.next_use_after(&v8, ProgramPoint::Block(block3)), u32::MAX); - - // next-use distance of v7 and v8 at entry to block2 should be accurate - assert!(liveness.is_live_at(&v7, ProgramPoint::Block(block2))); - assert_eq!(liveness.next_use(&v7, ProgramPoint::Block(block2)), 1); - assert!(liveness.is_live_at(&v8, ProgramPoint::Block(block2))); - assert_eq!(liveness.next_use(&v8, ProgramPoint::Block(block2)), 2); - - // v9 should be dead after last use in block2 - let v9 = Value::from_u32(9); - let v10_inst = function.dfg.value_data(Value::from_u32(10)).unwrap_inst(); - assert!(liveness.is_live_at(&v9, ProgramPoint::Inst(v10_inst))); - assert_eq!(liveness.next_use(&v9, ProgramPoint::Inst(v10_inst)), 0); - assert!(!liveness.is_live_after(&v9, ProgramPoint::Inst(v10_inst))); - assert_eq!(liveness.next_use_after(&v9, ProgramPoint::Inst(v10_inst)), u32::MAX); - - Ok(()) - } - - /// In this test, we're validating that all the properties of previous tests hold even when we - /// increase the loop depth. Additionally, we expect to see the following: - /// - /// * Values which are live across the outer loop, reflect the cumulative distance of the loop - /// nest, not just the outer loop. - /// * Values which are live across the inner loop, reflect the loop distance - /// * Both inner and outer loop header parameters have their live ranges end when the - /// corresponding loop is continued or exited - /// - /// The following HIR is constructed for this test: - /// - /// * `in=[v0:0,..]` indicates the set of live-in values and their next-use distance - /// * `out=[v0:0,..]` indicates the set of live-out values and their next-use distance - /// - /// ```text,ignore - /// (func (export #liveness) (param (ptr u64)) (param u32) (param u32) (result u64) - /// (block 0 (param v0 (ptr u64)) (param v1 u32) (param v2 u32) - /// (let (v3 u32) (const.u32 0)) - /// (let (v4 u32) (const.u32 0)) - /// (let (v5 u64) (const.u64 0)) - /// (br (block 1 v3 v4 v5))) - /// - /// (block 1 (param v6 u32) (param v7 u32) (param v8 u64)) ; outer loop - /// (let (v9 i1) (eq v6 v1)) - /// (cond_br v9 (block 2) (block 3))) - /// - /// (block 2 ; exit outer loop, return from function - /// (ret v8)) - /// - /// (block 3 ; split edge - /// (br (block 4 v7 v8))) - /// - /// (block 4 (param v10 u32) (param v11 u64) ; inner loop - /// (let (v12 i1) (eq v10 v2)) - /// (cond_br v12 (block 5) (block 6))) - /// - /// (block 5 ; increment row count, continue outer loop - /// (let (v13 u32) (add v6 1)) - /// (br (block 1 v13 v10 v11))) - /// - /// (block 6 ; load value at v0[row][col], add to sum, increment col, continue inner loop - /// (let (v14 u32) (sub.saturating v6 1)) ; row_offset := ROW_SIZE * row.saturating_sub(1) - /// (let (v15 u32) (mul v14 v2)) - /// (let (v16 u32) (add v10 v15)) ; offset := col + row_offset - /// (let (v17 u32) (ptrtoint v0)) ; ptr := (v0 as u32 + offset) as *u64 - /// (let (v18 u32) (add v17 v16)) - /// (let (v19 (ptr u64)) (ptrtoint v18)) - /// (let (v20 u64) (load v19)) ; sum += *ptr - /// (let (v21 u64) (add v11 v20)) - /// (let (v22 u32) (add v10 1)) ; col++ - /// (br (block 4 v22 v21))) - /// ) - /// ``` - #[test] - fn liveness_loop_nest() -> AnalysisResult<()> { - let context = TestContext::default(); - let id = "test::liveness".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U8))), - AbiParam::new(Type::U32), - AbiParam::new(Type::U32), - ], - [AbiParam::new(Type::U32)], - ), - ); - - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let (v0, v1, v2) = { - let args = builder.block_params(entry); - (args[0], args[1], args[2]) - }; - - let block1 = builder.create_block(); - let block2 = builder.create_block(); - let block3 = builder.create_block(); - let block4 = builder.create_block(); - let block5 = builder.create_block(); - let block6 = builder.create_block(); - - // entry - let v3 = builder.ins().u32(0, SourceSpan::UNKNOWN); - let v4 = builder.ins().u32(0, SourceSpan::UNKNOWN); - let v5 = builder.ins().u64(0, SourceSpan::UNKNOWN); - builder.ins().br(block1, &[v3, v4, v5], SourceSpan::UNKNOWN); - - // block1 - outer loop header - let v6 = builder.append_block_param(block1, Type::U32, SourceSpan::UNKNOWN); - let v7 = builder.append_block_param(block1, Type::U32, SourceSpan::UNKNOWN); - let v8 = builder.append_block_param(block1, Type::U64, SourceSpan::UNKNOWN); - builder.switch_to_block(block1); - let v9 = builder.ins().eq(v6, v1, SourceSpan::UNKNOWN); - builder.ins().cond_br(v9, block2, &[], block3, &[], SourceSpan::UNKNOWN); - - // block2 - outer exit - builder.switch_to_block(block2); - builder.ins().ret(Some(v8), SourceSpan::UNKNOWN); - - // block3 - split edge - builder.switch_to_block(block3); - builder.ins().br(block4, &[v7, v8], SourceSpan::UNKNOWN); - - // block4 - inner loop - let v10 = builder.append_block_param(block4, Type::U32, SourceSpan::UNKNOWN); - let v11 = builder.append_block_param(block4, Type::U64, SourceSpan::UNKNOWN); - builder.switch_to_block(block4); - let v12 = builder.ins().eq(v10, v2, SourceSpan::UNKNOWN); - builder.ins().cond_br(v12, block5, &[], block6, &[], SourceSpan::UNKNOWN); - - // block5 - inner latch - builder.switch_to_block(block5); - let v13 = builder.ins().add_imm_unchecked(v6, Immediate::U32(1), SourceSpan::UNKNOWN); - builder.ins().br(block1, &[v13, v10, v11], SourceSpan::UNKNOWN); - - // block6 - inner body - builder.switch_to_block(block6); - let v14 = builder.ins().add_imm_unchecked(v6, Immediate::U32(1), SourceSpan::UNKNOWN); - let v15 = builder.ins().mul_unchecked(v14, v2, SourceSpan::UNKNOWN); - let v16 = builder.ins().add_unchecked(v10, v15, SourceSpan::UNKNOWN); - let v17 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - let v18 = builder.ins().add_unchecked(v17, v16, SourceSpan::UNKNOWN); - let v19 = - builder.ins().inttoptr(v18, Type::Ptr(Box::new(Type::U64)), SourceSpan::UNKNOWN); - let v20 = builder.ins().load(v19, SourceSpan::UNKNOWN); - let v21 = builder.ins().add_unchecked(v11, v20, SourceSpan::UNKNOWN); - let v22 = builder.ins().add_imm_unchecked(v10, Immediate::U32(1), SourceSpan::UNKNOWN); - builder.ins().br(block4, &[v22, v21], SourceSpan::UNKNOWN); - } - - let mut analyses = AnalysisManager::default(); - let liveness = analyses.get_or_compute::(&function, &context.session)?; - let loops = analyses.get_or_compute::(&function, &context.session)?; - - dbg!(&liveness); - - let block0 = Block::from_u32(0); - let block1 = Block::from_u32(1); - let block2 = Block::from_u32(2); - let block3 = Block::from_u32(3); - let block4 = Block::from_u32(4); - let block5 = Block::from_u32(5); - let block6 = Block::from_u32(6); - - // Sanity check structure of the loop nest - let loop0 = Loop::from_u32(0); - let loop1 = Loop::from_u32(1); - assert_eq!(loops.loops().len(), 2); - assert!(loops.is_child_loop(loop1, loop0)); - assert_eq!(loops.innermost_loop(block0), None); - assert_eq!(loops.innermost_loop(block1), Some(loop0)); - assert_eq!(loops.innermost_loop(block2), None); - assert_eq!(loops.innermost_loop(block3), Some(loop0)); - assert_eq!(loops.innermost_loop(block4), Some(loop1)); - assert_eq!(loops.innermost_loop(block5), Some(loop0)); - assert_eq!(loops.innermost_loop(block6), Some(loop1)); - assert!(loops.is_in_loop(block1, loop0)); - assert!(!loops.is_in_loop(block2, loop0)); - assert!(loops.is_in_loop(block3, loop0)); - assert!(!loops.is_in_loop(block3, loop1)); - assert!(loops.is_in_loop(block4, loop0)); - assert!(loops.is_in_loop(block4, loop1)); - assert!(loops.is_in_loop(block5, loop0)); - assert!(!loops.is_in_loop(block5, loop1)); - assert!(loops.is_in_loop(block6, loop0)); - assert!(loops.is_in_loop(block6, loop1)); - assert_eq!(loops.is_loop_header(block1), Some(loop0)); - assert_eq!(loops.is_loop_header(block4), Some(loop1)); - - // v0's first usage occurs inside the inner loop, but that usage is reached without - // crossing any loop exits, so the next-use distance should not include any loop exit - // distance - let v0 = Value::from_u32(0); - assert!(liveness.is_live_after(&v0, ProgramPoint::Block(block0))); - assert_eq!(liveness.next_use_after(&v0, ProgramPoint::Block(block0)), 9); - - // v0's next usage from the perspective of the inner loop header should reflect the - // number of instructions from the header to its use in the inner loop body, _not_ - // the longer distance given by exiting out to the outer loop, then re-entering the - // inner loop - assert!(liveness.is_live_at(&v0, ProgramPoint::Block(block4))); - assert_eq!(liveness.next_use(&v0, ProgramPoint::Block(block4)), 5); - assert!(liveness.is_live_after(&v0, ProgramPoint::Block(block4))); - assert_eq!(liveness.next_use_after(&v0, ProgramPoint::Block(block4)), 4); - - // The same is true at the end of the inner loop body - the next usage _may_ occur - // across an exit (out to the outer loop, then back in to the inner loop); but its - // minimum next-use distance is another iteration of the inner loop, so there should - // be no loop exit distance included - let block6_term = function.dfg.last_inst(block6).unwrap(); - assert!(liveness.is_live_at(&v0, ProgramPoint::Inst(block6_term))); - assert_eq!(liveness.next_use(&v0, ProgramPoint::Inst(block6_term)), 6); - assert!(liveness.is_live_after(&v0, ProgramPoint::Block(block6))); - assert_eq!(liveness.next_use_after(&v0, ProgramPoint::Block(block6)), 6); - - // v1 is similar to v0, except rather than being used in the inner loop, it is used - // in the outer loop, and is unused in (but live-through) the inner loop. Thus we - // would expect v1 to have a normal distance from block0, and a loop-exit distance - // from blocks of the inner loop - we also expect v1 to be considered live in blocks - // of the inner loop, even though it is not used _in_ the inner loop - let v1 = Value::from_u32(1); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block0))); - assert_eq!(liveness.next_use_after(&v1, ProgramPoint::Block(block0)), 1); - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block1))); - assert_eq!(liveness.next_use(&v1, ProgramPoint::Block(block1)), 0); - - // The next nearest use of v1 after the use _in_ block1, requires entering the inner loop - // header and then exiting it almost immediately (i.e. not passing through block6) - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block1))); - assert_eq!( - liveness.next_use_after(&v1, ProgramPoint::Block(block1)), - 5 + LOOP_EXIT_DISTANCE - ); - - // Naturally, v1 must be live at all blocks which are on the path back to block1: - // - // block3 -> block4 -> block5 -> block1 - // -> block6 -> - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block3))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block3))); - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block4))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block4))); - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block5))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block5))); - assert!(liveness.is_live_at(&v1, ProgramPoint::Block(block6))); - assert!(liveness.is_live_after(&v1, ProgramPoint::Block(block6))); - - Ok(()) - } -} diff --git a/hir-analysis/src/loops.rs b/hir-analysis/src/loops.rs deleted file mode 100644 index a91f98846..000000000 --- a/hir-analysis/src/loops.rs +++ /dev/null @@ -1,563 +0,0 @@ -use cranelift_entity::{entity_impl, packed_option::PackedOption, PrimaryMap, SecondaryMap}; -use midenc_hir::{ - pass::{Analysis, AnalysisManager, AnalysisResult, PreservedAnalyses}, - Block, DataFlowGraph, Function, -}; -use midenc_session::Session; - -use super::{BlockPredecessor, ControlFlowGraph, DominatorTree}; - -/// Represents a loop in the loop tree of a function -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct Loop(u32); -entity_impl!(Loop, "loop"); - -/// A level in a loop nest. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct LoopLevel(u8); -impl LoopLevel { - const INVALID: u8 = u8::MAX; - - /// Get the root level (no loop). - pub const fn root() -> Self { - Self(0) - } - - /// Get the loop level. - pub const fn level(self) -> usize { - self.0 as usize - } - - /// Invalid loop level. - pub const fn invalid() -> Self { - Self(Self::INVALID) - } - - /// One loop level deeper. - pub fn inc(self) -> Self { - if self.0 == (Self::INVALID - 1) { - self - } else { - Self(self.0 + 1) - } - } - - /// A clamped loop level from a larger-width (usize) depth. - pub fn clamped(level: usize) -> Self { - Self( - u8::try_from(std::cmp::min(level, (Self::INVALID as usize) - 1)) - .expect("invalid clamped loop level"), - ) - } -} -impl Default for LoopLevel { - fn default() -> Self { - LoopLevel::invalid() - } -} - -struct LoopData { - header: Block, - parent: PackedOption, - level: LoopLevel, -} -impl LoopData { - /// Creates a `LoopData` object with the loop header and its eventual parent in the loop tree. - pub fn new(header: Block, parent: Option) -> Self { - Self { - header, - parent: parent.into(), - level: LoopLevel::invalid(), - } - } -} - -/// Loop tree information for a single function. -/// -/// Loops are referenced by the `Loop` type, and for each loop you can -/// access its header block, its eventual parent in the loop tree, and -/// all the blocks belonging to the loop. -pub struct LoopAnalysis { - loops: PrimaryMap, - block_loop_map: SecondaryMap>, - valid: bool, -} -impl Analysis for LoopAnalysis { - type Entity = Function; - - fn analyze( - function: &Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> AnalysisResult { - let cfg = analyses.get_or_compute(function, session)?; - let domtree = analyses.get_or_compute(function, session)?; - Ok(LoopAnalysis::with_function(function, &cfg, &domtree)) - } - - fn is_invalidated(&self, preserved: &PreservedAnalyses) -> bool { - !preserved.is_preserved::() || !preserved.is_preserved::() - } -} -impl Default for LoopAnalysis { - fn default() -> Self { - Self { - valid: false, - loops: PrimaryMap::new(), - block_loop_map: SecondaryMap::new(), - } - } -} -impl LoopAnalysis { - pub fn with_function( - function: &Function, - cfg: &ControlFlowGraph, - domtree: &DominatorTree, - ) -> Self { - let mut this = Self::default(); - this.compute(function, cfg, domtree); - this - } - - /// Detects the loops in a function. Needs the control flow graph and the dominator tree. - pub fn compute(&mut self, func: &Function, cfg: &ControlFlowGraph, domtree: &DominatorTree) { - self.loops.clear(); - self.block_loop_map.clear(); - self.block_loop_map.resize(func.dfg.num_blocks()); - self.find_loop_headers(cfg, domtree, &func.dfg); - self.discover_loop_blocks(cfg, domtree, &func.dfg); - self.assign_loop_levels(); - self.valid = true; - } - - /// Check if the loop analysis is in a valid state. - /// - /// Note that this doesn't perform any kind of validity checks. It simply checks if the - /// `compute()` method has been called since the last `clear()`. It does not check that the - /// loop analysis is consistent with the CFG. - pub fn is_valid(&self) -> bool { - self.valid - } - - /// Clear all the data structures contained in the loop analysis. This will leave the - /// analysis in a similar state to a context returned by `new()` except that allocated - /// memory be retained. - pub fn clear(&mut self) { - self.loops.clear(); - self.block_loop_map.clear(); - self.valid = false; - } - - /// Returns all the loops contained in a function. - pub fn loops(&self) -> cranelift_entity::Keys { - self.loops.keys() - } - - /// Returns the header block of a particular loop. - /// - /// The characteristic property of a loop header block is that it dominates some of its - /// predecessors. - pub fn loop_header(&self, lp: Loop) -> Block { - self.loops[lp].header - } - - /// Return the eventual parent of a loop in the loop tree. - pub fn loop_parent(&self, lp: Loop) -> Option { - self.loops[lp].parent.expand() - } - - /// Return the innermost loop for a given block. - pub fn innermost_loop(&self, block: Block) -> Option { - self.block_loop_map[block].expand() - } - - /// Determine if a Block is a loop header. If so, return the loop. - pub fn is_loop_header(&self, block: Block) -> Option { - self.innermost_loop(block).filter(|&lp| self.loop_header(lp) == block) - } - - /// Determine if a Block belongs to a loop by running a finger along the loop tree. - /// - /// Returns `true` if `block` is in loop `lp`. - pub fn is_in_loop(&self, block: Block, lp: Loop) -> bool { - let block_loop = self.block_loop_map[block]; - match block_loop.expand() { - None => false, - Some(block_loop) => self.is_child_loop(block_loop, lp), - } - } - - /// Determines if a loop is contained in another loop. - /// - /// `is_child_loop(child,parent)` returns `true` if and only if `child` is a child loop of - /// `parent` (or `child == parent`). - pub fn is_child_loop(&self, child: Loop, parent: Loop) -> bool { - let mut finger = Some(child); - while let Some(finger_loop) = finger { - if finger_loop == parent { - return true; - } - finger = self.loop_parent(finger_loop); - } - false - } - - /// Returns the loop-nest level of a given block. - pub fn loop_level(&self, block: Block) -> LoopLevel { - self.innermost_loop(block).map_or(LoopLevel(0), |lp| self.loops[lp].level) - } - - /// Returns the loop level of the given level - pub fn level(&self, lp: Loop) -> LoopLevel { - self.loops[lp].level - } - - // Traverses the CFG in reverse postorder and create a loop object for every block having a - // back edge. - fn find_loop_headers( - &mut self, - cfg: &ControlFlowGraph, - domtree: &DominatorTree, - dfg: &DataFlowGraph, - ) { - // We traverse the CFG in reverse postorder - for &block in domtree.cfg_postorder().iter().rev() { - for BlockPredecessor { - inst: pred_inst, .. - } in cfg.pred_iter(block) - { - // If the block dominates one of its predecessors it is a back edge - if domtree.dominates(block, pred_inst, dfg) { - // This block is a loop header, so we create its associated loop - let lp = self.loops.push(LoopData::new(block, None)); - self.block_loop_map[block] = lp.into(); - break; - // We break because we only need one back edge to identify a loop header. - } - } - } - } - - // Intended to be called after `find_loop_headers`. For each detected loop header, - // discovers all the block belonging to the loop and its inner loops. After a call to this - // function, the loop tree is fully constructed. - fn discover_loop_blocks( - &mut self, - cfg: &ControlFlowGraph, - domtree: &DominatorTree, - dfg: &DataFlowGraph, - ) { - let mut stack: Vec = Vec::new(); - // We handle each loop header in reverse order, corresponding to a pseudo postorder - // traversal of the graph. - for lp in self.loops().rev() { - for BlockPredecessor { - block: pred, - inst: pred_inst, - } in cfg.pred_iter(self.loops[lp].header) - { - // We follow the back edges - if domtree.dominates(self.loops[lp].header, pred_inst, dfg) { - stack.push(pred); - } - } - while let Some(node) = stack.pop() { - let continue_dfs: Option; - match self.block_loop_map[node].expand() { - None => { - // The node hasn't been visited yet, we tag it as part of the loop - self.block_loop_map[node] = PackedOption::from(lp); - continue_dfs = Some(node); - } - Some(node_loop) => { - // We copy the node_loop into a mutable reference passed along the while - let mut node_loop = node_loop; - // The node is part of a loop, which can be lp or an inner loop - let mut node_loop_parent_option = self.loops[node_loop].parent; - while let Some(node_loop_parent) = node_loop_parent_option.expand() { - if node_loop_parent == lp { - // We have encountered lp so we stop (already visited) - break; - } else { - // - node_loop = node_loop_parent; - // We lookup the parent loop - node_loop_parent_option = self.loops[node_loop].parent; - } - } - // Now node_loop_parent is either: - // - None and node_loop is an new inner loop of lp - // - Some(...) and the initial node_loop was a known inner loop of lp - match node_loop_parent_option.expand() { - Some(_) => continue_dfs = None, - None => { - if node_loop != lp { - self.loops[node_loop].parent = lp.into(); - continue_dfs = Some(self.loops[node_loop].header) - } else { - // If lp is a one-block loop then we make sure we stop - continue_dfs = None - } - } - } - } - } - // Now we have handled the popped node and need to continue the DFS by adding the - // predecessors of that node - if let Some(continue_dfs) = continue_dfs { - for BlockPredecessor { block: pred, .. } in cfg.pred_iter(continue_dfs) { - stack.push(pred) - } - } - } - } - } - - fn assign_loop_levels(&mut self) { - use smallvec::{smallvec, SmallVec}; - - let mut stack: SmallVec<[Loop; 8]> = smallvec![]; - for lp in self.loops.keys() { - if self.loops[lp].level == LoopLevel::invalid() { - stack.push(lp); - while let Some(&lp) = stack.last() { - if let Some(parent) = self.loops[lp].parent.into() { - if self.loops[parent].level != LoopLevel::invalid() { - self.loops[lp].level = self.loops[parent].level.inc(); - stack.pop(); - } else { - stack.push(parent); - } - } else { - self.loops[lp].level = LoopLevel::root().inc(); - stack.pop(); - } - } - } - } - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{AbiParam, FunctionBuilder, InstBuilder, Signature, SourceSpan, Type}; - - use super::*; - - #[test] - fn nested_loops_variant1_detection() { - let id = "test::nested_loops_test".parse().unwrap(); - let mut function = Function::new(id, Signature::new([AbiParam::new(Type::I1)], [])); - - let block0 = function.dfg.entry_block(); - let block1 = function.dfg.create_block(); - let block2 = function.dfg.create_block(); - let block3 = function.dfg.create_block(); - let block4 = function.dfg.create_block(); - { - let mut builder = FunctionBuilder::new(&mut function); - let cond = { - let args = builder.block_params(block0); - args[0] - }; - - builder.switch_to_block(block0); - builder.ins().br(block1, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block1); - builder.ins().br(block2, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block2); - builder.ins().cond_br(cond, block1, &[], block3, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block3); - builder.ins().cond_br(cond, block0, &[], block4, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block4); - builder.ins().ret(None, SourceSpan::UNKNOWN); - } - - let cfg = ControlFlowGraph::with_function(&function); - let domtree = DominatorTree::with_function(&function, &cfg); - let loop_analysis = LoopAnalysis::with_function(&function, &cfg, &domtree); - - let loops = loop_analysis.loops().collect::>(); - assert_eq!(loops.len(), 2); - assert_eq!(loop_analysis.loop_header(loops[0]), block0); - assert_eq!(loop_analysis.loop_header(loops[1]), block1); - assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0])); - assert_eq!(loop_analysis.loop_parent(loops[0]), None); - assert!(loop_analysis.is_in_loop(block0, loops[0])); - assert!(!loop_analysis.is_in_loop(block0, loops[1])); - assert!(loop_analysis.is_in_loop(block1, loops[1])); - assert!(loop_analysis.is_in_loop(block1, loops[0])); - assert!(loop_analysis.is_in_loop(block2, loops[1])); - assert!(loop_analysis.is_in_loop(block2, loops[0])); - assert!(loop_analysis.is_in_loop(block3, loops[0])); - assert!(!loop_analysis.is_in_loop(block0, loops[1])); - assert_eq!(loop_analysis.loop_level(block0).level(), 1); - assert_eq!(loop_analysis.loop_level(block1).level(), 2); - assert_eq!(loop_analysis.loop_level(block2).level(), 2); - assert_eq!(loop_analysis.loop_level(block3).level(), 1); - } - - #[test] - fn nested_loops_variant2_detection() { - let id = "test::nested_loops_test".parse().unwrap(); - let mut function = Function::new(id, Signature::new([AbiParam::new(Type::I1)], [])); - - let block0 = function.dfg.entry_block(); - let block1 = function.dfg.create_block(); - let block2 = function.dfg.create_block(); - let block3 = function.dfg.create_block(); - let block4 = function.dfg.create_block(); - let block5 = function.dfg.create_block(); - let block6 = function.dfg.create_block(); - let exit = function.dfg.create_block(); - { - let mut builder = FunctionBuilder::new(&mut function); - let cond = { - let args = builder.block_params(block0); - args[0] - }; - - // block0 is outside of any loop - builder.switch_to_block(block0); - builder.ins().cond_br(cond, block1, &[], exit, &[], SourceSpan::UNKNOWN); - - // block1 simply branches to a loop header - builder.switch_to_block(block1); - builder.ins().br(block2, &[], SourceSpan::UNKNOWN); - - // block2 is the outer loop, which is conditionally entered - builder.switch_to_block(block2); - builder.ins().cond_br(cond, block3, &[], exit, &[], SourceSpan::UNKNOWN); - - // block3 simply branches to a nested loop header - builder.switch_to_block(block3); - builder.ins().br(block4, &[], SourceSpan::UNKNOWN); - - // block4 is the inner loop, which is conditionally escaped to the outer loop - builder.switch_to_block(block4); - builder.ins().cond_br(cond, block5, &[], block6, &[], SourceSpan::UNKNOWN); - - // block5 is the loop body of the inner loop - builder.switch_to_block(block5); - builder.ins().br(block4, &[], SourceSpan::UNKNOWN); - - // block6 is a block along the exit edge of the inner loop to the outer loop - builder.switch_to_block(block6); - builder.ins().br(block2, &[], SourceSpan::UNKNOWN); - - // the exit loop leaves the function - builder.switch_to_block(exit); - builder.ins().ret(None, SourceSpan::UNKNOWN); - } - - let cfg = ControlFlowGraph::with_function(&function); - let domtree = DominatorTree::with_function(&function, &cfg); - let loop_analysis = LoopAnalysis::with_function(&function, &cfg, &domtree); - - let loops = loop_analysis.loops().collect::>(); - assert_eq!(loops.len(), 2); - assert_eq!(loop_analysis.loop_header(loops[0]), block2); - assert_eq!(loop_analysis.loop_header(loops[1]), block4); - assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0])); - assert_eq!(loop_analysis.loop_parent(loops[0]), None); - assert!(!loop_analysis.is_in_loop(block0, loops[0])); - assert!(!loop_analysis.is_in_loop(block0, loops[1])); - assert!(!loop_analysis.is_in_loop(block1, loops[0])); - assert!(!loop_analysis.is_in_loop(block1, loops[1])); - assert!(loop_analysis.is_in_loop(block2, loops[0])); - assert!(!loop_analysis.is_in_loop(block2, loops[1])); - assert!(loop_analysis.is_in_loop(block3, loops[0])); - assert!(!loop_analysis.is_in_loop(block3, loops[1])); - assert!(loop_analysis.is_in_loop(block4, loops[0])); - assert!(loop_analysis.is_in_loop(block4, loops[1])); - assert!(loop_analysis.is_in_loop(block5, loops[0])); - assert!(loop_analysis.is_in_loop(block5, loops[1])); - assert!(loop_analysis.is_in_loop(block6, loops[0])); - assert!(!loop_analysis.is_in_loop(block6, loops[1])); - assert!(domtree.dominates(block4, block6, &function.dfg)); - assert!(!loop_analysis.is_in_loop(exit, loops[0])); - assert!(!loop_analysis.is_in_loop(exit, loops[1])); - assert_eq!(loop_analysis.loop_level(block0).level(), 0); - assert_eq!(loop_analysis.loop_level(block1).level(), 0); - assert_eq!(loop_analysis.loop_level(block2).level(), 1); - assert_eq!(loop_analysis.loop_level(block3).level(), 1); - assert_eq!(loop_analysis.loop_level(block4).level(), 2); - assert_eq!(loop_analysis.loop_level(block5).level(), 2); - assert_eq!(loop_analysis.loop_level(block6).level(), 1); - } - - #[test] - fn complex_loop_detection() { - let id = "test::complex_loop_test".parse().unwrap(); - let mut function = Function::new(id, Signature::new([AbiParam::new(Type::I1)], [])); - - let entry = function.dfg.entry_block(); - let block1 = function.dfg.create_block(); - let block2 = function.dfg.create_block(); - let block3 = function.dfg.create_block(); - let block4 = function.dfg.create_block(); - let block5 = function.dfg.create_block(); - let block6 = function.dfg.create_block(); - let block7 = function.dfg.create_block(); - { - let mut builder = FunctionBuilder::new(&mut function); - let cond = { - let args = builder.block_params(entry); - args[0] - }; - - builder.switch_to_block(entry); - builder.ins().br(block1, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block1); - builder.ins().cond_br(cond, block2, &[], block4, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block2); - builder.ins().br(block3, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block3); - builder.ins().cond_br(cond, block2, &[], block6, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block4); - builder.ins().br(block5, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block5); - builder.ins().cond_br(cond, block4, &[], block6, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block6); - builder.ins().cond_br(cond, block1, &[], block7, &[], SourceSpan::UNKNOWN); - - builder.switch_to_block(block7); - builder.ins().ret(None, SourceSpan::UNKNOWN); - } - - let cfg = ControlFlowGraph::with_function(&function); - let domtree = DominatorTree::with_function(&function, &cfg); - let loop_analysis = LoopAnalysis::with_function(&function, &cfg, &domtree); - - let loops = loop_analysis.loops().collect::>(); - assert_eq!(loops.len(), 3); - assert_eq!(loop_analysis.loop_header(loops[0]), block1); - assert_eq!(loop_analysis.loop_header(loops[1]), block4); - assert_eq!(loop_analysis.loop_header(loops[2]), block2); - assert_eq!(loop_analysis.loop_parent(loops[1]), Some(loops[0])); - assert_eq!(loop_analysis.loop_parent(loops[2]), Some(loops[0])); - assert_eq!(loop_analysis.loop_parent(loops[0]), None); - assert!(loop_analysis.is_in_loop(block1, loops[0])); - assert!(loop_analysis.is_in_loop(block2, loops[2])); - assert!(loop_analysis.is_in_loop(block3, loops[2])); - assert!(loop_analysis.is_in_loop(block4, loops[1])); - assert!(loop_analysis.is_in_loop(block5, loops[1])); - assert!(loop_analysis.is_in_loop(block6, loops[0])); - assert_eq!(loop_analysis.loop_level(block1).level(), 1); - assert_eq!(loop_analysis.loop_level(block2).level(), 2); - assert_eq!(loop_analysis.loop_level(block3).level(), 2); - assert_eq!(loop_analysis.loop_level(block4).level(), 2); - assert_eq!(loop_analysis.loop_level(block5).level(), 2); - assert_eq!(loop_analysis.loop_level(block6).level(), 1); - } -} diff --git a/hir-analysis/src/solver.rs b/hir-analysis/src/solver.rs new file mode 100644 index 000000000..d6fdcf357 --- /dev/null +++ b/hir-analysis/src/solver.rs @@ -0,0 +1,578 @@ +mod allocator; + +use alloc::{collections::VecDeque, rc::Rc}; +use core::{any::TypeId, cell::RefCell, ptr::NonNull}; + +use midenc_hir::{ + hashbrown, pass::AnalysisManager, EntityRef, FxHashMap, Operation, ProgramPoint, Report, + SmallVec, +}; + +use self::{allocator::DataFlowSolverAlloc, analysis::AnalysisStrategy}; +use super::{ + analysis::state::{AnalysisStateDescriptor, AnalysisStateInfo, AnalysisStateKey}, + *, +}; + +pub type AnalysisQueue = VecDeque; + +/// The [DataFlowSolver] is responsible for running a collection of [DataFlowAnalysis] against a +/// specific operation in the IR, such that the analyses reach a fixpoint state. +/// +/// To do so, it maintains the storage for all analysis states, as well as a dependency graph which +/// is used to re-run analyses which are affected by changes to states they depend on. Every +/// [DataFlowAnalysis] implementation interacts with the solver in order to create their analysis +/// state, and request those of their dependencies. This enables the solver to reason about when +/// changes require further re-analysis to be performed - hence why it is called the "solver". +pub struct DataFlowSolver { + /// Global configuration for the data-flow analysis being performed + config: DataFlowConfig, + /// The queue of dependent analyses that need to be re-applied due to changes. + /// + /// This works a bit like a channel primitive: any analysis states that are being mutated by + /// the currently executing analysis will hold a reference to this queue in their + /// [AnalysisStateGuard], so that when/if the underlying state changes, dependent analyses can + /// be enqueued in this worklist without going through the solver. + worklist: Rc>, + /// The set of loaded analyses maintained by the solver + /// + /// This set is consumed during `initialize_and_run`, to use the solver multiple times, you must + /// ensure you re-load all the analyses you wish to run. + child_analyses: SmallVec<[NonNull; 8]>, + /// Metadata about each unique [AnalysisState] implementation type which has had at least one + /// instance created by the solver. + /// + /// The metadata for a given type is shared between all instances of that type, to avoid the + /// overhead of allocating the data many times. It is used primarily for constructing pointers + /// to state from the raw type-erased `AnalysisStateInfo` record. + analysis_state_impls: FxHashMap>, + /// The analysis states being tracked by the solver. + /// + /// Each key in this map represents the type of analysis state and its lattice anchor, i.e. the + /// thing to which the analysis state is attached. The value is a pointer to the state itself, + /// and will change over time as analyses are re-applied. + /// + /// This map also manages the implicit dependency graph between analyses and specific analysis + /// states. When an analysis requires a given state at a given program point, an entry is added + /// to the [AnalysisStateInfo] record which tracks the analysis and the dependent program point. + /// + /// When changes are later made to an analysis state, any dependent analyses are re-enqueued in + /// the solver work queue, so that affected program points are re-analyzed. + analysis_state: Rc>>>, + /// Uniqued [LatticeAnchor] values. + /// + /// Each lattice anchor will only be allocated a single time, uniqueness is established via Hash + anchors: RefCell>, + /// The current analysis being executed. + current_analysis: Option>, + /// A bump-allocator local to the solver, in which it allocates analyses, analysis states, and + /// ad-hoc lattice anchors. + /// + /// Most data required by the solver gets allocated here, the exceptions are limited to certain + /// data structures without custom allocator support, or ad-hoc items which we don't want + /// attached to the solver lifetime. + alloc: DataFlowSolverAlloc, +} +impl Default for DataFlowSolver { + fn default() -> Self { + Self::new(Default::default()) + } +} +impl DataFlowSolver { + /// Create a new solver instance with the provided configuration + pub fn new(config: DataFlowConfig) -> Self { + let alloc = DataFlowSolverAlloc::default(); + let worklist = Rc::new(RefCell::new(VecDeque::with_capacity_in(64, alloc.clone()))); + Self { + config, + alloc, + worklist, + child_analyses: Default::default(), + analysis_state_impls: Default::default(), + analysis_state: Default::default(), + anchors: Default::default(), + current_analysis: None, + } + } + + /// Access the current solver configuration + #[inline] + pub fn config(&self) -> &DataFlowConfig { + &self.config + } + + /// Load an analysis of type `A` into the solver. + /// + /// This uses the information provided by the [BuildableDataFlowAnalysis] implementation to + /// construct a valid instance of the [DataFlowAnalysis] interface which the solver will use + /// to run the analysis. + /// + /// In particular, an instance of `A` is created using [BuildableDataFlowAnalysis::new], and + /// then calls [Self::load_with_strategy] to instantiate the actual [DataFlowAnalysis] + /// implementation, by using the associated [BuildableDataFlowAnalysis::Strategy] type. + /// + /// # Panics + /// + /// This function will panic if you attempt to load new analyses while the solver is running. + /// It is only permitted to load analyses before calling [initialize_and_run], or after a call + /// to that function has returned, and you are starting a new round of analysis. + pub fn load(&mut self) + where + A: BuildableDataFlowAnalysis + 'static, + { + let analysis = ::new(self); + self.load_with_strategy(analysis) + } + + /// Load `analysis` into the solver. + /// + /// Since `analysis` might not implement [DataFlowAnalysis] itself, we must obtain an + /// implementation using the strategy type given via [BuildableDataFlowAnalysis::Strategy]. + /// Specifically, we invoke [AnalysisStrategy::build], passing in `analysis` as the underlying + /// implementation. + /// + /// Once instantiated, the resulting [DataFlowAnalysis] implementation is loaded into the solver + /// using [Self::load_analysis]. + /// + /// # Panics + /// + /// This function will panic if you attempt to load new analyses while the solver is running. + /// It is only permitted to load analyses before calling [initialize_and_run], or after a call + /// to that function has returned, and you are starting a new round of analysis. + pub fn load_with_strategy(&mut self, analysis: A) + where + A: BuildableDataFlowAnalysis + 'static, + { + let analysis = <::Strategy as AnalysisStrategy>::build( + analysis, self, + ); + self.load_analysis(analysis); + } + + /// Load `analysis` into the solver. + /// + /// The provided analysis is stored in a queue until [DataFlowSolver::initialize_and_run] is + /// invoked. + /// + /// If an attempt is made to load the same analysis type twice while a previous instance is + /// still pending, the load is a no-op. + /// + /// # Panics + /// + /// This function will panic if you attempt to load new analyses while the solver is running. + /// It is only permitted to load analyses before calling [initialize_and_run], or after a call + /// to that function has returned, and you are starting a new round of analysis. + pub fn load_analysis(&mut self, analysis: A) + where + A: DataFlowAnalysis + 'static, + { + assert!( + self.worklist.borrow().is_empty() && self.current_analysis.is_none(), + "it is not permitted to load analyses while the solver is running!" + ); + let type_id = analysis.analysis_id(); + let already_loaded = self + .child_analyses + .iter() + .any(|a| unsafe { a.as_ref().analysis_id() == type_id }); + if !already_loaded { + let analysis = unsafe { NonNull::new_unchecked(self.alloc.put(analysis)) }; + self.child_analyses.push(analysis as NonNull); + } + } + + /// Run the solver on `op`. + /// + /// It is expected that the caller has called [Self::load] for each analysis they wish to have + /// applied. If no analyses have been loaded, this function returns `Ok` immediately, i.e. it + /// is a no-op. + /// + /// This first initializes all of the analysis loaded via [Self::load], places them in a work + /// queue, and then runs them to fixpoint by invoking [DataFlowAnalysis::visit]. + /// + /// It is expected that the loaded analyses will create and attach states to anchors in the + /// program rooted at `op` as the output of the analysis. These can then be requested by other + /// analyses, or by the caller after this function returns. + /// + /// When a dependent analysis requires a specific analysis state for some anchor, it implicitly + /// subscribes them to changes to that state by subsequent analyses. If such changes occur, the + /// dependent analyses are re-enqueued, and the process starts again. Assuming well-formed + /// data-flow analyses, this process is guaranteed to reach fixpoint. Currently, we do not + /// impose a limit on the number of iterations performed, though we may introduce such limits, + /// or other forms of sanity checks in the future. + #[track_caller] + pub fn initialize_and_run( + &mut self, + op: &Operation, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + // If we have no analyses, there is nothing to do + if self.child_analyses.is_empty() { + // Log a warning when this happens, since the calling code might benefit from not + // even instantiating the solver in the first place. + let location = core::panic::Location::caller(); + log::warn!(target: "dataflow-solver", "dataflow solver was run without any loaded analyses at {location}"); + return Ok(()); + } + + self.analyze(op, analysis_manager.clone())?; + self.run_to_fixpoint() + } + + /// Run the initial analysis of all loaded analyses. + /// + /// This is the point at which analyses are first applied to the top-level operation, `op`, and + /// is also when dependencies between analyses are recorded in the analysis state dependency + /// graph. + /// + /// Once initialization is complete, every analysis has been run exactly once, but some may have + /// been re-enqueued due to dependencies on analysis states which changed during initialization. + fn analyze(&mut self, op: &Operation, analysis_manager: AnalysisManager) -> Result<(), Report> { + log::debug!(target: "dataflow-solver", "initializing loaded analyses"); + + for mut analysis in core::mem::take(&mut self.child_analyses) { + // priming analysis {analysis.debug_name()} + assert!(self.current_analysis.is_none()); + self.current_analysis = Some(analysis); + unsafe { + let analysis = analysis.as_mut(); + log::debug!(target: analysis.debug_name(), "initializing analysis"); + analysis.initialize(op, self, analysis_manager.clone())?; + log::debug!(target: analysis.debug_name(), "initialized successfully"); + } + self.current_analysis = None; + } + + log::debug!(target: "dataflow-solver", "initialization complete!"); + + Ok(()) + } + + /// Run analysis to fixpoint. + /// + /// As mentioned in the docs of [Self::analyze], the initial analysis of the top-level operation + /// may have established dependencies some analyses and the analysis states produced by others + /// at specific program points. If any changes were made to analysis states for which there are + /// dependent analyses, the dependents will have been re-enqueued in the solver's work queue. + /// + /// This function is responsible for consuming notifications from the work queue, indicating + /// that a specific analysis should be re-applied at a given program point. This, in turn, may + /// result in further re-analysis work. + /// + /// # Expected Behavior + /// + /// While the process described in the previous section seems like it could easily end up + /// cycling indefinitely, re-enqueing the same analyses over and over again due to conflicts, + /// this is not actually a risk, due to the properties of a well-formed data-flow analysis: + /// + /// A "well-formed"" data-flow analysis must adhere to certain rules, and those rules guarantee + /// us that this process must reach a fixpoint, and in a bounded amount of time: + /// + /// A state of a data-flow analysis is required to be a valid instance of one of the following: + /// + /// * A join-semilattice (forward data-flow analysis) + /// * A meet-semilattice (backward data-flow analysis) + /// * A lattice (i.e. both a join- and meet- semilattice) + /// + /// Specifically this requires the following properties to be upheld: + /// + /// * The analysis state has a most-minimal value (i.e. under-specified, unknown, bottom) + /// * The analysis state has a most-maximal value (i.e. over-specified, conflict, top) + /// * The _join_ of two instances of the analysis state, produces a new state which is either + /// equal to the old states, or the least upper bound of the two states. + /// * The _meet_ of two instances of the analysis state, produces a new state which is either + /// equal to the old states, or the greatest lower bound of the two states. + /// * The _join_ and _meet_ operations must be commutative, associative, and idempotent + /// + /// With this in mind, it becomes obvious how fixpoint is a guarantee: + /// + /// * Each change (via meet or join) to the analysis state, by definition, produces a new state + /// which is either unchanged, or has a value which is the greatest lower (or least upper) + /// bound of the input states. Thus changes always move in a single direction, either most- + /// maximal (or most-minimal). + /// * As a result, an analysis that is re-enqueued due to a changed analysis state, is + /// guaranteed to observe a new, unique state. No further changes are possible after a state + /// reaches its most-maximal (or most-minimal) representation. + /// + /// For example, integer range analysis adheres to these axioms, as integer ranges form a + /// partial order for which the `max` and `min` operators are commutative, associative, and + /// idemptoent. A value for which we do not know any bounds, is in the most-minimal state, since + /// the range must be treated as unbounded, i.e. it is under-specified. A value for which we know + /// only a lower bound for, is strictly less specific than a value for which we know both a lower + /// and upper bound for, and bounds can be further refined as analysis proceeds, potentially all + /// the way until it is determined that the range of a value is fixed to a single integral + /// value. The "most-maximal" state in this analysis however, is a conflict, i.e. the value is + /// over specified, because we are able to observe a counter-example. + fn run_to_fixpoint(&mut self) -> Result<(), Report> { + log::debug!(target: "dataflow-solver", "running queued dataflow analyses to fixpoint.."); + + // Run the analysis until fixpoint + while let Some(QueuedAnalysis { + point, + mut analysis, + }) = { + let mut worklist = self.worklist.borrow_mut(); + worklist.pop_front() + } { + self.current_analysis = Some(analysis); + unsafe { + let analysis = analysis.as_mut(); + log::debug!(target: analysis.debug_name(), "running analysis at {point}"); + analysis.visit(&point, self)?; + } + self.current_analysis = None; + } + + Ok(()) + } + + /// Allocate a custom [LatticeAnchor] with this solver + /// + /// NOTE: The resulting [LatticeAnchorRef] has a lifetime that is implicitly bound to that of + /// this solver. It is unlikely you would ever have a reason to dereference an anchor after the + /// solver is destroyed, but it is undefined behavior to do so. See [LatticeAnchorRef] for more. + pub fn create_lattice_anchor(&self, anchor: A) -> LatticeAnchorRef + where + A: LatticeAnchorExt, + { + LatticeAnchorRef::intern(&anchor, &self.alloc, &mut self.anchors.borrow_mut()) + } + + /// Get the [AnalysisState] attached to `anchor`, or `None` if not available. + /// + /// This does _not_ add an edge to the analysis state dependency graph for the current analysis, + /// as it is assumed that the analysis state is not a required dependency, but an optional one. + /// If you wish to be notified of changes to a specific analysis state, you should use + /// [Self::require]. + pub fn get(&self, anchor: &A) -> Option> + where + T: BuildableAnalysisState, + A: LatticeAnchorExt + Copy, + { + let anchor = self.create_lattice_anchor::(*anchor); + let key = AnalysisStateKey::new::(anchor); + let analysis_state_info_ptr = self.analysis_state.borrow().get(&key).copied()?; + Some(unsafe { + let info = analysis_state_info_ptr.as_ref(); + info.borrow_state::() + }) + } + + /// Get the [AnalysisState] attached to `anchor`, or allocate a default instance if not yet + /// created. + /// + /// This is expected to be used by the current analysis to initialize state it needs. The + /// resulting handle represents an immutable borrow of the analysis state. + /// + /// Because the current analysis "owns" this state in a sense, albeit readonly, it is not + /// treated as a dependent of this state, i.e. no edge is added to the analysis state + /// dependency graph for the current analysis, and is instead left up to the caller, to + /// avoid unintentional cyclical dependencies. + /// + /// This function returns an [AnalysisStateGuard], which guards the immutable reference to + /// the underlying [AnalysisState]. + #[track_caller] + pub fn get_or_create<'a, T, A>(&mut self, anchor: A) -> AnalysisStateGuard<'a, T> + where + T: BuildableAnalysisState, + A: LatticeAnchorExt, + { + use hashbrown::hash_map::Entry; + + log::trace!(target: "dataflow-solver", "computing analysis state entry key"); + log::trace!(target: "dataflow-solver", " loc = {}", core::panic::Location::caller()); + log::trace!(target: "dataflow-solver", " anchor = {anchor}"); + log::trace!(target: "dataflow-solver", " anchor ty = {}", core::any::type_name::()); + let anchor = self.create_lattice_anchor::(anchor); + log::trace!(target: "dataflow-solver", " anchor id = {}", anchor.anchor_id()); + let key = AnalysisStateKey::new::(anchor); + log::trace!(target: "dataflow-solver", " key = {key:?}"); + log::trace!(target: "dataflow-solver", " lattice = {}", core::any::type_name::()); + match self.analysis_state.borrow_mut().entry(key) { + Entry::Occupied(entry) => { + log::trace!(target: "dataflow-solver", "found existing analysis state entry"); + let info = *entry.get(); + unsafe { AnalysisStateGuard::::new(info) } + } + Entry::Vacant(entry) => { + log::trace!(target: "dataflow-solver", "creating new analysis state entry"); + use crate::analysis::state::RawAnalysisStateInfo; + let raw_info = RawAnalysisStateInfo::::alloc( + &self.alloc, + &mut self.analysis_state_impls, + key, + anchor, + ); + let info = RawAnalysisStateInfo::as_info_ptr(raw_info); + entry.insert(info); + unsafe { AnalysisStateGuard::::new(info) } + } + } + } + + /// Get the [AnalysisState] attached to `anchor`, or allocate a default instance if not yet + /// created. + /// + /// This is expected to be used by the current analysis to write changes it computes. In a + /// sense, this implies ownership by the current analysis - however in some cases multiple + /// analyses share ownership over some state (i.e. they can all make changes to it). Any + /// writer to some state is considered an owner for our purposes here. + /// + /// Because the current analysis "owns" this state, it is not treated as a dependent of this + /// state, i.e. no edge is added to the analysis state dependency graph for the current + /// analysis. This is because the current analysis will necessarily be changing the state, and + /// thus re-enqueueing it when changes occur would result in a cyclical dependency on itself. + /// + /// Instead, it is expected that the current analysis will be re-enqueued only if it depends + /// on _other_ analyses which run and make changes to their states of which this analysis + /// is a dependent. + /// + /// This function returns an [AnalysisStateGuardMut], which guards both the mutable reference + /// to this solver, as well as a mutable reference to the underlying [AnalysisState]. When the + /// guard is dropped (or consumed to produce an immutable reference), it will have the solver + /// re-enqueue any dependent analyses, if changes were made to the state since they last + /// observed it. See the docs of [AnalysisStateGuardMut] for more details on proper usage. + #[track_caller] + pub fn get_or_create_mut<'a, T, A>(&mut self, anchor: A) -> AnalysisStateGuardMut<'a, T> + where + T: BuildableAnalysisState, + A: LatticeAnchorExt, + { + use hashbrown::hash_map::Entry; + + log::trace!(target: "dataflow-solver", "computing analysis state entry key"); + log::trace!(target: "dataflow-solver", " loc = {}", core::panic::Location::caller()); + log::trace!(target: "dataflow-solver", " anchor = {anchor}"); + log::trace!(target: "dataflow-solver", " anchor ty = {}", core::any::type_name::()); + let anchor = self.create_lattice_anchor::(anchor); + log::trace!(target: "dataflow-solver", " anchor id = {}", anchor.anchor_id()); + let key = AnalysisStateKey::new::(anchor); + log::trace!(target: "dataflow-solver", " key = {key:?}"); + log::trace!(target: "dataflow-solver", " lattice = {}", core::any::type_name::()); + match self.analysis_state.borrow_mut().entry(key) { + Entry::Occupied(entry) => { + log::trace!(target: "dataflow-solver", "found existing analysis state entry"); + let info = *entry.get(); + unsafe { AnalysisStateGuardMut::::new(info, self.worklist.clone()) } + } + Entry::Vacant(entry) => { + log::trace!(target: "dataflow-solver", "creating new analysis state entry"); + use crate::analysis::state::RawAnalysisStateInfo; + let raw_info = RawAnalysisStateInfo::::alloc( + &self.alloc, + &mut self.analysis_state_impls, + key, + anchor, + ); + let info = RawAnalysisStateInfo::as_info_ptr(raw_info); + entry.insert(info); + unsafe { AnalysisStateGuardMut::::new(info, self.worklist.clone()) } + } + } + } + + /// Get the [AnalysisState] attached to `anchor`, indicating to the solver that it is required + /// by the current analysis at `dependent`, the program point at which the dependency is needed. + /// + /// In addition to returning the requested state, this function also adds an edge to the + /// analysis state dependency graph for the current analysis, so that any changes to the + /// state at `anchor` by later analyses, will cause the current analysis to be re-run at + /// the given program point. + /// + /// If an instance of the requested state has not been created yet, a default one is allocated + /// and returned. Typically, the resulting state will not be very useful, however, as mentioned + /// above, this analysis will be re-run if the state is ever modified, at which point it may + /// be able to do something more useful with the results. + #[track_caller] + pub fn require<'a, T, A>( + &mut self, + anchor: A, + dependent: ProgramPoint, + ) -> AnalysisStateGuard<'a, T> + where + T: BuildableAnalysisState, + A: LatticeAnchorExt, + { + use hashbrown::hash_map::Entry; + + use crate::{ + analysis::state::{RawAnalysisStateInfo, RawAnalysisStateInfoHandle}, + AnalysisStateSubscriptionBehavior, + }; + + log::trace!(target: "dataflow-solver", "computing analysis state entry key"); + log::trace!(target: "dataflow-solver", " loc = {}", core::panic::Location::caller()); + log::trace!(target: "dataflow-solver", " anchor = {anchor}"); + log::trace!(target: "dataflow-solver", " anchor ty = {}", core::any::type_name::()); + let anchor = self.create_lattice_anchor::(anchor); + log::trace!(target: "dataflow-solver", " anchor id = {}", anchor.anchor_id()); + let key = AnalysisStateKey::new::(anchor); + log::trace!(target: "dataflow-solver", " key = {key:?}"); + log::trace!(target: "dataflow-solver", " lattice = {}", core::any::type_name::()); + log::trace!(target: "dataflow-solver", " dependent = {dependent}"); + let (info, mut handle) = match self.analysis_state.borrow_mut().entry(key) { + Entry::Occupied(entry) => { + log::trace!(target: "dataflow-solver", "found existing analysis state entry"); + let info = *entry.get(); + (info, unsafe { RawAnalysisStateInfoHandle::new(info) }) + } + Entry::Vacant(entry) => { + log::trace!(target: "dataflow-solver", "creating new analysis state entry"); + let raw_info = RawAnalysisStateInfo::::alloc( + &self.alloc, + &mut self.analysis_state_impls, + key, + anchor, + ); + let info = RawAnalysisStateInfo::as_info_ptr(raw_info); + entry.insert(info); + (info, unsafe { RawAnalysisStateInfoHandle::new(info) }) + } + }; + + let current_analysis = self.current_analysis.unwrap(); + handle.with(|state, info| { + ::on_require_analysis( + state, + info, + current_analysis, + dependent, + ); + }); + + unsafe { AnalysisStateGuard::new(info) } + } + + /// Erase any cached analysis states attached to `anchor` + pub fn erase_state(&mut self, anchor: &A) + where + A: LatticeAnchorExt, + { + let anchor_id = anchor.anchor_id(); + self.analysis_state.borrow_mut().retain(|_, v| unsafe { + let analysis_anchor_id = v.as_ref().anchor_ref().anchor_id(); + analysis_anchor_id == anchor_id + }); + } +} + +/// Represents an analysis that has derived facts at a specific program point from the state of +/// another analysis, that has since changed. As a result, the dependent analysis must be re-applied +/// at that program point to determine if the state changes have any effect on the state of its +/// previous analysis. +#[derive(Copy, Clone)] +pub struct QueuedAnalysis { + /// The dependent program point + pub point: ProgramPoint, + /// The dependent analysis + pub analysis: NonNull, +} + +impl Eq for QueuedAnalysis {} + +impl PartialEq for QueuedAnalysis { + fn eq(&self, other: &Self) -> bool { + self.point == other.point + && core::ptr::addr_eq(self.analysis.as_ptr(), other.analysis.as_ptr()) + } +} diff --git a/hir-analysis/src/solver/allocator.rs b/hir-analysis/src/solver/allocator.rs new file mode 100644 index 000000000..d9ccc56d8 --- /dev/null +++ b/hir-analysis/src/solver/allocator.rs @@ -0,0 +1,70 @@ +use alloc::rc::Rc; +use core::{ + alloc::{AllocError, Allocator, Layout}, + ptr::NonNull, +}; + +/// This is a simple wrapper around [blink_alloc::Blink] that allows it to be used as an allocator +/// with standard library collections such as [alloc::collections::VecDeque], without binding the +/// lifetime of the collection to the allocator. +#[derive(Default, Clone)] +pub struct DataFlowSolverAlloc(Rc); + +impl core::ops::Deref for DataFlowSolverAlloc { + type Target = blink_alloc::Blink; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +unsafe impl Allocator for DataFlowSolverAlloc { + #[inline] + fn allocate(&self, layout: Layout) -> Result, AllocError> { + self.0.allocator().allocate(layout).map_err(|_| AllocError) + } + + #[inline] + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + self.0.allocator().deallocate(ptr, layout.size()); + } + + #[inline] + fn allocate_zeroed(&self, layout: Layout) -> Result, AllocError> { + self.0.allocator().allocate_zeroed(layout).map_err(|_| AllocError) + } + + #[inline] + unsafe fn grow( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, alloc::alloc::AllocError> { + self.0.allocator().grow(ptr, old_layout, new_layout).map_err(|_| AllocError) + } + + #[inline] + unsafe fn shrink( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, alloc::alloc::AllocError> { + self.0.allocator().shrink(ptr, old_layout, new_layout).map_err(|_| AllocError) + } + + #[inline] + unsafe fn grow_zeroed( + &self, + ptr: NonNull, + old_layout: Layout, + new_layout: Layout, + ) -> Result, alloc::alloc::AllocError> { + self.0 + .allocator() + .grow_zeroed(ptr, old_layout, new_layout) + .map_err(|_| AllocError) + } +} diff --git a/hir-analysis/src/sparse.rs b/hir-analysis/src/sparse.rs new file mode 100644 index 000000000..f5c33f3c5 --- /dev/null +++ b/hir-analysis/src/sparse.rs @@ -0,0 +1,341 @@ +mod backward; +mod forward; +mod lattice; + +use midenc_hir::{ + pass::AnalysisManager, Backward, CallOpInterface, Forward, OpOperandImpl, Operation, + ProgramPoint, RegionSuccessor, Report, +}; + +pub use self::{ + backward::{set_all_to_exit_states, SparseBackwardDataFlowAnalysis}, + forward::{set_all_to_entry_states, SparseForwardDataFlowAnalysis}, + lattice::SparseLattice, +}; +use super::{AnalysisStrategy, DataFlowAnalysis, DataFlowSolver, Sparse}; + +/// This type provides an [AnalysisStrategy] for sparse data-flow analyses. +/// +/// In short, it implements the [DataFlowAnalysis] trait, and handles all of the boilerplate that +/// any well-structured sparse data-flow analysis requires. Analyses can make use of this strategy +/// by implementing one of the sparse data-flow analysis traits, which [SparseDataFlowAnalysis] will +/// use to delegate analysis-specific details to the analysis implementation. The two traits are: +/// +/// * [SparseForwardDataFlowAnalysis], for forward-propagating sparse analyses +/// * [SparseBackwardDataFlowAnalysis], for backward-propagating sparse analyses +/// +/// ## What is a sparse analysis? +/// +/// A sparse data-flow analysis is one which associates analysis state with SSA value definitions, +/// in order to represent known facts about those values, either as a result of deriving them from +/// previous values used as operands of the defining operation (forward analysis), or as a result of +/// deriving them based on how the value is used along all possible paths that execution might take +/// (backward analysis). The state associated with a value does not change as a program executes, +/// it is fixed at the value definition, derived only from the states of other values and the +/// defining op itself. +/// +/// This is in contrast to dense data-flow analysis, which associates state with program points, +/// which then evolves as the program executes. This is also where the distinction between _dense_ +/// and _sparse_ comes from - program points are dense, while SSA value definitions are sparse +/// (insofar as the state associated with an SSA value only ever occurs once, while states +/// associated with program points are duplicated at each point). +/// +/// Some examples of sparse analyses: +/// +/// * Constant propagation - if a value is determined to be constant, the constant value is the +/// state associated with a given value definition. This determination is made based on the +/// semantics of an operation and its operands (i.e. if an operation can be constant-folded, then +/// the results of that operation are themselves constant). This is a forward analysis. +/// * Dead value analysis - determines whether or not a value is ever used. This is a backward +/// analysis, as it propagates uses to definitions. In our IR, we do not require this analysis, +/// as it is implicit in the use-def graph, however the concept is what we're interested in here. +/// +/// ## Usage +/// +/// This type is meant to be used indirectly, as an [AnalysisStrategy] implementation, rather than +/// directly as a [DataFlowAnalysis] implementation, as shown below: +/// +/// ```rust,ignore +/// use midenc_hir::dataflow::*; +/// +/// #[derive(Default)] +/// pub struct MyAnalysis; +/// impl BuildableDataFlowAnalysis for MyAnalysis { +/// type Strategy = SparseDataFlowAnalysis; +/// +/// fn new(_solver: &mut DataFlowSolver) -> Self { +/// Self +/// } +/// } +/// impl SparseForwardDataFlowAnalysis for MyAnalysis { +/// type Lattice = Lattice; +/// +/// //... +/// } +/// ``` +/// +/// The above permits us to load `MyAnalysis` into a `DataFlowSolver` without ever mentioning the +/// `SparseDataFlowAnalysis` type at all, like so: +/// +/// ```rust,ignore +/// let mut solver = DataFlowSolver::default(); +/// solver.load::(); +/// solver.initialize_and_run(&op, analysis_manager); +/// ``` +pub struct SparseDataFlowAnalysis { + analysis: A, + _direction: core::marker::PhantomData, +} + +impl AnalysisStrategy for SparseDataFlowAnalysis { + type Direction = Forward; + type Kind = Sparse; + + fn build(analysis: A, _solver: &mut DataFlowSolver) -> Self { + Self { + analysis, + _direction: core::marker::PhantomData, + } + } +} + +impl AnalysisStrategy + for SparseDataFlowAnalysis +{ + type Direction = Backward; + type Kind = Sparse; + + fn build(analysis: A, _solver: &mut DataFlowSolver) -> Self { + Self { + analysis, + _direction: core::marker::PhantomData, + } + } +} + +impl DataFlowAnalysis for SparseDataFlowAnalysis { + fn debug_name(&self) -> &'static str { + self.analysis.debug_name() + } + + fn analysis_id(&self) -> core::any::TypeId { + core::any::TypeId::of::() + } + + /// Initialize the analysis by visiting every owner of an SSA value: all operations and blocks. + fn initialize( + &self, + top: &Operation, + solver: &mut DataFlowSolver, + _analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::trace!( + target: self.analysis.debug_name(), + "initializing analysis for {top}", + ); + + // Mark the entry block arguments as having reached their pessimistic fixpoints. + for region in top.regions() { + if region.is_empty() { + continue; + } + + log::trace!( + target: self.analysis.debug_name(), + "initializing entry arguments of region {}", + region.region_number() + ); + for argument in region.entry().arguments() { + let argument = argument.borrow().as_value_ref(); + let mut lattice = solver.get_or_create_mut::<_, _>(argument); + ::set_to_entry_state( + &self.analysis, + &mut lattice, + ); + } + } + + forward::initialize_recursively(self, top, solver) + } + + /// Visit a program point. + /// + /// If this is at beginning of block and all control-flow predecessors or callsites are known, + /// then the arguments lattices are propagated from them. If this is after call operation or an + /// operation with region control-flow, then its result lattices are set accordingly. Otherwise, + /// the operation transfer function is invoked. + fn visit(&self, point: &ProgramPoint, solver: &mut DataFlowSolver) -> Result<(), Report> { + if !point.is_at_block_start() { + return forward::visit_operation( + self, + &point.prev_operation().unwrap().borrow(), + solver, + ); + } + + forward::visit_block(self, &point.block().unwrap().borrow(), solver); + + Ok(()) + } +} + +impl DataFlowAnalysis for SparseDataFlowAnalysis { + fn debug_name(&self) -> &'static str { + self.analysis.debug_name() + } + + fn analysis_id(&self) -> core::any::TypeId { + core::any::TypeId::of::() + } + + /// Initialize the analysis by visiting the operation and everything nested under it. + fn initialize( + &self, + top: &Operation, + solver: &mut DataFlowSolver, + _analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + log::trace!( + target: self.analysis.debug_name(), + "initializing analysis for {top}", + ); + backward::initialize_recursively(self, top, solver) + } + + /// Visit a program point. + /// + /// If it is after call operation or an operation with block or region control-flow, then + /// operand lattices are set accordingly. Otherwise, invokes the operation transfer function. + fn visit(&self, point: &ProgramPoint, solver: &mut DataFlowSolver) -> Result<(), Report> { + // For backward dataflow, we don't have to do any work for the blocks themselves. CFG edges + // between blocks are processed by the BranchOp logic in `visit_operation`, and entry blocks + // for functions are tied to the CallOp arguments by `visit_operation`. + if point.is_at_block_start() { + Ok(()) + } else { + backward::visit_operation(self, &point.prev_operation().unwrap().borrow(), solver) + } + } +} + +impl SparseForwardDataFlowAnalysis + for SparseDataFlowAnalysis +{ + type Lattice = ::Lattice; + + fn debug_name(&self) -> &'static str { + ::debug_name(&self.analysis) + } + + fn visit_operation( + &self, + op: &Operation, + operands: &[super::AnalysisStateGuard<'_, Self::Lattice>], + results: &mut [super::AnalysisStateGuardMut<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) -> Result<(), Report> { + ::visit_operation( + &self.analysis, + op, + operands, + results, + solver, + ) + } + + fn set_to_entry_state(&self, lattice: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>) { + ::set_to_entry_state(&self.analysis, lattice); + } + + fn visit_external_call( + &self, + call: &dyn CallOpInterface, + arguments: &[super::AnalysisStateGuard<'_, Self::Lattice>], + results: &mut [super::AnalysisStateGuardMut<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) { + ::visit_external_call( + &self.analysis, + call, + arguments, + results, + solver, + ); + } + + fn visit_non_control_flow_arguments( + &self, + op: &Operation, + successor: &RegionSuccessor<'_>, + arguments: &mut [super::AnalysisStateGuardMut<'_, Self::Lattice>], + first_index: usize, + solver: &mut DataFlowSolver, + ) { + ::visit_non_control_flow_arguments( + &self.analysis, + op, + successor, + arguments, + first_index, + solver, + ); + } +} + +impl SparseBackwardDataFlowAnalysis + for SparseDataFlowAnalysis +{ + type Lattice = ::Lattice; + + fn debug_name(&self) -> &'static str { + ::debug_name(&self.analysis) + } + + fn visit_operation( + &self, + op: &Operation, + operands: &mut [super::AnalysisStateGuardMut<'_, Self::Lattice>], + results: &[super::AnalysisStateGuard<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) -> Result<(), Report> { + ::visit_operation( + &self.analysis, + op, + operands, + results, + solver, + ) + } + + fn set_to_exit_state(&self, lattice: &mut super::AnalysisStateGuardMut<'_, Self::Lattice>) { + ::set_to_exit_state(&self.analysis, lattice); + } + + fn visit_call_operand(&self, operand: &OpOperandImpl, solver: &mut DataFlowSolver) { + ::visit_call_operand(&self.analysis, operand, solver); + } + + fn visit_external_call( + &self, + call: &dyn CallOpInterface, + arguments: &mut [super::AnalysisStateGuardMut<'_, Self::Lattice>], + results: &[super::AnalysisStateGuard<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) { + ::visit_external_call( + &self.analysis, + call, + arguments, + results, + solver, + ); + } + + fn visit_branch_operand(&self, operand: &OpOperandImpl, solver: &mut DataFlowSolver) { + ::visit_branch_operand( + &self.analysis, + operand, + solver, + ); + } +} diff --git a/hir-analysis/src/sparse/backward.rs b/hir-analysis/src/sparse/backward.rs new file mode 100644 index 000000000..5007c7ba9 --- /dev/null +++ b/hir-analysis/src/sparse/backward.rs @@ -0,0 +1,467 @@ +use alloc::boxed::Box; + +use bitvec::bitvec; +use midenc_hir::{ + traits::{BranchOpInterface, ReturnLike}, + AttributeValue, Backward, CallOpInterface, CallableOpInterface, EntityWithId, OpOperandImpl, + OpOperandRange, OpResultRange, Operation, OperationRef, ProgramPoint, RegionBranchOpInterface, + RegionBranchTerminatorOpInterface, RegionSuccessorIter, Report, SmallVec, StorableEntity, + SuccessorOperands, ValueRef, +}; + +use super::{SparseDataFlowAnalysis, SparseLattice}; +use crate::{ + analyses::dce::{Executable, PredecessorState}, + AnalysisStateGuard, AnalysisStateGuardMut, BuildableAnalysisState, DataFlowSolver, +}; + +/// A sparse (backward) data-flow analysis for propagating SSA value lattices backwards across the +/// IR by implementing transfer functions for operations. +/// +/// Visiting a program point in sparse backward data-flow analysis will invoke the transfer function +/// of the operation preceding the program point. Visiting a program point at the begining of block +/// will visit the block itself. +#[allow(unused_variables)] +pub trait SparseBackwardDataFlowAnalysis: 'static { + type Lattice: BuildableAnalysisState + SparseLattice; + + fn debug_name(&self) -> &'static str { + core::any::type_name::() + } + + /// The operation transfer function. + /// + /// Given the result lattices, this function is expected to set the operand lattices. + fn visit_operation( + &self, + op: &Operation, + operands: &mut [AnalysisStateGuardMut<'_, Self::Lattice>], + results: &[AnalysisStateGuard<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) -> Result<(), Report>; + + /// The transfer function for calls to external functions. + /// + /// This function is expected to set lattice values of the call operands. By default, this calls + /// [visit_call_operand] for all operands. + fn visit_external_call( + &self, + call: &dyn CallOpInterface, + arguments: &mut [AnalysisStateGuardMut<'_, Self::Lattice>], + results: &[AnalysisStateGuard<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) { + for operand in call.arguments() { + let operand = operand.borrow(); + self.visit_call_operand(&operand, solver); + } + } + + /// Visit operands on branch instructions that are not forwarded. + fn visit_branch_operand(&self, operand: &OpOperandImpl, solver: &mut DataFlowSolver); + + /// Visit operands on call instructions that are not forwarded. + fn visit_call_operand(&self, operand: &OpOperandImpl, solver: &mut DataFlowSolver); + + /// Set the given lattice element(s) at control flow exit point(s). + fn set_to_exit_state(&self, lattice: &mut AnalysisStateGuardMut<'_, Self::Lattice>); +} + +pub fn set_all_to_exit_states( + analysis: &A, + lattices: &mut [AnalysisStateGuardMut<'_, ::Lattice>], +) where + A: SparseBackwardDataFlowAnalysis, +{ + for lattice in lattices { + analysis.set_to_exit_state(lattice); + } +} + +/// Recursively initialize the analysis on nested operations and blocks. +pub(super) fn initialize_recursively( + analysis: &SparseDataFlowAnalysis, + op: &Operation, + solver: &mut DataFlowSolver, +) -> Result<(), Report> +where + A: SparseBackwardDataFlowAnalysis, +{ + log::trace!("initializing op recursively"); + visit_operation(analysis, op, solver)?; + + for region in op.regions() { + for block in region.body() { + log::trace!("initializing analysis for block {}", block.id()); + { + let state = + solver.get_or_create::(ProgramPoint::at_start_of(&*block)); + AnalysisStateGuard::subscribe(&state, analysis); + } + + // Initialize ops in reverse order, so we can do as much initial propagation as possible + // without having to go through the solver queue. + let mut ops = block.body().back(); + log::trace!("initializing ops of {} bottom-up", block.id()); + while let Some(op) = ops.as_pointer() { + ops.move_prev(); + + let op = op.borrow(); + initialize_recursively(analysis, &op, solver)?; + } + log::trace!("all ops of {} have been initialized", block.id()); + } + } + + Ok(()) +} + +/// Visit an operation. If this is a call operation or an operation with +/// region control-flow, then its operand lattices are set accordingly. +/// Otherwise, the operation transfer function is invoked. +pub(super) fn visit_operation( + analysis: &SparseDataFlowAnalysis, + op: &Operation, + solver: &mut DataFlowSolver, +) -> Result<(), Report> +where + A: SparseBackwardDataFlowAnalysis, +{ + // If we're in a dead block, bail out. + let in_dead_block = op.parent().is_some_and(|block| { + !solver + .get_or_create_mut::(ProgramPoint::at_start_of(block)) + .is_live() + }); + if in_dead_block { + log::trace!("skipping analysis for op in dead/non-executable block: {op}"); + return Ok(()); + } + + let current_point = ProgramPoint::after(op); + let mut operands = get_lattice_elements_mut::(op.operands().all(), solver); + let results = get_lattice_elements_for::(current_point, op.results().all(), solver); + + // Block arguments of region branch operations flow back into the operands of the parent op + if let Some(branch) = op.as_trait::() { + log::trace!("op implements RegionBranchOpInterface - handling as special case"); + visit_region_successors(analysis, branch, solver); + return Ok(()); + } + + // Block arguments of successor blocks flow back into our operands. + if let Some(branch) = op.as_trait::() { + log::trace!("op implements BranchOpInterface - handling as special case"); + + // We remember all operands not forwarded to any block in a bitvector. + // We can't just cut out a range here, since the non-forwarded ops might be non-contiguous + // (if there's more than one successor). + let mut unaccounted = bitvec![1; op.num_operands()]; + + for successor_index in 0..branch.num_successors() { + let successor_operands = branch.get_successor_operands(successor_index); + let forwarded = successor_operands.forwarded(); + if !forwarded.is_empty() { + let num_produced = successor_operands.num_produced(); + for (operand_index, operand) in forwarded.iter().enumerate() { + unaccounted.set(operand.index(), false); + if let Some(block_arg) = + branch.get_successor_block_argument(operand_index + num_produced) + { + let mut operand_lattice = + get_lattice_element_mut::(operand.borrow().as_value_ref(), solver); + let result_lattice = get_lattice_element_for::( + current_point, + block_arg.borrow().as_value_ref(), + solver, + ); + operand_lattice.meet(result_lattice.lattice()); + } + } + } + } + + // Operands not forwarded to successor blocks are typically parameters of the branch + // operation itself (for example the boolean for if/else). + for index in unaccounted.iter_ones() { + let operand = op.operands().all()[index].borrow(); + analysis.visit_branch_operand(&operand, solver); + } + + return Ok(()); + } + + // For function calls, connect the arguments of the entry blocks to the operands of the call op + // that are forwarded to these arguments. + if let Some(call) = op.as_trait::() { + log::trace!("op implements CallOpInterface - handling as special case"); + + // TODO: resolve_in_symbol_table + if let Some(callable_symbol) = call.resolve() { + let callable_symbol = callable_symbol.borrow(); + log::trace!("resolved callee as {}", callable_symbol.name()); + let callable_op = callable_symbol.as_symbol_operation(); + if let Some(callable) = callable_op.as_trait::() { + log::trace!("{} implements CallableOpInterface", callable_symbol.name()); + // Not all operands of a call op forward to arguments. Such operands are stored in + // `unaccounted`. + let mut unaccounted = bitvec![1; op.num_operands()]; + + // If the call invokes an external function (or a function treated as external due to + // config), defer to the corresponding extension hook. By default, it just does + // `visit_call_operand` for all operands. + let arg_operands = call.arguments(); + let region = callable.get_callable_region(); + if region.as_ref().is_none_or(|region| { + region.borrow().is_empty() || !solver.config().is_interprocedural() + }) { + log::trace!("{} is an external callee", callable_symbol.name()); + analysis.visit_external_call(call, &mut operands, &results, solver); + return Ok(()); + } + + // Otherwise, propagate information from the entry point of the function back to + // operands whenever possible. + log::trace!("propagating value lattices from callee entry to call operands"); + let region = region.unwrap(); + let region = region.borrow(); + let block = region.entry(); + for (block_arg, arg_operand) in block.arguments().iter().zip(arg_operands.iter()) { + let mut arg_lattice = + get_lattice_element_mut::(arg_operand.borrow().as_value_ref(), solver); + let result_lattice = get_lattice_element_for::( + current_point, + block_arg.borrow().as_value_ref(), + solver, + ); + arg_lattice.meet(result_lattice.lattice()); + unaccounted.set(arg_operand.borrow().index as usize, false); + } + + // Handle the operands of the call op that aren't forwarded to any arguments. + for index in unaccounted.iter_ones() { + let operand = op.operands().all()[index].borrow(); + analysis.visit_call_operand(&operand, solver); + } + + return Ok(()); + } + } + } + + // When the region of an op implementing `RegionBranchOpInterface` has a terminator implementing + // `RegionBranchTerminatorOpInterface` or a return-like terminator, the region's successors' + // arguments flow back into the "successor operands" of this terminator. + // + // A successor operand with respect to an op implementing `RegionBranchOpInterface` is an + // operand that is forwarded to a region successor's input. There are two types of successor + // operands: the operands of this op itself and the operands of the terminators of the regions + // of this op. + if let Some(terminator) = op.as_trait::() { + log::trace!("op implements RegionBranchTerminatorOpInterface"); + let parent_op = op.parent_op().unwrap(); + let parent_op = parent_op.borrow(); + if let Some(branch) = parent_op.as_trait::() { + log::trace!( + "op's parent implements RegionBranchOpInterface - handling as special case" + ); + visit_region_successors_from_terminator(analysis, terminator, branch, solver); + return Ok(()); + } + } + + if op.implements::() { + log::trace!("op implements ReturnLike"); + // Going backwards, the operands of the return are derived from the results of all CallOps + // calling this CallableOp. + let parent_op = op.parent_op().unwrap(); + let parent_op = parent_op.borrow(); + if let Some(callable) = parent_op.as_trait::() { + log::trace!("op's parent implements CallableOpInterface - visiting call sites"); + let callsites = solver.require::( + ProgramPoint::after(callable.as_operation()), + current_point, + ); + if callsites.all_predecessors_known() { + log::trace!( + "found all {} call sites of the current callable op", + callsites.known_predecessors().len() + ); + log::trace!("meeting lattices of return values and call site results"); + for call in callsites.known_predecessors() { + let call = call.borrow(); + let call_result_lattices = + get_lattice_elements_for::(current_point, call.results().all(), solver); + for (op, result) in operands.iter_mut().zip(call_result_lattices.into_iter()) { + op.meet(result.lattice()); + } + } + } else { + // If we don't know all the callers, we can't know where the returned values go. + // Note that, in particular, this will trigger for the return ops of any public + // functions. + log::trace!( + "not all call sites are known - setting return value lattices to exit state" + ); + set_all_to_exit_states(analysis, &mut operands); + } + return Ok(()); + } + } + + log::trace!("invoking {}::visit_operation", core::any::type_name::()); + analysis.visit_operation(op, &mut operands, &results, solver) +} + +/// Visit an op with regions (like e.g. `scf.while`) +fn visit_region_successors( + analysis: &SparseDataFlowAnalysis, + branch: &dyn RegionBranchOpInterface, + solver: &mut DataFlowSolver, +) where + A: SparseBackwardDataFlowAnalysis, +{ + let op = branch.as_operation(); + + let mut const_operands = + SmallVec::<[Option>; 2]>::with_capacity(op.num_operands()); + const_operands.resize_with(op.num_operands(), || None); + let successors = branch.get_entry_successor_regions(&const_operands); + + // All operands not forwarded to any successor. This set can be non-contiguous in the presence + // of multiple successors. + let mut unaccounted = bitvec![1; op.num_operands()]; + + for successor in successors { + let operands = branch.get_entry_successor_operands(*successor.branch_point()); + let inputs = successor.successor_inputs(); + for (operand, input) in operands.forwarded().iter().zip(inputs.iter()) { + let operand = operand.borrow(); + let operand_index = operand.index as usize; + let mut operand_lattice = get_lattice_element_mut::(operand.as_value_ref(), solver); + let point = ProgramPoint::after(op); + let input_lattice = get_lattice_element_for::(point, input, solver); + operand_lattice.meet(input_lattice.lattice()); + unaccounted.set(operand_index, false); + } + } + + // All operands not forwarded to regions are typically parameters of the branch operation itself + // (for example the boolean for if/else). + for index in unaccounted.iter_ones() { + analysis.visit_branch_operand(&op.operands().all()[index].borrow(), solver); + } +} + +/// Visit a `RegionBranchTerminatorOpInterface` to compute the lattice values +/// of its operands, given its parent op `branch`. The lattice value of an +/// operand is determined based on the corresponding arguments in +/// `terminator`'s region successor(s). +fn visit_region_successors_from_terminator( + analysis: &SparseDataFlowAnalysis, + terminator: &dyn RegionBranchTerminatorOpInterface, + branch: &dyn RegionBranchOpInterface, + solver: &mut DataFlowSolver, +) where + A: SparseBackwardDataFlowAnalysis, +{ + assert!( + OperationRef::ptr_eq( + &terminator.as_operation().parent_op().unwrap(), + &branch.as_operation().as_operation_ref() + ), + "expected `branch` to be the parent op of `terminator`" + ); + + let num_operands = terminator.num_operands(); + let mut const_operands = + SmallVec::<[Option>; 2]>::with_capacity(num_operands); + const_operands.resize_with(num_operands, || None); + + let terminator_op = terminator.as_operation(); + let successors = terminator.get_successor_regions(&const_operands); + let successors = RegionSuccessorIter::new(terminator_op, successors); + + // All operands not forwarded to any successor. This set can be non-contiguous in the presence + // of multiple successors. + let mut unaccounted = bitvec![1; num_operands]; + + for successor in successors { + let inputs = successor.successor_inputs(); + let operands = terminator.get_successor_operands(*successor.branch_point()); + for (operand, input) in operands.forwarded().iter().zip(inputs.iter()) { + let operand = operand.borrow(); + let mut operand_lattice = get_lattice_element_mut::(operand.as_value_ref(), solver); + let point = ProgramPoint::after(terminator_op); + let input_lattice = get_lattice_element_for::(point, input, solver); + operand_lattice.meet(input_lattice.lattice()); + unaccounted.set(operand.index(), false); + } + } + + // Visit operands of the branch op not forwarded to the next region. (Like e.g. the boolean of + // `scf.conditional`) + for index in unaccounted.iter_ones() { + analysis.visit_branch_operand(&terminator_op.operands()[index].borrow(), solver); + } +} + +#[inline] +fn get_lattice_element_mut<'guard, A>( + value: ValueRef, + solver: &mut DataFlowSolver, +) -> AnalysisStateGuardMut<'guard, ::Lattice> +where + A: SparseBackwardDataFlowAnalysis, +{ + log::trace!("getting lattice for {value}"); + solver.get_or_create_mut::<_, _>(value) +} + +#[inline] +fn get_lattice_element_for<'guard, A>( + point: ProgramPoint, + value: ValueRef, + solver: &mut DataFlowSolver, +) -> AnalysisStateGuard<'guard, ::Lattice> +where + A: SparseBackwardDataFlowAnalysis, +{ + log::trace!("getting lattice for {value} at {point}"); + solver.require::<_, _>(value, point) +} + +fn get_lattice_elements_mut<'guard, A>( + values: OpOperandRange<'_>, + solver: &mut DataFlowSolver, +) -> SmallVec<[AnalysisStateGuardMut<'guard, ::Lattice>; 2]> +where + A: SparseBackwardDataFlowAnalysis, +{ + log::trace!("getting lattices for {:#?}", values.as_slice()); + let mut results = SmallVec::with_capacity(values.len()); + for value in values.iter() { + let lattice = solver.get_or_create_mut::<_, _>(value.borrow().as_value_ref()); + results.push(lattice); + } + results +} + +/// Get the lattice elements for a range of values, and also set up dependencies so that the +/// analysis on the given ProgramPoint is re-invoked if any of the values change. +fn get_lattice_elements_for<'guard, A>( + point: ProgramPoint, + values: OpResultRange<'_>, + solver: &mut DataFlowSolver, +) -> SmallVec<[AnalysisStateGuard<'guard, ::Lattice>; 2]> +where + A: SparseBackwardDataFlowAnalysis, +{ + log::trace!("getting lattices for {:#?}", values.as_slice()); + let mut results = SmallVec::with_capacity(values.len()); + for value in values.iter() { + let lattice = solver.require(value.borrow().as_value_ref(), point); + results.push(lattice); + } + results +} diff --git a/hir-analysis/src/sparse/forward.rs b/hir-analysis/src/sparse/forward.rs new file mode 100644 index 000000000..fb1701c01 --- /dev/null +++ b/hir-analysis/src/sparse/forward.rs @@ -0,0 +1,586 @@ +use midenc_hir::{ + formatter::DisplayValues, traits::BranchOpInterface, Block, BlockArgument, BlockArgumentRange, + CallOpInterface, CallableOpInterface, EntityWithId, Forward, OpOperandRange, OpResult, + OpResultRange, Operation, ProgramPoint, RegionBranchOpInterface, RegionBranchPoint, + RegionBranchTerminatorOpInterface, RegionSuccessor, Report, SmallVec, Spanned, StorableEntity, + SuccessorOperands, ValueRef, +}; + +use super::{SparseDataFlowAnalysis, SparseLattice}; +use crate::{ + analyses::dce::{CfgEdge, Executable, PredecessorState}, + AnalysisState, AnalysisStateGuard, AnalysisStateGuardMut, BuildableAnalysisState, + DataFlowSolver, +}; + +/// The base trait for sparse forward data-flow analyses. +/// +/// A sparse analysis implements a transfer function on operations from the lattices of the operands +/// to the lattices of the results. This analysis will propagate lattices across control-flow edges +/// and the callgraph using liveness information. +/// +/// Visiting a program point in sparse forward data-flow analysis will invoke the transfer function +/// of the operation preceding the program point. Visiting a program point at the begining of block +/// will visit the block itself. +#[allow(unused_variables)] +pub trait SparseForwardDataFlowAnalysis: 'static { + type Lattice: BuildableAnalysisState + SparseLattice; + + fn debug_name(&self) -> &'static str { + core::any::type_name::() + } + + /// The operation transfer function. + /// + /// Given the operand lattices, this function is expected to set the result lattices. + fn visit_operation( + &self, + op: &Operation, + operands: &[AnalysisStateGuard<'_, Self::Lattice>], + results: &mut [AnalysisStateGuardMut<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) -> Result<(), Report>; + + /// The transfer function for calls to external functions. + fn visit_external_call( + &self, + call: &dyn CallOpInterface, + arguments: &[AnalysisStateGuard<'_, Self::Lattice>], + results: &mut [AnalysisStateGuardMut<'_, Self::Lattice>], + solver: &mut DataFlowSolver, + ) { + set_all_to_entry_states(self, results); + } + + /// Given an operation with region control-flow, the lattices of the operands, and a region + /// successor, compute the lattice values for block arguments that are not accounted for by the + /// branching control flow (ex. the bounds of loops). + /// + /// By default, this method marks all such lattice elements as having reached a pessimistic + /// fixpoint. + /// + /// `first_index` is the index of the first element of `arguments` that is set by control-flow. + fn visit_non_control_flow_arguments( + &self, + op: &Operation, + successor: &RegionSuccessor<'_>, + arguments: &mut [AnalysisStateGuardMut<'_, Self::Lattice>], + first_index: usize, + solver: &mut DataFlowSolver, + ) { + let (leading, rest) = arguments.split_at_mut(first_index); + let (_, trailing) = rest.split_at_mut(successor.successor_inputs().len()); + set_all_to_entry_states(self, leading); + set_all_to_entry_states(self, trailing); + } + + /// Set the given lattice element(s) at control flow entry point(s). + fn set_to_entry_state(&self, lattice: &mut AnalysisStateGuardMut<'_, Self::Lattice>); +} + +pub fn set_all_to_entry_states( + analysis: &A, + lattices: &mut [AnalysisStateGuardMut<'_, ::Lattice>], +) where + A: ?Sized + SparseForwardDataFlowAnalysis, +{ + for lattice in lattices { + analysis.set_to_entry_state(lattice); + } +} + +/// Recursively initialize the analysis on nested operations and blocks. +pub(super) fn initialize_recursively( + analysis: &SparseDataFlowAnalysis, + op: &Operation, + solver: &mut DataFlowSolver, +) -> Result<(), Report> +where + A: SparseForwardDataFlowAnalysis, +{ + // Initialize the analysis by visiting every owner of an SSA value (all operations and blocks). + visit_operation(analysis, op, solver)?; + + if !op.regions().is_empty() { + log::trace!(target: analysis.debug_name(), "visiting regions of '{}'", op.name()); + for region in op.regions() { + if region.is_empty() { + continue; + } + + for block in region.body() { + { + let point = ProgramPoint::at_start_of(block.as_block_ref()); + let exec = solver.get_or_create::(point); + log::trace!( + target: analysis.debug_name(), "subscribing to changes in liveness for {block} (current={exec})", + ); + AnalysisStateGuard::subscribe(&exec, analysis); + } + + visit_block(analysis, &block, solver); + + log::trace!(target: analysis.debug_name(), "visiting body of {} top-down", block.id()); + for op in block.body() { + initialize_recursively(analysis, &op, solver)?; + } + } + } + } + + Ok(()) +} + +/// Visit an operation. If this is a call operation or an operation with +/// region control-flow, then its result lattices are set accordingly. +/// Otherwise, the operation transfer function is invoked. +pub(super) fn visit_operation( + analysis: &SparseDataFlowAnalysis, + op: &Operation, + solver: &mut DataFlowSolver, +) -> Result<(), Report> +where + A: SparseForwardDataFlowAnalysis, +{ + log::trace!(target: analysis.debug_name(), "visiting operation {op}"); + + // Exit early on operations with no results. + if !op.has_results() { + log::debug!(target: analysis.debug_name(), "skipping analysis for {}: op has no results", op.name()); + return Ok(()); + } + + // If the containing block is not executable, bail out. + if op.parent().is_some_and(|block| { + !solver + .get_or_create_mut::(ProgramPoint::at_start_of(block)) + .is_live() + }) { + log::trace!(target: analysis.debug_name(), "skipping analysis for op in dead/non-executable block: {}", ProgramPoint::before(op)); + return Ok(()); + } + + // Get the result lattices. + log::trace!( + target: analysis.debug_name(), + "getting/initializing result lattices for {}", + DisplayValues::new(op.results().all().into_iter()) + ); + let mut result_lattices = get_lattice_elements_mut::(op.results().all(), solver); + + // The results of a region branch operation are determined by control-flow. + if let Some(branch) = op.as_trait::() { + let point = ProgramPoint::after(op); + visit_region_successors( + analysis, + point, + branch, + RegionBranchPoint::Parent, + &mut result_lattices, + solver, + ); + return Ok(()); + } + + // Grab the lattice elements of the operands. + let mut operand_lattices = SmallVec::<[_; 4]>::with_capacity(op.num_operands()); + // TODO: Visit unique operands first to initialize analysis state and subscribe to changes + for operand in op.operands().iter() { + let operand = { operand.borrow().as_value_ref() }; + log::trace!(target: analysis.debug_name(), "getting/initializing operand lattice for {operand}"); + let operand_lattice = get_lattice_element::(operand, solver); + log::trace!( + target: analysis.debug_name(), "subscribing to changes of operand {operand} (current={operand_lattice:#?})", + ); + AnalysisStateGuard::subscribe(&operand_lattice, analysis); + operand_lattices.push(operand_lattice); + } + + if let Some(call) = op.as_trait::() { + log::trace!(target: analysis.debug_name(), "{} is a call operation", op.name()); + // If the call operation is to an external function, attempt to infer the results from the + // call arguments. + // + // TODO: resolve_in_symbol_table + let callable = call.resolve(); + let callable = callable.as_ref().map(|c| c.borrow()); + let callable = callable + .as_ref() + .and_then(|c| c.as_symbol_operation().as_trait::()); + if !solver.config().is_interprocedural() + || callable.is_some_and(|c| c.get_callable_region().is_none()) + { + log::trace!(target: analysis.debug_name(), "callee {} is external", call.callable_for_callee()); + analysis.visit_external_call(call, &operand_lattices, &mut result_lattices, solver); + return Ok(()); + } + + // Otherwise, the results of a call operation are determined by the callgraph. + log::trace!(target: analysis.debug_name(), "resolved callee as {}", call.callable_for_callee()); + let return_point = ProgramPoint::after(op); + log::trace!(target: analysis.debug_name(), "getting/initializing predecessor state at {return_point}"); + let predecessors = solver + .require::(ProgramPoint::after(call.as_operation()), return_point); + log::trace!(target: analysis.debug_name(), "found {} known predecessors", predecessors.known_predecessors().len()); + + // If not all return sites are known, then conservatively assume we can't reason about the + //data-flow. + if !predecessors.all_predecessors_known() { + log::trace!(target: analysis.debug_name(), "not all predecessors are known - setting result lattices to entry state"); + set_all_to_entry_states(analysis, &mut result_lattices); + return Ok(()); + } + + let current_point = ProgramPoint::after(op); + log::trace!(target: analysis.debug_name(), "joining lattices from all call site predecessors at {current_point}"); + for predecessor in predecessors.known_predecessors() { + for (operand, result_lattice) in + predecessor.borrow().operands().all().iter().zip(result_lattices.iter_mut()) + { + let operand_lattice = get_lattice_element_for::( + current_point, + operand.borrow().as_value_ref(), + solver, + ); + result_lattice.join(operand_lattice.lattice()); + } + } + + return Ok(()); + } + + // Invoke the operation transfer function. + analysis.visit_operation(op, &operand_lattices, &mut result_lattices, solver) +} + +/// Visit a block to compute the lattice values of its arguments. If this is +/// an entry block, then the argument values are determined from the block's +/// "predecessors" as set by `PredecessorState`. The predecessors can be +/// region terminators or callable callsites. Otherwise, the values are +/// determined from block predecessors. +pub(super) fn visit_block( + analysis: &SparseDataFlowAnalysis, + block: &Block, + solver: &mut DataFlowSolver, +) where + A: SparseForwardDataFlowAnalysis, +{ + // Exit early on blocks with no arguments. + if !block.has_arguments() { + log::debug!(target: analysis.debug_name(), "skipping {block}: no block arguments to process"); + return; + } + + // If the block is not executable, bail out. + if !solver + .get_or_create_mut::(ProgramPoint::at_start_of(block)) + .is_live() + { + log::debug!(target: analysis.debug_name(), "skipping {block}: it is dead/non-executable"); + return; + } + + // Get the argument lattices. + let mut arg_lattices = SmallVec::<[_; 4]>::with_capacity(block.num_arguments()); + for argument in block.arguments().iter().copied() { + log::trace!(target: analysis.debug_name(), "getting/initializing lattice for {argument}"); + let lattice = get_lattice_element_mut::(argument as ValueRef, solver); + arg_lattices.push(lattice); + } + + // The argument lattices of entry blocks are set by region control-flow or the callgraph. + let current_point = ProgramPoint::at_start_of(block); + if block.is_entry_block() { + log::trace!(target: analysis.debug_name(), "{block} is a region entry block"); + // Check if this block is the entry block of a callable region. + let parent_op = block.parent_op().unwrap(); + let parent_op = parent_op.borrow(); + let callable = parent_op.as_trait::(); + if callable.is_some_and(|c| c.get_callable_region() == block.parent()) { + let callable = callable.unwrap(); + log::trace!( + target: analysis.debug_name(), + "{block} is the entry of a callable region - analyzing call sites", + ); + let callsites = solver.require::( + ProgramPoint::after(callable.as_operation()), + current_point, + ); + log::trace!(target: analysis.debug_name(), "found {} call sites", callsites.known_predecessors().len()); + + // If not all callsites are known, conservatively mark all lattices as having reached + // their pessimistic fixpoints. + if !callsites.all_predecessors_known() || !solver.config().is_interprocedural() { + log::trace!( + target: analysis.debug_name(), + "not all call sites are known - setting arguments to entry state" + ); + return set_all_to_entry_states(analysis, &mut arg_lattices); + } + + log::trace!(target: analysis.debug_name(), "joining lattices from all call site predecessors at {current_point}"); + for callsite in callsites.known_predecessors() { + let callsite = callsite.borrow(); + let call = callsite.as_trait::().unwrap(); + for (arg, arg_lattice) in call.arguments().iter().zip(arg_lattices.iter_mut()) { + let arg = arg.borrow().as_value_ref(); + let input = get_lattice_element_for::(current_point, arg, solver); + let change_result = arg_lattice.join(input.lattice()); + log::debug!(target: analysis.debug_name(), "updated lattice for {arg} to {arg_lattice:#?}: {change_result}"); + } + } + + return; + } + + // Check if the lattices can be determined from region control flow. + if let Some(branch) = parent_op.as_trait::() { + log::trace!( + target: analysis.debug_name(), + "{block} is the entry of an region control flow op", + ); + return visit_region_successors( + analysis, + current_point, + branch, + RegionBranchPoint::Child(block.parent().unwrap()), + &mut arg_lattices, + solver, + ); + } + + // Otherwise, we can't reason about the data-flow. + log::trace!(target: analysis.debug_name(), "unable to reason about control flow for {block}"); + let successor = RegionSuccessor::new( + RegionBranchPoint::Child(block.parent().unwrap()), + OpOperandRange::empty(), + ); + return analysis.visit_non_control_flow_arguments( + &parent_op, + &successor, + &mut arg_lattices, + 0, + solver, + ); + } + + // Iterate over the predecessors of the non-entry block. + log::trace!(target: analysis.debug_name(), "visiting predecessors of non-entry block {block}"); + for pred in block.predecessors() { + let predecessor = pred.predecessor().borrow(); + log::trace!(target: analysis.debug_name(), "visiting control flow edge {predecessor} -> {block} (index {})", pred.index); + + // If the edge from the predecessor block to the current block is not live, bail out. + let edge_executable = { + let anchor = solver.create_lattice_anchor(CfgEdge::new( + predecessor.as_block_ref(), + block.as_block_ref(), + predecessor.span(), + )); + let lattice = solver.get_or_create::(anchor); + log::trace!( + target: analysis.debug_name(), "subscribing to changes of control flow edge {anchor} (current={lattice})", + ); + lattice + }; + AnalysisStateGuard::subscribe(&edge_executable, analysis); + if !edge_executable.is_live() { + log::trace!(target: analysis.debug_name(), "skipping {predecessor}: control flow edge is dead/non-executable"); + continue; + } + + // Check if we can reason about the data-flow from the predecessor. + let terminator = pred.owner; + let terminator = terminator.borrow(); + if let Some(branch) = terminator.as_trait::() { + log::trace!( + target: analysis.debug_name(), + "joining operand lattices for successor {} of {predecessor}", + pred.index + ); + let operands = branch.get_successor_operands(pred.index()); + for (idx, lattice) in arg_lattices.iter_mut().enumerate() { + if let Some(operand) = + operands.get(idx).and_then(|operand| operand.into_value_ref()) + { + log::trace!(target: analysis.debug_name(), "joining lattice for {} with {operand}", lattice.anchor()); + let operand_lattice = + get_lattice_element_for::(current_point, operand, solver); + let change_result = lattice.join(operand_lattice.lattice()); + log::debug!(target: analysis.debug_name(), "updated lattice for {} to {:#?}: {change_result}", lattice.anchor(), lattice); + } else { + // Conservatively consider internally produced arguments as entry points. + log::trace!(target: analysis.debug_name(), "setting lattice for internally-produced argument {} to entry state", lattice.anchor()); + analysis.set_to_entry_state(lattice); + } + } + } else { + log::trace!( + target: analysis.debug_name(), + "unable to reason about predecessor control flow - setting argument lattices to \ + entry state" + ); + return set_all_to_entry_states(analysis, &mut arg_lattices); + } + } +} + +/// Visit a program point `point` with predecessors within a region branch +/// operation `branch`, which can either be the entry block of one of the +/// regions or the parent operation itself, and set either the argument or +/// parent result lattices. +fn visit_region_successors( + analysis: &SparseDataFlowAnalysis, + point: ProgramPoint, + branch: &dyn RegionBranchOpInterface, + successor: RegionBranchPoint, + lattices: &mut [AnalysisStateGuardMut<'_, ::Lattice>], + solver: &mut DataFlowSolver, +) where + A: SparseForwardDataFlowAnalysis, +{ + log::trace!(target: analysis.debug_name(), "getting/initializing predecessor state for {point}"); + let predecessors = solver.require::(point, point); + assert!(predecessors.all_predecessors_known(), "unexpected unresolved region successors"); + + log::debug!(target: analysis.debug_name(), "joining the lattices from {} known predecessors", predecessors.known_predecessors().len()); + for op in predecessors.known_predecessors().iter().copied() { + let operation = op.borrow(); + + // Get the incoming successor operands. + let mut operands = None; + + // Check if the predecessor is the parent op. + let predecessor_is_parent = op == branch.as_operation_ref(); + log::debug!(target: analysis.debug_name(), "analyzing predecessor {} (is parent = {predecessor_is_parent})", ProgramPoint::after(&*operation)); + if predecessor_is_parent { + operands = Some(branch.get_entry_successor_operands(successor)); + } else if let Some(region_terminator) = + operation.as_trait::() + { + // Otherwise, try to deduce the operands from a region return-like op. + operands = Some(region_terminator.get_successor_operands(successor)); + } + + let Some(operands) = operands else { + // We can't reason about the data-flow + log::debug!(target: analysis.debug_name(), "unable to reason about predecessor dataflow - setting to entry state"); + return set_all_to_entry_states(analysis, lattices); + }; + + let inputs = predecessors.successor_inputs(&op); + assert_eq!( + inputs.len(), + operands.len(), + "expected the same number of successor inputs as operands" + ); + + let mut first_index = 0; + if inputs.len() != lattices.len() { + log::trace!(target: analysis.debug_name(), "successor inputs and argument lattices have different lengths: {} vs {}", inputs.len(), lattices.len()); + if !point.is_at_block_start() { + if !inputs.is_empty() { + let input = inputs[0].borrow(); + first_index = input.downcast_ref::().unwrap().index(); + } + let results = branch.results().all(); + let results = OpResultRange::new( + first_index..(first_index + inputs.len()), + results.as_slice(), + ); + let successor = RegionSuccessor::new(RegionBranchPoint::Parent, results); + analysis.visit_non_control_flow_arguments( + branch.as_operation(), + &successor, + lattices, + first_index, + solver, + ); + } else { + if !inputs.is_empty() { + let input = inputs[0].borrow(); + first_index = input.downcast_ref::().unwrap().index(); + } + let region = point.block().unwrap().parent().unwrap(); + let region_borrowed = region.borrow(); + let entry = region_borrowed.entry(); + let successor_arg_range = BlockArgumentRange::new( + first_index..(first_index + inputs.len()), + entry.arguments(), + ); + let successor = + RegionSuccessor::new(RegionBranchPoint::Child(region), successor_arg_range); + analysis.visit_non_control_flow_arguments( + branch.as_operation(), + &successor, + lattices, + first_index, + solver, + ); + } + } + + for (operand, lattice) in + operands.forwarded().iter().zip(lattices.iter_mut().skip(first_index)) + { + let operand = operand.borrow().as_value_ref(); + log::trace!(target: analysis.debug_name(), "joining lattice for {} with {operand}", lattice.anchor()); + let operand_lattice = get_lattice_element_for::(point, operand, solver); + let change_result = lattice.join(operand_lattice.lattice()); + log::debug!(target: analysis.debug_name(), "updated lattice for {} to {:#?}: {change_result}", lattice.anchor(), lattice); + } + } +} + +#[inline] +fn get_lattice_element<'guard, A>( + value: ValueRef, + solver: &mut DataFlowSolver, +) -> AnalysisStateGuard<'guard, ::Lattice> +where + A: SparseForwardDataFlowAnalysis, +{ + let lattice: AnalysisStateGuard<'guard, ::Lattice> = + solver.get_or_create::<_, _>(value); + lattice +} + +#[inline] +fn get_lattice_element_mut<'guard, A>( + value: ValueRef, + solver: &mut DataFlowSolver, +) -> AnalysisStateGuardMut<'guard, ::Lattice> +where + A: SparseForwardDataFlowAnalysis, +{ + let lattice: AnalysisStateGuardMut<'guard, ::Lattice> = + solver.get_or_create_mut::<_, _>(value); + lattice +} + +#[inline] +fn get_lattice_element_for<'guard, A>( + point: ProgramPoint, + value: ValueRef, + solver: &mut DataFlowSolver, +) -> AnalysisStateGuard<'guard, ::Lattice> +where + A: SparseForwardDataFlowAnalysis, +{ + solver.require::<_, _>(value, point) +} + +fn get_lattice_elements_mut<'guard, A>( + values: OpResultRange<'_>, + solver: &mut DataFlowSolver, +) -> SmallVec<[AnalysisStateGuardMut<'guard, ::Lattice>; 2]> +where + A: SparseForwardDataFlowAnalysis, +{ + let mut results = SmallVec::with_capacity(values.len()); + for value in values.iter().copied() { + let lattice = solver.get_or_create_mut::<_, _>(value as ValueRef); + results.push(lattice); + } + results +} diff --git a/hir-analysis/src/sparse/lattice.rs b/hir-analysis/src/sparse/lattice.rs new file mode 100644 index 000000000..8e0d6d766 --- /dev/null +++ b/hir-analysis/src/sparse/lattice.rs @@ -0,0 +1,22 @@ +use crate::{AnalysisState, ChangeResult}; + +/// A [SparseLattice] represents some analysis state attached to a specific value. +/// +/// It is propagated through the IR by sparse data-flow analysis. +#[allow(unused_variables)] +pub trait SparseLattice: AnalysisState + core::fmt::Debug { + type Lattice; + + /// Get the underlying lattice value + fn lattice(&self) -> &Self::Lattice; + + /// Join `rhs` with `self`, returning whether or not a change was made + fn join(&mut self, rhs: &Self::Lattice) -> ChangeResult { + ChangeResult::Unchanged + } + + /// Meet `rhs` with `self`, returning whether or not a change was made + fn meet(&mut self, rhs: &Self::Lattice) -> ChangeResult { + ChangeResult::Unchanged + } +} diff --git a/hir-analysis/src/spill.rs b/hir-analysis/src/spill.rs deleted file mode 100644 index e497e161a..000000000 --- a/hir-analysis/src/spill.rs +++ /dev/null @@ -1,1748 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet, VecDeque}; - -use cranelift_entity::{entity_impl, EntityRef}; -use midenc_hir::{ - adt::{SmallMap, SmallSet}, - pass::{Analysis, AnalysisManager, AnalysisResult}, - Block, BranchInfo, Function, InsertionPoint, Inst, ProgramPoint, SourceSpan, Type, Value, -}; -use midenc_session::Session; -use smallvec::SmallVec; - -use crate::{ - liveness::LOOP_EXIT_DISTANCE, BlockPredecessor, ControlFlowGraph, DominatorTree, - LivenessAnalysis, Loop, LoopAnalysis, -}; - -/// This analysis is responsible for simulating the state of the operand stack at each program -/// point, taking into account the results of liveness analysis, and computing whether or not to -/// insert spills/reloads of values which would cause the operand stack depth to exceed 16 elements, -/// the maximum addressable depth. -/// -/// The algorithm here is based on the paper [_Register Spilling and Live-Range Splitting for -/// SSA-form Programs_ by Matthias Braun and Sebastian Hack](https://pp.ipd.kit.edu/uploads/publikationen/braun09cc.pdf), -/// which also happens to describe the algorithm we based our liveness analysis on. While the broad -/// strokes are the same, various modifications/tweaks to the algorithm they describe are needed in -/// order to be suitable for our use case. In particular, we must distinguish between the SSA values -/// which uniquely identify each operand, from the raw elements on the operand stack which represent -/// those values. The need for spills is determined solely on the low-level operand stack -/// representation, _not_ the number of live SSA values (although there can be a correspondence in -/// cases where each SSA value has an effective size of 1 stack element). As this is a type- -/// sensitive analysis, it differs from the algorithm in the paper, which is based on an assumption -/// that all operands are machine-word sized, and thus each value only requires a single register to -/// hold. -/// -/// Despite these differences, the overall approach is effectively identical. We still are largely -/// concerned with the SSA values, the primary difference being that we are computing spills based -/// on the raw operand stack state, rather than virtual register pressure as described in the paper. -/// As a result, the number of spills needed at a given program point are not necessarily 1:1, as -/// it may be necessary to spill multiple values in order to free sufficient capacity on the operand -/// stack to hold the required operands; or conversely, we may evict operands that free up more -/// operand stack space than is strictly needed due to the size of those values. -/// -/// The general algorithm, once liveness has been computed (see [LivenessAnalysis] for more -/// details), can be summarized as follows: -/// -/// In reverse CFG postorder, visit each block B, and: -/// -/// 1. Determine initialization of W at entry to B (W^entry). W is the set of operands on the -/// operand stack. From this we are able to determine what, if any, actions are required to -/// keep |W| <= K where K is the maximum allowed operand stack depth. -/// -/// 2. Determine initialization of S at entry to B (S^entry). S is the set of values which have -/// been spilled up to that point in the program. We can use S to determine whether or not -/// to actually emit a spill instruction when a spill is necessary, as due to the SSA form of -/// the program, every value has a single definition, so we need only emit a spill for a given -/// value once. -/// -/// 3. For each predecessor P of B, determine what, if any, spills and/or reloads are needed to -/// ensure that W and S are consistent regardless of what path is taken to reach B, and that -/// |W| <= K. Depending on whether P has multiple successors, it may be necessary to split the -/// edge between P and B, so that the emitted spills/reloads only apply along that edge. -/// -/// 4. Perform the MIN algorithm on B, which is used to determine spill/reloads at each instruction -/// in the block. MIN is designed to make optimal decisions about what to spill, so as to -/// minimize the number of spill/reload-related instructions executed by any given program -/// execution trace. It does this by using the next-use distance associated with values in W, -/// which is computed as part of our liveness analysis. Unlike traditional liveness analysis -/// which only tracks what is live at a given program point, next-use distances not only tell -/// you whether a value is live or dead, but how far away the next use of that value is. MIN -/// uses this information to select spill candidates from W furthest away from the current -/// instruction; and on top of this we also add an additional heuristic based on the size of -/// each candidate as represented on the operand stack. Given two values with equal next-use -/// distances, the largest candidates are spilled first, allowing us to free more operand stack -/// space with fewer spills. -/// -/// The MIN algorithm works as follows: -/// -/// 1. Starting at the top of the block, B, W is initialized with the set W^entry(B), and S with -/// S^entry(B) -/// -/// 2. For each instruction, I, in the block, update W and S according to the needs of I, while -/// attempting to preserve as many live values in W as possible. Each instruction fundamentally -/// requires that: On entry, W contains all the operands of I; on exit, W contains all of the -/// results of I; and that at all times, |W| <= K. This means that we may need to reload operands -/// of I that are not in W (because they were spilled), and we may need to spill values from W to -/// ensure that the stack depth <= K. The specific effects for I are computed as follows: -/// a. All operands of I not in W, must be reloaded in front of I, thus adding them to W. -/// This is also one means by which values are added to S, as by definition a reload -/// implies that the value must have been spilled, or it would still be in W. Thus, when -/// we emit reloads, we also ensure that the reloaded value is added to S. -/// b. If a reload would cause |W| to exceed K, we must select values in W to spill. Candidates -/// are selected from the set of values in W which are not operands of I, prioritized first -/// by greatest next-use distance, then by stack consumption, as determined by the -/// representation of the value type on the operand stack. -/// c. By definition, none of I's results can be in W directly in front of I, so we must -/// always ensure that W has sufficient capacity to hold all of I's results. The analysis -/// of sufficient capacity is somewhat subtle: -/// - Any of I's operands that are live-at I, but _not_ live-after I, do _not_ count towards -/// the operand stack usage when calculating available capacity for the results. This is -/// because those operands will be consumed, and their space can be re-used for results. -/// - Any of I's operands that are live-after I, however, _do_ count towards the stack usage -/// - If W still has insufficient capacity for all the results, we must select candidates -/// to spill. Candidates are the set of values in W which are either not operands of I, -/// or are operands of I which are live-after I. Selection criteria is the same as before. -/// -/// d. Operands of I which are _not_ live-after I, are removed from W on exit from I, thus W -/// reflects only those values which are live at the current program point. -/// e. Lastly, when we select a value to be spilled, we only emit spill instructions for those -/// values which are not yet in S, i.e. they have not yet been spilled; and which have a -/// finite next-use distance, i.e. the value is still live. If a value to be spilled _is_ -/// in S and/or is unused after that point in the program, we can elide the spill entirely. -/// -/// What we've described above represents both the analysis itself, as well as the effects of -/// applying that analysis to the actual control flow graph of the function. However, doing so -/// introduces a problem that must be addressed: SSA-form programs can only have a single definition -/// of each value, but by introducing spills (and consequently, reloads of the spilled values), we -/// have introduced new definitions of those values - each reload constitutes a new definition. -/// As a result, our program is no longer in SSA form, and we must restore that property in order -/// to proceed with compilation. -/// -/// **NOTE:** The way that we represent reloads doesn't _literally_ introduce multiple definitions -/// of a given [Value], our IR does not permit representing that. Instead, we represent reloads as -/// an instruction which takes the spilled SSA value we want to reload as an argument, and produces -/// a new SSA value representing the reloaded spill. As a result of this representation, our program -/// always remains technically in SSA form, but the essence of the problem remains the same: When a -/// value is spilled, its live range is terminated; a reload effectively brings the spilled value -/// back to life, starting a new live range. Thus references to the spilled value which are now -/// dominated by a reload in the control flow graph, are no longer semantically correct - they must -/// be rewritten to reference the nearest dominating definition. -/// -/// Restoring SSA form is not the responsibility of this analysis, however I will briefly describe -/// the method here, while you have the context at hand. The obvious assumption would be that we -/// simply treat each reload as a new SSA value, and update any uses of the original value with the -/// nearest dominating definition. The way we represent reloads in HIR already does the first step -/// for us, however there is a subtle problem with the second part: join points in the control flow -/// graph. Consider the following: -/// -/// ```text,ignore -/// (block 0 (param v0) (param v1) -/// (cond_br v1 (block 1) (block 2))) -/// -/// (block 1 -/// (spill v0) -/// ... -/// (let v2 (reload v0)) ; here we've assigned the reload of v0 a new SSA value -/// (br (block 3))) -/// -/// (block 2 -/// ... -/// (br (block 3))) -/// -/// (block 3 -/// (ret v2)) ; here we've updated a v0 reference to the nearest definition -/// ``` -/// -/// Above, control flow branches in one of two directions from the entry block, and along one of -/// those branches `v0` is spilled and later reloaded. Control flow joins again in the final block -/// where `v0` is returned. We attempted to restore the program to SSA form as described above, -/// first by assigning reloads a new SSA value, then by finding all uses of the spilled value and -/// rewriting those uses to reference the nearest dominating definition. -/// -/// Because the use of `v0` in block 3 is dominated by the reload in block 1, it is rewritten to -/// reference `v2` instead. The problem with that is obvious - the reload in block 1 does not -/// _strictly_ dominate the use in block 3, i.e. there are paths through the function which can -/// reach block 3 without passing through block 1 first, and `v2` will be undefined along those -/// paths! -/// -/// However this problem also has an obvious solution: introduce a new block parameter in block 3 -/// to represent the appropriate definition of `v0` that applies based on the predecessor used to -/// reach block 3. This ensures that the use in block 3 is strictly dominated by an appropriate -/// definition. -/// -/// So now that we've understood the problem with the naive approach, and the essence of the -/// solution to that particular problem, we can walk through the generalized solution that can be -/// used to reconstruct SSA form for any program we can represent in our IR. -/// -/// 1. Given the set of spilled values, S, visit the dominance tree in postorder (bottom-up) -/// 2. In each block, working towards the start of the block from the end, visit each instruction -/// until one of the following occurs: -/// a. We find a use of a value in S. We append the use to the list of other uses of that value -/// which are awaiting a rewrite while we search for the nearest dominating definition. -/// b. We find a reload of a value in S. This reload is, by construction, the nearest dominating -/// definition for all uses of the reloaded value that we have found so far. We rewrite all of -/// those uses to reference the reloaded value, and remove them from the list. -/// c. We find the original definition of a value in S. This is similar to what happens when we -/// find a reload, except no rewrite is needed, so we simply remove all pending uses of that -/// value from the list. -/// d. We reach the top of the block. Note that block parameters are treated as definitions, so -/// those are handled first as described in the previous point. However, an additional step -/// is required here: If the current block is in the iterated dominance frontier for S, i.e. -/// for any value in S, the current block is in the dominance frontier of the original -/// definition of that value - then for each such value for which we have found at least one -/// use, we must add a new block parameter representing that value; rewrite all uses we have -/// found so far to use the block parameter instead; remove those uses from the list; and -/// lastly, rewrite the branch instruction in each predecessor to pass the value as a new block -/// argument when branching to the current block. -/// 3. When we start processing a block, the union of the set of unresolved uses found in each -/// successor, forms the initial state of that set for the current block. If a block has no -/// successors, then the set is initially empty. This is how we propagate uses up the dominance -/// tree until we find an appropriate definition. Since we ensure that block parameters are added -/// along the dominance frontier for each spilled value, we guarantee that the first definition -/// we reach always strictly dominates the uses we have propagated to that point. -/// -/// NOTE: A nice side effect of this algorithm is that any reloads we reach for which we have -/// no uses, are dead and can be eliminated. Similarly, a reload we never reach must also be -/// dead code - but in practice that won't happen, since we do not visit unreachable blocks -/// during the spill analysis anyway. -#[derive(Debug, Default, Clone)] -pub struct SpillAnalysis { - // The set of control flow edges that must be split to accommodate spills/reloads. - pub splits: Vec, - // The set of values that have been spilled - pub spilled: BTreeSet, - // The spills themselves - pub spills: Vec, - // The set of instructions corresponding to the reload of a spilled value - pub reloads: Vec, - // The set of operands in registers on entry to a given block - w_entries: BTreeMap>, - // The set of operands in registers on exit from a given block - w_exits: BTreeMap>, - // The set of operands that have been spilled so far, on exit from a given block - s_exits: BTreeMap>, -} - -/// The state of the W and S sets on entry to a given block -#[derive(Debug)] -struct BlockInfo { - block_id: Block, - w_entry: SmallSet, - s_entry: SmallSet, -} - -/// Uniquely identifies a computed split control flow edge in a [SpillAnalysis] -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Split(u32); -entity_impl!(Split, "split"); - -/// Metadata about a control flow edge which needs to be split in order to accommodate spills and/or -/// reloads along that edge. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct SplitInfo { - pub id: Split, - /// The destination block for the control flow edge being split - pub block: Block, - /// The predecessor, or origin, of the control flow edge being split - pub predecessor: BlockPredecessor, - /// The block representing the split, if materialized - pub split: Option, -} - -/// Uniquely identifies a computed spill in a [SpillAnalysis] -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Spill(u32); -entity_impl!(Spill, "spill"); - -/// Metadata about a computed spill -#[derive(Debug, Clone)] -pub struct SpillInfo { - pub id: Spill, - /// The point in the program where this spill should be placed - pub place: Placement, - /// The value to be spilled - pub value: Value, - /// The type of the spilled value - pub ty: Type, - /// The span associated with the source code that triggered the spill - pub span: SourceSpan, - /// The spill instruction, if materialized - pub inst: Option, -} - -/// Uniquely identifies a computed reload in a [SpillAnalysis] -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Reload(u32); -entity_impl!(Reload, "reload"); - -/// Metadata about a computed reload -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct ReloadInfo { - pub id: Reload, - /// The point in the program where this spill should be placed - pub place: Placement, - /// The spilled value to be reloaded - pub value: Value, - /// The span associated with the source code that triggered the spill - pub span: SourceSpan, - /// The reload instruction, if materialized - pub inst: Option, -} - -/// This enumeration represents a program location where a spill or reload operation should be -/// placed. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Placement { - /// A concrete location in the current program. - /// - /// The operation will be placed according to the semantics of the given [InsertionPoint] - At(InsertionPoint), - /// A pseudo-location, corresponding to the end of the block that will be materialized - /// to split the control flow edge represented by [Split]. - Split(Split), -} - -/// An [Operand] is a possibly-aliased [Value], combined with the size of that value on the -/// Miden operand stack. This extra information is used to not only compute whether or not we -/// need to spill values during execution of a function, and how to prioritize those spills; -/// but also to track aliases of a [Value] introduced when we insert reloads of a spilled value. -/// -/// Once a spilled value is reloaded, the SSA property of the CFG is broken, as we now have two -/// definitions of the same [Value]. To restore the SSA property, we have to assign the reloaded -/// value a new id, and then update all uses of the reloaded value dominated by that reload to -/// refer to the new [Value]. We use the `alias` field of [Operand] to track distinct reloads of -/// a given [Value] during the initial insertion of reloads. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Operand { - /// The SSA value of this operand - pub value: Value, - /// When an SSA value is used multiple times by an instruction, each use must be accounted for - /// on the operand stack in order to properly determine whether a spill is needed or not. - /// We assign each unique copy an integer id in the register file to ensure this. - pub alias: u16, - /// The size in elements on the operand stack required by this operand - pub size: u16, -} -impl core::borrow::Borrow for Operand { - #[inline(always)] - fn borrow(&self) -> &Value { - &self.value - } -} - -impl Operand { - pub fn new(value: Value, function: &Function) -> Self { - let size = u16::try_from(function.dfg.value_type(value).size_in_felts()) - .expect("invalid value type: ssa values cannot be larger than a word"); - Self { - value, - alias: 0, - size, - } - } -} - -/// The maximum number of operand stack slots which can be assigned without spills. -const K: usize = 16; - -impl Analysis for SpillAnalysis { - type Entity = Function; - - fn analyze( - function: &Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> AnalysisResult { - let cfg = analyses.get_or_compute(function, session)?; - let domtree = analyses.get_or_compute(function, session)?; - let loops = analyses.get_or_compute(function, session)?; - let liveness = analyses.get_or_compute(function, session)?; - SpillAnalysis::compute(function, &cfg, &domtree, &loops, &liveness) - } -} - -/// Queries -impl SpillAnalysis { - /// Returns true if at least one value must be spilled - pub fn has_spills(&self) -> bool { - !self.spills.is_empty() - } - - /// Returns the set of control flow edges that must be split to accommodate spills/reloads - pub fn splits(&self) -> &[SplitInfo] { - self.splits.as_slice() - } - - /// Same as [SpillAnalysis::splits], but as a mutable reference - pub fn splits_mut(&mut self) -> &mut [SplitInfo] { - self.splits.as_mut_slice() - } - - pub fn get_split(&self, split: Split) -> &SplitInfo { - &self.splits[split.index()] - } - - /// Returns the set of values which require spills - pub fn spilled(&self) -> &BTreeSet { - &self.spilled - } - - /// Returns true if `value` is spilled at some point - pub fn is_spilled(&self, value: &Value) -> bool { - self.spilled.contains(value) - } - - /// Returns true if `value` is spilled at the given program point (i.e. inserted before) - pub fn is_spilled_at(&self, value: Value, pp: impl Into) -> bool { - let place = match pp.into() { - ProgramPoint::Block(split_block) => { - match self.splits.iter().find(|split| split.split == Some(split_block)) { - Some(split) => Placement::Split(split.id), - None => Placement::At(InsertionPoint::after(split_block.into())), - } - } - pp @ ProgramPoint::Inst(_) => Placement::At(InsertionPoint::before(pp)), - }; - self.spills.iter().any(|info| info.value == value && info.place == place) - } - - /// Returns true if `value` will be spilled in the given split - pub fn is_spilled_in_split(&self, value: Value, split: Split) -> bool { - self.spills.iter().any(|info| { - info.value == value && matches!(info.place, Placement::Split(s) if s == split) - }) - } - - /// Returns the set of computed spills - pub fn spills(&self) -> &[SpillInfo] { - self.spills.as_slice() - } - - /// Same as [SpillAnalysis::spills], but as a mutable reference - pub fn spills_mut(&mut self) -> &mut [SpillInfo] { - self.spills.as_mut_slice() - } - - /// Returns true if `value` is reloaded at some point - pub fn is_reloaded(&self, value: &Value) -> bool { - self.reloads.iter().any(|info| &info.value == value) - } - - /// Returns true if `value` is reloaded at the given program point (i.e. inserted before) - pub fn is_reloaded_at(&self, value: Value, pp: impl Into) -> bool { - let place = match pp.into() { - ProgramPoint::Block(split_block) => { - match self.splits.iter().find(|split| split.split == Some(split_block)) { - Some(split) => Placement::Split(split.id), - None => Placement::At(InsertionPoint::after(split_block.into())), - } - } - pp @ ProgramPoint::Inst(_) => Placement::At(InsertionPoint::before(pp)), - }; - self.reloads.iter().any(|info| info.value == value && info.place == place) - } - - /// Returns true if `value` will be reloaded in the given split - pub fn is_reloaded_in_split(&self, value: Value, split: Split) -> bool { - self.reloads.iter().any(|info| { - info.value == value && matches!(info.place, Placement::Split(s) if s == split) - }) - } - - /// Returns the set of computed reloads - pub fn reloads(&self) -> &[ReloadInfo] { - self.reloads.as_slice() - } - - /// Same as [SpillAnalysis::reloads], but as a mutable reference - pub fn reloads_mut(&mut self) -> &mut [ReloadInfo] { - self.reloads.as_mut_slice() - } - - /// Returns the operands in W upon entry to `block` - pub fn w_entry(&self, block: &Block) -> &[Operand] { - self.w_entries[block].as_slice() - } - - /// Returns the operands in W upon exit from `block` - pub fn w_exit(&self, block: &Block) -> &[Operand] { - self.w_exits[block].as_slice() - } - - /// Returns the operands in S upon exit from `block` - pub fn s_exit(&self, block: &Block) -> &[Operand] { - self.s_exits[block].as_slice() - } -} - -/// Analysis -impl SpillAnalysis { - pub fn compute( - function: &Function, - cfg: &ControlFlowGraph, - domtree: &DominatorTree, - loops: &LoopAnalysis, - liveness: &LivenessAnalysis, - ) -> AnalysisResult { - let mut analysis = Self::default(); - - // Visit the blocks of the CFG in reverse postorder (top-down) - let mut block_q = VecDeque::::default(); - block_q.extend(domtree.cfg_postorder().iter().rev().copied()); - - // If a block has a predecessor which it dominates (i.e. control flow always flows through - // the block in question before the given predecessor), then we must defer computing spills - // and reloads for that edge until we have visited the predecessor. This map is used to - // track deferred edges for each block. - let mut deferred = Vec::<(Block, SmallVec<[Block; 2]>)>::default(); - - // This is used to track the set of instructions in the current block being analyzed - let mut inst_q = VecDeque::::with_capacity(32); - - while let Some(block_id) = block_q.pop_front() { - inst_q.clear(); - - // Compute W^entry(B) - compute_w_entry(block_id, &mut analysis, function, cfg, loops, liveness); - let w_entry = analysis.w_entries[&block_id].clone(); - - // Compute S^entry(B) - let mut s_entry = SmallSet::::default(); - for pred in cfg.pred_iter(block_id) { - if let Some(s_exitp) = analysis.s_exits.get(&pred.block) { - s_entry = s_entry.into_union(s_exitp); - } - } - s_entry = s_entry.into_intersection(&w_entry); - - let block_info = BlockInfo { - block_id, - w_entry, - s_entry, - }; - - // For each predecessor P of B, insert spills/reloads along the inbound control flow - // edge as follows: - // - // * All variables in W^entry(B) \ W^exit(P) need to be reloaded - // * All variables in (S^entry(B) \ S^exit(P)) ∩ W^exit(P) need to be spilled - // - // If a given predecessor has not been processed yet, skip P, and revisit the edge later - // after we have processed P. - // - // NOTE: Because W^exit(P) does not contain the block parameters for any given - // successor, as those values are renamed predecessor operands, some work must be done - // to determine the true contents of W^exit(P) for each predecessor/successor edge, and - // only then insert spills/reloads as described above. - let mut deferred_preds = SmallVec::<[Block; 2]>::default(); - for pred in cfg.pred_iter(block_id) { - // As soon as we need to start inserting spills/reloads, mark the function changed - compute_control_flow_edge_spills_and_reloads( - &mut analysis, - &block_info, - &pred, - &mut deferred_preds, - function, - cfg, - liveness, - ); - } - if !deferred_preds.is_empty() { - deferred.push((block_id, deferred_preds)); - } - - // We have our W and S sets for the entry of B, and we have inserted all spills/reloads - // needed on incoming control flow edges to ensure that the contents of W and S are the - // same regardless of which predecessor we reach B from. - // - // Now, we essentially repeat this process for each instruction I in B, i.e. we apply - // the MIN algorithm to B. As a result, we will also have computed the contents of W - // and S at the exit of B, which will be needed subsequently for the successors of B - // when we process them. - // - // The primary differences here, are that we: - // - // * Assume that if a reload is needed (not in W), that it was previously spilled (must - // be in S) - // * We do not issue spills for values that have already been spilled - // * We do not emit spill instructions for values which are dead, they are just dropped - // * We must spill from W to make room for operands and results of I, if there is - // insufficient space to hold the current contents of W + whatever operands of I we - // need to reload + the results of I that will be placed on the operand stack. We do - // so by spilling values with the greatest next-use distance first, preferring to - // spill larger values where we have an option. We also may factor in liveness - if an - // operand of I is dead after I, we do not need to count that operand when computing - // the operand stack usage for results (thus reusing the space of the operand for one - // or more results). - // * It is important to note that we must count _all_ uses of the same value towards the - // operand stack usage, unless the semantics of an instruction explicitly dictate that - // a specific operand pattern only requires a single copy on the operand stack. - // Currently that is not the case for any instructions, and we would prefer to be more - // conservative at this point anyway. - let mut w = block_info.w_entry; - let mut s = block_info.s_entry; - inst_q.extend(function.dfg.block_insts(block_id)); - while let Some(current_inst) = inst_q.pop_front() { - min(current_inst, &mut w, &mut s, &mut analysis, function, liveness); - } - - analysis.w_exits.insert(block_id, w); - analysis.s_exits.insert(block_id, s); - } - - // We've visited all blocks at least once, now we need to go back and insert - // spills/reloads along loopback edges, as we skipped those on the first pass - for (block_id, preds) in deferred { - // W^entry(B) - let w_entry = analysis.w_entries[&block_id].clone(); - - // Compute S^entry(B) - let mut s_entry = SmallSet::::default(); - for pred in cfg.pred_iter(block_id) { - s_entry = s_entry.into_union(&analysis.s_exits[&pred.block]); - } - s_entry = s_entry.into_intersection(&w_entry); - - let block_info = BlockInfo { - block_id, - w_entry, - s_entry, - }; - - // For each predecessor P of B, insert spills/reloads along the inbound control flow - // edge as follows: - // - // * All variables in W^entry(B) \ W^exit(P) need to be reloaded - // * All variables in (S^entry(B) \ S^exit(P)) ∩ W^exit(P) need to be spilled - // - // If a given predecessor has not been processed yet, skip P, and revisit the edge later - // after we have processed P. - let mut _defer = SmallVec::default(); - for pred in cfg.pred_iter(block_id) { - // Only visit predecessors that were deferred - if !preds.contains(&pred.block) { - continue; - } - - compute_control_flow_edge_spills_and_reloads( - &mut analysis, - &block_info, - &pred, - &mut _defer, - function, - cfg, - liveness, - ); - } - } - - Ok(analysis) - } - - pub fn set_materialized_split(&mut self, split: Split, block: Block) { - self.splits[split.index()].split = Some(block); - } - - pub fn set_materialized_spill(&mut self, spill: Spill, inst: Inst) { - self.spills[spill.index()].inst = Some(inst); - } - - pub fn set_materialized_reload(&mut self, reload: Reload, inst: Inst) { - self.reloads[reload.index()].inst = Some(inst); - } - - fn spill( - &mut self, - place: Placement, - value: Value, - span: SourceSpan, - function: &Function, - ) -> Spill { - let id = Spill::new(self.spills.len()); - let ty = function.dfg.value_type(value).clone(); - self.spilled.insert(value); - self.spills.push(SpillInfo { - id, - place, - value, - ty, - span, - inst: None, - }); - id - } - - fn reload(&mut self, place: Placement, value: Value, span: SourceSpan) -> Reload { - let id = Reload::new(self.reloads.len()); - self.reloads.push(ReloadInfo { - id, - place, - value, - span, - inst: None, - }); - id - } - - fn split(&mut self, block: Block, predecessor: BlockPredecessor) -> Split { - let id = Split::new(self.splits.len()); - self.splits.push(SplitInfo { - id, - block, - predecessor, - split: None, - }); - id - } -} - -fn compute_w_entry( - block_id: Block, - analysis: &mut SpillAnalysis, - function: &Function, - cfg: &ControlFlowGraph, - loops: &LoopAnalysis, - liveness: &LivenessAnalysis, -) { - if let Some(loop_id) = loops.is_loop_header(block_id) { - compute_w_entry_loop(block_id, loop_id, analysis, function, cfg, loops, liveness); - } else { - compute_w_entry_normal(block_id, analysis, function, cfg, liveness); - } -} - -fn compute_w_entry_normal( - block_id: Block, - analysis: &mut SpillAnalysis, - function: &Function, - cfg: &ControlFlowGraph, - liveness: &LivenessAnalysis, -) { - let mut freq = SmallMap::::default(); - let mut take = SmallSet::::default(); - let mut cand = SmallSet::::default(); - - // Block arguments are always in w_entry by definition - for arg in function.dfg.block_params(block_id) { - take.insert(Operand::new(*arg, function)); - } - - // TODO(pauls): We likely need to account for the implicit spilling that occurs when the - // operand stack space required by the function arguments exceeds K. In such cases, the W set - // contains the function parameters up to the first parameter that would cause the operand - // stack to overflow, all subsequent parameters are placed on the advice stack, and are assumed - // to be moved from the advice stack to locals in the same order as they appear in the function - // signature as part of the function prologue. Thus, the S set is preloaded with those values - // which were spilled in this manner. - // - // NOTE: It should never be the case that the set of block arguments consumes more than K - assert!( - take.iter().map(|o| o.size as usize).sum::() <= K, - "unhandled spills implied by function/block parameter list" - ); - - // If this is the entry block, the operands in w_entry are guaranteed to be equal to the set of - // function arguments, so we're done. - if block_id == function.dfg.entry_block() { - analysis.w_entries.insert(block_id, take); - return; - } - - for pred in cfg.pred_iter(block_id) { - for o in analysis.w_exits[&pred.block].iter().cloned() { - // Do not add candidates which are not live-after the predecessor - if liveness.is_live_after(&o.value, ProgramPoint::Block(pred.block)) { - *freq.entry(o).or_insert(0) += 1; - cand.insert(o); - } - } - } - - let num_preds = cfg.num_predecessors(block_id); - for (v, count) in freq.iter() { - if *count as usize == num_preds { - cand.remove(v); - take.insert(*v); - } - } - - // If there are paths to B containing > K values on the operand stack, this must be due to the - // successor arguments that are renamed on entry to B, remaining live in B, which implicitly - // requires copying so that both the block parameter and the source value are both live in B. - // - // However, in order to actually fail this assertion, it would have to be the case that all - // predecessors of this block are passing the same value as a successor argument, _and_ that the - // value is still live in this block. This would imply that the block parameter is unnecessary - // in the first place. - // - // Since that is extraordinarily unlikely to occur, and we want to catch any situations in which - // this assertion fails, we do not attempt to handle it automatically. - let taken = take.iter().map(|o| o.size as usize).sum::(); - assert!( - taken <= K, - "implicit operand stack overflow along incoming control flow edges of {block_id}" - ); - - let entry = ProgramPoint::Inst(function.dfg.block_insts(block_id).next().unwrap()); - - // Prefer to select candidates with the smallest next-use distance, otherwise all else being - // equal, choose to keep smaller values on the operand stack, and spill larger values, thus - // freeing more space when spills are needed. - let mut cand = cand.into_vec(); - cand.sort_by(|a, b| { - liveness - .next_use(&a.value, entry) - .cmp(&liveness.next_use(&b.value, entry)) - .then(a.size.cmp(&b.size)) - }); - - let mut available = K - taken; - let mut cand = cand.into_iter(); - while available > 0 { - if let Some(candidate) = cand.next() { - let size = candidate.size as usize; - if size <= available { - take.insert(candidate); - available -= size; - continue; - } - } - break; - } - - analysis.w_entries.insert(block_id, take); -} - -fn compute_w_entry_loop( - block_id: Block, - loop_id: Loop, - analysis: &mut SpillAnalysis, - function: &Function, - cfg: &ControlFlowGraph, - loops: &LoopAnalysis, - liveness: &LivenessAnalysis, -) { - let entry = ProgramPoint::Inst(function.dfg.block_insts(block_id).next().unwrap()); - - let params = function.dfg.block_params(block_id); - let mut alive = params - .iter() - .copied() - .map(|v| Operand::new(v, function)) - .collect::>(); - alive.extend( - liveness - .live_at(ProgramPoint::Block(block_id)) - .map(|v| Operand::new(v, function)), - ); - - // Initial candidates are values live at block entry which are used in the loop body - let mut cand = alive - .iter() - .filter(|o| liveness.next_use(&o.value, ProgramPoint::Block(block_id)) < LOOP_EXIT_DISTANCE) - .cloned() - .collect::>(); - - // Values which are "live through" the loop, are those which are live at entry, but not - // used within the body of the loop. If we have excess available operand stack capacity, - // then we can avoid issuing spills/reloads for at least some of these values. - let live_through = alive.difference(&cand); - - let w_used = cand.iter().map(|o| o.size as usize).sum::(); - if w_used < K { - if let Some(mut free_in_loop) = - K.checked_sub(max_loop_pressure(loop_id, liveness, cfg, loops)) - { - let mut live_through = live_through.into_vec(); - live_through.sort_by(|a, b| { - liveness - .next_use(&a.value, entry) - .cmp(&liveness.next_use(&b.value, entry)) - .then(a.size.cmp(&b.size)) - }); - - let mut live_through = live_through.into_iter(); - while free_in_loop > 0 { - if let Some(operand) = live_through.next() { - if let Some(new_free) = free_in_loop.checked_sub(operand.size as usize) { - if cand.insert(operand) { - free_in_loop = new_free; - } - continue; - } - } - break; - } - } - - analysis.w_entries.insert(block_id, cand); - } else { - // We require the block parameters to be in W on entry - let mut take = - SmallSet::<_, 4>::from_iter(params.iter().map(|v| Operand::new(*v, function))); - - // So remove them from the set of candidates, then sort remaining by next-use and size - let mut cand = cand.into_vec(); - cand.retain(|o| !params.contains(&o.value)); - cand.sort_by(|a, b| { - liveness - .next_use(&a.value, entry) - .cmp(&liveness.next_use(&b.value, entry)) - .then(a.size.cmp(&b.size)) - }); - - // Fill `take` with as many of the candidates as we can - let mut taken = take.iter().map(|o| o.size as usize).sum::(); - take.extend(cand.into_iter().take_while(|operand| { - let size = operand.size as usize; - let new_size = taken + size; - if new_size <= K { - taken = new_size; - true - } else { - false - } - })); - analysis.w_entries.insert(block_id, take); - } -} - -/// Compute the maximum operand stack depth required within the body of the given loop. -/// -/// If the stack depth never reaches K, the excess capacity represents an opportunity to -/// avoid issuing spills/reloads for values which are live through the loop. -fn max_loop_pressure( - loop_id: Loop, - liveness: &LivenessAnalysis, - cfg: &ControlFlowGraph, - loops: &LoopAnalysis, -) -> usize { - let header = loops.loop_header(loop_id); - let mut max = liveness.max_operand_stack_pressure(&header); - let mut block_q = VecDeque::from_iter([header]); - let mut visited = SmallSet::::default(); - - while let Some(block) = block_q.pop_front() { - if !visited.insert(block) { - continue; - } - - block_q.extend(cfg.succ_iter(block).filter(|b| loops.is_in_loop(*b, loop_id))); - - max = core::cmp::max(max, liveness.max_operand_stack_pressure(&block)); - } - - max -} - -/// At join points in the control flow graph, the set of live and spilled values may, and likely -/// will, differ depending on which predecessor is taken to reach it. We must ensure that for -/// any given predecessor: -/// -/// * Spills are inserted for any values expected in S upon entry to the successor block, which have -/// not been spilled yet. This occurs when a spill is needed in some predecessor, but not in -/// another, thus we must make sure the spill slot is written to at join points. -/// * Reloads are inserted for any values expected in W upon entry to the successor block, which are -/// not in W yet. This occurs when a value is spilled on the path taken through a given -/// predecessor, and hasn't been reloaded again, thus we need to reload it now. -/// -/// NOTE: We are not actually mutating the function and inserting instructions here. Instead, we -/// are computing what instructions need to be inserted, and where, as part of the analysis. A -/// rewrite pass can then apply the analysis results to the function, if desired. -fn compute_control_flow_edge_spills_and_reloads( - analysis: &mut SpillAnalysis, - block_info: &BlockInfo, - pred: &BlockPredecessor, - deferred: &mut SmallVec<[Block; 2]>, - function: &Function, - _cfg: &ControlFlowGraph, - liveness: &LivenessAnalysis, -) { - // If we don't have W^exit(P), then P hasn't been processed yet - let Some(w_exitp) = analysis.w_exits.get(&pred.block) else { - deferred.push(pred.block); - return; - }; - - let mut to_reload = block_info.w_entry.difference(w_exitp); - let mut to_spill = block_info - .s_entry - .difference(&analysis.s_exits[&pred.block]) - .into_intersection(w_exitp); - - // We need to issue spills for any items in W^exit(P) / W^entry(B) that are not in S^exit(P), - // but are live-after P. - // - // This can occur when B is a loop header, and the computed W^entry(B) does not include values - // in W^exit(P) that are live-through the loop, typically because of loop pressure within the - // loop requiring us to place spills of those values outside the loop. - let must_spill = w_exitp - .difference(&block_info.w_entry) - .into_difference(&analysis.s_exits[&pred.block]); - to_spill.extend( - must_spill - .into_iter() - .filter(|o| liveness.is_live_at(&o.value, ProgramPoint::Block(block_info.block_id))), - ); - - // We expect any block parameters present to be in `to_reload` at this point, as they will never - // be in W^exit(P) (the parameters are not in scope at the end of P). The arguments provided in - // the predecessor corresponding to the block parameters may or may not be in W^exit(P), so we - // must determine which of those values need to be reloaded, and whether or not to spill any of - // them so that there is sufficient room in W to hold all the block parameters. Spills may be - // needed for two reasons: - // - // 1. There are multiple predecessors, and we need to spill a value to ensure it is spilled on - // all paths to the current block - // - // 2. An argument corresponding to a block parameter for this block is still live in/through - // this block. Due to values being renamed when used as block arguments, we must ensure there - // is a new copy of the argument so that the original value, and the renamed alias, are both - // live simultaneously. If there is insufficient operand stack space to accommodate both, - // then we must spill values from W to make room. - // - // So in short, we post-process `to_reload` by matching any values in the set which are block - // parameters, with the corresponding source values in W^exit(P) (issuing reloads if the value - // given as argument in the predecessor is not in W^exit(P)) - let pred_args = match function.dfg.analyze_branch(pred.inst) { - BranchInfo::SingleDest(successor) => successor.args, - BranchInfo::MultiDest(successors) => successors - .iter() - .find_map(|successor| { - if successor.destination == block_info.block_id { - Some(successor.args) - } else { - None - } - }) - .unwrap(), - BranchInfo::NotABranch => unreachable!(), - }; - - // Remove block params from `to_reload`, and replace them, as needed, with reloads of the value - // in the predecessor which was used as the successor argument - for (i, param) in function.dfg.block_params(block_info.block_id).iter().enumerate() { - to_reload.remove(param); - // Match up this parameter with its source argument, and if the source value is not in - // W^exit(P), then a reload is needed - let src = pred_args[i]; - if !w_exitp.contains(&src) { - to_reload.insert(Operand::new(src, function)); - } - } - - // If there are no reloads or spills needed, we're done - if to_reload.is_empty() && to_spill.is_empty() { - return; - } - - // Otherwise, we need to split the edge from P to B, and place any spills/reloads in the split, - // S, moving any block arguments for B, to the unconditional branch in S. - let split = analysis.split(block_info.block_id, *pred); - let place = Placement::Split(split); - let span = function.dfg.inst_span(pred.inst); - - // Insert spills first, to end the live ranges of as many variables as possible - for spill in to_spill { - analysis.spill(place, spill.value, span, function); - } - - // Then insert needed reloads - for reload in to_reload { - analysis.reload(place, reload.value, span); - } -} - -/// The MIN algorithm is used to compute the spills and reloads to insert at each instruction in a -/// block, so as to ensure that there is sufficient space to hold all instruction operands and -/// results without exceeding K elements on the operand stack simultaneously, and allocating spills -/// so as to minimize the number of live ranges needing to be split. -/// -/// MIN will spill values with the greatest next-use distance first, using the size of the operand -/// as a tie-breaker for values with equidistant next uses (i.e. the larger values get spilled -/// first, thus making more room on the operand stack). -/// -/// It is expected that upon entry to a given block, that the W and S sets are accurate, regardless -/// of which predecessor edge was used to reach the block. This is handled earlier during analysis -/// by computing the necessary spills and reloads to be inserted along each control flow edge, as -/// required. -fn min( - current_inst: Inst, - w: &mut SmallSet, - s: &mut SmallSet, - analysis: &mut SpillAnalysis, - function: &Function, - liveness: &LivenessAnalysis, -) { - let current_pp = ProgramPoint::Inst(current_inst); - let ip = InsertionPoint::before(current_pp); - let place = Placement::At(ip); - let span = function.dfg.inst_span(current_inst); - let opcode = function.dfg.inst(current_inst).opcode(); - let args = function.dfg.inst_args(current_inst); - match function.dfg.analyze_branch(current_inst) { - BranchInfo::NotABranch if opcode.is_terminator() => { - // A non-branching terminator is either a return, or an unreachable. - // In the latter case, there are no operands or results, so there is no - // effect on W or S In the former case, the operands to the instruction are - // the "results" from the perspective of the operand stack, so we are simply - // ensuring that those values are in W by issuing reloads as necessary, all - // other values are dead, so we do not actually issue any spills. - w.retain(|o| liveness.is_live_at(&o.value, current_pp)); - let to_reload = args.iter().map(|v| Operand::new(*v, function)); - for reload in to_reload { - if w.insert(Operand::new(reload.value, function)) { - analysis.reload(place, reload.value, span); - } - } - } - // All other instructions are handled more or less identically according to the effects - // an instruction has, as described in the documentation of the MIN algorithm. - // - // In the case of branch instructions, successor arguments are not considered inputs to - // the instruction. Instead, we handle spills/reloads for each control flow edge - // independently, as if they occur on exit from this instruction. The result is that - // we may or may not have all successor arguments in W on exit from I, but by the time - // each successor block is reached, all block parameters are guaranteed to be in W - branch_info => { - let mut to_reload = args - .iter() - .map(|v| Operand::new(*v, function)) - .collect::>(); - - // Remove the first occurrance of any operand already in W, remaining uses - // must be considered against the stack usage calculation (but will not - // actually be reloaded) - for operand in w.iter() { - if let Some(pos) = to_reload.iter().position(|o| o == operand) { - to_reload.swap_remove(pos); - } - } - - // Precompute the starting stack usage of W - let w_used = w.iter().map(|o| o.size as usize).sum::(); - - // Compute the needed operand stack space for all operands not currently - // in W, i.e. those which must be reloaded from a spill slot - let in_needed = to_reload.iter().map(|o| o.size as usize).sum::(); - - // Compute the needed operand stack space for results of I - let results = function.dfg.inst_results(current_inst); - let out_needed = results - .iter() - .map(|v| function.dfg.value_type(*v).size_in_felts()) - .sum::(); - - // Compute the amount of operand stack space needed for operands which are - // not live across the instruction, i.e. which do not consume stack space - // concurrently with the results. - let in_consumed = args - .iter() - .filter_map(|v| { - if liveness.is_live_after(v, current_pp) { - None - } else { - Some(function.dfg.value_type(*v).size_in_felts()) - } - }) - .sum::(); - - // If we have room for operands and results in W, then no spills are needed, - // otherwise we require two passes to compute the spills we will need to issue - let mut to_spill = SmallSet::::default(); - - // First pass: compute spills for entry to I (making room for operands) - // - // The max usage in is determined by the size of values currently in W, plus the size - // of any duplicate operands (i.e. values used as operands more than once), as well as - // the size of any operands which must be reloaded. - let max_usage_in = w_used + in_needed; - if max_usage_in > K { - // We must spill enough capacity to keep K >= 16 - let mut must_spill = max_usage_in - K; - // Our initial set of candidates consists of values in W which are not operands - // of the current instruction. - let mut candidates = - w.iter().filter(|o| !args.contains(&o.value)).cloned().collect::>(); - // We order the candidates such that those whose next-use distance is greatest, are - // placed last, and thus will be selected first. We further break ties between - // values with equal next-use distances by ordering them by the - // effective size on the operand stack, so that larger values are - // spilled first. - candidates.sort_by(|a, b| { - let a_dist = liveness.next_use_after(&a.value, current_pp); - let b_dist = liveness.next_use_after(&b.value, current_pp); - a_dist.cmp(&b_dist).then(a.size.cmp(&b.size)) - }); - // Spill until we have made enough room - while must_spill > 0 { - let candidate = candidates.pop().unwrap_or_else(|| { - panic!( - "unable to spill sufficient capacity to hold all operands on stack at \ - one time at {current_inst}" - ) - }); - must_spill = must_spill.saturating_sub(candidate.size as usize); - to_spill.insert(candidate); - } - } - - // Second pass: compute spills for exit from I (making room for results) - let spilled = to_spill.iter().map(|o| o.size as usize).sum::(); - // The max usage out is computed by adding the space required for all results of I, to - // the max usage in, then subtracting the size of all operands which are consumed by I, - // as well as the size of those values in W which we have spilled. - let max_usage_out = (max_usage_in + out_needed).saturating_sub(in_consumed + spilled); - if max_usage_out > K { - // We must spill enough capacity to keep K >= 16 - let mut must_spill = max_usage_out - K; - // For this pass, the set of candidates consists of values in W which are not - // operands of I, and which have not been spilled yet, as well as values in W - // which are operands of I that are live-after I. The latter group may sound - // contradictory, how can you spill something before it is used? However, what - // is actually happening is that we spill those values before I, so that we - // can treat those values as being "consumed" by I, such that their space in W - // can be reused by the results of I. - let mut candidates = w - .iter() - .filter(|o| { - if !args.contains(&o.value) { - // Not an argument, not yet spilled - !to_spill.contains(*o) - } else { - // A spillable argument - liveness.is_live_after(&o.value, current_pp) - } - }) - .cloned() - .collect::>(); - candidates.sort_by(|a, b| { - let a_dist = liveness.next_use_after(&a.value, current_pp); - let b_dist = liveness.next_use_after(&b.value, current_pp); - a_dist.cmp(&b_dist).then(a.size.cmp(&b.size)) - }); - while must_spill > 0 { - let candidate = candidates.pop().unwrap_or_else(|| { - panic!( - "unable to spill sufficient capacity to hold all operands on stack at \ - one time at {current_inst}" - ) - }); - // If we're spilling an operand of I, we can multiple the amount of space - // freed by the spill by the number of uses of the spilled value in I - let num_uses = - core::cmp::max(1, args.iter().filter(|v| *v == &candidate.value).count()); - let freed = candidate.size as usize * num_uses; - must_spill = must_spill.saturating_sub(freed); - to_spill.insert(candidate); - } - } - - // Emit spills first, to make space for reloaded values on the operand stack - for spill in to_spill.iter() { - if s.insert(*spill) { - analysis.spill(place, spill.value, span, function); - } - - // Remove spilled values from W - w.remove(spill); - } - - // Emit reloads for those operands of I not yet in W - for reload in to_reload { - // We only need to emit a reload for a given value once - if w.insert(reload) { - // By definition, if we are emitting a reload, the value must have been spilled - s.insert(reload); - analysis.reload(place, reload.value, span); - } - } - - // At this point, we've emitted our spills/reloads, so we need to prepare W for the next - // instruction by applying the effects of the instruction to W, i.e. consuming those - // operands which are consumed, and adding instruction results. - // - // First, we remove operands from W which are no longer live-after I, _except_ any - // which are used as successor arguments. This is because we must know which successor - // arguments are still in W at the block terminator when we are computing what to spill - // or reload along each control flow edge. - // - // Second, if applicable, we add in the instruction results - match branch_info { - BranchInfo::NotABranch => { - w.retain(|o| liveness.is_live_after(&o.value, current_pp)); - w.extend(results.iter().map(|v| Operand::new(*v, function))); - } - BranchInfo::SingleDest(successor) => { - w.retain(|o| { - successor.args.contains(&o.value) - || liveness.is_live_after(&o.value, current_pp) - }); - } - BranchInfo::MultiDest(successors) => { - w.retain(|o| { - let is_succ_arg = successors.iter().any(|s| s.args.contains(&o.value)); - is_succ_arg || liveness.is_live_after(&o.value, current_pp) - }); - } - } - } - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{ - testing::TestContext, AbiParam, FunctionBuilder, Immediate, InstBuilder, Signature, - }; - use pretty_assertions::assert_eq; - - use super::*; - - /// In this test, we force several values to be live simultaneously inside the same block, - /// of sufficient size on the operand stack so as to require some of them to be spilled - /// at least once, and later reloaded. - /// - /// The purpose here is to validate the MIN algorithm that determines whether or not we need - /// to spill operands at each program point, in the following ways: - /// - /// * Ensure that we spill values we expect to be spilled - /// * Ensure that spills are inserted at the appropriate locations - /// * Ensure that we reload values that were previously spilled - /// * Ensure that reloads are inserted at the appropriate locations - /// - /// The following HIR is constructed for this test: - /// - /// * `in` indicates the set of values in W at an instruction, with reloads included - /// * `out` indicates the set of values in W after an instruction, with spills excluded - /// * `spills` indicates the candidates from W which were selected to be spilled at the - /// instruction - /// * `reloads` indicates the set of values in S which must be reloaded at the instruction - /// - /// ```text,ignore - /// (func (export #spill) (param (ptr u8)) (result u32) - /// (block 0 (param v0 (ptr u8)) - /// (let (v1 u32) (ptrtoint v0)) ; in=[v0] out=[v1] - /// (let (v2 u32) (add v1 32)) ; in=[v1] out=[v1 v2] - /// (let (v3 (ptr u128)) (inttoptr v2)) ; in=[v1 v2] out=[v1 v2 v3] - /// (let (v4 u128) (load v3)) ; in=[v1 v2 v3] out=[v1 v2 v3 v4] - /// (let (v5 u32) (add v1 64)) ; in=[v1 v2 v3 v4] out=[v1 v2 v3 v4 v5] - /// (let (v6 (ptr u128)) (inttoptr v5)) ; in=[v1 v2 v3 v4 v5] out=[v1 v2 v3 v4 v6] - /// (let (v7 u128) (load v6)) ; in=[v1 v2 v3 v4 v6] out=[v1 v2 v3 v4 v6 v7] - /// (let (v8 u64) (const.u64 1)) ; in=[v1 v2 v3 v4 v6 v7] out=[v1 v2 v3 v4 v6 v7 v8] - /// (let (v9 u32) (call (#example) v6 v4 v7 v7 v8)) <-- operand stack pressure hits 18 here - /// ; in=[v1 v2 v3 v4 v6 v7 v7 v8] out=[v1 v7 v9] - /// ; spills=[v2 v3] (v2 is furthest next-use, followed by v3) - /// (let (v10 u32) (add v1 72)) ; in=[v1 v7] out=[v7 v10] - /// (let (v11 (ptr u64)) (inttoptr v10)) ; in=[v7 v10] out=[v7 v11] - /// (store v3 v7) ; reload=[v3] in=[v3 v7 v11] out=[v11] - /// (let (v12 u64) (load v11)) ; in=[v11] out=[v12] - /// (ret v2) ; reload=[v2] in=[v2] out=[v2] - /// ) - /// ``` - #[test] - fn spills_intra_block() -> AnalysisResult<()> { - let context = TestContext::default(); - let id = "test::spill".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - [AbiParam::new(Type::U32)], - ), - ); - - let v2; - let v3; - let call; - let store; - let ret; - { - let mut builder = FunctionBuilder::new(&mut function); - let example = builder - .import_function( - "foo", - "example", - Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U128))), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U64), - ], - [AbiParam::new(Type::U32)], - ), - SourceSpan::UNKNOWN, - ) - .unwrap(); - let entry = builder.current_block(); - let v0 = { - let args = builder.block_params(entry); - args[0] - }; - - // entry - let v1 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - v2 = builder.ins().add_imm_unchecked(v1, Immediate::U32(32), SourceSpan::UNKNOWN); - v3 = builder.ins().inttoptr(v2, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v4 = builder.ins().load(v3, SourceSpan::UNKNOWN); - let v5 = builder.ins().add_imm_unchecked(v1, Immediate::U32(64), SourceSpan::UNKNOWN); - let v6 = - builder.ins().inttoptr(v5, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v7 = builder.ins().load(v6, SourceSpan::UNKNOWN); - let v8 = builder.ins().u64(1, SourceSpan::UNKNOWN); - call = builder.ins().call(example, &[v6, v4, v7, v7, v8], SourceSpan::UNKNOWN); - let v10 = builder.ins().add_imm_unchecked(v1, Immediate::U32(72), SourceSpan::UNKNOWN); - store = builder.ins().store(v3, v7, SourceSpan::UNKNOWN); - let v11 = - builder.ins().inttoptr(v10, Type::Ptr(Box::new(Type::U64)), SourceSpan::UNKNOWN); - let _v12 = builder.ins().load(v11, SourceSpan::UNKNOWN); - ret = builder.ins().ret(Some(v2), SourceSpan::UNKNOWN); - } - - let mut analyses = AnalysisManager::default(); - let spills = analyses.get_or_compute::(&function, &context.session)?; - - assert!(spills.has_spills()); - assert_eq!(spills.spills().len(), 2); - assert!(spills.is_spilled(&v2)); - assert!(spills.is_spilled_at(v2, ProgramPoint::Inst(call))); - assert!(spills.is_spilled(&v3)); - assert!(spills.is_spilled_at(v3, ProgramPoint::Inst(call))); - assert_eq!(spills.reloads().len(), 2); - assert!(spills.is_reloaded_at(v3, ProgramPoint::Inst(store))); - assert!(spills.is_reloaded_at(v2, ProgramPoint::Inst(ret))); - - Ok(()) - } - - /// In this test, we are verifying the behavior of the spill analysis when applied to a - /// control flow graph with branching control flow, where spills are required along one - /// branch and not the other. This verifies the following: - /// - /// * Control flow edges are split as necessary to insert required spills/reloads - /// * Propagation of the W and S sets from predecessors to successors is correct - /// * The W and S sets are properly computed at join points in the CFG - /// - /// The following HIR is constructed for this test (see the first test in this file for - /// a description of the notation used, if unclear): - /// - /// ```text,ignore - /// (func (export #spill) (param (ptr u8)) (result u32) - /// (block 0 (param v0 (ptr u8)) - /// (let (v1 u32) (ptrtoint v0)) ; in=[v0] out=[v1] - /// (let (v2 u32) (add v1 32)) ; in=[v1] out=[v1 v2] - /// (let (v3 (ptr u128)) (inttoptr v2)) ; in=[v1 v2] out=[v1 v2 v3] - /// (let (v4 u128) (load v3)) ; in=[v1 v2 v3] out=[v1 v2 v3 v4] - /// (let (v5 u32) (add v1 64)) ; in=[v1 v2 v3 v4] out=[v1 v2 v3 v4 v5] - /// (let (v6 (ptr u128)) (inttoptr v5)) ; in=[v1 v2 v3 v4 v5] out=[v1 v2 v3 v4 v6] - /// (let (v7 u128) (load v6)) ; in=[v1 v2 v3 v4 v6] out=[v1 v2 v3 v4 v6 v7] - /// (let (v8 i1) (eq v1 0)) ; in=[v1 v2 v3 v4 v6, v7] out=[v1 v2 v3 v4 v6 v7, v8] - /// (cond_br v8 (block 1) (block 2))) - /// - /// (block 1 - /// (let (v9 u64) (const.u64 1)) ; in=[v1 v2 v3 v4 v6 v7] out=[v1 v2 v3 v4 v6 v7 v9] - /// (let (v10 u32) (call (#example) v6 v4 v7 v7 v9)) <-- operand stack pressure hits 18 here - /// ; in=[v1 v2 v3 v4 v6 v7 v7 v9] out=[v1 v7 v10] - /// ; spills=[v2 v3] (v2 is furthest next-use, followed by v3) - /// (br (block 3 v10))) ; this edge will be split to reload v2/v3 as expected by block3 - /// - /// (block 2 - /// (let (v11 u32) (add v1 8)) ; in=[v1 v2 v3 v7] out=[v1 v2 v3 v7 v11] - /// (br (block 3 v11))) ; this edge will be split to spill v2/v3 to match the edge from block1 - /// - /// (block 3 (param v12 u32)) ; we expect that the edge between block 2 and 3 will be split, and spills of v2/v3 inserted - /// (let (v13 u32) (add v1 72)) ; in=[v1 v7 v12] out=[v7 v12 v13] - /// (let (v14 u32) (add v13 v12)) ; in=[v7 v12 v13] out=[v7 v14] - /// (let (v15 (ptr u64)) (inttoptr v14)) ; in=[v7 v14] out=[v7 v15] - /// (store v3 v7) ; reload=[v3] in=[v3 v7 v15] out=[v15] - /// (let (v16 u64) (load v15)) ; in=[v15] out=[v16] - /// (ret v2)) ; reload=[v2] in=[v2] out=[v2] - /// ) - /// ``` - #[test] - fn spills_branching_control_flow() -> AnalysisResult<()> { - let context = TestContext::default(); - let id = "test::spill".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - [AbiParam::new(Type::U32)], - ), - ); - - let v2; - let v3; - let call; - { - let mut builder = FunctionBuilder::new(&mut function); - let example = builder - .import_function( - "foo", - "example", - Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U128))), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U64), - ], - [AbiParam::new(Type::U32)], - ), - SourceSpan::UNKNOWN, - ) - .unwrap(); - let entry = builder.current_block(); - let block1 = builder.create_block(); - let block2 = builder.create_block(); - let block3 = builder.create_block(); - let v0 = { - let args = builder.block_params(entry); - args[0] - }; - - // entry - let v1 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - v2 = builder.ins().add_imm_unchecked(v1, Immediate::U32(32), SourceSpan::UNKNOWN); - v3 = builder.ins().inttoptr(v2, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v4 = builder.ins().load(v3, SourceSpan::UNKNOWN); - let v5 = builder.ins().add_imm_unchecked(v1, Immediate::U32(64), SourceSpan::UNKNOWN); - let v6 = - builder.ins().inttoptr(v5, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v7 = builder.ins().load(v6, SourceSpan::UNKNOWN); - let v8 = builder.ins().eq_imm(v1, Immediate::U32(0), SourceSpan::UNKNOWN); - builder.ins().cond_br(v8, block1, &[], block2, &[], SourceSpan::UNKNOWN); - - // block1 - builder.switch_to_block(block1); - let v9 = builder.ins().u64(1, SourceSpan::UNKNOWN); - call = builder.ins().call(example, &[v6, v4, v7, v7, v9], SourceSpan::UNKNOWN); - let v10 = builder.func.dfg.first_result(call); - builder.ins().br(block3, &[v10], SourceSpan::UNKNOWN); - - // block2 - builder.switch_to_block(block2); - let v11 = builder.ins().add_imm_unchecked(v1, Immediate::U32(8), SourceSpan::UNKNOWN); - builder.ins().br(block3, &[v11], SourceSpan::UNKNOWN); - - // block3 - let v12 = builder.append_block_param(block3, Type::U32, SourceSpan::UNKNOWN); - builder.switch_to_block(block3); - let v13 = builder.ins().add_imm_unchecked(v1, Immediate::U32(72), SourceSpan::UNKNOWN); - let v14 = builder.ins().add_unchecked(v13, v12, SourceSpan::UNKNOWN); - let v15 = - builder.ins().inttoptr(v14, Type::Ptr(Box::new(Type::U64)), SourceSpan::UNKNOWN); - builder.ins().store(v3, v7, SourceSpan::UNKNOWN); - let _v16 = builder.ins().load(v15, SourceSpan::UNKNOWN); - builder.ins().ret(Some(v2), SourceSpan::UNKNOWN); - } - - let mut analyses = AnalysisManager::default(); - let spills = analyses.get_or_compute::(&function, &context.session)?; - - dbg!(&spills); - - assert!(spills.has_spills()); - assert_eq!(spills.spills().len(), 4); - // Block created by splitting edge between block 3 and its predecessors - assert_eq!(spills.splits().len(), 2); - let block1 = Block::from_u32(1); - let block2 = Block::from_u32(2); - let block3 = Block::from_u32(3); - let spill_blk1_blk3 = &spills.splits()[0]; - assert_eq!(spill_blk1_blk3.block, block3); - assert_eq!(spill_blk1_blk3.predecessor.block, block1); - let spill_blk2_blk3 = &spills.splits()[1]; - assert_eq!(spill_blk2_blk3.block, block3); - assert_eq!(spill_blk2_blk3.predecessor.block, block2); - assert!(spills.is_spilled(&v2)); - assert!(spills.is_spilled_at(v2, ProgramPoint::Inst(call))); - // v2 should have a spill inserted from block2 to block3, as it is spilled in block1 - assert!(spills.is_spilled_in_split(v2, spill_blk2_blk3.id)); - assert!(spills.is_spilled(&v3)); - // v3 should have a spill inserted from block2 to block3, as it is spilled in block1 - assert!(spills.is_spilled_at(v3, ProgramPoint::Inst(call))); - assert!(spills.is_spilled_in_split(v3, spill_blk2_blk3.id)); - // v2 and v3 should be reloaded on the edge from block1 to block3, since they were - // spilled previously, but must be in W on entry to block3 - assert_eq!(spills.reloads().len(), 2); - assert!(spills.is_reloaded_in_split(v2, spill_blk1_blk3.id)); - assert!(spills.is_reloaded_in_split(v3, spill_blk1_blk3.id)); - - Ok(()) - } - - /// In this test, we are verifying the behavior of the spill analysis when applied to a - /// control flow graph with cyclical control flow, i.e. loops. We're interested specifically in - /// the following properties: - /// - /// * W and S at entry to a loop are computed correctly - /// * Values live-through - but not live-in - a loop, which cannot survive the loop due to - /// operand stack pressure within the loop, are spilled outside of the loop, with reloads - /// placed on exit edges from the loop where needed - /// * W and S upon exit from a loop are computed correctly - /// - /// The following HIR is constructed for this test (see the first test in this file for - /// a description of the notation used, if unclear): - /// - /// ```text,ignore - /// (func (export #spill) (param (ptr u64)) (param u32) (param u32) (result u64) - /// (block 0 (param v0 (ptr u64)) (param v1 u32) (param v2 u32) - /// (let (v3 u32) (const.u32 0)) ; in=[v0, v1, v2] out=[v0, v1, v2, v3] - /// (let (v4 u32) (const.u32 0)) ; in=[v0, v1, v2, v3] out=[v0, v1, v2, v3, v4] - /// (let (v5 u64) (const.u64 0)) ; in=[v0, v1, v2, v3, v4] out=[v0, v1, v2, v3, v4, v5] - /// (br (block 1 v3 v4 v5))) - /// - /// (block 1 (param v6 u32) (param v7 u32) (param v8 u64)) ; outer loop - /// (let (v9 i1) (eq v6 v1)) ; in=[v0, v2, v6, v7, v8] out=[v0, v1, v2, v6, v7, v8, v9] - /// (cond_br v9 (block 2) (block 3))) ; in=[v0, v1, v2, v6, v7, v8, v9] out=[v0, v1, v2, v6, v7, v8] - /// - /// (block 2 ; exit outer loop, return from function - /// (ret v8)) ; in=[v0, v1, v2, v6, v7, v8] out=[v8] - /// - /// (block 3 ; split edge - /// (br (block 4 v7 v8))) ; in=[v0, v1, v2, v6, v7, v8] out=[v0, v1, v2, v6] - /// - /// (block 4 (param v10 u32) (param v11 u64) ; inner loop - /// (let (v12 i1) (eq v10 v2)) ; in=[v0, v1, v2, v6, v10, v11] out=[v0, v1, v2, v6, v10, v11, v12] - /// (cond_br v12 (block 5) (block 6))) ; in=[v0, v1, v2, v6, v10, v11, v12] out=[v0, v1, v2, v6, v10, v11] - /// - /// (block 5 ; increment row count, continue outer loop - /// (let (v13 u32) (add v6 1)) ; in=[v0, v1, v2, v6, v10, v11] out=[v0, v1, v2, v10, v11, v13] - /// (br (block 1 v13 v10 v11))) - /// - /// (block 6 ; load value at v0[row][col], add to sum, increment col, continue inner loop - /// (let (v14 u32) (sub.saturating v6 1)) ; row_offset := ROW_SIZE * row.saturating_sub(1) - /// ; in=[v0, v1, v2, v6, v10, v11] out=[v0, v1, v2, v6, v10, v11, 14] - /// (let (v15 u32) (mul v14 v2)) ; in=[v0, v1, v2, v6, v10, v11, 14] out=[v0, v1, v2, v6, v10, v11, 15] - /// (let (v16 u32) (add v10 v15)) ; offset := col + row_offset - /// ; in=[v0, v1, v2, v6, v10, v11, 15] out=[v0, v1, v2, v6, v10, v11, v16] - /// (let (v17 u32) (ptrtoint v0)) ; ptr := (v0 as u32 + offset) as *u64 - /// ; in=[v0, v1, v2, v6, v10, v11, v16] out=[v0, v1, v2, v6, v10, v11, v16, 17] - /// (let (v18 u32) (add v17 v16)) ; in=[v0, v1, v2, v6, v10, v11, v16, v17] out=[v0, v1, v2, v6, v10, v11, v18] - /// (let (v19 (ptr u64)) (ptrtoint v18)) ; in=[v0, v1, v2, v6, v10, v11, v18] out=[v0, v1, v2, v6, v10, v11, v19] - /// (let (v20 u64) (load v19)) ; sum += *ptr - /// ; in=[v0, v1, v2, v6, v10, v11, v19] out=[v0, v1, v2, v6, v10, v11, v20] - /// (let (v21 u64) (add v11 v20)) ; in=[v0, v1, v2, v6, v10, v11, v20] out=[v0, v1, v2, v6, v10, v21] - /// (let (v22 u32) (add v10 1)) ; col++ - /// ; in=[v0, v1, v2, v6, v10, v21] out=[v0, v1, v2, v6, v21, v22] - /// (br (block 4 v22 v21))) - /// ) - /// ``` - #[test] - fn spills_loop_nest() -> AnalysisResult<()> { - let context = TestContext::default(); - let id = "test::spill".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U64))), - AbiParam::new(Type::U64), - AbiParam::new(Type::U64), - ], - [AbiParam::new(Type::U64)], - ), - ); - - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let (v0, v1, v2) = { - let args = builder.block_params(entry); - (args[0], args[1], args[2]) - }; - - let block1 = builder.create_block(); - let block2 = builder.create_block(); - let block3 = builder.create_block(); - let block4 = builder.create_block(); - let block5 = builder.create_block(); - let block6 = builder.create_block(); - - // entry - let v3 = builder.ins().u64(0, SourceSpan::UNKNOWN); - let v4 = builder.ins().u64(0, SourceSpan::UNKNOWN); - let v5 = builder.ins().u64(0, SourceSpan::UNKNOWN); - builder.ins().br(block1, &[v3, v4, v5], SourceSpan::UNKNOWN); - - // block1 - outer loop header - let v6 = builder.append_block_param(block1, Type::U64, SourceSpan::UNKNOWN); - let v7 = builder.append_block_param(block1, Type::U64, SourceSpan::UNKNOWN); - let v8 = builder.append_block_param(block1, Type::U64, SourceSpan::UNKNOWN); - builder.switch_to_block(block1); - let v9 = builder.ins().eq(v6, v1, SourceSpan::UNKNOWN); - builder.ins().cond_br(v9, block2, &[], block3, &[], SourceSpan::UNKNOWN); - - // block2 - outer exit - builder.switch_to_block(block2); - builder.ins().ret(Some(v8), SourceSpan::UNKNOWN); - - // block3 - split edge - builder.switch_to_block(block3); - builder.ins().br(block4, &[v7, v8], SourceSpan::UNKNOWN); - - // block4 - inner loop - let v10 = builder.append_block_param(block4, Type::U64, SourceSpan::UNKNOWN); - let v11 = builder.append_block_param(block4, Type::U64, SourceSpan::UNKNOWN); - builder.switch_to_block(block4); - let v12 = builder.ins().eq(v10, v2, SourceSpan::UNKNOWN); - builder.ins().cond_br(v12, block5, &[], block6, &[], SourceSpan::UNKNOWN); - - // block5 - inner latch - builder.switch_to_block(block5); - let v13 = builder.ins().add_imm_unchecked(v6, Immediate::U64(1), SourceSpan::UNKNOWN); - builder.ins().br(block1, &[v13, v10, v11], SourceSpan::UNKNOWN); - - // block6 - inner body - builder.switch_to_block(block6); - let v14 = builder.ins().add_imm_unchecked(v6, Immediate::U64(1), SourceSpan::UNKNOWN); - let v15 = builder.ins().mul_unchecked(v14, v2, SourceSpan::UNKNOWN); - let v16 = builder.ins().add_unchecked(v10, v15, SourceSpan::UNKNOWN); - let v17 = builder.ins().ptrtoint(v0, Type::U64, SourceSpan::UNKNOWN); - let v18 = builder.ins().add_unchecked(v17, v16, SourceSpan::UNKNOWN); - let v19 = - builder.ins().inttoptr(v18, Type::Ptr(Box::new(Type::U64)), SourceSpan::UNKNOWN); - let v20 = builder.ins().load(v19, SourceSpan::UNKNOWN); - let v21 = builder.ins().add_unchecked(v11, v20, SourceSpan::UNKNOWN); - let v22 = builder.ins().add_imm_unchecked(v10, Immediate::U64(1), SourceSpan::UNKNOWN); - builder.ins().br(block4, &[v22, v21], SourceSpan::UNKNOWN); - } - - let mut analyses = AnalysisManager::default(); - let spills = analyses.get_or_compute::(&function, &context.session)?; - - dbg!(&spills); - - let block1 = Block::from_u32(1); - let block3 = Block::from_u32(3); - let block4 = Block::from_u32(4); - let block5 = Block::from_u32(5); - - assert!(spills.has_spills()); - assert_eq!(spills.splits().len(), 2); - - // We expect a spill from block3 to block4, as due to operand stack pressure in the loop, - // there is insufficient space to keep v1 on the operand stack through the loop - assert_eq!(spills.spills().len(), 1); - let split_blk3_blk4 = &spills.splits()[0]; - assert_eq!(split_blk3_blk4.block, block4); - assert_eq!(split_blk3_blk4.predecessor.block, block3); - let v1 = Value::from_u32(1); - assert!(spills.is_spilled(&v1)); - assert!(spills.is_spilled_in_split(v1, split_blk3_blk4.id)); - - // We expect a reload of v1 from block5 to block1, as block1 expects v1 on the operand stack - assert_eq!(spills.reloads().len(), 1); - let split_blk5_blk1 = &spills.splits()[1]; - assert_eq!(split_blk5_blk1.block, block1); - assert_eq!(split_blk5_blk1.predecessor.block, block5); - assert!(spills.is_reloaded(&v1)); - assert!(spills.is_reloaded_in_split(v1, split_blk5_blk1.id)); - - Ok(()) - } -} diff --git a/hir-analysis/src/treegraph.rs b/hir-analysis/src/treegraph.rs deleted file mode 100644 index c940928bd..000000000 --- a/hir-analysis/src/treegraph.rs +++ /dev/null @@ -1,767 +0,0 @@ -use std::{ - cmp::Ordering, - collections::{BTreeMap, BTreeSet, VecDeque}, - fmt, -}; - -use smallvec::SmallVec; - -use crate::dependency_graph::*; - -/// [OrderedTreeGraph] represents an immutable, fully-constructed and topologically sorted -/// [TreeGraph]. -/// -/// This is the representation we use during instruction scheduling. -#[derive(Default, Debug)] -pub struct OrderedTreeGraph { - /// The topological order of nodes in `graph` - ordering: Vec, - /// For each tree in `graph`, a data structure which tells us in what order - /// the nodes of that tree will be visited. The smaller the index, the earlier we - /// will emit that node. - indices: BTreeMap, - /// The underlying [TreeGraph] - graph: TreeGraph, -} -impl TryFrom for OrderedTreeGraph { - type Error = UnexpectedCycleError; - - fn try_from(depgraph: DependencyGraph) -> Result { - let graph = TreeGraph::from(depgraph.clone()); - let ordering = graph.toposort()?; - let indices = ordering.iter().copied().try_fold(BTreeMap::default(), |mut acc, root| { - acc.insert(root, depgraph.indexed(root)?); - Ok(acc) - })?; - - Ok(Self { - ordering, - indices, - graph, - }) - } -} -impl OrderedTreeGraph { - /// Compute an [OrderedTreeGraph] corresponding to the given [DependencyGraph] - pub fn new(depgraph: &DependencyGraph) -> Result { - Self::try_from(depgraph.clone()) - } - - /// Returns an iterator over nodes in the graph, in topological order. - #[inline] - pub fn iter(&self) -> impl DoubleEndedIterator + '_ { - self.ordering.iter().copied() - } - - /// Returns true if `a` is scheduled before `b` according to the graph - /// - /// See `cmp_scheduling` for details on what scheduling before/after implies. - #[inline] - pub fn is_scheduled_before(&self, a: A, b: B) -> bool - where - A: Into, - B: Into, - { - self.cmp_scheduling(a, b).is_lt() - } - - /// Returns true if `a` is scheduled after `b` according to the graph - /// - /// See `cmp_scheduling` for details on what scheduling before/after implies. - #[inline] - pub fn is_scheduled_after(&self, a: A, b: B) -> bool - where - A: Into, - B: Into, - { - self.cmp_scheduling(a, b).is_gt() - } - - /// Compare two nodes in terms of their scheduling in the graph. - /// - /// If `a` compares less than `b`, then `a` is scheduled before `b`, - /// and vice versa. Two nodes can only compare equal if they are the - /// same node. - /// - /// "Scheduled" here refers to when a node will be visited by the scheduler - /// during its planning phase, which is the opposite order that a given node - /// will be emitted during code generation. This is due to the fact that we visit - /// the dependency graph of a block lazily and bottom-up (i.e. for a given block, - /// we start at the terminator and then materialize any instructions/values - /// referenced by it). - pub fn cmp_scheduling(&self, a: A, b: B) -> Ordering - where - A: Into, - B: Into, - { - let a = a.into(); - let b = b.into(); - if a == b { - return Ordering::Equal; - } - - let a_tree = self.graph.root_id(a); - let b_tree = self.graph.root_id(b); - - // If the nodes reside in the same tree, the precise scheduling - // order is determined by the indexing order for that tree. - if a_tree == b_tree { - let indices = &self.indices[&a_tree]; - let a_idx = indices.get(a).unwrap(); - let b_idx = indices.get(b).unwrap(); - - return a_idx.cmp(&b_idx).reverse(); - } - - // Whichever tree appears first in the topological order will be - // scheduled before the other. - assert!( - !self.ordering.is_empty(), - "invalid treegraph: the topographical ordering is empty even though the underlying \ - graph is not" - ); - for n in self.ordering.iter().copied() { - if n == a_tree { - return Ordering::Less; - } - if n == b_tree { - return Ordering::Greater; - } - } - - unreachable!( - "invalid treegraph: there are roots in the dependency graph not represented in the \ - topographical ordering" - ) - } -} -impl core::convert::AsRef for OrderedTreeGraph { - #[inline(always)] - fn as_ref(&self) -> &TreeGraph { - &self.graph - } -} -impl core::ops::Deref for OrderedTreeGraph { - type Target = TreeGraph; - - #[inline(always)] - fn deref(&self) -> &Self::Target { - &self.graph - } -} - -/// A [TreeGraph] is used to represent dependencies between expression trees in a program, -/// derived from a [DependencyGraph]. -/// -/// ## What is a TreeGraph? -/// -/// An expression tree is a component of a [DependencyGraph] where each node has at most -/// one predecessor. For example, `(a + b - c) / 2` is an obvious case of such a tree, -/// as each sub-expression produces a single result which is used by the next operator, -/// ultimately producing the final result of the outermost expression. -/// -/// A program can be made up of many such trees, and in some cases the values produced by -/// a tree, or even sub-expressions within the tree, may be used multiple times. -/// To riff on our example above, consider the following: -/// -/// ```text,ignore -/// let d = (a + b - c); -/// let e = d / 2; -/// let f = e * e; -/// d == f -/// ``` -/// -/// This still contains the expression tree from before, but with parts of it reused from -/// another expression tree that, had it duplicated the expressions that are reused, would -/// have formed a larger expression tree. Instead, the reuse results in a forest of two -/// trees, where the "outer" tree depends on the results of the "inner" tree. -/// -/// This is the essence of a [TreeGraph] - it represents a program as a forest of expression -/// trees, just without requiring the program itself to be a tree, resulting in a directed, -/// acyclic graph rather than a forest in the graph-theoretic sense. -/// -/// Nodes in this graph are the roots of the expression trees represented, i.e. each expression -/// tree is condensed into a single node. This is because the graph is largely only concerned with -/// connections between the trees, but we still would be able to answer questions like: -/// -/// * Is a given [Node] a root in the tree graph -/// * If not, what is the root [Node] corresponding to that node -/// * Is a [Node] a member of a given tree -/// * What are the dependencies between trees -/// * What dependencies are condensed in the edge connecting two treegraph nodes -/// -/// The specific way we have implemented [TreeGraph] lets us do all of the above. -/// -/// ## Use Case -/// -/// The [TreeGraph] forms the foundation around which instruction scheduling is performed during -/// code generation. We construct a [DependencyGraph] for each block in a function, and derive -/// a corresponding [TreeGraph]. We then use the reverse topological ordering of the resulting -/// graph as the instruction schedule for that block. -/// -/// Fundamentally though, a [TreeGraph] is a data structure designed to solve the issue of -/// how to efficiently generate code for a stack machine from a non-stack-oriented program -/// representation. It allows one to keep everything on the operand stack, rather than requiring -/// loads/stores to temporaries, and naturally places operands exactly where they are needed, -/// when they are needed. This is a particularly good fit for Miden, because Miden IR is in SSA -/// form, and we need to convert it to efficient Miden Assembly which is a stack machine ISA. -/// -/// ## Additional Reading -/// -/// The treegraph data structure was (to my knowledge) first described in -/// [this paper](https://www.sciencedirect.com/science/article/pii/S1571066111001538?via=ihub) -/// by Park, et al., called _Treegraph-based Instruction Scheduling for Stack-based Virtual -/// Machines_. -/// -/// The implementation and usage of both [DependencyGraph] and [TreeGraph] are based on the design -/// and algorithms described in that paper, so it is worth reading if you are curious about it. -/// Our implementation here is tailored for our use case (i.e. the way we represent nodes has a -/// specific effect on the order in which we visit instructions and their arguments during -/// scheduling), but the overall properties of the data structure described in that paper hold for -/// [TreeGraph] as well. -#[derive(Default, Clone)] -pub struct TreeGraph { - /// The nodes which are explicitly represented in the graph - nodes: BTreeSet, - /// Edges between nodes in the graph, where an edge may carry multiple dependencies - edges: BTreeMap>, - /// A mapping of condensed nodes to the root node of the tree they were condensed into - condensed: BTreeMap, -} - -/// Represents an edge between [TreeGraph] roots. -/// -/// Each pair of nodes in a treegraph may only have one [EdgeId], but -/// multiple [DependencyEdge]s which represents each unique dependency -/// from predecessor root to successor root. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -struct EdgeId { - /// The treegraph root which is predecessor - predecessor: NodeId, - /// The treegraph root which is successor - successor: NodeId, -} - -/// Represents a unique edge between dependency graph nodes in a [TreeGraph]. -/// -/// The referenced nodes do not have to be represented explicitly in the treegraph as -/// roots, they may also be condensed members of one of the trees in the graph. -/// To help illustrate what I mean, consider an instruction whose result is used -/// twice: once as a block argument to a successor block, and once as an operand -/// to another instruction that is part of an expression tree. The result node will -/// be represented in the treegraph as a root (because it has multiple uses); as will -/// the block terminator (because a terminator can have no uses). However, the last -/// instruction which uses our result is part of an expression tree, so it will not -/// be a root in the treegraph, and instead will be condensed under a root representing -/// the expression tree. Thus we will have two [DependencyEdge] items, one where the -/// predecessor (dependent) is a root; and one where it is not. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -struct DependencyEdge { - /// The specific node in the dependency graph which is predecessor - predecessor: NodeId, - /// The specific node in the dependency graph which is successor - successor: NodeId, -} - -impl TreeGraph { - /// Returns true if `node` represents a tree in this graph - #[inline(always)] - pub fn is_root(&self, node: impl Into) -> bool { - self.nodes.contains(&node.into()) - } - - /// Return the node representing the root of the tree to which `node` belongs - /// - /// If `node` is the root of its tree, it simply returns itself. - /// - /// NOTE: This function will panic if `node` is not a root OR a node condensed - /// in any tree of the graph. - #[inline] - pub fn root(&self, node: impl Into) -> Node { - self.condensed[&node.into()].into() - } - - /// Same as [TreeGraph::root], but returns the node identifier, which avoids - /// decoding the [NodeId] into a [Node] if not needed. - #[inline] - pub fn root_id(&self, node: impl Into) -> NodeId { - self.condensed[&node.into()] - } - - /// Returns true if `node` is a member of the tree represented by `root` - /// - /// NOTE: This function will panic if `root` is not a tree root - pub fn is_member_of(&self, node: A, root: B) -> bool - where - A: Into, - B: Into, - { - let root = root.into(); - assert!(self.is_root(root)); - self.root(node).id() == root - } - - /// Return the number of times that `node` is referenced as a dependency. - /// - /// Here, `node` can be either explicitly represented in the graph as a tree - /// root, or implicitly, as a condensed node belonging to a tree. - /// - /// For tree roots, the number of dependencies can be different than the - /// number of predecessors, as edges can carry multiple dependencies, and - /// those dependencies can reference the root. However, tree roots by construction - /// must have either zero, or more than one dependent. - /// - /// Condensed nodes on the other hand, by construction have only one dependent, as they - /// belong to a tree; but they may also be referenced by dependencies in the edges - /// between treegraph nodes, so we must check all edges inbound on the tree containing - /// the node for dependencies on `node`. - pub fn num_dependents(&self, node: impl Into) -> usize { - let node = node.into(); - if self.is_root(node) { - self.dependents(node).count() - } else { - self.dependents(node).count() + 1 - } - } - - /// Return an iterator over every node which depends on `node` - pub fn dependents(&self, node: impl Into) -> impl Iterator + '_ { - let dependency_id = node.into(); - let root_id = self.root_id(dependency_id); - self.edges - .iter() - .filter_map(move |(eid, edges)| { - if eid.successor == root_id { - Some(edges.as_slice()) - } else { - None - } - }) - .flat_map(move |edges| { - edges.iter().filter_map(move |e| { - if e.successor == dependency_id { - Some(e.predecessor.into()) - } else { - None - } - }) - }) - } - - /// Return an iterator over each [Dependency] in the edge from `a` to `b` - /// - /// NOTE: This function will panic if either `a` or `b` are not tree roots - pub fn edges(&self, a: NodeId, b: NodeId) -> impl Iterator + '_ { - let id = EdgeId { - predecessor: a, - successor: b, - }; - self.edges[&id].iter().map(|e| Dependency { - dependent: e.predecessor, - dependency: e.successor, - }) - } - - /// Return the number of predecessors for `node` in this graph. - /// - /// NOTE: This function will panic if `node` is not a tree root - pub fn num_predecessors(&self, node: impl Into) -> usize { - let node_id = node.into(); - self.edges.keys().filter(|e| e.successor == node_id).count() - } - - /// Return an iterator over [Node]s which are predecessors of `node` in this graph. - /// - /// NOTE: This function will panic if `node` is not a tree root - pub fn predecessors(&self, node: impl Into) -> impl Iterator + '_ { - let node_id = node.into(); - self.edges.keys().filter_map(move |eid| { - if eid.successor == node_id { - Some(eid.predecessor.into()) - } else { - None - } - }) - } - - /// Return an iterator over [Node]s which are successors of `node` in this graph. - /// - /// NOTE: This function will panic if `node` is not a tree root - pub fn successors(&self, node: impl Into) -> impl Iterator + '_ { - let node_id = node.into(); - self.edges.keys().filter_map(move |eid| { - if eid.predecessor == node_id { - Some(eid.successor.into()) - } else { - None - } - }) - } - - /// Return an iterator over [NodeId]s for successors of `node` in this graph. - /// - /// NOTE: This function will panic if `node` is not a tree root - pub fn successor_ids(&self, node: impl Into) -> impl Iterator + '_ { - let node_id = node.into(); - self.edges.keys().filter_map(move |eid| { - if eid.predecessor == node_id { - Some(eid.successor) - } else { - None - } - }) - } - - /// Remove the edge connecting `a` and `b`. - /// - /// NOTE: This function will panic if either `a` or `b` are not tree roots - pub fn remove_edge(&mut self, a: NodeId, b: NodeId) { - self.edges.remove(&EdgeId { - predecessor: a, - successor: b, - }); - } - - /// Returns a vector of [TreeGraph] roots, sorted in topological order. - /// - /// Returns `Err` if a cycle is detected, making a topological sort impossible. - /// - /// The reverse topological ordering of a [TreeGraph] represents a valid scheduling of - /// the nodes in that graph, as each node is observed before any of it's dependents. - /// - /// Additionally, we ensure that any topological ordering of the graph schedules instruction - /// operands in stack order naturally, i.e. if we emit code from the schedule, there should - /// be little to no stack manipulation required to get instruction operands in the correct - /// order. - /// - /// This is done in the form of an implicit heuristic: the natural ordering for nodes in the - /// graph uses the [Ord] implementation of [NodeId]. When visiting nodes, this order is used, - /// and in the specific case of instruction arguments, which by construction only have a single - /// parent (dependent), they will be visited in this order. In general, this heuristic can be - /// thought of as falling back to the original program order when no other criteria is available - /// for sorting. In reality, it's more of a natural synergy in the data structures representing - /// the graph and nodes in the graph, so it is not nearly so explicit - but the effect is the - /// same. - /// - /// ## Example - /// - /// To better understand how the IR translates to the topological ordering described above, - /// consider the following IR: - /// - /// ```miden-ir,ignore - /// blk0(a, b): - /// c = mul b, b % inst1 - /// d = add a, c % inst2 - /// e = eq.imm d, 0 % inst3 - /// br blk1(d, e, b) % inst4 - /// ``` - /// - /// This code would be a simple expression tree, except we have an instruction result which - /// is used twice in order to pass an intermediate result to the successor block. This is - /// what we'd like to examine in order to determine how such a block will get scheduled. - /// - /// Above we've annotated each instruction in the block with the instruction identifier, which - /// we'll use when referring to those instructions from now on. - /// - /// This IR would get represented in a [DependencyGraph] like so: - /// - /// ```text,ignore - /// inst4 ---------- - /// / \ | - /// v v v - /// arg(0) arg(1) arg(2) - /// | | | - /// | v | - /// | result(e) | - /// | | | - /// | v | - /// | inst3 | - /// v | | - /// result(d)<--- | - /// | | - /// v | - /// inst2 | - /// |_______ | - /// v v | - /// arg(0) arg(1) | - /// | | | - /// v v | - /// stack(a) result(c) | - /// | | - /// v | - /// inst1 | - /// / \ | - /// v v | - /// arg(0) arg(1) | - /// \ / | - /// v v | - /// stack(b)<---- - /// ``` - /// - /// As you can see, arguments and results are explicitly represented in the dependency graph - /// so that we can precisely represent a few key properties: - /// - /// 1. The unique arguments of an instruction - /// 2. The source of an argument (instruction result or stack operand) - /// 3. Which instruction results are used or unused - /// 4. Which instruction results have multiple uses - /// 5. Which values must be copied or moved, and at which points that must happen - /// - /// In any case, the dependency graph above gets translated to the following [TreeGraph]: - /// - /// ```text,ignore - /// inst4 - /// / | - /// v | - /// result(d) | - /// \ | - /// v v - /// stack(b) - /// ``` - /// - /// That might be confusing, but the intuition here is straightforward: the nodes which are - /// explicitly represented in a [TreeGraph] are those nodes in the original [DependencyGraph] - /// with either no dependents, or multiple dependents. Nodes in the original dependency graph - /// with a single dependent are condensed in the [TreeGraph] under whichever ancestor node - /// is a root in the graph. Thus, entire expression trees are collapsed into a single node - /// representing the root of that tree. - /// - /// One thing not shown above, but present in the [TreeGraph] structure, is what information - /// is carried on the edge between treegraph roots, e.g. `inst4` and `result(d)`. - /// In [TreeGraph] terms, the edge simply indicates that the tree represented by `inst4` - /// depends on the tree represented by `result(d)`. However, internally the [TreeGraph] - /// structure also encodes all of the individual dependencies between the two trees that - /// drove the formation of that edge. As a result, we can answer questions such as the number - /// of dependencies on a specific node by finding the treegraph root of the node, and for - /// each predecessor root in the graph, unpacking all of the dependencies along that edge - /// which reference the node in question. - /// - /// So the topological ordering of this graph is simply `[inst4, result(d), stack(b)]`. - /// - /// ## Algorithm - /// - /// The algorithm used to produce the topological ordering is simple: - /// - /// 1. Seed a queue with the set of treegraph roots with no predecessors, enqueuing them - /// in their natural sort order. - /// 2. Pop the next root from the queue, remove all edges between that root and its - /// dependencies, and add the root to the output vector. If any of the dependencies have no - /// remaining predecessors after the aforementioned edge was removed, it is added to the - /// queue. - /// 3. The process in step 2 is repeated until the queue is empty. - /// 4. If there are any edges remaining in the graph, there was a cycle, and thus a - /// topological sort is impossible, this will result in an error being returned. - /// 5. Otherwise, the sort is complete. - /// - /// In effect, a node is only emitted once all of its dependents are emitted, so for codegen, - /// we can use this ordering for instruction scheduling, by visiting nodes in reverse - /// topological order (i.e. visiting dependencies before any of their dependents). This also - /// has the effect of placing items on the stack in the correct order needed for each - /// instruction, as instruction operands will be pushed on the stack right-to-left, so that - /// the first operand to an instruction is on top of the stack. - pub fn toposort(&self) -> Result, UnexpectedCycleError> { - let mut treegraph = self.clone(); - let mut output = Vec::::with_capacity(treegraph.nodes.len()); - let mut roots = treegraph - .nodes - .iter() - .copied() - .filter(|nid| treegraph.num_predecessors(*nid) == 0) - .collect::>(); - - let mut successors = SmallVec::<[NodeId; 4]>::default(); - while let Some(nid) = roots.pop_front() { - output.push(nid); - successors.clear(); - successors.extend(treegraph.successor_ids(nid)); - for mid in successors.drain(..) { - treegraph.remove_edge(nid, mid); - if treegraph.num_predecessors(mid) == 0 { - roots.push_back(mid); - } - } - } - - let has_cycle = treegraph.edges.values().any(|es| !es.is_empty()); - if has_cycle { - Err(UnexpectedCycleError) - } else { - Ok(output) - } - } -} -impl From for TreeGraph { - fn from(mut depgraph: DependencyGraph) -> Self { - let mut cutset = Vec::<(NodeId, NodeId)>::default(); - let mut treegraph = Self::default(); - - // Build cutset - for node_id in depgraph.node_ids() { - let is_multi_use = depgraph.num_predecessors(node_id) > 1; - if is_multi_use { - cutset.extend(depgraph.predecessors(node_id).map(|d| (d.dependent, d.dependency))); - } - } - - // Apply cutset - for (dependent_id, dependency_id) in cutset.iter() { - depgraph.remove_edge(*dependent_id, *dependency_id); - } - - // Add roots to treegraph - for node_id in depgraph.node_ids() { - if depgraph.num_predecessors(node_id) == 0 { - treegraph.nodes.insert(node_id); - } - } - - // Construct mapping from dependency graph nodes to their - // corresponding treegraph nodes - let mut worklist = VecDeque::::default(); - for root in treegraph.nodes.iter().copied() { - worklist.push_back(root); - while let Some(node) = worklist.pop_front() { - treegraph.condensed.insert(node, root); - for dependency in depgraph.successor_ids(node) { - worklist.push_back(dependency); - } - } - } - - // Add cutset edges to treegraph - for (dependent_id, dependency_id) in cutset.into_iter() { - let a = treegraph.condensed[&dependent_id]; - let b = treegraph.condensed[&dependency_id]; - let edges = treegraph - .edges - .entry(EdgeId { - predecessor: a, - successor: b, - }) - .or_insert_with(Default::default); - let edge = DependencyEdge { - predecessor: dependent_id, - successor: dependency_id, - }; - if edges.contains(&edge) { - continue; - } - edges.push(edge); - } - - treegraph - } -} - -impl fmt::Debug for TreeGraph { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("TreeGraph") - .field("nodes", &DebugNodes(self)) - .field("edges", &DebugEdges(self)) - .finish() - } -} - -struct DebugNodes<'a>(&'a TreeGraph); -impl<'a> fmt::Debug for DebugNodes<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list().entries(self.0.nodes.iter()).finish() - } -} - -struct DebugEdges<'a>(&'a TreeGraph); -impl<'a> fmt::Debug for DebugEdges<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut edges = f.debug_list(); - for EdgeId { - predecessor, - successor, - } in self.0.edges.keys() - { - edges.entry(&format_args!("{predecessor} => {successor}")); - } - edges.finish() - } -} - -#[cfg(test)] -mod tests { - use midenc_hir as hir; - - use super::*; - - /// See [simple_dependency_graph] for details on the input dependency graph. - /// - /// We're expecting to have a treegraph that looks like the following: - /// - /// ```text,ignore - /// inst2 (no predecessors) v3 (no predecessors) - /// | - /// --> v1 (multiply-used) - /// | | - /// | v - /// --> v0 (multiply-used) - /// ``` - /// - /// We expect that in terms of scheduling, trees earlier in the topographical - /// sort of the treegraph are visited earlier during codegen, and within a - /// tree, nodes earlier in the topographical sort of that tree's dependency - /// graph component are visited earlier than other nodes in that tree. - /// - /// For reference, here's the original IR: - /// - /// ```text,ignore - /// block0(v0: i32): - /// v1 = inst0 v0 - /// v3 = inst3 - /// v2 = inst1 v1, v0 - /// inst2 v2, block1(v1), block2(v1, v0) - /// ``` - #[test] - fn treegraph_construction() { - let graph = simple_dependency_graph(); - let treegraph = OrderedTreeGraph::try_from(graph).unwrap(); - - let v0 = hir::Value::from_u32(0); - let v1 = hir::Value::from_u32(1); - let v2 = hir::Value::from_u32(2); - let inst0 = hir::Inst::from_u32(0); - let inst1 = hir::Inst::from_u32(1); - let inst2 = hir::Inst::from_u32(2); - let inst3 = hir::Inst::from_u32(3); - let v0_node = Node::Stack(v0); - let v1_node = Node::Result { - value: v1, - index: 0, - }; - let v2_node = Node::Result { - value: v2, - index: 0, - }; - let inst0_node = Node::Inst { id: inst0, pos: 0 }; - let inst1_node = Node::Inst { id: inst1, pos: 2 }; - let inst2_node = Node::Inst { id: inst2, pos: 3 }; - let inst3_node = Node::Inst { id: inst3, pos: 1 }; - - assert_eq!(treegraph.cmp_scheduling(inst2_node, inst2_node), Ordering::Equal); - assert_eq!(treegraph.cmp_scheduling(inst2_node, inst3_node), Ordering::Less); - assert_eq!(treegraph.cmp_scheduling(inst2_node, inst1_node), Ordering::Less); - assert_eq!(treegraph.cmp_scheduling(inst1_node, inst2_node), Ordering::Greater); - assert_eq!(treegraph.cmp_scheduling(inst1_node, inst0_node), Ordering::Less); - assert_eq!(treegraph.cmp_scheduling(inst1_node, v1_node), Ordering::Less); - assert_eq!(treegraph.cmp_scheduling(inst0_node, v0_node), Ordering::Less); - - // Instructions must be scheduled before all of their dependencies - assert!(treegraph.is_scheduled_before(inst2_node, inst3_node)); - assert!(treegraph.is_scheduled_before(inst2_node, inst1_node)); - assert!(treegraph.is_scheduled_before(inst2_node, inst0_node)); - assert!(treegraph.is_scheduled_before(inst2_node, v0_node)); - assert!(treegraph.is_scheduled_before(inst2_node, v1_node)); - assert!(treegraph.is_scheduled_before(inst2_node, v2_node)); - assert!(treegraph.is_scheduled_before(inst1_node, v0_node)); - assert!(treegraph.is_scheduled_before(inst1_node, v1_node)); - assert!(treegraph.is_scheduled_before(inst0_node, v0_node)); - // Results are scheduled before instructions which produce them - assert!(treegraph.is_scheduled_before(v2_node, inst1_node)); - } -} diff --git a/hir-analysis/src/validation/block.rs b/hir-analysis/src/validation/block.rs deleted file mode 100644 index 1f0234abe..000000000 --- a/hir-analysis/src/validation/block.rs +++ /dev/null @@ -1,272 +0,0 @@ -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, Report, Severity, Spanned}, - *, -}; -use rustc_hash::FxHashSet; -use smallvec::SmallVec; - -use super::Rule; -use crate::DominatorTree; - -/// This validation rule ensures that all values definitions dominate their uses. -/// -/// For example, it is not valid to use a value in a block when its definition only -/// occurs along a subset of control flow paths which may be taken to that block. -/// -/// This also catches uses of values which are orphaned (i.e. they are defined by -/// a block parameter or instruction which is not attached to the function). -pub struct DefsDominateUses<'a> { - dfg: &'a DataFlowGraph, - domtree: &'a DominatorTree, -} -impl<'a> DefsDominateUses<'a> { - pub fn new(dfg: &'a DataFlowGraph, domtree: &'a DominatorTree) -> Self { - Self { dfg, domtree } - } -} -impl<'a> Rule for DefsDominateUses<'a> { - fn validate( - &mut self, - block_data: &BlockData, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - let current_block = block_data.id; - let mut uses = FxHashSet::::default(); - let mut defs = FxHashSet::::default(); - for node in block_data.insts.iter() { - let span = node.span(); - - uses.clear(); - defs.clear(); - - // Verify the integrity of the instruction results - for result in self.dfg.inst_results(node.key) { - // It should never be possible for a value id to be present in the result set twice - assert!(defs.insert(*result)); - } - - // Gather all value uses to check - uses.extend(node.arguments(&self.dfg.value_lists).iter().copied()); - match node.analyze_branch(&self.dfg.value_lists) { - BranchInfo::NotABranch => (), - BranchInfo::SingleDest(info) => { - uses.extend(info.args.iter().copied()); - } - BranchInfo::MultiDest(ref infos) => { - for info in infos.iter() { - uses.extend(info.args.iter().copied()); - } - } - } - - // Make sure there are no uses of the instructions own results - if !defs.is_disjoint(&uses) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - "an instruction may not use its own results as arguments", - ) - .with_help( - "This situation can only arise if one has manually modified the arguments \ - of an instruction, incorrectly inserting a value obtained from the set \ - of instruction results.", - ) - .into_report()); - } - - // Next, ensure that all used values are dominated by their definition - for value in uses.iter().copied() { - match self.dfg.value_data(value) { - // If the value comes from the current block's parameter list, this use is - // trivially dominated - ValueData::Param { block, .. } if block == ¤t_block => continue, - // If the value comes from another block, then as long as all paths to the - // current block flow through that block, then this use is - // dominated by its definition - ValueData::Param { block, .. } => { - if self.domtree.dominates(*block, current_block, self.dfg) { - continue; - } - } - // If the value is an instruction result, then as long as all paths to the - // current instruction flow through the defining - // instruction, then this use is dominated by its definition - ValueData::Inst { inst, .. } => { - if self.domtree.dominates(*inst, node.key, self.dfg) { - continue; - } - } - } - - // If we reach here, the use of `value` is not dominated by its definition, - // so this use is invalid - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - "an argument of this instruction, {value}, is not defined on all paths \ - leading to this point", - ) - .with_help( - "All uses of a value must be dominated by its definition, i.e. all \ - control flow paths from the function entry to the point of each use must \ - flow through the point where that value is defined.", - ) - .into_report()); - } - } - - Ok(()) - } -} - -/// This validation rule ensures that most block-local invariants are upheld: -/// -/// * A block may not be empty -/// * A block must end with a terminator instruction -/// * A block may not contain a terminator instruction in any position but the end -/// * A block which terminates with a branch instruction must reference a block that is present in -/// the function body (i.e. it is not valid to reference detached blocks) -/// * A multi-way branch instruction must have at least one successor -/// * A multi-way branch instruction must not specify the same block as a successor multiple times. -/// -/// This rule does not perform type checking, or verify use/def dominance. -pub struct BlockValidator<'a> { - dfg: &'a DataFlowGraph, - span: SourceSpan, -} -impl<'a> BlockValidator<'a> { - pub fn new(dfg: &'a DataFlowGraph, span: SourceSpan) -> Self { - Self { dfg, span } - } -} -impl<'a> Rule for BlockValidator<'a> { - fn validate( - &mut self, - block_data: &BlockData, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - // Ignore blocks which are not attached to the function body - if !self.dfg.is_block_linked(block_data.id) { - return Ok(()); - } - - // Ensure there is a terminator, and that it is valid - let id = block_data.id; - let terminator = block_data.insts.back().get(); - if terminator.is_none() { - // This block is empty - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label(self.span, "block cannot be empty") - .with_help("Empty blocks are only valid when detached from the function body") - .into_report()); - } - - let terminator = terminator.unwrap(); - let op = terminator.opcode(); - if !op.is_terminator() { - // This block is empty - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label(self.span, "invalid block terminator") - .with_help(format!( - "The last instruction in a block must be a terminator, but {id} ends with \ - {op} which is not a valid terminator" - )) - .into_report()); - } - match terminator.analyze_branch(&self.dfg.value_lists) { - BranchInfo::SingleDest(info) => { - if !self.dfg.is_block_linked(info.destination) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label(terminator.span(), "invalid successor") - .with_help(format!( - "A block reference is only valid if the referenced block is present \ - in the function layout. {id} references {destination}, but the \ - latter is not in the layout", - destination = info.destination - )) - .into_report()); - } - } - BranchInfo::MultiDest(ref infos) => { - if infos.is_empty() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label( - terminator.span(), - format!("incomplete '{op}' instruction"), - ) - .with_help( - "This instruction normally has 2 or more successors, but none were \ - given.", - ) - .into_report()); - } - - let mut seen = SmallVec::<[Block; 4]>::default(); - for info in infos.iter() { - let destination = info.destination; - if !self.dfg.is_block_linked(destination) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label(terminator.span(), "invalid successor") - .with_help(format!( - "A block reference is only valid if the referenced block is \ - present in the function layout. {id} references {destination}, \ - but the latter is not in the layout" - )) - .into_report()); - } - - if seen.contains(&destination) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label( - terminator.span(), - format!("invalid '{op}' instruction"), - ) - .with_help(format!( - "A given block may only be a successor along a single control \ - flow path, but {id} uses {destination} as a successor for more \ - than one path" - )) - .into_report()); - } - - seen.push(destination); - } - } - BranchInfo::NotABranch => (), - } - - // Verify that there are no terminator instructions in any other position than last - for node in block_data.insts.iter() { - let op = node.opcode(); - if op.is_terminator() && node.key != terminator.key { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label(self.span, "terminator found in middle of block") - .with_help(format!( - "A block may only have a terminator instruction as the last instruction \ - in the block, but {id} uses {op} before the end of the block" - )) - .into_report()); - } - } - - Ok(()) - } -} diff --git a/hir-analysis/src/validation/function.rs b/hir-analysis/src/validation/function.rs deleted file mode 100644 index 337b3d745..000000000 --- a/hir-analysis/src/validation/function.rs +++ /dev/null @@ -1,389 +0,0 @@ -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, Report, Severity, Spanned}, - *, -}; - -use super::{BlockValidator, DefsDominateUses, NamingConventions, Rule, TypeCheck}; -use crate::{ControlFlowGraph, DominatorTree}; - -/// This validation rule ensures that function-local invariants are upheld: -/// -/// * A function may not be empty -/// * All blocks in the function body must be valid -/// * All uses of values must be dominated by their definitions -/// * All value uses must type check, i.e. branching to a block with a value -/// of a different type than declared by the block parameter is invalid. -pub struct FunctionValidator { - in_kernel_module: bool, -} -impl FunctionValidator { - pub fn new(in_kernel_module: bool) -> Self { - Self { in_kernel_module } - } -} -impl Rule for FunctionValidator { - fn validate( - &mut self, - function: &Function, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - // Validate the function declaration - let mut rules = NamingConventions.chain(CoherentSignature::new(self.in_kernel_module)); - rules.validate(function, diagnostics)?; - - // Ensure basic integrity of the function body - let mut rules = BlockValidator::new(&function.dfg, function.id.span()); - for (_, block) in function.dfg.blocks() { - rules.validate(block, diagnostics)?; - } - - // Construct control flow and dominator tree analyses - let cfg = ControlFlowGraph::with_function(function); - let domtree = DominatorTree::with_function(function, &cfg); - - // Verify value usage - let mut rules = DefsDominateUses::new(&function.dfg, &domtree) - .chain(TypeCheck::new(&function.signature, &function.dfg)); - for (_, block) in function.dfg.blocks() { - rules.validate(block, diagnostics)?; - } - - Ok(()) - } -} - -/// This validation rule ensures that a [Signature] is coherent -/// -/// A signature is coherent if: -/// -/// 1. The linkage is valid for functions -/// 2. The calling convention is valid in the context the function is defined in -/// 3. The ABI of its parameters matches the calling convention -/// 4. The ABI of the parameters and results are coherent, e.g. there are no signed integer -/// parameters which are specified as being zero-extended, there are no results if an sret -/// parameter is present, etc. -struct CoherentSignature { - in_kernel_module: bool, -} -impl CoherentSignature { - pub fn new(in_kernel_module: bool) -> Self { - Self { in_kernel_module } - } -} - -impl Rule for CoherentSignature { - fn validate( - &mut self, - function: &Function, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - let span = function.id.span(); - - // 1 - let linkage = function.signature.linkage; - if !matches!(linkage, Linkage::External | Linkage::Internal) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "the signature of this function specifies '{linkage}' linkage, but only \ - 'external' or 'internal' are valid", - ) - .into_report()); - } - - // 2 - let cc = function.signature.cc; - let is_kernel_function = matches!(cc, CallConv::Kernel); - if self.in_kernel_module { - let is_public = function.signature.is_public(); - if is_public && !is_kernel_function { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - format!( - "the '{cc}' calling convention may only be used with 'internal' \ - linkage in kernel modules", - ), - ) - .with_help( - "This function is declared with 'external' linkage in a kernel module, so \ - it must use the 'kernel' calling convention", - ) - .into_report()); - } else if !is_public && is_kernel_function { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "the 'kernel' calling convention may only be used with 'external' linkage", - ) - .with_help( - "This function has 'internal' linkage, so it must either be made \ - 'external', or a different calling convention must be used", - ) - .into_report()); - } - } else if is_kernel_function { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "the 'kernel' calling convention may only be used in kernel modules", - ) - .with_help( - "Kernel functions may only be declared in kernel modules, so you must either \ - change the module type, or change the calling convention of this function", - ) - .into_report()); - } - - // 3 - // * sret parameters may not be used with kernel calling convention - // * pointer-typed parameters/results may not be used with kernel calling convention - // * parameters larger than 8 bytes must be passed by reference with fast/C calling - // conventions - // * results larger than 8 bytes require the use of an sret parameter with fast/C calling - // conventions - // * total size of all parameters when laid out on the operand stack may not exceed 64 bytes - // (16 field elements) - // - // 4 - // * parameter count and types must be consistent between the signature and the entry block - // * only sret parameter is permitted, and it must be the first parameter when present - // * the sret attribute may not be applied to results - // * sret parameters imply no results - // * signed integer values may not be combined with zero-extension - // * non-integer values may not be combined with argument extension - let mut sret_count = 0; - let mut effective_stack_usage = 0; - let params = function.dfg.block_args(function.dfg.entry_block()); - if params.len() != function.signature.arity() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "function signature and entry block have different arities", - ) - .with_help( - "This happens if the signature or entry block are modified without updating \ - the other, make sure the number and types of all parameters are the same in \ - both the signature and the entry block", - ) - .into_report()); - } - for (i, param) in function.signature.params.iter().enumerate() { - let is_first = i == 0; - let value = params[i]; - let span = function.dfg.value_span(value); - let param_ty = ¶m.ty; - let value_ty = function.dfg.value_type(value); - - if param_ty != value_ty { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "parameter type mismatch between signature and entry block", - ) - .with_help(format!( - "The function declares this parameter as having type {param_ty}, but the \ - actual type is {value_ty}" - )) - .into_report()); - } - - let is_integer = param_ty.is_integer(); - let is_signed_integer = param_ty.is_signed_integer(); - match param.extension { - ArgumentExtension::Zext if is_signed_integer => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "signed integer parameters may not be combined with zero-extension", - ) - .with_help( - "Zero-extending a signed-integer loses the signedness, you should use \ - signed-extension instead", - ) - .into_report()); - } - ArgumentExtension::Sext | ArgumentExtension::Zext if !is_integer => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "non-integer parameters may not be combined with argument extension \ - attributes", - ) - .with_help( - "Argument extension has no meaning for types other than integers", - ) - .into_report()); - } - _ => (), - } - - let is_pointer = param_ty.is_pointer(); - let is_sret = param.purpose == ArgumentPurpose::StructReturn; - if is_sret { - sret_count += 1; - } - - if is_kernel_function && (is_sret || is_pointer) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "functions using the 'kernel' calling convention may not use sret or \ - pointer-typed parameters", - ) - .with_help( - "Kernel functions are invoked in a different memory context, so they may \ - not pass or return values by reference", - ) - .into_report()); - } - - if !is_kernel_function { - if is_sret { - if sret_count > 1 || !is_first { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "a function may only have a single sret parameter, and it must be \ - the first parameter", - ) - .with_help( - "The sret parameter type is used to return a large value from a \ - function, but it may only be used for functions with a single \ - return value", - ) - .into_report()); - } - if !is_pointer { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "sret parameters must be pointer-typed, but got {param_ty}", - ) - .with_help(format!( - "Did you mean to define this parameter with type {}?", - &Type::Ptr(Box::new(param_ty.clone())) - )) - .into_report()); - } - - if !function.signature.results.is_empty() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "functions with an sret parameter must have no results", - ) - .with_help( - "An sret parameter is used in place of normal return values, but \ - this function uses both, which is not valid. You should remove \ - the results from the function signature.", - ) - .into_report()); - } - } - - let size_in_bytes = param_ty.size_in_bytes(); - if !is_pointer && size_in_bytes > 8 { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "this parameter type is too large to pass by value", - ) - .with_help(format!( - "This parameter has type {param_ty}, you must refactor this function \ - to pass it by reference instead" - )) - .into_report()); - } - } - - effective_stack_usage += - param_ty.clone().to_raw_parts().map(|parts| parts.len()).unwrap_or(0); - } - - if effective_stack_usage > 16 { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label(span, "this function has a signature with too many parameters") - .with_help( - "Due to the constraints of the Miden VM, all function parameters must fit on \ - the operand stack, which is 16 elements (each of which is effectively 4 \ - bytes, a maximum of 64 bytes). The layout of the parameter list of this \ - function requires more than this limit. You should either remove parameters, \ - or combine some of them into a struct which is then passed by reference.", - ) - .into_report()); - } - - for (i, result) in function.signature.results.iter().enumerate() { - if result.purpose == ArgumentPurpose::StructReturn { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "the sret attribute is only permitted on function parameters", - ) - .into_report()); - } - - if result.extension != ArgumentExtension::None { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - span, - "the argument extension attributes are only permitted on function \ - parameters", - ) - .into_report()); - } - - let size_in_bytes = result.ty.size_in_bytes(); - if !result.ty.is_pointer() && size_in_bytes > 8 { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function signature") - .with_primary_label( - function.id.span(), - "This function specifies a result type which is too large to pass by value", - ) - .with_help(format!( - "The parameter at index {} has type {}, you must refactor this function \ - to pass it by reference instead", - i, &result.ty - )) - .into_report()); - } - } - - Ok(()) - } -} diff --git a/hir-analysis/src/validation/mod.rs b/hir-analysis/src/validation/mod.rs deleted file mode 100644 index 59fa947f6..000000000 --- a/hir-analysis/src/validation/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -mod block; -mod function; -mod naming; -mod typecheck; - -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, Report}, - pass::{Analysis, AnalysisManager, AnalysisResult}, - *, -}; -use midenc_session::Session; - -use self::{ - block::{BlockValidator, DefsDominateUses}, - function::FunctionValidator, - naming::NamingConventions, - typecheck::TypeCheck, -}; - -inventory::submit! { - midenc_session::CompileFlag::new("validate") - .long("no-validate") - .action(midenc_session::FlagAction::SetFalse) - .help("If present, disables validation of the IR prior to codegen") - .help_heading("Analysis") -} - -/// A [Rule] validates some specific type of behavior on an item of type `T` -pub trait Rule { - /// Validate `item`, using `diagnostics` to emit relevant diagnostics. - fn validate(&mut self, item: &T, diagnostics: &DiagnosticsHandler) -> Result<(), Report>; - - /// Combine two rules into one rule - fn chain(self, rule: R) -> RuleSet - where - Self: Sized, - R: Rule, - { - RuleSet::new(self, rule) - } -} -impl Rule for &mut R -where - R: Rule, -{ - fn validate(&mut self, item: &T, diagnostics: &DiagnosticsHandler) -> Result<(), Report> { - (*self).validate(item, diagnostics) - } -} -impl Rule for Box -where - R: Rule, -{ - fn validate(&mut self, item: &T, diagnostics: &DiagnosticsHandler) -> Result<(), Report> { - (**self).validate(item, diagnostics) - } -} -impl Rule for dyn FnMut(&T, &DiagnosticsHandler) -> Result<(), Report> { - #[inline] - fn validate(&mut self, item: &T, diagnostics: &DiagnosticsHandler) -> Result<(), Report> { - self(item, diagnostics) - } -} - -/// A [RuleSet] is a combination of multiple rules into a single [Rule] -pub struct RuleSet { - a: A, - b: B, -} -impl RuleSet { - fn new(a: A, b: B) -> Self { - Self { a, b } - } -} -impl Copy for RuleSet -where - A: Copy, - B: Copy, -{ -} -impl Clone for RuleSet -where - A: Clone, - B: Clone, -{ - #[inline] - fn clone(&self) -> Self { - Self::new(self.a.clone(), self.b.clone()) - } -} -impl Rule for RuleSet -where - A: Rule, - B: Rule, -{ - fn validate(&mut self, item: &T, diagnostics: &DiagnosticsHandler) -> Result<(), Report> { - self.a - .validate(item, diagnostics) - .and_then(|_| self.b.validate(item, diagnostics)) - } -} - -/// The [ModuleValidationAnalysis] can be used to validate and emit diagnostics for a [Module]. -/// -/// This validates all rules which apply to items at/within module scope. -#[derive(PassInfo)] -pub struct ModuleValidationAnalysis(Result<(), Report>); -impl Analysis for ModuleValidationAnalysis { - type Entity = Module; - - fn analyze( - module: &Self::Entity, - _analyses: &mut AnalysisManager, - session: &Session, - ) -> AnalysisResult { - if session.get_flag("validate") { - return Ok(Self(Ok(()))); - } - - Ok(Self(Self::validate(module, session))) - } -} -impl ModuleValidationAnalysis { - fn validate(module: &Module, session: &Session) -> Result<(), Report> { - // Apply module-scoped rules - let mut rules = NamingConventions; - rules.validate(module, &session.diagnostics)?; - - // Apply global-scoped rules - let mut rules = NamingConventions; - for global in module.globals().iter() { - rules.validate(global, &session.diagnostics)?; - } - - // Apply function-scoped rules - let mut rules = FunctionValidator::new(module.is_kernel()); - for function in module.functions() { - rules.validate(function, &session.diagnostics)?; - } - - Ok(()) - } -} -impl From for Result<(), Report> { - fn from(analysis: ModuleValidationAnalysis) -> Self { - analysis.0 - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::testing::TestContext; - - use super::*; - - #[test] - fn module_validator_test() { - let context = TestContext::default(); - - // Define the 'test' module - let mut builder = ModuleBuilder::new("test"); - builder.with_span(context.current_span()); - testing::sum_matrix(&mut builder, &context); - let module = builder.build(); - - let analysis = ModuleValidationAnalysis::validate(&module, &context.session); - analysis.expect("module was expected to be valid") - } -} diff --git a/hir-analysis/src/validation/naming.rs b/hir-analysis/src/validation/naming.rs deleted file mode 100644 index 66800a71d..000000000 --- a/hir-analysis/src/validation/naming.rs +++ /dev/null @@ -1,308 +0,0 @@ -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, Report, Severity, Spanned}, - *, -}; - -use super::Rule; - -/// This validation rule ensures that all identifiers adhere to the rules of their respective items. -pub struct NamingConventions; -impl Rule for NamingConventions { - fn validate( - &mut self, - module: &Module, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - // Make sure all functions in this module have the same module name in their id - for function in module.functions() { - let id = function.id; - if id.module != module.name { - let expected_name = FunctionIdent { - module: module.name, - function: id.function, - }; - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function name") - .with_primary_label( - function.id.span(), - format!("the fully-qualified name of this function is '{id}'"), - ) - .with_secondary_label( - module.name.span(), - format!( - "but we expected '{expected_name}' because it belongs to this module" - ), - ) - .into_report()); - } - } - - // 1. Must not be empty - let name = module.name.as_str(); - if name.is_empty() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid module name") - .with_primary_label(module.name.span, "module name cannot be empty") - .into_report()); - } - - // 2. Must begin with a lowercase ASCII alphabetic character - if !name.starts_with(is_lower_ascii_alphabetic) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid module name") - .with_primary_label( - module.name.span(), - "module name must start with a lowercase, ascii-alphabetic character", - ) - .into_report()); - } - - // 3. May otherwise consist of any number of characters of the following classes: - // * `A-Z` - // * `a-z` - // * `0-9` - // * `_-+$@` - // 4. May only contain `:` when used via the namespacing operator, e.g. `std::math` - let mut char_indices = name.char_indices().peekable(); - let mut is_namespaced = false; - while let Some((offset, c)) = char_indices.next() { - let offset = offset as u32; - match c { - c if c.is_ascii_alphanumeric() => continue, - '_' | '-' | '+' | '$' | '@' => continue, - ':' => match char_indices.peek() { - Some((_, ':')) => { - char_indices.next(); - is_namespaced = true; - continue; - } - _ => { - let module_name_span = module.name.span(); - let source_id = module_name_span.source_id(); - let pos = module_name_span.start() + offset; - let span = SourceSpan::at(source_id, pos); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid module name") - .with_primary_label(span, "module name contains invalid character ':'") - .with_help("Did you mean to use the namespacing operator '::'?") - .into_report()); - } - }, - c if c.is_whitespace() => { - let module_name_span = module.name.span(); - let source_id = module_name_span.source_id(); - let pos = module_name_span.start() + offset; - let span = SourceSpan::at(source_id, pos); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid module name") - .with_primary_label(span, "module names may not contain whitespace") - .into_report()); - } - c => { - let module_name_span = module.name.span(); - let source_id = module_name_span.source_id(); - let pos = module_name_span.start() + offset; - let span = SourceSpan::at(source_id, pos); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid module name") - .with_primary_label(span, format!("'{c}' is not valid in module names")) - .into_report()); - } - } - } - - // 5. The namespacing operator may only appear between two valid module identifiers - // 6. Namespaced module names must adhere to the above rules in each submodule identifier - if is_namespaced { - let mut offset = 0u32; - for component in name.split("::") { - let len = component.as_bytes().len() as u32; - let module_name_span = module.name.span(); - let source_id = module_name_span.source_id(); - let start = module_name_span.start() + offset; - let span = SourceSpan::new(source_id, start..(start + len)); - if component.is_empty() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid module namespace") - .with_primary_label(span, "submodule names cannot be empty") - .into_report()); - } - - if !name.starts_with(is_lower_ascii_alphabetic) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid module namespace") - .with_primary_label( - span, - "submodule name must start with a lowercase, ascii-alphabetic \ - character", - ) - .into_report()); - } - - offset += len + 2; - } - } - - Ok(()) - } -} -impl Rule for NamingConventions { - fn validate( - &mut self, - function: &Function, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - let name = function.id.function.as_str(); - let span = function.id.function.span(); - - // 1. Must not be empty - if name.is_empty() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function name") - .with_primary_label(span, "function names cannot be empty") - .into_report()); - } - - // 2. Must start with an ASCII-alphabetic character, underscore, `$` or `@` - fn name_starts_with(c: char) -> bool { - c.is_ascii_alphabetic() || matches!(c, '_' | '$' | '@') - } - - // 3. Otherwise, no restrictions, but may not contain whitespace - if let Err((offset, c)) = is_valid_identifier(name, name_starts_with, char::is_whitespace) { - let offset = offset as u32; - if c.is_whitespace() { - let source_id = span.source_id(); - let pos = span.start() + offset; - let span = SourceSpan::at(source_id, pos); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function name") - .with_primary_label(span, "function names may not contain whitespace") - .into_report()); - } else { - debug_assert_eq!(offset, 0); - let source_id = span.source_id(); - let span = SourceSpan::at(source_id, span.start()); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function name") - .with_primary_label( - span, - "function names must start with an ascii-alphabetic character, '_', '$', \ - or '@'", - ) - .into_report()); - } - } - - Ok(()) - } -} -impl Rule for NamingConventions { - fn validate( - &mut self, - global: &GlobalVariableData, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - let span = global.name.span(); - let name = global.name.as_str(); - - // 1. Must not be empty - if name.is_empty() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid global variable name") - .with_primary_label(span, "global variable names cannot be empty") - .into_report()); - } - - // 2. Must start with an ASCII-alphabetic character, underscore, `.`, `$` or `@` - fn name_starts_with(c: char) -> bool { - c.is_ascii_alphabetic() || matches!(c, '_' | '.' | '$' | '@') - } - - // 3. Otherwise, no restrictions, but may not contain whitespace - if let Err((offset, c)) = is_valid_identifier(name, name_starts_with, char::is_whitespace) { - let offset = offset as u32; - if c.is_whitespace() { - let source_id = span.source_id(); - let pos = span.start() + offset; - let span = SourceSpan::at(source_id, pos); - - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid global variable name") - .with_primary_label(span, "global variable names may not contain whitespace") - .into_report()); - } else { - debug_assert_eq!(offset, 0); - let span = SourceSpan::at(span.source_id(), span.start()); - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid global variable name") - .with_primary_label( - span, - "global variable names must start with an ascii-alphabetic character, \ - '_', '.', '$', or '@'", - ) - .into_report()); - } - } - - Ok(()) - } -} - -#[inline(always)] -fn is_lower_ascii_alphabetic(c: char) -> bool { - c.is_ascii_alphabetic() && c.is_ascii_lowercase() -} - -/// This is necessary until [std::str::Pattern] is stabilized -trait Pattern { - fn matches(&self, c: char) -> bool; -} -impl Pattern for char { - #[inline(always)] - fn matches(&self, c: char) -> bool { - *self == c - } -} -impl Pattern for F -where - F: Fn(char) -> bool, -{ - #[inline(always)] - fn matches(&self, c: char) -> bool { - self(c) - } -} - -#[inline] -fn is_valid_identifier(id: &str, start_with: P1, forbidden: P2) -> Result<(), (usize, char)> -where - P1: Pattern, - P2: Pattern, -{ - for (offset, c) in id.char_indices() { - if offset == 0 && !start_with.matches(c) { - return Err((offset, c)); - } - - if forbidden.matches(c) { - return Err((offset, c)); - } - } - - Ok(()) -} diff --git a/hir-analysis/src/validation/typecheck.rs b/hir-analysis/src/validation/typecheck.rs deleted file mode 100644 index b31e55808..000000000 --- a/hir-analysis/src/validation/typecheck.rs +++ /dev/null @@ -1,1318 +0,0 @@ -use alloc::collections::BTreeMap; -use core::fmt; - -use midenc_hir::{ - diagnostics::{DiagnosticsHandler, Report, Severity, Spanned}, - *, -}; - -use super::Rule; - -/// This error is produced when type checking the IR for function or module -#[derive(Debug, thiserror::Error)] -pub enum TypeError { - /// The number of arguments given does not match what is expected by the instruction - #[error("expected {expected} arguments, but {actual} are given")] - IncorrectArgumentCount { expected: usize, actual: usize }, - /// The number of results produced does not match what is expected from the instruction - #[error("expected {expected} results, but {actual} are produced")] - IncorrectResultCount { expected: usize, actual: usize }, - /// One of the arguments is not of the correct type - #[error("expected argument of {expected} type at index {index}, got {actual}")] - IncorrectArgumentType { - expected: TypePattern, - actual: Type, - index: usize, - }, - /// One of the results is not of the correct type - #[error("expected result of {expected} type at index {index}, got {actual}")] - InvalidResultType { - expected: TypePattern, - actual: Type, - index: usize, - }, - /// An attempt was made to cast from a larger integer type to a smaller one via widening cast, - /// e.g. `zext` - #[error("expected result to be an integral type larger than {expected}, but got {actual}")] - InvalidWideningCast { expected: Type, actual: Type }, - /// An attempt was made to cast from a smaller integer type to a larger one via narrowing cast, - /// e.g. `trunc` - #[error("expected result to be an integral type smaller than {expected}, but got {actual}")] - InvalidNarrowingCast { expected: Type, actual: Type }, - /// The arguments of an instruction were supposed to be the same type, but at least one differs - /// from the controlling type - #[error( - "expected arguments to be the same type ({expected}), but argument at index {index} is \ - {actual}" - )] - MatchingArgumentTypeViolation { - expected: Type, - actual: Type, - index: usize, - }, - /// The result type of an instruction was supposed to be the same as the arguments, but it - /// wasn't - #[error("expected result to be the same type ({expected}) as the arguments, but got {actual}")] - MatchingResultTypeViolation { expected: Type, actual: Type }, -} - -/// This validation rule type checks a block to catch any type violations by instructions in that -/// block -pub struct TypeCheck<'a> { - signature: &'a Signature, - dfg: &'a DataFlowGraph, -} -impl<'a> TypeCheck<'a> { - pub fn new(signature: &'a Signature, dfg: &'a DataFlowGraph) -> Self { - Self { signature, dfg } - } -} -impl<'a> Rule for TypeCheck<'a> { - fn validate( - &mut self, - block_data: &BlockData, - diagnostics: &DiagnosticsHandler, - ) -> Result<(), Report> { - // Traverse the block, checking each instruction in turn - for node in block_data.insts.iter() { - let span = node.span(); - let opcode = node.opcode(); - let results = self.dfg.inst_results(node.key); - let typechecker = InstTypeChecker::new(diagnostics, self.dfg, node)?; - - match node.as_ref() { - Instruction::UnaryOp(UnaryOp { arg, .. }) => match opcode { - Opcode::ImmI1 - | Opcode::ImmU8 - | Opcode::ImmI8 - | Opcode::ImmU16 - | Opcode::ImmI16 - | Opcode::ImmU32 - | Opcode::ImmI32 - | Opcode::ImmU64 - | Opcode::ImmI64 - | Opcode::ImmFelt - | Opcode::ImmF64 => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "immediate opcode '{opcode}' cannot be used with \ - non-immediate argument" - ), - ) - .into_report()); - } - _ => { - typechecker.check(&[*arg], results)?; - } - }, - Instruction::UnaryOpImm(UnaryOpImm { imm, .. }) => match opcode { - Opcode::PtrToInt => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!("'{opcode}' cannot be used with an immediate value"), - ) - .into_report()); - } - _ => { - typechecker.check_immediate(&[], imm, results)?; - } - }, - Instruction::Load(LoadOp { ref ty, addr, .. }) => { - if ty.size_in_felts() > 4 { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "cannot load a value of type {ty} on the stack, as it is \ - larger than 16 bytes" - ), - ) - .into_report()); - } - typechecker.check(&[*addr], results)?; - } - Instruction::BinaryOpImm(BinaryOpImm { imm, arg, .. }) => { - typechecker.check_immediate(&[*arg], imm, results)?; - } - Instruction::PrimOpImm(PrimOpImm { imm, args, .. }) => { - let args = args.as_slice(&self.dfg.value_lists); - typechecker.check_immediate(args, imm, results)?; - } - Instruction::LocalVar(LocalVarOp { op, local, args }) => { - let args = args.as_slice(&self.dfg.value_lists); - match op { - Opcode::Store => { - let expected_ty = self.dfg.local_type(*local); - let actual_ty = self.dfg.value_type(args[0]); - if actual_ty != expected_ty { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label( - span, - format!( - "local type is {expected_ty}, but argument is \ - {actual_ty}" - ), - ) - .into_report()); - } - typechecker.check(args, results)?; - } - Opcode::Load => { - if !args.is_empty() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - "local.load does not accept any arguments", - ) - .into_report()); - } - if results.len() != 1 { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - "local.load should have exactly one result", - ) - .into_report()); - } - let local_ty = self.dfg.local_type(*local); - if local_ty.size_in_felts() > 4 { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - "cannot load a value of type {local_ty} on the stack, as \ - it is larger than 16 bytes", - ) - .into_report()); - } - let result_ty = self.dfg.value_type(results[0]); - if local_ty != result_ty { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label( - span, - format!( - "local type is {local_ty}, but result of load is \ - {result_ty}" - ), - ) - .into_report()); - } - } - opcode => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "opcode '{opcode}' cannot be used with local variables" - ), - ) - .into_report()); - } - } - } - Instruction::GlobalValue(_) - | Instruction::BinaryOp(_) - | Instruction::PrimOp(_) - | Instruction::Test(_) - | Instruction::InlineAsm(_) - | Instruction::Call(_) => { - let args = node.arguments(&self.dfg.value_lists); - typechecker.check(args, results)?; - } - Instruction::Ret(Ret { ref args, .. }) => { - let args = args.as_slice(&self.dfg.value_lists); - if args.len() != self.signature.results.len() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "the function signature states that {} results should be \ - returned, but {} were given", - self.signature.results.len(), - args.len() - ), - ) - .into_report()); - } - for (index, (expected, arg)) in - self.signature.results.iter().zip(args.iter().copied()).enumerate() - { - let actual = self.dfg.value_type(arg); - if actual != &expected.ty { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label( - span, - format!( - "result at index {index} is {actual}, but function \ - signature expects {}", - &expected.ty - ), - ) - .into_report()); - } - } - } - Instruction::RetImm(RetImm { ref arg, .. }) => { - if self.signature.results.len() != 1 { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "the function signature states that {} results should be \ - returned, but {} were given", - self.signature.results.len(), - 1 - ), - ) - .into_report()); - } - let expected = &self.signature.results[0].ty; - let actual = arg.ty(); - if &actual != expected { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label( - span, - format!( - "result is {actual}, but function signature expects {expected}" - ), - ) - .into_report()); - } - } - Instruction::Br(Br { - successor: - Successor { - destination, - ref args, - }, - .. - }) => { - let successor = *destination; - let expected = self.dfg.block_args(successor); - let args = args.as_slice(&self.dfg.value_lists); - if args.len() != expected.len() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "{successor} expects {} arguments, but is being given {}", - expected.len(), - args.len() - ), - ) - .into_report()); - } - for (index, (param, arg)) in - expected.iter().copied().zip(args.iter().copied()).enumerate() - { - let expected = self.dfg.value_type(param); - let actual = self.dfg.value_type(arg); - if actual != expected { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label( - span, - format!( - "{successor} argument at index {index} is expected to be \ - {expected}, but got {actual}" - ), - ) - .into_report()); - } - } - } - Instruction::CondBr(CondBr { - cond, - ref then_dest, - ref else_dest, - .. - }) => { - typechecker.check(&[*cond], results)?; - - for successor in [then_dest, else_dest].into_iter() { - let expected = self.dfg.block_args(successor.destination); - let args = successor.args.as_slice(&self.dfg.value_lists); - if args.len() != expected.len() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "{successor} expects {} arguments, but is being given {}", - expected.len(), - args.len(), - successor = successor.destination, - ), - ) - .into_report()); - } - for (index, (param, arg)) in - expected.iter().copied().zip(args.iter().copied()).enumerate() - { - let expected = self.dfg.value_type(param); - let actual = self.dfg.value_type(arg); - if actual != expected { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label( - span, - format!( - "{successor} argument at index {index} is expected to \ - be {expected}, but got {actual}", - successor = successor.destination - ), - ) - .into_report()); - } - } - } - } - Instruction::Switch(Switch { - arg, - arms, - default: fallback, - .. - }) => { - typechecker.check(&[*arg], results)?; - - let mut seen = BTreeMap::::default(); - for (i, arm) in arms.iter().enumerate() { - if let Some(prev) = seen.insert(arm.value, i) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "all arms of a 'switch' must have a unique discriminant, \ - but the arm at index {i} has the same discriminant as \ - the arm at {prev}" - ), - ) - .into_report()); - } - } - - for (i, successor) in arms - .iter() - .map(|arm| &arm.successor) - .chain(core::iter::once(fallback)) - .enumerate() - { - let expected = self.dfg.block_args(successor.destination); - let args = successor.args.as_slice(&self.dfg.value_lists); - if args.len() != expected.len() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "the destination for the arm at index {i}, {successor}, \ - expects {} arguments, but is being given {}", - expected.len(), - args.len(), - successor = successor.destination, - ), - ) - .into_report()); - } - for (index, (param, arg)) in - expected.iter().copied().zip(args.iter().copied()).enumerate() - { - let expected = self.dfg.value_type(param); - let actual = self.dfg.value_type(arg); - if actual != expected { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label( - span, - format!( - "invalid switch arm at index {i}: {successor} \ - argument at index {index} is expected to be \ - {expected}, but got {actual}", - successor = successor.destination - ), - ) - .into_report()); - } - } - } - } - } - } - - Ok(()) - } -} - -/// This type represents a match pattern over kinds of types. -/// -/// This is quite useful in the type checker, as otherwise we would have to handle many -/// type combinations for each instruction. -#[derive(Debug, PartialEq, Eq)] -pub enum TypePattern { - /// Matches any type - Any, - /// Matches any integer type - Int, - /// Matches any unsigned integer type - Uint, - /// Matches any signed integer type - #[allow(dead_code)] - Sint, - /// Matches any pointer type - Pointer, - /// Matches any primitive numeric or pointer type - Primitive, - /// Matches a specific type - Exact(Type), -} -impl TypePattern { - /// Returns true if this pattern matches `ty` - pub fn matches(&self, ty: &Type) -> bool { - match self { - Self::Any => true, - Self::Int => ty.is_integer(), - Self::Uint => ty.is_unsigned_integer(), - Self::Sint => ty.is_signed_integer(), - Self::Pointer => ty.is_pointer(), - Self::Primitive => ty.is_numeric() || ty.is_pointer(), - Self::Exact(expected) => expected.eq(ty), - } - } -} -impl From for TypePattern { - #[inline(always)] - fn from(ty: Type) -> Self { - Self::Exact(ty) - } -} -impl fmt::Display for TypePattern { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Any => f.write_str("any"), - Self::Int => f.write_str("integer"), - Self::Uint => f.write_str("unsigned integer"), - Self::Sint => f.write_str("signed integer"), - Self::Pointer => f.write_str("pointer"), - Self::Primitive => f.write_str("primitive"), - Self::Exact(ty) => write!(f, "{ty}"), - } - } -} - -/// This type represents kinds of instructions in terms of their argument and result types. -/// -/// Each instruction kind represents a category of instructions with similar semantics. -pub enum InstPattern { - /// The instruction matches if it has no arguments or results - Empty, - /// The instruction matches if it has one argument and one result, both of the given type - Unary(TypePattern), - /// The instruction matches if it has one argument of the given type and no results - UnaryNoResult(TypePattern), - /// The instruction matches if it has one argument of the first type and one result of the - /// second type - /// - /// This is used to represent things like `inttoptr` or `ptrtoint` which map one type to - /// another - UnaryMap(TypePattern, TypePattern), - /// The instruction matches if it has one argument of integral type, and one result of a larger - /// integral type - UnaryWideningCast(TypePattern, TypePattern), - /// The instruction matches if it has one argument of integral type, and one result of a - /// smaller integral type - UnaryNarrowingCast(TypePattern, TypePattern), - /// The instruction matches if it has two arguments of the given type, and one result which is - /// the same type as the first argument - Binary(TypePattern, TypePattern), - /// The instruction matches if it has two arguments and one result, all of the same type - BinaryMatching(TypePattern), - /// The instruction matches if it has two arguments of the same type, and no results - BinaryMatchingNoResult(TypePattern), - /// The instruction matches if it has two arguments of the same type, and returns a boolean - BinaryPredicate(TypePattern), - /// The instruction matches if its first argument matches the first type, with two more - /// arguments and one result matching the second type - /// - /// This is used to model instructions like `select` - TernaryMatching(TypePattern, TypePattern), - /// The instruction matches if it has the exact number of arguments and results given, each - /// corresponding to the given type - Exact(Vec, Vec), - /// The instruction matches any number of arguments and results, of any type - Any, -} -impl InstPattern { - /// Evaluate this pattern against the given arguments and results - pub fn into_match( - self, - dfg: &DataFlowGraph, - args: &[Value], - results: &[Value], - ) -> Result<(), TypeError> { - match self { - Self::Empty => { - if !args.is_empty() { - return Err(TypeError::IncorrectArgumentCount { - expected: 0, - actual: args.len(), - }); - } - if !results.is_empty() { - return Err(TypeError::IncorrectResultCount { - expected: 0, - actual: args.len(), - }); - } - Ok(()) - } - Self::Unary(_) - | Self::UnaryMap(..) - | Self::UnaryWideningCast(..) - | Self::UnaryNarrowingCast(..) => { - if args.len() != 1 { - return Err(TypeError::IncorrectArgumentCount { - expected: 1, - actual: args.len(), - }); - } - if results.len() != 1 { - return Err(TypeError::IncorrectResultCount { - expected: 1, - actual: results.len(), - }); - } - let actual_in = dfg.value_type(args[0]); - let actual_out = dfg.value_type(results[0]); - self.into_unary_match(actual_in, Some(actual_out)) - } - Self::UnaryNoResult(_) => { - if args.len() != 1 { - return Err(TypeError::IncorrectArgumentCount { - expected: 1, - actual: args.len(), - }); - } - if !results.is_empty() { - return Err(TypeError::IncorrectResultCount { - expected: 0, - actual: results.len(), - }); - } - let actual = dfg.value_type(args[0]); - self.into_unary_match(actual, None) - } - Self::Binary(..) | Self::BinaryMatching(_) | Self::BinaryPredicate(_) => { - if args.len() != 2 { - return Err(TypeError::IncorrectArgumentCount { - expected: 2, - actual: args.len(), - }); - } - if results.len() != 1 { - return Err(TypeError::IncorrectResultCount { - expected: 1, - actual: results.len(), - }); - } - let lhs = dfg.value_type(args[0]); - let rhs = dfg.value_type(args[1]); - let result = dfg.value_type(results[0]); - self.into_binary_match(lhs, rhs, Some(result)) - } - Self::BinaryMatchingNoResult(_) => { - if args.len() != 2 { - return Err(TypeError::IncorrectArgumentCount { - expected: 2, - actual: args.len(), - }); - } - if !results.is_empty() { - return Err(TypeError::IncorrectResultCount { - expected: 0, - actual: results.len(), - }); - } - let lhs = dfg.value_type(args[0]); - let rhs = dfg.value_type(args[1]); - self.into_binary_match(lhs, rhs, None) - } - Self::TernaryMatching(..) => { - if args.len() != 3 { - return Err(TypeError::IncorrectArgumentCount { - expected: 3, - actual: args.len(), - }); - } - if results.len() != 1 { - return Err(TypeError::IncorrectResultCount { - expected: 1, - actual: results.len(), - }); - } - let cond = dfg.value_type(args[0]); - let lhs = dfg.value_type(args[1]); - let rhs = dfg.value_type(args[2]); - let result = dfg.value_type(results[0]); - self.into_ternary_match(cond, lhs, rhs, result) - } - Self::Exact(expected_args, expected_results) => { - if args.len() != expected_args.len() { - return Err(TypeError::IncorrectArgumentCount { - expected: expected_args.len(), - actual: args.len(), - }); - } - if results.len() != expected_results.len() { - return Err(TypeError::IncorrectResultCount { - expected: expected_results.len(), - actual: results.len(), - }); - } - for (index, (expected, arg)) in - expected_args.into_iter().zip(args.iter().copied()).enumerate() - { - let actual = dfg.value_type(arg); - if !expected.matches(actual) { - return Err(TypeError::IncorrectArgumentType { - expected, - actual: actual.clone(), - index, - }); - } - } - for (index, (expected, result)) in - expected_results.into_iter().zip(results.iter().copied()).enumerate() - { - let actual = dfg.value_type(result); - if !expected.matches(actual) { - return Err(TypeError::InvalidResultType { - expected, - actual: actual.clone(), - index, - }); - } - } - - Ok(()) - } - Self::Any => Ok(()), - } - } - - /// Evaluate this pattern against the given arguments (including an immediate argument) and - /// results - pub fn into_match_with_immediate( - self, - dfg: &DataFlowGraph, - args: &[Value], - imm: &Immediate, - results: &[Value], - ) -> Result<(), TypeError> { - match self { - Self::Empty => panic!("invalid empty pattern for instruction with immediate argument"), - Self::Unary(_) - | Self::UnaryMap(..) - | Self::UnaryWideningCast(..) - | Self::UnaryNarrowingCast(..) => { - if !args.is_empty() { - return Err(TypeError::IncorrectArgumentCount { - expected: 1, - actual: args.len() + 1, - }); - } - if results.len() != 1 { - return Err(TypeError::IncorrectResultCount { - expected: 1, - actual: results.len(), - }); - } - let actual_in = imm.ty(); - let actual_out = dfg.value_type(results[0]); - self.into_unary_match(&actual_in, Some(actual_out)) - } - Self::UnaryNoResult(_) => { - if !args.is_empty() { - return Err(TypeError::IncorrectArgumentCount { - expected: 1, - actual: args.len() + 1, - }); - } - if !results.is_empty() { - return Err(TypeError::IncorrectResultCount { - expected: 0, - actual: results.len(), - }); - } - let actual = imm.ty(); - self.into_unary_match(&actual, None) - } - Self::Binary(..) | Self::BinaryMatching(_) | Self::BinaryPredicate(_) => { - if args.len() != 1 { - return Err(TypeError::IncorrectArgumentCount { - expected: 2, - actual: args.len() + 1, - }); - } - if results.len() != 1 { - return Err(TypeError::IncorrectResultCount { - expected: 1, - actual: results.len(), - }); - } - let lhs = dfg.value_type(args[0]); - let rhs = imm.ty(); - let result = dfg.value_type(results[0]); - self.into_binary_match(lhs, &rhs, Some(result)) - } - Self::BinaryMatchingNoResult(_) => { - if args.len() != 1 { - return Err(TypeError::IncorrectArgumentCount { - expected: 2, - actual: args.len() + 1, - }); - } - if !results.is_empty() { - return Err(TypeError::IncorrectResultCount { - expected: 0, - actual: results.len(), - }); - } - let lhs = dfg.value_type(args[0]); - let rhs = imm.ty(); - self.into_binary_match(lhs, &rhs, None) - } - Self::TernaryMatching(..) => { - if args.len() != 2 { - return Err(TypeError::IncorrectArgumentCount { - expected: 3, - actual: args.len() + 1, - }); - } - if results.len() != 1 { - return Err(TypeError::IncorrectResultCount { - expected: 1, - actual: results.len(), - }); - } - let cond = dfg.value_type(args[0]); - let lhs = dfg.value_type(args[1]); - let rhs = imm.ty(); - let result = dfg.value_type(results[0]); - self.into_ternary_match(cond, lhs, &rhs, result) - } - Self::Exact(expected_args, expected_results) => { - if args.len() != expected_args.len() { - return Err(TypeError::IncorrectArgumentCount { - expected: expected_args.len(), - actual: args.len(), - }); - } - if results.len() != expected_results.len() { - return Err(TypeError::IncorrectResultCount { - expected: expected_results.len(), - actual: results.len(), - }); - } - for (index, (expected, arg)) in - expected_args.into_iter().zip(args.iter().copied()).enumerate() - { - let actual = dfg.value_type(arg); - if !expected.matches(actual) { - return Err(TypeError::IncorrectArgumentType { - expected, - actual: actual.clone(), - index, - }); - } - } - for (index, (expected, result)) in - expected_results.into_iter().zip(results.iter().copied()).enumerate() - { - let actual = dfg.value_type(result); - if !expected.matches(actual) { - return Err(TypeError::InvalidResultType { - expected, - actual: actual.clone(), - index, - }); - } - } - - Ok(()) - } - Self::Any => Ok(()), - } - } - - fn into_unary_match( - self, - actual_in: &Type, - actual_out: Option<&Type>, - ) -> Result<(), TypeError> { - match self { - Self::Unary(expected) | Self::UnaryNoResult(expected) => { - if !expected.matches(actual_in) { - return Err(TypeError::IncorrectArgumentType { - expected, - actual: actual_in.clone(), - index: 0, - }); - } - if let Some(actual_out) = actual_out { - if actual_in != actual_out { - return Err(TypeError::MatchingResultTypeViolation { - expected: actual_in.clone(), - actual: actual_out.clone(), - }); - } - } - } - Self::UnaryMap(expected_in, expected_out) => { - if !expected_in.matches(actual_in) { - return Err(TypeError::IncorrectArgumentType { - expected: expected_in, - actual: actual_in.clone(), - index: 0, - }); - } - let actual_out = actual_out.expect("expected result type"); - if !expected_out.matches(actual_out) { - return Err(TypeError::InvalidResultType { - expected: expected_out, - actual: actual_out.clone(), - index: 0, - }); - } - } - Self::UnaryWideningCast(expected_in, expected_out) => { - if !expected_in.matches(actual_in) { - return Err(TypeError::IncorrectArgumentType { - expected: expected_in, - actual: actual_in.clone(), - index: 0, - }); - } - let actual_out = actual_out.expect("expected result type"); - if !expected_out.matches(actual_out) { - return Err(TypeError::InvalidResultType { - expected: expected_out, - actual: actual_out.clone(), - index: 0, - }); - } - if actual_in.size_in_bits() > actual_out.size_in_bits() { - return Err(TypeError::InvalidWideningCast { - expected: actual_in.clone(), - actual: actual_out.clone(), - }); - } - } - Self::UnaryNarrowingCast(expected_in, expected_out) => { - if !expected_in.matches(actual_in) { - return Err(TypeError::IncorrectArgumentType { - expected: expected_in, - actual: actual_in.clone(), - index: 0, - }); - } - let actual_out = actual_out.expect("expected result type"); - if !expected_out.matches(actual_out) { - return Err(TypeError::InvalidResultType { - expected: expected_out, - actual: actual_out.clone(), - index: 0, - }); - } - if actual_in.size_in_bits() < actual_out.size_in_bits() { - return Err(TypeError::InvalidNarrowingCast { - expected: actual_in.clone(), - actual: actual_out.clone(), - }); - } - } - Self::Empty - | Self::Binary(..) - | Self::BinaryMatching(_) - | Self::BinaryMatchingNoResult(_) - | Self::BinaryPredicate(_) - | Self::TernaryMatching(..) - | Self::Exact(..) - | Self::Any => unreachable!(), - } - - Ok(()) - } - - fn into_binary_match( - self, - lhs: &Type, - rhs: &Type, - result: Option<&Type>, - ) -> Result<(), TypeError> { - match self { - Self::Binary(expected_lhs, expected_rhs) => { - if !expected_lhs.matches(lhs) { - return Err(TypeError::IncorrectArgumentType { - expected: expected_lhs, - actual: lhs.clone(), - index: 0, - }); - } - if !expected_rhs.matches(rhs) { - return Err(TypeError::IncorrectArgumentType { - expected: expected_rhs, - actual: rhs.clone(), - index: 1, - }); - } - let result = result.expect("expected result type"); - if lhs != result { - return Err(TypeError::MatchingResultTypeViolation { - expected: lhs.clone(), - actual: result.clone(), - }); - } - } - Self::BinaryMatching(expected) | Self::BinaryMatchingNoResult(expected) => { - if !expected.matches(lhs) { - return Err(TypeError::IncorrectArgumentType { - expected, - actual: lhs.clone(), - index: 0, - }); - } - if lhs != rhs { - return Err(TypeError::MatchingArgumentTypeViolation { - expected: lhs.clone(), - actual: rhs.clone(), - index: 1, - }); - } - if let Some(result) = result { - if lhs != result { - return Err(TypeError::MatchingResultTypeViolation { - expected: lhs.clone(), - actual: result.clone(), - }); - } - } - } - Self::BinaryPredicate(expected) => { - if !expected.matches(lhs) { - return Err(TypeError::IncorrectArgumentType { - expected, - actual: lhs.clone(), - index: 0, - }); - } - if lhs != rhs { - return Err(TypeError::MatchingArgumentTypeViolation { - expected: lhs.clone(), - actual: rhs.clone(), - index: 1, - }); - } - let result = result.expect("expected result type"); - let expected = Type::I1; - if result != &expected { - return Err(TypeError::MatchingResultTypeViolation { - expected, - actual: result.clone(), - }); - } - } - Self::Empty - | Self::Unary(_) - | Self::UnaryNoResult(_) - | Self::UnaryMap(..) - | Self::UnaryWideningCast(..) - | Self::UnaryNarrowingCast(..) - | Self::TernaryMatching(..) - | Self::Exact(..) - | Self::Any => unreachable!(), - } - - Ok(()) - } - - fn into_ternary_match( - self, - cond: &Type, - lhs: &Type, - rhs: &Type, - result: &Type, - ) -> Result<(), TypeError> { - match self { - Self::TernaryMatching(expected_cond, expected_inout) => { - if !expected_cond.matches(cond) { - return Err(TypeError::IncorrectArgumentType { - expected: expected_cond, - actual: cond.clone(), - index: 0, - }); - } - if !expected_inout.matches(lhs) { - return Err(TypeError::IncorrectArgumentType { - expected: expected_inout, - actual: lhs.clone(), - index: 1, - }); - } - if lhs != rhs { - return Err(TypeError::IncorrectArgumentType { - expected: lhs.clone().into(), - actual: rhs.clone(), - index: 2, - }); - } - if lhs != result { - return Err(TypeError::MatchingResultTypeViolation { - expected: lhs.clone(), - actual: result.clone(), - }); - } - } - Self::Empty - | Self::Unary(_) - | Self::UnaryNoResult(_) - | Self::UnaryMap(..) - | Self::UnaryWideningCast(..) - | Self::UnaryNarrowingCast(..) - | Self::Binary(..) - | Self::BinaryMatching(_) - | Self::BinaryMatchingNoResult(_) - | Self::BinaryPredicate(_) - | Self::Exact(..) - | Self::Any => unreachable!(), - } - - Ok(()) - } -} - -/// This type plays the role of type checking instructions. -/// -/// It is separate from the [TypeCheck] rule itself to factor out -/// all the instruction-related boilerplate. -struct InstTypeChecker<'a> { - diagnostics: &'a DiagnosticsHandler, - dfg: &'a DataFlowGraph, - span: SourceSpan, - pattern: InstPattern, -} -impl<'a> InstTypeChecker<'a> { - /// Create a new instance of the type checker for the instruction represented by `node`. - pub fn new( - diagnostics: &'a DiagnosticsHandler, - dfg: &'a DataFlowGraph, - node: &InstNode, - ) -> Result { - let span = node.span(); - let opcode = node.opcode(); - let is_local_op = matches!(&*node.data, Instruction::LocalVar(_)); - let pattern = match opcode { - Opcode::Assert | Opcode::Assertz => InstPattern::UnaryNoResult(Type::I1.into()), - Opcode::AssertEq => InstPattern::BinaryMatchingNoResult(Type::I1.into()), - Opcode::ImmI1 => InstPattern::Unary(Type::I1.into()), - Opcode::ImmU8 => InstPattern::Unary(Type::U8.into()), - Opcode::ImmI8 => InstPattern::Unary(Type::I8.into()), - Opcode::ImmU16 => InstPattern::Unary(Type::U16.into()), - Opcode::ImmI16 => InstPattern::Unary(Type::I16.into()), - Opcode::ImmU32 => InstPattern::Unary(Type::U32.into()), - Opcode::ImmI32 => InstPattern::Unary(Type::I32.into()), - Opcode::ImmU64 => InstPattern::Unary(Type::U64.into()), - Opcode::ImmI64 => InstPattern::Unary(Type::I64.into()), - Opcode::ImmU128 => InstPattern::Unary(Type::U128.into()), - Opcode::ImmI128 => InstPattern::Unary(Type::I128.into()), - Opcode::ImmFelt => InstPattern::Unary(Type::Felt.into()), - Opcode::ImmF64 => InstPattern::Unary(Type::F64.into()), - Opcode::Alloca => InstPattern::Exact(vec![], vec![TypePattern::Pointer]), - Opcode::MemGrow => InstPattern::Exact(vec![Type::U32.into()], vec![Type::I32.into()]), - Opcode::MemSize => InstPattern::Exact(vec![], vec![Type::U32.into()]), - opcode @ Opcode::GlobalValue => match node.as_ref() { - Instruction::GlobalValue(GlobalValueOp { global, .. }) => { - match dfg.global_value(*global) { - GlobalValueData::Symbol { .. } | GlobalValueData::IAddImm { .. } => { - InstPattern::Exact(vec![], vec![TypePattern::Pointer]) - } - GlobalValueData::Load { ref ty, .. } => { - InstPattern::Exact(vec![], vec![ty.clone().into()]) - } - } - } - inst => panic!("invalid opcode '{opcode}' for {inst:#?}"), - }, - Opcode::Load if is_local_op => InstPattern::Unary(TypePattern::Any), - Opcode::Load => InstPattern::UnaryMap(TypePattern::Pointer, TypePattern::Any), - Opcode::Store if is_local_op => InstPattern::UnaryNoResult(TypePattern::Any), - Opcode::Store => { - InstPattern::Exact(vec![TypePattern::Pointer, TypePattern::Any], vec![]) - } - Opcode::MemSet => InstPattern::Exact( - vec![TypePattern::Pointer, Type::U32.into(), TypePattern::Any], - vec![], - ), - Opcode::MemCpy => InstPattern::Exact( - vec![TypePattern::Pointer, TypePattern::Pointer, Type::U32.into()], - vec![], - ), - Opcode::PtrToInt => InstPattern::UnaryMap(TypePattern::Pointer, TypePattern::Int), - Opcode::IntToPtr => InstPattern::UnaryMap(TypePattern::Uint, TypePattern::Pointer), - Opcode::Bitcast => InstPattern::UnaryMap(TypePattern::Int, TypePattern::Int), - Opcode::Cast => InstPattern::UnaryMap(TypePattern::Int, TypePattern::Int), - Opcode::Trunc => InstPattern::UnaryNarrowingCast(TypePattern::Int, TypePattern::Int), - Opcode::Zext => InstPattern::UnaryWideningCast(TypePattern::Int, TypePattern::Uint), - Opcode::Sext => InstPattern::UnaryWideningCast(TypePattern::Int, TypePattern::Int), - Opcode::Test => InstPattern::UnaryMap(TypePattern::Int, Type::I1.into()), - Opcode::Select => InstPattern::TernaryMatching(Type::I1.into(), TypePattern::Primitive), - Opcode::Add - | Opcode::Sub - | Opcode::Mul - | Opcode::Div - | Opcode::Mod - | Opcode::DivMod - | Opcode::Band - | Opcode::Bor - | Opcode::Bxor => InstPattern::BinaryMatching(TypePattern::Int), - Opcode::Exp | Opcode::Shl | Opcode::Shr | Opcode::Rotl | Opcode::Rotr => { - InstPattern::Binary(TypePattern::Int, TypePattern::Uint) - } - Opcode::Neg - | Opcode::Inv - | Opcode::Incr - | Opcode::Ilog2 - | Opcode::Pow2 - | Opcode::Bnot - | Opcode::Popcnt - | Opcode::Clz - | Opcode::Ctz - | Opcode::Clo - | Opcode::Cto => InstPattern::Unary(TypePattern::Int), - Opcode::Not => InstPattern::Unary(Type::I1.into()), - Opcode::And | Opcode::Or | Opcode::Xor => InstPattern::BinaryMatching(Type::I1.into()), - Opcode::Eq | Opcode::Neq => InstPattern::BinaryPredicate(TypePattern::Primitive), - Opcode::Gt | Opcode::Gte | Opcode::Lt | Opcode::Lte => { - InstPattern::BinaryPredicate(TypePattern::Int) - } - Opcode::IsOdd => InstPattern::Exact(vec![TypePattern::Int], vec![Type::I1.into()]), - Opcode::Min | Opcode::Max => InstPattern::BinaryMatching(TypePattern::Int), - Opcode::Call | Opcode::Syscall => match node.as_ref() { - Instruction::Call(Call { ref callee, .. }) => { - if let Some(import) = dfg.get_import(callee) { - let args = import - .signature - .params - .iter() - .map(|p| TypePattern::Exact(p.ty.clone())) - .collect(); - let results = import - .signature - .results - .iter() - .map(|p| TypePattern::Exact(p.ty.clone())) - .collect(); - InstPattern::Exact(args, results) - } else { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!("no signature is available for {callee}"), - ) - .with_help( - "Make sure you import functions before building calls to them.", - ) - .into_report()); - } - } - inst => panic!("invalid opcode '{opcode}' for {inst:#?}"), - }, - Opcode::Br => InstPattern::Any, - Opcode::CondBr => InstPattern::Exact(vec![Type::I1.into()], vec![]), - Opcode::Switch => InstPattern::Exact(vec![Type::U32.into()], vec![]), - Opcode::Ret => InstPattern::Any, - Opcode::Unreachable => InstPattern::Empty, - Opcode::InlineAsm => InstPattern::Any, - Opcode::Spill | Opcode::Reload => InstPattern::Any, - }; - Ok(Self { - diagnostics, - dfg, - span: node.span(), - pattern, - }) - } - - /// Checks that the given `operands` and `results` match the types represented by this - /// [InstTypeChecker] - pub fn check(self, operands: &[Value], results: &[Value]) -> Result<(), Report> { - let diagnostics = self.diagnostics; - let dfg = self.dfg; - match self.pattern.into_match(dfg, operands, results) { - Ok(_) => Ok(()), - Err(err) => Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label(self.span, err) - .into_report()), - } - } - - /// Checks that the given `operands` (with immediate) and `results` match the types represented - /// by this [InstTypeChecker] - pub fn check_immediate( - self, - operands: &[Value], - imm: &Immediate, - results: &[Value], - ) -> Result<(), Report> { - let diagnostics = self.diagnostics; - let dfg = self.dfg; - match self.pattern.into_match_with_immediate(dfg, operands, imm, results) { - Ok(_) => Ok(()), - Err(err) => Err(diagnostics - .diagnostic(Severity::Error) - .with_message("type error") - .with_primary_label(self.span, err) - .into_report()), - } - } -} diff --git a/hir-macros/CHANGELOG.md b/hir-macros/CHANGELOG.md index 4abf3afa2..fb090df37 100644 --- a/hir-macros/CHANGELOG.md +++ b/hir-macros/CHANGELOG.md @@ -6,6 +6,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-hir-macros-v0.1.5...midenc-hir-macros-v0.4.0) - 2025-08-15 + +### Other + +- update operation proc macro documentation + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-hir-macros-v0.0.7...midenc-hir-macros-v0.0.8) - 2025-04-24 + +### Added +- implement hir components, global ops, various fixes improvements to cntrol ops +- implement #[operation] macro + +### Fixed +- *(ir)* expose dialect name in op registration +- allow to use `GlobalVariableRef` in the `GlobalSymbol` builder +- restore op type constraint verification, fix `zext` vs `sext` usage +- temporary disable op constraint verification due to the [#378](https://github.com/0xMiden/compiler/pull/378) +- broken hir-macro test + +### Other +- treat warnings as compiler errors, +- update rust toolchain, clean up deps +- rename hir2 crates +- *(ir)* support custom op printers, improve printing infra +- switch compiler to hir2 +- *(ir)* rework handling of entities with parents +- fix clippy warnings +- finish initial rewrite of backend using hir2 +- codegen +- implement a variety of useful apis on regions/blocks/ops/values +- promote attributes to top level, add ability to clone and hash type-erased attribute values +- make callables fundamental, move function/module to hir dialect +- ir redesign + ## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/midenc-hir-macros-v0.0.5...midenc-hir-macros-v0.0.6) - 2024-09-06 ### Other diff --git a/hir-macros/Cargo.toml b/hir-macros/Cargo.toml index c65f18312..061008eb1 100644 --- a/hir-macros/Cargo.toml +++ b/hir-macros/Cargo.toml @@ -17,10 +17,8 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +darling = { version = "0.20", features = ["diagnostics"] } Inflector.workspace = true -proc-macro2 = "1.0" -quote = "1.0" - -[dependencies.syn] -version = "2.0" -features = ["full", "parsing", "derive", "extra-traits", "printing"] +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true diff --git a/hir-macros/src/lib.rs b/hir-macros/src/lib.rs index f1f1491fc..d41541360 100644 --- a/hir-macros/src/lib.rs +++ b/hir-macros/src/lib.rs @@ -1,9 +1,12 @@ +#![deny(warnings)] + extern crate proc_macro; +mod operation; mod spanned; use inflector::cases::kebabcase::to_kebab_case; -use quote::quote; +use quote::{format_ident, quote}; use syn::{parse_macro_input, spanned::Spanned, Data, DeriveInput, Error, Ident, Token}; #[proc_macro_derive(Spanned, attributes(span))] @@ -25,6 +28,71 @@ pub fn derive_spanned(input: proc_macro::TokenStream) -> proc_macro::TokenStream } } +/// #[operation( +/// dialect = HirDialect, +/// traits(Terminator), +/// implements(BranchOpInterface), +/// )] +/// pub struct Switch { +/// #[operand] +/// selector: UInt32, +/// #[successors(keyed)] +/// cases: SwitchArm, +/// #[successor] +/// fallback: Successor, +/// } +/// +/// pub struct Call { +/// #[attr] +/// callee: Symbol, +/// #[operands] +/// arguments: Vec, +/// #[results] +/// results: Vec, +/// } +/// +/// #[operation] +/// pub struct If { +/// #[operand] +/// condition: Bool, +/// #[region] +/// then_region: RegionRef, +/// #[region] +/// else_region: RegionRef, +/// } +#[proc_macro_attribute] +pub fn operation( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let attr = proc_macro2::TokenStream::from(attr); + let mut input = syn::parse_macro_input!(item as syn::ItemStruct); + let span = input.span(); + + // Reconstruct the input so we can treat this like a derive macro + // + // We can't _actually_ use derive, because we need to modify the item itself. + input.attrs.push(syn::Attribute { + pound_token: syn::token::Pound(span), + style: syn::AttrStyle::Outer, + bracket_token: syn::token::Bracket(span), + meta: syn::Meta::List(syn::MetaList { + path: syn::parse_str("operation").unwrap(), + delimiter: syn::MacroDelimiter::Paren(syn::token::Paren(span)), + tokens: attr, + }), + }); + + let input = syn::parse_quote! { + #input + }; + + match operation::derive_operation(input) { + Ok(token_stream) => proc_macro::TokenStream::from(token_stream), + Err(err) => err.write_errors().into(), + } +} + #[proc_macro_derive(PassInfo)] pub fn derive_pass_info(item: proc_macro::TokenStream) -> proc_macro::TokenStream { let derive_input = parse_macro_input!(item as DeriveInput); @@ -36,7 +104,7 @@ pub fn derive_pass_info(item: proc_macro::TokenStream) -> proc_macro::TokenStrea let pass_name = to_kebab_case(&name); let pass_name_lit = syn::Lit::Str(syn::LitStr::new(&pass_name, id.span())); - let doc_ident = syn::Ident::new("doc", derive_span); + let doc_ident = format_ident!("doc", span = derive_span); let docs = derive_input .attrs .iter() diff --git a/hir-macros/src/op.rs b/hir-macros/src/op.rs new file mode 100644 index 000000000..baf0f473c --- /dev/null +++ b/hir-macros/src/op.rs @@ -0,0 +1,268 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{punctuated::Punctuated, DeriveInput, Token}; + +pub struct Op { + generics: syn::Generics, + ident: syn::Ident, + fields: syn::Fields, + args: OpArgs, +} + +#[derive(Default)] +pub struct OpArgs { + pub code: Option, + pub severity: Option, + pub help: Option, + pub labels: Option, + pub source_code: Option, + pub url: Option, + pub forward: Option, + pub related: Option, + pub diagnostic_source: Option, +} + +pub enum OpArg { + Transparent, + Code(Code), + Severity(Severity), + Help(Help), + Url(Url), + Forward(Forward), +} + +impl Parse for OpArg { + fn parse(input: ParseStream) -> syn::Result { + let ident = input.fork().parse::()?; + if ident == "transparent" { + // consume the token + let _: syn::Ident = input.parse()?; + Ok(OpArg::Transparent) + } else if ident == "forward" { + Ok(OpArg::Forward(input.parse()?)) + } else if ident == "code" { + Ok(OpArg::Code(input.parse()?)) + } else if ident == "severity" { + Ok(OpArg::Severity(input.parse()?)) + } else if ident == "help" { + Ok(OpArg::Help(input.parse()?)) + } else if ident == "url" { + Ok(OpArg::Url(input.parse()?)) + } else { + Err(syn::Error::new(ident.span(), "Unrecognized diagnostic option")) + } + } +} + +impl OpArgs { + pub(crate) fn forward_or_override_enum( + &self, + variant: &syn::Ident, + which_fn: WhichFn, + mut f: impl FnMut(&ConcreteOpArgs) -> Option, + ) -> Option { + match self { + Self::Transparent(forward) => Some(forward.gen_enum_match_arm(variant, which_fn)), + Self::Concrete(concrete) => f(concrete).or_else(|| { + concrete + .forward + .as_ref() + .map(|forward| forward.gen_enum_match_arm(variant, which_fn)) + }), + } + } +} + +impl OpArgs { + fn parse( + _ident: &syn::Ident, + fields: &syn::Fields, + attrs: &[&syn::Attribute], + allow_transparent: bool, + ) -> syn::Result { + let mut errors = Vec::new(); + + let mut concrete = OpArgs::for_fields(fields)?; + for attr in attrs { + let args = attr.parse_args_with(Punctuated::::parse_terminated); + let args = match args { + Ok(args) => args, + Err(error) => { + errors.push(error); + continue; + } + }; + + concrete.add_args(attr, args, &mut errors); + } + + let combined_error = errors.into_iter().reduce(|mut lhs, rhs| { + lhs.combine(rhs); + lhs + }); + if let Some(error) = combined_error { + Err(error) + } else { + Ok(concrete) + } + } + + fn for_fields(fields: &syn::Fields) -> Result { + let labels = Labels::from_fields(fields)?; + let source_code = SourceCode::from_fields(fields)?; + let related = Related::from_fields(fields)?; + let help = Help::from_fields(fields)?; + let diagnostic_source = DiagnosticSource::from_fields(fields)?; + Ok(Self { + code: None, + help, + related, + severity: None, + labels, + url: None, + forward: None, + source_code, + diagnostic_source, + }) + } + + fn add_args( + &mut self, + attr: &syn::Attribute, + args: impl Iterator, + errors: &mut Vec, + ) { + for arg in args { + match arg { + OpArg::Transparent => { + errors.push(syn::Error::new_spanned(attr, "transparent not allowed")); + } + OpArg::Forward(to_field) => { + if self.forward.is_some() { + errors.push(syn::Error::new_spanned( + attr, + "forward has already been specified", + )); + } + self.forward = Some(to_field); + } + OpArg::Code(new_code) => { + if self.code.is_some() { + errors + .push(syn::Error::new_spanned(attr, "code has already been specified")); + } + self.code = Some(new_code); + } + OpArg::Severity(sev) => { + if self.severity.is_some() { + errors.push(syn::Error::new_spanned( + attr, + "severity has already been specified", + )); + } + self.severity = Some(sev); + } + OpArg::Help(hl) => { + if self.help.is_some() { + errors + .push(syn::Error::new_spanned(attr, "help has already been specified")); + } + self.help = Some(hl); + } + OpArg::Url(u) => { + if self.url.is_some() { + errors + .push(syn::Error::new_spanned(attr, "url has already been specified")); + } + self.url = Some(u); + } + } + } + } +} + +impl Op { + pub fn from_derive_input(input: DeriveInput) -> Result { + let input_attrs = input + .attrs + .iter() + .filter(|x| x.path().is_ident("operation")) + .collect::>(); + Ok(match input.data { + syn::Data::Struct(data_struct) => { + let args = OpArgs::parse(&input.ident, &data_struct.fields, &input_attrs, true)?; + + Op { + fields: data_struct.fields, + ident: input.ident, + generics: input.generics, + args, + } + } + syn::Data::Enum(_) | syn::Data::Union(_) => { + return Err(syn::Error::new( + input.ident.span(), + "Can't derive Op for enums or unions", + )) + } + }) + } + + pub fn gen(&self) -> TokenStream { + let (impl_generics, ty_generics, where_clause) = &self.generics.split_for_impl(); + let concrete = &self.args; + let forward = |which| concrete.forward.as_ref().map(|fwd| fwd.gen_struct_method(which)); + let code_body = concrete + .code + .as_ref() + .and_then(|x| x.gen_struct()) + .or_else(|| forward(WhichFn::Code)); + let help_body = concrete + .help + .as_ref() + .and_then(|x| x.gen_struct(fields)) + .or_else(|| forward(WhichFn::Help)); + let sev_body = concrete + .severity + .as_ref() + .and_then(|x| x.gen_struct()) + .or_else(|| forward(WhichFn::Severity)); + let rel_body = concrete + .related + .as_ref() + .and_then(|x| x.gen_struct()) + .or_else(|| forward(WhichFn::Related)); + let url_body = concrete + .url + .as_ref() + .and_then(|x| x.gen_struct(ident, fields)) + .or_else(|| forward(WhichFn::Url)); + let labels_body = concrete + .labels + .as_ref() + .and_then(|x| x.gen_struct(fields)) + .or_else(|| forward(WhichFn::Labels)); + let src_body = concrete + .source_code + .as_ref() + .and_then(|x| x.gen_struct(fields)) + .or_else(|| forward(WhichFn::SourceCode)); + let diagnostic_source = concrete + .diagnostic_source + .as_ref() + .and_then(|x| x.gen_struct()) + .or_else(|| forward(WhichFn::DiagnosticSource)); + quote! { + impl #impl_generics miette::Diagnostic for #ident #ty_generics #where_clause { + #code_body + #help_body + #sev_body + #rel_body + #url_body + #labels_body + #src_body + #diagnostic_source + } + } + } +} diff --git a/hir-macros/src/operation.rs b/hir-macros/src/operation.rs new file mode 100644 index 000000000..f8f526d7d --- /dev/null +++ b/hir-macros/src/operation.rs @@ -0,0 +1,2754 @@ +use std::rc::Rc; + +use darling::{ + util::{Flag, SpannedValue}, + Error, FromDeriveInput, FromField, FromMeta, +}; +use inflector::Inflector; +use quote::{format_ident, quote, ToTokens}; +use syn::{parse_quote, spanned::Spanned, Ident, Token}; + +pub fn derive_operation(input: syn::DeriveInput) -> darling::Result { + let op = OpDefinition::from_derive_input(&input)?; + + Ok(op.into_token_stream()) +} + +/// This struct represents the fully parsed and prepared definition of an operation, along with all +/// of its associated items, trait impls, etc. +pub struct OpDefinition { + /// The span of the original item decorated with `#[operation]` + span: proc_macro2::Span, + /// The name of the dialect type corresponding to the dialect this op belongs to + dialect: Ident, + /// The type name of the concrete `Op` implementation, i.e. the item with `#[operation]` on it + name: Ident, + /// The name of the operation in the textual form of the IR, e.g. `Add` would be `add`. + opcode: Ident, + /// The set of paths corresponding to the op traits we need to generate impls for + traits: darling::util::PathList, + /// The set of paths corresponding to the op traits manually implemented by this op + implements: darling::util::PathList, + /// The named regions declared for this op + regions: Vec, + /// The named attributes declared for this op + attrs: Vec, + /// The named operands, and operand groups, declared for this op + /// + /// Sequential individually named operands are collected into an "unnamed" operand group, i.e. + /// the group is not named, only the individual operands. Conversely, each "named" operand group + /// can refer to the group by name, but not the individual operands. + operands: Vec, + /// The named results of this operation + /// + /// An operation can have no results, one or more individually named results, or a single named + /// result group, but not a combination. + results: Option, + /// The named successors, and successor groups, declared for this op. + /// + /// This is represented almost identically to `operands`, except we also support successor + /// groups with "keyed" items represented by an implementation of the `KeyedSuccessor` trait. + /// Keyed successor groups are handled a bit differently than "normal" successor groups in terms + /// of the types expected by the op builder for this type. + successors: Vec, + /// The symbolic references held by this op + symbols: Vec, + /// The struct definition + op: syn::ItemStruct, + /// The implementation of `{Op}Builder` for this op. + op_builder_impl: OpBuilderImpl, + /// The implementation of `OpVerifier` for this op. + op_verifier_impl: OpVerifierImpl, +} +impl OpDefinition { + /// Initialize an [OpDefinition] from the parsed [Operation] received as input + fn from_operation(span: proc_macro2::Span, op: &mut Operation) -> darling::Result { + let dialect = op.dialect.clone(); + let name = op.ident.clone(); + let opcode = op.name.clone().unwrap_or_else(|| { + let name = name.to_string().to_snake_case(); + let name = name.strip_suffix("Op").unwrap_or(name.as_str()); + format_ident!("{name}", span = name.span()) + }); + let traits = core::mem::take(&mut op.traits); + let implements = core::mem::take(&mut op.implements); + + let fields = core::mem::replace( + &mut op.data, + darling::ast::Data::Struct(darling::ast::Fields::new( + darling::ast::Style::Struct, + vec![], + )), + ) + .take_struct() + .unwrap(); + + let mut named_fields = syn::punctuated::Punctuated::::new(); + // Add the `op` field (which holds the underlying Operation) + named_fields.push(syn::Field { + attrs: vec![], + vis: syn::Visibility::Inherited, + mutability: syn::FieldMutability::None, + ident: Some(format_ident!("op")), + colon_token: Some(syn::token::Colon(span)), + ty: make_type("::midenc_hir::Operation"), + }); + + let op = syn::ItemStruct { + attrs: core::mem::take(&mut op.attrs), + vis: op.vis.clone(), + struct_token: syn::token::Struct(span), + ident: name.clone(), + generics: core::mem::take(&mut op.generics), + fields: syn::Fields::Named(syn::FieldsNamed { + brace_token: syn::token::Brace(span), + named: named_fields, + }), + semi_token: None, + }; + + let op_builder_impl = OpBuilderImpl::empty(name.clone()); + let op_verifier_impl = + OpVerifierImpl::new(name.clone(), traits.clone(), implements.clone()); + + let mut this = Self { + span, + dialect, + name, + opcode, + traits, + implements, + regions: vec![], + attrs: vec![], + operands: vec![], + results: None, + successors: vec![], + symbols: vec![], + op, + op_builder_impl, + op_verifier_impl, + }; + + this.hydrate(fields)?; + + Ok(this) + } + + fn hydrate(&mut self, fields: darling::ast::Fields) -> darling::Result<()> { + let named_fields = match &mut self.op.fields { + syn::Fields::Named(syn::FieldsNamed { ref mut named, .. }) => named, + _ => unreachable!(), + }; + let mut create_params = vec![]; + let (_, mut fields) = fields.split(); + + // Compute the absolute ordering of op parameters as follows: + // + // * By default, the ordering is implied by the order of field declarations in the struct + // * A field can be decorated with #[order(N)], where `N` is an absolute index + // * If all fields have an explicit order, then the sort following that order is used + // * If a mix of fields have explicit ordering, so as to acheive a particular struct layout, + // then the implicit order given to a field ensures that it appears after the highest + // ordered field which comes before it in the struct. For example, if I have the following + // pseudo-struct definition: `{ #[order(2)] a, b, #[order(1)] c, d }`, then the actual + // order of the parameters corresponding to those fields will be `c`, `a`, `b`, `d`. This + // is due to the fact that a.) `b` is assigned an index of `3` because it is the next + // available index following `2`, which was assigned to `a` before it in the struct, and + // 2.) `d` is assigned an index of `4`, as it is the next highest available index after + // `2`, which is the highest explicitly ordered field that is defined before it in the + // struct. + let mut assigned_highwater = 0; + let mut highwater = 0; + let mut claimed_indices = fields.iter().filter_map(|f| f.attrs.order).collect::>(); + claimed_indices.sort(); + claimed_indices.dedup(); + for field in fields.iter_mut() { + match field.attrs.order { + // If this order precedes a previous #[order] field, skip it + Some(order) if highwater > order => continue, + Some(order) => { + // Move high water mark to `order` + highwater = order; + } + None => { + // Find the next unused index > `highwater` && `assigned_highwater` + assigned_highwater = core::cmp::max(assigned_highwater, highwater); + let mut next_index = assigned_highwater + 1; + while claimed_indices.contains(&next_index) { + next_index += 1; + } + assigned_highwater = next_index; + field.attrs.order = Some(next_index); + } + } + } + fields.sort_by_key(|field| field.attrs.order); + + for field in fields { + let field_name = field.ident.clone().unwrap(); + let field_span = field_name.span(); + let field_ty = field.ty.clone(); + + let op_field_ty = field.attrs.pseudo_type(); + match op_field_ty.as_deref() { + // Forwarded field + None => { + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::CustomField(field_name.clone(), field_ty), + r#default: field.attrs.default.is_present(), + }); + named_fields.push(syn::Field { + attrs: field.attrs.forwarded, + vis: field.vis, + mutability: syn::FieldMutability::None, + ident: Some(field_name), + colon_token: Some(syn::token::Colon(field_span)), + ty: field.ty, + }); + continue; + } + Some(OperationFieldType::Attr(kind)) => { + let attr = OpAttribute { + name: field_name, + ty: field_ty, + kind: *kind, + }; + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::Attr(attr.clone()), + r#default: field.attrs.default.is_present(), + }); + self.attrs.push(attr); + } + Some(OperationFieldType::Operand) => { + let operand = Operand { + name: field_name.clone(), + constraint: field_ty, + }; + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::Operand(operand.clone()), + r#default: field.attrs.default.is_present(), + }); + match self.operands.last_mut() { + None => { + self.operands.push(OpOperandGroup::Unnamed(vec![operand])); + } + Some(OpOperandGroup::Unnamed(ref mut operands)) => { + operands.push(operand); + } + Some(OpOperandGroup::Named(..)) => { + // Start a new group + self.operands.push(OpOperandGroup::Unnamed(vec![operand])); + } + } + } + Some(OperationFieldType::Operands) => { + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::OperandGroup( + field_name.clone(), + field_ty.clone(), + ), + r#default: field.attrs.default.is_present(), + }); + self.operands.push(OpOperandGroup::Named(field_name, field_ty)); + } + Some(OperationFieldType::Result) => { + let result = OpResult { + name: field_name.clone(), + constraint: field_ty, + }; + match self.results.as_mut() { + None => { + self.results = Some(OpResultGroup::Unnamed(vec![result])); + } + Some(OpResultGroup::Unnamed(ref mut results)) => { + results.push(result); + } + Some(OpResultGroup::Named(..)) => { + return Err(Error::custom("#[result] and #[results] cannot be mixed") + .with_span(&field_name)); + } + } + } + Some(OperationFieldType::Results) => match self.results.as_mut() { + None => { + self.results = Some(OpResultGroup::Named(field_name, field_ty)); + } + Some(OpResultGroup::Unnamed(_)) => { + return Err(Error::custom("#[result] and #[results] cannot be mixed") + .with_span(&field_name)); + } + Some(OpResultGroup::Named(..)) => { + return Err(Error::custom("#[results] may only appear on a single field") + .with_span(&field_name)); + } + }, + Some(OperationFieldType::Region) => { + self.regions.push(field_name); + } + Some(OperationFieldType::Successor) => { + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::Successor(field_name.clone()), + r#default: field.attrs.default.is_present(), + }); + match self.successors.last_mut() { + None => { + self.successors.push(SuccessorGroup::Unnamed(vec![field_name])); + } + Some(SuccessorGroup::Unnamed(ref mut ids)) => { + ids.push(field_name); + } + Some(SuccessorGroup::Named(_) | SuccessorGroup::Keyed(..)) => { + // Start a new group + self.successors.push(SuccessorGroup::Unnamed(vec![field_name])); + } + } + } + Some(OperationFieldType::Successors(SuccessorsType::Default)) => { + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::SuccessorGroupNamed(field_name.clone()), + r#default: field.attrs.default.is_present(), + }); + self.successors.push(SuccessorGroup::Named(field_name)); + } + Some(OperationFieldType::Successors(SuccessorsType::Keyed)) => { + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::SuccessorGroupKeyed( + field_name.clone(), + field_ty.clone(), + ), + r#default: field.attrs.default.is_present(), + }); + self.successors.push(SuccessorGroup::Keyed(field_name, field_ty)); + } + Some(OperationFieldType::Symbol(None)) => { + let symbol = Symbol { + name: field_name, + ty: SymbolType::Concrete(field_ty), + }; + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::Symbol(symbol.clone()), + r#default: field.attrs.default.is_present(), + }); + self.symbols.push(symbol); + } + Some(OperationFieldType::Symbol(Some(ty))) => { + let symbol = Symbol { + name: field_name, + ty: ty.clone(), + }; + create_params.push(OpCreateParam { + param_ty: OpCreateParamType::Symbol(symbol.clone()), + r#default: field.attrs.default.is_present(), + }); + self.symbols.push(symbol); + } + } + } + + self.op_builder_impl.set_create_params(&self.op.generics, create_params); + + Ok(()) + } +} +impl FromDeriveInput for OpDefinition { + fn from_derive_input(input: &syn::DeriveInput) -> darling::Result { + let span = input.span(); + let mut operation = Operation::from_derive_input(input)?; + Self::from_operation(span, &mut operation) + } +} + +struct OpCreateFn<'a> { + op: &'a OpDefinition, + generics: syn::Generics, +} +impl<'a> OpCreateFn<'a> { + pub fn new(op: &'a OpDefinition) -> Self { + // Op::create generic parameters + let generics = syn::Generics { + lt_token: Some(syn::token::Lt(op.span)), + params: syn::punctuated::Punctuated::from_iter( + [syn::parse_str("B: ?Sized + ::midenc_hir::Builder").unwrap()] + .into_iter() + .chain(op.op_builder_impl.buildable_op_impl.generics.params.iter().cloned()), + ), + gt_token: Some(syn::token::Gt(op.span)), + where_clause: op.op_builder_impl.buildable_op_impl.generics.where_clause.clone(), + }; + + Self { op, generics } + } +} + +struct WithAttrs<'a>(&'a OpDefinition); +impl quote::ToTokens for WithAttrs<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for param in self.0.op_builder_impl.create_params.iter() { + if let OpCreateParamType::Attr(OpAttribute { name, kind, .. }) = ¶m.param_ty { + let field_name = syn::Lit::Str(syn::LitStr::new(&format!("{name}"), name.span())); + if matches!(kind, AttrKind::Hidden) { + tokens.extend(quote! { + op_builder.with_hidden_attr(#field_name, #name); + }); + } else { + tokens.extend(quote! { + op_builder.with_attr(#field_name, #name); + }); + } + } + } + } +} + +struct WithSymbols<'a>(&'a OpDefinition); +impl quote::ToTokens for WithSymbols<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for param in self.0.op_builder_impl.create_params.iter() { + if let OpCreateParamType::Symbol(Symbol { name, ty }) = ¶m.param_ty { + let field_name = syn::Lit::Str(syn::LitStr::new(&format!("{name}"), name.span())); + match ty { + SymbolType::Any | SymbolType::Concrete(_) | SymbolType::Trait(_) => { + tokens.extend(quote! { + op_builder.with_symbol(#field_name, #name); + }); + } + SymbolType::Callable => { + tokens.extend(quote! { + op_builder.with_callable_symbol(#field_name, #name); + }); + } + } + } + } + } +} + +struct WithOperands<'a>(&'a OpDefinition); +impl quote::ToTokens for WithOperands<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for (group_index, group) in self.0.operands.iter().enumerate() { + match group { + OpOperandGroup::Unnamed(operands) => { + let group_index = syn::Lit::Int(syn::LitInt::new( + &format!("{group_index}usize"), + operands[0].name.span(), + )); + let operand_name = operands.iter().map(|o| &o.name).collect::>(); + let operand_constraint = operands.iter().map(|o| &o.constraint); + let constraint_violation = operands.iter().map(|o| { + syn::Lit::Str(syn::LitStr::new( + &format!("type constraint violation for '{}'", &o.name), + o.name.span(), + )) + }); + tokens.extend(quote! { + #( + { + let value = #operand_name.borrow(); + let value_ty = value.ty(); + if !<#operand_constraint as ::midenc_hir::traits::TypeConstraint>::matches(value_ty) { + let expected = <#operand_constraint as ::midenc_hir::traits::TypeConstraint>::description(); + return Err(builder.context() + .session() + .diagnostics + .diagnostic(::midenc_hir::diagnostics::Severity::Error) + .with_message("invalid operand") + .with_primary_label(span, #constraint_violation) + .with_secondary_label(value.span(), ::alloc::format!("this value has type '{value_ty}', but expected '{expected}'")) + .into_report()); + } + } + )* + op_builder.with_operands_in_group(#group_index, [#(#operand_name),*]); + }); + } + OpOperandGroup::Named(group_name, group_constraint) => { + let group_index = syn::Lit::Int(syn::LitInt::new( + &format!("{group_index}usize"), + group_name.span(), + )); + let constraint_violation = syn::Lit::Str(syn::LitStr::new( + &format!("type constraint violation for operand in '{group_name}'"), + group_name.span(), + )); + tokens.extend(quote! { + let #group_name = #group_name.into_iter().collect::<::alloc::vec::Vec<_>>(); + for operand in #group_name.iter() { + let value = operand.borrow(); + let value_ty = value.ty(); + if !<#group_constraint as ::midenc_hir::traits::TypeConstraint>::matches(value_ty) { + let expected = <#group_constraint as ::midenc_hir::traits::TypeConstraint>::description(); + return Err(builder.context() + .session() + .diagnostics + .diagnostic(::midenc_hir::diagnostics::Severity::Error) + .with_message("invalid operand") + .with_primary_label(span, #constraint_violation) + .with_secondary_label(value.span(), ::alloc::format!("this value has type '{value_ty}', but expected '{expected}'")) + .into_report()); + } + } + op_builder.with_operands_in_group(#group_index, #group_name); + }); + } + } + } + } +} + +struct InitializeCustomFields<'a>(&'a OpDefinition); +impl quote::ToTokens for InitializeCustomFields<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for param in self.0.op_builder_impl.create_params.iter() { + if let OpCreateParamType::CustomField(id, ..) = ¶m.param_ty { + tokens.extend(quote! { + core::ptr::addr_of_mut!((*__ptr).#id).write(#id); + }); + } + } + } +} + +struct WithResults<'a>(&'a OpDefinition); +impl quote::ToTokens for WithResults<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self.0.results.as_ref() { + None => (), + Some(OpResultGroup::Unnamed(results)) => { + let num_results = syn::Lit::Int(syn::LitInt::new( + &format!("{}usize", results.len()), + results[0].name.span(), + )); + tokens.extend(quote! { + op_builder.with_results(#num_results); + }); + } + // Named result groups can have an arbitrary number of results + Some(OpResultGroup::Named(..)) => (), + } + } +} + +struct WithSuccessors<'a>(&'a OpDefinition); +impl quote::ToTokens for WithSuccessors<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for group in self.0.successors.iter() { + match group { + SuccessorGroup::Unnamed(successors) => { + let successor_args = successors.iter().map(|s| format_ident!("{s}_args")); + tokens.extend(quote! { + op_builder.with_successors([ + #(( + #successors, + #successor_args.into_iter().collect::<::alloc::vec::Vec<_>>(), + ),)* + ]); + }); + } + SuccessorGroup::Named(name) => { + tokens.extend(quote! { + op_builder.with_successors(#name); + }); + } + SuccessorGroup::Keyed(name, _) => { + tokens.extend(quote! { + op_builder.with_keyed_successors(#name); + }); + } + } + } + } +} + +struct BuildOp<'a>(&'a OpDefinition); +impl quote::ToTokens for BuildOp<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + match self.0.results.as_ref() { + None => { + tokens.extend(quote! { + op_builder.build() + }); + } + Some(group) => { + let verify_result_constraints = match group { + OpResultGroup::Unnamed(results) => { + let verify_result = results.iter().map(|result| { + let result_name = &result.name; + let result_constraint = &result.constraint; + let constraint_violation = syn::Lit::Str(syn::LitStr::new( + &format!("type constraint violation for result '{result_name}'"), + result_name.span(), + )); + quote! { + { + let op_result = op.#result_name(); + let value_ty = op_result.ty(); + if !<#result_constraint as ::midenc_hir::traits::TypeConstraint>::matches(value_ty) { + let expected = <#result_constraint as ::midenc_hir::traits::TypeConstraint>::description(); + return Err(builder.context() + .session() + .diagnostics + .diagnostic(::midenc_hir::diagnostics::Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label(span, #constraint_violation) + .with_secondary_label(op_result.span(), ::alloc::format!("this value has type '{value_ty}', but expected '{expected}'")) + .into_report()); + } + } + } + }); + quote! { + #( + #verify_result + )* + } + } + OpResultGroup::Named(name, constraint) => { + let constraint_violation = syn::Lit::Str(syn::LitStr::new( + &format!("type constraint violation for result in '{name}'"), + name.span(), + )); + quote! { + { + let results = op.#name(); + for result in results.iter() { + let value = result.borrow(); + let value_ty = value.ty(); + if !<#constraint as ::midenc_hir::traits::TypeConstraint>::matches(value_ty) { + let expected = <#constraint as ::midenc_hir::traits::TypeConstraint>::description(); + return Err(builder.context() + .session() + .diagnostics + .diagnostic(::midenc_hir::diagnostics::Severity::Error) + .with_message("invalid operation") + .with_primary_label(span, #constraint_violation) + .with_secondary_label(value.span(), ::alloc::format!("this value has type '{value_ty}', but expected '{expected}'")) + .into_report()); + } + } + } + } + } + }; + + tokens.extend(quote! { + let op = op_builder.build()?; + + { + let op = op.borrow(); + #verify_result_constraints + } + + Ok(op) + }) + } + } + } +} + +impl quote::ToTokens for OpCreateFn<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let dialect = &self.op.dialect; + let (impl_generics, _, where_clause) = self.generics.split_for_impl(); + let param_names = + self.op.op_builder_impl.create_params.iter().flat_map(OpCreateParam::bindings); + let param_types = self + .op + .op_builder_impl + .create_params + .iter() + .flat_map(OpCreateParam::binding_types); + let initialize_custom_fields = InitializeCustomFields(self.op); + let with_symbols = WithSymbols(self.op); + let with_attrs = WithAttrs(self.op); + let with_operands = WithOperands(self.op); + let with_results = WithResults(self.op); + let with_regions = self.op.regions.iter().map(|_| { + quote! { + op_builder.create_region(); + } + }); + let with_successors = WithSuccessors(self.op); + let build_op = BuildOp(self.op); + + tokens.extend(quote! { + /// Manually construct a new [#op_ident] + /// + /// It is generally preferable to use [`::midenc_hir::Builder::create`] instead. + #[allow(clippy::too_many_arguments)] + pub fn create #impl_generics( + builder: &mut B, + span: ::midenc_hir::diagnostics::SourceSpan, + #( + #param_names: #param_types, + )* + ) -> Result<::midenc_hir::UnsafeIntrusiveEntityRef, ::midenc_hir::diagnostics::Report> + #where_clause + { + use ::midenc_hir::{Builder, Op}; + let mut __this = { + let __operation_name = { + let context = builder.context(); + let dialect = context.get_or_register_dialect::<#dialect>(); + dialect.expect_registered_name::() + }; + let __context = builder.context_rc(); + let mut __op = __context.alloc_uninit_tracked::(); + unsafe { + { + let mut __uninit = __op.borrow_mut(); + let __ptr = (*__uninit).as_mut_ptr(); + let __offset = core::mem::offset_of!(Self, op); + let __op_ptr = core::ptr::addr_of_mut!((*__ptr).op); + __op_ptr.write(::midenc_hir::Operation::uninit::(__context, __operation_name, __offset)); + #initialize_custom_fields + } + let mut __this = ::midenc_hir::RawEntityRef::assume_init(__op); + __this.borrow_mut().set_span(span); + __this + } + }; + + let mut op_builder = ::midenc_hir::OperationBuilder::new(builder, __this); + #with_attrs + #with_symbols + #with_operands + #( + #with_regions + )* + #with_successors + #with_results + + // Finalize construction of this op + #build_op + } + }); + } +} + +impl quote::ToTokens for OpDefinition { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let op_ident = &self.name; + let (impl_generics, ty_generics, where_clause) = self.op.generics.split_for_impl(); + + // struct $Op + self.op.to_tokens(tokens); + + // impl Spanned + tokens.extend(quote! { + impl #impl_generics ::midenc_hir::diagnostics::Spanned for #op_ident #ty_generics #where_clause { + fn span(&self) -> ::midenc_hir::diagnostics::SourceSpan { + ::midenc_hir::diagnostics::Spanned::span(&self.op) + } + } + }); + + // impl AsRef/AsMut + tokens.extend(quote! { + impl #impl_generics AsRef<::midenc_hir::Operation> for #op_ident #ty_generics #where_clause { + #[inline(always)] + fn as_ref(&self) -> &::midenc_hir::Operation { + &self.op + } + } + + impl #impl_generics AsMut<::midenc_hir::Operation> for #op_ident #ty_generics #where_clause { + #[inline(always)] + fn as_mut(&mut self) -> &mut ::midenc_hir::Operation { + &mut self.op + } + } + }); + + // impl Op + // impl OpRegistration + let dialect = &self.dialect; + let opcode = &self.opcode; + let opcode_str = syn::Lit::Str(syn::LitStr::new(&opcode.to_string(), opcode.span())); + let traits = &self.traits; + let implements = &self.implements; + tokens.extend(quote! { + impl #impl_generics ::midenc_hir::Op for #op_ident #ty_generics #where_clause { + #[inline] + fn name(&self) -> ::midenc_hir::OperationName { + self.op.name() + } + + #[inline(always)] + fn as_operation(&self) -> &::midenc_hir::Operation { + &self.op + } + + #[inline(always)] + fn as_operation_mut(&mut self) -> &mut ::midenc_hir::Operation { + &mut self.op + } + } + + impl #impl_generics ::midenc_hir::OpRegistration for #op_ident #ty_generics #where_clause { + fn dialect_name() -> ::midenc_hir::interner::Symbol { + let namespace = <#dialect as ::midenc_hir::DialectRegistration>::NAMESPACE; + ::midenc_hir::interner::Symbol::intern(namespace) + } + + fn name() -> ::midenc_hir::interner::Symbol { + ::midenc_hir::interner::Symbol::intern(#opcode_str) + } + + fn traits() -> ::alloc::boxed::Box<[::midenc_hir::traits::TraitInfo]> { + ::alloc::boxed::Box::from([ + ::midenc_hir::traits::TraitInfo::new::(), + ::midenc_hir::traits::TraitInfo::new::(), + #( + ::midenc_hir::traits::TraitInfo::new::(), + )* + #( + ::midenc_hir::traits::TraitInfo::new::(), + )* + ]) + } + } + }); + + // impl $OpBuilder + // impl BuildableOp + self.op_builder_impl.to_tokens(tokens); + + // impl $Op + { + let create_fn = OpCreateFn::new(self); + let custom_field_fns = OpCustomFieldFns(self); + let attr_fns = OpAttrFns(self); + let symbol_fns = OpSymbolFns(self); + let operand_fns = OpOperandFns(self); + let result_fns = OpResultFns(self); + let region_fns = OpRegionFns(self); + let successor_fns = OpSuccessorFns(self); + tokens.extend(quote! { + /// Construction + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #create_fn + } + + /// User-defined Fields + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #custom_field_fns + } + + /// Attributes + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #attr_fns + } + + /// Symbols + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #symbol_fns + } + + /// Operands + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #operand_fns + } + + /// Results + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #result_fns + } + + /// Regions + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #region_fns + } + + /// Successors + #[allow(unused)] + impl #impl_generics #op_ident #ty_generics #where_clause { + #successor_fns + } + }); + } + + // impl $DerivedTrait + for derived_trait in self.traits.iter() { + tokens.extend(quote! { + impl #impl_generics #derived_trait for #op_ident #ty_generics #where_clause {} + }); + } + + // impl OpVerifier + self.op_verifier_impl.to_tokens(tokens); + } +} + +struct OpCustomFieldFns<'a>(&'a OpDefinition); +impl quote::ToTokens for OpCustomFieldFns<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + // User-defined fields + for field in self.0.op.fields.iter() { + let field_name = field.ident.as_ref().unwrap(); + // Do not generate field functions for custom fields with private visibility + if matches!(field.vis, syn::Visibility::Inherited) { + continue; + } + let field_name_mut = format_ident!("{field_name}_mut"); + let set_field_name = format_ident!("set_{field_name}"); + let field_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the value of `{field_name}`"), + field_name.span(), + )); + let field_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the value of `{field_name}`"), + field_name.span(), + )); + let set_field_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Set the value of `{field_name}`"), + field_name.span(), + )); + let field_ty = &field.ty; + tokens.extend(quote! { + #[doc = #field_doc] + #[inline] + pub fn #field_name(&self) -> &#field_ty { + &self.#field_name + } + + #[doc = #field_mut_doc] + #[inline] + pub fn #field_name_mut(&mut self) -> &mut #field_ty { + &mut self.#field_name + } + + #[doc = #set_field_doc] + #[inline] + pub fn #set_field_name(&mut self, #field_name: #field_ty) { + self.#field_name = #field_name; + } + }); + } + } +} + +struct OpSymbolFns<'a>(&'a OpDefinition); +impl quote::ToTokens for OpSymbolFns<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + // Symbols + for Symbol { + name: ref symbol, + ty: ref symbol_kind, + } in self.0.symbols.iter() + { + let span = symbol.span(); + let symbol_str = syn::Lit::Str(syn::LitStr::new(&symbol.to_string(), span)); + let symbol_mut = format_ident!("{symbol}_mut"); + let set_symbol = format_ident!("set_{symbol}"); + let set_symbol_unchecked = format_ident!("set_{symbol}_unchecked"); + let symbol_symbol = format_ident!("{symbol}_symbol"); + let symbol_symbol_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get the symbol under which the `{symbol}` attribute is stored"), + span, + )); + let symbol_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the value of the `{symbol}` attribute."), + span, + )); + let symbol_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the value of the `{symbol}` attribute."), + span, + )); + let set_symbol_doc_lines = [ + syn::Lit::Str(syn::LitStr::new( + &format!(" Set the value of the `{symbol}` symbol."), + span, + )), + syn::Lit::Str(syn::LitStr::new("", span)), + syn::Lit::Str(syn::LitStr::new( + " Returns `Err` if the symbol cannot be resolved in the nearest symbol table.", + span, + )), + ]; + let set_symbol_unchecked_doc_lines = [ + syn::Lit::Str(syn::LitStr::new( + &format!( + " Set the value of the `{symbol}` symbol without attempting to resolve it." + ), + span, + )), + syn::Lit::Str(syn::LitStr::new("", span)), + syn::Lit::Str(syn::LitStr::new( + " Because this does not resolve the given symbol, the caller is responsible \ + for updating the symbol use list.", + span, + )), + ]; + + tokens.extend(quote! { + #[doc = #symbol_symbol_doc] + #[inline(always)] + pub fn #symbol_symbol() -> ::midenc_hir::interner::Symbol { + ::midenc_hir::interner::Symbol::intern(#symbol_str) + } + + #[doc = #symbol_doc] + pub fn #symbol(&self) -> &::midenc_hir::SymbolPathAttr { + self.op.get_typed_attribute(Self::#symbol_symbol()).unwrap() + } + + #[doc = #symbol_mut_doc] + pub fn #symbol_mut(&mut self) -> &mut ::midenc_hir::SymbolPathAttr { + self.op.get_typed_attribute_mut(Self::#symbol_symbol()).unwrap() + } + + #( + #[doc = #set_symbol_unchecked_doc_lines] + )* + pub fn #set_symbol_unchecked(&mut self, value: ::midenc_hir::SymbolPathAttr) { + self.op.set_attribute(Self::#symbol_symbol(), Some(value)); + } + }); + + let is_concrete_ty = match symbol_kind { + SymbolType::Concrete(ref ty) => [quote! { + // The way we check the type depends on whether `symbol` is a reference to `self` + let (data_ptr, _) = ::midenc_hir::SymbolRef::as_ptr(&symbol).to_raw_parts(); + if core::ptr::addr_eq(data_ptr, (self as *const Self as *const ())) { + if !self.op.is::<#ty>() { + return Err(::midenc_hir::InvalidSymbolRefError::InvalidType { + symbol: self.op.span(), + expected: stringify!(#ty), + got: self.op.name(), + }); + } + } else if !symbol.borrow().is::<#ty>() { + let symbol = symbol.borrow(); + let symbol_op = symbol.as_symbol_operation(); + return Err(::midenc_hir::InvalidSymbolRefError::InvalidType { + symbol: symbol_op.span(), + expected: stringify!(#ty), + got: symbol_op.name(), + }); + } + }], + _ => [quote! {}], + }; + + match symbol_kind { + SymbolType::Any | SymbolType::Trait(_) | SymbolType::Concrete(_) => { + tokens.extend(quote! { + #( + #[doc = #set_symbol_doc_lines] + )* + pub fn #set_symbol(&mut self, symbol: impl ::midenc_hir::AsSymbolRef) -> Result<(), ::midenc_hir::InvalidSymbolRefError> { + let symbol = symbol.as_symbol_ref(); + #(#is_concrete_ty)* + self.op.set_symbol_attribute(Self::#symbol_symbol(), symbol); + + Ok(()) + } + }); + } + SymbolType::Callable => { + tokens.extend(quote! { + #( + #[doc = #set_symbol_doc_lines] + )* + pub fn #set_symbol(&mut self, symbol: impl ::midenc_hir::AsCallableSymbolRef) -> Result<(), ::midenc_hir::InvalidSymbolRefError> { + use ::midenc_hir::Spanned; + let symbol = symbol.as_callable_symbol_ref(); + let (data_ptr, _) = ::midenc_hir::SymbolRef::as_ptr(&symbol).to_raw_parts(); + if core::ptr::addr_eq(data_ptr, (self as *const Self as *const ())) { + if !self.op.implements::() { + return Err(::midenc_hir::InvalidSymbolRefError::NotCallable { + symbol: self.span(), + }); + } + } else { + let symbol = symbol.borrow(); + let symbol_op = symbol.as_symbol_operation(); + if !symbol_op.implements::() { + return Err(::midenc_hir::InvalidSymbolRefError::NotCallable { + symbol: symbol_op.span(), + }); + } + } + self.op.set_symbol_attribute(Self::#symbol_symbol(), symbol.clone()); + + Ok(()) + } + }); + } + } + } + } +} + +struct OpAttrFns<'a>(&'a OpDefinition); +impl quote::ToTokens for OpAttrFns<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + // Attributes + for OpAttribute { + name: ref attr, + ty: ref attr_ty, + .. + } in self.0.attrs.iter() + { + let attr_str = syn::Lit::Str(syn::LitStr::new(&attr.to_string(), attr.span())); + let attr_mut = format_ident!("{attr}_mut"); + let set_attr = format_ident!("set_{attr}"); + let attr_symbol = format_ident!("{attr}_symbol"); + let attr_symbol_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get the symbol under which the `{attr}` attribute is stored"), + attr.span(), + )); + let attr_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the value of the `{attr}` attribute."), + attr.span(), + )); + let attr_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the value of the `{attr}` attribute."), + attr.span(), + )); + let set_attr_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Set the value of the `{attr}` attribute."), + attr.span(), + )); + tokens.extend(quote! { + #[doc = #attr_symbol_doc] + #[inline(always)] + pub fn #attr_symbol() -> ::midenc_hir::interner::Symbol { + ::midenc_hir::interner::Symbol::intern(#attr_str) + } + + #[doc = #attr_doc] + pub fn #attr(&self) -> &#attr_ty { + self.op.get_typed_attribute::<#attr_ty>(Self::#attr_symbol()).unwrap() + } + + #[doc = #attr_mut_doc] + pub fn #attr_mut(&mut self) -> &mut #attr_ty { + self.op.get_typed_attribute_mut::<#attr_ty>(Self::#attr_symbol()).unwrap() + } + + #[doc = #set_attr_doc] + pub fn #set_attr(&mut self, value: impl Into<#attr_ty>) { + self.op.set_intrinsic_attribute(Self::#attr_symbol(), Some(value.into())); + } + }); + } + } +} + +struct OpOperandFns<'a>(&'a OpDefinition); +impl quote::ToTokens for OpOperandFns<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for (group_index, operand_group) in self.0.operands.iter().enumerate() { + let group_index = syn::Lit::Int(syn::LitInt::new( + &format!("{group_index}usize"), + proc_macro2::Span::call_site(), + )); + match operand_group { + // Operands + OpOperandGroup::Unnamed(operands) => { + for ( + operand_index, + Operand { + name: ref operand, .. + }, + ) in operands.iter().enumerate() + { + let operand_index = syn::Lit::Int(syn::LitInt::new( + &format!("{operand_index}usize"), + proc_macro2::Span::call_site(), + )); + let operand_mut = format_ident!("{operand}_mut"); + let operand_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{operand}` operand."), + operand.span(), + )); + let operand_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{operand}` operand."), + operand.span(), + )); + tokens.extend(quote!{ + #[doc = #operand_doc] + #[inline] + pub fn #operand(&self) -> ::midenc_hir::EntityRef<'_, ::midenc_hir::OpOperandImpl> { + self.op.operands().group(#group_index)[#operand_index].borrow() + } + + #[doc = #operand_mut_doc] + #[inline] + pub fn #operand_mut(&mut self) -> ::midenc_hir::EntityMut<'_, ::midenc_hir::OpOperandImpl> { + self.op.operands_mut().group_mut(#group_index)[#operand_index].borrow_mut() + } + }); + } + } + // User-defined operand groups + OpOperandGroup::Named(group_name, _) => { + let group_name_mut = format_ident!("{group_name}_mut"); + let group_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{group_name}` operand group."), + group_name.span(), + )); + let group_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{group_name}` operand group."), + group_name.span(), + )); + tokens.extend(quote! { + #[doc = #group_doc] + #[inline] + pub fn #group_name(&self) -> ::midenc_hir::OpOperandRange<'_> { + self.op.operands().group(#group_index) + } + + #[doc = #group_mut_doc] + #[inline] + pub fn #group_name_mut(&mut self) -> ::midenc_hir::OpOperandRangeMut<'_> { + self.op.operands_mut().group_mut(#group_index) + } + }); + } + } + } + } +} + +struct OpResultFns<'a>(&'a OpDefinition); +impl quote::ToTokens for OpResultFns<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + if let Some(group) = self.0.results.as_ref() { + match group { + OpResultGroup::Unnamed(results) => { + for ( + index, + OpResult { + name: ref result, .. + }, + ) in results.iter().enumerate() + { + let index = syn::Lit::Int(syn::LitInt::new( + &format!("{index}usize"), + result.span(), + )); + let result_mut = format_ident!("{result}_mut"); + let result_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{result}` result."), + result.span(), + )); + let result_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{result}` result."), + result.span(), + )); + tokens.extend(quote!{ + #[doc = #result_doc] + #[inline] + pub fn #result(&self) -> ::midenc_hir::EntityRef<'_, ::midenc_hir::OpResult> { + self.op.results()[#index].borrow() + } + + #[doc = #result_mut_doc] + #[inline] + pub fn #result_mut(&mut self) -> ::midenc_hir::EntityMut<'_, ::midenc_hir::OpResult> { + self.op.results_mut()[#index].borrow_mut() + } + }); + } + } + OpResultGroup::Named(group, _) => { + let group_mut = format_ident!("{group}_mut"); + let group_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{group}` result group."), + group.span(), + )); + let group_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{group}` result group."), + group.span(), + )); + tokens.extend(quote! { + #[doc = #group_doc] + #[inline] + pub fn #group(&self) -> ::midenc_hir::OpResultRange<'_> { + self.results().group(0) + } + + #[doc = #group_mut_doc] + #[inline] + pub fn #group_mut(&mut self) -> ::midenc_hir::OpResultRangeMut<'_> { + self.op.results_mut().group_mut(0) + } + }); + } + } + } + } +} + +struct OpRegionFns<'a>(&'a OpDefinition); +impl quote::ToTokens for OpRegionFns<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + // Regions + for (index, region) in self.0.regions.iter().enumerate() { + let index = syn::Lit::Int(syn::LitInt::new(&format!("{index}usize"), region.span())); + let region_mut = format_ident!("{region}_mut"); + let region_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{region}` region."), + region.span(), + )); + let region_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{region}` region."), + region.span(), + )); + tokens.extend(quote! { + #[doc = #region_doc] + #[inline] + pub fn #region(&self) -> ::midenc_hir::EntityRef<'_, ::midenc_hir::Region> { + self.op.region(#index) + } + + #[doc = #region_mut_doc] + #[inline] + pub fn #region_mut(&mut self) -> ::midenc_hir::EntityMut<'_, ::midenc_hir::Region> { + self.op.region_mut(#index) + } + }); + } + } +} + +struct OpSuccessorFns<'a>(&'a OpDefinition); +impl quote::ToTokens for OpSuccessorFns<'_> { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + for (group_index, group) in self.0.successors.iter().enumerate() { + let group_index = syn::Lit::Int(syn::LitInt::new( + &format!("{group_index}usize"), + proc_macro2::Span::call_site(), + )); + match group { + // Successors + SuccessorGroup::Unnamed(successors) => { + for (index, successor) in successors.iter().enumerate() { + let index = syn::Lit::Int(syn::LitInt::new( + &format!("{index}usize"), + proc_macro2::Span::call_site(), + )); + let successor_mut = format_ident!("{successor}_mut"); + let successor_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{successor}` successor."), + successor.span(), + )); + let successor_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{successor}` successor."), + successor.span(), + )); + tokens.extend(quote! { + #[doc = #successor_doc] + #[inline] + pub fn #successor(&self) -> ::midenc_hir::OpSuccessor<'_> { + self.op.successor_in_group(#group_index, #index) + } + + #[doc = #successor_mut_doc] + #[inline] + pub fn #successor_mut(&mut self) -> ::midenc_hir::OpSuccessorMut<'_> { + self.op.successor_in_group_mut(#group_index, #index) + } + }); + } + } + // Variadic successor groups + SuccessorGroup::Named(group) => { + let group_mut = format_ident!("{group}_mut"); + let group_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{group}` successor group."), + group.span(), + )); + let group_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{group}` successor group."), + group.span(), + )); + tokens.extend(quote! { + #[doc = #group_doc] + #[inline] + pub fn #group(&self) -> ::midenc_hir::OpSuccessorRange<'_> { + self.op.successor_group(#group_index) + } + + #[doc = #group_mut_doc] + #[inline] + pub fn #group_mut(&mut self) -> ::midenc_hir::OpSuccessorRangeMut<'_> { + self.op.successor_group(#group_index) + } + }); + } + // User-defined successor groups + SuccessorGroup::Keyed(group, group_ty) => { + let group_mut = format_ident!("{group}_mut"); + let group_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a reference to the `{group}` successor group."), + group.span(), + )); + let group_mut_doc = syn::Lit::Str(syn::LitStr::new( + &format!(" Get a mutable reference to the `{group}` successor group."), + group.span(), + )); + tokens.extend(quote! { + #[doc = #group_doc] + #[inline] + pub fn #group(&self) -> ::midenc_hir::KeyedSuccessorRange<'_, #group_ty> { + self.op.keyed_successor_group::<#group_ty>(#group_index) + } + + #[doc = #group_mut_doc] + #[inline] + pub fn #group_mut(&mut self) -> ::midenc_hir::KeyedSuccessorRangeMut<'_, #group_ty> { + self.op.keyed_successor_group_mut::<#group_ty>(#group_index) + } + }); + } + } + } + } +} + +/// Represents a field decorated with `#[attr]` +/// +/// The type associated with an `#[attr]` field represents the concrete value type of the attribute, +/// and thus must implement the `AttributeValue` trait. +#[derive(Debug, Clone)] +pub struct OpAttribute { + /// The attribute name + pub name: Ident, + /// The value type of the attribute + pub ty: syn::Type, + /// The attribute kind + pub kind: AttrKind, +} + +/// Represents the type of a symbol +#[derive(Default, Debug, darling::FromMeta, Copy, Clone)] +#[darling(default)] +pub enum AttrKind { + /// A normal attribute + #[default] + Default, + /// A hidden attribute + Hidden, +} + +/// An abstraction over named vs unnamed groups of some IR entity +#[allow(clippy::large_enum_variant)] +pub enum EntityGroup { + /// An unnamed group consisting of individual named items + Unnamed(Vec), + /// A named group consisting of unnamed items + Named(Ident, syn::Type), +} + +/// A type representing a type constraint applied to a `Value` impl +pub type Constraint = syn::Type; + +#[derive(Debug, Clone)] +pub struct Operand { + pub name: Ident, + pub constraint: Constraint, +} + +pub type OpOperandGroup = EntityGroup; + +#[derive(Debug, Clone)] +pub struct OpResult { + pub name: Ident, + pub constraint: Constraint, +} + +pub type OpResultGroup = EntityGroup; + +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum SuccessorGroup { + /// An unnamed group consisting of individual named successors + Unnamed(Vec), + /// A named group consisting of unnamed successors + Named(Ident), + /// A named group consisting of unnamed successors with an associated key + Keyed(Ident, syn::Type), +} + +/// Represents the generated `$OpBuilder` type used to create instances of `$Op` +/// +/// The implementation of the type requires us to know the type signature specific to this op, +/// so that we can emit an implementation matching that signature. +pub struct OpBuilderImpl { + /// The `$Op` we're building + op: Ident, + /// The `$OpBuilder` type name + name: Ident, + /// The doc string for `$OpBuilder` + doc: DocString, + /// The doc string for `$OpBuilder::new` + new_doc: DocString, + /// The set of parameters expected by `$Op::create` + /// + /// The order of these parameters is determined by: + /// + /// 1. The `order = N` property of the corresponding attribute type, e.g. `#[attr(order = 1)]` + /// 2. The default "kind" ordering of: symbols, required user-defined fields, operands, successors, attributes + /// 3. The order of appearance of the fields in the struct + create_params: Rc<[OpCreateParam]>, + /// The implementation of the `BuildableOp` trait for `$Op` via `$OpBuilder` + buildable_op_impl: BuildableOpImpl, + /// The implementation of the `FnOnce` trait for `$OpBuilder` + fn_once_impl: OpBuilderFnOnceImpl, +} +impl OpBuilderImpl { + pub fn empty(op: Ident) -> Self { + let name = format_ident!("{}Builder", &op); + let doc = DocString::new( + op.span(), + format!( + " A specialized builder for [{op}], which is used by calling it like a function." + ), + ); + let new_doc = DocString::new( + op.span(), + format!(" Get a new [{name}] from the provided [::midenc_hir::Builder] impl and span."), + ); + let create_params = Rc::<[OpCreateParam]>::from([]); + let buildable_op_impl = BuildableOpImpl { + op: op.clone(), + op_builder: name.clone(), + op_generics: Default::default(), + generics: Default::default(), + required_generics: None, + params: Rc::clone(&create_params), + }; + let fn_once_impl = OpBuilderFnOnceImpl { + op: op.clone(), + op_builder: name.clone(), + generics: Default::default(), + required_generics: None, + params: Rc::clone(&create_params), + }; + Self { + op, + name, + doc, + new_doc, + create_params, + buildable_op_impl, + fn_once_impl, + } + } + + pub fn set_create_params(&mut self, op_generics: &syn::Generics, params: Vec) { + let span = self.op.span(); + + let create_params = Rc::from(params.into_boxed_slice()); + self.create_params = Rc::clone(&create_params); + + let has_required_variant = self.create_params.iter().any(|param| param.default); + + // BuildableOp generic parameters + self.buildable_op_impl.params = Rc::clone(&create_params); + self.buildable_op_impl.op_generics = op_generics.clone(); + self.buildable_op_impl.required_generics = if has_required_variant { + Some(syn::Generics { + lt_token: Some(syn::token::Lt(span)), + params: syn::punctuated::Punctuated::from_iter( + op_generics.params.iter().cloned().chain( + self.create_params.iter().flat_map(OpCreateParam::required_generic_types), + ), + ), + gt_token: Some(syn::token::Gt(span)), + where_clause: op_generics.where_clause.clone(), + }) + } else { + None + }; + self.buildable_op_impl.generics = syn::Generics { + lt_token: Some(syn::token::Lt(span)), + params: syn::punctuated::Punctuated::from_iter( + op_generics + .params + .iter() + .cloned() + .chain(self.create_params.iter().flat_map(OpCreateParam::generic_types)), + ), + gt_token: Some(syn::token::Gt(span)), + where_clause: op_generics.where_clause.clone(), + }; + + // FnOnce generic parameters + self.fn_once_impl.params = create_params; + self.fn_once_impl.required_generics = + self.buildable_op_impl.required_generics.as_ref().map( + |buildable_op_impl_required_generics| syn::Generics { + lt_token: Some(syn::token::Lt(span)), + params: syn::punctuated::Punctuated::from_iter( + [ + syn::GenericParam::Lifetime(syn::LifetimeParam { + attrs: vec![], + lifetime: syn::Lifetime::new("'a", proc_macro2::Span::call_site()), + colon_token: None, + bounds: Default::default(), + }), + syn::parse_str("B: ?Sized + ::midenc_hir::Builder").unwrap(), + ] + .into_iter() + .chain(buildable_op_impl_required_generics.params.iter().cloned()), + ), + gt_token: Some(syn::token::Gt(span)), + where_clause: buildable_op_impl_required_generics.where_clause.clone(), + }, + ); + self.fn_once_impl.generics = syn::Generics { + lt_token: Some(syn::token::Lt(span)), + params: syn::punctuated::Punctuated::from_iter( + [ + syn::GenericParam::Lifetime(syn::LifetimeParam { + attrs: vec![], + lifetime: syn::Lifetime::new("'a", proc_macro2::Span::call_site()), + colon_token: None, + bounds: Default::default(), + }), + syn::parse_str("B: ?Sized + ::midenc_hir::Builder").unwrap(), + ] + .into_iter() + .chain(self.buildable_op_impl.generics.params.iter().cloned()), + ), + gt_token: Some(syn::token::Gt(span)), + where_clause: self.buildable_op_impl.generics.where_clause.clone(), + }; + } +} +impl quote::ToTokens for OpBuilderImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + // Emit `$OpBuilder` + tokens.extend({ + let op_builder = &self.name; + let op_builder_doc = &self.doc; + let op_builder_new_doc = &self.new_doc; + quote! { + #op_builder_doc + pub struct #op_builder <'a, B: ?Sized> { + builder: &'a mut B, + span: ::midenc_hir::diagnostics::SourceSpan, + } + + impl<'a, B> #op_builder <'a, B> + where + B: ?Sized + ::midenc_hir::Builder, + { + #op_builder_new_doc + #[inline(always)] + pub fn new(builder: &'a mut B, span: ::midenc_hir::diagnostics::SourceSpan) -> Self { + Self { + builder, + span, + } + } + } + } + }); + + // Emit `impl BuildableOp for $OpBuilder` + self.buildable_op_impl.to_tokens(tokens); + + // Emit `impl FnOnce for $OpBuilder` + self.fn_once_impl.to_tokens(tokens); + } +} + +pub struct BuildableOpImpl { + op: Ident, + op_builder: Ident, + op_generics: syn::Generics, + generics: syn::Generics, + required_generics: Option, + params: Rc<[OpCreateParam]>, +} +impl quote::ToTokens for BuildableOpImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let op = &self.op; + let op_builder = &self.op_builder; + + // Minimal builder (specify only required parameters) + // + // NOTE: This is only emitted if there are `default` parameters + if let Some(required_generics) = self.required_generics.as_ref() { + let required_params = + self.params.iter().flat_map(OpCreateParam::required_binding_types); + let (_, required_ty_generics, _) = self.op_generics.split_for_impl(); + let (required_impl_generics, _, required_where_clause) = + required_generics.split_for_impl(); + let required_params_ty = syn::TypeTuple { + paren_token: syn::token::Paren(op.span()), + elems: syn::punctuated::Punctuated::from_iter(required_params), + }; + let quoted = quote! { + impl #required_impl_generics ::midenc_hir::BuildableOp<#required_params_ty> for #op #required_ty_generics #required_where_clause { + type Builder<'a, T: ?Sized + ::midenc_hir::Builder + 'a> = #op_builder <'a, T>; + + #[inline(always)] + fn builder<'b, B>(builder: &'b mut B, span: ::midenc_hir::diagnostics::SourceSpan) -> Self::Builder<'b, B> + where + B: ?Sized + ::midenc_hir::Builder + 'b, + { + #op_builder { + builder, + span, + } + } + } + }; + tokens.extend(quoted); + } + + // Maximal builder (specify all parameters) + let params = self.params.iter().flat_map(OpCreateParam::binding_types); + let (_, ty_generics, _) = self.op_generics.split_for_impl(); + let (impl_generics, _, where_clause) = self.generics.split_for_impl(); + let params_ty = syn::TypeTuple { + paren_token: syn::token::Paren(op.span()), + elems: syn::punctuated::Punctuated::from_iter(params), + }; + let quoted = quote! { + impl #impl_generics ::midenc_hir::BuildableOp<#params_ty> for #op #ty_generics #where_clause { + type Builder<'a, T: ?Sized + ::midenc_hir::Builder + 'a> = #op_builder <'a, T>; + + #[inline(always)] + fn builder<'b, B>(builder: &'b mut B, span: ::midenc_hir::diagnostics::SourceSpan) -> Self::Builder<'b, B> + where + B: ?Sized + ::midenc_hir::Builder + 'b, + { + #op_builder { + builder, + span, + } + } + } + }; + tokens.extend(quoted); + } +} + +pub struct OpBuilderFnOnceImpl { + op: Ident, + op_builder: Ident, + generics: syn::Generics, + required_generics: Option, + params: Rc<[OpCreateParam]>, +} +impl quote::ToTokens for OpBuilderFnOnceImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let op = &self.op; + let op_builder = &self.op_builder; + + let create_param_names = + self.params.iter().flat_map(OpCreateParam::bindings).collect::>(); + + // Minimal builder (specify only required parameters) + // + // NOTE: This is only emitted if there are `default` parameters + if let Some(required_generics) = self.required_generics.as_ref() { + let required_param_names = + self.params.iter().flat_map(OpCreateParam::required_bindings); + let defaulted_param_names = self.params.iter().flat_map(|param| { + if param.default { + param.bindings() + } else { + vec![] + } + }); + let required_param_types = + self.params.iter().flat_map(OpCreateParam::required_binding_types); + let (required_impl_generics, _required_ty_generics, required_where_clause) = + required_generics.split_for_impl(); + let required_params_ty = syn::TypeTuple { + paren_token: syn::token::Paren(op.span()), + elems: syn::punctuated::Punctuated::from_iter(required_param_types), + }; + let required_params_bound = syn::PatTuple { + attrs: Default::default(), + paren_token: syn::token::Paren(op.span()), + elems: syn::punctuated::Punctuated::from_iter( + required_param_names.into_iter().map(|id| { + syn::Pat::Ident(syn::PatIdent { + attrs: Default::default(), + by_ref: None, + mutability: None, + ident: id, + subpat: None, + }) + }), + ), + }; + tokens.extend(quote! { + impl #required_impl_generics ::core::ops::FnOnce<#required_params_ty> for #op_builder<'a, B> #required_where_clause { + type Output = Result<::midenc_hir::UnsafeIntrusiveEntityRef<#op>, ::midenc_hir::diagnostics::Report>; + + #[inline] + extern "rust-call" fn call_once(self, args: #required_params_ty) -> Self::Output { + let #required_params_bound = args; + #( + let #defaulted_param_names = Default::default(); + )* + <#op>::create(self.builder, self.span, #(#create_param_names),*) + } + } + }); + } + + // Maximal builder (specify all parameters) + let param_types = self.params.iter().flat_map(OpCreateParam::binding_types); + let (impl_generics, _ty_generics, where_clause) = self.generics.split_for_impl(); + let params_ty = syn::TypeTuple { + paren_token: syn::token::Paren(op.span()), + elems: syn::punctuated::Punctuated::from_iter(param_types), + }; + let params_bound = syn::PatTuple { + attrs: Default::default(), + paren_token: syn::token::Paren(op.span()), + elems: syn::punctuated::Punctuated::from_iter(create_param_names.iter().map(|id| { + syn::Pat::Ident(syn::PatIdent { + attrs: Default::default(), + by_ref: None, + mutability: None, + ident: id.clone(), + subpat: None, + }) + })), + }; + tokens.extend(quote! { + impl #impl_generics ::core::ops::FnOnce<#params_ty> for #op_builder<'a, B> #where_clause { + type Output = Result<::midenc_hir::UnsafeIntrusiveEntityRef<#op>, ::midenc_hir::diagnostics::Report>; + + #[inline] + extern "rust-call" fn call_once(self, args: #params_ty) -> Self::Output { + let #params_bound = args; + <#op>::create(self.builder, self.span, #(#create_param_names),*) + } + } + }); + } +} + +pub struct OpVerifierImpl { + op: Ident, + traits: darling::util::PathList, + implements: darling::util::PathList, +} +impl OpVerifierImpl { + pub fn new( + op: Ident, + traits: darling::util::PathList, + implements: darling::util::PathList, + ) -> Self { + Self { + op, + traits, + implements, + } + } +} +impl quote::ToTokens for OpVerifierImpl { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let op = &self.op; + if self.traits.is_empty() && self.implements.is_empty() { + tokens.extend(quote! { + /// No-op verifier implementation generated via `#[operation]` derive + /// + /// This implementation was chosen as no op traits were indicated as being derived _or_ + /// manually implemented by this type. + impl ::midenc_hir::OpVerifier for #op { + #[inline(always)] + fn verify(&self, _context: &::midenc_hir::Context) -> Result<(), ::midenc_hir::diagnostics::Report> { + Ok(()) + } + } + }); + return; + } + + let op_verifier_doc_lines = { + let span = self.op.span(); + let mut lines = vec![ + syn::Lit::Str(syn::LitStr::new( + " Generated verifier implementation via `#[operation]` attribute", + span, + )), + syn::Lit::Str(syn::LitStr::new("", span)), + syn::Lit::Str(syn::LitStr::new(" Traits verified by this implementation:", span)), + syn::Lit::Str(syn::LitStr::new("", span)), + ]; + for derived_trait in self.traits.iter() { + lines.push(syn::Lit::Str(syn::LitStr::new( + &format!(" * [{}]", derived_trait.get_ident().unwrap()), + span, + ))); + } + for implemented_trait in self.implements.iter() { + lines.push(syn::Lit::Str(syn::LitStr::new( + &format!(" * [{}]", implemented_trait.get_ident().unwrap()), + span, + ))); + } + lines.push(syn::Lit::Str(syn::LitStr::new("", span))); + lines.push(syn::Lit::Str(syn::LitStr::new( + " Use `cargo-expand` to view the generated code if you suspect verification is \ + broken.", + span, + ))); + lines + }; + + let derived_traits = &self.traits; + let implemented_traits = &self.implements; + tokens.extend(quote! { + #( + #[doc = #op_verifier_doc_lines] + )* + impl ::midenc_hir::OpVerifier for #op { + fn verify(&self, context: &::midenc_hir::Context) -> Result<(), ::midenc_hir::diagnostics::Report> { + /// This type represents the concrete set of derived traits for some op `T`, paired with a + /// type-erased [::midenc_hir::Operation] reference for an instance of that op. + /// + /// This is used for two purposes: + /// + /// 1. To generate a specialized [::midenc_hir::OpVerifier] for `T` which contains all of the type and + /// trait-specific validation logic for that `T`. + /// 2. To apply the specialized verifier for `T` using the wrapped [::midenc_hir::Operation] reference. + struct OpVerifierImpl<'a, T> { + op: &'a ::midenc_hir::Operation, + _t: ::core::marker::PhantomData, + #[allow(unused_parens)] + _derived: ::core::marker::PhantomData<(#(&'a dyn #derived_traits,)* #(&'a dyn #implemented_traits),*)>, + } + impl<'a, T> OpVerifierImpl<'a, T> { + const fn new(op: &'a ::midenc_hir::Operation) -> Self { + Self { + op, + _t: ::core::marker::PhantomData, + _derived: ::core::marker::PhantomData, + } + } + } + impl<'a, T> ::core::ops::Deref for OpVerifierImpl<'a, T> { + type Target = ::midenc_hir::Operation; + + fn deref(&self) -> &Self::Target { + self.op + } + } + + #[allow(unused_parens)] + impl<'a> ::midenc_hir::OpVerifier for OpVerifierImpl<'a, #op> + where + #( + #op: ::midenc_hir::verifier::Verifier, + )* + #( + #op: ::midenc_hir::verifier::Verifier, + )* + { + #[inline] + fn verify(&self, context: &::midenc_hir::Context) -> Result<(), ::midenc_hir::diagnostics::Report> { + let op = self.downcast_ref::<#op>().unwrap(); + #( + if const { !<#op as ::midenc_hir::verifier::Verifier>::VACUOUS } { + <#op as ::midenc_hir::verifier::Verifier>::maybe_verify(op, context)?; + } + )* + #( + if const { !<#op as ::midenc_hir::verifier::Verifier>::VACUOUS } { + <#op as ::midenc_hir::verifier::Verifier>::maybe_verify(op, context)?; + } + )* + + Ok(()) + } + } + + let verifier = OpVerifierImpl::<#op>::new(&self.op); + verifier.verify(context) + } + } + }); + } +} + +/// Represents the parsed struct definition for the operation we wish to define +/// +/// Only named structs are allowed at this time. +#[derive(Debug, FromDeriveInput)] +#[darling( + attributes(operation), + supports(struct_named), + forward_attrs(doc, cfg, allow, derive) +)] +pub struct Operation { + ident: Ident, + vis: syn::Visibility, + generics: syn::Generics, + attrs: Vec, + data: darling::ast::Data<(), OperationField>, + dialect: Ident, + #[darling(default)] + name: Option, + #[darling(default)] + traits: darling::util::PathList, + #[darling(default)] + implements: darling::util::PathList, +} + +/// Represents a field in the input struct +#[derive(Debug, FromField)] +#[darling(forward_attrs( + doc, cfg, allow, attr, operand, operands, region, successor, successors, result, results, + default, order, symbol +))] +pub struct OperationField { + /// The name of this field. + /// + /// This will always be `Some`, as we do not support any types other than structs + ident: Option, + /// The visibility assigned to this field + vis: syn::Visibility, + /// The type assigned to this field + ty: syn::Type, + /// The processed attributes of this field + #[darling(with = OperationFieldAttrs::new)] + attrs: OperationFieldAttrs, +} + +#[derive(Default, Debug)] +pub struct OperationFieldAttrs { + /// Attributes we don't care about, and are forwarding along untouched + forwarded: Vec, + /// Whether or not to create instances of this op using the `Default` impl for this field + r#default: Flag, + /// Whether or not to assign an explicit order to this field. + /// + /// Once an explicit order has been assigned to a field, all subsequent fields must either have + /// an explicit order, or they will be assigned the next largest unallocated index in the order. + order: Option, + /// Was this an `#[attr]` field? + attr: Option>>, + /// Was this an `#[operand]` field? + operand: Flag, + /// Was this an `#[operands]` field? + operands: Flag, + /// Was this a `#[result]` field? + result: Flag, + /// Was this a `#[results]` field? + results: Flag, + /// Was this a `#[region]` field? + region: Flag, + /// Was this a `#[successor]` field? + successor: Flag, + /// Was this a `#[successors]` field? + successors: Option>, + /// Was this a `#[symbol]` field? + symbol: Option>>, +} + +impl OperationFieldAttrs { + pub fn new(attrs: Vec) -> darling::Result { + let mut result = Self::default(); + let mut prev_decorator = None; + for attr in attrs { + if let Some(name) = attr.path().get_ident().map(|id| id.to_string()) { + match name.as_str() { + "attr" => { + if let Some(prev) = prev_decorator.replace("attr") { + return Err(Error::custom(format!( + "#[attr] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + let span = attr.span(); + let mut kind = None; + match &attr.meta { + // A bare #[attr], nothing to do + syn::Meta::Path(_) => (), + syn::Meta::List(ref list) => { + list.parse_nested_meta(|meta| { + if meta.path.is_ident("hidden") { + kind = Some(AttrKind::Hidden); + Ok(()) + } else { + Err(meta.error(format!( + "invalid #[attr] decorator: unrecognized key '{}'", + meta.path.get_ident().unwrap() + ))) + } + }) + .map_err(Error::from)?; + } + meta @ syn::Meta::NameValue(_) => { + return Err(Error::custom( + "invalid #[attr] decorator: invalid format, expected either \ + bare 'attr' or a meta list", + ) + .with_span(meta)); + } + } + result.attr = Some(SpannedValue::new(kind, span)); + } + "operand" => { + if let Some(prev) = prev_decorator.replace("operand") { + return Err(Error::custom(format!( + "#[operand] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + result.operand = Flag::from_meta(&attr.meta).unwrap(); + } + "operands" => { + if let Some(prev) = prev_decorator.replace("operands") { + return Err(Error::custom(format!( + "#[operands] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + result.operands = Flag::from_meta(&attr.meta).unwrap(); + } + "result" => { + if let Some(prev) = prev_decorator.replace("result") { + return Err(Error::custom(format!( + "#[result] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + result.result = Flag::from_meta(&attr.meta).unwrap(); + } + "results" => { + if let Some(prev) = prev_decorator.replace("results") { + return Err(Error::custom(format!( + "#[results] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + result.results = Flag::from_meta(&attr.meta).unwrap(); + } + "region" => { + if let Some(prev) = prev_decorator.replace("region") { + return Err(Error::custom(format!( + "#[region] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + result.region = Flag::from_meta(&attr.meta).unwrap(); + } + "successor" => { + if let Some(prev) = prev_decorator.replace("successor") { + return Err(Error::custom(format!( + "#[successor] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + result.successor = Flag::from_meta(&attr.meta).unwrap(); + } + "successors" => { + if let Some(prev) = prev_decorator.replace("successors") { + return Err(Error::custom(format!( + "#[successors] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + let span = attr.span(); + let mut succ_ty = SuccessorsType::Default; + match attr.parse_nested_meta(|meta| { + if meta.path.is_ident("keyed") { + succ_ty = SuccessorsType::Keyed; + Ok(()) + } else { + Err(meta.error(format!( + "invalid #[successors] decorator: unrecognized key '{}'", + meta.path.get_ident().unwrap() + ))) + } + }) { + Ok(_) => { + result.successors = Some(SpannedValue::new(succ_ty, span)); + } + Err(err) => { + return Err(Error::from(err)); + } + } + } + "symbol" => { + if let Some(prev) = prev_decorator.replace("symbol") { + return Err(Error::custom(format!( + "#[symbol] conflicts with a previous #[{prev}] decorator" + )) + .with_span(&attr)); + } + let span = attr.span(); + let mut symbol_ty = None; + match &attr.meta { + // A bare #[symbol], nothing to do + syn::Meta::Path(_) => (), + syn::Meta::List(ref list) => { + list.parse_nested_meta(|meta| { + if meta.path.is_ident("callable") { + symbol_ty = Some(SymbolType::Callable); + Ok(()) + } else if meta.path.is_ident("any") { + symbol_ty = Some(SymbolType::Any); + Ok(()) + } else if meta.path.is_ident("bounds") { + let symbol_bound = meta + .input + .parse::() + .map_err(Error::from)?; + symbol_ty = Some(symbol_bound.into()); + Ok(()) + } else { + Err(meta.error(format!( + "invalid #[symbol] decorator: unrecognized key '{}'", + meta.path.get_ident().unwrap() + ))) + } + }) + .map_err(Error::from)?; + } + meta @ syn::Meta::NameValue(_) => { + return Err(Error::custom( + "invalid #[symbol] decorator: invalid format, expected either \ + bare 'symbol' or a meta list", + ) + .with_span(meta)); + } + } + result.symbol = Some(SpannedValue::new(symbol_ty, span)); + } + "default" => { + result.default = Flag::present(); + } + "order" => { + result.order = Some( + attr.parse_args::() + .map_err(Error::from) + .and_then(|n| n.base10_parse::().map_err(Error::from))?, + ); + } + _ => { + result.forwarded.push(attr); + } + } + } else { + result.forwarded.push(attr); + } + } + + Ok(result) + } +} + +impl OperationFieldAttrs { + pub fn pseudo_type(&self) -> Option> { + use darling::util::SpannedValue; + if self.attr.is_some() { + self.attr + .as_ref() + .map(|kind| kind.map_ref(|kind| OperationFieldType::Attr(kind.unwrap_or_default()))) + } else if self.operand.is_present() { + Some(SpannedValue::new(OperationFieldType::Operand, self.operand.span())) + } else if self.operands.is_present() { + Some(SpannedValue::new(OperationFieldType::Operands, self.operands.span())) + } else if self.result.is_present() { + Some(SpannedValue::new(OperationFieldType::Result, self.result.span())) + } else if self.results.is_present() { + Some(SpannedValue::new(OperationFieldType::Results, self.results.span())) + } else if self.region.is_present() { + Some(SpannedValue::new(OperationFieldType::Region, self.region.span())) + } else if self.successor.is_present() { + Some(SpannedValue::new(OperationFieldType::Successor, self.successor.span())) + } else if self.successors.is_some() { + self.successors.map(|succ| succ.map_ref(|s| OperationFieldType::Successors(*s))) + } else if self.symbol.is_some() { + self.symbol + .as_ref() + .map(|sym| sym.map_ref(|sym| OperationFieldType::Symbol(sym.clone()))) + } else { + None + } + } +} + +#[derive(Debug, Clone)] +#[allow(clippy::large_enum_variant)] +pub enum OperationFieldType { + /// An operation attribute + Attr(AttrKind), + /// A named operand + Operand, + /// A named variadic operand group (zero or more operands) + Operands, + /// A named result + Result, + /// A named variadic result group (zero or more results) + Results, + /// A named region + Region, + /// A named successor + Successor, + /// A named variadic successor group (zero or more successors) + Successors(SuccessorsType), + /// A symbol operand + /// + /// Symbols are handled differently than regular operands, as they are not SSA values, and + /// are tracked using a different use/def graph than normal values. + /// + /// If the symbol type is `None`, it implies we should use the concrete field type as the + /// expected symbol type. Otherwise, use the provided symbol type to derive bounds for that + /// field. + Symbol(Option), +} +impl core::fmt::Display for OperationFieldType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Attr(AttrKind::Default) => f.write_str("attr"), + Self::Attr(AttrKind::Hidden) => f.write_str("attr(hidden)"), + Self::Operand => f.write_str("operand"), + Self::Operands => f.write_str("operands"), + Self::Result => f.write_str("result"), + Self::Results => f.write_str("results"), + Self::Region => f.write_str("region"), + Self::Successor => f.write_str("successor"), + Self::Successors(SuccessorsType::Default) => f.write_str("successors"), + Self::Successors(SuccessorsType::Keyed) => f.write_str("successors(keyed)"), + Self::Symbol(None) => f.write_str("symbol"), + Self::Symbol(Some(SymbolType::Any)) => f.write_str("symbol(any)"), + Self::Symbol(Some(SymbolType::Callable)) => f.write_str("symbol(callable)"), + Self::Symbol(Some(SymbolType::Concrete(_))) => write!(f, "symbol(concrete)"), + Self::Symbol(Some(SymbolType::Trait(_))) => write!(f, "symbol(trait)"), + } + } +} + +/// The type of successor group +#[derive(Default, Debug, darling::FromMeta, Copy, Clone)] +#[darling(default)] +pub enum SuccessorsType { + /// The default successor type consists of a `BlockRef` and an iterable of `ValueRef` + #[default] + Default, + /// A keyed successor is a custom type that implements the `KeyedSuccessor` trait + Keyed, +} + +/// Represents parameter information for `$Op::create` and the associated builder infrastructure. +#[derive(Debug)] +pub struct OpCreateParam { + /// The actual parameter type and payload + param_ty: OpCreateParamType, + /// Is this value initialized using `Default::default` when `Op::create` is called? + r#default: bool, +} + +#[derive(Debug)] +pub enum OpCreateParamType { + Attr(OpAttribute), + Operand(Operand), + #[allow(dead_code)] + OperandGroup(Ident, syn::Type), + CustomField(Ident, syn::Type), + Successor(Ident), + SuccessorGroupNamed(Ident), + SuccessorGroupKeyed(Ident, syn::Type), + Symbol(Symbol), +} +impl OpCreateParam { + /// Returns the names of all bindings implied by this parameter. + pub fn bindings(&self) -> Vec { + match &self.param_ty { + OpCreateParamType::Attr(OpAttribute { name, .. }) + | OpCreateParamType::CustomField(name, _) + | OpCreateParamType::Operand(Operand { name, .. }) + | OpCreateParamType::OperandGroup(name, _) + | OpCreateParamType::SuccessorGroupNamed(name) + | OpCreateParamType::SuccessorGroupKeyed(name, _) + | OpCreateParamType::Symbol(Symbol { name, .. }) => vec![name.clone()], + OpCreateParamType::Successor(name) => { + vec![name.clone(), format_ident!("{}_args", name)] + } + } + } + + /// Returns the names of all required (i.e. non-defaulted) bindings implied by this parameter. + pub fn required_bindings(&self) -> Vec { + if self.default { + return vec![]; + } + self.bindings() + } + + /// Returns the types assigned to the bindings returned by [Self::bindings] + pub fn binding_types(&self) -> Vec { + match &self.param_ty { + OpCreateParamType::Attr(OpAttribute { ty, .. }) + | OpCreateParamType::CustomField(_, ty) => { + vec![ty.clone()] + } + OpCreateParamType::Operand(_) => vec![make_type("::midenc_hir::ValueRef")], + OpCreateParamType::OperandGroup(group_name, _) + | OpCreateParamType::SuccessorGroupNamed(group_name) + | OpCreateParamType::SuccessorGroupKeyed(group_name, _) => { + vec![make_type(format!("T{}", group_name.to_string().to_pascal_case()))] + } + OpCreateParamType::Successor(name) => vec![ + make_type("::midenc_hir::BlockRef"), + make_type(format!("T{}Args", name.to_string().to_pascal_case())), + ], + OpCreateParamType::Symbol(Symbol { name, ty }) => match ty { + SymbolType::Any | SymbolType::Callable | SymbolType::Trait(_) => { + vec![make_type(format!("T{}", name.to_string().to_pascal_case()))] + } + SymbolType::Concrete(ty) => { + vec![parse_quote! { ::midenc_hir::UnsafeIntrusiveEntityRef<#ty> }] + } + }, + } + } + + /// Returns the types assigned to the bindings returned by [Self::required_bindings] + pub fn required_binding_types(&self) -> Vec { + if self.default { + return vec![]; + } + self.binding_types() + } + + /// Returns the generic type parameters bound for use by the types in [Self::binding_typess] + pub fn generic_types(&self) -> Vec { + match &self.param_ty { + OpCreateParamType::OperandGroup(name, _) => { + let value_iter_bound: syn::TypeParamBound = + syn::parse_str("IntoIterator").unwrap(); + vec![syn::GenericParam::Type(syn::TypeParam { + attrs: vec![], + ident: format_ident!( + "T{}", + &name.to_string().to_pascal_case(), + span = name.span() + ), + colon_token: Some(syn::token::Colon(name.span())), + bounds: syn::punctuated::Punctuated::from_iter([value_iter_bound]), + eq_token: None, + r#default: None, + })] + } + OpCreateParamType::Successor(name) => { + let value_iter_bound: syn::TypeParamBound = + syn::parse_str("IntoIterator").unwrap(); + vec![syn::GenericParam::Type(syn::TypeParam { + attrs: vec![], + ident: format_ident!( + "T{}Args", + &name.to_string().to_pascal_case(), + span = name.span() + ), + colon_token: Some(syn::token::Colon(name.span())), + bounds: syn::punctuated::Punctuated::from_iter([value_iter_bound]), + eq_token: None, + r#default: None, + })] + } + OpCreateParamType::SuccessorGroupNamed(name) => { + let value_iter_bound: syn::TypeParamBound = syn::parse_str( + "IntoIterator)>", + ) + .unwrap(); + vec![syn::GenericParam::Type(syn::TypeParam { + attrs: vec![], + ident: format_ident!( + "T{}", + &name.to_string().to_pascal_case(), + span = name.span() + ), + colon_token: Some(syn::token::Colon(name.span())), + bounds: syn::punctuated::Punctuated::from_iter([value_iter_bound]), + eq_token: None, + r#default: None, + })] + } + OpCreateParamType::SuccessorGroupKeyed(name, ty) => { + let item_name = name.to_string().to_pascal_case(); + let iterator_ty = format_ident!("T{item_name}", span = name.span()); + vec![syn::parse_quote! { + #iterator_ty: IntoIterator + }] + } + OpCreateParamType::Symbol(Symbol { name, ty }) => match ty { + SymbolType::Any => { + let as_symbol_ref_bound = + syn::parse_str::("::midenc_hir::AsSymbolRef").unwrap(); + vec![syn::GenericParam::Type(syn::TypeParam { + attrs: vec![], + ident: format_ident!("T{}", name.to_string().to_pascal_case()), + colon_token: Some(syn::token::Colon(name.span())), + bounds: syn::punctuated::Punctuated::from_iter([as_symbol_ref_bound]), + eq_token: None, + r#default: None, + })] + } + SymbolType::Callable => { + let as_callable_symbol_ref_bound = + syn::parse_str::("::midenc_hir::AsCallableSymbolRef") + .unwrap(); + vec![syn::GenericParam::Type(syn::TypeParam { + attrs: vec![], + ident: format_ident!("T{}", name.to_string().to_pascal_case()), + colon_token: Some(syn::token::Colon(name.span())), + bounds: syn::punctuated::Punctuated::from_iter([ + as_callable_symbol_ref_bound, + ]), + eq_token: None, + r#default: None, + })] + } + SymbolType::Concrete(_) => vec![], + SymbolType::Trait(bounds) => { + let as_symbol_ref_bound = syn::parse_str("::midenc_hir::AsSymbolRef").unwrap(); + vec![syn::GenericParam::Type(syn::TypeParam { + attrs: vec![], + ident: format_ident!("T{}", name.to_string().to_pascal_case()), + colon_token: Some(syn::token::Colon(name.span())), + bounds: syn::punctuated::Punctuated::from_iter( + [as_symbol_ref_bound].into_iter().chain(bounds.iter().cloned()), + ), + eq_token: None, + r#default: None, + })] + } + }, + _ => vec![], + } + } + + /// Returns the generic type parameters bound for use by the types in [Self::required_binding_typess] + pub fn required_generic_types(&self) -> Vec { + if self.default { + return vec![]; + } + self.generic_types() + } +} + +/// A symbol value +#[derive(Debug, Clone)] +pub struct Symbol { + pub name: Ident, + pub ty: SymbolType, +} + +/// Represents the type of a symbol +#[derive(Debug, Clone)] +pub enum SymbolType { + /// Any `Symbol` implementation can be used + Any, + /// Any `Symbol + CallableOpInterface` implementation can be used + Callable, + /// Only the specific concrete type can be used, it must implement `Op` and `Symbol` traits + Concrete(syn::Type), + /// Any implementation of the provided trait can be used. + /// + /// The given trait type _must_ have `Symbol` as a supertrait. + Trait(syn::punctuated::Punctuated), +} + +struct SymbolTraitBound { + _eq_token: Token![=], + bounds: syn::punctuated::Punctuated, +} +impl syn::parse::Parse for SymbolTraitBound { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if !lookahead.peek(Token![=]) { + return Err(lookahead.error()); + } + + let _eq_token = input.parse::()?; + let bounds = syn::punctuated::Punctuated::parse_separated_nonempty(input)?; + + Ok(Self { _eq_token, bounds }) + } +} +impl From for SymbolType { + #[inline] + fn from(value: SymbolTraitBound) -> Self { + SymbolType::Trait(value.bounds) + } +} + +pub struct DocString { + span: proc_macro2::Span, + doc: String, +} +impl DocString { + pub fn new(span: proc_macro2::Span, doc: String) -> Self { + Self { span, doc } + } +} +impl quote::ToTokens for DocString { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + let attr = syn::Attribute { + pound_token: syn::token::Pound(self.span), + style: syn::AttrStyle::Outer, + bracket_token: syn::token::Bracket(self.span), + meta: syn::Meta::NameValue(syn::MetaNameValue { + path: attr_path("doc"), + eq_token: syn::token::Eq(self.span), + value: syn::Expr::Lit(syn::ExprLit { + attrs: vec![], + lit: syn::Lit::Str(syn::LitStr::new(&self.doc, self.span)), + }), + }), + }; + + attr.to_tokens(tokens); + } +} + +#[derive(Copy, Clone)] +enum PathStyle { + Default, + Absolute, +} + +fn make_type(s: impl AsRef) -> syn::Type { + let s = s.as_ref(); + let path = type_path(s); + syn::Type::Path(syn::TypePath { qself: None, path }) +} + +fn type_path(s: impl AsRef) -> syn::Path { + let s = s.as_ref(); + let (s, style) = if let Some(s) = s.strip_prefix("::") { + (s, PathStyle::Absolute) + } else { + (s, PathStyle::Default) + }; + let parts = s.split("::"); + make_path(parts, style) +} + +fn attr_path(s: impl AsRef) -> syn::Path { + make_path([s.as_ref()], PathStyle::Default) +} + +fn make_path<'a>(parts: impl IntoIterator, style: PathStyle) -> syn::Path { + use proc_macro2::Span; + + syn::Path { + leading_colon: match style { + PathStyle::Default => None, + PathStyle::Absolute => Some(syn::token::PathSep(Span::call_site())), + }, + segments: syn::punctuated::Punctuated::from_iter(parts.into_iter().map(|part| { + syn::PathSegment { + ident: format_ident!("{}", part), + arguments: syn::PathArguments::None, + } + })), + } +} + +#[cfg(test)] +mod tests { + #![allow(dead_code)] + + #[test] + fn operation_impl_test() { + let item_input: syn::DeriveInput = syn::parse_quote! { + /// Two's complement sum + #[operation( + dialect = HirDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface), + )] + pub struct Add { + /// The left-hand operand + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, + #[attr] + overflow: Overflow, + } + }; + + let output = super::derive_operation(item_input); + match output { + Ok(output) => { + let formatted = format_output(&output.to_string()); + println!("{formatted}"); + } + Err(err) => { + panic!("{err}"); + } + } + } + + fn format_output(input: &str) -> String { + use std::{ + io::{Read, Write}, + process::{Command, Stdio}, + }; + + let mut child = Command::new("rustfmt") + .args(["--edition", "2024"]) + .args([ + "--config", + "unstable_features=true,normalize_doc_attributes=true,\ + use_field_init_shorthand=true,condense_wildcard_suffixes=true,\ + format_strings=true,group_imports=StdExternalCrate,imports_granularity=Crate", + ]) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("failed to spawn 'rustfmt'"); + + { + let mut stdin = child.stdin.take().unwrap(); + stdin.write_all(input.as_bytes()).expect("failed to write input to 'rustfmt'"); + } + let mut buf = String::new(); + let mut stdout = child.stdout.take().unwrap(); + stdout.read_to_string(&mut buf).expect("failed to read output from 'rustfmt'"); + match child.wait() { + Ok(status) => { + if status.success() { + buf + } else { + let mut stderr = child.stderr.take().unwrap(); + let mut err_buf = String::new(); + let _ = stderr.read_to_string(&mut err_buf).ok(); + panic!( + "command 'rustfmt' failed with status {:?}\n\nReason: {}", + status.code(), + if err_buf.is_empty() { + "" + } else { + err_buf.as_str() + }, + ); + } + } + Err(err) => panic!("command 'rustfmt' failed with {err}"), + } + } +} diff --git a/hir-symbol/CHANGELOG.md b/hir-symbol/CHANGELOG.md index e3250c8c6..b5bf9ddd8 100644 --- a/hir-symbol/CHANGELOG.md +++ b/hir-symbol/CHANGELOG.md @@ -6,6 +6,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-hir-symbol-v0.1.5...midenc-hir-symbol-v0.4.0) - 2025-08-15 + +### Added + +- implement advice map API in Miden SDK + +### Other + +- rename `io` to `advice`, export modules in stdlib SDK +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-hir-symbol-v0.0.7...midenc-hir-symbol-v0.0.8) - 2025-04-24 + +### Added +- implement pretty-print trait for Symbol/Type +- support no-std builds of midenc_hir_symbol + +### Fixed +- expose sync module from hir-symbol + +### Other +- treat warnings as compiler errors, +- update pre-interned symbols +- update rust toolchain, clean up deps +- update rust toolchain to 1-16 nightly @ 1.86.0 +- codegen +- normalize use of fxhash-based hash maps +- implement hir dialect ops, flesh out remaining core ir infra +- basic structure landed, starting refinement + ## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/midenc-hir-symbol-v0.0.5...midenc-hir-symbol-v0.0.6) - 2024-09-06 ### Other diff --git a/hir-symbol/Cargo.toml b/hir-symbol/Cargo.toml index 6274ede9d..8350e23c9 100644 --- a/hir-symbol/Cargo.toml +++ b/hir-symbol/Cargo.toml @@ -12,13 +12,24 @@ readme.workspace = true edition.workspace = true [features] -default = [] +default = ["std"] +std = ["dep:parking_lot"] serde = ["dep:serde"] +compact_str = ["dep:compact_str"] [dependencies] +compact_str = { workspace = true, optional = true } +lock_api = "0.4" +miden-formatting.workspace = true +parking_lot = { version = "0.12", optional = true } serde = { workspace = true, optional = true } [build-dependencies] Inflector.workspace = true +hashbrown.workspace = true +hashbrown_old_nightly_hack.workspace = true rustc-hash.workspace = true toml.workspace = true + +[package.metadata.cargo-machete] +ignored = ["Inflector", "hashbrown", "rustc-hash", "toml"] diff --git a/hir-symbol/build.rs b/hir-symbol/build.rs index 96d42798a..5c716dacc 100644 --- a/hir-symbol/build.rs +++ b/hir-symbol/build.rs @@ -1,3 +1,4 @@ +extern crate hashbrown; extern crate inflector; extern crate rustc_hash; extern crate toml; @@ -12,9 +13,10 @@ use std::{ }; use inflector::Inflector; -use rustc_hash::FxHashSet; use toml::{value::Table, Value}; +type FxHashSet = hashbrown::HashSet; + #[derive(Debug, Default, Clone)] struct Symbol { key: String, @@ -91,7 +93,7 @@ impl Section { for (name, value) in table.iter() { let mut sym = Symbol::from_value(name, value); sym.is_keyword = section.name == "keywords"; - assert!(section.keys.insert(sym), "duplicate symbol {}", name); + assert!(section.keys.insert(sym), "duplicate symbol {name}"); } section } diff --git a/hir-symbol/src/lib.rs b/hir-symbol/src/lib.rs index 7d24dc6f6..70e31e4fc 100644 --- a/hir-symbol/src/lib.rs +++ b/hir-symbol/src/lib.rs @@ -1,26 +1,32 @@ -use core::{fmt, mem, ops::Deref, str}; -use std::{ +#![no_std] +#![deny(warnings)] + +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + +pub mod sync; + +use alloc::{ + boxed::Box, collections::BTreeMap, - sync::{OnceLock, RwLock}, + string::{String, ToString}, + vec::Vec, }; +use core::{fmt, mem, ops::Deref, str}; -static SYMBOL_TABLE: OnceLock = OnceLock::new(); +use miden_formatting::prettier::PrettyPrint; pub mod symbols { include!(env!("SYMBOLS_RS")); } +static SYMBOL_TABLE: sync::LazyLock = sync::LazyLock::new(SymbolTable::default); + +#[derive(Default)] struct SymbolTable { - interner: RwLock, -} -impl SymbolTable { - pub fn new() -> Self { - Self { - interner: RwLock::new(Interner::new()), - } - } + interner: sync::RwLock, } -unsafe impl Sync for SymbolTable {} /// A symbol is an interned string. #[derive(Clone, Copy, PartialEq, Eq, Hash)] @@ -43,7 +49,7 @@ impl<'de> serde::Deserialize<'de> for Symbol { D: serde::Deserializer<'de>, { struct SymbolVisitor; - impl<'de> serde::de::Visitor<'de> for SymbolVisitor { + impl serde::de::Visitor<'_> for SymbolVisitor { type Value = Symbol; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { @@ -68,8 +74,8 @@ impl Symbol { } /// Maps a string to its interned representation. - pub fn intern>(string: S) -> Self { - let string = string.into(); + pub fn intern(string: impl ToString) -> Self { + let string = string.to_string(); with_interner(|interner| interner.intern(string)) } @@ -107,6 +113,12 @@ impl fmt::Display for Symbol { fmt::Display::fmt(&self.as_str(), f) } } +impl PrettyPrint for Symbol { + fn render(&self) -> miden_formatting::prettier::Document { + use miden_formatting::prettier::*; + const_text(self.as_str()) + } +} impl PartialOrd for Symbol { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -117,11 +129,53 @@ impl Ord for Symbol { self.as_str().cmp(other.as_str()) } } +impl AsRef for Symbol { + #[inline(always)] + fn as_ref(&self) -> &str { + self.as_str() + } +} +impl core::borrow::Borrow for Symbol { + #[inline(always)] + fn borrow(&self) -> &str { + self.as_str() + } +} impl> PartialEq for Symbol { fn eq(&self, other: &T) -> bool { self.as_str() == other.deref() } } +impl From<&'static str> for Symbol { + fn from(s: &'static str) -> Self { + with_interner(|interner| interner.insert(s)) + } +} +impl From for Symbol { + fn from(s: String) -> Self { + Self::intern(s) + } +} +impl From> for Symbol { + fn from(s: Box) -> Self { + Self::intern(s) + } +} +impl From> for Symbol { + fn from(s: alloc::borrow::Cow<'static, str>) -> Self { + use alloc::borrow::Cow; + match s { + Cow::Borrowed(s) => s.into(), + Cow::Owned(s) => Self::intern(s), + } + } +} +#[cfg(feature = "compact_str")] +impl From for Symbol { + fn from(s: compact_str::CompactString) -> Self { + Self::intern(s.into_string()) + } +} #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] struct SymbolIndex(u32); @@ -159,22 +213,26 @@ impl From for usize { } } -#[derive(Default)] struct Interner { pub names: BTreeMap<&'static str, Symbol>, pub strings: Vec<&'static str>, } -impl Interner { - pub fn new() -> Self { - let mut this = Interner::default(); +impl Default for Interner { + fn default() -> Self { + let mut this = Self { + names: BTreeMap::default(), + strings: Vec::with_capacity(symbols::__SYMBOLS.len()), + }; for (sym, s) in symbols::__SYMBOLS { this.names.insert(s, *sym); this.strings.push(s); } this } +} +impl Interner { pub fn intern(&mut self, string: String) -> Symbol { if let Some(&name) = self.names.get(string.as_str()) { return name; @@ -189,7 +247,17 @@ impl Interner { name } - pub fn get(&self, symbol: Symbol) -> &str { + pub fn insert(&mut self, s: &'static str) -> Symbol { + if let Some(&name) = self.names.get(s) { + return name; + } + let name = Symbol::new(self.strings.len() as u32); + self.strings.push(s); + self.names.insert(s, name); + name + } + + pub fn get(&self, symbol: Symbol) -> &'static str { self.strings[symbol.0.as_usize()] } } @@ -197,14 +265,12 @@ impl Interner { // If an interner exists, return it. Otherwise, prepare a fresh one. #[inline] fn with_interner T>(f: F) -> T { - let table = SYMBOL_TABLE.get_or_init(SymbolTable::new); - let mut r = table.interner.write().unwrap(); - f(&mut r) + let mut table = SYMBOL_TABLE.interner.write(); + f(&mut table) } #[inline] fn with_read_only_interner T>(f: F) -> T { - let table = SYMBOL_TABLE.get_or_init(SymbolTable::new); - let r = table.interner.read().unwrap(); - f(&r) + let table = SYMBOL_TABLE.interner.read(); + f(&table) } diff --git a/hir-symbol/src/symbols.toml b/hir-symbol/src/symbols.toml index bababf11f..acb1e4b65 100644 --- a/hir-symbol/src/symbols.toml +++ b/hir-symbol/src/symbols.toml @@ -4,14 +4,13 @@ true = { id = 1 } empty = { value = "" } [keywords] -kernel = {} +world = {} +component = {} module = {} +function = {} +public = {} +private = {} internal = {} -odr = {} -external = {} -extern = {} -pub = {} -fn = {} cc = {} fast = {} sret = {} @@ -22,11 +21,19 @@ ret = {} call = {} syscall = {} br = {} -condbr = {} +cond_br = {} switch = {} -test = {} +if = {} +while = {} +index_switch = {} +yield = {} +condition = {} load = {} +store = {} memcpy = {} +memset = {} +memory_size = {} +memory_grow = {} asm = {} min = {} max = {} @@ -43,7 +50,6 @@ gt = {} gte = {} lt = {} lte = {} -store = {} inv = {} pow2 = {} not = {} @@ -51,14 +57,15 @@ bnot = {} popcnt = {} is_odd = { value = "is_odd" } cast = {} -ptrtoint = {} -inttoptr = {} +ptr_to_int = {} +int_to_ptr = {} neg = {} select = {} assert = {} assertz = {} alloca = {} unreachable = {} +poison = {} i1 = {} i8 = {} u8 = {} @@ -76,15 +83,36 @@ felt = {} mut = {} as = {} id = {} -global = {} -symbol = {} -iadd = {} -const = {} - -[passes] -inline_blocks = { value = "inline-blocks" } -split_critical_edges = { value = "split-critical-edges" } -treeify = { value = "treeify" } +global_variable = {} +global_symbol = {} +segment = {} +constant = {} [attributes] -entrypoint = {} +inline = {} + +[dialects] +arith = {} +builtin = {} +cf = {} +hir = {} +scf = {} +test = {} +ub = {} + +[modules] +account = {} +blake3 = {} +crypto = {} +debug = {} +dsa = {} +felt_module = { value = "felt" } +hashes = {} +intrinsics = {} +advice = {} +miden = {} +mem = {} +note = {} +rpo_falcon512 = {} +std = {} +tx = {} diff --git a/hir-symbol/src/sync.rs b/hir-symbol/src/sync.rs new file mode 100644 index 000000000..ffcab049a --- /dev/null +++ b/hir-symbol/src/sync.rs @@ -0,0 +1,20 @@ +#[cfg(all(not(feature = "std"), target_family = "wasm"))] +mod lazy_lock; +#[cfg(all(not(feature = "std"), target_family = "wasm"))] +mod rw_lock; + +#[cfg(feature = "std")] +pub use std::sync::LazyLock; + +#[cfg(feature = "std")] +#[allow(unused)] +pub use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +#[cfg(all(not(feature = "std"), target_family = "wasm"))] +pub use self::lazy_lock::RacyLock as LazyLock; +#[cfg(all(not(feature = "std"), target_family = "wasm"))] +#[allow(unused)] +pub use self::rw_lock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; + +#[cfg(all(not(feature = "std"), not(target_family = "wasm")))] +compile_error!("no_std builds of this crate are only supported on wasm targets"); diff --git a/hir-symbol/src/sync/lazy_lock.rs b/hir-symbol/src/sync/lazy_lock.rs new file mode 100644 index 000000000..157cd41ed --- /dev/null +++ b/hir-symbol/src/sync/lazy_lock.rs @@ -0,0 +1,187 @@ +use alloc::boxed::Box; +use core::{ + fmt, + ops::Deref, + ptr, + sync::atomic::{AtomicPtr, Ordering}, +}; + +/// Thread-safe, non-blocking, lazily evaluated lock with the same interface +/// as [`std::sync::LazyLock`]. +/// +/// Concurrent threads will race to set the value atomically, and memory allocated by losing threads +/// will be dropped immediately after they fail to set the pointer. +/// +/// The underlying implementation is based on `once_cell::race::OnceBox` which relies on +/// [`core::sync::atomic::AtomicPtr`] to ensure that the data race results in a single successful +/// write to the relevant pointer, namely the first write. +/// See . +/// +/// Performs lazy evaluation and can be used for statics. +pub struct RacyLock T> +where + F: Fn() -> T, +{ + inner: AtomicPtr, + f: F, +} + +impl RacyLock +where + F: Fn() -> T, +{ + /// Creates a new lazy, racy value with the given initializing function. + pub const fn new(f: F) -> Self { + Self { + inner: AtomicPtr::new(ptr::null_mut()), + f, + } + } + + /// Forces the evaluation of the locked value and returns a reference to + /// the result. This is equivalent to the [`Self::deref`]. + /// + /// There is no blocking involved in this operation. Instead, concurrent + /// threads will race to set the underlying pointer. Memory allocated by + /// losing threads will be dropped immediately after they fail to set the pointer. + /// + /// This function's interface is designed around [`std::sync::LazyLock::force`] but + /// the implementation is derived from `once_cell::race::OnceBox::get_or_try_init`. + pub fn force(this: &RacyLock) -> &T { + let mut ptr = this.inner.load(Ordering::Acquire); + + // Pointer is not yet set, attempt to set it ourselves. + if ptr.is_null() { + // Execute the initialization function and allocate. + let val = (this.f)(); + ptr = Box::into_raw(Box::new(val)); + + // Attempt atomic store. + let exchange = this.inner.compare_exchange( + ptr::null_mut(), + ptr, + Ordering::AcqRel, + Ordering::Acquire, + ); + + // Pointer already set, load. + if let Err(old) = exchange { + drop(unsafe { Box::from_raw(ptr) }); + ptr = old; + } + } + + unsafe { &*ptr } + } +} + +impl Default for RacyLock { + /// Creates a new lock that will evaluate the underlying value based on `T::default`. + #[inline] + fn default() -> RacyLock { + RacyLock::new(T::default) + } +} + +impl fmt::Debug for RacyLock +where + T: fmt::Debug, + F: Fn() -> T, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "RacyLock({:?})", self.inner.load(Ordering::Relaxed)) + } +} + +impl Deref for RacyLock +where + F: Fn() -> T, +{ + type Target = T; + + /// Either sets or retrieves the value, and dereferences it. + /// + /// See [`Self::force`] for more details. + #[inline] + fn deref(&self) -> &T { + RacyLock::force(self) + } +} + +impl Drop for RacyLock +where + F: Fn() -> T, +{ + /// Drops the underlying pointer. + fn drop(&mut self) { + let ptr = *self.inner.get_mut(); + if !ptr.is_null() { + // SAFETY: for any given value of `ptr`, we are guaranteed to have at most a single + // instance of `RacyLock` holding that value. Hence, synchronizing threads + // in `drop()` is not necessary, and we are guaranteed never to double-free. + // In short, since `RacyLock` doesn't implement `Clone`, the only scenario + // where there can be multiple instances of `RacyLock` across multiple threads + // referring to the same `ptr` value is when `RacyLock` is used in a static variable. + drop(unsafe { Box::from_raw(ptr) }); + } + } +} + +#[cfg(test)] +mod tests { + use alloc::vec::Vec; + + use super::*; + + #[test] + fn deref_default() { + // Lock a copy type and validate default value. + let lock: RacyLock = RacyLock::default(); + assert_eq!(*lock, 0); + } + + #[test] + fn deref_copy() { + // Lock a copy type and validate value. + let lock = RacyLock::new(|| 42); + assert_eq!(*lock, 42); + } + + #[test] + fn deref_clone() { + // Lock a no copy type. + let lock = RacyLock::new(|| Vec::from([1, 2, 3])); + + // Use the value so that the compiler forces us to clone. + let mut v = lock.clone(); + v.push(4); + + // Validate the value. + assert_eq!(v, Vec::from([1, 2, 3, 4])); + } + + #[test] + fn deref_static() { + // Create a static lock. + static VEC: RacyLock> = RacyLock::new(|| Vec::from([1, 2, 3])); + + // Validate that the address of the value does not change. + let addr = &*VEC as *const Vec; + for _ in 0..5 { + assert_eq!(*VEC, [1, 2, 3]); + assert_eq!(addr, &(*VEC) as *const Vec) + } + } + + #[test] + fn type_inference() { + // Check that we can infer `T` from closure's type. + let _ = RacyLock::new(|| ()); + } + + #[test] + fn is_sync_send() { + fn assert_traits() {} + assert_traits::>>(); + } +} diff --git a/hir-symbol/src/sync/rw_lock.rs b/hir-symbol/src/sync/rw_lock.rs new file mode 100644 index 000000000..66b61bf0a --- /dev/null +++ b/hir-symbol/src/sync/rw_lock.rs @@ -0,0 +1,220 @@ +use core::{ + hint, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use lock_api::RawRwLock; + +/// An implementation of a reader-writer lock, based on a spinlock primitive, no-std compatible +/// +/// See [lock_api::RwLock] for usage. +pub type RwLock = lock_api::RwLock; + +/// See [lock_api::RwLockReadGuard] for usage. +pub type RwLockReadGuard<'a, T> = lock_api::RwLockReadGuard<'a, Spinlock, T>; + +/// See [lock_api::RwLockWriteGuard] for usage. +pub type RwLockWriteGuard<'a, T> = lock_api::RwLockWriteGuard<'a, Spinlock, T>; + +/// The underlying raw reader-writer primitive that implements [lock_api::RawRwLock] +/// +/// This is fundamentally a spinlock, in that blocking operations on the lock will spin until +/// they succeed in acquiring/releasing the lock. +/// +/// To acheive the ability to share the underlying data with multiple readers, or hold +/// exclusive access for one writer, the lock state is based on a "locked" count, where shared +/// access increments the count by an even number, and acquiring exclusive access relies on the +/// use of the lowest order bit to stop further shared acquisition, and indicate that the lock +/// is exclusively held (the difference between the two is irrelevant from the perspective of +/// a thread attempting to acquire the lock, but internally the state uses `usize::MAX` as the +/// "exlusively locked" sentinel). +/// +/// This mechanism gets us the following: +/// +/// * Whether the lock has been acquired (shared or exclusive) +/// * Whether the lock is being exclusively acquired +/// * How many times the lock has been acquired +/// * Whether the acquisition(s) are exclusive or shared +/// +/// Further implementation details, such as how we manage draining readers once an attempt to +/// exclusively acquire the lock occurs, are described below. +/// +/// NOTE: This is a simple implementation, meant for use in no-std environments; there are much +/// more robust/performant implementations available when OS primitives can be used. +pub struct Spinlock { + /// The state of the lock, primarily representing the acquisition count, but relying on + /// the distinction between even and odd values to indicate whether or not exclusive access + /// is being acquired. + state: AtomicUsize, + /// A counter used to wake a parked writer once the last shared lock is released during + /// acquisition of an exclusive lock. The actual count is not acutally important, and + /// simply wraps around on overflow, but what is important is that when the value changes, + /// the writer will wake and resume attempting to acquire the exclusive lock. + writer_wake_counter: AtomicUsize, +} + +impl Default for Spinlock { + #[inline(always)] + fn default() -> Self { + Self::new() + } +} + +impl Spinlock { + pub const fn new() -> Self { + Self { + state: AtomicUsize::new(0), + writer_wake_counter: AtomicUsize::new(0), + } + } +} + +unsafe impl RawRwLock for Spinlock { + type GuardMarker = lock_api::GuardSend; + + // This is intentional on the part of the [RawRwLock] API, basically a hack to provide + // initial values as static items. + #[allow(clippy::declare_interior_mutable_const)] + const INIT: Spinlock = Spinlock::new(); + + /// The operation invoked when calling `RwLock::read`, blocks the caller until acquired + fn lock_shared(&self) { + let mut s = self.state.load(Ordering::Relaxed); + loop { + // If the exclusive bit is unset, attempt to acquire a read lock + if s & 1 == 0 { + match self.state.compare_exchange_weak( + s, + s + 2, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return, + // Someone else beat us to the punch and acquired a lock + Err(e) => s = e, + } + } + // If an exclusive lock is held/being acquired, loop until the lock state changes + // at which point, try to acquire the lock again + if s & 1 == 1 { + loop { + let next = self.state.load(Ordering::Relaxed); + if s == next { + hint::spin_loop(); + continue; + } else { + s = next; + break; + } + } + } + } + } + + /// The operation invoked when calling `RwLock::try_read`, returns whether or not the + /// lock was acquired + fn try_lock_shared(&self) -> bool { + let s = self.state.load(Ordering::Relaxed); + if s & 1 == 0 { + self.state + .compare_exchange_weak(s, s + 2, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + } else { + false + } + } + + /// The operation invoked when dropping a `RwLockReadGuard` + unsafe fn unlock_shared(&self) { + if self.state.fetch_sub(2, Ordering::Release) == 3 { + // The lock is being exclusively acquired, and we're the last shared acquisition + // to be released, so wake the writer by incrementing the wake counter + self.writer_wake_counter.fetch_add(1, Ordering::Release); + } + } + + /// The operation invoked when calling `RwLock::write`, blocks the caller until acquired + fn lock_exclusive(&self) { + let mut s = self.state.load(Ordering::Relaxed); + loop { + // Attempt to acquire the lock immediately, or complete acquistion of the lock + // if we're continuing the loop after acquiring the exclusive bit. If another + // thread acquired it first, we race to be the first thread to acquire it once + // released, by busy looping here. + if s <= 1 { + match self.state.compare_exchange( + s, + usize::MAX, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => return, + Err(e) => { + s = e; + hint::spin_loop(); + continue; + } + } + } + + // Only shared locks have been acquired, attempt to acquire the exclusive bit, + // which will prevent further shared locks from being acquired. It does not + // in and of itself grant us exclusive access however. + if s & 1 == 0 { + if let Err(e) = + self.state.compare_exchange(s, s + 1, Ordering::Relaxed, Ordering::Relaxed) + { + // The lock state has changed before we could acquire the exclusive bit, + // update our view of the lock state and try again + s = e; + continue; + } + } + + // We've acquired the exclusive bit, now we need to busy wait until all shared + // acquisitions are released. + let w = self.writer_wake_counter.load(Ordering::Acquire); + s = self.state.load(Ordering::Relaxed); + + // "Park" the thread here (by busy looping), until the release of the last shared + // lock, which is communicated to us by it incrementing the wake counter. + if s >= 2 { + while self.writer_wake_counter.load(Ordering::Acquire) == w { + hint::spin_loop(); + } + s = self.state.load(Ordering::Relaxed); + } + + // All shared locks have been released, go back to the top and try to complete + // acquisition of exclusive access. + } + } + + /// The operation invoked when calling `RwLock::try_write`, returns whether or not the + /// lock was acquired + fn try_lock_exclusive(&self) -> bool { + let s = self.state.load(Ordering::Relaxed); + if s <= 1 { + self.state + .compare_exchange(s, usize::MAX, Ordering::Acquire, Ordering::Relaxed) + .is_ok() + } else { + false + } + } + + /// The operation invoked when dropping a `RwLockWriteGuard` + unsafe fn unlock_exclusive(&self) { + // Infallible, as we hold an exclusive lock + // + // Note the use of `Release` ordering here, which ensures any loads of the lock state + // by other threads, are ordered after this store. + self.state.store(0, Ordering::Release); + // This fetch_add isn't important for signaling purposes, however it serves a key + // purpose, in that it imposes a memory ordering on any loads of this field that + // have an `Acquire` ordering, i.e. they will read the value stored here. Without + // a `Release` store, loads/stores of this field could be reordered relative to + // each other. + self.writer_wake_counter.fetch_add(1, Ordering::Release); + } +} diff --git a/hir-transform/CHANGELOG.md b/hir-transform/CHANGELOG.md index 42b0b834f..25fa715c7 100644 --- a/hir-transform/CHANGELOG.md +++ b/hir-transform/CHANGELOG.md @@ -6,6 +6,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-hir-transform-v0.4.0...midenc-hir-transform-v0.4.1) - 2025-09-03 + +### Fixed + +- only keep spills that feed a live reload dominated by this spill +- intra block spills rewrite to process all ops in the block + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-hir-transform-v0.1.5...midenc-hir-transform-v0.4.0) - 2025-08-15 + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-hir-transform-v0.1.0...midenc-hir-transform-v0.1.5) - 2025-07-01 + +### Fixed + +- delayed registration of scf dialect causes canonicalizations to be skipped +- release borrowed op in `spill.rs` (to be `borrow_mut()`-ed later) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-hir-transform-v0.0.7...midenc-hir-transform-v0.0.8) - 2025-04-24 + +### Fixed +- *(ir)* address a few aliasing violations when full tracing is enabled + +### Other +- treat warnings as compiler errors, +- rename hir2 crates +- remove old contents of hir, hir-analysis, hir-transform +- normalize use of fxhash-based hash maps +- rename Call IR op to Exec + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-hir-transform-v0.0.6...midenc-hir-transform-v0.0.7) - 2024-09-17 ### Other diff --git a/hir-transform/Cargo.toml b/hir-transform/Cargo.toml index 88b3f959d..392098ed5 100644 --- a/hir-transform/Cargo.toml +++ b/hir-transform/Cargo.toml @@ -12,14 +12,7 @@ readme.workspace = true edition.workspace = true [dependencies] -anyhow.workspace = true -inventory.workspace = true log.workspace = true midenc-hir.workspace = true midenc-hir-analysis.workspace = true midenc-session.workspace = true -rustc-hash.workspace = true -smallvec.workspace = true - -[dev-dependencies] -pretty_assertions.workspace = true diff --git a/hir-transform/src/adt/mod.rs b/hir-transform/src/adt/mod.rs deleted file mode 100644 index 38aa84015..000000000 --- a/hir-transform/src/adt/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod scoped_map; - -pub use self::scoped_map::ScopedMap; diff --git a/hir-transform/src/adt/scoped_map.rs b/hir-transform/src/adt/scoped_map.rs deleted file mode 100644 index fe6562b11..000000000 --- a/hir-transform/src/adt/scoped_map.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{borrow::Borrow, fmt, hash::Hash, rc::Rc}; - -use rustc_hash::FxHashMap; - -#[derive(Clone)] -pub struct ScopedMap -where - K: Eq + Hash, -{ - parent: Option>>, - map: FxHashMap, -} -impl Default for ScopedMap -where - K: Eq + Hash, -{ - fn default() -> Self { - Self { - parent: None, - map: Default::default(), - } - } -} -impl ScopedMap -where - K: Eq + Hash, -{ - pub fn new(parent: Option>>) -> Self { - Self { - parent, - map: Default::default(), - } - } - - pub fn get(&self, k: &Q) -> Option<&V> - where - K: Borrow, - Q: Hash + Eq + ?Sized, - { - self.map.get(k).or_else(|| self.parent.as_ref().and_then(|p| p.get(k))) - } - - pub fn insert(&mut self, k: K, v: V) { - self.map.insert(k, v); - } - - pub fn extend(&mut self, iter: I) - where - I: IntoIterator, - { - self.map.extend(iter); - } -} -impl fmt::Debug for ScopedMap -where - K: Eq + Hash + fmt::Debug, - V: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ScopedMap") - .field("parent", &self.parent) - .field("map", &self.map) - .finish() - } -} diff --git a/hir-transform/src/canonicalization.rs b/hir-transform/src/canonicalization.rs new file mode 100644 index 000000000..095979a40 --- /dev/null +++ b/hir-transform/src/canonicalization.rs @@ -0,0 +1,152 @@ +use alloc::{boxed::Box, format, rc::Rc}; + +use midenc_hir::{ + pass::{OperationPass, Pass, PassExecutionState, PostPassStatus}, + patterns::{self, FrozenRewritePatternSet, GreedyRewriteConfig, RewritePatternSet}, + Context, EntityMut, Operation, OperationName, Report, Spanned, +}; +use midenc_session::diagnostics::Severity; + +/// This pass performs various types of canonicalizations over a set of operations by iteratively +/// applying the canonicalization patterns of all loaded dialects until either a fixpoint is reached +/// or the maximum number of iterations/rewrites is exhausted. Canonicalization is best-effort and +/// does not guarantee that the entire IR is in a canonical form after running this pass. +/// +/// See the docs for [crate::traits::Canonicalizable] for more details. +pub struct Canonicalizer { + config: GreedyRewriteConfig, + rewrites: Option>, + require_convergence: bool, +} + +impl Default for Canonicalizer { + fn default() -> Self { + let mut config = GreedyRewriteConfig::default(); + config.with_top_down_traversal(true); + Self { + config, + rewrites: None, + require_convergence: false, + } + } +} + +impl Canonicalizer { + pub fn new(config: GreedyRewriteConfig, require_convergence: bool) -> Self { + Self { + config, + rewrites: None, + require_convergence, + } + } + + /// Creates an instance of this pass, configured with default settings. + pub fn create() -> Box { + Box::new(Self::default()) + } + + /// Creates an instance of this pass with the specified config. + pub fn create_with_config(config: &GreedyRewriteConfig) -> Box { + Box::new(Self { + config: config.clone(), + rewrites: None, + require_convergence: false, + }) + } +} + +impl Pass for Canonicalizer { + type Target = Operation; + + fn name(&self) -> &'static str { + "canonicalizer" + } + + fn argument(&self) -> &'static str { + "canonicalizer" + } + + fn description(&self) -> &'static str { + "Performs canonicalization over a set of operations" + } + + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn initialize(&mut self, context: Rc) -> Result<(), Report> { + log::trace!(target: "canonicalization", "initializing canonicalizer pass"); + let mut rewrites = RewritePatternSet::new(context.clone()); + + for dialect in context.registered_dialects().values() { + for op in dialect.registered_ops().iter() { + op.populate_canonicalization_patterns(&mut rewrites, context.clone()); + } + } + + self.rewrites = Some(Rc::new(FrozenRewritePatternSet::new(rewrites))); + + Ok(()) + } + + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + let Some(rewrites) = self.rewrites.as_ref() else { + log::debug!(target: "canonicalization", "skipping canonicalization as there are no rewrite patterns to apply"); + state.set_post_pass_status(PostPassStatus::Unchanged); + return Ok(()); + }; + let op = { + let ptr = op.as_operation_ref(); + drop(op); + log::debug!(target: "canonicalization", "applying canonicalization to {}", ptr.borrow()); + log::debug!(target: "canonicalization", " require_convergence = {}", self.require_convergence); + ptr + }; + let converged = + patterns::apply_patterns_and_fold_greedily(op, rewrites.clone(), self.config.clone()); + if self.require_convergence && converged.is_err() { + log::debug!(target: "canonicalization", "canonicalization could not converge"); + let span = op.borrow().span(); + return Err(state + .context() + .diagnostics() + .diagnostic(Severity::Error) + .with_message("canonicalization failed") + .with_primary_label( + span, + format!( + "canonicalization did not converge{}", + self.config + .max_iterations() + .map(|max| format!(" after {max} iterations")) + .unwrap_or_default() + ), + ) + .into_report()); + } + + let op = op.borrow(); + let changed = match converged { + Ok(changed) => { + log::debug!(target: "canonicalization", "canonicalization converged for '{}', changed={changed}", op.name()); + changed + } + Err(changed) => { + log::warn!( + target: "canonicalization", + "canonicalization failed to converge for '{}', changed={changed}", + op.name() + ); + changed + } + }; + let ir_changed = changed.into(); + state.set_post_pass_status(ir_changed); + + Ok(()) + } +} diff --git a/hir-transform/src/cfg_to_scf.rs b/hir-transform/src/cfg_to_scf.rs new file mode 100644 index 000000000..d8ebb5f22 --- /dev/null +++ b/hir-transform/src/cfg_to_scf.rs @@ -0,0 +1,403 @@ +//! This code is an implementation of the algorithm described in _Perfect Reconstructability of +//! Control Flow from Demand Dependence Graphs_, by Bahmann, Reismann, Jahre, and Meyer. 2015. +//! See https://doi.org/10.1145/2693261. +//! +//! It defines an algorithm to translate any control flow graph with a single entry and single exit +//! block into structured control flow operations consisting of regions of do-while loops and +//! operations conditionally dispatching to one out of multiple regions before continuing after the +//! operation. This includes control flow graphs containing irreducible control flow. +//! +//! The implementation here additionally supports the transformation on regions with multiple exit +//! blocks. This is implemented by first transforming all occurrences of return-like operations to +//! branch to a single exit block containing an instance of that return-like operation. If there are +//! multiple kinds of return-like operations, multiple exit blocks are created. In that case the +//! transformation leaves behind a conditional control flow graph operation that dispatches to the +//! given regions terminating with different kinds of return-like operations each. +//! +//! If the function only contains a single kind of return-like operations, it is guaranteed that all +//! control flow graph ops will be lifted to structured control flow, and that no more control flow +//! graph ops remain after the operation. +//! +//! The algorithm to lift CFGs consists of two transformations applied after each other on any +//! single-entry, single-exit region: +//! +//! 1. Lifting cycles to structured control flow loops +//! 2. Lifting conditional branches to structured control flow branches +//! +//! These are then applied recursively on any new single-entry single-exit regions created by the +//! transformation until no more CFG operations remain. +//! +//! The first part of cycle lifting is to detect any cycles in the CFG. This is done using an +//! algorithm for iterating over SCCs. Every SCC representing a cycle is then transformed into a +//! structured loop with a single entry block and a single latch containing the only back edge to +//! the entry block and the only edge to an exit block outside the loop. Rerouting control flow to +//! create single entry and exit blocks is achieved via a multiplexer construct that can be +//! visualized as follows: +//! +//! ```text,ignore +//! +-----+ +-----+ +-----+ +//! | bb0 | | bb1 |...| bbN | +//! +--+--+ +--+--+ +-+---+ +//! | | | +//! | v | +//! | +------+ | +//! | ++ ++<----+ +//! | | Region | +//! +>| |<----+ +//! ++ ++ | +//! +------+------+ +//! ``` +//! +//! The above transforms to: +//! +//! ```text,ignore +//! +-----+ +-----+ +-----+ +//! | bb0 | | bb1 |...| bbN | +//! +-----+ +--|--+ ++----+ +//! | v | +//! +->+-----+<---+ +//! | bbM |<-------+ +//! +---+-+ | +//! +---+ | +----+ | +//! | v | | +//! | +------+ | | +//! | ++ ++<-+ | +//! +->| Region | | +//! ++ ++ | +//! +------+-------+ +//! ``` +//! +//! bbM in the above is the multiplexer block, and any block previously branching to an entry block +//! of the region are redirected to it. This includes any branches from within the region. Using a +//! block argument, bbM then dispatches to the correct entry block of the region dependent on the +//! predecessor. +//! +//! A similar transformation is done to create the latch block with the single back edge and loop +//! exit edge. +//! +//! The above form has the advantage that bbM now acts as the loop header of the loop body. After +//! the transformation on the latch, this results in a structured loop that can then be lifted to +//! structured control flow. The conditional branches created in bbM are later lifted to conditional +//! branches. +//! +//! Lifting conditional branches is done by analyzing the *first* conditional branch encountered in +//! the entry region. The algorithm then identifies all blocks that are dominated by a specific +//! control flow edge and the region where control flow continues: +//! +//! ```text,ignore +//! +-----+ +//! +-----+ bb0 +----+ +//! v +-----+ v +//! Region 1 +-+-+ ... +-+-+ Region n +//! +---+ +---+ +//! ... ... +//! | | +//! | +---+ | +//! +---->++ ++<---+ +//! | | +//! ++ ++ Region T +//! +---+ +//! ``` +//! +//! Every region following bb0 consists of 0 or more blocks that eventually branch to Region T. If +//! there are multiple entry blocks into Region T, a single entry block is created using a +//! multiplexer block as shown above. Region 1 to Region n are then lifted together with the +//! conditional control flow operation terminating bb0 into a structured conditional operation +//! followed by the operations of the entry block of Region T. +mod edges; +mod transform; + +use midenc_hir::{ + adt::SmallSet, dominance::DominanceInfo, traits::BranchOpInterface, BlockRef, Builder, + OpBuilder, Operation, OperationRef, Region, RegionRef, Report, SmallVec, SourceSpan, Type, + Value, ValueRange, ValueRef, WalkResult, +}; + +use self::transform::TransformationContext; + +/// This trait is used to abstract over the dialect-specific aspects of the control flow lifting +/// transformation performed by [transform_cfg_to_scf]. +/// +/// Implementations must be able to create switch-like control flow operations in order to +/// facilitate intermediate transformations; as well as the various structured control flow ops +/// represented by each method (e.g. `scf.if`, `scf.while`). +pub trait CFGToSCFInterface { + /// Creates a structured control flow operation branching to one of `regions`. + /// + /// It replaces `control_flow_cond_op` and must produce `result_types` as results. + /// + /// `regions` contains the list of branch regions corresponding to each successor of + /// `control_flow_cond_op`. Their bodies must simply be taken and left as is. + /// + /// Returns `Err` if incapable of converting the control flow graph operation. + fn create_structured_branch_region_op( + &self, + builder: &mut OpBuilder, + control_flow_cond_op: OperationRef, + result_types: &[Type], + regions: &mut SmallVec<[RegionRef; 2]>, + ) -> Result; + + /// Creates a return-like terminator for a branch region of the op returned by + /// [CFGToSCFInterface::create_structured_branch_region_op]. + /// + /// * `branch_region_op` is the operation returned by `create_structured_branch_region_op`. + /// * `replaced_control_flow_op` is the control flow op being replaced by the terminator or + /// `None` if the terminator is not replacing any existing control flow op. + /// * `results` are the values that should be returned by the branch region. + fn create_structured_branch_region_terminator_op( + &self, + span: SourceSpan, + builder: &mut OpBuilder, + branch_region_op: OperationRef, + replaced_control_flow_op: Option, + results: ValueRange<'_, 2>, + ) -> Result<(), Report>; + + /// Creates a structured control flow operation representing a do-while loop. + /// + /// The do-while loop is expected to have the exact same result types as the types of the + /// iteration values. `loop_body` is the body of the loop. + /// + /// Implementations must create a suitable terminator op at the end of the last block in + /// `loop_body` which continues the loop if `condition` is 1, and exits the loop if 0. + /// + /// `loop_values_next_iter` are the values that have to be passed as the iteration values for + /// the next iteration if continuing, or the result of the loop if exiting. + /// + /// `condition` is guaranteed to be of the same type as values returned by + /// `get_cfg_switch_value` with either 0 or 1 as value. + /// + /// `loop_values_init` are the values used to initialize the iteration values of the loop. + /// + /// Returns `Err` if incapable of creating a loop op. + fn create_structured_do_while_loop_op( + &self, + builder: &mut OpBuilder, + replaced_op: OperationRef, + loop_values_init: ValueRange<'_, 2>, + condition: ValueRef, + loop_values_next_iter: ValueRange<'_, 2>, + loop_body: RegionRef, + ) -> Result; + + /// Creates a constant operation with a result representing `value` that is suitable as flag + /// for `create_cfg_switch_op`. + fn get_cfg_switch_value( + &self, + span: SourceSpan, + builder: &mut OpBuilder, + value: u32, + ) -> ValueRef; + + /// Creates a switch-like unstructured branch operation, branching to one of `case_destinations` + /// or `default_dest`. + /// + /// This is used by [transform_cfg_to_scfg] for intermediate transformations before lifting to + /// structured control flow. + /// + /// The switch op branches based on `flag` which is guaranteed to be of the same type as values + /// returned by `get_cfg_switch_value`. The insertion block of the builder is guaranteed to have + /// its predecessors already set to create an equivalent CFG after this operation. + /// + /// NOTE: `case_values` and other related slices may be empty to represent an unconditional + /// branch. + #[allow(clippy::too_many_arguments)] + fn create_cfg_switch_op( + &self, + span: SourceSpan, + builder: &mut OpBuilder, + flag: ValueRef, + case_values: &[u32], + case_destinations: &[BlockRef], + case_arguments: &[ValueRange<'_, 2>], + default_dest: BlockRef, + default_args: ValueRange<'_, 2>, + ) -> Result<(), Report>; + + /// Creates a constant operation returning an undefined instance of `type`. + /// + /// This is required by the transformation as the lifting process might create control flow + /// paths where an SSA-value is undefined. + fn get_undef_value(&self, span: SourceSpan, builder: &mut OpBuilder, ty: Type) -> ValueRef; + + /// Creates a return-like terminator indicating unreachable. + /// + /// This is required when the transformation encounters a statically known infinite loop. Since + /// structured control flow ops are not terminators, after lifting an infinite loop, a + /// terminator has to be placed after to possibly satisfy the terminator requirement of the + /// region originally passed to [transform_cfg_to_scf]. + /// + /// `region` is guaranteed to be the region originally passed to [transform_cfg_to_scf] and the + /// op is guaranteed to always be an op in a block directly nested under `region` after the + /// transformation. + /// + /// Returns `Err` if incapable of creating an unreachable terminator. + fn create_unreachable_terminator( + &self, + span: SourceSpan, + builder: &mut OpBuilder, + region: RegionRef, + ) -> Result; + + /// Helper function to create an unconditional branch using [create_cfg_switch_op]. + fn create_single_destination_branch( + &self, + span: SourceSpan, + builder: &mut OpBuilder, + dummy_flag: ValueRef, + destination: BlockRef, + arguments: ValueRange<'_, 2>, + ) -> Result<(), Report> { + self.create_cfg_switch_op(span, builder, dummy_flag, &[], &[], &[], destination, arguments) + } + + /// Helper function to create a conditional branch using [create_cfg_switch_op]. + #[allow(clippy::too_many_arguments)] + fn create_conditional_branch( + &self, + span: SourceSpan, + builder: &mut OpBuilder, + condition: ValueRef, + true_dest: BlockRef, + true_args: ValueRange<'_, 2>, + false_dest: BlockRef, + false_args: ValueRange<'_, 2>, + ) -> Result<(), Report> { + self.create_cfg_switch_op( + span, + builder, + condition, + &[0], + &[false_dest], + &[false_args], + true_dest, + true_args, + ) + } +} + +/// This function transforms all unstructured control flow operations within `region`, to equivalent +/// structured control flow operations. +/// +/// This transformation is dialect-agnostic, facilitated by delegating the dialect-specific aspects +/// of lifting operations, to the implementation of [CFGToSCFInterface] that is provided. +/// +/// If the region contains only a single kind of return-like operation, all control flow graph +/// operations will be converted successfully. Otherwise a single control flow graph operation +/// branching to one block per return-like operation kind remains. +/// +/// The transformation currently requires that all control flow graph operations have no side +/// effects, implement the [crate::traits::BranchOpInterface], and do not have any operation- +/// produced successor operands. +/// +/// Returns `Err` if any of the preconditions are violated or if any of the methods of `interface` +/// failed. The IR is left in an unspecified state in such cases. +/// +/// If successful, returns a boolean indicating whether the IR was changed. +pub fn transform_cfg_to_scf( + region: RegionRef, + interface: &mut dyn CFGToSCFInterface, + dominance_info: &mut DominanceInfo, +) -> Result { + { + let region = region.borrow(); + if region.is_empty() || region.has_one_block() { + return Ok(false); + } + + check_transformation_preconditions(®ion)?; + } + + let mut transform_ctx = TransformationContext::new(region, interface, dominance_info)?; + + let mut worklist = SmallVec::<[BlockRef; 4]>::from_slice(&[transform_ctx.entry()]); + while let Some(current) = worklist.pop() { + // Turn all top-level cycles in the CFG to structured control flow first. + // After this transformation, the remaining CFG ops form a DAG. + let mut new_regions = transform_ctx.transform_cycles_to_scf_loops(current)?; + + // Add the newly created subregions to the worklist. These are the bodies of the loops. + worklist.extend(new_regions.iter().copied()); + // Invalidate the dominance tree as blocks have been moved, created and added during the + // cycle to structured loop transformation. + if !new_regions.is_empty() { + let parent_region = current.parent().unwrap(); + transform_ctx.invalidate_dominance_info_for_region(parent_region); + } + new_regions = transform_ctx.transform_to_structured_cf_branches(current)?; + + // Invalidating the dominance tree is generally not required by the transformation above as + // the new region entries correspond to unaffected subtrees in the dominator tree. Only its + // parent nodes have changed but won't be visited again. + worklist.extend(new_regions); + } + + // Clean up garbage we may have created during the transformation + // + // NOTE: This is not guaranteed to clean up _everything_ that may be garbage, only things we + // have accounted for. Canonicalization and other optimization passes can take care of anything + // else that may remain + transform_ctx.garbage_collect(); + + Ok(true) +} + +/// Checks all preconditions of the transformation prior to any transformations. +/// +/// Returns failure if any precondition is violated. +fn check_transformation_preconditions(region: &Region) -> Result<(), Report> { + use midenc_hir::SuccessorOperands; + + for block in region.body() { + if !block.has_predecessors() && !block.is_entry_block() { + return Err(Report::msg("transformation does not support unreachable blocks")); + } + } + + let walk_result = region.prewalk(|op: &Operation| { + if !op.has_successors() { + return WalkResult::Skip; + } + + // This transformation requires all ops with successors to implement the branch op interface. + // It is impossible to adjust their block arguments otherwise. + let branch_op_interface = match op.as_trait::().ok_or_else(|| { + Report::msg( + "transformation does not support terminators with successors not implementing \ + BranchOpInterface", + ) + }) { + Ok(boi) => boi, + Err(err) => return WalkResult::Break(err), + }; + + // Branch operations must have no side effects. Replacing them would not be valid otherwise. + if !op.is_memory_effect_free() { + return WalkResult::Break(Report::msg( + "transformation does not support terminators with side effects", + )); + } + + for index in 0..op.num_successors() { + let succ_ops = branch_op_interface.get_successor_operands(index); + + // We cannot support operations with operation-produced successor operands as it is + // currently not possible to pass them to any block arguments other than the first. This + // breaks creating multiplexer blocks and would likely need special handling elsewhere + // too. + if succ_ops.num_produced() == 0 { + continue; + } + + return WalkResult::Break(Report::msg( + "transformation does not support operations with operation-produced successor \ + operands", + )); + } + + WalkResult::Continue(()) + }); + + walk_result.into_result() +} diff --git a/hir-transform/src/cfg_to_scf/edges.rs b/hir-transform/src/cfg_to_scf/edges.rs new file mode 100644 index 000000000..9d93960e1 --- /dev/null +++ b/hir-transform/src/cfg_to_scf/edges.rs @@ -0,0 +1,440 @@ +use midenc_hir::{ + adt::SmallDenseMap, AsValueRange, BlockRef, OpBuilder, Report, SmallVec, SourceSpan, Type, + ValueRef, +}; + +use super::*; + +/// Type representing an edge in the CFG. +/// +/// Consists of a from-block, a successor and corresponding successor operands passed to the block +/// arguments of the successor. +#[derive(Debug, Copy, Clone)] +pub struct Edge { + pub from_block: BlockRef, + pub successor_index: usize, +} + +impl Edge { + pub fn get_from_block(&self) -> BlockRef { + self.from_block + } + + pub fn get_successor(&self) -> BlockRef { + let from_block = self.from_block.borrow(); + from_block.get_successor(self.successor_index) + } + + pub fn get_predecessor(&self) -> OperationRef { + let from_block = self.from_block.borrow(); + from_block.terminator().unwrap() + } + + /// Sets the successor of the edge, adjusting the terminator in the from-block. + pub fn set_successor(&self, block: BlockRef) { + let mut terminator = { + let from_block = self.from_block.borrow(); + from_block.terminator().unwrap() + }; + let mut terminator = terminator.borrow_mut(); + let mut succ = terminator.successor_mut(self.successor_index); + succ.set(block); + } +} + +impl core::fmt::Display for Edge { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!( + f, + "{} -> {} (index {})", + self.from_block, + self.get_successor(), + self.successor_index + ) + } +} + +/// Utility-class for transforming a region to only have one single block for every return-like +/// operation. +/// Iterates over a range of all edges from `block` to each of its successors. +pub struct SuccessorEdges { + block: BlockRef, + num_successors: usize, + current: usize, +} + +impl SuccessorEdges { + pub fn new(block: BlockRef) -> Self { + let num_successors = block.borrow().num_successors(); + Self { + block, + num_successors, + current: 0, + } + } +} + +impl Iterator for SuccessorEdges { + type Item = Edge; + + fn next(&mut self) -> Option { + let successor_index = self.current; + if successor_index >= self.num_successors { + return None; + } + self.current += 1; + Some(Edge { + from_block: self.block, + successor_index, + }) + } +} + +/// Structure containing the entry, exit and back edges of a cycle. +/// +/// A cycle is a generalization of a loop that may have multiple entry edges. See also +/// https://llvm.org/docs/CycleTerminology.html. +#[derive(Debug, Default)] +pub struct CycleEdges { + /// All edges from a block outside the cycle to a block inside the cycle. + /// The targets of these edges are entry blocks. + pub entry_edges: SmallVec<[Edge; 1]>, + /// All edges from a block inside the cycle to a block outside the cycle. + pub exit_edges: SmallVec<[Edge; 1]>, + /// All edges from a block inside the cycle to an entry block. + pub back_edges: SmallVec<[Edge; 1]>, +} + +/// Calculates entry, exit and back edges of the given cycle. +pub fn calculate_cycle_edges(cycles: &[BlockRef]) -> CycleEdges { + let mut result = CycleEdges::default(); + let mut entry_blocks = SmallSet::::default(); + + // First identify all exit and entry edges by checking whether any successors or predecessors + // are from outside the cycles. + for block_ref in cycles.iter().copied() { + let block = block_ref.borrow(); + for pred in block.predecessors() { + let from_block = pred.predecessor(); + if cycles.contains(&from_block) { + continue; + } + + result.entry_edges.push(Edge { + from_block, + successor_index: pred.index as usize, + }); + entry_blocks.insert(block_ref); + } + + let terminator = block.terminator().unwrap(); + let terminator = terminator.borrow(); + for succ in terminator.successor_iter() { + let succ_operand = succ.dest.borrow(); + if cycles.contains(&succ_operand.successor()) { + continue; + } + + result.exit_edges.push(Edge { + from_block: block_ref, + successor_index: succ_operand.index as usize, + }); + } + } + + // With the entry blocks identified, find all the back edges. + for block_ref in cycles.iter().copied() { + let block = block_ref.borrow(); + let terminator = block.terminator().unwrap(); + let terminator = terminator.borrow(); + for succ in terminator.successor_iter() { + let succ = succ.dest.borrow(); + if !entry_blocks.contains(&succ.successor()) { + continue; + } + + result.back_edges.push(Edge { + from_block: block_ref, + successor_index: succ.index as usize, + }); + } + } + + result +} + +/// Typed used to orchestrate creation of so-called edge multiplexers. +/// +/// This class creates a new basic block and routes all inputs edges to this basic block before +/// branching to their original target. The purpose of this transformation is to create single-entry, +/// single-exit regions. +pub struct EdgeMultiplexer<'multiplexer, 'context: 'multiplexer> { + transform_ctx: &'multiplexer mut TransformationContext<'context>, + /// Newly created multiplexer block. + multiplexer_block: BlockRef, + /// Mapping of the block arguments of an entry block to the corresponding block arguments in the + /// multiplexer block. Block arguments of an entry block are simply appended ot the multiplexer + /// block. This map simply contains the offset to the range in the multiplexer block. + block_arg_mapping: SmallDenseMap, + /// Discriminator value used in the multiplexer block to dispatch to the correct entry block. + /// `None` if not required due to only having one entry block. + discriminator: Option, +} + +impl<'multiplexer, 'context: 'multiplexer> EdgeMultiplexer<'multiplexer, 'context> { + /// Creates a new edge multiplexer capable of redirecting all edges to one of the `entry_blocks`. + /// + /// This creates the multiplexer basic block with appropriate block arguments after the first + /// entry block. `extra_args` contains the types of possible extra block arguments passed to the + /// multiplexer block that are added to the successor operands of every outgoing edge. + /// + /// NOTE: This does not yet redirect edges to branch to the multiplexer block nor code + /// dispatching from the multiplexer code to the original successors. See [Self::redirect_edge] + /// and [Self::create_switch]. + pub fn create( + transform_ctx: &'multiplexer mut TransformationContext<'context>, + span: SourceSpan, + entry_blocks: &[BlockRef], + extra_args: &[Type], + ) -> Self { + assert!(!entry_blocks.is_empty(), "require at least one entry block"); + + let mut multiplexer_block = transform_ctx.create_block(); + log::trace!( + target: "cfg-to-scf", + "creating edge multiplexer {multiplexer_block} for {entry_blocks:?} with extra arguments {extra_args:?}" + ); + { + let mut mb = multiplexer_block.borrow_mut(); + mb.insert_after(entry_blocks[0]); + } + + // To implement the multiplexer block, we have to add the block arguments of every distinct + // successor block to the multiplexer block. When redirecting edges, block arguments + // designated for blocks that aren't branched to will be assigned the `get_undef_value`. The + // amount of block arguments and their offset is saved in the map for `redirect_edge` to + // transform the edges. + let mut block_arg_mapping = SmallDenseMap::default(); + for entry_block in entry_blocks.iter().copied() { + let argc = multiplexer_block.borrow().num_arguments(); + if block_arg_mapping.insert_new(entry_block, argc) { + log::trace!( + target: "cfg-to-scf", + "adding {} multiplexer arguments at offset {argc} for {entry_block}", + entry_block.borrow().num_arguments() + ); + transform_ctx.add_block_arguments_from_other(multiplexer_block, entry_block); + } else { + log::trace!(target: "cfg-to-scf", "{entry_block} is already present in the multiplexer, reusing"); + } + } + + // If we have more than one successor, we have to additionally add a discriminator value, + // denoting which successor to jump to. When redirecting edges, an appropriate value will be + // passed using `get_switch_value`. + let discriminator = if block_arg_mapping.len() > 1 { + let val = transform_ctx.get_switch_value(0); + let discriminator_arg = transform_ctx.append_block_argument( + multiplexer_block, + val.borrow().ty().clone(), + span, + ); + log::trace!(target: "cfg-to-scf", "discriminator required by multiplexer, {discriminator_arg} was added"); + Some(discriminator_arg) + } else { + None + }; + + if !extra_args.is_empty() { + for ty in extra_args { + transform_ctx.append_block_argument(multiplexer_block, ty.clone(), span); + } + } + + Self { + transform_ctx, + multiplexer_block, + block_arg_mapping, + discriminator, + } + } + + /// Returns the created multiplexer block. + pub fn get_multiplexer_block(&self) -> BlockRef { + self.multiplexer_block + } + + #[inline(always)] + pub fn transform(&mut self) -> &mut TransformationContext<'context> { + self.transform_ctx + } + + /// Redirects `edge` to branch to the multiplexer block before continuing to its original + /// target. The edges successor must have originally been part of the entry blocks array passed + /// to the `create` function. `extraArgs` must be used to pass along any additional values + /// corresponding to `extraArgs` in `create`. + pub fn redirect_edge(&mut self, edge: &Edge, extra_args: &[ValueRef]) { + let edge_argv_offset = self + .block_arg_mapping + .get(&edge.get_successor()) + .copied() + .expect("edge was not originally passed to `create`"); + + let succ_block = edge.get_successor(); + log::trace!( + target: "cfg-to-scf", + "redirecting edge {} -> {succ_block} with {} arguments starting at offset {edge_argv_offset}", + edge.from_block, + edge.from_block.borrow().num_arguments() + ); + + let mut terminator_ref = edge.get_predecessor(); + let mut terminator = terminator_ref.borrow_mut(); + let context = terminator.context_rc(); + let mut succ = terminator.successor_mut(edge.successor_index); + + // Extra arguments are always appended at the end of the block arguments. + let multiplexer_block = self.multiplexer_block.borrow(); + let multiplexer_argc = multiplexer_block.num_arguments(); + let extra_args_begin_index = multiplexer_argc - extra_args.len(); + // If a discriminator exists, it is right before the extra arguments. + let discriminator_index = self.discriminator.map(|_| extra_args_begin_index - 1); + + log::trace!(target: "cfg-to-scf", "multiplexer block {multiplexer_block} has {multiplexer_argc} arguments"); + log::trace!(target: "cfg-to-scf", "extra arguments for edge will begin at {extra_args_begin_index}"); + log::trace!(target: "cfg-to-scf", "discriminator index, if present, will be {discriminator_index:?}"); + + // NOTE: Here, we're redirecting the edge from the entry block, to the multiplexer block. + // This requires us to ensure the successor operand vector is large enough for all of the + // required multiplexer block arguments, and then to redirect the original entry block + // arguments to their corresponding index in the multiplexer block parameter list. The + // remaining arguments will either be undef, the discriminator value, or extra arguments. + let mut new_succ_operands = SmallVec::<[_; 4]>::with_capacity(multiplexer_argc); + log::trace!(target: "cfg-to-scf", "visiting multiplexer block arguments for edge"); + for arg in multiplexer_block.arguments().iter() { + let arg = arg.borrow(); + let index = arg.index(); + assert_eq!(new_succ_operands.len(), index); + log::trace!(target: "cfg-to-scf", "visiting multiplexer block argument {arg} at index {index}"); + if index >= edge_argv_offset && index < edge_argv_offset + succ.arguments.len() { + log::trace!(target: "cfg-to-scf", "arg corresponds to original block argument at index {}", index - edge_argv_offset); + log::trace!(target: "cfg-to-scf", "new successor operand is {}", succ.arguments[index - edge_argv_offset].borrow().as_value_ref()); + // Original block arguments to the entry block. + new_succ_operands + .push(succ.arguments[index - edge_argv_offset].borrow().as_value_ref()); + continue; + } + + // Discriminator value if it exists. + if discriminator_index.is_some_and(|di| di == index) { + log::trace!(target: "cfg-to-scf", "arg corresponds to discriminator index"); + let succ_index = + self.block_arg_mapping.iter().position(|(k, _)| k == &succ_block).unwrap() + as u32; + let value = self.transform_ctx.get_switch_value(succ_index); + log::trace!(target: "cfg-to-scf", "new successor operand is {value}"); + new_succ_operands.push(value); + continue; + } + + // Followed by the extra arguments. + if index >= extra_args_begin_index { + log::trace!(target: "cfg-to-scf", "arg corresponds to extra argument at index {}", index - extra_args_begin_index); + log::trace!(target: "cfg-to-scf", "new successor operand is {}", extra_args[index - extra_args_begin_index]); + new_succ_operands.push(extra_args[index - extra_args_begin_index]); + continue; + } + + log::trace!(target: "cfg-to-scf", "arg is undef on this edge"); + // Otherwise undef values for any unused block arguments used by other entry blocks. + let undef_value = self.transform_ctx.get_undef_value(arg.ty()); + log::trace!(target: "cfg-to-scf", "new successor operand is {undef_value}"); + new_succ_operands.push(undef_value); + } + + drop(multiplexer_block); + + succ.set(self.multiplexer_block); + succ.arguments.set_operands(new_succ_operands, terminator_ref, &context); + + drop(terminator); + } + + /// Creates a switch op using `builder` which dispatches to the original successors of the edges + /// passed to `create` minus the ones in `excluded`. The builder's insertion point has to be in a + /// block dominated by the multiplexer block. All edges to the multiplexer block must have already + /// been redirected using `redirectEdge`. + pub fn create_switch( + &mut self, + span: SourceSpan, + builder: &mut OpBuilder, + excluded: &[BlockRef], + ) -> Result<(), Report> { + let multiplexer_block_args = { + let multiplexer_block = self.multiplexer_block.borrow(); + SmallVec::<[ValueRef; 4]>::from_iter( + multiplexer_block.arguments().iter().copied().map(|arg| arg as ValueRef), + ) + }; + + // We create the switch by creating a case for all entries and then splitting of the last + // entry as a default case. + let mut case_arguments = SmallVec::<[_; 4]>::default(); + let mut case_values = SmallVec::<[u32; 4]>::default(); + let mut case_destinations = SmallVec::<[BlockRef; 4]>::default(); + + log::trace!( + target: "cfg-to-scf", + "creating switch, exclusions = {excluded:?}, multiplexer argc = {}", + multiplexer_block_args.len() + ); + + for (index, (succ, offset)) in self.block_arg_mapping.iter().enumerate() { + if excluded.contains(succ) { + continue; + } + + case_values.push(index as u32); + case_destinations.push(*succ); + let succ = succ.borrow(); + let offset = *offset; + log::trace!( + target: "cfg-to-scf", + "adding target {succ} (at index {index}) with {} arguments from offset {offset}", + succ.num_arguments() + ); + + case_arguments.push( + multiplexer_block_args[offset..(offset + succ.num_arguments())].as_value_range(), + ); + } + + // If we don't have a discriminator due to only having one entry we have to create a dummy + // flag for the switch. + let real_discriminator = if self.discriminator.is_none_or(|_| case_arguments.len() == 1) { + self.transform_ctx.get_switch_value(0) + } else { + self.discriminator.unwrap() + }; + + case_values.pop(); + let default_dest = case_destinations.pop().unwrap(); + let default_args = case_arguments.pop().unwrap(); + + assert!( + builder.insertion_block().is_some_and(|b| b.borrow().has_predecessors()), + "edges need to be redirected prior to creating switch" + ); + + self.transform_ctx.interface_mut().create_cfg_switch_op( + span, + builder, + real_discriminator, + &case_values, + &case_destinations, + &case_arguments, + default_dest, + default_args, + ) + } +} diff --git a/hir-transform/src/cfg_to_scf/transform.rs b/hir-transform/src/cfg_to_scf/transform.rs new file mode 100644 index 000000000..b639852b1 --- /dev/null +++ b/hir-transform/src/cfg_to_scf/transform.rs @@ -0,0 +1,1355 @@ +use alloc::rc::Rc; + +use midenc_hir::{ + adt::{SmallDenseMap, SmallSet}, + cfg::Graph, + dominance::{DominanceInfo, PreOrderDomTreeIter}, + formatter::DisplayValues, + smallvec, AsValueRange, Block, BlockRef, Builder, Context, EntityWithId, FxHashMap, OpBuilder, + OperationRef, ProgramPoint, Region, RegionRef, Report, SmallVec, SourceSpan, Spanned, Type, + Usable, Value, ValueRange, ValueRef, +}; + +use super::{ + edges::{Edge, EdgeMultiplexer, SuccessorEdges}, + *, +}; + +/// This type represents the necessary context required for performing the control flow lifting +/// transformation over some region. +/// +/// It is not meant to be used directly, but as an internal implementation detail of +/// [transform_cfg_to_scf]. +pub struct TransformationContext<'a> { + span: SourceSpan, + region: RegionRef, + entry: BlockRef, + context: Rc, + interface: &'a mut dyn CFGToSCFInterface, + dominance_info: &'a mut DominanceInfo, + typed_undef_cache: FxHashMap, + // The transformation only creates all values in the range of 0 to max(num_successors). + // Therefore using a vector instead of a map. + switch_value_cache: SmallVec<[Option; 2]>, + return_like_to_combined_exit: FxHashMap, +} + +impl<'a> TransformationContext<'a> { + pub fn new( + region: RegionRef, + interface: &'a mut dyn CFGToSCFInterface, + dominance_info: &'a mut DominanceInfo, + ) -> Result { + let parent = region.parent().unwrap(); + let entry = region.borrow().entry_block_ref().unwrap(); + let op = parent.borrow(); + + let mut this = Self { + span: op.span(), + region, + entry, + context: op.context_rc(), + interface, + dominance_info, + typed_undef_cache: Default::default(), + switch_value_cache: Default::default(), + return_like_to_combined_exit: Default::default(), + }; + + this.create_single_exit_blocks_for_return_like()?; + + Ok(this) + } + + #[inline] + pub const fn entry(&self) -> BlockRef { + self.entry + } + + #[inline] + pub fn create_block(&self) -> BlockRef { + self.context.create_block() + } + + #[inline] + pub fn append_block_argument(&self, block: BlockRef, ty: Type, span: SourceSpan) -> ValueRef { + self.context.append_block_argument(block, ty, span) + } + + /// Appends all the block arguments from `other` to the block arguments of `block`, copying their + /// types and locations. + pub fn add_block_arguments_from_other(&self, block: BlockRef, other: BlockRef) { + let other = other.borrow(); + for arg in other.arguments() { + let arg = arg.borrow(); + self.context.append_block_argument(block, arg.ty().clone(), arg.span()); + } + } + + pub fn invalidate_dominance_info_for_region(&mut self, region: RegionRef) { + self.dominance_info.invalidate_region(region); + } + + #[inline(always)] + pub fn interface_mut(&mut self) -> &mut dyn CFGToSCFInterface { + self.interface + } + + pub fn get_undef_value(&mut self, ty: &Type) -> ValueRef { + use midenc_hir::hashbrown::hash_map::Entry; + + match self.typed_undef_cache.entry(ty.clone()) { + Entry::Vacant(entry) => { + let mut constant_builder = OpBuilder::new(self.context.clone()); + constant_builder.set_insertion_point_to_start(self.entry); + let value = + self.interface.get_undef_value(self.span, &mut constant_builder, ty.clone()); + entry.insert(value); + value + } + Entry::Occupied(entry) => *entry.get(), + } + } + + pub fn get_switch_value(&mut self, discriminant: u32) -> ValueRef { + let index = discriminant as usize; + + if let Some(val) = self.switch_value_cache.get(index).copied().flatten() { + return val; + } + + // Make sure the cache is large enough + let new_cache_size = core::cmp::max(self.switch_value_cache.len(), index + 1); + self.switch_value_cache.resize(new_cache_size, None); + + let mut constant_builder = OpBuilder::new(self.context.clone()); + constant_builder.set_insertion_point_to_start(self.entry); + let result = + self.interface + .get_cfg_switch_value(self.span, &mut constant_builder, discriminant); + self.switch_value_cache[index] = Some(result); + result + } + + pub fn garbage_collect(&mut self) { + // If any of the temporary switch values we created are unused, remove them now + for value in self.switch_value_cache.drain(..).flatten() { + let mut defining_op = { + let val = value.borrow(); + if val.is_used() { + continue; + } + val.get_defining_op().unwrap() + }; + + defining_op.borrow_mut().erase(); + } + + for (_, value) in self.typed_undef_cache.drain() { + let mut defining_op = { + let val = value.borrow(); + if val.is_used() { + continue; + } + val.get_defining_op().unwrap() + }; + + defining_op.borrow_mut().erase(); + } + } + + /// Transforms the region to only have a single block for every kind of return-like operation that + /// all previous occurrences of the return-like op branch to. + /// + /// If the region only contains a single kind of return-like operation, it creates a single-entry + /// and single-exit region. + fn create_single_exit_blocks_for_return_like(&mut self) -> Result<(), Report> { + // Do not borrow the region while visiting its blocks, as some parts of the transformation + // may need to mutably borrow the region to add new blocks. Here, we only borrow it long + // enough to get the next block in the list + let mut next = { + let region = self.region.borrow(); + region.body().front().as_pointer() + }; + + while let Some(block_ref) = next.take() { + let block = block_ref.borrow(); + if block.num_successors() == 0 { + let terminator = block.terminator().unwrap(); + drop(block); + self.combine_exit(terminator)?; + } + + next = block_ref.next(); + } + + // Invalidate any dominance tree on the region as the exit combiner has added new blocks and + // edges. + self.dominance_info.invalidate_region(self.region); + + Ok(()) + } + + /// Transforms `returnLikeOp` to a branch to the only block in the region with an instance of + /// `return_like_op`s kind. + fn combine_exit(&mut self, mut return_like_op_ref: OperationRef) -> Result<(), Report> { + use midenc_hir::hashbrown::hash_map::Entry; + + log::trace!(target: "cfg-to-scf", "combining exit for {}", return_like_op_ref.borrow()); + let key = ReturnLikeOpKey(return_like_op_ref); + match self.return_like_to_combined_exit.entry(key) { + Entry::Occupied(entry) => { + if OperationRef::ptr_eq(&entry.key().0, &return_like_op_ref) { + log::trace!(target: "cfg-to-scf", "exit already combined for {}", return_like_op_ref.borrow()); + return Ok(()); + } + + let exit_block = *entry.get(); + log::trace!(target: "cfg-to-scf", "found equivalent return-like exit in {exit_block}"); + let mut builder = OpBuilder::new(self.context.clone()); + builder.set_insertion_point_to_end(return_like_op_ref.parent().unwrap()); + let dummy_value = self.get_switch_value(0); + let return_like_op = return_like_op_ref.borrow(); + let operands = return_like_op.operands().as_value_range().into_owned(); + let span = return_like_op.span(); + log::trace!(target: "cfg-to-scf", "creating branch to return-like exit in {exit_block} from {} with operands {operands}", return_like_op.parent().unwrap()); + let parent_region = return_like_op.parent_region().unwrap(); + drop(return_like_op); + self.interface.create_single_destination_branch( + span, + &mut builder, + dummy_value, + exit_block, + operands, + )?; + + return_like_op_ref.borrow_mut().erase(); + + log::trace!(target: "cfg-to-scf", "return-like rewritten: {}", parent_region.borrow().print(&Default::default())); + } + Entry::Vacant(entry) => { + let mut return_like_op = return_like_op_ref.borrow_mut(); + let operands = return_like_op.operands().as_value_range(); + let args = SmallVec::<[Type; 2]>::from_iter( + operands.iter().map(|o| o.borrow().ty().clone()), + ); + + let mut builder = OpBuilder::new(self.context.clone()); + let exit_block = builder.create_block(self.region, None, &args); + log::trace!(target: "cfg-to-scf", "no equivalent return-like exit exists yet, created {exit_block} for this purpose"); + entry.insert(exit_block); + + log::trace!(target: "cfg-to-scf", "creating branch to return-like exit in {exit_block} from {} with operands {operands}", return_like_op_ref.parent().unwrap()); + builder.set_insertion_point_to_end(return_like_op_ref.parent().unwrap()); + let dummy_value = self.get_switch_value(0); + let span = return_like_op.span(); + self.interface.create_single_destination_branch( + span, + &mut builder, + dummy_value, + exit_block, + operands, + )?; + + log::trace!(target: "cfg-to-scf", "moving original return-like op to {exit_block}"); + return_like_op.move_to(ProgramPoint::at_end_of(exit_block)); + let exit_block = exit_block.borrow(); + let exit_args = exit_block.arguments().as_value_range(); + log::trace!(target: "cfg-to-scf", "rewriting original return-like op operands to {exit_args}"); + return_like_op.set_operands(exit_args); + } + } + + Ok(()) + } + + /// Transforms all outer-most cycles in the region with the region entry block `region_entry` into + /// structured loops. + /// + /// Returns the entry blocks of any newly created regions potentially requiring further + /// transformations. + pub fn transform_cycles_to_scf_loops( + &mut self, + region_entry: BlockRef, + ) -> Result, Report> { + use midenc_hir::cfg::StronglyConnectedComponents; + + log::trace!( + target: "cfg-to-scf", + "transforming cycles to structured loops from region entry {region_entry}" + ); + + let mut new_sub_regions = SmallVec::<[BlockRef; 4]>::default(); + + let scc_iter = StronglyConnectedComponents::new(®ion_entry); + + for scc in scc_iter { + if !scc.has_cycle() { + continue; + } + + // Save the set and increment the SCC iterator early to avoid our modifications breaking + // the SCC iterator. + let edges = edges::calculate_cycle_edges(scc.as_slice()); + let mut cycle_block_set = SmallSet::::from_iter(scc); + let mut loop_header = edges.entry_edges[0].get_successor(); + + // First turn the cycle into a loop by creating a single entry block if needed. + if edges.entry_edges.len() > 1 { + let mut edges_to_entry_blocks = SmallVec::<[Edge; 4]>::default(); + edges_to_entry_blocks.extend_from_slice(&edges.entry_edges); + edges_to_entry_blocks.extend_from_slice(&edges.back_edges); + + let loop_header_term = loop_header.borrow().terminator().unwrap(); + let span = loop_header_term.borrow().span(); + let multiplexer = self.create_single_entry_block(span, &edges_to_entry_blocks)?; + loop_header = multiplexer.get_multiplexer_block(); + } + cycle_block_set.insert(loop_header); + + // Then turn it into a structured loop by creating a single latch. + let from_block = edges.back_edges[0].get_from_block(); + let from_block_term = from_block.borrow().terminator().unwrap(); + let span = from_block_term.borrow().span(); + let loop_properties = + self.create_single_exiting_latch(span, &edges.back_edges, &edges.exit_edges)?; + + let latch_block_ref = loop_properties.latch; + let mut exit_block_ref = loop_properties.exit_block; + cycle_block_set.insert(latch_block_ref); + + // Finally, turn it into reduce form. + let iteration_values = self.transform_to_reduce_loop( + loop_header, + exit_block_ref, + cycle_block_set.as_slice(), + ); + + // Create a block acting as replacement for the loop header and insert the structured + // loop into it. + let mut new_loop_parent_block_ref = self.context.create_block(); + new_loop_parent_block_ref.borrow_mut().insert_before(loop_header); + self.add_block_arguments_from_other(new_loop_parent_block_ref, loop_header); + + let mut region_ref = region_entry.parent().unwrap(); + + let mut loop_body_ref = self.context.alloc_tracked(Region::default()); + { + let mut region = region_ref.borrow_mut(); + let blocks = region.body_mut(); + let mut loop_body = loop_body_ref.borrow_mut(); + + // Make sure the loop header is the entry block. + loop_body.push_back(unsafe { + let mut cursor = blocks.cursor_mut_from_ptr(loop_header); + cursor.remove().unwrap() + }); + + for block in cycle_block_set { + if !BlockRef::ptr_eq(&block, &latch_block_ref) + && !BlockRef::ptr_eq(&block, &loop_header) + { + loop_body.push_back(unsafe { + let mut cursor = blocks.cursor_mut_from_ptr(block); + cursor.remove().unwrap() + }); + } + } + + // And the latch is the last block. + loop_body.push_back(unsafe { + let mut cursor = blocks.cursor_mut_from_ptr(latch_block_ref); + cursor.remove().unwrap() + }); + } + + let mut old_terminator = latch_block_ref.borrow().terminator().unwrap(); + old_terminator.borrow_mut().remove(); + + let mut builder = OpBuilder::new(self.context.clone()); + builder.set_insertion_point_to_end(new_loop_parent_block_ref); + + let loop_values_init = { + let new_loop_parent_block = new_loop_parent_block_ref.borrow(); + new_loop_parent_block.arguments().as_value_range().into_owned() + }; + let structured_loop_op = self.interface.create_structured_do_while_loop_op( + &mut builder, + old_terminator, + loop_values_init, + loop_properties.condition, + iteration_values, + loop_body_ref, + )?; + + // The old terminator has been replaced, erase it now + old_terminator.borrow_mut().erase(); + + new_sub_regions.push(loop_header); + + let structured_loop = structured_loop_op.borrow(); + let loop_results = structured_loop.results().all(); + let mut exit_block = exit_block_ref.borrow_mut(); + for (mut old_value, new_value) in + exit_block.arguments().iter().copied().zip(loop_results) + { + let new_value = new_value.borrow().as_value_ref(); + old_value.borrow_mut().replace_all_uses_with(new_value); + } + + loop_header.borrow_mut().replace_all_uses_with(new_loop_parent_block_ref); + + // Merge the exit block right after the loop operation. + new_loop_parent_block_ref.borrow_mut().splice_block(&mut exit_block); + + assert!(exit_block.is_empty()); + exit_block.erase(); + } + + Ok(new_sub_regions) + } + + /// Transforms the first occurrence of conditional control flow in `region_entry` into + /// conditionally executed regions. Returns the entry block of the created regions and the + /// region after the conditional control flow. + pub fn transform_to_structured_cf_branches( + &mut self, + mut region_entry: BlockRef, + ) -> Result, Report> { + log::trace!( + target: "cfg-to-scf", + "transforming conditional control flow for region reachable from {region_entry}" + ); + + let num_successors = region_entry.borrow().num_successors(); + + log::trace!(target: "cfg-to-scf", "{region_entry} has {num_successors} successors"); + + // Trivial region. + if num_successors == 0 { + return Ok(Default::default()); + } + + // Single successor we can just splice on to the entry block. + if num_successors == 1 { + let region_entry_block = region_entry.borrow(); + let mut successor = region_entry_block.get_successor(0); + let succ = successor.borrow(); + // Replace all uses of the successor block arguments (if any) with the operands of the + // block terminator + let mut entry_terminator = region_entry_block.terminator().unwrap(); + let mut terminator = entry_terminator.borrow_mut(); + let terminator_succ = terminator.successor(0); + for (mut old_value, new_value) in + succ.arguments().iter().copied().zip(terminator_succ.arguments) + { + let mut old_value = old_value.borrow_mut(); + old_value.replace_all_uses_with(new_value.borrow().as_value_ref()); + } + + // Erase the original region entry block terminator, as it will be replaced with the + // contents of the successor block once spliced + // + // NOTE: In order to erase the terminator, we must not be borrowing its parent block + drop(region_entry_block); + drop(succ); + terminator.drop_all_references(); + terminator.erase(); + + let mut succ = successor.borrow_mut(); + + // Splice the operations of `succ` to `region_entry` + region_entry.borrow_mut().splice_block(&mut succ); + + // Erase the successor block now that we have emptied it + assert!(succ.is_empty()); + succ.erase(); + + return Ok(smallvec![region_entry]); + } + + // Split the CFG into "#num_successors + 1" regions. + // + // For every edge to a successor, the blocks it solely dominates are determined and become + // the region following that edge. The last region is the continuation that follows the + // branch regions. + let mut not_continuation = SmallSet::::default(); + not_continuation.insert(region_entry); + + let mut successor_branch_regions = SmallVec::<[SmallVec<[BlockRef; 2]>; 2]>::default(); + successor_branch_regions.resize_with(num_successors, Default::default); + + let terminator = region_entry.borrow().terminator().unwrap(); + { + let terminator = terminator.borrow(); + for (block_list, succ) in + successor_branch_regions.iter_mut().zip(terminator.successor_iter()) + { + let dest = succ.successor(); + + // If the region entry is not the only predecessor, then the edge does not dominate the + // block it leads to. + if dest.borrow().get_single_predecessor().is_none() { + continue; + } + + // Otherwise get all blocks it dominates in DFS/pre-order. + let node = self.dominance_info.node(dest).unwrap(); + for curr in PreOrderDomTreeIter::new(node) { + if let Some(block) = curr.block() { + block_list.push(block); + not_continuation.insert(block); + } + } + + log::trace!(target: "cfg-to-scf", "computed region for successor {dest} as [{}]", DisplayValues::new(block_list.iter())); + } + } + + log::trace!(target: "cfg-to-scf", "non-continuation blocks: [{}]", DisplayValues::new(not_continuation.iter())); + + // Finds all relevant edges and checks the shape of the control flow graph at this point. + // + // Branch regions may either: + // + // * Be post-dominated by the continuation + // * Be post-dominated by a return-like op + // * Dominate a return-like op and have an edge to the continuation. + // + // The control flow graph may then be one of three cases: + // + // 1) All branch regions are post-dominated by the continuation. This is the usual case. If + // there are multiple entry blocks into the continuation a single entry block has to be + // created. A structured control flow op can then be created from the branch regions. + // + // 2) No branch region has an edge to a continuation: + // + // +-----+ + // +-----+ bb0 +----+ + // v +-----+ v + // Region 1 +-+--+ ... +-+--+ Region n + // |ret1| |ret2| + // +----+ +----+ + // + // This can only occur if every region ends with a different kind of return-like op. In + // that case the control flow operation must stay as we are unable to create a single + // exit-block. We can nevertheless process all its successors as they single-entry, + // single-exit regions. + // + // 3) Only some branch regions are post-dominated by the continuation. The other branch + // regions may either be post-dominated by a return-like op or lead to either the + // continuation or return-like op. In this case we also create a single entry block like + // in Case 1 that also includes all edges to the return-like op: + // + // +-----+ + // +-----+ bb0 +----+ + // v +-----+ v + // Region 1 +-+-+ ... +-+-+ Region n + // +---+ +---+ + // +---+ |... ... + // |ret|<-+ | | + // +---+ | +---+ | + // +---->++ ++<---+ + // | | + // ++ ++ Region T + // +---+ + // This transforms to: + // +-----+ + // +-----+ bb0 +----+ + // v +-----+ v + // Region 1 +-+-+ ... +-+-+ Region n + // +---+ +---+ + // ... +-----+ ... + // +---->+ bbM +<---+ + // +-----+ + // +-----+ | + // | v + // +---+ | +---+ + // |ret+<---+ ++ ++ + // +---+ | | + // ++ ++ Region T + // +---+ + // + // bb0 to bbM is now a single-entry, single-exit region that applies to Case 1. The control + // flow op at the end of bbM will trigger Case 2. + let mut continuation_edges = SmallVec::<[Edge; 2]>::default(); + let mut continuation_post_dominates_all_regions = true; + let mut no_successor_has_continuation_edge = true; + + for (entry_edge, branch_region) in + SuccessorEdges::new(region_entry).zip(successor_branch_regions.iter_mut()) + { + log::trace!( + target: "cfg-to-scf", + "analyzing branch region for edge {entry_edge}: [{}]", + DisplayValues::new(branch_region.iter()) + ); + + // If the branch region is empty then the branch target itself is part of the + // continuation. + if branch_region.is_empty() { + continuation_edges.push(entry_edge); + log::trace!(target: "cfg-to-scf", " branch region is empty"); + no_successor_has_continuation_edge = false; + continue; + } + + for block_ref in branch_region.iter() { + let block = block_ref.borrow(); + if is_region_exit_block(&block) { + log::trace!(target: "cfg-to-scf", " {block} is a region exit"); + // If a return-like op is part of the branch region then the continuation no + // longer post-dominates the branch region. Add all its incoming edges to edge + // list to create the single-exit block for all branch regions. + continuation_post_dominates_all_regions = false; + for pred in block.predecessors() { + continuation_edges.push(Edge { + from_block: pred.predecessor(), + successor_index: pred.index as usize, + }); + } + continue; + } + + for edge in SuccessorEdges::new(*block_ref) { + log::trace!(target: "cfg-to-scf", "analyzing successor edge {edge}"); + if not_continuation.contains(&edge.get_successor()) { + continue; + } + + continuation_edges.push(edge); + no_successor_has_continuation_edge = false; + } + } + } + + log::trace!( + target: "cfg-to-scf", + " found continuation edges: [{}]", DisplayValues::new(continuation_edges.iter()) + ); + + // Case 2: Keep the control flow op but process its successors further. + if no_successor_has_continuation_edge { + log::trace!(target: "cfg-to-scf", " no successor has a continuation edge"); + let term = region_entry.borrow().terminator().unwrap(); + let term = term.borrow(); + return Ok(term.successor_iter().map(|s| s.dest.borrow().successor()).collect()); + } + + // Collapse to a single continuation block, or None + let mut continuation = None; + { + for edge in continuation_edges.iter() { + match continuation.as_ref() { + None => { + continuation = Some(edge.get_successor()); + } + Some(prev) => { + if !BlockRef::ptr_eq(prev, &edge.get_successor()) { + continuation = None; + break; + } + } + } + } + } + + log::trace!(target: "cfg-to-scf", " continuation = {:?}", continuation.map(|c| c.borrow().id())); + log::trace!(target: "cfg-to-scf", " continuation_post_dominates_all_regions = {continuation_post_dominates_all_regions}"); + + // In Case 3, or if not all continuation edges have the same entry block, create a single + // entry block as continuation for all branch regions. + if continuation.is_none() || !continuation_post_dominates_all_regions { + let term = continuation_edges[0].get_from_block().borrow().terminator().unwrap(); + let span = term.borrow().span(); + let multiplexer = self.create_single_entry_block(span, &continuation_edges)?; + continuation = Some(multiplexer.get_multiplexer_block()); + log::trace!(target: "cfg-to-scf", " created new single entry continuation = {}", multiplexer.get_multiplexer_block()); + } + + // Trigger reprocessing of Case 3 after creating the single entry block. + if !continuation_post_dominates_all_regions { + // Unlike in the general case, we are explicitly revisiting the same region entry again + // after having changed its control flow edges and dominance. We have to therefore + // explicitly invalidate the dominance tree. + let region = region_entry.parent().unwrap(); + self.dominance_info.invalidate_region(region); + return Ok(smallvec![region_entry]); + } + + let mut continuation = continuation.unwrap(); + let mut new_sub_regions = SmallVec::<[BlockRef; 4]>::default(); + + // Empty blocks with the values they return to the parent op. + let mut created_empty_blocks = + SmallVec::<[(BlockRef, ValueRange<'static, 2>); 2]>::default(); + + // Create the branch regions. + let mut conditional_regions = SmallVec::<[RegionRef; 2]>::default(); + for (branch_region, entry_edge) in + successor_branch_regions.iter_mut().zip(SuccessorEdges::new(region_entry)) + { + let mut conditional_region = self.context.alloc_tracked(Region::default()); + conditional_regions.push(conditional_region); + + if branch_region.is_empty() { + // If no block is part of the branch region, we create a dummy block to place the + // region terminator into. + let mut empty_block = self.context.create_block(); + let pred = entry_edge.from_block.borrow().terminator().unwrap(); + let pred = pred.borrow(); + let succ = pred.successor(entry_edge.successor_index); + let succ_operands = + succ.arguments.iter().map(|o| o.borrow().as_value_ref()).collect(); + created_empty_blocks.push((empty_block, succ_operands)); + empty_block.borrow_mut().insert_at_end(conditional_region); + continue; + } + + self.create_single_exit_branch_region( + branch_region, + continuation, + &mut created_empty_blocks, + conditional_region, + ); + + // The entries of the branch regions may only have redundant block arguments since the + // edge to the branch region is always dominating. + let mut cond_region = conditional_region.borrow_mut(); + let mut sub_region_entry_block = cond_region.entry_mut(); + let pred = entry_edge.from_block.borrow().terminator().unwrap(); + let pred = pred.borrow(); + let succ = pred.successor(entry_edge.successor_index); + for (mut old_value, new_value) in sub_region_entry_block + .arguments() + .iter() + .copied() + .zip(succ.arguments.as_slice()) + { + old_value.borrow_mut().replace_all_uses_with(new_value.borrow().as_value_ref()); + } + + sub_region_entry_block.erase_arguments(|_| true); + + new_sub_regions.push(sub_region_entry_block.as_block_ref()); + } + + let structured_cond_op = { + let mut builder = OpBuilder::new(self.context.clone()); + builder.set_insertion_point_to_end(region_entry); + + let arg_types = { + let cont = continuation.borrow(); + cont.arguments() + .iter() + .map(|arg| arg.borrow().ty().clone()) + .collect::>() + }; + let mut terminator = region_entry.borrow().terminator().unwrap(); + let op = self.interface.create_structured_branch_region_op( + &mut builder, + terminator, + &arg_types, + &mut conditional_regions, + )?; + let mut term = terminator.borrow_mut(); + term.drop_all_references(); + term.erase(); + op + }; + + for (block, value_range) in created_empty_blocks { + let mut builder = OpBuilder::new(self.context.clone()); + builder.set_insertion_point_to_end(block); + + let span = structured_cond_op.span(); + self.interface.create_structured_branch_region_terminator_op( + span, + &mut builder, + structured_cond_op, + None, + value_range, + )?; + } + + // Any leftover users of the continuation must be from unconditional branches in a branch + // region. There can only be at most one per branch region as all branch regions have been + // made single-entry single-exit above. Replace them with the region terminator. + let mut next_use = continuation.borrow().uses().front().as_pointer(); + while let Some(user) = next_use.take() { + next_use = user.next(); + + let mut owner = user.borrow().owner; + assert_eq!(owner.borrow().num_successors(), 1); + + let mut builder = OpBuilder::new(self.context.clone()); + builder.set_insertion_point_after(owner); + + let args = { + let pred = owner.borrow(); + pred.successor(0).arguments.as_value_range().into_owned() + }; + self.interface.create_structured_branch_region_terminator_op( + owner.span(), + &mut builder, + structured_cond_op, + Some(owner), + args, + )?; + + owner.borrow_mut().erase(); + } + assert!(continuation.borrow().uses().is_empty()); + + let mut cont = continuation.borrow_mut(); + let structured_cond = structured_cond_op.borrow(); + for (mut old_value, new_value) in cont + .arguments() + .iter() + .copied() + .zip(structured_cond.results().iter().map(|r| r.borrow().as_value_ref())) + { + old_value.borrow_mut().replace_all_uses_with(new_value); + } + + // Splice together the continuations operations with the region entry. + region_entry.borrow_mut().splice_block(&mut cont); + + // Remove the empty continuation block + assert!(cont.is_empty()); + cont.erase(); + + // After splicing the continuation, the region has to be reprocessed as it has new + // successors. + new_sub_regions.push(region_entry); + + Ok(new_sub_regions) + } + + /// Transforms a structured loop into a loop in reduce form. + /// + /// Reduce form is defined as a structured loop where: + /// + /// 1. No values defined within the loop body are used outside the loop body. + /// 2. The block arguments and successor operands of the exit block are equal to the block arguments + /// of the loop header and the successor operands of the back edge. + /// + /// This is required for many structured control flow ops as they tend to not have separate "loop + /// result arguments" and "loop iteration arguments" at the end of the block. Rather, the "loop + /// iteration arguments" from the last iteration are the result of the loop. + /// + /// Note that the requirement of 1 is shared with LCSSA form in LLVM. However, due to this being a + /// structured loop instead of a general loop, we do not require complicated dominance algorithms + /// nor SSA updating making this implementation easier than creating a generic LCSSA transformation + /// pass. + pub fn transform_to_reduce_loop( + &mut self, + loop_header: BlockRef, + exit_block: BlockRef, + loop_blocks: &[BlockRef], + ) -> ValueRange<'static, 2> { + let latch = { + let exit_block = exit_block.borrow(); + let latch = exit_block + .get_single_predecessor() + .expect("exit block must have only latch as predecessor at this point"); + assert_eq!( + exit_block.arguments().len(), + 0, + "exit block musn't have any block arguments at this point" + ); + latch + }; + + let latch_block = latch.borrow(); + + let mut loop_header_index = 0; + let mut exit_block_index = 1; + if !BlockRef::ptr_eq(&latch_block.get_successor(loop_header_index), &loop_header) { + core::mem::swap(&mut loop_header_index, &mut exit_block_index); + } + + assert!(BlockRef::ptr_eq(&latch_block.get_successor(loop_header_index), &loop_header)); + assert!(BlockRef::ptr_eq(&latch_block.get_successor(exit_block_index), &exit_block)); + + let mut latch_terminator = latch_block.terminator().unwrap(); + let latch_term = latch_terminator.borrow(); + // Take a snapshot of the loop header successor operands as we cannot hold a reference to + // them and mutate them at the same time + let mut loop_header_successor_operands = latch_term + .successor(loop_header_index) + .arguments + .as_value_range() + .into_smallvec(); + drop(latch_term); + drop(latch_block); + + // Add all values used in the next iteration to the exit block. Replace any uses that are + // outside the loop with the newly created exit block. + for mut arg in loop_header_successor_operands.iter().copied() { + let argument = arg.borrow(); + let exit_arg = self.context.append_block_argument( + exit_block, + argument.ty().clone(), + argument.span(), + ); + drop(argument); + + let operand = self.context.make_operand(arg, latch_terminator, 0); + { + let mut latch_term = latch_terminator.borrow_mut(); + latch_term.successor_mut(exit_block_index).arguments.push(operand); + } + arg.borrow_mut().replace_uses_with_if(exit_arg, |user| { + !loop_blocks.contains(&user.owner.parent().unwrap()) + }); + } + + // Loop below might add block arguments to the latch and loop header. Save the block + // arguments prior to the loop to not process these. + let latch_block_arguments_prior = + latch.borrow().arguments().iter().copied().collect::>(); + let loop_header_arguments_prior = + loop_header.borrow().arguments().iter().copied().collect::>(); + + // Ensure the dominance tree DFS numbers have been computed + if !self.region.borrow().has_one_block() { + self.dominance_info.dominance(self.region).update_dfs_numbers(); + } + + // Go over all values defined within the loop body. If any of them are used outside the loop + // body, create a block argument on the exit block and loop header and replace the outside + // uses with the exit block argument. The loop header block argument is added to satisfy + // requirement (1) in the reduce form condition. + for loop_block_ref in loop_blocks.iter() { + // Cache dominance queries for loop_block_ref. + // There are likely to be many duplicate queries as there can be many value definitions + // within a block. + let mut dominance_cache = SmallDenseMap::::default(); + // Returns true if `loop_block_ref` dominates `block`. + let mut loop_block_dominates = |block: BlockRef, dominance_info: &DominanceInfo| { + use midenc_hir::adt::smallmap::Entry; + match dominance_cache.entry(block) { + Entry::Occupied(entry) => { + let dominates = *entry.get(); + log::trace!(target: "cfg-to-scf", "{loop_block_ref} dominates {block}: {dominates}"); + dominates + } + Entry::Vacant(entry) => { + let dominates = dominance_info.dominates(loop_block_ref, &block); + log::trace!(target: "cfg-to-scf", "{loop_block_ref} dominates {block}: {dominates}"); + entry.insert(dominates); + dominates + } + } + }; + + let mut check_value = |ctx: &mut TransformationContext<'_>, value: ValueRef| { + log::trace!(target: "cfg-to-scf", "checking if value {value} escapes loop"); + let mut block_argument = None; + let mut next_use = { value.borrow().uses().front().as_pointer() }; + while let Some(mut user) = next_use.take() { + next_use = user.next(); + log::trace!(target: "cfg-to-scf", " checking use of {value} by {}", user.borrow().owner()); + + // Go through all the parent blocks and find the one part of the region of the + // loop. If the block is part of the loop, then the value does not escape the + // loop through this use. + let mut curr_block = user.borrow().owner.parent(); + while let Some(cb) = curr_block { + if cb.parent().is_none_or(|r| loop_header.parent().unwrap() != r) { + curr_block = cb.grandparent().and_then(|op| op.parent()); + continue; + } + + break; + } + + let curr_block = curr_block.unwrap(); + if loop_blocks.contains(&curr_block) { + log::trace!(target: "cfg-to-scf", " use is within loop"); + continue; + } + log::trace!(target: "cfg-to-scf", " use in {curr_block} escapes loop {}", DisplayValues::new(loop_blocks.iter())); + + // Block argument is only created the first time it is required. + if block_argument.is_none() { + let (value_ty, span, value_block) = { + let val = value.borrow(); + (val.ty().clone(), val.span(), val.parent_block().unwrap()) + }; + block_argument = Some(ctx.context.append_block_argument( + exit_block, + value_ty.clone(), + span, + )); + log::trace!(target: "cfg-to-scf", "introducing block argument to prevent escape of {value}"); + log::trace!(target: "cfg-to-scf", " created block argument {} in user's block", block_argument.unwrap()); + let _loop_header_arg = + ctx.context.append_block_argument(loop_header, value_ty.clone(), span); + log::trace!(target: "cfg-to-scf", " created block argument {_loop_header_arg} in loop header"); + + // `value` might be defined in a block that does not dominate `latch` but + // previously dominated an exit block with a use. In this case, add a block + // argument to the latch and go through all predecessors. If the value + // dominates the predecessor, pass the value as a successor operand, + // otherwise pass undef. The above is unnecessary if the value is a block + // argument of the latch or if `value` dominates all predecessors. + let mut argument = value; + if value_block != latch + && latch.borrow().predecessors().any(|pred| { + !loop_block_dominates(pred.predecessor(), ctx.dominance_info) + }) + { + log::trace!(target: "cfg-to-scf", " {argument} is defined in {value_block}, and at least one predecessor of the latch {latch} is not dominated by {loop_block_ref}"); + argument = + ctx.context.append_block_argument(latch, value_ty.clone(), span); + log::trace!(target: "cfg-to-scf", " creating block argument {argument} in latch"); + for pred in latch.borrow().predecessors() { + let mut succ_operand = value; + log::trace!(target: "cfg-to-scf", " initializing predecessor operand for {argument} with {succ_operand}"); + if !loop_block_dominates(pred.predecessor(), ctx.dominance_info) { + succ_operand = ctx.get_undef_value(&value_ty); + log::trace!(target: "cfg-to-scf", " predecessor {} is not dominated by {loop_block_ref}, successor operand changed to {succ_operand}", pred.predecessor()); + } + + let succ_operand = + ctx.context.make_operand(succ_operand, pred.owner, 0); + + let mut pred_op = pred.owner; + let mut pred_op = pred_op.borrow_mut(); + let mut succ = pred_op.successor_mut(pred.index as usize); + succ.arguments.push(succ_operand); + } + } + + log::trace!(target: "cfg-to-scf", " appending {argument} to loop header successor operands"); + loop_header_successor_operands.push(argument); + for edge in SuccessorEdges::new(latch) { + let mut pred = edge.from_block.borrow().terminator().unwrap(); + log::trace!(target: "cfg-to-scf", " appending {argument} to successor operands of {edge}"); + let operand = ctx.context.make_operand(argument, pred, 0); + let mut pred = pred.borrow_mut(); + let mut succ = pred.successor_mut(edge.successor_index); + succ.arguments.push(operand); + } + } + + log::trace!(target: "cfg-to-scf", " setting use of {value} to {}", block_argument.unwrap()); + user.borrow_mut().set(block_argument.unwrap()); + } + }; + + if *loop_block_ref == latch { + for arg in latch_block_arguments_prior.as_value_range() { + check_value(self, arg); + } + } else if *loop_block_ref == loop_header { + for arg in loop_header_arguments_prior.as_value_range() { + check_value(self, arg); + } + } else { + let loop_block = loop_block_ref.borrow(); + for arg in loop_block.arguments().as_value_range() { + check_value(self, arg); + } + } + + let mut loop_block_cursor = loop_block_ref.borrow().body().front().as_pointer(); + while let Some(op) = loop_block_cursor.take() { + loop_block_cursor = op.next(); + let op = op.borrow(); + for result in op.results().as_value_range() { + check_value(self, result); + } + } + } + + // New block arguments may have been added to the loop header. Adjust the entry edges to + // pass undef values to these. + let loop_header = loop_header.borrow(); + log::trace!(target: "cfg-to-scf", "checking that all predecessors of {loop_header} pass {} successor operands", loop_header.num_arguments()); + for pred in loop_header.predecessors() { + // Latch successor arguments have already been handled. + if pred.predecessor() == latch { + continue; + } + + let mut op = pred.owner; + let mut op = op.borrow_mut(); + let mut succ = op.successor_mut(pred.index as usize); + if cfg!(debug_assertions) && succ.arguments.len() != loop_header.num_arguments() { + log::trace!(target: "cfg-to-scf", " {} has only {} successor operands", pred.predecessor(), succ.arguments.len()); + } + succ.arguments + .extend(loop_header.arguments().iter().skip(succ.arguments.len()).map(|arg| { + let val = self.get_undef_value(arg.borrow().ty()); + log::trace!(target: "cfg-to-scf", " appending {val} to successor operands for missing parameter {arg}"); + self.context.make_operand(val, pred.owner, 0) + })); + } + + loop_header_successor_operands.into() + } + + /// Creates a single entry block out of multiple entry edges using an edge multiplexer and returns + /// it. + fn create_single_entry_block( + &mut self, + span: SourceSpan, + entry_edges: &[Edge], + ) -> Result, Report> { + let entry_blocks = SmallVec::<[BlockRef; 2]>::from_iter( + entry_edges.iter().map(|edge| edge.get_successor()), + ); + let context = self.context.clone(); + let mut multiplexer = EdgeMultiplexer::create(self, span, &entry_blocks, &[]); + + // Redirect the edges prior to creating the switch op. We guarantee that predecessors are up + // to date. + for edge in entry_edges { + multiplexer.redirect_edge(edge, &[]); + } + + let mut builder = OpBuilder::new(context); + builder.set_insertion_point_to_end(multiplexer.get_multiplexer_block()); + multiplexer.create_switch(span, &mut builder, &[])?; + + Ok(multiplexer) + } + + /// Makes sure the branch region only has a single exit. + /// + /// This is required by the recursive part of the algorithm, as it expects the CFG to be single- + /// entry and single-exit. This is done by simply creating an empty block if there is more than one + /// block with an edge to the continuation block. All blocks with edges to the continuation are then + /// redirected to this block. A region terminator is later placed into the block. + #[allow(clippy::type_complexity)] + fn create_single_exit_branch_region( + &mut self, + branch_region: &[BlockRef], + continuation: BlockRef, + created_empty_blocks: &mut SmallVec<[(BlockRef, ValueRange<'static, 2>); 2]>, + conditional_region: RegionRef, + ) { + let mut single_exit_block = None; + let mut previous_edge_to_continuation = None; + let mut branch_region_parent = branch_region[0].parent().unwrap(); + + log::trace!(target: "cfg-to-scf", "creating single-exit branch region"); + log::trace!(target: "cfg-to-scf", " continuation = {continuation}"); + for mut block_ref in branch_region.iter().copied() { + log::trace!(target: "cfg-to-scf", " processing region block: {block_ref}"); + for edge in SuccessorEdges::new(block_ref) { + log::trace!(target: "cfg-to-scf", " processing edge: {} -> {}", edge.from_block, edge.get_successor()); + log::trace!(target: "cfg-to-scf", " single-exit block: {single_exit_block:?}"); + if !BlockRef::ptr_eq(&edge.get_successor(), &continuation) { + continue; + } + + if previous_edge_to_continuation.is_none() { + previous_edge_to_continuation = Some(edge); + continue; + } + + // If this is not the first edge to the continuation we create the single exit block + // and redirect the edges. + if single_exit_block.is_none() { + let seb = self.context.create_block(); + single_exit_block = Some(seb); + self.add_block_arguments_from_other(seb, continuation); + previous_edge_to_continuation.as_mut().unwrap().set_successor(seb); + let seb_block = seb.borrow(); + let seb_args = seb_block + .arguments() + .iter() + .map(|arg| arg.borrow().as_value_ref()) + .collect(); + created_empty_blocks.push((seb, seb_args)); + } + + edge.set_successor(single_exit_block.unwrap()); + } + + let mut brp = branch_region_parent.borrow_mut(); + unsafe { + let mut cursor = brp.body_mut().cursor_mut_from_ptr(block_ref); + cursor.remove(); + } + + block_ref.borrow_mut().insert_at_end(conditional_region); + } + + if let Some(mut single_exit_block) = single_exit_block { + let mut single_exit_block = single_exit_block.borrow_mut(); + single_exit_block.insert_at_end(conditional_region); + } + } + + /// Transforms a loop into a structured loop with only a single back edge and + /// exiting edge, originating from the same block. + fn create_single_exiting_latch( + &mut self, + span: SourceSpan, + back_edges: &[Edge], + exit_edges: &[Edge], + ) -> Result { + assert!( + all_same_block(back_edges, |edge| edge.get_successor()), + "all repetition edges must lead to the single loop header" + ); + + // First create the multiplexer block, which will be our latch, for all back edges and exit + // edges. We pass an additional argument to the multiplexer block which indicates whether + // the latch was reached from what was originally a back edge or an exit block. This is + // later used to branch using the new only back edge. + let mut successors = SmallVec::<[BlockRef; 4]>::default(); + successors.extend(back_edges.iter().map(|edge| edge.get_successor())); + successors.extend(exit_edges.iter().map(|edge| edge.get_successor())); + + let extra_args = [self.get_switch_value(0).borrow().ty().clone()]; + let context = self.context.clone(); + let mut multiplexer = EdgeMultiplexer::create(self, span, &successors, &extra_args); + + let latch_block = multiplexer.get_multiplexer_block(); + + // Create a separate exit block that comes right after the latch. + let mut exit_block = multiplexer.transform().create_block(); + exit_block.borrow_mut().insert_after(latch_block); + + // Since this is a loop, all back edges point to the same loop header. + let loop_header = back_edges[0].get_successor(); + + // Redirect the edges prior to creating the switch op. We guarantee that predecessors are up + // to date. + + // Redirecting back edges with `should_repeat` as 1. + for edge in back_edges { + let extra_args = [multiplexer.transform().get_switch_value(1)]; + multiplexer.redirect_edge(edge, &extra_args); + } + + // Redirecting exits edges with `should_repeat` as 0. + for edge in exit_edges { + let extra_args = [multiplexer.transform().get_switch_value(0)]; + multiplexer.redirect_edge(edge, &extra_args); + } + + // Create the new only back edge to the loop header. Branch to the exit block otherwise. + let should_repeat = latch_block.borrow().arguments().last().copied().unwrap(); + let should_repeat = should_repeat.borrow().as_value_ref(); + { + let mut builder = OpBuilder::new(context.clone()); + builder.set_insertion_point_to_end(latch_block); + + let num_args = loop_header.borrow().num_arguments(); + let latch_args = { + let latch_block = latch_block.borrow(); + ValueRange::from_iter(latch_block.arguments().iter().copied().take(num_args)) + }; + multiplexer.transform().interface_mut().create_conditional_branch( + span, + &mut builder, + should_repeat, + loop_header, + latch_args, + exit_block, + ValueRange::Empty, + )?; + } + + { + let mut builder = OpBuilder::new(context); + builder.set_insertion_point_to_end(exit_block); + + if exit_edges.is_empty() { + // A loop without an exit edge is a statically known infinite loop. + // Since structured control flow ops are not terminator ops, the caller has to + // create a fitting return-like unreachable terminator operation. + let region = latch_block.parent().unwrap(); + let terminator = multiplexer + .transform() + .interface_mut() + .create_unreachable_terminator(span, &mut builder, region)?; + // Transform the just created transform operation in the case that an occurrence of + // it existed in input IR. + multiplexer.transform().combine_exit(terminator)?; + } else { + // Create the switch dispatching to what were originally the multiple exit blocks. + // The loop header has to explicitly be excluded in the below switch as we would + // otherwise be creating a new loop again. All back edges leading to the loop header + // have already been handled in the switch above. The remaining edges can only jump + // to blocks outside the loop. + multiplexer.create_switch(span, &mut builder, &[loop_header])?; + } + } + + Ok(StructuredLoopProperties { + latch: latch_block, + condition: should_repeat, + exit_block, + }) + } +} + +/// Alternative implementation of Eq/Hash for Operation, using the operation equivalence infra to +/// check whether two 'return-like' operations are equivalent in the context of this transformation. +/// +/// This means that both operations are of the same kind, have the same amount of operands and types +/// and the same attributes and properties. The operands themselves don't have to be equivalent. +#[derive(Copy, Clone)] +struct ReturnLikeOpKey(OperationRef); +impl Eq for ReturnLikeOpKey {} +impl PartialEq for ReturnLikeOpKey { + fn eq(&self, other: &Self) -> bool { + use midenc_hir::equivalence::{ignore_value_equivalence, OperationEquivalenceFlags}; + let a = self.0.borrow(); + a.is_equivalent_with_options( + &other.0.borrow(), + OperationEquivalenceFlags::IGNORE_LOCATIONS, + ignore_value_equivalence, + ) + } +} +impl core::hash::Hash for ReturnLikeOpKey { + fn hash(&self, state: &mut H) { + use midenc_hir::equivalence::{IgnoreValueEquivalenceOperationHasher, OperationHasher}; + + const HASHER: IgnoreValueEquivalenceOperationHasher = IgnoreValueEquivalenceOperationHasher; + + HASHER.hash_operation(&self.0.borrow(), state); + } +} + +/// Special loop properties of a structured loop. +/// +/// A structured loop is a loop satisfying all of the following: +/// +/// * Has at most one entry, one exit and one back edge. +/// * The back edge originates from the same block as the exit edge. +#[derive(Debug)] +struct StructuredLoopProperties { + /// Block containing both the single exit edge and the single back edge. + latch: BlockRef, + /// Loop condition of type equal to a value returned by `getSwitchValue`. + condition: ValueRef, + /// Exit block which is the only successor of the loop. + exit_block: BlockRef, +} + +fn all_same_block(edges: &[Edge], callback: F) -> bool +where + F: Fn(&Edge) -> BlockRef, +{ + let Some((first, rest)) = edges.split_first() else { + return true; + }; + + let expected = callback(first); + rest.iter().all(|edge| callback(edge) == expected) +} + +/// Returns true if this block is an exit block of the region. +fn is_region_exit_block(block: &Block) -> bool { + block.num_successors() == 0 +} diff --git a/hir-transform/src/inline_blocks.rs b/hir-transform/src/inline_blocks.rs deleted file mode 100644 index aa89c9522..000000000 --- a/hir-transform/src/inline_blocks.rs +++ /dev/null @@ -1,504 +0,0 @@ -use std::collections::VecDeque; - -use midenc_hir::{ - self as hir, - pass::{AnalysisManager, RewritePass, RewriteResult}, - *, -}; -use midenc_hir_analysis::ControlFlowGraph; -use midenc_session::{diagnostics::IntoDiagnostic, Session}; -use rustc_hash::FxHashSet; -use smallvec::SmallVec; - -use crate::adt::ScopedMap; - -/// This pass inlines unnecessary blocks, and removes unnecessary block arguments. -/// -/// Specifically, the blocks affected are those with a single predecessor, as they -/// by definition can be inlined into their immediate predecessor in such cases. -/// -/// Blocks like this may have been introduced for the following reasons: -/// -/// * Due to less than optimal lowering to SSA form -/// * To split critical edges in preparation for dataflow analysis and related transformations, -/// but ultimately no code introduced along those edges, and critical edges no longer present -/// an obstacle to further optimization or codegen. -/// * During treeification of the CFG, where blocks with multiple predecessors were duplicated -/// to produce a CFG in tree form, where no blocks (other than loop headers) have multiple -/// predecessors. This process removed block arguments from these blocks, and rewrote instructions -/// dominated by those block arguments to reference the values passed from the original -/// predecessor to whom the subtree is attached. This transformation can expose a chain of blocks -/// which all have a single predecessor and successor, introducing branches where none are needed, -/// and by removing those redundant branches, all of the code from blocks in the chain can be -/// inlined in the first block of the chain. -#[derive(Default, PassInfo, ModuleRewritePassAdapter)] -pub struct InlineBlocks; -impl RewritePass for InlineBlocks { - type Entity = hir::Function; - - fn apply( - &mut self, - function: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - let mut cfg = analyses - .take::(&function.id) - .unwrap_or_else(|| ControlFlowGraph::with_function(function)); - - let entry = function.dfg.entry_block(); - let mut changed = false; - let mut rewrites = ScopedMap::::default(); - let mut visited = FxHashSet::::default(); - let mut worklist = VecDeque::::default(); - worklist.push_back(entry); - - // First, search down the CFG for non-loop header blocks with only a single successor. - // These blocks form possible roots of a chain of blocks that can be inlined. - // - // For each such root, we then check if the successor block has a single predecessor, - // if so, then we can remove the terminator instruction from the root block, and then - // move all of the code from the successor block into the root block. We can then repeat - // this process until we inline a terminator instruction that is not an unconditional branch - // to a single successor. - while let Some(p) = worklist.pop_front() { - // If we've already visited a block, skip it - if !visited.insert(p) { - continue; - } - - // If the block is detached, then it has already been inlined, - // so we do not need to run inlining on it, however we should - // visit its successors anyway, as there may be inlining - // opportunities later in the CFG. - let is_detached = !function.dfg.is_block_linked(p); - - // If this block has multiple successors, or multiple predecessors, add all of it's - // successors to the work queue and move on. - if is_detached || cfg.num_successors(p) > 1 || cfg.num_predecessors(p) > 1 { - for b in cfg.succ_iter(p) { - worklist.push_back(b); - } - continue; - } - - // This block is a candidate for inlining - // - // If inlining can proceed, do so until we reach a point where the inlined terminator - // returns from the function, has multiple successors, or branches to a block with - // multiple predecessors. - while let BranchInfo::SingleDest(succ) = - function.dfg.analyze_branch(function.dfg.last_inst(p).unwrap()) - { - let destination = succ.destination; - - // If this successor has other predecessors, it can't be inlined, so - // add it to the work list and move on - if cfg.num_predecessors(destination) > 1 { - worklist.push_back(destination); - break; - } - - // Only inline if the successor has no block arguments - // - // TODO: We can inline blocks with arguments as well, but with higher cost, - // as we must visit all uses of the block arguments and update them. This - // is left as a future extension of this pass should we find that it is - // valuable as an optimization. - if !succ.args.is_empty() { - // Compute the set of values to rewrite - for (from, to) in function - .dfg - .block_params(destination) - .iter() - .copied() - .zip(succ.args.iter().copied()) - { - rewrites.insert(from, to); - } - } - - inline(destination, p, function, &mut worklist, &rewrites, &mut cfg); - - // Mark that the control flow graph as modified - changed = true; - } - } - - rewrite_uses(entry, function, &rewrites); - - analyses.insert(function.id, cfg); - if !changed { - analyses.mark_preserved::(&function.id); - } - - session.print(&*function, Self::FLAG).into_diagnostic()?; - if session.should_print_cfg(Self::FLAG) { - use std::io::Write; - let cfg = function.cfg_printer(); - let mut stdout = std::io::stdout().lock(); - write!(&mut stdout, "{cfg}").into_diagnostic()?; - } - - Ok(()) - } -} - -fn inline( - from: Block, - to: Block, - function: &mut hir::Function, - worklist: &mut VecDeque, - rewrites: &ScopedMap, - cfg: &mut ControlFlowGraph, -) { - assert_ne!(from, to); - let mut from_terminator = None; - { - let mut from_insts = function.dfg.block_mut(from).insts.take(); - let to_insts = &mut function.dfg.blocks[to].insts; - // Remove the original terminator - to_insts.pop_back(); - // Move all instructions from their original block to the parent, - // updating the instruction data along the way to reflect the change - // in location - while let Some(unsafe_ix_ref) = from_insts.pop_front() { - let mut ix = unsafe { UnsafeRef::into_box(unsafe_ix_ref) }; - ix.block = to; - rewrite_use(ix.as_mut(), &mut function.dfg.value_lists, rewrites); - // We need to clone the original terminator so we can continue to - // navigate the control flow graph - if ix.opcode().is_terminator() { - let replacement = Box::new(ix.deep_clone(&mut function.dfg.value_lists)); - assert!( - from_terminator.replace(replacement).is_none(), - "a block can only have one terminator" - ); - } - to_insts.push_back(UnsafeRef::from_box(ix)); - } - } - // Append the cloned terminator back to the inlined block before we detach it - let from_terminator = from_terminator.expect("a block must have a terminator"); - match (*from_terminator).as_ref() { - Instruction::Br(Br { successor, .. }) => { - worklist.push_back(successor.destination); - } - Instruction::CondBr(CondBr { - then_dest, - else_dest, - .. - }) => { - worklist.push_back(then_dest.destination); - worklist.push_back(else_dest.destination); - } - Instruction::Switch(hir::Switch { - arms, - default: default_dest, - .. - }) => { - worklist.extend(arms.iter().map(|arm| arm.successor.destination)); - worklist.push_back(default_dest.destination); - } - _ => (), - } - function.dfg.blocks[from].insts.push_back(UnsafeRef::from_box(from_terminator)); - // Detach the original block from the function - function.dfg.detach_block(from); - // Update the control flow graph to reflect the changes - cfg.detach_block(from); - cfg.recompute_block(&function.dfg, to); -} - -fn rewrite_uses(root: Block, function: &mut hir::Function, rewrites: &ScopedMap) { - let mut visited = FxHashSet::::default(); - let mut worklist = VecDeque::::default(); - worklist.push_back(root); - - while let Some(b) = worklist.pop_front() { - // Do not visit the same block twice - if !visited.insert(b) { - continue; - } - - let block = &mut function.dfg.blocks[b]; - // Take the list of instructions away from the block to simplify traversing the block - let mut insts = block.insts.take(); - // Take each instruction out of the list, top to bottom, modify it, then - // add it back to the instruction list of the block directly. This ensures - // we traverse the list and rewrite instructions in a single pass without - // any additional overhead - while let Some(inst) = insts.pop_front() { - let mut inst = unsafe { UnsafeRef::into_box(inst) }; - let to_visit = rewrite_use(inst.as_mut(), &mut function.dfg.value_lists, rewrites); - if !to_visit.is_empty() { - worklist.extend(to_visit); - } - - block.insts.push_back(UnsafeRef::from_box(inst)); - } - } -} - -fn rewrite_use( - inst: &mut Instruction, - pool: &mut hir::ValueListPool, - rewrites: &ScopedMap, -) -> SmallVec<[Block; 2]> { - let mut worklist = SmallVec::<[Block; 2]>::default(); - match inst { - Instruction::Br(Br { - ref mut successor, .. - }) => { - worklist.push(successor.destination); - for arg in successor.args.as_mut_slice(pool) { - if let Some(replacement) = rewrites.get(arg).copied() { - *arg = replacement; - } - } - } - Instruction::CondBr(CondBr { - ref mut cond, - ref mut then_dest, - ref mut else_dest, - .. - }) => { - worklist.push(then_dest.destination); - worklist.push(else_dest.destination); - if let Some(replacement) = rewrites.get(cond).copied() { - *cond = replacement; - } - for arg in then_dest.args.as_mut_slice(pool) { - if let Some(replacement) = rewrites.get(arg).copied() { - *arg = replacement; - } - } - for arg in else_dest.args.as_mut_slice(pool) { - if let Some(replacement) = rewrites.get(arg).copied() { - *arg = replacement; - } - } - } - Instruction::Switch(hir::Switch { - ref mut arg, - ref mut arms, - default: ref mut default_dest, - .. - }) => { - worklist.extend(arms.iter().map(|arm| arm.successor.destination)); - worklist.push(default_dest.destination); - if let Some(replacement) = rewrites.get(arg).copied() { - *arg = replacement; - } - for arg in default_dest.args.as_mut_slice(pool) { - if let Some(replacement) = rewrites.get(arg).copied() { - *arg = replacement; - } - } - for arm in arms.iter_mut() { - for arg in arm.successor.args.as_mut_slice(pool) { - if let Some(replacement) = rewrites.get(arg).copied() { - *arg = replacement; - } - } - } - } - op => { - for arg in op.arguments_mut(pool) { - if let Some(replacement) = rewrites.get(arg).copied() { - *arg = replacement; - } - } - } - } - - worklist -} - -#[cfg(test)] -mod tests { - use midenc_hir::{ - pass::{AnalysisManager, RewritePass}, - testing::TestContext, - AbiParam, Function, FunctionBuilder, Immediate, InstBuilder, Signature, SourceSpan, Type, - }; - use pretty_assertions::{assert_eq, assert_ne}; - - use crate::InlineBlocks; - - /// Run the inlining pass on the following IR: - /// - /// The following IR is unnecessarily verbose: - /// - /// ```text,ignore - /// pub fn test(*mut u8, i32) -> *mut u8 { - /// entry(ptr0: *mut u8, offset: i32): - /// zero = const.u32 0; - /// ptr1 = ptrtoint ptr0 : u32; - /// is_null = eq ptr1, zero; - /// br blk0(ptr1, is_null); - /// - /// blk0(ptr2: u32, is_null1: i1): - /// condbr is_null1, blk2(ptr0), blk1(ptr2); - /// - /// blk1(ptr3: u32): - /// ptr4 = add ptr3, offset; - /// is_null2 = eq ptr4, zero; - /// condbr is_null2, blk4(ptr0), blk5(ptr4); - /// - /// blk2(result0: *mut u8): - /// br blk3; - /// - /// blk3: - /// ret result0; - /// - /// blk4(result1: *mut u8): - /// ret result1; - /// - /// blk5(ptr5: u32): - /// ptr6 = inttoptr ptr5 : *mut u8; - /// ret ptr6; - /// } - /// ``` - /// - /// We want the inlining pass to result in something like: - /// - /// ```text,ignore - /// pub fn test(*mut u8, i32) -> *mut u8 { - /// entry(ptr0: *mut u8, offset: i32): - /// zero = const.u32 0; - /// ptr1 = ptrtoint ptr0 : u32; - /// is_null = eq ptr1, zero; - /// condbr is_null, blk2, blk1; - /// - /// blk1: - /// ptr2 = add ptr1, offset; - /// is_null1 = eq ptr2, zero; - /// condbr is_null1, blk3, blk4; - /// - /// blk2: - /// ret ptr0; - /// - /// blk3: - /// ret ptr0; - /// - /// blk4: - /// ptr3 = inttoptr ptr2 : *mut u8; - /// ret ptr3; - /// } - /// ``` - /// - /// In short, regardless of block arguments, control flow edges between blocks - /// where the predecessor is the only predecessor, and the successor is the only - /// successor, represent edges which can be removed by inlining the successor - /// block into the predecessor block. Any uses of values introduced as block - /// parameters of the successor block, must be rewritten to use the values - /// provided in the predecessor block for those parameters. - #[test] - fn inline_blocks_simple_tree_cfg_test() { - let context = TestContext::default(); - let id = "test::inlining_test".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8))), AbiParam::new(Type::I32)], - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - ), - ); - - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let (ptr0, offset) = { - let args = builder.block_params(entry); - (args[0], args[1]) - }; - - let a = builder.create_block(); // blk0(ptr2: u32, is_null1: i1) - let ptr2 = builder.append_block_param(a, Type::U32, SourceSpan::UNKNOWN); - let is_null1 = builder.append_block_param(a, Type::I1, SourceSpan::UNKNOWN); - let b = builder.create_block(); // blk1(ptr3: u32) - let ptr3 = builder.append_block_param(b, Type::U32, SourceSpan::UNKNOWN); - let c = builder.create_block(); // blk2(result0: *mut u8) - let result0 = - builder.append_block_param(c, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); - let d = builder.create_block(); // blk3 - let e = builder.create_block(); // blk4(result1: *mut u8) - let result1 = - builder.append_block_param(e, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); - let f = builder.create_block(); // blk5(ptr5: u32) - let ptr5 = builder.append_block_param(f, Type::U32, SourceSpan::UNKNOWN); - - // entry - let ptr1 = builder.ins().ptrtoint(ptr0, Type::U32, SourceSpan::UNKNOWN); - let is_null = builder.ins().eq_imm(ptr1, Immediate::U32(0), SourceSpan::UNKNOWN); - builder.ins().br(a, &[ptr1, is_null], SourceSpan::UNKNOWN); - - // blk0 - builder.switch_to_block(a); - builder.ins().cond_br(is_null1, c, &[ptr0], b, &[ptr2], SourceSpan::UNKNOWN); - - // blk1 - builder.switch_to_block(b); - let ptr3_i32 = builder.ins().cast(ptr3, Type::I32, SourceSpan::UNKNOWN); - let ptr4_i32 = builder.ins().add_checked(ptr3_i32, offset, SourceSpan::UNKNOWN); - let ptr4 = builder.ins().cast(ptr4_i32, Type::U32, SourceSpan::UNKNOWN); - let is_null2 = builder.ins().eq_imm(ptr4, Immediate::U32(0), SourceSpan::UNKNOWN); - builder.ins().cond_br(is_null2, e, &[ptr0], f, &[ptr4], SourceSpan::UNKNOWN); - - // blk2 - builder.switch_to_block(c); - builder.ins().br(d, &[], SourceSpan::UNKNOWN); - - // blk3 - builder.switch_to_block(d); - builder.ins().ret(Some(result0), SourceSpan::UNKNOWN); - - // blk4 - builder.switch_to_block(e); - builder.ins().ret(Some(result1), SourceSpan::UNKNOWN); - - // blk5 - builder.switch_to_block(f); - let ptr6 = - builder.ins().inttoptr(ptr5, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); - builder.ins().ret(Some(ptr6), SourceSpan::UNKNOWN); - } - - let original = function.to_string(); - let mut analyses = AnalysisManager::default(); - let mut rewrite = InlineBlocks; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("inlining failed"); - - let expected = "\ -(func (export #inlining_test) (param (ptr u8)) (param i32) (result (ptr u8)) - (block 0 (param v0 (ptr u8)) (param v1 i32) - (let (v8 u32) (ptrtoint v0)) - (let (v9 i1) (eq v8 0)) - (condbr v9 (block 3 v0) (block 2 v8))) - - (block 2 (param v4 u32) - (let (v10 i32) (cast v4)) - (let (v11 i32) (add.checked v10 v1)) - (let (v12 u32) (cast v11)) - (let (v13 i1) (eq v12 0)) - (condbr v13 (block 5 v0) (block 6 v12))) - - (block 3 (param v5 (ptr u8)) - (ret v5)) - - (block 5 (param v6 (ptr u8)) - (ret v6)) - - (block 6 (param v7 u32) - (let (v14 (ptr u8)) (inttoptr v7)) - (ret v14)) -)"; - - let inlined = function.to_string(); - assert_ne!(inlined, original); - assert_eq!(inlined.as_str(), expected); - } -} diff --git a/hir-transform/src/lib.rs b/hir-transform/src/lib.rs index 3313d9cc8..0e4a798d4 100644 --- a/hir-transform/src/lib.rs +++ b/hir-transform/src/lib.rs @@ -1,12 +1,27 @@ -pub(crate) mod adt; -mod inline_blocks; +#![no_std] +#![feature(new_range_api)] +#![deny(warnings)] + +extern crate alloc; +#[cfg(test)] +extern crate std; + +mod canonicalization; +mod cfg_to_scf; +//mod cse; +//mod dce; +//mod inliner; +mod sccp; +mod sink; mod spill; -mod split_critical_edges; -mod treeify; +//pub use self::cse::CommonSubexpressionElimination; +//pub use self::dce::{DeadSymbolElmination, DeadValueElimination}; +//pub use self::inliner::Inliner; pub use self::{ - inline_blocks::InlineBlocks, - spill::{ApplySpills, InsertSpills, RewriteSpills}, - split_critical_edges::SplitCriticalEdges, - treeify::Treeify, + canonicalization::Canonicalizer, + cfg_to_scf::{transform_cfg_to_scf, CFGToSCFInterface}, + sccp::SparseConditionalConstantPropagation, + sink::{ControlFlowSink, SinkOperandDefs}, + spill::{transform_spills, ReloadLike, SpillLike, TransformSpillsInterface}, }; diff --git a/hir-transform/src/sccp.rs b/hir-transform/src/sccp.rs new file mode 100644 index 000000000..a80a6e194 --- /dev/null +++ b/hir-transform/src/sccp.rs @@ -0,0 +1,167 @@ +use midenc_hir::{ + pass::{Pass, PassExecutionState}, + patterns::NoopRewriterListener, + BlockRef, Builder, EntityMut, OpBuilder, Operation, OperationFolder, OperationName, RegionList, + Report, SmallVec, ValueRef, +}; +use midenc_hir_analysis::{ + analyses::{constant_propagation::ConstantValue, DeadCodeAnalysis, SparseConstantPropagation}, + DataFlowSolver, Lattice, +}; + +/// This pass implements a general algorithm for sparse conditional constant propagation. +/// +/// This algorithm detects values that are known to be constant and optimistically propagates this +/// throughout the IR. Any values proven to be constant are replaced, and removed if possible. +/// +/// This implementation is based on the algorithm described by Wegman and Zadeck in +/// [“Constant Propagation with Conditional Branches”](https://dl.acm.org/doi/10.1145/103135.103136) +/// (1991). +pub struct SparseConditionalConstantPropagation; + +impl Pass for SparseConditionalConstantPropagation { + type Target = Operation; + + fn name(&self) -> &'static str { + "sparse-conditional-constant-propagation" + } + + fn argument(&self) -> &'static str { + "sparse-conditional-constant-propagation" + } + + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn run_on_operation( + &mut self, + mut op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + // Run sparse constant propagation + dead code analysis + let mut solver = DataFlowSolver::default(); + solver.load::(); + solver.load::(); + solver.initialize_and_run(&op, state.analysis_manager().clone())?; + + // Rewrite based on results of analysis + self.rewrite(&mut op, state, &solver) + } +} + +impl SparseConditionalConstantPropagation { + /// Rewrite the given regions using the computing analysis. This replaces the uses of all values + /// that have been computed to be constant, and erases as many newly dead operations. + fn rewrite( + &mut self, + op: &mut Operation, + state: &mut PassExecutionState, + solver: &DataFlowSolver, + ) -> Result<(), Report> { + let mut worklist = SmallVec::<[BlockRef; 8]>::default(); + + let add_to_worklist = |regions: &RegionList, worklist: &mut SmallVec<[BlockRef; 8]>| { + for region in regions { + for block in region.body().iter().rev() { + worklist.push(block.as_block_ref()); + } + } + }; + + // An operation folder used to create and unique constants. + let context = op.context_rc(); + let mut folder = OperationFolder::new(context.clone(), None::); + let mut builder = OpBuilder::new(context.clone()); + + add_to_worklist(op.regions(), &mut worklist); + + let mut replaced_any = false; + while let Some(mut block) = worklist.pop() { + let mut block = block.borrow_mut(); + let body = block.body_mut(); + let mut ops = body.front(); + + while let Some(mut op) = ops.as_pointer() { + ops.move_next(); + + builder.set_insertion_point_after(op); + + // Replace any result with constants. + let num_results = op.borrow().num_results(); + let mut replaced_all = num_results != 0; + for index in 0..num_results { + let result = { op.borrow().get_result(index).borrow().as_value_ref() }; + let replaced = replace_with_constant(solver, &mut builder, &mut folder, result); + + replaced_any |= replaced; + replaced_all &= replaced; + } + + // If all of the results of the operation were replaced, try to erase the operation + // completely. + let mut op = op.borrow_mut(); + if replaced_all && op.would_be_trivially_dead() { + assert!(!op.is_used(), "expected all uses to be replaced"); + op.erase(); + continue; + } + + // Add any of the regions of this operation to the worklist + add_to_worklist(op.regions(), &mut worklist); + } + + // Replace any block arguments with constants + builder.set_insertion_point_to_start(block.as_block_ref()); + + for arg in block.arguments() { + replaced_any |= replace_with_constant( + solver, + &mut builder, + &mut folder, + arg.borrow().as_value_ref(), + ); + } + } + + state.set_post_pass_status(replaced_any.into()); + + Ok(()) + } +} + +/// Replace the given value with a constant if the corresponding lattice represents a constant. +/// +/// Returns success if the value was replaced, failure otherwise. +fn replace_with_constant( + solver: &DataFlowSolver, + builder: &mut OpBuilder, + folder: &mut OperationFolder, + mut value: ValueRef, +) -> bool { + let Some(lattice) = solver.get::, _>(&value) else { + return false; + }; + if lattice.value().is_uninitialized() { + return false; + } + + let Some(constant_value) = lattice.value().constant_value() else { + return false; + }; + + // Attempt to materialize a constant for the given value. + let dialect = lattice.value().constant_dialect().unwrap(); + let constant = folder.get_or_create_constant( + builder.insertion_block().unwrap(), + dialect, + constant_value, + value.borrow().ty().clone(), + ); + if let Some(constant) = constant { + value.borrow_mut().replace_all_uses_with(constant); + true + } else { + false + } +} diff --git a/hir-transform/src/scheduling.rs b/hir-transform/src/scheduling.rs new file mode 100644 index 000000000..542cd1b70 --- /dev/null +++ b/hir-transform/src/scheduling.rs @@ -0,0 +1,598 @@ +use smallvec::SmallVec; + +use crate::{ + adt::{SmallDenseMap, SmallSet}, + dataflow::analyses::{scheduling::*, LivenessAnalysis}, + dialects::builtin::Function, + dominance::DominanceInfo, + effects::*, + pass::{Pass, PassExecutionState}, + traits::Terminator, + Block, BlockRef, EntityMut, Op, Operation, ProgramPoint, Report, ValueRef, +}; + +pub struct InstructionScheduler; + +impl Pass for InstructionScheduler { + type Target = Function; + + fn name(&self) -> &'static str { + "scheduler" + } + + fn argument(&self) -> &'static str { + "scheduler" + } + + fn can_schedule_on(&self, _name: &crate::OperationName) -> bool { + true + } + + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + log::debug!(target: "scheduler", "optimizing instruction schedule for {}", op.as_operation()); + + // First, push down all constants to their uses by materializing copies of those operations + // + // We do this because it avoids maintaining huge live ranges for values that are always + // cheaper to emit just-in-time when generating code for the Miden VM. This improves the + // quality + + let mut operation = op.as_operation_ref(); + let entry_block = op.entry_block(); + drop(op); + + let dominfo = state.analysis_manager().get_analysis::()?; + let liveness = state.analysis_manager().get_analysis_for::()?; + + let depgraph = build_dependency_graph(entry_block, &operation.borrow(), &liveness); + dbg!(&depgraph); + let treegraph = OrderedTreeGraph::new(&depgraph) + .expect("unable to topologically sort treegraph for block"); + dbg!(&treegraph); + //let mut blockq = SmallVec::<[BlockRef; 8]>::from_slice(self.domtree.cfg_postorder()); + //block_scheduler.schedule(schedule); + + self.rewrite(&mut operation.borrow_mut(), &dominfo, &liveness) + } +} + +impl InstructionScheduler { + fn rewrite( + &mut self, + op: &mut Operation, + dominfo: &DominanceInfo, + liveness: &LivenessAnalysis, + ) -> Result<(), Report> { + todo!() + } +} + +fn build_dependency_graph( + block_id: BlockRef, + op: &Operation, + liveness: &LivenessAnalysis, +) -> DependencyGraph { + let mut graph = DependencyGraph::default(); + + // This set represents the values which are guaranteed to be materialized for an instruction + let mut materialized_args = SmallSet::::default(); + // This map represents values used as block arguments, and the successors which use them + let mut block_arg_uses = SmallDenseMap::>::default(); + + // For each instruction, record it and it's arguments/results in the graph + let block = block_id.borrow(); + for inst in block.body().iter() { + materialized_args.clear(); + block_arg_uses.clear(); + + let inst_index = inst.get_or_compute_order(); + let inst_id = inst.as_operation_ref(); + let node_id = graph.add_node(Node::Inst { + op: inst_id, + pos: inst_index as u16, + }); + + let pp = ProgramPoint::before(&*inst); + for arg in inst.operands().group(0).iter().copied() { + let value = arg.borrow().as_value_ref(); + materialized_args.insert(value); + let arg_node = ArgumentNode::Direct(arg); + graph.add_data_dependency(node_id, arg_node, value, pp); + } + + // Ensure all result nodes are added to the graph, otherwise unused results will not be + // present in the graph which will cause problems when we check for those results later + for (result_idx, result) in inst.results().iter().copied().enumerate() { + let result_node = Node::Result { + value: result, + index: result_idx as u8, + }; + let result_node_id = graph.add_node(result_node); + graph.add_dependency(result_node_id, node_id); + } + + match inst.num_successors() { + 0 => {} + 1 => { + // Add edges representing these data dependencies in later blocks + for arg in inst.successor(0).arguments.into_iter().copied() { + let value = arg.borrow().as_value_ref(); + let arg_node = ArgumentNode::Indirect(arg); + graph.add_data_dependency(node_id, arg_node, value, pp); + } + } + _ => { + // Preprocess the arguments which are used so we can determine materialization + // requirements + for succ in inst.successor_iter() { + for arg in succ.arguments.iter() { + let arg = arg.borrow().as_value_ref(); + block_arg_uses + .entry(arg) + .or_insert_with(Default::default) + .insert(succ.successor()); + } + } + + // For each successor, check if we should implicitly require an argument along that + // edge due to liveness analysis indicating that it is used + // somewhere downstream. We only consider block arguments passed to + // at least one other successor, and which are not already explicitly + // provided to this successor. + let materialization_threshold = inst.num_successors(); + // Finally, add edges to the dependency graph representing the nature of each + // argument + for succ in inst.successor_iter() { + for arg in succ.arguments.iter().copied() { + let value = arg.borrow().as_value_ref(); + let is_conditionally_materialized = + block_arg_uses[&value].len() < materialization_threshold; + let must_materialize = + materialized_args.contains(&value) || !is_conditionally_materialized; + let arg_node = if must_materialize { + ArgumentNode::Indirect(arg) + } else { + ArgumentNode::Conditional(arg) + }; + graph.add_data_dependency(node_id, arg_node, value, pp); + } + } + } + } + } + + // HACK: If there are any instruction nodes with no predecessors, with the exception of the + // block terminator, then we must add a control dependency to the graph to reflect the fact + // that the instruction must have been placed in this block intentionally. However, we are + // free to schedule the instruction as we see fit to avoid de-optimizing the normal + // instruction schedule unintentionally. + // + // We also avoid adding control dependencies for instructions without side effects that are not + // live beyond the current block, as those are dead code and should be eliminated by DCE anyway + // + // The actual scheduling decision for the instruction is deferred to `analyze_inst`, where we + // treat the instruction similarly to argument materialization, and either make it a + // pre-requisite of the instruction or execute it in the post-execution phase depending on + // the terminator type + assign_effect_dependencies(&mut graph, &block, liveness); + + // Eliminate dead code as indicated by the state of the dependency graph + //dce(&mut graph, block_id, function, liveness); + + graph +} + +/// This function performs two primary tasks: +/// +/// 1. Ensure that there are edges in the graph between instructions that must not be reordered +/// past each other during scheduling due to effects on a shared resource or global resources. +/// An obvious example is loads and stores: you might have two independent expression trees that +/// read and write to the same memory location - but if the order in which the corresponding +/// loads and stores are scheduled changes, it can change the behavior of the program. Other +/// examples include function calls with side effects and inline assembly. +/// +/// 2. Ensure that even if an instruction has no predecessors, it still gets scheduled if it has +/// side effects. This can happen if there are no other effectful instructions in the same block. +/// We add an edge from the block terminator to these instructions, which will guarantee that +/// they are executed before leaving the block. +/// +/// This step is essential for selecting a correct instruction schedule, as effect-free instructions +/// can be freely reordered, ideal for optimization, whereas reordering instructions with effects +/// must be done very conservatively. For example, the IR allows us to represent effects against +/// a specific symbol (e.g. global variable), however there are instructions that allow one to +/// materialize a pointer to that symbol as an SSA value, and then pass that around. Thus, +/// reordering two operations around each other, one that affects the symbol and one that affects a +/// value representing a pointer to that symbol, is not safe, even though initially it may appear +/// that they affect different areas of memory. +/// +/// NOTE: This function only assigns control dependencies for instructions _with_ side effects. An +/// instruction with no dependents, and no side effects, is treated as dead code, since by +/// definition its effects cannot be visible. +fn assign_effect_dependencies( + graph: &mut DependencyGraph, + block: &Block, + liveness: &LivenessAnalysis, +) { + let terminator = { + let op = block.terminator().unwrap(); + let index = op.borrow().get_or_compute_order(); + Node::Inst { + op, + pos: index as u16, + } + }; + + let mut reads: Vec<(Node, Option)> = vec![]; + let mut writes: Vec<(Node, Option)> = vec![]; + + let mut handle_effects = |op: &Operation, + node: Node, + interface: &dyn EffectOpInterface, + graph: &mut DependencyGraph| { + if interface.has_no_effect() { + return; + } + + for effect in interface.effects() { + if matches!(effect.effect(), MemoryEffect::Read | MemoryEffect::Write) { + // Look for any stores to either the entire heap or the same value + + // If the effect applies to a specific value, look for the last store of that value + let value = effect.value(); + if let Some(value) = value { + if let Some(last_store) = writes.iter().find_map(|(prev_store, written_to)| { + if written_to.is_none_or(|v| v == value) { + Some(*prev_store) + } else { + None + } + }) { + // Only add the dependency if there is no path from this instruction + // to that one + if !graph.is_reachable_from(node, last_store) { + graph.add_dependency(node, last_store); + } + } + } else if let Some(_symbol) = effect.symbol() { + // If the effect applies to a specific symbol, look for the last store of that + // symbol + // + // TODO(pauls): Use this to narrow the scope for reads/writes of a global + // variable + + // For now we just look for the last write to any value + if let Some((last_store, _)) = writes.last() { + // Only add the dependency if there is no path from this instruction + // to that one + if !graph.is_reachable_from(node, *last_store) { + graph.add_dependency(node, *last_store); + } + } + } else { + // This is an unscoped read/write, so order with regard to any other write + if let Some((last_store, _)) = writes.last() { + // Only add the dependency if there is no path from this instruction + // to that one + if !graph.is_reachable_from(node, *last_store) { + graph.add_dependency(node, *last_store); + } + } + } + + if matches!(effect.effect(), MemoryEffect::Read) { + reads.push((node, value)); + continue; + } + + // This is a write effect: have there been any loads observed? + if let Some(value) = value { + if let Some(last_read) = reads.iter().find_map(|(prev_read, read_from)| { + if read_from.is_none_or(|v| v == value) { + Some(*prev_read) + } else { + None + } + }) { + // Only add the dependency if there is no path from this instruction + // to that one + if !graph.is_reachable_from(node, last_read) { + graph.add_dependency(node, last_read); + } + } + } else if let Some(_symbol) = effect.symbol() { + // If the effect applies to a specific symbol, look for the last read of that + // symbol + // + // TODO(pauls): Use this to narrow the scope for reads/writes of a global + // variable + + // For now we just look for the last read to any value + if let Some((last_read, _)) = reads.last() { + // Only add the dependency if there is no path from this instruction + // to that one + if !graph.is_reachable_from(node, *last_read) { + graph.add_dependency(node, *last_read); + } + } + } else { + // This is an unscoped write, so order with regard to any other read + if let Some((last_read, _)) = reads.last() { + // Only add the dependency if there is no path from this instruction + // to that one + if !graph.is_reachable_from(node, *last_read) { + graph.add_dependency(node, *last_read); + } + } + } + + writes.push((node, value)); + } else { + // We treat other memory effects as global read/writes + if let Some((last_write, _)) = writes.last() { + if !graph.is_reachable_from(node, *last_write) { + graph.add_dependency(node, *last_write); + } + } + if let Some((last_read, _)) = reads.last() { + if !graph.is_reachable_from(node, *last_read) { + graph.add_dependency(node, *last_read); + } + } + reads.push((node, None)); + writes.push((node, None)); + } + } + }; + + for op in block.body().iter() { + // Skip the block terminator + if op.implements::() { + continue; + } + + let inst_index = op.get_or_compute_order(); + let node = Node::Inst { + op: op.as_operation_ref(), + pos: inst_index as u16, + }; + + // Does this instruction have memory effects? + // + // If it reads memory, ensure that there is an edge in the graph from the last observed + // store. In effect, this makes the read dependent on the most recent write, even if there + // is no direct connection between the two instructions otherwise. + // + // If it writes memory, ensure that there is an edge in the graph from the last observed + // load. In effect, this makes the write dependent on the most recent read, even if there + // is no direct connection between the two instructions otherwise. + // + // If it both reads and writes, ensure there are edges to both the last load and store. + let has_side_effects = !op.is_memory_effect_free(); + if let Some(memory_effects) = op.as_trait::>() { + handle_effects(&op, node, memory_effects, graph) + } + + // At this point, we want to handle adding a control dependency from the terminator to this + // instruction, if there are no other nodes on which to attach one, and if the instruction + // requires one. + + // Skip instructions with transitive dependents on at least one result, or a direct + // dependent + let has_dependents = graph.predecessors(node).any(|pred| { + if pred.dependent.is_result() { + graph.num_predecessors(pred.dependent) > 0 + } else { + true + } + }); + if has_dependents { + continue; + } + + // Instructions with no side effects require a control dependency if at least + // one result is live after the end of the current block. We add the dependency + // to the instruction results if present, otherwise to the instruction itself. + let mut live_results = SmallVec::<[Node; 2]>::default(); + for pred in graph.predecessors(node) { + match pred.dependent { + Node::Result { value, .. } => { + let is_live_after = + liveness.is_live_at_end(value as ValueRef, block.as_block_ref()); + if is_live_after { + live_results.push(pred.dependent); + } + } + _ => continue, + } + } + + let has_live_results = !live_results.is_empty(); + for result_node in live_results.into_iter() { + graph.add_dependency(terminator, result_node); + } + + // Instructions with side effects but no live results require a control dependency + if has_side_effects && !has_live_results { + // Only add one if there is no other transitive dependency that accomplishes + // the same goal + if !graph.is_reachable_from(terminator, node) { + graph.add_dependency(terminator, node); + } + continue; + } + } +} + +/* +fn dce( + graph: &mut DependencyGraph, + block_id: hir::Block, + function: &hir::Function, + liveness: &LivenessAnalysis, +) { + // Perform dead-code elimination + // + // Find all instruction nodes in the graph, and if none of the instruction results + // are used, or are live beyond it's containing block; and the instruction has no + // side-effects, then remove all of the nodes related to that instruction, continuing + // until there are no more nodes to process. + let mut worklist = VecDeque::<(hir::Inst, NodeId)>::from_iter( + function.dfg.block_insts(block_id).enumerate().map(|(i, inst)| { + ( + inst, + Node::Inst { + id: inst, + pos: i as u16, + } + .into(), + ) + }), + ); + let mut remove_nodes = Vec::::default(); + while let Some((inst, inst_node)) = worklist.pop_front() { + // If the instruction is not dead at this point, leave it alone + if !is_dead_instruction(inst, block_id, function, liveness, graph) { + continue; + } + let inst_block = function.dfg.insts[inst].block; + let inst_args = function.dfg.inst_args(inst); + let branch_info = function.dfg.analyze_branch(inst); + // Visit the immediate successors of the instruction node in the dependency graph, + // these by construction may only be Argument or BlockArgument nodes. + for succ in graph.successors(inst_node) { + let dependency_node_id = succ.dependency; + // For each argument, remove the edge from instruction to argument, and from + // argument to the item it references. If the argument references an instruction + // result in the same block, add that instruction back to the worklist to check + // again in case we have made it dead + match succ.dependency.into() { + Node::Argument(ArgumentNode::Direct { index, .. }) => { + let value = inst_args[index as usize]; + match function.dfg.value_data(value) { + hir::ValueData::Inst { + inst: value_inst, .. + } => { + let value_inst = *value_inst; + let value_inst_block = function.dfg.insts[value_inst].block; + if value_inst_block == inst_block { + let pos = function + .dfg + .block_insts(inst_block) + .position(|id| id == value_inst) + .unwrap(); + // Check `value_inst` later to see if it has been made dead + worklist.push_back(( + value_inst, + Node::Inst { + id: value_inst, + pos: pos as u16, + } + .into(), + )); + } + } + hir::ValueData::Param { .. } => {} + } + } + Node::Argument( + ArgumentNode::Indirect { + successor, index, .. + } + | ArgumentNode::Conditional { + successor, index, .. + }, + ) => { + let successor = successor as usize; + let index = index as usize; + let value = match &branch_info { + BranchInfo::SingleDest(succ) => { + assert_eq!(successor, 0); + succ.args[index] + } + BranchInfo::MultiDest(ref succs) => succs[successor].args[index], + BranchInfo::NotABranch => unreachable!( + "indirect/conditional arguments are only valid as successors of a \ + branch instruction" + ), + }; + match function.dfg.value_data(value) { + hir::ValueData::Inst { + inst: value_inst, .. + } => { + let value_inst = *value_inst; + let value_inst_block = function.dfg.insts[value_inst].block; + if value_inst_block == inst_block { + let pos = function + .dfg + .block_insts(inst_block) + .position(|id| id == value_inst) + .unwrap(); + // Check `value_inst` later to see if it has been made dead + worklist.push_back(( + value_inst, + Node::Inst { + id: value_inst, + pos: pos as u16, + } + .into(), + )); + } + } + hir::ValueData::Param { .. } => {} + } + } + // This is a control dependency added intentionally, skip it + Node::Inst { .. } => continue, + // No other node types are possible + Node::Result { .. } | Node::Stack(_) => { + unreachable!("invalid successor for instruction node") + } + } + remove_nodes.push(dependency_node_id); + } + + // Remove all of the result nodes because the instruction is going away + for pred in graph.predecessors(inst_node) { + remove_nodes.push(pred.dependent); + } + + // Remove the instruction last + remove_nodes.push(inst_node); + + // All of the nodes to be removed are queued, so remove them now before we proceed + for remove_id in remove_nodes.iter().copied() { + graph.remove_node(remove_id); + } + } +} + */ + +/* +fn is_dead_instruction( + op: &Operation, + block_id: BlockRef, + liveness: &LivenessAnalysis, + graph: &DependencyGraph, +) -> bool { + if op.is_trivially_dead() { + return true; + } + + let is_live = op.results().iter().copied().enumerate().any(|(result_idx, result)| { + let result_node = Node::Result { + value: result, + index: result_idx as u8, + }; + if graph.num_predecessors(result_node) > 0 { + return true; + } + liveness.is_live_at_end(result as ValueRef, block_id) + }); + + !is_live && op.is_memory_effect_free() +} +*/ diff --git a/hir-transform/src/sink.rs b/hir-transform/src/sink.rs new file mode 100644 index 000000000..c5c97ef1f --- /dev/null +++ b/hir-transform/src/sink.rs @@ -0,0 +1,601 @@ +use alloc::vec::Vec; + +use midenc_hir::{ + adt::SmallDenseMap, + dominance::DominanceInfo, + matchers::{self, Matcher}, + pass::{Pass, PassExecutionState, PostPassStatus}, + traits::{ConstantLike, Terminator}, + Backward, Builder, EntityMut, Forward, FxHashSet, OpBuilder, Operation, OperationName, + OperationRef, ProgramPoint, RawWalk, Region, RegionBranchOpInterface, + RegionBranchTerminatorOpInterface, RegionRef, Report, SmallVec, Usable, ValueRef, +}; + +/// This transformation sinks operations as close as possible to their uses, one of two ways: +/// +/// 1. If there exists only a single use of the operation, move it before it's use so that it is +/// in an ideal position for code generation. +/// +/// 2. If there exist multiple uses, materialize a duplicate operation for all but one of the uses, +/// placing them before the use. The last use will receive the original operation. +/// +/// To make this rewrite even more useful, we take care to place the operation at a position before +/// the using op, such that when generating code, the operation value will be placed on the stack +/// at the appropriate place relative to the other operands of the using op. This makes the operand +/// stack scheduling optimizer's job easier. +/// +/// The purpose of this rewrite is to improve the quality of generated code by reducing the live +/// ranges of values that are trivial to materialize on-demand. +/// +/// # Restrictions +/// +/// This transform will not sink operations under the following conditions: +/// +/// * The operation has side effects +/// * The operation is a block terminator +/// * The operation has regions +/// +/// # Implementation +/// +/// Given a list of regions, perform control flow sinking on them. For each region, control-flow +/// sinking moves operations that dominate the region but whose only users are in the region into +/// the regions so that they aren't executed on paths where their results are not needed. +/// +/// TODO: For the moment, this is a *simple* control-flow sink, i.e., no duplicating of ops. It +/// should be made to accept a cost model to determine whether duplicating a particular op is +/// profitable. +/// +/// Example: +/// +/// ```mlir +/// %0 = arith.addi %arg0, %arg1 +/// scf.if %cond { +/// scf.yield %0 +/// } else { +/// scf.yield %arg2 +/// } +/// ``` +/// +/// After control-flow sink: +/// +/// ```mlir +/// scf.if %cond { +/// %0 = arith.addi %arg0, %arg1 +/// scf.yield %0 +/// } else { +/// scf.yield %arg2 +/// } +/// ``` +/// +/// If using the `control_flow_sink` function, callers can supply a callback +/// `should_move_into_region` that determines whether the given operation that only has users in the +/// given operation should be moved into that region. If this returns true, `move_into_region` is +/// called on the same operation and region. +/// +/// `move_into_region` must move the operation into the region such that dominance of the operation +/// is preserved; for example, by moving the operation to the start of the entry block. This ensures +/// the preservation of SSA dominance of the operation's results. +pub struct ControlFlowSink; + +impl Pass for ControlFlowSink { + type Target = Operation; + + fn name(&self) -> &'static str { + "control-flow-sink" + } + + fn argument(&self) -> &'static str { + "control-flow-sink" + } + + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + let op = op.into_entity_ref(); + log::debug!(target: "control-flow-sink", "sinking operations in {op}"); + + let operation = op.as_operation_ref(); + drop(op); + + let dominfo = state.analysis_manager().get_analysis::()?; + + let mut sunk = PostPassStatus::Unchanged; + operation.raw_prewalk_all::(|op: OperationRef| { + let regions_to_sink = { + let op = op.borrow(); + let Some(branch) = op.as_trait::() else { + return; + }; + let mut regions = SmallVec::<[_; 4]>::default(); + // Get the regions are that known to be executed at most once. + get_singly_executed_regions_to_sink(branch, &mut regions); + regions + }; + + // Sink side-effect free operations. + sunk = control_flow_sink( + ®ions_to_sink, + &dominfo, + |op: &Operation, _region: &Region| op.is_memory_effect_free(), + |mut op: OperationRef, region: RegionRef| { + // Move the operation to the beginning of the region's entry block. + // This guarantees the preservation of SSA dominance of all of the + // operation's uses are in the region. + let entry_block = region.borrow().entry_block_ref().unwrap(); + op.borrow_mut().move_to(ProgramPoint::at_start_of(entry_block)); + }, + ); + }); + + state.set_post_pass_status(sunk); + + Ok(()) + } +} + +/// This transformation sinks constants as close as possible to their uses, one of two ways: +/// +/// 1. If there exists only a single use of the constant, move it before it's use so that it is +/// in an ideal position for code generation. +/// +/// 2. If there exist multiple uses, materialize a duplicate constant for all but one of the uses, +/// placing them before the use. The last use will receive the original constant. +/// +/// To make this rewrite even more useful, we take care to place the constant at a position before +/// the using op, such that when generating code, the constant value will be placed on the stack +/// at the appropriate place relative to the other operands of the using op. This makes the operand +/// stack scheduling optimizer's job easier. +/// +/// The purpose of this rewrite is to improve the quality of generated code by reducing the live +/// ranges of values that are trivial to materialize on-demand. +pub struct SinkOperandDefs; + +impl Pass for SinkOperandDefs { + type Target = Operation; + + fn name(&self) -> &'static str { + "sink-operand-defs" + } + + fn argument(&self) -> &'static str { + "sink-operand-defs" + } + + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + let operation = op.as_operation_ref(); + drop(op); + + log::debug!(target: "sink-operand-defs", "sinking operand defs for regions of {}", operation.borrow()); + + // For each operation, we enqueue it in this worklist, we then recurse on each of it's + // dependency operations until all dependencies have been visited. We move up blocks from + // the bottom, and skip any operations we've already visited. Once the queue is built, we + // then process the worklist, moving everything into position. + let mut worklist = alloc::collections::VecDeque::default(); + + let mut changed = PostPassStatus::Unchanged; + // Visit ops in "true" post-order (i.e. block bodies are visited bottom-up). + operation.raw_postwalk_all::(|operation: OperationRef| { + // Determine if any of this operation's operands represent one of the following: + // + // 1. A constant value + // 2. The sole use of the defining op's single result, and that op has no side-effects + // + // If 1, then we either materialize a fresh copy of the constant, or move the original + // if there are no more uses. + // + // In both cases, to the extent possible, we order operand dependencies such that the + // values will be on the Miden operand stack in the correct order. This means that we + // visit operands in reverse order, and move defining ops directly before `op` when + // possible. Some values may be block arguments, or refer to op's we're unable to move, + // and thus those values be out of position on the operand stack, but the overall + // result will reduce the amount of unnecessary stack movement. + let op = operation.borrow(); + + log::trace!(target: "sink-operand-defs", "visiting {op}"); + + for operand in op.operands().iter().rev() { + let value = operand.borrow(); + let value = value.value(); + let is_sole_user = value.iter_uses().all(|user| user.owner == operation); + + let Some(defining_op) = value.get_defining_op() else { + // Skip block arguments, nothing to move in that situation + // + // NOTE: In theory, we could move effect-free operations _up_ the block to place + // them closer to the block arguments they use, but that's unlikely to be all + // that profitable of a rewrite in practice. + log::trace!(target: "sink-operand-defs", " ignoring block argument operand '{value}'"); + continue; + }; + + log::trace!(target: "sink-operand-defs", " evaluating operand '{value}'"); + + let def = defining_op.borrow(); + if def.implements::() { + log::trace!(target: "sink-operand-defs", " defining '{}' is constant-like", def.name()); + worklist.push_back(OpOperandSink::new(operation)); + break; + } + + let incorrect_result_count = def.num_results() != 1; + let has_effects = !def.is_memory_effect_free(); + if !is_sole_user || incorrect_result_count || has_effects { + // Skip this operand if the defining op cannot be safely moved + // + // NOTE: For now we do not move ops that produce more than a single result, but + // if the other results are unused, or the users would still be dominated by + // the new location, then we could still move those ops. + log::trace!(target: "sink-operand-defs", " defining '{}' cannot be moved:", def.name()); + log::trace!(target: "sink-operand-defs", " * op has multiple uses"); + if incorrect_result_count { + log::trace!(target: "sink-operand-defs", " * op has incorrect number of results ({})", def.num_results()); + } + if has_effects { + log::trace!(target: "sink-operand-defs", " * op has memory effects"); + } + } else { + log::trace!(target: "sink-operand-defs", " defining '{}' is moveable, but is non-constant", def.name()); + worklist.push_back(OpOperandSink::new(operation)); + break; + } + } + }); + + for sinker in worklist.iter() { + log::debug!(target: "sink-operand-defs", "sink scheduled for {}", sinker.operation.borrow()); + } + + let mut visited = FxHashSet::default(); + let mut erased = FxHashSet::default(); + 'next_operation: while let Some(mut sink_state) = worklist.pop_front() { + let mut operation = sink_state.operation; + let op = operation.borrow(); + + // If this operation is unused, remove it now if it has no side effects + let is_memory_effect_free = + op.is_memory_effect_free() || op.implements::(); + if !op.is_used() + && is_memory_effect_free + && !op.implements::() + && !op.implements::() + && erased.insert(operation) + { + log::debug!(target: "sink-operand-defs", "erasing unused, effect-free, non-terminator op {op}"); + drop(op); + operation.borrow_mut().erase(); + continue; + } + + // If we've already worked this operation, skip it + if !visited.insert(operation) && sink_state.next_operand_index == op.num_operands() { + log::trace!(target: "sink-operand-defs", "already visited {}", operation.borrow()); + continue; + } else { + log::trace!(target: "sink-operand-defs", "visiting {}", operation.borrow()); + } + + let mut builder = OpBuilder::new(op.context_rc()); + builder.set_insertion_point(sink_state.ip); + 'next_operand: loop { + // The next operand index starts at `op.num_operands()` when first initialized, so + // we subtract 1 immediately to get the actual index of the current operand + let Some(next_operand_index) = sink_state.next_operand_index.checked_sub(1) else { + // We're done processing this operation's operands + break; + }; + + log::debug!(target: "sink-operand-defs", " sinking next operand def for {op} at index {next_operand_index}"); + + let mut operand = op.operands()[next_operand_index]; + sink_state.next_operand_index = next_operand_index; + let operand_value = operand.borrow().as_value_ref(); + log::trace!(target: "sink-operand-defs", " visiting operand {operand_value}"); + + // Reuse moved/materialized replacements when the same operand is used multiple times + if let Some(replacement) = sink_state.replacements.get(&operand_value).copied() { + if replacement != operand_value { + log::trace!(target: "sink-operand-defs", " rewriting operand {operand_value} as {replacement}"); + operand.borrow_mut().set(replacement); + + changed = PostPassStatus::Changed; + // If no other uses of this value remain, then remove the original + // operation, as it is now dead. + if !operand_value.borrow().is_used() { + log::trace!(target: "sink-operand-defs", " {operand_value} is no longer used, erasing definition"); + // Replacements are only ever for op results + let mut defining_op = operand_value.borrow().get_defining_op().unwrap(); + defining_op.borrow_mut().erase(); + } + } + continue 'next_operand; + } + + let value = operand_value.borrow(); + let is_sole_user = value.iter_uses().all(|user| user.owner == operation); + + let Some(mut defining_op) = value.get_defining_op() else { + // Skip block arguments, nothing to move in that situation + // + // NOTE: In theory, we could move effect-free operations _up_ the block to place + // them closer to the block arguments they use, but that's unlikely to be all + // that profitable of a rewrite in practice. + log::trace!(target: "sink-operand-defs", " {value} is a block argument, ignoring.."); + continue 'next_operand; + }; + + log::trace!(target: "sink-operand-defs", " is sole user of {value}? {is_sole_user}"); + + let def = defining_op.borrow(); + if let Some(attr) = matchers::constant().matches(&*def) { + if !is_sole_user { + log::trace!(target: "sink-operand-defs", " defining op is a constant with multiple uses, materializing fresh copy"); + // Materialize a fresh copy of the original constant + let span = value.span(); + let ty = value.ty(); + let Some(new_def) = + def.dialect().materialize_constant(&mut builder, attr, ty, span) + else { + log::trace!(target: "sink-operand-defs", " unable to materialize copy, skipping rewrite of this operand"); + continue 'next_operand; + }; + drop(def); + drop(value); + let replacement = new_def.borrow().results()[0] as ValueRef; + log::trace!(target: "sink-operand-defs", " rewriting operand {operand_value} as {replacement}"); + sink_state.replacements.insert(operand_value, replacement); + operand.borrow_mut().set(replacement); + changed = PostPassStatus::Changed; + } else { + log::trace!(target: "sink-operand-defs", " defining op is a constant with no other uses, moving into place"); + // The original op can be moved + drop(def); + drop(value); + defining_op.borrow_mut().move_to(*builder.insertion_point()); + sink_state.replacements.insert(operand_value, operand_value); + } + } else if !is_sole_user || def.num_results() != 1 || !def.is_memory_effect_free() { + // Skip this operand if the defining op cannot be safely moved + // + // NOTE: For now we do not move ops that produce more than a single result, but + // if the other results are unused, or the users would still be dominated by + // the new location, then we could still move those ops. + log::trace!(target: "sink-operand-defs", " defining op is unsuitable for sinking, ignoring this operand"); + } else { + // The original op can be moved + // + // Determine if we _should_ move it: + // + // 1. If the use is inside a loop, and the def is outside a loop, do not + // move the defining op into the loop unless it is profitable to do so, + // i.e. a cost model indicates it is more efficient than the equivalent + // operand stack movement instructions + // + // 2. + drop(def); + drop(value); + log::trace!(target: "sink-operand-defs", " defining op can be moved and has no other uses, moving into place"); + defining_op.borrow_mut().move_to(*builder.insertion_point()); + sink_state.replacements.insert(operand_value, operand_value); + + // Enqueue the defining op to be visited before continuing with this op's operands + log::trace!(target: "sink-operand-defs", " enqueing defining op for immediate processing"); + //sink_state.ip = *builder.insertion_point(); + sink_state.ip = ProgramPoint::before(operation); + worklist.push_front(sink_state); + worklist.push_front(OpOperandSink::new(defining_op)); + continue 'next_operation; + } + } + } + + state.set_post_pass_status(changed); + Ok(()) + } +} + +struct OpOperandSink { + operation: OperationRef, + ip: ProgramPoint, + replacements: SmallDenseMap, + next_operand_index: usize, +} + +impl OpOperandSink { + pub fn new(operation: OperationRef) -> Self { + Self { + operation, + ip: ProgramPoint::before(operation), + replacements: SmallDenseMap::new(), + next_operand_index: operation.borrow().num_operands(), + } + } +} + +/// A helper struct for control-flow sinking. +struct Sinker<'a, P, F> { + /// Dominance info to determine op user dominance with respect to regions. + dominfo: &'a DominanceInfo, + /// The callback to determine whether an op should be moved in to a region. + should_move_into_region: P, + /// The calback to move an operation into the region. + move_into_region: F, + /// The number of operations sunk + num_sunk: usize, +} +impl<'a, P, F> Sinker<'a, P, F> +where + P: Fn(&Operation, &Region) -> bool, + F: Fn(OperationRef, RegionRef), +{ + /// Create an operation sinker with given dominance info. + pub fn new( + dominfo: &'a DominanceInfo, + should_move_into_region: P, + move_into_region: F, + ) -> Self { + Self { + dominfo, + should_move_into_region, + move_into_region, + num_sunk: 0, + } + } + + /// Given a list of regions, find operations to sink and sink them. + /// + /// Returns the number of operations sunk. + pub fn sink_regions(mut self, regions: &[RegionRef]) -> usize { + for region in regions.iter().copied() { + if !region.borrow().is_empty() { + self.sink_region(region); + } + } + + self.num_sunk + } + + /// Given a region and an op which dominates the region, returns true if all + /// users of the given op are dominated by the entry block of the region, and + /// thus the operation can be sunk into the region. + fn all_users_dominated_by(&self, op: &Operation, region: &Region) -> bool { + assert!( + region.find_ancestor_op(op.as_operation_ref()).is_none(), + "expected op to be defined outside the region" + ); + let region_entry = region.entry_block_ref().unwrap(); + op.results().iter().all(|result| { + let result = result.borrow(); + result.iter_uses().all(|user| { + // The user is dominated by the region if its containing block is dominated + // by the region's entry block. + self.dominfo.dominates(®ion_entry, &user.owner.parent().unwrap()) + }) + }) + } + + /// Given a region and a top-level op (an op whose parent region is the given + /// region), determine whether the defining ops of the op's operands can be + /// sunk into the region. + /// + /// Add moved ops to the work queue. + fn try_to_sink_predecessors( + &mut self, + user: OperationRef, + region: RegionRef, + stack: &mut Vec, + ) { + log::trace!(target: "control-flow-sink", "contained op: {}", user.borrow()); + let user = user.borrow(); + for operand in user.operands().iter() { + let op = operand.borrow().value().get_defining_op(); + // Ignore block arguments and ops that are already inside the region. + if op.is_none_or(|op| op.grandparent().is_some_and(|r| r == region)) { + continue; + } + + let op = unsafe { op.unwrap_unchecked() }; + + log::trace!(target: "control-flow-sink", "try to sink op: {}", op.borrow()); + + // If the op's users are all in the region and it can be moved, then do so. + let (all_users_dominated_by, should_move_into_region) = { + let op = op.borrow(); + let region = region.borrow(); + let all_users_dominated_by = self.all_users_dominated_by(&op, ®ion); + let should_move_into_region = (self.should_move_into_region)(&op, ®ion); + (all_users_dominated_by, should_move_into_region) + }; + if all_users_dominated_by && should_move_into_region { + (self.move_into_region)(op, region); + + self.num_sunk += 1; + + // Add the op to the work queue + stack.push(op); + } + } + } + + /// Iterate over all the ops in a region and try to sink their predecessors. + /// Recurse on subgraphs using a work queue. + fn sink_region(&mut self, region: RegionRef) { + // Initialize the work queue with all the ops in the region. + let mut stack = Vec::new(); + for block in region.borrow().body() { + for op in block.body() { + stack.push(op.as_operation_ref()); + } + } + + // Process all the ops depth-first. This ensures that nodes of subgraphs are sunk in the + // correct order. + while let Some(op) = stack.pop() { + self.try_to_sink_predecessors(op, region, &mut stack); + } + } +} + +pub fn control_flow_sink( + regions: &[RegionRef], + dominfo: &DominanceInfo, + should_move_into_region: P, + move_into_region: F, +) -> PostPassStatus +where + P: Fn(&Operation, &Region) -> bool, + F: Fn(OperationRef, RegionRef), +{ + let sinker = Sinker::new(dominfo, should_move_into_region, move_into_region); + let sunk_regions = sinker.sink_regions(regions); + (sunk_regions > 0).into() +} + +/// Populates `regions` with regions of the provided region branch op that are executed at most once +/// at that are reachable given the current operands of the op. These regions can be passed to +/// `control_flow_sink` to perform sinking on the regions of the operation. +fn get_singly_executed_regions_to_sink( + branch: &dyn RegionBranchOpInterface, + regions: &mut SmallVec<[RegionRef; 4]>, +) { + use midenc_hir::matchers::Matcher; + + // Collect constant operands. + let mut operands = SmallVec::<[_; 4]>::with_capacity(branch.num_operands()); + + for operand in branch.operands().iter() { + let matcher = matchers::foldable_operand(); + operands.push(matcher.matches(operand)); + } + + // Get the invocation bounds. + let bounds = branch.get_region_invocation_bounds(&operands); + + // For a simple control-flow sink, only consider regions that are executed at most once. + for (region, bound) in branch.regions().iter().zip(bounds) { + use core::range::Bound; + match bound.max() { + Bound::Unbounded => continue, + Bound::Excluded(bound) if *bound > 2 => continue, + Bound::Excluded(0) => continue, + Bound::Included(bound) if *bound > 1 => continue, + _ => { + regions.push(region.as_region_ref()); + } + } + } +} diff --git a/hir-transform/src/spill.rs b/hir-transform/src/spill.rs index b9e69ae10..94e3111f2 100644 --- a/hir-transform/src/spill.rs +++ b/hir-transform/src/spill.rs @@ -1,476 +1,596 @@ -#![allow(clippy::mutable_key_type)] -use std::collections::{BTreeMap, VecDeque}; +use alloc::{collections::VecDeque, rc::Rc}; use midenc_hir::{ - self as hir, - adt::{SmallMap, SmallSet}, - pass::{AnalysisManager, RewritePass, RewriteResult}, - *, + adt::{SmallDenseMap, SmallSet}, + cfg::Graph, + dominance::{DomTreeNode, DominanceFrontier, DominanceInfo}, + pass::{AnalysisManager, PostPassStatus}, + traits::SingleRegion, + BlockRef, Builder, Context, FxHashMap, OpBuilder, OpOperand, Operation, OperationRef, + ProgramPoint, Region, RegionBranchOpInterface, RegionBranchPoint, RegionRef, Report, Rewriter, + SmallVec, SourceSpan, Spanned, StorableEntity, Usable, ValueRange, ValueRef, }; -use midenc_hir_analysis::{ - spill::Placement, ControlFlowGraph, DominanceFrontier, DominatorTree, SpillAnalysis, Use, User, +use midenc_hir_analysis::analyses::{ + spills::{Placement, Predecessor}, + SpillAnalysis, }; -use midenc_session::{diagnostics::IntoDiagnostic, Emit, Session}; -use rustc_hash::FxHashSet; -/// This pass places spills of SSA values to temporaries to cap the depth of the operand stack. -/// -/// Internally it handles orchestrating the [InsertSpills] and [RewriteSpills] passes, and should -/// be preferred over using those two passes directly. See their respective documentation to better -/// understand what this pass does as a whole. -/// -/// In addition to running the two passes, and maintaining the [AnalysisManager] state between them, -/// this pass also handles applying an additional run of [crate::InlineBlocks] if spills were -/// introduced, so as to ensure that the output of the spills transformation is cleaned up. As -/// applying a pass conditionally like that is a bit tricky, we handle that here to ensure that is -/// a detail downstream users do not have to deal with. -#[derive(Default, PassInfo, ModuleRewritePassAdapter)] -pub struct ApplySpills; -impl RewritePass for ApplySpills { - type Entity = hir::Function; - - fn apply( +/// This interface is used in conjunction with [transform_spills] so that the transform can be used +/// with any dialect, and more importantly, avoids forming a dependency on our own dialects for the +/// subset of operations we need to emit/rewrite. +pub trait TransformSpillsInterface { + /// Create an unconditional branch to `destination` + fn create_unconditional_branch( + &self, + builder: &mut OpBuilder, + destination: BlockRef, + arguments: &[ValueRef], + span: SourceSpan, + ) -> Result<(), Report>; + + /// Create a spill for `value`, returning the spill instruction + fn create_spill( + &self, + builder: &mut OpBuilder, + value: ValueRef, + span: SourceSpan, + ) -> Result; + + /// Create a reload of `value`, returning the reload instruction + fn create_reload( + &self, + builder: &mut OpBuilder, + value: ValueRef, + span: SourceSpan, + ) -> Result; + + /// Convert `spill`, a [SpillLike] operation, into a primitive memory store of the spilled + /// value. + fn convert_spill_to_store( &mut self, - function: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - use midenc_hir::pass::{RewriteFn, RewriteSet}; - - let mut rewrites = RewriteSet::pair(InsertSpills, RewriteSpills); - - // If the spills transformation is run, we want to run the block inliner again to - // clean up the output, but _only_ if there were actually spills, otherwise running - // the inliner again will have no effect. To avoid that case, we wrap the second run - // in a closure which will only apply the pass if there were spills - let maybe_rerun_block_inliner: Box> = Box::new( - |function: &mut hir::Function, - analyses: &mut AnalysisManager, - session: &Session| - -> RewriteResult { - let has_spills = analyses - .get::(&function.id) - .map(|spills| spills.has_spills()) - .unwrap_or(false); - if has_spills { - let mut inliner = crate::InlineBlocks; - inliner.apply(function, analyses, session) - } else { - Ok(()) - } - }, - ); - rewrites.push(maybe_rerun_block_inliner); - - // Apply the above collectively - rewrites.apply(function, analyses, session)?; - - session.print(&*function, Self::FLAG).into_diagnostic()?; - if session.should_print_cfg(Self::FLAG) { - use std::io::Write; - let cfg = function.cfg_printer(); - let mut stdout = std::io::stdout().lock(); - write!(&mut stdout, "{cfg}").into_diagnostic()?; - } - Ok(()) - } + rewriter: &mut dyn Rewriter, + spill: OperationRef, + ) -> Result<(), Report>; + + /// Convert `reload`, a [ReloadLike] operation, into a primitive memory load of the spilled + /// value. + fn convert_reload_to_load( + &mut self, + rewriter: &mut dyn Rewriter, + reload: OperationRef, + ) -> Result<(), Report>; } -/// This pass inserts spills and reloads as computed by running a [SpillAnalysis] on the given -/// function, recording materialized splits, spills, and reloads in the analysis results. -/// -/// **IMPORTANT:** This pass is intended to be combined with the [RewriteSpills] pass when used -/// as part of a compilation pipeline - it performs the first phase of a two-phase transformation, -/// and compilation _will_ fail if you forget to apply [RewriteSpills] after this pass when the -/// [SpillAnalysis] directed spills to be injected. -/// -/// ## Design -/// -/// The full spills transformation is split into an analysis and two rewrite passes, corresponding -/// to the three phases of the transformation: +/// An operation trait for operations that implement spill-like behavior for purposes of the +/// spills transformation/rewrite. /// -/// 1. Analyze the function to determine if and when to spill values, which values to spill, and -/// where to place reloads. -/// 2. Insert the computed spills and reloads, temporarily breaking the SSA form of the program -/// 3. Reconstruct SSA form, by rewriting uses of spilled values to use the nearest dominating -/// definition, inserting block parameters as needed to ensure that all uses are strictly -/// dominated by the corresponding definitions. -/// -/// Additionally, splitting it up this way makes each phase independently testable and verifiable, -/// essential due to the complexity of the overall transformation. -/// -/// This pass corresponds to Phase 2 above, application of computed spills and reloads. It is very -/// simple, and can be seen as essentially materializing the analysis results in the IR. In addition -/// to setting the stage for Phase 3, this pass can also be used to validate that the scheduling of -/// spills and reloads is correct, matching the order in which we expect those operations to occur. +/// A spill-like operation is expected to take a single value, and store it somewhere in memory +/// temporarily, such that the live range of the original value is terminated by the spill. Spilled +/// values may then be reloaded, starting a new live range, using the corresponding [ReloadLike] op. +pub trait SpillLike { + /// Returns the operand corresponding to the spilled value + fn spilled(&self) -> OpOperand; + /// Returns a reference to the spilled value + fn spilled_value(&self) -> ValueRef { + self.spilled().borrow().as_value_ref() + } +} + +/// An operation trait for operations that implement reload-like behavior for purposes of the +/// spills transformation/rewrite. /// -/// ## Notes About The Validity of Emitted IR +/// A reload-like operation is expected to take a single value, for which a dominating [SpillLike] +/// op exists, and produce a new, unique SSA value corresponding to the reloaded spill value. The +/// spills transformation will handle rewriting any uses of the [SpillLike] and [ReloadLike] ops +/// such that they are not present after the transformation, in conjunction with an implementation +/// of the [TransformSpillsInterface]. +pub trait ReloadLike { + /// Returns the operand corresponding to the spilled value + fn spilled(&self) -> OpOperand; + /// Returns a reference to the spilled value + fn spilled_value(&self) -> ValueRef { + self.spilled().borrow().as_value_ref() + } + /// Returns the value representing this unique reload of the spilled value + /// + /// Generally, this always corresponds to this op's result + fn reloaded(&self) -> ValueRef; +} + +/// This transformation rewrites `op` by applying the results of the provided [SpillAnalysis], +/// using the provided implementation of the [TransformSpillsInterface]. /// -/// It is implied in my earlier notice, but I want to make it explicit here - this pass may produce -/// IR that is semantically invalid. Such IR is technically valid, and self-consistent, but cannot -/// be compiled to Miden Assembly. First, the spill and reload pseudo-instructions are expected to -/// only ever exist in the IR during application of the [InsertSpills] and [RewriteSpills] passes; -/// later passes do not know how to handle them, and may panic if encountered, particularly the code -/// generation pass, which will raise an error on any unhandled instructions. Second, the semantics -/// of spills and reloads dictates that when a spill occurs, the live range of the spilled value is -/// terminated; and may only be resurrected by an explicit reload of that value. However, because -/// the new definition produced by a reload instruction is not actually used in the IR until after -/// the [RewriteSpills] pass is applied, the IR immediately after the [InsertSpills] pass is -/// semantically invalid - values will be dropped from the operand stack by a spill, yet there will -/// be code later in the same function which expects them to still be live (and thus on the operand -/// stack), which will fail to compile. +/// In effect, it performs the following steps: /// -/// **TL;DR:** Unless testing or debugging, always apply [InsertSpills] and [RewriteSpills] -/// consecutively! -#[derive(Default)] -pub struct InsertSpills; -impl RewritePass for InsertSpills { - type Entity = hir::Function; - - fn apply( - &mut self, - function: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - let mut spills = { - // Compute the spills - let spills = analyses.get_or_compute::(function, session)?; - // If there are no spills to process, we're done - if !spills.has_spills() { - analyses.mark_all_preserved::(&function.id); - return Ok(()); +/// * Splits all control flow edges that need to carry spills/reloads +/// * Inserts all spills and reloads at their computed locations +/// * Rewrites `op` such that all uses of a spilled value dominated by a reload, are rewritten to +/// use that reload, or in the case of crossing a dominance frontier, a materialized block +/// argument/phi representing the closest definition of that value from each predecessor. +/// * Rewrites all spill and reload instructions to their primitive memory store/load ops +pub fn transform_spills( + op: OperationRef, + analysis: &mut SpillAnalysis, + interface: &mut dyn TransformSpillsInterface, + analysis_manager: AnalysisManager, +) -> Result { + assert!( + op.borrow().implements::(), + "the spills transformation is not supported when the root op is multi-region" + ); + + let mut builder = OpBuilder::new(op.borrow().context_rc()); + + log::debug!(target: "insert-spills", "analysis determined that some spills were required"); + log::debug!(target: "insert-spills", " edges to split = {}", analysis.splits().len()); + log::debug!(target: "insert-spills", " values spilled = {}", analysis.spills().len()); + log::debug!(target: "insert-spills", " reloads issued = {}", analysis.reloads().len()); + + // Split all edges along which spills/reloads are required + for split_info in analysis.splits_mut() { + log::trace!(target: "insert-spills", "splitting control flow edge {} -> {}", match split_info.predecessor { + Predecessor::Parent => ProgramPoint::before(split_info.predecessor.operation(split_info.point)), + Predecessor::Block { op, .. } | Predecessor::Region(op) => ProgramPoint::at_end_of(op.parent().unwrap()), + }, split_info.point); + + let predecessor_block = split_info + .predecessor + .block() + .unwrap_or_else(|| todo!("implement support for splits following a region branch op")); + let predecessor_region = predecessor_block.parent().unwrap(); + + // Create the split and switch the insertion point to the end of it + let split = builder.create_block(predecessor_region, Some(predecessor_block), &[]); + log::trace!(target: "insert-spills", "created {split} to hold contents of split edge"); + + // Record the block we created for this split + split_info.split = Some(split); + + // Rewrite the terminator in the predecessor so that it transfers control to the + // original successor via `split`, moving any block arguments into the unconditional + // branch that terminates `split`. + match split_info.predecessor { + Predecessor::Block { mut op, index } => { + log::trace!(target: "insert-spills", "redirecting {predecessor_block} to {split}"); + let mut op = op.borrow_mut(); + let mut succ = op.successor_mut(index as usize); + let prev_dest = succ.dest.parent().unwrap(); + succ.dest.borrow_mut().set(split); + log::trace!(target: "insert-spills", "creating edge from {split} to {prev_dest}"); + let arguments = succ + .arguments + .take() + .into_iter() + .map(|mut operand| { + let mut operand = operand.borrow_mut(); + let value = operand.as_value_ref(); + // It is our responsibility to unlink the operands we removed from `succ` + operand.unlink(); + value + }) + .collect::>(); + match split_info.point { + ProgramPoint::Block { block, .. } => { + assert_eq!( + prev_dest, block, + "unexpected mismatch between predecessor target and successor block" + ); + interface.create_unconditional_branch( + &mut builder, + block, + &arguments, + op.span(), + )?; + } + point => panic!( + "unexpected program point for split: unstructured control flow requires a \ + block entry, got {point}" + ), + } + } + Predecessor::Region(predecessor) => { + log::trace!(target: "insert-spills", "splitting region control flow edge to {} from {predecessor}", split_info.point); + todo!() + } + Predecessor::Parent => unimplemented!( + "support for splits on exit from region branch ops is not yet implemented" + ), + } + } + + // Insert all spills + for spill in analysis.spills.iter_mut() { + let ip = match spill.place { + Placement::Split(split) => { + let split_block = analysis.splits[split.as_usize()] + .split + .expect("expected split to have been materialized"); + let terminator = split_block.borrow().terminator().unwrap(); + ProgramPoint::before(terminator) + } + Placement::At(ip) => ip, + }; + log::trace!(target: "insert-spills", "inserting spill of {} at {ip}", spill.value); + builder.set_insertion_point(ip); + let inst = interface.create_spill(&mut builder, spill.value, spill.span)?; + spill.inst = Some(inst); + } + + // Insert all reloads + for reload in analysis.reloads.iter_mut() { + let ip = match reload.place { + Placement::Split(split) => { + let split_block = analysis.splits[split.as_usize()] + .split + .expect("expected split to have been materialized"); + let terminator = split_block.borrow().terminator().unwrap(); + ProgramPoint::before(terminator) + } + Placement::At(ip) => ip, + }; + log::trace!(target: "insert-spills", "inserting reload of {} at {ip}", reload.value); + builder.set_insertion_point(ip); + let inst = interface.create_reload(&mut builder, reload.value, reload.span)?; + reload.inst = Some(inst); + } + + log::trace!(target: "insert-spills", "all spills and reloads inserted successfully"); + + log::trace!(target: "insert-spills", "op {} after inserting spills: {}", op.name(), op.borrow()); + + let dominfo = analysis_manager.get_analysis::()?; + + let region = op.borrow().regions().front().as_pointer().unwrap(); + if region.borrow().has_one_block() { + rewrite_single_block_spills(op, region, analysis, interface, analysis_manager)?; + } else { + rewrite_cfg_spills( + builder.context_rc(), + region, + analysis, + interface, + &dominfo, + analysis_manager, + )?; + } + + log::trace!(target: "insert-spills", "op {} after rewriting spills: {}", op.name(), op.borrow()); + Ok(PostPassStatus::Changed) +} + +fn rewrite_single_block_spills( + op: OperationRef, + region: RegionRef, + analysis: &mut SpillAnalysis, + interface: &mut dyn TransformSpillsInterface, + _analysis_manager: AnalysisManager, +) -> Result<(), Report> { + // In a flattened CFG with only structured control flow, no dominance tree is required. + // + // Instead, similar to a regular CFG, we walk the region graph in post-order, doing the + // following: + // + // 1. If we encounter a use of a spilled value, we add it to a use list + // 2. If we encounter a reloaded spill, we rewrite any uses found so far to use the reloaded + // value + // 3. If we encounter a spill, then we clear the set of uses of that spill found so far and + // continue + // 4. If we reach the top of a region's entry block, and the region has no predecessors other + // than the containing operation, then we do nothing but continue the traversal. + // 5. If we reach the top of a region's entry block, and the region has multiple predecessors, + // then for each spilled value for which we have found at least one use, we must insert a + // new region argument representing the spilled value, and rewrite all uses to use that + // argument instead. For any dominating predecessors, the original spilled value is passed + // as the value of the new argument. + + struct Node { + block: BlockRef, + cursor: Option, + is_first_visit: bool, + } + impl Node { + pub fn new(block: BlockRef) -> Self { + Self { + block, + cursor: block.borrow().body().back().as_pointer(), + is_first_visit: true, } - // Drop the reference to the analysis so that we can take ownership of it - drop(spills); - analyses.take::(&function.id).unwrap() + } + + pub fn current(&self) -> Option { + self.cursor + } + + pub fn move_next(&mut self) -> Option { + let next = self.cursor.take()?; + self.cursor = next.prev(); + Some(next) + } + } + + let mut block_states = + FxHashMap::, 4>>::default(); + let entry_block = region.borrow().entry_block_ref().unwrap(); + let mut block_q = VecDeque::from([Node::new(entry_block)]); + + while let Some(mut node) = block_q.pop_back() { + let Some(operation) = node.current() else { + // We've reached the top of the block, remove any uses of the block arguments, if they + // were spilled, as they represent the original definitions of those values. + let block = node.block.borrow(); + let used = block_states.entry(node.block).or_default(); + for arg in ValueRange::<2>::from(block.arguments()) { + if analysis.is_spilled(&arg) { + used.remove(&arg); + } + } + continue; }; - // Apply all splits - for split_info in spills.splits_mut() { - let mut builder = FunctionBuilder::at( - function, - InsertionPoint::after(split_info.predecessor.block.into()), - ); - - // Create the split - let split = builder.create_block(); - builder.switch_to_block(split); - - // Record the block we created for this split - split_info.split = Some(split); - - // Rewrite the terminator in the predecessor so that it transfers control to the - // original successor via `split`, moving any block arguments into the - // unconditional branch that terminates `split`. - let span = builder.func.dfg.inst_span(split_info.predecessor.inst); - let ix = builder.func.dfg.inst_mut(split_info.predecessor.inst); - let args = match ix { - Instruction::Br(Br { - ref mut successor, .. - }) => { - assert_eq!(successor.destination, split_info.block); - successor.destination = split; - successor.args.take() + let op = operation.borrow(); + if let Some(branch) = op.as_trait::() { + // Before we process this op, we need to visit all if it's regions first, as rewriting + // those regions might introduce new region arguments that we must rewrite here. So, + // if this is our first visit to this op, we recursively visit its regions in postorder + // first, and then mark the op has visited. The next time we visit this op, we will + // skip this part, and proceed to handling uses/defs of spilled values at the op entry/ + // exit. + if node.is_first_visit { + node.is_first_visit = false; + block_q.push_back(node); + for region in Region::postorder_region_graph_for(branch).into_iter().rev() { + let region = region.borrow(); + assert!( + region.has_one_block(), + "multi-block regions are not currently supported" + ); + let entry = region.entry(); + block_q.push_back(Node::new(entry.as_block_ref())); } - Instruction::CondBr(CondBr { - ref mut then_dest, - ref mut else_dest, - .. - }) => { - if then_dest.destination == split_info.block { - then_dest.destination = split; - then_dest.args.take() - } else { - assert_eq!(else_dest.destination, split_info.block); - else_dest.destination = split; - else_dest.args.take() + continue; + } else { + // Process any uses in the entry regions of this op before proceeding + for region in branch.get_successor_regions(RegionBranchPoint::Parent) { + let Some(region) = region.into_successor() else { + continue; + }; + + let region_entry = region.borrow().entry_block_ref().unwrap(); + if let Some(uses) = block_states.remove(®ion_entry) { + let parent_uses = block_states.entry(node.block).or_default(); + for (spilled, users) in uses { + // TODO(pauls): If `users` is non-empty, and `region` has multiple + // predecessors, then we need to introduce a new region argument to + // represent the definition of each spilled value from those + // predecessors, and then rewrite the uses to use the new argument. + let parent_users = parent_uses.entry(spilled).or_default(); + let merged = users.into_union(parent_users); + *parent_users = merged; + } } } - Instruction::Switch(_) => { - panic!("expected switch instructions to have been rewritten prior to this pass") - } - ix => unimplemented!("unhandled branch instruction: {}", ix.opcode()), - }; - builder.ins().Br(Opcode::Br, Type::Unit, split_info.block, args, span); + } } - // Insert all spills - for spill_info in spills.spills.iter_mut() { - let ip = match spill_info.place { - Placement::Split(split) => { - let split_block = spills.splits[split.as_u32() as usize] - .split - .expect("expected split to have been materialized"); - let terminator = function.dfg.last_inst(split_block).unwrap(); - InsertionPoint::before(terminator.into()) - } - Placement::At(ip) => ip, - }; - let mut builder = FunctionBuilder::at(function, ip); - let mut args = ValueList::default(); - args.push(spill_info.value, &mut builder.func.dfg.value_lists); - let inst = builder.ins().PrimOp(Opcode::Spill, Type::Unit, args, spill_info.span).0; - spill_info.inst = Some(inst); - } + let used = block_states.entry(node.block).or_default(); - // Insert all reloads - for reload in spills.reloads.iter_mut() { - let ip = match reload.place { - Placement::Split(split) => { - let split_block = spills.splits[split.as_u32() as usize] - .split - .expect("expected split to have been materialized"); - let terminator = function.dfg.last_inst(split_block).unwrap(); - InsertionPoint::before(terminator.into()) - } - Placement::At(ip) => ip, - }; + let reload_like = op.as_trait::(); + let is_reload_like = reload_like.is_some(); + if let Some(reload_like) = reload_like { + // We've found a reload of a spilled value, rewrite all uses of the spilled value + // found so far to use the reload instead. + let spilled = reload_like.spilled_value(); + let reloaded = reload_like.reloaded(); - let ty = function.dfg.value_type(reload.value).clone(); - let mut builder = FunctionBuilder::at(function, ip); - let mut args = ValueList::default(); - args.push(reload.value, &mut builder.func.dfg.value_lists); - let inst = builder.ins().PrimOp(Opcode::Reload, ty, args, reload.span).0; - reload.inst = Some(inst); + if let Some(to_rewrite) = used.remove(&spilled) { + debug_assert!(!to_rewrite.is_empty(), "expected empty use sets to be removed"); + + for mut user in to_rewrite { + user.borrow_mut().set(reloaded); + } + } else { + // This reload is unused, so remove it entirely, and move to the next op + node.move_next(); + block_q.push_back(node); + continue; + } } - // Save the updated analysis results, and mark it preserved for later passes - analyses.insert(function.id, spills); - analyses.mark_preserved::(&function.id); + // Advance the cursor in this block + node.move_next(); + block_q.push_back(node); - if session.options.print_ir_after_all { - function.write_to_stdout(session).into_diagnostic()?; + // Remove any use tracking for spilled values defined by this op + for result in ValueRange::<2>::from(op.results().all()) { + if analysis.is_spilled(&result) { + used.remove(&result); + continue; + } } - Ok(()) + // Record any uses of spilled values by this op, so long as the op is not reload-like + if !is_reload_like { + for operand in op.operands().iter().copied() { + let value = operand.borrow().as_value_ref(); + if analysis.is_spilled(&value) { + used.entry(value).or_default().insert(operand); + } + } + } } + + let context = { op.borrow().context_rc() }; + rewrite_spill_pseudo_instructions(context, analysis, interface, None) } -/// This pass rewrites a function annotated by the [InsertSpills] pass, by means of the spill -/// and reload pseudo-instructions, such that the resulting function is semantically equivalent -/// to the original function, but with the additional property that the function will keep the -/// operand stack depth <= 16 at all times. -/// -/// This rewrite consists of the following main objectives: -/// -/// * Match all uses of spilled values with the nearest dominating definition, modifying the IR as -/// required to ensure that all uses are strictly dominated by their definitions. -/// * Allocate sufficient procedure locals to store concurrently-active spills -/// * Rewrite all `spill` instructions to primitive `local.store` instructions -/// * Rewrite used `reload` instructions to primitive `local.load` instructions -/// * Remove unused `reload` instructions as dead code -/// -/// **NOTE:** This pass is intended to be combined with the [InsertSpills] pass. If run on its own, -/// it is effectively a no-op, so it is safe to do, but nonsensical. In a normal compilation -/// pipeline, this pass is run immediately after [InsertSpills]. It is _not_ safe to run other -/// passes between [InsertSpills] and [RewriteSpills], unless that pass specifically is designed to -/// preserve the results of the [SpillAnalysis] computed and used by [InsertSpills] to place spills -/// and reloads. Conversely, you can't just run [InsertSpills] without this pass, or the resulting -/// IR will fail to codegen. -/// -/// ## Design -/// -/// See [SpillAnalysis] and [InsertSpills] for more context and details. -/// -/// The primary purpose of this pass is twofold: reconstruct SSA form after insertion of spills and -/// reloads by [InsertSpills], and lowering of the spill and reload pseudo-instructions to primitive -/// stores and loads from procedure-local variables. It is the final, and most important phase of -/// the spills transformation. -/// -/// Unlike [InsertSpills], which mainly just materializes the results of the [SpillAnalysis], this -/// pass must do a tricky combo of dataflow analysis and rewrite in a single postorder traversal of -/// the CFG (i.e. bottom-up): -/// -/// * We need to find uses of spilled values as we encounter them, and keep track of them until -/// we find an appropriate definition for each use. -/// * We need to propagate uses up the dominance tree until all uses are matched with definitions -/// * We need to rewrite uses when we find a definition -/// * We need to identify whether a block we are about to leave (on our way up the CFG), is in -/// the iterated dominance frontier for the set of spilled values we've found uses for. If it is, -/// we must append a new block parameter, rewrite the terminator of any predecessor blocks, and -/// rewrite all uses found so far by using the new block parameter as the dominating definition. -/// -/// Technically, this pass could be generalized a step further, such that it fixes up invalid -/// def-use relationships in general, rather than just the narrow case of spills/reloads - but it is -/// more efficient to keep it specialized for now, we can always generalize later. -/// -/// This pass guarantees that: -/// -/// 1. No `spill` or `reload` instructions remain in the IR -/// 2. The semantics of the original IR on which [InsertSpills] was run, will be preserved, if: -/// * The original IR was valid -/// * No modification to the IR was made between [InsertSpills] and [RewriteSpills] -/// 3. The resulting function, once compiled to Miden Assembly, will keep the operand stack depth <= -/// 16 elements, so long as the schedule produced by the backend preserves the scheduling -/// semantics. For example, spills/reloads are computed based on an implied scheduling of -/// operations, given by following the control flow graph, and visiting instructions in a block -/// top-down. If the backend reschedules operations for more optimal placement of operands on the -/// operand stack, it is possible that this rescheduling could result in the operand stack depth -/// exceeding 16 elements. However, at this point, it is not expected that this will be a -/// practical issue, even if it does occur, since the introduction of spills and reloads, not -/// only place greater constraints on backend scheduling, but also ensure that more live ranges -/// are split, and thus operands will spend less time on the operand stack overall. Time will -/// tell whether this holds true or not. -#[derive(Default)] -pub struct RewriteSpills; -impl RewritePass for RewriteSpills { - type Entity = hir::Function; - - fn apply( - &mut self, - function: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - // At this point, we've potentially emitted spills/reloads, but these are not yet being - // used to split the live ranges of the SSA values to which they apply. Our job now, is - // to walk the CFG bottom-up, finding uses of values for which we have issued reloads, - // and then looking for the dominating definition (either original, or reload) that controls - // that use, rewriting the use with the SSA value corresponding to the reloaded value. - // - // This has the effect of "reconstructing" the SSA form - although in our case it is more - // precise to say that we are fixing up the original program to reflect the live-range - // splits that we have computed (and inserted pseudo-instructions for). In the original - // paper, they actually had multiple definitions of reloaded SSA values, which is why - // this phase is referred to as "reconstructing", as it is intended to recover the SSA - // property that was lost once multiple definitions are introduced. - // - // * For each original definition of a spilled value `v`, get the new definitions of `v` - // (reloads) and the uses of `v`. - // * For each use of `v`, walk the dominance tree upwards until a definition of `v` is - // found that is responsible for that use. If an iterated dominance frontier is passed, - // a block argument is inserted such that appropriate definitions from each predecessor - // are wired up to that block argument, which is then the definition of `v` responsible - // for subsequent uses. The predecessor instructions which branch to it are new uses - // which we visit in the same manner as described above. After visiting all uses, any - // definitions (reloads) which are dead will have no uses of the reloaded value, and can - // thus be eliminated. - - // We consume the spill analysis in this pass, as it will no longer be valid after this - let spills = match analyses.get::(&function.id) { - Some(spills) if spills.has_spills() => spills, - _ => { - analyses.mark_all_preserved::(&function.id); - return Ok(()); - } +fn rewrite_cfg_spills( + context: Rc, + region: RegionRef, + analysis: &mut SpillAnalysis, + interface: &mut dyn TransformSpillsInterface, + dominfo: &DominanceInfo, + _analysis_manager: AnalysisManager, +) -> Result<(), Report> { + // At this point, we've potentially emitted spills/reloads, but these are not yet being + // used to split the live ranges of the SSA values to which they apply. Our job now, is + // to walk the CFG bottom-up, finding uses of values for which we have issued reloads, + // and then looking for the dominating definition (either original, or reload) that controls + // that use, rewriting the use with the SSA value corresponding to the reloaded value. + // + // This has the effect of "reconstructing" the SSA form - although in our case it is more + // precise to say that we are fixing up the original program to reflect the live-range + // splits that we have computed (and inserted pseudo-instructions for). In the original + // paper, they actually had multiple definitions of reloaded SSA values, which is why + // this phase is referred to as "reconstructing", as it is intended to recover the SSA + // property that was lost once multiple definitions are introduced. + // + // * For each original definition of a spilled value `v`, get the new definitions of `v` + // (reloads) and the uses of `v`. + // * For each use of `v`, walk the dominance tree upwards until a definition of `v` is + // found that is responsible for that use. If an iterated dominance frontier is passed, + // a block argument is inserted such that appropriate definitions from each predecessor + // are wired up to that block argument, which is then the definition of `v` responsible + // for subsequent uses. The predecessor instructions which branch to it are new uses + // which we visit in the same manner as described above. After visiting all uses, any + // definitions (reloads) which are dead will have no uses of the reloaded value, and can + // thus be eliminated. + + // We consume the spill analysis in this pass, as it will no longer be valid after this + let domtree = dominfo.dominance(region); + let domf = DominanceFrontier::new(&domtree); + + // Make sure that any block in the iterated dominance frontier of a spilled value, has + // a new phi (block argument) inserted, if one is not already present. These must be in + // the CFG before we search for dominating definitions. + let inserted_phis = insert_required_phis(&context, analysis, &domf); + + // Traverse the CFG bottom-up, doing the following along the way: + // + // 0. Merge the "used" sets of each successor of the current block (see remaining steps for + // how the "used" set is computed for a block). NOTE: We elaborate in step 4 on how to + // handle computing the "used" set for a successor, from the "used" set at the start of + // the successor block. + // 1. If we encounter a use of a spilled value, record the location of that use in the set + // of uses we're seeking a dominating definition for, i.e. the "used" set + // 2. If we reach a definition for a value with uses in the "used" set: + // * If the definition is the original definition of the value, no action is needed, so we + // remove all uses of that value from the "used" set. + // * If the definition is a reload, rewrite all of the uses in the "used" set to use the + // reload instead, removing them from the "used" set. Mark the reload used. + // 3. When we reach the start of the block, the state of the "used" set is associated with + // the current block. This will be used as the starting state of the "used" set in each + // predecessor of the block + // 4. When computing the "used" set in the predecessor (i.e. step 0), we also check whether + // a given successor is in the iterated dominance frontier for any values in the "used" + // set of that successor. If so, we need to insert a block parameter for each such value, + // rewrite all uses of that value to use the new block parameter, and add the "used" + // value as an additional argument to that successor. The resulting "used" set will thus + // retain a single entry for each of the values for which uses were rewritten + // (corresponding to the block arguments for the successor), but all of the uses + // dominated by the introduced block parameter are no longer in the set, as their + // dominating definition has been found. Any values in the "used" set for which the + // successor is not in the iterated dominance frontier for that value, are retained in + // the "used" set without any changes. + let mut used_sets = + SmallDenseMap::, 8>, 8>::default(); + let mut block_q = VecDeque::from(domtree.postorder()); + while let Some(node) = block_q.pop_front() { + let Some(block_ref) = node.block() else { + continue; }; - let cfg = analyses.get_or_compute::(function, session).unwrap(); - let domtree = analyses.get_or_compute::(function, session).unwrap(); - let domf = DominanceFrontier::compute(&domtree, &cfg, function); - - // Make sure that any block in the iterated dominance frontier of a spilled value, has - // a new phi (block argument) inserted, if one is not already present. These must be in - // the CFG before we search for dominating definitions. - let inserted_phis = insert_required_phis(&spills, function, &cfg, &domf); - - // Traverse the CFG bottom-up, doing the following along the way: - // - // 0. Merge the "used" sets of each successor of the current block (see remaining steps for - // how the "used" set is computed for a block). NOTE: We elaborate in step 4 on how to - // handle computing the "used" set for a successor, from the "used" set at the start of - // the successor block. - // 1. If we encounter a use of a spilled value, record the location of that use in the set - // of uses we're seeking a dominating definition for, i.e. the "used" set - // 2. If we reach a definition for a value with uses in the "used" set: - // * If the definition is the original definition of the value, no action is needed, so we - // remove all uses of that value from the "used" set. - // * If the definition is a reload, rewrite all of the uses in the "used" set to use the - // reload instead, removing them from the "used" set. Mark the reload used. - // 3. When we reach the start of the block, the state of the "used" set is associated with - // the current block. This will be used as the starting state of the "used" set in each - // predecessor of the block - // 4. When computing the "used" set in the predecessor (i.e. step 0), we also check whether - // a given successor is in the iterated dominance frontier for any values in the "used" - // set of that successor. If so, we need to insert a block parameter for each such value, - // rewrite all uses of that value to use the new block parameter, and add the "used" - // value as an additional argument to that successor. The resulting "used" set will thus - // retain a single entry for each of the values for which uses were rewritten - // (corresponding to the block arguments for the successor), but all of the uses - // dominated by the introduced block parameter are no longer in the set, as their - // dominating definition has been found. Any values in the "used" set for which the - // successor is not in the iterated dominance frontier for that value, are retained in - // the "used" set without any changes. - let mut used_sets = BTreeMap::>>::default(); - let mut block_q = VecDeque::from_iter(domtree.cfg_postorder().iter().copied()); - while let Some(block_id) = block_q.pop_front() { - // Compute the initial "used" set for this block - let mut used = BTreeMap::>::default(); - for succ in cfg.succ_iter(block_id) { - if let Some(succ_used) = used_sets.get_mut(&succ) { - // Union the used set from this successor with the others - for (value, users) in succ_used.iter() { - used.entry(*value).or_default().extend(users.iter().cloned()); - } - } - } - // Traverse the block bottom-up, recording uses of spilled values while looking for - // definitions - let mut insts = function.dfg.block(block_id).insts().collect::>(); - while let Some(current_inst) = insts.pop() { - find_inst_uses(current_inst, &mut used, function, &spills); - } + // Compute the initial "used" set for this block + let mut used = SmallDenseMap::, 8>::default(); + for succ in Rc::::children(node) { + let Some(succ_block) = succ.block() else { + continue; + }; - // At the top of the block, if any of the block parameters are in the "used" set, remove - // those uses, as the block parameters are the original definitions for - // those values, and thus no rewrite is needed. - for arg in function.dfg.block_args(block_id) { - used.remove(arg); + if let Some(usages) = used_sets.get_mut(&succ_block) { + // Union the used set from this successor with the others + for (value, users) in usages.iter() { + used.entry(*value).or_default().extend(users.iter().copied()); + } } + } - rewrite_inserted_phi_uses(&inserted_phis, block_id, &mut used, function); + // Traverse the block bottom-up, recording uses of spilled values while looking for + // definitions + let block = block_ref.borrow(); + for op in block.body().iter().rev() { + find_inst_uses(&op, &mut used, analysis); + } - // What remains are the unsatisfied uses of spilled values for this block and its - // successors - used_sets.insert(block_id, used); + // At the top of the block, if any of the block parameters are in the "used" set, remove + // those uses, as the block parameters are the original definitions for those values, and + // thus no rewrite is needed. + for arg in ValueRange::<2>::from(block.arguments()) { + used.remove(&arg); } - rewrite_spill_pseudo_instructions(function, &domtree, &spills); + rewrite_inserted_phi_uses(&inserted_phis, block_ref, &mut used); - Ok(()) + // What remains are the unsatisfied uses of spilled values for this block and its + // successors + used_sets.insert(block_ref, used); } + + rewrite_spill_pseudo_instructions(context, analysis, interface, Some(dominfo)) } -// Insert additional phi nodes as follows: -// -// 1. For each spilled value V -// 2. Obtain the set of blocks, R, containing a reload of V -// 3. For each block B in the iterated dominance frontier of R, insert a phi in B for V -// 4. For every predecessor of B, append a new block argument to B, passing V initially -// 5. Traverse the CFG bottom-up, finding uses of V, until we reach an inserted phi, a reload, or -// the original definition. Rewrite all found uses of V up to that point, to use this definition. +/// Insert additional phi nodes as follows: +/// +/// 1. For each spilled value V +/// 2. Obtain the set of blocks, R, containing a reload of V +/// 3. For each block B in the iterated dominance frontier of R, insert a phi in B for V +/// 4. For every predecessor of B, append a new block argument to B, passing V initially +/// 5. Traverse the CFG bottom-up, finding uses of V, until we reach an inserted phi, a reload, or +/// the original definition. Rewrite all found uses of V up to that point, to use this +/// definition. fn insert_required_phis( - spills: &SpillAnalysis, - function: &mut hir::Function, - cfg: &ControlFlowGraph, + context: &Context, + analysis: &SpillAnalysis, domf: &DominanceFrontier, -) -> BTreeMap> { - let mut required_phis = BTreeMap::>::default(); - for info in spills.reloads() { - let r_block = function.dfg.inst_block(info.inst.unwrap()).unwrap(); - let r = required_phis.entry(info.value).or_default(); - r.insert(r_block); +) -> SmallDenseMap, 8> { + use midenc_hir::adt::smallmap::Entry; + + let mut required_phis = SmallDenseMap::, 4>::default(); + for reload in analysis.reloads() { + let block = reload.inst.unwrap().parent().unwrap(); + log::trace!(target: "insert-spills", "add required_phis for {}", reload.value); + let r = required_phis.entry(reload.value).or_default(); + r.insert(block); } - let mut inserted_phis = BTreeMap::>::default(); + let mut inserted_phis = + SmallDenseMap::, 8>::default(); for (value, domf_r) in required_phis { // Compute the iterated dominance frontier, DF+(R) let idf_r = domf.iterate_all(domf_r); // Add phi to each B in DF+(R) - let data = function.dfg.value_data(value); - let ty = data.ty().clone(); - let span = data.span(); - for b in idf_r { + let (ty, span) = { + let value = value.borrow(); + (value.ty().clone(), value.span()) + }; + for mut b in idf_r { // Allocate new block parameter/phi, if one is not already present let phis = inserted_phis.entry(b).or_default(); - if let adt::smallmap::Entry::Vacant(entry) = phis.entry(value) { - let phi = function.dfg.append_block_param(b, ty.clone(), span); + if let Entry::Vacant(entry) = phis.entry(value) { + let phi = context.append_block_argument(b, ty.clone(), span); entry.insert(phi); // Append `value` as new argument to every predecessor to satisfy new parameter - for pred in cfg.pred_iter(b) { - function.dfg.append_branch_destination_argument(pred.inst, b, value); + let block = b.borrow_mut(); + let mut next_use = block.uses().front().as_pointer(); + while let Some(pred) = next_use.take() { + next_use = pred.next(); + + let (mut predecessor, successor_index) = { + let pred = pred.borrow(); + (pred.owner, pred.index as usize) + }; + let operand = context.make_operand(value, predecessor, 0); + predecessor.borrow_mut().successor_mut(successor_index).arguments.push(operand); } } } @@ -480,149 +600,67 @@ fn insert_required_phis( } fn find_inst_uses( - current_inst: Inst, - used: &mut BTreeMap>, - function: &mut hir::Function, - spills: &SpillAnalysis, + op: &Operation, + used: &mut SmallDenseMap, 8>, + analysis: &SpillAnalysis, ) { - // If `current_inst` is a branch or terminator, it cannot define a value, so - // we simply record any uses, and move on. - match function.dfg.analyze_branch(current_inst) { - BranchInfo::SingleDest(SuccessorInfo { args, .. }) => { - for (index, arg) in args.iter().enumerate() { - if spills.is_spilled(arg) { - used.entry(*arg).or_default().insert(User::new( - current_inst, - *arg, - Use::BlockArgument { - succ: 0, - index: index as u16, - }, - )); - } - } - } - BranchInfo::MultiDest(infos) => { - for (succ_index, info) in infos.into_iter().enumerate() { - for (index, arg) in info.args.iter().enumerate() { - if spills.is_spilled(arg) { - used.entry(*arg).or_default().insert(User::new( - current_inst, - *arg, - Use::BlockArgument { - succ: succ_index as u16, - index: index as u16, - }, - )); - } - } + let reload_like = op.as_trait::(); + let is_reload = reload_like.is_some(); + if let Some(reload_like) = reload_like { + // We have found a new definition for a spilled value, we must rewrite all uses of the + // spilled value found so far, with the reloaded value. + let spilled = reload_like.spilled_value(); + let reloaded = reload_like.reloaded(); + + if let Some(to_rewrite) = used.remove(&spilled) { + debug_assert!(!to_rewrite.is_empty(), "expected empty use sets to be removed"); + + for mut user in to_rewrite { + user.borrow_mut().set(reloaded); } + } else { + // This reload is unused, so remove it entirely, and move to the next op + return; } - BranchInfo::NotABranch => { - // Does this instruction provide a definition for any spilled values? - let ix = function.dfg.inst(current_inst); - - let is_reload = matches!(ix.opcode(), Opcode::Reload); - if is_reload { - // We have found a new definition for a spilled value, we must rewrite - // all uses of the spilled value found so - // far, with the reloaded value - let spilled = ix.arguments(&function.dfg.value_lists)[0]; - let reloaded = function.dfg.first_result(current_inst); - - if let Some(to_rewrite) = used.remove(&spilled) { - debug_assert!(!to_rewrite.is_empty(), "expected empty use sets to be removed"); - - for user in to_rewrite.iter() { - match user.ty { - Use::BlockArgument { - succ: succ_succ, - index, - } => { - function.dfg.replace_successor_argument( - user.inst, - succ_succ as usize, - index as usize, - reloaded, - ); - } - Use::Operand { index } => { - function.dfg.replace_argument(user.inst, index as usize, reloaded); - } - } - } - } else { - // This reload is unused, so remove it entirely, and move to the - // next instruction - return; - } - } + } - for spilled in function - .dfg - .inst_results(current_inst) - .iter() - .filter(|result| spills.is_spilled(result)) - { - // This op is the original definition for `spilled`, so remove any uses - // of it we've accumulated so far, as they - // do not need to be rewritten - used.remove(spilled); - } + for result in ValueRange::<2>::from(op.results().all()) { + if analysis.is_spilled(&result) { + // This op is the original definition for a spilled value, so remove any + // uses of it we've accumulated so far, as they do not need to be rewritten + used.remove(&result); } } - // Record any uses of spilled values in the argument list for `current_inst` (except - // reloads) - let ignored = matches!(function.dfg.inst(current_inst).opcode(), Opcode::Reload); - if !ignored { - for (index, arg) in function.dfg.inst_args(current_inst).iter().enumerate() { - if spills.is_spilled(arg) { - used.entry(*arg).or_default().insert(User::new( - current_inst, - *arg, - Use::Operand { - index: index as u16, - }, - )); + // Record any uses of spilled values in the argument list for `op`, but ignore reload-likes + if !is_reload { + for operand in op.operands().iter().copied() { + let value = operand.borrow().as_value_ref(); + if analysis.is_spilled(&value) { + used.entry(value).or_default().insert(operand); } } } } fn rewrite_inserted_phi_uses( - inserted_phis: &BTreeMap>, - block_id: Block, - used: &mut BTreeMap>, - function: &mut hir::Function, + inserted_phis: &SmallDenseMap, 8>, + block_ref: BlockRef, + used: &mut SmallDenseMap, 8>, ) { // If we have inserted any phis in this block, rewrite uses of the spilled values they // represent. - if let Some(phis) = inserted_phis.get(&block_id) { + if let Some(phis) = inserted_phis.get(&block_ref) { for (spilled, phi) in phis.iter() { if let Some(to_rewrite) = used.remove(spilled) { debug_assert!(!to_rewrite.is_empty(), "expected empty use sets to be removed"); - for user in to_rewrite.iter() { - match user.ty { - Use::BlockArgument { - succ: succ_succ, - index, - } => { - function.dfg.replace_successor_argument( - user.inst, - succ_succ as usize, - index as usize, - *phi, - ); - } - Use::Operand { index } => { - function.dfg.replace_argument(user.inst, index as usize, *phi); - } - } + for mut user in to_rewrite { + user.borrow_mut().set(*phi); } } else { // TODO(pauls): This phi is unused, we should be able to remove it + log::warn!(target: "insert-spills", "unused phi {phi} encountered during rewrite phase"); continue; } } @@ -642,432 +680,85 @@ fn rewrite_inserted_phi_uses( /// those spills which do not dominate any reloads - if a store to a spill slot can never /// be read, then the store can be elided. fn rewrite_spill_pseudo_instructions( - function: &mut hir::Function, - domtree: &DominatorTree, - spills: &SpillAnalysis, -) { - let mut locals = BTreeMap::::default(); - for spill_info in spills.spills() { - let spill = spill_info.inst.expect("expected spill to have been materialized"); - let spilled = spill_info.value; - let stored = function.dfg.inst_args(spill)[0]; - let is_used = spills.reloads().iter().any(|info| { - if info.value == spilled { - let reload = info.inst.unwrap(); - domtree.dominates(spill, reload, &function.dfg) - } else { - false + context: Rc, + analysis: &mut SpillAnalysis, + interface: &mut dyn TransformSpillsInterface, + dominfo: Option<&DominanceInfo>, +) -> Result<(), Report> { + use midenc_hir::{ + dominance::Dominates, + patterns::{NoopRewriterListener, RewriterImpl}, + }; + + let mut builder = RewriterImpl::::new(context); + for spill in analysis.spills() { + let operation = spill.inst.expect("expected spill to have been materialized"); + let spilled = { + let op = operation.borrow(); + let spill_like = op + .as_trait::() + .expect("expected materialized spill operation to implement SpillLike"); + spill_like.spilled_value() + }; + // Only keep spills that feed a live reload dominated by this spill + let mut is_used = false; + for rinfo in analysis.reloads() { + if rinfo.value != spilled { + continue; } - }); + let Some(reload_op) = rinfo.inst else { + continue; + }; + let (reload_used, dom_ok) = { + let rop = reload_op.borrow(); + let rl = rop + .as_trait::() + .expect("expected materialized reload op to implement ReloadLike"); + let used = rl.reloaded().borrow().is_used(); + let dom_ok = match dominfo { + None => true, + Some(dominfo) => { + let sop = operation.borrow(); + sop.dominates(&rop, dominfo) + } + }; + (used, dom_ok) + }; + if reload_used && dom_ok { + is_used = true; + break; + } + } + if is_used { - let local = *locals - .entry(spilled) - .or_insert_with(|| function.dfg.alloc_local(spill_info.ty.clone())); - let builder = ReplaceBuilder::new(&mut function.dfg, spill); - builder.store_local(local, stored, spill_info.span); + builder.set_insertion_point_after(operation); + interface.convert_spill_to_store(&mut builder, operation)?; } else { - let spill_block = function.dfg.inst_block(spill).unwrap(); - let block = function.dfg.block_mut(spill_block); - block.cursor_mut_at_inst(spill).remove(); + builder.erase_op(operation); } } // Rewrite all used reload instructions as `local.load` instructions from the corresponding // procedure local - for reload_info in spills.reloads() { - let inst = reload_info.inst.expect("expected reload to have been materialized"); - let spilled = function.dfg.inst_args(inst)[0]; - let builder = ReplaceBuilder::new(&mut function.dfg, inst); - builder.load_local(locals[&spilled], reload_info.span); - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::testing::TestContext; - use pretty_assertions::{assert_ne, assert_str_eq}; - - use super::*; - - #[test] - fn spills_intra_block() { - let context = TestContext::default(); - let id = "test::spill".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - [AbiParam::new(Type::U32)], - ), - ); - - { - let mut builder = FunctionBuilder::new(&mut function); - let example = builder - .import_function( - "foo", - "example", - Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U128))), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U64), - ], - [AbiParam::new(Type::U32)], - ), - SourceSpan::UNKNOWN, - ) - .unwrap(); - let entry = builder.current_block(); - let v0 = { - let args = builder.block_params(entry); - args[0] - }; - - // entry - let v1 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - let v2 = builder.ins().add_imm_unchecked(v1, Immediate::U32(32), SourceSpan::UNKNOWN); - let v3 = - builder.ins().inttoptr(v2, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v4 = builder.ins().load(v3, SourceSpan::UNKNOWN); - let v5 = builder.ins().add_imm_unchecked(v1, Immediate::U32(64), SourceSpan::UNKNOWN); - let v6 = - builder.ins().inttoptr(v5, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v7 = builder.ins().load(v6, SourceSpan::UNKNOWN); - let v8 = builder.ins().u64(1, SourceSpan::UNKNOWN); - builder.ins().call(example, &[v6, v4, v7, v7, v8], SourceSpan::UNKNOWN); - let v10 = builder.ins().add_imm_unchecked(v1, Immediate::U32(72), SourceSpan::UNKNOWN); - builder.ins().store(v3, v7, SourceSpan::UNKNOWN); - let v11 = - builder.ins().inttoptr(v10, Type::Ptr(Box::new(Type::U64)), SourceSpan::UNKNOWN); - let _v12 = builder.ins().load(v11, SourceSpan::UNKNOWN); - builder.ins().ret(Some(v2), SourceSpan::UNKNOWN); - } - - let original = function.to_string(); - let mut analyses = AnalysisManager::default(); - let mut rewrite = InsertSpills; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("spill insertion failed"); - - analyses.invalidate::(&function.id); - - let mut rewrite = RewriteSpills; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("spill cleanup failed"); - - let expected = "\ -(func (export #spill) (param (ptr u8)) (result u32) - (block 0 (param v0 (ptr u8)) - (let (v1 u32) (ptrtoint v0)) - (let (v2 u32) (add.unchecked v1 32)) - (let (v3 (ptr u128)) (inttoptr v2)) - (let (v4 u128) (load v3)) - (let (v5 u32) (add.unchecked v1 64)) - (let (v6 (ptr u128)) (inttoptr v5)) - (let (v7 u128) (load v6)) - (let (v8 u64) (const.u64 1)) - (store.local local0 v2) - (store.local local1 v3) - (let (v9 u32) (call (#foo #example) v6 v4 v7 v7 v8)) - (let (v10 u32) (add.unchecked v1 72)) - (let (v13 (ptr u128)) (load.local local1)) - (store v13 v7) - (let (v11 (ptr u64)) (inttoptr v10)) - (let (v12 u64) (load v11)) - (let (v14 u32) (load.local local0)) - (ret v14)) -)"; - - let transformed = function.to_string(); - assert_ne!(transformed, original); - assert_str_eq!(transformed.as_str(), expected); - } - - #[test] - fn spills_branching_control_flow() { - let context = TestContext::default(); - let id = "test::spill".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - [AbiParam::new(Type::U32)], - ), - ); - - { - let mut builder = FunctionBuilder::new(&mut function); - let example = builder - .import_function( - "foo", - "example", - Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U128))), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U128), - AbiParam::new(Type::U64), - ], - [AbiParam::new(Type::U32)], - ), - SourceSpan::UNKNOWN, - ) - .unwrap(); - let entry = builder.current_block(); - let block1 = builder.create_block(); - let block2 = builder.create_block(); - let block3 = builder.create_block(); - let v0 = { - let args = builder.block_params(entry); - args[0] - }; - - // entry - let v1 = builder.ins().ptrtoint(v0, Type::U32, SourceSpan::UNKNOWN); - let v2 = builder.ins().add_imm_unchecked(v1, Immediate::U32(32), SourceSpan::UNKNOWN); - let v3 = - builder.ins().inttoptr(v2, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v4 = builder.ins().load(v3, SourceSpan::UNKNOWN); - let v5 = builder.ins().add_imm_unchecked(v1, Immediate::U32(64), SourceSpan::UNKNOWN); - let v6 = - builder.ins().inttoptr(v5, Type::Ptr(Box::new(Type::U128)), SourceSpan::UNKNOWN); - let v7 = builder.ins().load(v6, SourceSpan::UNKNOWN); - let v8 = builder.ins().eq_imm(v1, Immediate::U32(0), SourceSpan::UNKNOWN); - builder.ins().cond_br(v8, block1, &[], block2, &[], SourceSpan::UNKNOWN); - - // block1 - builder.switch_to_block(block1); - let v9 = builder.ins().u64(1, SourceSpan::UNKNOWN); - let call = builder.ins().call(example, &[v6, v4, v7, v7, v9], SourceSpan::UNKNOWN); - let v10 = builder.func.dfg.first_result(call); - builder.ins().br(block3, &[v10], SourceSpan::UNKNOWN); - - // block2 - builder.switch_to_block(block2); - let v11 = builder.ins().add_imm_unchecked(v1, Immediate::U32(8), SourceSpan::UNKNOWN); - builder.ins().br(block3, &[v11], SourceSpan::UNKNOWN); - - // block3 - let v12 = builder.append_block_param(block3, Type::U32, SourceSpan::UNKNOWN); - builder.switch_to_block(block3); - let v13 = builder.ins().add_imm_unchecked(v1, Immediate::U32(72), SourceSpan::UNKNOWN); - let v14 = builder.ins().add_unchecked(v13, v12, SourceSpan::UNKNOWN); - let v15 = - builder.ins().inttoptr(v14, Type::Ptr(Box::new(Type::U64)), SourceSpan::UNKNOWN); - builder.ins().store(v3, v7, SourceSpan::UNKNOWN); - let _v16 = builder.ins().load(v15, SourceSpan::UNKNOWN); - builder.ins().ret(Some(v2), SourceSpan::UNKNOWN); + for reload in analysis.reloads() { + let operation = reload.inst.expect("expected reload to have been materialized"); + let op = operation.borrow(); + let reload_like = op + .as_trait::() + .expect("expected materialized reload op to implement ReloadLike"); + let is_used = reload_like.reloaded().borrow().is_used(); + drop(op); + + // Avoid emitting loads for unused reloads + if is_used { + log::trace!(target: "insert-spills", "convert reload to load {:?}", reload.place); + builder.set_insertion_point_after(operation); + interface.convert_reload_to_load(&mut builder, operation)?; + } else { + log::trace!(target: "insert-spills", "erase unused reload {:?}", reload.value); + builder.erase_op(operation); } - - let original = function.to_string(); - let mut analyses = AnalysisManager::default(); - let mut rewrite = InsertSpills; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("spill insertion failed"); - - analyses.invalidate::(&function.id); - - let mut rewrite = RewriteSpills; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("spill cleanup failed"); - - let expected = "\ -(func (export #spill) (param (ptr u8)) (result u32) - (block 0 (param v0 (ptr u8)) - (let (v1 u32) (ptrtoint v0)) - (let (v2 u32) (add.unchecked v1 32)) - (let (v3 (ptr u128)) (inttoptr v2)) - (let (v4 u128) (load v3)) - (let (v5 u32) (add.unchecked v1 64)) - (let (v6 (ptr u128)) (inttoptr v5)) - (let (v7 u128) (load v6)) - (let (v8 i1) (eq v1 0)) - (condbr v8 (block 1) (block 2))) - - (block 1 - (let (v9 u64) (const.u64 1)) - (store.local local0 v2) - (store.local local1 v3) - (let (v10 u32) (call (#foo #example) v6 v4 v7 v7 v9)) - (br (block 4))) - - (block 2 - (let (v11 u32) (add.unchecked v1 8)) - (br (block 5))) - - (block 3 (param v12 u32) (param v19 u32) (param v20 (ptr u128)) - (let (v13 u32) (add.unchecked v1 72)) - (let (v14 u32) (add.unchecked v13 v12)) - (let (v15 (ptr u64)) (inttoptr v14)) - (store v20 v7) - (let (v16 u64) (load v15)) - (ret v19)) - - (block 4 - (let (v17 (ptr u128)) (load.local local1)) - (let (v18 u32) (load.local local0)) - (br (block 3 v10 v18 v17))) - - (block 5 - (br (block 3 v11 v2 v3))) -)"; - - let transformed = function.to_string(); - assert_ne!(transformed, original); - assert_str_eq!(transformed.as_str(), expected); } - #[test] - fn spills_loop_nest() { - let context = TestContext::default(); - let id = "test::spill".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U64))), - AbiParam::new(Type::U64), - AbiParam::new(Type::U64), - ], - [AbiParam::new(Type::U64)], - ), - ); - - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let (v0, v1, v2) = { - let args = builder.block_params(entry); - (args[0], args[1], args[2]) - }; - - let block1 = builder.create_block(); - let block2 = builder.create_block(); - let block3 = builder.create_block(); - let block4 = builder.create_block(); - let block5 = builder.create_block(); - let block6 = builder.create_block(); - - // entry - let v3 = builder.ins().u64(0, SourceSpan::UNKNOWN); - let v4 = builder.ins().u64(0, SourceSpan::UNKNOWN); - let v5 = builder.ins().u64(0, SourceSpan::UNKNOWN); - builder.ins().br(block1, &[v3, v4, v5], SourceSpan::UNKNOWN); - - // block1 - outer loop header - let v6 = builder.append_block_param(block1, Type::U64, SourceSpan::UNKNOWN); - let v7 = builder.append_block_param(block1, Type::U64, SourceSpan::UNKNOWN); - let v8 = builder.append_block_param(block1, Type::U64, SourceSpan::UNKNOWN); - builder.switch_to_block(block1); - let v9 = builder.ins().eq(v6, v1, SourceSpan::UNKNOWN); - builder.ins().cond_br(v9, block2, &[], block3, &[], SourceSpan::UNKNOWN); - - // block2 - outer exit - builder.switch_to_block(block2); - builder.ins().ret(Some(v8), SourceSpan::UNKNOWN); - - // block3 - split edge - builder.switch_to_block(block3); - builder.ins().br(block4, &[v7, v8], SourceSpan::UNKNOWN); - - // block4 - inner loop - let v10 = builder.append_block_param(block4, Type::U64, SourceSpan::UNKNOWN); - let v11 = builder.append_block_param(block4, Type::U64, SourceSpan::UNKNOWN); - builder.switch_to_block(block4); - let v12 = builder.ins().eq(v10, v2, SourceSpan::UNKNOWN); - builder.ins().cond_br(v12, block5, &[], block6, &[], SourceSpan::UNKNOWN); - - // block5 - inner latch - builder.switch_to_block(block5); - let v13 = builder.ins().add_imm_unchecked(v6, Immediate::U64(1), SourceSpan::UNKNOWN); - builder.ins().br(block1, &[v13, v10, v11], SourceSpan::UNKNOWN); - - // block6 - inner body - builder.switch_to_block(block6); - let v14 = builder.ins().add_imm_unchecked(v6, Immediate::U64(1), SourceSpan::UNKNOWN); - let v15 = builder.ins().mul_unchecked(v14, v2, SourceSpan::UNKNOWN); - let v16 = builder.ins().add_unchecked(v10, v15, SourceSpan::UNKNOWN); - let v17 = builder.ins().ptrtoint(v0, Type::U64, SourceSpan::UNKNOWN); - let v18 = builder.ins().add_unchecked(v17, v16, SourceSpan::UNKNOWN); - let v19 = - builder.ins().inttoptr(v18, Type::Ptr(Box::new(Type::U64)), SourceSpan::UNKNOWN); - let v20 = builder.ins().load(v19, SourceSpan::UNKNOWN); - let v21 = builder.ins().add_unchecked(v11, v20, SourceSpan::UNKNOWN); - let v22 = builder.ins().add_imm_unchecked(v10, Immediate::U64(1), SourceSpan::UNKNOWN); - builder.ins().br(block4, &[v22, v21], SourceSpan::UNKNOWN); - } - - let original = function.to_string(); - let mut analyses = AnalysisManager::default(); - let mut rewrite = InsertSpills; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("spill insertion failed"); - - analyses.invalidate::(&function.id); - - let mut rewrite = RewriteSpills; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("spill cleanup failed"); - - let expected = "\ -(func (export #spill) (param (ptr u64)) (param u64) (param u64) (result u64) - (block 0 (param v0 (ptr u64)) (param v1 u64) (param v2 u64) - (let (v3 u64) (const.u64 0)) - (let (v4 u64) (const.u64 0)) - (let (v5 u64) (const.u64 0)) - (br (block 1 v3 v4 v5 v1))) - - (block 1 (param v6 u64) (param v7 u64) (param v8 u64) (param v24 u64) - (let (v9 i1) (eq v6 v24)) - (condbr v9 (block 2) (block 3))) - - (block 2 - (ret v8)) - - (block 3 - (br (block 7))) - - (block 4 (param v10 u64) (param v11 u64) - (let (v12 i1) (eq v10 v2)) - (condbr v12 (block 5) (block 6))) - - (block 5 - (let (v13 u64) (add.unchecked v6 1)) - (br (block 8))) - - (block 6 - (let (v14 u64) (add.unchecked v6 1)) - (let (v15 u64) (mul.unchecked v14 v2)) - (let (v16 u64) (add.unchecked v10 v15)) - (let (v17 u64) (ptrtoint v0)) - (let (v18 u64) (add.unchecked v17 v16)) - (let (v19 (ptr u64)) (inttoptr v18)) - (let (v20 u64) (load v19)) - (let (v21 u64) (add.unchecked v11 v20)) - (let (v22 u64) (add.unchecked v10 1)) - (br (block 4 v22 v21))) - - (block 7 - (store.local local0 v24) - (br (block 4 v7 v8))) - - (block 8 - (let (v23 u64) (load.local local0)) - (br (block 1 v13 v10 v11 v23))) -)"; - - let transformed = function.to_string(); - assert_ne!(transformed, original); - assert_str_eq!(transformed.as_str(), expected); - } + Ok(()) } diff --git a/hir-transform/src/split_critical_edges.rs b/hir-transform/src/split_critical_edges.rs deleted file mode 100644 index 1cef2d1e9..000000000 --- a/hir-transform/src/split_critical_edges.rs +++ /dev/null @@ -1,287 +0,0 @@ -use std::collections::VecDeque; - -use midenc_hir::{ - self as hir, - pass::{AnalysisManager, RewritePass, RewriteResult}, - Block as BlockId, *, -}; -use midenc_hir_analysis::ControlFlowGraph; -use midenc_session::{diagnostics::IntoDiagnostic, Session}; -use rustc_hash::FxHashSet; -use smallvec::SmallVec; - -/// This pass breaks any critical edges in the CFG of a function. -/// -/// A critical edge occurs when control flow may exit a block, which we'll call `P`, to -/// more than one successor block, which we'll call `S`, where any `S` has more than one -/// predecessor from which it may receive control. Put another way, in the control flow graph, -/// a critical edge is one which connects two nodes where the source node has multiple outgoing -/// edges, and the destination node has multiple incoming edges. -/// -/// These types of edges cause unnecessary complications with certain types of dataflow analyses -/// and transformations, and so we fix this by splitting these edges. This is done by introducing -/// a new block, `B`, in which we insert a branch to `S` with whatever arguments were originally -/// provided in `P`, and then rewriting the branch in `P` that went to `S`, to go to `B` instead. -/// -/// After this pass completes, no node in the control flow graph will have both multiple -/// predecessors and multiple successors. -#[derive(Default, PassInfo, ModuleRewritePassAdapter)] -pub struct SplitCriticalEdges; -impl RewritePass for SplitCriticalEdges { - type Entity = hir::Function; - - fn apply( - &mut self, - function: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - // Search for blocks with multiple successors with edges to blocks with - // multiple predecessors; these blocks form critical edges in the control - // flow graph which must be split. - // - // We split the critical edge by inserting a new block after the predecessor - // and updating the predecessor instruction to transfer to the new block - // instead. We then insert an unconditional branch in the new block that - // passes the block arguments that were meant for the "real" successor. - let mut visited = FxHashSet::::default(); - let mut worklist = VecDeque::::default(); - worklist.push_back(function.dfg.entry_block()); - - let mut cfg = analyses - .take::(&function.id) - .unwrap_or_else(|| ControlFlowGraph::with_function(function)); - - while let Some(p) = worklist.pop_front() { - // If we've already visited a block, skip it - if !visited.insert(p) { - continue; - } - - // Make sure we visit all of the successors of this block next - for b in cfg.succ_iter(p) { - worklist.push_back(b); - } - - // Unless this block has multiple successors, skip it - if cfg.num_successors(p) < 2 { - continue; - } - - let succs = SmallVec::<[BlockId; 2]>::from_iter(cfg.succ_iter(p)); - for b in succs.into_iter() { - // Unless this successor has multiple predecessors, skip it - if cfg.num_predecessors(b) < 2 { - continue; - } - - // We found a critical edge, so perform the following steps: - // - // * Create a new block, placed after the predecessor in the layout - // * Rewrite the terminator of the predecessor to refer to the new - // block, but without passing any block arguments - // * Insert an unconditional branch to the successor with the block - // arguments of the original terminator - // * Recompute the control flow graph for affected blocks - let split = function.dfg.create_block_after(p); - let terminator = function.dfg.last_inst(p).unwrap(); - let span = function.dfg.inst_span(terminator); - let ix = function.dfg.inst_mut(terminator); - let args: ValueList; - match ix { - Instruction::Br(hir::Br { - ref mut successor, .. - }) => { - args = successor.args.take(); - successor.destination = split; - } - Instruction::CondBr(hir::CondBr { - ref mut then_dest, - ref mut else_dest, - .. - }) => { - if then_dest.destination == b { - then_dest.destination = split; - args = then_dest.args.take(); - } else { - else_dest.destination = split; - args = else_dest.args.take(); - } - } - Instruction::Switch(_) => unimplemented!(), - _ => unreachable!(), - } - function.dfg.insert_inst( - InsertionPoint { - at: ProgramPoint::Block(split), - action: Insert::After, - }, - Instruction::Br(hir::Br { - op: hir::Opcode::Br, - successor: hir::Successor { - destination: b, - args, - }, - }), - Type::Unknown, - span, - ); - - cfg.recompute_block(&function.dfg, split); - } - - cfg.recompute_block(&function.dfg, p); - } - - analyses.insert(function.id, cfg); - - session.print(&*function, Self::FLAG).into_diagnostic()?; - if session.should_print_cfg(Self::FLAG) { - use std::io::Write; - let cfg = function.cfg_printer(); - let mut stdout = std::io::stdout().lock(); - write!(&mut stdout, "{cfg}").into_diagnostic()?; - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{ - pass::{AnalysisManager, RewritePass}, - testing::TestContext, - AbiParam, Function, FunctionBuilder, Immediate, InstBuilder, Signature, SourceSpan, Type, - }; - use pretty_assertions::{assert_eq, assert_ne}; - - use crate::SplitCriticalEdges; - - /// Run the split critical edges pass on the following IR: - /// - /// The following IR is contains a critical edge to split, specifically - /// `blk0` is critical because it has multiple predecessors, and multiple - /// successors: - /// - /// ```text,ignore - /// pub fn test(*mut u8, u32) -> *mut u8 { - /// entry(ptr0: *mut u8, n0: u32): - /// ptr1 = ptrtoint ptr0 : u32; - /// br blk0(ptr1, n0); - /// - /// blk0(ptr2: u32, n1: u32): - /// is_null = eq ptr2, 0; - /// condbr is_null, blk2(ptr0), blk1(ptr2, n1); - /// - /// blk1(ptr3: u32, n2: u32): - /// ptr4 = sub ptr3, n2; - /// n3 = sub n2, 1; - /// is_zero = eq n3, 0; - /// condbr is_zero, blk2(ptr4), blk0(ptr4, n3); - /// - /// blk2(result0: *mut u8) - /// ret result0; - /// } - /// ``` - /// - /// We expect this pass to introduce new blocks along all control flow paths - /// where the successor has multiple predecessors. This may result in some - /// superfluous blocks after the pass is run, but this can be addressed by - /// running the [InlineBlocks] pass afterwards, which will flatten the CFG. - #[test] - fn split_critical_edges_simple_test() { - let context = TestContext::default(); - let id = "test::sce".parse().unwrap(); - let mut function = Function::new( - id, - Signature::new( - [AbiParam::new(Type::Ptr(Box::new(Type::U8))), AbiParam::new(Type::U32)], - [AbiParam::new(Type::Ptr(Box::new(Type::U8)))], - ), - ); - - { - let mut builder = FunctionBuilder::new(&mut function); - let entry = builder.current_block(); - let (ptr0, n0) = { - let args = builder.block_params(entry); - (args[0], args[1]) - }; - - let a = builder.create_block(); // blk0(ptr2: u32, n1: u32) - let ptr2 = builder.append_block_param(a, Type::U32, SourceSpan::UNKNOWN); - let n1 = builder.append_block_param(a, Type::U32, SourceSpan::UNKNOWN); - let b = builder.create_block(); // blk1(ptr3: u32, n2: u32) - let ptr3 = builder.append_block_param(b, Type::U32, SourceSpan::UNKNOWN); - let n2 = builder.append_block_param(b, Type::U32, SourceSpan::UNKNOWN); - let c = builder.create_block(); // blk2(result0: u32) - let result0 = builder.append_block_param(c, Type::U32, SourceSpan::UNKNOWN); - - // entry - let ptr1 = builder.ins().ptrtoint(ptr0, Type::U32, SourceSpan::UNKNOWN); - builder.ins().br(a, &[ptr1, n0], SourceSpan::UNKNOWN); - - // blk0 - builder.switch_to_block(a); - let is_null = builder.ins().eq_imm(ptr2, Immediate::U32(0), SourceSpan::UNKNOWN); - builder.ins().cond_br(is_null, c, &[ptr0], b, &[ptr2, n1], SourceSpan::UNKNOWN); - - // blk1 - builder.switch_to_block(b); - let ptr4 = builder.ins().sub_checked(ptr3, n2, SourceSpan::UNKNOWN); - let n3 = builder.ins().sub_imm_checked(n2, Immediate::U32(1), SourceSpan::UNKNOWN); - let is_zero = builder.ins().eq_imm(n3, Immediate::U32(0), SourceSpan::UNKNOWN); - builder.ins().cond_br(is_zero, c, &[ptr4], a, &[ptr4, n3], SourceSpan::UNKNOWN); - - // blk2 - builder.switch_to_block(c); - let result1 = - builder - .ins() - .inttoptr(result0, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); - builder.ins().ret(Some(result1), SourceSpan::UNKNOWN); - } - - let original = function.to_string(); - let mut analyses = AnalysisManager::default(); - let mut rewrite = SplitCriticalEdges; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("splitting critical edges failed"); - - let expected = "\ -(func (export #sce) (param (ptr u8)) (param u32) (result (ptr u8)) - (block 0 (param v0 (ptr u8)) (param v1 u32) - (let (v7 u32) (ptrtoint v0)) - (br (block 1 v7 v1))) - - (block 1 (param v2 u32) (param v3 u32) - (let (v8 i1) (eq v2 0)) - (condbr v8 (block 4) (block 2 v2 v3))) - - (block 4 - (br (block 3 v0))) - - (block 2 (param v4 u32) (param v5 u32) - (let (v9 u32) (sub.checked v4 v5)) - (let (v10 u32) (sub.checked v5 1)) - (let (v11 i1) (eq v10 0)) - (condbr v11 (block 6) (block 5))) - - (block 6 - (br (block 3 v9))) - - (block 5 - (br (block 1 v9 v10))) - - (block 3 (param v6 u32) - (let (v12 (ptr u8)) (inttoptr v6)) - (ret v12)) -)"; - - let transformed = function.to_string(); - assert_ne!(transformed, original); - assert_eq!(transformed.as_str(), expected); - } -} diff --git a/hir-transform/src/treeify.rs b/hir-transform/src/treeify.rs deleted file mode 100644 index 5f2920bb3..000000000 --- a/hir-transform/src/treeify.rs +++ /dev/null @@ -1,892 +0,0 @@ -use std::{ - collections::{BTreeMap, VecDeque}, - rc::Rc, -}; - -use midenc_hir::{ - self as hir, - pass::{AnalysisManager, RewritePass, RewriteResult}, - Block as BlockId, Value as ValueId, *, -}; -use midenc_hir_analysis::{BlockPredecessor, ControlFlowGraph, DominatorTree, Loop, LoopAnalysis}; -use midenc_session::{ - diagnostics::{IntoDiagnostic, Report}, - Session, -}; -use smallvec::{smallvec, SmallVec}; - -use crate::adt::ScopedMap; - -/// This pass rewrites the CFG of a function so that it forms a tree. -/// -/// While we technically call this treeification, the CFG cannot be fully converted into a -/// tree in general, as loops must be preserved (they can be copied along multiple control -/// flow paths, but we want to preserve the loop structure in the CFG). -/// -/// The treeify transformation concerns itself with any block B which has multiple predecessors -/// in the control flow graph, where for at least two of those predecessors, the predecessor is -/// always visited before B, if control flows through both. This is a slightly less restrictive -/// conditon than the dominance property, but is very much related - the primary difference being -/// that unlike dominance, what we are capturing is that the predecessor block is not along a -/// loopback edge. It is quite common for a predecessor block to always be visited first in the -/// CFG, while not dominating its successor: consider an if/else expression, where control splits -/// at the `if/else`, and rejoins afterwards, the code in the final block where control is joined -/// can only be reached after either the `if` or `else` block has executed, but neither the `if` -/// nor the `else` blocks can be considered to "dominate" the final block in the graph theoretical -/// sense. -/// -/// The actual treeification process works like so: -/// -/// 1. For each block B, in the postorder sort of the CFG, determine if B has more than one -/// predecessor P, where P appears before B in the reverse postorder sort of the CFG. a. If -/// found, treeify the block as described in subsequent steps b. Otherwise, ignore this block and -/// proceed -/// 2. For each P, clone B to a new block B', and rewrite P such that it branches to B' rather than -/// B. -/// 3. For each successor S of B: -/// a. If S is a loop header, and S appears before B in the reverse postorder sort of the CFG, -/// then it is a loopback edge, so the corresponding edge from B' to S is left intact. -/// b. If S is a loop header, but S appears after B in the reverse postorder sort of the CFG, -/// then it is treated like other blocks (see c.) -/// c. Otherwise, clone S to S', and rewrite B' to branch to S' instead of S. -/// 4. Repeat step 2 for the successors of S, recursively, until the subgraph reachable from B -/// -/// Since we are treeifying blocks from the leaves of the CFG to the root, and because we do not -/// follow loopback edges which escape/continue an outer loop - whenever we clone a subgraph of -/// the CFG, we know that it has already been treeified, as we only start to treeify a block once -/// all of the blocks reachable via that block have been treeified. -/// -/// In short, we're trying to split blocks with multiple predecessors such that all blocks have -/// either zero or one predecessors, i.e. the CFG forms a tree. As mentioned previously, we must -/// make an exception for loop headers, which by definition must have at least one predecessor -/// which is a loopback edge, but this suits us just fine, as Miden Assembly provides control flow -/// instructions compatible with lowering from such a CFG. -/// -/// # Examples -/// -/// ## Basic DAG -/// -/// This example demonstrates how the DAG of a function with multiple returns gets transformed: -/// -/// ```text,ignore -/// blk0 -/// | -/// v -/// blk1 -> blk3 -> ret -/// | / -/// | / -/// | / -/// v v -/// blk2 -/// | -/// v -/// ret -/// ``` -/// -/// Becomes: -/// -/// ```text,ignore -/// blk0 -/// | -/// v -/// blk1 -> blk3 -> ret -/// | | -/// | | -/// | | -/// v v -/// blk2 blk2 -/// | | -/// v v -/// ret ret -/// ``` -/// -/// ## Basic Loop -/// -/// This is an example of a function with multiple returns and a simple loop: -/// -/// ```text,ignore -/// blk0 -/// | ------- -/// v v | -/// blk1 -> blk3 -> blk4 -> blk5 -> ret -/// | / -/// | / -/// | / -/// v v -/// blk2 -/// | -/// v -/// ret -/// ``` -/// -/// Becomes: -/// -/// ```text,ignore -/// blk0 -/// | ------- -/// v v | -/// blk1 -> blk3 -> blk4 -> blk5 -> ret -/// | | -/// | | -/// | | -/// v v -/// blk2 blk2 -/// | | -/// v v -/// ret ret -/// ``` -/// -/// ## Complex Loop -/// -/// This is an example of a function with a complex loop (i.e. multiple exit points): -/// -/// ```text,ignore -/// blk0 -/// | -/// v -/// blk1 -/// | \ -/// | blk2 <----- -/// | | | -/// | blk3 | -/// | / \ | -/// | / blk4-- -/// | / | -/// vv | -/// blk5 blk6 -/// ``` -/// -/// Becomes: -/// -/// ```text,ignore -/// blk0 -/// | -/// v -/// blk1 -/// | \ -/// | \ -/// | blk2 <--- -/// | | | -/// | v | -/// | blk3 | -/// | | \ | -/// | | blk4-- -/// | | | -/// v v v -/// blk5 blk5 blk6 -/// ``` -/// -/// NOTE: Here, when generating code for `blk5` and `blk6`, the loop depth is 0, so -/// we will emit a single `push.0` at the end of both blocks which will terminate the -/// containing loop, and then return from the function as we've reached the bottom -/// of the tree. -/// -/// ## Nested Loops -/// -/// This is an extension of the example above, but with nested loops: -/// -/// ```text,ignore -/// blk0 -/// | -/// v -/// blk1 -/// | \ -/// | blk2 <------- -/// | | | | -/// | blk3 | | -/// | / \ | | -/// | / blk4-- | -/// | / | | -/// vv v | -/// blk5<- blk6-->blk7-->blk8 -/// | ^ | -/// | |_____________| -/// | | -/// |__________________| -/// ``` -/// -/// We have two loops, the outer one starting at `blk2`: -/// -/// * `blk2->blk3->blk4->blk2` -/// * `blk2->blk3->blk4->blk6->blk7->blk2` -/// -/// And the inner one starting at `blk6`: -/// -/// * `blk6->blk7->blk8->blk6` -/// -/// Additionally, there are multiple exits through the loops, depending on the path taken: -/// -/// * `blk2->blk3->blk5` -/// * `blk2->blk3->blk4->blk6->blk7->blk8->blk5` -/// * `blk6->blk7->blk8->blk5` -/// -/// After transformation, this becomes: -/// -/// ```text,ignore -/// blk0 -/// | -/// v -/// blk1 -/// | \ -/// | blk2 <------- -/// | | | | -/// | blk3 | | -/// | | \ | | -/// | | blk4-- | -/// | | | | -/// v v v | -/// blk5 blk5 blk6-->blk7-->blk8 -/// ^ | | -/// |_____________|_| -/// | -/// v -/// blk5 -/// ``` -/// -/// During codegen though, we end up with the following tree of stack machine code. -/// -/// At each point where control flow either continues a loop or leaves it, we must -/// -/// * Duplicate loop headers on control flow edges leading to those headers -/// * Emit N `push.0` instructions on control flow edges exiting the function from a loop depth of N -/// * Emit a combination of the above on control flow edges exiting an inner loop for an outer loop, -/// depending on what depths the predecessor and successor blocks are at -/// -/// ```text,ignore -/// blk0 -/// blk1 -/// if.true -/// blk2 -/// while.true -/// blk3 -/// if.true -/// blk4 -/// if.true -/// blk2 # duplicated outer loop header -/// else -/// blk6 -/// while.true -/// blk7 -/// if.true -/// blk2 # duplicated outer loop header -/// push.0 # break out of inner loop -/// else -/// blk8 -/// if.true -/// blk6 # duplicated inner loop header -/// else -/// blk5 -/// push.0 # break out of outer loop -/// push.0 # break out of inner loop -/// end -/// end -/// end -/// end -/// else -/// blk5 -/// push.0 # break out of outer loop -/// end -/// end -/// else -/// blk5 -/// end -/// ``` -#[derive(Default, PassInfo, ModuleRewritePassAdapter)] -pub struct Treeify; -impl RewritePass for Treeify { - type Entity = hir::Function; - - fn apply( - &mut self, - function: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - let cfg = analyses.get_or_compute::(function, session)?; - let domtree = analyses.get_or_compute::(function, session)?; - let loops = analyses.get_or_compute::(function, session)?; - - // Obtain the set of all blocks we need to check for treeification in a new vector. - // - // We must do this because as we treeify the CFG, we will be updating it, as well - // as all of the analyses, such as the dominator tree, so we can't iterate it at - // the same time as we do treeification. - // - // Additionally, this set never changes - we are visiting the function bottom-up, - // so we only start to treeify a block once all of the blocks reachable via that - // block have been treeified. As a result, the tree reachable from B is already - // treeified. - let to_visit = domtree.cfg_postorder().to_vec(); - - // This outer loop visits all of the original blocks of the CFG postorder (bottom-up), - // and is simply searching for blocks of the function which meet the criteria for - // treeification. - // - // The inner loop is responsible for actually treeifying those blocks. This - // necessarily has the effect of mutating the function, and therefore requires - // us to recompute some of the analyses so that we can properly determine how - // to handle certain blocks in the portion of the CFG being treeified, namely - // loops (via loop headers). - // - // Loops require special handling, as during treeification we typically will be - // cloning blocks that belong to the portion of the CFG rooted at the block being - // treeified. However, if we are treeifying a block that belongs to a loop, we do - // not want to clone along control flow edges representing continuation or breaking - // out of an outer loop. On the other hand, if we reach a loop that is only reachable - // via the block being treeified, we do want to copy those, as each branch of the tree - // will need its own copy of that loop. - // - // To handle this, we require the ability to: - // - // * Identify loop headers (which requires the loops analysis) - // * Identify the reverse postorder index of a block (which requires the dominator tree) - // - // The dominator tree requires the control flow graph analysis, and the loop analysis - // requires the dominator tree - as a result, each time we modify the CFG, we must also - // ensure that all three analyses reflect any effects of such modifications. - // - // However, this would be very expensive to compute as frequently as would be required - // by this transformation. Instead, since the transformation is essentially just cloning - // multiple copies of various subgraphs of the original CFG, we can use the analyses of - // the original CFG as well, by mapping each copied block back to the block in the CFG - // from which it is derived. By doing so, we can determine if that block is a loop header, - // or how two blocks are sorted relative to each other in the reverse postorder, without - // having to ever recompute the three analyses mentioned above. - let mut block_infos = BlockInfos::new(cfg, domtree, loops); - - // For each block B, treeify B IFF it has multiple predecessors, where for each - // predecessor P, P appears before B in the reverse postorder sort of the CFG. - // Treeifying B involves creating a copy of B and the subgraph of the CFG rooted at B, - // for each P. - // - // The blocks are selected this way, since by splitting these nodes in the CFG, such - // that each predecessor gets its own copy of the subgraph reached via B, the CFG is - // made more tree-like. Once all nodes are split, then the CFG is either a tree, or - // a DAG that is almost a tree, with the only remaining DAG edges being loopback edges - // for loops that appear in the CFG. - let mut block_q = VecDeque::::default(); - let mut changed = false; - for b in to_visit { - // Check if this block meets the conditions for treeification - let predecessors = block_infos - .cfg - .pred_iter(b) - .filter(|bp| block_infos.rpo_cmp(bp.block, b).is_lt()) - .collect::>(); - - if predecessors.len() < 2 { - continue; - } - log::trace!("found candidate for treeification: {b}"); - - // For each predecessor, create a clone of B and all of its successors, with - // the exception of successors which are loop headers where the loop header - // appears before B in the reverse postorder sort of the CFG. Such edges are - // loopback edges to an outer loop, which must be preserved, even when cloning - // the subgraph rooted at B. - for p in predecessors { - assert!(block_q.is_empty()); - log::trace!("scheduling copy of {b} for predecessor {}", p.block); - block_q.push_back(CopyBlock::new(b, p)); - let root = b; - - while let Some(CopyBlock { - b, - ref p, - value_map, - block_map, - }) = block_q.pop_front() - { - // If we enqueue a successor block to be copied, and that block is a loop header - // which appears before the root block in the CFG, then it is a loopback edge - // that escapes the portion of the CFG being treeified, and we do not want not - // actually copy it. - if block_infos.is_loop_header(b).is_some() - && block_infos.rpo_cmp(b, root).is_lt() - { - log::trace!( - "skipping copy of {b} for {} as {b} dominates {root} (i.e. it is a \ - loopback edge)", - p.block - ); - continue; - } - - // Copy this block and its successors - treeify(b, p, function, &mut block_infos, &mut block_q, value_map, block_map)?; - } - - // Mark the control flow graph as modified - changed = true; - } - } - - // If we made any changes, we need to recompute all analyses - if !changed { - analyses.mark_all_preserved::(&function.id); - } else { - // Recompute the CFG and dominator tree and remove all unreachable blocks - let cfg = ControlFlowGraph::with_function(function); - let domtree = DominatorTree::with_function(function, &cfg); - let mut to_remove = vec![]; - for (b, _) in function.dfg.blocks() { - if domtree.is_reachable(b) { - continue; - } - to_remove.push(b); - } - // Remove all blocks from the function that were unreachable - for b in to_remove { - function.dfg.detach_block(b); - } - } - - session.print(&*function, Self::FLAG).into_diagnostic()?; - if session.should_print_cfg(Self::FLAG) { - use std::io::Write; - let cfg = function.cfg_printer(); - let mut stdout = std::io::stdout().lock(); - write!(&mut stdout, "{cfg}").into_diagnostic()?; - } - Ok(()) - } -} - -#[allow(clippy::too_many_arguments)] -fn treeify( - b: BlockId, - p: &BlockPredecessor, - function: &mut hir::Function, - block_infos: &mut BlockInfos, - block_q: &mut VecDeque, - mut value_map: ScopedMap, - mut block_map: ScopedMap, -) -> Result<(), Report> { - // Check if we're dealing with a loop header - let is_loop = block_infos.is_loop_header(b).is_some(); - - log::trace!( - "starting treeification for {b} from {} (is {b} loop header? {is_loop})", - p.block - ); - - // 1. Create a new block `b'`, without block arguments, unless it is a loop header, - // in which case we want to preserve the block arguments, just with new value ids - let b_prime = function.dfg.create_block_after(p.block); - log::trace!("created block {b_prime} as clone of {b}"); - block_map.insert(b, b_prime); - block_infos.insert_copy(b_prime, b); - - // 2. Remap values in the cloned block: - // - // * If this is a loop header, we need to replace references to the old block arguments with the - // new block arguments. - // * If this is not a loop header, then we need to replace references to the block arguments - // with the values which were passed as arguments in the predecessor block - if is_loop { - function.dfg.clone_block_params(b, b_prime); - for (src, dest) in function - .dfg - .block_params(b) - .iter() - .copied() - .zip(function.dfg.block_params(b_prime).iter().copied()) - { - value_map.insert(src, dest); - } - } else { - match function.dfg.analyze_branch(p.inst) { - BranchInfo::SingleDest(info) => { - value_map.extend( - function.dfg.block_args(b).iter().copied().zip(info.args.iter().copied()), - ); - } - BranchInfo::MultiDest(ref infos) => { - let info = infos.iter().find(|info| info.destination == b).unwrap(); - value_map.extend( - function.dfg.block_args(b).iter().copied().zip(info.args.iter().copied()), - ); - } - BranchInfo::NotABranch => unreachable!(), - } - } - - // 3. Update the predecessor instruction to reference the new block, remove block arguments if - // this is not a loop header. - let mut seen = false; // Only update the first occurrance of this predecessor - update_predecessor(function, p, |successor, pool| { - log::trace!("maybe updating successor {} of {}", successor.destination, p.block); - if successor.destination == b && !seen { - seen = true; - successor.destination = b_prime; - if !is_loop { - successor.args.clear(pool); - } - } - }); - assert!(seen); - - // 4. Copy contents of `b` to `b'`, inserting defs in the lookup table, and mapping operands to - // their new "corrected" values - copy_instructions(b, b_prime, function, &mut value_map, &block_map); - - // 5. Clone the children of `b` and append to `b_prime`, but do not clone children of `b` that - // are loop headers, only clone the edge. - copy_children(b, b_prime, function, block_q, value_map, block_map) -} - -#[allow(clippy::too_many_arguments)] -fn copy_children( - b: BlockId, - b_prime: BlockId, - function: &mut hir::Function, - block_q: &mut VecDeque, - value_map: ScopedMap, - block_map: ScopedMap, -) -> Result<(), Report> { - let pred = BlockPredecessor { - inst: function.dfg.last_inst(b_prime).expect("expected non-empty block"), - block: b_prime, - }; - let successors = match function.dfg.analyze_branch(function.dfg.last_inst(b).unwrap()) { - BranchInfo::NotABranch => return Ok(()), - BranchInfo::SingleDest(info) => smallvec![info.destination], - BranchInfo::MultiDest(infos) => { - SmallVec::<[_; 2]>::from_iter(infos.into_iter().map(|info| info.destination)) - } - }; - let value_map = Rc::new(value_map); - let block_map = Rc::new(block_map); - - for succ in successors { - if let Some(succ_prime) = block_map.get(&succ) { - update_predecessor(function, &pred, |successor, _| { - if successor.destination == succ { - successor.destination = *succ_prime; - } - }); - } - - block_q.push_back(CopyBlock { - b: succ, - p: pred, - value_map: ScopedMap::new(Some(value_map.clone())), - block_map: ScopedMap::new(Some(block_map.clone())), - }); - } - - Ok(()) -} - -fn copy_instructions( - b: BlockId, - b_prime: BlockId, - function: &mut hir::Function, - value_map: &mut ScopedMap, - block_map: &ScopedMap, -) { - // Initialize the cursor at the first instruction in `b` - let mut next = { - let cursor = function.dfg.block(b).insts.front(); - cursor.get().map(|inst_data| inst_data as *const InstNode) - }; - - while let Some(ptr) = next.take() { - // Get the id of the instruction at the current cursor position, then advance the cursor - let src_inst = { - let mut cursor = unsafe { function.dfg.block(b).insts.cursor_from_ptr(ptr) }; - let id = cursor.get().unwrap().key; - cursor.move_next(); - next = cursor.get().map(|inst_data| inst_data as *const InstNode); - id - }; - - // Clone the source instruction data - let inst = function.dfg.clone_inst(src_inst); - - // We need to fix up the cloned instruction data - let data = &mut function.dfg.insts[inst]; - // First, we're going to be placing it in b', so make sure the instruction is aware of that - data.block = b_prime; - // Second, we need to rewrite value/block references contained in the instruction - match data.as_mut() { - Instruction::Br(hir::Br { - ref mut successor, .. - }) => { - if let Some(new_dest) = block_map.get(&successor.destination) { - successor.destination = *new_dest; - } - let args = successor.args.as_mut_slice(&mut function.dfg.value_lists); - for arg in args.iter_mut() { - if let Some(arg_prime) = value_map.get(arg) { - *arg = *arg_prime; - } - } - } - Instruction::CondBr(hir::CondBr { - ref mut cond, - ref mut then_dest, - ref mut else_dest, - .. - }) => { - if let Some(cond_prime) = value_map.get(cond) { - *cond = *cond_prime; - } - if let Some(new_dest) = block_map.get(&then_dest.destination) { - then_dest.destination = *new_dest; - } - let then_args = then_dest.args.as_mut_slice(&mut function.dfg.value_lists); - for arg in then_args.iter_mut() { - if let Some(arg_prime) = value_map.get(arg) { - *arg = *arg_prime; - } - } - if let Some(new_dest) = block_map.get(&else_dest.destination) { - else_dest.destination = *new_dest; - } - let else_args = else_dest.args.as_mut_slice(&mut function.dfg.value_lists); - for arg in else_args.iter_mut() { - if let Some(arg_prime) = value_map.get(arg) { - *arg = *arg_prime; - } - } - } - Instruction::Switch(hir::Switch { - ref mut arg, - ref mut arms, - default: ref mut default_succ, - .. - }) => { - if let Some(arg_prime) = value_map.get(arg) { - *arg = *arg_prime; - } - if let Some(new_default_dest) = block_map.get(&default_succ.destination) { - default_succ.destination = *new_default_dest; - } - let default_args = default_succ.args.as_mut_slice(&mut function.dfg.value_lists); - for arg in default_args.iter_mut() { - if let Some(arg_prime) = value_map.get(arg) { - *arg = *arg_prime; - } - } - for arm in arms.iter_mut() { - if let Some(new_dest) = block_map.get(&arm.successor.destination) { - arm.successor.destination = *new_dest; - } - let args = arm.successor.args.as_mut_slice(&mut function.dfg.value_lists); - for arg in args.iter_mut() { - if let Some(arg_prime) = value_map.get(arg) { - *arg = *arg_prime; - } - } - } - } - other => { - for arg in other.arguments_mut(&mut function.dfg.value_lists).iter_mut() { - if let Some(arg_prime) = value_map.get(arg) { - *arg = *arg_prime; - } - } - } - } - // Finally, append the cloned instruction to the block layout - let node = unsafe { UnsafeRef::from_raw(data) }; - function.dfg.block_mut(b_prime).insts.push_back(node); - value_map.extend( - function - .dfg - .inst_results(src_inst) - .iter() - .copied() - .zip(function.dfg.inst_results(inst).iter().copied()), - ); - } -} - -struct CopyBlock { - b: BlockId, - p: BlockPredecessor, - value_map: ScopedMap, - block_map: ScopedMap, -} -impl CopyBlock { - fn new(b: BlockId, p: BlockPredecessor) -> Self { - Self { - b, - p, - value_map: Default::default(), - block_map: Default::default(), - } - } -} - -#[inline] -fn update_predecessor(function: &mut hir::Function, p: &BlockPredecessor, mut callback: F) -where - F: FnMut(&mut hir::Successor, &mut ValueListPool), -{ - match &mut *function.dfg.insts[p.inst].data { - Instruction::Br(hir::Br { - ref mut successor, .. - }) => { - callback(successor, &mut function.dfg.value_lists); - } - Instruction::CondBr(hir::CondBr { - ref mut then_dest, - ref mut else_dest, - .. - }) => { - assert_ne!(then_dest.destination, else_dest.destination, "unexpected critical edge"); - let value_lists = &mut function.dfg.value_lists; - callback(then_dest, value_lists); - callback(else_dest, value_lists); - } - Instruction::Switch(_) => { - panic!("expected switch instructions to have been simplified prior to treeification") - } - _ => unreachable!(), - } -} - -struct BlockInfos { - blocks: BTreeMap, - cfg: Rc, - domtree: Rc, - loops: Rc, -} -impl BlockInfos { - pub fn new( - cfg: Rc, - domtree: Rc, - loops: Rc, - ) -> Self { - Self { - blocks: Default::default(), - cfg, - domtree, - loops, - } - } - - pub fn insert_copy(&mut self, copied: BlockId, original: BlockId) { - let resolved = self.to_original_block(original); - self.blocks.insert(copied, resolved); - } - - pub fn is_loop_header(&self, block_id: BlockId) -> Option { - let resolved = self.to_original_block(block_id); - self.loops.is_loop_header(resolved) - } - - pub fn rpo_cmp(&self, a: BlockId, b: BlockId) -> core::cmp::Ordering { - let a_orig = self.to_original_block(a); - let b_orig = self.to_original_block(b); - self.domtree.rpo_cmp_block(a_orig, b_orig) - } - - fn to_original_block(&self, mut block_id: BlockId) -> BlockId { - loop { - if let Some(copied_from) = self.blocks.get(&block_id).copied() { - block_id = copied_from; - continue; - } - - break block_id; - } - } -} - -#[cfg(test)] -mod tests { - use midenc_hir::{ - pass::{AnalysisManager, RewritePass}, - testing::{self, TestContext}, - ModuleBuilder, - }; - use pretty_assertions::{assert_eq, assert_ne}; - - use crate::Treeify; - - /// Run the treeify pass on the IR of the [testing::sum_matrix] function. - /// - /// This function corresponds forms a directed, cyclic graph; containing a loop - /// two levels deep, with control flow paths that join multiple predecessors. - /// It has no critical edges, as if we had already run the [SplitCriticalEdges] - /// pass, and doesn't contain any superfluous blocks: - /// - /// We expect this pass to identify that the exit block, `blk0` has multiple predecessors - /// and is not a loop header, and thus a candidate for treeification. We expect `blk0` - /// to be duplicated, so that each of it's predecessors, `entry` and `blk2` respectively, - /// have their own copies of the block. The terminators of those blocks should be - /// updated accordingly. Additionally, because the new versions of `blk0` have only - /// a single predecessor, the block arguments previously needed, should be removed - /// and the `ret` instruction should directly reference the return value originally - /// provided via `entry`/`blk2`. - #[test] - fn treeify_simple_test() { - let context = TestContext::default(); - - // Define the 'test' module - let mut builder = ModuleBuilder::new("test"); - let id = testing::sum_matrix(&mut builder, &context); - let mut module = builder.build(); - let mut function = module.cursor_mut_at(id.function).remove().expect("undefined function"); - - let original = function.to_string(); - let mut analyses = AnalysisManager::default(); - let mut rewrite = Treeify; - rewrite - .apply(&mut function, &mut analyses, &context.session) - .expect("treeification failed"); - - let expected = "\ -(func (export #sum_matrix) - (param (ptr u32)) (param u32) (param u32) (result u32) - (block 0 (param v0 (ptr u32)) (param v1 u32) (param v2 u32) - (let (v10 u32) (const.u32 0)) - (let (v11 u32) (ptrtoint v0)) - (let (v12 i1) (neq v11 0)) - (condbr v12 (block 2) (block 7))) - - (block 7 - (ret v10)) - - (block 2 - (let (v13 u32) (const.u32 0)) - (let (v14 u32) (const.u32 0)) - (let (v15 u32) (mul.checked v2 4)) - (br (block 3 v10 v13 v14))) - - (block 3 (param v4 u32) (param v5 u32) (param v6 u32) - (let (v16 i1) (lt v5 v1)) - (let (v17 u32) (mul.checked v5 v15)) - (condbr v16 (block 4 v4 v5 v6) (block 8))) - - (block 8 - (ret v4)) - - (block 4 (param v7 u32) (param v8 u32) (param v9 u32) - (let (v18 i1) (lt v9 v2)) - (condbr v18 (block 5) (block 6))) - - (block 5 - (let (v19 u32) (mul.checked v9 4)) - (let (v20 u32) (add.checked v17 v19)) - (let (v21 u32) (add.checked v11 v20)) - (let (v22 (ptr u32)) (inttoptr v21)) - (let (v23 u32) (load v22)) - (let (v24 u32) (add.checked v7 v23)) - (let (v25 u32) (incr.wrapping v9)) - (br (block 4 v24 v8 v25))) - - (block 6 - (let (v26 u32) (incr.wrapping v8)) - (let (v27 u32) (const.u32 0)) - (br (block 3 v7 v26 v27))) -)"; - - let transformed = function.to_string(); - assert_ne!(transformed, original); - assert_eq!(transformed.as_str(), expected); - } -} diff --git a/hir-type/CHANGELOG.md b/hir-type/CHANGELOG.md index 1e3a442a9..936e4bed4 100644 --- a/hir-type/CHANGELOG.md +++ b/hir-type/CHANGELOG.md @@ -6,6 +6,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.2] + +### Added + +- Added `TypeRepr::BigEndian` to aid in making some existing legacy protocol library types representable in the type system, to be used as a temporary +work-around until we are able to redefine those types in a standard layout + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-hir-type-v0.1.5...midenc-hir-type-v0.4.0) - 2025-08-15 + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-hir-type-v0.0.7...midenc-hir-type-v0.0.8) - 2025-04-24 + +### Added +- *(types)* clean up hir-type for use outside the compiler +- implement pretty-print trait for Symbol/Type + +### Other +- treat warnings as compiler errors, +- update rust toolchain, clean up deps +- implement hir dialect ops, flesh out remaining core ir infra + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-hir-type-v0.0.6...midenc-hir-type-v0.0.7) - 2024-09-17 ### Other diff --git a/hir-type/Cargo.toml b/hir-type/Cargo.toml index bee94a9d2..bc686504f 100644 --- a/hir-type/Cargo.toml +++ b/hir-type/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "midenc-hir-type" description = "Type system and utilities for Miden HIR" -version.workspace = true -rust-version.workspace = true +version = "0.4.2" +rust-version = "1.88" authors.workspace = true repository.workspace = true categories.workspace = true @@ -13,9 +13,11 @@ edition.workspace = true [features] default = ["serde"] -serde = ["dep:serde", "dep:serde_repr"] +serde = ["dep:serde", "dep:serde_repr", "smallvec/serde"] [dependencies] +miden-formatting.workspace = true smallvec.workspace = true serde = { workspace = true, optional = true } -serde_repr = { workspace = true, optional = true } +serde_repr = { version = "0.1", optional = true } +thiserror = { version = "2.0", default-features = false } diff --git a/hir-type/README.md b/hir-type/README.md new file mode 100644 index 000000000..0c91f89bc --- /dev/null +++ b/hir-type/README.md @@ -0,0 +1,9 @@ +# hir-type + +This crate defines the type system used by Miden's compiler infrastructure, as well as in its assembler and packaging crates. + +See the crate documentation for details on the type system and its APIs. + +# License + +MIT diff --git a/hir-type/src/alignable.rs b/hir-type/src/alignable.rs new file mode 100644 index 000000000..eb9fe2f3a --- /dev/null +++ b/hir-type/src/alignable.rs @@ -0,0 +1,218 @@ +/// This trait represents an alignable primitive integer value representing an address +pub trait Alignable { + /// This function computes the offset, in bytes, needed to align `self` upwards so that + /// it is aligned to `align` bytes. + /// + /// The following must be true, or this function will panic: + /// + /// * `align` is non-zero + /// * `align` is a power of two + fn align_offset(self, align: Self) -> Self; + /// This function aligns `self` to the specified alignment (in bytes), aligning upwards. + /// + /// The following must be true, or this function will panic: + /// + /// * `align` is non-zero + /// * `align` is a power of two + /// * `self` + `align` must be less than `Self::MAX` + fn align_up(self, align: Self) -> Self; + + /// Compute the nearest power of two less than or equal to `self` + fn prev_power_of_two(self) -> Self; +} + +macro_rules! alignable { + ($($ty:ty),+) => { + $( + alignable_impl!($ty); + )* + }; +} + +macro_rules! alignable_impl { + ($ty:ty) => { + #[allow(unstable_name_collisions)] + impl Alignable for $ty { + #[inline] + fn align_offset(self, align: Self) -> Self { + self.align_up(align) - self + } + + #[inline] + fn align_up(self, align: Self) -> Self { + assert_ne!(align, 0); + assert!(align.is_power_of_two()); + self.checked_next_multiple_of(align).expect("alignment overflow") + } + + #[inline] + fn prev_power_of_two(self) -> Self { + if self.is_power_of_two() { + self + } else { + core::cmp::max(self.next_power_of_two() / 2, 1) + } + } + } + }; +} + +alignable!(u8, u16, u32, u64, usize); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn alignable_next_multiple_of() { + let addr = 0u32; + assert_eq!(addr.next_multiple_of(1), 0); + assert_eq!(addr.next_multiple_of(2), 0); + assert_eq!(addr.next_multiple_of(4), 0); + assert_eq!(addr.next_multiple_of(8), 0); + assert_eq!(addr.next_multiple_of(16), 0); + assert_eq!(addr.next_multiple_of(32), 0); + + let addr = 1u32; + assert_eq!(addr.next_multiple_of(1), 1); + assert_eq!(addr.next_multiple_of(2), 2); + assert_eq!(addr.next_multiple_of(4), 4); + assert_eq!(addr.next_multiple_of(8), 8); + assert_eq!(addr.next_multiple_of(16), 16); + assert_eq!(addr.next_multiple_of(32), 32); + + let addr = 2u32; + assert_eq!(addr.next_multiple_of(1), 2); + assert_eq!(addr.next_multiple_of(2), 2); + assert_eq!(addr.next_multiple_of(4), 4); + assert_eq!(addr.next_multiple_of(8), 8); + assert_eq!(addr.next_multiple_of(16), 16); + assert_eq!(addr.next_multiple_of(32), 32); + + let addr = 3u32; + assert_eq!(addr.next_multiple_of(1), 3); + assert_eq!(addr.next_multiple_of(2), 4); + assert_eq!(addr.next_multiple_of(4), 4); + assert_eq!(addr.next_multiple_of(8), 8); + assert_eq!(addr.next_multiple_of(16), 16); + assert_eq!(addr.next_multiple_of(32), 32); + + let addr = 127u32; + assert_eq!(addr.next_multiple_of(1), 127); + assert_eq!(addr.next_multiple_of(2), 128); + assert_eq!(addr.next_multiple_of(4), 128); + assert_eq!(addr.next_multiple_of(8), 128); + assert_eq!(addr.next_multiple_of(16), 128); + assert_eq!(addr.next_multiple_of(32), 128); + + let addr = 130u32; + assert_eq!(addr.next_multiple_of(1), 130); + assert_eq!(addr.next_multiple_of(2), 130); + assert_eq!(addr.next_multiple_of(4), 132); + assert_eq!(addr.next_multiple_of(8), 136); + assert_eq!(addr.next_multiple_of(16), 144); + assert_eq!(addr.next_multiple_of(32), 160); + } + + #[test] + fn alignable_align_offset_test() { + let addr = 0u32; + assert_eq!(addr.align_offset(1), 0); + assert_eq!(addr.align_offset(2), 0); + assert_eq!(addr.align_offset(4), 0); + assert_eq!(addr.align_offset(8), 0); + assert_eq!(addr.align_offset(16), 0); + assert_eq!(addr.align_offset(32), 0); + + let addr = 1u32; + assert_eq!(addr.align_offset(1), 0); + assert_eq!(addr.align_offset(2), 1); + assert_eq!(addr.align_offset(4), 3); + assert_eq!(addr.align_offset(8), 7); + assert_eq!(addr.align_offset(16), 15); + assert_eq!(addr.align_offset(32), 31); + + let addr = 2u32; + assert_eq!(addr.align_offset(1), 0); + assert_eq!(addr.align_offset(2), 0); + assert_eq!(addr.align_offset(4), 2); + assert_eq!(addr.align_offset(8), 6); + assert_eq!(addr.align_offset(16), 14); + assert_eq!(addr.align_offset(32), 30); + + let addr = 3u32; + assert_eq!(addr.align_offset(1), 0); + assert_eq!(addr.align_offset(2), 1); + assert_eq!(addr.align_offset(4), 1); + assert_eq!(addr.align_offset(8), 5); + assert_eq!(addr.align_offset(16), 13); + assert_eq!(addr.align_offset(32), 29); + + let addr = 127u32; + assert_eq!(addr.align_offset(1), 0); + assert_eq!(addr.align_offset(2), 1); + assert_eq!(addr.align_offset(4), 1); + assert_eq!(addr.align_offset(8), 1); + assert_eq!(addr.align_offset(16), 1); + assert_eq!(addr.align_offset(32), 1); + + let addr = 130u32; + assert_eq!(addr.align_offset(1), 0); + assert_eq!(addr.align_offset(2), 0); + assert_eq!(addr.align_offset(4), 2); + assert_eq!(addr.align_offset(8), 6); + assert_eq!(addr.align_offset(16), 14); + assert_eq!(addr.align_offset(32), 30); + } + + #[test] + fn alignable_align_up_test() { + let addr = 0u32; + assert_eq!(addr.align_up(1), 0); + assert_eq!(addr.align_up(2), 0); + assert_eq!(addr.align_up(4), 0); + assert_eq!(addr.align_up(8), 0); + assert_eq!(addr.align_up(16), 0); + assert_eq!(addr.align_up(32), 0); + + let addr = 1u32; + assert_eq!(addr.align_up(1), 1); + assert_eq!(addr.align_up(2), 2); + assert_eq!(addr.align_up(4), 4); + assert_eq!(addr.align_up(8), 8); + assert_eq!(addr.align_up(16), 16); + assert_eq!(addr.align_up(32), 32); + + let addr = 2u32; + assert_eq!(addr.align_up(1), 2); + assert_eq!(addr.align_up(2), 2); + assert_eq!(addr.align_up(4), 4); + assert_eq!(addr.align_up(8), 8); + assert_eq!(addr.align_up(16), 16); + assert_eq!(addr.align_up(32), 32); + + let addr = 3u32; + assert_eq!(addr.align_up(1), 3); + assert_eq!(addr.align_up(2), 4); + assert_eq!(addr.align_up(4), 4); + assert_eq!(addr.align_up(8), 8); + assert_eq!(addr.align_up(16), 16); + assert_eq!(addr.align_up(32), 32); + + let addr = 127u32; + assert_eq!(addr.align_up(1), 127); + assert_eq!(addr.align_up(2), 128); + assert_eq!(addr.align_up(4), 128); + assert_eq!(addr.align_up(8), 128); + assert_eq!(addr.align_up(16), 128); + assert_eq!(addr.align_up(32), 128); + + let addr = 130u32; + assert_eq!(addr.align_up(1), 130); + assert_eq!(addr.align_up(2), 130); + assert_eq!(addr.align_up(4), 132); + assert_eq!(addr.align_up(8), 136); + assert_eq!(addr.align_up(16), 144); + assert_eq!(addr.align_up(32), 160); + } +} diff --git a/hir-type/src/array_type.rs b/hir-type/src/array_type.rs new file mode 100644 index 000000000..6c0e1af11 --- /dev/null +++ b/hir-type/src/array_type.rs @@ -0,0 +1,69 @@ +use super::{Alignable, Type}; + +/// A fixed-size, homogenous vector type. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct ArrayType { + pub ty: Type, + pub len: usize, +} + +impl ArrayType { + /// Create a new [ArrayType] of length `len` and element type of `ty` + pub fn new(ty: Type, len: usize) -> Self { + Self { ty, len } + } + + /// Get the size of this array type + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.len + } + + /// Get the element type of this array type + pub fn element_type(&self) -> &Type { + &self.ty + } + + /// Returns true if this array type represents a zero-sized type + pub fn is_zst(&self) -> bool { + self.len == 0 || self.ty.is_zst() + } + + /// Returns the minimum alignment required by this type + pub fn min_alignment(&self) -> usize { + self.ty.min_alignment() + } + + /// Returns the size in bits of this array type + pub fn size_in_bits(&self) -> usize { + match self.len { + // Zero-sized arrays have no size in memory + 0 => 0, + // An array of one element is the same as just the element + 1 => self.ty.size_in_bits(), + // All other arrays require alignment padding between elements + n => { + let min_align = self.ty.min_alignment() * 8; + let element_size = self.ty.size_in_bits(); + let padded_element_size = element_size.align_up(min_align); + element_size + (padded_element_size * (n - 1)) + } + } + } +} + +impl core::fmt::Display for ArrayType { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use miden_formatting::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl miden_formatting::prettier::PrettyPrint for ArrayType { + fn render(&self) -> miden_formatting::prettier::Document { + use miden_formatting::prettier::*; + + const_text("[") + self.ty.render() + const_text("; ") + self.len.render() + const_text("]") + } +} diff --git a/hir-type/src/function_type/abi.rs b/hir-type/src/function_type/abi.rs new file mode 100644 index 000000000..0bc6ef160 --- /dev/null +++ b/hir-type/src/function_type/abi.rs @@ -0,0 +1,194 @@ +use core::fmt; + +/// Represents the calling convention of a function. +/// +/// Calling conventions are part of a program's ABI (Application Binary Interface), and defines the +/// contract between caller and callee, by specifying the architecture-specific details of how +/// arguments are passed, results are returned, what effects may/will occur (e.g. context switches), +/// etc. +/// +/// Additionally, calling conventions define the set of allowed types for function arguments and +/// results, and how those types are represented in the target ABI. It may impose additional +/// restrictions on callers, such as only allowing calls under specific conditions. +/// +/// It is not required that callers be functions of the same convention as the callee, it is +/// perfectly acceptable to mix conventions in a program. The only requirement is that the +/// convention used at a given call site, matches the convention of the callee, i.e. it must +/// be the case that caller and callee agree on the convention used for that call. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash)] +#[cfg_attr( + feature = "serde", + derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) +)] +#[repr(u8)] +pub enum CallConv { + /// This convention passes all arguments and results by value, and thus requires that the types + /// of its arguments and results be valid immediates in the Miden ABI. The representation of + /// those types on the operand stack is specified by the Miden ABI. + /// + /// Additional properties of this convention: + /// + /// * It is always executed in the caller's context + /// * May only be the target of a `exec` or `dynexec` instruction + /// * Must not require more than 16 elements of the operand stack for arguments or results + /// * Callees are expected to preserve the state of the operand stack following the function + /// arguments, such that from the caller's perspective, the effect of the call on the operand + /// stack is as if the function arguments had been popped, and the function results were + /// pushed. + /// + /// This convention is optimal when both caller and callee are in the same language, no context + /// switching is required, and the arguments and results can be passed on the operand stack + /// without the potential for overflow. + Fast, + /// The C calling convention, as specified for System V (x86), adapted to Miden's ABI. + /// + /// The purpose of this convention is to support cross-language interop via a foreign function + /// interface (FFI) based on the C data layout rules. It is specifically designed for interop + /// occurring _within_ the same context. For cross-language, cross-context interop, we require + /// the use of the Wasm Component Model, see the `CanonLift` convention for more. + /// + /// Notable properties of this convention: + /// + /// * It is always executed in the caller's context + /// * May only be the target of a `exec` or `dynexec` instruction + /// * Supports any IR type that corresponds to a valid C type (i.e. structs, arrays, etc.) + /// * Aggregates (structs and arrays) must be returned by reference, where the caller is + /// responsible for allocating the memory to which the return value will be written. The + /// caller passes the pointer to that memory as the first parameter of the function, which + /// must be of matching pointer type, and marked with the `sret` attribute. When the "struct + /// return" protocol is in use, the function does not return any values directly. + /// * Aggregates up to 64 bits may be passed by value, all other structs must be passed by + /// reference. + /// * Integer types up to 128 bits are supported, and are passed by value + /// * Floating-point types are not supported + /// * Callees must preserve the state of the operand stack following the function arguments + /// * If the function arguments require more than 16 elements of the operand stack to represent, + /// then arguments will be spilled to the caller's stack frame, such that no more than 15 + /// elements are required. The caller must then obtain the value of the stack pointer on + /// entry, and offset it to access spilled arguments as desired. The arguments are spilled + /// in reverse order, i.e. the last argument in the argument list has the greatest offset, + /// while the first argument of the argument list to be spilled starts at `sp` (the value + /// of the stack pointer on entry). + /// + /// NOTE: This convention is non-optimal if not being used for cross-language interop. + /// + #[default] + SystemV, + /// This convention is used to represent functions translated from WebAssembly. + /// + /// It has the following properties: + /// + /// * It is always executed in the caller's context + /// * May only be the target of a `exec` or `dynexec` instruction + /// * Only supports IR types which correspond to a valid WebAssembly type. Notably this does + /// not include aggregates (except via reference types, which are not currently supported). + /// * Floating-point types are not allowed, except `f32` which is used to represent field + /// elements in the WebAssembly type system. + /// * Callees must preserve the state of the operand stack following the function arguments + /// * Uses the same argument spilling strategy as the `SystemV` convention + Wasm, + /// This convention represents one of the host-defined primitives of the Wasm Component Model. + /// + /// In particular, this convention corresponds to functions synthesized via a `(canon lift)` + /// declaration, which is used to export a core Wasm function with a Canonical ABI signature. + /// These synthesized functions are responsible for "lowering" arguments out of the Canonical + /// ABI into a `Wasm`-compatible representation, and "lifting" results back into the Canonical + /// ABI. + /// + /// NOTE: Some important details of this calling convention are described by the Canonical ABI + /// spec covering the `canon lift` primitive, as well as how types are lifted/lowered from/to + /// the "flattened" core Wasm type representation. See + /// [this document](https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md#lifting-and-lowering-values) + /// for those details, and other useful information about the Canonical ABI. + /// + /// It has the following properties: + /// + /// * It is always executed in a new context + /// * May only be the target of a `call` or `dyncall` instruction + /// * May only be called from a `CanonLower` function + /// * Only supports IR types which correspond to a valid Canonical ABI type. Notably this does + /// not include pointer types. No support for `resource` types exists at this time due to + /// limitations of the Miden VM. + /// * Callers must ensure that sensitive data is removed from the first 16 elements of the + /// operand stack when lowering calls to `CanonLift` functions. This compiler will zero-pad + /// the unused portions of the operand stack where applicable. + /// * If the function arguments require more than 16 elements of the operand stack to represent, + /// then arguments will be spilled to the advice provider, as a block of memory (in words) + /// sufficiently large to hold all of the spilled arguments. The block will be hashed and + /// the digest used as the key in the advice map under which the block will be stored. The + /// digest will then be passed by-value to the callee as the first argument. This requires + /// a word (4 elements) of operand stack to be unused, so when spilling arguments, the first + /// 12 elements are preserved, while the rest are spilled. The callee is expected to, as part + /// of its prologue, immediately fetch the spilled arguments from the advice map using the + /// provided digest on top of the operand stack, and write them into the current stack frame. + /// The spilled arguments can then be accessed by computing the offset from the stack pointer + /// to the desired argument. Note that, like the spill strategy for `SystemV`, the spilled + /// arguments will be written into memory in reverse order (the closer to the front of the + /// argument list, the smaller the offset). + /// + /// Unlike `CanonLower`, the details of this calling convention are stable, as it is designed + /// expressly for cross-language, cross-context interop, and is in fact the only supported + /// way to represent cross-context function calls at this time. + CanonLift, + /// This convention represents one of the host-defined primitives of the Wasm Component Model. + /// + /// In particular, this convention corresponds to functions synthesized via a `(canon lower)` + /// declaration, which is used to import a Canonical ABI function into a core Wasm module, + /// by providing a `Wasm`-compatible adapter for the underlying Canonical ABI function. These + /// synthesized functions are responsible for "lifting" the core Wasm arguments into the + /// Canonical ABI representation, and "lowering" the results out of that representation. + /// + /// This convention is identical to `Wasm`, with the following additional properties: + /// + /// * It is the only convention which may contain calls to a `CanonLift` function + /// * Functions using this convention are not allowed to have `Public` visibility + /// * Functions using this convention are considered to be compiler-generated, and thus are + /// aggressively inlined/eliminated where possible. + /// + /// This should be considered an unstable, compiler-internal calling convention, and the details + /// of this convention can change at any time. Currently, it is only used by the Wasm frontend + /// to distinguish functions synthesized from a `(canon lower)`. + CanonLower, + /// This convention is like `Fast`, but indicates that the function implements a syscall as + /// part of a kernel module definition. + /// + /// Additional properties include: + /// + /// * It is always executed in the _root_ context, and therefore a context switch is + /// involved. + /// * This convention may only be called via the `syscall` instruction, and may not be + /// called from another `Kernel` function. + /// * This convention is only permitted on function _definitions_ when emitting a kernel library + /// * In addition to the type restrictions described by the `Fast` convention, it additionally + /// forbids any arguments/results of pointer type, due to the context switch that occurs. + Kernel, +} + +impl CallConv { + /// Returns true if this convention corresponds to one of the two Canonical ABI conventions + pub fn is_wasm_canonical_abi(&self) -> bool { + matches!(self, Self::CanonLift | Self::CanonLower) + } +} + +impl fmt::Display for CallConv { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use miden_formatting::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl miden_formatting::prettier::PrettyPrint for CallConv { + fn render(&self) -> miden_formatting::prettier::Document { + use miden_formatting::prettier::const_text; + + match self { + Self::Fast => const_text("fast"), + Self::SystemV => const_text("C"), + Self::Wasm => const_text("wasm"), + Self::CanonLift => const_text("canon-lift"), + Self::CanonLower => const_text("canon-lower"), + Self::Kernel => const_text("kernel"), + } + } +} diff --git a/hir-type/src/function_type/mod.rs b/hir-type/src/function_type/mod.rs new file mode 100644 index 000000000..c60ce34e5 --- /dev/null +++ b/hir-type/src/function_type/mod.rs @@ -0,0 +1,104 @@ +mod abi; + +use core::fmt; + +use smallvec::SmallVec; + +pub use self::abi::CallConv; +use super::Type; + +/// This represents the type of a function, i.e. it's parameters and results, and expected calling +/// convention. +/// +/// Function types are reference types, i.e. they are always implicitly a handle/pointer to a +/// function, not a function value. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct FunctionType { + /// The calling convention/ABI of the function represented by this type + pub abi: CallConv, + /// The parameter types of this function + pub params: SmallVec<[Type; 4]>, + /// The result types of this function + pub results: SmallVec<[Type; 1]>, +} +impl FunctionType { + /// Create a new function type with the given calling/convention ABI + pub fn new, R: IntoIterator>( + abi: CallConv, + params: P, + results: R, + ) -> Self { + Self { + abi, + params: params.into_iter().collect(), + results: results.into_iter().collect(), + } + } + + /// Set the calling convention/ABI for this function type + pub fn with_calling_convention(mut self, abi: CallConv) -> Self { + self.abi = abi; + self + } + + /// The calling convention/ABI represented by this function type + pub fn calling_convention(&self) -> CallConv { + self.abi + } + + /// The number of parameters expected by the function + pub fn arity(&self) -> usize { + self.params.len() + } + + /// The types of the function parameters as a slice + pub fn params(&self) -> &[Type] { + self.params.as_slice() + } + + /// The types of the function results as a slice + pub fn results(&self) -> &[Type] { + self.results.as_slice() + } +} + +impl fmt::Display for FunctionType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use miden_formatting::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl miden_formatting::prettier::PrettyPrint for FunctionType { + fn render(&self) -> miden_formatting::prettier::Document { + use alloc::format; + + use miden_formatting::prettier::*; + + let abi = const_text("extern") + text(format!(" \"{}\" ", &self.abi)); + + let params = self.params.iter().enumerate().fold(const_text("("), |acc, (i, param)| { + if i > 0 { + acc + const_text(", ") + param.render() + } else { + acc + param.render() + } + }) + const_text(")"); + + let without_results = abi + const_text("fn") + params; + if self.results.is_empty() { + return without_results; + } + + let results = self.results.iter().enumerate().fold(Document::Empty, |acc, (i, item)| { + if i > 0 { + acc + const_text(", ") + item.render() + } else { + item.render() + } + }); + + without_results + const_text(" -> ") + results + } +} diff --git a/hir-type/src/layout.rs b/hir-type/src/layout.rs index 8b13f094c..a8b4acd56 100644 --- a/hir-type/src/layout.rs +++ b/hir-type/src/layout.rs @@ -1,7 +1,7 @@ use alloc::{alloc::Layout, collections::VecDeque}; use core::cmp::{self, Ordering}; -use smallvec::SmallVec; +use smallvec::{smallvec, SmallVec}; use super::*; @@ -72,336 +72,323 @@ impl Type { | Self::U32 | Self::Ptr(_) | Self::I16 - | Self::U16) => { + | Self::U16 + | Self::Function(_)) => { let len = ty.size_in_bytes(); let remaining = len - n; match (n, remaining) { (0, _) | (_, 0) => unreachable!(), (1, 1) => (Type::U8, Some(Type::U8)), - (1, remaining) => (Type::U8, Some(Type::Array(Box::new(Type::U8), remaining))), - (taken, 1) => (Type::Array(Box::new(Type::U8), taken), Some(Type::U8)), + (1, remaining) => { + (Type::U8, Some(Type::from(ArrayType::new(Type::U8, remaining)))) + } + (taken, 1) => (Type::from(ArrayType::new(Type::U8, taken)), Some(Type::U8)), (taken, remaining) => ( - Type::Array(Box::new(Type::U8), taken), - Some(Type::Array(Box::new(Type::U8), remaining)), + Type::from(ArrayType::new(Type::U8, taken)), + Some(Type::from(ArrayType::new(Type::U8, remaining))), ), } } - Self::NativePtr(pointee, _) => { - let struct_ty = Type::Struct(StructType { - repr: TypeRepr::Default, - size: 12, - fields: Vec::from([ - StructField { - index: 0, - align: 4, - offset: 0, - ty: Type::Ptr(pointee), - }, - StructField { - index: 1, - align: 4, - offset: 4, - ty: Type::U8, - }, - StructField { - index: 2, - align: 4, - offset: 8, - ty: Type::U8, - }, - ]), - }); - struct_ty.split(n) - } - Self::Array(elem_ty, 1) => elem_ty.split(n), - Self::Array(elem_ty, array_len) => { - let elem_size = elem_ty.size_in_bytes(); - if n >= elem_size { - // The requested split consumes 1 or more elements.. - let take = n / elem_size; - let extra = n % elem_size; - if extra == 0 { - // The split is on an element boundary - let split = match take { - 1 => (*elem_ty).clone(), - _ => Self::Array(elem_ty.clone(), take), - }; - let rest = match array_len - take { - 0 => unreachable!(), - 1 => *elem_ty, - len => Self::Array(elem_ty, len), - }; - (split, Some(rest)) - } else { - // The element type must be split somewhere in order to get the input type - // down to the requested size - let (partial1, partial2) = (*elem_ty).clone().split(elem_size - extra); - match array_len - take { - 0 => unreachable!(), - 1 => { - let taken = Self::Array(elem_ty, take); - let split = Self::Struct(StructType::new_with_repr( - TypeRepr::packed(1), - [taken, partial1], - )); - (split, partial2) - } - remaining => { - let remaining_input = Self::Array(elem_ty.clone(), remaining); - let taken = Self::Array(elem_ty, take); - let split = Self::Struct(StructType::new_with_repr( - TypeRepr::packed(1), - [taken, partial1], - )); - let rest = Self::Struct(StructType::new_with_repr( - TypeRepr::packed(1), - [partial2.unwrap(), remaining_input], - )); - (split, Some(rest)) + Self::Array(array_type) => match &*array_type { + ArrayType { + ty: elem_ty, + len: 1, + } => elem_ty.clone().split(n), + ArrayType { + ty: elem_ty, + len: array_len, + } => { + let elem_size = elem_ty.size_in_bytes(); + if n >= elem_size { + // The requested split consumes 1 or more elements.. + let take = n / elem_size; + let extra = n % elem_size; + if extra == 0 { + // The split is on an element boundary + let split = match take { + 1 => (*elem_ty).clone(), + _ => Self::from(ArrayType::new(elem_ty.clone(), take)), + }; + let rest = match array_len - take { + 0 => unreachable!(), + 1 => elem_ty.clone(), + len => Self::from(ArrayType::new(elem_ty.clone(), len)), + }; + (split, Some(rest)) + } else { + // The element type must be split somewhere in order to get the input type + // down to the requested size + let (partial1, partial2) = (*elem_ty).clone().split(elem_size - extra); + match array_len - take { + 0 => unreachable!(), + 1 => { + let taken = Self::from(ArrayType::new(elem_ty.clone(), take)); + let split = Self::from(StructType::new_with_repr( + TypeRepr::packed(1), + [taken, partial1], + )); + (split, partial2) + } + remaining => { + let remaining_input = + Self::from(ArrayType::new(elem_ty.clone(), remaining)); + let taken = Self::from(ArrayType::new(elem_ty.clone(), take)); + let split = Self::from(StructType::new_with_repr( + TypeRepr::packed(1), + [taken, partial1], + )); + let rest = Self::from(StructType::new_with_repr( + TypeRepr::packed(1), + [partial2.unwrap(), remaining_input], + )); + (split, Some(rest)) + } } } + } else { + // The requested split consumes less than one element + let (partial1, partial2) = (*elem_ty).clone().split(n); + let remaining_input = match array_len - 1 { + 0 => unreachable!(), + 1 => (*elem_ty).clone(), + len => Self::from(ArrayType::new(elem_ty.clone(), len - 1)), + }; + let rest = Self::from(StructType::new_with_repr( + TypeRepr::packed(1), + [partial2.unwrap(), remaining_input], + )); + (partial1, Some(rest)) } - } else { - // The requested split consumes less than one element - let (partial1, partial2) = (*elem_ty).clone().split(n); - let remaining_input = match array_len - 1 { - 0 => unreachable!(), - 1 => (*elem_ty).clone(), - len => Self::Array(elem_ty, len - 1), - }; - let rest = Self::Struct(StructType::new_with_repr( - TypeRepr::packed(1), - [partial2.unwrap(), remaining_input], - )); - (partial1, Some(rest)) } - } - Self::Struct(StructType { - repr: TypeRepr::Transparent, - fields, - .. - }) => { - let underlying = fields - .into_iter() - .find(|f| !f.ty.is_zst()) - .expect("invalid type: expected non-zero sized field"); - underlying.ty.split(n) - } - Self::Struct(struct_ty) => { - let original_repr = struct_ty.repr; - let original_size = struct_ty.size; - let mut fields = VecDeque::from(struct_ty.fields); - let mut split = StructType { - repr: original_repr, - size: 0, - fields: Vec::new(), - }; - let mut remaining = StructType { - repr: TypeRepr::packed(1), - size: 0, - fields: Vec::new(), - }; - let mut needed: u32 = n.try_into().expect( - "invalid type split: number of bytes is larger than what is representable in \ - memory", - ); - let mut current_offset = 0u32; - while let Some(mut field) = fields.pop_front() { - let padding = field.offset - current_offset; - // If the padding was exactly what was needed, add it to the `split` - // struct, and then place the remaining fields in a new struct - let original_offset = field.offset; - if padding == needed { - split.size += needed; - // Handle the edge case where padding is at the front of the struct - if split.fields.is_empty() { - split.fields.push(StructField { - index: 0, - align: 1, - offset: 0, - ty: Type::Array(Box::new(Type::U8), needed as usize), - }); + }, + Self::Struct(struct_ty) => match &*struct_ty { + StructType { + repr: TypeRepr::Transparent, + fields, + .. + } => { + let underlying = fields + .into_iter() + .find(|f| !f.ty.is_zst()) + .expect("invalid type: expected non-zero sized field"); + underlying.ty.clone().split(n) + } + struct_ty => { + let original_repr = struct_ty.repr; + let original_size = struct_ty.size; + let mut fields = VecDeque::from_iter(struct_ty.fields.iter().cloned()); + let mut split = StructType { + repr: original_repr, + size: 0, + fields: smallvec![], + }; + let mut remaining = StructType { + repr: TypeRepr::packed(1), + size: 0, + fields: smallvec![], + }; + let mut needed: u32 = n.try_into().expect( + "invalid type split: number of bytes is larger than what is representable \ + in memory", + ); + let mut current_offset = 0u32; + while let Some(mut field) = fields.pop_front() { + let padding = field.offset - current_offset; + // If the padding was exactly what was needed, add it to the `split` + // struct, and then place the remaining fields in a new struct + let original_offset = field.offset; + if padding == needed { + split.size += needed; + // Handle the edge case where padding is at the front of the struct + if split.fields.is_empty() { + split.fields.push(StructField { + index: 0, + align: 1, + offset: 0, + ty: Type::from(ArrayType::new(Type::U8, needed as usize)), + }); + } + let mut prev_offset = original_offset; + let mut field_offset = 0; + field.index = 0; + field.offset = field_offset; + remaining.repr = TypeRepr::Default; + remaining.size = original_size - split.size; + remaining.fields.reserve(1 + fields.len()); + field_offset += field.ty.size_in_bytes() as u32; + remaining.fields.push(field); + for (index, mut field) in fields.into_iter().enumerate() { + field.index = (index + 1) as u8; + let align_offset = field.offset - prev_offset; + let field_size = field.ty.size_in_bytes() as u32; + prev_offset = field.offset + field_size; + field.offset = field_offset + align_offset; + field_offset += align_offset; + field_offset += field_size; + remaining.fields.push(field); + } + break; } - let mut prev_offset = original_offset; - let mut field_offset = 0; - field.index = 0; - field.offset = field_offset; - remaining.repr = TypeRepr::Default; - remaining.size = original_size - split.size; - remaining.fields.reserve(1 + fields.len()); - field_offset += field.ty.size_in_bytes() as u32; - remaining.fields.push(field); - for (index, mut field) in fields.into_iter().enumerate() { - field.index = (index + 1) as u8; - let align_offset = field.offset - prev_offset; - let field_size = field.ty.size_in_bytes() as u32; - prev_offset = field.offset + field_size; - field.offset = field_offset + align_offset; - field_offset += align_offset; - field_offset += field_size; + + // If the padding is more than was needed, we fill out the rest of the + // request by padding the size of the `split` struct, and then adjust + // the remaining struct to account for the leftover padding. + if padding > needed { + // The struct size must match the requested split size + split.size += needed; + // Handle the edge case where padding is at the front of the struct + if split.fields.is_empty() { + split.fields.push(StructField { + index: 0, + align: 1, + offset: 0, + ty: Type::from(ArrayType::new(Type::U8, needed as usize)), + }); + } + // What's left must account for what has been split off + let leftover_padding = u16::try_from(padding - needed).expect( + "invalid type: padding is larger than maximum allowed alignment", + ); + let effective_alignment = leftover_padding.prev_power_of_two(); + let align_offset = leftover_padding % effective_alignment; + let default_alignment = cmp::max( + fields.iter().map(|f| f.align).max().unwrap_or(1), + field.align, + ); + let repr = match default_alignment.cmp(&effective_alignment) { + Ordering::Equal => TypeRepr::Default, + Ordering::Greater => TypeRepr::packed(effective_alignment), + Ordering::Less => TypeRepr::align(effective_alignment), + }; + let mut prev_offset = original_offset; + let mut field_offset = align_offset as u32; + field.index = 0; + field.offset = field_offset; + remaining.repr = repr; + remaining.size = original_size - split.size; + remaining.fields.reserve(1 + fields.len()); + field_offset += field.ty.size_in_bytes() as u32; remaining.fields.push(field); + for (index, mut field) in fields.into_iter().enumerate() { + field.index = (index + 1) as u8; + let align_offset = field.offset - prev_offset; + let field_size = field.ty.size_in_bytes() as u32; + prev_offset = field.offset + field_size; + field.offset = field_offset + align_offset; + field_offset += align_offset; + field_offset += field_size; + remaining.fields.push(field); + } + break; } - break; - } - // If the padding is more than was needed, we fill out the rest of the - // request by padding the size of the `split` struct, and then adjust - // the remaining struct to account for the leftover padding. - if padding > needed { - // The struct size must match the requested split size - split.size += needed; - // Handle the edge case where padding is at the front of the struct - if split.fields.is_empty() { + // The padding must be less than what was needed, so consume it, and + // then process the current field for the rest of the request + split.size += padding; + needed -= padding; + current_offset += padding; + let field_size = field.ty.size_in_bytes() as u32; + // If the field fully satisifies the remainder of the request, then + // finalize the `split` struct, and place remaining fields in a trailing + // struct with an appropriate repr + if field_size == needed { + split.size += field_size; + field.offset = current_offset; + split.fields.push(field); + + debug_assert!( + !fields.is_empty(), + "expected struct that is the exact size of the split request to \ + have been handled elsewhere" + ); + + remaining.repr = original_repr; + remaining.size = original_size - split.size; + remaining.fields.reserve(fields.len()); + let mut prev_offset = current_offset + field_size; + let mut field_offset = 0; + for (index, mut field) in fields.into_iter().enumerate() { + field.index = index as u8; + let align_offset = field.offset - prev_offset; + let field_size = field.ty.size_in_bytes() as u32; + prev_offset = field.offset + field_size; + field.offset = field_offset + align_offset; + field_offset += align_offset; + field_offset += field_size; + remaining.fields.push(field); + } + break; + } + + // If the field is larger than what is needed, we have to split it + if field_size > needed { + split.size += needed; + + // Add the portion needed to `split` + let index = field.index; + let offset = current_offset; + let align = field.align; + let (partial1, partial2) = field.ty.split(needed as usize); + // The second half of the split will always be a type + let partial2 = partial2.unwrap(); split.fields.push(StructField { + index, + offset, + align, + ty: partial1, + }); + + // Build a struct with the remaining fields and trailing partial field + let mut prev_offset = current_offset + needed; + let mut field_offset = needed + partial2.size_in_bytes() as u32; + remaining.size = original_size - split.size; + remaining.fields.reserve(1 + fields.len()); + remaining.fields.push(StructField { index: 0, + offset: 1, align: 1, - offset: 0, - ty: Type::Array(Box::new(Type::U8), needed as usize), + ty: partial2, }); + for (index, mut field) in fields.into_iter().enumerate() { + field.index = (index + 1) as u8; + let align_offset = field.offset - prev_offset; + let field_size = field.ty.size_in_bytes() as u32; + prev_offset = field.offset + needed + field_size; + field.offset = field_offset + align_offset; + field_offset += align_offset; + field_offset += field_size; + remaining.fields.push(field); + } + break; } - // What's left must account for what has been split off - let leftover_padding = u16::try_from(padding - needed).expect( - "invalid type: padding is larger than maximum allowed alignment", - ); - let effective_alignment = leftover_padding.prev_power_of_two(); - let align_offset = leftover_padding % effective_alignment; - let default_alignment = cmp::max( - fields.iter().map(|f| f.align).max().unwrap_or(1), - field.align, - ); - let repr = match default_alignment.cmp(&effective_alignment) { - Ordering::Equal => TypeRepr::Default, - Ordering::Greater => TypeRepr::packed(effective_alignment), - Ordering::Less => TypeRepr::align(effective_alignment), - }; - let mut prev_offset = original_offset; - let mut field_offset = align_offset as u32; - field.index = 0; - field.offset = field_offset; - remaining.repr = repr; - remaining.size = original_size - split.size; - remaining.fields.reserve(1 + fields.len()); - field_offset += field.ty.size_in_bytes() as u32; - remaining.fields.push(field); - for (index, mut field) in fields.into_iter().enumerate() { - field.index = (index + 1) as u8; - let align_offset = field.offset - prev_offset; - let field_size = field.ty.size_in_bytes() as u32; - prev_offset = field.offset + field_size; - field.offset = field_offset + align_offset; - field_offset += align_offset; - field_offset += field_size; - remaining.fields.push(field); - } - break; - } - // The padding must be less than what was needed, so consume it, and - // then process the current field for the rest of the request - split.size += padding; - needed -= padding; - current_offset += padding; - let field_size = field.ty.size_in_bytes() as u32; - // If the field fully satisifies the remainder of the request, then - // finalize the `split` struct, and place remaining fields in a trailing - // struct with an appropriate repr - if field_size == needed { + // We need to process more fields for this request (i.e. field_size < needed) + needed -= field_size; split.size += field_size; field.offset = current_offset; + current_offset += field_size; split.fields.push(field); - - debug_assert!( - !fields.is_empty(), - "expected struct that is the exact size of the split request to have \ - been handled elsewhere" - ); - - remaining.repr = original_repr; - remaining.size = original_size - split.size; - remaining.fields.reserve(fields.len()); - let mut prev_offset = current_offset + field_size; - let mut field_offset = 0; - for (index, mut field) in fields.into_iter().enumerate() { - field.index = index as u8; - let align_offset = field.offset - prev_offset; - let field_size = field.ty.size_in_bytes() as u32; - prev_offset = field.offset + field_size; - field.offset = field_offset + align_offset; - field_offset += align_offset; - field_offset += field_size; - remaining.fields.push(field); - } - break; } - // If the field is larger than what is needed, we have to split it - if field_size > needed { - split.size += needed; - - // Add the portion needed to `split` - let index = field.index; - let offset = current_offset; - let align = field.align; - let (partial1, partial2) = field.ty.split(needed as usize); - // The second half of the split will always be a type - let partial2 = partial2.unwrap(); - split.fields.push(StructField { - index, - offset, - align, - ty: partial1, - }); - - // Build a struct with the remaining fields and trailing partial field - let mut prev_offset = current_offset + needed; - let mut field_offset = needed + partial2.size_in_bytes() as u32; - remaining.size = original_size - split.size; - remaining.fields.reserve(1 + fields.len()); - remaining.fields.push(StructField { - index: 0, - offset: 1, - align: 1, - ty: partial2, - }); - for (index, mut field) in fields.into_iter().enumerate() { - field.index = (index + 1) as u8; - let align_offset = field.offset - prev_offset; - let field_size = field.ty.size_in_bytes() as u32; - prev_offset = field.offset + needed + field_size; - field.offset = field_offset + align_offset; - field_offset += align_offset; - field_offset += field_size; - remaining.fields.push(field); - } - break; + let split = if split.fields.len() > 1 { + Type::from(split) + } else { + split.fields.pop().map(|f| f.ty).unwrap() + }; + match remaining.fields.len() { + 0 => (split, None), + 1 => (split, remaining.fields.pop().map(|f| f.ty)), + _ => (split, Some(remaining.into())), } - - // We need to process more fields for this request (i.e. field_size < needed) - needed -= field_size; - split.size += field_size; - field.offset = current_offset; - current_offset += field_size; - split.fields.push(field); } - - let split = if split.fields.len() > 1 { - Type::Struct(split) - } else { - split.fields.pop().map(|f| f.ty).unwrap() - }; - match remaining.fields.len() { - 0 => (split, None), - 1 => (split, remaining.fields.pop().map(|f| f.ty)), - _ => (split, Some(remaining.into())), - } - } + }, Type::List(_) => { todo!("invalid type: list has no defined representation yet, so cannot be split") } // These types either have no size, or are 1 byte in size, so must have // been handled above when checking if the size of the type is <= the // requested split size - Self::Unknown | Self::Unit | Self::Never | Self::I1 | Self::U8 | Self::I8 => { + Self::Unknown | Self::Never | Self::I1 | Self::U8 | Self::I8 => { unreachable!() } } @@ -411,7 +398,7 @@ impl Type { pub fn min_alignment(&self) -> usize { match self { // These types don't have a meaningful alignment, so choose byte-aligned - Self::Unknown | Self::Unit | Self::Never => 1, + Self::Unknown | Self::Never => 1, // Felts must be naturally aligned to a 32-bit boundary (4 bytes) Self::Felt => 4, // 256-bit and 128-bit integers must be word-aligned @@ -419,7 +406,7 @@ impl Type { // 64-bit integers and floats must be element-aligned Self::I64 | Self::U64 | Self::F64 => 4, // 32-bit integers and pointers must be element-aligned - Self::I32 | Self::U32 | Self::Ptr(_) | Self::NativePtr(..) => 4, + Self::I32 | Self::U32 | Self::Ptr(_) | Self::Function(..) => 4, // 16-bit integers can be naturally aligned Self::I16 | Self::U16 => 2, // 8-bit integers and booleans can be naturally aligned @@ -427,7 +414,7 @@ impl Type { // Structs use the minimum alignment of their first field, or 1 if a zero-sized type Self::Struct(ref struct_ty) => struct_ty.min_alignment(), // Arrays use the minimum alignment of their element type - Self::Array(ref element_ty, _) => element_ty.min_alignment(), + Self::Array(ref array_ty) => array_ty.min_alignment(), // Lists use the minimum alignment of their element type Self::List(ref element_ty) => element_ty.min_alignment(), } @@ -437,7 +424,7 @@ impl Type { pub fn size_in_bits(&self) -> usize { match self { // These types have no representation in memory - Self::Unknown | Self::Unit | Self::Never => 0, + Self::Unknown | Self::Never => 0, // Booleans are represented as i1 Self::I1 => 1, // Integers are naturally sized @@ -453,22 +440,10 @@ impl Type { Self::I128 | Self::U128 => 128, Self::U256 => 256, // Raw pointers are 32-bits, the same size as the native integer width, u32 - Self::Ptr(_) => 32, - // Native pointers are essentially a tuple/struct, composed of three 32-bit parts - Self::NativePtr(..) => 96, + Self::Ptr(_) | Self::Function(_) => 32, // Packed structs have no alignment padding between fields Self::Struct(ref struct_ty) => struct_ty.size as usize * 8, - // Zero-sized arrays have no size in memory - Self::Array(_, 0) => 0, - // An array of one element is the same as just the element - Self::Array(ref element_ty, 1) => element_ty.size_in_bits(), - // All other arrays require alignment padding between elements - Self::Array(ref element_ty, n) => { - let min_align = element_ty.min_alignment() * 8; - let element_size = element_ty.size_in_bits(); - let padded_element_size = element_size.align_up(min_align); - element_size + (padded_element_size * (n - 1)) - } + Self::Array(ref array_ty) => array_ty.size_in_bits(), Type::List(_) => todo!( "invalid type: list has no defined representation yet, so its size cannot be \ determined" @@ -479,7 +454,7 @@ impl Type { /// Returns the minimum number of bytes required to store a value of this type pub fn size_in_bytes(&self) -> usize { let bits = self.size_in_bits(); - (bits / 8) + (bits % 8 > 0) as usize + (bits / 8) + (!bits.is_multiple_of(8)) as usize } /// Same as `size_in_bytes`, but with sufficient padding to guarantee alignment of the value. @@ -543,69 +518,7 @@ impl Type { } } -/// This trait represents an alignable primitive integer value representing an address -pub trait Alignable { - /// This function computes the offset, in bytes, needed to align `self` upwards so that - /// it is aligned to `align` bytes. - /// - /// The following must be true, or this function will panic: - /// - /// * `align` is non-zero - /// * `align` is a power of two - fn align_offset(self, align: Self) -> Self; - /// This function aligns `self` to the specified alignment (in bytes), aligning upwards. - /// - /// The following must be true, or this function will panic: - /// - /// * `align` is non-zero - /// * `align` is a power of two - /// * `self` + `align` must be less than `Self::MAX` - fn align_up(self, align: Self) -> Self; - - /// Compute the nearest power of two less than or equal to `self` - fn prev_power_of_two(self) -> Self; -} - -macro_rules! alignable { - ($($ty:ty),+) => { - $( - alignable_impl!($ty); - )* - }; -} - -macro_rules! alignable_impl { - ($ty:ty) => { - #[allow(unstable_name_collisions)] - impl Alignable for $ty { - #[inline] - fn align_offset(self, align: Self) -> Self { - self.align_up(align) - self - } - - #[inline] - fn align_up(self, align: Self) -> Self { - assert_ne!(align, 0); - assert!(align.is_power_of_two()); - self.checked_next_multiple_of(align).expect("alignment overflow") - } - - #[inline] - fn prev_power_of_two(self) -> Self { - if self.is_power_of_two() { - self - } else { - cmp::max(self.next_power_of_two() / 2, 1) - } - } - } - }; -} - -alignable!(u8, u16, u32, u64, usize); - #[cfg(test)] -#[allow(unstable_name_collisions)] mod tests { use smallvec::smallvec; @@ -613,7 +526,7 @@ mod tests { #[test] fn struct_type_test() { - let ptr_ty = Type::Ptr(Box::new(Type::U32)); + let ptr_ty = Type::from(PointerType::new(Type::U32)); // A struct with default alignment and padding between fields let struct_ty = StructType::new([ptr_ty.clone(), Type::U8, Type::I32]); assert_eq!(struct_ty.min_alignment(), ptr_ty.min_alignment()); @@ -715,199 +628,33 @@ mod tests { #[test] fn type_to_raw_parts_test() { - let ty = Type::Array(Box::new(Type::U8), 5); + let ty = Type::from(ArrayType::new(Type::U8, 5)); assert_eq!( ty.to_raw_parts(), - Some(smallvec![Type::Array(Box::new(Type::U8), 4), Type::U8,]) + Some(smallvec![Type::from(ArrayType::new(Type::U8, 4)), Type::U8,]) ); - let ty = Type::Array(Box::new(Type::I16), 3); + let ty = Type::from(ArrayType::new(Type::I16, 3)); assert_eq!( ty.to_raw_parts(), - Some(smallvec![Type::Array(Box::new(Type::I16), 2), Type::I16,]) + Some(smallvec![Type::from(ArrayType::new(Type::I16, 2)), Type::I16,]) ); - let native_ptr_ty = Type::NativePtr(Box::new(Type::U32), AddressSpace::Root); - let ptr_ty = Type::Ptr(Box::new(Type::U32)); - let ty = Type::Array(Box::new(native_ptr_ty), 2); - assert_eq!( - ty.to_raw_parts(), - Some(smallvec![ - ptr_ty.clone(), - Type::U8, - Type::U8, - ptr_ty.clone(), - Type::U8, - Type::U8 - ]) - ); + let ptr_ty = Type::from(PointerType::new(Type::U32)); // Default struct - let ty = Type::Struct(StructType::new([ptr_ty.clone(), Type::U8, Type::I32])); + let ty = Type::from(StructType::new([ptr_ty.clone(), Type::U8, Type::I32])); assert_eq!(ty.to_raw_parts(), Some(smallvec![ptr_ty.clone(), Type::U8, Type::I32,])); // Packed struct - let ty = Type::Struct(StructType::new_with_repr( + let ty = Type::from(StructType::new_with_repr( TypeRepr::packed(1), [ptr_ty.clone(), Type::U8, Type::I32], )); - let partial_ty = Type::Struct(StructType::new_with_repr( + let partial_ty = Type::from(StructType::new_with_repr( TypeRepr::packed(1), - [Type::U8, Type::Array(Box::new(Type::U8), 3)], + [Type::U8, Type::from(ArrayType::new(Type::U8, 3))], )); assert_eq!(ty.to_raw_parts(), Some(smallvec![ptr_ty.clone(), partial_ty, Type::U8])); } - - #[test] - fn alignable_next_multiple_of() { - let addr = 0u32; - assert_eq!(addr.next_multiple_of(1), 0); - assert_eq!(addr.next_multiple_of(2), 0); - assert_eq!(addr.next_multiple_of(4), 0); - assert_eq!(addr.next_multiple_of(8), 0); - assert_eq!(addr.next_multiple_of(16), 0); - assert_eq!(addr.next_multiple_of(32), 0); - - let addr = 1u32; - assert_eq!(addr.next_multiple_of(1), 1); - assert_eq!(addr.next_multiple_of(2), 2); - assert_eq!(addr.next_multiple_of(4), 4); - assert_eq!(addr.next_multiple_of(8), 8); - assert_eq!(addr.next_multiple_of(16), 16); - assert_eq!(addr.next_multiple_of(32), 32); - - let addr = 2u32; - assert_eq!(addr.next_multiple_of(1), 2); - assert_eq!(addr.next_multiple_of(2), 2); - assert_eq!(addr.next_multiple_of(4), 4); - assert_eq!(addr.next_multiple_of(8), 8); - assert_eq!(addr.next_multiple_of(16), 16); - assert_eq!(addr.next_multiple_of(32), 32); - - let addr = 3u32; - assert_eq!(addr.next_multiple_of(1), 3); - assert_eq!(addr.next_multiple_of(2), 4); - assert_eq!(addr.next_multiple_of(4), 4); - assert_eq!(addr.next_multiple_of(8), 8); - assert_eq!(addr.next_multiple_of(16), 16); - assert_eq!(addr.next_multiple_of(32), 32); - - let addr = 127u32; - assert_eq!(addr.next_multiple_of(1), 127); - assert_eq!(addr.next_multiple_of(2), 128); - assert_eq!(addr.next_multiple_of(4), 128); - assert_eq!(addr.next_multiple_of(8), 128); - assert_eq!(addr.next_multiple_of(16), 128); - assert_eq!(addr.next_multiple_of(32), 128); - - let addr = 130u32; - assert_eq!(addr.next_multiple_of(1), 130); - assert_eq!(addr.next_multiple_of(2), 130); - assert_eq!(addr.next_multiple_of(4), 132); - assert_eq!(addr.next_multiple_of(8), 136); - assert_eq!(addr.next_multiple_of(16), 144); - assert_eq!(addr.next_multiple_of(32), 160); - } - - #[test] - fn alignable_align_offset_test() { - let addr = 0u32; - assert_eq!(addr.align_offset(1), 0); - assert_eq!(addr.align_offset(2), 0); - assert_eq!(addr.align_offset(4), 0); - assert_eq!(addr.align_offset(8), 0); - assert_eq!(addr.align_offset(16), 0); - assert_eq!(addr.align_offset(32), 0); - - let addr = 1u32; - assert_eq!(addr.align_offset(1), 0); - assert_eq!(addr.align_offset(2), 1); - assert_eq!(addr.align_offset(4), 3); - assert_eq!(addr.align_offset(8), 7); - assert_eq!(addr.align_offset(16), 15); - assert_eq!(addr.align_offset(32), 31); - - let addr = 2u32; - assert_eq!(addr.align_offset(1), 0); - assert_eq!(addr.align_offset(2), 0); - assert_eq!(addr.align_offset(4), 2); - assert_eq!(addr.align_offset(8), 6); - assert_eq!(addr.align_offset(16), 14); - assert_eq!(addr.align_offset(32), 30); - - let addr = 3u32; - assert_eq!(addr.align_offset(1), 0); - assert_eq!(addr.align_offset(2), 1); - assert_eq!(addr.align_offset(4), 1); - assert_eq!(addr.align_offset(8), 5); - assert_eq!(addr.align_offset(16), 13); - assert_eq!(addr.align_offset(32), 29); - - let addr = 127u32; - assert_eq!(addr.align_offset(1), 0); - assert_eq!(addr.align_offset(2), 1); - assert_eq!(addr.align_offset(4), 1); - assert_eq!(addr.align_offset(8), 1); - assert_eq!(addr.align_offset(16), 1); - assert_eq!(addr.align_offset(32), 1); - - let addr = 130u32; - assert_eq!(addr.align_offset(1), 0); - assert_eq!(addr.align_offset(2), 0); - assert_eq!(addr.align_offset(4), 2); - assert_eq!(addr.align_offset(8), 6); - assert_eq!(addr.align_offset(16), 14); - assert_eq!(addr.align_offset(32), 30); - } - - #[test] - fn alignable_align_up_test() { - let addr = 0u32; - assert_eq!(addr.align_up(1), 0); - assert_eq!(addr.align_up(2), 0); - assert_eq!(addr.align_up(4), 0); - assert_eq!(addr.align_up(8), 0); - assert_eq!(addr.align_up(16), 0); - assert_eq!(addr.align_up(32), 0); - - let addr = 1u32; - assert_eq!(addr.align_up(1), 1); - assert_eq!(addr.align_up(2), 2); - assert_eq!(addr.align_up(4), 4); - assert_eq!(addr.align_up(8), 8); - assert_eq!(addr.align_up(16), 16); - assert_eq!(addr.align_up(32), 32); - - let addr = 2u32; - assert_eq!(addr.align_up(1), 2); - assert_eq!(addr.align_up(2), 2); - assert_eq!(addr.align_up(4), 4); - assert_eq!(addr.align_up(8), 8); - assert_eq!(addr.align_up(16), 16); - assert_eq!(addr.align_up(32), 32); - - let addr = 3u32; - assert_eq!(addr.align_up(1), 3); - assert_eq!(addr.align_up(2), 4); - assert_eq!(addr.align_up(4), 4); - assert_eq!(addr.align_up(8), 8); - assert_eq!(addr.align_up(16), 16); - assert_eq!(addr.align_up(32), 32); - - let addr = 127u32; - assert_eq!(addr.align_up(1), 127); - assert_eq!(addr.align_up(2), 128); - assert_eq!(addr.align_up(4), 128); - assert_eq!(addr.align_up(8), 128); - assert_eq!(addr.align_up(16), 128); - assert_eq!(addr.align_up(32), 128); - - let addr = 130u32; - assert_eq!(addr.align_up(1), 130); - assert_eq!(addr.align_up(2), 130); - assert_eq!(addr.align_up(4), 132); - assert_eq!(addr.align_up(8), 136); - assert_eq!(addr.align_up(16), 144); - assert_eq!(addr.align_up(32), 160); - } } diff --git a/hir-type/src/lib.rs b/hir-type/src/lib.rs index c19c93c2d..90128575e 100644 --- a/hir-type/src/lib.rs +++ b/hir-type/src/lib.rs @@ -1,96 +1,94 @@ #![no_std] +#![deny(warnings)] extern crate alloc; +mod alignable; +mod array_type; +mod function_type; mod layout; +mod pointer_type; +mod struct_type; -use alloc::{boxed::Box, vec::Vec}; -use core::{fmt, num::NonZeroU16, str::FromStr}; +use alloc::{boxed::Box, sync::Arc}; +use core::fmt; -pub use self::layout::Alignable; +use miden_formatting::prettier::PrettyPrint; -/// Represents the type of a value +pub use self::{ + alignable::Alignable, array_type::ArrayType, function_type::*, pointer_type::*, struct_type::*, +}; + +/// Represents the type of a value in the HIR type system #[derive(Debug, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Type { /// This indicates a failure to type a value, or a value which is untypable Unknown, - /// This type is used to indicate the absence of a value, such as a function which returns - /// nothing - Unit, /// This type is the bottom type, and represents divergence, akin to Rust's Never/! type Never, + /// A 1-bit integer, i.e. a boolean value. + /// + /// When the bit is 1, the value is true; 0 is false. I1, + /// An 8-bit signed integer. I8, + /// An 8-bit unsigned integer. U8, + /// A 16-bit signed integer. I16, + /// A 16-bit unsigned integer. U16, + /// A 32-bit signed integer. I32, + /// A 32-bit unsigned integer. U32, + /// A 64-bit signed integer. I64, + /// A 64-bit unsigned integer. U64, + /// A 128-bit signed integer. I128, + /// A 128-bit unsigned integer. U128, + /// A 256-bit unsigned integer. U256, + /// A 64-bit IEEE-754 floating-point value. + /// + /// NOTE: These are currently unsupported in practice, but is reserved here for future use. F64, - /// Field element + /// A field element corresponding to the native Miden field (currently the Goldilocks field) Felt, - /// A pointer to a value in the default byte-addressable address space used by the IR. - /// - /// Pointers of this type will be translated to an appropriate address space during code - /// generation. - Ptr(Box), - /// A pointer to a valude in Miden's native word-addressable address space. - /// - /// In the type system, we represent the type of the pointee, as well as an address space - /// identifier. + /// A pointer to a value in a byte-addressable address space. /// - /// This pointer type is represented on Miden's operand stack as a u64 value, consisting of - /// two 32-bit elements, the most-significant bits being on top of the stack: - /// - /// 1. Metadata for the pointer (in the upper 32-bit limb): - /// * The least-significant 2 bits represent a zero-based element index (range is 0-3) - /// * The next most significant 4 bits represent a zero-based byte index (range is 0-31) - /// * The remaining 26 bits represent an address space identifier - /// 2. The lower 32-bit limb contains the word-aligned address, which forms the base address of - /// the pointer. - /// - /// Dereferencing a pointer of this type involves popping the pointer metadata, and determining - /// what type of load to issue based on the size of the value being loaded, and where the - /// start of the data is according to the metadata. Then the word-aligned address is popped - /// and the value is loaded. - /// - /// If the load is naturally aligned, i.e. the element index and byte offset are zero, and the - /// size is exactly one element or word; then a mem_load or mem_loadw are issued and no - /// further action is required. If the load is not naturally aligned, then either one or - /// two words will be loaded, depending on the type being loaded, unused elements will be - /// dropped, and if the byte offset is non-zero, the data will be shifted bitwise into - /// alignment on an element boundary. - NativePtr(Box, AddressSpace), + /// Pointers of this type are _not_ equivalent to element addresses as referred to in the + /// Miden Assembly documentation, but do have a straightforward conversion. + Ptr(Arc), /// A compound type of fixed shape and size - Struct(StructType), + Struct(Arc), /// A vector of fixed size - Array(Box, usize), + Array(Arc), /// A dynamically sized list of values of the given type /// /// NOTE: Currently this only exists to support the Wasm Canonical ABI, /// but it has no defined represenation yet, so in practice cannot be /// used in most places except during initial translation in the Wasm frontend. - List(Box), + List(Arc), + /// A reference to a function with the given type signature + Function(Arc), } impl Type { /// Returns true if this type is a zero-sized type, which includes: /// - /// * Types with no size, e.g. `Type::Unit` + /// * Types with no size, e.g. `Never` /// * Zero-sized arrays /// * Arrays with a zero-sized element type /// * Structs composed of nothing but zero-sized fields pub fn is_zst(&self) -> bool { match self { Self::Unknown => false, - Self::Never | Self::Unit => true, - Self::Array(_, 0) => true, - Self::Array(ref elem_ty, _) => elem_ty.is_zst(), + Self::Never => true, + Self::Array(ref ty) => ty.is_zst(), Self::Struct(ref struct_ty) => struct_ty.fields.iter().all(|f| f.ty.is_zst()), Self::I1 | Self::I8 @@ -107,11 +105,12 @@ impl Type { | Self::F64 | Self::Felt | Self::Ptr(_) - | Self::NativePtr(..) - | Self::List(_) => false, + | Self::List(_) + | Self::Function(_) => false, } } + /// Returns true if this type is any numeric type pub fn is_numeric(&self) -> bool { matches!( self, @@ -132,6 +131,7 @@ impl Type { ) } + /// Returns true if this type is any integral type pub fn is_integer(&self) -> bool { matches!( self, @@ -151,10 +151,12 @@ impl Type { ) } + /// Returns true if this type is any signed integral type pub fn is_signed_integer(&self) -> bool { matches!(self, Self::I8 | Self::I16 | Self::I32 | Self::I64 | Self::I128) } + /// Returns true if this type is any unsigned integral type pub fn is_unsigned_integer(&self) -> bool { matches!(self, Self::I1 | Self::U8 | Self::U16 | Self::U32 | Self::U64 | Self::U128) } @@ -192,586 +194,175 @@ impl Type { } } + /// Returns true if this type is a floating-point type #[inline] pub fn is_float(&self) -> bool { matches!(self, Self::F64) } + /// Returns true if this type is the field element type #[inline] pub fn is_felt(&self) -> bool { matches!(self, Self::Felt) } + /// Returns true if this type is a pointer type #[inline] pub fn is_pointer(&self) -> bool { - matches!(self, Self::Ptr(_) | Self::NativePtr(_, _)) + matches!(self, Self::Ptr(_)) + } + + /// Returns the type of the pointee, if this type is a pointer type + #[inline] + pub fn pointee(&self) -> Option<&Type> { + match self { + Self::Ptr(ty) => Some(ty.pointee()), + _ => None, + } } + /// Returns true if this type is a struct type #[inline] pub fn is_struct(&self) -> bool { matches!(self, Self::Struct(_)) } + /// Returns true if this type is an array type #[inline] pub fn is_array(&self) -> bool { - matches!(self, Self::Array(_, _)) + matches!(self, Self::Array(_)) } - /// Returns true if `self` and `other` are compatible operand types for a binary operator, e.g. - /// `add` - /// - /// In short, the rules are as follows: - /// - /// * The operand order is assumed to be `self other`, i.e. `op` is being applied to `self` - /// using `other`. The left-hand operand is used as the "controlling" type for the operator, - /// i.e. it determines what instruction will be used to perform the operation. - /// * The operand types must be numeric, or support being manipulated numerically - /// * If the controlling type is unsigned, it is never compatible with signed types, because - /// Miden instructions for unsigned types use a simple unsigned binary encoding, thus they - /// will not handle signed operands using two's complement correctly. - /// * If the controlling type is signed, it is compatible with both signed and unsigned types, - /// as long as the values fit in the range of the controlling type, e.g. adding a `u16` to an - /// `i32` is fine, but adding a `u32` to an `i32` is not. - /// * Pointer types are permitted to be the controlling type, and since they are represented - /// using u32, they have the same compatibility set as u32 does. In all other cases, pointer - /// types are treated the same as any other non-numeric type. - /// * Non-numeric types are always incompatible, since no operators support these types - pub fn is_compatible_operand(&self, other: &Type) -> bool { - match (self, other) { - (Type::I1, Type::I1) => true, - (Type::I8, Type::I8) => true, - (Type::U8, Type::U8) => true, - (Type::I16, Type::I8 | Type::U8 | Type::I16) => true, - (Type::U16, Type::U8 | Type::U16) => true, - (Type::I32, Type::I8 | Type::U8 | Type::I16 | Type::U16 | Type::I32) => true, - (Type::U32, Type::U8 | Type::U16 | Type::U32) => true, - ( - Type::I64, - Type::I8 | Type::U8 | Type::I16 | Type::U16 | Type::I32 | Type::U32 | Type::I64, - ) => true, - (Type::U64, Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::Felt) => true, - ( - Type::I128, - Type::I8 - | Type::U8 - | Type::I16 - | Type::U16 - | Type::I32 - | Type::U32 - | Type::I64 - | Type::U64 - | Type::Felt - | Type::I128, - ) => true, - ( - Type::U128, - Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::Felt | Type::U128, - ) => true, - (Type::U256, rty) => rty.is_unsigned_integer(), - (Type::Felt, Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::Felt) => true, - (Type::F64, Type::F64) => true, - (Type::Ptr(_) | Type::NativePtr(..), Type::U8 | Type::U16 | Type::U32) => true, - _ => false, - } + /// Returns true if this type is a dynamically-sized vector/list type + #[inline] + pub fn is_list(&self) -> bool { + matches!(self, Self::List(_)) } + /// Returns true if this type is a function reference type #[inline] - pub fn pointee(&self) -> Option<&Type> { - use core::ops::Deref; - match self { - Self::Ptr(ty) | Self::NativePtr(ty, _) => Some(ty.deref()), - _ => None, - } + pub fn is_function(&self) -> bool { + matches!(self, Self::Function(_)) } } + impl From for Type { #[inline] fn from(ty: StructType) -> Type { - Type::Struct(ty) - } -} -impl fmt::Display for Type { - /// Print this type for display using the provided module context - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use core::fmt::Write; - match self { - Self::Unknown => f.write_str("?"), - Self::Unit => f.write_str("()"), - Self::Never => f.write_char('!'), - Self::I1 => f.write_str("i1"), - Self::I8 => f.write_str("i8"), - Self::U8 => f.write_str("u8"), - Self::I16 => f.write_str("i16"), - Self::U16 => f.write_str("u16"), - Self::I32 => f.write_str("i32"), - Self::U32 => f.write_str("u32"), - Self::I64 => f.write_str("i64"), - Self::U64 => f.write_str("u64"), - Self::I128 => f.write_str("i128"), - Self::U128 => f.write_str("u128"), - Self::U256 => f.write_str("u256"), - Self::F64 => f.write_str("f64"), - Self::Felt => f.write_str("felt"), - Self::Ptr(inner) => write!(f, "(ptr {inner})"), - Self::NativePtr(inner, AddressSpace::Unknown) => { - write!(f, "(ptr (addrspace ?) {inner})") - } - Self::NativePtr(inner, AddressSpace::Root) => { - write!(f, "(ptr (addrspace 0) {inner})") - } - Self::NativePtr(inner, AddressSpace::Id(id)) => { - write!(f, "(ptr (addrspace {id}) {inner})") - } - Self::Struct(sty) => write!(f, "{sty}"), - Self::Array(element_ty, arity) => write!(f, "(array {element_ty} {arity})"), - Self::List(ty) => write!(f, "(list {ty})"), - } + Type::Struct(Arc::new(ty)) } } -/// This represents metadata about how a structured type will be represented in memory -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum TypeRepr { - /// This corresponds to the C ABI representation for a given type - #[default] - Default, - /// This modifies the default representation, by raising the minimum alignment. - /// - /// The alignment must be a power of two, e.g. 32, and values from 1 to 2^16 are allowed. - /// - /// The alignment must be greater than the default minimum alignment of the type - /// or this representation has no effect. - Align(NonZeroU16), - /// This modifies the default representation, by lowering the minimum alignment of - /// a type, and in the case of structs, changes the alignments of the fields to be - /// the smaller of the specified alignment and the default alignment. This has the - /// effect of changing the layout of a struct. - /// - /// Notably, `Packed(1)` will result in a struct that has no alignment requirement, - /// and no padding between fields. - /// - /// The alignment must be a power of two, e.g. 32, and values from 1 to 2^16 are allowed. - /// - /// The alignment must be smaller than the default alignment, or this representation - /// has no effect. - Packed(NonZeroU16), - /// This may only be used on structs with no more than one non-zero sized field, and - /// indicates that the representation of that field should be used for the struct. - Transparent, -} -impl TypeRepr { +impl From> for Type { #[inline] - pub fn packed(align: u16) -> Self { - Self::Packed( - NonZeroU16::new(align).expect("invalid alignment: expected value in range 1..=65535"), - ) + fn from(ty: Box) -> Type { + Type::Struct(Arc::from(ty)) } +} +impl From> for Type { #[inline] - pub fn align(align: u16) -> Self { - Self::Align( - NonZeroU16::new(align).expect("invalid alignment: expected value in range 1..=65535"), - ) - } - - /// Return true if this type representation is transparent - pub fn is_transparent(&self) -> bool { - matches!(self, Self::Transparent) - } - - /// Return true if this type representation is packed - pub fn is_packed(&self) -> bool { - matches!(self, Self::Packed(_)) - } - - /// Get the custom alignment given for this type representation, if applicable - pub fn min_alignment(&self) -> Option { - match self { - Self::Packed(align) | Self::Align(align) => Some(align.get() as usize), - _ => None, - } + fn from(ty: Arc) -> Type { + Type::Struct(ty) } } -/// This represents metadata about a field of a [StructType] -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct StructField { - /// The index of this field in the final layout - pub index: u8, - /// The specified alignment for this field - pub align: u16, - /// The offset of this field relative to the base of the struct - pub offset: u32, - /// The type of this field - pub ty: Type, -} -impl fmt::Display for StructField { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", &self.ty) +impl From for Type { + #[inline] + fn from(ty: ArrayType) -> Type { + Type::Array(Arc::new(ty)) } } -/// This represents a structured aggregate type -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct StructType { - /// The representation to use for this type - pub(crate) repr: TypeRepr, - /// The computed size of this struct - pub(crate) size: u32, - /// The fields of this struct, in the original order specified - /// - /// The actual order of fields in the final layout is determined by the index - /// associated with each field, not the index in this vector, although for `repr(C)` - /// structs they will be the same - pub(crate) fields: Vec, -} -impl StructType { - /// Create a new struct with default representation, i.e. a struct with representation of - /// `TypeRepr::Packed(1)`. +impl From> for Type { #[inline] - pub fn new>(fields: I) -> Self { - Self::new_with_repr(TypeRepr::Default, fields) - } - - /// Create a new struct with the given representation. - /// - /// This function will panic if the rules of the given representation are violated. - pub fn new_with_repr>(repr: TypeRepr, fields: I) -> Self { - let tys = fields.into_iter().collect::>(); - let mut fields = Vec::with_capacity(tys.len()); - let size = match repr { - TypeRepr::Transparent => { - let mut offset = 0u32; - for (index, ty) in tys.into_iter().enumerate() { - let index: u8 = - index.try_into().expect("invalid struct: expected no more than 255 fields"); - let field_size: u32 = ty - .size_in_bytes() - .try_into() - .expect("invalid type: size is larger than 2^32 bytes"); - if field_size == 0 { - fields.push(StructField { - index, - align: 1, - offset, - ty, - }); - } else { - let align = ty.min_alignment().try_into().expect( - "invalid struct field alignment: expected power of two between 1 and \ - 2^16", - ); - assert_eq!( - offset, 0, - "invalid transparent representation for struct: repr(transparent) is \ - only valid for structs with a single non-zero sized field" - ); - fields.push(StructField { - index, - align, - offset, - ty, - }); - offset += field_size; - } - } - offset - } - repr => { - let mut offset = 0u32; - let default_align: u16 = - tys.iter().map(|t| t.min_alignment()).max().unwrap_or(1).try_into().expect( - "invalid struct field alignment: expected power of two between 1 and 2^16", - ); - let align = match repr { - TypeRepr::Align(align) => core::cmp::max(align.get(), default_align), - TypeRepr::Packed(align) => core::cmp::min(align.get(), default_align), - TypeRepr::Transparent | TypeRepr::Default => default_align, - }; - - for (index, ty) in tys.into_iter().enumerate() { - let index: u8 = - index.try_into().expect("invalid struct: expected no more than 255 fields"); - let field_size: u32 = ty - .size_in_bytes() - .try_into() - .expect("invalid type: size is larger than 2^32 bytes"); - let default_align: u16 = ty.min_alignment().try_into().expect( - "invalid struct field alignment: expected power of two between 1 and 2^16", - ); - let align: u16 = match repr { - TypeRepr::Packed(align) => core::cmp::min(align.get(), default_align), - _ => default_align, - }; - offset += offset.align_offset(align as u32); - fields.push(StructField { - index, - align, - offset, - ty, - }); - offset += field_size; - } - offset.align_up(align as u32) - } - }; - Self { repr, size, fields } + fn from(ty: Box) -> Type { + Type::Array(Arc::from(ty)) } +} - /// Get the [TypeRepr] for this struct +impl From> for Type { #[inline] - pub const fn repr(&self) -> TypeRepr { - self.repr - } - - /// Get the minimum alignment for this struct - pub fn min_alignment(&self) -> usize { - self.repr - .min_alignment() - .unwrap_or_else(|| self.fields.iter().map(|f| f.align as usize).max().unwrap_or(1)) + fn from(ty: Arc) -> Type { + Type::Array(ty) } +} - /// Get the total size in bytes required to hold this struct, including alignment padding +impl From for Type { #[inline] - pub fn size(&self) -> usize { - self.size as usize - } - - /// Get the struct field at `index`, relative to declaration order. - pub fn get(&self, index: usize) -> &StructField { - &self.fields[index] - } - - /// Get the struct fields as a slice - pub fn fields(&self) -> &[StructField] { - self.fields.as_slice() - } - - /// Returns true if this struct has no fields - pub fn is_empty(&self) -> bool { - self.fields.is_empty() - } - - /// Get the length of this struct (i.e. number of fields) - pub fn len(&self) -> usize { - self.fields.len() + fn from(ty: PointerType) -> Type { + Type::Ptr(Arc::new(ty)) } } -impl TryFrom for StructType { - type Error = Type; - fn try_from(ty: Type) -> Result { - match ty { - Type::Struct(ty) => Ok(ty), - other => Err(other), - } - } -} -impl fmt::Display for StructType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.repr { - TypeRepr::Default => f.write_str("(struct ")?, - TypeRepr::Transparent => f.write_str("(struct (repr transparent) ")?, - TypeRepr::Align(align) => write!(f, "(struct (repr (align {align})) ")?, - TypeRepr::Packed(align) => write!(f, "(struct (repr (packed {align})) ")?, - }; - for (i, field) in self.fields.iter().enumerate() { - if i > 0 { - write!(f, " {}", field)?; - } else { - write!(f, "{}", field)?; - } - } - f.write_str(")") +impl From> for Type { + #[inline] + fn from(ty: Box) -> Type { + Type::Ptr(Arc::from(ty)) } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] -#[cfg_attr( - feature = "serde", - derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) -)] -#[repr(u8)] -pub enum Abi { - /// The type signature of a function in canonical form. Canonical in this context means that - /// no special lowering is required between caller and callee - all the compiler needs to - /// deal with are the details of the specific calling convention. - #[default] - Canonical, - /// The type signature of a function expressed in terms of the Canonical ABI of the Wasm - /// Component Model. This indicates that additional lowering/lifting code is required between - /// caller and callee. It also dictates the calling convention for the callee. - Wasm, -} - -impl fmt::Display for Abi { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Canonical => f.write_str("canon"), - Self::Wasm => f.write_str("wasm"), - } +impl From> for Type { + #[inline] + fn from(ty: Arc) -> Type { + Type::Ptr(ty) } } -/// This represents the type of a function, including the ABI, result types, and parameter types -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct FunctionType { - /// The ABI of this function type - pub abi: Abi, - /// The result types of this function - pub results: Vec, - /// The parameter types of this function - pub params: Vec, -} -impl FunctionType { - /// Create a new function type with the canonical ABI - pub fn new, R: IntoIterator>( - params: P, - results: R, - ) -> Self { - Self { - abi: Abi::Canonical, - results: results.into_iter().collect(), - params: params.into_iter().collect(), - } - } - - /// Create a new function type with the Wasm Component Model ABI - pub fn new_wasm, R: IntoIterator>( - params: P, - results: R, - ) -> Self { - Self { - abi: Abi::Wasm, - results: results.into_iter().collect(), - params: params.into_iter().collect(), - } - } - - /// Set the ABI for this function type - pub fn with_abi(mut self, abi: Abi) -> Self { - self.abi = abi; - self - } - - pub fn arity(&self) -> usize { - self.params.len() - } - - pub fn results(&self) -> &[Type] { - self.results.as_slice() - } - - pub fn params(&self) -> &[Type] { - self.params.as_slice() +impl From for Type { + #[inline] + fn from(ty: FunctionType) -> Type { + Type::Function(Arc::new(ty)) } +} - pub fn abi(&self) -> Abi { - self.abi +impl From> for Type { + #[inline] + fn from(ty: Box) -> Type { + Type::Function(Arc::from(ty)) } } -impl fmt::Display for FunctionType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use core::fmt::Write; - f.write_str("(func")?; - f.write_fmt(format_args!(" (abi {abi}) ", abi = self.abi))?; - for ty in self.params.iter() { - write!(f, " (param {ty})")?; - } - if !self.results.is_empty() { - f.write_str(" (result")?; - for ty in self.results.iter() { - write!(f, " {ty}")?; - } - f.write_char(')')?; - } - f.write_char(')') +impl From> for Type { + #[inline] + fn from(ty: Arc) -> Type { + Type::Function(ty) } } -/// This error is raised when parsing an [AddressSpace] -#[derive(Debug)] -pub enum InvalidAddressSpaceError { - InvalidId, - InvalidIdOverflow, -} -impl fmt::Display for InvalidAddressSpaceError { +impl fmt::Display for Type { + /// Print this type for display using the provided module context fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InvalidId => { - f.write_str("invalid address space identifier: expected integer value or 'unknown'") - } - Self::InvalidIdOverflow => f.write_str( - "invalid address space identifier: value is too large, expected range is 0..=65535", - ), - } + self.pretty_print(f) } } -/// This type uniquely identifies the address space associated with a native -/// Miden pointer value -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub enum AddressSpace { - /// The address space is not known statically, but is available - /// at runtime in the pointer metadata. - /// - /// This is also the type associated with user contexts in Miden, - /// as it cannot be known statically how many such contexts will - /// be used at runtime. - #[default] - Unknown, - /// This address space corresponds to the root context in Miden - /// - /// The root context is the default context in the program entrypoint, - /// and for use cases outside the typical smart contract usage, may be - /// the only context in use at any given time. - /// - /// This address space corresponds to an address space identifier of 0. - Root, - /// This address space corresponds to a statically allocated separate - /// memory region. This can be used to represent things in separate - /// linear memory regions which are accessible simultaneously. - /// - /// Any non-zero identifier can be used for these address spaces. - /// - /// NOTE: It is up to the user to ensure that there are no conflicts - /// between address space identifiers. - Id(NonZeroU16), -} -impl fmt::Display for AddressSpace { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl PrettyPrint for Type { + fn render(&self) -> miden_formatting::prettier::Document { + use miden_formatting::prettier::*; + match self { - Self::Unknown => f.write_str("?"), - Self::Root => f.write_str("0"), - Self::Id(id) => write!(f, "{id}"), - } - } -} -impl FromStr for AddressSpace { - type Err = InvalidAddressSpaceError; - - fn from_str(s: &str) -> Result { - match s { - "unknown" => Ok(Self::Unknown), - id => { - use core::num::IntErrorKind; - match NonZeroU16::from_str(id) { - Ok(id) => Ok(Self::Id(id)), - Err(err) => match err.kind() { - IntErrorKind::Zero => Ok(Self::Root), - IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => { - Err(InvalidAddressSpaceError::InvalidIdOverflow) - } - _ => Err(InvalidAddressSpaceError::InvalidId), - }, - } - } + Self::Unknown => const_text("?"), + Self::Never => const_text("!"), + Self::I1 => const_text("i1"), + Self::I8 => const_text("i8"), + Self::U8 => const_text("u8"), + Self::I16 => const_text("i16"), + Self::U16 => const_text("u16"), + Self::I32 => const_text("i32"), + Self::U32 => const_text("u32"), + Self::I64 => const_text("i64"), + Self::U64 => const_text("u64"), + Self::I128 => const_text("i128"), + Self::U128 => const_text("u128"), + Self::U256 => const_text("u256"), + Self::F64 => const_text("f64"), + Self::Felt => const_text("felt"), + Self::Ptr(ptr_ty) => ptr_ty.render(), + Self::Struct(struct_ty) => struct_ty.render(), + Self::Array(array_ty) => array_ty.render(), + Self::List(ty) => const_text("list<") + ty.render() + const_text(">"), + Self::Function(ty) => ty.render(), } } } diff --git a/hir-type/src/pointer_type.rs b/hir-type/src/pointer_type.rs new file mode 100644 index 000000000..315aa92f3 --- /dev/null +++ b/hir-type/src/pointer_type.rs @@ -0,0 +1,117 @@ +use core::{fmt, str::FromStr}; + +use super::Type; + +/// A pointer to an object in memory +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct PointerType { + /// The address space used by pointers of this type. + pub addrspace: AddressSpace, + /// The type of value located at the pointed-to address + pub pointee: Type, +} + +impl PointerType { + /// Create a new byte-addressable pointer type to `pointee` + pub fn new(pointee: Type) -> Self { + Self { + addrspace: AddressSpace::Byte, + pointee, + } + } + + /// Create a new pointer type to `pointee` in `addrspace` + pub fn new_with_address_space(pointee: Type, addrspace: AddressSpace) -> Self { + Self { addrspace, pointee } + } + + /// Returns the type pointed to by pointers of this type + pub fn pointee(&self) -> &Type { + &self.pointee + } + + /// Returns the address space of this pointer type + pub fn addrspace(&self) -> AddressSpace { + self.addrspace + } + + /// Returns true if this pointer type represents a byte pointer + pub fn is_byte_pointer(&self) -> bool { + matches!(self.addrspace, AddressSpace::Byte) + } +} + +impl fmt::Display for PointerType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use miden_formatting::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl miden_formatting::prettier::PrettyPrint for PointerType { + fn render(&self) -> miden_formatting::prettier::Document { + use miden_formatting::prettier::*; + + const_text("ptr<") + + self.addrspace.render() + + const_text(", ") + + self.pointee.render() + + const_text(">") + } +} + +/// This error is raised when parsing an [AddressSpace] +#[derive(Debug, thiserror::Error)] +pub enum InvalidAddressSpaceError { + #[error("invalid address space identifier: expected 'byte' or 'element'")] + InvalidId, +} + +/// The address space a pointer address is evaluated in. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum AddressSpace { + /// The pointer address is evaluated as a byte address. + /// + /// This is the default unit type for pointers in HIR. + #[default] + Byte, + /// The pointer address is evaluated as an element address. + /// + /// This is the unit type for native Miden VM addresses. + /// + /// All byte-addressable pointers must be converted to element pointers at runtime before + /// loading/storing memory. + Element, +} + +impl fmt::Display for AddressSpace { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use miden_formatting::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl miden_formatting::prettier::PrettyPrint for AddressSpace { + fn render(&self) -> miden_formatting::prettier::Document { + use miden_formatting::prettier::*; + + match self { + Self::Byte => const_text("byte"), + Self::Element => const_text("element"), + } + } +} + +impl FromStr for AddressSpace { + type Err = InvalidAddressSpaceError; + + fn from_str(s: &str) -> Result { + match s { + "byte" => Ok(Self::Byte), + "element" => Ok(Self::Element), + _ => Err(InvalidAddressSpaceError::InvalidId), + } + } +} diff --git a/hir-type/src/struct_type.rs b/hir-type/src/struct_type.rs new file mode 100644 index 000000000..468c7134b --- /dev/null +++ b/hir-type/src/struct_type.rs @@ -0,0 +1,339 @@ +use core::{fmt, num::NonZeroU16}; + +use smallvec::SmallVec; + +use super::{Alignable, Type}; + +/// This represents a structured aggregate type +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct StructType { + /// The representation to use for this type + pub(crate) repr: TypeRepr, + /// The computed size of this struct + pub(crate) size: u32, + /// The fields of this struct, in the original order specified + /// + /// The actual order of fields in the final layout is determined by the index + /// associated with each field, not the index in this vector, although for `repr(C)` + /// structs they will be the same + pub(crate) fields: SmallVec<[StructField; 2]>, +} + +/// This represents metadata about a field of a [StructType] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct StructField { + /// The index of this field in the final layout + pub index: u8, + /// The specified alignment for this field + pub align: u16, + /// The offset of this field relative to the base of the struct + pub offset: u32, + /// The type of this field + pub ty: Type, +} + +/// This represents metadata about how a structured type will be represented in memory +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub enum TypeRepr { + /// This corresponds to the C ABI representation for a given type + #[default] + Default, + /// This modifies the default representation, by raising the minimum alignment. + /// + /// The alignment must be a power of two, e.g. 32, and values from 1 to 2^16 are allowed. + /// + /// The alignment must be greater than the default minimum alignment of the type + /// or this representation has no effect. + Align(NonZeroU16), + /// This modifies the default representation, by lowering the minimum alignment of + /// a type, and in the case of structs, changes the alignments of the fields to be + /// the smaller of the specified alignment and the default alignment. This has the + /// effect of changing the layout of a struct. + /// + /// Notably, `Packed(1)` will result in a struct that has no alignment requirement, + /// and no padding between fields. + /// + /// The alignment must be a power of two, e.g. 32, and values from 1 to 2^16 are allowed. + /// + /// The alignment must be smaller than the default alignment, or this representation + /// has no effect. + Packed(NonZeroU16), + /// This may only be used on structs with no more than one non-zero sized field, and + /// indicates that the representation of that field should be used for the struct. + Transparent, + /// This is equivalent to the default representation, except it indicates that if multiple + /// field elements are required to represent the value on Miden's operand stack (i.e. the + /// value is larger than 4 bytes), then the field elements will be ordered on the operand stack + /// with the highest-addressed bytes at the top. + /// + /// Normally, types are laid out in natural order (i.e. lowest-addressed bytes on top of the + /// stack), and when lowering word-sized loads/stores, we are required to reverse the order + /// of the elements into big-endian order. + /// + /// This representation essentially disables this implicit reversal, keeping elements on the + /// operand stack in the order produced by `mem_loadw`. + /// + /// NOTE: This is meant to be a temporary work around to permit us to represent some legacy + /// types in the transaction kernel API which use a different representation on the operand + /// stack than in memory - this _will_ be deprecated in the future. + BigEndian, +} + +impl TypeRepr { + /// Construct a packed representation with the given alignment + #[inline] + pub fn packed(align: u16) -> Self { + Self::Packed( + NonZeroU16::new(align).expect("invalid alignment: expected value in range 1..=65535"), + ) + } + + /// Construct a representation with the given minimum alignment + #[inline] + pub fn align(align: u16) -> Self { + Self::Align( + NonZeroU16::new(align).expect("invalid alignment: expected value in range 1..=65535"), + ) + } + + /// Return true if this type representation is transparent + pub fn is_transparent(&self) -> bool { + matches!(self, Self::Transparent) + } + + /// Return true if this type representation is packed + pub fn is_packed(&self) -> bool { + matches!(self, Self::Packed(_)) + } + + /// Get the custom alignment given for this type representation, if applicable + pub fn min_alignment(&self) -> Option { + match self { + Self::Packed(align) | Self::Align(align) => Some(align.get() as usize), + _ => None, + } + } +} + +impl StructType { + /// Create a new struct with default representation, i.e. a struct with representation of + /// `TypeRepr::Packed(1)`. + #[inline] + pub fn new>(fields: I) -> Self { + Self::new_with_repr(TypeRepr::Default, fields) + } + + /// Create a new struct with the given representation. + /// + /// This function will panic if the rules of the given representation are violated. + pub fn new_with_repr>(repr: TypeRepr, fields: I) -> Self { + let tys = fields.into_iter().collect::>(); + let mut fields = SmallVec::<[_; 2]>::with_capacity(tys.len()); + let size = match repr { + TypeRepr::Transparent => { + let mut offset = 0u32; + for (index, ty) in tys.into_iter().enumerate() { + let index: u8 = + index.try_into().expect("invalid struct: expected no more than 255 fields"); + let field_size: u32 = ty + .size_in_bytes() + .try_into() + .expect("invalid type: size is larger than 2^32 bytes"); + if field_size == 0 { + fields.push(StructField { + index, + align: 1, + offset, + ty, + }); + } else { + let align = ty.min_alignment().try_into().expect( + "invalid struct field alignment: expected power of two between 1 and \ + 2^16", + ); + assert_eq!( + offset, 0, + "invalid transparent representation for struct: repr(transparent) is \ + only valid for structs with a single non-zero sized field" + ); + fields.push(StructField { + index, + align, + offset, + ty, + }); + offset += field_size; + } + } + offset + } + repr => { + let mut offset = 0u32; + let default_align: u16 = + tys.iter().map(|t| t.min_alignment()).max().unwrap_or(1).try_into().expect( + "invalid struct field alignment: expected power of two between 1 and 2^16", + ); + let align = match repr { + TypeRepr::Align(align) => core::cmp::max(align.get(), default_align), + TypeRepr::Packed(align) => core::cmp::min(align.get(), default_align), + TypeRepr::Transparent | TypeRepr::Default | TypeRepr::BigEndian => { + default_align + } + }; + + for (index, ty) in tys.into_iter().enumerate() { + let index: u8 = + index.try_into().expect("invalid struct: expected no more than 255 fields"); + let field_size: u32 = ty + .size_in_bytes() + .try_into() + .expect("invalid type: size is larger than 2^32 bytes"); + let default_align: u16 = ty.min_alignment().try_into().expect( + "invalid struct field alignment: expected power of two between 1 and 2^16", + ); + let align: u16 = match repr { + TypeRepr::Packed(align) => core::cmp::min(align.get(), default_align), + _ => default_align, + }; + offset += offset.align_offset(align as u32); + fields.push(StructField { + index, + align, + offset, + ty, + }); + offset += field_size; + } + offset.align_up(align as u32) + } + }; + + Self { repr, size, fields } + } + + /// Get the [TypeRepr] for this struct + #[inline] + pub const fn repr(&self) -> TypeRepr { + self.repr + } + + /// Get the minimum alignment for this struct + pub fn min_alignment(&self) -> usize { + self.repr + .min_alignment() + .unwrap_or_else(|| self.fields.iter().map(|f| f.align as usize).max().unwrap_or(1)) + } + + /// Get the total size in bytes required to hold this struct, including alignment padding + #[inline] + pub fn size(&self) -> usize { + self.size as usize + } + + /// Get the struct field at `index`, relative to declaration order. + pub fn get(&self, index: usize) -> &StructField { + &self.fields[index] + } + + /// Get the struct fields as a slice + pub fn fields(&self) -> &[StructField] { + self.fields.as_slice() + } + + /// Returns true if this struct has no fields + pub fn is_empty(&self) -> bool { + self.fields.is_empty() + } + + /// Get the length of this struct (i.e. number of fields) + pub fn len(&self) -> usize { + self.fields.len() + } +} + +impl TryFrom for StructType { + type Error = Type; + + fn try_from(ty: Type) -> Result { + match ty { + Type::Struct(ty) => Ok(alloc::sync::Arc::unwrap_or_clone(ty)), + other => Err(other), + } + } +} + +impl fmt::Display for StructType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use miden_formatting::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl miden_formatting::prettier::PrettyPrint for StructType { + fn render(&self) -> miden_formatting::prettier::Document { + use miden_formatting::prettier::*; + + let header = match self.repr.render() { + Document::Empty => const_text("struct "), + repr => const_text("struct ") + const_text("#[repr(") + repr + const_text(")] "), + }; + + let singleline = self.fields.iter().enumerate().fold(Document::Empty, |acc, (i, field)| { + if i > 0 { + acc + const_text(", ") + field.render() + } else { + field.render() + } + }); + let multiline = indent( + 4, + self.fields.iter().enumerate().fold(Document::Empty, |acc, (i, field)| { + if i > 0 { + acc + nl() + field.render() + } else { + nl() + field.render() + } + }), + ); + let body = const_text("{") + (singleline | multiline) + const_text("}"); + + header + body + } +} + +impl fmt::Display for StructField { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.ty, f) + } +} + +impl miden_formatting::prettier::PrettyPrint for StructField { + fn render(&self) -> miden_formatting::prettier::Document { + self.ty.render() + } +} + +impl fmt::Display for TypeRepr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use miden_formatting::prettier::PrettyPrint; + self.pretty_print(f) + } +} + +impl miden_formatting::prettier::PrettyPrint for TypeRepr { + fn render(&self) -> miden_formatting::prettier::Document { + use alloc::format; + + use miden_formatting::prettier::*; + match self { + Self::Default => Document::Empty, + Self::Transparent => const_text("transparent"), + Self::Align(align) => text(format!("align({align})")), + Self::Packed(align) => text(format!("packed({align})")), + Self::BigEndian => const_text("big-endian"), + } + } +} diff --git a/hir/CHANGELOG.md b/hir/CHANGELOG.md index 104006b93..cd70dc6d6 100644 --- a/hir/CHANGELOG.md +++ b/hir/CHANGELOG.md @@ -6,6 +6,122 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-hir-v0.4.0...midenc-hir-v0.4.1) - 2025-09-03 + +### Fixed + +- handle empty dominance frontier at `DominanceFrontier::iterate_all` +- typo in `find_nearest_common_common_dominator` call +- infinite loop in `dominated_by_slow_tree_walk`. + +### Other + +- Add type aliases `Int128` for `SizedInt<128>`. +- formatting + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-hir-v0.1.5...midenc-hir-v0.4.0) - 2025-08-15 + +### Added + +- panic if ops marked with SameOperandsAndResultType have no results +- panic if ops marked with SameTypeOperands and/or SameOperandsAndResultType do not have operands + +### Fixed + +- don't panic on no operands when verifying SameTypeOperands[AndResult] + +### Other + +- Merge pull request #546 from lambdaclass/fabrizioorsi/i404-symbol-table-op-builder +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) +- remove InvalidOpsWithReturn entirely and replace with test::add +- replace if statement with assert! when checking result vector size +- remove out-dated comment +- apply formatting +- Typo fixes + enhacements in `SameTypeOperands` verification documentation. +- Update comment regarding Operation::verify +- Remove un-used imports +- Uncomment `#[ignore]` macro in `derived_op_verifier_test()` now that the verifier is enabled +- Delete op.verification all-together +- Add a comment explaining why we do an initial verification +- Add pass manager in test +- Momentarily verify early +- Pushing changes to save changes. +- Verify the parent traits before the operation trait. +- Remove the additional pattern declaration, leave an optional semi-colon which matches both patterns +- Remove additional spaces +- Add documentation to SameOperandsAndResultType and derive macro +- Update diagnostic description +- Make it possible to inherit an arbitrary amount of parent traits (comma separated) +- Remove operand type check from SameOperandsAndResultType +- Call parent trait verify function before OPTrait verify +- Add $ParentTrait pattern to verify macro + Add SameTypeOperands as a explicit dependency +- Implement SameOperandsAndResultType test. +- Declare the InvalidOpsWithReturn struct and register it on the TestDialect Dialect +- First implementation for SameOperandsAndResultType validation +- Enable operands_are_the_same_type validation +- Use the midenc_hir `test` dialect for ops in the new tests. + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-hir-v0.1.0...midenc-hir-v0.1.5) - 2025-07-01 + +### Fixed + +- prevent global variables from overlapping with data segments + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-hir-v0.0.7...midenc-hir-v0.0.8) - 2025-04-24 + +### Added +- *(types)* clean up hir-type for use outside the compiler +- *(ir)* provide some useful symbol path helpers +- *(ir)* allow converting exclusive immutable borrow into a mutable one +- *(ir)* provide utility pass for printing ir of specific operations +- *(frontend)* generate `CanonLower` synthetic functions for +- *(frontend)* generate `CanonLift` synthetic functions for exports +- add `CallConv::CrossCtx` calling convention for cross-context +- draft Wasm CM function type flattening, checks for scalar type +- on Miden CCABI lifting/lowering get import/export function signature from +- store the imported function type in the component import +- rewrite the function calls from the imported function (Miden CCABI) to +- draft lifting function generation in `LiftImportsCrossCtxStage` +- draft `LiftImportsCrossCtxStage` scaffolding +- add `ComponentBuilder::load`, `with_exports` to modify +- implement cross-context Call op, rename IR Exec back to Call +- restore module and function names of the intrinsics and Miden + +### Fixed +- *(ir)* missing result value for builtin.global_symbol when printed +- *(ir)* resolve symbol paths without leaf +- *(ir)* custom printer for global_symbol, add missing traits +- *(ir)* missed closing paren in function decl signatures +- *(ir)* would_be_trivially_dead_even_if_terminator +- build after rebase +- implement `FunctionBuilderExt::change_branch_destination` +- a few rebasing issues +- populate `Linker::allow_missing` with linked library exports +- refine `Component` imports and exports to reference module imports + +### Other +- treat warnings as compiler errors, +- update Miden VM to v0.13.2 and uncomment the Miden package +- *(frontend)* rework handling of symbols in frontend +- update pre-interned symbols +- implement MemoryEffectOpInterface for test.constant +- specify log target during region simplification, pattern rewrites +- update rust toolchain, clean up deps +- move `Context::change_branch_destination` to be a method in `BranchOpInterface` +- use `OpBuilder` in `ComponentBuilder::define_function` +- rename hir2 crates +- remove old contents of hir, hir-analysis, hir-transform +- update the Miden VM with updated `miden-package` crate +- update rust toolchain to 1-16 nightly @ 1.86.0 +- normalize use of fxhash-based hash maps +- replace `CallConv::CrossCtx` with `CanonLift` and `CanonLower` +- rename `CanonAbiImport::interface_function_ty` +- switch from passing `Module` to `Component` in the compiler stages +- rename Call IR op to Exec +- add note script compilation test; +- remove digest-in-function-name encoding and `MidenAbiImport::digest`, + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-hir-v0.0.6...midenc-hir-v0.0.7) - 2024-09-17 ### Other diff --git a/hir/Cargo.toml b/hir/Cargo.toml index d486dc05e..990f46e94 100644 --- a/hir/Cargo.toml +++ b/hir/Cargo.toml @@ -13,46 +13,32 @@ edition.workspace = true [features] default = ["std"] -std = ["rustc-demangle/std"] -serde = [ - "dep:serde", - "dep:serde_repr", - "dep:serde_bytes", - "midenc-hir-symbol/serde", -] - -[build-dependencies] -lalrpop = { version = "0.20", default-features = false } +std = ["rustc-demangle/std", "compact_str/std", "semver/std"] +debug_refcell = [] [dependencies] anyhow.workspace = true -either.workspace = true -cranelift-entity.workspace = true +bitvec.workspace = true +bitflags.workspace = true +blink-alloc.workspace = true +compact_str.workspace = true +hashbrown.workspace = true +hashbrown_old_nightly_hack.workspace = true intrusive-collections.workspace = true inventory.workspace = true -lalrpop-util = "0.20" log.workspace = true miden-core.workspace = true -miden-assembly.workspace = true midenc-hir-symbol.workspace = true midenc-hir-type.workspace = true midenc-hir-macros.workspace = true midenc-session.workspace = true -num-bigint = "0.4" -num-traits = "0.2" -petgraph.workspace = true -paste.workspace = true rustc-hash.workspace = true rustc-demangle = "0.1.19" -serde = { workspace = true, optional = true } -serde_repr = { workspace = true, optional = true } -serde_bytes = { workspace = true, optional = true } +semver.workspace = true smallvec.workspace = true thiserror.workspace = true -typed-arena = "2.0" -unicode-width = { version = "0.1", features = ["no_std"] } -derive_more.workspace = true -indexmap.workspace = true [dev-dependencies] +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging pretty_assertions = "1.0" +env_logger.workspace = true diff --git a/hir/build.rs b/hir/build.rs deleted file mode 100644 index 23c7d3f80..000000000 --- a/hir/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate lalrpop; - -fn main() { - lalrpop::process_root().unwrap(); -} diff --git a/hir/src/adt.rs b/hir/src/adt.rs new file mode 100644 index 000000000..e5b90e5fa --- /dev/null +++ b/hir/src/adt.rs @@ -0,0 +1,21 @@ +pub mod arena; +pub mod smalldeque; +pub mod smallmap; +pub mod smallordset; +pub mod smallprio; +pub mod smallset; + +pub use self::{ + arena::Arena, + smalldeque::SmallDeque, + smallmap::{SmallDenseMap, SmallOrdMap}, + smallordset::SmallOrdSet, + smallprio::SmallPriorityQueue, + smallset::SmallSet, +}; + +#[doc(hidden)] +pub trait SizedTypeProperties: Sized { + const IS_ZST: bool = core::mem::size_of::() == 0; +} +impl SizedTypeProperties for T {} diff --git a/hir/src/adt/arena.rs b/hir/src/adt/arena.rs new file mode 100644 index 000000000..02d850d1d --- /dev/null +++ b/hir/src/adt/arena.rs @@ -0,0 +1,481 @@ +use alloc::{ + alloc::{self as sysalloc, Layout}, + boxed::Box, + rc::Rc, + vec::Vec, +}; +use core::{ + cell::RefCell, + mem::MaybeUninit, + ptr::NonNull, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use intrusive_collections::{intrusive_adapter, LinkedListLink}; + +use crate::adt::SizedTypeProperties; + +/// A typed arena with the following properties: +/// +/// * Amortized growth (i.e. memory is allocated in chunks, allocated as capacity runs out) +/// * Append-only (items can't be deleted from the arena) +/// * Pinned storage (items never move once stored in the arena) +/// * Thanks to the previous points, can be allocated from while holding references to items held in +/// the arena +/// * Can be sent between threads (though it is not `Sync`) +/// * Default instance allocates no memory, so is cheap to create +/// * Can be very efficiently extended from iterators, particularly for stdlib collections/types +/// * `Vec` and `Box<[T]>` values can be used to extend the arena without any allocations or +/// copies - the arena takes direct ownership over their backing storage as chunks in the arena. +/// +pub struct Arena { + chunks: RefCell>, + min_capacity: usize, +} + +unsafe impl Send for Arena {} + +impl Default for Arena { + fn default() -> Self { + Self::new() + } +} + +impl Arena { + const DEFAULT_CHUNK_SIZE: usize = 64; + + /// Create an empty arena with no allocated capacity + pub fn new() -> Self { + Self { + chunks: Default::default(), + min_capacity: Self::DEFAULT_CHUNK_SIZE, + } + } + + /// Allocates an arena with capacity for at least `capacity` items. + /// + /// The actual allocated capacity may be larger. + pub fn with_capacity(capacity: usize) -> Self { + let mut chunks = ChunkList::default(); + chunks.push_back(ChunkHeader::new(capacity)); + Self { + chunks: RefCell::new(chunks), + min_capacity: core::cmp::max(capacity, Self::DEFAULT_CHUNK_SIZE), + } + } + + /// Allocate `item` in the arena, returning a non-null pointer to the allocation. + /// + /// If `T` is a zero-sized type, this returns [NonNull::dangling], which is a well-aligned + /// pointer, but not necessarily a valid one. As far as I can tell, it is technically allowed to + /// construct a reference from that pointer when the type is zero-sized, as zero-sized types do + /// not refer to any memory at all (thus the reference is meaningless). That said, you should + /// probably _not_ rely on that. + pub fn push(&self, item: T) -> NonNull { + if T::IS_ZST { + return NonNull::dangling(); + } + + let mut chunks = self.chunks.borrow_mut(); + if chunks.back().get().is_none_or(|chunk| chunk.available_capacity() == 0) { + chunks.push_back(ChunkHeader::new(self.min_capacity)); + } + let chunk = unsafe { chunks.back().get().unwrap_unchecked() }; + chunk.alloc(item) + } + + /// Get a pointer to the `index`th item stored in the arena, or `None` if the index is invalid. + /// + /// # Safety + /// + /// This function is unsafe for two reasons: + /// + /// * The caller is responsible for knowing the indices of items in the arena when using this. + /// This is not hard to do, but the second point below requires it. + /// * The caller must ensure that, should any reference be created from the returned pointer, + /// that the aliasing rules of Rust are upheld, i.e. it is undefined behavior to create a + /// reference if there outstanding mutable references (and vice versa). + pub unsafe fn get(&self, index: usize) -> Option> { + let chunks = self.chunks.borrow(); + let mut cursor = chunks.front(); + let mut current_index = 0; + while current_index <= index { + let chunk = cursor.clone_pointer()?; + cursor.move_next(); + let chunk_len = chunk.len(); + let next_index = current_index + chunk_len; + if next_index > index { + // We found our chunk + let offset = index - current_index; + return Some(unsafe { chunk.data().add(offset).cast() }); + } else { + // Try the next one + current_index = next_index; + } + } + + None + } + + /// Allocates `items` in the arena contiguously, returning a non-null pointer to the allocation. + /// + /// NOTE: This may potentially waste capacity of the currently allocated chunk, if the given + /// items do not fit in its available capacity, but this is considered a minor issue for now. + pub fn extend(&self, items: I) -> NonNull<[T]> + where + I: IntoIterator, + { + if T::IS_ZST { + return NonNull::slice_from_raw_parts(NonNull::dangling(), 0); + } + + let mut chunks = self.chunks.borrow_mut(); + items.extend_arena(&mut chunks) + } +} + +impl FromIterator for Arena { + fn from_iter>(iter: I) -> Self { + let arena = Self::new(); + arena.extend(iter); + arena + } +} + +impl IntoIterator for Arena { + type IntoIter = IntoIter; + type Item = T; + + fn into_iter(mut self) -> Self::IntoIter { + IntoIter { + chunks: self.chunks.get_mut().take(), + current_chunk: None, + current_len: 0, + current_index: 0, + } + } +} + +#[doc(hidden)] +pub struct IntoIter { + chunks: ChunkList, + current_chunk: Option>>, + current_len: usize, + current_index: usize, +} +impl core::iter::FusedIterator for IntoIter {} +impl ExactSizeIterator for IntoIter { + fn len(&self) -> usize { + let remaining_in_current = self.current_len - self.current_index; + let remaining_in_rest = + self.chunks.iter().map(|chunk| chunk.len.load(Ordering::Relaxed)).sum::(); + remaining_in_current + remaining_in_rest + } +} +impl Iterator for IntoIter { + type Item = T; + + fn next(&mut self) -> Option { + let current_chunk = match self.current_chunk.as_ref() { + Some(current_chunk) if self.current_index < self.current_len => { + Rc::clone(current_chunk) + } + _ => { + loop { + // When we take a chunk off the list, we take ownership over the length as + // well, setting that of the chunk to zero. This is to ensure that if a + // panic occurs, we still drop all of the items pending in the iterator + // without violating any memory safety guarantees. + let current_chunk = self.chunks.pop_front()?; + self.current_chunk = Some(current_chunk.clone()); + self.current_len = current_chunk.len.swap(0, Ordering::Relaxed); + self.current_index = 0; + if self.current_len > 0 { + break current_chunk; + } + } + } + }; + + let item = unsafe { + let ptr = current_chunk.data().add(self.current_index).cast::(); + ptr.read() + }; + + self.current_index += 1; + Some(item) + } +} + +impl Drop for IntoIter { + fn drop(&mut self) { + // Drop any items in the current chunk that we're responsible for dropping + if let Some(current_chunk) = self.current_chunk.take() { + if core::mem::needs_drop::() { + let ptr = current_chunk.data(); + while self.current_index < self.current_len { + unsafe { + let ptr = ptr.add(self.current_index); + core::ptr::drop_in_place(ptr.as_ptr()); + self.current_index += 1; + } + } + } + } + + // Drop any leftover chunks + self.chunks.clear(); + } +} + +intrusive_adapter!(ChunkHeaderAdapter = Rc>: ChunkHeader { link: LinkedListLink }); + +type ChunkList = intrusive_collections::LinkedList>; + +struct ChunkHeader { + link: LinkedListLink, + /// Pointer to the allocated chunk + chunk: NonNull, + /// Allocated capacity of the chunk in units of T + /// + /// To obtain the allocated size of the chunk, you must use `Layout::array::(self.capacity)` + capacity: usize, + /// The number of elements that have been stored in this chunk + len: AtomicUsize, + /// The alignment offset from `self.chunk` where the first element starts + offset: usize, + _marker: core::marker::PhantomData, +} + +impl ChunkHeader { + pub fn new(capacity: usize) -> Rc { + if T::IS_ZST { + let chunk = NonNull::::dangling(); + Rc::new(Self { + link: LinkedListLink::new(), + chunk: chunk.cast(), + capacity: usize::MAX, + len: Default::default(), + offset: 0, + _marker: core::marker::PhantomData, + }) + } else { + let layout = Self::layout(capacity); + let chunk = unsafe { sysalloc::alloc(layout) }; + match NonNull::new(chunk) { + Some(chunk) => { + let offset = chunk.align_offset(core::mem::align_of::()); + Rc::new(Self { + link: LinkedListLink::new(), + chunk, + capacity, + len: Default::default(), + offset, + _marker: core::marker::PhantomData, + }) + } + None => sysalloc::handle_alloc_error(layout), + } + } + } + + pub fn alloc(&self, item: T) -> NonNull { + // Reserve the slot in which we're going to write `item` + let index = self.len.fetch_add(1, Ordering::Release); + assert!( + index < self.capacity, + "unguarded call to `alloc` without checking capacity of chunk" + ); + + unsafe { + let ptr = self.data().add(index).cast::(); + let uninit_item = ptr.as_uninit_mut(); + uninit_item.write(item); + ptr + } + } + + pub fn alloc_slice(&self, len: usize) -> NonNull<[T]> { + // Reserve the slot(s) in which we're going to write the slice elements + let index = self.len.fetch_add(len, Ordering::Release); + assert!( + index + len <= self.capacity, + "unguarded call to `alloc_slice` without checking capacity of chunk" + ); + + unsafe { + let ptr = self.data().add(index); + NonNull::slice_from_raw_parts(ptr.cast::(), len) + } + } + + /// Get a pointer to the first element of this chunk + pub fn data(&self) -> NonNull> { + unsafe { self.chunk.byte_add(self.offset).cast() } + } + + #[inline] + pub fn len(&self) -> usize { + self.len.load(Ordering::Acquire) + } + + pub fn available_capacity(&self) -> usize { + self.capacity - self.len() + } + + #[inline] + fn layout(capacity: usize) -> Layout { + Layout::array::(capacity).expect("invalid capacity") + } +} + +impl Drop for ChunkHeader { + fn drop(&mut self) { + // We do not allocate any memory for zero-sized types + if T::IS_ZST { + return; + } + + // Drop any initialized items in this chunk, if T has drop glue + if core::mem::needs_drop::() { + let data = self.data().cast::(); + for index in 0..self.len.load(Ordering::Relaxed) { + unsafe { + let data = data.add(index); + core::ptr::drop_in_place(data.as_ptr()); + } + } + } + + // Deallocate chunk + unsafe { + sysalloc::dealloc(self.chunk.as_ptr(), Self::layout(self.capacity)); + } + } +} + +trait SpecArenaExtend: IntoIterator { + fn extend_arena(self, chunks: &mut ChunkList) -> NonNull<[Self::Item]>; +} + +impl SpecArenaExtend for I +where + I: IntoIterator, +{ + default fn extend_arena(self, chunks: &mut ChunkList) -> NonNull<[Self::Item]> { + // We don't know the final capacity, so let Vec figure it out, and then take ownership + // over its allocation and create a chunk representing it. We will place this chunk + // before the current (unused) chunk, so that remaining capacity in that chunk can + // continue to be filled + self.into_iter().collect::>().into_boxed_slice().extend_arena(chunks) + } +} + +impl SpecArenaExtend for I +where + I: IntoIterator, + ::IntoIter: ExactSizeIterator + core::iter::TrustedLen, +{ + default fn extend_arena(self, chunks: &mut ChunkList) -> NonNull<[Self::Item]> { + // We know the exact capacity required, see if we can use our current chunk, or allocate + // a new one. + let iter = self.into_iter(); + let len = iter.len(); + assert_ne!(len, usize::MAX, "invalid iterator: input too large"); + let capacity = core::cmp::max(len, Arena::<::Item>::DEFAULT_CHUNK_SIZE); + let mut cursor = chunks.back_mut(); + + // Allocate the backing memory for the slice + let ptr = if cursor.is_null() { + let chunk = ChunkHeader::<::Item>::new(capacity); + let ptr = chunk.alloc_slice(len); + cursor.insert_after(chunk); + ptr + } else { + let chunk = unsafe { cursor.get().unwrap_unchecked() }; + if chunk.available_capacity() < len { + // We don't have enough capacity in the current chunk, allocate a new one + let chunk = ChunkHeader::<::Item>::new(capacity); + let ptr = chunk.alloc_slice(len); + // Place the chunk we just allocated before the last one, since it still has + // available capacity + cursor.insert_before(chunk); + ptr + } else { + // We have enough capacity, use it + chunk.alloc_slice(len) + } + }; + + // Write the items + let items = unsafe { ptr.as_uninit_slice_mut() }; + for (i, item) in iter.enumerate() { + items[i].write(item); + } + + ptr + } +} + +impl SpecArenaExtend for Vec { + #[inline] + fn extend_arena(self, chunks: &mut ChunkList) -> NonNull<[T]> { + self.into_boxed_slice().extend_arena(chunks) + } +} + +impl SpecArenaExtend for Box<[T]> { + fn extend_arena(self, chunks: &mut ChunkList) -> NonNull<[T]> { + let capacity = self.len(); + let ptr = unsafe { NonNull::new_unchecked(Box::into_raw(self)) }; + let mut cursor = chunks.back_mut(); + cursor.insert_before(Rc::new(ChunkHeader { + link: LinkedListLink::new(), + chunk: ptr.as_non_null_ptr().cast(), + capacity, + len: AtomicUsize::new(capacity), + offset: 0, + _marker: core::marker::PhantomData, + })); + ptr + } +} + +impl SpecArenaExtend for [T; N] { + fn extend_arena(self, chunks: &mut ChunkList) -> NonNull<[T]> { + // We know the exact capacity required, see if we can use our current chunk, or allocate + // a new one. + let mut cursor = chunks.back_mut(); + let capacity = core::cmp::max(N, Arena::::DEFAULT_CHUNK_SIZE); + + // Allocate the backing memory for the slice + let ptr = if cursor.is_null() { + let chunk = ChunkHeader::::new(capacity); + let ptr = chunk.alloc_slice(N); + cursor.insert_after(chunk); + ptr + } else { + let chunk = unsafe { cursor.get().unwrap_unchecked() }; + if chunk.available_capacity() < N { + // We don't have enough capacity in the current chunk, allocate a new one + let chunk = ChunkHeader::::new(capacity); + let ptr = chunk.alloc_slice(N); + // Place the chunk we just allocated before the last one, since it still has + // available capacity + cursor.insert_before(chunk); + ptr + } else { + // We have enough capacity, use it + chunk.alloc_slice(N) + } + }; + + // Write the items + let items = unsafe { ptr.as_uninit_slice_mut() }; + for (i, item) in self.into_iter().enumerate() { + items[i].write(item); + } + + ptr + } +} diff --git a/hir/src/adt/mod.rs b/hir/src/adt/mod.rs deleted file mode 100644 index 9aeca6bfb..000000000 --- a/hir/src/adt/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub mod smallmap; -pub mod smallordset; -pub mod smallset; -pub mod sparsemap; - -pub use self::{ - smallmap::SmallMap, - smallordset::SmallOrdSet, - smallset::SmallSet, - sparsemap::{SparseMap, SparseMapValue}, -}; diff --git a/hir/src/adt/smalldeque.rs b/hir/src/adt/smalldeque.rs new file mode 100644 index 000000000..62c24b485 --- /dev/null +++ b/hir/src/adt/smalldeque.rs @@ -0,0 +1,3771 @@ +use alloc::vec::Vec; +use core::{ + cmp::Ordering, + fmt, + iter::{repeat_n, repeat_with, ByRefSized}, + ops::{Index, IndexMut, Range, RangeBounds}, + ptr::{self, NonNull}, +}; + +use smallvec::SmallVec; + +use super::SizedTypeProperties; + +/// [SmallDeque] is a [alloc::collections::VecDeque]-like structure that can store a specified +/// number of elements inline (i.e. on the stack) without allocating memory from the heap. +/// +/// This data structure is designed to basically provide the functionality of `VecDeque` without +/// needing to allocate on the heap for small numbers of nodes. +/// +/// Internally, [SmallDeque] is implemented on top of [SmallVec]. +/// +/// Most of the implementation is ripped from the standard library `VecDeque` impl, but adapted +/// for `SmallVec` +pub struct SmallDeque { + /// `self[0]`, if it exists, is `buf[head]`. + /// `head < buf.capacity()`, unless `buf.capacity() == 0` when `head == 0`. + head: usize, + /// The number of initialized elements, starting from the one at `head` and potentially + /// wrapping around. + /// + /// If `len == 0`, the exact value of `head` is unimportant. + /// + /// If `T` is zero-sized, then `self.len <= usize::MAX`, otherwise + /// `self.len <= isize::MAX as usize` + len: usize, + buf: SmallVec<[T; N]>, +} +impl Clone for SmallDeque { + fn clone(&self) -> Self { + let mut deq = Self::with_capacity(self.len()); + deq.extend(self.iter().cloned()); + deq + } + + fn clone_from(&mut self, source: &Self) { + self.clear(); + self.extend(source.iter().cloned()); + } +} +impl Default for SmallDeque { + fn default() -> Self { + Self { + head: 0, + len: 0, + buf: Default::default(), + } + } +} +impl SmallDeque { + /// Returns a new, empty [SmallDeque] + #[inline] + #[must_use] + pub const fn new() -> Self { + Self { + head: 0, + len: 0, + buf: SmallVec::new_const(), + } + } + + /// Create an empty deque with pre-allocated space for `capacity` elements. + #[must_use] + pub fn with_capacity(capacity: usize) -> Self { + Self { + head: 0, + len: 0, + buf: SmallVec::with_capacity(capacity), + } + } + + /// Returns true if this map is empty + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns the number of key/value pairs in this map + pub fn len(&self) -> usize { + self.len + } + + /// Return a front-to-back iterator. + pub fn iter(&self) -> Iter<'_, T> { + let (a, b) = self.as_slices(); + Iter::new(a.iter(), b.iter()) + } + + /// Return a front-to-back iterator that returns mutable references + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + let (a, b) = self.as_mut_slices(); + IterMut::new(a.iter_mut(), b.iter_mut()) + } + + /// Returns a pair of slices which contain, in order, the contents of the + /// deque. + /// + /// If [`SmallDeque::make_contiguous`] was previously called, all elements of the + /// deque will be in the first slice and the second slice will be empty. + #[inline] + pub fn as_slices(&self) -> (&[T], &[T]) { + let (a_range, b_range) = self.slice_ranges(.., self.len); + // SAFETY: `slice_ranges` always returns valid ranges into the physical buffer. + unsafe { (&*self.buffer_range(a_range), &*self.buffer_range(b_range)) } + } + + /// Returns a pair of slices which contain, in order, the contents of the + /// deque. + /// + /// If [`SmallDeque::make_contiguous`] was previously called, all elements of the + /// deque will be in the first slice and the second slice will be empty. + #[inline] + pub fn as_mut_slices(&mut self) -> (&mut [T], &mut [T]) { + let (a_range, b_range) = self.slice_ranges(.., self.len); + // SAFETY: `slice_ranges` always returns valid ranges into the physical buffer. + unsafe { (&mut *self.buffer_range_mut(a_range), &mut *self.buffer_range_mut(b_range)) } + } + + /// Given a range into the logical buffer of the deque, this function + /// return two ranges into the physical buffer that correspond to + /// the given range. The `len` parameter should usually just be `self.len`; + /// the reason it's passed explicitly is that if the deque is wrapped in + /// a `Drain`, then `self.len` is not actually the length of the deque. + /// + /// # Safety + /// + /// This function is always safe to call. For the resulting ranges to be valid + /// ranges into the physical buffer, the caller must ensure that the result of + /// calling `slice::range(range, ..len)` represents a valid range into the + /// logical buffer, and that all elements in that range are initialized. + fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) + where + R: RangeBounds, + { + let Range { start, end } = core::slice::range(range, ..len); + let len = end - start; + + if len == 0 { + (0..0, 0..0) + } else { + // `slice::range` guarantees that `start <= end <= len`. + // because `len != 0`, we know that `start < end`, so `start < len` + // and the indexing is valid. + let wrapped_start = self.to_physical_idx(start); + + // this subtraction can never overflow because `wrapped_start` is + // at most `self.capacity()` (and if `self.capacity != 0`, then `wrapped_start` is strictly less + // than `self.capacity`). + let head_len = self.capacity() - wrapped_start; + + if head_len >= len { + // we know that `len + wrapped_start <= self.capacity <= usize::MAX`, so this addition can't overflow + (wrapped_start..wrapped_start + len, 0..0) + } else { + // can't overflow because of the if condition + let tail_len = len - head_len; + (wrapped_start..self.capacity(), 0..tail_len) + } + } + } + + /// Creates an iterator that covers the specified range in the deque. + /// + /// # Panics + /// + /// Panics if the starting point is greater than the end point or if + /// the end point is greater than the length of the deque. + #[inline] + pub fn range(&self, range: R) -> Iter<'_, T> + where + R: RangeBounds, + { + let (a_range, b_range) = self.slice_ranges(range, self.len); + // SAFETY: The ranges returned by `slice_ranges` + // are valid ranges into the physical buffer, so + // it's ok to pass them to `buffer_range` and + // dereference the result. + let a = unsafe { &*self.buffer_range(a_range) }; + let b = unsafe { &*self.buffer_range(b_range) }; + Iter::new(a.iter(), b.iter()) + } + + /// Creates an iterator that covers the specified mutable range in the deque. + /// + /// # Panics + /// + /// Panics if the starting point is greater than the end point or if + /// the end point is greater than the length of the deque. + #[inline] + pub fn range_mut(&mut self, range: R) -> IterMut<'_, T> + where + R: RangeBounds, + { + let (a_range, b_range) = self.slice_ranges(range, self.len); + // SAFETY: The ranges returned by `slice_ranges` + // are valid ranges into the physical buffer, so + // it's ok to pass them to `buffer_range` and + // dereference the result. + let a = unsafe { &mut *self.buffer_range_mut(a_range) }; + let b = unsafe { &mut *self.buffer_range_mut(b_range) }; + IterMut::new(a.iter_mut(), b.iter_mut()) + } + + /// Get a reference to the element at the given index. + /// + /// Element at index 0 is the front of the queue. + pub fn get(&self, index: usize) -> Option<&T> { + if index < self.len { + let index = self.to_physical_idx(index); + unsafe { Some(&*self.ptr().add(index)) } + } else { + None + } + } + + /// Get a mutable reference to the element at the given index. + /// + /// Element at index 0 is the front of the queue. + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + if index < self.len { + let index = self.to_physical_idx(index); + unsafe { Some(&mut *self.ptr_mut().add(index)) } + } else { + None + } + } + + /// Swaps elements at indices `i` and `j` + /// + /// `i` and `j` may be equal. + /// + /// Element at index 0 is the front of the queue. + /// + /// # Panics + /// + /// Panics if either index is out of bounds. + pub fn swap(&mut self, i: usize, j: usize) { + assert!(i < self.len()); + assert!(j < self.len()); + let ri = self.to_physical_idx(i); + let rj = self.to_physical_idx(j); + unsafe { ptr::swap(self.ptr_mut().add(ri), self.ptr_mut().add(rj)) } + } + + /// Returns the number of elements the deque can hold without reallocating. + #[inline] + pub fn capacity(&self) -> usize { + if T::IS_ZST { + usize::MAX + } else { + self.buf.capacity() + } + } + + /// Reserves the minimum capacity for at least `additional` more elements to be inserted in the + /// given deque. Does nothing if the capacity is already sufficient. + /// + /// Note that the allocator may give the collection more space than it requests. Therefore + /// capacity can not be relied upon to be precisely minimal. Prefer [`reserve`] if future + /// insertions are expected. + /// + /// # Panics + /// + /// Panics if the new capacity overflows `usize`. + pub fn reserve_exact(&mut self, additional: usize) { + let new_cap = self.len.checked_add(additional).expect("capacity overflow"); + let old_cap = self.capacity(); + + if new_cap > old_cap { + self.buf.try_grow(new_cap).expect("capacity overflow"); + unsafe { + self.handle_capacity_increase(old_cap); + } + } + } + + /// Reserves capacity for at least `additional` more elements to be inserted in the given + /// deque. The collection may reserve more space to speculatively avoid frequent reallocations. + /// + /// # Panics + /// + /// Panics if the new capacity overflows `usize`. + pub fn reserve(&mut self, additional: usize) { + let new_cap = self.len.checked_add(additional).expect("capacity overflow"); + let old_cap = self.capacity(); + + if new_cap > old_cap { + // we don't need to reserve_exact(), as the size doesn't have + // to be a power of 2. + self.buf.try_grow(new_cap).expect("capacity overflow"); + unsafe { + self.handle_capacity_increase(old_cap); + } + } + } + + /// Shortens the deque, keeping the first `len` elements and dropping + /// the rest. + /// + /// If `len` is greater or equal to the deque's current length, this has + /// no effect. + pub fn truncate(&mut self, len: usize) { + /// Runs the destructor for all items in the slice when it gets dropped (normally or + /// during unwinding). + struct Dropper<'a, T>(&'a mut [T]); + + impl Drop for Dropper<'_, T> { + fn drop(&mut self) { + unsafe { + ptr::drop_in_place(self.0); + } + } + } + + // Safe because: + // + // * Any slice passed to `drop_in_place` is valid; the second case has + // `len <= front.len()` and returning on `len > self.len()` ensures + // `begin <= back.len()` in the first case + // * The head of the SmallDeque is moved before calling `drop_in_place`, + // so no value is dropped twice if `drop_in_place` panics + unsafe { + if len >= self.len { + return; + } + + let (front, back) = self.as_mut_slices(); + if len > front.len() { + let begin = len - front.len(); + let drop_back = back.get_unchecked_mut(begin..) as *mut _; + self.len = len; + ptr::drop_in_place(drop_back); + } else { + let drop_back = back as *mut _; + let drop_front = front.get_unchecked_mut(len..) as *mut _; + self.len = len; + + // Make sure the second half is dropped even when a destructor + // in the first one panics. + let _back_dropper = Dropper(&mut *drop_back); + ptr::drop_in_place(drop_front); + } + } + } + + /// Removes the specified range from the deque in bulk, returning all + /// removed elements as an iterator. If the iterator is dropped before + /// being fully consumed, it drops the remaining removed elements. + /// + /// The returned iterator keeps a mutable borrow on the queue to optimize + /// its implementation. + /// + /// + /// # Panics + /// + /// Panics if the starting point is greater than the end point or if + /// the end point is greater than the length of the deque. + /// + /// # Leaking + /// + /// If the returned iterator goes out of scope without being dropped (due to + /// [`mem::forget`], for example), the deque may have lost and leaked + /// elements arbitrarily, including elements outside the range. + #[inline] + pub fn drain(&mut self, range: R) -> Drain<'_, T, N> + where + R: RangeBounds, + { + // Memory safety + // + // When the Drain is first created, the source deque is shortened to + // make sure no uninitialized or moved-from elements are accessible at + // all if the Drain's destructor never gets to run. + // + // Drain will ptr::read out the values to remove. + // When finished, the remaining data will be copied back to cover the hole, + // and the head/tail values will be restored correctly. + // + let Range { start, end } = core::slice::range(range, ..self.len); + let drain_start = start; + let drain_len = end - start; + + // The deque's elements are parted into three segments: + // * 0 -> drain_start + // * drain_start -> drain_start+drain_len + // * drain_start+drain_len -> self.len + // + // H = self.head; T = self.head+self.len; t = drain_start+drain_len; h = drain_head + // + // We store drain_start as self.len, and drain_len and self.len as + // drain_len and orig_len respectively on the Drain. This also + // truncates the effective array such that if the Drain is leaked, we + // have forgotten about the potentially moved values after the start of + // the drain. + // + // H h t T + // [. . . o o x x o o . . .] + // + // "forget" about the values after the start of the drain until after + // the drain is complete and the Drain destructor is run. + + unsafe { Drain::new(self, drain_start, drain_len) } + } + + /// Clears the deque, removing all values. + #[inline] + pub fn clear(&mut self) { + self.truncate(0); + // Not strictly necessary, but leaves things in a more consistent/predictable state. + self.head = 0; + } + + /// Returns `true` if the deque contains an element equal to the + /// given value. + /// + /// This operation is *O*(*n*). + /// + /// Note that if you have a sorted `SmallDeque`, [`binary_search`] may be faster. + /// + /// [`binary_search`]: SmallDeque::binary_search + pub fn contains(&self, x: &T) -> bool + where + T: PartialEq, + { + let (a, b) = self.as_slices(); + a.contains(x) || b.contains(x) + } + + /// Provides a reference to the front element, or `None` if the deque is + /// empty. + pub fn front(&self) -> Option<&T> { + self.get(0) + } + + /// Provides a mutable reference to the front element, or `None` if the + /// deque is empty. + pub fn front_mut(&mut self) -> Option<&mut T> { + self.get_mut(0) + } + + /// Provides a reference to the back element, or `None` if the deque is + /// empty. + pub fn back(&self) -> Option<&T> { + self.get(self.len.wrapping_sub(1)) + } + + /// Provides a mutable reference to the back element, or `None` if the + /// deque is empty. + pub fn back_mut(&mut self) -> Option<&mut T> { + self.get_mut(self.len.wrapping_sub(1)) + } + + /// Removes the first element and returns it, or `None` if the deque is + /// empty. + pub fn pop_front(&mut self) -> Option { + if self.is_empty() { + None + } else { + let old_head = self.head; + self.head = self.to_physical_idx(1); + self.len -= 1; + unsafe { + core::hint::assert_unchecked(self.len < self.capacity()); + Some(self.buffer_read(old_head)) + } + } + } + + /// Removes the last element from the deque and returns it, or `None` if + /// it is empty. + pub fn pop_back(&mut self) -> Option { + if self.is_empty() { + None + } else { + self.len -= 1; + unsafe { + core::hint::assert_unchecked(self.len < self.capacity()); + Some(self.buffer_read(self.to_physical_idx(self.len))) + } + } + } + + /// Prepends an element to the deque. + pub fn push_front(&mut self, value: T) { + if self.is_full() { + self.grow(); + } + + self.head = self.wrap_sub(self.head, 1); + self.len += 1; + + unsafe { + self.buffer_write(self.head, value); + } + } + + /// Appends an element to the back of the deque. + pub fn push_back(&mut self, value: T) { + if self.is_full() { + self.grow(); + } + + unsafe { self.buffer_write(self.to_physical_idx(self.len), value) } + self.len += 1; + } + + #[inline] + fn is_contiguous(&self) -> bool { + // Do the calculation like this to avoid overflowing if len + head > usize::MAX + self.head <= self.capacity() - self.len + } + + /// Removes an element from anywhere in the deque and returns it, + /// replacing it with the first element. + /// + /// This does not preserve ordering, but is *O*(1). + /// + /// Returns `None` if `index` is out of bounds. + /// + /// Element at index 0 is the front of the queue. + pub fn swap_remove_front(&mut self, index: usize) -> Option { + let length = self.len; + if index < length && index != 0 { + self.swap(index, 0); + } else if index >= length { + return None; + } + self.pop_front() + } + + /// Removes an element from anywhere in the deque and returns it, + /// replacing it with the last element. + /// + /// This does not preserve ordering, but is *O*(1). + /// + /// Returns `None` if `index` is out of bounds. + /// + /// Element at index 0 is the front of the queue. + pub fn swap_remove_back(&mut self, index: usize) -> Option { + let length = self.len; + if length > 0 && index < length - 1 { + self.swap(index, length - 1); + } else if index >= length { + return None; + } + self.pop_back() + } + + /// Inserts an element at `index` within the deque, shifting all elements + /// with indices greater than or equal to `index` towards the back. + /// + /// Element at index 0 is the front of the queue. + /// + /// # Panics + /// + /// Panics if `index` is greater than deque's length + pub fn insert(&mut self, index: usize, value: T) { + assert!(index <= self.len(), "index out of bounds"); + if self.is_full() { + self.grow(); + } + + let k = self.len - index; + if k < index { + // `index + 1` can't overflow, because if index was usize::MAX, then either the + // assert would've failed, or the deque would've tried to grow past usize::MAX + // and panicked. + unsafe { + // see `remove()` for explanation why this wrap_copy() call is safe. + self.wrap_copy(self.to_physical_idx(index), self.to_physical_idx(index + 1), k); + self.buffer_write(self.to_physical_idx(index), value); + self.len += 1; + } + } else { + let old_head = self.head; + self.head = self.wrap_sub(self.head, 1); + unsafe { + self.wrap_copy(old_head, self.head, index); + self.buffer_write(self.to_physical_idx(index), value); + self.len += 1; + } + } + } + + /// Removes and returns the element at `index` from the deque. + /// Whichever end is closer to the removal point will be moved to make + /// room, and all the affected elements will be moved to new positions. + /// Returns `None` if `index` is out of bounds. + /// + /// Element at index 0 is the front of the queue. + pub fn remove(&mut self, index: usize) -> Option { + if self.len <= index { + return None; + } + + let wrapped_idx = self.to_physical_idx(index); + + let elem = unsafe { Some(self.buffer_read(wrapped_idx)) }; + + let k = self.len - index - 1; + // safety: due to the nature of the if-condition, whichever wrap_copy gets called, + // its length argument will be at most `self.len / 2`, so there can't be more than + // one overlapping area. + if k < index { + unsafe { self.wrap_copy(self.wrap_add(wrapped_idx, 1), wrapped_idx, k) }; + self.len -= 1; + } else { + let old_head = self.head; + self.head = self.to_physical_idx(1); + unsafe { self.wrap_copy(old_head, self.head, index) }; + self.len -= 1; + } + + elem + } + + /// Splits the deque into two at the given index. + /// + /// Returns a newly allocated `SmallDeque`. `self` contains elements `[0, at)`, + /// and the returned deque contains elements `[at, len)`. + /// + /// Note that the capacity of `self` does not change. + /// + /// Element at index 0 is the front of the queue. + /// + /// # Panics + /// + /// Panics if `at > len`. + #[inline] + #[must_use = "use `.truncate()` if you don't need the other half"] + pub fn split_off(&mut self, at: usize) -> Self { + let len = self.len; + assert!(at <= len, "`at` out of bounds"); + + let other_len = len - at; + let mut other = Self::with_capacity(other_len); + + unsafe { + let (first_half, second_half) = self.as_slices(); + + let first_len = first_half.len(); + let second_len = second_half.len(); + if at < first_len { + // `at` lies in the first half. + let amount_in_first = first_len - at; + + ptr::copy_nonoverlapping( + first_half.as_ptr().add(at), + other.ptr_mut(), + amount_in_first, + ); + + // just take all of the second half. + ptr::copy_nonoverlapping( + second_half.as_ptr(), + other.ptr_mut().add(amount_in_first), + second_len, + ); + } else { + // `at` lies in the second half, need to factor in the elements we skipped + // in the first half. + let offset = at - first_len; + let amount_in_second = second_len - offset; + ptr::copy_nonoverlapping( + second_half.as_ptr().add(offset), + other.ptr_mut(), + amount_in_second, + ); + } + } + + // Cleanup where the ends of the buffers are + self.len = at; + other.len = other_len; + + other + } + + /// Moves all the elements of `other` into `self`, leaving `other` empty. + /// + /// # Panics + /// + /// Panics if the new number of elements in self overflows a `usize`. + #[inline] + pub fn append(&mut self, other: &mut Self) { + if T::IS_ZST { + self.len = self.len.checked_add(other.len).expect("capacity overflow"); + other.len = 0; + other.head = 0; + return; + } + + self.reserve(other.len); + unsafe { + let (left, right) = other.as_slices(); + self.copy_slice(self.to_physical_idx(self.len), left); + // no overflow, because self.capacity() >= old_cap + left.len() >= self.len + left.len() + self.copy_slice(self.to_physical_idx(self.len + left.len()), right); + } + // SAFETY: Update pointers after copying to avoid leaving doppelganger + // in case of panics. + self.len += other.len; + // Now that we own its values, forget everything in `other`. + other.len = 0; + other.head = 0; + } + + /// Retains only the elements specified by the predicate. + /// + /// In other words, remove all elements `e` for which `f(&e)` returns false. + /// This method operates in place, visiting each element exactly once in the + /// original order, and preserves the order of the retained elements. + pub fn retain(&mut self, mut f: F) + where + F: FnMut(&T) -> bool, + { + self.retain_mut(|elem| f(elem)); + } + + /// Retains only the elements specified by the predicate. + /// + /// In other words, remove all elements `e` for which `f(&e)` returns false. + /// This method operates in place, visiting each element exactly once in the + /// original order, and preserves the order of the retained elements. + pub fn retain_mut(&mut self, mut f: F) + where + F: FnMut(&mut T) -> bool, + { + let len = self.len; + let mut idx = 0; + let mut cur = 0; + + // Stage 1: All values are retained. + while cur < len { + if !f(&mut self[cur]) { + cur += 1; + break; + } + cur += 1; + idx += 1; + } + // Stage 2: Swap retained value into current idx. + while cur < len { + if !f(&mut self[cur]) { + cur += 1; + continue; + } + + self.swap(idx, cur); + cur += 1; + idx += 1; + } + // Stage 3: Truncate all values after idx. + if cur != idx { + self.truncate(idx); + } + } + + // Double the buffer size. This method is inline(never), so we expect it to only + // be called in cold paths. + // This may panic or abort + #[inline(never)] + fn grow(&mut self) { + // Extend or possibly remove this assertion when valid use-cases for growing the + // buffer without it being full emerge + debug_assert!(self.is_full()); + let old_cap = self.capacity(); + self.buf.grow(old_cap + 1); + unsafe { + self.handle_capacity_increase(old_cap); + } + debug_assert!(!self.is_full()); + } + + /// Modifies the deque in-place so that `len()` is equal to `new_len`, + /// either by removing excess elements from the back or by appending + /// elements generated by calling `generator` to the back. + pub fn resize_with(&mut self, new_len: usize, generator: impl FnMut() -> T) { + let len = self.len; + + if new_len > len { + self.extend(repeat_with(generator).take(new_len - len)) + } else { + self.truncate(new_len); + } + } + + /// Rearranges the internal storage of this deque so it is one contiguous + /// slice, which is then returned. + /// + /// This method does not allocate and does not change the order of the + /// inserted elements. As it returns a mutable slice, this can be used to + /// sort a deque. + /// + /// Once the internal storage is contiguous, the [`as_slices`] and + /// [`as_mut_slices`] methods will return the entire contents of the + /// deque in a single slice. + /// + /// [`as_slices`]: SmallDeque::as_slices + /// [`as_mut_slices`]: SmallDeque::as_mut_slices + pub fn make_contiguous(&mut self) -> &mut [T] { + if T::IS_ZST { + self.head = 0; + } + + if self.is_contiguous() { + unsafe { + return core::slice::from_raw_parts_mut(self.ptr_mut().add(self.head), self.len); + } + } + + let &mut Self { head, len, .. } = self; + let ptr = self.ptr_mut(); + let cap = self.capacity(); + + let free = cap - len; + let head_len = cap - head; + let tail = len - head_len; + let tail_len = tail; + + if free >= head_len { + // there is enough free space to copy the head in one go, + // this means that we first shift the tail backwards, and then + // copy the head to the correct position. + // + // from: DEFGH....ABC + // to: ABCDEFGH.... + unsafe { + self.copy(0, head_len, tail_len); + // ...DEFGH.ABC + self.copy_nonoverlapping(head, 0, head_len); + // ABCDEFGH.... + } + + self.head = 0; + } else if free >= tail_len { + // there is enough free space to copy the tail in one go, + // this means that we first shift the head forwards, and then + // copy the tail to the correct position. + // + // from: FGH....ABCDE + // to: ...ABCDEFGH. + unsafe { + self.copy(head, tail, head_len); + // FGHABCDE.... + self.copy_nonoverlapping(0, tail + head_len, tail_len); + // ...ABCDEFGH. + } + + self.head = tail; + } else { + // `free` is smaller than both `head_len` and `tail_len`. + // the general algorithm for this first moves the slices + // right next to each other and then uses `slice::rotate` + // to rotate them into place: + // + // initially: HIJK..ABCDEFG + // step 1: ..HIJKABCDEFG + // step 2: ..ABCDEFGHIJK + // + // or: + // + // initially: FGHIJK..ABCDE + // step 1: FGHIJKABCDE.. + // step 2: ABCDEFGHIJK.. + + // pick the shorter of the 2 slices to reduce the amount + // of memory that needs to be moved around. + if head_len > tail_len { + // tail is shorter, so: + // 1. copy tail forwards + // 2. rotate used part of the buffer + // 3. update head to point to the new beginning (which is just `free`) + + unsafe { + // if there is no free space in the buffer, then the slices are already + // right next to each other and we don't need to move any memory. + if free != 0 { + // because we only move the tail forward as much as there's free space + // behind it, we don't overwrite any elements of the head slice, and + // the slices end up right next to each other. + self.copy(0, free, tail_len); + } + + // We just copied the tail right next to the head slice, + // so all of the elements in the range are initialized + let slice = &mut *self.buffer_range_mut(free..self.capacity()); + + // because the deque wasn't contiguous, we know that `tail_len < self.len == slice.len()`, + // so this will never panic. + slice.rotate_left(tail_len); + + // the used part of the buffer now is `free..self.capacity()`, so set + // `head` to the beginning of that range. + self.head = free; + } + } else { + // head is shorter so: + // 1. copy head backwards + // 2. rotate used part of the buffer + // 3. update head to point to the new beginning (which is the beginning of the buffer) + + unsafe { + // if there is no free space in the buffer, then the slices are already + // right next to each other and we don't need to move any memory. + if free != 0 { + // copy the head slice to lie right behind the tail slice. + self.copy(self.head, tail_len, head_len); + } + + // because we copied the head slice so that both slices lie right + // next to each other, all the elements in the range are initialized. + let slice = &mut *self.buffer_range_mut(0..self.len); + + // because the deque wasn't contiguous, we know that `head_len < self.len == slice.len()` + // so this will never panic. + slice.rotate_right(head_len); + + // the used part of the buffer now is `0..self.len`, so set + // `head` to the beginning of that range. + self.head = 0; + } + } + } + + unsafe { core::slice::from_raw_parts_mut(ptr.add(self.head), self.len) } + } + + /// Rotates the double-ended queue `n` places to the left. + /// + /// Equivalently, + /// - Rotates item `n` into the first position. + /// - Pops the first `n` items and pushes them to the end. + /// - Rotates `len() - n` places to the right. + /// + /// # Panics + /// + /// If `n` is greater than `len()`. Note that `n == len()` + /// does _not_ panic and is a no-op rotation. + /// + /// # Complexity + /// + /// Takes `*O*(min(n, len() - n))` time and no extra space. + pub fn rotate_left(&mut self, n: usize) { + assert!(n <= self.len()); + let k = self.len - n; + if n <= k { + unsafe { self.rotate_left_inner(n) } + } else { + unsafe { self.rotate_right_inner(k) } + } + } + + /// Rotates the double-ended queue `n` places to the right. + /// + /// Equivalently, + /// - Rotates the first item into position `n`. + /// - Pops the last `n` items and pushes them to the front. + /// - Rotates `len() - n` places to the left. + /// + /// # Panics + /// + /// If `n` is greater than `len()`. Note that `n == len()` + /// does _not_ panic and is a no-op rotation. + /// + /// # Complexity + /// + /// Takes `*O*(min(n, len() - n))` time and no extra space. + pub fn rotate_right(&mut self, n: usize) { + assert!(n <= self.len()); + let k = self.len - n; + if n <= k { + unsafe { self.rotate_right_inner(n) } + } else { + unsafe { self.rotate_left_inner(k) } + } + } + + // SAFETY: the following two methods require that the rotation amount + // be less than half the length of the deque. + // + // `wrap_copy` requires that `min(x, capacity() - x) + copy_len <= capacity()`, + // but then `min` is never more than half the capacity, regardless of x, + // so it's sound to call here because we're calling with something + // less than half the length, which is never above half the capacity. + unsafe fn rotate_left_inner(&mut self, mid: usize) { + debug_assert!(mid * 2 <= self.len()); + unsafe { + self.wrap_copy(self.head, self.to_physical_idx(self.len), mid); + } + self.head = self.to_physical_idx(mid); + } + + unsafe fn rotate_right_inner(&mut self, k: usize) { + debug_assert!(k * 2 <= self.len()); + self.head = self.wrap_sub(self.head, k); + unsafe { + self.wrap_copy(self.to_physical_idx(self.len), self.head, k); + } + } + + /// Binary searches this `SmallDeque` for a given element. + /// If the `SmallDeque` is not sorted, the returned result is unspecified and + /// meaningless. + /// + /// If the value is found then [`Result::Ok`] is returned, containing the + /// index of the matching element. If there are multiple matches, then any + /// one of the matches could be returned. If the value is not found then + /// [`Result::Err`] is returned, containing the index where a matching + /// element could be inserted while maintaining sorted order. + /// + /// See also [`binary_search_by`], [`binary_search_by_key`], and [`partition_point`]. + /// + /// [`binary_search_by`]: SmallDeque::binary_search_by + /// [`binary_search_by_key`]: SmallDeque::binary_search_by_key + /// [`partition_point`]: SmallDeque::partition_point + /// + #[inline] + pub fn binary_search(&self, x: &T) -> Result + where + T: Ord, + { + self.binary_search_by(|e| e.cmp(x)) + } + + /// Binary searches this `SmallDeque` with a comparator function. + /// + /// The comparator function should return an order code that indicates + /// whether its argument is `Less`, `Equal` or `Greater` the desired + /// target. + /// If the `SmallDeque` is not sorted or if the comparator function does not + /// implement an order consistent with the sort order of the underlying + /// `SmallDeque`, the returned result is unspecified and meaningless. + /// + /// If the value is found then [`Result::Ok`] is returned, containing the + /// index of the matching element. If there are multiple matches, then any + /// one of the matches could be returned. If the value is not found then + /// [`Result::Err`] is returned, containing the index where a matching + /// element could be inserted while maintaining sorted order. + /// + /// See also [`binary_search`], [`binary_search_by_key`], and [`partition_point`]. + /// + /// [`binary_search`]: SmallDeque::binary_search + /// [`binary_search_by_key`]: SmallDeque::binary_search_by_key + /// [`partition_point`]: SmallDeque::partition_point + pub fn binary_search_by<'a, F>(&'a self, mut f: F) -> Result + where + F: FnMut(&'a T) -> Ordering, + { + let (front, back) = self.as_slices(); + // clippy doesn't recognize that `f` would be moved if we followed it's recommendation + #[allow(clippy::redundant_closure)] + let cmp_back = back.first().map(|e| f(e)); + + if let Some(Ordering::Equal) = cmp_back { + Ok(front.len()) + } else if let Some(Ordering::Less) = cmp_back { + back.binary_search_by(f) + .map(|idx| idx + front.len()) + .map_err(|idx| idx + front.len()) + } else { + front.binary_search_by(f) + } + } + + /// Binary searches this `SmallDeque` with a key extraction function. + /// + /// Assumes that the deque is sorted by the key, for instance with + /// [`make_contiguous().sort_by_key()`] using the same key extraction function. + /// If the deque is not sorted by the key, the returned result is + /// unspecified and meaningless. + /// + /// If the value is found then [`Result::Ok`] is returned, containing the + /// index of the matching element. If there are multiple matches, then any + /// one of the matches could be returned. If the value is not found then + /// [`Result::Err`] is returned, containing the index where a matching + /// element could be inserted while maintaining sorted order. + /// + /// See also [`binary_search`], [`binary_search_by`], and [`partition_point`]. + /// + /// [`make_contiguous().sort_by_key()`]: SmallDeque::make_contiguous + /// [`binary_search`]: SmallDeque::binary_search + /// [`binary_search_by`]: SmallDeque::binary_search_by + /// [`partition_point`]: SmallDeque::partition_point + #[inline] + pub fn binary_search_by_key<'a, B, F>(&'a self, b: &B, mut f: F) -> Result + where + F: FnMut(&'a T) -> B, + B: Ord, + { + self.binary_search_by(|k| f(k).cmp(b)) + } + + /// Returns the index of the partition point according to the given predicate + /// (the index of the first element of the second partition). + /// + /// The deque is assumed to be partitioned according to the given predicate. + /// This means that all elements for which the predicate returns true are at the start of the deque + /// and all elements for which the predicate returns false are at the end. + /// For example, `[7, 15, 3, 5, 4, 12, 6]` is partitioned under the predicate `x % 2 != 0` + /// (all odd numbers are at the start, all even at the end). + /// + /// If the deque is not partitioned, the returned result is unspecified and meaningless, + /// as this method performs a kind of binary search. + /// + /// See also [`binary_search`], [`binary_search_by`], and [`binary_search_by_key`]. + /// + /// [`binary_search`]: SmallDeque::binary_search + /// [`binary_search_by`]: SmallDeque::binary_search_by + /// [`binary_search_by_key`]: SmallDeque::binary_search_by_key + pub fn partition_point

(&self, mut pred: P) -> usize + where + P: FnMut(&T) -> bool, + { + let (front, back) = self.as_slices(); + + #[allow(clippy::redundant_closure)] + if let Some(true) = back.first().map(|v| pred(v)) { + back.partition_point(pred) + front.len() + } else { + front.partition_point(pred) + } + } +} + +impl SmallDeque { + /// Modifies the deque in-place so that `len()` is equal to new_len, + /// either by removing excess elements from the back or by appending clones of `value` + /// to the back. + pub fn resize(&mut self, new_len: usize, value: T) { + if new_len > self.len() { + let extra = new_len - self.len(); + self.extend(repeat_n(value, extra)) + } else { + self.truncate(new_len); + } + } +} + +impl SmallDeque { + #[inline] + fn ptr(&self) -> *const T { + self.buf.as_ptr() + } + + #[inline] + fn ptr_mut(&mut self) -> *mut T { + self.buf.as_mut_ptr() + } + + /// Appends an element to the buffer. + /// + /// # Safety + /// + /// May only be called if `deque.len() < deque.capacity()` + #[inline] + unsafe fn push_unchecked(&mut self, element: T) { + // SAFETY: Because of the precondition, it's guaranteed that there is space in the logical + // array after the last element. + unsafe { self.buffer_write(self.to_physical_idx(self.len), element) }; + // This can't overflow because `deque.len() < deque.capacity() <= usize::MAX` + self.len += 1; + } + + /// Moves an element out of the buffer + #[inline] + unsafe fn buffer_read(&mut self, offset: usize) -> T { + unsafe { ptr::read(self.ptr().add(offset)) } + } + + /// Writes an element into the buffer, moving it. + #[inline] + unsafe fn buffer_write(&mut self, offset: usize, value: T) { + unsafe { + ptr::write(self.ptr_mut().add(offset), value); + } + } + + /// Returns a slice pointer into the buffer. + /// `range` must lie inside `0..self.capacity()`. + #[inline] + unsafe fn buffer_range(&self, range: core::ops::Range) -> *const [T] { + unsafe { ptr::slice_from_raw_parts(self.ptr().add(range.start), range.end - range.start) } + } + + /// Returns a slice pointer into the buffer. + /// `range` must lie inside `0..self.capacity()`. + #[inline] + unsafe fn buffer_range_mut(&mut self, range: core::ops::Range) -> *mut [T] { + unsafe { + ptr::slice_from_raw_parts_mut(self.ptr_mut().add(range.start), range.end - range.start) + } + } + + /// Returns `true` if the buffer is at full capacity. + #[inline] + fn is_full(&self) -> bool { + self.len == self.capacity() + } + + /// Returns the index in the underlying buffer for a given logical element index + addend. + #[inline] + fn wrap_add(&self, idx: usize, addend: usize) -> usize { + wrap_index(idx.wrapping_add(addend), self.capacity()) + } + + #[inline] + fn to_physical_idx(&self, idx: usize) -> usize { + self.wrap_add(self.head, idx) + } + + /// Returns the index in the underlying buffer for a given logical element index - subtrahend. + #[inline] + fn wrap_sub(&self, idx: usize, subtrahend: usize) -> usize { + wrap_index(idx.wrapping_sub(subtrahend).wrapping_add(self.capacity()), self.capacity()) + } + + /// Copies a contiguous block of memory len long from src to dst + #[inline] + unsafe fn copy(&mut self, src: usize, dst: usize, len: usize) { + debug_assert!( + dst + len <= self.capacity(), + "cpy dst={} src={} len={} cap={}", + dst, + src, + len, + self.capacity() + ); + debug_assert!( + src + len <= self.capacity(), + "cpy dst={} src={} len={} cap={}", + dst, + src, + len, + self.capacity() + ); + unsafe { + ptr::copy(self.ptr().add(src), self.ptr_mut().add(dst), len); + } + } + + /// Copies a contiguous block of memory len long from src to dst + #[inline] + unsafe fn copy_nonoverlapping(&mut self, src: usize, dst: usize, len: usize) { + debug_assert!( + dst + len <= self.capacity(), + "cno dst={} src={} len={} cap={}", + dst, + src, + len, + self.capacity() + ); + debug_assert!( + src + len <= self.capacity(), + "cno dst={} src={} len={} cap={}", + dst, + src, + len, + self.capacity() + ); + unsafe { + ptr::copy_nonoverlapping(self.ptr().add(src), self.ptr_mut().add(dst), len); + } + } + + /// Copies a potentially wrapping block of memory len long from src to dest. + /// (abs(dst - src) + len) must be no larger than capacity() (There must be at + /// most one continuous overlapping region between src and dest). + unsafe fn wrap_copy(&mut self, src: usize, dst: usize, len: usize) { + debug_assert!( + core::cmp::min(src.abs_diff(dst), self.capacity() - src.abs_diff(dst)) + len + <= self.capacity(), + "wrc dst={} src={} len={} cap={}", + dst, + src, + len, + self.capacity() + ); + + // If T is a ZST, don't do any copying. + if T::IS_ZST || src == dst || len == 0 { + return; + } + + let dst_after_src = self.wrap_sub(dst, src) < len; + + let src_pre_wrap_len = self.capacity() - src; + let dst_pre_wrap_len = self.capacity() - dst; + let src_wraps = src_pre_wrap_len < len; + let dst_wraps = dst_pre_wrap_len < len; + + match (dst_after_src, src_wraps, dst_wraps) { + (_, false, false) => { + // src doesn't wrap, dst doesn't wrap + // + // S . . . + // 1 [_ _ A A B B C C _] + // 2 [_ _ A A A A B B _] + // D . . . + // + unsafe { + self.copy(src, dst, len); + } + } + (false, false, true) => { + // dst before src, src doesn't wrap, dst wraps + // + // S . . . + // 1 [A A B B _ _ _ C C] + // 2 [A A B B _ _ _ A A] + // 3 [B B B B _ _ _ A A] + // . . D . + // + unsafe { + self.copy(src, dst, dst_pre_wrap_len); + self.copy(src + dst_pre_wrap_len, 0, len - dst_pre_wrap_len); + } + } + (true, false, true) => { + // src before dst, src doesn't wrap, dst wraps + // + // S . . . + // 1 [C C _ _ _ A A B B] + // 2 [B B _ _ _ A A B B] + // 3 [B B _ _ _ A A A A] + // . . D . + // + unsafe { + self.copy(src + dst_pre_wrap_len, 0, len - dst_pre_wrap_len); + self.copy(src, dst, dst_pre_wrap_len); + } + } + (false, true, false) => { + // dst before src, src wraps, dst doesn't wrap + // + // . . S . + // 1 [C C _ _ _ A A B B] + // 2 [C C _ _ _ B B B B] + // 3 [C C _ _ _ B B C C] + // D . . . + // + unsafe { + self.copy(src, dst, src_pre_wrap_len); + self.copy(0, dst + src_pre_wrap_len, len - src_pre_wrap_len); + } + } + (true, true, false) => { + // src before dst, src wraps, dst doesn't wrap + // + // . . S . + // 1 [A A B B _ _ _ C C] + // 2 [A A A A _ _ _ C C] + // 3 [C C A A _ _ _ C C] + // D . . . + // + unsafe { + self.copy(0, dst + src_pre_wrap_len, len - src_pre_wrap_len); + self.copy(src, dst, src_pre_wrap_len); + } + } + (false, true, true) => { + // dst before src, src wraps, dst wraps + // + // . . . S . + // 1 [A B C D _ E F G H] + // 2 [A B C D _ E G H H] + // 3 [A B C D _ E G H A] + // 4 [B C C D _ E G H A] + // . . D . . + // + debug_assert!(dst_pre_wrap_len > src_pre_wrap_len); + let delta = dst_pre_wrap_len - src_pre_wrap_len; + unsafe { + self.copy(src, dst, src_pre_wrap_len); + self.copy(0, dst + src_pre_wrap_len, delta); + self.copy(delta, 0, len - dst_pre_wrap_len); + } + } + (true, true, true) => { + // src before dst, src wraps, dst wraps + // + // . . S . . + // 1 [A B C D _ E F G H] + // 2 [A A B D _ E F G H] + // 3 [H A B D _ E F G H] + // 4 [H A B D _ E F F G] + // . . . D . + // + debug_assert!(src_pre_wrap_len > dst_pre_wrap_len); + let delta = src_pre_wrap_len - dst_pre_wrap_len; + unsafe { + self.copy(0, delta, len - src_pre_wrap_len); + self.copy(self.capacity() - delta, 0, delta); + self.copy(src, dst, dst_pre_wrap_len); + } + } + } + } + + /// Copies all values from `src` to `dst`, wrapping around if needed. + /// Assumes capacity is sufficient. + #[inline] + unsafe fn copy_slice(&mut self, dst: usize, src: &[T]) { + debug_assert!(src.len() <= self.capacity()); + let head_room = self.capacity() - dst; + if src.len() <= head_room { + unsafe { + ptr::copy_nonoverlapping(src.as_ptr(), self.ptr_mut().add(dst), src.len()); + } + } else { + let (left, right) = src.split_at(head_room); + unsafe { + ptr::copy_nonoverlapping(left.as_ptr(), self.ptr_mut().add(dst), left.len()); + ptr::copy_nonoverlapping(right.as_ptr(), self.ptr_mut(), right.len()); + } + } + } + + /// Writes all values from `iter` to `dst`. + /// + /// # Safety + /// + /// Assumes no wrapping around happens. + /// Assumes capacity is sufficient. + #[inline] + unsafe fn write_iter( + &mut self, + dst: usize, + iter: impl Iterator, + written: &mut usize, + ) { + iter.enumerate().for_each(|(i, element)| unsafe { + self.buffer_write(dst + i, element); + *written += 1; + }); + } + + /// Writes all values from `iter` to `dst`, wrapping + /// at the end of the buffer and returns the number + /// of written values. + /// + /// # Safety + /// + /// Assumes that `iter` yields at most `len` items. + /// Assumes capacity is sufficient. + unsafe fn write_iter_wrapping( + &mut self, + dst: usize, + mut iter: impl Iterator, + len: usize, + ) -> usize { + struct Guard<'a, T, const N: usize> { + deque: &'a mut SmallDeque, + written: usize, + } + + impl Drop for Guard<'_, T, N> { + fn drop(&mut self) { + self.deque.len += self.written; + } + } + + let head_room = self.capacity() - dst; + + let mut guard = Guard { + deque: self, + written: 0, + }; + + if head_room >= len { + unsafe { guard.deque.write_iter(dst, iter, &mut guard.written) }; + } else { + unsafe { + guard.deque.write_iter( + dst, + ByRefSized(&mut iter).take(head_room), + &mut guard.written, + ); + guard.deque.write_iter(0, iter, &mut guard.written) + }; + } + + guard.written + } + + /// Frobs the head and tail sections around to handle the fact that we + /// just reallocated. Unsafe because it trusts old_capacity. + #[inline] + unsafe fn handle_capacity_increase(&mut self, old_capacity: usize) { + let new_capacity = self.capacity(); + debug_assert!(new_capacity >= old_capacity); + + // Move the shortest contiguous section of the ring buffer + // + // H := head + // L := last element (`self.to_physical_idx(self.len - 1)`) + // + // H L + // [o o o o o o o o ] + // H L + // A [o o o o o o o o . . . . . . . . ] + // L H + // [o o o o o o o o ] + // H L + // B [. . . o o o o o o o o . . . . . ] + // L H + // [o o o o o o o o ] + // L H + // C [o o o o o o . . . . . . . . o o ] + + // can't use is_contiguous() because the capacity is already updated. + if self.head <= old_capacity - self.len { + // A + // Nop + } else { + let head_len = old_capacity - self.head; + let tail_len = self.len - head_len; + if head_len > tail_len && new_capacity - old_capacity >= tail_len { + // B + unsafe { + self.copy_nonoverlapping(0, old_capacity, tail_len); + } + } else { + // C + let new_head = new_capacity - head_len; + unsafe { + // can't use copy_nonoverlapping here, because if e.g. head_len = 2 + // and new_capacity = old_capacity + 1, then the heads overlap. + self.copy(self.head, new_head, head_len); + } + self.head = new_head; + } + } + debug_assert!(self.head < self.capacity() || self.capacity() == 0); + } +} + +/// Returns the index in the underlying buffer for a given logical element index. +#[inline] +fn wrap_index(logical_index: usize, capacity: usize) -> usize { + debug_assert!( + (logical_index == 0 && capacity == 0) + || logical_index < capacity + || (logical_index - capacity) < capacity + ); + if logical_index >= capacity { + logical_index - capacity + } else { + logical_index + } +} + +impl PartialEq for SmallDeque { + fn eq(&self, other: &Self) -> bool { + if self.len != other.len() { + return false; + } + let (sa, sb) = self.as_slices(); + let (oa, ob) = other.as_slices(); + match sa.len().cmp(&oa.len()) { + Ordering::Equal => sa == oa && sb == ob, + Ordering::Less => { + // Always divisible in three sections, for example: + // self: [a b c|d e f] + // other: [0 1 2 3|4 5] + // front = 3, mid = 1, + // [a b c] == [0 1 2] && [d] == [3] && [e f] == [4 5] + let front = sa.len(); + let mid = oa.len() - front; + + let (oa_front, oa_mid) = oa.split_at(front); + let (sb_mid, sb_back) = sb.split_at(mid); + debug_assert_eq!(sa.len(), oa_front.len()); + debug_assert_eq!(sb_mid.len(), oa_mid.len()); + debug_assert_eq!(sb_back.len(), ob.len()); + sa == oa_front && sb_mid == oa_mid && sb_back == ob + } + Ordering::Greater => { + let front = oa.len(); + let mid = sa.len() - front; + + let (sa_front, sa_mid) = sa.split_at(front); + let (ob_mid, ob_back) = ob.split_at(mid); + debug_assert_eq!(sa_front.len(), oa.len()); + debug_assert_eq!(sa_mid.len(), ob_mid.len()); + debug_assert_eq!(sb.len(), ob_back.len()); + sa_front == oa && sa_mid == ob_mid && sb == ob_back + } + } + } +} + +impl Eq for SmallDeque {} + +macro_rules! __impl_slice_eq1 { + ([$($vars:tt)*] $lhs:ty, $rhs:ty, $($constraints:tt)*) => { + impl PartialEq<$rhs> for $lhs + where + T: PartialEq, + $($constraints)* + { + fn eq(&self, other: &$rhs) -> bool { + if self.len() != other.len() { + return false; + } + let (sa, sb) = self.as_slices(); + let (oa, ob) = other[..].split_at(sa.len()); + sa == oa && sb == ob + } + } + } +} + +__impl_slice_eq1! { [A: alloc::alloc::Allocator, const N: usize] SmallDeque, Vec, } +__impl_slice_eq1! { [const N: usize] SmallDeque, SmallVec<[U; N]>, } +__impl_slice_eq1! { [const N: usize] SmallDeque, &[U], } +__impl_slice_eq1! { [const N: usize] SmallDeque, &mut [U], } +__impl_slice_eq1! { [const N: usize, const M: usize] SmallDeque, [U; M], } +__impl_slice_eq1! { [const N: usize, const M: usize] SmallDeque, &[U; M], } +__impl_slice_eq1! { [const N: usize, const M: usize] SmallDeque, &mut [U; M], } + +impl PartialOrd for SmallDeque { + fn partial_cmp(&self, other: &Self) -> Option { + self.iter().partial_cmp(other.iter()) + } +} + +impl Ord for SmallDeque { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + self.iter().cmp(other.iter()) + } +} + +impl core::hash::Hash for SmallDeque { + fn hash(&self, state: &mut H) { + state.write_length_prefix(self.len); + // It's not possible to use Hash::hash_slice on slices + // returned by as_slices method as their length can vary + // in otherwise identical deques. + // + // Hasher only guarantees equivalence for the exact same + // set of calls to its methods. + self.iter().for_each(|elem| elem.hash(state)); + } +} + +impl Index for SmallDeque { + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &T { + self.get(index).expect("Out of bounds access") + } +} + +impl IndexMut for SmallDeque { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut T { + self.get_mut(index).expect("Out of bounds access") + } +} + +impl FromIterator for SmallDeque { + fn from_iter>(iter: I) -> Self { + SpecFromIter::spec_from_iter(iter.into_iter()) + } +} + +impl IntoIterator for SmallDeque { + type IntoIter = IntoIter; + type Item = T; + + /// Consumes the deque into a front-to-back iterator yielding elements by + /// value. + fn into_iter(self) -> IntoIter { + IntoIter::new(self) + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a SmallDeque { + type IntoIter = Iter<'a, T>; + type Item = &'a T; + + fn into_iter(self) -> Iter<'a, T> { + self.iter() + } +} + +impl<'a, T, const N: usize> IntoIterator for &'a mut SmallDeque { + type IntoIter = IterMut<'a, T>; + type Item = &'a mut T; + + fn into_iter(self) -> IterMut<'a, T> { + self.iter_mut() + } +} + +impl Extend for SmallDeque { + fn extend>(&mut self, iter: I) { + >::spec_extend(self, iter.into_iter()); + } + + #[inline] + fn extend_one(&mut self, elem: T) { + self.push_back(elem); + } + + #[inline] + fn extend_reserve(&mut self, additional: usize) { + self.reserve(additional); + } + + #[inline] + unsafe fn extend_one_unchecked(&mut self, item: T) { + // SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly. + unsafe { + self.push_unchecked(item); + } + } +} + +impl<'a, T: 'a + Copy, const N: usize> Extend<&'a T> for SmallDeque { + fn extend>(&mut self, iter: I) { + self.spec_extend(iter.into_iter()); + } + + #[inline] + fn extend_one(&mut self, &elem: &'a T) { + self.push_back(elem); + } + + #[inline] + fn extend_reserve(&mut self, additional: usize) { + self.reserve(additional); + } + + #[inline] + unsafe fn extend_one_unchecked(&mut self, &item: &'a T) { + // SAFETY: Our preconditions ensure the space has been reserved, and `extend_reserve` is implemented correctly. + unsafe { + self.push_unchecked(item); + } + } +} + +impl fmt::Debug for SmallDeque { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} + +impl From> for SmallDeque { + /// Turn a [`SmallVec<[T; N]>`] into a [`SmallDeque`]. + /// + /// [`SmallVec<[T; N]>`]: smallvec::SmallVec + /// [`SmallDeque`]: crate::adt::SmallDeque + /// + /// This conversion is guaranteed to run in *O*(1) time + /// and to not re-allocate the `Vec`'s buffer or allocate + /// any additional memory. + #[inline] + fn from(buf: SmallVec<[T; N]>) -> Self { + let len = buf.len(); + Self { head: 0, len, buf } + } +} + +impl From> for SmallVec<[T; N]> { + /// Turn a [`SmallDeque`] into a [`SmallVec<[T; N]>`]. + /// + /// [`SmallVec<[T; N]>`]: smallvec::SmallVec + /// [`SmallDeque`]: crate::adt::SmallDeque + /// + /// This never needs to re-allocate, but does need to do *O*(*n*) data movement if + /// the circular buffer doesn't happen to be at the beginning of the allocation. + fn from(mut other: SmallDeque) -> Self { + use core::mem::ManuallyDrop; + + other.make_contiguous(); + + unsafe { + if other.buf.spilled() { + let mut other = ManuallyDrop::new(other); + let buf = other.buf.as_mut_ptr(); + let len = other.len(); + let cap = other.capacity(); + + if other.head != 0 { + ptr::copy(buf.add(other.head), buf, len); + } + SmallVec::from_raw_parts(buf, len, cap) + } else { + // `other` is entirely stack-allocated, so we need to produce a new copy that + // has all of the elements starting at index 0, if not already + if other.head == 0 { + // Steal the underlying vec, and make sure that the length is set + let mut buf = other.buf; + buf.set_len(other.len); + buf + } else { + let mut other = ManuallyDrop::new(other); + let ptr = other.buf.as_mut_ptr(); + let len = other.len(); + + // Construct an uninitialized array on the stack of the same size as the target + // SmallVec's inline size, "move" `len` items into it, and the construct the + // SmallVec from the raw buffer and len + let mut buf = [const { core::mem::MaybeUninit::::uninit() }; N]; + let buf_ptr = core::mem::MaybeUninit::slice_as_mut_ptr(&mut buf); + ptr::copy(ptr.add(other.head), buf_ptr, len); + // While we are technically potentially letting a subset of elements in the + // array that never got uninitialized, be assumed to have been initialized + // here - that fact is never material: no references are ever created to those + // items, and the array is never dropped, as it is immediately placed in a + // ManuallyDrop, and the vector length is set to `len` before any access can + // be made to the vector + SmallVec::from_buf_and_len(core::mem::MaybeUninit::array_assume_init(buf), len) + } + } + } + } +} + +impl From<[T; N]> for SmallDeque { + /// Converts a `[T; N]` into a `SmallDeque`. + fn from(arr: [T; N]) -> Self { + use core::mem::ManuallyDrop; + + let mut deq = SmallDeque::<_, N>::with_capacity(N); + let arr = ManuallyDrop::new(arr); + if !::IS_ZST { + // SAFETY: SmallDeque::with_capacity ensures that there is enough capacity. + unsafe { + ptr::copy_nonoverlapping(arr.as_ptr(), deq.ptr_mut(), N); + } + } + deq.head = 0; + deq.len = N; + deq + } +} + +/// An iterator over the elements of a `SmallDeque`. +/// +/// This `struct` is created by the [`iter`] method on [`SmallDeque`]. See its +/// documentation for more. +/// +/// [`iter`]: SmallDeque::iter +#[derive(Clone)] +pub struct Iter<'a, T: 'a> { + i1: core::slice::Iter<'a, T>, + i2: core::slice::Iter<'a, T>, +} + +impl<'a, T> Iter<'a, T> { + pub(super) fn new(i1: core::slice::Iter<'a, T>, i2: core::slice::Iter<'a, T>) -> Self { + Self { i1, i2 } + } +} + +impl fmt::Debug for Iter<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Iter") + .field(&self.i1.as_slice()) + .field(&self.i2.as_slice()) + .finish() + } +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + #[inline] + fn next(&mut self) -> Option<&'a T> { + match self.i1.next() { + Some(val) => Some(val), + None => { + // most of the time, the iterator will either always + // call next(), or always call next_back(). By swapping + // the iterators once the first one is empty, we ensure + // that the first branch is taken as often as possible, + // without sacrificing correctness, as i1 is empty anyways + core::mem::swap(&mut self.i1, &mut self.i2); + self.i1.next() + } + } + } + + fn advance_by(&mut self, n: usize) -> Result<(), core::num::NonZero> { + let remaining = self.i1.advance_by(n); + match remaining { + Ok(()) => Ok(()), + Err(n) => { + core::mem::swap(&mut self.i1, &mut self.i2); + self.i1.advance_by(n.get()) + } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } + + fn fold(self, accum: Acc, mut f: F) -> Acc + where + F: FnMut(Acc, Self::Item) -> Acc, + { + let accum = self.i1.fold(accum, &mut f); + self.i2.fold(accum, &mut f) + } + + fn try_fold(&mut self, init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: core::ops::Try, + { + let acc = self.i1.try_fold(init, &mut f)?; + self.i2.try_fold(acc, &mut f) + } + + #[inline] + fn last(mut self) -> Option<&'a T> { + self.next_back() + } +} + +impl<'a, T> DoubleEndedIterator for Iter<'a, T> { + #[inline] + fn next_back(&mut self) -> Option<&'a T> { + match self.i2.next_back() { + Some(val) => Some(val), + None => { + // most of the time, the iterator will either always + // call next(), or always call next_back(). By swapping + // the iterators once the second one is empty, we ensure + // that the first branch is taken as often as possible, + // without sacrificing correctness, as i2 is empty anyways + core::mem::swap(&mut self.i1, &mut self.i2); + self.i2.next_back() + } + } + } + + fn advance_back_by(&mut self, n: usize) -> Result<(), core::num::NonZero> { + match self.i2.advance_back_by(n) { + Ok(()) => Ok(()), + Err(n) => { + core::mem::swap(&mut self.i1, &mut self.i2); + self.i2.advance_back_by(n.get()) + } + } + } + + fn rfold(self, accum: Acc, mut f: F) -> Acc + where + F: FnMut(Acc, Self::Item) -> Acc, + { + let accum = self.i2.rfold(accum, &mut f); + self.i1.rfold(accum, &mut f) + } + + fn try_rfold(&mut self, init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: core::ops::Try, + { + let acc = self.i2.try_rfold(init, &mut f)?; + self.i1.try_rfold(acc, &mut f) + } +} + +impl ExactSizeIterator for Iter<'_, T> { + fn len(&self) -> usize { + self.i1.len() + self.i2.len() + } + + fn is_empty(&self) -> bool { + self.i1.is_empty() && self.i2.is_empty() + } +} + +impl core::iter::FusedIterator for Iter<'_, T> {} + +unsafe impl core::iter::TrustedLen for Iter<'_, T> {} + +/// A mutable iterator over the elements of a `SmallDeque`. +/// +/// This `struct` is created by the [`iter_mut`] method on [`SmallDeque`]. See its +/// documentation for more. +/// +/// [`iter_mut`]: SmallDeque::iter_mut +pub struct IterMut<'a, T: 'a> { + i1: core::slice::IterMut<'a, T>, + i2: core::slice::IterMut<'a, T>, +} + +impl<'a, T> IterMut<'a, T> { + pub(super) fn new(i1: core::slice::IterMut<'a, T>, i2: core::slice::IterMut<'a, T>) -> Self { + Self { i1, i2 } + } +} + +impl fmt::Debug for IterMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("IterMut") + .field(&self.i1.as_slice()) + .field(&self.i2.as_slice()) + .finish() + } +} + +impl<'a, T> Iterator for IterMut<'a, T> { + type Item = &'a mut T; + + #[inline] + fn next(&mut self) -> Option<&'a mut T> { + match self.i1.next() { + Some(val) => Some(val), + None => { + // most of the time, the iterator will either always + // call next(), or always call next_back(). By swapping + // the iterators once the first one is empty, we ensure + // that the first branch is taken as often as possible, + // without sacrificing correctness, as i1 is empty anyways + core::mem::swap(&mut self.i1, &mut self.i2); + self.i1.next() + } + } + } + + fn advance_by(&mut self, n: usize) -> Result<(), core::num::NonZero> { + match self.i1.advance_by(n) { + Ok(()) => Ok(()), + Err(remaining) => { + core::mem::swap(&mut self.i1, &mut self.i2); + self.i1.advance_by(remaining.get()) + } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.len(); + (len, Some(len)) + } + + fn fold(self, accum: Acc, mut f: F) -> Acc + where + F: FnMut(Acc, Self::Item) -> Acc, + { + let accum = self.i1.fold(accum, &mut f); + self.i2.fold(accum, &mut f) + } + + fn try_fold(&mut self, init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: core::ops::Try, + { + let acc = self.i1.try_fold(init, &mut f)?; + self.i2.try_fold(acc, &mut f) + } + + #[inline] + fn last(mut self) -> Option<&'a mut T> { + self.next_back() + } +} + +impl<'a, T> DoubleEndedIterator for IterMut<'a, T> { + #[inline] + fn next_back(&mut self) -> Option<&'a mut T> { + match self.i2.next_back() { + Some(val) => Some(val), + None => { + // most of the time, the iterator will either always + // call next(), or always call next_back(). By swapping + // the iterators once the first one is empty, we ensure + // that the first branch is taken as often as possible, + // without sacrificing correctness, as i2 is empty anyways + core::mem::swap(&mut self.i1, &mut self.i2); + self.i2.next_back() + } + } + } + + fn advance_back_by(&mut self, n: usize) -> Result<(), core::num::NonZero> { + match self.i2.advance_back_by(n) { + Ok(()) => Ok(()), + Err(remaining) => { + core::mem::swap(&mut self.i1, &mut self.i2); + self.i2.advance_back_by(remaining.get()) + } + } + } + + fn rfold(self, accum: Acc, mut f: F) -> Acc + where + F: FnMut(Acc, Self::Item) -> Acc, + { + let accum = self.i2.rfold(accum, &mut f); + self.i1.rfold(accum, &mut f) + } + + fn try_rfold(&mut self, init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: core::ops::Try, + { + let acc = self.i2.try_rfold(init, &mut f)?; + self.i1.try_rfold(acc, &mut f) + } +} + +impl ExactSizeIterator for IterMut<'_, T> { + fn len(&self) -> usize { + self.i1.len() + self.i2.len() + } + + fn is_empty(&self) -> bool { + self.i1.is_empty() && self.i2.is_empty() + } +} + +impl core::iter::FusedIterator for IterMut<'_, T> {} + +unsafe impl core::iter::TrustedLen for IterMut<'_, T> {} + +/// An owning iterator over the elements of a `SmallDeque`. +/// +/// This `struct` is created by the [`into_iter`] method on [`SmallDeque`] +/// (provided by the [`IntoIterator`] trait). See its documentation for more. +/// +/// [`into_iter`]: SmallDeque::into_iter +#[derive(Clone)] +pub struct IntoIter { + inner: SmallDeque, +} + +impl IntoIter { + pub(super) fn new(inner: SmallDeque) -> Self { + IntoIter { inner } + } + + pub(super) fn into_smalldeque(self) -> SmallDeque { + self.inner + } +} + +impl fmt::Debug for IntoIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("IntoIter").field(&self.inner).finish() + } +} + +impl Iterator for IntoIter { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + self.inner.pop_front() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.inner.len(); + (len, Some(len)) + } + + #[inline] + fn advance_by(&mut self, n: usize) -> Result<(), core::num::NonZero> { + let len = self.inner.len; + let rem = if len < n { + self.inner.clear(); + n - len + } else { + self.inner.drain(..n); + 0 + }; + core::num::NonZero::new(rem).map_or(Ok(()), Err) + } + + #[inline] + fn count(self) -> usize { + self.inner.len + } + + fn try_fold(&mut self, mut init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: core::ops::Try, + { + struct Guard<'a, T, const M: usize> { + deque: &'a mut SmallDeque, + // `consumed <= deque.len` always holds. + consumed: usize, + } + + impl Drop for Guard<'_, T, M> { + fn drop(&mut self) { + self.deque.len -= self.consumed; + self.deque.head = self.deque.to_physical_idx(self.consumed); + } + } + + let mut guard = Guard { + deque: &mut self.inner, + consumed: 0, + }; + + let (head, tail) = guard.deque.as_slices(); + + init = head + .iter() + .map(|elem| { + guard.consumed += 1; + // SAFETY: Because we incremented `guard.consumed`, the + // deque effectively forgot the element, so we can take + // ownership + unsafe { ptr::read(elem) } + }) + .try_fold(init, &mut f)?; + + tail.iter() + .map(|elem| { + guard.consumed += 1; + // SAFETY: Same as above. + unsafe { ptr::read(elem) } + }) + .try_fold(init, &mut f) + } + + #[inline] + fn fold(mut self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + match self.try_fold(init, |b, item| Ok::(f(b, item))) { + Ok(b) => b, + Err(e) => match e {}, + } + } + + #[inline] + fn last(mut self) -> Option { + self.inner.pop_back() + } + + fn next_chunk( + &mut self, + ) -> Result<[Self::Item; N], core::array::IntoIter> { + let mut raw_arr = [const { core::mem::MaybeUninit::uninit() }; N]; + let raw_arr_ptr = raw_arr.as_mut_ptr().cast(); + let (head, tail) = self.inner.as_slices(); + + if head.len() >= N { + // SAFETY: By manually adjusting the head and length of the deque, we effectively + // make it forget the first `N` elements, so taking ownership of them is safe. + unsafe { ptr::copy_nonoverlapping(head.as_ptr(), raw_arr_ptr, N) }; + self.inner.head = self.inner.to_physical_idx(N); + self.inner.len -= N; + // SAFETY: We initialized the entire array with items from `head` + return Ok(unsafe { raw_arr.transpose().assume_init() }); + } + + // SAFETY: Same argument as above. + unsafe { ptr::copy_nonoverlapping(head.as_ptr(), raw_arr_ptr, head.len()) }; + let remaining = N - head.len(); + + if tail.len() >= remaining { + // SAFETY: Same argument as above. + unsafe { + ptr::copy_nonoverlapping(tail.as_ptr(), raw_arr_ptr.add(head.len()), remaining) + }; + self.inner.head = self.inner.to_physical_idx(N); + self.inner.len -= N; + // SAFETY: We initialized the entire array with items from `head` and `tail` + Ok(unsafe { raw_arr.transpose().assume_init() }) + } else { + // SAFETY: Same argument as above. + unsafe { + ptr::copy_nonoverlapping(tail.as_ptr(), raw_arr_ptr.add(head.len()), tail.len()) + }; + let init = head.len() + tail.len(); + // We completely drained all the deques elements. + self.inner.head = 0; + self.inner.len = 0; + // SAFETY: We copied all elements from both slices to the beginning of the array, so + // the given range is initialized. + Err(unsafe { core::array::IntoIter::new_unchecked(raw_arr, 0..init) }) + } + } +} + +impl DoubleEndedIterator for IntoIter { + #[inline] + fn next_back(&mut self) -> Option { + self.inner.pop_back() + } + + #[inline] + fn advance_back_by(&mut self, n: usize) -> Result<(), core::num::NonZero> { + let len = self.inner.len; + let rem = if len < n { + self.inner.clear(); + n - len + } else { + self.inner.truncate(len - n); + 0 + }; + core::num::NonZero::new(rem).map_or(Ok(()), Err) + } + + fn try_rfold(&mut self, mut init: B, mut f: F) -> R + where + F: FnMut(B, Self::Item) -> R, + R: core::ops::Try, + { + struct Guard<'a, T, const N: usize> { + deque: &'a mut SmallDeque, + // `consumed <= deque.len` always holds. + consumed: usize, + } + + impl Drop for Guard<'_, T, N> { + fn drop(&mut self) { + self.deque.len -= self.consumed; + } + } + + let mut guard = Guard { + deque: &mut self.inner, + consumed: 0, + }; + + let (head, tail) = guard.deque.as_slices(); + + init = tail + .iter() + .map(|elem| { + guard.consumed += 1; + // SAFETY: See `try_fold`'s safety comment. + unsafe { ptr::read(elem) } + }) + .try_rfold(init, &mut f)?; + + head.iter() + .map(|elem| { + guard.consumed += 1; + // SAFETY: Same as above. + unsafe { ptr::read(elem) } + }) + .try_rfold(init, &mut f) + } + + #[inline] + fn rfold(mut self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + match self.try_rfold(init, |b, item| Ok::(f(b, item))) { + Ok(b) => b, + Err(e) => match e {}, + } + } +} + +impl ExactSizeIterator for IntoIter { + #[inline] + fn is_empty(&self) -> bool { + self.inner.is_empty() + } +} + +impl core::iter::FusedIterator for IntoIter {} + +unsafe impl core::iter::TrustedLen for IntoIter {} + +/// A draining iterator over the elements of a `SmallDeque`. +/// +/// This `struct` is created by the [`drain`] method on [`SmallDeque`]. See its +/// documentation for more. +/// +/// [`drain`]: SmallDeque::drain +pub struct Drain<'a, T: 'a, const N: usize> { + // We can't just use a &mut SmallDeque, as that would make Drain invariant over T + // and we want it to be covariant instead + deque: NonNull>, + // drain_start is stored in deque.len + drain_len: usize, + // index into the logical array, not the physical one (always lies in [0..deque.len)) + idx: usize, + // number of elements remaining after dropping the drain + new_len: usize, + remaining: usize, + // Needed to make Drain covariant over T + _marker: core::marker::PhantomData<&'a T>, +} + +impl<'a, T, const N: usize> Drain<'a, T, N> { + pub(super) unsafe fn new( + deque: &'a mut SmallDeque, + drain_start: usize, + drain_len: usize, + ) -> Self { + let orig_len = core::mem::replace(&mut deque.len, drain_start); + let new_len = orig_len - drain_len; + Drain { + deque: NonNull::from(deque), + drain_len, + idx: drain_start, + new_len, + remaining: drain_len, + _marker: core::marker::PhantomData, + } + } + + // Only returns pointers to the slices, as that's all we need + // to drop them. May only be called if `self.remaining != 0`. + unsafe fn as_slices(&mut self) -> (*mut [T], *mut [T]) { + unsafe { + let deque = self.deque.as_mut(); + + // We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. + let logical_remaining_range = self.idx..self.idx + self.remaining; + + // SAFETY: `logical_remaining_range` represents the + // range into the logical buffer of elements that + // haven't been drained yet, so they're all initialized, + // and `slice::range(start..end, end) == start..end`, + // so the preconditions for `slice_ranges` are met. + let (a_range, b_range) = + deque.slice_ranges(logical_remaining_range.clone(), logical_remaining_range.end); + (deque.buffer_range_mut(a_range), deque.buffer_range_mut(b_range)) + } + } +} + +impl fmt::Debug for Drain<'_, T, N> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Drain") + .field(&self.drain_len) + .field(&self.idx) + .field(&self.new_len) + .field(&self.remaining) + .finish() + } +} + +unsafe impl Sync for Drain<'_, T, N> {} +unsafe impl Send for Drain<'_, T, N> {} + +impl Drop for Drain<'_, T, N> { + fn drop(&mut self) { + struct DropGuard<'r, 'a, T, const N: usize>(&'r mut Drain<'a, T, N>); + + let guard = DropGuard(self); + + if core::mem::needs_drop::() && guard.0.remaining != 0 { + unsafe { + // SAFETY: We just checked that `self.remaining != 0`. + let (front, back) = guard.0.as_slices(); + // since idx is a logical index, we don't need to worry about wrapping. + guard.0.idx += front.len(); + guard.0.remaining -= front.len(); + ptr::drop_in_place(front); + guard.0.remaining = 0; + ptr::drop_in_place(back); + } + } + + // Dropping `guard` handles moving the remaining elements into place. + impl Drop for DropGuard<'_, '_, T, N> { + #[inline] + fn drop(&mut self) { + if core::mem::needs_drop::() && self.0.remaining != 0 { + unsafe { + // SAFETY: We just checked that `self.remaining != 0`. + let (front, back) = self.0.as_slices(); + ptr::drop_in_place(front); + ptr::drop_in_place(back); + } + } + + let source_deque = unsafe { self.0.deque.as_mut() }; + + let drain_len = self.0.drain_len; + let new_len = self.0.new_len; + + if T::IS_ZST { + // no need to copy around any memory if T is a ZST + source_deque.len = new_len; + return; + } + + let head_len = source_deque.len; // #elements in front of the drain + let tail_len = new_len - head_len; // #elements behind the drain + + // Next, we will fill the hole left by the drain with as few writes as possible. + // The code below handles the following control flow and reduces the amount of + // branches under the assumption that `head_len == 0 || tail_len == 0`, i.e. + // draining at the front or at the back of the dequeue is especially common. + // + // H = "head index" = `deque.head` + // h = elements in front of the drain + // d = elements in the drain + // t = elements behind the drain + // + // Note that the buffer may wrap at any point and the wrapping is handled by + // `wrap_copy` and `to_physical_idx`. + // + // Case 1: if `head_len == 0 && tail_len == 0` + // Everything was drained, reset the head index back to 0. + // H + // [ . . . . . d d d d . . . . . ] + // H + // [ . . . . . . . . . . . . . . ] + // + // Case 2: else if `tail_len == 0` + // Don't move data or the head index. + // H + // [ . . . h h h h d d d d . . . ] + // H + // [ . . . h h h h . . . . . . . ] + // + // Case 3: else if `head_len == 0` + // Don't move data, but move the head index. + // H + // [ . . . d d d d t t t t . . . ] + // H + // [ . . . . . . . t t t t . . . ] + // + // Case 4: else if `tail_len <= head_len` + // Move data, but not the head index. + // H + // [ . . h h h h d d d d t t . . ] + // H + // [ . . h h h h t t . . . . . . ] + // + // Case 5: else + // Move data and the head index. + // H + // [ . . h h d d d d t t t t . . ] + // H + // [ . . . . . . h h t t t t . . ] + + // When draining at the front (`.drain(..n)`) or at the back (`.drain(n..)`), + // we don't need to copy any data. The number of elements copied would be 0. + if head_len != 0 && tail_len != 0 { + join_head_and_tail_wrapping(source_deque, drain_len, head_len, tail_len); + // Marking this function as cold helps LLVM to eliminate it entirely if + // this branch is never taken. + // We use `#[cold]` instead of `#[inline(never)]`, because inlining this + // function into the general case (`.drain(n..m)`) is fine. + // See `tests/codegen/vecdeque-drain.rs` for a test. + #[cold] + fn join_head_and_tail_wrapping( + source_deque: &mut SmallDeque, + drain_len: usize, + head_len: usize, + tail_len: usize, + ) { + // Pick whether to move the head or the tail here. + let (src, dst, len); + if head_len < tail_len { + src = source_deque.head; + dst = source_deque.to_physical_idx(drain_len); + len = head_len; + } else { + src = source_deque.to_physical_idx(head_len + drain_len); + dst = source_deque.to_physical_idx(head_len); + len = tail_len; + }; + + unsafe { + source_deque.wrap_copy(src, dst, len); + } + } + } + + if new_len == 0 { + // Special case: If the entire dequeue was drained, reset the head back to 0, + // like `.clear()` does. + source_deque.head = 0; + } else if head_len < tail_len { + // If we moved the head above, then we need to adjust the head index here. + source_deque.head = source_deque.to_physical_idx(drain_len); + } + source_deque.len = new_len; + } + } + } +} + +impl Iterator for Drain<'_, T, N> { + type Item = T; + + #[inline] + fn next(&mut self) -> Option { + if self.remaining == 0 { + return None; + } + let wrapped_idx = unsafe { self.deque.as_ref().to_physical_idx(self.idx) }; + self.idx += 1; + self.remaining -= 1; + Some(unsafe { self.deque.as_mut().buffer_read(wrapped_idx) }) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.remaining; + (len, Some(len)) + } +} + +impl DoubleEndedIterator for Drain<'_, T, N> { + #[inline] + fn next_back(&mut self) -> Option { + if self.remaining == 0 { + return None; + } + self.remaining -= 1; + let wrapped_idx = unsafe { self.deque.as_ref().to_physical_idx(self.idx + self.remaining) }; + Some(unsafe { self.deque.as_mut().buffer_read(wrapped_idx) }) + } +} + +impl ExactSizeIterator for Drain<'_, T, N> {} + +impl core::iter::FusedIterator for Drain<'_, T, N> {} + +/// Specialization trait used for `SmallDeque::from_iter` +trait SpecFromIter { + fn spec_from_iter(iter: I) -> Self; +} + +impl SpecFromIter for SmallDeque +where + I: Iterator, +{ + default fn spec_from_iter(iterator: I) -> Self { + // Since converting is O(1) now, just re-use the `SmallVec` logic for + // anything where we can't do something extra-special for `SmallDeque`, + // especially as that could save us some monomorphization work + // if one uses the same iterators (like slice ones) with both. + SmallVec::from_iter(iterator).into() + } +} + +impl SpecFromIter> for SmallDeque { + #[inline] + fn spec_from_iter(iterator: IntoIter) -> Self { + iterator.into_smalldeque() + } +} + +// Specialization trait used for SmallDeque::extend +trait SpecExtend { + fn spec_extend(&mut self, iter: I); +} + +impl SpecExtend for SmallDeque +where + I: Iterator, +{ + default fn spec_extend(&mut self, mut iter: I) { + // This function should be the moral equivalent of: + // + // for item in iter { + // self.push_back(item); + // } + + // May only be called if `deque.len() < deque.capacity()` + unsafe fn push_unchecked(deque: &mut SmallDeque, element: T) { + // SAFETY: Because of the precondition, it's guaranteed that there is space + // in the logical array after the last element. + unsafe { deque.buffer_write(deque.to_physical_idx(deque.len), element) }; + // This can't overflow because `deque.len() < deque.capacity() <= usize::MAX`. + deque.len += 1; + } + + while let Some(element) = iter.next() { + let (lower, _) = iter.size_hint(); + self.reserve(lower.saturating_add(1)); + + // SAFETY: We just reserved space for at least one element. + unsafe { push_unchecked(self, element) }; + + // Inner loop to avoid repeatedly calling `reserve`. + while self.len < self.capacity() { + let Some(element) = iter.next() else { + return; + }; + // SAFETY: The loop condition guarantees that `self.len() < self.capacity()`. + unsafe { push_unchecked(self, element) }; + } + } + } +} + +impl SpecExtend for SmallDeque +where + I: core::iter::TrustedLen, +{ + default fn spec_extend(&mut self, iter: I) { + // This is the case for a TrustedLen iterator. + let (low, high) = iter.size_hint(); + if let Some(additional) = high { + debug_assert_eq!( + low, + additional, + "TrustedLen iterator's size hint is not exact: {:?}", + (low, high) + ); + self.reserve(additional); + + let written = unsafe { + self.write_iter_wrapping(self.to_physical_idx(self.len), iter, additional) + }; + + debug_assert_eq!( + additional, written, + "The number of items written to SmallDeque doesn't match the TrustedLen size hint" + ); + } else { + // Per TrustedLen contract a `None` upper bound means that the iterator length + // truly exceeds usize::MAX, which would eventually lead to a capacity overflow anyway. + // Since the other branch already panics eagerly (via `reserve()`) we do the same here. + // This avoids additional codegen for a fallback code path which would eventually + // panic anyway. + panic!("capacity overflow"); + } + } +} + +impl<'a, T: 'a, I, const N: usize> SpecExtend<&'a T, I> for SmallDeque +where + I: Iterator, + T: Copy, +{ + default fn spec_extend(&mut self, iterator: I) { + self.spec_extend(iterator.copied()) + } +} + +impl<'a, T: 'a, const N: usize> SpecExtend<&'a T, core::slice::Iter<'a, T>> for SmallDeque +where + T: Copy, +{ + fn spec_extend(&mut self, iterator: core::slice::Iter<'a, T>) { + let slice = iterator.as_slice(); + self.reserve(slice.len()); + + unsafe { + self.copy_slice(self.to_physical_idx(self.len), slice); + self.len += slice.len(); + } + } +} + +#[cfg(test)] +mod tests { + use alloc::{boxed::Box, vec}; + use core::iter::TrustedLen; + + use smallvec::SmallVec; + + use super::*; + + #[test] + fn test_swap_front_back_remove() { + fn test(back: bool) { + // This test checks that every single combination of tail position and length is tested. + // Capacity 15 should be large enough to cover every case. + let mut tester = SmallDeque::<_, 16>::with_capacity(15); + let usable_cap = tester.capacity(); + let final_len = usable_cap / 2; + + for len in 0..final_len { + let expected: SmallDeque<_, 16> = if back { + (0..len).collect() + } else { + (0..len).rev().collect() + }; + for head_pos in 0..usable_cap { + tester.head = head_pos; + tester.len = 0; + if back { + for i in 0..len * 2 { + tester.push_front(i); + } + for i in 0..len { + assert_eq!(tester.swap_remove_back(i), Some(len * 2 - 1 - i)); + } + } else { + for i in 0..len * 2 { + tester.push_back(i); + } + for i in 0..len { + let idx = tester.len() - 1 - i; + assert_eq!(tester.swap_remove_front(idx), Some(len * 2 - 1 - i)); + } + } + assert!(tester.head <= tester.capacity()); + assert!(tester.len <= tester.capacity()); + assert_eq!(tester, expected); + } + } + } + test(true); + test(false); + } + + #[test] + fn test_insert() { + // This test checks that every single combination of tail position, length, and + // insertion position is tested. Capacity 15 should be large enough to cover every case. + + let mut tester = SmallDeque::<_, 16>::with_capacity(15); + // can't guarantee we got 15, so have to get what we got. + // 15 would be great, but we will definitely get 2^k - 1, for k >= 4, or else + // this test isn't covering what it wants to + let cap = tester.capacity(); + + // len is the length *after* insertion + let minlen = if cfg!(miri) { cap - 1 } else { 1 }; // Miri is too slow + for len in minlen..cap { + // 0, 1, 2, .., len - 1 + let expected = (0..).take(len).collect::>(); + for head_pos in 0..cap { + for to_insert in 0..len { + tester.head = head_pos; + tester.len = 0; + for i in 0..len { + if i != to_insert { + tester.push_back(i); + } + } + tester.insert(to_insert, to_insert); + assert!(tester.head <= tester.capacity()); + assert!(tester.len <= tester.capacity()); + assert_eq!(tester, expected); + } + } + } + } + + #[test] + fn test_get() { + let mut tester = SmallDeque::<_, 16>::new(); + tester.push_back(1); + tester.push_back(2); + tester.push_back(3); + + assert_eq!(tester.len(), 3); + + assert_eq!(tester.get(1), Some(&2)); + assert_eq!(tester.get(2), Some(&3)); + assert_eq!(tester.get(0), Some(&1)); + assert_eq!(tester.get(3), None); + + tester.remove(0); + + assert_eq!(tester.len(), 2); + assert_eq!(tester.get(0), Some(&2)); + assert_eq!(tester.get(1), Some(&3)); + assert_eq!(tester.get(2), None); + } + + #[test] + fn test_get_mut() { + let mut tester = SmallDeque::<_, 16>::new(); + tester.push_back(1); + tester.push_back(2); + tester.push_back(3); + + assert_eq!(tester.len(), 3); + + if let Some(elem) = tester.get_mut(0) { + assert_eq!(*elem, 1); + *elem = 10; + } + + if let Some(elem) = tester.get_mut(2) { + assert_eq!(*elem, 3); + *elem = 30; + } + + assert_eq!(tester.get(0), Some(&10)); + assert_eq!(tester.get(2), Some(&30)); + assert_eq!(tester.get_mut(3), None); + + tester.remove(2); + + assert_eq!(tester.len(), 2); + assert_eq!(tester.get(0), Some(&10)); + assert_eq!(tester.get(1), Some(&2)); + assert_eq!(tester.get(2), None); + } + + #[test] + fn test_swap() { + let mut tester = SmallDeque::<_, 3>::new(); + tester.push_back(1); + tester.push_back(2); + tester.push_back(3); + + assert_eq!(tester, [1, 2, 3]); + + tester.swap(0, 0); + assert_eq!(tester, [1, 2, 3]); + tester.swap(0, 1); + assert_eq!(tester, [2, 1, 3]); + tester.swap(2, 1); + assert_eq!(tester, [2, 3, 1]); + tester.swap(1, 2); + assert_eq!(tester, [2, 1, 3]); + tester.swap(0, 2); + assert_eq!(tester, [3, 1, 2]); + tester.swap(2, 2); + assert_eq!(tester, [3, 1, 2]); + } + + #[test] + #[should_panic = "assertion failed: j < self.len()"] + fn test_swap_panic() { + let mut tester = SmallDeque::<_>::new(); + tester.push_back(1); + tester.push_back(2); + tester.push_back(3); + tester.swap(2, 3); + } + + #[test] + fn test_reserve_exact() { + let mut tester: SmallDeque = SmallDeque::with_capacity(1); + assert_eq!(tester.capacity(), 1); + tester.reserve_exact(50); + assert_eq!(tester.capacity(), 50); + tester.reserve_exact(40); + // reserving won't shrink the buffer + assert_eq!(tester.capacity(), 50); + tester.reserve_exact(200); + assert_eq!(tester.capacity(), 200); + } + + #[test] + #[should_panic = "capacity overflow"] + fn test_reserve_exact_panic() { + let mut tester: SmallDeque = SmallDeque::new(); + tester.reserve_exact(usize::MAX); + } + + #[test] + fn test_contains() { + let mut tester = SmallDeque::<_>::new(); + tester.push_back(1); + tester.push_back(2); + tester.push_back(3); + + assert!(tester.contains(&1)); + assert!(tester.contains(&3)); + assert!(!tester.contains(&0)); + assert!(!tester.contains(&4)); + tester.remove(0); + assert!(!tester.contains(&1)); + assert!(tester.contains(&2)); + assert!(tester.contains(&3)); + } + + #[test] + fn test_rotate_left_right() { + let mut tester: SmallDeque<_> = (1..=10).collect(); + tester.reserve(1); + + assert_eq!(tester.len(), 10); + + tester.rotate_left(0); + assert_eq!(tester, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + tester.rotate_right(0); + assert_eq!(tester, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + + tester.rotate_left(3); + assert_eq!(tester, [4, 5, 6, 7, 8, 9, 10, 1, 2, 3]); + + tester.rotate_right(5); + assert_eq!(tester, [9, 10, 1, 2, 3, 4, 5, 6, 7, 8]); + + tester.rotate_left(tester.len()); + assert_eq!(tester, [9, 10, 1, 2, 3, 4, 5, 6, 7, 8]); + + tester.rotate_right(tester.len()); + assert_eq!(tester, [9, 10, 1, 2, 3, 4, 5, 6, 7, 8]); + + tester.rotate_left(1); + assert_eq!(tester, [10, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + } + + #[test] + #[should_panic = "assertion failed: n <= self.len()"] + fn test_rotate_left_panic() { + let mut tester: SmallDeque<_> = (1..=10).collect(); + tester.rotate_left(tester.len() + 1); + } + + #[test] + #[should_panic = "assertion failed: n <= self.len()"] + fn test_rotate_right_panic() { + let mut tester: SmallDeque<_> = (1..=10).collect(); + tester.rotate_right(tester.len() + 1); + } + + #[test] + fn test_binary_search() { + // If the givin SmallDeque is not sorted, the returned result is unspecified and meaningless, + // as this method performs a binary search. + + let tester: SmallDeque<_, 11> = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55].into(); + + assert_eq!(tester.binary_search(&0), Ok(0)); + assert_eq!(tester.binary_search(&5), Ok(5)); + assert_eq!(tester.binary_search(&55), Ok(10)); + assert_eq!(tester.binary_search(&4), Err(5)); + assert_eq!(tester.binary_search(&-1), Err(0)); + assert!(matches!(tester.binary_search(&1), Ok(1..=2))); + + let tester: SmallDeque<_, 14> = [1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3].into(); + assert_eq!(tester.binary_search(&1), Ok(0)); + assert!(matches!(tester.binary_search(&2), Ok(1..=4))); + assert!(matches!(tester.binary_search(&3), Ok(5..=13))); + assert_eq!(tester.binary_search(&-2), Err(0)); + assert_eq!(tester.binary_search(&0), Err(0)); + assert_eq!(tester.binary_search(&4), Err(14)); + assert_eq!(tester.binary_search(&5), Err(14)); + } + + #[test] + fn test_binary_search_by() { + // If the givin SmallDeque is not sorted, the returned result is unspecified and meaningless, + // as this method performs a binary search. + + let tester: SmallDeque = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55].into(); + + assert_eq!(tester.binary_search_by(|x| x.cmp(&0)), Ok(0)); + assert_eq!(tester.binary_search_by(|x| x.cmp(&5)), Ok(5)); + assert_eq!(tester.binary_search_by(|x| x.cmp(&55)), Ok(10)); + assert_eq!(tester.binary_search_by(|x| x.cmp(&4)), Err(5)); + assert_eq!(tester.binary_search_by(|x| x.cmp(&-1)), Err(0)); + assert!(matches!(tester.binary_search_by(|x| x.cmp(&1)), Ok(1..=2))); + } + + #[test] + fn test_binary_search_key() { + // If the givin SmallDeque is not sorted, the returned result is unspecified and meaningless, + // as this method performs a binary search. + + let tester: SmallDeque<_, 13> = [ + (-1, 0), + (2, 10), + (6, 5), + (7, 1), + (8, 10), + (10, 2), + (20, 3), + (24, 5), + (25, 18), + (28, 13), + (31, 21), + (32, 4), + (54, 25), + ] + .into(); + + assert_eq!(tester.binary_search_by_key(&-1, |&(a, _b)| a), Ok(0)); + assert_eq!(tester.binary_search_by_key(&8, |&(a, _b)| a), Ok(4)); + assert_eq!(tester.binary_search_by_key(&25, |&(a, _b)| a), Ok(8)); + assert_eq!(tester.binary_search_by_key(&54, |&(a, _b)| a), Ok(12)); + assert_eq!(tester.binary_search_by_key(&-2, |&(a, _b)| a), Err(0)); + assert_eq!(tester.binary_search_by_key(&1, |&(a, _b)| a), Err(1)); + assert_eq!(tester.binary_search_by_key(&4, |&(a, _b)| a), Err(2)); + assert_eq!(tester.binary_search_by_key(&13, |&(a, _b)| a), Err(6)); + assert_eq!(tester.binary_search_by_key(&55, |&(a, _b)| a), Err(13)); + assert_eq!(tester.binary_search_by_key(&100, |&(a, _b)| a), Err(13)); + + let tester: SmallDeque<_, 13> = [ + (0, 0), + (2, 1), + (6, 1), + (5, 1), + (3, 1), + (1, 2), + (2, 3), + (4, 5), + (5, 8), + (8, 13), + (1, 21), + (2, 34), + (4, 55), + ] + .into(); + + assert_eq!(tester.binary_search_by_key(&0, |&(_a, b)| b), Ok(0)); + assert!(matches!(tester.binary_search_by_key(&1, |&(_a, b)| b), Ok(1..=4))); + assert_eq!(tester.binary_search_by_key(&8, |&(_a, b)| b), Ok(8)); + assert_eq!(tester.binary_search_by_key(&13, |&(_a, b)| b), Ok(9)); + assert_eq!(tester.binary_search_by_key(&55, |&(_a, b)| b), Ok(12)); + assert_eq!(tester.binary_search_by_key(&-1, |&(_a, b)| b), Err(0)); + assert_eq!(tester.binary_search_by_key(&4, |&(_a, b)| b), Err(7)); + assert_eq!(tester.binary_search_by_key(&56, |&(_a, b)| b), Err(13)); + assert_eq!(tester.binary_search_by_key(&100, |&(_a, b)| b), Err(13)); + } + + #[test] + fn make_contiguous_big_head() { + let mut tester = SmallDeque::<_>::with_capacity(15); + + for i in 0..3 { + tester.push_back(i); + } + + for i in 3..10 { + tester.push_front(i); + } + + // 012......9876543 + assert_eq!(tester.capacity(), 15); + assert_eq!((&[9, 8, 7, 6, 5, 4, 3] as &[_], &[0, 1, 2] as &[_]), tester.as_slices()); + + let expected_start = tester.as_slices().1.len(); + tester.make_contiguous(); + assert_eq!(tester.head, expected_start); + assert_eq!((&[9, 8, 7, 6, 5, 4, 3, 0, 1, 2] as &[_], &[] as &[_]), tester.as_slices()); + } + + #[test] + fn make_contiguous_big_tail() { + let mut tester = SmallDeque::<_>::with_capacity(15); + + for i in 0..8 { + tester.push_back(i); + } + + for i in 8..10 { + tester.push_front(i); + } + + // 01234567......98 + let expected_start = 0; + tester.make_contiguous(); + assert_eq!(tester.head, expected_start); + assert_eq!((&[9, 8, 0, 1, 2, 3, 4, 5, 6, 7] as &[_], &[] as &[_]), tester.as_slices()); + } + + #[test] + fn make_contiguous_small_free() { + let mut tester = SmallDeque::<_>::with_capacity(16); + + for i in b'A'..b'I' { + tester.push_back(i as char); + } + + for i in b'I'..b'N' { + tester.push_front(i as char); + } + + assert_eq!(tester, ['M', 'L', 'K', 'J', 'I', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']); + + // ABCDEFGH...MLKJI + let expected_start = 0; + tester.make_contiguous(); + assert_eq!(tester.head, expected_start); + assert_eq!( + ( + &['M', 'L', 'K', 'J', 'I', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H'] as &[_], + &[] as &[_] + ), + tester.as_slices() + ); + + tester.clear(); + for i in b'I'..b'N' { + tester.push_back(i as char); + } + + for i in b'A'..b'I' { + tester.push_front(i as char); + } + + // IJKLM...HGFEDCBA + let expected_start = 3; + tester.make_contiguous(); + assert_eq!(tester.head, expected_start); + assert_eq!( + ( + &['H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', 'I', 'J', 'K', 'L', 'M'] as &[_], + &[] as &[_] + ), + tester.as_slices() + ); + } + + #[test] + fn make_contiguous_head_to_end() { + let mut tester = SmallDeque::<_>::with_capacity(16); + + for i in b'A'..b'L' { + tester.push_back(i as char); + } + + for i in b'L'..b'Q' { + tester.push_front(i as char); + } + + assert_eq!( + tester, + ['P', 'O', 'N', 'M', 'L', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K'] + ); + + // ABCDEFGHIJKPONML + let expected_start = 0; + tester.make_contiguous(); + assert_eq!(tester.head, expected_start); + assert_eq!( + ( + &['P', 'O', 'N', 'M', 'L', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K'] + as &[_], + &[] as &[_] + ), + tester.as_slices() + ); + + tester.clear(); + for i in b'L'..b'Q' { + tester.push_back(i as char); + } + + for i in b'A'..b'L' { + tester.push_front(i as char); + } + + // LMNOPKJIHGFEDCBA + let expected_start = 0; + tester.make_contiguous(); + assert_eq!(tester.head, expected_start); + assert_eq!( + ( + &['K', 'J', 'I', 'H', 'G', 'F', 'E', 'D', 'C', 'B', 'A', 'L', 'M', 'N', 'O', 'P'] + as &[_], + &[] as &[_] + ), + tester.as_slices() + ); + } + + #[test] + fn make_contiguous_head_to_end_2() { + // Another test case for #79808, taken from #80293. + + let mut dq = SmallDeque::<_>::from_iter(0..6); + dq.pop_front(); + dq.pop_front(); + dq.push_back(6); + dq.push_back(7); + dq.push_back(8); + dq.make_contiguous(); + let collected: Vec<_> = dq.iter().copied().collect(); + assert_eq!(dq.as_slices(), (&collected[..], &[] as &[_])); + } + + #[test] + fn test_remove() { + // This test checks that every single combination of tail position, length, and + // removal position is tested. Capacity 15 should be large enough to cover every case. + + let mut tester = SmallDeque::<_>::with_capacity(15); + // can't guarantee we got 15, so have to get what we got. + // 15 would be great, but we will definitely get 2^k - 1, for k >= 4, or else + // this test isn't covering what it wants to + let cap = tester.capacity(); + + // len is the length *after* removal + let minlen = if cfg!(miri) { cap - 2 } else { 0 }; // Miri is too slow + for len in minlen..cap - 1 { + // 0, 1, 2, .., len - 1 + let expected = (0..).take(len).collect::>(); + for head_pos in 0..cap { + for to_remove in 0..=len { + tester.head = head_pos; + tester.len = 0; + for i in 0..len { + if i == to_remove { + tester.push_back(1234); + } + tester.push_back(i); + } + if to_remove == len { + tester.push_back(1234); + } + tester.remove(to_remove); + assert!(tester.head <= tester.capacity()); + assert!(tester.len <= tester.capacity()); + assert_eq!(tester, expected); + } + } + } + } + + #[test] + fn test_range() { + let mut tester: SmallDeque = SmallDeque::<_>::with_capacity(7); + + let cap = tester.capacity(); + let minlen = if cfg!(miri) { cap - 1 } else { 0 }; // Miri is too slow + for len in minlen..=cap { + for head in 0..=cap { + for start in 0..=len { + for end in start..=len { + tester.head = head; + tester.len = 0; + for i in 0..len { + tester.push_back(i); + } + + // Check that we iterate over the correct values + let range: SmallDeque<_> = tester.range(start..end).copied().collect(); + let expected: SmallDeque<_> = (start..end).collect(); + assert_eq!(range, expected); + } + } + } + } + } + + #[test] + fn test_range_mut() { + let mut tester: SmallDeque = SmallDeque::with_capacity(7); + + let cap = tester.capacity(); + for len in 0..=cap { + for head in 0..=cap { + for start in 0..=len { + for end in start..=len { + tester.head = head; + tester.len = 0; + for i in 0..len { + tester.push_back(i); + } + + let head_was = tester.head; + let len_was = tester.len; + + // Check that we iterate over the correct values + let range: SmallDeque<_> = + tester.range_mut(start..end).map(|v| *v).collect(); + let expected: SmallDeque<_> = (start..end).collect(); + assert_eq!(range, expected); + + // We shouldn't have changed the capacity or made the + // head or tail out of bounds + assert_eq!(tester.capacity(), cap); + assert_eq!(tester.head, head_was); + assert_eq!(tester.len, len_was); + } + } + } + } + } + + #[test] + fn test_drain() { + let mut tester: SmallDeque = SmallDeque::with_capacity(7); + + let cap = tester.capacity(); + for len in 0..=cap { + for head in 0..cap { + for drain_start in 0..=len { + for drain_end in drain_start..=len { + tester.head = head; + tester.len = 0; + for i in 0..len { + tester.push_back(i); + } + + // Check that we drain the correct values + let drained: SmallDeque<_> = tester.drain(drain_start..drain_end).collect(); + let drained_expected: SmallDeque<_> = (drain_start..drain_end).collect(); + assert_eq!(drained, drained_expected); + + // We shouldn't have changed the capacity or made the + // head or tail out of bounds + assert_eq!(tester.capacity(), cap); + assert!(tester.head <= tester.capacity()); + assert!(tester.len <= tester.capacity()); + + // We should see the correct values in the SmallDeque + let expected: SmallDeque<_> = + (0..drain_start).chain(drain_end..len).collect(); + assert_eq!(expected, tester); + } + } + } + } + } + + #[test] + fn test_split_off() { + // This test checks that every single combination of tail position, length, and + // split position is tested. Capacity 15 should be large enough to cover every case. + + let mut tester = SmallDeque::with_capacity(15); + // can't guarantee we got 15, so have to get what we got. + // 15 would be great, but we will definitely get 2^k - 1, for k >= 4, or else + // this test isn't covering what it wants to + let cap = tester.capacity(); + + // len is the length *before* splitting + let minlen = if cfg!(miri) { cap - 1 } else { 0 }; // Miri is too slow + for len in minlen..cap { + // index to split at + for at in 0..=len { + // 0, 1, 2, .., at - 1 (may be empty) + let expected_self = (0..).take(at).collect::>(); + // at, at + 1, .., len - 1 (may be empty) + let expected_other = (at..).take(len - at).collect::>(); + + for head_pos in 0..cap { + tester.head = head_pos; + tester.len = 0; + for i in 0..len { + tester.push_back(i); + } + let result = tester.split_off(at); + assert!(tester.head <= tester.capacity()); + assert!(tester.len <= tester.capacity()); + assert!(result.head <= result.capacity()); + assert!(result.len <= result.capacity()); + assert_eq!(tester, expected_self); + assert_eq!(result, expected_other); + } + } + } + } + + #[test] + fn test_from_smallvec() { + for cap in 0..35 { + for len in 0..=cap { + let mut vec = SmallVec::<[_; 16]>::with_capacity(cap); + vec.extend(0..len); + + let vd = SmallDeque::from(vec.clone()); + assert_eq!(vd.len(), vec.len()); + assert!(vd.into_iter().eq(vec)); + } + } + } + + #[test] + fn test_extend_basic() { + test_extend_impl(false); + } + + #[ignore = "trusted_len extension is broken, needs further analysis"] + #[test] + fn test_extend_trusted_len() { + test_extend_impl(true); + } + + fn test_extend_impl(trusted_len: bool) { + struct SmallDequeTester { + test: SmallDeque, + expected: SmallDeque, + trusted_len: bool, + } + + impl SmallDequeTester { + fn new(trusted_len: bool) -> Self { + Self { + test: SmallDeque::new(), + expected: SmallDeque::new(), + trusted_len, + } + } + + fn test_extend(&mut self, iter: I) + where + I: Iterator + TrustedLen + Clone, + { + struct BasicIterator(I); + impl Iterator for BasicIterator + where + I: Iterator, + { + type Item = usize; + + fn next(&mut self) -> Option { + self.0.next() + } + } + + if self.trusted_len { + self.test.extend(iter.clone()); + } else { + self.test.extend(BasicIterator(iter.clone())); + } + + for item in iter { + self.expected.push_back(item) + } + + assert_eq!(self.test, self.expected); + } + + fn drain + Clone>(&mut self, range: R) { + self.test.drain(range.clone()); + self.expected.drain(range); + + assert_eq!(self.test, self.expected); + } + + fn clear(&mut self) { + self.test.clear(); + self.expected.clear(); + } + + fn remaining_capacity(&self) -> usize { + self.test.capacity() - self.test.len() + } + } + + let mut tester = SmallDequeTester::new(trusted_len); + + // Initial capacity + tester.test_extend(0..tester.remaining_capacity()); + + // Grow + tester.test_extend(1024..2048); + + // Wrap around + tester.drain(..128); + + tester.test_extend(0..tester.remaining_capacity()); + + // Continue + tester.drain(256..); + tester.test_extend(4096..8196); + + tester.clear(); + + // Start again + tester.test_extend(0..32); + } + + #[test] + fn test_from_array() { + fn test() { + let mut array: [usize; N] = [0; N]; + + for (i, v) in array.iter_mut().enumerate() { + *v = i; + } + + let deq: SmallDeque<_, N> = array.into(); + + for i in 0..N { + assert_eq!(deq[i], i); + } + + assert_eq!(deq.len(), N); + } + test::<0>(); + test::<1>(); + test::<2>(); + test::<32>(); + test::<35>(); + } + + #[test] + fn test_smallvec_from_smalldeque() { + fn create_vec_and_test_convert(capacity: usize, offset: usize, len: usize) { + let mut vd = SmallDeque::<_, 16>::with_capacity(capacity); + for _ in 0..offset { + vd.push_back(0); + vd.pop_front(); + } + vd.extend(0..len); + + let vec: SmallVec<_> = SmallVec::from(vd.clone()); + assert_eq!(vec.len(), vd.len()); + assert!(vec.into_iter().eq(vd)); + } + + // Miri is too slow + let max_pwr = if cfg!(miri) { 5 } else { 7 }; + + for cap_pwr in 0..max_pwr { + // Make capacity as a (2^x)-1, so that the ring size is 2^x + let cap = (2i32.pow(cap_pwr) - 1) as usize; + + // In these cases there is enough free space to solve it with copies + for len in 0..cap.div_ceil(2) { + // Test contiguous cases + for offset in 0..(cap - len) { + create_vec_and_test_convert(cap, offset, len) + } + + // Test cases where block at end of buffer is bigger than block at start + for offset in (cap - len)..(cap - (len / 2)) { + create_vec_and_test_convert(cap, offset, len) + } + + // Test cases where block at start of buffer is bigger than block at end + for offset in (cap - (len / 2))..cap { + create_vec_and_test_convert(cap, offset, len) + } + } + + // Now there's not (necessarily) space to straighten the ring with simple copies, + // the ring will use swapping when: + // (cap + 1 - offset) > (cap + 1 - len) && (len - (cap + 1 - offset)) > (cap + 1 - len)) + // right block size > free space && left block size > free space + for len in cap.div_ceil(2)..cap { + // Test contiguous cases + for offset in 0..(cap - len) { + create_vec_and_test_convert(cap, offset, len) + } + + // Test cases where block at end of buffer is bigger than block at start + for offset in (cap - len)..(cap - (len / 2)) { + create_vec_and_test_convert(cap, offset, len) + } + + // Test cases where block at start of buffer is bigger than block at end + for offset in (cap - (len / 2))..cap { + create_vec_and_test_convert(cap, offset, len) + } + } + } + } + + #[test] + fn test_clone_from() { + use smallvec::smallvec; + + let m = smallvec![1; 8]; + let n = smallvec![2; 12]; + let limit = if cfg!(miri) { 4 } else { 8 }; // Miri is too slow + for pfv in 0..limit { + for pfu in 0..limit { + for longer in 0..2 { + let (vr, ur) = if longer == 0 { (&m, &n) } else { (&n, &m) }; + let mut v = SmallDeque::<_>::from(vr.clone()); + for _ in 0..pfv { + v.push_front(1); + } + let mut u = SmallDeque::<_>::from(ur.clone()); + for _ in 0..pfu { + u.push_front(2); + } + v.clone_from(&u); + assert_eq!(&v, &u); + } + } + } + } + + #[test] + fn test_vec_deque_truncate_drop() { + static mut DROPS: u32 = 0; + #[derive(Clone)] + struct Elem(#[allow(dead_code)] i32); + impl Drop for Elem { + fn drop(&mut self) { + unsafe { + DROPS += 1; + } + } + } + + let v = vec![Elem(1), Elem(2), Elem(3), Elem(4), Elem(5)]; + for push_front in 0..=v.len() { + let v = v.clone(); + let mut tester = SmallDeque::<_>::with_capacity(5); + for (index, elem) in v.into_iter().enumerate() { + if index < push_front { + tester.push_front(elem); + } else { + tester.push_back(elem); + } + } + assert_eq!(unsafe { DROPS }, 0); + tester.truncate(3); + assert_eq!(unsafe { DROPS }, 2); + tester.truncate(0); + assert_eq!(unsafe { DROPS }, 5); + unsafe { + DROPS = 0; + } + } + } + + #[test] + fn issue_53529() { + let mut dst = SmallDeque::<_>::new(); + dst.push_front(Box::new(1)); + dst.push_front(Box::new(2)); + assert_eq!(*dst.pop_back().unwrap(), 1); + + let mut src = SmallDeque::<_>::new(); + src.push_front(Box::new(2)); + dst.append(&mut src); + for a in dst { + assert_eq!(*a, 2); + } + } + + #[test] + fn issue_80303() { + use core::{ + hash::{Hash, Hasher}, + iter, + num::Wrapping, + }; + + // This is a valid, albeit rather bad hash function implementation. + struct SimpleHasher(Wrapping); + + impl Hasher for SimpleHasher { + fn finish(&self) -> u64 { + self.0 .0 + } + + fn write(&mut self, bytes: &[u8]) { + // This particular implementation hashes value 24 in addition to bytes. + // Such an implementation is valid as Hasher only guarantees equivalence + // for the exact same set of calls to its methods. + for &v in iter::once(&24).chain(bytes) { + self.0 = Wrapping(31) * self.0 + Wrapping(u64::from(v)); + } + } + } + + fn hash_code(value: impl Hash) -> u64 { + let mut hasher = SimpleHasher(Wrapping(1)); + value.hash(&mut hasher); + hasher.finish() + } + + // This creates two deques for which values returned by as_slices + // method differ. + let vda: SmallDeque = (0..10).collect(); + let mut vdb = SmallDeque::with_capacity(10); + vdb.extend(5..10); + (0..5).rev().for_each(|elem| vdb.push_front(elem)); + assert_ne!(vda.as_slices(), vdb.as_slices()); + assert_eq!(vda, vdb); + assert_eq!(hash_code(vda), hash_code(vdb)); + } +} diff --git a/hir/src/adt/smallmap.rs b/hir/src/adt/smallmap.rs index d3f4afff6..b1f294a00 100644 --- a/hir/src/adt/smallmap.rs +++ b/hir/src/adt/smallmap.rs @@ -7,7 +7,7 @@ use core::{ use smallvec::SmallVec; -/// [SmallMap] is a [BTreeMap]-like structure that can store a specified number +/// [SmallOrdMap] is a [BTreeMap]-like structure that can store a specified number /// of elements inline (i.e. on the stack) without allocating memory from the heap. /// /// This data structure is designed with two goals in mind: @@ -21,18 +21,103 @@ use smallvec::SmallVec; /// when all of the data is stored inline, but may not be a good fit for all use cases. /// /// Due to its design constraints, it only supports keys which implement [Ord]. -pub struct SmallMap { +pub type SmallOrdMap = SmallMap; + +/// [SmallDenseMap] is a [HashMap]-like structure that can store a specified number of elements inline +/// (i.e. on the stack) without allocating memory from the heap. +/// +/// This data structure is designed with two goals in mind: +/// +/// * Support efficient key/value operations over a small set of keys +/// * Preserve the insertion order of keys +/// * Avoid allocating data on the heap for the typical case +/// +/// Internally, [SmallDenseMap] is implemented on top of [SmallVec], and uses linear search to locate +/// elements. This is quite efficient for small numbers of keys, and is particularly fast when all +/// of the data is stored inline, but may not be a good fit for all use cases. +/// +/// Due to its design constraints, it only supports keys which implement [Eq]. +pub type SmallDenseMap = SmallMap; + +/// Marker type for Ord-based maps +#[derive(Copy, Clone, Default)] +#[doc(hidden)] +pub struct Ordered; + +/// Marker type for Eq-based maps +#[derive(Copy, Clone, Default)] +#[doc(hidden)] +pub struct Unordered; + +pub struct SmallMap { items: SmallVec<[KeyValuePair; N]>, + finder: T, +} + +pub trait SmallMapKind { + fn find(items: &[KeyValuePair], item: &Q) -> Result; } -impl SmallMap + +impl SmallMapKind for Unordered where - K: Ord, + K: Eq + Borrow, + Q: Eq + ?Sized, { + fn find(items: &[KeyValuePair], item: &Q) -> Result { + match items.iter().position(|kv| kv.key.borrow() == item) { + None => Err(items.len()), + Some(pos) => Ok(pos), + } + } +} + +impl SmallMapKind for Ordered +where + K: Ord + Borrow, + Q: Ord + ?Sized, +{ + fn find(items: &[KeyValuePair], item: &Q) -> Result { + items.binary_search_by(|probe| Ord::cmp(probe.key.borrow(), item)) + } +} + +impl SmallMap { + /// Returns a new, empty [SmallMap] + pub fn new() -> Self { + Self { + items: SmallVec::new_const(), + finder: Unordered, + } + } + + /// Returns a new, empty [SmallMap], with capacity for `capacity` nodes without reallocating. + pub fn with_capacity(capacity: usize) -> Self { + Self { + items: SmallVec::with_capacity(capacity), + finder: Unordered, + } + } +} + +impl SmallMap { /// Returns a new, empty [SmallMap] pub fn new() -> Self { - Self::default() + Self { + items: SmallVec::new_const(), + finder: Ordered, + } } + /// Returns a new, empty [SmallMap], with capacity for `capacity` nodes without reallocating. + pub fn with_capacity(capacity: usize) -> Self { + Self { + items: SmallVec::with_capacity(capacity), + finder: Ordered, + } + } +} + +impl SmallMap { /// Returns true if this map is empty pub fn is_empty(&self) -> bool { self.items.is_empty() @@ -53,11 +138,19 @@ where self.items.iter_mut().map(|pair| (&pair.key, &mut pair.value)) } + /// Clear the content of the map + pub fn clear(&mut self) { + self.items.clear(); + } +} + +impl SmallMap { /// Returns true if `key` has been inserted in this map - pub fn contains(&self, key: &Q) -> bool + pub fn contains_key(&self, key: &Q) -> bool where + T: SmallMapKind, K: Borrow, - Q: Ord + ?Sized, + Q: ?Sized, { self.find(key).is_ok() } @@ -65,8 +158,9 @@ where /// Returns the value under `key` in this map, if it exists pub fn get(&self, key: &Q) -> Option<&V> where + T: SmallMapKind, K: Borrow, - Q: Ord + ?Sized, + Q: ?Sized, { match self.find(key) { Ok(idx) => Some(&self.items[idx].value), @@ -77,8 +171,9 @@ where /// Returns a mutable reference to the value under `key` in this map, if it exists pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> where + T: SmallMapKind, K: Borrow, - Q: Ord + ?Sized, + Q: ?Sized, { match self.find(key) { Ok(idx) => Some(&mut self.items[idx].value), @@ -86,7 +181,35 @@ where } } - /// Inserts a new entry in this map using `key` and `value`. + /// Removes the value inserted under `key`, if it exists + pub fn remove(&mut self, key: &Q) -> Option + where + T: SmallMapKind, + K: Borrow, + Q: ?Sized, + { + match self.find(key) { + Ok(idx) => Some(self.items.remove(idx).value), + Err(_) => None, + } + } + + #[inline(always)] + fn find(&self, item: &Q) -> Result + where + T: SmallMapKind, + K: Borrow, + Q: ?Sized, + { + >::find(&self.items, item) + } +} + +impl SmallMap +where + T: SmallMapKind, +{ + /// Inserts or updates the entry in this map for `key` with `value`. /// /// Returns the previous value, if `key` was already present in the map. pub fn insert(&mut self, key: K, value: V) -> Option { @@ -99,54 +222,45 @@ where } } - /// Removes the value inserted under `key`, if it exists - pub fn remove(&mut self, key: &Q) -> Option - where - K: Borrow, - Q: Ord + ?Sized, - { - match self.find(key) { - Ok(idx) => Some(self.items.remove(idx).value), - Err(_) => None, + /// Inserts a new entry in this map for `key` and `value`. + /// + /// Returns `true` if `key` was not yet present in the map, otherwise `false` + pub fn insert_new(&mut self, key: K, value: V) -> bool { + match self.entry(key) { + Entry::Occupied(_) => false, + Entry::Vacant(entry) => { + entry.insert(value); + true + } } } - /// Clear the content of the map - pub fn clear(&mut self) { - self.items.clear(); - } - /// Returns an [Entry] which can be used to combine `contains`+`insert` type operations. - pub fn entry(&mut self, key: K) -> Entry<'_, K, V, N> { + pub fn entry(&mut self, key: K) -> Entry<'_, K, V, T, N> { match self.find(&key) { Ok(idx) => Entry::occupied(self, idx), Err(idx) => Entry::vacant(self, idx, key), } } - - #[inline] - fn find(&self, item: &Q) -> Result - where - K: Borrow, - Q: Ord + ?Sized, - { - self.items.binary_search_by(|probe| Ord::cmp(probe.key.borrow(), item)) - } } -impl Default for SmallMap { + +impl Default for SmallMap { fn default() -> Self { Self { items: Default::default(), + finder: T::default(), } } } -impl Eq for SmallMap + +impl Eq for SmallMap where K: Eq, V: Eq, { } -impl PartialEq for SmallMap + +impl PartialEq for SmallMap where K: PartialEq, V: PartialEq, @@ -159,7 +273,8 @@ where .eq(other.items.iter().map(|pair| (&pair.key, &pair.value))) } } -impl fmt::Debug for SmallMap + +impl fmt::Debug for SmallMap where K: fmt::Debug + Ord, V: fmt::Debug, @@ -170,8 +285,10 @@ where .finish() } } -impl Clone for SmallMap + +impl Clone for SmallMap where + T: Copy, K: Clone, V: Clone, { @@ -179,13 +296,12 @@ where fn clone(&self) -> Self { Self { items: self.items.clone(), + finder: self.finder, } } } -impl IntoIterator for SmallMap -where - K: Ord, -{ + +impl IntoIterator for SmallMap { type IntoIter = SmallMapIntoIter; type Item = (K, V); @@ -196,9 +312,10 @@ where } } } -impl FromIterator<(K, V)> for SmallMap + +impl FromIterator<(K, V)> for SmallMap where - K: Ord, + T: Default + SmallMapKind, { fn from_iter(iter: I) -> Self where @@ -211,10 +328,11 @@ where map } } -impl Index<&Q> for SmallMap +impl Index<&Q> for SmallMap where - K: Borrow + Ord, - Q: Ord + ?Sized, + T: SmallMapKind, + K: Borrow, + Q: ?Sized, { type Output = V; @@ -222,10 +340,11 @@ where self.get(key).unwrap() } } -impl IndexMut<&Q> for SmallMap +impl IndexMut<&Q> for SmallMap where - K: Borrow + Ord, - Q: Ord + ?Sized, + T: SmallMapKind, + K: Borrow, + Q: ?Sized, { fn index_mut(&mut self, key: &Q) -> &mut Self::Output { self.get_mut(key).unwrap() @@ -261,8 +380,8 @@ impl Iterator for SmallMapIntoIter { } #[inline] - fn last(self) -> Option<(K, V)> { - self.iter.last().map(|pair| (pair.key, pair.value)) + fn last(mut self) -> Option<(K, V)> { + self.iter.next_back().map(|pair| (pair.key, pair.value)) } #[inline] @@ -283,16 +402,27 @@ impl DoubleEndedIterator for SmallMapIntoIter { } /// Represents an key/value pair entry in a [SmallMap] -pub enum Entry<'a, K, V, const N: usize> { - Occupied(OccupiedEntry<'a, K, V, N>), - Vacant(VacantEntry<'a, K, V, N>), +pub enum Entry<'a, K, V, T, const N: usize> { + Occupied(OccupiedEntry<'a, K, V, T, N>), + Vacant(VacantEntry<'a, K, V, T, N>), +} +impl<'a, K, V: Default, T, const N: usize> Entry<'a, K, V, T, N> { + pub fn or_default(self) -> &'a mut V { + match self { + Self::Occupied(entry) => entry.into_mut(), + Self::Vacant(entry) => entry.insert(Default::default()), + } + } } -impl<'a, K, V, const N: usize> Entry<'a, K, V, N> { - fn occupied(map: &'a mut SmallMap, idx: usize) -> Self { +impl<'a, K, V, T, const N: usize> Entry<'a, K, V, T, N> +where + T: SmallMapKind, +{ + fn occupied(map: &'a mut SmallMap, idx: usize) -> Self { Self::Occupied(OccupiedEntry { map, idx }) } - fn vacant(map: &'a mut SmallMap, idx: usize, key: K) -> Self { + fn vacant(map: &'a mut SmallMap, idx: usize, key: K) -> Self { Self::Vacant(VacantEntry { map, idx, key }) } @@ -335,11 +465,11 @@ impl<'a, K, V, const N: usize> Entry<'a, K, V, N> { } /// Represents an occupied entry in a [SmallMap] -pub struct OccupiedEntry<'a, K, V, const N: usize> { - map: &'a mut SmallMap, +pub struct OccupiedEntry<'a, K, V, T, const N: usize> { + map: &'a mut SmallMap, idx: usize, } -impl<'a, K, V, const N: usize> OccupiedEntry<'a, K, V, N> { +impl<'a, K, V, T, const N: usize> OccupiedEntry<'a, K, V, T, N> { #[inline(always)] fn get_entry(&self) -> &KeyValuePair { &self.map.items[self.idx] @@ -367,12 +497,12 @@ impl<'a, K, V, const N: usize> OccupiedEntry<'a, K, V, N> { } /// Represents a vacant entry in a [SmallMap] -pub struct VacantEntry<'a, K, V, const N: usize> { - map: &'a mut SmallMap, +pub struct VacantEntry<'a, K, V, T, const N: usize> { + map: &'a mut SmallMap, idx: usize, key: K, } -impl<'a, K, V, const N: usize> VacantEntry<'a, K, V, N> { +impl<'a, K, V, T, const N: usize> VacantEntry<'a, K, V, T, N> { pub fn key(&self) -> &K { &self.key } @@ -407,7 +537,8 @@ impl<'a, K, V, const N: usize> VacantEntry<'a, K, V, N> { } } -struct KeyValuePair { +#[doc(hidden)] +pub struct KeyValuePair { key: K, value: V, } diff --git a/hir/src/adt/smallprio.rs b/hir/src/adt/smallprio.rs new file mode 100644 index 000000000..b5b5258e8 --- /dev/null +++ b/hir/src/adt/smallprio.rs @@ -0,0 +1,167 @@ +use core::{cmp::Ordering, fmt}; + +use smallvec::SmallVec; + +use super::SmallDeque; + +/// [SmallPriorityQueue] is a priority queue structure that can store a specified number +/// of elements inline (i.e. on the stack) without allocating memory from the heap. +/// +/// Elements in the queue are stored "largest priority first", as determined by the [Ord] +/// implementation of the element type. If you instead wish to have a "lowest priority first" +/// queue, you can use [core::cmp::Reverse] to invert the natural order of the type. +/// +/// It is an exercise for the reader to figure out how to wrap a type with a custom comparator +/// function. Since that isn't particularly needed yet, no built-in support for that is provided. +pub struct SmallPriorityQueue { + pq: SmallDeque, +} +impl fmt::Debug for SmallPriorityQueue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.iter()).finish() + } +} +impl Default for SmallPriorityQueue { + fn default() -> Self { + Self { + pq: Default::default(), + } + } +} +impl Clone for SmallPriorityQueue { + fn clone(&self) -> Self { + Self { + pq: self.pq.clone(), + } + } + + fn clone_from(&mut self, source: &Self) { + self.pq.clone_from(&source.pq); + } +} + +impl SmallPriorityQueue { + /// Returns true if this map is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.pq.is_empty() + } + + /// Returns the number of key/value pairs in this map + #[inline] + pub fn len(&self) -> usize { + self.pq.len() + } + + /// Pop the highest priority item from the queue + #[inline] + pub fn pop(&mut self) -> Option { + self.pq.pop_front() + } + + /// Pop the lowest priority item from the queue + #[inline] + pub fn pop_last(&mut self) -> Option { + self.pq.pop_back() + } + + /// Get a reference to the next highest priority item in the queue + #[inline] + pub fn front(&self) -> Option<&T> { + self.pq.front() + } + + /// Get a reference to the lowest highest priority item in the queue + #[inline] + pub fn back(&self) -> Option<&T> { + self.pq.back() + } + + /// Get a front-to-back iterator over the items in the queue + #[inline] + pub fn iter(&self) -> super::smalldeque::Iter<'_, T> { + self.pq.iter() + } +} + +impl SmallPriorityQueue +where + T: Ord, +{ + /// Returns a new, empty [SmallPriorityQueue] + pub const fn new() -> Self { + Self { + pq: SmallDeque::new(), + } + } + + /// Push an item on the queue. + /// + /// If the item's priority is equal to, or greater than any other item in the queue, the newly + /// pushed item will be placed at the front of the queue. Otherwise, the item is placed in the + /// queue at the next slot where it's priority is at least the same as the next value in the + /// queue at that slot. + pub fn push(&mut self, item: T) { + if let Some(head) = self.pq.front() { + match head.cmp(&item) { + Ordering::Greater => self.push_slow(item), + Ordering::Equal | Ordering::Less => { + // Push to the front for efficiency + self.pq.push_front(item); + } + } + } else { + self.pq.push_back(item); + } + } + + /// Push an item on the queue, by conducting a search for the most appropriate index at which + /// to insert the new item, based upon a comparator function that compares the priorities of + /// the items. + fn push_slow(&mut self, item: T) { + match self.pq.binary_search_by(|probe| probe.cmp(&item)) { + Ok(index) => { + self.pq.insert(index, item); + } + Err(index) => { + self.pq.insert(index, item); + } + } + } +} + +impl IntoIterator for SmallPriorityQueue { + type IntoIter = super::smalldeque::IntoIter; + type Item = T; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.pq.into_iter() + } +} + +impl FromIterator for SmallPriorityQueue +where + T: Ord, +{ + #[inline] + fn from_iter>(iter: I) -> Self { + let mut pq = SmallDeque::from_iter(iter); + + let items = pq.make_contiguous(); + + items.sort(); + + Self { pq } + } +} + +impl From> for SmallPriorityQueue { + fn from(mut value: SmallVec<[T; N]>) -> Self { + value.sort(); + + Self { + pq: SmallDeque::from(value), + } + } +} diff --git a/hir/src/adt/smallset.rs b/hir/src/adt/smallset.rs index 73c4aabc5..1b9be316e 100644 --- a/hir/src/adt/smallset.rs +++ b/hir/src/adt/smallset.rs @@ -177,6 +177,15 @@ where self.items } + /// Drain items from the set + #[inline] + pub fn drain( + &mut self, + range: impl core::ops::RangeBounds, + ) -> impl Iterator + '_ { + self.items.drain(range) + } + #[inline] fn find(&self, item: &Q) -> Option where @@ -192,7 +201,7 @@ where T: Clone + Eq, { /// Obtain a new [SmallSet] containing the unique elements of both `self` and `other` - pub fn union(&self, other: &Self) -> Self { + pub fn union(&self, other: &SmallSet) -> Self { let mut result = self.clone(); for item in other.items.iter() { @@ -206,7 +215,7 @@ where } /// Obtain a new [SmallSet] containing the unique elements of both `self` and `other` - pub fn into_union(mut self, other: &Self) -> Self { + pub fn into_union(mut self, other: &SmallSet) -> Self { for item in other.items.iter() { if self.contains(item) { continue; @@ -218,7 +227,7 @@ where } /// Obtain a new [SmallSet] containing the elements in common between `self` and `other` - pub fn intersection(&self, other: &Self) -> Self { + pub fn intersection(&self, other: &SmallSet) -> Self { let mut result = Self::default(); for item in self.items.iter() { @@ -231,7 +240,7 @@ where } /// Obtain a new [SmallSet] containing the elements in common between `self` and `other` - pub fn into_intersection(self, other: &Self) -> Self { + pub fn into_intersection(self, other: &SmallSet) -> Self { let mut result = Self::default(); for item in self.items.into_iter() { @@ -244,7 +253,7 @@ where } /// Obtain a new [SmallSet] containing the elements in `self` but not in `other` - pub fn difference(&self, other: &Self) -> Self { + pub fn difference(&self, other: &SmallSet) -> Self { let mut result = Self::default(); for item in self.items.iter() { @@ -258,14 +267,14 @@ where } /// Obtain a new [SmallSet] containing the elements in `self` but not in `other` - pub fn into_difference(mut self, other: &Self) -> Self { + pub fn into_difference(mut self, other: &SmallSet) -> Self { Self { items: self.items.drain_filter(|item| !other.contains(item)).collect(), } } /// Obtain a new [SmallSet] containing the elements in `self` or `other`, but not in both - pub fn symmetric_difference(&self, other: &Self) -> Self { + pub fn symmetric_difference(&self, other: &SmallSet) -> Self { let mut result = Self::default(); for item in self.items.iter() { diff --git a/hir/src/adt/sparsemap.rs b/hir/src/adt/sparsemap.rs deleted file mode 100644 index c5f7ca961..000000000 --- a/hir/src/adt/sparsemap.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! This module is based on [cranelift_entity::SparseMap], but implemented in-tree -//! because the SparseMapValueTrait is not implemented for any standard library types -use cranelift_entity::{EntityRef, SecondaryMap}; - -pub trait SparseMapValue { - fn key(&self) -> K; -} -impl> SparseMapValue for Box { - fn key(&self) -> K { - (**self).key() - } -} -impl> SparseMapValue for std::rc::Rc { - fn key(&self) -> K { - (**self).key() - } -} -impl SparseMapValue for crate::Value { - fn key(&self) -> crate::Value { - *self - } -} -impl SparseMapValue for crate::Inst { - fn key(&self) -> crate::Inst { - *self - } -} -impl SparseMapValue for crate::Block { - fn key(&self) -> crate::Block { - *self - } -} - -/// A sparse mapping of entity references. -/// -/// A `SparseMap` map provides: -/// -/// - Memory usage equivalent to `SecondaryMap` + `Vec`, so much smaller than -/// `SecondaryMap` for sparse mappings of larger `V` types. -/// - Constant time lookup, slightly slower than `SecondaryMap`. -/// - A very fast, constant time `clear()` operation. -/// - Fast insert and erase operations. -/// - Stable iteration that is as fast as a `Vec`. -/// -/// # Compared to `SecondaryMap` -/// -/// When should we use a `SparseMap` instead of a secondary `SecondaryMap`? First of all, -/// `SparseMap` does not provide the functionality of a `PrimaryMap` which can allocate and assign -/// entity references to objects as they are pushed onto the map. It is only the secondary entity -/// maps that can be replaced with a `SparseMap`. -/// -/// - A secondary entity map assigns a default mapping to all keys. It doesn't distinguish between -/// an unmapped key and one that maps to the default value. `SparseMap` does not require `Default` -/// values, and it tracks accurately if a key has been mapped or not. -/// - Iterating over the contents of an `SecondaryMap` is linear in the size of the *key space*, -/// while iterating over a `SparseMap` is linear in the number of elements in the mapping. This is -/// an advantage precisely when the mapping is sparse. -/// - `SparseMap::clear()` is constant time and super-fast. `SecondaryMap::clear()` is linear in the -/// size of the key space. (Or, rather the required `resize()` call following the `clear()` is). -/// - `SparseMap` requires the values to implement `SparseMapValue` which means that they must -/// contain their own key. -pub struct SparseMap -where - K: EntityRef, - V: SparseMapValue, -{ - sparse: SecondaryMap, - dense: Vec, -} -impl Default for SparseMap -where - K: EntityRef, - V: SparseMapValue, -{ - fn default() -> Self { - Self { - sparse: SecondaryMap::new(), - dense: Vec::new(), - } - } -} -impl core::fmt::Debug for SparseMap -where - K: EntityRef + core::fmt::Debug, - V: SparseMapValue + core::fmt::Debug, -{ - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - f.debug_map().entries(self.values().map(|v| (v.key(), v))).finish() - } -} -impl SparseMap -where - K: EntityRef, - V: SparseMapValue, -{ - /// Create a new empty mapping. - #[inline] - pub fn new() -> Self { - Self::default() - } - - /// Returns the number of elements in the map. - pub fn len(&self) -> usize { - self.dense.len() - } - - /// Returns true is the map contains no elements. - pub fn is_empty(&self) -> bool { - self.dense.is_empty() - } - - /// Remove all elements from the mapping. - pub fn clear(&mut self) { - self.dense.clear(); - } - - /// Returns a reference to the value corresponding to the key. - pub fn get(&self, key: K) -> Option<&V> { - if let Some(idx) = self.sparse.get(key).cloned() { - if let Some(entry) = self.dense.get(idx as usize) { - if entry.key() == key { - return Some(entry); - } - } - } - None - } - - /// Returns a mutable reference to the value corresponding to the key. - /// - /// Note that the returned value must not be mutated in a way that would change its key. This - /// would invalidate the sparse set data structure. - pub fn get_mut(&mut self, key: K) -> Option<&mut V> { - if let Some(idx) = self.sparse.get(key).cloned() { - if let Some(entry) = self.dense.get_mut(idx as usize) { - if entry.key() == key { - return Some(entry); - } - } - } - None - } - - /// Return the index into `dense` of the value corresponding to `key`. - fn index(&self, key: K) -> Option { - if let Some(idx) = self.sparse.get(key).cloned() { - let idx = idx as usize; - if let Some(entry) = self.dense.get(idx) { - if entry.key() == key { - return Some(idx); - } - } - } - None - } - - /// Return `true` if the map contains a value corresponding to `key`. - pub fn contains_key(&self, key: K) -> bool { - self.get(key).is_some() - } - - /// Insert a value into the map. - /// - /// If the map did not have this key present, `None` is returned. - /// - /// If the map did have this key present, the value is updated, and the old value is returned. - /// - /// It is not necessary to provide a key since the value knows its own key already. - pub fn insert(&mut self, value: V) -> Option { - let key = value.key(); - - // Replace the existing entry for `key` if there is one. - if let Some(entry) = self.get_mut(key) { - return Some(core::mem::replace(entry, value)); - } - - // There was no previous entry for `key`. Add it to the end of `dense`. - let idx = self.dense.len(); - debug_assert!(idx <= u32::MAX as usize, "SparseMap overflow"); - self.dense.push(value); - self.sparse[key] = idx as u32; - None - } - - /// Remove a value from the map and return it. - pub fn remove(&mut self, key: K) -> Option { - if let Some(idx) = self.index(key) { - let back = self.dense.pop().unwrap(); - - // Are we popping the back of `dense`? - if idx == self.dense.len() { - return Some(back); - } - - // We're removing an element from the middle of `dense`. - // Replace the element at `idx` with the back of `dense`. - // Repair `sparse` first. - self.sparse[back.key()] = idx as u32; - return Some(core::mem::replace(&mut self.dense[idx], back)); - } - - // Nothing to remove. - None - } - - /// Remove the last value from the map. - pub fn pop(&mut self) -> Option { - self.dense.pop() - } - - /// Get an iterator over the values in the map. - /// - /// The iteration order is entirely determined by the preceding sequence of `insert` and - /// `remove` operations. In particular, if no elements were removed, this is the insertion - /// order. - pub fn values(&self) -> core::slice::Iter { - self.dense.iter() - } - - /// Get the values as a slice. - pub fn as_slice(&self) -> &[V] { - self.dense.as_slice() - } -} - -/// Iterating over the elements of a set. -impl<'a, K, V> IntoIterator for &'a SparseMap -where - K: EntityRef, - V: SparseMapValue, -{ - type IntoIter = core::slice::Iter<'a, V>; - type Item = &'a V; - - fn into_iter(self) -> Self::IntoIter { - self.values() - } -} diff --git a/hir/src/any.rs b/hir/src/any.rs new file mode 100644 index 000000000..c8c8187b3 --- /dev/null +++ b/hir/src/any.rs @@ -0,0 +1,282 @@ +use alloc::boxed::Box; +use core::{any::Any, marker::Unsize}; + +pub trait AsAny: Any + Unsize { + #[inline] + fn type_name(&self) -> &'static str { + core::any::type_name::() + } + + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self + } + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + #[inline(always)] + fn into_any(self: Box) -> Box { + self + } +} + +impl> AsAny for T {} + +/// # Safety +/// +/// This trait is not safe (or possible) to implement manually. +/// +/// It is automatically derived for all `T: Unsize` +pub unsafe trait Is: Unsize {} +unsafe impl Is for T where T: ?Sized + Unsize {} + +pub trait IsObjOf {} +impl IsObjOf for Trait +where + T: ?Sized + Is, + Trait: ?Sized, +{ +} + +#[allow(unused)] +pub trait DowncastRef: IsObjOf { + fn downcast_ref(&self) -> Option<&To>; + fn downcast_mut(&mut self) -> Option<&mut To>; +} +impl DowncastRef for From +where + From: ?Sized, + To: ?Sized + DowncastFromRef, +{ + #[inline(always)] + fn downcast_ref(&self) -> Option<&To> { + To::downcast_from_ref(self) + } + + #[inline(always)] + fn downcast_mut(&mut self) -> Option<&mut To> { + To::downcast_from_mut(self) + } +} + +pub trait DowncastFromRef: Is { + fn downcast_from_ref(from: &From) -> Option<&Self>; + fn downcast_from_mut(from: &mut From) -> Option<&mut Self>; +} +impl DowncastFromRef for To +where + From: ?Sized + AsAny, + To: Is + 'static, +{ + #[inline] + fn downcast_from_ref(from: &From) -> Option<&Self> { + from.as_any().downcast_ref() + } + + #[inline] + fn downcast_from_mut(from: &mut From) -> Option<&mut Self> { + from.as_any_mut().downcast_mut() + } +} + +#[allow(unused)] +pub trait Downcast: DowncastRef + Is { + fn downcast(self: Box) -> Result, Box>; +} +impl Downcast for From +where + From: ?Sized + DowncastRef + Is, + To: ?Sized + DowncastFrom, + Obj: ?Sized, +{ + #[inline] + fn downcast(self: Box) -> Result, Box> { + To::downcast_from(self) + } +} + +pub trait DowncastFrom: DowncastFromRef +where + From: ?Sized + Is, + Obj: ?Sized, +{ + #[allow(dead_code)] + fn downcast_from(from: Box) -> Result, Box>; +} +impl DowncastFrom for To +where + From: ?Sized + Is + AsAny + 'static, + To: DowncastFromRef + 'static, + Obj: ?Sized, +{ + fn downcast_from(from: Box) -> Result, Box> { + if !from.as_any().is::() { + Ok(from.into_any().downcast().unwrap()) + } else { + Err(from) + } + } +} + +pub trait Upcast: TryUpcastRef { + #[allow(dead_code)] + fn upcast_ref(&self) -> &To; + #[allow(dead_code)] + fn upcast_mut(&mut self) -> &mut To; + #[allow(dead_code)] + fn upcast(self: Box) -> Box; +} +impl Upcast for From +where + From: ?Sized + Is, + To: ?Sized, +{ + #[inline(always)] + fn upcast_ref(&self) -> &To { + self + } + + #[inline(always)] + fn upcast_mut(&mut self) -> &mut To { + self + } + + #[inline(always)] + fn upcast(self: Box) -> Box { + self + } +} + +pub trait TryUpcastRef: Is +where + To: ?Sized, +{ + #[allow(unused)] + fn is_of(&self) -> bool; + #[allow(dead_code)] + fn try_upcast_ref(&self) -> Option<&To>; + #[allow(dead_code)] + fn try_upcast_mut(&mut self) -> Option<&mut To>; +} +impl TryUpcastRef for From +where + From: Upcast + ?Sized, + To: ?Sized, +{ + #[inline(always)] + fn is_of(&self) -> bool { + true + } + + #[inline(always)] + fn try_upcast_ref(&self) -> Option<&To> { + Some(self.upcast_ref()) + } + + #[inline(always)] + fn try_upcast_mut(&mut self) -> Option<&mut To> { + Some(self.upcast_mut()) + } +} + +pub trait TryUpcast: Is + TryUpcastRef +where + To: ?Sized, + Obj: ?Sized, +{ + #[allow(dead_code)] + #[inline(always)] + fn try_upcast(self: Box) -> Result, Box> { + Err(self) + } +} +impl TryUpcast for From +where + From: Is + Upcast + ?Sized, + To: ?Sized, + Obj: ?Sized, +{ + #[inline] + fn try_upcast(self: Box) -> Result, Box> { + Ok(self.upcast()) + } +} + +/// Upcasts a type into a trait object +#[allow(unused)] +pub trait UpcastFrom +where + From: ?Sized, +{ + fn upcast_from_ref(from: &From) -> &Self; + fn upcast_from_mut(from: &mut From) -> &mut Self; + fn upcast_from(from: Box) -> Box; +} + +impl UpcastFrom for To +where + From: Upcast + ?Sized, + To: ?Sized, +{ + #[inline] + fn upcast_from_ref(from: &From) -> &Self { + from.upcast_ref() + } + + #[inline] + fn upcast_from_mut(from: &mut From) -> &mut Self { + from.upcast_mut() + } + + #[inline] + fn upcast_from(from: Box) -> Box { + from.upcast() + } +} + +#[allow(unused)] +pub trait TryUpcastFromRef: IsObjOf +where + From: ?Sized, +{ + fn try_upcast_from_ref(from: &From) -> Option<&Self>; + fn try_upcast_from_mut(from: &mut From) -> Option<&mut Self>; +} + +impl TryUpcastFromRef for To +where + From: TryUpcastRef + ?Sized, + To: ?Sized, +{ + #[inline] + fn try_upcast_from_ref(from: &From) -> Option<&Self> { + from.try_upcast_ref() + } + + #[inline] + fn try_upcast_from_mut(from: &mut From) -> Option<&mut Self> { + from.try_upcast_mut() + } +} + +#[allow(unused)] +pub trait TryUpcastFrom: TryUpcastFromRef +where + From: Is + ?Sized, + Obj: ?Sized, +{ + fn try_upcast_from(from: Box) -> Result, Box>; +} + +impl TryUpcastFrom for To +where + From: TryUpcast + ?Sized, + To: ?Sized, + Obj: ?Sized, +{ + #[inline] + fn try_upcast_from(from: Box) -> Result, Box> { + from.try_upcast() + } +} diff --git a/hir/src/asm/builder.rs b/hir/src/asm/builder.rs deleted file mode 100644 index 103d9c131..000000000 --- a/hir/src/asm/builder.rs +++ /dev/null @@ -1,2248 +0,0 @@ -use smallvec::smallvec; - -use super::*; -use crate::{ - diagnostics::{SourceSpan, Span}, - CallConv, Felt, FunctionIdent, Inst, InstBuilder, Instruction, Overflow, Value, -}; - -/// Used to construct an [InlineAsm] instruction, while checking the input/output types, -/// and enforcing various safety invariants. -pub struct MasmBuilder { - /// The [InstBuilderBase] which we are building from - builder: B, - /// The span of the resulting inline assembly block - span: SourceSpan, - /// The inline assembly block we're building - asm: InlineAsm, - /// The current code block in the inline assembly that the builder is inserting into - current_block: MasmBlockId, - /// The emulated operand stack, primarily used to track the number of stack elements - /// upon entry and exit from the inline assembly block. - /// - /// The only `Type` which is represented on this stack is `Type::Felt`, since we're only - /// interested in the number of stack elements at any given point. In the future, we may - /// choose to do additional type checking. - /// - /// Upon exit from the inline assembly block, the state of the stack must have enough elements - /// to store a value of the expected result type, represented by `ty`. Whether those elements - /// actually store a valid value of that type is beyond the scope of this builder, for now. - stack: OperandStack, -} -impl<'f, B: InstBuilder<'f>> MasmBuilder { - /// Construct a new inline assembly builder in the function represented by `dfg`, to be inserted - /// at `ip`. - /// - /// The `args` list represents the arguments which will be visible on the operand stack in this - /// inline assembly block. - /// - /// The `results` set represents the types that are expected to be found on the operand stack - /// when the inline assembly block finishes executing. Use an empty set to represent no - /// results. - /// - /// Any attempt to modify the operand stack beyond what is made visible via arguments, or - /// introduced within the inline assembly block, will cause an assertion to fail. - pub fn new(mut builder: B, args: &[Value], results: Vec, span: SourceSpan) -> Self { - // Construct the initial operand stack with the given arguments - let mut stack = OperandStack::::default(); - { - let dfg = builder.data_flow_graph(); - for arg in args.iter().rev().copied() { - stack.push(dfg.value_type(arg).clone()); - } - } - - // Construct an empty inline assembly block with the given arguments - let mut asm = InlineAsm::new(results); - { - let dfg = builder.data_flow_graph_mut(); - let mut vlist = ValueList::default(); - vlist.extend(args.iter().copied(), &mut dfg.value_lists); - asm.args = vlist; - } - - let current_block = asm.body; - Self { - builder, - span, - asm, - current_block, - stack, - } - } - - /// Create a new, empty MASM code block, for use with control flow instructions - #[inline] - pub fn create_block(&mut self) -> MasmBlockId { - self.asm.create_block() - } - - /// Change the insertion point of the builder to the end of `block` - #[inline(always)] - pub fn switch_to_block(&mut self, block: MasmBlockId) { - self.current_block = block; - } - - /// Get a builder for a single MASM instruction - pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { - MasmOpBuilder { - dfg: self.builder.data_flow_graph_mut(), - asm: &mut self.asm, - stack: &mut self.stack, - ip: self.current_block, - } - } - - /// Finalize this inline assembly block, inserting it into the `Function` from which this - /// builder was derived. - /// - /// Returns the [Inst] which corresponds to the inline assembly instruction, and the inner - /// [DataFlowGraph] reference held by the underlying [InstBuilderBase]. - pub fn build(self) -> Inst { - if self.asm.results.is_empty() { - assert!( - self.stack.is_empty(), - "invalid inline assembly: expected operand stack to be empty upon exit, found: \ - {:?}", - self.stack.debug() - ); - } else { - let mut len = 0; - for ty in self.asm.results.iter() { - len += ty.size_in_felts(); - } - assert_eq!( - len, - self.stack.len(), - "invalid inline assembly: needed {} elements on the operand stack, found: {:?}", - len, - self.stack.debug() - ); - } - - let span = self.span; - let data = Instruction::InlineAsm(self.asm); - self.builder.build(data, Type::Unit, span).0 - } -} - -/// Used to construct a single MASM opcode -pub struct MasmOpBuilder<'a> { - dfg: &'a mut DataFlowGraph, - /// The inline assembly block being created - asm: &'a mut InlineAsm, - /// The state of the operand stack at this point in the program - stack: &'a mut OperandStack, - /// The block to which this builder should append the instruction it builds - ip: MasmBlockId, -} -impl<'a> MasmOpBuilder<'a> { - /// Pads the stack with four zero elements - pub fn padw(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Padw, span); - } - - /// Pushes an element on the stack - pub fn push(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::Push(imm), span); - } - - /// Pushes a word on the stack - pub fn pushw(mut self, word: [Felt; 4], span: SourceSpan) { - self.build(self.ip, MasmOp::Pushw(word), span); - } - - /// Pushes an element representing an unsigned 8-bit integer on the stack - pub fn push_u8(mut self, imm: u8, span: SourceSpan) { - self.build(self.ip, MasmOp::PushU8(imm), span); - } - - /// Pushes an element representing an unsigned 16-bit integer on the stack - pub fn push_u16(mut self, imm: u16, span: SourceSpan) { - self.build(self.ip, MasmOp::PushU16(imm), span); - } - - /// Pushes an element representing an unsigned 32-bit integer on the stack - pub fn push_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::PushU32(imm), span); - } - - /// Drops the element on the top of the stack - pub fn drop(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Drop, span); - } - - /// Drops the word (first four elements) on the top of the stack - pub fn dropw(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Dropw, span); - } - - /// Duplicates the `n`th element from the top of the stack, to the top of the stack - /// - /// A `n` of zero, duplicates the element on top of the stack - /// - /// The valid range for `n` is 0..=15 - pub fn dup(mut self, n: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Dup(n as u8), span); - } - - /// Duplicates the `n`th word from the top of the stack, to the top of the stack - /// - /// A `n` of zero, duplicates the word on top of the stack - /// - /// The valid range for `n` is 0..=3 - pub fn dupw(mut self, n: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Dupw(n as u8), span); - } - - /// Swaps the `n`th element and the element on top of the stack - /// - /// The valid range for `n` is 1..=15 - pub fn swap(mut self, n: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Swap(n as u8), span); - } - - /// Swaps the `n`th word and the word on top of the stack - /// - /// The valid range for `n` is 1..=3 - pub fn swapw(mut self, n: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Swapw(n as u8), span); - } - - /// Swaps the top 2 and bottom 2 words on the stack - pub fn swapdw(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Swapdw, span); - } - - /// Moves the `n`th element to the top of the stack - /// - /// The valid range for `n` is 2..=15 - pub fn movup(mut self, idx: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Movup(idx as u8), span); - } - - /// Moves the `n`th word to the top of the stack - /// - /// The valid range for `n` is 2..=3 - pub fn movupw(mut self, idx: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Movupw(idx as u8), span); - } - - /// Moves the element on top of the stack, making it the `n`th element - /// - /// The valid range for `n` is 2..=15 - pub fn movdn(mut self, idx: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Movdn(idx as u8), span); - } - - /// Moves the word on top of the stack, making it the `n`th word - /// - /// The valid range for `n` is 2..=3 - pub fn movdnw(mut self, idx: usize, span: SourceSpan) { - self.build(self.ip, MasmOp::Movdnw(idx as u8), span); - } - - /// Pops a boolean element off the stack, and swaps the top two elements - /// on the stack if that boolean is true. - /// - /// Traps if the conditional is not 0 or 1. - pub fn cswap(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Cswap, span); - } - - /// Pops a boolean element off the stack, and swaps the top two words - /// on the stack if that boolean is true. - /// - /// Traps if the conditional is not 0 or 1. - pub fn cswapw(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Cswapw, span); - } - - /// Pops a boolean element off the stack, and drops the top element on the - /// stack if the boolean is true, otherwise it drops the next element down. - /// - /// Traps if the conditional is not 0 or 1. - pub fn cdrop(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Cdrop, span); - } - - /// Pops a boolean element off the stack, and drops the top word on the - /// stack if the boolean is true, otherwise it drops the next word down. - /// - /// Traps if the conditional is not 0 or 1. - pub fn cdropw(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Cdropw, span); - } - - /// Pops the top element on the stack, and traps if that element is != 1. - pub fn assert(mut self, error_code: Option, span: SourceSpan) { - let op = error_code.map(MasmOp::AssertWithError).unwrap_or(MasmOp::Assert); - self.build(self.ip, op, span); - } - - /// Pops the top element on the stack, and traps if that element is != 0. - pub fn assertz(mut self, error_code: Option, span: SourceSpan) { - let op = error_code.map(MasmOp::AssertzWithError).unwrap_or(MasmOp::Assertz); - self.build(self.ip, op, span); - } - - /// Pops the top two elements on the stack, and traps if they are not equal. - pub fn assert_eq(mut self, error_code: Option, span: SourceSpan) { - let op = error_code.map(MasmOp::AssertEqWithError).unwrap_or(MasmOp::AssertEq); - self.build(self.ip, op, span); - } - - /// Pops the top two words on the stack, and traps if they are not equal. - pub fn assert_eqw(mut self, error_code: Option, span: SourceSpan) { - let op = error_code.map(MasmOp::AssertEqwWithError).unwrap_or(MasmOp::AssertEqw); - self.build(self.ip, op, span); - } - - /// Pops an element containing a memory address from the top of the stack, - /// and loads the first element of the word at that address to the top of the stack. - pub fn load(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::MemLoad, span); - } - - /// Loads the first element of the word at the given address to the top of the stack. - pub fn load_imm(mut self, addr: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::MemLoadImm(addr), span); - } - - /// Pops an element containing a memory address from the top of the stack, - /// and loads the word at that address to the top of the stack. - pub fn loadw(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::MemLoadw, span); - } - - /// Loads the word at the given address to the top of the stack. - pub fn loadw_imm(mut self, addr: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::MemLoadwImm(addr), span); - } - - /// Pops two elements, the first containing a memory address from the top of the stack, - /// the second the value to be stored as the first element of the word at that address. - pub fn store(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::MemStore, span); - } - - /// Pops an element from the top of the stack, and stores it as the first element of - /// the word at the given address. - pub fn store_imm(mut self, addr: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::MemStoreImm(addr), span); - } - - /// Pops an element containing a memory address from the top of the stack, - /// and then pops a word from the stack and stores it as the word at that address. - pub fn storew(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::MemStorew, span); - } - - /// Pops a word from the stack and stores it as the word at the given address. - pub fn storew_imm(mut self, addr: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::MemStorewImm(addr), span); - } - - /// Begins construction of a `if.true` statement. - /// - /// An `if.true` pops a boolean value off the stack, and uses it to choose between - /// one of two branches. The "then" branch is taken if the conditional is true, - /// the "else" branch otherwise. - /// - /// NOTE: This function will panic if the top of the operand stack is not of boolean type - /// when called. - /// - /// You must ensure that both branches of the `if.true` statement leave the operand stack - /// in the same abstract state, so that when control resumes after the `if.true`, the remaining - /// program is well-formed. This will be validated automatically for you, but if validation - /// fails, the builder will panic. - pub fn if_true(self, span: SourceSpan) -> IfTrueBuilder<'a> { - let cond = self.stack.pop().expect("operand stack is empty"); - assert_eq!(cond, Type::I1, "expected while.true condition to be a boolean value"); - let out_stack = self.stack.clone(); - IfTrueBuilder { - dfg: self.dfg, - asm: self.asm, - in_stack: self.stack, - out_stack, - span, - ip: self.ip, - then_blk: None, - else_blk: None, - } - } - - /// Begins construction of a `while.true` loop. - /// - /// A `while.true` pops a boolean value off the stack to use as the condition for - /// entering the loop, and will then execute the loop body for as long as the value - /// on top of the stack is a boolean and true. If the condition is not a boolean, - /// execution traps. - /// - /// NOTE: This function will panic if the top of the operand stack is not of boolean type - /// when called. - /// - /// Before finalizing construction of the loop body, you must ensure two things: - /// - /// 1. There is a value of boolean type on top of the operand stack - /// 2. The abstract state of the operand stack, assuming the boolean just mentioned - /// has been popped, must be consistent with the state of the operand stack when the - /// loop was entered, as well as if the loop was skipped due to the conditional being - /// false. The abstract state referred to here is the number, and type, of the elements - /// on the operand stack. - /// - /// Both of these are validated by [LoopBuilder], and a panic is raised if validation fails. - pub fn while_true(self, span: SourceSpan) -> LoopBuilder<'a> { - let cond = self.stack.pop().expect("operand stack is empty"); - assert_eq!(cond, Type::I1, "expected while.true condition to be a boolean value"); - let out_stack = self.stack.clone(); - let body = self.asm.create_block(); - LoopBuilder { - dfg: self.dfg, - asm: self.asm, - in_stack: self.stack, - out_stack, - span, - ip: self.ip, - body, - style: LoopType::While, - } - } - - /// Begins construction of a `repeat` loop, with an iteration count of `n`. - /// - /// A `repeat` instruction requires no operands on the stack, and will execute the loop body `n` - /// times. - /// - /// NOTE: The iteration count must be non-zero, or this function will panic. - pub fn repeat(self, n: u16, span: SourceSpan) -> LoopBuilder<'a> { - assert!(n > 0, "invalid iteration count for `repeat.n`, must be non-zero"); - let out_stack = self.stack.clone(); - let body = self.asm.create_block(); - LoopBuilder { - dfg: self.dfg, - asm: self.asm, - in_stack: self.stack, - out_stack, - span, - ip: self.ip, - body, - style: LoopType::Repeat(n), - } - } - - /// Executes the named procedure as a regular function. - pub fn exec(mut self, id: FunctionIdent, span: SourceSpan) { - self.build(self.ip, MasmOp::Exec(id), span); - } - - /// Execute a procedure indirectly. - /// - /// Expects the hash of a function's MAST root on the stack, see `procref` - pub fn dynexec(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::DynExec, span) - } - - /// Call a procedure indirectly. - /// - /// Expects the hash of a function's MAST root on the stack, see `procref` - pub fn dyncall(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::DynCall, span) - } - - /// Executes the named procedure as a call. - pub fn call(mut self, id: FunctionIdent, span: SourceSpan) { - self.build(self.ip, MasmOp::Call(id), span); - } - - /// Executes the named procedure as a syscall. - pub fn syscall(mut self, id: FunctionIdent, span: SourceSpan) { - self.build(self.ip, MasmOp::Syscall(id), span); - } - - /// Push the hash of the named function on the stack for use with dyn(exec|call) - pub fn procref(mut self, id: FunctionIdent, span: SourceSpan) { - self.build(self.ip, MasmOp::ProcRef(id), span) - } - - /// Pops two field elements from the stack, adds them, and places the result on the stack. - pub fn add(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Add, span); - } - - /// Pops a field element from the stack, adds the given value to it, and places the result on - /// the stack. - pub fn add_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::AddImm(imm), span); - } - - /// Pops two field elements from the stack, subtracts the second from the first, and places the - /// result on the stack. - pub fn sub(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Sub, span); - } - - /// Pops a field element from the stack, subtracts the given value from it, and places the - /// result on the stack. - pub fn sub_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::SubImm(imm), span); - } - - /// Pops two field elements from the stack, multiplies them, and places the result on the stack. - pub fn mul(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Mul, span); - } - - /// Pops a field element from the stack, multiplies it by the given value, and places the result - /// on the stack. - pub fn mul_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::MulImm(imm), span); - } - - /// Pops two field elements from the stack, divides the first by the second, and places the - /// result on the stack. - pub fn div(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Div, span); - } - - /// Pops a field element from the stack, divides it by the given value, and places the result on - /// the stack. - pub fn div_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::DivImm(imm), span); - } - - /// Negates the field element on top of the stack - pub fn neg(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Neg, span); - } - - /// Replaces the field element on top of the stack with it's multiplicative inverse, i.e. `a^-1 - /// mod p` - pub fn inv(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Inv, span); - } - - /// Increments the field element on top of the stack - pub fn incr(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Incr, span); - } - - /// Pops an element, `a`, from the top of the stack, and places the integral base 2 logarithm - /// of that value on the stack. - /// - /// Traps if `a` is 0. - pub fn ilog2(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Ilog2, span); - } - - /// Pops an element, `a`, from the top of the stack, and places the result of `2^a` on the - /// stack. - /// - /// Traps if `a` is not in the range 0..=63 - pub fn pow2(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Pow2, span); - } - - /// Pops two elements from the stack, `b` and `a` respectively, and places the result of `a^b` - /// on the stack. - /// - /// Traps if `b` is not in the range 0..=63 - pub fn exp(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Exp, span); - } - - /// Pops an element from the stack, `a`, and places the result of `a^b` on the stack, where `b` - /// is the given immediate value. - /// - /// Traps if `b` is not in the range 0..=63 - pub fn exp_imm(mut self, exponent: u8, span: SourceSpan) { - self.build(self.ip, MasmOp::ExpImm(exponent), span); - } - - /// Pops a value off the stack, and applies logical NOT, and places the result back on the - /// stack. - /// - /// Traps if the value is not 0 or 1. - pub fn not(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Not, span); - } - - /// Pops two values off the stack, applies logical AND, and places the result back on the stack. - /// - /// Traps if either value is not 0 or 1. - pub fn and(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::And, span); - } - - /// Pops a value off the stack, applies logical AND with the given immediate, and places the - /// result back on the stack. - /// - /// Traps if the value is not 0 or 1. - pub fn and_imm(mut self, imm: bool, span: SourceSpan) { - self.build(self.ip, MasmOp::AndImm(imm), span); - } - - /// Pops two values off the stack, applies logical OR, and places the result back on the stack. - /// - /// Traps if either value is not 0 or 1. - pub fn or(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Or, span); - } - - /// Pops a value off the stack, applies logical OR with the given immediate, and places the - /// result back on the stack. - /// - /// Traps if the value is not 0 or 1. - pub fn or_imm(mut self, imm: bool, span: SourceSpan) { - self.build(self.ip, MasmOp::OrImm(imm), span); - } - - /// Pops two values off the stack, applies logical XOR, and places the result back on the stack. - /// - /// Traps if either value is not 0 or 1. - pub fn xor(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Xor, span); - } - - /// Pops a value off the stack, applies logical XOR with the given immediate, and places the - /// result back on the stack. - /// - /// Traps if the value is not 0 or 1. - pub fn xor_imm(mut self, imm: bool, span: SourceSpan) { - self.build(self.ip, MasmOp::XorImm(imm), span); - } - - /// Pops two elements off the stack, and pushes 1 on the stack if they are equal, else 0. - pub fn eq(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Eq, span); - } - - /// Pops an element off the stack, and pushes 1 on the stack if that value and the given - /// immediate are equal, else 0. - pub fn eq_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::EqImm(imm), span); - } - - /// Pops two words off the stack, and pushes 1 on the stack if they are equal, else 0. - pub fn eqw(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Eqw, span); - } - - /// Pops two elements off the stack, and pushes 1 on the stack if they are not equal, else 0. - pub fn neq(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Neq, span); - } - - /// Pops an element off the stack, and pushes 1 on the stack if that value and the given - /// immediate are not equal, else 0. - pub fn neq_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::NeqImm(imm), span); - } - - /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than the - /// second, else 0. - pub fn gt(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Gt, span); - } - - /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than the - /// given immediate, else 0. - pub fn gt_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::GtImm(imm), span); - } - - /// Pops two elements off the stack, and pushes 1 on the stack if the first is greater than or - /// equal to the second, else 0. - pub fn gte(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Gte, span); - } - - /// Pops an element off the stack, and pushes 1 on the stack if that value is greater than or - /// equal to the given immediate, else 0. - pub fn gte_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::GteImm(imm), span); - } - - /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than the - /// second, else 0. - pub fn lt(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Lt, span); - } - - /// Pops an element off the stack, and pushes 1 on the stack if that value is less than the - /// given immediate, else 0. - pub fn lt_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::LtImm(imm), span); - } - - /// Pops two elements off the stack, and pushes 1 on the stack if the first is less than or - /// equal to the second, else 0. - pub fn lte(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Lte, span); - } - - /// Pops an element off the stack, and pushes 1 on the stack if that value is less than or equal - /// to the given immediate, else 0. - pub fn lte_imm(mut self, imm: Felt, span: SourceSpan) { - self.build(self.ip, MasmOp::LteImm(imm), span); - } - - /// Pops an element off the stack, and pushes 1 on the stack if that value is an odd number, - /// else 0. - pub fn is_odd(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::IsOdd, span); - } - - /// Pushes the current value of the cycle counter (clock) on the stack - pub fn clk(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Clk, span); - } - - /// Pushes the hash of the caller on the stack - pub fn caller(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Caller, span); - } - - /// Pushes the current stack depth on the stack - pub fn current_stack_depth(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::Sdepth, span); - } - - /// Pushes 1 on the stack if the element on top of the stack is less than 2^32, else 0. - pub fn test_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Test, span); - } - - /// Pushes 1 on the stack if every element of the word on top of the stack is less than 2^32, - /// else 0. - pub fn testw_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Testw, span); - } - - /// Traps if the element on top of the stack is greater than or equal to 2^32 - pub fn assert_u32(mut self, error_code: Option, span: SourceSpan) { - let op = error_code.map(MasmOp::U32AssertWithError).unwrap_or(MasmOp::U32Assert); - self.build(self.ip, op, span); - } - - /// Traps if either of the first two elements on top of the stack are greater than or equal to - /// 2^32 - pub fn assert2_u32(mut self, error_code: Option, span: SourceSpan) { - let op = error_code.map(MasmOp::U32Assert2WithError).unwrap_or(MasmOp::U32Assert2); - self.build(self.ip, op, span); - } - - /// Traps if any element of the first word on the stack are greater than or equal to 2^32 - pub fn assertw_u32(mut self, error_code: Option, span: SourceSpan) { - let op = error_code.map(MasmOp::U32AssertwWithError).unwrap_or(MasmOp::U32Assertw); - self.build(self.ip, op, span); - } - - /// Casts the element on top of the stack, `a`, to a valid u32 value, by computing `a mod 2^32` - pub fn cast_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Cast, span); - } - - /// Pops an element, `a`, from the stack, and splits it into two elements, `b` and `c`, each of - /// which are a valid u32 value. - /// - /// The value for `b` is given by `a mod 2^32`, and the value for `c` by `a / 2^32`. They are - /// pushed on the stack in that order, i.e. `c` will be on top of the stack afterwards. - pub fn split_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Split, span); - } - - /// Performs unsigned addition of the top two elements on the stack, `b` and `a` respectively, - /// which are expected to be valid u32 values. - /// - /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. - pub fn add_u32(mut self, overflow: Overflow, span: SourceSpan) { - let op = match overflow { - Overflow::Unchecked => MasmOp::Add, - Overflow::Checked => { - return self.build_many( - self.ip, - [ - Span::new(span, MasmOp::U32Assert2), - Span::new(span, MasmOp::Add), - Span::new(span, MasmOp::U32Assert), - ], - ); - } - Overflow::Overflowing => MasmOp::U32OverflowingAdd, - Overflow::Wrapping => MasmOp::U32WrappingAdd, - }; - self.build(self.ip, op, span); - } - - /// Same as above, but `a` is provided by the given immediate. - pub fn add_imm_u32(mut self, imm: u32, overflow: Overflow, span: SourceSpan) { - let op = match overflow { - Overflow::Unchecked if imm == 1 => MasmOp::Incr, - Overflow::Unchecked => MasmOp::AddImm(Felt::new(imm as u64)), - Overflow::Checked => { - return self.build_many( - self.ip, - [ - Span::new(span, MasmOp::U32Assert), - Span::new(span, MasmOp::AddImm(Felt::new(imm as u64))), - Span::new(span, MasmOp::U32Assert), - ], - ); - } - Overflow::Overflowing => MasmOp::U32OverflowingAddImm(imm), - Overflow::Wrapping => MasmOp::U32WrappingAddImm(imm), - }; - self.build(self.ip, op, span); - } - - /// Pops three elements from the stack, `c`, `b`, and `a`, and computes `a + b + c` using the - /// overflowing semantics of `add_u32`. The first two elements on the stack after this - /// instruction will be a boolean indicating whether addition overflowed, and the result - /// itself, mod 2^32. - pub fn add3_overflowing_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32OverflowingAdd3, span); - } - - /// Pops three elements from the stack, `c`, `b`, and `a`, and computes `a + b + c` using the - /// wrapping semantics of `add_u32`. The result will be on top of the stack afterwards, mod - /// 2^32. - pub fn add3_wrapping_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32WrappingAdd3, span); - } - - /// Performs unsigned subtraction of the top two elements on the stack, `b` and `a` - /// respectively, which are expected to be valid u32 values. - /// - /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. - pub fn sub_u32(mut self, overflow: Overflow, span: SourceSpan) { - let op = match overflow { - Overflow::Unchecked => MasmOp::Sub, - Overflow::Checked => { - return self.build_many( - self.ip, - [ - Span::new(span, MasmOp::U32Assert2), - Span::new(span, MasmOp::Sub), - Span::new(span, MasmOp::U32Assert), - ], - ); - } - Overflow::Overflowing => MasmOp::U32OverflowingSub, - Overflow::Wrapping => MasmOp::U32WrappingSub, - }; - self.build(self.ip, op, span); - } - - /// Same as above, but `a` is provided by the given immediate. - pub fn sub_imm_u32(mut self, imm: u32, overflow: Overflow, span: SourceSpan) { - let op = match overflow { - Overflow::Unchecked => MasmOp::SubImm(Felt::new(imm as u64)), - Overflow::Checked => { - return self.build_many( - self.ip, - [ - Span::new(span, MasmOp::U32Assert), - Span::new(span, MasmOp::SubImm(Felt::new(imm as u64))), - Span::new(span, MasmOp::U32Assert), - ], - ); - } - Overflow::Overflowing => MasmOp::U32OverflowingSubImm(imm), - Overflow::Wrapping => MasmOp::U32WrappingSubImm(imm), - }; - self.build(self.ip, op, span); - } - - /// Performs unsigned multiplication of the top two elements on the stack, `b` and `a` - /// respectively, which are expected to be valid u32 values. - /// - /// See the [Overflow] enum for how `overflow` modifies the semantics of this instruction. - pub fn mul_u32(mut self, overflow: Overflow, span: SourceSpan) { - let op = match overflow { - Overflow::Unchecked => MasmOp::Mul, - Overflow::Checked => { - return self.build_many( - self.ip, - [ - Span::new(span, MasmOp::U32Assert2), - Span::new(span, MasmOp::Mul), - Span::new(span, MasmOp::U32Assert), - ], - ); - } - Overflow::Overflowing => MasmOp::U32OverflowingMul, - Overflow::Wrapping => MasmOp::U32WrappingMul, - }; - self.build(self.ip, op, span); - } - - /// Same as above, but `a` is provided by the given immediate. - pub fn mul_imm_u32(mut self, imm: u32, overflow: Overflow, span: SourceSpan) { - let op = match overflow { - Overflow::Unchecked => MasmOp::MulImm(Felt::new(imm as u64)), - Overflow::Checked => { - return self.build_many( - self.ip, - [ - Span::new(span, MasmOp::U32Assert), - Span::new(span, MasmOp::MulImm(Felt::new(imm as u64))), - Span::new(span, MasmOp::U32Assert), - ], - ); - } - Overflow::Overflowing => MasmOp::U32OverflowingMulImm(imm), - Overflow::Wrapping => MasmOp::U32WrappingMulImm(imm), - }; - self.build(self.ip, op, span); - } - - /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using - /// overflowing semantics, i.e. the result is wrapped mod 2^32, and a flag is pushed on the - /// stack if the result overflowed the u32 range. - pub fn madd_overflowing_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32OverflowingMadd, span); - } - - /// Pops three elements from the stack, `b`, `a`, and `c`, and computes `a * b + c`, using - /// wrapping semantics, i.e. the result is wrapped mod 2^32. - pub fn madd_wrapping_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32WrappingMadd, span); - } - - /// Performs unsigned division of the top two elements on the stack, `b` and `a` respectively, - /// which are expected to be valid u32 values. - /// - /// This operation is unchecked, so if either operand is >= 2^32, the result is undefined. - /// - /// Traps if `b` is 0. - pub fn div_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Div, span); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn div_imm_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::U32DivImm(imm), span); - } - - /// Pops two elements off the stack, `b` and `a` respectively, and computes `a mod b`. - /// - /// This operation is unchecked, so if either operand is >= 2^32, the result is undefined. - /// - /// Traps if `b` is 0. - pub fn mod_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Mod, span); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn mod_imm_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::U32ModImm(imm), span); - } - - /// Pops two elements off the stack, `b` and `a` respectively, and computes `a / b`, and `a mod - /// b`, pushing the results of each on the stack in that order. - /// - /// This operation is unchecked, so if either operand is >= 2^32, the results are undefined. - /// - /// Traps if `b` is 0. - pub fn divmod_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32DivMod, span); - } - - /// Same as above, but `b` is provided by the given immediate - pub fn divmod_imm_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::U32DivModImm(imm), span); - } - - /// Pops two elements off the stack, and computes the bitwise AND of those values, placing the - /// result on the stack. - /// - /// Traps if either element is not a valid u32 value. - pub fn band_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32And, span); - } - - /// Pops two elements off the stack, and computes the bitwise OR of those values, placing the - /// result on the stack. - /// - /// Traps if either element is not a valid u32 value. - pub fn bor_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Or, span); - } - - /// Pops two elements off the stack, and computes the bitwise XOR of those values, placing the - /// result on the stack. - /// - /// Traps if either element is not a valid u32 value. - pub fn bxor_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Xor, span); - } - - /// Pops an element off the stack, and computes the bitwise NOT of that value, placing the - /// result on the stack. - /// - /// Traps if the element is not a valid u32 value. - pub fn bnot_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Not, span); - } - - /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` left by `b` bits. - /// More precisely, the result is computed as `(a * 2^b) mod 2^32`. - /// - /// The result is undefined if `a` is not a valid u32, or `b` is > 31. - pub fn shl_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Shl, span); - } - - /// Same as `shl_u32`, but `b` is provided by immediate. - pub fn shl_imm_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::U32ShlImm(imm), span); - } - - /// Pops two elements off the stack, `b` and `a` respectively, and shifts `a` right by `b` bits. - /// More precisely, the result is computed as `a / 2^b`. - /// - /// The result is undefined if `a` is not a valid u32, or `b` is > 31. - pub fn shr_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Shr, span); - } - - /// Same as `shr_u32`, but `b` is provided by immediate. - pub fn shr_imm_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::U32ShrImm(imm), span); - } - - /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary - /// representation of `a` left by `b` bits. - /// - /// The result is undefined if `a` is not a valid u32, or `b` is > 31. - pub fn rotl_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Rotl, span); - } - - /// Same as `rotl_u32`, but `b` is provided by immediate. - pub fn rotl_imm_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::U32RotlImm(imm), span); - } - - /// Pops two elements off the stack, `b` and `a` respectively, and rotates the binary - /// representation of `a` right by `b` bits. - /// - /// The result is undefined if `a` is not a valid u32, or `b` is > 31. - pub fn rotr_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Rotr, span); - } - - /// Same as `rotr_u32`, but `b` is provided by immediate. - pub fn rotr_imm_u32(mut self, imm: u32, span: SourceSpan) { - self.build(self.ip, MasmOp::U32RotrImm(imm), span); - } - - /// Pops an element off the stack, and computes the number of set bits in its binary - /// representation, i.e. its hamming weight, and places the result on the stack. - /// - /// The result is undefined if the input value is not a valid u32. - pub fn popcnt_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Popcnt, span); - } - - /// Pops an element off the stack, and computes the number of leading zeros in its binary - /// representation, and places the result on the stack. - /// - /// The result is undefined if the input value is not a valid u32. - pub fn clz_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Clz, span); - } - - /// Pops an element off the stack, and computes the number of trailing zeros in its binary - /// representation, and places the result on the stack. - /// - /// The result is undefined if the input value is not a valid u32. - pub fn ctz_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Ctz, span); - } - - /// Pops an element off the stack, and computes the number of leading ones in its binary - /// representation, and places the result on the stack. - /// - /// The result is undefined if the input value is not a valid u32. - pub fn clo_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Clo, span); - } - - /// Pops an element off the stack, and computes the number of trailing ones in its binary - /// representation, and places the result on the stack. - /// - /// The result is undefined if the input value is not a valid u32. - pub fn cto_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Cto, span); - } - - /// Pushes a boolean on the stack by computing `a < b` for `[b, a]` - /// - /// The result is undefined if either operand is not a valid u32 value. - pub fn lt_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Lt, span); - } - - /// Pushes a boolean on the stack by computing `a <= b` for `[b, a]` - /// - /// The result is undefined if either operand is not a valid u32 value. - pub fn lte_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Lte, span); - } - - /// Pushes a boolean on the stack by computing `a > b` for `[b, a]` - /// - /// The result is undefined if either operand is not a valid u32 value. - pub fn gt_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Gt, span); - } - - /// Pushes a boolean on the stack by computing `a >= b` for `[b, a]` - /// - /// The result is undefined if either operand is not a valid u32 value. - pub fn gte_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Gte, span); - } - - /// Computes the minimum of the two elements on top of the stack. - /// - /// The result is undefined if either operand is not a valid u32 value. - pub fn min_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Min, span); - } - - /// Computes the maximum of the two elements on top of the stack. - /// - /// The result is undefined if either operand is not a valid u32 value. - pub fn max_u32(mut self, span: SourceSpan) { - self.build(self.ip, MasmOp::U32Max, span); - } - - #[inline(never)] - fn build(&mut self, ip: MasmBlockId, op: MasmOp, span: SourceSpan) { - apply_op_stack_effects(&op, self.stack, self.dfg, self.asm); - self.asm.push(ip, op, span); - } - - #[inline(never)] - fn build_many(&mut self, ip: MasmBlockId, ops: impl IntoIterator>) { - for op in ops.into_iter() { - apply_op_stack_effects(&op, self.stack, self.dfg, self.asm); - self.asm.push(ip, op.into_inner(), op.span()); - } - } -} - -#[doc(hidden)] -enum IfBranch { - Then, - Else, -} - -/// This builder is used to construct an `if.true` instruction, while maintaining -/// the invariant that the operand stack has a uniform state upon exit from either -/// branch of the `if.true`, i.e. the number of elements, and their types, must -/// match. -/// -/// We do this by snapshotting the state of the operand stack on entry, using it -/// when visiting each branch as the initial stack state, and then validating that -/// when both branches have been constructed, that the stack state on exit is the -/// same. The first branch to be completed defines the expected state of the stack -/// for the remaining branch. -/// -/// # Example -/// -/// The general usage here looks like this, where `masm_builder` is an instance of -/// [MasmBuilder]: -/// -/// ```text,ignore -/// // If the current top of the stack is > 0, decrement the next stack element, which -/// is a counter, and then call a function, otherwise, pop the counter, push 0, and proceed. -/// masm_builder.ins().gt_imm(Felt::ZERO); -/// let if_builder = masm_builder.ins().if_true(); -/// -/// // Build the then branch -/// let then_b = if_builder.build_then(); -/// then_b.ins().sub_imm(Felt::new(1 as u64)); -/// then_b.ins().exec("do_some_stuff_and_return_a_boolean".parse().unwrap()); -/// then_b.end(); -/// -/// // Build the else branch -/// let else_b = if_builder.build_else(); -/// else_b.ins().pop(); -/// else_b.ins().push(Felt::ZERO); -/// else_b.end(); -/// -/// // Finalize -/// if_builder.build(); -/// ``` -pub struct IfTrueBuilder<'a> { - dfg: &'a mut DataFlowGraph, - asm: &'a mut InlineAsm, - /// This reference is to the operand stack in the parent [MasmOpBuilder], - /// which represents the operand stack on entry to the `if.true`. Upon - /// finalizatio of the `if.true`, we use update this operand stack to - /// reflect the state upon exit from the `if.true`. - /// - /// In effect, when the call to `if_true` returns, the operand stack in the - /// parent builder will look as if the `if.true` instruction has finished executing. - in_stack: &'a mut OperandStack, - /// This is set when the first branch is finished being constructed, and - /// will be used as the expected state of the operand stack when we finish - /// constructing the second branch and validate the `if.true`. - out_stack: OperandStack, - /// The source span associated with the conditional - span: SourceSpan, - /// This is the block to which the `if.true` will be appended - ip: MasmBlockId, - /// The block id for the then branch, unset until it has been finalized - then_blk: Option, - /// The block id for the else branch, unset until it has been finalized - else_blk: Option, -} -impl<'f> IfTrueBuilder<'f> { - /// Start constructing the then block for this `if.true` instruction - /// - /// NOTE: This function will panic if the then block has already been built - pub fn build_then<'a: 'f, 'b: 'f + 'a>(&'b mut self) -> IfTrueBlockBuilder<'a> { - assert!(self.then_blk.is_none(), "cannot build the 'then' branch twice"); - let then_blk = self.asm.create_block(); - let stack = self.in_stack.clone(); - IfTrueBlockBuilder { - builder: self, - stack, - block: then_blk, - branch: IfBranch::Then, - } - } - - /// Start constructing the else block for this `if.true` instruction - /// - /// NOTE: This function will panic if the else block has already been built - pub fn build_else<'a: 'f, 'b: 'f + 'a>(&'b mut self) -> IfTrueBlockBuilder<'a> { - assert!(self.else_blk.is_none(), "cannot build the 'else' branch twice"); - let else_blk = self.asm.create_block(); - let stack = self.in_stack.clone(); - IfTrueBlockBuilder { - builder: self, - stack, - block: else_blk, - branch: IfBranch::Else, - } - } - - /// Finalize this `if.true` instruction, inserting it into the block this - /// builder was constructed from. - pub fn build(mut self) { - let then_blk = self.then_blk.expect("missing 'then' block"); - let else_blk = self.else_blk.expect("missing 'else' block"); - self.asm.push(self.ip, MasmOp::If(then_blk, else_blk), self.span); - // Update the operand stack to represent the state after execution of the `if.true` - let in_stack = self.in_stack.stack_mut(); - in_stack.clear(); - in_stack.append(self.out_stack.stack_mut()); - } -} - -/// Used to construct a single branch of an `if.true` instruction -/// -/// See [IfTrueBuilder] for usage. -pub struct IfTrueBlockBuilder<'a> { - builder: &'a mut IfTrueBuilder<'a>, - // The state of the operand stack in this block - stack: OperandStack, - // The block we're building - block: MasmBlockId, - branch: IfBranch, -} -impl<'f> IfTrueBlockBuilder<'f> { - /// Construct a MASM instruction in this block - pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { - MasmOpBuilder { - dfg: self.builder.dfg, - asm: self.builder.asm, - stack: &mut self.stack, - ip: self.block, - } - } - - /// Finalize this block, and release the builder - pub fn end(self) {} -} -impl<'a> Drop for IfTrueBlockBuilder<'a> { - fn drop(&mut self) { - match self.branch { - IfBranch::Then => { - self.builder.then_blk = Some(self.block); - } - IfBranch::Else => { - self.builder.else_blk = Some(self.block); - } - } - - // If the if.true instruction is complete, validate that the operand stack in - // both branches is identical - // - // Otherwise, save the state of the stack here to be compared to the other - // branch when it is constructed - let is_complete = self.builder.then_blk.is_some() && self.builder.else_blk.is_some(); - if is_complete { - assert_eq!( - self.stack.stack(), - self.builder.out_stack.stack(), - "expected the operand stack to be in the same abstract state upon exit from \ - either branch of this if.true instruction" - ); - } else { - core::mem::swap(&mut self.builder.out_stack, &mut self.stack); - } - } -} - -#[doc(hidden)] -enum LoopType { - While, - Repeat(u16), -} - -/// This builder is used to construct both `while.true` and `repeat.n` loops, enforcing -/// their individual invariants with regard to the operand stack. -/// -/// In particular, this builder ensures that the body of a `while.true` loop is valid, -/// i.e. that when returning to the top of the loop to evaluate the conditional, that -/// there is a boolean value on top of the stack for that purpose. Similarly, it validates -/// that after the conditional has been evaluated, that the abstract state of the operand -/// stack is the same across iterations, and regardless of whether the loop is taken. The -/// abstract state in question is the number, and type, of the operands on the stack. -/// -/// # Example -/// -/// The general usage here looks like this, where `masm_builder` is an instance of -/// [MasmBuilder]: -/// -/// ```text,ignore -/// // For our example here, we're generating inline assembly that performs -/// // the equivalent of `for (i = 0; i < len; i++) sum += array[i / 4][i % 4]`, -/// // where `array` is a pointer to words, and we're attempting to sum `len` -/// // field elements, across how ever many words that spans. -/// // -/// // We assume the operand stack is as follows (top to bottom): -/// // -/// // [len, sum, array] -/// // -/// // First, build out the loop header -/// masm_builder.ins().push(Felt::ZERO); // [i, len, sum, array] -/// masm_builder.ins().dup(0); // [i, i, len, sum, array] -/// masm_builder.ins().dup(2); // [len, i, i, len, sum, array] -/// masm_builder.ins().lt(); // [i < len, i, len, sum, array] -/// -/// // Now, build the loop body -/// // -/// // The state of the stack on entry is: [i, len, sum, array] -/// let mut lb = masm_builder.ins().while_true(); -/// -/// // Calculate `i / 4` -/// lb.ins().dup(0); // [i, i, len, sum, array] -/// lb.ins().div_imm(4); // [word_offset, i, len, sum, array] -/// -/// // Calculate the address for `array[i / 4]` -/// lb.ins().dup(4); // [array, word_offset, ..] -/// lb.ins().add_u32(Overflow::Checked); // [array + word_offset, i, ..] -/// -/// // Calculate the `i % 4` -/// lb.ins().dup(1); // [i, array + word_offset, ..] -/// lb.ins().mod_imm_u32(4); // [element_offset, array + word_offset, ..] -/// -/// // Precalculate what elements of the word to drop, so that -/// // we are only left with the specific element we wanted -/// lb.ins().dup(0); // [element_offset, element_offset, ..] -/// lb.ins().lt_imm(Felt::new(3)); // [element_offset < 3, element_offset, ..] -/// lb.ins().dup(1); // [element_offset, element_offset < 3, ..] -/// lb.ins().lt_imm(Felt::new(2)); // [element_offset < 2, element_offset < 3, ..] -/// lb.ins().dup(2); // [element_offset, element_offset < 2, ..] -/// lb.ins().lt_imm(Felt::new(1)); // [element_offset < 1, element_offset < 2, ..] -/// -/// // Load the word -/// lb.ins().dup(4); // [array + word_offset, element_offset < 1] -/// lb.ins().loadw(); // [word[0], word[1], word[2], word[3], element_offset < 1] -/// -/// // Select the element, `E`, that we want by conditionally dropping -/// // elements on the operand stack with a carefully chosen sequence -/// // of conditionals: E < N forall N in 0..=3 -/// lb.ins().movup(4); // [element_offset < 1, word[0], ..] -/// lb.ins().cdrop(); // [word[0 or 1], word[2], word[3], element_offset < 2] -/// lb.ins().movup(3); // [element_offset < 2, word[0 or 1], ..] -/// lb.ins().cdrop(); // [word[0 or 1 or 2], word[3], element_offset < 3] -/// lb.ins().movup(2); // [element_offset < 3, ..] -/// lb.ins().cdrop(); // [array[i], i, len, sum, array] -/// lb.ins().movup(3); // [sum, array[i], i, len, array] -/// lb.ins().add(); // [sum + array[i], i, len, array] -/// lb.ins().movdn(2); // [i, len, sum + array[i], array] -/// -/// // We've reached the end of the loop, but we need a copy of the -/// // loop header here in order to use the expression `i < len` as -/// // the condition for the loop -/// lb.ins().dup(0); // [i, i, len, ..] -/// lb.ins().dup(2); // [len, i, i, len, ..] -/// lb.ins().lt(); // [i < len, i, len, sum, array] -/// -/// // Finalize, it is at this point that validation will occur -/// lb.build(); -/// ``` -pub struct LoopBuilder<'a> { - dfg: &'a mut DataFlowGraph, - asm: &'a mut InlineAsm, - /// This reference is to the operand stack in the parent [MasmOpBuilder], - /// which represents the operand stack on entry to the loop. Upon finalization - /// of the loop, we use update this operand stack to reflect the state upon - /// exit from the loop. - /// - /// In effect, when the call to `while_true` or `repeat` returns, the operand - /// stack in the parent builder will look as if the loop instruction has finished - /// executing. - in_stack: &'a mut OperandStack, - /// This is the operand stack state within the loop. - /// - /// Upon finalization of the loop instruction, this state is used to validate - /// the effect of the loop body on the operand stack. For `repeat`, which is - /// unconditionally entered, no special validation is performed. However, for - /// `while.true`, we must validate two things: - /// - /// 1. That the top of the stack holds a boolean value - /// 2. That after popping the boolean, the output state of the operand stack matches the input - /// state in number and type of elements. This is required, as otherwise program behavior is - /// undefined based on whether the loop is entered or not. - out_stack: OperandStack, - /// The source span associated with the loop - span: SourceSpan, - /// The block to which the loop instruction will be appended - ip: MasmBlockId, - /// The top-level block for the loop - body: MasmBlockId, - /// The type of loop we're building - style: LoopType, -} -impl<'f> LoopBuilder<'f> { - /// Get a builder for a single MASM instruction - pub fn ins<'a, 'b: 'a>(&'b mut self) -> MasmOpBuilder<'a> { - MasmOpBuilder { - dfg: self.dfg, - asm: self.asm, - stack: &mut self.out_stack, - ip: self.body, - } - } - - /// Finalize construction of this loop, performing any final validation. - pub fn build(mut self) { - match self.style { - LoopType::While => { - // First, validate that the top of the stack holds a boolean - let cond = self.out_stack.pop().expect("operand stack is empty"); - assert_eq!( - cond, - Type::I1, - "expected there to be a boolean on top of the stack at the end of the \ - while.true body" - ); - // Next, validate that the contents of the operand stack match - // the input stack, in order to ensure that the operand stack - // is consistent whether the loop is taken or not - assert_eq!( - self.in_stack.stack(), - self.out_stack.stack(), - "expected the operand stack to be in the same abstract state whether the \ - while.true loop is taken or skipped" - ); - self.asm.push(self.ip, MasmOp::While(self.body), self.span); - } - LoopType::Repeat(1) => { - // This is an edge case, but a single iteration `repeat` is no different than - // inlining the loop body into the outer code block and eliding the `repeat`. - // - // Since that is the case, we literally do that transformation here, to simplify - // the IR as much as possible during construction. - let id = self.body; - let mut block = core::mem::replace( - &mut self.asm.blocks[id], - MasmBlock { - id, - ops: smallvec![], - }, - ); - self.asm.blocks[self.ip].append(&mut block.ops); - } - LoopType::Repeat(n) => { - // Apply the stack effects of the loop body `n` times, asserting if some operation - // in the loop fails due to type mismatches. This is sufficient to validate - // `repeat`, as it's iteration count is known statically, entry into - // the loop is unconditional, and the only way to exit the loop is - // to complete all `n` iterations. - // - // By validating in this way, we also implicitly validate the following: - // - // 1. If we were to translate this to SSA form, the resulting control flow graph - // would - // have the same number and type of arguments passed to the loop header both on - // entry and along the loopback edges. - // - // 2. If the body of the loop removes elements from the stack, we ensure that all - // `n` - // iterations can be performed without exhausting the stack, or perform any other - // invalid stack operation. - let code = &self.asm.blocks[self.body]; - for _ in 1..n { - for op in code.ops.iter() { - apply_op_stack_effects(op, &mut self.out_stack, self.dfg, self.asm); - } - } - self.asm.push(self.ip, MasmOp::Repeat(n, self.body), self.span); - } - } - - // Update the operand stack to represent the state after execution of this loop - let in_stack = self.in_stack.stack_mut(); - in_stack.clear(); - in_stack.append(self.out_stack.stack_mut()); - } -} - -/// Asserts that the given value is an integer type which is compatible with u32 operations -macro_rules! assert_compatible_u32_operand { - ($ty:ident) => { - assert!( - $ty.is_pointer() || Type::U32.is_compatible_operand(&$ty), - "expected operand to be u32-compatible, got {}", - $ty - ); - }; - - ($ty:ident, $op:expr) => { - assert!( - $ty.is_pointer() || Type::U32.is_compatible_operand(&$ty), - "expected operand for {} to be u32-compatible, got {}", - $op, - $ty - ); - }; -} - -/// Asserts that the given value is an integer type which is compatible with u32 operations -macro_rules! assert_compatible_u32_operands { - ($lty:ident, $rty:ident) => { - assert_matches!( - $lty, - Type::U8 | Type::U16 | Type::U32 | Type::Ptr(_), - "expected controlling type to be u32-compatible, got {}", - $lty - ); - assert_compatible_operand_types!($lty, $rty); - }; - - ($lty:ident, $rty:ident, $op:expr) => { - assert_matches!( - $lty, - Type::U8 | Type::U16 | Type::U32 | Type::Ptr(_), - "expected controlling type for {} to be u32-compatible, got {}", - $op, - $lty - ); - assert_compatible_operand_types!($lty, $rty, $op); - }; -} - -/// Asserts that the given value is an integer or pointer type which is compatible with basic felt -/// arithmetic -macro_rules! assert_compatible_arithmetic_operand { - ($ty:ident) => { - assert!( - $ty.is_pointer() || Type::Felt.is_compatible_operand(&$ty), - "expected operand to be felt-compatible, got {}", - $ty - ); - }; - - ($ty:ident, $op:expr) => { - assert!( - $ty.is_pointer() || Type::Felt.is_compatible_operand(&$ty), - "expected operand for {} to be felt-compatible, got {}", - $op, - $ty - ); - }; -} - -/// Asserts that the given value is an integer type which is compatible with felt operations -macro_rules! assert_compatible_felt_operand { - ($ty:ident) => { - assert!( - Type::Felt.is_compatible_operand(&$ty), - "expected operand to be felt-compatible, got {}", - $ty - ); - }; - - ($ty:ident, $op:expr) => { - assert!( - Type::Felt.is_compatible_operand(&$ty), - "expected operand for {} to be felt-compatible, got {}", - $op, - $ty - ); - }; -} - -/// Asserts that the given value is an integer or pointer type which is compatible with basic -/// arithmetic operations as a felt -macro_rules! assert_compatible_arithmetic_operands { - ($lty:ident, $rty:ident) => { - assert_matches!( - $lty, - Type::U8 - | Type::I8 - | Type::U16 - | Type::I16 - | Type::U32 - | Type::I32 - | Type::Ptr(_) - | Type::Felt, - "expected controlling type to be felt-compatible, got {}", - $lty - ); - assert_compatible_operand_types!($lty, $rty); - }; - - ($lty:ident, $rty:ident, $op:expr) => { - assert_matches!( - $lty, - Type::U8 - | Type::I8 - | Type::U16 - | Type::I16 - | Type::U32 - | Type::I32 - | Type::Ptr(_) - | Type::Felt, - "expected controlling type for {} to be felt-compatible, got {}", - $op, - $lty - ); - assert_compatible_operand_types!($lty, $rty, $op); - }; -} - -/// Asserts that the given value is an integer type which is compatible with felt operations -macro_rules! assert_compatible_felt_operands { - ($lty:ident, $rty:ident) => { - assert_matches!( - $lty, - Type::U8 | Type::I8 | Type::U16 | Type::I16 | Type::U32 | Type::I32 | Type::Felt, - "expected controlling type to be felt-compatible, got {}", - $lty - ); - assert_compatible_operand_types!($lty, $rty); - }; - - ($lty:ident, $rty:ident, $op:expr) => { - assert_matches!( - $lty, - Type::U8 | Type::I8 | Type::U16 | Type::I16 | Type::U32 | Type::I32 | Type::Felt, - "expected controlling type for {} to be felt-compatible, got {}", - $op, - $lty - ); - assert_compatible_operand_types!($lty, $rty, $op); - }; -} - -/// Asserts that the two operands are of compatible types, where the first operand is assumed to -/// determine the controlling type -macro_rules! assert_compatible_operand_types { - ($lty:ident, $rty:ident) => { - assert!( - $lty.is_compatible_operand(&$rty), - "expected operands to be compatible types, the controlling type {} is not compatible \ - with {}", - $lty, - $rty - ); - }; - - ($lty:ident, $rty:ident, $op:expr) => { - assert!( - $lty.is_compatible_operand(&$rty), - "expected operands for {} to be compatible types, the controlling type {} is not \ - compatible with {}", - $op, - $lty, - $rty - ); - }; -} - -fn apply_op_stack_effects( - op: &MasmOp, - stack: &mut OperandStack, - dfg: &DataFlowGraph, - asm: &InlineAsm, -) { - match op { - MasmOp::Padw => { - stack.padw(); - } - MasmOp::Push(_) => { - stack.push(Type::Felt); - } - MasmOp::Push2(_) => { - stack.push(Type::Felt); - stack.push(Type::Felt); - } - MasmOp::Pushw(_) => { - stack.padw(); - } - MasmOp::PushU8(_) => { - stack.push(Type::U8); - } - MasmOp::PushU16(_) => { - stack.push(Type::U16); - } - MasmOp::PushU32(_) => { - stack.push(Type::U32); - } - MasmOp::Drop => { - stack.drop(); - } - MasmOp::Dropw => { - stack.dropw(); - } - MasmOp::Dup(idx) => { - stack.dup(*idx as usize); - } - MasmOp::Dupw(idx) => { - stack.dupw(*idx as usize); - } - MasmOp::Swap(idx) => { - stack.swap(*idx as usize); - } - MasmOp::Swapw(idx) => { - stack.swapw(*idx as usize); - } - MasmOp::Swapdw => { - stack.swapdw(); - } - MasmOp::Movup(idx) => { - stack.movup(*idx as usize); - } - MasmOp::Movupw(idx) => { - stack.movupw(*idx as usize); - } - MasmOp::Movdn(idx) => { - stack.movdn(*idx as usize); - } - MasmOp::Movdnw(idx) => { - stack.movdnw(*idx as usize); - } - MasmOp::Cswap | MasmOp::Cswapw => { - let ty = stack.pop().expect("operand stack is empty"); - assert_eq!(ty, Type::I1, "expected boolean operand on top of the stack"); - } - MasmOp::Cdrop => { - let ty = stack.pop().expect("operand stack is empty"); - assert_eq!(ty, Type::I1, "expected boolean operand on top of the stack"); - stack.drop(); - } - MasmOp::Cdropw => { - let ty = stack.pop().expect("operand stack is empty"); - assert_eq!(ty, Type::I1, "expected boolean operand on top of the stack"); - stack.dropw(); - } - MasmOp::Assert - | MasmOp::Assertz - | MasmOp::AssertWithError(_) - | MasmOp::AssertzWithError(_) => { - stack.drop(); - } - MasmOp::AssertEq | MasmOp::AssertEqWithError(_) => { - stack.dropn(2); - } - MasmOp::AssertEqw | MasmOp::AssertEqwWithError(_) => { - stack.dropn(8); - } - MasmOp::LocAddr(_id) - | MasmOp::LocLoad(_id) - | MasmOp::LocLoadw(_id) - | MasmOp::LocStore(_id) - | MasmOp::LocStorew(_id) => unreachable!(), - MasmOp::MemLoad => { - let ty = stack.pop().expect("operand stack is empty"); - assert_matches!( - ty, - Type::Ptr(_) | Type::NativePtr(_, _), - "invalid load: expected pointer operand" - ); - stack.push(ty.pointee().unwrap().clone()); - } - MasmOp::MemLoadImm(_) => { - // We don't know what we're loading, so fall back to the default type of field element - stack.push(Type::Felt); - } - MasmOp::MemLoadw => { - let ty = stack.pop().expect("operand stack is empty"); - assert_matches!( - ty, - Type::Ptr(_) | Type::NativePtr(_, _), - "invalid load: expected pointer operand" - ); - // We're always loading a raw word with this op - stack.padw(); - } - MasmOp::MemLoadwImm(_) => { - // We're always loading a raw word with this op - stack.padw(); - } - MasmOp::MemStore => { - let ty = stack.pop().expect("operand stack is empty"); - assert_matches!( - ty, - Type::Ptr(_) | Type::NativePtr(_, _), - "invalid store: expected pointer operand" - ); - stack.drop(); - } - MasmOp::MemStoreImm(_) => { - stack.drop(); - } - MasmOp::MemStorew => { - let ty = stack.pop().expect("operand stack is empty"); - assert_matches!( - ty, - Type::Ptr(_) | Type::NativePtr(_, _), - "invalid store: expected pointer operand" - ); - stack.dropw(); - } - MasmOp::MemStorewImm(_) => { - stack.dropw(); - } - MasmOp::MemStream => { - // Read two sequential words from memory starting at `a`, overwriting the first two - // words on the stack, and advancing `a` to the next address following the - // two that were loaded [C, B, A, a] <- [*a, *(a + 1), A, a + 2] - assert!(stack.len() > 12, "expected at least 13 elements on the stack for mem_stream"); - stack.dropw(); - stack.dropw(); - stack.padw(); - stack.padw(); - } - MasmOp::AdvPipe => { - // Pops the next two words from the advice stack, overwrites the - // top of the operand stack with them, and also writes these words - // into memory at `a` and `a + 1` - // - // [C, B, A, a] <- [*a, *(a + 1), A, a + 2] - assert!(stack.len() > 12, "expected at least 13 elements on the stack for mem_stream"); - stack.dropw(); - stack.dropw(); - stack.padw(); - stack.padw(); - } - MasmOp::AdvPush(n) => { - for _ in 0..(*n) { - stack.push(Type::Felt); - } - } - MasmOp::AdvLoadw => { - assert!(stack.len() > 3, "expected at least 4 elements on the stack for mem_stream"); - stack.dropw(); - stack.padw(); - } - MasmOp::AdvInjectInsertMem - | MasmOp::AdvInjectInsertHperm - | MasmOp::AdvInjectInsertHdword - | MasmOp::AdvInjectInsertHdwordImm(_) - | MasmOp::AdvInjectPushMapVal - | MasmOp::AdvInjectPushMapValImm(_) - | MasmOp::AdvInjectPushMapValN - | MasmOp::AdvInjectPushMapValNImm(_) - | MasmOp::AdvInjectPushU64Div - | MasmOp::AdvInjectPushMTreeNode - | MasmOp::AdvInjectPushSignature(_) => (), - MasmOp::Hash => { - assert!(stack.len() > 3, "expected at least 4 elements on the stack for hash"); - } - MasmOp::Hperm => { - assert!(stack.len() >= 12, "expected at least 12 elements on the stack for hperm"); - } - MasmOp::Hmerge => { - assert!(stack.len() >= 8, "expected at least 8 elements on the stack for hmerge"); - stack.dropw(); - } - MasmOp::MtreeGet => { - assert!(stack.len() >= 6, "expected at least 6 elements on the stack for mtree_get"); - stack.dropn(2); - stack.pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); - } - MasmOp::MtreeSet => { - assert!(stack.len() >= 10, "expected at least 10 elements on the stack for mtree_set"); - stack.dropn(2); - stack.swapw(1); - } - MasmOp::MtreeMerge => { - assert!(stack.len() >= 8, "expected at least 8 elements on the stack for mtree_merge"); - stack.dropw(); - } - MasmOp::MtreeVerify | MasmOp::MtreeVerifyWithError(_) => { - assert!( - stack.len() >= 10, - "expected at least 10 elements on the stack for mtree_verify" - ); - } - MasmOp::RCombBase => { - assert!(stack.len() >= 15, "expected at least 15 elements on the stack for rcomb_base"); - } - MasmOp::FriExt2Fold4 => { - assert!( - stack.len() >= 16, - "expected at least 16 elements on the stack for fri_ext2fold4" - ); - } - // This function is not called from [MasmOpBuilder] when building an `if.true` instruction, - // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` - // instruction and applying the stack effects of instructions which have already been - // inserted once. - // - // NOTE: We only apply the effects from a single branch, because we have already validated - // that regardless of which branch is taken, the stack effects are the same. - MasmOp::If(then_body, _else_body) => { - let lty = stack.pop().expect("operand stack is empty"); - assert_eq!(lty, Type::I1, "expected boolean conditional"); - let body = asm.blocks[*then_body].ops.as_slice(); - for op in body.iter() { - apply_op_stack_effects(op, stack, dfg, asm); - } - } - // This function is not called from [MasmOpBuilder] when building an `while.true` - // instruction, instead, the only time we are evaluating this is when traversing the - // body of a `repeat.n` instruction and applying the stack effects of instructions - // which have already been inserted once. - // - // NOTE: We don't need to traverse the body of the `while.true`, because we have already - // validated that whether the loop is taken or not, the stack effects are the same - MasmOp::While(_body) => { - let lty = stack.pop().expect("operand stack is empty"); - assert_eq!(lty, Type::I1, "expected boolean conditional"); - } - // This function is not called from [MasmOpBuilder] when building an `repeat.n` instruction, - // instead, the only time we are evaluating this is when traversing the body of a `repeat.n` - // instruction and applying the stack effects of instructions which have already been - // inserted once. - MasmOp::Repeat(n, body) => { - let body = asm.blocks[*body].ops.as_slice(); - for _ in 0..*n { - for op in body.iter() { - apply_op_stack_effects(op, stack, dfg, asm); - } - } - } - MasmOp::Exec(ref id) => { - execute_call(id, false, stack, dfg); - } - MasmOp::Call(ref id) => { - execute_call(id, false, stack, dfg); - } - MasmOp::Syscall(ref id) => { - execute_call(id, true, stack, dfg); - } - MasmOp::DynExec | MasmOp::DynCall => { - assert!( - stack.len() > 3, - "expected at least 4 elements on the stack for dynexec/dyncall" - ); - } - MasmOp::ProcRef(_) => { - stack.pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); - } - MasmOp::Add | MasmOp::Sub => { - let rty = stack.pop().expect("operand stack is empty"); - let lty = stack.pop().expect("operand stack is empty"); - assert_compatible_arithmetic_operands!(lty, rty, op); - stack.push(lty); - } - MasmOp::Mul | MasmOp::Div => { - let rty = stack.pop().expect("operand stack is empty"); - let lty = stack.pop().expect("operand stack is empty"); - assert_compatible_felt_operands!(lty, rty, op); - stack.push(lty); - } - MasmOp::AddImm(_) | MasmOp::SubImm(_) | MasmOp::Incr => { - let ty = stack.peek().expect("operand stack is empty"); - assert_compatible_arithmetic_operand!(ty, op); - } - MasmOp::MulImm(_) - | MasmOp::DivImm(_) - | MasmOp::Neg - | MasmOp::Inv - | MasmOp::Ilog2 - | MasmOp::Pow2 - | MasmOp::ExpImm(_) - | MasmOp::ExpBitLength(_) => { - let ty = stack.peek().expect("operand stack is empty"); - assert_compatible_felt_operand!(ty, op); - } - MasmOp::Exp => { - let rty = stack.pop().expect("operand stack is empty"); - let lty = stack.pop().expect("operand stack is empty"); - assert_compatible_felt_operands!(lty, rty); - stack.push(lty); - } - MasmOp::Not | MasmOp::AndImm(_) | MasmOp::OrImm(_) | MasmOp::XorImm(_) => { - let ty = stack.pop().expect("operand stack is empty"); - assert_eq!(ty, Type::I1, "expected boolean type"); - stack.push(ty); - } - MasmOp::And | MasmOp::Or | MasmOp::Xor => { - let rty = stack.pop().expect("operand stack is empty"); - let lty = stack.pop().expect("operand stack is empty"); - assert_eq!(lty, rty, "expected operands for {} to be the same type", op); - assert_eq!(lty, Type::I1, "expected boolean operands for {}", op); - stack.push(lty); - } - MasmOp::Eq | MasmOp::Neq | MasmOp::Gt | MasmOp::Gte | MasmOp::Lt | MasmOp::Lte => { - let rty = stack.pop().expect("operand stack is empty"); - let lty = stack.pop().expect("operand stack is empty"); - assert_compatible_felt_operands!(lty, rty, op); - stack.push(Type::I1); - } - MasmOp::EqImm(_) - | MasmOp::NeqImm(_) - | MasmOp::GtImm(_) - | MasmOp::GteImm(_) - | MasmOp::LtImm(_) - | MasmOp::LteImm(_) - | MasmOp::IsOdd => { - let ty = stack.pop().expect("operand stack is empty"); - assert_compatible_felt_operand!(ty, op); - stack.push(Type::I1); - } - MasmOp::Eqw => { - stack.dropn(8); - stack.push(Type::I1); - } - op @ (MasmOp::Ext2add | MasmOp::Ext2sub | MasmOp::Ext2mul | MasmOp::Ext2div) => { - assert!(stack.len() > 3, "expected at least 4 elements on the operand stack for {op}"); - stack.dropn(2); - } - op @ (MasmOp::Ext2neg | MasmOp::Ext2inv) => { - assert!(stack.len() > 1, "expected at least 2 elements on the operand stack for {op}"); - } - MasmOp::Clk | MasmOp::Sdepth => { - stack.push(Type::Felt); - } - MasmOp::Caller => { - assert!(stack.len() > 3, "expected at least 4 elements on the operand stack"); - stack.popw(); - stack.pushw([Type::Felt, Type::Felt, Type::Felt, Type::Felt]); - } - MasmOp::U32Test => { - assert!(!stack.is_empty()); - stack.push(Type::I1); - } - MasmOp::U32Testw => { - assert!(stack.len() > 3, "expected at least 4 elements on the operand stack"); - stack.push(Type::I1); - } - MasmOp::U32Assert | MasmOp::U32AssertWithError(_) => { - assert!(!stack.is_empty()); - } - MasmOp::U32Assert2 | MasmOp::U32Assert2WithError(_) => { - assert!(stack.len() > 1, "expected at least 2 elements on the operand stack"); - } - MasmOp::U32Assertw | MasmOp::U32AssertwWithError(_) => { - assert!(stack.len() > 3, "expected at least 4 elements on the operand stack"); - } - MasmOp::U32Cast => { - let lty = stack.pop().expect("operand stack is empty"); - assert_eq!(lty, Type::Felt, "expected felt operand"); - stack.push(Type::U32); - } - MasmOp::U32Split => { - let lty = stack.pop().expect("operand stack is empty"); - assert_eq!(lty, Type::Felt, "expected felt operand"); - stack.push(Type::U32); - stack.push(Type::U32); - } - MasmOp::U32Lt | MasmOp::U32Gt | MasmOp::U32Lte | MasmOp::U32Gte => { - let rty = stack.pop().expect("failed to pop right operand: stack is empty"); - let lty = stack.pop().expect("failed to pop left operand: stack is empty"); - assert_compatible_u32_operands!(lty, rty, op); - stack.push(Type::I1); - } - MasmOp::U32Div - | MasmOp::U32Mod - | MasmOp::U32DivMod - | MasmOp::U32Shl - | MasmOp::U32Shr - | MasmOp::U32Rotl - | MasmOp::U32Rotr - | MasmOp::U32Min - | MasmOp::U32Max - | MasmOp::U32WrappingAdd - | MasmOp::U32WrappingSub - | MasmOp::U32WrappingMul - | MasmOp::U32And - | MasmOp::U32Or - | MasmOp::U32Xor => { - let rty = stack.pop().expect("operand stack is empty"); - let lty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operands!(lty, rty, op); - stack.push(lty); - } - MasmOp::U32DivImm(_) - | MasmOp::U32ModImm(_) - | MasmOp::U32DivModImm(_) - | MasmOp::U32ShlImm(_) - | MasmOp::U32ShrImm(_) - | MasmOp::U32RotlImm(_) - | MasmOp::U32RotrImm(_) - | MasmOp::U32Popcnt - | MasmOp::U32Clz - | MasmOp::U32Ctz - | MasmOp::U32Clo - | MasmOp::U32Cto - | MasmOp::U32WrappingAddImm(_) - | MasmOp::U32WrappingSubImm(_) - | MasmOp::U32WrappingMulImm(_) - | MasmOp::U32Not - | MasmOp::U32LtImm(_) - | MasmOp::U32LteImm(_) - | MasmOp::U32GtImm(_) - | MasmOp::U32GteImm(_) - | MasmOp::U32MinImm(_) - | MasmOp::U32MaxImm(_) => { - let ty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operand!(ty, op); - stack.push(ty); - } - MasmOp::U32OverflowingAdd | MasmOp::U32OverflowingSub | MasmOp::U32OverflowingMul => { - let rty = stack.pop().expect("operand stack is empty"); - let lty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operands!(lty, rty, op); - stack.push(lty); - stack.push(Type::I1); - } - MasmOp::U32OverflowingAddImm(_) - | MasmOp::U32OverflowingSubImm(_) - | MasmOp::U32OverflowingMulImm(_) => { - let ty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operand!(ty, op); - stack.push(ty); - stack.push(Type::I1); - } - MasmOp::U32OverflowingAdd3 => { - let cty = stack.pop().expect("operand stack is empty"); - let bty = stack.pop().expect("operand stack is empty"); - let aty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operands!(aty, bty); - assert_compatible_u32_operands!(aty, cty); - stack.push(aty); - stack.push(Type::U32); - } - MasmOp::U32OverflowingMadd => { - let bty = stack.pop().expect("operand stack is empty"); - let aty = stack.pop().expect("operand stack is empty"); - let cty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operands!(aty, bty); - assert_compatible_u32_operands!(aty, cty); - stack.push(aty); - stack.push(Type::U32); - } - MasmOp::U32WrappingAdd3 => { - let cty = stack.pop().expect("operand stack is empty"); - let bty = stack.pop().expect("operand stack is empty"); - let aty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operands!(aty, bty); - assert_compatible_u32_operands!(aty, cty); - stack.push(aty); - } - MasmOp::U32WrappingMadd => { - let bty = stack.pop().expect("operand stack is empty"); - let aty = stack.pop().expect("operand stack is empty"); - let cty = stack.pop().expect("operand stack is empty"); - assert_compatible_u32_operands!(aty, bty); - assert_compatible_u32_operands!(aty, cty); - stack.push(aty); - } - MasmOp::Emit(_) | MasmOp::Trace(_) | MasmOp::Breakpoint => (), - MasmOp::DebugStack - | MasmOp::DebugStackN(_) - | MasmOp::DebugMemory - | MasmOp::DebugMemoryAt(_) - | MasmOp::DebugMemoryRange(..) - | MasmOp::DebugFrame - | MasmOp::DebugFrameAt(_) - | MasmOp::DebugFrameRange(..) - | MasmOp::Nop => (), - } -} - -/// Validate that a call to `id` is possible given the current state of the operand stack, -/// and if so, update the state of the operand stack to reflect the call. -fn execute_call( - id: &FunctionIdent, - is_syscall: bool, - stack: &mut OperandStack, - dfg: &DataFlowGraph, -) { - let import = dfg.get_import(id).expect("unknown function, are you missing an import?"); - if is_syscall { - assert_eq!( - import.signature.cc, - CallConv::Kernel, - "cannot call a non-kernel function with the `syscall` instruction" - ); - } else { - assert_ne!( - import.signature.cc, - CallConv::Kernel, - "`syscall` cannot be used to call non-kernel functions" - ); - } - match import.signature.cc { - // For now, we're treating all calling conventions the same as SystemV - CallConv::Fast | CallConv::SystemV | CallConv::Kernel => { - // Visit the argument list in reverse (so that the top of the stack on entry - // is the first argument), and allocate elements based on the argument types. - let mut elements_needed = 0; - for param in import.signature.params().iter().rev() { - elements_needed += param.ty.size_in_felts(); - } - - // Verify that we have `elements_needed` values on the operand stack - let elements_available = stack.len(); - assert!( - elements_needed <= elements_available, - "the operand stack does not contain enough values to call {} ({} exepected vs {} \ - available)", - id, - elements_needed, - elements_available - ); - stack.dropn(elements_needed); - - // Update the operand stack to reflect the results - for result in import.signature.results().iter().rev() { - push_type_on_stack(result.ty.clone(), stack); - } - } - CallConv::Wasm => unimplemented!("canonical abi support is not yet implemented"), - } -} - -fn push_type_on_stack(ty: Type, stack: &mut OperandStack) { - let parts = ty.to_raw_parts().expect("invalid unknown type: cannot proceed"); - for part in parts.into_iter().rev() { - stack.push(part); - } -} diff --git a/hir/src/asm/display.rs b/hir/src/asm/display.rs deleted file mode 100644 index 9cf785938..000000000 --- a/hir/src/asm/display.rs +++ /dev/null @@ -1,332 +0,0 @@ -use core::fmt; - -use super::*; -use crate::{ - formatter::{Document, PrettyPrint}, - FunctionIdent, Ident, Symbol, -}; - -pub struct DisplayInlineAsm<'a> { - function: Option, - asm: &'a InlineAsm, - dfg: &'a DataFlowGraph, -} -impl<'a> DisplayInlineAsm<'a> { - pub fn new( - function: Option, - asm: &'a InlineAsm, - dfg: &'a DataFlowGraph, - ) -> Self { - Self { function, asm, dfg } - } -} -impl<'a> PrettyPrint for DisplayInlineAsm<'a> { - fn render(&self) -> Document { - use crate::formatter::*; - - let params = self - .asm - .args - .as_slice(&self.dfg.value_lists) - .iter() - .copied() - .map(display) - .reduce(|acc, p| acc + const_text(" ") + p); - - let body = DisplayMasmBlock { - function: self.function, - imports: None, - blocks: &self.asm.blocks, - block: self.asm.body, - }; - let body = - const_text("(") + const_text("masm") + indent(4, body.render()) + const_text(")"); - - const_text("(") - + const_text("asm") - + const_text(" ") - + body - + params - .map(|params| const_text(" ") + const_text("(") + params + const_text(")")) - .unwrap_or_default() - + const_text(")") - } -} -impl<'a> fmt::Display for DisplayInlineAsm<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} - -pub struct DisplayMasmBlock<'a> { - function: Option, - imports: Option<&'a ModuleImportInfo>, - blocks: &'a PrimaryMap, - block: MasmBlockId, -} -impl<'a> DisplayMasmBlock<'a> { - pub fn new( - function: Option, - imports: Option<&'a ModuleImportInfo>, - blocks: &'a PrimaryMap, - block: MasmBlockId, - ) -> Self { - Self { - function, - imports, - blocks, - block, - } - } -} -impl<'a> PrettyPrint for DisplayMasmBlock<'a> { - fn render(&self) -> Document { - use crate::formatter::*; - - let block = &self.blocks[self.block]; - let multiline = block - .ops - .iter() - .map(|op| { - let op = DisplayOp { - function: self.function, - imports: self.imports, - blocks: self.blocks, - op, - }; - op.render() - }) - .reduce(|acc, e| acc + nl() + e) - .unwrap_or_default(); - - if block.ops.len() < 5 && !block.ops.iter().any(|op| op.has_regions()) { - let singleline = block - .ops - .iter() - .map(|op| { - let op = DisplayOp { - function: self.function, - imports: self.imports, - blocks: self.blocks, - op, - }; - op.render() - }) - .reduce(|acc, e| acc + const_text(" ") + e) - .unwrap_or_default(); - singleline | multiline - } else { - multiline - } - } -} -impl<'a> fmt::Display for DisplayMasmBlock<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} - -struct DisplayOp<'a> { - function: Option, - imports: Option<&'a ModuleImportInfo>, - blocks: &'a PrimaryMap, - op: &'a MasmOp, -} -impl<'a> DisplayOp<'a> { - #[inline(always)] - pub fn is_local_module(&self, id: &Ident) -> bool { - match self.function { - Some(function) => &function.module == id, - None => self.imports.map(|imports| !imports.is_import(id)).unwrap_or(false), - } - } - - // TODO(pauls): Remove when we no longer need to deal with referencing aliased identifiers - #[allow(unused)] - pub fn get_module_alias(&self, module: Ident) -> Symbol { - self.imports - .and_then(|imports| imports.alias(&module)) - .unwrap_or(module) - .as_symbol() - } -} -impl<'a> PrettyPrint for DisplayOp<'a> { - fn render(&self) -> Document { - use crate::formatter::*; - - match self.op { - MasmOp::Push(imm) => const_text("push") + const_text(".") + display(*imm), - MasmOp::Push2([a, b]) => { - const_text("push") + const_text(".") + display(*a) + const_text(".") + display(*b) - } - MasmOp::Pushw(word) => { - const_text("push") - + const_text(".") - + display(word[0]) - + const_text(".") - + display(word[1]) - + const_text(".") - + display(word[2]) - + const_text(".") - + display(word[3]) - } - MasmOp::PushU8(imm) => const_text("push") + const_text(".") + display(*imm), - MasmOp::PushU16(imm) => const_text("push") + const_text(".") + display(*imm), - MasmOp::PushU32(imm) => const_text("push") + const_text(".") + display(*imm), - op @ (MasmOp::Dup(idx) - | MasmOp::Dupw(idx) - | MasmOp::Swap(idx) - | MasmOp::Swapw(idx) - | MasmOp::Movup(idx) - | MasmOp::Movupw(idx) - | MasmOp::Movdn(idx) - | MasmOp::Movdnw(idx)) => text(format!("{op}")) + const_text(".") + display(*idx), - op @ (MasmOp::LocAddr(id) - | MasmOp::LocStore(id) - | MasmOp::LocStorew(id) - | MasmOp::LocLoad(id) - | MasmOp::LocLoadw(id)) => { - text(format!("{op}")) + const_text(".") + display(id.as_usize()) - } - op @ (MasmOp::MemLoadImm(addr) - | MasmOp::MemLoadwImm(addr) - | MasmOp::MemStoreImm(addr) - | MasmOp::MemStorewImm(addr)) => { - text(format!("{op}")) - + const_text(".") - + text(format!("{:#x}", DisplayHex(addr.to_be_bytes().as_slice()))) - } - MasmOp::AdvPush(n) => const_text("adv_push") + const_text(".") + display(*n), - MasmOp::If(then_blk, else_blk) => { - let then_body = DisplayMasmBlock { - function: self.function, - imports: self.imports, - blocks: self.blocks, - block: *then_blk, - } - .render(); - let else_body = DisplayMasmBlock { - function: self.function, - imports: self.imports, - blocks: self.blocks, - block: *else_blk, - } - .render(); - const_text("if.true") - + indent(4, nl() + then_body) - + nl() - + const_text("else") - + indent(4, nl() + else_body) - + nl() - + const_text("end") - } - MasmOp::While(blk) => { - let body = DisplayMasmBlock { - function: self.function, - imports: self.imports, - blocks: self.blocks, - block: *blk, - } - .render(); - const_text("while.true") + indent(4, nl() + body) + nl() + const_text("end") - } - MasmOp::Repeat(n, blk) => { - let body = DisplayMasmBlock { - function: self.function, - imports: self.imports, - blocks: self.blocks, - block: *blk, - } - .render(); - - const_text("repeat") - + const_text(".") - + display(*n) - + indent(4, nl() + body) - + nl() - + const_text("end") - } - op @ (MasmOp::Exec(id) - | MasmOp::Call(id) - | MasmOp::Syscall(id) - | MasmOp::ProcRef(id)) => { - let FunctionIdent { module, function } = id; - let function = if miden_assembly::ast::Ident::validate(function.as_str()).is_ok() { - text(function.as_str()) - } else { - text(format!("\"{}\"", function.as_str())) - }; - if self.is_local_module(module) { - text(format!("{op}")) + const_text(".") + function - } else { - text(format!("{op}")) - + const_text(".::") - + display(module.as_str()) - + const_text("::") - + function - } - } - op @ (MasmOp::AndImm(imm) | MasmOp::OrImm(imm) | MasmOp::XorImm(imm)) => { - text(format!("{op}")) + const_text(".") + display(*imm) - } - MasmOp::ExpImm(imm) => const_text("exp") + const_text(".") + display(*imm), - op @ (MasmOp::AddImm(imm) - | MasmOp::SubImm(imm) - | MasmOp::MulImm(imm) - | MasmOp::DivImm(imm) - | MasmOp::EqImm(imm) - | MasmOp::NeqImm(imm) - | MasmOp::GtImm(imm) - | MasmOp::GteImm(imm) - | MasmOp::LtImm(imm) - | MasmOp::LteImm(imm)) => text(format!("{op}")) + const_text(".") + display(*imm), - op @ (MasmOp::U32OverflowingAddImm(imm) - | MasmOp::U32WrappingAddImm(imm) - | MasmOp::U32OverflowingSubImm(imm) - | MasmOp::U32WrappingSubImm(imm) - | MasmOp::U32OverflowingMulImm(imm) - | MasmOp::U32WrappingMulImm(imm) - | MasmOp::U32DivImm(imm) - | MasmOp::U32ModImm(imm) - | MasmOp::U32DivModImm(imm) - | MasmOp::U32ShlImm(imm) - | MasmOp::U32ShrImm(imm) - | MasmOp::U32RotlImm(imm) - | MasmOp::U32RotrImm(imm)) => text(format!("{op}")) + const_text(".") + display(*imm), - op @ (MasmOp::AdvInjectPushMapValImm(offset) - | MasmOp::AdvInjectPushMapValNImm(offset)) => { - text(format!("{op}")) + const_text(".") + display(*offset) - } - op @ MasmOp::AdvInjectInsertHdwordImm(domain) => { - text(format!("{op}")) + const_text(".") + display(*domain) - } - op @ MasmOp::DebugStackN(n) => text(format!("{op}")) + const_text(".") + display(*n), - op @ MasmOp::DebugMemoryAt(start) => { - text(format!("{op}")) + const_text(".") + display(*start) - } - op @ MasmOp::DebugMemoryRange(start, end) => { - text(format!("{op}")) - + const_text(".") - + display(*start) - + const_text(".") - + display(*end) - } - op @ MasmOp::DebugFrameAt(start) => { - text(format!("{op}")) + const_text(".") + display(*start) - } - op @ MasmOp::DebugFrameRange(start, end) => { - text(format!("{op}")) - + const_text(".") - + display(*start) - + const_text(".") - + display(*end) - } - op => text(format!("{op}")), - } - } -} -impl<'a> fmt::Display for DisplayOp<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} diff --git a/hir/src/asm/events.rs b/hir/src/asm/events.rs deleted file mode 100644 index d9538ce0d..000000000 --- a/hir/src/asm/events.rs +++ /dev/null @@ -1,53 +0,0 @@ -//! This module contains the set of compiler-emitted event codes, and their explanations -use core::num::NonZeroU32; - -/// This event is emitted via `trace`, and indicates that a procedure call frame is entered -/// -/// The mnemonic here is F = frame, 0 = open -pub const TRACE_FRAME_START: u32 = 0xf0; - -/// This event is emitted via `trace`, and indicates that a procedure call frame is exited -/// -/// The mnemonic here is F = frame, C = close -pub const TRACE_FRAME_END: u32 = 0xfc; - -/// A typed wrapper around the raw trace events known to the compiler -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(u32)] -pub enum TraceEvent { - FrameStart, - FrameEnd, - AssertionFailed(Option), - Unknown(u32), -} -impl TraceEvent { - #[inline(always)] - pub fn is_frame_start(&self) -> bool { - matches!(self, Self::FrameStart) - } - - #[inline(always)] - pub fn is_frame_end(&self) -> bool { - matches!(self, Self::FrameEnd) - } -} -impl From for TraceEvent { - fn from(raw: u32) -> Self { - match raw { - TRACE_FRAME_START => Self::FrameStart, - TRACE_FRAME_END => Self::FrameEnd, - _ => Self::Unknown(raw), - } - } -} -impl From for u32 { - fn from(event: TraceEvent) -> Self { - match event { - TraceEvent::FrameStart => TRACE_FRAME_START, - TraceEvent::FrameEnd => TRACE_FRAME_END, - TraceEvent::AssertionFailed(None) => 0, - TraceEvent::AssertionFailed(Some(code)) => code.get(), - TraceEvent::Unknown(code) => code, - } - } -} diff --git a/hir/src/asm/import.rs b/hir/src/asm/import.rs deleted file mode 100644 index 2a0a2f844..000000000 --- a/hir/src/asm/import.rs +++ /dev/null @@ -1,279 +0,0 @@ -use core::{ - fmt::Write, - hash::{Hash, Hasher}, - str::FromStr, -}; - -use anyhow::bail; -use rustc_hash::{FxHashMap, FxHashSet}; - -use crate::{ - diagnostics::{SourceSpan, Spanned}, - FunctionIdent, Ident, Symbol, -}; - -#[derive(Default, Debug, Clone)] -pub struct ModuleImportInfo { - /// This maps original, fully-qualified module names to their corresponding import - modules: FxHashMap, - /// This maps known aliases to their fully-qualified identifiers - aliases: FxHashMap, - /// This maps short-form/aliased module names to the functions imported from that module - functions: FxHashMap>, -} -impl ModuleImportInfo { - /// Inserts a new import in the table - pub fn insert(&mut self, import: MasmImport) { - let name = Ident::new(import.name, import.span); - assert!(self.modules.insert(name, import).is_none()); - } - - /// Adds an import of the given function to the import table - /// - /// NOTE: It is assumed that the caller is adding imports using fully-qualified names. - pub fn add(&mut self, id: FunctionIdent) { - use std::collections::hash_map::Entry; - - let module_id = id.module; - match self.modules.entry(module_id) { - Entry::Vacant(entry) => { - let alias = module_id_alias(module_id); - let span = module_id.span(); - let alias_id = if self.aliases.contains_key(&alias) { - // The alias is already used by another module, we must - // produce a new, unique alias to avoid conflicts. We - // use the hash of the fully-qualified name for this - // purpose, hex-encoded - let mut hasher = rustc_hash::FxHasher::default(); - alias.as_str().hash(&mut hasher); - let mut buf = String::with_capacity(16); - write!(&mut buf, "{:x}", hasher.finish()).expect("failed to write string"); - let alias = Symbol::intern(buf.as_str()); - let alias_id = Ident::new(alias, span); - assert_eq!( - self.aliases.insert(alias_id, module_id), - None, - "unexpected aliasing conflict" - ); - alias_id - } else { - Ident::new(alias, span) - }; - entry.insert(MasmImport { - span, - name: module_id.as_symbol(), - alias: alias_id.name, - }); - self.aliases.insert(alias_id, module_id); - self.functions.entry(alias_id).or_default().insert(id); - } - Entry::Occupied(_) => { - let module_id_alias = module_id_alias(module_id); - let alias = self.aliases[&module_id_alias]; - let functions = self.functions.entry(alias).or_default(); - functions.insert(id); - } - } - } - - /// Returns true if there are no imports recorded - pub fn is_empty(&self) -> bool { - self.modules.is_empty() - } - - /// Given a fully-qualified module name, look up the corresponding import metadata - pub fn get(&self, module: &Q) -> Option<&MasmImport> - where - Ident: core::borrow::Borrow, - Q: Hash + Eq + ?Sized, - { - self.modules.get(module) - } - - /// Given a fully-qualified module name, get the aliased identifier - pub fn alias(&self, module: &Q) -> Option - where - Ident: core::borrow::Borrow, - Q: Hash + Eq + ?Sized, - { - self.modules.get(module).map(|i| Ident::new(i.alias, i.span)) - } - - /// Given an aliased module name, get the fully-qualified identifier - pub fn unalias(&self, alias: &Q) -> Option - where - Ident: core::borrow::Borrow, - Q: Hash + Eq + ?Sized, - { - self.aliases.get(alias).copied() - } - - /// Returns true if `module` is an imported module - pub fn is_import(&self, module: &Q) -> bool - where - Ident: core::borrow::Borrow, - Q: Hash + Eq + ?Sized, - { - self.modules.contains_key(module) - } - - /// Given a module alias, get the set of functions imported from that module - pub fn imported(&self, alias: &Q) -> Option<&FxHashSet> - where - Ident: core::borrow::Borrow, - Q: Hash + Eq + ?Sized, - { - self.functions.get(alias) - } - - /// Get an iterator over the [MasmImport] records in this table - pub fn iter(&self) -> impl Iterator { - self.modules.values() - } - - /// Get an iterator over the aliased module names in this table - pub fn iter_module_names(&self) -> impl Iterator { - self.modules.keys() - } -} - -fn module_id_alias(module_id: Ident) -> Symbol { - match module_id.as_str().rsplit_once("::") { - None => module_id.as_symbol(), - Some((_, alias)) => Symbol::intern(alias), - } -} - -/// This represents an import statement in Miden Assembly -#[derive(Debug, Copy, Clone, Spanned)] -pub struct MasmImport { - /// The source span corresponding to this import statement, if applicable - #[span] - pub span: SourceSpan, - /// The fully-qualified name of the imported module, e.g. `std::math::u64` - pub name: Symbol, - /// The name to which the imported module is aliased locally, e.g. `u64` - /// is the alias for `use std::math::u64`, which is the default behavior. - /// - /// However, custom aliases are permitted, and we may use this to disambiguate - /// imported modules, e.g. `use std::math::u64->my_u64` will result in the - /// alias for this import being `my_u64`. - pub alias: Symbol, -} -impl MasmImport { - /// Returns true if this import has a custom alias, or if it uses the - /// default aliasing behavior for imports - pub fn is_aliased(&self) -> bool { - !self.name.as_str().ends_with(self.alias.as_str()) - } - - /// Returns true if this import conflicts with `other` - /// - /// A conflict arises when the same name is used to reference two different - /// imports locally within a module, i.e. the aliases conflict - pub fn conflicts_with(&self, other: &Self) -> bool { - self.alias == other.alias && self.name != other.name - } -} -impl Eq for MasmImport {} -impl PartialEq for MasmImport { - fn eq(&self, other: &Self) -> bool { - // If the names are different, the imports can't be equivalent - if self.name != other.name { - return false; - } - // Otherwise, equivalence depends on the aliasing of the import - match (self.is_aliased(), other.is_aliased()) { - (true, true) => { - // Two imports that are custom aliased are equivalent only if - // both the fully-qualified name and the alias are identical - self.alias == other.alias - } - (true, false) | (false, true) => { - // If one import is aliased and the other is not, the imports - // are never equivalent, because they can't possibly refer to - // the same module by the same name - false - } - (false, false) => { - // Two unaliased imports are the same if their names are the same - true - } - } - } -} -impl PartialOrd for MasmImport { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl Ord for MasmImport { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.name.cmp(&other.name).then_with(|| self.alias.cmp(&other.alias)) - } -} -impl Hash for MasmImport { - fn hash(&self, state: &mut H) { - self.name.hash(state); - self.alias.hash(state); - } -} -impl TryFrom for MasmImport { - type Error = anyhow::Error; - - fn try_from(module: Ident) -> Result { - let name = module.as_str(); - if name.contains(char::is_whitespace) { - bail!("invalid module identifier '{name}': cannot contain whitespace",); - } - match name.rsplit_once("::") { - None => { - let name = module.as_symbol(); - Ok(Self { - span: module.span(), - name, - alias: name, - }) - } - Some((_, "")) => { - bail!("invalid module identifier '{name}': trailing '::' is invalid"); - } - Some((_, alias)) => { - let name = module.as_symbol(); - let alias = Symbol::intern(alias); - Ok(Self { - span: module.span(), - name, - alias, - }) - } - } - } -} -impl FromStr for MasmImport { - type Err = anyhow::Error; - - /// Parse an import statement as seen in Miden Assembly, e.g. `use std::math::u64->bigint` - fn from_str(s: &str) -> Result { - let s = s.trim(); - let s = s.strip_prefix("use ").unwrap_or(s); - match s.rsplit_once("->") { - None => { - let name = Ident::with_empty_span(Symbol::intern(s)); - name.try_into() - } - Some((_, "")) => { - bail!("invalid import '{s}': alias cannot be empty") - } - Some((fqn, alias)) => { - let name = Symbol::intern(fqn); - let alias = Symbol::intern(alias); - Ok(Self { - span: SourceSpan::UNKNOWN, - name, - alias, - }) - } - } - } -} diff --git a/hir/src/asm/isa.rs b/hir/src/asm/isa.rs deleted file mode 100644 index f365ff1b1..000000000 --- a/hir/src/asm/isa.rs +++ /dev/null @@ -1,1999 +0,0 @@ -use std::{collections::BTreeSet, fmt}; - -use cranelift_entity::entity_impl; -pub use miden_assembly::ast::{AdviceInjectorNode, DebugOptions}; -use smallvec::{smallvec, SmallVec}; - -use crate::{ - diagnostics::{SourceSpan, Span}, - Felt, FunctionIdent, Ident, LocalId, -}; - -/// A handle that refers to a MASM code block -#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct MasmBlockId(u32); -entity_impl!(MasmBlockId, "blk"); - -/// Represents a single code block in Miden Assembly -#[derive(Debug, Clone, PartialEq)] -pub struct MasmBlock { - pub id: MasmBlockId, - pub ops: SmallVec<[Span; 4]>, -} -impl MasmBlock { - /// Returns true if there are no instructions in this block - #[inline(always)] - pub fn is_empty(&self) -> bool { - self.ops.is_empty() - } - - /// Returns the instructions contained in this block as a slice - #[inline(always)] - pub fn ops(&self) -> &[Span] { - self.ops.as_slice() - } - - /// Appends `op` to this code block - #[inline(always)] - pub fn push(&mut self, op: MasmOp, span: SourceSpan) { - self.ops.push(Span::new(span, op)); - } - - /// Append `n` copies of `op` to the current block - #[inline] - pub fn push_n(&mut self, count: usize, op: MasmOp, span: SourceSpan) { - let op = Span::new(span, op); - for _ in 0..count { - self.ops.push(op); - } - } - - /// Append `n` copies of the sequence `ops` to this block - #[inline] - pub fn push_repeat(&mut self, ops: &[Span], count: usize) { - for _ in 0..count { - self.ops.extend_from_slice(ops); - } - } - - /// Append `n` copies of the sequence `ops` to this block - #[inline] - pub fn push_template(&mut self, count: usize, template: F) - where - F: Fn(usize) -> [Span; N], - { - for n in 0..count { - self.ops.extend_from_slice(&template(n)); - } - } - - /// Appends instructions from `slice` to the end of this block - #[inline] - pub fn extend_from_slice(&mut self, slice: &[Span]) { - self.ops.extend_from_slice(slice); - } - - /// Appends instructions from `slice` to the end of this block - #[inline] - pub fn extend(&mut self, ops: impl IntoIterator>) { - self.ops.extend(ops); - } - - /// Appends instructions from `other` to the end of this block - #[inline] - pub fn append(&mut self, other: &mut SmallVec) - where - B: smallvec::Array>, - { - self.ops.append(other); - } -} - -/// This enum represents the Miden Assembly (MASM) instruction set. -/// -/// Not all MASM instructions are necessarily represented here, only those we -/// actually use, or intend to use, when compiling from Miden IR. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum MasmOp { - /// Pushes a null word on the stack, i.e. four 0 values - Padw, - /// Pushes the given field element constant on top of the stack - Push(Felt), - /// Pushes a pair of field elements on top of the stack - Push2([Felt; 2]), - /// Pushes the given word constant on top of the stack - Pushw([Felt; 4]), - /// Pushes the given 8-bit constant on top of the stack - PushU8(u8), - /// Pushes the given 16-bit constant on top of the stack - PushU16(u16), - /// Pushes the given 32-bit constant on top of the stack - PushU32(u32), - /// Removes the item on the top of the stack - Drop, - /// Removes the top 4 items on the stack - Dropw, - /// Copies the `n`th item on the stack to the top of stack - /// - /// * `Dup(0)` duplicates the item on top of the stack - Dup(u8), - /// Copies the `n`th word on the stack, to the top of the stack - /// - /// The only values of `n` which are valid, are 0, 1, 2, 3; or - /// in other words, the 4 words which make up the top 16 elements - /// of the stack. - Dupw(u8), - /// Swaps the 1st and `n`th items on the stack - /// - /// * `Swap(1)` swaps the top two elements of the stack - Swap(u8), - /// Swaps the 1st and `n`th words on the stack - /// - /// The only values of `n` which are valid, are 1, 2, 3; or - /// in other words, the 3 words which make up the last 12 elements - /// of the stack. - Swapw(u8), - // Swaps the two words on top of the stack, with the two words at the bottom of the stack - Swapdw, - /// Moves the `n`th stack item to top of stack - /// - /// * `Movup(1)` is equivalent to `Swap(1)` - Movup(u8), - /// Moves the `n`th stack word to the top of the stack - /// - /// The only values of `n` which are valid are 2 and 3. Use `Swapw(1)` - /// if you want to move the second word to the top. - Movupw(u8), - /// Moves the top of stack to the `n`th index of the stack - /// - /// * `Movdn(1)` is equivalent to `Swap(1)` - Movdn(u8), - /// Moves the top word of the stack, into position as the `n`th word on the stack. - /// - /// The only values of `n` which are valid are 2 and 3. Use `Swapw(1)` - /// if you want to make the top word the second word. - Movdnw(u8), - /// Pops `c, b, a` off the stack, and swaps `b` and `a` if `c` is 1, or leaves - /// them as-is when 0. - /// - /// Traps if `c` is > 1. - Cswap, - /// Pops `c, B, A` off the stack, where `B` and `A` are words, and swaps `B` and `A` - /// if `c` is 1, or leaves them as-is when 0. - /// - /// Traps if `c` is > 1. - Cswapw, - /// Pops `c, b, a` off the stack, and pushes back `b` if `c` is 1, and `a` if 0. - /// - /// Traps if `c` is > 1. - Cdrop, - /// Pops `c, B, A` off the stack, where `B` and `A` are words, and pushes back `B` - /// if `c` is 1, and `A` if 0. - /// - /// Traps if `c` is > 1. - Cdropw, - /// Pops a value off the stack and asserts that it is equal to 1 - Assert, - /// Pops a value off the stack and asserts that it is equal to 1, raising the given error code - AssertWithError(u32), - /// Pops a value off the stack and asserts that it is equal to 0 - Assertz, - /// Pops a value off the stack and asserts that it is equal to 0, raising the given error code - AssertzWithError(u32), - /// Pops two values off the stack and asserts that they are equal - AssertEq, - /// Pops two values off the stack and asserts that they are equal, raising the given error code - AssertEqWithError(u32), - /// Pops two words off the stack and asserts that they are equal - AssertEqw, - /// Pops two words off the stack and asserts that they are equal, raising the given error code - AssertEqwWithError(u32), - /// Places the memory address of the given local index on top of the stack - LocAddr(LocalId), - /// Writes a value to the first element of the word at the address corresponding to the given - /// local index - LocStore(LocalId), - /// Writes a word to the address corresponding to the given local index - LocStorew(LocalId), - /// Reads a value from the first element of the word at the address corresponding to the given - /// local index - LocLoad(LocalId), - /// Reads a word from the address corresponding to the given local index - LocLoadw(LocalId), - /// Pops `a`, representing a memory address, from the top of the stack, then loads the - /// first element of the word starting at that address, placing it on top of the stack. - /// - /// Traps if `a` >= 2^32 - MemLoad, - /// Same as above, but the address is given as an immediate - MemLoadImm(u32), - /// Pops `a`, representing a memory address, from the top of the stack, then overwrites - /// the top word of the stack with the word starting at that address. - /// - /// Traps if `a` >= 2^32 - MemLoadw, - /// Same as above, but the address is given as an immediate - MemLoadwImm(u32), - /// Pops `a, v` from the stack, where `a` represents a memory address, and `v` the value - /// to be stored, and stores `v` as the element as the first element of the word starting - /// at that address. The remaining elements of the word are not modified. - /// - /// Traps if `a` >= 2^32 - MemStore, - /// Same as above, but the address is given as an immediate - MemStoreImm(u32), - /// Pops `a, V` from the stack, where `a` represents a memory address, and `V` is a word to be - /// stored at that location, and overwrites the word located at `a`. - /// - /// Traps if `a` >= 2^32 - MemStorew, - /// Same as above, but the address is given as an immediate - MemStorewImm(u32), - /// Read two sequential words from memory starting at `a`, overwriting the first two words on - /// the stack, and advancing `a` to the next address following the two that were loaded - /// [C, B, A, a] <- [*a, *(a + 1), A, a + 2] - MemStream, - /// Pops the next two words from the advice stack, overwrites the - /// top of the operand stack with them, and also writes these words - /// into memory at `a` and `a + 1` - /// - /// [C, B, A, a] <- [*a, *(a + 1), A, a + 2] - AdvPipe, - /// Pops `n` elements from the advice stack, and pushes them on the operand stack - /// - /// Fails if less than `n` elements are available. - /// - /// Valid values of `n` fall in the range 1..=16 - AdvPush(u8), - /// Pop the next word from the advice stack and overwrite the word on top of the operand stack - /// with it. - /// - /// Fails if the advice stack does not have at least one word. - AdvLoadw, - /// Push the result of u64 division on the advice stack - /// - /// ```text,ignore - /// [b_hi, b_lo, a_hi, a_lo] - /// ``` - AdvInjectPushU64Div, - /// Pushes a list of field elements on the advice stack. - /// - /// The list is looked up in the advice map using the word on top of the operand stack. - /// - /// ```text,ignore - /// [K] - /// ``` - AdvInjectPushMapVal, - /// Pushes a list of field elements on the advice stack. - /// - /// The list is looked up in the advice map using the word starting at `index` on the operand - /// stack. - /// - /// ```text,ignore - /// [K] - /// ``` - AdvInjectPushMapValImm(u8), - /// Pushes a list of field elements, along with the number of elements on the advice stack. - /// - /// The list is looked up in the advice map using the word on top of the operand stack. - /// - /// ```text,ignore - /// [K] - /// ``` - AdvInjectPushMapValN, - /// Pushes a list of field elements, along with the number of elements on the advice stack. - /// - /// The list is looked up in the advice map using the word starting at `index` on the operand - /// stack. - /// - /// ```text,ignore - /// [K] - /// ``` - AdvInjectPushMapValNImm(u8), - /// Pushes a node of a Merkle tree with root `R` at depth `d` and index `i` from the Merkle - /// store onto the advice stack - /// - /// ```text,ignore - /// [d, i, R] - /// ``` - AdvInjectPushMTreeNode, - /// Reads words `mem[a]..mem[b]` from memory, and saves the data into the advice map under `K` - /// - /// ```text,ignore - /// [K, a, b] - /// ``` - AdvInjectInsertMem, - /// Reads the top two words from the stack, and computes a key `K` as `hash(A || B, 0)`. - /// - /// The two words that were hashed are then saved into the advice map under `K`. - /// - /// ```text,ignore - /// [B, A] - /// ``` - AdvInjectInsertHdword, - /// Reads the top two words from the stack, and computes a key `K` as `hash(A || B, d)`. - /// - /// `d` is a domain value which can be in the range 0..=255 - /// - /// The two words that were hashed are then saved into the advice map under `K` as `[A, B]`. - /// - /// ```text,ignore - /// [B, A] - /// ``` - AdvInjectInsertHdwordImm(u8), - /// Reads the top three words from the stack, and computes a key `K` as `permute(C, A, - /// B).digest`. - /// - /// The words `A` and `B` are saved into the advice map under `K` as `[A, B]` - /// - /// ```text,ignore - /// [B, A, C] - /// ``` - AdvInjectInsertHperm, - /// TODO - AdvInjectPushSignature(miden_assembly::ast::SignatureKind), - /// Compute the Rescue Prime Optimized (RPO) hash of the word on top of the operand stack. - /// - /// The resulting hash of one word is placed on the operand stack. - /// - /// The input operand is consumed. - Hash, - /// Computes a 2-to-1 RPO hash of the two words on top of the operand stack. - /// - /// The resulting hash of one word is placed on the operand stack. - /// - /// The input operands are consumed. - Hmerge, - /// Compute an RPO permutation on the top 3 words of the operand stack, where the top 2 words - /// (C and B) are the rate, and the last word (A) is the capacity. - /// - /// The digest output is the word E. - /// - /// ```text,ignore - /// [C, B, A] => [F, E, D] - /// ``` - Hperm, - /// Fetches the value `V` of the Merkle tree with root `R`, at depth `d`, and index `i` from - /// the advice provider, and runs a verification equivalent to `mtree_verify`, returning - /// the value if successful. - /// - /// ```text,ignore - /// [d, i, R] => [V, R] - /// ``` - MtreeGet, - /// Sets the value to `V'` of the Merkle tree with root `R`, at depth `d`, and index `i`. - /// - /// `R'` is the Merkle root of the new tree, and `V` is the old value of the node. - /// - /// Requires that a Merkle tree with root `R` is present in the advice provider, otherwise it - /// fails. - /// - /// Both trees are in the advice provider upon return. - /// - /// ```text,ignore - /// [d, i, R, V'] => [V, R'] - /// ``` - MtreeSet, - /// Create a new Merkle tree root `M`, that joins two other Merkle trees, `R` and `L`. - /// - /// Both the new tree and the input trees are in the advice provider upon return. - /// - /// ```text,ignore - /// [R, L] => [M] - /// ``` - MtreeMerge, - /// Verifies that a Merkle tree with root `R` opens to node `V` at depth `d` and index `i`. - /// - /// The Merkle tree with root `R` must be present in the advice provider or the operation - /// fails. - /// - /// ```text,ignore - /// [V, d, i, R] => [V, d, i, R] - /// ``` - MtreeVerify, - /// Verifies that a Merkle tree with root `R` opens to node `V` at depth `d` and index `i`. - /// - /// The Merkle tree with root `R` must be present in the advice provider or the operation - /// fails. - /// - /// ```text,ignore - /// [V, d, i, R] => [V, d, i, R] - /// ``` - /// Raise the given error code if the verification fails - MtreeVerifyWithError(u32), - /// Performs FRI layer folding by a factor of 4 for FRI protocol executed in a degree 2 - /// extension of the base field. Additionally, performs several computations which simplify - /// FRI verification procedure. - /// - /// * Folds 4 query values: `(v0, v1)`, `(v2, v3)`, `(v4, v5)`, and `(v6, v7)` into a single - /// value `(ne0, ne1)` - /// * Computes new value of the domain generator power: `poe' = poe^4` - /// * Increments layer pointer (`cptr`) by 2 - /// * Shifts the stack left to move an item from the overflow table to bottom of stack - /// - /// ```text,ignore - /// [v7, v6, v5, v4, v3, v2, v1, v0, f_pos, d_seg, poe, pe1, pe0, a1, a0, cptr] - /// => [t1, t0, s1, s0, df3, df2, df1, df0, poe^2, f_tau, cptr+2, poe^4, f_pos, ne1, ne0, eptr] - /// ``` - /// - /// Above, `eptr` is moved from the overflow table and is expected to be the address of the - /// final FRI layer. - FriExt2Fold4, - /// Perform a single step of a random linear combination defining the DEEP composition - /// polynomial, i.e. the input to the FRI protocol. - /// - /// ```text,ignore - /// [t7, t6, t5, t4, t3, t2, t1, t0, p1, p0, r1, r0, x_addr, z_addr, a_addr] - /// => [t0, t7, t6, t5, t4, t3, t2, t1, p1', p0', r1', r0', x_addr, z_addr+1, a_addr+1] - /// ``` - /// - /// Where: - /// - /// * `tN` stands for the value of the `N`th trace polynomial for the current query, i.e. - /// `tN(x)` - /// * `p0` and `p1` stand for an extension field element accumulating the values for the - /// quotients with common denominator `x - z` - /// * `r0` and `r1` stand for an extension field element accumulating the values for the - /// quotients with common denominator `x - gz` - /// * `x_addr` is the memory address from which we are loading the `tN`s using the `mem_stream` - /// instruction - /// * `z_addr` is the memory address to the `N`th OOD evaluations at `z` and `gz` - /// * `a_addr` is the memory address of the `N`th random element `alpha_i` used in batching the - /// trace polynomial quotients - RCombBase, - /// [b1, b0, a1, a0] => [c1, c0] - /// - /// c1 = (a1 + b1) mod p - /// c0 = (a0 + b0) mod p - Ext2add, - /// [b1, b0, a1, a0] => [c1, c0] - /// - /// c1 = (a1 - b1) mod p - /// c0 = (a0 - b0) mod p - Ext2sub, - /// [b1, b0, a1, a0] => [c1, c0] - /// - /// c1 = ((a0 + a1) * (b0 + b1)) mod p - /// c0 = ((a0 * b0) - 2(a1 * b1)) mod p - Ext2mul, - /// [a1, a0] => [a1', a0'] - /// - /// a1' = -a1 - /// a0' = -a0 - Ext2neg, - /// [a1, a0] => [a1', a0'] - /// - /// a' = a^-1 mod q (where `q` is the extension field prime) - /// - /// Fails if `a` = 0. - Ext2inv, - /// [b1, b0, a1, a0] => [c1, c0] - /// - /// c = a * b^-1 - /// - /// Fails if `b` is 0. Multiplication and inversion are defined by the ops above. - Ext2div, - /// Pops the top of the stack, and evaluates the ops in - /// the block of code corresponding to the branch taken. - /// - /// If the value is `1`, corresponding to `true`, the first block - /// is evaluated. Otherwise, the value must be `0`, corresponding to - /// `false`, and the second block is evaluated. - If(MasmBlockId, MasmBlockId), - /// Pops the top of the stack, and evaluates the given block of - /// code if the value is `1`, corresponding to `true`. - /// - /// Otherwise, the value must be `0`, corresponding to `false`, - /// and the block is skipped. - While(MasmBlockId), - /// Repeatedly evaluates the given block, `n` times. - Repeat(u16, MasmBlockId), - /// Pops `N` args off the stack, executes the procedure, results will be placed on the stack - Exec(FunctionIdent), - /// Pops `N` args off the stack, executes the procedure in the root context, results will be - /// placed on the stack - Syscall(FunctionIdent), - /// Pops `N` args off the stack, executes the procedure in a new context, results will be - /// placed on the stack - Call(FunctionIdent), - /// Pops the address (MAST root hash) of a callee off the stack, and dynamically `exec` the - /// function - DynExec, - /// TODO - DynCall, - /// Pushes the address (MAST root hash) of the given function on the stack, to be used by - /// `dynexec` or `dyncall` - ProcRef(FunctionIdent), - /// Pops `b, a` off the stack, and places the result of `(a + b) mod p` on the stack - Add, - /// Same as above, but the immediate is used for `b` - AddImm(Felt), - /// Pops `b, a` off the stack, and places the result of `(a - b) mod p` on the stack - Sub, - /// Same as above, but the immediate is used for `b` - SubImm(Felt), - /// Pops `b, a` off the stack, and places the result of `(a * b) mod p` on the stack - Mul, - /// Same as above, but the immediate is used for `b` - MulImm(Felt), - /// Pops `b, a` off the stack, and places the result of `(a * b^-1) mod p` on the stack - /// - /// NOTE: `b` must not be 0 - Div, - /// Same as above, but the immediate is used for `b` - DivImm(Felt), - /// Pops `a` off the stack, and places the result of `-a mod p` on the stack - Neg, - /// Pops `a` off the stack, and places the result of `a^-1 mod p` on the stack - /// - /// NOTE: `a` must not be equal to 0 - Inv, - /// Pops `a` off the stack, and places the result of incrementing it by 1 back on the stack - Incr, - /// Computes the base 2 logarithm of `a`, rounded down, and places it on the advice stack. - Ilog2, - /// Pops `a` off the stack, and places the result of `2^a` on the stack - /// - /// NOTE: `a` must not be > 63 - Pow2, - /// Pops `a` and `b` off the stack, and places the result of `a^b` on the stack - /// - /// NOTE: `b` must not be > 63 - Exp, - /// Pops `a` off the stack, and places the result of `a^` on the stack - /// - /// NOTE: `imm` must not be > 63 - ExpImm(u8), - ExpBitLength(u8), - /// Pops `a` off the stack, and places the result of `1 - a` on the stack - /// - /// NOTE: `a` must be boolean - Not, - /// Pops `b, a` off the stack, and places the result of `a * b` on the stack - /// - /// NOTE: `a` must be boolean - And, - /// Same as above, but `a` is taken from the stack, and `b` is the immediate. - /// - /// NOTE: `a` must be boolean - AndImm(bool), - /// Pops `b, a` off the stack, and places the result of `a + b - a * b` on the stack - /// - /// NOTE: `a` must be boolean - Or, - /// Same as above, but `a` is taken from the stack, and `b` is the immediate. - /// - /// NOTE: `a` must be boolean - OrImm(bool), - /// Pops `b, a` off the stack, and places the result of `a + b - 2 * a * b` on the stack - /// - /// NOTE: `a` and `b` must be boolean - Xor, - /// Same as above, but `a` is taken from the stack, and `b` is the immediate. - /// - /// NOTE: `a` must be boolean - XorImm(bool), - /// Pops `b, a` off the stack, and places the result of `a == b` on the stack - Eq, - /// Same as above, but `b` is provided by the immediate - EqImm(Felt), - /// Pops `b, a` off the stack, and places the result of `a != b` on the stack - Neq, - /// Same as above, but `b` is provided by the immediate - NeqImm(Felt), - /// Pops `b, a` off the stack, and places the result of `a > b` on the stack - Gt, - /// Same as above, but `b` is provided by the immediate - GtImm(Felt), - /// Pops `b, a` off the stack, and places the result of `a >= b` on the stack - Gte, - /// Same as above, but `b` is provided by the immediate - GteImm(Felt), - /// Pops `b, a` off the stack, and places the result of `a < b` on the stack - Lt, - /// Same as above, but `b` is provided by the immediate - LtImm(Felt), - /// Pops `b, a` off the stack, and places the result of `a <= b` on the stack - Lte, - /// Same as above, but `b` is provided by the immediate - LteImm(Felt), - /// Pops `a` off the stack, and places the 1 on the stack if `a` is odd, else 0 - IsOdd, - /// Pops `B, A` off the stack, and places the result of `A == B` on the stack, - /// where the uppercase variables here represent words, rather than field elements. - /// - /// The comparison works by comparing pairs of elements from each word - Eqw, - /// Pushes the current depth of the operand stack, on the stack - Sdepth, - /// When the current procedure is called via `syscall`, this pushes the hash of the caller's - /// MAST root on the stack - Caller, - /// Pushes the current value of the cycle counter (clock) on the stack - Clk, - /// Peeks `a` from the top of the stack, and places the 1 on the stack if `a < 2^32`, else 0 - U32Test, - /// Peeks `A` from the top of the stack, and places the 1 on the stack if `forall a : A, a < - /// 2^32`, else 0 - U32Testw, - /// Peeks `a` from the top of the stack, and traps if `a >= 2^32` - U32Assert, - /// Peeks `a` from the top of the stack, and traps if `a >= 2^32`, raising the given error code - U32AssertWithError(u32), - /// Peeks `b, a` from the top of the stack, and traps if either `a` or `b` is >= 2^32 - U32Assert2, - /// Peeks `b, a` from the top of the stack, and traps if either `a` or `b` is >= 2^32, raising - /// the given error code - U32Assert2WithError(u32), - /// Peeks `A` from the top of the stack, and traps unless `forall a : A, a < 2^32`, else 0 - U32Assertw, - /// Peeks `A` from the top of the stack, and traps unless `forall a : A, a < 2^32`, else 0, - /// raising the given error code - U32AssertwWithError(u32), - /// Pops `a` from the top of the stack, and places the result of `a mod 2^32` on the stack - /// - /// This is used to cast a field element to the u32 range - U32Cast, - /// Pops `a` from the top of the stack, and splits it into upper and lower 32-bit values, - /// placing them back on the stack. The lower part is calculated as `a mod 2^32`, - /// and the higher part as `a / 2^32`. The higher part will be on top of the stack after. - U32Split, - /// Pops `b, a` from the stack, and places the result of `(a + b) mod 2^32` on the stack, - /// followed by 1 if `(a + b) >= 2^32`, else 0. Thus the first item on the stack will be - /// a boolean indicating whether the arithmetic overflowed, and the second will be the - /// result of the addition. - /// - /// The behavior is undefined if either `b` or `a` are >= 2^32 - U32OverflowingAdd, - /// Same as above, but with `b` provided by the immediate - U32OverflowingAddImm(u32), - /// Pops `b, a` from the stack, and places the result of `(a + b) mod 2^32` on the stack. - /// - /// The behavior is undefined if either `b` or `a` are >= 2^32 - U32WrappingAdd, - /// Same as above, but with `b` provided by the immediate - U32WrappingAddImm(u32), - /// Pops `c, b, a` from the stack, adds them together, and splits the result into higher - /// and lower parts. The lower part is calculated as `(a + b + c) mod 2^32`, - /// the higher part as `(a + b + c) / 2^32`. - /// - /// The behavior is undefined if any of `c`, `b` or `a` are >= 2^32 - U32OverflowingAdd3, - /// Pops `c, b, a` from the stack, adds them together, and splits the result into higher - /// and lower parts. The lower part is calculated as `(a + b + c) mod 2^32`, - /// the higher part as `(a + b + c) / 2^32`. - /// - /// The behavior is undefined if any of `c`, `b` or `a` are >= 2^32 - U32WrappingAdd3, - /// Pops `b, a` from the stack, and places the result of `(a - b) mod 2^32` on the stack, - /// followed by 1 if `a < b`, else 0. Thus the first item on the stack will be - /// a boolean indicating whether the arithmetic underflowed, and the second will be the - /// result of the subtraction. - /// - /// The behavior is undefined if either `b` or `a` are >= 2^32 - U32OverflowingSub, - /// Same as above, but with `b` provided by the immediate - U32OverflowingSubImm(u32), - /// Pops `b, a` from the stack, and places the result of `(a - b) mod 2^32` on the stack. - /// - /// The behavior is undefined if either `b` or `a` are >= 2^32 - U32WrappingSub, - /// Same as above, but with `b` provided by the immediate - U32WrappingSubImm(u32), - /// Pops `b, a` from the stack, and places the result of `(a * b) mod 2^32` on the stack, - /// followed by `(a * b) / 2^32`. Thus the first item on the stack will be the number - /// of times the multiplication overflowed, followed by the result. - /// - /// The behavior is undefined if either `b` or `a` are >= 2^32 - U32OverflowingMul, - /// Same as above, but with `b` provided by the immediate - U32OverflowingMulImm(u32), - /// Pops `b, a` from the stack, and places the result of `(a * b) mod 2^32` on the stack. - /// - /// The behavior is undefined if either `b` or `a` are >= 2^32 - U32WrappingMul, - /// Same as above, but with `b` provided by the immediate - U32WrappingMulImm(u32), - /// Pops `c, b, a` off the stack, and calculates `d = c * b + a`, then splits the result - /// into higher and lower parts, the lower given by `d mod 2^32`, the higher by `d / 2^32`, - /// and pushes them back on the stack, with the higher part on top of the stack at the end. - /// - /// Behavior is undefined if any of `a`, `b`, or `c` are >= 2^32 - U32OverflowingMadd, - /// Pops `c, b, a` off the stack, and pushes `(c * a + b) mod 2^32` on the stack. - /// - /// Behavior is undefined if any of `a`, `b`, or `c` are >= 2^32 - U32WrappingMadd, - /// Pops `b, a` off the stack, and pushes `a / b` on the stack. - /// - /// This operation traps if `b` is zero. - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Div, - /// Same as above, except `b` is provided by the immediate - U32DivImm(u32), - /// Pops `b, a` off the stack, and pushes `a mod b` on the stack. - /// - /// This operation traps if `b` is zero. - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Mod, - /// Same as above, except `b` is provided by the immediate - U32ModImm(u32), - /// Pops `b, a` off the stack, and first pushes `a / b` on the stack, followed by `a mod b`. - /// - /// This operation traps if `b` is zero. - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32DivMod, - /// Same as above, except `b` is provided by the immediate - U32DivModImm(u32), - /// Pops `b, a` off the stack, and places the bitwise AND of `a` and `b` on the stack. - /// - /// Traps if either `a` or `b` >= 2^32 - U32And, - /// Pops `b, a` off the stack, and places the bitwise OR of `a` and `b` on the stack. - /// - /// Traps if either `a` or `b` >= 2^32 - U32Or, - /// Pops `b, a` off the stack, and places the bitwise XOR of `a` and `b` on the stack. - /// - /// Traps if either `a` or `b` >= 2^32 - U32Xor, - /// Pops `a` off the stack, and places the bitwise NOT of `a` on the stack. - /// - /// Traps if `a >= 2^32` - U32Not, - /// Pops `b, a` off the stack, and places the result of `(a * 2^b) mod 2^32` on the stack. - /// - /// Truncates if the shift would cause overflow. - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Shl, - /// Same as above, except `b` is provided by the immediate - U32ShlImm(u32), - /// Pops `b, a` off the stack, and places the result of `a / 2^b` on the stack. - /// - /// Truncates if the shift would cause overflow. - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Shr, - /// Same as above, except `b` is provided by the immediate - U32ShrImm(u32), - /// Pops `b, a` off the stack, and places the result of rotating the 32-bit - /// representation of `a` to the left by `b` bits. - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Rotl, - /// Same as above, except `b` is provided by the immediate - U32RotlImm(u32), - /// Pops `b, a` off the stack, and places the result of rotating the 32-bit - /// representation of `a` to the right by `b` bits. - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Rotr, - /// Same as above, except `b` is provided by the immediate - U32RotrImm(u32), - /// Pops `a` off the stack, and places the number of set bits in `a` (it's hamming weight). - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Popcnt, - /// Computes the number of leading zero bits in `a`, and places it on the advice stack - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Clz, - /// Computes the number of trailing zero bits in `a`, and places it on the advice stack - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Ctz, - /// Computes the number of leading one bits in `a`, and places it on the advice stack - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Clo, - /// Computes the number of trailing one bits in `a`, and places it on the advice stack - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Cto, - /// Pops `b, a` from the stack, and places 1 on the stack if `a < b`, else 0 - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Lt, - /// Same as above, but `b` is provided by the immediate - U32LtImm(u32), - /// Pops `b, a` from the stack, and places 1 on the stack if `a <= b`, else 0 - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Lte, - /// Same as above, but `b` is provided by the immediate - U32LteImm(u32), - /// Pops `b, a` from the stack, and places 1 on the stack if `a > b`, else 0 - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Gt, - /// Same as above, but `b` is provided by the immediate - U32GtImm(u32), - /// Pops `b, a` from the stack, and places 1 on the stack if `a >= b`, else 0 - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Gte, - /// Same as above, but `b` is provided by the immediate - U32GteImm(u32), - /// Pops `b, a` from the stack, and places `a` back on the stack if `a < b`, else `b` - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Min, - /// Same as above, but `b` is provided by the immediate - U32MinImm(u32), - /// Pops `b, a` from the stack, and places `a` back on the stack if `a > b`, else `b` - /// - /// This operation is unchecked, so the result is undefined if the operands are not valid u32 - U32Max, - /// Same as above, but `b` is provided by the immediate - U32MaxImm(u32), - /// Trigger a breakpoint when this instruction is reached - Breakpoint, - /// Print out the contents of the stack - DebugStack, - /// Print out the top `n` contents of the stack - DebugStackN(u8), - /// Print out the entire contents of RAM - DebugMemory, - /// Print out the contents of RAM starting at address `n` - DebugMemoryAt(u32), - /// Print out the contents of RAM in the range `n..=m` - DebugMemoryRange(u32, u32), - /// Print out the local memory for the current procedure - DebugFrame, - /// Print out the local memory for the current procedure starting at index `n` - DebugFrameAt(u16), - /// Print out the local memory for the current procedure for indices in the range `n..=m` - DebugFrameRange(u16, u16), - /// Emit an event with the given event code - Emit(u32), - /// Emit a trace event with the given code - Trace(u32), - /// No operation - Nop, -} - -macro_rules! unwrap_imm { - ($imm:ident) => {{ - match $imm { - miden_assembly::ast::Immediate::Value(imm) => imm.into_inner(), - miden_assembly::ast::Immediate::Constant(id) => { - panic!("invalid reference to constant definition: '{id}'") - } - } - }}; -} - -macro_rules! unwrap_u32 { - ($imm:ident) => {{ - match $imm { - miden_assembly::ast::Immediate::Value(imm) => imm.into_inner(), - miden_assembly::ast::Immediate::Constant(id) => { - panic!("invalid reference to constant definition: '{id}'") - } - } - }}; -} - -macro_rules! unwrap_u16 { - ($imm:ident) => {{ - match $imm { - miden_assembly::ast::Immediate::Value(imm) => imm.into_inner(), - miden_assembly::ast::Immediate::Constant(id) => { - panic!("invalid reference to constant definition: '{id}'") - } - } - }}; -} - -macro_rules! unwrap_u8 { - ($imm:ident) => {{ - match $imm { - miden_assembly::ast::Immediate::Value(imm) => imm.into_inner(), - miden_assembly::ast::Immediate::Constant(id) => { - panic!("invalid reference to constant definition: '{id}'") - } - } - }}; -} - -impl MasmOp { - pub fn has_regions(&self) -> bool { - matches!(self, Self::If(_, _) | Self::While(_) | Self::Repeat(_, _)) - } - - /// The cost of this instruction in cycles - pub fn cost(&self) -> usize { - match self { - Self::Padw => 4, - Self::Push(_) | Self::PushU8(_) | Self::PushU16(_) | Self::PushU32(_) => 1, - Self::Push2(_) => 2, - Self::Pushw(_) => 4, - Self::Drop => 1, - Self::Dropw => 4, - Self::Dup(8) | Self::Dup(10) | Self::Dup(12) | Self::Dup(14) => 3, - Self::Dup(_) => 1, - Self::Dupw(_) => 4, - Self::Swap(1) => 1, - Self::Swap(2..=8) => 2, - Self::Swap(_) => 6, - Self::Swapw(_) | Self::Swapdw => 1, - Self::Movup(2..=8) => 1, - Self::Movup(_) => 4, - Self::Movupw(2) => 2, - Self::Movupw(_) => 3, - Self::Movdn(2..=8) => 1, - Self::Movdn(_) => 4, - Self::Movdnw(2) => 2, - Self::Movdnw(_) => 3, - Self::Cswap => 1, - Self::Cswapw => 1, - Self::Cdrop => 2, - Self::Cdropw => 5, - Self::Assert | Self::AssertWithError(_) => 1, - Self::Assertz | Self::AssertzWithError(_) => 2, - Self::AssertEq | Self::AssertEqWithError(_) => 2, - Self::AssertEqw | Self::AssertEqwWithError(_) => 11, - Self::LocAddr(_) => 2, - Self::LocStore(id) if id.as_usize() == 1 => 5, - Self::LocStore(_) => 4, - Self::LocStorew(id) if id.as_usize() == 1 => 4, - Self::LocStorew(_) => 3, - Self::LocLoad(id) | Self::LocLoadw(id) if id.as_usize() == 1 => 4, - Self::LocLoad(_) | Self::LocLoadw(_) => 3, - Self::MemLoad | Self::MemLoadw => 1, - Self::MemLoadImm(_) | Self::MemLoadwImm(_) => 2, - Self::MemStore => 2, - Self::MemStoreImm(1) => 4, - Self::MemStoreImm(_) => 3, - Self::MemStorew => 1, - Self::MemStorewImm(1) => 3, - Self::MemStorewImm(_) => 2, - Self::MemStream => 1, - Self::AdvPipe => 1, - Self::AdvPush(n) => *n as usize, - Self::AdvLoadw => 1, - // This is based on cycle counts gathered from a simple program that compares a - // cdrop-based conditional select to an if-based one, where the only - // difference is the `cdrop` and `if` instructions. The `cdrop` solution - // was 39 cycles, the `if` solution was 49, with `cdrop` taking 2 cycles, - // this gives us a difference of 10 cycles, hence 12 for our cost. - Self::If(..) => 12, - // The cost for `while` appears to be the same as `if`, however comparisons are tricky - // as we can only really compare to `repeat`, which has no apparent cost - Self::While(_) => 12, - // Comparing a small program with `repeat.1` vs without the `repeat.1` (simply using the - // body of the `repeat` instead), there is no apparent cycle cost. We give - // it a cost of 0 to reflect that using `repeat` is no different than - // copying its body `N` times. - Self::Repeat(..) => 0, - Self::ProcRef(_) => 4, - Self::Exec(_) => 2, - // A `call` appears to have the same overhead as `if` and `while` - Self::Call(_) | Self::Syscall(_) => 12, - // A `dynexec` appears to be 8 cycles, based on comparisons against `exec`, with an - // extra `dropw` in the callee that we deduct from the cycle count - Self::DynExec => 8, - // A `dyncall` requires an additional 8 cycles compared to `dynexec` - Self::DynCall => 16, - Self::Add | Self::Sub | Self::Mul => 1, - Self::AddImm(imm) => match imm.as_int() { - 0 => 0, - 1 => 1, - _ => 2, - }, - Self::SubImm(imm) | Self::MulImm(imm) => match imm.as_int() { - 0 => 0, - _ => 2, - }, - Self::Div => 2, - Self::DivImm(imm) => match imm.as_int() { - 1 => 0, - _ => 2, - }, - Self::Neg | Self::Inv | Self::Incr => 1, - Self::Ilog2 => 44, - Self::Pow2 => 16, - // The cost of this instruction is 9 + log2(b), but we don't know `b`, so we use a value - // of 32 to estimate average cost - Self::Exp => 9 + 32usize.ilog2() as usize, - Self::ExpImm(0) => 3, - Self::ExpImm(1) => 1, - Self::ExpImm(2) => 2, - Self::ExpImm(3) => 4, - Self::ExpImm(4) => 6, - Self::ExpImm(5) => 8, - Self::ExpImm(6) => 10, - Self::ExpImm(7) => 12, - Self::ExpImm(imm) | Self::ExpBitLength(imm) => { - 9 + unsafe { f64::from(*imm).log2().ceil().to_int_unchecked::() } - } - Self::Not | Self::And | Self::Or => 1, - Self::AndImm(_) | Self::OrImm(_) => 2, - Self::Xor => 7, - Self::XorImm(_) => 8, - Self::Eq => 1, - Self::EqImm(imm) => match imm.as_int() { - 0 => 1, - _ => 2, - }, - Self::Neq => 2, - Self::NeqImm(imm) => match imm.as_int() { - 0 => 1, - _ => 3, - }, - Self::Gt => 15, - Self::GtImm(_) => 16, - Self::Gte => 16, - Self::GteImm(_) => 17, - Self::Lt => 14, - Self::LtImm(_) => 15, - Self::Lte => 15, - Self::LteImm(_) => 16, - Self::IsOdd => 5, - Self::Eqw => 15, - Self::Hash => 20, - Self::Hmerge => 16, - Self::Hperm => 1, - Self::MtreeGet => 9, - Self::MtreeSet => 29, - Self::MtreeMerge => 16, - Self::MtreeVerify | Self::MtreeVerifyWithError(_) => 1, - // This hasn't been measured, just a random guess due to the complexity - Self::FriExt2Fold4 | Self::RCombBase => 50, - Self::Ext2add => 5, - Self::Ext2sub => 7, - Self::Ext2mul => 3, - Self::Ext2neg => 4, - Self::Ext2inv => 8, - Self::Ext2div => 11, - Self::Clk | Self::Caller | Self::Sdepth => 1, - Self::U32Test => 5, - Self::U32Testw => 23, - Self::U32Assert | Self::U32AssertWithError(_) => 3, - Self::U32Assert2 | Self::U32Assert2WithError(_) => 1, - Self::U32Assertw | Self::U32AssertwWithError(_) => 6, - Self::U32Cast => 2, - Self::U32Split => 1, - Self::U32OverflowingAdd => 1, - Self::U32OverflowingAddImm(_) => 2, - Self::U32WrappingAdd => 2, - Self::U32WrappingAddImm(_) => 3, - Self::U32OverflowingAdd3 => 1, - Self::U32WrappingAdd3 => 2, - Self::U32OverflowingSub => 1, - Self::U32OverflowingSubImm(_) => 2, - Self::U32WrappingSub => 2, - Self::U32WrappingSubImm(_) => 3, - Self::U32OverflowingMul => 1, - Self::U32OverflowingMulImm(_) => 2, - Self::U32WrappingMul => 2, - Self::U32WrappingMulImm(_) => 3, - Self::U32OverflowingMadd => 1, - Self::U32WrappingMadd => 2, - Self::U32Div => 2, - Self::U32DivImm(_) => 3, - Self::U32Mod => 3, - Self::U32ModImm(_) => 4, - Self::U32DivMod => 1, - Self::U32DivModImm(_) => 2, - Self::U32And => 1, - Self::U32Or => 6, - Self::U32Xor => 1, - Self::U32Not => 5, - Self::U32Shl => 18, - Self::U32ShlImm(0) => 0, - Self::U32ShlImm(_) => 3, - Self::U32Shr => 18, - Self::U32ShrImm(0) => 0, - Self::U32ShrImm(_) => 3, - Self::U32Rotl => 18, - Self::U32RotlImm(0) => 0, - Self::U32RotlImm(_) => 3, - Self::U32Rotr => 22, - Self::U32RotrImm(0) => 0, - Self::U32RotrImm(_) => 3, - Self::U32Popcnt => 33, - Self::U32Clz => 37, - Self::U32Ctz => 34, - Self::U32Clo => 36, - Self::U32Cto => 33, - Self::U32Lt => 3, - Self::U32LtImm(_) => 4, - Self::U32Lte => 5, - Self::U32LteImm(_) => 6, - Self::U32Gt => 4, - Self::U32GtImm(_) => 5, - Self::U32Gte => 4, - Self::U32GteImm(_) => 5, - Self::U32Min => 8, - Self::U32MinImm(_) => 9, - Self::U32Max => 9, - Self::U32MaxImm(_) => 10, - // These instructions do not modify the VM state, so we place set their cost at 0 for - // now - Self::Emit(_) - | Self::Trace(_) - | Self::AdvInjectPushU64Div - | Self::AdvInjectPushMapVal - | Self::AdvInjectPushMapValImm(_) - | Self::AdvInjectPushMapValN - | Self::AdvInjectPushMapValNImm(_) - | Self::AdvInjectPushMTreeNode - | Self::AdvInjectInsertMem - | Self::AdvInjectInsertHdword - | Self::AdvInjectInsertHdwordImm(_) - | Self::AdvInjectInsertHperm - | Self::AdvInjectPushSignature(_) - | Self::DebugStack - | Self::DebugStackN(_) - | Self::DebugMemory - | Self::DebugMemoryAt(_) - | Self::DebugMemoryRange(..) - | Self::DebugFrame - | Self::DebugFrameAt(_) - | Self::DebugFrameRange(..) - | Self::Breakpoint - | Self::Nop => 0, - } - } - - pub fn from_masm( - current_module: Ident, - ix: miden_assembly::ast::Instruction, - ) -> SmallVec<[Self; 2]> { - use miden_assembly::ast::{Instruction, InvocationTarget}; - - use crate::Symbol; - - let op = match ix { - Instruction::Assert => Self::Assert, - Instruction::AssertWithError(code) => Self::AssertWithError(unwrap_u32!(code)), - Instruction::AssertEq => Self::AssertEq, - Instruction::AssertEqWithError(code) => Self::AssertEqWithError(unwrap_u32!(code)), - Instruction::AssertEqw => Self::AssertEqw, - Instruction::AssertEqwWithError(code) => Self::AssertEqwWithError(unwrap_u32!(code)), - Instruction::Assertz => Self::Assertz, - Instruction::AssertzWithError(code) => Self::AssertzWithError(unwrap_u32!(code)), - Instruction::Add => Self::Add, - Instruction::AddImm(imm) => Self::AddImm(unwrap_imm!(imm)), - Instruction::Sub => Self::Sub, - Instruction::SubImm(imm) => Self::SubImm(unwrap_imm!(imm)), - Instruction::Mul => Self::Mul, - Instruction::MulImm(imm) => Self::MulImm(unwrap_imm!(imm)), - Instruction::Div => Self::Div, - Instruction::DivImm(imm) => Self::DivImm(unwrap_imm!(imm)), - Instruction::Neg => Self::Neg, - Instruction::Inv => Self::Inv, - Instruction::Incr => Self::Incr, - Instruction::ILog2 => Self::Ilog2, - Instruction::Pow2 => Self::Pow2, - Instruction::Exp => Self::Exp, - Instruction::ExpImm(imm) => { - Self::ExpImm(unwrap_imm!(imm).as_int().try_into().expect("invalid exponent")) - } - Instruction::ExpBitLength(imm) => Self::ExpBitLength(imm), - Instruction::Not => Self::Not, - Instruction::And => Self::And, - Instruction::Or => Self::Or, - Instruction::Xor => Self::Xor, - Instruction::Eq => Self::Eq, - Instruction::EqImm(imm) => Self::EqImm(unwrap_imm!(imm)), - Instruction::Neq => Self::Neq, - Instruction::NeqImm(imm) => Self::NeqImm(unwrap_imm!(imm)), - Instruction::Eqw => Self::Eqw, - Instruction::Lt => Self::Lt, - Instruction::Lte => Self::Lte, - Instruction::Gt => Self::Gt, - Instruction::Gte => Self::Gte, - Instruction::IsOdd => Self::IsOdd, - Instruction::Hash => Self::Hash, - Instruction::HMerge => Self::Hmerge, - Instruction::HPerm => Self::Hperm, - Instruction::MTreeGet => Self::MtreeGet, - Instruction::MTreeSet => Self::MtreeSet, - Instruction::MTreeMerge => Self::MtreeMerge, - Instruction::MTreeVerify => Self::MtreeVerify, - Instruction::MTreeVerifyWithError(code) => { - Self::MtreeVerifyWithError(unwrap_u32!(code)) - } - Instruction::Ext2Add => Self::Ext2add, - Instruction::Ext2Sub => Self::Ext2sub, - Instruction::Ext2Mul => Self::Ext2mul, - Instruction::Ext2Div => Self::Ext2div, - Instruction::Ext2Neg => Self::Ext2neg, - Instruction::Ext2Inv => Self::Ext2inv, - Instruction::FriExt2Fold4 => Self::FriExt2Fold4, - Instruction::RCombBase => Self::RCombBase, - Instruction::U32Test => Self::U32Test, - Instruction::U32TestW => Self::U32Testw, - Instruction::U32Assert => Self::U32Assert, - Instruction::U32AssertWithError(code) => Self::U32AssertWithError(unwrap_u32!(code)), - Instruction::U32Assert2 => Self::U32Assert2, - Instruction::U32Assert2WithError(code) => Self::U32Assert2WithError(unwrap_u32!(code)), - Instruction::U32AssertW => Self::U32Assertw, - Instruction::U32AssertWWithError(code) => Self::U32AssertwWithError(unwrap_u32!(code)), - Instruction::U32Split => Self::U32Split, - Instruction::U32Cast => Self::U32Cast, - Instruction::U32WrappingAdd => Self::U32WrappingAdd, - Instruction::U32WrappingAddImm(imm) => Self::U32WrappingAddImm(unwrap_u32!(imm)), - Instruction::U32OverflowingAdd => Self::U32OverflowingAdd, - Instruction::U32OverflowingAddImm(imm) => Self::U32OverflowingAddImm(unwrap_u32!(imm)), - Instruction::U32OverflowingAdd3 => Self::U32OverflowingAdd3, - Instruction::U32WrappingAdd3 => Self::U32WrappingAdd3, - Instruction::U32WrappingSub => Self::U32WrappingSub, - Instruction::U32WrappingSubImm(imm) => Self::U32WrappingSubImm(unwrap_u32!(imm)), - Instruction::U32OverflowingSub => Self::U32OverflowingSub, - Instruction::U32OverflowingSubImm(imm) => Self::U32OverflowingSubImm(unwrap_u32!(imm)), - Instruction::U32WrappingMul => Self::U32WrappingMul, - Instruction::U32WrappingMulImm(imm) => Self::U32WrappingMulImm(unwrap_u32!(imm)), - Instruction::U32OverflowingMul => Self::U32OverflowingMul, - Instruction::U32OverflowingMulImm(imm) => Self::U32OverflowingMulImm(unwrap_u32!(imm)), - Instruction::U32OverflowingMadd => Self::U32OverflowingMadd, - Instruction::U32WrappingMadd => Self::U32WrappingMadd, - Instruction::U32Div => Self::U32Div, - Instruction::U32DivImm(imm) => Self::U32DivImm(unwrap_u32!(imm)), - Instruction::U32Mod => Self::U32Mod, - Instruction::U32ModImm(imm) => Self::U32ModImm(unwrap_u32!(imm)), - Instruction::U32DivMod => Self::U32DivMod, - Instruction::U32DivModImm(imm) => Self::U32DivModImm(unwrap_u32!(imm)), - Instruction::U32And => Self::U32And, - Instruction::U32Or => Self::U32Or, - Instruction::U32Xor => Self::U32Xor, - Instruction::U32Not => Self::U32Not, - Instruction::U32Shr => Self::U32Shr, - Instruction::U32ShrImm(imm) => Self::U32ShrImm(unwrap_u8!(imm) as u32), - Instruction::U32Shl => Self::U32Shl, - Instruction::U32ShlImm(imm) => Self::U32ShlImm(unwrap_u8!(imm) as u32), - Instruction::U32Rotr => Self::U32Rotr, - Instruction::U32RotrImm(imm) => Self::U32RotrImm(unwrap_u8!(imm) as u32), - Instruction::U32Rotl => Self::U32Rotl, - Instruction::U32RotlImm(imm) => Self::U32RotlImm(unwrap_u8!(imm) as u32), - Instruction::U32Popcnt => Self::U32Popcnt, - Instruction::U32Clz => Self::U32Clz, - Instruction::U32Ctz => Self::U32Ctz, - Instruction::U32Clo => Self::U32Clo, - Instruction::U32Cto => Self::U32Cto, - Instruction::U32Lt => Self::U32Lt, - Instruction::U32Lte => Self::U32Lte, - Instruction::U32Gt => Self::U32Gt, - Instruction::U32Gte => Self::U32Gte, - Instruction::U32Min => Self::U32Min, - Instruction::U32Max => Self::U32Max, - Instruction::Drop => Self::Drop, - Instruction::DropW => Self::Dropw, - Instruction::PadW => Self::Padw, - Instruction::Dup0 => Self::Dup(0), - Instruction::Dup1 => Self::Dup(1), - Instruction::Dup2 => Self::Dup(2), - Instruction::Dup3 => Self::Dup(3), - Instruction::Dup4 => Self::Dup(4), - Instruction::Dup5 => Self::Dup(5), - Instruction::Dup6 => Self::Dup(6), - Instruction::Dup7 => Self::Dup(7), - Instruction::Dup8 => Self::Dup(8), - Instruction::Dup9 => Self::Dup(9), - Instruction::Dup10 => Self::Dup(10), - Instruction::Dup11 => Self::Dup(11), - Instruction::Dup12 => Self::Dup(12), - Instruction::Dup13 => Self::Dup(13), - Instruction::Dup14 => Self::Dup(14), - Instruction::Dup15 => Self::Dup(15), - Instruction::DupW0 => Self::Dupw(0), - Instruction::DupW1 => Self::Dupw(1), - Instruction::DupW2 => Self::Dupw(2), - Instruction::DupW3 => Self::Dupw(3), - Instruction::Swap1 => Self::Swap(1), - Instruction::Swap2 => Self::Swap(2), - Instruction::Swap3 => Self::Swap(3), - Instruction::Swap4 => Self::Swap(4), - Instruction::Swap5 => Self::Swap(5), - Instruction::Swap6 => Self::Swap(6), - Instruction::Swap7 => Self::Swap(7), - Instruction::Swap8 => Self::Swap(8), - Instruction::Swap9 => Self::Swap(9), - Instruction::Swap10 => Self::Swap(10), - Instruction::Swap11 => Self::Swap(11), - Instruction::Swap12 => Self::Swap(12), - Instruction::Swap13 => Self::Swap(13), - Instruction::Swap14 => Self::Swap(14), - Instruction::Swap15 => Self::Swap(15), - Instruction::SwapW1 => Self::Swapw(1), - Instruction::SwapW2 => Self::Swapw(2), - Instruction::SwapW3 => Self::Swapw(3), - Instruction::SwapDw => Self::Swapdw, - Instruction::MovUp2 => Self::Movup(2), - Instruction::MovUp3 => Self::Movup(3), - Instruction::MovUp4 => Self::Movup(4), - Instruction::MovUp5 => Self::Movup(5), - Instruction::MovUp6 => Self::Movup(6), - Instruction::MovUp7 => Self::Movup(7), - Instruction::MovUp8 => Self::Movup(8), - Instruction::MovUp9 => Self::Movup(9), - Instruction::MovUp10 => Self::Movup(10), - Instruction::MovUp11 => Self::Movup(11), - Instruction::MovUp12 => Self::Movup(12), - Instruction::MovUp13 => Self::Movup(13), - Instruction::MovUp14 => Self::Movup(14), - Instruction::MovUp15 => Self::Movup(15), - Instruction::MovUpW2 => Self::Movupw(2), - Instruction::MovUpW3 => Self::Movupw(3), - Instruction::MovDn2 => Self::Movdn(2), - Instruction::MovDn3 => Self::Movdn(3), - Instruction::MovDn4 => Self::Movdn(4), - Instruction::MovDn5 => Self::Movdn(5), - Instruction::MovDn6 => Self::Movdn(6), - Instruction::MovDn7 => Self::Movdn(7), - Instruction::MovDn8 => Self::Movdn(8), - Instruction::MovDn9 => Self::Movdn(9), - Instruction::MovDn10 => Self::Movdn(10), - Instruction::MovDn11 => Self::Movdn(11), - Instruction::MovDn12 => Self::Movdn(12), - Instruction::MovDn13 => Self::Movdn(13), - Instruction::MovDn14 => Self::Movdn(14), - Instruction::MovDn15 => Self::Movdn(15), - Instruction::MovDnW2 => Self::Movdnw(2), - Instruction::MovDnW3 => Self::Movdnw(3), - Instruction::CSwap => Self::Cswap, - Instruction::CSwapW => Self::Cswapw, - Instruction::CDrop => Self::Cdrop, - Instruction::CDropW => Self::Cdropw, - Instruction::Push(elem) => Self::Push(unwrap_imm!(elem)), - Instruction::PushU8(elem) => Self::PushU8(elem), - Instruction::PushU16(elem) => Self::PushU16(elem), - Instruction::PushU32(elem) => Self::PushU32(elem), - Instruction::PushFelt(elem) => Self::Push(elem), - Instruction::PushWord(word) => Self::Pushw(word), - Instruction::PushU8List(u8s) => return u8s.into_iter().map(Self::PushU8).collect(), - Instruction::PushU16List(u16s) => return u16s.into_iter().map(Self::PushU16).collect(), - Instruction::PushU32List(u32s) => return u32s.into_iter().map(Self::PushU32).collect(), - Instruction::PushFeltList(felts) => return felts.into_iter().map(Self::Push).collect(), - Instruction::Locaddr(id) => Self::LocAddr(LocalId::from_u16(unwrap_u16!(id))), - Instruction::LocStore(id) => Self::LocStore(LocalId::from_u16(unwrap_u16!(id))), - Instruction::LocStoreW(id) => Self::LocStorew(LocalId::from_u16(unwrap_u16!(id))), - Instruction::Clk => Self::Clk, - Instruction::MemLoad => Self::MemLoad, - Instruction::MemLoadImm(addr) => Self::MemLoadImm(unwrap_u32!(addr)), - Instruction::MemLoadW => Self::MemLoadw, - Instruction::MemLoadWImm(addr) => Self::MemLoadwImm(unwrap_u32!(addr)), - Instruction::MemStore => Self::MemStore, - Instruction::MemStoreImm(addr) => Self::MemStoreImm(unwrap_u32!(addr)), - Instruction::MemStoreW => Self::MemStorew, - Instruction::MemStoreWImm(addr) => Self::MemStorewImm(unwrap_u32!(addr)), - Instruction::LocLoad(imm) => Self::LocLoad(LocalId::from_u16(unwrap_u16!(imm))), - Instruction::LocLoadW(imm) => Self::LocLoadw(LocalId::from_u16(unwrap_u16!(imm))), - Instruction::MemStream => Self::MemStream, - Instruction::AdvPipe => Self::AdvPipe, - Instruction::AdvPush(byte) => Self::AdvPush(unwrap_u8!(byte)), - Instruction::AdvLoadW => Self::AdvLoadw, - Instruction::AdvInject(AdviceInjectorNode::InsertMem) => Self::AdvInjectInsertMem, - Instruction::AdvInject(AdviceInjectorNode::InsertHperm) => Self::AdvInjectInsertHperm, - Instruction::AdvInject(AdviceInjectorNode::InsertHdword) => Self::AdvInjectInsertHdword, - Instruction::AdvInject(AdviceInjectorNode::InsertHdwordImm { domain }) => { - Self::AdvInjectInsertHdwordImm(unwrap_u8!(domain)) - } - Instruction::AdvInject(AdviceInjectorNode::PushU64Div) => Self::AdvInjectPushU64Div, - Instruction::AdvInject(AdviceInjectorNode::PushMtNode) => Self::AdvInjectPushMTreeNode, - Instruction::AdvInject(AdviceInjectorNode::PushMapVal) => Self::AdvInjectPushMapVal, - Instruction::AdvInject(AdviceInjectorNode::PushMapValImm { offset }) => { - Self::AdvInjectPushMapValImm(unwrap_u8!(offset)) - } - Instruction::AdvInject(AdviceInjectorNode::PushMapValN) => Self::AdvInjectPushMapValN, - Instruction::AdvInject(AdviceInjectorNode::PushMapValNImm { offset }) => { - Self::AdvInjectPushMapValNImm(unwrap_u8!(offset)) - } - Instruction::AdvInject(AdviceInjectorNode::PushSignature { kind }) => { - Self::AdvInjectPushSignature(kind) - } - Instruction::AdvInject(injector) => { - unimplemented!("unsupported advice injector: {injector:?}") - } - ref ix @ (Instruction::Exec(ref target) - | Instruction::SysCall(ref target) - | Instruction::Call(ref target) - | Instruction::ProcRef(ref target)) => { - let id = match target { - InvocationTarget::AbsoluteProcedurePath { name, path } => { - let name: &str = name.as_ref(); - let function = Ident::with_empty_span(Symbol::intern(name)); - let module = Ident::with_empty_span(Symbol::intern(path.to_string())); - FunctionIdent { module, function } - } - InvocationTarget::ProcedurePath { name, module } => { - let name: &str = name.as_ref(); - let function = Ident::with_empty_span(Symbol::intern(name)); - let module = Ident::with_empty_span(Symbol::intern(module.as_str())); - FunctionIdent { module, function } - } - InvocationTarget::ProcedureName(name) => { - let name: &str = name.as_ref(); - let function = Ident::with_empty_span(Symbol::intern(name)); - FunctionIdent { - module: current_module, - function, - } - } - InvocationTarget::MastRoot(_root) => { - todo!("support for referencing mast roots is not yet implemented") - } - }; - match ix { - Instruction::Exec(_) => Self::Exec(id), - Instruction::SysCall(_) => Self::Syscall(id), - Instruction::Call(_) => Self::Call(id), - Instruction::ProcRef(_) => Self::ProcRef(id), - _ => unreachable!(), - } - } - Instruction::DynExec => Self::DynExec, - Instruction::DynCall => Self::DynCall, - Instruction::Caller => Self::Caller, - Instruction::Sdepth => Self::Sdepth, - Instruction::Breakpoint => Self::Breakpoint, - Instruction::Emit(event) => Self::Emit(unwrap_u32!(event)), - Instruction::Trace(event) => Self::Trace(unwrap_u32!(event)), - Instruction::Debug(DebugOptions::StackAll) => Self::DebugStack, - Instruction::Debug(DebugOptions::StackTop(n)) => Self::DebugStackN(unwrap_u8!(n)), - Instruction::Debug(DebugOptions::MemAll) => Self::DebugMemory, - Instruction::Debug(DebugOptions::MemInterval(start, end)) => { - Self::DebugMemoryRange(unwrap_u32!(start), unwrap_u32!(end)) - } - Instruction::Debug(DebugOptions::LocalAll) => Self::DebugFrame, - Instruction::Debug(DebugOptions::LocalRangeFrom(start)) => { - Self::DebugFrameAt(unwrap_u16!(start)) - } - Instruction::Debug(DebugOptions::LocalInterval(start, end)) => { - Self::DebugFrameRange(unwrap_u16!(start), unwrap_u16!(end)) - } - Instruction::Nop => Self::Nop, - }; - smallvec![op] - } - - pub fn into_masm( - self, - imports: &super::ModuleImportInfo, - locals: &BTreeSet, - ) -> SmallVec<[miden_assembly::ast::Instruction; 2]> { - use miden_assembly::{ - ast::{Instruction, InvocationTarget, ProcedureName}, - LibraryPath, - }; - let inst = match self { - Self::Padw => Instruction::PadW, - Self::Push(v) => Instruction::PushFelt(v), - Self::Push2([a, b]) => Instruction::PushFeltList(vec![a, b]), - Self::Pushw(word) => Instruction::PushWord(word), - Self::PushU8(v) => Instruction::PushU8(v), - Self::PushU16(v) => Instruction::PushU16(v), - Self::PushU32(v) => Instruction::PushU32(v), - Self::Drop => Instruction::Drop, - Self::Dropw => Instruction::DropW, - Self::Dup(0) => Instruction::Dup0, - Self::Dup(1) => Instruction::Dup1, - Self::Dup(2) => Instruction::Dup2, - Self::Dup(3) => Instruction::Dup3, - Self::Dup(4) => Instruction::Dup4, - Self::Dup(5) => Instruction::Dup5, - Self::Dup(6) => Instruction::Dup6, - Self::Dup(7) => Instruction::Dup7, - Self::Dup(8) => Instruction::Dup8, - Self::Dup(9) => Instruction::Dup9, - Self::Dup(10) => Instruction::Dup10, - Self::Dup(11) => Instruction::Dup11, - Self::Dup(12) => Instruction::Dup12, - Self::Dup(13) => Instruction::Dup13, - Self::Dup(14) => Instruction::Dup14, - Self::Dup(15) => Instruction::Dup15, - Self::Dup(n) => { - panic!("invalid dup instruction, valid index range is 0..=15, got {n}") - } - Self::Dupw(0) => Instruction::DupW0, - Self::Dupw(1) => Instruction::DupW1, - Self::Dupw(2) => Instruction::DupW2, - Self::Dupw(3) => Instruction::DupW3, - Self::Dupw(n) => { - panic!("invalid dupw instruction, valid index range is 0..=3, got {n}") - } - Self::Swap(1) => Instruction::Swap1, - Self::Swap(2) => Instruction::Swap2, - Self::Swap(3) => Instruction::Swap3, - Self::Swap(4) => Instruction::Swap4, - Self::Swap(5) => Instruction::Swap5, - Self::Swap(6) => Instruction::Swap6, - Self::Swap(7) => Instruction::Swap7, - Self::Swap(8) => Instruction::Swap8, - Self::Swap(9) => Instruction::Swap9, - Self::Swap(10) => Instruction::Swap10, - Self::Swap(11) => Instruction::Swap11, - Self::Swap(12) => Instruction::Swap12, - Self::Swap(13) => Instruction::Swap13, - Self::Swap(14) => Instruction::Swap14, - Self::Swap(15) => Instruction::Swap15, - Self::Swap(n) => { - panic!("invalid swap instruction, valid index range is 1..=15, got {n}") - } - Self::Swapw(1) => Instruction::SwapW1, - Self::Swapw(2) => Instruction::SwapW2, - Self::Swapw(3) => Instruction::SwapW3, - Self::Swapw(n) => { - panic!("invalid swapw instruction, valid index range is 1..=3, got {n}") - } - Self::Swapdw => Instruction::SwapDw, - Self::Movup(2) => Instruction::MovUp2, - Self::Movup(3) => Instruction::MovUp3, - Self::Movup(4) => Instruction::MovUp4, - Self::Movup(5) => Instruction::MovUp5, - Self::Movup(6) => Instruction::MovUp6, - Self::Movup(7) => Instruction::MovUp7, - Self::Movup(8) => Instruction::MovUp8, - Self::Movup(9) => Instruction::MovUp9, - Self::Movup(10) => Instruction::MovUp10, - Self::Movup(11) => Instruction::MovUp11, - Self::Movup(12) => Instruction::MovUp12, - Self::Movup(13) => Instruction::MovUp13, - Self::Movup(14) => Instruction::MovUp14, - Self::Movup(15) => Instruction::MovUp15, - Self::Movup(n) => { - panic!("invalid movup instruction, valid index range is 2..=15, got {n}") - } - Self::Movupw(2) => Instruction::MovUpW2, - Self::Movupw(3) => Instruction::MovUpW3, - Self::Movupw(n) => { - panic!("invalid movupw instruction, valid index range is 2..=3, got {n}") - } - Self::Movdn(2) => Instruction::MovDn2, - Self::Movdn(3) => Instruction::MovDn3, - Self::Movdn(4) => Instruction::MovDn4, - Self::Movdn(5) => Instruction::MovDn5, - Self::Movdn(6) => Instruction::MovDn6, - Self::Movdn(7) => Instruction::MovDn7, - Self::Movdn(8) => Instruction::MovDn8, - Self::Movdn(9) => Instruction::MovDn9, - Self::Movdn(10) => Instruction::MovDn10, - Self::Movdn(11) => Instruction::MovDn11, - Self::Movdn(12) => Instruction::MovDn12, - Self::Movdn(13) => Instruction::MovDn13, - Self::Movdn(14) => Instruction::MovDn14, - Self::Movdn(15) => Instruction::MovDn15, - Self::Movdn(n) => { - panic!("invalid movdn instruction, valid index range is 2..=15, got {n}") - } - Self::Movdnw(2) => Instruction::MovDnW2, - Self::Movdnw(3) => Instruction::MovDnW3, - Self::Movdnw(n) => { - panic!("invalid movdnw instruction, valid index range is 2..=3, got {n}") - } - Self::Cswap => Instruction::CSwap, - Self::Cswapw => Instruction::CSwapW, - Self::Cdrop => Instruction::CDrop, - Self::Cdropw => Instruction::CDropW, - Self::Assert => Instruction::Assert, - Self::AssertWithError(code) => Instruction::AssertWithError(code.into()), - Self::Assertz => Instruction::Assertz, - Self::AssertzWithError(code) => Instruction::AssertzWithError(code.into()), - Self::AssertEq => Instruction::AssertEq, - Self::AssertEqWithError(code) => Instruction::AssertEqWithError(code.into()), - Self::AssertEqw => Instruction::AssertEqw, - Self::AssertEqwWithError(code) => Instruction::AssertEqwWithError(code.into()), - Self::LocAddr(id) => Instruction::Locaddr(id.into()), - Self::LocLoad(id) => Instruction::LocLoad(id.into()), - Self::LocLoadw(id) => Instruction::LocLoadW(id.into()), - Self::LocStore(id) => Instruction::LocStore(id.into()), - Self::LocStorew(id) => Instruction::LocStoreW(id.into()), - Self::MemLoad => Instruction::MemLoad, - Self::MemLoadImm(addr) => Instruction::MemLoadImm(addr.into()), - Self::MemLoadw => Instruction::MemLoadW, - Self::MemLoadwImm(addr) => Instruction::MemLoadWImm(addr.into()), - Self::MemStore => Instruction::MemStore, - Self::MemStoreImm(addr) => Instruction::MemStoreImm(addr.into()), - Self::MemStorew => Instruction::MemStoreW, - Self::MemStorewImm(addr) => Instruction::MemStoreWImm(addr.into()), - Self::MemStream => Instruction::MemStream, - Self::AdvPipe => Instruction::AdvPipe, - Self::AdvPush(n) => Instruction::AdvPush(n.into()), - Self::AdvLoadw => Instruction::AdvLoadW, - Self::AdvInjectPushU64Div => Instruction::AdvInject(AdviceInjectorNode::PushU64Div), - Self::AdvInjectPushMTreeNode => Instruction::AdvInject(AdviceInjectorNode::PushMtNode), - Self::AdvInjectPushMapVal => Instruction::AdvInject(AdviceInjectorNode::PushMapVal), - Self::AdvInjectPushMapValImm(n) => { - Instruction::AdvInject(AdviceInjectorNode::PushMapValImm { offset: n.into() }) - } - Self::AdvInjectPushMapValN => Instruction::AdvInject(AdviceInjectorNode::PushMapValN), - Self::AdvInjectPushMapValNImm(n) => { - Instruction::AdvInject(AdviceInjectorNode::PushMapValNImm { offset: n.into() }) - } - Self::AdvInjectInsertMem => Instruction::AdvInject(AdviceInjectorNode::InsertMem), - Self::AdvInjectInsertHperm => Instruction::AdvInject(AdviceInjectorNode::InsertHperm), - Self::AdvInjectInsertHdword => Instruction::AdvInject(AdviceInjectorNode::InsertHdword), - Self::AdvInjectInsertHdwordImm(domain) => { - Instruction::AdvInject(AdviceInjectorNode::InsertHdwordImm { - domain: domain.into(), - }) - } - Self::AdvInjectPushSignature(kind) => { - Instruction::AdvInject(AdviceInjectorNode::PushSignature { kind }) - } - Self::If(..) | Self::While(_) | Self::Repeat(..) => { - panic!("control flow instructions are meant to be handled specially by the caller") - } - op @ (Self::Exec(ref callee) - | Self::Call(ref callee) - | Self::Syscall(ref callee) - | Self::ProcRef(ref callee)) => { - let target = if locals.contains(callee) { - let callee = ProcedureName::new_unchecked(super::utils::translate_ident( - callee.function, - )); - InvocationTarget::ProcedureName(callee) - } else if let Some(alias) = imports.alias(&callee.module) { - let alias = super::utils::translate_ident(alias); - let name = ProcedureName::new_unchecked(super::utils::translate_ident( - callee.function, - )); - InvocationTarget::ProcedurePath { - name, - module: alias, - } - } else { - let name = ProcedureName::new_unchecked(super::utils::translate_ident( - callee.function, - )); - let path = - LibraryPath::new(callee.module.as_str()).expect("invalid procedure path"); - InvocationTarget::AbsoluteProcedurePath { name, path } - }; - match op { - Self::Exec(_) => Instruction::Exec(target), - Self::Call(_) => Instruction::Call(target), - Self::Syscall(_) => Instruction::SysCall(target), - Self::ProcRef(_) => Instruction::ProcRef(target), - _ => unreachable!(), - } - } - Self::DynExec => Instruction::DynExec, - Self::DynCall => Instruction::DynCall, - Self::Add => Instruction::Add, - Self::AddImm(imm) => Instruction::AddImm(imm.into()), - Self::Sub => Instruction::Sub, - Self::SubImm(imm) => Instruction::SubImm(imm.into()), - Self::Mul => Instruction::Mul, - Self::MulImm(imm) => Instruction::MulImm(imm.into()), - Self::Div => Instruction::Div, - Self::DivImm(imm) => Instruction::DivImm(imm.into()), - Self::Neg => Instruction::Neg, - Self::Inv => Instruction::Inv, - Self::Incr => Instruction::Incr, - Self::Ilog2 => Instruction::ILog2, - Self::Pow2 => Instruction::Pow2, - Self::Exp => Instruction::Exp, - Self::ExpImm(imm) => Instruction::ExpImm(Felt::new(imm as u64).into()), - Self::ExpBitLength(imm) => Instruction::ExpBitLength(imm), - Self::Not => Instruction::Not, - Self::And => Instruction::And, - Self::AndImm(imm) => { - return smallvec![Instruction::PushU8(imm as u8), Instruction::And] - } - Self::Or => Instruction::Or, - Self::OrImm(imm) => return smallvec![Instruction::PushU8(imm as u8), Instruction::Or], - Self::Xor => Instruction::Xor, - Self::XorImm(imm) => { - return smallvec![Instruction::PushU8(imm as u8), Instruction::Xor] - } - Self::Eq => Instruction::Eq, - Self::EqImm(imm) => Instruction::EqImm(imm.into()), - Self::Neq => Instruction::Neq, - Self::NeqImm(imm) => Instruction::NeqImm(imm.into()), - Self::Gt => Instruction::Gt, - Self::GtImm(imm) => return smallvec![Instruction::PushFelt(imm), Instruction::Gt], - Self::Gte => Instruction::Gte, - Self::GteImm(imm) => return smallvec![Instruction::PushFelt(imm), Instruction::Gte], - Self::Lt => Instruction::Lt, - Self::LtImm(imm) => return smallvec![Instruction::PushFelt(imm), Instruction::Lt], - Self::Lte => Instruction::Lte, - Self::LteImm(imm) => return smallvec![Instruction::PushFelt(imm), Instruction::Lte], - Self::IsOdd => Instruction::IsOdd, - Self::Eqw => Instruction::Eqw, - Self::Ext2add => Instruction::Ext2Add, - Self::Ext2sub => Instruction::Ext2Sub, - Self::Ext2mul => Instruction::Ext2Mul, - Self::Ext2div => Instruction::Ext2Div, - Self::Ext2neg => Instruction::Ext2Neg, - Self::Ext2inv => Instruction::Ext2Inv, - Self::Clk => Instruction::Clk, - Self::Caller => Instruction::Caller, - Self::Sdepth => Instruction::Sdepth, - Self::Hash => Instruction::Hash, - Self::Hperm => Instruction::HPerm, - Self::Hmerge => Instruction::HMerge, - Self::MtreeGet => Instruction::MTreeGet, - Self::MtreeSet => Instruction::MTreeSet, - Self::MtreeMerge => Instruction::MTreeMerge, - Self::MtreeVerify => Instruction::MTreeVerify, - Self::MtreeVerifyWithError(code) => Instruction::MTreeVerifyWithError(code.into()), - Self::FriExt2Fold4 => Instruction::FriExt2Fold4, - Self::RCombBase => Instruction::RCombBase, - Self::U32Test => Instruction::U32Test, - Self::U32Testw => Instruction::U32TestW, - Self::U32Assert => Instruction::U32Assert, - Self::U32AssertWithError(code) => Instruction::U32AssertWithError(code.into()), - Self::U32Assert2 => Instruction::U32Assert2, - Self::U32Assert2WithError(code) => Instruction::U32Assert2WithError(code.into()), - Self::U32Assertw => Instruction::U32AssertW, - Self::U32AssertwWithError(code) => Instruction::U32AssertWWithError(code.into()), - Self::U32Cast => Instruction::U32Cast, - Self::U32Split => Instruction::U32Split, - Self::U32OverflowingAdd => Instruction::U32OverflowingAdd, - Self::U32OverflowingAddImm(imm) => Instruction::U32OverflowingAddImm(imm.into()), - Self::U32WrappingAdd => Instruction::U32WrappingAdd, - Self::U32WrappingAddImm(imm) => Instruction::U32WrappingAddImm(imm.into()), - Self::U32OverflowingAdd3 => Instruction::U32OverflowingAdd3, - Self::U32WrappingAdd3 => Instruction::U32WrappingAdd3, - Self::U32OverflowingSub => Instruction::U32OverflowingSub, - Self::U32OverflowingSubImm(imm) => Instruction::U32OverflowingSubImm(imm.into()), - Self::U32WrappingSub => Instruction::U32WrappingSub, - Self::U32WrappingSubImm(imm) => Instruction::U32WrappingSubImm(imm.into()), - Self::U32OverflowingMul => Instruction::U32OverflowingMul, - Self::U32OverflowingMulImm(imm) => Instruction::U32OverflowingMulImm(imm.into()), - Self::U32WrappingMul => Instruction::U32WrappingMul, - Self::U32WrappingMulImm(imm) => Instruction::U32WrappingMulImm(imm.into()), - Self::U32OverflowingMadd => Instruction::U32OverflowingMadd, - Self::U32WrappingMadd => Instruction::U32WrappingMadd, - Self::U32Div => Instruction::U32Div, - Self::U32DivImm(imm) => Instruction::U32DivImm(imm.into()), - Self::U32Mod => Instruction::U32Mod, - Self::U32ModImm(imm) => Instruction::U32ModImm(imm.into()), - Self::U32DivMod => Instruction::U32DivMod, - Self::U32DivModImm(imm) => Instruction::U32DivModImm(imm.into()), - Self::U32And => Instruction::U32And, - Self::U32Or => Instruction::U32Or, - Self::U32Xor => Instruction::U32Xor, - Self::U32Not => Instruction::U32Not, - Self::U32Shl => Instruction::U32Shl, - Self::U32ShlImm(imm) => { - let shift = u8::try_from(imm).expect("invalid shift"); - Instruction::U32ShlImm(shift.into()) - } - Self::U32Shr => Instruction::U32Shr, - Self::U32ShrImm(imm) => { - let shift = u8::try_from(imm).expect("invalid shift"); - Instruction::U32ShrImm(shift.into()) - } - Self::U32Rotl => Instruction::U32Rotl, - Self::U32RotlImm(imm) => { - let rotate = u8::try_from(imm).expect("invalid rotation"); - Instruction::U32RotlImm(rotate.into()) - } - Self::U32Rotr => Instruction::U32Rotr, - Self::U32RotrImm(imm) => { - let rotate = u8::try_from(imm).expect("invalid rotation"); - Instruction::U32RotrImm(rotate.into()) - } - Self::U32Popcnt => Instruction::U32Popcnt, - Self::U32Clz => Instruction::U32Clz, - Self::U32Ctz => Instruction::U32Ctz, - Self::U32Clo => Instruction::U32Clo, - Self::U32Cto => Instruction::U32Cto, - Self::U32Lt => Instruction::U32Lt, - Self::U32LtImm(imm) => return smallvec![Instruction::PushU32(imm), Instruction::U32Lt], - Self::U32Lte => Instruction::U32Lte, - Self::U32LteImm(imm) => { - return smallvec![Instruction::PushU32(imm), Instruction::U32Lte] - } - Self::U32Gt => Instruction::U32Gt, - Self::U32GtImm(imm) => return smallvec![Instruction::PushU32(imm), Instruction::U32Gt], - Self::U32Gte => Instruction::U32Gte, - Self::U32GteImm(imm) => { - return smallvec![Instruction::PushU32(imm), Instruction::U32Gte]; - } - Self::U32Min => Instruction::U32Min, - Self::U32MinImm(imm) => { - return smallvec![Instruction::PushU32(imm), Instruction::U32Min]; - } - Self::U32Max => Instruction::U32Max, - Self::U32MaxImm(imm) => { - return smallvec![Instruction::PushU32(imm), Instruction::U32Max]; - } - Self::Breakpoint => Instruction::Breakpoint, - Self::DebugStack => Instruction::Debug(DebugOptions::StackAll), - Self::DebugStackN(n) => Instruction::Debug(DebugOptions::StackTop(n.into())), - Self::DebugMemory => Instruction::Debug(DebugOptions::MemAll), - Self::DebugMemoryAt(start) => { - Instruction::Debug(DebugOptions::MemInterval(start.into(), u32::MAX.into())) - } - Self::DebugMemoryRange(start, end) => { - Instruction::Debug(DebugOptions::MemInterval(start.into(), end.into())) - } - Self::DebugFrame => Instruction::Debug(DebugOptions::LocalAll), - Self::DebugFrameAt(start) => { - Instruction::Debug(DebugOptions::LocalRangeFrom(start.into())) - } - Self::DebugFrameRange(start, end) => { - Instruction::Debug(DebugOptions::LocalInterval(start.into(), end.into())) - } - Self::Emit(ev) => Instruction::Emit(ev.into()), - Self::Trace(ev) => Instruction::Trace(ev.into()), - Self::Nop => Instruction::Nop, - }; - smallvec![inst] - } -} - -/// This implementation displays the opcode name for the given [MasmOp] -impl fmt::Display for MasmOp { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Padw => f.write_str("padw"), - Self::Push(_) - | Self::Push2(_) - | Self::Pushw(_) - | Self::PushU8(_) - | Self::PushU16(_) - | Self::PushU32(_) => f.write_str("push"), - Self::Drop => f.write_str("drop"), - Self::Dropw => f.write_str("dropw"), - Self::Dup(_) => f.write_str("dup"), - Self::Dupw(_) => f.write_str("dupw"), - Self::Swap(_) => f.write_str("swap"), - Self::Swapw(_) => f.write_str("swapw"), - Self::Swapdw => f.write_str("swapdw"), - Self::Movup(_) => f.write_str("movup"), - Self::Movupw(_) => f.write_str("movupw"), - Self::Movdn(_) => f.write_str("movdn"), - Self::Movdnw(_) => f.write_str("movdnw"), - Self::Cswap => f.write_str("cswap"), - Self::Cswapw => f.write_str("cswapw"), - Self::Cdrop => f.write_str("cdrop"), - Self::Cdropw => f.write_str("cdropw"), - Self::Assert => f.write_str("assert"), - Self::AssertWithError(code) => write!(f, "assert.err={code}"), - Self::Assertz => f.write_str("assertz"), - Self::AssertzWithError(code) => write!(f, "assertz.err={code}"), - Self::AssertEq => f.write_str("assert_eq"), - Self::AssertEqWithError(code) => write!(f, "assert_eq.err={code}"), - Self::AssertEqw => f.write_str("assert_eqw"), - Self::AssertEqwWithError(code) => write!(f, "assert_eqw.err={code}"), - Self::LocAddr(_) => f.write_str("locaddr"), - Self::LocLoad(_) => f.write_str("loc_load"), - Self::LocLoadw(_) => f.write_str("loc_loadw"), - Self::LocStore(_) => f.write_str("loc_store"), - Self::LocStorew(_) => f.write_str("loc_storew"), - Self::MemLoad | Self::MemLoadImm(_) => f.write_str("mem_load"), - Self::MemLoadw | Self::MemLoadwImm(_) => f.write_str("mem_loadw"), - Self::MemStore | Self::MemStoreImm(_) => f.write_str("mem_store"), - Self::MemStorew | Self::MemStorewImm(_) => f.write_str("mem_storew"), - Self::MemStream => f.write_str("mem_stream"), - Self::AdvPipe => f.write_str("adv_pipe"), - Self::AdvPush(_) => f.write_str("adv_push"), - Self::AdvLoadw => f.write_str("adv_loadw"), - Self::AdvInjectPushU64Div => f.write_str("adv.push_u64div"), - Self::AdvInjectPushMTreeNode => f.write_str("adv.push_mtnode"), - Self::AdvInjectPushMapVal | Self::AdvInjectPushMapValImm(_) => { - f.write_str("adv.push_mapval") - } - Self::AdvInjectPushMapValN | Self::AdvInjectPushMapValNImm(_) => { - f.write_str("adv.push_mapvaln") - } - Self::AdvInjectInsertMem => f.write_str("adv.insert_mem"), - Self::AdvInjectInsertHperm => f.write_str("adv.insert_hperm"), - Self::AdvInjectInsertHdword | Self::AdvInjectInsertHdwordImm(_) => { - f.write_str("adv.insert_hdword") - } - Self::AdvInjectPushSignature(kind) => write!(f, "adv.push_sig.{kind}"), - Self::If(..) => f.write_str("if.true"), - Self::While(_) => f.write_str("while.true"), - Self::Repeat(..) => f.write_str("repeat"), - Self::Exec(_) => f.write_str("exec"), - Self::Syscall(_) => f.write_str("syscall"), - Self::Call(_) => f.write_str("call"), - Self::DynExec => f.write_str("dynexec"), - Self::DynCall => f.write_str("dyncall"), - Self::ProcRef(_) => f.write_str("procref"), - Self::Add | Self::AddImm(_) => f.write_str("add"), - Self::Sub | Self::SubImm(_) => f.write_str("sub"), - Self::Mul | Self::MulImm(_) => f.write_str("mul"), - Self::Div | Self::DivImm(_) => f.write_str("div"), - Self::Neg => f.write_str("neg"), - Self::Inv => f.write_str("inv"), - Self::Incr => f.write_str("add.1"), - Self::Ilog2 => f.write_str("ilog2"), - Self::Pow2 => f.write_str("pow2"), - Self::Exp => f.write_str("exp"), - Self::ExpImm(imm) => write!(f, "exp.{imm}"), - Self::ExpBitLength(imm) => write!(f, "exp.u{imm}"), - Self::Not => f.write_str("not"), - Self::And | Self::AndImm(_) => f.write_str("and"), - Self::Or | Self::OrImm(_) => f.write_str("or"), - Self::Xor | Self::XorImm(_) => f.write_str("xor"), - Self::Eq | Self::EqImm(_) => f.write_str("eq"), - Self::Neq | Self::NeqImm(_) => f.write_str("neq"), - Self::Gt | Self::GtImm(_) => f.write_str("gt"), - Self::Gte | Self::GteImm(_) => f.write_str("gte"), - Self::Lt | Self::LtImm(_) => f.write_str("lt"), - Self::Lte | Self::LteImm(_) => f.write_str("lte"), - Self::IsOdd => f.write_str("is_odd"), - Self::Eqw => f.write_str("eqw"), - Self::Ext2add => f.write_str("ext2add"), - Self::Ext2sub => f.write_str("ext2sub"), - Self::Ext2mul => f.write_str("ext2mul"), - Self::Ext2div => f.write_str("ext2div"), - Self::Ext2neg => f.write_str("ext2neg"), - Self::Ext2inv => f.write_str("ext2inv"), - Self::Clk => f.write_str("clk"), - Self::Caller => f.write_str("caller"), - Self::Sdepth => f.write_str("sdepth"), - Self::Hash => f.write_str("hash"), - Self::Hperm => f.write_str("hperm"), - Self::Hmerge => f.write_str("hmerge"), - Self::MtreeGet => f.write_str("mtree_get"), - Self::MtreeSet => f.write_str("mtree_set"), - Self::MtreeMerge => f.write_str("mtree_merge"), - Self::MtreeVerify => f.write_str("mtree_verify"), - Self::MtreeVerifyWithError(code) => write!(f, "mtree_verify.err={code}"), - Self::FriExt2Fold4 => f.write_str("fri_ext2fold4"), - Self::RCombBase => f.write_str("rcomb_base"), - Self::U32Test => f.write_str("u32test"), - Self::U32Testw => f.write_str("u32testw"), - Self::U32Assert => f.write_str("u32assert"), - Self::U32AssertWithError(code) => write!(f, "u32assert.err={code}"), - Self::U32Assert2 => f.write_str("u32assert2"), - Self::U32Assert2WithError(code) => write!(f, "u32assert2.err={code}"), - Self::U32Assertw => f.write_str("u32assertw"), - Self::U32AssertwWithError(code) => write!(f, "u32assertw.err={code}"), - Self::U32Cast => f.write_str("u32cast"), - Self::U32Split => f.write_str("u32split"), - Self::U32OverflowingAdd | Self::U32OverflowingAddImm(_) => { - f.write_str("u32overflowing_add") - } - Self::U32WrappingAdd | Self::U32WrappingAddImm(_) => f.write_str("u32wrapping_add"), - Self::U32OverflowingAdd3 => f.write_str("u32overflowing_add3"), - Self::U32WrappingAdd3 => f.write_str("u32wrapping_add3"), - Self::U32OverflowingSub | Self::U32OverflowingSubImm(_) => { - f.write_str("u32overflowing_sub") - } - Self::U32WrappingSub | Self::U32WrappingSubImm(_) => f.write_str("u32wrapping_sub"), - Self::U32OverflowingMul | Self::U32OverflowingMulImm(_) => { - f.write_str("u32overflowing_mul") - } - Self::U32WrappingMul | Self::U32WrappingMulImm(_) => f.write_str("u32wrapping_mul"), - Self::U32OverflowingMadd => f.write_str("u32overflowing_madd"), - Self::U32WrappingMadd => f.write_str("u32wrapping_madd"), - Self::U32Div | Self::U32DivImm(_) => f.write_str("u32div"), - Self::U32Mod | Self::U32ModImm(_) => f.write_str("u32mod"), - Self::U32DivMod | Self::U32DivModImm(_) => f.write_str("u32divmod"), - Self::U32And => f.write_str("u32and"), - Self::U32Or => f.write_str("u32or"), - Self::U32Xor => f.write_str("u32xor"), - Self::U32Not => f.write_str("u32not"), - Self::U32Shl | Self::U32ShlImm(_) => f.write_str("u32shl"), - Self::U32Shr | Self::U32ShrImm(_) => f.write_str("u32shr"), - Self::U32Rotl | Self::U32RotlImm(_) => f.write_str("u32rotl"), - Self::U32Rotr | Self::U32RotrImm(_) => f.write_str("u32rotr"), - Self::U32Popcnt => f.write_str("u32popcnt"), - Self::U32Clz => f.write_str("u32clz"), - Self::U32Ctz => f.write_str("u32ctz"), - Self::U32Clo => f.write_str("u32clo"), - Self::U32Cto => f.write_str("u32cto"), - Self::U32Lt | Self::U32LtImm(_) => f.write_str("u32lt"), - Self::U32Lte | Self::U32LteImm(_) => f.write_str("u32lte"), - Self::U32Gt | Self::U32GtImm(_) => f.write_str("u32gt"), - Self::U32Gte | Self::U32GteImm(_) => f.write_str("u32gte"), - Self::U32Min | Self::U32MinImm(_) => f.write_str("u32min"), - Self::U32Max | Self::U32MaxImm(_) => f.write_str("u32max"), - Self::Breakpoint => f.write_str("breakpoint"), - Self::DebugStack | Self::DebugStackN(_) => f.write_str("debug.stack"), - Self::DebugMemory | Self::DebugMemoryAt(_) | Self::DebugMemoryRange(..) => { - f.write_str("debug.mem") - } - Self::DebugFrame | Self::DebugFrameAt(_) | Self::DebugFrameRange(..) => { - f.write_str("debug.local") - } - Self::Emit(_) => f.write_str("emit"), - Self::Trace(_) => f.write_str("trace"), - Self::Nop => f.write_str("nop"), - } - } -} diff --git a/hir/src/asm/mod.rs b/hir/src/asm/mod.rs deleted file mode 100644 index 179574b87..000000000 --- a/hir/src/asm/mod.rs +++ /dev/null @@ -1,107 +0,0 @@ -mod assertions; -mod builder; -mod display; -mod events; -mod import; -mod isa; -mod stack; -pub mod utils; - -use cranelift_entity::PrimaryMap; -use smallvec::smallvec; - -pub use self::{ - assertions::*, - builder::*, - display::{DisplayInlineAsm, DisplayMasmBlock}, - events::*, - import::{MasmImport, ModuleImportInfo}, - isa::*, - stack::{OperandStack, Stack, StackElement}, -}; -use crate::{diagnostics::SourceSpan, DataFlowGraph, Opcode, Type, ValueList}; - -/// Represents Miden Assembly (MASM) directly in the IR -/// -/// Each block of inline assembly executes in its own pseudo-isolated environment, -/// i.e. other than arguments provided to the inline assembly, and values introduced -/// within the inline assembly, it is not permitted to access anything else on the -/// operand stack. -/// -/// In addition to arguments, inline assembly can produce zero or more results, -/// see [MasmBuilder] for more info. -/// -/// Inline assembly can be built using [InstBuilder::inline_asm]. -#[derive(Debug, Clone)] -pub struct InlineAsm { - pub op: Opcode, - /// Arguments on which the inline assembly can operate - /// - /// The operand stack will be set up such that the given arguments - /// will appear in LIFO order, i.e. the first argument will be on top - /// of the stack, and so on. - /// - /// The inline assembly will be validated so that all other values on - /// the operand stack below the given arguments will remain on the stack - /// when the inline assembly finishes executing. - pub args: ValueList, - /// The types of the results produced by this inline assembly block - pub results: Vec, - /// The main code block - pub body: MasmBlockId, - /// The set of all code blocks contained in this inline assembly - /// - /// This is necessary to support control flow operations within asm blocks - pub blocks: PrimaryMap, -} -impl InlineAsm { - /// Constructs a new, empty inline assembly block with the given result type(s). - pub fn new(results: Vec) -> Self { - let mut blocks = PrimaryMap::::new(); - let id = blocks.next_key(); - let body = blocks.push(MasmBlock { - id, - ops: smallvec![], - }); - Self { - op: Opcode::InlineAsm, - args: ValueList::default(), - results, - body, - blocks, - } - } - - /// Create a new code block for use with this inline assembly - pub fn create_block(&mut self) -> MasmBlockId { - let id = self.blocks.next_key(); - self.blocks.push(MasmBlock { - id, - ops: smallvec![], - }); - id - } - - /// Appends `op` to the end of `block` - pub fn push(&mut self, block: MasmBlockId, op: MasmOp, span: SourceSpan) { - self.blocks[block].push(op, span); - } - - pub fn display<'a, 'b: 'a>( - &'b self, - function: Option, - dfg: &'b DataFlowGraph, - ) -> DisplayInlineAsm<'a> { - DisplayInlineAsm::new(function, self, dfg) - } - - pub fn render( - &self, - current_function: crate::FunctionIdent, - dfg: &DataFlowGraph, - ) -> crate::formatter::Document { - use crate::formatter::PrettyPrint; - - self.display(Some(current_function), dfg).render() - } -} diff --git a/hir/src/asm/stack.rs b/hir/src/asm/stack.rs deleted file mode 100644 index 0f4dd2689..000000000 --- a/hir/src/asm/stack.rs +++ /dev/null @@ -1,666 +0,0 @@ -use core::{ - fmt, - ops::{Index, IndexMut}, -}; - -use crate::{Felt, FieldElement, Type}; - -/// This trait is used to represent the basic plumbing of the operand stack in -/// Miden Assembly. -/// -/// Implementations of this trait may attach different semantics to the meaning of -/// elements on the stack. As a result, certain operations which are contingent on the -/// specific value of an element, may behave differently depending on the specific -/// implementation. -/// -/// In general however, it is expected that use of this trait in a generic context will -/// be rare, if ever the case. As mentioned above, it is meant to handle the common -/// plumbing of an operand stack implementation, but in practice users will be working -/// with a concrete implementation with this trait in scope to provide access to the -/// basic functionality of the stack. -/// -/// It is expected that implementations will override functions in this trait as necessary -/// to implement custom behavior above and beyond what is provided by the default implementation. -pub trait Stack: IndexMut::Element> { - type Element: StackElement; - - /// Return a reference to the underlying "raw" stack data structure, a vector - fn stack(&self) -> &Vec; - /// Return a mutable reference to the underlying "raw" stack data structure, a vector - fn stack_mut(&mut self) -> &mut Vec; - - /// Clear the contents of this stack while keeping the underlying - /// memory allocated for reuse. - fn clear(&mut self); - - /// Display this stack using its debugging representation - fn debug(&self) -> DebugStack { - DebugStack::new(self) - } - - /// Returns true if the operand stack is empty - #[inline(always)] - fn is_empty(&self) -> bool { - self.stack().is_empty() - } - - /// Returns the number of elements on the stack - #[inline] - fn len(&self) -> usize { - self.stack().len() - } - - /// Returns the value on top of the stack, without consuming it - #[inline] - fn peek(&self) -> Option { - self.stack().last().cloned() - } - - /// Returns the word on top of the stack, without consuming it - /// - /// The top of the stack will be the first element of the word - #[inline] - fn peekw(&self) -> Option<[Self::Element; 4]> { - let stack = self.stack(); - let end = stack.len().checked_sub(1)?; - Some([ - stack[end].clone(), - stack[end - 1].clone(), - stack[end - 2].clone(), - stack[end - 3].clone(), - ]) - } - - /// Pushes a word of zeroes on top of the stack - fn padw(&mut self) { - self.stack_mut().extend([ - Self::Element::DEFAULT, - Self::Element::DEFAULT, - Self::Element::DEFAULT, - Self::Element::DEFAULT, - ]); - } - - /// Pushes `value` on top of the stac - fn push(&mut self, value: Self::Element) { - self.stack_mut().push(value); - } - - /// Pushes `word` on top of the stack - /// - /// The first element of `word` will be on top of the stack - fn pushw(&mut self, mut word: [Self::Element; 4]) { - word.reverse(); - self.stack_mut().extend(word); - } - - /// Pops the value on top of the stack - fn pop(&mut self) -> Option { - self.stack_mut().pop() - } - - /// Pops the first word on top of the stack - /// - /// The top of the stack will be the first element in the result - fn popw(&mut self) -> Option<[Self::Element; 4]> { - let stack = self.stack_mut(); - let a = stack.pop()?; - let b = stack.pop()?; - let c = stack.pop()?; - let d = stack.pop()?; - Some([a, b, c, d]) - } - - /// Drops the top item on the stack - fn drop(&mut self) { - self.dropn(1); - } - - /// Drops the top word on the stack - fn dropw(&mut self) { - self.dropn(4); - } - - #[inline] - fn dropn(&mut self, n: usize) { - let stack = self.stack_mut(); - let len = stack.len(); - assert!(n <= len, "unable to drop {} elements, operand stack only has {}", n, len); - stack.truncate(len - n); - } - - /// Duplicates the value in the `n`th position on the stack - /// - /// If `n` is 0, duplicates the top of the stack. - fn dup(&mut self, n: usize) { - let value = self[n].clone(); - self.stack_mut().push(value); - } - - /// Duplicates the `n`th word on the stack, to the top of the stack. - /// - /// Valid values for `n` are 0, 1, 2, or 3. - /// - /// If `n` is 0, duplicates the top word of the stack. - fn dupw(&mut self, n: usize) { - assert!(n < 4, "invalid word index: must be in the range 0..=3"); - let len = self.stack().len(); - let index = n * 4; - assert!( - index < len, - "invalid operand stack index ({}), only {} elements are available", - index, - len - ); - match index { - 0 => { - let word = self.peekw().expect("operand stack is empty"); - self.pushw(word); - } - n => { - let end = len - n - 1; - let word = { - let stack = self.stack(); - [ - stack[end].clone(), - stack[end - 1].clone(), - stack[end - 2].clone(), - stack[end - 3].clone(), - ] - }; - self.pushw(word); - } - } - } - - /// Swaps the `n`th value from the top of the stack, with the top of the stack - /// - /// If `n` is 1, it swaps the first two elements on the stack. - /// - /// NOTE: This function will panic if `n` is 0, or out of bounds. - fn swap(&mut self, n: usize) { - assert_ne!(n, 0, "invalid swap, index must be in the range 1..=15"); - let stack = self.stack_mut(); - let len = stack.len(); - assert!( - n < len, - "invalid operand stack index ({}), only {} elements are available", - n, - len - ); - let a = len - 1; - let b = a - n; - stack.swap(a, b); - } - - /// Swaps the `n`th word from the top of the stack, with the word on top of the stack - /// - /// If `n` is 1, it swaps the first two words on the stack. - /// - /// Valid values for `n` are: 1, 2, 3. - fn swapw(&mut self, n: usize) { - assert_ne!(n, 0, "invalid swap, index must be in the range 1..=3"); - let stack = self.stack_mut(); - let len = stack.len(); - let index = n * 4; - assert!( - index < len, - "invalid operand stack index ({}), only {} elements are available", - index, - len - ); - let end = len - 1; - for offset in 0..4 { - // The index of the element in the top word - let a = end - offset; - // The index of the element in the `n`th word - let b = end - offset - index; - stack.swap(a, b); - } - } - - /// Swaps the top two and bottom two words on the stack. - fn swapdw(&mut self) { - let stack = self.stack_mut(); - stack.rotate_left(8); - } - - /// Moves the `n`th value to the top of the stack - /// - /// If `n` is 1, this is equivalent to `swap(1)`. - /// - /// NOTE: This function will panic if `n` is 0, or out of bounds. - fn movup(&mut self, n: usize) { - assert_ne!(n, 0, "invalid move, index must be in the range 1..=15"); - let stack = self.stack_mut(); - let len = stack.len(); - assert!( - n < len, - "invalid operand stack index ({}), only {} elements are available", - n, - len - ); - // Pick the midpoint by counting backwards from the end - let mid = len - (n + 1); - // Split the stack, and rotate the half that - // contains our desired value to place it on top. - let (_, r) = stack.split_at_mut(mid); - r.rotate_left(1); - } - - /// Moves the `n`th word to the top of the stack - /// - /// If `n` is 1, this is equivalent to `swapw(1)`. - /// - /// Valid values for `n` are: 1, 2, 3 - fn movupw(&mut self, n: usize) { - assert_ne!(n, 0, "invalid move, index must be in the range 1..=3"); - let stack = self.stack_mut(); - let len = stack.len(); - let index = (n * 4) + 4; - assert!( - index < len, - "invalid operand stack index ({}), only {} elements are available", - index, - len - ); - // Pick the midpoint index by counting backwards from the end - let mid = len - index; - // Split the stack, and rotate the half that - // contains our desired word to place it on top. - let (_, r) = stack.split_at_mut(mid); - r.rotate_left(4); - } - - /// Makes the value on top of the stack, the `n`th value on the stack - /// - /// If `n` is 1, this is equivalent to `swap(1)`. - /// - /// NOTE: This function will panic if `n` is 0, or out of bounds. - fn movdn(&mut self, n: usize) { - assert_ne!(n, 0, "invalid move: index must be in the range 1..=15"); - let stack = self.stack_mut(); - let len = stack.len(); - assert!( - n < len, - "invalid operand stack index ({}), only {} elements are available", - n, - len - ); - // Split the stack so that the desired position is in the top half - let mid = len - (n + 1); - let (_, r) = stack.split_at_mut(mid); - // Move all elements above the `n`th position up by one, moving the top element to the `n`th - // position - r.rotate_right(1); - } - - /// Makes the word on top of the stack, the `n`th word on the stack - /// - /// If `n` is 1, this is equivalent to `swapw(1)`. - /// - /// Valid values for `n` are: 1, 2, 3 - fn movdnw(&mut self, n: usize) { - assert_ne!(n, 0, "invalid move, index must be in the range 1..=3"); - let stack = self.stack_mut(); - let len = stack.len(); - let index = (n * 4) + 4; - assert!( - index < len, - "invalid operand stack index ({}), only {} elements are available", - index, - len - ); - // Split the stack so that the desired position is in the top half - let mid = len - index; - let (_, r) = stack.split_at_mut(mid); - // Move all elements above the `n`th word up by one word, moving the top word to the `n`th - // position - r.rotate_right(4); - } -} - -/// This trait is used to represent expected behavior/properties of elements -/// that can be used in conjunction with the [Stack] trait. -pub trait StackElement: Clone { - type Debug: fmt::Debug; - - /// A value of this type which represents the "zero" value for the type - const DEFAULT: Self; - - /// Format this stack element for display - fn debug(&self) -> Self::Debug; -} - -impl StackElement for Felt { - type Debug = u64; - - const DEFAULT: Self = Felt::ZERO; - - #[inline] - fn debug(&self) -> Self::Debug { - self.as_int() - } -} -impl StackElement for Type { - type Debug = Type; - - const DEFAULT: Self = Type::Felt; - - #[inline] - fn debug(&self) -> Self::Debug { - self.clone() - } -} - -/// This structure is a concrete implementation of the [Stack] trait, implemented -/// for use with two different element types: -/// -/// * [Felt], for actual emulation of the Miden VM operand stack -/// * [Type], for tracking the state of the operand stack in abstract -pub struct OperandStack { - stack: Vec, -} -impl Clone for OperandStack { - fn clone(&self) -> Self { - Self { - stack: self.stack.clone(), - } - } -} -impl Default for OperandStack { - fn default() -> Self { - Self { stack: vec![] } - } -} -impl Stack for OperandStack { - type Element = T; - - #[inline(always)] - fn stack(&self) -> &Vec { - &self.stack - } - - #[inline(always)] - fn stack_mut(&mut self) -> &mut Vec { - &mut self.stack - } - - #[inline(always)] - fn clear(&mut self) { - self.stack.clear(); - } -} -impl OperandStack { - /// Pushes `value` on top of the stack, with an optional set of aliases - pub fn push_u8(&mut self, value: u8) { - self.stack.push(Felt::new(value as u64)); - } - - /// Pushes `value` on top of the stack, with an optional set of aliases - pub fn push_u16(&mut self, value: u16) { - self.stack.push(Felt::new(value as u64)); - } - - /// Pushes `value` on top of the stack, with an optional set of aliases - pub fn push_u32(&mut self, value: u32) { - self.stack.push(Felt::new(value as u64)); - } -} -impl Index for OperandStack { - type Output = T; - - fn index(&self, index: usize) -> &Self::Output { - let len = self.stack.len(); - assert!( - index < 16, - "invalid operand stack index ({}), only the top 16 elements are directly accessible", - index - ); - assert!( - index < len, - "invalid operand stack index ({}), only {} elements are available", - index, - len - ); - &self.stack[len - index - 1] - } -} -impl IndexMut for OperandStack { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - let len = self.stack.len(); - assert!( - index < 16, - "invalid operand stack index ({}), only the top 16 elements are directly accessible", - index - ); - assert!( - index < len, - "invalid operand stack index ({}), only {} elements are available", - index, - len - ); - &mut self.stack[len - index - 1] - } -} - -#[doc(hidden)] -pub struct DebugStack<'a, T: ?Sized + Stack> { - stack: &'a T, - limit: Option, -} -impl<'a, E: StackElement, T: ?Sized + Stack> DebugStack<'a, T> { - pub fn new(stack: &'a T) -> Self { - Self { stack, limit: None } - } - - pub fn take(mut self, n: usize) -> Self { - self.limit = Some(n); - self - } -} -impl<'a, E: StackElement, T: ?Sized + Stack> fmt::Debug for DebugStack<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - #[allow(unused)] - struct StackEntry<'a, E: StackElement> { - index: usize, - value: &'a E, - } - impl<'a, E: StackElement> fmt::Debug for StackEntry<'a, E> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("StackEntry") - .field("index", &self.index) - .field("value", &self.value.debug()) - .finish() - } - } - - f.debug_list() - .entries( - self.stack - .stack() - .iter() - .rev() - .enumerate() - .take(self.limit.unwrap_or(self.stack.len())) - .map(|(index, value)| StackEntry { index, value }), - ) - .finish() - } -} -impl<'a, E: StackElement + fmt::Debug, T: ?Sized + Stack> fmt::Display - for DebugStack<'a, T> -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list() - .entries( - self.stack - .stack() - .iter() - .rev() - .enumerate() - .take(self.limit.unwrap_or(self.stack.len())), - ) - .finish() - } -} -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn operand_stack_primitive_ops_test() { - let mut stack = OperandStack::::default(); - - let zero = Felt::new(0); - let one = Felt::new(1); - let two = Felt::new(2); - let three = Felt::new(3); - let four = Felt::new(4); - let five = Felt::new(5); - let six = Felt::new(6); - let seven = Felt::new(7); - - // push - stack.push(zero); - stack.push(one); - stack.push(two); - stack.push(three); - assert_eq!(stack.len(), 4); - assert_eq!(stack[0].as_int(), 3); - assert_eq!(stack[1].as_int(), 2); - assert_eq!(stack[2].as_int(), 1); - assert_eq!(stack[3].as_int(), 0); - - #[inline(always)] - fn as_int(word: [Felt; 4]) -> [u64; 4] { - [word[0].as_int(), word[1].as_int(), word[2].as_int(), word[3].as_int()] - } - - // peekw - assert_eq!(stack.peekw().map(as_int), Some([3, 2, 1, 0])); - - // dupw - stack.dupw(0); - assert_eq!(stack.len(), 8); - assert_eq!(stack.peekw().map(as_int), Some([3, 2, 1, 0])); - - // padw - stack.padw(); - assert_eq!(stack.len(), 12); - assert_eq!(stack.peekw().map(as_int), Some([0; 4])); - - // swapw - stack.swapw(1); - assert_eq!(stack.len(), 12); - assert_eq!(stack.peekw().map(as_int), Some([3, 2, 1, 0])); - - // popw - let word = stack.popw(); - assert_eq!(stack.len(), 8); - assert_eq!(word.map(as_int), Some([3, 2, 1, 0])); - - // pushw - stack.pushw(word.unwrap()); - stack.pushw([seven, six, five, four]); - assert_eq!(stack.len(), 16); - assert_eq!(stack.peekw().map(as_int), Some([7, 6, 5, 4])); - - // movupw - stack.movupw(2); - assert_eq!(stack.len(), 16); - assert_eq!(stack.peekw().map(as_int), Some([0; 4])); - assert_eq!(stack[8].as_int(), 3); - assert_eq!(stack[9].as_int(), 2); - assert_eq!(stack[10].as_int(), 1); - assert_eq!(stack[11].as_int(), 0); - - // movdnw - stack.movdnw(2); - assert_eq!(stack.len(), 16); - assert_eq!(stack.peekw().map(as_int), Some([7, 6, 5, 4])); - assert_eq!(stack[8].as_int(), 0); - assert_eq!(stack[9].as_int(), 0); - assert_eq!(stack[10].as_int(), 0); - assert_eq!(stack[11].as_int(), 0); - - // dropw - stack.movupw(2); - stack.dropw(); - assert_eq!(stack.len(), 12); - assert_eq!(stack.peekw().map(as_int), Some([7, 6, 5, 4])); - - // dup(n) - stack.dup(0); - assert_eq!(stack.len(), 13); - assert_eq!(stack[0].as_int(), 7); - assert_eq!(stack[1].as_int(), 7); - assert_eq!(stack[2].as_int(), 6); - assert_eq!(stack[3].as_int(), 5); - assert_eq!(stack[4].as_int(), 4); - stack.dup(3); - assert_eq!(stack.len(), 14); - assert_eq!(stack[0].as_int(), 5); - assert_eq!(stack[1].as_int(), 7); - assert_eq!(stack[2].as_int(), 7); - assert_eq!(stack[3].as_int(), 6); - assert_eq!(stack[4].as_int(), 5); - assert_eq!(stack[5].as_int(), 4); - - // swap(n) - stack.swap(1); - assert_eq!(stack.len(), 14); - assert_eq!(stack[0].as_int(), 7); - assert_eq!(stack[1].as_int(), 5); - assert_eq!(stack[2].as_int(), 7); - assert_eq!(stack[3].as_int(), 6); - assert_eq!(stack[4].as_int(), 5); - assert_eq!(stack[5].as_int(), 4); - - // movup(n) - stack.movup(3); - assert_eq!(stack.len(), 14); - assert_eq!(stack[0].as_int(), 6); - assert_eq!(stack[1].as_int(), 7); - assert_eq!(stack[2].as_int(), 5); - assert_eq!(stack[3].as_int(), 7); - assert_eq!(stack[4].as_int(), 5); - assert_eq!(stack[5].as_int(), 4); - - // movdn(n) - stack.movdn(3); - assert_eq!(stack.len(), 14); - assert_eq!(stack[0].as_int(), 7); - assert_eq!(stack[1].as_int(), 5); - assert_eq!(stack[2].as_int(), 7); - assert_eq!(stack[3].as_int(), 6); - assert_eq!(stack[4].as_int(), 5); - assert_eq!(stack[5].as_int(), 4); - - // drop - stack.drop(); - assert_eq!(stack.len(), 13); - assert_eq!(stack[0].as_int(), 5); - assert_eq!(stack[1].as_int(), 7); - assert_eq!(stack[2].as_int(), 6); - assert_eq!(stack[3].as_int(), 5); - assert_eq!(stack[4].as_int(), 4); - - // dropn - stack.dropn(2); - assert_eq!(stack.len(), 11); - assert_eq!(stack[0].as_int(), 6); - assert_eq!(stack[1].as_int(), 5); - assert_eq!(stack[2].as_int(), 4); - - // push - stack.push(six); - stack.push(seven); - assert_eq!(stack.len(), 13); - assert_eq!(stack[0].as_int(), 7); - assert_eq!(stack[1].as_int(), 6); - assert_eq!(stack[2].as_int(), 6); - assert_eq!(stack[3].as_int(), 5); - assert_eq!(stack[4].as_int(), 4); - } -} diff --git a/hir/src/asm/utils.rs b/hir/src/asm/utils.rs deleted file mode 100644 index 508061838..000000000 --- a/hir/src/asm/utils.rs +++ /dev/null @@ -1,9 +0,0 @@ -use alloc::sync::Arc; - -use crate::diagnostics::Span; - -/// Obtain a [miden_assembly::ast::Ident] from a [crate::Ident], with source span intact. -pub fn translate_ident(id: crate::Ident) -> miden_assembly::ast::Ident { - let name = Arc::from(id.as_str().to_string().into_boxed_str()); - miden_assembly::ast::Ident::new_unchecked(Span::new(id.span, name)) -} diff --git a/hir/src/attribute.rs b/hir/src/attribute.rs deleted file mode 100644 index 12f090449..000000000 --- a/hir/src/attribute.rs +++ /dev/null @@ -1,272 +0,0 @@ -use alloc::collections::BTreeMap; -use core::{borrow::Borrow, fmt}; - -use crate::Symbol; - -pub mod attributes { - use super::*; - use crate::symbols; - - /// This attribute indicates that the decorated function is the entrypoint - /// for its containing program, regardless of what module it is defined in. - pub const ENTRYPOINT: Attribute = Attribute { - name: symbols::Entrypoint, - value: AttributeValue::Unit, - }; -} - -/// An [AttributeSet] is a uniqued collection of attributes associated with some IR entity -#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] -pub struct AttributeSet(BTreeMap); -impl FromIterator for AttributeSet { - fn from_iter(attrs: T) -> Self - where - T: IntoIterator, - { - let mut map = BTreeMap::default(); - for attr in attrs.into_iter() { - map.insert(attr.name, attr.value); - } - Self(map) - } -} -impl FromIterator<(Symbol, AttributeValue)> for AttributeSet { - fn from_iter(attrs: T) -> Self - where - T: IntoIterator, - { - let mut map = BTreeMap::default(); - for (name, value) in attrs.into_iter() { - map.insert(name, value); - } - Self(map) - } -} -impl AttributeSet { - /// Get a new, empty [AttributeSet] - pub fn new() -> Self { - Self::default() - } - - /// Insert a new [Attribute] in this set by `name` and `value` - pub fn insert(&mut self, name: impl Into, value: impl Into) { - self.0.insert(name.into(), value.into()); - } - - /// Adds `attr` to this set - pub fn set(&mut self, attr: Attribute) { - self.0.insert(attr.name, attr.value); - } - - /// Remove an [Attribute] by name from this set - pub fn remove(&mut self, name: &Q) - where - Symbol: Borrow, - Q: Ord + ?Sized, - { - self.0.remove(name); - } - - /// Determine if the named [Attribute] is present in this set - pub fn has(&self, key: &Q) -> bool - where - Symbol: Borrow, - Q: Ord + ?Sized, - { - self.0.contains_key(key) - } - - /// Get the [AttributeValue] associated with the named [Attribute] - pub fn get(&self, key: &Q) -> Option<&AttributeValue> - where - Symbol: Borrow, - Q: Ord + ?Sized, - { - self.0.get(key) - } - - /// Get the value associated with the named [Attribute] as a boolean, or `None`. - pub fn get_bool(&self, key: &Q) -> Option - where - Symbol: Borrow, - Q: Ord + ?Sized, - { - self.0.get(key).and_then(|v| v.as_bool()) - } - - /// Get the value associated with the named [Attribute] as an integer, or `None`. - pub fn get_int(&self, key: &Q) -> Option - where - Symbol: Borrow, - Q: Ord + ?Sized, - { - self.0.get(key).and_then(|v| v.as_int()) - } - - /// Get the value associated with the named [Attribute] as a [Symbol], or `None`. - pub fn get_symbol(&self, key: &Q) -> Option - where - Symbol: Borrow, - Q: Ord + ?Sized, - { - self.0.get(key).and_then(|v| v.as_symbol()) - } - - /// Iterate over each [Attribute] in this set - pub fn iter(&self) -> impl Iterator + '_ { - self.0.iter().map(|(k, v)| Attribute { - name: *k, - value: *v, - }) - } -} - -/// An [Attribute] associates some data with a well-known identifier (name). -/// -/// Attributes are used for representing metadata that helps guide compilation, -/// but which is not part of the code itself. For example, `cfg` flags in Rust -/// are an example of something which you could represent using an [Attribute]. -/// They can also be used to store documentation, source locations, and more. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Attribute { - /// The name of this attribute - pub name: Symbol, - /// The value associated with this attribute - pub value: AttributeValue, -} -impl Attribute { - pub fn new(name: impl Into, value: impl Into) -> Self { - Self { - name: name.into(), - value: value.into(), - } - } -} -impl fmt::Display for Attribute { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.value { - AttributeValue::Unit => write!(f, "#[{}]", self.name.as_str()), - value => write!(f, "#[{}({value})]", &self.name), - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum AttributeValue { - /// No concrete value (i.e. presence of the attribute is significant) - Unit, - /// A boolean value - Bool(bool), - /// A signed integer - Int(isize), - /// An interned string - String(Symbol), -} -impl AttributeValue { - pub fn as_bool(&self) -> Option { - match self { - Self::Bool(value) => Some(*value), - _ => None, - } - } - - pub fn as_int(&self) -> Option { - match self { - Self::Int(value) => Some(*value), - _ => None, - } - } - - pub fn as_symbol(&self) -> Option { - match self { - Self::String(value) => Some(*value), - _ => None, - } - } -} -impl fmt::Display for AttributeValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Unit => f.write_str("()"), - Self::Bool(value) => write!(f, "{value}"), - Self::Int(value) => write!(f, "{value}"), - Self::String(value) => write!(f, "\"{}\"", value.as_str().escape_default()), - } - } -} -impl From<()> for AttributeValue { - fn from(_: ()) -> Self { - Self::Unit - } -} -impl From for AttributeValue { - fn from(value: bool) -> Self { - Self::Bool(value) - } -} -impl From for AttributeValue { - fn from(value: isize) -> Self { - Self::Int(value) - } -} -impl From<&str> for AttributeValue { - fn from(value: &str) -> Self { - Self::String(Symbol::intern(value)) - } -} -impl From for AttributeValue { - fn from(value: String) -> Self { - Self::String(Symbol::intern(value.as_str())) - } -} -impl From for AttributeValue { - fn from(value: u8) -> Self { - Self::Int(value as isize) - } -} -impl From for AttributeValue { - fn from(value: i8) -> Self { - Self::Int(value as isize) - } -} -impl From for AttributeValue { - fn from(value: u16) -> Self { - Self::Int(value as isize) - } -} -impl From for AttributeValue { - fn from(value: i16) -> Self { - Self::Int(value as isize) - } -} -impl From for AttributeValue { - fn from(value: u32) -> Self { - Self::Int(value as isize) - } -} -impl From for AttributeValue { - fn from(value: i32) -> Self { - Self::Int(value as isize) - } -} -impl TryFrom for AttributeValue { - type Error = core::num::TryFromIntError; - - fn try_from(value: usize) -> Result { - Ok(Self::Int(value.try_into()?)) - } -} -impl TryFrom for AttributeValue { - type Error = core::num::TryFromIntError; - - fn try_from(value: u64) -> Result { - Ok(Self::Int(value.try_into()?)) - } -} -impl TryFrom for AttributeValue { - type Error = core::num::TryFromIntError; - - fn try_from(value: i64) -> Result { - Ok(Self::Int(value.try_into()?)) - } -} diff --git a/hir/src/attributes.rs b/hir/src/attributes.rs new file mode 100644 index 000000000..654c9dd15 --- /dev/null +++ b/hir/src/attributes.rs @@ -0,0 +1,712 @@ +mod overflow; +mod visibility; + +use alloc::{boxed::Box, collections::BTreeMap, vec, vec::Vec}; +use core::{any::Any, borrow::Borrow, fmt}; + +pub use self::{overflow::Overflow, visibility::Visibility}; +use crate::{interner::Symbol, Immediate}; + +pub mod markers { + use midenc_hir_symbol::symbols; + + use super::*; + + pub const INLINE: Attribute = Attribute { + name: symbols::Inline, + value: None, + intrinsic: false, + }; +} + +/// An [AttributeSet] is a uniqued collection of attributes associated with some IR entity +#[derive(Debug, Default, Clone)] +pub struct AttributeSet(Vec); +impl FromIterator for AttributeSet { + fn from_iter(attrs: T) -> Self + where + T: IntoIterator, + { + let mut map = BTreeMap::default(); + for attr in attrs.into_iter() { + map.insert(attr.name, (attr.value, attr.intrinsic)); + } + Self( + map.into_iter() + .map(|(name, (value, intrinsic))| Attribute { + name, + value, + intrinsic, + }) + .collect(), + ) + } +} +impl FromIterator<(Symbol, Option>)> for AttributeSet { + fn from_iter(attrs: T) -> Self + where + T: IntoIterator>)>, + { + let mut map = BTreeMap::default(); + for (name, value) in attrs.into_iter() { + map.insert(name, value); + } + Self( + map.into_iter() + .map(|(name, value)| Attribute { + name, + value, + intrinsic: false, + }) + .collect(), + ) + } +} +impl AttributeSet { + /// Get a new, empty [AttributeSet] + pub const fn new() -> Self { + Self(Vec::new()) + } + + /// Insert a new [Attribute] in this set by `name` and `value` + pub fn insert(&mut self, name: impl Into, value: Option) { + self.set(Attribute { + name: name.into(), + value: value.map(|v| Box::new(v) as Box), + intrinsic: false, + }); + } + + /// Adds `attr` to this set + pub fn set(&mut self, attr: Attribute) { + match self.0.binary_search_by_key(&attr.name, |attr| attr.name) { + Ok(index) => { + self.0[index].value = attr.value; + } + Err(index) => { + if index == self.0.len() { + self.0.push(attr); + } else { + self.0.insert(index, attr); + } + } + } + } + + pub fn mark_intrinsic(&mut self, key: impl Into) { + let key = key.into(); + if let Ok(index) = self.0.binary_search_by_key(&key, |attr| attr.name) { + self.0[index].intrinsic = true; + } + } + + /// Remove an [Attribute] by name from this set + pub fn remove(&mut self, name: impl Into) { + let name = name.into(); + match self.0.binary_search_by_key(&name, |attr| attr.name) { + Ok(index) if index + 1 == self.0.len() => { + self.0.pop(); + } + Ok(index) => { + self.0.remove(index); + } + Err(_) => (), + } + } + + /// Determine if the named [Attribute] is present in this set + pub fn has(&self, key: impl Into) -> bool { + let key = key.into(); + self.0.binary_search_by_key(&key, |attr| attr.name).is_ok() + } + + /// Get the [AttributeValue] associated with the named [Attribute] + pub fn get_any(&self, key: impl Into) -> Option<&dyn AttributeValue> { + let key = key.into(); + match self.0.binary_search_by_key(&key, |attr| attr.name) { + Ok(index) => self.0[index].value.as_deref(), + Err(_) => None, + } + } + + /// Get the [AttributeValue] associated with the named [Attribute] + pub fn get_any_mut(&mut self, key: impl Into) -> Option<&mut dyn AttributeValue> { + let key = key.into(); + match self.0.binary_search_by_key(&key, |attr| attr.name) { + Ok(index) => self.0[index].value.as_deref_mut(), + Err(_) => None, + } + } + + /// Get the value associated with the named [Attribute] as a value of type `V`, or `None`. + pub fn get(&self, key: impl Into) -> Option<&V> + where + V: AttributeValue, + { + self.get_any(key).and_then(|v| v.downcast_ref::()) + } + + /// Get the value associated with the named [Attribute] as a mutable value of type `V`, or + /// `None`. + pub fn get_mut(&mut self, key: impl Into) -> Option<&mut V> + where + V: AttributeValue, + { + self.get_any_mut(key).and_then(|v| v.downcast_mut::()) + } + + /// Iterate over each [Attribute] in this set + pub fn iter(&self) -> impl Iterator + '_ { + self.0.iter() + } +} + +impl Eq for AttributeSet {} +impl PartialEq for AttributeSet { + fn eq(&self, other: &Self) -> bool { + if self.0.len() != other.0.len() { + return false; + } + + for attr in self.0.iter() { + if !other.has(attr.name) { + return false; + } + + let other_value = other.get_any(attr.name); + if attr.value() != other_value { + return false; + } + } + + true + } +} + +impl core::hash::Hash for AttributeSet { + fn hash(&self, state: &mut H) { + self.0.len().hash(state); + + for attr in self.0.iter() { + attr.hash(state); + } + } +} + +/// An [Attribute] associates some data with a well-known identifier (name). +/// +/// Attributes are used for representing metadata that helps guide compilation, +/// but which is not part of the code itself. For example, `cfg` flags in Rust +/// are an example of something which you could represent using an [Attribute]. +/// They can also be used to store documentation, source locations, and more. +#[derive(Debug, Hash)] +pub struct Attribute { + /// The name of this attribute + pub name: Symbol, + /// The value associated with this attribute + pub value: Option>, + /// This attribute represents an intrinsic property of an operation + pub intrinsic: bool, +} +impl Clone for Attribute { + fn clone(&self) -> Self { + Self { + name: self.name, + value: self.value.as_ref().map(|v| v.clone_value()), + intrinsic: self.intrinsic, + } + } +} +impl Attribute { + pub fn new(name: impl Into, value: Option) -> Self { + Self { + name: name.into(), + value: value.map(|v| Box::new(v) as Box), + intrinsic: false, + } + } + + pub fn intrinsic(name: impl Into, value: Option) -> Self { + Self { + name: name.into(), + value: value.map(|v| Box::new(v) as Box), + intrinsic: true, + } + } + + pub fn value(&self) -> Option<&dyn AttributeValue> { + self.value.as_deref() + } + + pub fn value_as(&self) -> Option<&V> + where + V: AttributeValue, + { + match self.value.as_deref() { + Some(value) => value.downcast_ref::(), + None => None, + } + } +} + +pub trait AttributeValue: + Any + fmt::Debug + crate::AttrPrinter + crate::DynPartialEq + crate::DynHash + 'static +{ + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; + fn clone_value(&self) -> Box; +} + +impl dyn AttributeValue { + pub fn is(&self) -> bool { + self.as_any().is::() + } + + pub fn downcast(self: Box) -> Result, Box> { + if self.is::() { + let ptr = Box::into_raw(self); + Ok(unsafe { Box::from_raw(ptr.cast()) }) + } else { + Err(self) + } + } + + pub fn downcast_ref(&self) -> Option<&T> { + self.as_any().downcast_ref::() + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.as_any_mut().downcast_mut::() + } + + pub fn as_bool(&self) -> Option { + if let Some(imm) = self.downcast_ref::() { + imm.as_bool() + } else { + self.downcast_ref::().copied() + } + } + + pub fn as_u32(&self) -> Option { + if let Some(imm) = self.downcast_ref::() { + imm.as_u32() + } else { + self.downcast_ref::().copied() + } + } + + pub fn as_immediate(&self) -> Option { + self.downcast_ref::().copied() + } +} + +impl core::hash::Hash for dyn AttributeValue { + fn hash(&self, state: &mut H) { + use crate::DynHash; + + let hashable = self as &dyn DynHash; + hashable.dyn_hash(state); + } +} + +impl Eq for dyn AttributeValue {} +impl PartialEq for dyn AttributeValue { + fn eq(&self, other: &Self) -> bool { + use crate::DynPartialEq; + + let partial_eqable = self as &dyn DynPartialEq; + partial_eqable.dyn_eq(other as &dyn DynPartialEq) + } +} + +#[derive(Clone)] +pub struct ArrayAttr { + values: Vec, +} +impl Default for ArrayAttr { + fn default() -> Self { + Self { + values: Default::default(), + } + } +} +impl FromIterator for ArrayAttr { + fn from_iter>(iter: I) -> Self { + Self { + values: Vec::::from_iter(iter), + } + } +} +impl ArrayAttr { + pub fn is_empty(&self) -> bool { + self.values.is_empty() + } + + pub fn len(&self) -> usize { + self.values.len() + } + + pub fn iter(&self) -> core::slice::Iter<'_, T> { + self.values.iter() + } + + pub fn push(&mut self, value: T) { + self.values.push(value); + } + + pub fn remove(&mut self, index: usize) -> T { + self.values.remove(index) + } +} +impl ArrayAttr +where + T: Eq, +{ + pub fn contains(&self, value: &T) -> bool { + self.values.contains(value) + } +} +impl Eq for ArrayAttr where T: Eq {} +impl PartialEq for ArrayAttr +where + T: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.values == other.values + } +} +impl fmt::Debug for ArrayAttr +where + T: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.values.iter()).finish() + } +} +impl crate::formatter::PrettyPrint for ArrayAttr +where + T: crate::formatter::PrettyPrint, +{ + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + let entries = self.values.iter().fold(Document::Empty, |acc, v| match acc { + Document::Empty => v.render(), + _ => acc + const_text(", ") + v.render(), + }); + if self.values.is_empty() { + const_text("[]") + } else { + const_text("[") + entries + const_text("]") + } + } +} +impl core::hash::Hash for ArrayAttr +where + T: core::hash::Hash, +{ + fn hash(&self, state: &mut H) { + as core::hash::Hash>::hash(&self.values, state); + } +} +impl AttributeValue for ArrayAttr +where + T: fmt::Debug + crate::formatter::PrettyPrint + Clone + Eq + core::hash::Hash + 'static, +{ + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } + + #[inline] + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct SetAttr { + values: Vec, +} +impl Default for SetAttr { + fn default() -> Self { + Self { + values: Default::default(), + } + } +} +impl SetAttr +where + K: Ord + Clone, +{ + pub fn insert(&mut self, key: K) -> bool { + match self.values.binary_search_by(|k| key.cmp(k)) { + Ok(index) => { + self.values[index] = key; + false + } + Err(index) => { + self.values.insert(index, key); + true + } + } + } + + pub fn contains(&self, key: &K) -> bool { + self.values.binary_search_by(|k| key.cmp(k)).is_ok() + } + + pub fn iter(&self) -> core::slice::Iter<'_, K> { + self.values.iter() + } + + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: ?Sized + Ord, + { + match self.values.binary_search_by(|k| key.cmp(k.borrow())) { + Ok(index) => Some(self.values.remove(index)), + Err(_) => None, + } + } +} +impl Eq for SetAttr where K: Eq {} +impl PartialEq for SetAttr +where + K: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.values == other.values + } +} +impl fmt::Debug for SetAttr +where + K: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_set().entries(self.values.iter()).finish() + } +} +impl crate::formatter::PrettyPrint for SetAttr +where + K: crate::formatter::PrettyPrint, +{ + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + let entries = self.values.iter().fold(Document::Empty, |acc, k| match acc { + Document::Empty => k.render(), + _ => acc + const_text(", ") + k.render(), + }); + if self.values.is_empty() { + const_text("{}") + } else { + const_text("{") + entries + const_text("}") + } + } +} +impl core::hash::Hash for SetAttr +where + K: core::hash::Hash, +{ + fn hash(&self, state: &mut H) { + as core::hash::Hash>::hash(&self.values, state); + } +} +impl AttributeValue for SetAttr +where + K: fmt::Debug + crate::formatter::PrettyPrint + Clone + Eq + core::hash::Hash + 'static, +{ + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } + + #[inline] + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } +} + +#[derive(Clone)] +pub struct DictAttr { + values: Vec<(K, V)>, +} +impl Default for DictAttr { + fn default() -> Self { + Self { values: vec![] } + } +} +impl DictAttr +where + K: Ord, + V: Clone, +{ + pub fn insert(&mut self, key: K, value: V) { + match self.values.binary_search_by(|(k, _)| key.cmp(k)) { + Ok(index) => { + self.values[index].1 = value; + } + Err(index) => { + self.values.insert(index, (key, value)); + } + } + } + + pub fn contains_key(&self, key: &Q) -> bool + where + K: Borrow, + Q: ?Sized + Ord, + { + self.values.binary_search_by(|(k, _)| key.cmp(k.borrow())).is_ok() + } + + pub fn get(&self, key: &Q) -> Option<&V> + where + K: Borrow, + Q: ?Sized + Ord, + { + match self.values.binary_search_by(|(k, _)| key.cmp(k.borrow())) { + Ok(index) => Some(&self.values[index].1), + Err(_) => None, + } + } + + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: ?Sized + Ord, + { + match self.values.binary_search_by(|(k, _)| key.cmp(k.borrow())) { + Ok(index) => Some(self.values.remove(index).1), + Err(_) => None, + } + } + + pub fn iter(&self) -> core::slice::Iter<'_, (K, V)> { + self.values.iter() + } +} +impl Eq for DictAttr +where + K: Eq, + V: Eq, +{ +} +impl PartialEq for DictAttr +where + K: PartialEq, + V: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.values == other.values + } +} +impl fmt::Debug for DictAttr +where + K: fmt::Debug, + V: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .entries(self.values.iter().map(|entry| (&entry.0, &entry.1))) + .finish() + } +} +impl crate::formatter::PrettyPrint for DictAttr +where + K: crate::formatter::PrettyPrint, + V: crate::formatter::PrettyPrint, +{ + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + let entries = self.values.iter().fold(Document::Empty, |acc, (k, v)| match acc { + Document::Empty => k.render() + const_text(" = ") + v.render(), + _ => acc + const_text(", ") + k.render() + const_text(" = ") + v.render(), + }); + if self.values.is_empty() { + const_text("{}") + } else { + const_text("{") + entries + const_text("}") + } + } +} +impl core::hash::Hash for DictAttr +where + K: core::hash::Hash, + V: core::hash::Hash, +{ + fn hash(&self, state: &mut H) { + as core::hash::Hash>::hash(&self.values, state); + } +} +impl AttributeValue for DictAttr +where + K: fmt::Debug + crate::formatter::PrettyPrint + Clone + Eq + core::hash::Hash + 'static, + V: fmt::Debug + crate::formatter::PrettyPrint + Clone + Eq + core::hash::Hash + 'static, +{ + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } + + #[inline] + fn clone_value(&self) -> Box { + Box::new(self.clone()) + } +} + +#[macro_export] +macro_rules! define_attr_type { + ($T:ty) => { + impl $crate::AttributeValue for $T { + #[inline(always)] + fn as_any(&self) -> &dyn core::any::Any { + self as &dyn core::any::Any + } + + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn core::any::Any { + self as &mut dyn core::any::Any + } + + #[inline] + fn clone_value(&self) -> ::alloc::boxed::Box { + ::alloc::boxed::Box::new(self.clone()) + } + } + }; +} + +define_attr_type!(bool); +define_attr_type!(u8); +define_attr_type!(i8); +define_attr_type!(u16); +define_attr_type!(i16); +define_attr_type!(u32); +define_attr_type!(core::num::NonZeroU32); +define_attr_type!(i32); +define_attr_type!(u64); +define_attr_type!(i64); +define_attr_type!(usize); +define_attr_type!(isize); +define_attr_type!(Symbol); +define_attr_type!(super::Immediate); +define_attr_type!(super::Type); diff --git a/hir/src/attributes/overflow.rs b/hir/src/attributes/overflow.rs new file mode 100644 index 000000000..8bc7eae00 --- /dev/null +++ b/hir/src/attributes/overflow.rs @@ -0,0 +1,66 @@ +use core::fmt; + +use crate::define_attr_type; + +/// This enumeration represents the various ways in which arithmetic operations +/// can be configured to behave when either the operands or results over/underflow +/// the range of the integral type. +/// +/// Always check the documentation of the specific instruction involved to see if there +/// are any specific differences in how this enum is interpreted compared to the default +/// meaning of each variant. +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)] +pub enum Overflow { + /// Typically, this means the operation is performed using the equivalent field element + /// operation, rather than a dedicated operation for the given type. Because of this, the + /// result of the operation may exceed that of the integral type expected, but this will + /// not be caught right away. + /// + /// It is the callers responsibility to ensure that resulting value is in range. + #[default] + Unchecked, + /// The operation will trap if the operands, or the result, is not valid for the range of the + /// integral type involved, e.g. u32. + Checked, + /// The operation will wrap around, depending on the range of the integral type. For example, + /// given a u32 value, this is done by applying `mod 2^32` to the result. + Wrapping, + /// The result of the operation will be computed as in [Wrapping], however in addition to the + /// result, this variant also pushes a value on the stack which represents whether or not the + /// operation over/underflowed; either 1 if over/underflow occurred, or 0 otherwise. + Overflowing, +} +impl Overflow { + /// Returns true if overflow is unchecked + pub fn is_unchecked(&self) -> bool { + matches!(self, Self::Unchecked) + } + + /// Returns true if overflow will cause a trap + pub fn is_checked(&self) -> bool { + matches!(self, Self::Checked) + } + + /// Returns true if overflow will add an extra boolean on top of the stack + pub fn is_overflowing(&self) -> bool { + matches!(self, Self::Overflowing) + } +} +impl fmt::Display for Overflow { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Unchecked => f.write_str("unchecked"), + Self::Checked => f.write_str("checked"), + Self::Wrapping => f.write_str("wrapping"), + Self::Overflowing => f.write_str("overflow"), + } + } +} +impl crate::formatter::PrettyPrint for Overflow { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + display(self) + } +} + +define_attr_type!(Overflow); diff --git a/hir/src/attributes/visibility.rs b/hir/src/attributes/visibility.rs new file mode 100644 index 000000000..81b1122c4 --- /dev/null +++ b/hir/src/attributes/visibility.rs @@ -0,0 +1,78 @@ +use core::{fmt, str::FromStr}; + +use crate::define_attr_type; + +/// The types of visibility that a [Symbol] may have +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(u8)] +pub enum Visibility { + /// The symbol is public and may be referenced anywhere internal or external to the visible + /// references in the IR. + /// + /// Public visibility implies that we cannot remove the symbol even if we are unaware of any + /// references, and no other constraints apply, as we must assume that the symbol has references + /// we don't know about. + #[default] + Public, + /// The symbol is private and may only be referenced by ops local to operations within the + /// current symbol table. + /// + /// Private visibility implies that we know all uses of the symbol, and that those uses must + /// all exist within the current symbol table. + Private, + /// The symbol is public, but may only be referenced by symbol tables in the current compilation + /// graph, thus retaining the ability to observe all uses, and optimize based on that + /// information. + /// + /// Internal visibility implies that we know all uses of the symbol, but that there may be uses + /// in other symbol tables in addition to the current one. + Internal, +} +define_attr_type!(Visibility); +impl Visibility { + #[inline] + pub fn is_public(&self) -> bool { + matches!(self, Self::Public) + } + + #[inline] + pub fn is_private(&self) -> bool { + matches!(self, Self::Private) + } + + #[inline] + pub fn is_internal(&self) -> bool { + matches!(self, Self::Internal) + } +} +impl crate::formatter::PrettyPrint for Visibility { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + match self { + Self::Public => const_text("public"), + Self::Private => const_text("private"), + Self::Internal => const_text("internal"), + } + } +} +impl fmt::Display for Visibility { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Public => f.write_str("public"), + Self::Private => f.write_str("private"), + Self::Internal => f.write_str("internal"), + } + } +} +impl FromStr for Visibility { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "public" => Ok(Self::Public), + "private" => Ok(Self::Private), + "internal" => Ok(Self::Internal), + _ => Err(()), + } + } +} diff --git a/hir/src/block.rs b/hir/src/block.rs deleted file mode 100644 index fc34ea21c..000000000 --- a/hir/src/block.rs +++ /dev/null @@ -1,171 +0,0 @@ -use cranelift_entity::entity_impl; - -use super::*; - -/// A handle to a single function block -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Block(u32); -entity_impl!(Block, "block"); -impl Default for Block { - #[inline] - fn default() -> Self { - use cranelift_entity::packed_option::ReservedValue; - - Self::reserved_value() - } -} -impl formatter::PrettyPrint for Block { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - const_text("(") + const_text("block") + const_text(" ") + display(self.0) + const_text(")") - } -} - -/// Data associated with a `Block`. -/// -/// Blocks have arguments, and consist of a sequence of instructions. -pub struct BlockData { - pub id: Block, - pub params: ValueList, - pub insts: InstructionList, -} -impl Drop for BlockData { - fn drop(&mut self) { - self.insts.fast_clear(); - } -} -impl Clone for BlockData { - fn clone(&self) -> Self { - Self { - id: self.id, - params: self.params, - insts: Default::default(), - } - } -} -impl BlockData { - pub(crate) fn new(id: Block) -> Self { - Self { - id, - params: ValueList::new(), - insts: Default::default(), - } - } - - #[inline] - pub fn arity(&self, pool: &ValueListPool) -> usize { - self.params.len(pool) - } - - #[inline] - pub fn params<'a, 'b: 'a>(&'b self, pool: &'a ValueListPool) -> &'a [Value] { - self.params.as_slice(pool) - } - - pub fn insts(&self) -> impl Iterator + '_ { - Insts { - cursor: self.insts.front(), - } - } - - #[inline(always)] - pub fn prepend(&mut self, inst: UnsafeRef) { - self.insts.push_front(inst); - } - - #[inline(always)] - pub fn append(&mut self, inst: UnsafeRef) { - self.insts.push_back(inst); - } - - #[inline(always)] - pub fn front<'a, 'b: 'a>(&'b self) -> InstructionCursor<'a> { - self.insts.front() - } - - #[inline(always)] - pub fn back<'a, 'b: 'a>(&'b self) -> InstructionCursor<'a> { - self.insts.back() - } - - pub fn cursor_at_inst<'a, 'b: 'a>(&'b self, inst: Inst) -> InstructionCursor<'a> { - let mut cursor = self.insts.front(); - while let Some(current_inst) = cursor.get() { - if current_inst.key == inst { - break; - } - cursor.move_next(); - } - cursor - } - - pub fn cursor_mut_at_inst<'a, 'b: 'a>(&'b mut self, inst: Inst) -> InstructionCursorMut<'a> { - let mut cursor = self.insts.front_mut(); - while let Some(current_inst) = cursor.get() { - if current_inst.key == inst { - break; - } - cursor.move_next(); - } - cursor - } - - #[inline(always)] - pub fn cursor_mut<'a, 'b: 'a>(&'b mut self) -> InstructionCursorMut<'a> { - self.insts.front_mut() - } - - pub fn cursor_mut_at<'a, 'b: 'a>(&'b mut self, index: usize) -> InstructionCursorMut<'a> { - let mut cursor = self.insts.front_mut(); - for _ in 0..index { - cursor.move_next(); - assert!(!cursor.is_null(), "index out of bounds"); - } - cursor - } - - #[inline] - pub fn insert_after(&mut self, index: usize, inst: UnsafeRef) { - let mut cursor = self.cursor_mut_at(index); - cursor.insert_after(inst); - } - - #[inline] - pub fn insert_before(&mut self, index: usize, inst: UnsafeRef) { - let mut cursor = self.cursor_mut_at(index); - cursor.insert_before(inst); - } - - pub fn first(&self) -> Option { - self.insts.front().get().map(|data| data.key) - } - - pub fn last(&self) -> Option { - self.insts.back().get().map(|data| data.key) - } - - pub fn is_empty(&self) -> bool { - self.insts.is_empty() - } - - pub fn len(&self) -> usize { - self.insts.iter().count() - } -} - -struct Insts<'f> { - cursor: InstructionCursor<'f>, -} -impl<'f> Iterator for Insts<'f> { - type Item = Inst; - - fn next(&mut self) -> Option { - if self.cursor.is_null() { - return None; - } - let next = self.cursor.get().map(|data| data.key); - self.cursor.move_next(); - next - } -} diff --git a/hir/src/builder.rs b/hir/src/builder.rs deleted file mode 100644 index 1f9a481f7..000000000 --- a/hir/src/builder.rs +++ /dev/null @@ -1,1791 +0,0 @@ -use crate::{diagnostics::Span, *}; - -pub struct FunctionBuilder<'f> { - pub func: &'f mut Function, - ip: InsertionPoint, -} -impl<'f> FunctionBuilder<'f> { - pub fn new(func: &'f mut Function) -> Self { - let entry = func.dfg.entry_block(); - let position = func.dfg.last_block().unwrap_or(entry); - - Self { - func, - ip: InsertionPoint::after(ProgramPoint::Block(position)), - } - } - - pub fn at(func: &'f mut Function, ip: InsertionPoint) -> Self { - Self { func, ip } - } - - pub fn entry_block(&self) -> Block { - self.func.dfg.entry_block() - } - - #[inline] - pub fn current_block(&self) -> Block { - self.ip.block(self.func) - } - - #[inline] - pub fn switch_to_block(&mut self, block: Block) { - self.ip = InsertionPoint::after(ProgramPoint::Block(block)); - } - - pub fn create_block(&mut self) -> Block { - self.func.dfg.create_block() - } - - pub fn detach_block(&mut self, block: Block) { - assert_ne!( - block, - self.current_block(), - "cannot remove block the builder is currently inserting in" - ); - self.func.dfg.detach_block(block); - } - - pub fn block_params(&self, block: Block) -> &[Value] { - self.func.dfg.block_params(block) - } - - pub fn append_block_param(&mut self, block: Block, ty: Type, span: SourceSpan) -> Value { - self.func.dfg.append_block_param(block, ty, span) - } - - pub fn inst_results(&self, inst: Inst) -> &[Value] { - self.func.dfg.inst_results(inst) - } - - pub fn first_result(&self, inst: Inst) -> Value { - self.func.dfg.first_result(inst) - } - - pub fn create_global_value(&mut self, data: GlobalValueData) -> GlobalValue { - self.func.dfg.create_global_value(data) - } - - pub fn create_local(&mut self, ty: Type) -> LocalId { - self.func.dfg.alloc_local(ty) - } - - pub fn get_import(&self, id: &FunctionIdent) -> Option<&ExternalFunction> { - self.func.dfg.get_import(id) - } - - #[inline] - pub fn import_function, F: AsRef>( - &mut self, - module: M, - function: F, - signature: Signature, - span: SourceSpan, - ) -> Result { - let module = Ident::with_empty_span(Symbol::intern(module.as_ref())); - let function = Ident::new(Symbol::intern(function.as_ref()), span); - self.func.dfg.import_function(module, function, signature) - } - - pub fn ins<'a, 'b: 'a>(&'b mut self) -> DefaultInstBuilder<'a> { - DefaultInstBuilder::at(&mut self.func.dfg, self.ip) - } -} - -pub struct DefaultInstBuilder<'f> { - dfg: &'f mut DataFlowGraph, - ip: InsertionPoint, -} -impl<'f> DefaultInstBuilder<'f> { - pub(crate) fn new(dfg: &'f mut DataFlowGraph, block: Block) -> Self { - assert!(dfg.is_block_linked(block)); - - Self { - dfg, - ip: InsertionPoint::after(ProgramPoint::Block(block)), - } - } - - pub fn at(dfg: &'f mut DataFlowGraph, ip: InsertionPoint) -> Self { - Self { dfg, ip } - } -} -impl<'f> InstBuilderBase<'f> for DefaultInstBuilder<'f> { - fn data_flow_graph(&self) -> &DataFlowGraph { - self.dfg - } - - fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { - self.dfg - } - - fn insertion_point(&self) -> InsertionPoint { - self.ip - } - - fn build(self, data: Instruction, ty: Type, span: SourceSpan) -> (Inst, &'f mut DataFlowGraph) { - if cfg!(debug_assertions) { - if let InsertionPoint { - at: ProgramPoint::Block(blk), - action: Insert::After, - } = self.ip - { - debug_assert!( - !self.dfg.is_block_terminated(blk), - "cannot append an instruction to a block that is already terminated" - ); - } - } - - let inst = self.dfg.insert_inst(self.ip, data, ty, span); - (inst, self.dfg) - } -} - -/// Instruction builder that replaces an existing instruction. -/// -/// The inserted instruction will have the same `Inst` number as the old one. -/// -/// If the old instruction still has result values attached, it is assumed that the new instruction -/// produces the same number and types of results. The old result values are preserved. If the -/// replacement instruction format does not support multiple results, the builder panics. It is a -/// bug to leave result values dangling. -pub struct ReplaceBuilder<'f> { - dfg: &'f mut DataFlowGraph, - inst: Inst, -} - -impl<'f> ReplaceBuilder<'f> { - /// Create a `ReplaceBuilder` that will overwrite `inst`. - pub fn new(dfg: &'f mut DataFlowGraph, inst: Inst) -> Self { - Self { dfg, inst } - } -} - -impl<'f> InstBuilderBase<'f> for ReplaceBuilder<'f> { - fn data_flow_graph(&self) -> &DataFlowGraph { - self.dfg - } - - fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { - self.dfg - } - - fn insertion_point(&self) -> InsertionPoint { - InsertionPoint::before(ProgramPoint::Inst(self.inst)) - } - - fn build( - self, - data: Instruction, - ctrl_typevar: Type, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - // Splat the new instruction on top of the old one. - self.dfg.insts[self.inst].replace(Span::new(span, data)); - // The old result values, if any, were either detached or non-existent. - // Construct new ones. - self.dfg.replace_results(self.inst, ctrl_typevar); - - (self.inst, self.dfg) - } -} - -pub trait InstBuilderBase<'f>: Sized { - /// Get a reference to the underlying [DataFlowGraph] for this builder - fn data_flow_graph(&self) -> &DataFlowGraph; - /// Get a mutable reference to the underlying [DataFlowGraph] for this builder - fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph; - /// Return the insertion point of this builder - fn insertion_point(&self) -> InsertionPoint; - /// Build the given instruction, returing it's handle and the inner [DataFlowGraph] - fn build( - self, - data: Instruction, - ctrl_ty: Type, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph); - /// Get a default instruction builder using the dataflow graph and insertion point of the - /// current builder - fn ins<'a, 'b: 'a>(&'b mut self) -> DefaultInstBuilder<'a> { - let ip = self.insertion_point(); - DefaultInstBuilder::at(self.data_flow_graph_mut(), ip) - } -} - -macro_rules! into_first_result { - ($built:expr) => {{ - let (inst, dfg) = $built; - dfg.first_result(inst) - }}; -} - -macro_rules! assert_integer_operands { - ($this:ident, $lhs:ident, $rhs:ident) => {{ - let lty = require_matching_operands!($this, $lhs, $rhs); - assert!( - lty.is_integer(), - "expected {} and {} to be of integral type", - stringify!($lhs), - stringify!($rhs) - ); - lty.clone() - }}; - - ($this:ident, $lhs:ident) => {{ - let ty = require_integer!($this, $lhs); - ty.clone() - }}; -} - -macro_rules! require_matching_operands { - ($this:ident, $lhs:ident, $rhs:ident) => {{ - let lty = $this.data_flow_graph().value_type($lhs); - let rty = $this.data_flow_graph().value_type($rhs); - assert_eq!( - lty, - rty, - "expected {} and {} to be of the same type", - stringify!($lhs), - stringify!($rhs) - ); - lty - }}; -} - -macro_rules! require_unsigned_integer { - ($this:ident, $val:ident) => {{ - let ty = $this.data_flow_graph().value_type($val); - assert!( - ty.is_unsigned_integer(), - "expected {} to be an unsigned integral type", - stringify!($val) - ); - ty - }}; -} - -macro_rules! require_integer { - ($this:ident, $val:ident) => {{ - let ty = $this.data_flow_graph().value_type($val); - assert!(ty.is_integer(), "expected {} to be of integral type", stringify!($val)); - ty - }}; - - ($this:ident, $val:ident, $ty:expr) => {{ - let ty = $this.data_flow_graph().value_type($val); - let expected_ty = $ty; - assert!(ty.is_integer(), "expected {} to be of integral type", stringify!($val)); - assert_eq!(ty, &expected_ty, "expected {} to be a {}", stringify!($val), &expected_ty); - ty - }}; -} - -macro_rules! require_felt_sized_integer { - ($this:ident, $val:ident) => {{ - let ty = $this.data_flow_graph().value_type($val); - assert!(ty.is_integer(), "expected {} to be of integral type", stringify!($val)); - assert_eq!( - ty.size_in_felts(), - 1, - "expected {} to be an integer no larger than 1 felt in size", - stringify!($val) - ); - ty - }}; -} - -macro_rules! require_pointer { - ($this:ident, $val:ident) => {{ - let ty = $this.data_flow_graph().value_type($val); - assert!( - ty.is_pointer(), - "expected {} to be of pointer type, got {}", - stringify!($val), - &ty - ); - ty - }}; -} - -macro_rules! require_pointee { - ($this:ident, $val:ident) => {{ - let ty = $this.data_flow_graph().value_type($val); - let pointee_ty = ty.pointee(); - assert!( - pointee_ty.is_some(), - "expected {} to be of pointer type, got {}", - stringify!($val), - &ty - ); - pointee_ty.unwrap() - }}; -} - -macro_rules! binary_int_op { - ($name:ident, $op:expr) => { - paste::paste! { - fn $name(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs, rhs); - into_first_result!(self.Binary($op, lty, lhs, rhs, span)) - } - fn [<$name _imm>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - into_first_result!(self.BinaryImm($op, lty, lhs, imm, span)) - } - } - }; -} - -macro_rules! binary_int_op_with_overflow { - ($name:ident, $op:expr) => { - paste::paste! { - fn [<$name _checked>](self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs, rhs); - into_first_result!(self.BinaryWithOverflow($op, lty, lhs, rhs, Overflow::Checked, span)) - } - fn [<$name _unchecked>](self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs, rhs); - into_first_result!(self.BinaryWithOverflow($op, lty, lhs, rhs, Overflow::Unchecked, span)) - } - fn [<$name _wrapping>](self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs, rhs); - into_first_result!(self.BinaryWithOverflow($op, lty, lhs, rhs, Overflow::Wrapping, span)) - } - fn [<$name _overflowing>](self, lhs: Value, rhs: Value, span: SourceSpan) -> Inst { - let lty = assert_integer_operands!(self, lhs, rhs); - self.BinaryWithOverflow($op, lty, lhs, rhs, Overflow::Overflowing, span).0 - } - fn [<$name _imm_checked>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - into_first_result!(self.BinaryImmWithOverflow($op, lty, lhs, imm, Overflow::Checked, span)) - } - fn [<$name _imm_unchecked>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - into_first_result!(self.BinaryImmWithOverflow($op, lty, lhs, imm, Overflow::Unchecked, span)) - } - fn [<$name _imm_wrapping>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - into_first_result!(self.BinaryImmWithOverflow($op, lty, lhs, imm, Overflow::Wrapping, span)) - } - fn [<$name _imm_overflowing>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Inst { - let lty = assert_integer_operands!(self, lhs); - self.BinaryImmWithOverflow($op, lty, lhs, imm, Overflow::Overflowing, span).0 - } - } - } -} - -macro_rules! checked_binary_int_op { - ($name:ident, $op:expr) => { - paste::paste! { - fn [<$name _checked>](self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs, rhs); - into_first_result!(self.BinaryWithOverflow($op, lty, lhs, rhs, Overflow::Checked, span)) - } - fn [<$name _unchecked>](self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs, rhs); - into_first_result!(self.BinaryWithOverflow($op, lty, lhs, rhs, Overflow::Unchecked, span)) - } - fn [<$name _imm_checked>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - into_first_result!(self.BinaryImmWithOverflow($op, lty, lhs, imm, Overflow::Checked, span)) - } - fn [<$name _imm_unchecked>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - into_first_result!(self.BinaryImmWithOverflow($op, lty, lhs, imm, Overflow::Unchecked, span)) - } - } - }; -} - -macro_rules! binary_boolean_op { - ($name:ident, $op:expr) => { - paste::paste! { - fn $name(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = require_matching_operands!(self, lhs, rhs).clone(); - assert_eq!(lty, Type::I1, concat!(stringify!($name), " requires boolean operands")); - into_first_result!(self.Binary($op, lty, lhs, rhs, span)) - } - fn [<$name _imm>](self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs, Type::I1).clone(); - assert_eq!(lty, Type::I1, concat!(stringify!($name), " requires boolean operands")); - into_first_result!(self.BinaryImm($op, lty, lhs, imm, span)) - } - } - }; -} - -macro_rules! unary_int_op { - ($name:ident, $op:expr) => { - fn $name(self, rhs: Value, span: SourceSpan) -> Value { - let rty = assert_integer_operands!(self, rhs); - into_first_result!(self.Unary($op, rty, rhs, span)) - } - }; -} - -macro_rules! unary_int_op_with_overflow { - ($name:ident, $op:expr) => { - paste::paste! { - fn [<$name _checked>](self, rhs: Value, span: SourceSpan) -> Value { - let rty = assert_integer_operands!(self, rhs); - into_first_result!(self.UnaryWithOverflow($op, rty, rhs, Overflow::Checked, span)) - } - fn [<$name _unchecked>](self, rhs: Value, span: SourceSpan) -> Value { - let rty = assert_integer_operands!(self, rhs); - into_first_result!(self.UnaryWithOverflow($op, rty, rhs, Overflow::Unchecked, span)) - } - fn [<$name _wrapping>](self, rhs: Value, span: SourceSpan) -> Value { - let rty = assert_integer_operands!(self, rhs); - into_first_result!(self.UnaryWithOverflow($op, rty, rhs, Overflow::Wrapping, span)) - } - fn [<$name _overflowing>](self, rhs: Value, span: SourceSpan) -> Inst { - let rty = assert_integer_operands!(self, rhs); - self.UnaryWithOverflow($op, rty, rhs, Overflow::Overflowing, span).0 - } - } - }; -} - -macro_rules! unary_boolean_op { - ($name:ident, $op:expr) => { - fn $name(self, rhs: Value, span: SourceSpan) -> Value { - let rty = require_integer!(self, rhs, Type::I1).clone(); - into_first_result!(self.Unary($op, rty, rhs, span)) - } - }; -} - -macro_rules! integer_literal { - ($width:literal) => { - paste::paste! { - unsigned_integer_literal!($width); - signed_integer_literal!($width); - } - }; - - ($width:ident) => { - paste::paste! { - unsigned_integer_literal!($width); - signed_integer_literal!($width); - } - }; - - ($width:literal, $ty:ty) => { - paste::paste! { - unsigned_integer_literal!($width, $ty); - signed_integer_literal!($width, $ty); - } - }; - - ($width:ident, $ty:ty) => { - paste::paste! { - unsigned_integer_literal!($width, $ty); - signed_integer_literal!($width, $ty); - } - }; -} - -macro_rules! unsigned_integer_literal { - ($width:literal) => { - paste::paste! { - unsigned_integer_literal!($width, []); - } - }; - - ($width:ident) => { - paste::paste! { - unsigned_integer_literal!($width, []); - } - }; - - ($width:literal, $ty:ty) => { - paste::paste! { - unsigned_integer_literal!([], [], $ty); - } - }; - - ($width:ident, $ty:ty) => { - paste::paste! { - unsigned_integer_literal!([], [], $ty); - } - }; - - ($name:ident, $suffix:ident, $ty:ty) => { - paste::paste! { - unsigned_integer_literal!($name, $suffix, $ty, []); - } - }; - - ($name:ident, $suffix:ident, $ty:ty, $op:ident) => { - paste::paste! { - fn $name(self, imm: $ty, span: SourceSpan) -> Value { - into_first_result!(self.UnaryImm(Opcode::$op, Type::$suffix, Immediate::$suffix(imm), span)) - } - } - }; -} - -macro_rules! signed_integer_literal { - ($width:literal) => { - paste::paste! { - signed_integer_literal!($width, []); - } - }; - - ($width:ident) => { - paste::paste! { - signed_integer_literal!($width, []); - } - }; - - ($width:literal, $ty:ty) => { - paste::paste! { - signed_integer_literal!([], [], $ty); - } - }; - - ($width:ident, $ty:ty) => { - paste::paste! { - signed_integer_literal!([], [], $ty); - } - }; - - ($name:ident, $suffix:ident, $ty:ty) => { - paste::paste! { - signed_integer_literal!($name, $suffix, $ty, []); - } - }; - - ($name:ident, $suffix:ident, $ty:ty, $op:ident) => { - paste::paste! { - fn $name(self, imm: $ty, span: SourceSpan) -> Value { - into_first_result!(self.UnaryImm(Opcode::$op, Type::$suffix, Immediate::$suffix(imm), span)) - } - } - }; -} - -pub trait InstBuilder<'f>: InstBuilderBase<'f> { - fn assert(mut self, value: Value, span: SourceSpan) -> Inst { - require_felt_sized_integer!(self, value); - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.push(value, pool); - } - self.PrimOp(Opcode::Assert, Type::Unit, vlist, span).0 - } - - fn assert_with_error(mut self, value: Value, code: u32, span: SourceSpan) -> Inst { - require_felt_sized_integer!(self, value); - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.push(value, pool); - } - self.PrimOpImm(Opcode::Assert, Type::Unit, Immediate::U32(code), vlist, span).0 - } - - fn assertz(mut self, value: Value, span: SourceSpan) -> Inst { - require_felt_sized_integer!(self, value); - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.push(value, pool); - } - self.PrimOp(Opcode::Assertz, Type::Unit, vlist, span).0 - } - - fn assertz_with_error(mut self, value: Value, code: u32, span: SourceSpan) -> Inst { - require_felt_sized_integer!(self, value); - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.push(value, pool); - } - self.PrimOpImm(Opcode::Assertz, Type::Unit, Immediate::U32(code), vlist, span).0 - } - - fn assert_eq(mut self, lhs: Value, rhs: Value, span: SourceSpan) -> Inst { - require_matching_operands!(self, lhs, rhs); - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.extend([rhs, lhs], pool); - } - self.PrimOp(Opcode::AssertEq, Type::Unit, vlist, span).0 - } - - fn assert_eq_imm(mut self, lhs: Immediate, rhs: Value, span: SourceSpan) -> Inst { - require_integer!(self, rhs); - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.push(rhs, pool); - } - self.PrimOpImm(Opcode::AssertEq, Type::Unit, lhs, vlist, span).0 - } - - signed_integer_literal!(1, bool); - integer_literal!(8); - integer_literal!(16); - integer_literal!(32); - integer_literal!(64); - integer_literal!(128); - - fn felt(self, i: Felt, span: SourceSpan) -> Value { - into_first_result!(self.UnaryImm(Opcode::ImmFelt, Type::Felt, Immediate::Felt(i), span)) - } - - fn f64(self, f: f64, span: SourceSpan) -> Value { - into_first_result!(self.UnaryImm(Opcode::ImmF64, Type::F64, Immediate::F64(f), span)) - } - - fn character(self, c: char, span: SourceSpan) -> Value { - self.i32((c as u32) as i32, span) - } - - fn alloca(self, ty: Type, span: SourceSpan) -> Value { - into_first_result!(self.PrimOp( - Opcode::Alloca, - Type::Ptr(Box::new(ty)), - ValueList::default(), - span, - )) - } - - /// Grow the global heap by `num_pages` pages, in 64kb units. - /// - /// Returns the previous size (in pages) of the heap, or -1 if the heap could not be grown. - fn mem_grow(mut self, num_pages: Value, span: SourceSpan) -> Value { - require_integer!(self, num_pages, Type::U32); - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.push(num_pages, pool); - } - into_first_result!(self.PrimOp(Opcode::MemGrow, Type::I32, vlist, span)) - } - - /// Return the size of the global heap in pages, where each page is 64kb. - fn mem_size(self, span: SourceSpan) -> Value { - into_first_result!(self.PrimOp(Opcode::MemSize, Type::U32, ValueList::default(), span)) - } - - /// Get a [GlobalValue] which represents the address of a global variable whose symbol is `name` - /// - /// On it's own, this does nothing, you must use the resulting [GlobalValue] with a builder - /// that expects one as an argument, or use `global_value` to obtain a [Value] from it. - fn symbol>(self, name: S, span: SourceSpan) -> GlobalValue { - self.symbol_relative(name, 0, span) - } - - /// Same semantics as `symbol`, but applies a constant offset to the address of the given - /// symbol. - /// - /// If the offset is zero, this is equivalent to `symbol` - fn symbol_relative>( - mut self, - name: S, - offset: i32, - span: SourceSpan, - ) -> GlobalValue { - self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { - name: Ident::new(Symbol::intern(name.as_ref()), span), - offset, - }) - } - - /// Get the address of a global variable whose symbol is `name` - /// - /// The type of the pointer produced is given as `ty`. It is up to the caller - /// to ensure that loading memory from that pointer is valid for the provided - /// type. - fn symbol_addr>(self, name: S, ty: Type, span: SourceSpan) -> Value { - self.symbol_relative_addr(name, 0, ty, span) - } - - /// Same semantics as `symbol_addr`, but applies a constant offset to the address of the given - /// symbol. - /// - /// If the offset is zero, this is equivalent to `symbol_addr` - fn symbol_relative_addr>( - mut self, - name: S, - offset: i32, - ty: Type, - span: SourceSpan, - ) -> Value { - assert!(ty.is_pointer(), "expected pointer type, got '{}'", &ty); - let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { - name: Ident::new(Symbol::intern(name.as_ref()), span), - offset, - }); - into_first_result!(self.Global(gv, ty, span)) - } - - /// Loads a value of type `ty` from the global variable whose symbol is `name`. - /// - /// NOTE: There is no requirement that the memory contents at the given symbol - /// contain a valid value of type `ty`. That is left entirely up the caller to - /// guarantee at a higher level. - fn load_symbol>(self, name: S, ty: Type, span: SourceSpan) -> Value { - self.load_symbol_relative(name, ty, 0, span) - } - - /// Same semantics as `load_symbol`, but a constant offset is applied to the address before - /// issuing the load. - fn load_symbol_relative>( - mut self, - name: S, - ty: Type, - offset: i32, - span: SourceSpan, - ) -> Value { - let base = self.data_flow_graph_mut().create_global_value(GlobalValueData::Symbol { - name: Ident::new(Symbol::intern(name.as_ref()), span), - offset: 0, - }); - self.load_global_relative(base, ty, offset, span) - } - - /// Loads a value of type `ty` from the address represented by `addr` - /// - /// NOTE: There is no requirement that the memory contents at the given symbol - /// contain a valid value of type `ty`. That is left entirely up the caller to - /// guarantee at a higher level. - fn load_global(self, addr: GlobalValue, ty: Type, span: SourceSpan) -> Value { - self.load_global_relative(addr, ty, 0, span) - } - - /// Same semantics as `load_global_relative`, but a constant offset is applied to the address - /// before issuing the load. - fn load_global_relative( - mut self, - base: GlobalValue, - ty: Type, - offset: i32, - span: SourceSpan, - ) -> Value { - if let GlobalValueData::Load { - ty: ref base_ty, .. - } = self.data_flow_graph().global_value(base) - { - // If the base global is a load, the target address cannot be computed until runtime, - // so expand this to the appropriate sequence of instructions to do so in that case - assert!(base_ty.is_pointer(), "expected global value to have pointer type"); - let base_ty = base_ty.clone(); - let base = self.ins().load_global(base, base_ty.clone(), span); - let addr = self.ins().ptrtoint(base, Type::U32, span); - let offset_addr = if offset >= 0 { - self.ins().add_imm_checked(addr, Immediate::U32(offset as u32), span) - } else { - self.ins().sub_imm_checked(addr, Immediate::U32(offset.unsigned_abs()), span) - }; - let ptr = self.ins().inttoptr(offset_addr, base_ty, span); - self.load(ptr, span) - } else { - // The global address can be computed statically - let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::Load { - base, - offset, - ty: ty.clone(), - }); - into_first_result!(self.Global(gv, ty, span)) - } - } - - /// Computes an address relative to the pointer produced by `base`, by applying an offset - /// given by multiplying `offset` * the size in bytes of `unit_ty`. - /// - /// The type of the pointer produced is the same as the type of the pointer given by `base` - /// - /// This is useful in some scenarios where `load_global_relative` is not, namely when computing - /// the effective address of an element of an array stored in a global variable. - fn global_addr_offset( - mut self, - base: GlobalValue, - offset: i32, - unit_ty: Type, - span: SourceSpan, - ) -> Value { - if let GlobalValueData::Load { - ty: ref base_ty, .. - } = self.data_flow_graph().global_value(base) - { - // If the base global is a load, the target address cannot be computed until runtime, - // so expand this to the appropriate sequence of instructions to do so in that case - assert!(base_ty.is_pointer(), "expected global value to have pointer type"); - let base_ty = base_ty.clone(); - let base = self.ins().load_global(base, base_ty.clone(), span); - let addr = self.ins().ptrtoint(base, Type::U32, span); - let unit_size: i32 = unit_ty - .size_in_bytes() - .try_into() - .expect("invalid type: size is larger than 2^32"); - let computed_offset = unit_size * offset; - let offset_addr = if computed_offset >= 0 { - self.ins().add_imm_checked(addr, Immediate::U32(offset as u32), span) - } else { - self.ins().sub_imm_checked(addr, Immediate::U32(offset.unsigned_abs()), span) - }; - let ptr = self.ins().inttoptr(offset_addr, base_ty, span); - self.load(ptr, span) - } else { - // The global address can be computed statically - let gv = self.data_flow_graph_mut().create_global_value(GlobalValueData::IAddImm { - base, - offset, - ty: unit_ty.clone(), - }); - let ty = self.data_flow_graph().global_type(gv); - into_first_result!(self.Global(gv, ty, span)) - } - } - - /// Loads a value of the type pointed to by the given pointer, on to the stack - /// - /// NOTE: This function will panic if `ptr` is not a pointer typed value - fn load(self, addr: Value, span: SourceSpan) -> Value { - let ty = require_pointee!(self, addr).clone(); - let data = Instruction::Load(LoadOp { - op: Opcode::Load, - addr, - ty: ty.clone(), - }); - into_first_result!(self.build(data, Type::Ptr(Box::new(ty)), span)) - } - - /// Loads a value from the given temporary (local variable), of the type associated with that - /// local. - fn load_local(self, local: LocalId, span: SourceSpan) -> Value { - let data = Instruction::LocalVar(LocalVarOp { - op: Opcode::Load, - local, - args: ValueList::default(), - }); - let ty = self.data_flow_graph().local_type(local).clone(); - into_first_result!(self.build(data, Type::Ptr(Box::new(ty)), span)) - } - - /// Stores `value` to the address given by `ptr` - /// - /// NOTE: This function will panic if the pointer and pointee types do not match - fn store(mut self, ptr: Value, value: Value, span: SourceSpan) -> Inst { - let pointee_ty = require_pointee!(self, ptr); - let value_ty = self.data_flow_graph().value_type(value); - assert_eq!(pointee_ty, value_ty, "expected value to be a {}, got {}", pointee_ty, value_ty); - let mut vlist = ValueList::default(); - { - let dfg = self.data_flow_graph_mut(); - vlist.extend([ptr, value], &mut dfg.value_lists); - } - self.PrimOp(Opcode::Store, Type::Unit, vlist, span).0 - } - - /// Stores `value` to the given temporary (local variable). - /// - /// NOTE: This function will panic if the type of `value` does not match the type of the local - /// variable. - fn store_local(mut self, local: LocalId, value: Value, span: SourceSpan) -> Inst { - let mut vlist = ValueList::default(); - { - let dfg = self.data_flow_graph_mut(); - let local_ty = dfg.local_type(local); - let value_ty = dfg.value_type(value); - assert_eq!(local_ty, value_ty, "expected value to be a {}, got {}", local_ty, value_ty); - vlist.push(value, &mut dfg.value_lists); - } - let data = Instruction::LocalVar(LocalVarOp { - op: Opcode::Store, - local, - args: vlist, - }); - self.build(data, Type::Unit, span).0 - } - - /// Writes `count` copies of `value` to memory starting at address `dst`. - /// - /// Each copy of `value` will be written to memory starting at the next aligned address from - /// the previous copy. This instruction will trap if the input address does not meet the - /// minimum alignment requirements of the type. - fn memset(mut self, dst: Value, count: Value, value: Value, span: SourceSpan) -> Inst { - require_integer!(self, count, Type::U32); - let dst_ty = require_pointee!(self, dst); - let value_ty = self.data_flow_graph().value_type(value); - assert_eq!(value_ty, dst_ty, "expected value to be a {}, got {}", dst_ty, value_ty); - let mut vlist = ValueList::default(); - { - let dfg = self.data_flow_graph_mut(); - vlist.extend([dst, count, value], &mut dfg.value_lists); - } - self.PrimOp(Opcode::MemSet, Type::Unit, vlist, span).0 - } - - /// Copies `count` values from the memory at address `src`, to the memory at address `dst`. - /// - /// The unit size for `count` is determined by the `src` pointer type, i.e. a pointer to u8 - /// will copy one `count` bytes, a pointer to u16 will copy `count * 2` bytes, and so on. - /// - /// NOTE: The source and destination pointer types must match, or this function will panic. - fn memcpy(mut self, src: Value, dst: Value, count: Value, span: SourceSpan) -> Inst { - require_integer!(self, count); - let src_ty = require_pointer!(self, src); - let dst_ty = require_pointer!(self, dst); - assert_eq!(src_ty, dst_ty, "the source and destination pointers must be the same type"); - let mut vlist = ValueList::default(); - { - let dfg = self.data_flow_graph_mut(); - vlist.extend([src, dst, count], &mut dfg.value_lists); - } - self.PrimOp(Opcode::MemCpy, Type::Unit, vlist, span).0 - } - - /// This is a cast operation that permits performing arithmetic on pointer values - /// by casting a pointer to a specified integral type. - fn ptrtoint(self, arg: Value, ty: Type, span: SourceSpan) -> Value { - require_pointer!(self, arg); - assert!(ty.is_integer(), "expected integral type, got {}", &ty); - into_first_result!(self.Unary(Opcode::PtrToInt, ty, arg, span)) - } - - /// This is the inverse of `ptrtoint`, used to recover a pointer that was - /// previously cast to an integer type. It may also be used to cast arbitrary - /// integer values to pointers. - /// - /// In both cases, use of the resulting pointer must not violate the semantics - /// of the higher level language being represented in Miden IR. - fn inttoptr(self, arg: Value, ty: Type, span: SourceSpan) -> Value { - require_unsigned_integer!(self, arg); - assert!(ty.is_pointer(), "expected pointer type, got {}", &ty); - into_first_result!(self.Unary(Opcode::IntToPtr, ty, arg, span)) - } - - /// This is an intrinsic which derives a new pointer from an existing pointer to an aggregate. - /// - /// In short, this represents the common need to calculate a new pointer from an existing - /// pointer, but without losing provenance of the original pointer. It is specifically - /// intended for use in obtaining a pointer to an element/field of an array/struct, of the - /// correct type, given a well typed pointer to the aggregate. - /// - /// This function will panic if the pointer is not to an aggregate type - /// - /// The new pointer is derived by statically navigating the structure of the pointee type, using - /// `offsets` to guide the traversal. Initially, the first offset is relative to the original - /// pointer, where `0` refers to the base/first field of the object. The second offset is then - /// relative to the base of the object selected by the first offset, and so on. Offsets must - /// remain in bounds, any attempt to index outside a type's boundaries will result in a - /// panic. - fn getelementptr(mut self, ptr: Value, mut indices: &[usize], span: SourceSpan) -> Value { - let mut ty = require_pointee!(self, ptr); - assert!(!indices.is_empty(), "getelementptr requires at least one index"); - - // Calculate the offset in bytes from `ptr` to get the element pointer - let mut offset = 0; - while let Some((index, rest)) = indices.split_first().map(|(i, rs)| (*i, rs)) { - indices = rest; - match ty { - Type::Array(ref element_ty, len) => { - assert!( - index < *len, - "invalid getelementptr: index of {} is out of bounds for {}", - index, - ty - ); - let element_size = element_ty.size_in_bytes(); - let min_align = element_ty.min_alignment(); - let padded_element_size = element_size.align_up(min_align); - ty = element_ty; - offset += padded_element_size * index; - } - Type::Struct(ref struct_ty) => { - assert!( - index < struct_ty.len(), - "invalid getelementptr: index of {} is out of bounds for {}", - index, - ty - ); - let field = struct_ty.get(index); - offset += field.offset as usize; - ty = &field.ty; - } - other => panic!("invalid getelementptr: cannot index values of type {}", other), - } - } - - // Emit the instruction sequence for offsetting the pointer - let ty = Type::Ptr(Box::new(ty.clone())); - // Cast the pointer to an integer - let addr = self.ins().ptrtoint(ptr, Type::U32, span); - // Add the element offset to the pointer - let offset: u32 = offset.try_into().expect( - "invalid getelementptr type: computed offset cannot possibly fit in linear memory", - ); - let new_addr = self.ins().add_imm_checked(addr, Immediate::U32(offset), span); - // Cast back to a pointer to the selected element type - self.inttoptr(new_addr, ty, span) - } - - /// Cast `arg` to a value of type `ty` - /// - /// NOTE: This is only supported for integral types currently, and the types must be of the same - /// size in bytes, i.e. i32 -> u32 or vice versa. - /// - /// The intention of bitcasts is to reinterpret a value with different semantics, with no - /// validation that is typically implied by casting from one type to another. - fn bitcast(self, arg: Value, ty: Type, span: SourceSpan) -> Value { - let arg_ty = self.data_flow_graph().value_type(arg); - let both_numeric = arg_ty.is_numeric() && ty.is_numeric(); - assert!( - both_numeric, - "invalid bitcast, expected source and target types to be of the same type: value is \ - of type {}, and target type is {}", - &arg_ty, &ty - ); - into_first_result!(self.Unary(Opcode::Bitcast, ty, arg, span)) - } - - /// Cast `arg` to a value of type `ty` - /// - /// NOTE: This is only valid for numeric to numeric, or pointer to pointer casts. - /// For numeric to pointer, or pointer to numeric casts, use `inttoptr` and `ptrtoint` - /// respectively. - fn cast(self, arg: Value, ty: Type, span: SourceSpan) -> Value { - let arg_ty = self.data_flow_graph().value_type(arg); - let both_numeric = arg_ty.is_numeric() && ty.is_numeric(); - let both_pointers = arg_ty.is_pointer() && ty.is_pointer(); - let kind_matches = both_numeric || both_pointers; - assert!( - kind_matches, - "invalid cast, expected source and target types to be of the same kind, where a kind \ - is either numeric or pointer: value is of type {}, and target type is {}", - &arg_ty, &ty - ); - into_first_result!(self.Unary(Opcode::Cast, ty, arg, span)) - } - - /// Truncates an integral value as necessary to fit in `ty`. - /// - /// NOTE: Truncating a value into a larger type has undefined behavior, it is - /// equivalent to extending a value without doing anything with the new high-order - /// bits of the resulting value. - fn trunc(self, arg: Value, ty: Type, span: SourceSpan) -> Value { - require_integer!(self, arg); - assert!(ty.is_integer(), "expected integer type, got {}", &ty); - into_first_result!(self.Unary(Opcode::Trunc, ty, arg, span)) - } - - /// Extends an integer into a larger integeral type, by zero-extending the value, - /// i.e. the new high-order bits of the resulting value will be all zero. - /// - /// NOTE: This function will panic if `ty` is smaller than `arg`. - /// - /// If `arg` is the same type as `ty`, `arg` is returned as-is - fn zext(self, arg: Value, ty: Type, span: SourceSpan) -> Value { - let arg_ty = require_integer!(self, arg); - assert!(ty.is_integer(), "expected integer type, got {}", &ty); - if arg_ty == &ty { - return arg; - } - assert!( - arg_ty.size_in_bits() <= ty.size_in_bits(), - "invalid extension: target type ({:?}) is smaller than the argument type ({:?})", - &ty, - &arg_ty - ); - into_first_result!(self.Unary(Opcode::Zext, ty, arg, span)) - } - - /// Extends an integer into a larger integeral type, by sign-extending the value, - /// i.e. the new high-order bits of the resulting value will all match the sign bit. - /// - /// NOTE: This function will panic if `ty` is smaller than `arg`. - /// - /// If `arg` is the same type as `ty`, `arg` is returned as-is - fn sext(self, arg: Value, ty: Type, span: SourceSpan) -> Value { - let arg_ty = require_integer!(self, arg); - assert!(ty.is_integer(), "expected integer type, got {}", &ty); - if arg_ty == &ty { - return arg; - } - assert!( - arg_ty.size_in_bits() <= ty.size_in_bits(), - "invalid extension: target type ({:?}) is smaller than the argument type ({:?})", - &ty, - &arg_ty - ); - into_first_result!(self.Unary(Opcode::Sext, ty, arg, span)) - } - - binary_int_op_with_overflow!(add, Opcode::Add); - binary_int_op_with_overflow!(sub, Opcode::Sub); - binary_int_op_with_overflow!(mul, Opcode::Mul); - checked_binary_int_op!(div, Opcode::Div); - binary_int_op!(min, Opcode::Min); - binary_int_op!(max, Opcode::Max); - checked_binary_int_op!(r#mod, Opcode::Mod); - checked_binary_int_op!(divmod, Opcode::DivMod); - binary_int_op!(exp, Opcode::Exp); - binary_boolean_op!(and, Opcode::And); - binary_int_op!(band, Opcode::Band); - binary_boolean_op!(or, Opcode::Or); - binary_int_op!(bor, Opcode::Bor); - binary_boolean_op!(xor, Opcode::Xor); - binary_int_op!(bxor, Opcode::Bxor); - unary_int_op!(neg, Opcode::Neg); - unary_int_op!(inv, Opcode::Inv); - unary_int_op_with_overflow!(incr, Opcode::Incr); - unary_int_op!(ilog2, Opcode::Ilog2); - unary_int_op!(pow2, Opcode::Pow2); - unary_boolean_op!(not, Opcode::Not); - unary_int_op!(bnot, Opcode::Bnot); - unary_int_op!(popcnt, Opcode::Popcnt); - unary_int_op!(clz, Opcode::Clz); - unary_int_op!(ctz, Opcode::Ctz); - unary_int_op!(clo, Opcode::Clo); - unary_int_op!(cto, Opcode::Cto); - - fn rotl(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - require_integer!(self, rhs, Type::U32); - into_first_result!(self.BinaryWithOverflow( - Opcode::Rotl, - lty, - lhs, - rhs, - Overflow::Wrapping, - span - )) - } - - fn rotl_imm(self, lhs: Value, shift: u32, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - into_first_result!(self.BinaryImmWithOverflow( - Opcode::Rotl, - lty, - lhs, - shift.into(), - Overflow::Wrapping, - span - )) - } - - fn rotr(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - require_integer!(self, rhs, Type::U32); - into_first_result!(self.BinaryWithOverflow( - Opcode::Rotr, - lty, - lhs, - rhs, - Overflow::Wrapping, - span - )) - } - - fn rotr_imm(self, lhs: Value, shift: u32, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - into_first_result!(self.BinaryImmWithOverflow( - Opcode::Rotr, - lty, - lhs, - shift.into(), - Overflow::Wrapping, - span - )) - } - - fn shl(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - require_integer!(self, rhs, Type::U32); - into_first_result!(self.BinaryWithOverflow( - Opcode::Shl, - lty, - lhs, - rhs, - Overflow::Wrapping, - span - )) - } - - fn shl_imm(self, lhs: Value, shift: u32, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - into_first_result!(self.BinaryImmWithOverflow( - Opcode::Shl, - lty, - lhs, - shift.into(), - Overflow::Wrapping, - span - )) - } - - fn shr(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - require_integer!(self, rhs, Type::U32); - into_first_result!(self.BinaryWithOverflow( - Opcode::Shr, - lty, - lhs, - rhs, - Overflow::Wrapping, - span - )) - } - - fn shr_imm(self, lhs: Value, shift: u32, span: SourceSpan) -> Value { - let lty = require_integer!(self, lhs).clone(); - into_first_result!(self.BinaryImmWithOverflow( - Opcode::Shr, - lty, - lhs, - shift.into(), - Overflow::Wrapping, - span - )) - } - - fn eq(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - into_first_result!(self.Binary(Opcode::Eq, Type::I1, lhs, rhs, span)) - } - - fn eq_imm(self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - assert_eq!( - lty, - imm.ty(), - "expected immediate to be the same type as non-immediate operand", - ); - into_first_result!(self.BinaryImm(Opcode::Eq, Type::I1, lhs, imm, span)) - } - - fn neq(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - into_first_result!(self.Binary(Opcode::Neq, Type::I1, lhs, rhs, span)) - } - - fn neq_imm(self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - assert_eq!( - lty, - imm.ty(), - "expected immediate to be the same type as non-immediate operand", - ); - into_first_result!(self.BinaryImm(Opcode::Neq, Type::I1, lhs, imm, span)) - } - - fn gt(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - require_integer!(self, lhs); - require_integer!(self, rhs); - into_first_result!(self.Binary(Opcode::Gt, Type::I1, lhs, rhs, span)) - } - - fn gt_imm(self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - assert_eq!( - lty, - imm.ty(), - "expected immediate to be the same type as non-immediate operand", - ); - into_first_result!(self.BinaryImm(Opcode::Gt, Type::I1, lhs, imm, span)) - } - - fn gte(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - require_integer!(self, lhs); - require_integer!(self, rhs); - into_first_result!(self.Binary(Opcode::Gte, Type::I1, lhs, rhs, span)) - } - - fn gte_imm(self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - assert_eq!( - lty, - imm.ty(), - "expected immediate to be the same type as non-immediate operand", - ); - into_first_result!(self.BinaryImm(Opcode::Gte, Type::I1, lhs, imm, span)) - } - - fn lt(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - require_integer!(self, lhs); - require_integer!(self, rhs); - into_first_result!(self.Binary(Opcode::Lt, Type::I1, lhs, rhs, span)) - } - - fn lt_imm(self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - assert_eq!( - lty, - imm.ty(), - "expected immediate to be the same type as non-immediate operand", - ); - into_first_result!(self.BinaryImm(Opcode::Lt, Type::I1, lhs, imm, span)) - } - - fn lte(self, lhs: Value, rhs: Value, span: SourceSpan) -> Value { - require_integer!(self, lhs); - require_integer!(self, rhs); - into_first_result!(self.Binary(Opcode::Lte, Type::I1, lhs, rhs, span)) - } - - fn lte_imm(self, lhs: Value, imm: Immediate, span: SourceSpan) -> Value { - let lty = assert_integer_operands!(self, lhs); - assert_eq!( - lty, - imm.ty(), - "expected immediate to be the same type as non-immediate operand", - ); - into_first_result!(self.BinaryImm(Opcode::Lte, Type::I1, lhs, imm, span)) - } - - #[allow(clippy::wrong_self_convention)] - fn is_odd(self, value: Value, span: SourceSpan) -> Value { - require_integer!(self, value); - into_first_result!(self.Unary(Opcode::IsOdd, Type::I1, value, span)) - } - - fn call(mut self, callee: FunctionIdent, args: &[Value], span: SourceSpan) -> Inst { - let mut vlist = ValueList::default(); - { - let dfg = self.data_flow_graph_mut(); - assert!( - dfg.get_import(&callee).is_some(), - "must import callee ({}) before calling it", - &callee - ); - vlist.extend(args.iter().copied(), &mut dfg.value_lists); - } - self.Call(Opcode::Call, callee, vlist, span).0 - } - - fn syscall(mut self, callee: FunctionIdent, args: &[Value], span: SourceSpan) -> Inst { - let mut vlist = ValueList::default(); - { - let dfg = self.data_flow_graph_mut(); - assert!( - dfg.get_import(&callee).is_some(), - "must import callee ({}) before calling it", - &callee - ); - vlist.extend(args.iter().copied(), &mut dfg.value_lists); - } - self.Call(Opcode::Syscall, callee, vlist, span).0 - } - - fn select(mut self, cond: Value, a: Value, b: Value, span: SourceSpan) -> Value { - let mut vlist = ValueList::default(); - let ty = require_matching_operands!(self, a, b).clone(); - { - let dfg = self.data_flow_graph_mut(); - assert_eq!( - dfg.value_type(cond), - &Type::I1, - "select expect the type of the condition to be i1" - ); - vlist.extend([cond, a, b], &mut dfg.value_lists); - } - into_first_result!(self.PrimOp(Opcode::Select, ty, vlist, span)) - } - - fn br(mut self, block: Block, args: &[Value], span: SourceSpan) -> Inst { - let mut vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.extend(args.iter().copied(), pool); - } - self.Br(Opcode::Br, Type::Unit, block, vlist, span).0 - } - - fn cond_br( - mut self, - cond: Value, - then_dest: Block, - then_args: &[Value], - else_dest: Block, - else_args: &[Value], - span: SourceSpan, - ) -> Inst { - require_integer!(self, cond, Type::I1); - let mut then_vlist = ValueList::default(); - let mut else_vlist = ValueList::default(); - { - let pool = &mut self.data_flow_graph_mut().value_lists; - then_vlist.extend(then_args.iter().copied(), pool); - else_vlist.extend(else_args.iter().copied(), pool); - } - self.CondBr(cond, then_dest, then_vlist, else_dest, else_vlist, span).0 - } - - fn switch(self, arg: Value, span: SourceSpan) -> SwitchBuilder<'f, Self> { - require_integer!(self, arg, Type::U32); - SwitchBuilder::new(self, arg, span) - } - - fn ret(mut self, returning: Option, span: SourceSpan) -> Inst { - let mut vlist = ValueList::default(); - if let Some(value) = returning { - let pool = &mut self.data_flow_graph_mut().value_lists; - vlist.push(value, pool); - } - self.Ret(vlist, span).0 - } - - fn ret_imm(self, arg: Immediate, span: SourceSpan) -> Inst { - let data = Instruction::RetImm(RetImm { - op: Opcode::Ret, - arg, - }); - self.build(data, Type::Unit, span).0 - } - - fn unreachable(self, span: SourceSpan) -> Inst { - let data = Instruction::PrimOp(PrimOp { - op: Opcode::Unreachable, - args: ValueList::default(), - }); - self.build(data, Type::Never, span).0 - } - - fn inline_asm( - self, - args: &[Value], - results: impl IntoIterator, - span: SourceSpan, - ) -> MasmBuilder { - MasmBuilder::new(self, args, results.into_iter().collect(), span) - } - - #[allow(non_snake_case)] - fn CondBr( - self, - cond: Value, - then_dest: Block, - then_args: ValueList, - else_dest: Block, - else_args: ValueList, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::CondBr(CondBr { - op: Opcode::CondBr, - cond, - then_dest: Successor { - destination: then_dest, - args: then_args, - }, - else_dest: Successor { - destination: else_dest, - args: else_args, - }, - }); - self.build(data, Type::Unit, span) - } - - #[allow(non_snake_case)] - fn Br( - self, - op: Opcode, - ty: Type, - destination: Block, - args: ValueList, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::Br(Br { - op, - successor: Successor { destination, args }, - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn Switch( - self, - arg: Value, - arms: Vec, - default: Successor, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::Switch(Switch { - op: Opcode::Switch, - arg, - arms, - default, - }); - self.build(data, Type::Unit, span) - } - - #[allow(non_snake_case)] - fn Ret(self, args: ValueList, span: SourceSpan) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::Ret(Ret { - op: Opcode::Ret, - args, - }); - self.build(data, Type::Unit, span) - } - - #[allow(non_snake_case)] - fn Call( - self, - op: Opcode, - callee: FunctionIdent, - args: ValueList, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::Call(Call { op, callee, args }); - self.build(data, Type::Unit, span) - } - - #[allow(non_snake_case)] - fn Binary( - self, - op: Opcode, - ty: Type, - lhs: Value, - rhs: Value, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::BinaryOp(BinaryOp { - op, - overflow: None, - // We place arguments in stack order for more efficient codegen - args: [rhs, lhs], - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn BinaryWithOverflow( - self, - op: Opcode, - ty: Type, - lhs: Value, - rhs: Value, - overflow: Overflow, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::BinaryOp(BinaryOp { - op, - overflow: Some(overflow), - // We place arguments in stack order for more efficient codegen - args: [rhs, lhs], - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn BinaryImm( - self, - op: Opcode, - ty: Type, - arg: Value, - imm: Immediate, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::BinaryOpImm(BinaryOpImm { - op, - overflow: None, - arg, - imm, - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn BinaryImmWithOverflow( - self, - op: Opcode, - ty: Type, - arg: Value, - imm: Immediate, - overflow: Overflow, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::BinaryOpImm(BinaryOpImm { - op, - overflow: Some(overflow), - arg, - imm, - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn Unary( - self, - op: Opcode, - ty: Type, - arg: Value, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::UnaryOp(UnaryOp { - op, - overflow: None, - arg, - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn UnaryWithOverflow( - self, - op: Opcode, - ty: Type, - arg: Value, - overflow: Overflow, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::UnaryOp(UnaryOp { - op, - overflow: Some(overflow), - arg, - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn UnaryImm( - self, - op: Opcode, - ty: Type, - imm: Immediate, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::UnaryOpImm(UnaryOpImm { - op, - overflow: None, - imm, - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn UnaryImmWithOverflow( - self, - op: Opcode, - ty: Type, - imm: Immediate, - overflow: Overflow, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::UnaryOpImm(UnaryOpImm { - op, - overflow: Some(overflow), - imm, - }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn Test( - self, - op: Opcode, - ret: Type, - arg: Value, - ty: Type, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::Test(Test { op, arg, ty }); - self.build(data, ret, span) - } - - #[allow(non_snake_case)] - fn PrimOp( - self, - op: Opcode, - ty: Type, - args: ValueList, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::PrimOp(PrimOp { op, args }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn PrimOpImm( - self, - op: Opcode, - ty: Type, - imm: Immediate, - args: ValueList, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::PrimOpImm(PrimOpImm { op, imm, args }); - self.build(data, ty, span) - } - - #[allow(non_snake_case)] - fn Global( - self, - global: GlobalValue, - ty: Type, - span: SourceSpan, - ) -> (Inst, &'f mut DataFlowGraph) { - let data = Instruction::GlobalValue(GlobalValueOp { - op: Opcode::GlobalValue, - global, - }); - self.build(data, ty, span) - } -} - -impl<'f, T: InstBuilderBase<'f>> InstBuilder<'f> for T {} - -/// An instruction builder for `switch`, to ensure it is validated during construction -pub struct SwitchBuilder<'f, T: InstBuilder<'f>> { - builder: T, - arg: Value, - span: SourceSpan, - arms: Vec, - _marker: core::marker::PhantomData<&'f Function>, -} -impl<'f, T: InstBuilder<'f>> SwitchBuilder<'f, T> { - fn new(builder: T, arg: Value, span: SourceSpan) -> Self { - Self { - builder, - arg, - span, - arms: Default::default(), - _marker: core::marker::PhantomData, - } - } - - /// Specify to what block a specific discriminant value should be dispatched - pub fn case(mut self, discriminant: u32, target: Block, args: &[Value]) -> Self { - assert_eq!( - self.arms - .iter() - .find(|arm| arm.value == discriminant) - .map(|arm| arm.successor.destination), - None, - "duplicate switch case value '{discriminant}': already matched" - ); - let mut vlist = ValueList::default(); - { - let pool = &mut self.builder.data_flow_graph_mut().value_lists; - vlist.extend(args.iter().copied(), pool); - } - let arm = SwitchArm { - value: discriminant, - successor: Successor { - destination: target, - args: vlist, - }, - }; - self.arms.push(arm); - self - } - - /// Build the `switch` by specifying the fallback destination if none of the arms match - pub fn or_else(mut self, target: Block, args: &[Value]) -> Inst { - let mut vlist = ValueList::default(); - { - let pool = &mut self.builder.data_flow_graph_mut().value_lists; - vlist.extend(args.iter().copied(), pool); - } - let fallback = Successor { - destination: target, - args: vlist, - }; - self.builder.Switch(self.arg, self.arms, fallback, self.span).0 - } -} diff --git a/hir/src/component/interface.rs b/hir/src/component/interface.rs deleted file mode 100644 index 3a3f3fbb2..000000000 --- a/hir/src/component/interface.rs +++ /dev/null @@ -1,80 +0,0 @@ -use core::fmt; - -use midenc_hir_symbol::Symbol; - -use crate::formatter::PrettyPrint; - -/// A fully-qualified identifier for the interface being imported, e.g. -/// `namespace::package/interface@version` -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InterfaceIdent { - /// A fully-qualified identifier for the interface being imported, e.g. - /// `namespace::package/interface@version` - pub full_name: Symbol, -} - -impl InterfaceIdent { - /// Create a new [InterfaceIdent] from a fully-qualified interface identifier, e.g. - /// `namespace::package/interface@version` - pub fn from_full_ident(full_ident: String) -> Self { - Self { - full_name: Symbol::intern(full_ident), - } - } -} - -impl fmt::Debug for InterfaceIdent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - fmt::Display::fmt(&self.full_name, f) - } -} -impl fmt::Display for InterfaceIdent { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\"{}\"", self.full_name.as_str().escape_default()) - } -} - -/// An identifier for a function in an interface -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct InterfaceFunctionIdent { - /// An interface identifier for the interface being imported (e.g. - /// `namespace::package/interface@version`) - pub interface: InterfaceIdent, - /// The name of the function from the interface - pub function: Symbol, -} - -impl InterfaceFunctionIdent { - /// Create a new [InterfaceFunctionIdent] from a fully-qualified interface - /// identifier(e.g. `namespace::package/interface@version`) and a function name - pub fn from_full(interface: String, function: String) -> Self { - Self { - interface: InterfaceIdent::from_full_ident(interface.to_string()), - function: Symbol::intern(function), - } - } -} - -impl fmt::Debug for InterfaceFunctionIdent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} @ {:?}", &self.function, &self.interface) - } -} -impl fmt::Display for InterfaceFunctionIdent { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} -impl PrettyPrint for InterfaceFunctionIdent { - fn render(&self) -> crate::formatter::Document { - use crate::formatter::*; - - flatten( - const_text("(") - + display(self.interface) - + const_text(" ") - + text(format!("#{}", self.function)) - + const_text(")"), - ) - } -} diff --git a/hir/src/component/mod.rs b/hir/src/component/mod.rs deleted file mode 100644 index 81f9df164..000000000 --- a/hir/src/component/mod.rs +++ /dev/null @@ -1,434 +0,0 @@ -use alloc::collections::BTreeMap; -use core::ops::{Deref, DerefMut}; - -use indexmap::IndexMap; -use miden_core::crypto::hash::RpoDigest; - -use self::formatter::PrettyPrint; -use crate::{ - diagnostics::{DiagnosticsHandler, Report}, - *, -}; - -mod interface; - -pub use interface::*; - -/// Canonical ABI options associated with a lifted or lowered function. -#[derive(Debug, Clone)] -pub struct CanonicalOptions { - /// The realloc function used by these options, if specified. - pub realloc: Option, - /// The post-return function used by these options, if specified. - pub post_return: Option, -} - -/// A component import translated from a Wasm component import that is following -/// the Wasm Component Model Canonical ABI. -#[derive(Debug, Clone)] -pub struct CanonAbiImport { - /// The interfact function name that is being imported - pub interface_function: InterfaceFunctionIdent, - /// The component(lifted) type of the imported function - pub function_ty: FunctionType, - /// The MAST root hash of the function to be used in codegen - pub digest: RpoDigest, - /// Any options associated with this import - pub options: CanonicalOptions, -} - -/// A Miden (sdklib, tx kernel) function import that is following the Miden ABI. -#[derive(Debug, Clone)] -pub struct MidenAbiImport { - /// The Miden function type as it is defined in the MASM - pub function_ty: FunctionType, - /// The MAST root hash of the function to be used in codegen - pub digest: RpoDigest, -} - -/// A component import -#[derive(Debug, Clone)] -pub enum ComponentImport { - /// A Wasm import that is following the Wasm Component Model Canonical ABI - CanonAbiImport(CanonAbiImport), - /// A Miden import that is following the Miden ABI - MidenAbiImport(MidenAbiImport), -} - -impl ComponentImport { - pub fn digest(&self) -> RpoDigest { - match self { - ComponentImport::CanonAbiImport(import) => import.digest, - ComponentImport::MidenAbiImport(import) => import.digest, - } - } - - pub fn unwrap_canon_abi_import(&self) -> &CanonAbiImport { - match self { - ComponentImport::CanonAbiImport(import) => import, - _ => panic!("Expected CanonAbiImport"), - } - } -} - -impl fmt::Display for ComponentImport { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} - -impl formatter::PrettyPrint for ComponentImport { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - let function_ty_str = match self { - ComponentImport::CanonAbiImport(import) => import.function_ty.to_string(), - ComponentImport::MidenAbiImport(import) => import.function_ty.to_string(), - }; - let name = match self { - ComponentImport::CanonAbiImport(import) => { - format!("{} ", import.interface_function) - } - ComponentImport::MidenAbiImport(_import) => "".to_string(), - }; - const_text("(") - + text(name) - + const_text("(") - + const_text("digest") - + const_text(" ") - + display(self.digest()) - + const_text(")") - + const_text(" ") - + const_text("(") - + const_text("type") - + const_text(" ") - + text(function_ty_str) - + const_text(")") - + const_text(")") - } -} - -/// The name of a exported function -#[derive( - Debug, Clone, Ord, PartialEq, PartialOrd, Eq, Hash, derive_more::From, derive_more::Into, -)] -pub struct FunctionExportName(Symbol); - -/// A component export -#[derive(Debug)] -pub struct ComponentExport { - /// The module function that is being exported - pub function: FunctionIdent, - /// The component(lifted) type of the exported function - pub function_ty: FunctionType, - /// Any options associated with this export - pub options: CanonicalOptions, -} - -/// A [Component] is a collection of [Module]s that are being compiled together as a package and -/// have exports/imports. -#[derive(Default)] -pub struct Component { - /// This tree stores all of the modules. - /// The modules should be stored in a topological order - modules: IndexMap>, - - /// A list of this component's imports, indexed by function identifier - imports: BTreeMap, - - /// A list of this component's exports, indexed by export name - exports: BTreeMap, -} - -impl Component { - /// Create a new, empty [Component]. - #[inline(always)] - pub fn new() -> Self { - Self::default() - } - - /// Return a reference to the module table for this program - pub fn modules(&self) -> &IndexMap> { - &self.modules - } - - pub fn to_modules(mut self) -> Vec<(Ident, Box)> { - self.modules.drain(..).collect() - } - - /// Return a mutable reference to the module table for this program - pub fn modules_mut(&mut self) -> &mut IndexMap> { - &mut self.modules - } - - /// Returns true if `name` is defined in this program. - pub fn contains(&self, name: Ident) -> bool { - !self.modules.contains_key(&name) - } - - /// Look up the signature of a function in this program by `id` - pub fn signature(&self, id: &FunctionIdent) -> Option<&Signature> { - let module = self.modules.get(&id.module)?; - module.function(id.function).map(|f| &f.signature) - } - - pub fn imports(&self) -> &BTreeMap { - &self.imports - } - - pub fn exports(&self) -> &BTreeMap { - &self.exports - } - - /// Get the first module in this component - pub fn first_module(&self) -> &Module { - self.modules - .values() - .next() - .expect("Expected at least one module in the component") - } - - /// Extracts the single module consuming this component, panicking if there is not exactly one. - pub fn unwrap_one_module(self) -> Box { - assert_eq!(self.modules.len(), 1, "Expected exactly one module in the component"); - self.to_modules().drain(..).next().unwrap().1 - } -} - -impl fmt::Display for Component { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} - -impl formatter::PrettyPrint for Component { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let imports = self - .imports - .iter() - .map(|(id, import)| { - const_text("(") - + const_text("lower") - + const_text(" ") - + import.render() - + const_text(" ") - + id.render() - + const_text(")") - }) - .reduce(|acc, doc| acc + nl() + doc) - .map(|doc| const_text(";; Component Imports") + nl() + doc) - .unwrap_or(Document::Empty); - - let modules = self - .modules - .values() - .map(PrettyPrint::render) - .reduce(|acc, doc| acc + nl() + doc) - .map(|doc| const_text(";; Modules") + nl() + doc) - .unwrap_or(Document::Empty); - - let body = vec![imports, modules].into_iter().filter(|section| !section.is_empty()).fold( - nl(), - |a, b| { - if matches!(a, Document::Newline) { - indent(4, a + b) - } else { - a + nl() + indent(4, nl() + b) - } - }, - ); - - let header = const_text("(") + const_text("component") + const_text(" "); - - if body.is_empty() { - header + const_text(")") + nl() - } else { - header + body + nl() + const_text(")") + nl() - } - } -} - -/// This struct provides an ergonomic way to construct a [Component] in an imperative fashion. -/// -/// Simply create the builder, add/build one or more modules, then call `link` to obtain a -/// [Component]. -pub struct ComponentBuilder<'a> { - modules: IndexMap>, - imports: BTreeMap, - exports: BTreeMap, - entry: Option, - diagnostics: &'a DiagnosticsHandler, -} -impl<'a> ComponentBuilder<'a> { - pub fn new(diagnostics: &'a DiagnosticsHandler) -> Self { - Self { - modules: Default::default(), - entry: None, - diagnostics, - exports: Default::default(), - imports: Default::default(), - } - } - - /// Set the entrypoint for the [Component] being built. - #[inline] - pub fn with_entrypoint(mut self, id: FunctionIdent) -> Self { - self.entry = Some(id); - self - } - - /// Add `module` to the set of modules to link into the final [Component] - /// - /// Unlike `add_module`, this function consumes the current builder state - /// and returns a new one, to allow for chaining builder calls together. - /// - /// Returns `Err` if a module with the same name already exists - pub fn with_module(mut self, module: Box) -> Result { - self.add_module(module).map(|_| self) - } - - /// Add `module` to the set of modules to link into the final [Component] - /// - /// Returns `Err` if a module with the same name already exists - pub fn add_module(&mut self, module: Box) -> Result<(), ModuleConflictError> { - let module_name = module.name; - if self.modules.contains_key(&module_name) { - return Err(ModuleConflictError::new(module_name)); - } - - self.modules.insert(module_name, module); - - Ok(()) - } - - /// Start building a [Module] with the given name. - /// - /// When the builder is done, the resulting [Module] will be inserted - /// into the set of modules to be linked into the final [Component]. - pub fn module>(&mut self, name: S) -> ComponentModuleBuilder<'_, 'a> { - let name = name.into(); - let module = match self.modules.shift_remove(&name) { - None => Box::new(Module::new(name)), - Some(module) => module, - }; - ComponentModuleBuilder { - cb: self, - mb: ModuleBuilder::from(module), - } - } - - pub fn add_import(&mut self, function_id: FunctionIdent, import: ComponentImport) { - self.imports.insert(function_id, import); - } - - pub fn add_export(&mut self, name: FunctionExportName, export: ComponentExport) { - self.exports.insert(name, export); - } - - pub fn build(self) -> Component { - assert!(!self.modules.is_empty(), "Cannot build a component with no modules"); - Component { - modules: self.modules, - imports: self.imports, - exports: self.exports, - } - } -} - -/// This is used to build a [Module] from a [ComponentBuilder]. -/// -/// It is basically just a wrapper around [ModuleBuilder], but overrides two things: -/// -/// * `build` will add the module to the [ComponentBuilder] directly, rather than returning it -/// * `function` will delegate to [ComponentFunctionBuilder] which plays a similar role to this -/// struct, but for [ModuleFunctionBuilder]. -pub struct ComponentModuleBuilder<'a, 'b: 'a> { - cb: &'a mut ComponentBuilder<'b>, - mb: ModuleBuilder, -} -impl<'a, 'b: 'a> ComponentModuleBuilder<'a, 'b> { - /// Start building a [Function] wwith the given name and signature. - pub fn function<'c, 'd: 'c, S: Into>( - &'d mut self, - name: S, - signature: Signature, - ) -> Result, SymbolConflictError> { - Ok(ComponentFunctionBuilder { - diagnostics: self.cb.diagnostics, - fb: self.mb.function(name, signature)?, - }) - } - - /// Build the current [Module], adding it to the [ComponentBuilder]. - /// - /// Returns `err` if a module with that name already exists. - pub fn build(self) -> Result<(), ModuleConflictError> { - let pb = self.cb; - let mb = self.mb; - - pb.add_module(mb.build())?; - Ok(()) - } -} -impl<'a, 'b: 'a> Deref for ComponentModuleBuilder<'a, 'b> { - type Target = ModuleBuilder; - - fn deref(&self) -> &Self::Target { - &self.mb - } -} -impl<'a, 'b: 'a> DerefMut for ComponentModuleBuilder<'a, 'b> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.mb - } -} -impl<'a, 'b: 'a> AsRef for ComponentModuleBuilder<'a, 'b> { - fn as_ref(&self) -> &ModuleBuilder { - &self.mb - } -} -impl<'a, 'b: 'a> AsMut for ComponentModuleBuilder<'a, 'b> { - fn as_mut(&mut self) -> &mut ModuleBuilder { - &mut self.mb - } -} - -/// This is used to build a [Function] from a [ComponentModuleBuilder]. -/// -/// It is basically just a wrapper around [ModuleFunctionBuilder], but overrides -/// `build` to use the [DiagnosticsHandler] of the parent -/// [ComponentBuilder]. -pub struct ComponentFunctionBuilder<'a, 'b: 'a> { - diagnostics: &'b DiagnosticsHandler, - fb: ModuleFunctionBuilder<'a>, -} -impl<'a, 'b: 'a> ComponentFunctionBuilder<'a, 'b> { - /// Build the current function - pub fn build(self) -> Result { - let diagnostics = self.diagnostics; - self.fb.build(diagnostics) - } -} -impl<'a, 'b: 'a> Deref for ComponentFunctionBuilder<'a, 'b> { - type Target = ModuleFunctionBuilder<'a>; - - fn deref(&self) -> &Self::Target { - &self.fb - } -} -impl<'a, 'b: 'a> DerefMut for ComponentFunctionBuilder<'a, 'b> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.fb - } -} -impl<'a, 'b: 'a> AsRef> for ComponentFunctionBuilder<'a, 'b> { - fn as_ref(&self) -> &ModuleFunctionBuilder<'a> { - &self.fb - } -} -impl<'a, 'b: 'a> AsMut> for ComponentFunctionBuilder<'a, 'b> { - fn as_mut(&mut self) -> &mut ModuleFunctionBuilder<'a> { - &mut self.fb - } -} diff --git a/hir/src/constants.rs b/hir/src/constants.rs index 3ecad2c36..d1f20888f 100644 --- a/hir/src/constants.rs +++ b/hir/src/constants.rs @@ -1,6 +1,7 @@ -use std::{collections::BTreeMap, fmt, str::FromStr, sync::Arc}; +use alloc::{collections::BTreeMap, sync::Arc, vec, vec::Vec}; +use core::{fmt, str::FromStr}; -use cranelift_entity::{entity_impl, EntityRef}; +use crate::define_attr_type; pub trait IntoBytes { fn into_bytes(self) -> Vec; @@ -26,15 +27,47 @@ impl IntoBytes for i16 { /// A handle to a constant #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Constant(u32); -entity_impl!(Constant, "const"); +pub struct ConstantId(u32); +impl ConstantId { + pub fn new(id: usize) -> Self { + assert!(id <= u32::MAX as usize); + Self(id as u32) + } + + #[inline] + pub const fn as_usize(&self) -> usize { + self.0 as usize + } +} +impl fmt::Debug for ConstantId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Display::fmt(self, f) + } +} +impl fmt::Display for ConstantId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "const{}", &self.0) + } +} + +define_attr_type!(ConstantId); + +impl crate::AttrPrinter for ConstantId { + fn print( + &self, + _flags: &crate::OpPrintingFlags, + context: &crate::Context, + ) -> crate::formatter::Document { + let data = context.get_constant(*self); + crate::formatter::display(data) + } +} /// This type represents the raw data of a constant. /// /// The data is expected to be in little-endian order. #[derive(Debug, Clone, PartialEq, Eq, Default, PartialOrd, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct ConstantData(#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))] Vec); +pub struct ConstantData(Vec); impl ConstantData { /// Return the number of bytes in the constant. pub fn len(&self) -> usize { @@ -63,8 +96,7 @@ impl ConstantData { pub fn zext(mut self, expected_size: usize) -> Self { assert!( self.len() <= expected_size, - "the constant is already larger than {} bytes", - expected_size + "the constant is already larger than {expected_size} bytes" ); self.0.resize(expected_size, 0); self @@ -145,7 +177,7 @@ impl ConstantData { let s = s.strip_prefix("0x").unwrap_or(s); let len = s.len(); - if len % 2 != 0 { + if !len.is_multiple_of(2) { return Err(NOT_EVEN); } // Parse big-endian @@ -171,21 +203,22 @@ impl ConstantData { /// This maintains the storage for constants used within a function #[derive(Default)] -pub struct ConstantPool { +pub(crate) struct ConstantPool { /// This mapping maintains the insertion order as long as Constants are created with /// sequentially increasing integers. /// /// It is important that, by construction, no entry in that list gets removed. If that ever /// need to happen, don't forget to update the `Constant` generation scheme. - constants: BTreeMap>, + constants: BTreeMap>, /// Mapping of hashed `ConstantData` to the index into the other hashmap. /// /// This allows for deduplication of entries into the `handles_to_values` mapping. - cache: BTreeMap, Constant>, + cache: BTreeMap, ConstantId>, } impl ConstantPool { /// Returns true if the pool is empty + #[allow(unused)] pub fn is_empty(&self) -> bool { self.constants.is_empty() } @@ -196,16 +229,17 @@ impl ConstantPool { } /// Retrieve the constant data as a reference-counted pointer, given a handle. - pub fn get(&self, id: Constant) -> Arc { + pub fn get(&self, id: ConstantId) -> Arc { Arc::clone(&self.constants[&id]) } /// Retrieve the constant data by reference given a handle. - pub fn get_by_ref(&self, id: Constant) -> &ConstantData { + pub fn get_by_ref(&self, id: ConstantId) -> &ConstantData { self.constants[&id].as_ref() } /// Returns true if this pool contains the given constant data + #[allow(unused)] pub fn contains(&self, data: &ConstantData) -> bool { self.cache.contains_key(data) } @@ -213,25 +247,26 @@ impl ConstantPool { /// Insert constant data into the pool, returning a handle for later referencing; when constant /// data is inserted that is a duplicate of previous constant data, the existing handle will be /// returned. - pub fn insert(&mut self, data: ConstantData) -> Constant { + pub fn insert(&mut self, data: ConstantData) -> ConstantId { if let Some(cst) = self.cache.get(&data) { return *cst; } let data = Arc::new(data); - let id = Constant::new(self.len()); + let id = ConstantId::new(self.len()); self.constants.insert(id, Arc::clone(&data)); self.cache.insert(data, id); id } /// Same as [ConstantPool::insert], but for data already allocated in an [Arc]. - pub fn insert_arc(&mut self, data: Arc) -> Constant { + #[allow(unused)] + pub fn insert_arc(&mut self, data: Arc) -> ConstantId { if let Some(cst) = self.cache.get(data.as_ref()) { return *cst; } - let id = Constant::new(self.len()); + let id = ConstantId::new(self.len()); self.constants.insert(id, Arc::clone(&data)); self.cache.insert(data, id); id @@ -239,7 +274,8 @@ impl ConstantPool { /// Traverse the contents of the pool #[inline] - pub fn iter(&self) -> impl Iterator)> + '_ { + #[allow(unused)] + pub fn iter(&self) -> impl Iterator)> + '_ { self.constants.iter().map(|(k, v)| (*k, Arc::clone(v))) } } diff --git a/hir/src/dataflow.rs b/hir/src/dataflow.rs deleted file mode 100644 index 1f5d5a4e2..000000000 --- a/hir/src/dataflow.rs +++ /dev/null @@ -1,907 +0,0 @@ -use core::ops::{Deref, DerefMut, Index, IndexMut}; - -use cranelift_entity::{PrimaryMap, SecondaryMap}; -use rustc_hash::FxHashMap; -use smallvec::SmallVec; - -use crate::{ - diagnostics::{SourceSpan, Span, Spanned}, - *, -}; - -pub struct DataFlowGraph { - pub entry: Block, - pub attrs: AttributeSet, - pub blocks: OrderedArenaMap, - pub insts: ArenaMap, - pub results: SecondaryMap, - pub values: PrimaryMap, - pub value_lists: ValueListPool, - pub imports: FxHashMap, - pub globals: PrimaryMap, - pub locals: PrimaryMap, - pub constants: ConstantPool, -} -impl Default for DataFlowGraph { - fn default() -> Self { - let mut dfg = Self::new_uninit(); - let entry = dfg.blocks.create(); - dfg.entry = entry; - dfg.blocks.append(entry, BlockData::new(entry)); - dfg - } -} -impl DataFlowGraph { - /// Create a new, completely uninitialized DataFlowGraph - pub fn new_uninit() -> Self { - Self { - entry: Block::from_u32(0), - attrs: AttributeSet::default(), - blocks: OrderedArenaMap::new(), - insts: ArenaMap::new(), - results: SecondaryMap::new(), - values: PrimaryMap::new(), - value_lists: ValueListPool::new(), - imports: Default::default(), - globals: PrimaryMap::new(), - locals: PrimaryMap::new(), - constants: ConstantPool::default(), - } - } - - /// Return the value associated with attribute `name` for this function - pub fn get_attribute(&self, name: &Q) -> Option<&AttributeValue> - where - Symbol: std::borrow::Borrow, - Q: Ord + ?Sized, - { - self.attrs.get(name) - } - - /// Return true if this function has an attributed named `name` - pub fn has_attribute(&self, name: &Q) -> bool - where - Symbol: std::borrow::Borrow, - Q: Ord + ?Sized, - { - self.attrs.has(name) - } - - /// Set the attribute `name` with `value` for this function. - pub fn set_attribute(&mut self, name: impl Into, value: impl Into) { - self.attrs.insert(name, value); - } - - /// Remove any attribute with the given name from this function - pub fn remove_attribute(&mut self, name: &Q) - where - Symbol: std::borrow::Borrow, - Q: Ord + ?Sized, - { - self.attrs.remove(name); - } - - /// Returns an [ExternalFunction] given its [FunctionIdent] - pub fn get_import(&self, id: &FunctionIdent) -> Option<&ExternalFunction> { - self.imports.get(id) - } - - /// Look up an [ExternalFunction] given it's module and function name - pub fn get_import_by_name, F: AsRef>( - &self, - module: M, - name: F, - ) -> Option<&ExternalFunction> { - let id = FunctionIdent { - module: Ident::with_empty_span(Symbol::intern(module.as_ref())), - function: Ident::with_empty_span(Symbol::intern(name.as_ref())), - }; - self.imports.get(&id) - } - - /// Returns an iterator over the [ExternalFunction]s imported by this function - pub fn imports<'a, 'b: 'a>(&'b self) -> impl Iterator + 'a { - self.imports.values() - } - - /// Imports function `name` from `module`, with `signature`, returning a [FunctionIdent] - /// corresponding to the import. - /// - /// If the function is already imported, and the signature doesn't match, `Err` is returned. - pub fn import_function( - &mut self, - module: Ident, - name: Ident, - signature: Signature, - ) -> Result { - use std::collections::hash_map::Entry; - - let id = FunctionIdent { - module, - function: name, - }; - match self.imports.entry(id) { - Entry::Vacant(entry) => { - entry.insert(ExternalFunction { id, signature }); - Ok(id) - } - Entry::Occupied(entry) => { - if entry.get().signature != signature { - Err(SymbolConflictError(id)) - } else { - Ok(id) - } - } - } - } - - /// Create a new global value reference - pub fn create_global_value(&mut self, data: GlobalValueData) -> GlobalValue { - self.globals.push(data) - } - - /// Gets the data associated with the given [GlobalValue] - pub fn global_value(&self, gv: GlobalValue) -> &GlobalValueData { - &self.globals[gv] - } - - /// Returns true if the given [GlobalValue] represents an address - pub fn is_global_addr(&self, gv: GlobalValue) -> bool { - match &self.globals[gv] { - GlobalValueData::Symbol { .. } | GlobalValueData::IAddImm { .. } => true, - GlobalValueData::Load { base, .. } => self.is_global_addr(*base), - } - } - - /// Returns the type of the given global value - pub fn global_type(&self, gv: GlobalValue) -> Type { - match &self.globals[gv] { - GlobalValueData::Symbol { .. } => Type::Ptr(Box::new(Type::I8)), - GlobalValueData::IAddImm { base, .. } => self.global_type(*base), - GlobalValueData::Load { ref ty, .. } => ty.clone(), - } - } - - pub fn make_value(&mut self, data: ValueData) -> Value { - self.values.push(data) - } - - pub fn value_type(&self, v: Value) -> &Type { - self.values[v].ty() - } - - pub fn value_span(&self, v: Value) -> SourceSpan { - match &self.values[v] { - ValueData::Param { span, .. } => *span, - ValueData::Inst { inst, .. } => self.inst_span(*inst), - } - } - - #[inline(always)] - pub fn value_data(&self, v: Value) -> &ValueData { - &self.values[v] - } - - pub fn set_value_type(&mut self, v: Value, ty: Type) { - self.values[v].set_type(ty) - } - - pub fn get_value(&self, v: Value) -> ValueData { - self.values[v].clone() - } - - pub fn value_block(&self, v: Value) -> Block { - match self.value_data(v) { - ValueData::Inst { inst, .. } => self - .inst_block(*inst) - .expect("invalid value reference: instruction is not attached to a block"), - ValueData::Param { block, .. } => *block, - } - } - - /// Get a reference to the metadata for an instruction - #[inline(always)] - pub fn inst_node(&self, inst: Inst) -> &InstNode { - &self.insts[inst] - } - - /// Get a reference to the data for an instruction - #[inline(always)] - pub fn inst(&self, inst: Inst) -> &Instruction { - &self.insts[inst].data - } - - /// Get a mutable reference to the metadata for an instruction - #[inline(always)] - pub fn inst_mut(&mut self, inst: Inst) -> &mut Instruction { - &mut self.insts[inst].data - } - - pub fn inst_span(&self, inst: Inst) -> SourceSpan { - self.inst_node(inst).span() - } - - pub fn inst_args(&self, inst: Inst) -> &[Value] { - self.insts[inst].arguments(&self.value_lists) - } - - pub fn inst_block(&self, inst: Inst) -> Option { - let inst_data = &self.insts[inst]; - if inst_data.link.is_linked() { - Some(inst_data.block) - } else { - None - } - } - - pub fn inst_results(&self, inst: Inst) -> &[Value] { - self.results[inst].as_slice(&self.value_lists) - } - - /// Append a new instruction to the end of `block`, using the provided instruction - /// data, controlling type variable, and source span - #[inline] - pub fn append_inst( - &mut self, - block: Block, - data: Instruction, - ctrl_ty: Type, - span: SourceSpan, - ) -> Inst { - self.insert_inst(InsertionPoint::after(ProgramPoint::Block(block)), data, ctrl_ty, span) - } - - /// Insert a new instruction at `ip`, using the provided instruction - /// data, controlling type variable, and source span - pub fn insert_inst( - &mut self, - ip: InsertionPoint, - data: Instruction, - ctrl_ty: Type, - span: SourceSpan, - ) -> Inst { - // Allocate the key for this instruction - let id = self.insts.alloc_key(); - let block_id = match ip.at { - ProgramPoint::Block(block) => block, - ProgramPoint::Inst(inst) => { - self.inst_block(inst).expect("cannot insert after detached instruction") - } - }; - // Store the instruction metadata - self.insts.append(id, InstNode::new(id, block_id, Span::new(span, data))); - // Manufacture values for all of the instruction results - self.make_results(id, ctrl_ty); - // Insert the instruction based on the insertion point provided - let data = unsafe { UnsafeRef::from_raw(&self.insts[id]) }; - let block = &mut self.blocks[block_id]; - match ip { - InsertionPoint { - at: ProgramPoint::Block(_), - action: Insert::After, - } => { - // Insert at the end of this block - block.append(data); - } - InsertionPoint { - at: ProgramPoint::Block(_), - action: Insert::Before, - } => { - // Insert at the start of this block - block.prepend(data); - } - InsertionPoint { - at: ProgramPoint::Inst(inst), - action, - } => { - let mut cursor = block.cursor_mut(); - while let Some(ix) = cursor.get() { - if ix.key == inst { - break; - } - cursor.move_next(); - } - assert!(!cursor.is_null()); - match action { - // Insert just after `inst` in this block - Insert::After => cursor.insert_after(data), - // Insert just before `inst` in this block - Insert::Before => cursor.insert_before(data), - } - } - } - id - } - - /// Create a new instruction which is a clone of `inst`, but detached from any block. - /// - /// NOTE: The instruction is in a temporarily invalid state, because if it has arguments, - /// they will reference values from the scope of the original instruction, but the clone - /// hasn't been inserted anywhere yet. It is up to the caller to ensure that the cloned - /// instruction is updated appropriately once inserted. - pub fn clone_inst(&mut self, inst: Inst) -> Inst { - let id = self.insts.alloc_key(); - let span = self.insts[inst].data.span(); - let data = self.insts[inst].data.deep_clone(&mut self.value_lists); - self.insts - .append(id, InstNode::new(id, Block::default(), Span::new(span, data))); - - // Derive results for the cloned instruction using the results - // of the original instruction - let results = SmallVec::<[Value; 1]>::from_slice(self.inst_results(inst)); - for result in results.into_iter() { - let ty = self.value_type(result).clone(); - self.append_result(id, ty); - } - id - } - - /// Create a `ReplaceBuilder` that will replace `inst` with a new instruction in-place. - pub fn replace(&mut self, inst: Inst) -> ReplaceBuilder { - ReplaceBuilder::new(self, inst) - } - - pub fn append_result(&mut self, inst: Inst, ty: Type) -> Value { - let res = self.values.next_key(); - let num = self.results[inst].push(res, &mut self.value_lists); - debug_assert!(num <= u16::MAX as usize, "too many result values"); - self.make_value(ValueData::Inst { - ty, - inst, - num: num as u16, - }) - } - - pub fn first_result(&self, inst: Inst) -> Value { - self.results[inst].first(&self.value_lists).expect("instruction has no results") - } - - pub fn has_results(&self, inst: Inst) -> bool { - !self.results[inst].is_empty() - } - - fn make_results(&mut self, inst: Inst, ctrl_ty: Type) { - self.results[inst].clear(&mut self.value_lists); - - let opcode = self.insts[inst].opcode(); - if let Some(fdata) = self.call_signature(inst) { - let results = - SmallVec::<[Type; 2]>::from_iter(fdata.results().iter().map(|abi| abi.ty.clone())); - for ty in results.into_iter() { - self.append_result(inst, ty); - } - } else { - match self.insts[inst].data.deref() { - Instruction::InlineAsm(ref asm) => { - let results = asm.results.clone(); - for ty in results.into_iter() { - self.append_result(inst, ty); - } - } - ix => { - let overflow = ix.overflow(); - for ty in opcode.results(overflow, ctrl_ty).into_iter() { - self.append_result(inst, ty); - } - } - } - } - } - - pub(super) fn replace_results(&mut self, inst: Inst, ctrl_ty: Type) { - let opcode = self.insts[inst].opcode(); - let old_results = - SmallVec::<[Value; 1]>::from_slice(self.results[inst].as_slice(&self.value_lists)); - let mut new_results = SmallVec::<[Type; 1]>::default(); - if let Some(fdata) = self.call_signature(inst) { - new_results.extend(fdata.results().iter().map(|p| p.ty.clone())); - } else { - match self.insts[inst].data.deref() { - Instruction::InlineAsm(ref asm) => { - new_results.extend(asm.results.as_slice().iter().cloned()); - } - ix => { - let overflow = ix.overflow(); - new_results = opcode.results(overflow, ctrl_ty); - } - } - } - let old_results_len = old_results.len(); - let new_results_len = new_results.len(); - if old_results_len > new_results_len { - self.results[inst].truncate(new_results_len, &mut self.value_lists); - } - for (index, ty) in new_results.into_iter().enumerate() { - if index >= old_results_len { - // We must allocate a new value for this result - self.append_result(inst, ty); - } else { - // We're updating the old value with a new type - let value = old_results[index]; - self.values[value].set_type(ty); - } - } - } - - /// Replace uses of `value` with `replacement` in the arguments of `inst` - pub fn replace_uses(&mut self, inst: Inst, value: Value, replacement: Value) { - let ix = &mut self.insts[inst]; - match ix.data.deref_mut() { - Instruction::Br(Br { - ref mut successor, .. - }) => { - let args = successor.args.as_mut_slice(&mut self.value_lists); - for arg in args.iter_mut() { - if arg == &value { - *arg = replacement; - } - } - } - Instruction::CondBr(CondBr { - ref mut cond, - ref mut then_dest, - ref mut else_dest, - .. - }) => { - if cond == &value { - *cond = replacement; - } - let then_args = then_dest.args.as_mut_slice(&mut self.value_lists); - for arg in then_args.iter_mut() { - if arg == &value { - *arg = replacement; - } - } - let else_args = else_dest.args.as_mut_slice(&mut self.value_lists); - for arg in else_args.iter_mut() { - if arg == &value { - *arg = replacement; - } - } - } - Instruction::Switch(Switch { - ref mut arg, - ref mut arms, - default: default_succ, - .. - }) => { - if arg == &value { - *arg = replacement; - } - let default_args = default_succ.args.as_mut_slice(&mut self.value_lists); - for arg in default_args.iter_mut() { - if arg == &value { - *arg = replacement; - } - } - for arm in arms.iter_mut() { - let args = arm.successor.args.as_mut_slice(&mut self.value_lists); - for arg in args.iter_mut() { - if arg == &value { - *arg = replacement; - } - } - } - } - ix => { - for arg in ix.arguments_mut(&mut self.value_lists) { - if arg == &value { - *arg = replacement; - } - } - } - } - } - - /// Replace argument at `index` in the argument list of `inst` - /// - /// NOTE: This should not be used for successor arguments, as each successor gets its - /// own distinct argument list, separate from the instruction argument list. - pub fn replace_argument(&mut self, inst: Inst, index: usize, replacement: Value) { - self.insts[inst].data.arguments_mut(&mut self.value_lists)[index] = replacement; - } - - /// Replace the block argument at `index`, for the successor argument list of the - /// successor at `succ_index`, in the set of successors for `inst`. - pub fn replace_successor_argument( - &mut self, - inst: Inst, - succ_index: usize, - index: usize, - replacement: Value, - ) { - let ix = &mut self.insts[inst]; - match ix.data.deref_mut() { - Instruction::Br(Br { - ref mut successor, .. - }) => { - debug_assert_eq!(succ_index, 0); - successor.args.as_mut_slice(&mut self.value_lists)[index] = replacement; - } - Instruction::CondBr(CondBr { - ref mut then_dest, - ref mut else_dest, - .. - }) => match succ_index { - 0 => { - then_dest.args.as_mut_slice(&mut self.value_lists)[index] = replacement; - } - 1 => { - else_dest.args.as_mut_slice(&mut self.value_lists)[index] = replacement; - } - _ => unreachable!("expected valid successor index for cond_br, got {succ_index}"), - }, - Instruction::Switch(Switch { - ref mut arms, - default: ref mut default_succ, - .. - }) => { - debug_assert!(succ_index < arms.len() + 1); - if succ_index == arms.len() { - default_succ.args.as_mut_slice(&mut self.value_lists)[index] = replacement; - } - arms[succ_index].successor.args.as_mut_slice(&mut self.value_lists)[index] = - replacement; - } - ix => panic!("invalid instruction: expected branch instruction, got {ix:#?}"), - } - } - - pub fn pp_block(&self, pp: ProgramPoint) -> Block { - match pp { - ProgramPoint::Block(block) => block, - ProgramPoint::Inst(inst) => self.inst_block(inst).expect("program point not in layout"), - } - } - - pub fn pp_cmp(&self, a: A, b: B) -> core::cmp::Ordering - where - A: Into, - B: Into, - { - let a = a.into(); - let b = b.into(); - debug_assert_eq!(self.pp_block(a), self.pp_block(b)); - let a_seq = match a { - ProgramPoint::Block(_) => 0, - ProgramPoint::Inst(inst) => { - let block = self.insts[inst].block; - self.blocks[block].insts().position(|i| i == inst).unwrap() + 1 - } - }; - let b_seq = match b { - ProgramPoint::Block(_) => 0, - ProgramPoint::Inst(inst) => { - let block = self.insts[inst].block; - self.blocks[block].insts().position(|i| i == inst).unwrap() + 1 - } - }; - a_seq.cmp(&b_seq) - } - - pub fn call_signature(&self, inst: Inst) -> Option<&Signature> { - match self.insts[inst].analyze_call(&self.value_lists) { - CallInfo::NotACall => None, - CallInfo::Direct(ref f, _) => Some(&self.imports[f].signature), - } - } - - pub fn analyze_call(&self, inst: Inst) -> CallInfo<'_> { - self.insts[inst].analyze_call(&self.value_lists) - } - - pub fn analyze_branch(&self, inst: Inst) -> BranchInfo<'_> { - self.insts[inst].analyze_branch(&self.value_lists) - } - - pub fn blocks(&self) -> impl Iterator { - Blocks { - cursor: self.blocks.cursor(), - } - } - - /// Get the block identifier for the entry block - #[inline(always)] - pub fn entry_block(&self) -> Block { - self.entry - } - - /// Get a reference to the data for the entry block - #[inline] - pub fn entry(&self) -> &BlockData { - &self.blocks[self.entry] - } - - /// Get a mutable reference to the data for the entry block - #[inline] - pub fn entry_mut(&mut self) -> &mut BlockData { - &mut self.blocks[self.entry] - } - - pub(super) fn last_block(&self) -> Option { - self.blocks.last().map(|b| b.key()) - } - - pub fn num_blocks(&self) -> usize { - self.blocks.iter().count() - } - - /// Get an immutable reference to the block data for `block` - pub fn block(&self, block: Block) -> &BlockData { - &self.blocks[block] - } - - /// Get a mutable reference to the block data for `block` - pub fn block_mut(&mut self, block: Block) -> &mut BlockData { - &mut self.blocks[block] - } - - pub fn block_args(&self, block: Block) -> &[Value] { - self.blocks[block].params.as_slice(&self.value_lists) - } - - pub fn block_insts(&self, block: Block) -> impl Iterator + '_ { - self.blocks[block].insts() - } - - pub fn block_cursor(&self, block: Block) -> InstructionCursor<'_> { - self.blocks[block].front() - } - - pub fn block_cursor_at(&self, inst: Inst) -> InstructionCursor<'_> { - let block = self.inst_block(inst).expect("instruction is not linked to a block"); - let cursor = self.blocks[block].cursor_at_inst(inst); - assert!(!cursor.is_null()); - cursor - } - - pub fn last_inst(&self, block: Block) -> Option { - self.blocks[block].last() - } - - pub fn is_block_linked(&self, block: Block) -> bool { - self.blocks.contains(block) - } - - pub fn is_block_empty(&self, block: Block) -> bool { - self.blocks[block].is_empty() - } - - pub fn create_block(&mut self) -> Block { - let id = self.blocks.create(); - let data = BlockData::new(id); - self.blocks.append(id, data); - id - } - - pub fn append_block(&mut self, block: Block) { - self.blocks.append(block, BlockData::new(block)); - } - - /// Creates a new block, inserted into the function layout just after `block` - pub fn create_block_after(&mut self, block: Block) -> Block { - let id = self.blocks.create(); - let data = BlockData::new(id); - assert!( - self.blocks.get(block).is_some(), - "cannot insert a new block after {block}, it is not linked" - ); - self.blocks.insert_after(id, block, data); - id - } - - /// Removes `block` from the body of this function, without destroying it's data - pub fn detach_block(&mut self, block: Block) { - self.blocks.remove(block); - } - - pub fn num_block_params(&self, block: Block) -> usize { - self.blocks[block].params.len(&self.value_lists) - } - - pub fn block_params(&self, block: Block) -> &[Value] { - self.blocks[block].params.as_slice(&self.value_lists) - } - - pub fn block_param(&self, block: Block, index: usize) -> &ValueData { - self.blocks[block] - .params - .get(index, &self.value_lists) - .map(|id| self.value_data(id)) - .expect("block argument index is out of bounds") - } - - pub fn block_param_types(&self, block: Block) -> SmallVec<[Type; 1]> { - self.block_params(block).iter().map(|&v| self.value_type(v).clone()).collect() - } - - /// Clone the block parameters of `src` as a new set of values, derived from the data used to - /// crate the originals, and use them to populate the block arguments of `dest`, in the same - /// order. - pub fn clone_block_params(&mut self, src: Block, dest: Block) { - debug_assert_eq!( - self.num_block_params(dest), - 0, - "cannot clone block params to a block that already has params" - ); - let num_params = self.num_block_params(src); - for i in 0..num_params { - let value = self.block_param(src, i); - let ty = value.ty().clone(); - let span = value.span(); - self.append_block_param(dest, ty, span); - } - } - - pub fn append_block_param(&mut self, block: Block, ty: Type, span: SourceSpan) -> Value { - let param = self.values.next_key(); - let num = self.blocks[block].params.push(param, &mut self.value_lists); - debug_assert!(num <= u16::MAX as usize, "too many parameters on block"); - self.make_value(ValueData::Param { - ty, - num: num as u16, - block, - span, - }) - } - - pub fn is_block_terminated(&self, block: Block) -> bool { - if let Some(inst) = self.last_inst(block) { - self.inst(inst).opcode().is_terminator() - } else { - false - } - } - - /// Removes `val` from `block`'s parameters by a standard linear time list removal which - /// preserves ordering. Also updates the values' data. - pub fn remove_block_param(&mut self, val: Value) { - let (block, num) = if let ValueData::Param { num, block, .. } = self.values[val] { - (block, num) - } else { - panic!("{} must be a block parameter", val); - }; - self.blocks[block].params.remove(num as usize, &mut self.value_lists); - for index in num..(self.num_block_params(block) as u16) { - let value_data = &mut self.values - [self.blocks[block].params.get(index as usize, &self.value_lists).unwrap()]; - let mut value_data_clone = value_data.clone(); - match &mut value_data_clone { - ValueData::Param { ref mut num, .. } => { - *num -= 1; - *value_data = value_data_clone; - } - _ => panic!( - "{} must be a block parameter", - self.blocks[block].params.get(index as usize, &self.value_lists).unwrap() - ), - } - } - } - - /// Appends `value` as an argument to the `branch_inst` instruction arguments list if the - /// destination block of the `branch_inst` is `dest`. - /// Panics if `branch_inst` is not a branch instruction. - pub fn append_branch_destination_argument( - &mut self, - branch_inst: Inst, - dest: Block, - value: Value, - ) { - match self.insts[branch_inst].data.deref_mut() { - Instruction::Br(Br { - ref mut successor, .. - }) => { - debug_assert_eq!(successor.destination, dest); - successor.args.push(value, &mut self.value_lists); - } - Instruction::CondBr(CondBr { - ref mut then_dest, - ref mut else_dest, - .. - }) => { - if then_dest.destination == dest { - then_dest.args.push(value, &mut self.value_lists); - } - if else_dest.destination == dest { - else_dest.args.push(value, &mut self.value_lists); - } - } - Instruction::Switch(Switch { - ref mut arms, - default: ref mut default_succ, - .. - }) => { - if default_succ.destination == dest { - default_succ.args.push(value, &mut self.value_lists); - } - for arm in arms.iter_mut() { - if arm.successor.destination == dest { - arm.successor.args.push(value, &mut self.value_lists); - } - } - } - _ => panic!("{} must be a branch instruction", branch_inst), - } - } - - /// Try to locate a valid definition of `value` in the current block, looking up the block from - /// `user` - pub fn nearest_definition_in_block(&self, user: Inst, value: Value) -> Option { - let mut cursor = self.block_cursor_at(user); - // Move to the first instruction preceding this one, or the null cursor if this - // is the first instruction in its containing block - cursor.move_prev(); - - while let Some(current_inst) = cursor.get() { - match self.inst(current_inst.key) { - Instruction::PrimOp(PrimOp { - op: Opcode::Reload, - args, - }) => { - if args.as_slice(&self.value_lists).contains(&value) { - // We have found the closest definition of `value`, which - // is a reload from a spill slot - return Some(self.first_result(current_inst.key)); - } - } - _ => { - if self.inst_results(current_inst.key).contains(&value) { - // We have reached the original definition of `value` - return Some(value); - } - } - } - - cursor.move_prev(); - } - - // Search block parameter list - let current_block = self.inst_block(user).unwrap(); - match self.value_data(value) { - ValueData::Param { block, .. } if block == ¤t_block => Some(value), - _ => None, - } - } - - pub fn alloc_local(&mut self, ty: Type) -> LocalId { - let id = self.locals.next_key(); - self.locals.push(Local { id, ty }) - } - - pub fn local_type(&self, id: LocalId) -> &Type { - &self.locals[id].ty - } - - pub fn locals(&self) -> impl Iterator + '_ { - self.locals.values() - } -} -impl Index for DataFlowGraph { - type Output = Instruction; - - fn index(&self, inst: Inst) -> &Self::Output { - &self.insts[inst] - } -} -impl IndexMut for DataFlowGraph { - fn index_mut(&mut self, inst: Inst) -> &mut Self::Output { - &mut self.insts[inst] - } -} - -struct Blocks<'f> { - cursor: intrusive_collections::linked_list::Cursor<'f, LayoutAdapter>, -} -impl<'f> Iterator for Blocks<'f> { - type Item = (Block, &'f BlockData); - - fn next(&mut self) -> Option { - if self.cursor.is_null() { - return None; - } - let next = self.cursor.get().map(|data| (data.key(), data.value())); - self.cursor.move_next(); - next - } -} diff --git a/hir/src/demangle.rs b/hir/src/demangle.rs new file mode 100644 index 000000000..d94959d16 --- /dev/null +++ b/hir/src/demangle.rs @@ -0,0 +1,23 @@ +use alloc::{string::String, vec::Vec}; + +/// Demangle `name`, where `name` was mangled using Rust's mangling scheme +#[inline] +pub fn demangle>(name: S) -> String { + demangle_impl(name.as_ref()) +} + +#[cfg(not(feature = "std"))] +fn demangle_impl(name: &str) -> String { + use alloc::string::ToString; + + rustc_demangle::demangle(s).as_str().to_string(); +} + +#[cfg(feature = "std")] +fn demangle_impl(name: &str) -> String { + let mut input = name.as_bytes(); + let mut demangled = Vec::with_capacity(input.len() * 2); + rustc_demangle::demangle_stream(&mut input, &mut demangled, /* include_hash= */ false) + .expect("failed to write demangled identifier"); + String::from_utf8(demangled).expect("demangled identifier contains invalid utf-8") +} diff --git a/hir/src/derive.rs b/hir/src/derive.rs new file mode 100644 index 000000000..714351ac0 --- /dev/null +++ b/hir/src/derive.rs @@ -0,0 +1,275 @@ +pub use midenc_hir_macros::operation; + +/// This macro is used to generate the boilerplate for operation trait implementations. +/// Super traits have to be declared as a comma separated list of traits, instead of the traditional +/// "+" separated list of traits. +/// Example: +/// +/// pub trait SomeTrait: SuperTraitA, SuperTraitB {} +#[macro_export] +macro_rules! derive { + ( + $(#[$outer:meta])* + $vis:vis trait $OpTrait:ident $(:)? $( $ParentTrait:ident ),* $(,)? { + $( + $OpTraitItem:item + )* + } + + verify { + $( + fn $verify_fn:ident($op:ident: &$OperationPath:path, $ctx:ident: &$ContextPath:path) -> $VerifyResult:ty $verify:block + )+ + } + + $($t:tt)* + ) => { + $crate::__derive_op_trait! { + $(#[$outer])* + $vis trait $OpTrait : $( $ParentTrait , )* { + $( + $OpTraitItem:item + )* + } + + verify { + $( + fn $verify_fn($op: &$OperationPath, $ctx: &$ContextPath) -> $VerifyResult $verify + )* + } + } + + $($t)* + }; + + ( + $(#[$outer:meta])* + $vis:vis trait $OpTrait:ident { + $( + $OpTraitItem:item + )* + } + + $($t:tt)* + ) => { + $crate::__derive_op_trait! { + $(#[$outer])* + $vis trait $OpTrait { + $( + $OpTraitItem:item + )* + } + } + + $($t)* + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __derive_op_trait { + ( + $(#[$outer:meta])* + $vis:vis trait $OpTrait:ident $(:)? $( $ParentTrait:ident ),* $(,)? { + $( + $OpTraitItem:item + )* + } + + verify { + $( + fn $verify_fn:ident($op:ident: &$OperationPath:path, $ctx:ident: &$ContextPath:path) -> $VerifyResult:ty $verify:block + )+ + } + ) => { + $(#[$outer])* + $vis trait $OpTrait : $( $ParentTrait + )* { + $( + $OpTraitItem + )* + } + + impl $crate::Verify for T { + #[inline] + fn verify(&self, context: &$crate::Context) -> Result<(), $crate::Report> { + $( + <$crate::Operation as $crate::Verify>::verify(self.as_operation(), context)?; + )* + <$crate::Operation as $crate::Verify>::verify(self.as_operation(), context) + } + } + + impl $crate::Verify for $crate::Operation { + fn should_verify(&self, _context: &$crate::Context) -> bool { + $( + self.implements::() + && + )* + self.implements::() + } + + fn verify(&self, context: &$crate::Context) -> Result<(), $crate::Report> { + $( + #[inline] + fn $verify_fn($op: &$OperationPath, $ctx: &$ContextPath) -> $VerifyResult $verify + )* + + $( + $verify_fn(self, context)?; + )* + + Ok(()) + } + } + }; + + ( + $(#[$outer:meta])* + $vis:vis trait $OpTrait:ident { + $( + $OpTraitItem:item + )* + } + ) => { + $(#[$outer])* + $vis trait $OpTrait { + $( + $OpTraitItem + )* + } + }; +} + +#[cfg(test)] +mod tests { + use alloc::{format, rc::Rc}; + + use midenc_session::diagnostics::Severity; + + use crate::{ + attributes::Overflow, + dialects::test::{self, Add}, + pass::{Nesting, PassManager}, + Builder, BuilderExt, Context, Op, Operation, Report, Spanned, Value, + }; + + derive! { + /// A marker trait for arithmetic ops + trait ArithmeticOp {} + + verify { + fn is_binary_op(op: &Operation, ctx: &Context) -> Result<(), Report> { + if op.num_operands() == 2 { + Ok(()) + } else { + Err( + ctx.diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid operation") + .with_primary_label(op.span(), format!("incorrect number of operands, expected 2, got {}", op.num_operands())) + .with_help("this operator implements 'ArithmeticOp' which requires ops to be binary") + .into_report() + ) + } + } + } + } + + impl ArithmeticOp for Add {} + + #[test] + fn derived_op_builder_test() { + use crate::{SourceSpan, Type}; + + let context = Rc::new(Context::default()); + context.register_dialect_hook::(|info, _ctx| { + info.register_operation_trait::(); + }); + let block = context.create_block_with_params([Type::U32, Type::U32]); + let (lhs, rhs) = { + let block = block.borrow(); + let lhs = block.get_argument(0).upcast::(); + let rhs = block.get_argument(1).upcast::(); + (lhs, rhs) + }; + let mut builder = context.builder(); + builder.set_insertion_point_to_end(block); + let op_builder = builder.create::(SourceSpan::default()); + let op = op_builder(lhs, rhs, Overflow::Wrapping); + let op = op.expect("failed to create AddOp"); + let op = op.borrow(); + assert!(op.as_operation().implements::()); + assert!(core::hint::black_box( + !>::VACUOUS + )); + } + + #[test] + #[should_panic = "expected 'u32', got 'i64'"] + fn derived_op_verifier_test() { + use crate::{SourceSpan, Type}; + + let context = Rc::new(Context::default()); + + let block = context.create_block_with_params([Type::U32, Type::I64]); + + context.get_or_register_dialect::(); + context.registered_dialects(); + + let (lhs, invalid_rhs) = { + let block = block.borrow(); + let lhs = block.get_argument(0).upcast::(); + let rhs = block.get_argument(1).upcast::(); + (lhs, rhs) + }; + + let mut builder = context.clone().builder(); + builder.set_insertion_point_to_end(block); + // Try to create instance of AddOp with mismatched operand types + let op_builder = builder.create::(SourceSpan::default()); + let op = op_builder(lhs, invalid_rhs, Overflow::Wrapping); + let op = op.unwrap(); + + // Construct a pass manager with the default pass pipeline + let mut pm = PassManager::on::(context.clone(), Nesting::Implicit); + // Run pass pipeline + pm.run(op.as_operation_ref()).unwrap(); + } + + /// Fails if [`InvalidOpsWithReturn`] is created successfully. [`InvalidOpsWithReturn`] is a + /// struct that has differing types in its result and arguments, despite implementing the + /// [`SameOperandsAndResultType`] trait. + #[test] + #[should_panic = "expected 'i32', got 'u64'"] + fn same_operands_and_result_type_verifier_test() { + use crate::{SourceSpan, Type}; + + let context = Rc::new(Context::default()); + let block = context.create_block_with_params([Type::I32, Type::I32]); + let (lhs, rhs) = { + let block = block.borrow(); + let lhs = block.get_argument(0).upcast::(); + let rhs = block.get_argument(1).upcast::(); + (lhs, rhs) + }; + let mut builder = context.clone().builder(); + builder.set_insertion_point_to_end(block); + + let op_builder = builder.create::(SourceSpan::default()); + let op = op_builder(lhs, rhs, Overflow::Wrapping); + let mut op = op.unwrap(); + + // NOTE: We override the result's type in order to force the SameOperandsAndResultType + // verification function to trigger an error + { + let mut binding = op.borrow_mut(); + let mut result = binding.result_mut(); + result.set_type(Type::U64); + } + + // Construct a pass manager with the default pass pipeline + let mut pm = PassManager::on::(context.clone(), Nesting::Implicit); + // Run pass pipeline + pm.run(op.as_operation_ref()).unwrap(); + } +} diff --git a/hir/src/dialects.rs b/hir/src/dialects.rs new file mode 100644 index 000000000..225bb47dd --- /dev/null +++ b/hir/src/dialects.rs @@ -0,0 +1,2 @@ +pub mod builtin; +pub mod test; diff --git a/hir/src/dialects/builtin.rs b/hir/src/dialects/builtin.rs new file mode 100644 index 000000000..d6bb2711b --- /dev/null +++ b/hir/src/dialects/builtin.rs @@ -0,0 +1,64 @@ +mod builders; +mod ops; + +use alloc::boxed::Box; + +pub use self::{ + builders::{BuiltinOpBuilder, ComponentBuilder, FunctionBuilder, ModuleBuilder, WorldBuilder}, + ops::*, +}; +use crate::{ + AttributeValue, Builder, Dialect, DialectInfo, DialectRegistration, OperationRef, SourceSpan, + Type, +}; + +#[derive(Debug)] +pub struct BuiltinDialect { + info: DialectInfo, +} + +impl BuiltinDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl DialectRegistration for BuiltinDialect { + const NAMESPACE: &'static str = "builtin"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + } +} + +impl Dialect for BuiltinDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + _builder: &mut dyn Builder, + _attr: Box, + _ty: &Type, + _span: SourceSpan, + ) -> Option { + None + } +} diff --git a/hir/src/dialects/builtin/builders.rs b/hir/src/dialects/builtin/builders.rs new file mode 100644 index 000000000..5e0bbff28 --- /dev/null +++ b/hir/src/dialects/builtin/builders.rs @@ -0,0 +1,125 @@ +mod component; +mod function; +mod module; +mod world; + +pub use self::{component::*, function::*, module::*, world::*}; +use super::ops::*; +use crate::{ + constants::ConstantData, Builder, BuilderExt, Ident, Immediate, OpBuilder, Report, Signature, + SourceSpan, Spanned, Type, UnsafeIntrusiveEntityRef, ValueRef, Visibility, +}; + +pub trait BuiltinOpBuilder<'f, B: ?Sized + Builder> { + fn create_interface(&mut self, name: Ident) -> Result { + let op_builder = self.builder_mut().create::(name.span()); + op_builder(name) + } + + fn create_module(&mut self, name: Ident) -> Result { + let op_builder = self.builder_mut().create::(name.span()); + op_builder(name) + } + + fn create_function( + &mut self, + name: Ident, + signature: Signature, + ) -> Result { + let op_builder = self.builder_mut().create::(name.span()); + op_builder(name, signature) + } + + fn create_global_variable( + &mut self, + name: Ident, + visibility: Visibility, + ty: Type, + ) -> Result { + let op_builder = self.builder_mut().create::(name.span()); + op_builder(name, visibility, ty) + } + + fn create_data_segment( + &mut self, + offset: u32, + data: impl Into, + readonly: bool, + span: SourceSpan, + ) -> Result, Report> { + let data = self.builder().context().create_constant(data); + let op_builder = self.builder_mut().create::(span); + op_builder(offset, data, readonly) + } + + fn unrealized_conversion_cast( + &mut self, + value: ValueRef, + ty: Type, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(value, ty)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn ret( + &mut self, + returning: I, + span: SourceSpan, + ) -> Result, Report> + where + I: IntoIterator, + { + let op_builder = self.builder_mut().create::(span); + op_builder(returning) + } + + fn ret_imm( + &mut self, + arg: Immediate, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(arg) + } + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +impl<'f, B: ?Sized + Builder> BuiltinOpBuilder<'f, B> for FunctionBuilder<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + FunctionBuilder::builder(self) + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + FunctionBuilder::builder_mut(self) + } +} + +impl<'f> BuiltinOpBuilder<'f, OpBuilder> for &'f mut OpBuilder { + #[inline(always)] + fn builder(&self) -> &OpBuilder { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut OpBuilder { + self + } +} + +impl BuiltinOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/hir/src/dialects/builtin/builders/component.rs b/hir/src/dialects/builtin/builders/component.rs new file mode 100644 index 000000000..e3e636e27 --- /dev/null +++ b/hir/src/dialects/builtin/builders/component.rs @@ -0,0 +1,62 @@ +use super::BuiltinOpBuilder; +use crate::{ + dialects::builtin::{ComponentRef, FunctionRef, InterfaceRef, Module, ModuleRef}, + Builder, Ident, Op, OpBuilder, Report, Signature, SymbolName, SymbolPath, SymbolTable, +}; + +pub struct ComponentBuilder { + pub component: ComponentRef, + builder: OpBuilder, +} +impl ComponentBuilder { + pub fn new(component: ComponentRef) -> Self { + let component_ref = component.borrow(); + let context = component_ref.as_operation().context_rc(); + let mut builder = OpBuilder::new(context); + + let body = component_ref.body(); + if let Some(current_block) = body.entry_block_ref() { + builder.set_insertion_point_to_end(current_block); + } else { + let body_ref = body.as_region_ref(); + drop(body); + builder.create_block(body_ref, None, &[]); + } + + Self { component, builder } + } + + pub fn define_interface(&mut self, name: Ident) -> Result { + self.builder.create_interface(name) + } + + pub fn define_module(&mut self, name: Ident) -> Result { + let module_ref = self.builder.create_module(name)?; + Ok(module_ref) + } + + pub fn find_module(&self, name: SymbolName) -> Option { + self.component.borrow().get(name).and_then(|symbol_ref| { + let op = symbol_ref.borrow(); + op.as_symbol_operation().downcast_ref::().map(|m| m.as_module_ref()) + }) + } + + pub fn resolve_module(&self, path: &SymbolPath) -> Option { + self.component.borrow().resolve(path).and_then(|symbol_ref| { + let op = symbol_ref.borrow(); + op.as_symbol_operation().downcast_ref::().map(|m| m.as_module_ref()) + }) + } + + /// Declare a new [crate::dialects::hir::Function] in this component with the given name and + /// signature. + pub fn define_function( + &mut self, + name: Ident, + signature: Signature, + ) -> Result { + let function_ref = self.builder.create_function(name, signature)?; + Ok(function_ref) + } +} diff --git a/hir/src/dialects/builtin/builders/function.rs b/hir/src/dialects/builtin/builders/function.rs new file mode 100644 index 000000000..531f556bb --- /dev/null +++ b/hir/src/dialects/builtin/builders/function.rs @@ -0,0 +1,96 @@ +use crate::{dialects::builtin::FunctionRef, *}; + +pub struct FunctionBuilder<'f, B: ?Sized> { + pub func: FunctionRef, + builder: &'f mut B, +} +impl<'f, B: ?Sized + Builder> FunctionBuilder<'f, B> { + pub fn new(mut function: FunctionRef, builder: &'f mut B) -> Self { + let mut func = function.borrow_mut(); + let current_block = if func.body().is_empty() { + func.create_entry_block() + } else { + func.last_block() + }; + + builder.set_insertion_point_to_end(current_block); + + Self { + func: function, + builder, + } + } + + pub fn at(builder: &'f mut B, func: FunctionRef, ip: ProgramPoint) -> Self { + builder.set_insertion_point(ip); + + Self { func, builder } + } + + pub fn as_parts_mut(&mut self) -> (FunctionRef, &mut B) { + (self.func, self.builder) + } + + pub fn body_region(&self) -> RegionRef { + self.func.borrow().body().as_region_ref() + } + + pub fn entry_block(&self) -> BlockRef { + self.func.borrow().entry_block() + } + + #[inline] + pub fn current_block(&self) -> BlockRef { + self.builder.insertion_block().expect("builder has no insertion point set") + } + + #[inline] + pub fn switch_to_block(&mut self, block: BlockRef) { + self.builder.set_insertion_point_to_end(block); + } + + pub fn create_block(&mut self) -> BlockRef { + let ip = *self.builder.insertion_point(); + let block = self.builder.create_block(self.body_region(), None, &[]); + self.builder.restore_insertion_point(ip); + block + } + + pub fn create_block_in_region(&mut self, region: RegionRef) -> BlockRef { + let ip = *self.builder.insertion_point(); + let block = self.builder.create_block(region, None, &[]); + self.builder.restore_insertion_point(ip); + block + } + + pub fn detach_block(&mut self, mut block: BlockRef) { + assert_ne!( + block, + self.current_block(), + "cannot remove block the builder is currently inserting in" + ); + assert_eq!( + block.parent().map(|p| RegionRef::as_ptr(&p)), + Some(RegionRef::as_ptr(&self.func.borrow().body().as_region_ref())), + "cannot detach a block that does not belong to this function" + ); + unsafe { + let mut func = self.func.borrow_mut(); + let mut body = func.body_mut(); + body.body_mut().cursor_mut_from_ptr(block).remove(); + } + block.borrow_mut().uses_mut().clear(); + } + + pub fn append_block_param(&mut self, block: BlockRef, ty: Type, span: SourceSpan) -> ValueRef { + self.builder.context().append_block_argument(block, ty, span) + } + + pub fn builder(&self) -> &B { + self.builder + } + + pub fn builder_mut(&mut self) -> &mut B { + self.builder + } +} diff --git a/hir/src/dialects/builtin/builders/module.rs b/hir/src/dialects/builtin/builders/module.rs new file mode 100644 index 000000000..f6de35541 --- /dev/null +++ b/hir/src/dialects/builtin/builders/module.rs @@ -0,0 +1,138 @@ +use super::BuiltinOpBuilder; +use crate::{ + constants::ConstantData, + dialects::builtin::{ + Function, FunctionRef, GlobalVariable, GlobalVariableRef, Module, ModuleRef, + PrimModuleBuilder, Segment, + }, + Builder, Ident, Op, OpBuilder, Report, Signature, SourceSpan, Spanned, SymbolName, SymbolTable, + Type, UnsafeIntrusiveEntityRef, Visibility, +}; + +/// A specialized builder for constructing/modifying [crate::dialects::hir::Module] +pub struct ModuleBuilder { + pub module: ModuleRef, + builder: OpBuilder, +} +impl ModuleBuilder { + /// Create a builder over `module` + pub fn new(module: ModuleRef) -> Self { + let module_ref = module.borrow(); + let context = module_ref.as_operation().context_rc(); + let mut builder = OpBuilder::new(context); + + { + let body = module_ref.body(); + + if let Some(current_block) = body.entry_block_ref() { + builder.set_insertion_point_to_end(current_block); + } else { + let body_ref = body.as_region_ref(); + drop(body); + builder.create_block(body_ref, None, &[]); + } + } + + Self { module, builder } + } + + /// Get the underlying [OpBuilder] + pub fn builder(&mut self) -> &mut OpBuilder { + &mut self.builder + } + + /// Declare a new [crate::dialects::hir::Function] in this module with the given name and + /// signature. + /// + /// The returned [FunctionRef] can be used to construct a [FunctionBuilder] to define the body + /// of the function. + pub fn define_function( + &mut self, + name: Ident, + signature: Signature, + ) -> Result { + let function_ref = self.builder.create_function(name, signature)?; + Ok(function_ref) + } + + /// Declare a new [GlobalVariable] in this module with the given name, visibility, and type. + /// + /// The returned [UnsafeIntrusiveEntityRef] can be used to construct a [InitializerBuilder] + /// over the body of the global variable initializer region. + pub fn define_global_variable( + &mut self, + name: Ident, + visibility: Visibility, + ty: Type, + ) -> Result, Report> { + let global_var_ref = self.builder.create_global_variable(name, visibility, ty)?; + Ok(global_var_ref) + } + + pub fn define_data_segment( + &mut self, + offset: u32, + data: impl Into, + readonly: bool, + span: SourceSpan, + ) -> Result, Report> { + self.builder.create_data_segment(offset, data, readonly, span) + } + + pub fn get_function(&self, name: &str) -> Option { + let symbol = SymbolName::intern(name); + match self.module.borrow().get(symbol) { + Some(symbol_ref) => { + let op = symbol_ref.borrow(); + match op.as_symbol_operation().downcast_ref::() { + Some(function) => Some(function.as_function_ref()), + None => panic!("expected {name} to be a function"), + } + } + None => None, + } + } + + pub fn set_function_visibility(&mut self, name: &str, visibility: Visibility) { + let symbol = SymbolName::intern(name); + match self.module.borrow_mut().get(symbol) { + Some(mut symbol_ref) => { + let mut op = symbol_ref.borrow_mut(); + match op.as_symbol_operation_mut().downcast_mut::() { + Some(function) => { + function.signature_mut().visibility = visibility; + } + None => panic!("expected {name} to be a function"), + } + } + None => { + panic!("failed to find function {name} in module {}", self.module.borrow().name()) + } + } + } + + pub fn get_global_var(&self, name: SymbolName) -> Option { + self.module.borrow().get(name).and_then(|gv_symbol| { + let op_ref = gv_symbol.borrow().as_operation_ref(); + op_ref + .borrow() + .downcast_ref::() + .map(|gv| gv.as_global_var_ref()) + }) + } + + /// Declare a new nested module `name` + pub fn declare_module(&mut self, name: Ident) -> Result { + let builder = PrimModuleBuilder::new(&mut self.builder, name.span()); + let module_ref = builder(name)?; + Ok(module_ref) + } + + /// Resolve a nested module with `name`, if declared/defined + pub fn find_module(&self, name: SymbolName) -> Option { + self.module.borrow().get(name).and_then(|symbol_ref| { + let op = symbol_ref.borrow(); + op.as_symbol_operation().downcast_ref::().map(|m| m.as_module_ref()) + }) + } +} diff --git a/hir/src/dialects/builtin/builders/world.rs b/hir/src/dialects/builtin/builders/world.rs new file mode 100644 index 000000000..f42de3b3e --- /dev/null +++ b/hir/src/dialects/builtin/builders/world.rs @@ -0,0 +1,133 @@ +use alloc::format; + +use crate::{ + dialects::builtin::{ + Component, ComponentId, ComponentRef, Module, ModuleBuilder, ModuleRef, + PrimComponentBuilder, PrimModuleBuilder, World, WorldRef, + }, + version::Version, + Builder, Ident, Op, OpBuilder, Report, Spanned, SymbolName, SymbolNameComponent, SymbolPath, + SymbolTable, UnsafeIntrusiveEntityRef, +}; + +pub struct WorldBuilder { + pub world: WorldRef, + builder: OpBuilder, +} +impl WorldBuilder { + pub fn new(world_ref: WorldRef) -> Self { + let world = world_ref.borrow(); + let context = world.as_operation().context_rc(); + let mut builder = OpBuilder::new(context); + + let body = world.body(); + if let Some(current_block) = body.entry_block_ref() { + builder.set_insertion_point_to_end(current_block); + } else { + let body_ref = body.as_region_ref(); + drop(body); + builder.create_block(body_ref, None, &[]); + } + + Self { + world: world_ref, + builder, + } + } + + pub fn define_component( + &mut self, + ns: Ident, + name: Ident, + ver: Version, + ) -> Result { + let builder = PrimComponentBuilder::new(&mut self.builder, name.span()); + let component_ref = builder(ns, name, ver.clone())?; + Ok(component_ref) + } + + pub fn find_component(&self, id: &ComponentId) -> Option { + self.world.borrow().get(SymbolName::intern(id)).and_then(|symbol_ref| { + let op = symbol_ref.borrow(); + op.as_symbol_operation() + .downcast_ref::() + .map(|c| c.as_component_ref()) + }) + } + + /// Declare a new world-level module `name` + pub fn declare_module(&mut self, name: Ident) -> Result { + let builder = PrimModuleBuilder::new(&mut self.builder, name.span()); + let module_ref = builder(name)?; + Ok(module_ref) + } + + /// Resolve a world-level module with `name`, if declared/defined + pub fn find_module(&self, name: SymbolName) -> Option { + self.world.borrow().get(name).and_then(|symbol_ref| { + let op = symbol_ref.borrow(); + op.as_symbol_operation().downcast_ref::().map(|m| m.as_module_ref()) + }) + } + + /// Recursively declare a hierarchy of modules, given a [SymbolPath] which contains the modules + /// that must either exist, or will be created. + /// + /// Think of this as `mkdir -p ` for modules. + /// + /// NOTE: The entire [SymbolPath], ignoring root and leaf components, must resolve to a Module, + /// or to nothing. A path component which resolves to some other operation will be treated as + /// a conflict, and an error will be returned. + pub fn declare_module_tree(&mut self, path: &SymbolPath) -> Result { + let mut parts = path.components().peekable(); + parts.next_if_eq(&SymbolNameComponent::Root); + + let mut current_symbol_table = self.world.as_operation_ref(); + let mut leaf_module = None; + while let Some(SymbolNameComponent::Component(module_name)) = parts.next() { + let symbol = current_symbol_table.borrow().as_symbol_table().unwrap().get(module_name); + if symbol.is_some_and(|sym| !sym.borrow().as_symbol_operation().is::()) { + return Err(Report::msg(format!( + "could not declare module path component '{module_name}': a non-module symbol \ + with that name already exists" + ))); + } + + let module = symbol.and_then(|symbol_ref| { + symbol_ref + .borrow() + .as_symbol_operation() + .downcast_ref::() + .map(|m| m.as_module_ref()) + }); + let is_parent_module = current_symbol_table.borrow().is::(); + let module = match module { + Some(module) => module, + None if is_parent_module => { + let parent_module = { + current_symbol_table + .borrow() + .downcast_ref::() + .unwrap() + .as_module_ref() + }; + let mut module_builder = ModuleBuilder::new(parent_module); + module_builder.declare_module(module_name.into())? + } + None => { + let world = unsafe { + UnsafeIntrusiveEntityRef::from_raw( + current_symbol_table.borrow().downcast_ref::().unwrap(), + ) + }; + let mut world_builder = WorldBuilder::new(world); + world_builder.declare_module(module_name.into())? + } + }; + current_symbol_table = module.as_operation_ref(); + leaf_module = Some(module); + } + + Ok(leaf_module.expect("invalid empty module path")) + } +} diff --git a/hir/src/dialects/builtin/ops.rs b/hir/src/dialects/builtin/ops.rs new file mode 100644 index 000000000..53f214cb5 --- /dev/null +++ b/hir/src/dialects/builtin/ops.rs @@ -0,0 +1,24 @@ +mod cast; +mod component; +mod function; +mod global_variable; +mod interface; +mod module; +mod segment; +mod world; + +pub use self::{ + cast::UnrealizedConversionCast, + component::{ + Component, ComponentBuilder as PrimComponentBuilder, ComponentExport, ComponentId, + ComponentInterface, ComponentRef, ModuleExport, ModuleInterface, + }, + function::{ + Function, FunctionBuilder as PrimFunctionBuilder, FunctionRef, LocalVariable, Ret, RetImm, + }, + global_variable::*, + interface::{Interface, InterfaceBuilder as PrimInterfaceBuilder, InterfaceRef}, + module::{Module, ModuleBuilder as PrimModuleBuilder, ModuleRef}, + segment::*, + world::{World, WorldRef}, +}; diff --git a/hir/src/dialects/builtin/ops/cast.rs b/hir/src/dialects/builtin/ops/cast.rs new file mode 100644 index 000000000..4f3ce1912 --- /dev/null +++ b/hir/src/dialects/builtin/ops/cast.rs @@ -0,0 +1,39 @@ +use crate::{ + derive::operation, + dialects::builtin::BuiltinDialect, + effects::{EffectIterator, EffectOpInterface, MemoryEffect, MemoryEffectOpInterface}, + traits::{AnyType, InferTypeOpInterface, UnaryOp}, + Context, Report, Spanned, Type, Value, +}; + +#[operation( + dialect = BuiltinDialect, + traits(UnaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct UnrealizedConversionCast { + #[operand] + operand: AnyType, + #[attr(hidden)] + ty: Type, + #[result] + result: AnyType, +} + +impl InferTypeOpInterface for UnrealizedConversionCast { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.ty().clone(); + self.result_mut().set_type(ty); + Ok(()) + } +} + +impl EffectOpInterface for UnrealizedConversionCast { + fn has_no_effect(&self) -> bool { + true + } + + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec::smallvec![]) + } +} diff --git a/hir/src/dialects/builtin/ops/component.rs b/hir/src/dialects/builtin/ops/component.rs new file mode 100644 index 000000000..71a37fcb9 --- /dev/null +++ b/hir/src/dialects/builtin/ops/component.rs @@ -0,0 +1,230 @@ +mod interface; + +pub use self::interface::{ + ComponentExport, ComponentId, ComponentInterface, ModuleExport, ModuleInterface, +}; +use crate::{ + derive::operation, + dialects::builtin::BuiltinDialect, + traits::{ + GraphRegionNoTerminator, HasOnlyGraphRegion, IsolatedFromAbove, NoRegionArguments, + NoTerminator, SingleBlock, SingleRegion, + }, + version::Version, + Ident, OpPrinter, Operation, RegionKind, RegionKindInterface, Symbol, SymbolManager, + SymbolManagerMut, SymbolMap, SymbolName, SymbolRef, SymbolTable, SymbolUseList, + UnsafeIntrusiveEntityRef, Usable, Visibility, +}; + +pub type ComponentRef = UnsafeIntrusiveEntityRef; + +/// A [Component] is a modular abstraction operation, i.e. it is designed to model shared-nothing +/// boundaries between groups of shared-everything modules in a system. +/// +/// Components can contain the following entities: +/// +/// * [Interface], used to export groups of related functionality from the component. Interfaces +/// always have `Public` visibility. +/// * [Function] used to export standalone component-level functions, e.g. a program entrypoint, +/// or component initializer. These functions always have `Public` visibility, and must be +/// representable using the Canonical ABI. +/// * [Module], used to implement the functionality exported backing an [Interface] or a component- +/// level [Function]. Modules may not have `Public` visibility. All modules within a [Component] +/// are within the same shared-everything boundary, so conflicting data segment declarations are +/// not allowed. Additionally, global variables within the same shared-everything boundary +/// are allocated in the same linear memory address space. +/// +/// Externally-defined functions are represented as declarations, and must be referenced using their +/// fully-qualified name in order to resolve them. +/// +/// ## Linking +/// +/// NOTE: Components always have `Public` visibility. +/// +/// Components are linked into Miden Assembly according to the following rules: +/// +/// * A [Component] corresponds to a Miden Assembly namespace, and a Miden package +/// * Component-level functions are emitted to a MASM module corresponding to the root of the +/// namespace, i.e. as if defined in `mod.masm` at the root of a MASM source project. +/// * Each [Interface] of a component is emitted to a MASM module of the same name +/// * Each [Module] of a component is emitted to a MASM module of the same name +/// * The [Segment] declarations of all modules in the component are gathered together, checked for +/// overlap, hashed, and then added to the set of advice map entries to be initialized when the +/// resulting package is loaded. The initialization code generated to load the data segments into +/// the linear memory of the component, is placed in a top-level component function called `init`. +/// * The [GlobalVariable] declarations of all modules in the component are gathered together, +/// de-duplicated, initializer data hashed and added to the set of advice map entries of the +/// package, and allocated specific offsets in the address space of the component. Loads/stores +/// of these variables will be lowered to use these allocated offsets. The initialization code +/// for each global will be emitted in the top-level component function called `init`. +/// * The set of externally-defined components that have at least one reference, will be added as +/// dependencies of the output package. +#[operation( + dialect = BuiltinDialect, + traits( + SingleRegion, + SingleBlock, + NoRegionArguments, + NoTerminator, + HasOnlyGraphRegion, + GraphRegionNoTerminator, + IsolatedFromAbove, + ), + implements(RegionKindInterface, SymbolTable, Symbol, OpPrinter) +)] +pub struct Component { + #[attr] + namespace: Ident, + #[attr] + name: Ident, + #[attr] + version: Version, + #[attr] + #[default] + visibility: Visibility, + #[region] + body: RegionRef, + #[default] + symbols: SymbolMap, + #[default] + uses: SymbolUseList, +} + +impl OpPrinter for Component { + fn print( + &self, + flags: &crate::OpPrintingFlags, + _context: &crate::Context, + ) -> crate::formatter::Document { + use crate::formatter::*; + + let header = display(self.op.name()) + const_text(" ") + display(self.id()); + let body = crate::print::render_regions(&self.op, flags); + header + body + } +} + +impl midenc_session::Emit for Component { + fn name(&self) -> Option { + Some(self.name().as_symbol()) + } + + fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { + midenc_session::OutputType::Hir + } + + fn write_to( + &self, + mut writer: W, + _mode: midenc_session::OutputMode, + _session: &midenc_session::Session, + ) -> anyhow::Result<()> { + let flags = crate::OpPrintingFlags::default(); + let document = ::print(self, &flags, self.op.context()); + writer.write_fmt(format_args!("{document}")) + } +} + +impl RegionKindInterface for Component { + #[inline(always)] + fn kind(&self) -> RegionKind { + RegionKind::Graph + } +} + +impl Usable for Component { + type Use = crate::SymbolUse; + + #[inline(always)] + fn uses(&self) -> &SymbolUseList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut SymbolUseList { + &mut self.uses + } +} + +impl Symbol for Component { + #[inline(always)] + fn as_symbol_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn name(&self) -> SymbolName { + let id = ComponentId { + namespace: self.namespace().as_symbol(), + name: Component::name(self).as_symbol(), + version: self.version().clone(), + }; + SymbolName::intern(id) + } + + fn set_name(&mut self, name: SymbolName) { + let ComponentId { + name, + namespace, + version, + } = name.as_str().parse::().expect("invalid component identifier"); + self.name_mut().name = name; + self.namespace_mut().name = namespace; + *self.version_mut() = version; + } + + fn visibility(&self) -> Visibility { + *Component::visibility(self) + } + + fn set_visibility(&mut self, visibility: Visibility) { + *self.visibility_mut() = visibility; + } +} + +impl SymbolTable for Component { + #[inline(always)] + fn as_symbol_table_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_table_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn symbol_manager(&self) -> SymbolManager<'_> { + SymbolManager::new(&self.op, crate::Symbols::Borrowed(&self.symbols)) + } + + fn symbol_manager_mut(&mut self) -> SymbolManagerMut<'_> { + SymbolManagerMut::new(&mut self.op, crate::SymbolsMut::Borrowed(&mut self.symbols)) + } + + #[inline] + fn get(&self, name: SymbolName) -> Option { + self.symbols.get(name) + } +} + +impl Component { + pub fn id(&self) -> ComponentId { + let namespace = self.namespace().as_symbol(); + let name = self.name().as_symbol(); + let version = self.version().clone(); + ComponentId { + namespace, + name, + version, + } + } + + #[inline(always)] + pub fn as_component_ref(&self) -> ComponentRef { + unsafe { ComponentRef::from_raw(self) } + } +} diff --git a/hir/src/dialects/builtin/ops/component/interface.rs b/hir/src/dialects/builtin/ops/component/interface.rs new file mode 100644 index 000000000..af591dfb3 --- /dev/null +++ b/hir/src/dialects/builtin/ops/component/interface.rs @@ -0,0 +1,451 @@ +use alloc::format; +use core::fmt; + +use super::Component; +use crate::{ + diagnostics::{miette, Diagnostic}, + dialects::builtin::{Function, Module}, + version::Version, + FxHashMap, Signature, Symbol, SymbolName, SymbolNameComponent, SymbolPath, SymbolTable, Type, + Visibility, +}; + +/// The fully-qualfied identifier of a component +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ComponentId { + /// The namespace in which the component is defined + pub namespace: SymbolName, + /// The name of this component + pub name: SymbolName, + /// The semantic version number of this component + pub version: Version, +} + +impl fmt::Display for ComponentId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}@{}", &self.namespace, &self.name, &self.version) + } +} + +impl ComponentId { + /// Returns true if `self` and `other` are equal according to semantic versioning rules: + /// + /// * Namespace and name are identical + /// * Version numbers are considered equal according to semantic versioning (i.e. if the version + /// strings differ only in build metadata, then they are considered equal). + pub fn is_match(&self, other: &Self) -> bool { + self.namespace == other.namespace + && self.name == other.name + && self.version.cmp_precedence(&other.version).is_eq() + } + + /// Get the Miden Assembly [LibraryPath] that uniquely identifies this interface. + pub fn to_library_path(&self) -> midenc_session::LibraryPath { + use midenc_session::{LibraryNamespace, LibraryPath}; + + let ns = format!("{}:{}@{}", &self.namespace, &self.name, &self.version); + let namespace = LibraryNamespace::User(ns.into_boxed_str().into()); + LibraryPath::new_from_components(namespace, []) + } +} + +#[derive(thiserror::Error, Debug, Diagnostic)] +pub enum InvalidComponentIdError { + #[error("invalid component id: missing namespace identifier")] + #[diagnostic()] + MissingNamespace, + #[error("invalid component id: missing component name")] + #[diagnostic()] + MissingName, + #[error("invalid component id: missing version")] + #[diagnostic()] + MissingVersion, + #[error("invalid component version: {0}")] + #[diagnostic()] + InvalidVersion(#[from] crate::version::semver::Error), +} + +impl TryFrom<&SymbolPath> for ComponentId { + type Error = InvalidComponentIdError; + + fn try_from(path: &SymbolPath) -> Result { + let mut components = path.components().peekable(); + components.next_if_eq(&SymbolNameComponent::Root); + + let (ns, name, version) = match components.next().map(|c| c.as_symbol_name()) { + None => return Err(InvalidComponentIdError::MissingNamespace), + Some(name) => match name.as_str().split_once(':') { + Some((ns, name)) => match name.split_once('@') { + Some((name, version)) => ( + SymbolName::intern(ns), + SymbolName::intern(name), + Version::parse(version).map_err(InvalidComponentIdError::InvalidVersion)?, + ), + None => return Err(InvalidComponentIdError::MissingVersion), + }, + None => return Err(InvalidComponentIdError::MissingNamespace), + }, + }; + + Ok(Self { + namespace: ns, + name, + version, + }) + } +} + +impl core::str::FromStr for ComponentId { + type Err = InvalidComponentIdError; + + fn from_str(s: &str) -> Result { + let (version, rest) = match s.rsplit_once('@') { + None => (Version::new(1, 0, 0), s), + Some((rest, version)) => (version.parse::()?, rest), + }; + let (ns, name) = match rest.split_once(':') { + Some((ns, name)) => (SymbolName::intern(ns), SymbolName::intern(name)), + None => return Err(InvalidComponentIdError::MissingNamespace), + }; + Ok(Self { + namespace: ns, + name, + version, + }) + } +} + +impl From<&Component> for ComponentId { + fn from(value: &Component) -> Self { + let namespace = value.namespace().as_symbol(); + let name = value.name().as_symbol(); + let version = value.version().clone(); + + Self { + namespace, + name, + version, + } + } +} + +/// A [ComponentInterface] is a description of the "skeleton" of a component, i.e.: +/// +/// * Basic metadata about the component itself, e.g. name +/// * The set of interfaces it requires to be provided in order to instantiate the component +/// * The set of exported items provided by the interface, which can be used to fulfill imports +/// of other components. +/// +/// This type is derived from a [Component] operation, but does not represent an operation itself, +/// instead, this is used by the compiler to reason about what components are available, what is +/// required, and whether or not all requirements can be met. +pub struct ComponentInterface { + id: ComponentId, + /// The visibility of this component in the interface (public or internal) + visibility: Visibility, + /// This flag is set to `true` if the interface is completely abstract (no definitions) + is_externally_defined: bool, + /// The set of imports required by this component. + /// + /// An import can be satisfied by any [Component] whose interface matches the one specified. + /// In specific terms, this refers to the signatures/types of all symbols in the interface, + /// rather than the names. The compiler will handle rebinding uses of symbols in the interface + /// to the concrete symbols of the provided implementation - the important part is that the + /// implementation is explicitly provided as an implementation of that interface, i.e. we do + /// not try to simply find a match amongst all components. + /// + /// In the Wasm Component Model, such explicit instantiations are provided for us, so wiring + /// up the component hierarchy derived from a Wasm component should be straightforward. It + /// remains to be seen if there are non-Wasm sources where this is more problematic. + imports: FxHashMap, + /// The set of items which form the interface of this component, and can be referenced from + /// other components. + /// + /// All "exports" from a component interface are named, but can represent a variety of IR + /// entities. + exports: FxHashMap, +} + +impl ComponentInterface { + /// Derive a [ComponentInterface] from the given [Component] + pub fn new(component: &Component) -> Self { + let mut imports = FxHashMap::default(); + let mut exports = FxHashMap::default(); + let mut is_externally_defined = true; + + let id = ComponentId::from(component); + + let symbol_manager = component.symbol_manager(); + for symbol_ref in symbol_manager.symbols().symbols() { + let symbol = symbol_ref.borrow(); + let symbol_op = symbol.as_symbol_operation(); + let name = symbol.name(); + if let Some(module) = symbol_op.downcast_ref::() { + let interface = ModuleInterface::new(module); + let visibility = interface.visibility; + let is_abstract = interface.is_abstract; + let item = ComponentExport::Module(interface); + // Modules at the top level of a component are always exports, however we care about + // whether the module is abstract or not. Abstract module interfaces are only + // permitted in abstract component interfaces, otherwise all modules in the component + // must be definitions. We assert that this is the case, in order to catch any + // instances where the compiler produces invalid component IR. + if is_abstract { + // This represents an abstract module interface provided by this component + assert!( + is_externally_defined, + "invalid component: abstract module '{name}' is not permitted in a \ + non-abstract component" + ); + assert!(visibility.is_public(), "abstract modules must have public visibility"); + exports.insert(name, item); + } else { + // This represents a concrete module definition + assert!( + !is_externally_defined || exports.is_empty(), + "invalid component: concrete module '{name}' is not permitted in an \ + abstract component interface" + ); + // We only export public or internal modules + if !visibility.is_private() { + exports.insert(name, item); + } + is_externally_defined = false; + } + } else if let Some(child_component) = symbol_op.downcast_ref::() { + let interface = ComponentInterface::new(child_component); + let visibility = interface.visibility; + if interface.is_externally_defined { + // This is an import of an externally-defined component + let import_id = interface.id.clone(); + imports.insert(import_id, interface); + } else { + if !visibility.is_private() { + // This is an exported component definition (either internally or globally) + exports.insert(name, ComponentExport::Component(interface)); + } + is_externally_defined = false; + } + } else { + // If this happens we should assert - something is definitely wrong + unimplemented!( + "unsupported symbol type `{}` in component: '{}'", + symbol_op.name(), + symbol.name() + ); + } + } + + Self { + id, + is_externally_defined, + visibility: *component.visibility(), + imports, + exports, + } + } + + pub fn id(&self) -> &ComponentId { + &self.id + } + + /// Returns true if this interface describes a component for which we do not have a definition. + pub fn is_externally_defined(&self) -> bool { + self.is_externally_defined + } + + /// Returns the visibility of this component in its current context. + /// + /// This is primarily used to determine whether or not this component is exportable from its + /// parent component, and whether or not symbols defined within it are visible to siblings. + pub fn visibility(&self) -> Visibility { + self.visibility + } + + pub fn exports(&self) -> &FxHashMap { + &self.exports + } + + /// Returns true if this component exports `name` + /// + /// The given symbol name is expected to be found at the top level of the component, i.e. this + /// function does not attempt to resolve nested symbol references. + pub fn exports_symbol(&self, name: SymbolName) -> bool { + self.exports.contains_key(&name) + } + + /// Returns true if this component provides the given [ModuleInterface]. + /// + /// A component "provides" a module if it defines a module with the same name, and which + /// contains all of the symbols of the given interface with matching types/signatures, i.e. it + /// is a superset of the provided interface. + /// + /// NOTE: This does not return false if the component or module are externally-defined, it only + /// answers the question of whether or not, if we had an instance of this component, would it + /// satisfy the provided interface's requirements. + pub fn provides_module(&self, interface: &ModuleInterface) -> bool { + self.exports + // Do we export a symbol with the given name + .get(&interface.name) + // The symbol must be a module + .and_then(|export| match export { + ComponentExport::Module(definition) => Some(definition), + _ => None, + }) + // The module must provide exports for all of `interface`'s imports + .map(|definition| { + interface.imports.iter().all(|(imported_symbol, import)| { + definition.exports.get(imported_symbol).is_some_and(|export| export == import) + }) + }) + // If we didn't find the symbol, or it wasn't a module, return false + .unwrap_or(false) + } + + /// Returns true if this component provides the given [ComponentInterface]. + /// + /// A component "provides" a component if either of the following are true: + /// + /// * The component itself has the same name as the given interface, and defines all of the + /// items imported by the interface, with matching types/signatures where appropriate. + /// * The component exports a component that matches the given interface as described above. + /// + /// NOTE: This does not return false if the component or a matching child component are + /// externally-defined, it only answers the question of whether or not, if we had an instance of + /// the matching component, would it satisfy the provided interface's requirements. + pub fn provides_component(&self, interface: &ComponentInterface) -> bool { + if self.matches(interface) { + return true; + } + + self.exports + // Do we export a symbol with the given name + .get(&interface.id.name) + // The symbol must be a component + .and_then(|export| match export { + ComponentExport::Component(definition) => Some(definition), + _ => None, + }) + // The component must provide exports for all of `interface`'s imports + .map(|definition| definition.matches(interface)) + // If we didn't find the symbol, or it wasn't a module, return false + .unwrap_or(false) + } + + /// Returns true if `self` provides a superset of the imports required by `other`, or put + /// another way - `self` matches the component import described by `other`. + pub fn matches(&self, other: &Self) -> bool { + if !self.id.is_match(&other.id) { + return false; + } + + other.imports.iter().all(|(imported_id, import)| { + self.exports + .get(&imported_id.name) + .is_some_and(|export| export.matches_component(import)) + }) + } +} + +pub enum ComponentExport { + /// A nested component which has public visibility and is thus exported from its parent component + Component(ComponentInterface), + /// A module which has public visibility and is thus exported from its parent component + Module(ModuleInterface), +} +impl ComponentExport { + /// Returns true if this export describes a component that provides a superset of the imports + /// required by `other`, i.e. `self` is a component which matches the component import described + /// by `other`. + pub fn matches_component(&self, other: &ComponentInterface) -> bool { + let Self::Component(definition) = self else { + return false; + }; + + definition.matches(other) + } +} + +pub struct ModuleInterface { + name: SymbolName, + /// The visibility of this module in the interface (public vs internal) + visibility: Visibility, + /// This flag is set to `true` if the interface is completely abstract (no definitions) + is_abstract: bool, + imports: FxHashMap, + exports: FxHashMap, +} + +impl ModuleInterface { + /// Derive a [ModuleInterface] from the given [Module] + pub fn new(module: &Module) -> Self { + let mut imports = FxHashMap::default(); + let mut exports = FxHashMap::default(); + let mut is_abstract = true; + + let symbol_manager = module.symbol_manager(); + for symbol_ref in symbol_manager.symbols().symbols() { + let symbol = symbol_ref.borrow(); + let name = symbol.name(); + if let Some(func) = symbol.as_symbol_operation().downcast_ref::() { + let signature = func.signature().clone(); + let visibility = signature.visibility; + let item = ModuleExport::Function { name, signature }; + if func.is_declaration() { + // This is an import of an externally-defined function + imports.insert(name, item); + } else { + if !visibility.is_private() { + // This is an exported function definition (either internally or globally) + exports.insert(name, item); + } + is_abstract = false; + } + } + + // TODO: GlobalVariable + } + + Self { + name: module.name().as_symbol(), + visibility: *module.visibility(), + is_abstract, + imports, + exports, + } + } + + pub fn name(&self) -> SymbolName { + self.name + } + + pub fn visibility(&self) -> Visibility { + self.visibility + } + + pub fn is_externally_defined(&self) -> bool { + self.is_abstract + } + + pub fn imports(&self) -> &FxHashMap { + &self.imports + } + + pub fn exports(&self) -> &FxHashMap { + &self.exports + } +} + +#[derive(Clone, PartialEq, Eq)] +pub enum ModuleExport { + /// A global symbol with the given type (if known/specified) + /// + /// NOTE: Global variables are _not_ exportable across component boundaries + #[allow(unused)] + Global { name: SymbolName, ty: Option }, + /// A function symbol with the given signature + Function { + name: SymbolName, + signature: Signature, + }, +} diff --git a/hir/src/dialects/builtin/ops/function.rs b/hir/src/dialects/builtin/ops/function.rs new file mode 100644 index 000000000..95004f693 --- /dev/null +++ b/hir/src/dialects/builtin/ops/function.rs @@ -0,0 +1,358 @@ +use alloc::format; + +use smallvec::SmallVec; + +use crate::{ + define_attr_type, + derive::operation, + dialects::builtin::BuiltinDialect, + traits::{AnyType, IsolatedFromAbove, ReturnLike, SingleRegion, Terminator}, + AttrPrinter, BlockRef, CallableOpInterface, Context, Ident, Immediate, Op, OpPrinter, + OpPrintingFlags, Operation, RegionKind, RegionKindInterface, RegionRef, Signature, Symbol, + SymbolName, SymbolUse, SymbolUseList, Type, UnsafeIntrusiveEntityRef, Usable, ValueRef, + Visibility, +}; + +trait UsableSymbol = Usable; + +pub type FunctionRef = UnsafeIntrusiveEntityRef; + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct LocalVariable { + function: FunctionRef, + index: u16, +} + +impl LocalVariable { + fn new(function: FunctionRef, id: usize) -> Self { + assert!( + id <= u16::MAX as usize, + "system limit: unable to allocate more than u16::MAX locals per function" + ); + Self { + function, + index: id as u16, + } + } + + #[inline(always)] + pub const fn as_usize(&self) -> usize { + self.index as usize + } + + pub fn ty(&self) -> Type { + self.function.borrow().get_local(self).clone() + } + + /// Compute the absolute offset from the start of the procedure locals for this local variable + pub fn absolute_offset(&self) -> usize { + let index = self.as_usize(); + self.function.borrow().locals()[..index] + .iter() + .map(|ty| ty.size_in_felts()) + .sum::() + } +} + +impl core::fmt::Debug for LocalVariable { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("LocalVariable") + .field_with("function", |f| write!(f, "{}", self.function.borrow().name().as_str())) + .field("index", &self.index) + .finish() + } +} + +define_attr_type!(LocalVariable); + +impl AttrPrinter for LocalVariable { + fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> crate::formatter::Document { + use crate::formatter::*; + + text(format!("lv{}", self.as_usize())) + } +} + +#[operation( + dialect = BuiltinDialect, + traits(SingleRegion, IsolatedFromAbove), + implements( + UsableSymbol, + Symbol, + CallableOpInterface, + RegionKindInterface, + OpPrinter + ) +)] +pub struct Function { + #[attr] + name: Ident, + #[attr] + signature: Signature, + #[region] + body: RegionRef, + /// The set of local variables allocated within this function + #[default] + locals: SmallVec<[Type; 2]>, + /// The uses of this function as a symbol + #[default] + uses: SymbolUseList, +} + +impl OpPrinter for Function { + fn print(&self, flags: &OpPrintingFlags, _context: &Context) -> crate::formatter::Document { + use crate::formatter::*; + + let signature = self.signature(); + let prelude = display(signature.visibility) + + const_text(" ") + + display(self.as_operation().name()) + + text(format!(" @{}", self.name().as_str())); + let arglist = if self.body().is_empty() { + // Declaration + signature.params().iter().enumerate().fold(const_text("("), |doc, (i, param)| { + let doc = if i > 0 { doc + const_text(", ") } else { doc }; + let mut param_attrs = Document::Empty; + match param.purpose { + crate::ArgumentPurpose::Default => (), + crate::ArgumentPurpose::StructReturn => { + param_attrs += const_text("sret "); + } + } + match param.extension { + crate::ArgumentExtension::None => (), + crate::ArgumentExtension::Zext => { + param_attrs += const_text("zext "); + } + crate::ArgumentExtension::Sext => { + param_attrs += const_text("sext "); + } + } + doc + display(¶m.ty) + }) + const_text(")") + } else { + let body = self.body(); + let entry = body.entry(); + entry.arguments().iter().zip(signature.params().iter()).enumerate().fold( + const_text("("), + |doc, (i, (entry_arg, param))| { + let doc = if i > 0 { doc + const_text(", ") } else { doc }; + let mut param_attrs = Document::Empty; + match param.purpose { + crate::ArgumentPurpose::Default => (), + crate::ArgumentPurpose::StructReturn => { + param_attrs += const_text("sret "); + } + } + match param.extension { + crate::ArgumentExtension::None => (), + crate::ArgumentExtension::Zext => { + param_attrs += const_text("zext "); + } + crate::ArgumentExtension::Sext => { + param_attrs += const_text("sext "); + } + } + doc + display(*entry_arg as ValueRef) + const_text(": ") + display(¶m.ty) + }, + ) + const_text(")") + }; + + let results = + signature + .results() + .iter() + .enumerate() + .fold(Document::Empty, |doc, (i, result)| { + if i > 0 { + doc + const_text(", ") + display(&result.ty) + } else { + doc + display(&result.ty) + } + }); + let results = if results.is_empty() { + results + } else { + const_text(" -> ") + results + }; + + let signature = prelude + arglist + results; + if self.body().is_empty() { + signature + const_text(";") + } else { + signature + const_text(" ") + self.body().print(flags) + const_text(";") + } + } +} + +/// Builders +impl Function { + /// Conver this function from a declaration (no body) to a definition (has a body) by creating + /// the entry block based on the function signature. + /// + /// NOTE: The resulting function is _invalid_ until the block has a terminator inserted into it. + /// + /// This function will panic if an entry block has already been created + pub fn create_entry_block(&mut self) -> BlockRef { + assert!(self.body().is_empty(), "entry block already exists"); + let signature = self.signature(); + let block = self + .as_operation() + .context() + .create_block_with_params(signature.params().iter().map(|p| p.ty.clone())); + let mut body = self.body_mut(); + body.push_back(block); + block + } +} + +/// Accessors +impl Function { + #[inline] + pub fn entry_block(&self) -> BlockRef { + self.body() + .body() + .front() + .as_pointer() + .expect("cannot get entry block for declaration") + } + + pub fn last_block(&self) -> BlockRef { + self.body() + .body() + .back() + .as_pointer() + .expect("cannot access blocks of a function declaration") + } + + pub fn num_locals(&self) -> usize { + self.locals.len() + } + + #[inline] + pub fn locals(&self) -> &[Type] { + &self.locals + } + + #[inline] + pub fn get_local(&self, id: &LocalVariable) -> &Type { + assert_eq!( + self.as_operation_ref(), + id.function.as_operation_ref(), + "attempted to use local variable reference from different function" + ); + &self.locals[id.as_usize()] + } + + pub fn alloc_local(&mut self, ty: Type) -> LocalVariable { + let id = self.locals.len(); + self.locals.push(ty); + LocalVariable::new(self.as_function_ref(), id) + } + + #[inline(always)] + pub fn as_function_ref(&self) -> FunctionRef { + unsafe { FunctionRef::from_raw(self) } + } +} + +impl RegionKindInterface for Function { + #[inline(always)] + fn kind(&self) -> RegionKind { + RegionKind::SSA + } +} + +impl Usable for Function { + type Use = SymbolUse; + + #[inline(always)] + fn uses(&self) -> &SymbolUseList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut SymbolUseList { + &mut self.uses + } +} + +impl Symbol for Function { + #[inline(always)] + fn as_symbol_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn name(&self) -> SymbolName { + Self::name(self).as_symbol() + } + + fn set_name(&mut self, name: SymbolName) { + self.name_mut().name = name; + } + + fn visibility(&self) -> Visibility { + self.signature().visibility + } + + fn set_visibility(&mut self, visibility: Visibility) { + self.signature_mut().visibility = visibility; + } + + /// Returns true if this operation is a declaration, rather than a definition, of a symbol + /// + /// The default implementation assumes that all operations are definitions + #[inline] + fn is_declaration(&self) -> bool { + self.body().is_empty() + } +} + +impl CallableOpInterface for Function { + fn get_callable_region(&self) -> Option { + if self.is_declaration() { + None + } else { + self.op.regions().front().as_pointer() + } + } + + #[inline] + fn signature(&self) -> &Signature { + Function::signature(self) + } +} + +/// Returns from the enclosing function with the provided operands as its results. +#[operation( + dialect = BuiltinDialect, + traits(Terminator, ReturnLike), +)] +pub struct Ret { + #[operands] + values: AnyType, +} + +/// Returns from the enclosing function with the provided immediate value as its result. +#[operation( + dialect = BuiltinDialect, + traits(Terminator, ReturnLike), + implements(OpPrinter) +)] +pub struct RetImm { + #[attr(hidden)] + value: Immediate, +} + +impl OpPrinter for RetImm { + fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> crate::formatter::Document { + use crate::formatter::*; + + display(self.op.name()) + const_text(" ") + display(self.value()) + const_text(";") + } +} diff --git a/hir/src/dialects/builtin/ops/global_variable.rs b/hir/src/dialects/builtin/ops/global_variable.rs new file mode 100644 index 000000000..c46c0cf10 --- /dev/null +++ b/hir/src/dialects/builtin/ops/global_variable.rs @@ -0,0 +1,206 @@ +use smallvec::smallvec; + +use crate::{ + derive::operation, + dialects::builtin::BuiltinDialect, + effects::{ + AlwaysSpeculatable, ConditionallySpeculatable, EffectIterator, EffectOpInterface, + MemoryEffect, MemoryEffectOpInterface, Pure, + }, + traits::{ + InferTypeOpInterface, IsolatedFromAbove, NoRegionArguments, PointerOf, SingleBlock, + SingleRegion, UInt8, + }, + AsSymbolRef, Context, Ident, Op, OpPrinter, Operation, PointerType, Report, Spanned, Symbol, + SymbolName, SymbolRef, SymbolUseList, Type, UnsafeIntrusiveEntityRef, Usable, Value, + Visibility, +}; + +pub type GlobalVariableRef = UnsafeIntrusiveEntityRef; + +/// A [GlobalVariable] represents a named, typed, location in memory. +/// +/// Global variables may also specify an initializer, but if not provided, the underlying bytes +/// will be zeroed, which may or may not be a valid instance of the type. It is up to frontends +/// to ensure that an initializer is specified if necessary. +/// +/// Global variables, like functions, may also be assigned a visibility. This is only used when +/// resolving symbol uses, and does not impose any access restrictions once lowered to Miden +/// Assembly. +#[operation( + dialect = BuiltinDialect, + traits( + SingleRegion, + SingleBlock, + NoRegionArguments, + IsolatedFromAbove, + ), + implements(Symbol, OpPrinter) +)] +pub struct GlobalVariable { + #[attr] + name: Ident, + #[attr] + visibility: Visibility, + #[attr] + ty: Type, + #[region] + initializer: RegionRef, + #[default] + uses: SymbolUseList, +} + +impl GlobalVariable { + #[inline(always)] + pub fn as_global_var_ref(&self) -> GlobalVariableRef { + unsafe { GlobalVariableRef::from_raw(self) } + } +} + +impl Usable for GlobalVariable { + type Use = crate::SymbolUse; + + #[inline(always)] + fn uses(&self) -> &SymbolUseList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut SymbolUseList { + &mut self.uses + } +} + +impl Symbol for GlobalVariable { + #[inline(always)] + fn as_symbol_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn name(&self) -> SymbolName { + GlobalVariable::name(self).as_symbol() + } + + fn set_name(&mut self, name: SymbolName) { + let id = self.name_mut(); + id.name = name; + } + + fn visibility(&self) -> Visibility { + *GlobalVariable::visibility(self) + } + + fn set_visibility(&mut self, visibility: Visibility) { + *self.visibility_mut() = visibility; + } + + /// Returns true if this operation is a declaration, rather than a definition, of a symbol + /// + /// The default implementation assumes that all operations are definitions + #[inline] + fn is_declaration(&self) -> bool { + self.initializer().is_empty() + } +} + +impl AsSymbolRef for GlobalVariable { + fn as_symbol_ref(&self) -> SymbolRef { + unsafe { SymbolRef::from_raw(self as &dyn Symbol) } + } +} + +impl OpPrinter for GlobalVariable { + fn print( + &self, + flags: &crate::OpPrintingFlags, + _context: &crate::Context, + ) -> crate::formatter::Document { + use crate::formatter::*; + + let header = display(self.op.name()) + + const_text(" ") + + display(self.visibility()) + + const_text(" @") + + display(self.name()) + + const_text(" : ") + + display(self.ty()); + let body = crate::print::render_regions(&self.op, flags); + header + body + } +} + +/// A [GlobalSymbol] reifies the address of a [GlobalVariable] as a value. +/// +/// An optional signed offset value may also be provided, which will be applied by the operation +/// internally. +/// +/// The result type is always a pointer, whose pointee type is derived from the referenced symbol. +#[operation( + dialect = BuiltinDialect, + traits(Pure, AlwaysSpeculatable), + implements(InferTypeOpInterface, OpPrinter, ConditionallySpeculatable, MemoryEffectOpInterface) +)] +pub struct GlobalSymbol { + /// The name of the global variable that is referenced + #[symbol] + symbol: GlobalVariable, + /// A constant offset, in bytes, from the address of the symbol + #[attr] + #[default] + offset: i32, + #[result] + addr: PointerOf, +} + +impl OpPrinter for GlobalSymbol { + fn print( + &self, + _flags: &crate::OpPrintingFlags, + _context: &crate::Context, + ) -> crate::formatter::Document { + use crate::formatter::*; + + let results = crate::print::render_operation_results(self.as_operation()); + let prefix = results + + display(self.op.name()) + + const_text(" ") + + const_text("@") + + display(&self.symbol().path); + + let offset = *self.offset(); + let doc = match *self.offset() { + 0 => prefix, + n if n > 0 => prefix + const_text("+") + display(offset), + _ => prefix + const_text("-") + display(offset), + }; + + doc + const_text(" : ") + display(self.addr().ty()) + } +} + +impl ConditionallySpeculatable for GlobalSymbol { + fn speculatability(&self) -> crate::effects::Speculatability { + crate::effects::Speculatability::Speculatable + } +} +impl EffectOpInterface for GlobalSymbol { + fn effects(&self) -> crate::effects::EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } + + fn has_no_effect(&self) -> bool { + true + } +} + +impl InferTypeOpInterface for GlobalSymbol { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.addr_mut().set_type(Type::from(PointerType::new(Type::U8))); + Ok(()) + } +} diff --git a/hir/src/dialects/builtin/ops/interface.rs b/hir/src/dialects/builtin/ops/interface.rs new file mode 100644 index 000000000..856b18a99 --- /dev/null +++ b/hir/src/dialects/builtin/ops/interface.rs @@ -0,0 +1,153 @@ +use midenc_session::LibraryPath; + +use crate::{ + derive::operation, + dialects::builtin::{self, BuiltinDialect}, + traits::{ + GraphRegionNoTerminator, HasOnlyGraphRegion, IsolatedFromAbove, NoRegionArguments, + NoTerminator, SingleBlock, SingleRegion, + }, + Ident, Op, Operation, RegionKind, RegionKindInterface, Symbol, SymbolManager, SymbolManagerMut, + SymbolMap, SymbolName, SymbolRef, SymbolTable, SymbolUseList, UnsafeIntrusiveEntityRef, Usable, + Visibility, +}; + +pub type InterfaceRef = UnsafeIntrusiveEntityRef; + +/// An [Interface] is a modular abstraction operation, i.e. it is designed to model groups of +/// related functionality meant to be produced/consumed together. +/// +/// An [Interface] itself represents a shared-nothing boundary, i.e. the functionality it exports +/// uses the Canonical ABI of the Wasm Component Model. However, it is possible for a [Component] +/// to export multiple interfaces which are implemented within the same shared-everything boundary. +/// Even when that is the case, calls to any [Interface] export from within that boundary, will +/// still be treated as crossing a shared-nothing boundary. In this way, components can both define +/// and re-export interfaces from other components, without callers needing to know where the +/// actual definition is provided from. +/// +/// Interfaces correspond to component _instances_ exported from a component _definition_ in the +/// Wasm Component Model. This means that they are almost identical concepts, however we distinguish +/// between [Component] and [Interface] in the IR to better model the relationships between these +/// concepts, as well as to draw a connection to interfaces in WIT (WebAssembly Interface Types). +/// +/// ## Contents +/// +/// Interfaces may only contain [Function] items, and may only _export_ functions with the +/// `CanonLift` calling convention. It is expected that these functions will rely on implementation +/// details defined in a sibling [Module], though that is not strictly required. +#[operation( + dialect = BuiltinDialect, + traits( + SingleRegion, + SingleBlock, + NoRegionArguments, + NoTerminator, + HasOnlyGraphRegion, + GraphRegionNoTerminator, + IsolatedFromAbove, + ), + implements(RegionKindInterface, SymbolTable, Symbol) +)] +pub struct Interface { + #[attr] + name: Ident, + #[region] + body: RegionRef, + #[default] + symbols: SymbolMap, + #[default] + uses: SymbolUseList, +} + +impl RegionKindInterface for Interface { + #[inline(always)] + fn kind(&self) -> RegionKind { + RegionKind::Graph + } +} + +impl Usable for Interface { + type Use = crate::SymbolUse; + + #[inline(always)] + fn uses(&self) -> &SymbolUseList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut SymbolUseList { + &mut self.uses + } +} + +impl Symbol for Interface { + #[inline(always)] + fn as_symbol_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn name(&self) -> SymbolName { + Interface::name(self).as_symbol() + } + + fn set_name(&mut self, name: SymbolName) { + let id = self.name_mut(); + id.name = name; + } + + fn visibility(&self) -> Visibility { + Visibility::Public + } + + fn set_visibility(&mut self, visibility: Visibility) { + assert_eq!( + visibility, + Visibility::Public, + "cannot give interfaces a visibility other than public" + ); + } +} + +impl SymbolTable for Interface { + #[inline(always)] + fn as_symbol_table_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_table_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn symbol_manager(&self) -> SymbolManager<'_> { + SymbolManager::new(&self.op, crate::Symbols::Borrowed(&self.symbols)) + } + + fn symbol_manager_mut(&mut self) -> SymbolManagerMut<'_> { + SymbolManagerMut::new(&mut self.op, crate::SymbolsMut::Borrowed(&mut self.symbols)) + } + + #[inline] + fn get(&self, name: SymbolName) -> Option { + self.symbols.get(name) + } +} + +impl Interface { + /// Get the Miden Assembly [LibraryPath] that uniquely identifies this interface. + pub fn library_path(&self) -> Option { + let parent = self.as_operation().parent_op()?; + let parent = parent.borrow(); + let component = parent + .downcast_ref::() + .expect("invalid parent for interface operation: expected component"); + let component_id = component.id(); + let path = component_id.to_library_path(); + Some(path.append_unchecked(self.name().as_str())) + } +} diff --git a/hir/src/dialects/builtin/ops/module.rs b/hir/src/dialects/builtin/ops/module.rs new file mode 100644 index 000000000..bf20c76f4 --- /dev/null +++ b/hir/src/dialects/builtin/ops/module.rs @@ -0,0 +1,196 @@ +use crate::{ + derive::operation, + dialects::builtin::BuiltinDialect, + traits::{ + GraphRegionNoTerminator, HasOnlyGraphRegion, IsolatedFromAbove, NoRegionArguments, + NoTerminator, SingleBlock, SingleRegion, + }, + Ident, OpPrinter, Operation, RegionKind, RegionKindInterface, Symbol, SymbolManager, + SymbolManagerMut, SymbolMap, SymbolName, SymbolRef, SymbolTable, SymbolUseList, + UnsafeIntrusiveEntityRef, Usable, Visibility, +}; + +pub type ModuleRef = UnsafeIntrusiveEntityRef; + +/// A [Module] is a namespaced container for [Function] definitions, and represents the most atomic +/// translation unit that supports compilation to Miden Assembly. +/// +/// [Module] cannot be nested, use [Component] for such use cases. +/// +/// Modules can contain one of the following entities: +/// +/// * [Segment], describing how a specific region of memory should be initialized (i.e. what content +/// it should be assumed to contain on program start). Segment definitions must not conflict +/// within a shared-everything boundary. For example, multiple segments within the same module, +/// or segments defined in sibling modules of the same [Component]. +/// * [Function], either a declaration of an externally-defined function, or a definition. +/// Declarations are required in order to reference functions which are not in the compilation +/// graph, but are expected to be provided at runtime. The difference between the two depends on +/// whether or not the [Function] operation has a region (no region == declaration). +/// * [GlobalVariable], either a declaration of an externally-defined global, or a definition, same +/// as [Function]. +/// +/// Multiple modules can be grouped together into a [Component]. Doing so allows interprocedural +/// analysis to reason across call boundaries for functions defined in different modules, in +/// particular, dead code analysis. +/// +/// Modules may also have a specified [Visibility]: +/// +/// * `Visibility::Public` indicates that all functions exported from the module with `Public` +/// visibility form the public interface of the module, and thus are not permitted to be dead- +/// code eliminated, or otherwise rewritten by optimizations in a way that changes the public +/// interface. +/// * `Visibility::Internal` indicates that all functions exported from the module with `Public` +/// or `Internal` visibility are only visibile by modules in the current compilation graph, and +/// are thus eligible for dead-code elimination or other invasive rewrites so long as all +/// callsites are known statically. If the address of any of those functions is captured, they +/// must not be modified. +/// * `Visibility::Private` indicates that the module and its exports are only visible to other +/// modules in the same [Component], and otherwise adheres to the same rules as `Internal`. +#[operation( + dialect = BuiltinDialect, + traits( + SingleRegion, + SingleBlock, + NoRegionArguments, + NoTerminator, + HasOnlyGraphRegion, + GraphRegionNoTerminator, + IsolatedFromAbove, + ), + implements(RegionKindInterface, SymbolTable, Symbol, OpPrinter) +)] +pub struct Module { + #[attr] + name: Ident, + #[attr] + #[default] + visibility: Visibility, + #[region] + body: RegionRef, + #[default] + symbols: SymbolMap, + #[default] + uses: SymbolUseList, +} + +impl Module { + #[inline(always)] + pub fn as_module_ref(&self) -> ModuleRef { + unsafe { ModuleRef::from_raw(self) } + } +} + +impl OpPrinter for Module { + fn print( + &self, + flags: &crate::OpPrintingFlags, + _context: &crate::Context, + ) -> crate::formatter::Document { + use crate::formatter::*; + + let header = display(self.op.name()) + + const_text(" ") + + display(self.visibility()) + + const_text(" @") + + display(self.name().as_str()); + let body = crate::print::render_regions(&self.op, flags); + header + body + } +} + +impl midenc_session::Emit for Module { + fn name(&self) -> Option { + Some(self.name().as_symbol()) + } + + fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { + midenc_session::OutputType::Hir + } + + fn write_to( + &self, + mut writer: W, + _mode: midenc_session::OutputMode, + _session: &midenc_session::Session, + ) -> anyhow::Result<()> { + let flags = crate::OpPrintingFlags::default(); + let document = ::print(self, &flags, self.op.context()); + writer.write_fmt(format_args!("{document}")) + } +} + +impl RegionKindInterface for Module { + #[inline(always)] + fn kind(&self) -> RegionKind { + RegionKind::Graph + } +} + +impl Usable for Module { + type Use = crate::SymbolUse; + + #[inline(always)] + fn uses(&self) -> &SymbolUseList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut SymbolUseList { + &mut self.uses + } +} + +impl Symbol for Module { + #[inline(always)] + fn as_symbol_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn name(&self) -> SymbolName { + Module::name(self).as_symbol() + } + + fn set_name(&mut self, name: SymbolName) { + let id = self.name_mut(); + id.name = name; + } + + fn visibility(&self) -> Visibility { + *Module::visibility(self) + } + + fn set_visibility(&mut self, visibility: Visibility) { + *self.visibility_mut() = visibility; + } +} + +impl SymbolTable for Module { + #[inline(always)] + fn as_symbol_table_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_table_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn symbol_manager(&self) -> SymbolManager<'_> { + SymbolManager::new(&self.op, crate::Symbols::Borrowed(&self.symbols)) + } + + fn symbol_manager_mut(&mut self) -> SymbolManagerMut<'_> { + SymbolManagerMut::new(&mut self.op, crate::SymbolsMut::Borrowed(&mut self.symbols)) + } + + #[inline] + fn get(&self, name: SymbolName) -> Option { + self.symbols.get(name) + } +} diff --git a/hir/src/dialects/builtin/ops/segment.rs b/hir/src/dialects/builtin/ops/segment.rs new file mode 100644 index 000000000..354461b25 --- /dev/null +++ b/hir/src/dialects/builtin/ops/segment.rs @@ -0,0 +1,267 @@ +use alloc::{collections::VecDeque, format, sync::Arc}; +use core::fmt; + +use midenc_hir_macros::operation; +use midenc_session::diagnostics::{miette, Diagnostic}; + +use crate::{ + constants::{ConstantData, ConstantId}, + dialects::builtin::BuiltinDialect, + traits::*, + Alignable, Op, OpPrinter, UnsafeIntrusiveEntityRef, +}; + +pub type SegmentRef = UnsafeIntrusiveEntityRef; + +/// Declare a data segment in the shared memory of a [Component]. +/// +/// This operation type is only permitted in the body of a [Module] op, it is an error to use it +/// anywhere else. At best it will be ignored. +/// +/// Data segments can have a size that is larger than the initializer data it describes; in such +/// cases, the remaining memory is either assumed to be arbitrary bytes, or if `zeroed` is set, +/// it is zeroed so that the padding bytes are all zero. +/// +/// A data segment can be marked `readonly`, which indicates to the optimizer that it is allowed +/// to assume that no writes will ever occur in the boundaries of the segment, i.e. a value loaded +/// from within those bounds does not need to be reloaded after side-effecting operations, and +/// can in fact be rescheduled around them. Additionally, if a write is detected that would effect +/// memory in a readonly data segment boundary, an error will be raised. +/// +/// NOTE: It is not guaranteed that the optimizer will make any assumptions with regard to data +/// segments. For the moment, even if `readonly` is set, the compiler assumes that segments are +/// mutable. +#[operation( + dialect = BuiltinDialect, + traits( + SingleBlock, + NoRegionArguments, + IsolatedFromAbove, + ), + implements(OpPrinter) +)] +pub struct Segment { + /// The offset from the start of linear memory where this segment starts + #[attr] + offset: u32, + /// The data to initialize this segment with, determines the size of the segment + #[attr] + data: ConstantId, + /// Whether or not this segment is intended to be read-only data + #[attr] + #[default] + readonly: bool, +} + +impl Segment { + /// The size, in bytes, of this data segment. + /// + /// By default this will be the same size as `init`, unless explicitly given. + pub fn size_in_bytes(&self) -> usize { + let id = *self.data(); + self.as_operation().context().get_constant_size_in_bytes(id) + } + + /// Get the data, as bytes, to initialize this data segment with. + pub fn initializer(&self) -> Arc { + let id = *self.data(); + self.as_operation().context().get_constant(id) + } +} + +impl fmt::Debug for Segment { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let data = self.initializer(); + f.debug_struct("Segment") + .field("offset", self.offset()) + .field("size", &data.len()) + .field("init", &format_args!("{data}")) + .field("readonly", self.readonly()) + .finish() + } +} + +impl OpPrinter for Segment { + fn print( + &self, + _flags: &crate::OpPrintingFlags, + _context: &crate::Context, + ) -> crate::formatter::Document { + use crate::formatter::*; + + let header = display(self.op.name()); + let header = if *self.readonly() { + header + const_text(" ") + const_text("readonly") + } else { + header + }; + let header = header + const_text(" ") + text(format!("@{}", self.offset())); + let data = self.initializer(); + header + const_text(" = ") + text(format!("0x{data};")) + } +} + +/// This error is raised when attempting to declare a [Segment] that in some way conflicts with +/// previously declared data segments. +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum DataSegmentError { + /// The current segment overlaps with a previously allocated segment + #[error( + "invalid data segment: segment of {size1} bytes at {offset1:#x} overlaps with segment of \ + {size2} bytes at {offset2:#x}" + )] + #[diagnostic()] + OverlappingSegments { + offset1: u32, + size1: u32, + offset2: u32, + size2: u32, + }, + /// The current segment and a previous definition of that segment do + /// not agree on the data or read/write properties of the memory they + /// represent. + #[error( + "invalid data segment: segment at {0:#x} conflicts with a previous segment declaration at \ + this address" + )] + #[diagnostic()] + Mismatch(u32), + /// The current segment and size do not fall in the boundaries of the heap + /// which is allocatable to globals and other heap allocations. + /// + /// For example, Miden reserves some amount of memory for procedure locals + /// at a predetermined address, and we do not permit segments to be allocated + /// past that point. + #[error( + "invalid data segment: segment of {size} bytes at {offset:#x} would extend beyond the end \ + of the usable heap" + )] + #[diagnostic()] + OutOfBounds { offset: u32, size: u32 }, + /// The initializer for the current segment has a size greater than `u32::MAX` bytes + #[error( + "invalid data segment: segment at {0:#x} was declared with an initializer larger than \ + 2^32 bytes" + )] + #[diagnostic()] + InitTooLarge(u32), + /// The initializer for the current segment has a size greater than the declared segment size + #[error( + "invalid data segment: segment of {size} bytes at {offset:#x} has an initializer of \ + {actual} bytes" + )] + #[diagnostic()] + InitOutOfBounds { offset: u32, size: u32, actual: u32 }, +} + +/// This structure tracks a set of data segments to be placed in the same address space, and ensures +/// that the segments are laid out in that space without conflict. +#[derive(Default, Clone)] +pub struct DataSegmentLayout { + segments: VecDeque, +} + +impl DataSegmentLayout { + /// Returns true if the table has no segments defined + pub fn is_empty(&self) -> bool { + self.segments.is_empty() + } + + /// Returns the number of segments in the layout + pub fn len(&self) -> usize { + self.segments.len() + } + + /// Returns the offset in linear memory where the last data segment ends + pub fn next_available_offset(&self) -> u32 { + if let Some(last_segment) = self.segments.back() { + let last_segment = last_segment.borrow(); + let next_offset = *last_segment.offset() + last_segment.size_in_bytes() as u32; + // Ensure the start of the next segment is word-aligned + next_offset.align_up(32) + } else { + 0 + } + } + + /// Insert a [Segment] into the layout, while preserving the order of the segments. + /// + /// This will fail if the segment is invalid, or overlaps/conflicts with an existing segment. + pub fn insert(&mut self, segment_ref: SegmentRef) -> Result<(), DataSegmentError> { + if self.is_empty() { + self.segments.push_back(segment_ref); + return Ok(()); + } + + let segment = segment_ref.borrow(); + let offset = *segment.offset(); + let size = u32::try_from(segment.size_in_bytes()) + .map_err(|_| DataSegmentError::InitTooLarge(offset))?; + let end = offset + size; + for (index, current_segment_ref) in self.segments.iter().enumerate() { + let current_segment = current_segment_ref.borrow(); + let current_offset = *current_segment.offset(); + let current_size = current_segment.size_in_bytes() as u32; + let segment_end = current_offset + current_size; + + // If this segment starts after the segment we're declaring, we do not need to continue + // searching for conflicts, and can go a head and perform the insert + if current_offset >= end { + self.segments.insert(index, segment_ref); + return Ok(()); + } + + // If this segment starts at the same place as the one we're declaring that's a + // guaranteed conflict + if current_offset == offset { + // If the two segments have the same size and offset, then + // if they match in all other respects, we're done. If they + // don't match, then we raise a mismatch error. + if current_size == size + && current_segment.initializer() == segment.initializer() + && current_segment.readonly() == segment.readonly() + { + return Ok(()); + } + return Err(DataSegmentError::Mismatch(offset)); + } + + // This segment starts before the segment we're declaring, make sure that this segment + // ends before our segment starts + if segment_end > offset { + return Err(DataSegmentError::OverlappingSegments { + offset1: offset, + size1: size, + offset2: current_offset, + size2: current_size, + }); + } + } + + self.segments.push_back(segment_ref); + + Ok(()) + } + + /// Traverse the data segments in the table in ascending order by offset + pub fn iter(&self) -> impl Iterator + '_ { + self.segments.iter().copied() + } + + /// Remove the first data segment from the table + #[inline] + pub fn pop_front(&mut self) -> Option { + self.segments.pop_front() + } +} + +impl fmt::Debug for DataSegmentLayout { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut builder = f.debug_list(); + for segment in self.segments.iter() { + let segment = segment.borrow(); + builder.entry(&segment); + } + builder.finish() + } +} diff --git a/hir/src/dialects/builtin/ops/world.rs b/hir/src/dialects/builtin/ops/world.rs new file mode 100644 index 000000000..9a922b632 --- /dev/null +++ b/hir/src/dialects/builtin/ops/world.rs @@ -0,0 +1,88 @@ +use crate::{ + derive::operation, + dialects::builtin::BuiltinDialect, + traits::{ + GraphRegionNoTerminator, HasOnlyGraphRegion, IsolatedFromAbove, NoRegionArguments, + NoTerminator, SingleBlock, SingleRegion, + }, + Operation, RegionKind, RegionKindInterface, SymbolManager, SymbolManagerMut, SymbolMap, + SymbolName, SymbolRef, SymbolTable, SymbolUseList, UnsafeIntrusiveEntityRef, Usable, +}; + +pub type WorldRef = UnsafeIntrusiveEntityRef; + +/// A [World] is a component abstraction operation, i.e. it is designed to tie particular +/// [Component]s together. +/// +/// Worlds can contain only [Component]s. +/// +/// NOTE: Worlds always have `Public` visibility. +/// +/// Worlds are linked into Miden Assembly according to the following rules: +#[operation( + dialect = BuiltinDialect, + traits( + SingleRegion, + SingleBlock, + NoRegionArguments, + NoTerminator, + HasOnlyGraphRegion, + GraphRegionNoTerminator, + IsolatedFromAbove, + ), + implements(RegionKindInterface, SymbolTable) +)] +pub struct World { + #[region] + body: RegionRef, + #[default] + symbols: SymbolMap, + #[default] + uses: SymbolUseList, +} + +impl RegionKindInterface for World { + #[inline(always)] + fn kind(&self) -> RegionKind { + RegionKind::Graph + } +} + +impl Usable for World { + type Use = crate::SymbolUse; + + #[inline(always)] + fn uses(&self) -> &SymbolUseList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut SymbolUseList { + &mut self.uses + } +} + +impl SymbolTable for World { + #[inline(always)] + fn as_symbol_table_operation(&self) -> &Operation { + &self.op + } + + #[inline(always)] + fn as_symbol_table_operation_mut(&mut self) -> &mut Operation { + &mut self.op + } + + fn symbol_manager(&self) -> SymbolManager<'_> { + SymbolManager::new(&self.op, crate::Symbols::Borrowed(&self.symbols)) + } + + fn symbol_manager_mut(&mut self) -> SymbolManagerMut<'_> { + SymbolManagerMut::new(&mut self.op, crate::SymbolsMut::Borrowed(&mut self.symbols)) + } + + #[inline] + fn get(&self, name: SymbolName) -> Option { + self.symbols.get(name) + } +} diff --git a/hir/src/dialects/test.rs b/hir/src/dialects/test.rs new file mode 100644 index 000000000..049e8874b --- /dev/null +++ b/hir/src/dialects/test.rs @@ -0,0 +1,126 @@ +mod builders; +mod ops; + +use alloc::boxed::Box; + +pub use self::{builders::TestOpBuilder, ops::*}; +use crate::{ + AttributeValue, Builder, BuilderExt, Dialect, DialectInfo, DialectRegistration, Immediate, + OperationRef, SourceSpan, Type, +}; + +#[derive(Debug)] +pub struct TestDialect { + info: DialectInfo, +} + +impl TestDialect { + #[inline] + pub fn num_registered(&self) -> usize { + self.registered_ops().len() + } +} + +impl Dialect for TestDialect { + #[inline] + fn info(&self) -> &DialectInfo { + &self.info + } + + fn materialize_constant( + &self, + builder: &mut dyn Builder, + attr: Box, + ty: &Type, + span: SourceSpan, + ) -> Option { + use crate::Op; + + // Save the current insertion point + let mut builder = crate::InsertionGuard::new(builder); + + // Only integer constants are supported for now + if !ty.is_integer() { + return None; + } + + // Currently, we expect folds to produce `Immediate`-valued attributes + if let Some(&imm) = attr.downcast_ref::() { + // If the immediate value is of the same type as the expected result type, we're ready + // to materialize the constant + let imm_ty = imm.ty(); + if &imm_ty == ty { + let op_builder = builder.create::(span); + return op_builder(imm) + .ok() + .map(|op| op.borrow().as_operation().as_operation_ref()); + } + + // The immediate value has a different type than expected, but we can coerce types, so + // long as the value fits in the target type + if imm_ty.size_in_bits() > ty.size_in_bits() { + return None; + } + + let imm = match ty { + Type::I8 => match imm { + Immediate::I1(value) => Immediate::I8(value as i8), + Immediate::U8(value) => Immediate::I8(i8::try_from(value).ok()?), + _ => return None, + }, + Type::U8 => match imm { + Immediate::I1(value) => Immediate::U8(value as u8), + Immediate::I8(value) => Immediate::U8(u8::try_from(value).ok()?), + _ => return None, + }, + Type::I16 => match imm { + Immediate::I1(value) => Immediate::I16(value as i16), + Immediate::I8(value) => Immediate::I16(value as i16), + Immediate::U8(value) => Immediate::I16(value.into()), + Immediate::U16(value) => Immediate::I16(i16::try_from(value).ok()?), + _ => return None, + }, + Type::U16 => match imm { + Immediate::I1(value) => Immediate::U16(value as u16), + Immediate::I8(value) => Immediate::U16(u16::try_from(value).ok()?), + Immediate::U8(value) => Immediate::U16(value as u16), + Immediate::I16(value) => Immediate::U16(u16::try_from(value).ok()?), + _ => return None, + }, + Type::I32 => Immediate::I32(imm.as_i32()?), + Type::U32 => Immediate::U32(imm.as_u32()?), + Type::I64 => Immediate::I64(imm.as_i64()?), + Type::U64 => Immediate::U64(imm.as_u64()?), + Type::I128 => Immediate::I128(imm.as_i128()?), + Type::U128 => Immediate::U128(imm.as_u128()?), + Type::Felt => Immediate::Felt(imm.as_felt()?), + ty => unimplemented!("unrecognized integral type '{ty}'"), + }; + + let op_builder = builder.create::(span); + return op_builder(imm).ok().map(|op| op.borrow().as_operation().as_operation_ref()); + } + + None + } +} + +impl DialectRegistration for TestDialect { + const NAMESPACE: &'static str = "test"; + + #[inline] + fn init(info: DialectInfo) -> Self { + Self { info } + } + + fn register_operations(info: &mut DialectInfo) { + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + info.register_operation::(); + } +} diff --git a/hir/src/dialects/test/builders.rs b/hir/src/dialects/test/builders.rs new file mode 100644 index 000000000..5ff5009ef --- /dev/null +++ b/hir/src/dialects/test/builders.rs @@ -0,0 +1,153 @@ +use crate::{ + dialects::{builtin::FunctionBuilder, test::*}, + Builder, BuilderExt, OpBuilder, Report, UnsafeIntrusiveEntityRef, ValueRef, +}; + +pub trait TestOpBuilder<'f, B: ?Sized + Builder> { + fn u32(&mut self, value: u32, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let constant = op_builder(Immediate::U32(value))?; + Ok(constant.borrow().result().as_value_ref()) + } + + fn eq(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn neq(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement addition which traps on overflow + fn add(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, crate::Overflow::Checked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Unchecked two's complement addition. Behavior is undefined if the result overflows. + fn add_unchecked( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, crate::Overflow::Unchecked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement addition which wraps around on overflow, e.g. `wrapping_add` + fn add_wrapping( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, crate::Overflow::Wrapping)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement multiplication which traps on overflow + fn mul(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, crate::Overflow::Checked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Unchecked two's complement multiplication. Behavior is undefined if the result overflows. + fn mul_unchecked( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, crate::Overflow::Unchecked)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Two's complement multiplication which wraps around on overflow, e.g. `wrapping_mul` + fn mul_wrapping( + &mut self, + lhs: ValueRef, + rhs: ValueRef, + span: SourceSpan, + ) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs, crate::Overflow::Wrapping)?; + Ok(op.borrow().result().as_value_ref()) + } + + fn shl(&mut self, lhs: ValueRef, rhs: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(lhs, rhs)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Loads a value of the type pointed to by the given pointer, on to the stack + /// + /// NOTE: This function will panic if `ptr` is not a pointer typed value + fn load(&mut self, addr: ValueRef, span: SourceSpan) -> Result { + let op_builder = self.builder_mut().create::(span); + let op = op_builder(addr)?; + Ok(op.borrow().result().as_value_ref()) + } + + /// Stores `value` to the address given by `ptr` + /// + /// NOTE: This function will panic if the pointer and pointee types do not match + fn store( + &mut self, + ptr: ValueRef, + value: ValueRef, + span: SourceSpan, + ) -> Result, Report> { + let op_builder = self.builder_mut().create::(span); + op_builder(ptr, value) + } + + fn builder(&self) -> &B; + fn builder_mut(&mut self) -> &mut B; +} + +impl<'f, B: ?Sized + Builder> TestOpBuilder<'f, B> for FunctionBuilder<'f, B> { + #[inline(always)] + fn builder(&self) -> &B { + FunctionBuilder::builder(self) + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + FunctionBuilder::builder_mut(self) + } +} + +impl<'f> TestOpBuilder<'f, OpBuilder> for &'f mut OpBuilder { + #[inline(always)] + fn builder(&self) -> &OpBuilder { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut OpBuilder { + self + } +} + +impl TestOpBuilder<'_, B> for B { + #[inline(always)] + fn builder(&self) -> &B { + self + } + + #[inline(always)] + fn builder_mut(&mut self) -> &mut B { + self + } +} diff --git a/hir/src/dialects/test/ops.rs b/hir/src/dialects/test/ops.rs new file mode 100644 index 000000000..f91e85178 --- /dev/null +++ b/hir/src/dialects/test/ops.rs @@ -0,0 +1,6 @@ +mod binary; +mod constants; +mod control; +mod mem; + +pub use self::{binary::*, constants::*, control::*, mem::*}; diff --git a/hir/src/dialects/test/ops/binary.rs b/hir/src/dialects/test/ops/binary.rs new file mode 100644 index 000000000..01f7e2825 --- /dev/null +++ b/hir/src/dialects/test/ops/binary.rs @@ -0,0 +1,132 @@ +use crate::{derive::operation, dialects::test::TestDialect, effects::*, traits::*, *}; + +macro_rules! infer_return_ty_for_binary_op { + ($Op:ty) => { + impl InferTypeOpInterface for $Op { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let lhs = self.lhs().ty().clone(); + self.result_mut().set_type(lhs); + Ok(()) + } + } + }; + + ($Op:ty as $manually_specified_ty:expr) => { + impl InferTypeOpInterface for $Op { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + self.result_mut().set_type($manually_specified_ty); + Ok(()) + } + } + }; +} + +macro_rules! has_no_effects { + ($Op:ty) => { + impl EffectOpInterface for $Op { + fn has_no_effect(&self) -> bool { + true + } + + fn effects(&self) -> EffectIterator<::midenc_hir::effects::MemoryEffect> { + EffectIterator::from_smallvec(::midenc_hir::smallvec![]) + } + } + }; +} + +/// Two's complement sum +#[operation( + dialect = TestDialect, + traits(BinaryOp, Commutative, SameTypeOperands, SameOperandsAndResultType), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Add { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, + #[attr] + overflow: Overflow, +} + +infer_return_ty_for_binary_op!(Add); +has_no_effects!(Add); + +/// Two's complement product +#[operation( + dialect = TestDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Mul { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: AnyInteger, + #[attr] + overflow: Overflow, +} + +infer_return_ty_for_binary_op!(Mul); +has_no_effects!(Mul); + +/// Bitwise shift-left +/// +/// Shifts larger than the bitwidth of the value will be wrapped to zero. +#[operation( + dialect = TestDialect, + traits(BinaryOp), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Shl { + #[operand] + lhs: AnyInteger, + #[operand] + shift: UInt32, + #[result] + result: AnyInteger, +} + +infer_return_ty_for_binary_op!(Shl); +has_no_effects!(Shl); + +/// Equality comparison +#[operation( + dialect = TestDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Eq { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Eq as Type::I1); +has_no_effects!(Eq); + +/// Inequality comparison +#[operation( + dialect = TestDialect, + traits(BinaryOp, Commutative, SameTypeOperands), + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Neq { + #[operand] + lhs: AnyInteger, + #[operand] + rhs: AnyInteger, + #[result] + result: Bool, +} + +infer_return_ty_for_binary_op!(Neq as Type::I1); +has_no_effects!(Neq); diff --git a/hir/src/dialects/test/ops/constants.rs b/hir/src/dialects/test/ops/constants.rs new file mode 100644 index 000000000..784b66afd --- /dev/null +++ b/hir/src/dialects/test/ops/constants.rs @@ -0,0 +1,50 @@ +use alloc::boxed::Box; + +use crate::{derive::operation, dialects::test::TestDialect, effects::*, traits::*, *}; + +/// An operation for expressing constant immediate values. +/// +/// This is used to materialize folded constants for the HIR dialect. +#[operation( + dialect = TestDialect, + traits(ConstantLike), + implements(InferTypeOpInterface, Foldable, MemoryEffectOpInterface) +)] +pub struct Constant { + #[attr(hidden)] + value: Immediate, + #[result] + result: AnyInteger, +} + +impl InferTypeOpInterface for Constant { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let ty = self.value().ty(); + self.result_mut().set_type(ty); + + Ok(()) + } +} + +impl Foldable for Constant { + #[inline] + fn fold(&self, results: &mut smallvec::SmallVec<[OpFoldResult; 1]>) -> FoldResult { + results.push(OpFoldResult::Attribute(self.get_attribute("value").unwrap().clone_value())); + FoldResult::Ok(()) + } + + #[inline(always)] + fn fold_with( + &self, + _operands: &[Option>], + results: &mut smallvec::SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + self.fold(results) + } +} + +impl EffectOpInterface for Constant { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![]) + } +} diff --git a/hir/src/dialects/test/ops/control.rs b/hir/src/dialects/test/ops/control.rs new file mode 100644 index 000000000..1148e6b35 --- /dev/null +++ b/hir/src/dialects/test/ops/control.rs @@ -0,0 +1,11 @@ +use crate::{derive::operation, dialects::test::TestDialect, traits::*}; + +/// Returns from the enclosing function with the provided operands as its results. +#[operation( + dialect = TestDialect, + traits(Terminator, ReturnLike) +)] +pub struct Ret { + #[operands] + values: AnyType, +} diff --git a/hir/src/dialects/test/ops/mem.rs b/hir/src/dialects/test/ops/mem.rs new file mode 100644 index 000000000..079ba8728 --- /dev/null +++ b/hir/src/dialects/test/ops/mem.rs @@ -0,0 +1,82 @@ +use smallvec::smallvec; + +use crate::{derive::operation, dialects::test::*, effects::*, traits::*, *}; + +/// Store `value` on the heap at `addr` +#[operation( + dialect = TestDialect, + implements(MemoryEffectOpInterface) +)] +pub struct Store { + #[operand] + addr: AnyPointer, + #[operand] + value: AnyType, +} + +impl EffectOpInterface for Store { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Write, + self.addr().as_value_ref() + )]) + } +} + +/// Load `result` from the heap at `addr` +/// +/// The type of load is determined by the pointer operand type - cast the pointer to the type you +/// wish to load, so long as such a load is safe according to the semantics of your high-level +/// language. +#[operation( + dialect = TestDialect, + implements(InferTypeOpInterface, MemoryEffectOpInterface) +)] +pub struct Load { + #[operand] + addr: AnyPointer, + #[result] + result: AnyType, +} + +impl EffectOpInterface for Load { + fn effects(&self) -> EffectIterator { + EffectIterator::from_smallvec(smallvec![EffectInstance::new_for_value( + MemoryEffect::Read, + self.addr().as_value_ref() + )]) + } +} + +impl InferTypeOpInterface for Load { + fn infer_return_types(&mut self, _context: &Context) -> Result<(), Report> { + let _span = self.span(); + let pointee = { + let addr = self.addr(); + let addr_value = addr.value(); + addr_value.ty().pointee().cloned() + }; + match pointee { + Some(pointee) => { + self.result_mut().set_type(pointee); + Ok(()) + } + None => { + // let addr = self.addr(); + // let addr_value = addr.value(); + // let addr_ty = addr_value.ty(); + // Err(context + // .session + // .diagnostics + // .diagnostic(midenc_session::diagnostics::Severity::Error) + // .with_message("invalid operand for 'load'") + // .with_primary_label( + // span, + // format!("invalid 'addr' operand, expected pointer, got '{addr_ty}'"), + // ) + // .into_report()) + Ok(()) + } + } + } +} diff --git a/hir/src/direction.rs b/hir/src/direction.rs new file mode 100644 index 000000000..68fd6a267 --- /dev/null +++ b/hir/src/direction.rs @@ -0,0 +1,35 @@ +/// A marker trait for abstracting over the direction in which a traversal is performed, or +/// information is propagated by an analysis, i.e. forward or backward. +/// +/// This trait is sealed as there are only two possible directions. +#[allow(private_bounds)] +pub trait Direction: sealed::Direction { + fn is_forward() -> bool { + Self::IS_FORWARD + } + fn is_backward() -> bool { + !Self::IS_FORWARD + } +} + +impl Direction for D {} + +mod sealed { + pub trait Direction: Default { + const IS_FORWARD: bool; + } + + #[derive(Debug, Copy, Clone, Default)] + pub struct Forward; + impl Direction for Forward { + const IS_FORWARD: bool = true; + } + + #[derive(Debug, Copy, Clone, Default)] + pub struct Backward; + impl Direction for Backward { + const IS_FORWARD: bool = false; + } +} + +pub use self::sealed::{Backward, Forward}; diff --git a/hir/src/display.rs b/hir/src/display.rs deleted file mode 100644 index eeeed5070..000000000 --- a/hir/src/display.rs +++ /dev/null @@ -1,85 +0,0 @@ -use core::{cell::Cell, fmt}; - -use super::{Block, Inst}; - -/// This trait is used to decorate the textual formatting of blocks and instructions -/// with additional information, e.g liveness. -pub trait Decorator { - type Display<'a>: fmt::Display - where - Self: 'a; - - /// Emit no decoration for this block when true - fn skip_block(&self, _block: Block) -> bool { - false - } - /// Emit no decoration for this instruction when true - fn skip_inst(&self, _inst: Inst) -> bool { - false - } - /// Emit decoration for `block` by returning a displayable object - fn decorate_block<'a, 'd: 'a>(&'d self, block: Block) -> Self::Display<'a>; - /// Emit decoration for `inst` by returning a displayable object - fn decorate_inst<'a, 'd: 'a>(&'d self, inst: Inst) -> Self::Display<'a>; -} -impl Decorator for () { - type Display<'a> = &'a str; - - fn skip_block(&self, _block: Block) -> bool { - true - } - - fn skip_inst(&self, _inst: Inst) -> bool { - true - } - - fn decorate_block<'a, 'd: 'a>(&'d self, _block: Block) -> Self::Display<'a> { - "" - } - - fn decorate_inst<'a, 'd: 'a>(&'d self, _inst: Inst) -> Self::Display<'a> { - "" - } -} - -/// Render an iterator of `T`, comma-separated -pub struct DisplayValues(Cell>); -impl DisplayValues { - pub fn new(inner: T) -> Self { - Self(Cell::new(Some(inner))) - } -} -impl fmt::Display for DisplayValues -where - T: fmt::Display, - I: Iterator, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let iter = self.0.take().unwrap(); - for (i, item) in iter.enumerate() { - if i == 0 { - write!(f, "{}", item)?; - } else { - write!(f, ", {}", item)?; - } - } - Ok(()) - } -} - -/// Render an `Option` using the `Display` impl for `T` -pub struct DisplayOptional<'a, T>(pub Option<&'a T>); -impl<'a, T: fmt::Display> fmt::Display for DisplayOptional<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.0 { - None => f.write_str("None"), - Some(item) => write!(f, "Some({item})"), - } - } -} -impl<'a, T: fmt::Display> fmt::Debug for DisplayOptional<'a, T> { - #[inline] - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} diff --git a/hir/src/eq.rs b/hir/src/eq.rs new file mode 100644 index 000000000..f605213bd --- /dev/null +++ b/hir/src/eq.rs @@ -0,0 +1,17 @@ +use core::any::Any; + +/// A type-erased version of [PartialEq] +pub trait DynPartialEq: Any { + fn dyn_eq(&self, rhs: &dyn DynPartialEq) -> bool; +} + +impl DynPartialEq for T +where + T: PartialEq + 'static, +{ + #[inline] + default fn dyn_eq(&self, rhs: &dyn DynPartialEq) -> bool { + let rhs = rhs as &dyn Any; + rhs.downcast_ref::().map(|rhs| self.eq(rhs)).unwrap_or(false) + } +} diff --git a/hir/src/folder.rs b/hir/src/folder.rs new file mode 100644 index 000000000..360103a73 --- /dev/null +++ b/hir/src/folder.rs @@ -0,0 +1,477 @@ +use alloc::{boxed::Box, rc::Rc}; + +use smallvec::{smallvec, SmallVec}; + +use crate::{ + adt::SmallDenseMap, + matchers::Matcher, + patterns::{RewriterImpl, RewriterListener}, + traits::{ConstantLike, Foldable, IsolatedFromAbove}, + AttributeValue, BlockRef, Builder, Context, Dialect, FoldResult, FxHashMap, OpFoldResult, + OperationRef, ProgramPoint, RegionRef, Rewriter, SourceSpan, Spanned, Type, Value, ValueRef, +}; + +/// Represents a constant value uniqued by dialect, value, and type. +struct UniquedConstant { + dialect: Rc, + value: Box, + ty: Type, +} +impl Eq for UniquedConstant {} +impl PartialEq for UniquedConstant { + fn eq(&self, other: &Self) -> bool { + use core::hash::{Hash, Hasher}; + + let v1_hash = { + let mut hasher = rustc_hash::FxHasher::default(); + self.value.hash(&mut hasher); + hasher.finish() + }; + let v2_hash = { + let mut hasher = rustc_hash::FxHasher::default(); + other.value.hash(&mut hasher); + hasher.finish() + }; + + self.dialect.name() == other.dialect.name() && v1_hash == v2_hash && self.ty == other.ty + } +} +impl UniquedConstant { + pub fn new(op: &OperationRef, value: Box) -> Self { + let op = op.borrow(); + let dialect = op.dialect(); + let ty = op.results()[0].borrow().ty().clone(); + + Self { dialect, value, ty } + } +} +impl core::hash::Hash for UniquedConstant { + fn hash(&self, state: &mut H) { + self.dialect.name().hash(state); + self.value.hash(state); + self.ty.hash(state); + } +} + +/// A map of uniqued constants, to their defining operations +type ConstantMap = FxHashMap; + +/// The [OperationFolder] is responsible for orchestrating operation folding, and the effects that +/// folding an operation has on the containing region. +/// +/// It handles the following details related to operation folding: +/// +/// * Attempting to fold the operation itself +/// * Materializing constants +/// * Uniquing/de-duplicating materialized constants, including moving them up the CFG to ensure +/// that new uses of a uniqued constant are dominated by the constant definition. +/// * Removing folded operations (or cleaning up failed attempts), and notifying any listeners of +/// those actions. +pub struct OperationFolder { + rewriter: Box, + /// A mapping between an insertion region and the constants that have been created within it. + scopes: SmallDenseMap, + /// This map tracks all of the dialects that an operation is referenced by; given that multiple + /// dialects may generate the same constant. + referenced_dialects: SmallDenseMap; 1]>>, + /// The location to use for folder-owned constants + erased_folded_location: SourceSpan, +} +impl OperationFolder { + pub fn new(context: Rc, listener: L) -> Self + where + L: RewriterListener + 'static, + { + Self { + rewriter: Box::new(RewriterImpl::::new(context).with_listener(listener)), + scopes: Default::default(), + referenced_dialects: Default::default(), + erased_folded_location: SourceSpan::UNKNOWN, + } + } + + /// Tries to perform folding on `op`, including unifying de-duplicated constants. + /// + /// If successful, replaces uses of `op`'s results with the folded results, and returns + /// a [FoldResult]. + pub fn try_fold(&mut self, mut op: OperationRef) -> FoldResult { + // If this is a uniqued constant, return failure as we know that it has already been + // folded. + if self.is_folder_owned_constant(&op) { + // Check to see if we should rehoist, i.e. if a non-constant operation was inserted + // before this one. + let block = op.parent().unwrap(); + if block.borrow().front().unwrap() != op + && !self.is_folder_owned_constant(&op.prev().unwrap()) + { + let mut op = op.borrow_mut(); + op.move_to(ProgramPoint::at_start_of(block)); + op.set_span(self.erased_folded_location); + } + return FoldResult::Failed; + } + + // Try to fold the operation + let mut fold_results = SmallVec::default(); + match op.borrow_mut().fold(&mut fold_results) { + FoldResult::InPlace => { + // Folding API does not notify listeners, so we need to do so manually + self.rewriter.notify_operation_modified(op); + + FoldResult::InPlace + } + FoldResult::Ok(_) => { + assert!( + !fold_results.is_empty(), + "expected non-empty fold results from a successful fold" + ); + if let FoldResult::Ok(replacements) = self.process_fold_results(op, &fold_results) { + // Constant folding succeeded. Replace all of the result values and erase the + // operation. + self.notify_removal(op); + self.rewriter.replace_op_with_values(op, &replacements); + FoldResult::Ok(()) + } else { + FoldResult::Failed + } + } + failed @ FoldResult::Failed => failed, + } + } + + /// Try to process a set of fold results. + /// + /// Returns the folded values if successful. + fn process_fold_results( + &mut self, + op: OperationRef, + fold_results: &[OpFoldResult], + ) -> FoldResult; 2]>> { + let borrowed_op = op.borrow(); + assert_eq!(fold_results.len(), borrowed_op.num_results()); + + // Create a builder to insert new operations into the entry block of the insertion region + let insert_region = get_insertion_region(borrowed_op.parent().unwrap()); + let entry = insert_region.borrow().entry_block_ref().unwrap(); + self.rewriter.set_insertion_point_to_start(entry); + + // Create the result constants and replace the results. + let dialect = borrowed_op.dialect(); + let mut out = SmallVec::default(); + for (op_result, fold_result) in borrowed_op.results().iter().zip(fold_results) { + match fold_result { + // Check if the result was an SSA value. + OpFoldResult::Value(value) => { + out.push(Some(*value)); + continue; + } + // Check to see if there is a canonicalized version of this constant. + OpFoldResult::Attribute(attr_repl) => { + if let Some(mut const_op) = self.try_get_or_create_constant( + insert_region, + dialect.clone(), + attr_repl.clone_value(), + op_result.borrow().ty().clone(), + self.erased_folded_location, + ) { + // Ensure that this constant dominates the operation we are replacing. + // + // This may not automatically happen if the operation being folded was + // inserted before the constant within the insertion block. + let op_block = borrowed_op.parent().unwrap(); + if op_block == const_op.parent().unwrap() + && op_block.borrow().front().unwrap() != const_op + { + const_op.borrow_mut().move_to(ProgramPoint::at_start_of(op_block)); + } + out.push(Some(const_op.borrow().get_result(0).borrow().as_value_ref())); + continue; + } + + // If materialization fails, clean up any operations generated for the previous + // results and return failure. + let inserted_before = self.rewriter.insertion_point().operation(); + if let Some(inserted_before) = inserted_before { + while let Some(inserted_op) = inserted_before.prev() { + self.notify_removal(inserted_op); + self.rewriter.erase_op(inserted_op); + } + } + + return FoldResult::Failed; + } + } + } + + FoldResult::Ok(out) + } + + /// Notifies that the given constant `op` should be removed from this folder's internal + /// bookkeeping. + /// + /// NOTE: This method must be called if a constant op is to be deleted externally to this + /// folder. `op` must be constant. + pub fn notify_removal(&mut self, op: OperationRef) { + // Check to see if this operation is uniqued within the folder. + let Some(referenced_dialects) = self.referenced_dialects.get_mut(&op) else { + return; + }; + + let borrowed_op = op.borrow(); + + // Get the constant value for this operation, this is the value that was used to unique + // the operation internally. + let value = crate::matchers::constant().matches(&borrowed_op).unwrap(); + + // Get the constant map that this operation was uniqued in. + let insert_region = get_insertion_region(borrowed_op.parent().unwrap()); + let uniqued_constants = self.scopes.get_mut(&insert_region).unwrap(); + + // Erase all of the references to this operation. + let ty = borrowed_op.results()[0].borrow().ty().clone(); + for dialect in referenced_dialects.drain(..) { + let uniqued_constant = UniquedConstant { + dialect, + value: value.clone_value(), + ty: ty.clone(), + }; + uniqued_constants.remove(&uniqued_constant); + } + } + + /// CLear out any constants cached inside the folder. + pub fn clear(&mut self) { + self.scopes.clear(); + self.referenced_dialects.clear(); + } + + /// Tries to fold a pre-existing constant operation. + /// + /// `value` represents the value of the constant, and can be optionally passed if the value is + /// already known (e.g. if the constant was discovered by a pattern match). This is purely an + /// optimization opportunity for callers that already know the value of the constant. + /// + /// Returns `false` if an existing constant for `op` already exists in the folder, in which case + /// `op` is replaced and erased. Otherwise, returns `true` and `op` is inserted into the folder + /// and hoisted if necessary. + pub fn insert_known_constant( + &mut self, + mut op: OperationRef, + value: Option>, + ) -> bool { + let block = op.parent().unwrap(); + + // If this is a constant we uniqued, we don't need to insert, but we can check to see if + // we should rehoist it. + if self.is_folder_owned_constant(&op) { + if block.borrow().front().unwrap() != op + && !self.is_folder_owned_constant(&op.prev().unwrap()) + { + let mut op = op.borrow_mut(); + op.move_to(ProgramPoint::at_start_of(block)); + op.set_span(self.erased_folded_location); + } + return true; + } + + // Get the constant value of the op if necessary. + let value = value.unwrap_or_else(|| { + crate::matchers::constant() + .matches(&op.borrow()) + .expect("expected `op` to be a constant") + }); + + // Check for an existing constant operation for the attribute value. + let insert_region = get_insertion_region(block); + let uniqued_constants = self.scopes.entry(insert_region).or_default(); + let uniqued_constant = UniquedConstant::new(&op, value); + let mut is_new = false; + let mut folder_const_op = *uniqued_constants.entry(uniqued_constant).or_insert_with(|| { + is_new = true; + op + }); + + // If there is an existing constant, replace `op` + if !is_new { + self.notify_removal(op); + self.rewriter.replace_op(op, folder_const_op); + folder_const_op.borrow_mut().set_span(self.erased_folded_location); + return false; + } + + // Otherwise, we insert `op`. If `op` is in the insertion block and is either already at the + // front of the block, or the previous operation is already a constant we uniqued (i.e. one + // we inserted), then we don't need to do anything. Otherwise, we move the constant to the + // insertion block. + let insert_block = insert_region.borrow().entry_block_ref().unwrap(); + if block != insert_block + || (insert_block.borrow().front().unwrap() != op + && !self.is_folder_owned_constant(&op.prev().unwrap())) + { + let mut op = op.borrow_mut(); + op.move_to(ProgramPoint::at_start_of(insert_block)); + op.set_span(self.erased_folded_location); + } + + let referenced_dialects = self.referenced_dialects.entry(op).or_default(); + let dialect = op.borrow().dialect(); + let dialect_name = dialect.name(); + if !referenced_dialects.iter().any(|d| d.name() == dialect_name) { + referenced_dialects.push(dialect); + } + + true + } + + /// Get or create a constant for use in the specified block. + /// + /// The constant may be created in a parent block. On success, this returns the result of the + /// constant operation, or `None` otherwise. + pub fn get_or_create_constant( + &mut self, + block: BlockRef, + dialect: Rc, + value: Box, + ty: Type, + ) -> Option { + // Find an insertion point for the constant. + let insert_region = get_insertion_region(block); + let entry = insert_region.borrow().entry_block_ref().unwrap(); + self.rewriter.set_insertion_point_to_start(entry); + + // Get the constant map for the insertion region of this operation. + // Use erased location since the op is being built at the front of the block. + let const_op = self.try_get_or_create_constant( + insert_region, + dialect, + value, + ty, + self.erased_folded_location, + )?; + Some(const_op.borrow().results()[0].borrow().as_value_ref()) + } + + /// Try to get or create a new constant entry. + /// + /// On success, this returns the constant operation, `None` otherwise + fn try_get_or_create_constant( + &mut self, + insert_region: RegionRef, + dialect: Rc, + value: Box, + ty: Type, + span: SourceSpan, + ) -> Option { + let uniqued_constants = self.scopes.entry(insert_region).or_default(); + let uniqued_constant = UniquedConstant { + dialect: dialect.clone(), + value, + ty, + }; + if let Some(mut const_op) = uniqued_constants.get(&uniqued_constant).cloned() { + { + let mut const_op = const_op.borrow_mut(); + if const_op.span() != span { + const_op.set_span(span); + } + } + return Some(const_op); + } + + // If one doesn't exist, try to materialize one. + let const_op = materialize_constant( + self.rewriter.as_mut(), + dialect.clone(), + uniqued_constant.value.clone_value(), + &uniqued_constant.ty, + span, + )?; + + // Check to see if the generated constant is in the expected dialect. + let new_dialect = const_op.borrow().dialect(); + if new_dialect.name() == dialect.name() { + self.referenced_dialects.entry(const_op).or_default().push(new_dialect); + return Some(const_op); + } + + // If it isn't, then we also need to make sure that the mapping for the new dialect is valid + let new_uniqued_constant = UniquedConstant { + dialect: new_dialect.clone(), + value: uniqued_constant.value.clone_value(), + ty: uniqued_constant.ty.clone(), + }; + let maybe_existing_op = uniqued_constants.get(&new_uniqued_constant).cloned(); + uniqued_constants.insert(uniqued_constant, maybe_existing_op.unwrap_or(const_op)); + if let Some(mut existing_op) = maybe_existing_op { + self.notify_removal(const_op); + self.rewriter.erase_op(const_op); + self.referenced_dialects + .get_mut(&existing_op) + .unwrap() + .push(new_uniqued_constant.dialect.clone()); + let mut existing = existing_op.borrow_mut(); + if existing.span() != span { + existing.set_span(span); + } + Some(existing_op) + } else { + self.referenced_dialects.insert(const_op, smallvec![dialect, new_dialect]); + uniqued_constants.insert(new_uniqued_constant, const_op); + Some(const_op) + } + } + + /// Returns true if the given operation is an already folded constant that is owned by this + /// folder. + #[inline(always)] + fn is_folder_owned_constant(&self, op: &OperationRef) -> bool { + self.referenced_dialects.contains_key(op) + } +} + +/// Materialize a constant for a given attribute and type. +/// +/// Returns a constant operation if successful, otherwise `None` +fn materialize_constant( + builder: &mut dyn Builder, + dialect: Rc, + value: Box, + ty: &Type, + span: SourceSpan, +) -> Option { + let ip = *builder.insertion_point(); + + // Ask the dialect to materialize a constant operation for this value. + let const_op = dialect.materialize_constant(builder, value, ty, span)?; + assert_eq!(ip, *builder.insertion_point()); + assert!(const_op.borrow().implements::()); + Some(const_op) +} + +/// Given the containing block of an operation, find the parent region that folded constants should +/// be inserted into. +fn get_insertion_region(insertion_block: BlockRef) -> RegionRef { + use crate::EntityWithId; + + let mut insertion_block = Some(insertion_block); + while let Some(block) = insertion_block.take() { + let parent_region = block.parent().unwrap_or_else(|| { + panic!("expected block {} to be attached to a region", block.borrow().id()) + }); + // Insert in this region for any of the following scenarios: + // + // * The parent is known to be isolated from above + // * The parent is a top-level operation + let parent_op = + parent_region.parent().expect("expected region to be attached to an operation"); + let parent = parent_op.borrow(); + let parent_block = parent.parent(); + if parent.implements::() || parent_block.is_none() { + return parent_region; + } + + insertion_block = parent_block; + } + + unreachable!("expected valid insertion region") +} diff --git a/hir/src/formatter.rs b/hir/src/formatter.rs new file mode 100644 index 000000000..ded14e5b9 --- /dev/null +++ b/hir/src/formatter.rs @@ -0,0 +1,59 @@ +use core::{cell::Cell, fmt}; + +pub use miden_core::{ + prettier::*, + utils::{DisplayHex, ToHex}, +}; + +pub struct DisplayIndent(pub usize); +impl fmt::Display for DisplayIndent { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + const INDENT: &str = " "; + for _ in 0..self.0 { + f.write_str(INDENT)?; + } + Ok(()) + } +} + +/// Render an iterator of `T`, comma-separated +pub struct DisplayValues(Cell>); +impl DisplayValues { + pub fn new(inner: T) -> Self { + Self(Cell::new(Some(inner))) + } +} +impl fmt::Display for DisplayValues +where + T: fmt::Display, + I: Iterator, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let iter = self.0.take().unwrap(); + for (i, item) in iter.enumerate() { + if i == 0 { + write!(f, "{item}")?; + } else { + write!(f, ", {item}")?; + } + } + Ok(()) + } +} + +/// Render an `Option` using the `Display` impl for `T` +pub struct DisplayOptional<'a, T>(pub Option<&'a T>); +impl fmt::Display for DisplayOptional<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + None => f.write_str("None"), + Some(item) => write!(f, "Some({item})"), + } + } +} +impl fmt::Debug for DisplayOptional<'_, T> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} diff --git a/hir/src/formatter/mod.rs b/hir/src/formatter/mod.rs deleted file mode 100644 index 08eab2ae5..000000000 --- a/hir/src/formatter/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use core::fmt; - -pub use miden_core::{ - prettier::*, - utils::{DisplayHex, ToHex}, -}; - -pub struct DisplayIndent(pub usize); -impl fmt::Display for DisplayIndent { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - const INDENT: &str = " "; - for _ in 0..self.0 { - f.write_str(INDENT)?; - } - Ok(()) - } -} diff --git a/hir/src/function.rs b/hir/src/function.rs deleted file mode 100644 index 5dbcc76e1..000000000 --- a/hir/src/function.rs +++ /dev/null @@ -1,714 +0,0 @@ -use cranelift_entity::entity_impl; -use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink}; - -use self::formatter::PrettyPrint; -use crate::{ - diagnostics::{miette, Diagnostic, Spanned}, - *, -}; - -/// This error is raised when two function declarations conflict with the same symbol name -#[derive(Debug, thiserror::Error, Diagnostic)] -#[error("item named '{}' has already been declared, or cannot be merged", .0)] -#[diagnostic()] -pub struct SymbolConflictError(pub FunctionIdent); - -/// A handle that refers to an [ExternalFunction] -#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct FuncRef(u32); -entity_impl!(FuncRef, "fn"); - -/// Represents the calling convention of a function. -/// -/// Calling conventions are part of a program's ABI (Application Binary Interface), and -/// they define things such how arguments are passed to a function, how results are returned, -/// etc. In essence, the contract between caller and callee is described by the calling convention -/// of a function. -/// -/// Importantly, it is perfectly normal to mix calling conventions. For example, the public -/// API for a C library will use whatever calling convention is used by C on the target -/// platform (for Miden, that would be `SystemV`). However, internally that library may use -/// the `Fast` calling convention to allow the compiler to optimize more effectively calls -/// from the public API to private functions. In short, choose a calling convention that is -/// well-suited for a given function, to the extent that other constraints don't impose a choice -/// on you. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -#[cfg_attr( - feature = "serde", - derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) -)] -#[repr(u8)] -pub enum CallConv { - /// This calling convention is what I like to call "chef's choice" - the - /// compiler chooses it's own convention that optimizes for call performance. - /// - /// As a result of this, it is not permitted to use this convention in externally - /// linked functions, as the convention is unstable, and the compiler can't ensure - /// that the caller in another translation unit will use the correct convention. - Fast, - /// The standard calling convention used for C on most platforms - #[default] - SystemV, - /// A function which is using the WebAssembly Component Model "Canonical ABI". - Wasm, - /// A function with this calling convention must be called using - /// the `syscall` instruction. Attempts to call it with any other - /// call instruction will cause a validation error. The one exception - /// to this rule is when calling another function with the `Kernel` - /// convention that is defined in the same module, which can use the - /// standard `call` instruction. - /// - /// Kernel functions may only be defined in a kernel [Module]. - /// - /// In all other respects, this calling convention is the same as `SystemV` - Kernel, -} -impl fmt::Display for CallConv { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Fast => f.write_str("fast"), - Self::SystemV => f.write_str("C"), - Self::Wasm => f.write_str("wasm"), - Self::Kernel => f.write_str("kernel"), - } - } -} - -/// Represents whether an argument or return value has a special purpose in -/// the calling convention of a function. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -#[cfg_attr( - feature = "serde", - derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) -)] -#[repr(u8)] -pub enum ArgumentPurpose { - /// No special purpose, the argument is passed/returned by value - #[default] - Default, - /// Used for platforms where the calling convention expects return values of - /// a certain size to be written to a pointer passed in by the caller. - StructReturn, -} -impl fmt::Display for ArgumentPurpose { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Default => f.write_str("default"), - Self::StructReturn => f.write_str("sret"), - } - } -} - -/// Represents how to extend a small integer value to native machine integer width. -/// -/// For Miden, native integrals are unsigned 64-bit field elements, but it is typically -/// going to be the case that we are targeting the subset of Miden Assembly where integrals -/// are unsigned 32-bit integers with a standard twos-complement binary representation. -/// -/// It is for the latter scenario that argument extension is really relevant. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -#[cfg_attr( - feature = "serde", - derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) -)] -#[repr(u8)] -pub enum ArgumentExtension { - /// Do not perform any extension, high bits have undefined contents - #[default] - None, - /// Zero-extend the value - Zext, - /// Sign-extend the value - Sext, -} -impl fmt::Display for ArgumentExtension { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::None => f.write_str("none"), - Self::Zext => f.write_str("zext"), - Self::Sext => f.write_str("sext"), - } - } -} - -/// Describes a function parameter or result. -#[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct AbiParam { - /// The type associated with this value - pub ty: Type, - /// The special purpose, if any, of this parameter or result - pub purpose: ArgumentPurpose, - /// The desired approach to extending the size of this value to - /// a larger bit width, if applicable. - pub extension: ArgumentExtension, -} -impl AbiParam { - pub fn new(ty: Type) -> Self { - Self { - ty, - purpose: ArgumentPurpose::default(), - extension: ArgumentExtension::default(), - } - } - - pub fn sret(ty: Type) -> Self { - assert!(ty.is_pointer(), "sret parameters must be pointers"); - Self { - ty, - purpose: ArgumentPurpose::StructReturn, - extension: ArgumentExtension::default(), - } - } -} -impl formatter::PrettyPrint for AbiParam { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let mut doc = const_text("(") + const_text("param") + const_text(" "); - if !matches!(self.purpose, ArgumentPurpose::Default) { - doc += const_text("(") + display(self.purpose) + const_text(")") + const_text(" "); - } - if !matches!(self.extension, ArgumentExtension::None) { - doc += const_text("(") + display(self.extension) + const_text(")") + const_text(" "); - } - doc + text(format!("{}", &self.ty)) + const_text(")") - } -} - -/// A [Signature] represents the type, ABI, and linkage of a function. -/// -/// A function signature provides us with all of the necessary detail to correctly -/// validate and emit code for a function, whether from the perspective of a caller, -/// or the callee. -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Signature { - /// The arguments expected by this function - pub params: Vec, - /// The results returned by this function - pub results: Vec, - /// The calling convention that applies to this function - pub cc: CallConv, - /// The linkage that should be used for this function - pub linkage: Linkage, -} -impl Signature { - /// Create a new signature with the given parameter and result types, - /// for a public function using the `SystemV` calling convention - pub fn new, R: IntoIterator>( - params: P, - results: R, - ) -> Self { - Self { - params: params.into_iter().collect(), - results: results.into_iter().collect(), - cc: CallConv::SystemV, - linkage: Linkage::External, - } - } - - /// Returns true if this function is externally visible - pub fn is_public(&self) -> bool { - matches!(self.linkage, Linkage::External) - } - - /// Returns true if this function is only visible within it's containing module - pub fn is_private(&self) -> bool { - matches!(self.linkage, Linkage::Internal) - } - - /// Returns true if this function is a kernel function - pub fn is_kernel(&self) -> bool { - matches!(self.cc, CallConv::Kernel) - } - - /// Returns the number of arguments expected by this function - pub fn arity(&self) -> usize { - self.params().len() - } - - /// Returns a slice containing the parameters for this function - pub fn params(&self) -> &[AbiParam] { - self.params.as_slice() - } - - /// Returns the parameter at `index`, if present - #[inline] - pub fn param(&self, index: usize) -> Option<&AbiParam> { - self.params.get(index) - } - - /// Returns a slice containing the results of this function - pub fn results(&self) -> &[AbiParam] { - match self.results.as_slice() { - [AbiParam { ty: Type::Unit, .. }] => &[], - [AbiParam { - ty: Type::Never, .. - }] => &[], - results => results, - } - } -} -impl Eq for Signature {} -impl PartialEq for Signature { - fn eq(&self, other: &Self) -> bool { - self.linkage == other.linkage - && self.cc == other.cc - && self.params.len() == other.params.len() - && self.results.len() == other.results.len() - } -} -impl formatter::PrettyPrint for Signature { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let cc = if matches!(self.cc, CallConv::SystemV) { - None - } else { - Some( - const_text("(") - + const_text("cc") - + const_text(" ") - + display(self.cc) - + const_text(")"), - ) - }; - - let params = self.params.iter().fold(cc.unwrap_or(Document::Empty), |acc, param| { - if acc.is_empty() { - param.render() - } else { - acc + const_text(" ") + param.render() - } - }); - - if self.results.is_empty() { - params - } else { - let open = const_text("(") + const_text("result"); - let results = self - .results - .iter() - .fold(open, |acc, e| acc + const_text(" ") + text(format!("{}", &e.ty))) - + const_text(")"); - if matches!(params, Document::Empty) { - results - } else { - params + const_text(" ") + results - } - } - } -} - -/// An [ExternalFunction] represents a function whose name and signature are known, -/// but which may or may not be compiled as part of the current translation unit. -/// -/// When building a [Function], we use [ExternalFunction] to represent references to -/// other functions in the program which are called from its body. One "imports" a -/// function to make it callable. -/// -/// At link time, we make sure all external function references are either defined in -/// the current program, or are well-known functions that are provided as part of a kernel -/// or standard library in the Miden VM. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ExternalFunction { - pub id: FunctionIdent, - pub signature: Signature, -} -impl Ord for ExternalFunction { - #[inline] - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.id.cmp(&other.id) - } -} -impl PartialOrd for ExternalFunction { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl formatter::PrettyPrint for ExternalFunction { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let header = const_text("(") - + const_text("func") - + const_text(" ") - + const_text("(") - + const_text("import") - + const_text(" ") - + display(self.id.module) - + const_text(" ") - + display(self.id.function) - + const_text(")"); - let signature = (const_text(" ") + self.signature.render() + const_text(")")) - | indent(6, nl() + self.signature.render() + const_text(")")); - - header + signature - } -} - -intrusive_adapter!(pub FunctionListAdapter = Box: Function { link: LinkedListLink }); - -/// A type alias for `LinkedList` -pub type FunctionList = LinkedList; - -/// [Function] corresponds to a function definition, in single-static assignment (SSA) form. -/// -/// * Functions may have zero or more parameters, and produce zero or more results. -/// * Functions are namespaced in [Module]s. You may define a function separately from a module, -/// to aid in parallelizing compilation, but functions must be attached to a module prior to code -/// generation. Furthermore, in order to reference other functions, you must do so using their -/// fully-qualified names. -/// * Functions consist of one or more basic blocks, where the entry block is predefined based -/// on the function signature. -/// * Basic blocks consist of a sequence of [Instruction] without any control flow (excluding -/// calls), terminating with a control flow instruction. Our SSA representation uses block -/// arguments rather than phi nodes to represent join points in the control flow graph. -/// * Instructions consume zero or more arguments, and produce zero or more results. Results -/// produced by an instruction constitute definitions of those values. A value may only ever have -/// a single definition, e.g. you can't reassign a value after it is introduced by an instruction. -/// -/// References to functions and global variables from a [Function] are not fully validated until -/// link-time/code generation. -#[derive(Spanned, AnalysisKey)] -pub struct Function { - link: LinkedListLink, - #[span] - #[analysis_key] - pub id: FunctionIdent, - pub signature: Signature, - pub dfg: DataFlowGraph, -} -impl Function { - /// Create a new [Function] with the given name, signature, and source location. - /// - /// The resulting function will be given default internal linkage, i.e. it will only - /// be visible within it's containing [Module]. - pub fn new(id: FunctionIdent, signature: Signature) -> Self { - let mut dfg = DataFlowGraph::default(); - let entry = dfg.entry_block(); - for param in signature.params() { - dfg.append_block_param(entry, param.ty.clone(), id.span()); - } - dfg.imports.insert( - id, - ExternalFunction { - id, - signature: signature.clone(), - }, - ); - Self { - link: Default::default(), - id, - signature, - dfg, - } - } - - /// This function is like [Function::new], except it does not initialize the - /// function entry block using the provided [Signature]. Instead, it is expected - /// that the caller does this manually. - /// - /// This is primarily intended for use by the IR parser. - pub(crate) fn new_uninit(id: FunctionIdent, signature: Signature) -> Self { - let mut dfg = DataFlowGraph::new_uninit(); - dfg.imports.insert( - id, - ExternalFunction { - id, - signature: signature.clone(), - }, - ); - Self { - link: Default::default(), - id, - signature, - dfg, - } - } - - /// Returns true if this function has yet to be attached to a [Module] - pub fn is_detached(&self) -> bool { - !self.link.is_linked() - } - - /// Returns true if this function is a kernel function - pub fn is_kernel(&self) -> bool { - self.signature.is_kernel() - } - - /// Returns true if this function has external linkage - pub fn is_public(&self) -> bool { - self.signature.is_public() - } - - /// Return the [Signature] for this function - #[inline] - pub fn signature(&self) -> &Signature { - &self.signature - } - - /// Return the [Signature] for this function - #[inline] - pub fn signature_mut(&mut self) -> &mut Signature { - &mut self.signature - } - - /// Return the number of parameters this function expects - pub fn arity(&self) -> usize { - self.signature.arity() - } - - /// Return the [Linkage] type for this function - pub fn linkage(&self) -> Linkage { - self.signature.linkage - } - - /// Set the linkage type for this function - pub fn set_linkage(&mut self, linkage: Linkage) { - self.signature.linkage = linkage; - } - - /// Return the [CallConv] type for this function - pub fn calling_convention(&self) -> CallConv { - self.signature.cc - } - - /// Set the linkage type for this function - pub fn set_calling_convention(&mut self, cc: CallConv) { - self.signature.cc = cc; - } - - /// Return true if this function has attribute `name` - pub fn has_attribute(&self, name: &Q) -> bool - where - Q: Ord + ?Sized, - Symbol: std::borrow::Borrow, - { - self.dfg.has_attribute(name) - } - - /// Iterate over all of the external functions imported by this function - pub fn imports<'a, 'b: 'a>(&'b self) -> impl Iterator + 'a { - self.dfg.imports().filter(|ext| ext.id != self.id) - } - - pub fn builder(&mut self) -> FunctionBuilder { - FunctionBuilder::new(self) - } - - pub fn cfg_printer(&self) -> impl fmt::Display + '_ { - CfgPrinter { function: self } - } -} -impl fmt::Debug for Function { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Function") - .field("id", &self.id) - .field("signature", &self.signature) - .finish() - } -} -impl formatter::PrettyPrint for Function { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let name = if self.is_public() { - const_text("(") - + const_text("export") - + const_text(" ") - + self.id.function.render() - + const_text(")") - } else { - self.id.function.render() - }; - let header = const_text("(") + const_text("func") + const_text(" ") + name; - - let signature = - (const_text(" ") + self.signature.render()) | indent(6, nl() + self.signature.render()); - - let body = self.dfg.blocks().fold(nl(), |acc, (_, block_data)| { - let open = const_text("(") - + const_text("block") - + const_text(" ") - + display(block_data.id.as_u32()); - let params = block_data - .params(&self.dfg.value_lists) - .iter() - .map(|value| { - let ty = self.dfg.value_type(*value); - const_text("(") - + const_text("param") - + const_text(" ") - + display(*value) - + const_text(" ") - + text(format!("{ty}")) - + const_text(")") - }) - .collect::>(); - - let params_singleline = params - .iter() - .cloned() - .reduce(|acc, e| acc + const_text(" ") + e) - .map(|params| const_text(" ") + params) - .unwrap_or(Document::Empty); - let params_multiline = params - .into_iter() - .reduce(|acc, e| acc + nl() + e) - .map(|doc| indent(8, nl() + doc)) - .unwrap_or(Document::Empty); - let header = open + (params_singleline | params_multiline); - - let body = indent( - 4, - block_data - .insts() - .map(|inst| { - let inst_printer = crate::instruction::InstPrettyPrinter { - current_function: self.id, - id: inst, - dfg: &self.dfg, - }; - inst_printer.render() - }) - .reduce(|acc, doc| acc + nl() + doc) - .map(|body| nl() + body) - .unwrap_or_default(), - ); - - if matches!(acc, Document::Newline) { - indent(4, acc + header + body + const_text(")")) - } else { - acc + nl() + indent(4, nl() + header + body + const_text(")")) - } - }); - - header + signature + body + nl() + const_text(")") - } -} -impl fmt::Display for Function { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} -impl Eq for Function {} -impl PartialEq for Function { - fn eq(&self, other: &Self) -> bool { - let is_eq = self.id == other.id && self.signature == other.signature; - if !is_eq { - return false; - } - - // We expect the entry block to be the same - if self.dfg.entry != other.dfg.entry { - return false; - } - - // We expect the blocks to be laid out in the same order, and to have the same parameter - // lists - for (block_id, block) in self.dfg.blocks() { - if let Some(other_block) = other.dfg.blocks.get(block_id) { - if block.params.as_slice(&self.dfg.value_lists) - != other_block.params.as_slice(&other.dfg.value_lists) - { - return false; - } - // We expect the instructions in each block to be the same - if !block - .insts - .iter() - .map(|i| InstructionWithValueListPool { - inst: i, - value_lists: &self.dfg.value_lists, - }) - .eq(other_block.insts.iter().map(|i| InstructionWithValueListPool { - inst: i, - value_lists: &other.dfg.value_lists, - })) - { - return false; - } - } else { - return false; - } - } - - // We expect both functions to have the same imports - self.dfg.imports == other.dfg.imports - } -} -impl midenc_session::Emit for Function { - fn name(&self) -> Option { - Some(self.id.function.as_symbol()) - } - - fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { - midenc_session::OutputType::Hir - } - - fn write_to( - &self, - mut writer: W, - mode: midenc_session::OutputMode, - _session: &midenc_session::Session, - ) -> std::io::Result<()> { - assert_eq!( - mode, - midenc_session::OutputMode::Text, - "binary mode is not supported for HIR functions" - ); - writer.write_fmt(format_args!("{}\n", self)) - } -} - -struct CfgPrinter<'a> { - function: &'a Function, -} -impl<'a> fmt::Display for CfgPrinter<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use std::collections::{BTreeSet, VecDeque}; - - f.write_str("flowchart TB\n")?; - - let mut block_q = VecDeque::from([self.function.dfg.entry_block()]); - let mut visited = BTreeSet::::default(); - while let Some(block_id) = block_q.pop_front() { - if !visited.insert(block_id) { - continue; - } - if let Some(last_inst) = self.function.dfg.last_inst(block_id) { - match self.function.dfg.analyze_branch(last_inst) { - BranchInfo::NotABranch => { - // Must be a return or unreachable, print opcode for readability - let opcode = self.function.dfg.inst(last_inst).opcode(); - writeln!(f, " {block_id} --> {opcode}")?; - } - BranchInfo::SingleDest(info) => { - assert!( - self.function.dfg.is_block_linked(info.destination), - "reference to detached block in attached block {}", - info.destination - ); - writeln!(f, " {block_id} --> {}", info.destination)?; - block_q.push_back(info.destination); - } - BranchInfo::MultiDest(ref infos) => { - for info in infos { - assert!( - self.function.dfg.is_block_linked(info.destination), - "reference to detached block in attached block {}", - info.destination - ); - writeln!(f, " {block_id} --> {}", info.destination)?; - block_q.push_back(info.destination); - } - } - } - } - } - - Ok(()) - } -} diff --git a/hir/src/globals.rs b/hir/src/globals.rs deleted file mode 100644 index f2a70fb8b..000000000 --- a/hir/src/globals.rs +++ /dev/null @@ -1,833 +0,0 @@ -use alloc::{alloc::Layout, collections::BTreeMap, sync::Arc}; -use core::{ - fmt::{self, Write}, - hash::{Hash, Hasher}, -}; - -use cranelift_entity::entity_impl; -use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink}; - -use crate::{ - diagnostics::{miette, Diagnostic, Spanned}, - *, -}; - -/// The policy to apply to a global variable (or function) when linking -/// together a program during code generation. -/// -/// Miden doesn't (currently) have a notion of a symbol table for things like global variables. -/// At runtime, there are not actually symbols at all in any familiar sense, instead functions, -/// being the only entities with a formal identity in MASM, are either inlined at all their call -/// sites, or are referenced by the hash of their MAST root, to be unhashed at runtime if the call -/// is executed. -/// -/// Because of this, and because we cannot perform linking ourselves (we must emit separate modules, -/// and leave it up to the VM to link them into the MAST), there are limits to what we can do in -/// terms of linking function symbols. We essentially just validate that given a set of modules in -/// a [Program], that there are no invalid references across modules to symbols which either don't -/// exist, or which exist, but have internal linkage. -/// -/// However, with global variables, we have a bit more freedom, as it is a concept that we are -/// completely inventing from whole cloth without explicit support from the VM or Miden Assembly. -/// In short, when we compile a [Program] to MASM, we first gather together all of the global -/// variables into a program-wide table, merging and garbage collecting as appropriate, and updating -/// all references to them in each module. This global variable table is then assumed to be laid out -/// in memory starting at the base of the linear memory address space in the same order, with -/// appropriate padding to ensure accesses are aligned. Then, when emitting MASM instructions which -/// reference global values, we use the layout information to derive the address where that global -/// value is allocated. -/// -/// This has some downsides however, the biggest of which is that we can't prevent someone from -/// loading modules generated from a [Program] with either their own hand-written modules, or -/// even with modules from another [Program]. In such cases, assumptions about the allocation of -/// linear memory from different sets of modules will almost certainly lead to undefined behavior. -/// In the future, we hope to have a better solution to this problem, preferably one involving -/// native support from the Miden VM itself. For now though, we're working with what we've got. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] -#[cfg_attr( - feature = "serde", - derive(serde_repr::Deserialize_repr, serde_repr::Serialize_repr) -)] -#[repr(u8)] -pub enum Linkage { - /// This symbol is only visible in the containing module. - /// - /// Internal symbols may be renamed to avoid collisions - /// - /// Unreferenced internal symbols can be discarded at link time. - Internal, - /// This symbol will be linked using the "one definition rule", i.e. symbols with - /// the same name, type, and linkage will be merged into a single definition. - /// - /// Unlike `internal` linkage, unreferenced `odr` symbols cannot be discarded. - /// - /// NOTE: `odr` symbols cannot satisfy external symbol references - Odr, - /// This symbol is visible externally, and can be used to resolve external symbol references. - #[default] - External, -} -impl fmt::Display for Linkage { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Internal => f.write_str("internal"), - Self::Odr => f.write_str("odr"), - Self::External => f.write_str("external"), - } - } -} - -intrusive_adapter!(pub GlobalVariableAdapter = UnsafeRef: GlobalVariableData { link: LinkedListLink }); - -/// This error is raised when attempting to declare [GlobalVariableData] -/// with a conflicting symbol name and/or linkage. -/// -/// For example, two global variables with the same name, but differing -/// types will result in this error, as there is no way to resolve the -/// conflict. -#[derive(Debug, thiserror::Error, Diagnostic)] -pub enum GlobalVariableError { - /// There are multiple conflicting definitions of the given global symbol - #[error( - "invalid global variable: there are multiple conflicting definitions for symbol '{0}'" - )] - #[diagnostic()] - NameConflict(Ident), - /// An attempt was made to set the initializer for a global that already has one - #[error("cannot set an initializer for '{0}', it is already initialized")] - #[diagnostic()] - AlreadyInitialized(Ident), - /// The initializer data is invalid for the declared type of the given global, e.g. size - /// mismatch. - #[error( - "invalid global variable initializer for '{0}': the data does not match the declared type" - )] - #[diagnostic()] - InvalidInit(Ident), -} - -/// Describes the way in which global variable conflicts will be handled -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)] -pub enum ConflictResolutionStrategy { - /// Do not attempt to resolve conflicts - /// - /// NOTE: This does not change the behavior of "one definition rule" linkage, - /// when the globals have identical definitions. - None, - /// Attempt to resolve conflicts by renaming symbols with `internal` linkage. - #[default] - Rename, -} - -/// This table is used to lay out and link together global variables for a [Program]. -/// -/// See the docs for [Linkage], [GlobalVariableData], and [GlobalVariableTable::declare] for more -/// details. -pub struct GlobalVariableTable { - layout: LinkedList, - names: BTreeMap, - arena: ArenaMap, - data: ConstantPool, - next_unique_id: usize, - conflict_strategy: ConflictResolutionStrategy, -} -impl Default for GlobalVariableTable { - fn default() -> Self { - Self::new(Default::default()) - } -} -impl GlobalVariableTable { - pub fn new(conflict_strategy: ConflictResolutionStrategy) -> Self { - Self { - layout: Default::default(), - names: Default::default(), - arena: Default::default(), - data: ConstantPool::default(), - next_unique_id: 0, - conflict_strategy, - } - } - - /// Returns the number of global variables in this table - pub fn len(&self) -> usize { - self.layout.iter().count() - } - - /// Returns true if the global variable table is empty - pub fn is_empty(&self) -> bool { - self.layout.is_empty() - } - - /// Get a double-ended iterator over the current table layout - pub fn iter<'a, 'b: 'a>( - &'b self, - ) -> intrusive_collections::linked_list::Iter<'a, GlobalVariableAdapter> { - self.layout.iter() - } - - /// Returns true if a global variable with `name` has been declared - pub fn exists(&self, name: Ident) -> bool { - self.names.contains_key(&name) - } - - /// Looks up a [GlobalVariable] by name. - pub fn find(&self, name: Ident) -> Option { - self.names.get(&name).copied() - } - - /// Gets the data associated with the given [GlobalVariable] - pub fn get(&self, id: GlobalVariable) -> &GlobalVariableData { - &self.arena[id] - } - - /// Checks if the given `id` can be found in this table - pub fn contains_key(&self, id: GlobalVariable) -> bool { - self.arena.contains(id) - } - - /// Removes the global variable associated with `id` from this table - /// - /// The actual definition remains behind, in order to ensure that `id` - /// remains valid should there be any other outstanding references, - /// however the data is removed from the layout, and will not be - /// seen when traversing the table. - pub fn remove(&mut self, id: GlobalVariable) { - let mut cursor = self.layout.front_mut(); - while let Some(gv) = cursor.get() { - if gv.id == id { - cursor.remove(); - return; - } - - cursor.move_next(); - } - } - - /// Computes the total size in bytes of the table, as it is currently laid out. - pub fn size_in_bytes(&self) -> usize { - // We mimic the allocation process here, by visiting each - // global variable, padding the current heap pointer as necessary - // to provide the necessary minimum alignment for the value, and - // then bumping it by the size of the value itself. - // - // At the end, the effective address of the pointer is the total - // size in bytes of the allocation - let mut size = 0; - for gv in self.layout.iter() { - let layout = gv.layout(); - size += layout.size().align_up(layout.align()); - } - size - } - - /// Computes the offset, in bytes, of the given [GlobalVariable] from the - /// start of the segment in which globals are allocated, assuming that the - /// layout of the global variable table up to and including `id` remains - /// unchanged. - /// - /// # Safety - /// - /// This should only be used once all data segments and global variables have - /// been declared, and the layout of the table has been decided. It is technically - /// safe to use offsets obtained before all global variables are declared, _IF_ the - /// data segments and global variable layout up to and including those global variables - /// remains unchanged after that point. - /// - /// If the offset for a given global variable is obtained, and the heap layout is - /// subsequently changed in such a way that the original offset is no longer - /// accurate, bad things will happen. - pub unsafe fn offset_of(&self, id: GlobalVariable) -> u32 { - let mut size = 0usize; - for gv in self.layout.iter() { - let layout = gv.layout(); - let align_offset = layout.size().align_offset(layout.align()); - size += align_offset; - - // If the current variable is the one we're after, - // the aligned address is the offset to the start - // of the allocation, so we're done - if gv.id == id { - break; - } - - size += layout.size(); - } - size.try_into().expect("data segment table is invalid") - } - - /// Get the constant data associated with `id` - pub fn get_constant(&self, id: Constant) -> Arc { - self.data.get(id) - } - - /// Inserts the given constant data into this table without allocating a global - pub fn insert_constant(&mut self, data: ConstantData) -> Constant { - self.data.insert(data) - } - - /// Inserts the given constant data into this table without allocating a global - pub fn insert_refcounted_constant(&mut self, data: Arc) -> Constant { - self.data.insert_arc(data) - } - - /// Returns true if the given constant data is in the constant pool - pub fn contains_constant(&self, data: &ConstantData) -> bool { - self.data.contains(data) - } - - /// Traverse all of the constants in the table - #[inline] - pub fn constants(&self) -> impl Iterator)> + '_ { - self.data.iter() - } - - /// Returns true if the table has constant data stored - pub fn has_constants(&self) -> bool { - !self.data.is_empty() - } - - /// Declares a new global variable with the given symbol name, type, linkage, and optional - /// initializer. - /// - /// If successful, `Ok` is returned, with the [GlobalVariable] corresponding to the data for the - /// symbol. - /// - /// Returns an error if the specification of the global is invalid in any way, or the - /// declaration conflicts with a previous declaration of the same name. - /// - /// NOTE: While similar to `try_insert`, a key difference is that `try_declare` does not attempt - /// to resolve conflicts. If the given name has been previously declared, and the - /// declarations are not identical, then an error will be returned. This is because conflict - /// resolution is a process performed when linking together modules. Declaring globals is - /// done during the initial construction of a module, where any attempt to rename a global - /// variable locally would cause unexpected issues as references to that global are emitted. - /// Once a module is constructed, globals it declares with internal linkage can be renamed - /// freely, as the name is no longer significant. - pub fn declare( - &mut self, - name: Ident, - ty: Type, - linkage: Linkage, - init: Option, - ) -> Result { - assert_ne!( - name.as_symbol(), - symbols::Empty, - "global variable declarations require a non-empty symbol name" - ); - - // Validate the initializer - let init = match init { - None => None, - Some(init) => { - let layout = ty.layout(); - if init.len() > layout.size() { - return Err(GlobalVariableError::InvalidInit(name)); - } - Some(self.data.insert(init)) - } - }; - - let data = GlobalVariableData { - link: Default::default(), - id: Default::default(), - name, - ty, - linkage, - init, - }; - - // If the symbol is already declared, but the declarations are compatible, then - // return the id of the existing declaration. If the declarations are incompatible, - // then we raise an error. - // - // If the symbol is not declared yet, proceed with insertion. - if let Some(gv) = self.names.get(&data.name).copied() { - if data.is_compatible_with(&self.arena[gv]) { - // If the declarations are compatible, and the new declaration has an initializer, - // then the previous declaration must either have no initializer, or the same one, - // but we want to make sure that the initializer is set if not already. - if data.init.is_some() { - self.arena[gv].init = data.init; - } - Ok(gv) - } else { - Err(GlobalVariableError::NameConflict(data.name)) - } - } else { - Ok(unsafe { self.insert(data) }) - } - } - - /// Attempt to insert the given [GlobalVariableData] into this table. - /// - /// Returns the id of the global variable in the table, along with a flag indicating whether the - /// global symbol was renamed to resolve a conflict with an existing symbol. The caller is - /// expected to handle such renames so that any references to the original name that are - /// affected can be updated. - /// - /// If there was an unresolvable conflict, an error will be returned. - pub fn try_insert( - &mut self, - mut data: GlobalVariableData, - ) -> Result<(GlobalVariable, bool), GlobalVariableError> { - assert_ne!( - data.name.as_symbol(), - symbols::Empty, - "global variable declarations require a non-empty symbol name" - ); - - if let Some(gv) = self.names.get(&data.name).copied() { - // The symbol is already declared, check to see if they are compatible - if data.is_compatible_with(&self.arena[gv]) { - // If the declarations are compatible, and the new declaration has an initializer, - // then the previous declaration must either have no initializer, or the same one, - // but we make sure that the initializer is set. - if data.init.is_some() { - self.arena[gv].init = data.init; - } - return Ok((gv, false)); - } - - // Otherwise, the declarations conflict, but depending on the conflict resolution - // strategy, we may yet be able to proceed. - let rename_internal_symbols = - matches!(self.conflict_strategy, ConflictResolutionStrategy::Rename); - match data.linkage { - // Conflicting declarations with internal linkage can be resolved by renaming - Linkage::Internal if rename_internal_symbols => { - let mut generated = String::from(data.name.as_str()); - let original_len = generated.len(); - loop { - // Allocate a new unique integer value to mix into the hash - let unique_id = self.next_unique_id; - self.next_unique_id += 1; - // Calculate the hash of the global variable data - let mut hasher = rustc_hash::FxHasher::default(); - data.hash(&mut hasher); - unique_id.hash(&mut hasher); - let hash = hasher.finish(); - // Append `.` as a suffix to the original symbol name - write!(&mut generated, ".{:x}", hash) - .expect("failed to write unique suffix to global variable name"); - // If by some stroke of bad luck we generate a symbol name that - // is in use, try again with a different unique id until we find - // an unused name - if !self.names.contains_key(generated.as_str()) { - data.name = - Ident::new(Symbol::intern(generated.as_str()), data.name.span()); - break; - } - // Strip off the suffix we just added before we try again - generated.truncate(original_len); - } - - let gv = unsafe { self.insert(data) }; - Ok((gv, true)) - } - // In all other cases, a conflicting declaration cannot be resolved - Linkage::External | Linkage::Internal | Linkage::Odr => { - Err(GlobalVariableError::NameConflict(data.name)) - } - } - } else { - let gv = unsafe { self.insert(data) }; - Ok((gv, false)) - } - } - - /// This sets the initializer for the given [GlobalVariable] to `init`. - /// - /// This function will return `Err` if any of the following occur: - /// - /// * The global variable already has an initializer - /// * The given data does not match the type of the global variable, i.e. more data than the - /// type supports. - /// - /// If the data is smaller than the type of the global variable, the data will be zero-extended - /// to fill it out. - /// - /// NOTE: The initializer data is expected to be in little-endian order. - pub fn set_initializer( - &mut self, - gv: GlobalVariable, - init: ConstantData, - ) -> Result<(), GlobalVariableError> { - let global = &mut self.arena[gv]; - let layout = global.layout(); - if init.len() > layout.size() { - return Err(GlobalVariableError::InvalidInit(global.name)); - } - - match global.init { - // If the global is uninitialized, we're good to go - None => { - global.init = Some(self.data.insert(init)); - } - // If it is already initialized, but the initializers are the - // same, then we consider this a successful, albeit redundant, - // operation; otherwise we raise an error. - Some(prev_init) => { - let prev = self.data.get_by_ref(prev_init); - if prev != &init { - return Err(GlobalVariableError::AlreadyInitialized(global.name)); - } - } - } - - Ok(()) - } - - /// This is a low-level operation to insert global variable data directly into the table, - /// allocating a fresh unique id, which is then returned. - /// - /// # SAFETY - /// - /// It is expected that the caller has already guaranteed that the name of the given global - /// variable is not present in the table, and that all validation rules for global variables - /// have been enforced. - pub(crate) unsafe fn insert(&mut self, mut data: GlobalVariableData) -> GlobalVariable { - let name = data.name; - // Allocate the data in the arena - let gv = if data.id == GlobalVariable::default() { - let gv = self.arena.alloc_key(); - data.id = gv; - gv - } else { - data.id - }; - self.arena.append(gv, data); - // Add the symbol name to the symbol map - self.names.insert(name, gv); - - // Add the global variable to the layout - let unsafe_ref = unsafe { - let ptr = self.arena.get_raw(gv).unwrap(); - UnsafeRef::from_raw(ptr.as_ptr()) - }; - self.layout.push_back(unsafe_ref); - gv - } -} -impl fmt::Debug for GlobalVariableTable { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list().entries(self.layout.iter()).finish() - } -} - -/// A handle to a global variable definition -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct GlobalVariable(u32); -entity_impl!(GlobalVariable, "gvar"); -impl Default for GlobalVariable { - #[inline] - fn default() -> Self { - use cranelift_entity::packed_option::ReservedValue; - - Self::reserved_value() - } -} - -/// A [GlobalVariable] represents a concrete definition for a symbolic value, -/// i.e. it corresponds to the actual allocated memory referenced by a [GlobalValueData::Symbol] -/// value. -#[derive(Clone)] -pub struct GlobalVariableData { - /// The intrusive link used for storing this global variable in a list - link: LinkedListLink, - /// The unique identifier associated with this global variable - id: GlobalVariable, - /// The symbol name for this global variable - pub name: Ident, - /// The type of the value this variable is allocated for. - /// - /// Nothing prevents one from accessing the variable as if it is - /// another type, but at a minimum this type is used to derive the - /// size and alignment requirements for this global variable on - /// the heap. - pub ty: Type, - /// The linkage for this global variable - pub linkage: Linkage, - /// The initializer for this global variable, if applicable - pub init: Option, -} -impl GlobalVariableData { - pub(crate) fn new( - id: GlobalVariable, - name: Ident, - ty: Type, - linkage: Linkage, - init: Option, - ) -> Self { - Self { - link: LinkedListLink::new(), - id, - name, - ty, - linkage, - init, - } - } - - /// Get the unique identifier assigned to this global variable - #[inline] - pub fn id(&self) -> GlobalVariable { - self.id - } - - /// Return the [Layout] of this global variable in memory - pub fn layout(&self) -> Layout { - self.ty.layout() - } - - /// Return a handle to the initializer for this global variable, if present - pub fn initializer(&self) -> Option { - self.init - } - - /// Returns true if `self` is compatible with `other`, meaning that the two declarations are - /// identical in terms of type and linkage, and do not have conflicting initializers. - /// - /// NOTE: The name of the global is not considered here, only the properties of the value - /// itself. - pub fn is_compatible_with(&self, other: &Self) -> bool { - let compatible_init = - self.init.is_none() || other.init.is_none() || self.init == other.init; - self.ty == other.ty && self.linkage == other.linkage && compatible_init - } -} -impl Eq for GlobalVariableData {} -impl PartialEq for GlobalVariableData { - fn eq(&self, other: &Self) -> bool { - self.linkage == other.linkage - && self.ty == other.ty - && self.name == other.name - && self.init == other.init - } -} -impl Hash for GlobalVariableData { - fn hash(&self, state: &mut H) { - self.name.hash(state); - self.ty.hash(state); - self.linkage.hash(state); - self.init.hash(state); - } -} -impl fmt::Debug for GlobalVariableData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("GlobalVariableData") - .field("id", &self.id) - .field("name", &self.name) - .field("ty", &self.ty) - .field("linkage", &self.linkage) - .field("init", &self.init) - .finish() - } -} -impl formatter::PrettyPrint for GlobalVariableData { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let name = if matches!(self.linkage, Linkage::Internal) { - display(self.name) - } else { - const_text("(") - + const_text("export") - + const_text(" ") - + display(self.name) - + const_text(")") - }; - - let doc = const_text("(") - + const_text("global") - + const_text(" ") - + name - + const_text(" ") - + const_text("(") - + const_text("id") - + const_text(" ") - + display(self.id.as_u32()) - + const_text(")") - + const_text(" ") - + const_text("(") - + const_text("type") - + const_text(" ") - + text(format!("{}", &self.ty)) - + const_text(")"); - - if let Some(init) = self.init { - doc + const_text(" ") - + const_text("(") - + const_text("const") - + const_text(" ") - + display(init.as_u32()) - + const_text(")") - + const_text(")") - } else { - doc + const_text(")") - } - } -} - -/// A handle to a global variable definition -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct GlobalValue(u32); -entity_impl!(GlobalValue, "gv"); -impl Default for GlobalValue { - #[inline] - fn default() -> Self { - use cranelift_entity::packed_option::ReservedValue; - - Self::reserved_value() - } -} - -/// Data associated with a `GlobalValue`. -/// -/// Globals are allocated statically, and live for the lifetime of the program. -/// In Miden, we allocate globals at the start of the heap. Since all globals are -/// known statically, we instructions which manipulate globals are converted to -/// loads/stores using constant addresses when translated to MASM. -/// -/// Like other entities, globals may also have a [crate::diagnostics::SourceSpan] associated with -/// them. -#[derive(Debug, Clone)] -pub enum GlobalValueData { - /// A symbolic reference to a global variable symbol - /// - /// The type of a symbolic global value is always a pointer, the address - /// of the referenced global variable. - Symbol { - /// The name of the global variable that is referenced - name: Ident, - /// A constant offset, in bytes, from the address of the symbol - offset: i32, - }, - /// A global whose value is given by reading the value from the address - /// derived from another global value and an offset. - Load { - /// The global value whose value is the base pointer - base: GlobalValue, - /// A constant offset, in bytes, from the base address - offset: i32, - /// The type of the value stored at `base + offset` - ty: Type, - }, - /// A global whose value is an address computed as the offset from another global - /// - /// This can be used for `getelementptr`-like situations, such as calculating the - /// address of a field in a struct that is stored in a global variable. - IAddImm { - /// The global value whose value is the base pointer - base: GlobalValue, - /// A constant offset, in units of `ty`, from the base address - offset: i32, - /// The unit type of the offset - /// - /// This can be helpful when computing addresses to elements of an array - /// stored in a global variable. - ty: Type, - }, -} -impl GlobalValueData { - /// Returns true if this global value is a symbolic or computed address - /// which can be resolved at compile-time. - /// - /// Notably, global loads may produce an address, but the value of that - /// address is not known until runtime. - pub fn is_constant_addr(&self) -> bool { - !matches!(self, Self::Load { .. }) - } - - /// Return the computed offset for this global value (relative to it's position in the global - /// table) - pub fn offset(&self) -> i32 { - match self { - Self::Symbol { offset, .. } => *offset, - Self::Load { offset, .. } => *offset, - Self::IAddImm { ref ty, offset, .. } => { - let offset = *offset as usize * ty.size_in_bytes(); - offset - .try_into() - .expect("invalid iadd expression: expected computed offset to fit in i32 range") - } - } - } - - /// Get the type associated with this value, if applicable - pub fn ty(&self) -> Option<&Type> { - match self { - Self::Symbol { .. } => None, - Self::Load { ref ty, .. } => Some(ty), - Self::IAddImm { ref ty, .. } => Some(ty), - } - } - - pub(crate) fn render(&self, dfg: &DataFlowGraph) -> formatter::Document { - use crate::formatter::*; - - match self { - Self::Symbol { name, offset } => { - let offset = *offset; - let offset = if offset == 0 { - None - } else { - Some( - const_text("(") - + const_text("offset") - + const_text(" ") - + display(offset) - + const_text(")"), - ) - }; - - const_text("(") - + const_text("global.symbol") - + const_text(" ") - + display(*name) - + offset.map(|offset| const_text(" ") + offset).unwrap_or_default() - + const_text(")") - } - Self::Load { base, offset, ty } => { - let offset = *offset; - let offset = if offset == 0 { - None - } else { - Some( - const_text("(") - + const_text("offset") - + const_text(" ") - + display(offset) - + const_text(")"), - ) - }; - - const_text("(") - + const_text("global.load") - + const_text(" ") - + text(format!("{}", ty)) - + offset.map(|offset| const_text(" ") + offset).unwrap_or_default() - + const_text(" ") - + dfg.global_value(*base).render(dfg) - + const_text(")") - } - Self::IAddImm { base, offset, ty } => { - const_text("(") - + const_text("global.iadd") - + const_text(" ") - + const_text("(") - + const_text("offset") - + const_text(" ") - + display(*offset) - + const_text(" ") - + const_text(".") - + const_text(" ") - + text(format!("{}", ty)) - + const_text(")") - + const_text(" ") - + dfg.global_value(*base).render(dfg) - + const_text(")") - } - } - } -} diff --git a/hir/src/hash.rs b/hir/src/hash.rs new file mode 100644 index 000000000..18a81c0b7 --- /dev/null +++ b/hir/src/hash.rs @@ -0,0 +1,97 @@ +use core::hash::{Hash, Hasher}; + +/// A type-erased version of [core::hash::Hash] +pub trait DynHash { + fn dyn_hash(&self, hasher: &mut dyn Hasher); +} + +impl DynHash for H { + #[inline] + fn dyn_hash(&self, hasher: &mut dyn Hasher) { + let mut hasher = DynHasher(hasher); + ::hash(self, &mut hasher) + } +} + +pub struct DynHasher<'a>(&'a mut dyn Hasher); + +impl<'a> DynHasher<'a> { + pub fn new(hasher: &'a mut H) -> Self + where + H: Hasher, + { + Self(hasher) + } +} + +impl Hasher for DynHasher<'_> { + #[inline] + fn finish(&self) -> u64 { + self.0.finish() + } + + #[inline] + fn write(&mut self, bytes: &[u8]) { + self.0.write(bytes) + } + + #[inline] + fn write_u8(&mut self, i: u8) { + self.0.write_u8(i); + } + + #[inline] + fn write_i8(&mut self, i: i8) { + self.0.write_i8(i); + } + + #[inline] + fn write_u16(&mut self, i: u16) { + self.0.write_u16(i); + } + + #[inline] + fn write_i16(&mut self, i: i16) { + self.0.write_i16(i); + } + + #[inline] + fn write_u32(&mut self, i: u32) { + self.0.write_u32(i); + } + + #[inline] + fn write_i32(&mut self, i: i32) { + self.0.write_i32(i); + } + + #[inline] + fn write_u64(&mut self, i: u64) { + self.0.write_u64(i); + } + + #[inline] + fn write_i64(&mut self, i: i64) { + self.0.write_i64(i); + } + + #[inline] + fn write_u128(&mut self, i: u128) { + self.0.write_u128(i); + } + + #[inline] + fn write_i128(&mut self, i: i128) { + self.0.write_i128(i); + } + + #[inline] + fn write_usize(&mut self, i: usize) { + self.0.write_usize(i); + } + + #[inline] + fn write_isize(&mut self, i: isize) { + self.0.write_isize(i); + } +} diff --git a/hir/src/immediates.rs b/hir/src/immediates.rs deleted file mode 100644 index 1c211bdfd..000000000 --- a/hir/src/immediates.rs +++ /dev/null @@ -1,790 +0,0 @@ -use core::{ - fmt, - hash::{Hash, Hasher}, -}; - -use super::{Felt, FieldElement, Type}; - -#[derive(Debug, Copy, Clone)] -pub enum Immediate { - I1(bool), - U8(u8), - I8(i8), - U16(u16), - I16(i16), - U32(u32), - I32(i32), - U64(u64), - I64(i64), - U128(u128), - I128(i128), - F64(f64), - Felt(Felt), -} -impl Immediate { - pub fn ty(&self) -> Type { - match self { - Self::I1(_) => Type::I1, - Self::U8(_) => Type::U8, - Self::I8(_) => Type::I8, - Self::U16(_) => Type::U16, - Self::I16(_) => Type::I16, - Self::U32(_) => Type::U32, - Self::I32(_) => Type::I32, - Self::U64(_) => Type::U64, - Self::I64(_) => Type::I64, - Self::U128(_) => Type::U128, - Self::I128(_) => Type::I128, - Self::F64(_) => Type::F64, - Self::Felt(_) => Type::Felt, - } - } - - /// Returns true if this immediate is a non-negative value - pub fn is_non_negative(&self) -> bool { - match self { - Self::I1(i) => *i, - Self::I8(i) => *i > 0, - Self::U8(i) => *i > 0, - Self::I16(i) => *i > 0, - Self::U16(i) => *i > 0, - Self::I32(i) => *i > 0, - Self::U32(i) => *i > 0, - Self::I64(i) => *i > 0, - Self::U64(i) => *i > 0, - Self::U128(i) => *i > 0, - Self::I128(i) => *i > 0, - Self::F64(f) => f.is_sign_positive(), - Self::Felt(_) => true, - } - } - - /// Returns true if this immediate can represent negative values - pub fn is_signed(&self) -> bool { - matches!( - self, - Self::I8(_) | Self::I16(_) | Self::I32(_) | Self::I64(_) | Self::I128(_) | Self::F64(_) - ) - } - - /// Returns true if this immediate can only represent non-negative values - pub fn is_unsigned(&self) -> bool { - matches!( - self, - Self::I1(_) - | Self::U8(_) - | Self::U16(_) - | Self::U32(_) - | Self::U64(_) - | Self::U128(_) - | Self::Felt(_) - ) - } - - /// Returns true if this immediate is an odd integer, otherwise false - /// - /// If the immediate is not an integer, returns `None` - pub fn is_odd(&self) -> Option { - match self { - Self::I1(b) => Some(*b), - Self::U8(i) => Some(*i % 2 == 0), - Self::I8(i) => Some(*i % 2 == 0), - Self::U16(i) => Some(*i % 2 == 0), - Self::I16(i) => Some(*i % 2 == 0), - Self::U32(i) => Some(*i % 2 == 0), - Self::I32(i) => Some(*i % 2 == 0), - Self::U64(i) => Some(*i % 2 == 0), - Self::I64(i) => Some(*i % 2 == 0), - Self::Felt(i) => Some(i.as_int() % 2 == 0), - Self::U128(i) => Some(*i % 2 == 0), - Self::I128(i) => Some(*i % 2 == 0), - Self::F64(_) => None, - } - } - - /// Returns true if this immediate is a non-zero integer, otherwise false - /// - /// If the immediate is not an integer, returns `None` - pub fn as_bool(self) -> Option { - match self { - Self::I1(b) => Some(b), - Self::U8(i) => Some(i != 0), - Self::I8(i) => Some(i != 0), - Self::U16(i) => Some(i != 0), - Self::I16(i) => Some(i != 0), - Self::U32(i) => Some(i != 0), - Self::I32(i) => Some(i != 0), - Self::U64(i) => Some(i != 0), - Self::I64(i) => Some(i != 0), - Self::Felt(i) => Some(i.as_int() != 0), - Self::U128(i) => Some(i != 0), - Self::I128(i) => Some(i != 0), - Self::F64(_) => None, - } - } - - /// Attempts to convert this value to a u32 - pub fn as_u32(self) -> Option { - match self { - Self::I1(b) => Some(b as u32), - Self::U8(b) => Some(b as u32), - Self::I8(b) if b >= 0 => Some(b as u32), - Self::I8(_) => None, - Self::U16(b) => Some(b as u32), - Self::I16(b) if b >= 0 => Some(b as u32), - Self::I16(_) => None, - Self::U32(b) => Some(b), - Self::I32(b) if b >= 0 => Some(b as u32), - Self::I32(_) => None, - Self::U64(b) => u32::try_from(b).ok(), - Self::I64(b) if b >= 0 => u32::try_from(b as u64).ok(), - Self::I64(_) => None, - Self::Felt(i) => u32::try_from(i.as_int()).ok(), - Self::U128(b) if b <= (u32::MAX as u64 as u128) => Some(b as u32), - Self::U128(_) => None, - Self::I128(b) if b >= 0 && b <= (u32::MAX as u64 as i128) => Some(b as u32), - Self::I128(_) => None, - Self::F64(f) => FloatToInt::::to_int(f).ok(), - } - } - - /// Attempts to convert this value to i32 - pub fn as_i32(self) -> Option { - match self { - Self::I1(b) => Some(b as i32), - Self::U8(i) => Some(i as i32), - Self::I8(i) => Some(i as i32), - Self::U16(i) => Some(i as i32), - Self::I16(i) => Some(i as i32), - Self::U32(i) => i.try_into().ok(), - Self::I32(i) => Some(i), - Self::U64(i) => i.try_into().ok(), - Self::I64(i) => i.try_into().ok(), - Self::Felt(i) => i.as_int().try_into().ok(), - Self::U128(i) if i <= (i32::MAX as u32 as u128) => Some(i as u32 as i32), - Self::U128(_) => None, - Self::I128(i) if i >= (i32::MIN as i128) && i <= (i32::MAX as i128) => Some(i as i32), - Self::I128(_) => None, - Self::F64(f) => FloatToInt::::to_int(f).ok(), - } - } - - /// Attempts to convert this value to a field element - pub fn as_felt(self) -> Option { - match self { - Self::I1(b) => Some(Felt::new(b as u64)), - Self::U8(b) => Some(Felt::new(b as u64)), - Self::I8(b) => u64::try_from(b).ok().map(Felt::new), - Self::U16(b) => Some(Felt::new(b as u64)), - Self::I16(b) => u64::try_from(b).ok().map(Felt::new), - Self::U32(b) => Some(Felt::new(b as u64)), - Self::I32(b) => u64::try_from(b).ok().map(Felt::new), - Self::U64(b) => Some(Felt::new(b)), - Self::I64(b) => u64::try_from(b).ok().map(Felt::new), - Self::Felt(i) => Some(i), - Self::U128(b) => u64::try_from(b).ok().map(Felt::new), - Self::I128(b) => u64::try_from(b).ok().map(Felt::new), - Self::F64(f) => FloatToInt::::to_int(f).ok(), - } - } - - /// Attempts to convert this value to u64 - pub fn as_u64(self) -> Option { - match self { - Self::I1(b) => Some(b as u64), - Self::U8(i) => Some(i as u64), - Self::I8(i) if i >= 0 => Some(i as u64), - Self::I8(_) => None, - Self::U16(i) => Some(i as u64), - Self::I16(i) if i >= 0 => Some(i as u16 as u64), - Self::I16(_) => None, - Self::U32(i) => Some(i as u64), - Self::I32(i) if i >= 0 => Some(i as u32 as u64), - Self::I32(_) => None, - Self::U64(i) => Some(i), - Self::I64(i) if i >= 0 => Some(i as u64), - Self::I64(_) => None, - Self::Felt(i) => Some(i.as_int()), - Self::U128(i) => (i).try_into().ok(), - Self::I128(i) if i >= 0 => (i).try_into().ok(), - Self::I128(_) => None, - Self::F64(f) => FloatToInt::::to_int(f).ok(), - } - } - - /// Attempts to convert this value to i64 - pub fn as_i64(self) -> Option { - match self { - Self::I1(b) => Some(b as i64), - Self::U8(i) => Some(i as i64), - Self::I8(i) => Some(i as i64), - Self::U16(i) => Some(i as i64), - Self::I16(i) => Some(i as i64), - Self::U32(i) => Some(i as i64), - Self::I32(i) => Some(i as i64), - Self::U64(i) => (i).try_into().ok(), - Self::I64(i) => Some(i), - Self::Felt(i) => i.as_int().try_into().ok(), - Self::U128(i) if i <= i64::MAX as u128 => Some(i as u64 as i64), - Self::U128(_) => None, - Self::I128(i) => (i).try_into().ok(), - Self::F64(f) => FloatToInt::::to_int(f).ok(), - } - } - - /// Attempts to convert this value to u128 - pub fn as_u128(self) -> Option { - match self { - Self::I1(b) => Some(b as u128), - Self::U8(i) => Some(i as u128), - Self::I8(i) if i >= 0 => Some(i as u128), - Self::I8(_) => None, - Self::U16(i) => Some(i as u128), - Self::I16(i) if i >= 0 => Some(i as u16 as u128), - Self::I16(_) => None, - Self::U32(i) => Some(i as u128), - Self::I32(i) if i >= 0 => Some(i as u32 as u128), - Self::I32(_) => None, - Self::U64(i) => Some(i as u128), - Self::I64(i) if i >= 0 => Some(i as u128), - Self::I64(_) => None, - Self::Felt(i) => Some(i.as_int() as u128), - Self::U128(i) => Some(i), - Self::I128(i) if i >= 0 => (i).try_into().ok(), - Self::I128(_) => None, - Self::F64(f) => FloatToInt::::to_int(f).ok(), - } - } - - /// Attempts to convert this value to i128 - pub fn as_i128(self) -> Option { - match self { - Self::I1(b) => Some(b as i128), - Self::U8(i) => Some(i as i128), - Self::I8(i) => Some(i as i128), - Self::U16(i) => Some(i as i128), - Self::I16(i) => Some(i as i128), - Self::U32(i) => Some(i as i128), - Self::I32(i) => Some(i as i128), - Self::U64(i) => Some(i as i128), - Self::I64(i) => Some(i as i128), - Self::Felt(i) => Some(i.as_int() as i128), - Self::U128(i) if i <= i128::MAX as u128 => Some(i as i128), - Self::U128(_) => None, - Self::I128(i) => Some(i), - Self::F64(f) => FloatToInt::::to_int(f).ok(), - } - } -} -impl fmt::Display for Immediate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::I1(i) => write!(f, "{}", i), - Self::U8(i) => write!(f, "{}", i), - Self::I8(i) => write!(f, "{}", i), - Self::U16(i) => write!(f, "{}", i), - Self::I16(i) => write!(f, "{}", i), - Self::U32(i) => write!(f, "{}", i), - Self::I32(i) => write!(f, "{}", i), - Self::U64(i) => write!(f, "{}", i), - Self::I64(i) => write!(f, "{}", i), - Self::U128(i) => write!(f, "{}", i), - Self::I128(i) => write!(f, "{}", i), - Self::F64(n) => write!(f, "{}", n), - Self::Felt(i) => write!(f, "{}", i), - } - } -} -impl Hash for Immediate { - fn hash(&self, state: &mut H) { - let d = std::mem::discriminant(self); - d.hash(state); - match self { - Self::I1(i) => i.hash(state), - Self::U8(i) => i.hash(state), - Self::I8(i) => i.hash(state), - Self::U16(i) => i.hash(state), - Self::I16(i) => i.hash(state), - Self::U32(i) => i.hash(state), - Self::I32(i) => i.hash(state), - Self::U64(i) => i.hash(state), - Self::I64(i) => i.hash(state), - Self::U128(i) => i.hash(state), - Self::I128(i) => i.hash(state), - Self::F64(f) => { - let bytes = f.to_be_bytes(); - bytes.hash(state) - } - Self::Felt(i) => i.as_int().hash(state), - } - } -} -impl Eq for Immediate {} -impl PartialEq for Immediate { - fn eq(&self, other: &Self) -> bool { - match (*self, *other) { - (Self::I8(x), Self::I8(y)) => x == y, - (Self::U16(x), Self::U16(y)) => x == y, - (Self::I16(x), Self::I16(y)) => x == y, - (Self::U32(x), Self::U32(y)) => x == y, - (Self::I32(x), Self::I32(y)) => x == y, - (Self::U64(x), Self::U64(y)) => x == y, - (Self::I64(x), Self::I64(y)) => x == y, - (Self::U128(x), Self::U128(y)) => x == y, - (Self::I128(x), Self::I128(y)) => x == y, - (Self::F64(x), Self::F64(y)) => x == y, - (Self::Felt(x), Self::Felt(y)) => x == y, - _ => false, - } - } -} -impl PartialEq for Immediate { - fn eq(&self, other: &isize) -> bool { - let y = *other; - match *self { - Self::I1(x) => x == (y == 1), - Self::U8(_) if y < 0 => false, - Self::U8(x) => x as isize == y, - Self::I8(x) => x as isize == y, - Self::U16(_) if y < 0 => false, - Self::U16(x) => x as isize == y, - Self::I16(x) => x as isize == y, - Self::U32(_) if y < 0 => false, - Self::U32(x) => x as isize == y, - Self::I32(x) => x as isize == y, - Self::U64(_) if y < 0 => false, - Self::U64(x) => x == y as i64 as u64, - Self::I64(x) => x == y as i64, - Self::U128(_) if y < 0 => false, - Self::U128(x) => x == y as i128 as u128, - Self::I128(x) => x == y as i128, - Self::F64(_) => false, - Self::Felt(_) if y < 0 => false, - Self::Felt(x) => x.as_int() == y as i64 as u64, - } - } -} -impl PartialOrd for Immediate { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl Ord for Immediate { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - use std::cmp::Ordering; - - match (self, other) { - // Floats require special treatment - (Self::F64(x), Self::F64(y)) => x.total_cmp(y), - // Here we're attempting to compare against any integer immediate, - // so we must attempt to convert the float to the largest possible - // integer representation, i128, and then promote the integer immediate - // to i128 for comparison - // - // If the float is not an integer value, truncate it and compare, then - // adjust the result to account for the truncation - (Self::F64(x), y) => { - let y = y - .as_i128() - .expect("expected rhs to be an integer capable of fitting in an i128"); - if let Ok(x) = FloatToInt::::to_int(*x) { - x.cmp(&y) - } else { - let is_positive = x.is_sign_positive(); - if let Ok(x) = FloatToInt::::to_int((*x).trunc()) { - // Edge case for equality: the float must be bigger due to truncation - match x.cmp(&y) { - Ordering::Equal if is_positive => Ordering::Greater, - Ordering::Equal => Ordering::Less, - o => o, - } - } else { - // The float is larger than i128 can represent, the sign tells us in what - // direction - if is_positive { - Ordering::Greater - } else { - Ordering::Less - } - } - } - } - (x, y @ Self::F64(_)) => y.cmp(x).reverse(), - // u128 immediates require separate treatment - (Self::U128(x), Self::U128(y)) => x.cmp(y), - (Self::U128(x), y) => { - let y = y.as_u128().expect("expected rhs to be an integer in the range of u128"); - x.cmp(&y) - } - (x, Self::U128(y)) => { - let x = x.as_u128().expect("expected lhs to be an integer in the range of u128"); - x.cmp(y) - } - // i128 immediates require separate treatment - (Self::I128(x), Self::I128(y)) => x.cmp(y), - // We're only comparing against values here which are u64, i64, or smaller than 64-bits - (Self::I128(x), y) => { - let y = y.as_i128().expect("expected rhs to be an integer smaller than i128"); - x.cmp(&y) - } - (x, Self::I128(y)) => { - let x = x.as_i128().expect("expected lhs to be an integer smaller than i128"); - x.cmp(y) - } - // u64 immediates may not fit in an i64 - (Self::U64(x), Self::U64(y)) => x.cmp(y), - // We're only comparing against values here which are i64, or smaller than 64-bits - (Self::U64(x), y) => { - let y = - y.as_i64().expect("expected rhs to be an integer capable of fitting in an i64") - as u64; - x.cmp(&y) - } - (x, Self::U64(y)) => { - let x = - x.as_i64().expect("expected lhs to be an integer capable of fitting in an i64") - as u64; - x.cmp(y) - } - // All immediates at this point are i64 or smaller - (x, y) => { - let x = - x.as_i64().expect("expected lhs to be an integer capable of fitting in an i64"); - let y = - y.as_i64().expect("expected rhs to be an integer capable of fitting in an i64"); - x.cmp(&y) - } - } - } -} -impl From for Type { - #[inline] - fn from(imm: Immediate) -> Self { - imm.ty() - } -} -impl From<&Immediate> for Type { - #[inline(always)] - fn from(imm: &Immediate) -> Self { - imm.ty() - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: bool) -> Self { - Self::I1(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: i8) -> Self { - Self::I8(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: u8) -> Self { - Self::U8(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: i16) -> Self { - Self::I16(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: u16) -> Self { - Self::U16(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: i32) -> Self { - Self::I32(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: u32) -> Self { - Self::U32(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: i64) -> Self { - Self::I64(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: u64) -> Self { - Self::U64(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: u128) -> Self { - Self::U128(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: i128) -> Self { - Self::I128(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: f64) -> Self { - Self::F64(value) - } -} -impl From for Immediate { - #[inline(always)] - fn from(value: char) -> Self { - Self::I32(value as u32 as i32) - } -} - -trait FloatToInt: Sized { - const ZERO: T; - - fn upper_bound() -> Self; - fn lower_bound() -> Self; - fn to_int(self) -> Result; - unsafe fn to_int_unchecked(self) -> T; -} -impl FloatToInt for f64 { - const ZERO: i8 = 0; - - fn upper_bound() -> Self { - f64::from(i8::MAX) + 1.0 - } - - fn lower_bound() -> Self { - f64::from(i8::MIN) - 1.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> i8 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: u8 = 0; - - fn upper_bound() -> Self { - f64::from(u8::MAX) + 1.0 - } - - fn lower_bound() -> Self { - 0.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> u8 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: i16 = 0; - - fn upper_bound() -> Self { - f64::from(i16::MAX) + 1.0 - } - - fn lower_bound() -> Self { - f64::from(i16::MIN) - 1.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> i16 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: u16 = 0; - - fn upper_bound() -> Self { - f64::from(u16::MAX) + 1.0 - } - - fn lower_bound() -> Self { - 0.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> u16 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: i32 = 0; - - fn upper_bound() -> Self { - f64::from(i32::MAX) + 1.0 - } - - fn lower_bound() -> Self { - f64::from(i32::MIN) - 1.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> i32 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: u32 = 0; - - fn upper_bound() -> Self { - f64::from(u32::MAX) + 1.0 - } - - fn lower_bound() -> Self { - 0.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> u32 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: i64 = 0; - - fn upper_bound() -> Self { - 63.0f64.exp2() - } - - fn lower_bound() -> Self { - (63.0f64.exp2() * -1.0) - 1.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> i64 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: u64 = 0; - - fn upper_bound() -> Self { - 64.0f64.exp2() - } - - fn lower_bound() -> Self { - 0.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> u64 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: Felt = Felt::ZERO; - - fn upper_bound() -> Self { - 64.0f64.exp2() - 32.0f64.exp2() + 1.0 - } - - fn lower_bound() -> Self { - 0.0 - } - - fn to_int(self) -> Result { - float_to_int(self).map(Felt::new) - } - - unsafe fn to_int_unchecked(self) -> Felt { - Felt::new(f64::to_int_unchecked::(self)) - } -} -impl FloatToInt for f64 { - const ZERO: u128 = 0; - - fn upper_bound() -> Self { - 128.0f64.exp2() - } - - fn lower_bound() -> Self { - 0.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> u128 { - f64::to_int_unchecked(self) - } -} -impl FloatToInt for f64 { - const ZERO: i128 = 0; - - fn upper_bound() -> Self { - f64::from(i128::BITS - 1).exp2() - } - - fn lower_bound() -> Self { - (f64::from(i128::BITS - 1) * -1.0).exp2() - 1.0 - } - - fn to_int(self) -> Result { - float_to_int(self) - } - - unsafe fn to_int_unchecked(self) -> i128 { - f64::to_int_unchecked(self) - } -} - -fn float_to_int(f: f64) -> Result -where - I: Copy, - f64: FloatToInt, -{ - use std::num::FpCategory; - match f.classify() { - FpCategory::Nan | FpCategory::Infinite | FpCategory::Subnormal => Err(()), - FpCategory::Zero => Ok(>::ZERO), - FpCategory::Normal => { - if f == f.trunc() - && f > >::lower_bound() - && f < >::upper_bound() - { - // SAFETY: We know that x must be integral, and within the bounds of its type - Ok(unsafe { >::to_int_unchecked(f) }) - } else { - Err(()) - } - } - } -} diff --git a/hir/src/insert.rs b/hir/src/insert.rs deleted file mode 100644 index f6d91cd8c..000000000 --- a/hir/src/insert.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::{Block, Function, ProgramPoint}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Insert { - Before, - After, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct InsertionPoint { - pub at: ProgramPoint, - pub action: Insert, -} -impl InsertionPoint { - #[inline] - pub const fn new(at: ProgramPoint, action: Insert) -> Self { - Self { at, action } - } - - #[inline] - pub const fn before(at: ProgramPoint) -> Self { - Self { - at, - action: Insert::Before, - } - } - - #[inline] - pub const fn after(at: ProgramPoint) -> Self { - Self { - at, - action: Insert::After, - } - } - - pub fn block(&self, function: &Function) -> Block { - match self.at { - ProgramPoint::Block(block) => block, - ProgramPoint::Inst(inst) => function - .dfg - .inst_block(inst) - .expect("cannot insert relative to detached instruction"), - } - } -} diff --git a/hir/src/instruction.rs b/hir/src/instruction.rs deleted file mode 100644 index 36eae627f..000000000 --- a/hir/src/instruction.rs +++ /dev/null @@ -1,1463 +0,0 @@ -use core::ops::{Deref, DerefMut}; -use std::collections::BTreeSet; - -use cranelift_entity::entity_impl; -use intrusive_collections::{intrusive_adapter, LinkedListLink}; -use smallvec::{smallvec, SmallVec}; - -use self::formatter::PrettyPrint; -use crate::{ - diagnostics::{Span, Spanned}, - *, -}; - -/// A handle to a single instruction -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Inst(u32); -entity_impl!(Inst, "inst"); - -/// Represents the data associated with an `Inst`. -/// -/// Specifically, this represents a leaf node in the control flow graph of -/// a function, i.e. it links a specific instruction in to the sequence of -/// instructions belonging to a specific block. -#[derive(Spanned)] -pub struct InstNode { - pub link: LinkedListLink, - pub key: Inst, - pub block: Block, - #[span] - pub data: Span, -} -impl fmt::Debug for InstNode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", &self.data) - } -} -impl InstNode { - pub fn new(key: Inst, block: Block, data: Span) -> Self { - Self { - link: LinkedListLink::default(), - key, - block, - data, - } - } - - pub fn deep_clone(&self, value_lists: &mut ValueListPool) -> Self { - let span = self.data.span(); - Self { - link: LinkedListLink::default(), - key: self.key, - block: self.block, - data: Span::new(span, self.data.deep_clone(value_lists)), - } - } - - pub fn replace(&mut self, data: Span) { - self.data = data; - } -} -impl Deref for InstNode { - type Target = Instruction; - - #[inline] - fn deref(&self) -> &Self::Target { - &self.data - } -} -impl DerefMut for InstNode { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.data - } -} -impl AsRef for InstNode { - #[inline] - fn as_ref(&self) -> &Instruction { - &self.data - } -} -impl AsMut for InstNode { - #[inline] - fn as_mut(&mut self) -> &mut Instruction { - &mut self.data - } -} - -intrusive_adapter!(pub InstAdapter = UnsafeRef: InstNode { link: LinkedListLink }); - -/// A type alias for `LinkedList` -pub type InstructionList = intrusive_collections::LinkedList; - -pub type InstructionCursor<'a> = intrusive_collections::linked_list::Cursor<'a, InstAdapter>; -pub type InstructionCursorMut<'a> = intrusive_collections::linked_list::CursorMut<'a, InstAdapter>; - -/// Represents the type of instruction associated with a particular opcode -#[derive(Debug)] -pub enum Instruction { - GlobalValue(GlobalValueOp), - LocalVar(LocalVarOp), - BinaryOp(BinaryOp), - BinaryOpImm(BinaryOpImm), - UnaryOp(UnaryOp), - UnaryOpImm(UnaryOpImm), - Call(Call), - Br(Br), - CondBr(CondBr), - Switch(Switch), - Ret(Ret), - RetImm(RetImm), - Load(LoadOp), - PrimOp(PrimOp), - PrimOpImm(PrimOpImm), - Test(Test), - InlineAsm(InlineAsm), -} -impl Instruction { - pub fn deep_clone(&self, value_lists: &mut ValueListPool) -> Self { - match self { - Self::GlobalValue(gv) => Self::GlobalValue(gv.clone()), - Self::LocalVar(lv) => Self::LocalVar(lv.clone()), - Self::BinaryOp(op) => Self::BinaryOp(op.clone()), - Self::BinaryOpImm(op) => Self::BinaryOpImm(op.clone()), - Self::UnaryOp(op) => Self::UnaryOp(op.clone()), - Self::UnaryOpImm(op) => Self::UnaryOpImm(op.clone()), - Self::Call(call) => Self::Call(Call { - args: call.args.deep_clone(value_lists), - ..call.clone() - }), - Self::Br(br) => Self::Br(Br { - successor: br.successor.deep_clone(value_lists), - ..br.clone() - }), - Self::CondBr(br) => Self::CondBr(CondBr { - then_dest: br.then_dest.deep_clone(value_lists), - else_dest: br.else_dest.deep_clone(value_lists), - ..br.clone() - }), - Self::Switch(op) => Self::Switch(Switch { - arms: op - .arms - .iter() - .map(|arm| SwitchArm { - value: arm.value, - successor: arm.successor.deep_clone(value_lists), - }) - .collect(), - default: op.default.deep_clone(value_lists), - ..op.clone() - }), - Self::Ret(op) => Self::Ret(Ret { - args: op.args.deep_clone(value_lists), - ..op.clone() - }), - Self::RetImm(op) => Self::RetImm(op.clone()), - Self::Load(op) => Self::Load(op.clone()), - Self::PrimOp(op) => Self::PrimOp(PrimOp { - args: op.args.deep_clone(value_lists), - ..op.clone() - }), - Self::PrimOpImm(op) => Self::PrimOpImm(PrimOpImm { - args: op.args.deep_clone(value_lists), - ..op.clone() - }), - Self::Test(op) => Self::Test(op.clone()), - Self::InlineAsm(op) => Self::InlineAsm(InlineAsm { - args: op.args.deep_clone(value_lists), - ..op.clone() - }), - } - } - - pub fn opcode(&self) -> Opcode { - match self { - Self::GlobalValue(GlobalValueOp { ref op, .. }) - | Self::LocalVar(LocalVarOp { ref op, .. }) - | Self::BinaryOp(BinaryOp { ref op, .. }) - | Self::BinaryOpImm(BinaryOpImm { ref op, .. }) - | Self::UnaryOp(UnaryOp { ref op, .. }) - | Self::UnaryOpImm(UnaryOpImm { ref op, .. }) - | Self::Call(Call { ref op, .. }) - | Self::Br(Br { ref op, .. }) - | Self::CondBr(CondBr { ref op, .. }) - | Self::Switch(Switch { ref op, .. }) - | Self::Ret(Ret { ref op, .. }) - | Self::RetImm(RetImm { ref op, .. }) - | Self::Load(LoadOp { ref op, .. }) - | Self::PrimOp(PrimOp { ref op, .. }) - | Self::PrimOpImm(PrimOpImm { ref op, .. }) - | Self::Test(Test { ref op, .. }) - | Self::InlineAsm(InlineAsm { ref op, .. }) => *op, - } - } - - /// Returns true if this instruction has side effects, or may have side effects - /// - /// Side effects are defined as control flow, writing memory, trapping execution, - /// I/O, etc. - #[inline] - pub fn has_side_effects(&self) -> bool { - self.opcode().has_side_effects() - } - - /// Returns true if this instruction is a binary operator requiring two operands - /// - /// NOTE: Binary operators with immediate operands are not considered binary for - /// this purpose, as they only require a single operand to be provided to the - /// instruction, the immediate being the other one provided by the instruction - /// itself. - pub fn is_binary(&self) -> bool { - matches!(self, Self::BinaryOp(_)) - } - - /// Returns true if this instruction is a binary operator whose operands may - /// appear in any order. - #[inline] - pub fn is_commutative(&self) -> bool { - self.opcode().is_commutative() - } - - /// Get the [Overflow] flag for this instruction, if applicable - pub fn overflow(&self) -> Option { - match self { - Self::BinaryOp(BinaryOp { overflow, .. }) - | Self::BinaryOpImm(BinaryOpImm { overflow, .. }) - | Self::UnaryOp(UnaryOp { overflow, .. }) - | Self::UnaryOpImm(UnaryOpImm { overflow, .. }) => *overflow, - _ => None, - } - } - - pub fn arguments<'a>(&'a self, pool: &'a ValueListPool) -> &[Value] { - match self { - Self::BinaryOp(BinaryOp { ref args, .. }) => args.as_slice(), - Self::BinaryOpImm(BinaryOpImm { ref arg, .. }) => core::slice::from_ref(arg), - Self::UnaryOp(UnaryOp { ref arg, .. }) => core::slice::from_ref(arg), - Self::Call(Call { ref args, .. }) => args.as_slice(pool), - Self::CondBr(CondBr { ref cond, .. }) => core::slice::from_ref(cond), - Self::Switch(Switch { ref arg, .. }) => core::slice::from_ref(arg), - Self::Ret(Ret { ref args, .. }) => args.as_slice(pool), - Self::Load(LoadOp { ref addr, .. }) => core::slice::from_ref(addr), - Self::PrimOp(PrimOp { ref args, .. }) => args.as_slice(pool), - Self::PrimOpImm(PrimOpImm { ref args, .. }) => args.as_slice(pool), - Self::Test(Test { ref arg, .. }) => core::slice::from_ref(arg), - Self::InlineAsm(InlineAsm { ref args, .. }) => args.as_slice(pool), - Self::LocalVar(LocalVarOp { ref args, .. }) => args.as_slice(pool), - Self::GlobalValue(_) | Self::UnaryOpImm(_) | Self::Br(_) | Self::RetImm(_) => &[], - } - } - - pub fn arguments_mut<'a>(&'a mut self, pool: &'a mut ValueListPool) -> &mut [Value] { - match self { - Self::BinaryOp(BinaryOp { ref mut args, .. }) => args.as_mut_slice(), - Self::BinaryOpImm(BinaryOpImm { ref mut arg, .. }) => core::slice::from_mut(arg), - Self::UnaryOp(UnaryOp { ref mut arg, .. }) => core::slice::from_mut(arg), - Self::Call(Call { ref mut args, .. }) => args.as_mut_slice(pool), - Self::CondBr(CondBr { ref mut cond, .. }) => core::slice::from_mut(cond), - Self::Switch(Switch { ref mut arg, .. }) => core::slice::from_mut(arg), - Self::Ret(Ret { ref mut args, .. }) => args.as_mut_slice(pool), - Self::Load(LoadOp { ref mut addr, .. }) => core::slice::from_mut(addr), - Self::PrimOp(PrimOp { ref mut args, .. }) => args.as_mut_slice(pool), - Self::PrimOpImm(PrimOpImm { ref mut args, .. }) => args.as_mut_slice(pool), - Self::Test(Test { ref mut arg, .. }) => core::slice::from_mut(arg), - Self::InlineAsm(InlineAsm { ref mut args, .. }) => args.as_mut_slice(pool), - Self::LocalVar(LocalVarOp { ref mut args, .. }) => args.as_mut_slice(pool), - Self::GlobalValue(_) | Self::UnaryOpImm(_) | Self::Br(_) | Self::RetImm(_) => &mut [], - } - } - - pub fn analyze_branch<'a>(&'a self, pool: &'a ValueListPool) -> BranchInfo<'a> { - match self { - Self::Br(Br { successor, .. }) => { - BranchInfo::SingleDest(SuccessorInfo::new(successor, pool)) - } - Self::CondBr(CondBr { - ref then_dest, - ref else_dest, - .. - }) => BranchInfo::MultiDest(vec![ - SuccessorInfo::new(then_dest, pool), - SuccessorInfo::new(else_dest, pool), - ]), - Self::Switch(Switch { - ref arms, - ref default, - .. - }) => { - let mut targets = arms - .iter() - .map(|succ| SuccessorInfo::new(&succ.successor, pool)) - .collect::>(); - targets.push(SuccessorInfo::new(default, pool)); - BranchInfo::MultiDest(targets) - } - _ => BranchInfo::NotABranch, - } - } - - pub fn analyze_call<'a>(&'a self, pool: &'a ValueListPool) -> CallInfo<'a> { - match self { - Self::Call(ref c) => CallInfo::Direct(c.callee, c.args.as_slice(pool)), - _ => CallInfo::NotACall, - } - } -} - -#[derive(Debug)] -pub enum BranchInfo<'a> { - NotABranch, - SingleDest(SuccessorInfo<'a>), - MultiDest(Vec>), -} - -pub enum CallInfo<'a> { - NotACall, - Direct(FunctionIdent, &'a [Value]), -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Opcode { - /// Asserts the given value is 1 - Assert, - /// Asserts the given value is 0 - Assertz, - /// Asserts the two given values are equal - AssertEq, - /// Represents an immediate boolean value (1-bit integer) - ImmI1, - /// Represents an immediate unsigned 8-bit integer value - ImmU8, - /// Represents an immediate signed 8-bit integer value - ImmI8, - /// Represents an immediate unsigned 16-bit integer value - ImmU16, - /// Represents an immediate signed 16-bit integer value - ImmI16, - /// Represents an immediate unsigned 32-bit integer value - ImmU32, - /// Represents an immediate signed 32-bit integer value - ImmI32, - /// Represents an immediate unsigned 64-bit integer value - ImmU64, - /// Represents an immediate signed 64-bit integer value - ImmI64, - /// Represents an immediate unsigned 128-bit integer value - ImmU128, - /// Represents an immediate signed 128-bit integer value - ImmI128, - /// Represents an immediate field element - ImmFelt, - /// Represents an immediate 64-bit floating-point value - ImmF64, - /// Allocates a new "null" value in a temporary memory slot, where null is defined by - /// the semantics of the type. The result of this instruction is always a pointer to - /// the allocated type. - /// - /// For integral types, the null value is always zero. - /// - /// For pointer types, the null value is equal to the address of the start of the linear - /// memory range, i.e. address `0x0`. - /// - /// For structs and arrays, the null value is a value equal in size (in bytes) to the size - /// of the type, but whose contents are undefined, i.e. you cannot assume that the binary - /// representation of the value is zeroed. - Alloca, - /// Like the WebAssembly `memory.grow` instruction, this allocates a given number of pages from - /// the global heap, and returns the previous size of the heap, in pages. Each page is 64kb - /// by default. - /// - /// For the time being, this instruction is emulated using a heap pointer global which tracks - /// the "end" of the available heap. Nothing actually prevents one from accessing memory past - /// that point (assuming it is within the 32-bit address range), however this allows us to - /// support code compiled for the `wasm32-unknown-unknown` target cleanly. - MemGrow, - /// Corresponds to the WebAssembly `memory.size` instruction, this returns the current number - /// of pages allocated to the global heap. Each page is 64kb by default. - /// - /// This simply computes the offset in pages of the current global heap pointer - MemSize, - /// This instruction is used to represent a global value in the IR - /// - /// See [GlobalValueOp] and [GlobalValueData] for details on what types of values are - /// represented behind this opcode. - GlobalValue, - /// Loads a value from a pointer to memory - Load, - /// Stores a value to a pointer to memory - Store, - /// Writes `n` copies of a value starting at a given address in memory - MemSet, - /// Copies `n` values of a given type from a source pointer to a destination pointer - MemCpy, - /// Casts a pointer value to an integral type - PtrToInt, - /// Casts an integral type to a pointer value - IntToPtr, - /// Casts from a field element type to an integral type - /// - /// It is not valid to perform a cast on any value other than a field element, see - /// `Trunc`, `Zext`, and `Sext` for casts between machine integer types. - Cast, - /// Reinterprets the bits of an integral type from signed to unsigned or vice versa - /// - /// This is intended for representing casts that have no validation whatsoever. In - /// particular, it is designed to support WebAssembly translation, which represents all - /// numeric types using signed types by default, and selectively interprets them as - /// unsigned as needed. This results in many casts where we do not want to generate - /// asserts and other forms of dynamic checks, as the types are already validated. - Bitcast, - /// Truncates a larger integral type to a smaller integral type, e.g. i64 -> i32 - Trunc, - /// Zero-extends a smaller unsigned integral type to a larger unsigned integral type, e.g. u32 - /// -> u64 - Zext, - /// Sign-extends a smaller signed integral type to a larger signed integral type, e.g. i32 -> - /// i64 - Sext, - /// Returns true if argument fits in the given integral type, e.g. u32, otherwise false - Test, - /// Selects between two values given a conditional - Select, - Add, - Sub, - Mul, - Div, - Mod, - DivMod, - Neg, - Inv, - Incr, - Ilog2, - Pow2, - Exp, - Not, - Bnot, - And, - Band, - Or, - Bor, - Xor, - Bxor, - Shl, - Shr, - Rotl, - Rotr, - Popcnt, - Clz, - Ctz, - Clo, - Cto, - Eq, - Neq, - Gt, - Gte, - Lt, - Lte, - IsOdd, - Min, - Max, - Call, - Syscall, - Br, - CondBr, - Switch, - Ret, - Unreachable, - InlineAsm, - /// NOTE: Internal Use Only! - /// This is used during the spill pass to record spilling a value to temporary memory, - /// which will eventually be allocated to a local variable slot and rewritten as store.local - /// before that pass completes - Spill, - /// NOTE: Internal Use Only! - /// This is used during the spill pass to record reloading a spilled value as a new copy. - /// This is rewritten to a load.local before the pass completes. - Reload, -} -impl Opcode { - pub fn is_terminator(&self) -> bool { - matches!(self, Self::Br | Self::CondBr | Self::Switch | Self::Ret | Self::Unreachable) - } - - pub fn is_branch(&self) -> bool { - matches!(self, Self::Br | Self::CondBr | Self::Switch) - } - - pub fn is_call(&self) -> bool { - matches!(self, Self::Call | Self::Syscall) - } - - pub fn is_commutative(&self) -> bool { - matches!( - self, - Self::Add - | Self::Mul - | Self::Min - | Self::Max - | Self::Eq - | Self::Neq - | Self::And - | Self::Band - | Self::Or - | Self::Bor - | Self::Xor - | Self::Bxor - ) - } - - pub fn is_assertion(&self) -> bool { - matches!(self, Self::Assert | Self::Assertz | Self::AssertEq) - } - - pub fn reads_memory(&self) -> bool { - matches!( - self, - Self::MemGrow - | Self::MemCpy - | Self::MemSize - | Self::Load - | Self::Call - | Self::Syscall - | Self::InlineAsm - | Self::Reload - ) - } - - pub fn writes_memory(&self) -> bool { - matches!( - self, - Self::MemGrow - | Self::MemSet - | Self::MemCpy - | Self::Store - | Self::Call - | Self::Syscall - | Self::InlineAsm - | Self::Spill - ) - } - - pub fn has_side_effects(&self) -> bool { - match self { - // These opcodes are all effectful - Self::Assert - | Self::Assertz - | Self::AssertEq - | Self::Store - | Self::Alloca - | Self::MemGrow - | Self::MemSet - | Self::MemCpy - | Self::Call - | Self::Syscall - | Self::Br - | Self::CondBr - | Self::Switch - | Self::Ret - | Self::Unreachable - | Self::InlineAsm - | Self::Spill - | Self::Reload => true, - // These opcodes are not - Self::ImmI1 - | Self::ImmU8 - | Self::ImmI8 - | Self::ImmU16 - | Self::ImmI16 - | Self::ImmU32 - | Self::ImmI32 - | Self::ImmU64 - | Self::ImmI64 - | Self::ImmU128 - | Self::ImmI128 - | Self::ImmFelt - | Self::ImmF64 - | Self::MemSize - | Self::GlobalValue - | Self::Load - | Self::PtrToInt - | Self::IntToPtr - | Self::Cast - | Self::Bitcast - | Self::Trunc - | Self::Zext - | Self::Sext - | Self::Test - | Self::Select - | Self::Add - | Self::Sub - | Self::Mul - | Self::Div - | Self::Mod - | Self::DivMod - | Self::Neg - | Self::Inv - | Self::Incr - | Self::Ilog2 - | Self::Pow2 - | Self::Exp - | Self::Not - | Self::Bnot - | Self::And - | Self::Band - | Self::Or - | Self::Bor - | Self::Xor - | Self::Bxor - | Self::Shl - | Self::Shr - | Self::Rotl - | Self::Rotr - | Self::Popcnt - | Self::Clz - | Self::Ctz - | Self::Clo - | Self::Cto - | Self::Eq - | Self::Neq - | Self::Gt - | Self::Gte - | Self::Lt - | Self::Lte - | Self::IsOdd - | Self::Min - | Self::Max => false, - } - } - - pub fn num_fixed_args(&self) -> usize { - match self { - Self::Assert | Self::Assertz => 1, - Self::AssertEq => 2, - // Immediates/constants have none - Self::ImmI1 - | Self::ImmU8 - | Self::ImmI8 - | Self::ImmU16 - | Self::ImmI16 - | Self::ImmU32 - | Self::ImmI32 - | Self::ImmU64 - | Self::ImmI64 - | Self::ImmU128 - | Self::ImmI128 - | Self::ImmFelt - | Self::ImmF64 => 0, - // Binary ops always have two - Self::Store - | Self::Add - | Self::Sub - | Self::Mul - | Self::Div - | Self::Mod - | Self::DivMod - | Self::Exp - | Self::And - | Self::Band - | Self::Or - | Self::Bor - | Self::Xor - | Self::Bxor - | Self::Shl - | Self::Shr - | Self::Rotl - | Self::Rotr - | Self::Eq - | Self::Neq - | Self::Gt - | Self::Gte - | Self::Lt - | Self::Lte - | Self::Min - | Self::Max => 2, - // Unary ops always have one - Self::MemGrow - | Self::Load - | Self::PtrToInt - | Self::IntToPtr - | Self::Cast - | Self::Bitcast - | Self::Trunc - | Self::Zext - | Self::Sext - | Self::Test - | Self::Neg - | Self::Inv - | Self::Incr - | Self::Ilog2 - | Self::Pow2 - | Self::Popcnt - | Self::Clz - | Self::Ctz - | Self::Clo - | Self::Cto - | Self::Not - | Self::Bnot - | Self::IsOdd => 1, - // Select requires condition, arg1, and arg2 - Self::Select => 3, - // memset requires destination, arity, and value - // memcpy requires source, destination, and arity - Self::MemSet | Self::MemCpy => 3, - // Calls are entirely variable - Self::Call | Self::Syscall => 0, - // Unconditional branches have no fixed arguments - Self::Br => 0, - // Ifs have a single argument, the conditional - Self::CondBr => 1, - // Switches have a single argument, the input value - Self::Switch => 1, - // Returns require at least one argument - Self::Ret => 1, - // The following require no arguments - Self::MemSize - | Self::GlobalValue - | Self::Alloca - | Self::Unreachable - | Self::InlineAsm => 0, - // Spills/reloads take a single argument - Self::Spill | Self::Reload => 1, - } - } - - pub(super) fn results(&self, overflow: Option, ctrl_ty: Type) -> SmallVec<[Type; 1]> { - use smallvec::smallvec; - - match self { - // These ops have no results - Self::Assert - | Self::Assertz - | Self::AssertEq - | Self::Store - | Self::MemSet - | Self::MemCpy - | Self::Br - | Self::CondBr - | Self::Switch - | Self::Ret - | Self::Unreachable - | Self::Spill => smallvec![], - // These ops have fixed result types - Self::Test - | Self::IsOdd - | Self::Not - | Self::And - | Self::Or - | Self::Xor - | Self::Eq - | Self::Neq - | Self::Gt - | Self::Gte - | Self::Lt - | Self::Lte => smallvec![Type::I1], - // For these ops, the controlling type variable determines the type for the op - Self::ImmI1 - | Self::ImmU8 - | Self::ImmI8 - | Self::ImmU16 - | Self::ImmI16 - | Self::ImmU32 - | Self::ImmI32 - | Self::ImmU64 - | Self::ImmI64 - | Self::ImmU128 - | Self::ImmI128 - | Self::ImmFelt - | Self::ImmF64 - | Self::GlobalValue - | Self::Alloca - | Self::PtrToInt - | Self::IntToPtr - | Self::Cast - | Self::Bitcast - | Self::Trunc - | Self::Zext - | Self::Sext - | Self::Select - | Self::Div - | Self::Min - | Self::Max - | Self::Neg - | Self::Inv - | Self::Pow2 - | Self::Mod - | Self::DivMod - | Self::Exp - | Self::Bnot - | Self::Band - | Self::Bor - | Self::Bxor - | Self::Rotl - | Self::Rotr - | Self::MemGrow - | Self::MemSize - | Self::Reload => { - smallvec![ctrl_ty] - } - // These ops always return a usize/u32 type - Self::Ilog2 | Self::Popcnt | Self::Clz | Self::Clo | Self::Ctz | Self::Cto => { - smallvec![Type::U32] - } - // These ops have overflowing variants which returns an additional result in that case - Self::Add | Self::Sub | Self::Mul | Self::Incr | Self::Shl | Self::Shr => { - match overflow { - Some(Overflow::Overflowing) => smallvec![Type::I1, ctrl_ty], - _ => smallvec![ctrl_ty], - } - } - // The result type of a load is derived from the pointee type - Self::Load => { - smallvec![ctrl_ty.pointee().expect("expected pointer type").clone()] - } - // Call results are handled separately - Self::Call | Self::Syscall | Self::InlineAsm => unreachable!(), - } - } -} -impl fmt::Display for Opcode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Assert => f.write_str("assert"), - Self::Assertz => f.write_str("assertz"), - Self::AssertEq => f.write_str("assert.eq"), - Self::ImmI1 => f.write_str("const.i1"), - Self::ImmU8 => f.write_str("const.u8"), - Self::ImmI8 => f.write_str("const.i8"), - Self::ImmU16 => f.write_str("const.u16"), - Self::ImmI16 => f.write_str("const.i16"), - Self::ImmU32 => f.write_str("const.u32"), - Self::ImmI32 => f.write_str("const.i32"), - Self::ImmU64 => f.write_str("const.u64"), - Self::ImmI64 => f.write_str("const.i64"), - Self::ImmU128 => f.write_str("const.u128"), - Self::ImmI128 => f.write_str("const.i128"), - Self::ImmFelt => f.write_str("const.felt"), - Self::ImmF64 => f.write_str("const.f64"), - Self::GlobalValue => f.write_str("global"), - Self::Alloca => f.write_str("alloca"), - Self::MemGrow => f.write_str("memory.grow"), - Self::MemSize => f.write_str("memory.size"), - Self::Load => f.write_str("load"), - Self::Store => f.write_str("store"), - Self::MemSet => f.write_str("memset"), - Self::MemCpy => f.write_str("memcpy"), - Self::PtrToInt => f.write_str("ptrtoint"), - Self::IntToPtr => f.write_str("inttoptr"), - Self::Cast => f.write_str("cast"), - Self::Bitcast => f.write_str("bitcast"), - Self::Trunc => f.write_str("trunc"), - Self::Zext => f.write_str("zext"), - Self::Sext => f.write_str("sext"), - Self::Br => f.write_str("br"), - Self::CondBr => f.write_str("condbr"), - Self::Switch => f.write_str("switch"), - Self::Call => f.write_str("call"), - Self::Syscall => f.write_str("syscall"), - Self::Ret => f.write_str("ret"), - Self::Test => f.write_str("test"), - Self::Select => f.write_str("select"), - Self::Add => f.write_str("add"), - Self::Sub => f.write_str("sub"), - Self::Mul => f.write_str("mul"), - Self::Div => f.write_str("div"), - Self::Mod => f.write_str("mod"), - Self::DivMod => f.write_str("divmod"), - Self::Exp => f.write_str("exp"), - Self::Neg => f.write_str("neg"), - Self::Inv => f.write_str("inv"), - Self::Incr => f.write_str("incr"), - Self::Ilog2 => f.write_str("ilog2"), - Self::Pow2 => f.write_str("pow2"), - Self::Not => f.write_str("not"), - Self::Bnot => f.write_str("bnot"), - Self::And => f.write_str("and"), - Self::Band => f.write_str("band"), - Self::Or => f.write_str("or"), - Self::Bor => f.write_str("bor"), - Self::Xor => f.write_str("xor"), - Self::Bxor => f.write_str("bxor"), - Self::Shl => f.write_str("shl"), - Self::Shr => f.write_str("shr"), - Self::Rotl => f.write_str("rotl"), - Self::Rotr => f.write_str("rotr"), - Self::Popcnt => f.write_str("popcnt"), - Self::Clz => f.write_str("clz"), - Self::Ctz => f.write_str("ctz"), - Self::Clo => f.write_str("clo"), - Self::Cto => f.write_str("cto"), - Self::Eq => f.write_str("eq"), - Self::Neq => f.write_str("neq"), - Self::Gt => f.write_str("gt"), - Self::Gte => f.write_str("gte"), - Self::Lt => f.write_str("lt"), - Self::Lte => f.write_str("lte"), - Self::IsOdd => f.write_str("is_odd"), - Self::Min => f.write_str("min"), - Self::Max => f.write_str("max"), - Self::Unreachable => f.write_str("unreachable"), - Self::InlineAsm => f.write_str("asm"), - Self::Spill => f.write_str("spill"), - Self::Reload => f.write_str("reload"), - } - } -} - -/// This enumeration represents the various ways in which arithmetic operations -/// can be configured to behave when either the operands or results over/underflow -/// the range of the integral type. -/// -/// Always check the documentation of the specific instruction involved to see if there -/// are any specific differences in how this enum is interpreted compared to the default -/// meaning of each variant. -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] -pub enum Overflow { - /// Typically, this means the operation is performed using the equivalent field element - /// operation, rather than a dedicated operation for the given type. Because of this, the - /// result of the operation may exceed that of the integral type expected, but this will - /// not be caught right away. - /// - /// It is the callers responsibility to ensure that resulting value is in range. - #[default] - Unchecked, - /// The operation will trap if the operands, or the result, is not valid for the range of the - /// integral type involved, e.g. u32. - Checked, - /// The operation will wrap around, depending on the range of the integral type. For example, - /// given a u32 value, this is done by applying `mod 2^32` to the result. - Wrapping, - /// The result of the operation will be computed as in [Wrapping], however in addition to the - /// result, this variant also pushes a value on the stack which represents whether or not the - /// operation over/underflowed; either 1 if over/underflow occurred, or 0 otherwise. - Overflowing, -} -impl Overflow { - /// Returns true if overflow is unchecked - pub fn is_unchecked(&self) -> bool { - matches!(self, Self::Unchecked) - } - - /// Returns true if overflow will cause a trap - pub fn is_checked(&self) -> bool { - matches!(self, Self::Checked) - } - - /// Returns true if overflow will add an extra boolean on top of the stack - pub fn is_overflowing(&self) -> bool { - matches!(self, Self::Overflowing) - } -} -impl fmt::Display for Overflow { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Unchecked => f.write_str("unchecked"), - Self::Checked => f.write_str("checked"), - Self::Wrapping => f.write_str("wrapping"), - Self::Overflowing => f.write_str("overflow"), - } - } -} - -#[derive(Debug, Clone)] -pub struct GlobalValueOp { - pub op: Opcode, - pub global: GlobalValue, -} - -#[derive(Debug, Clone)] -pub struct LocalVarOp { - pub op: Opcode, - pub local: LocalId, - pub args: ValueList, -} - -#[derive(Debug, Clone)] -pub struct BinaryOp { - pub op: Opcode, - pub overflow: Option, - /// NOTE: These arguments are in stack order, i.e. `a + b` will appear here as `[b, a]` - pub args: [Value; 2], -} - -#[derive(Debug, Clone)] -pub struct BinaryOpImm { - pub op: Opcode, - pub overflow: Option, - pub arg: Value, - pub imm: Immediate, -} - -#[derive(Debug, Clone)] -pub struct UnaryOp { - pub op: Opcode, - pub overflow: Option, - pub arg: Value, -} - -#[derive(Debug, Clone)] -pub struct UnaryOpImm { - pub op: Opcode, - pub overflow: Option, - pub imm: Immediate, -} - -#[derive(Debug, Clone)] -pub struct Call { - pub op: Opcode, - pub callee: FunctionIdent, - /// NOTE: Call arguments are always in stack order, i.e. the top operand on - /// the stack is the first function argument - pub args: ValueList, -} - -/// Branch -#[derive(Debug, Clone)] -pub struct Br { - pub op: Opcode, - pub successor: Successor, -} - -/// Conditional Branch -#[derive(Debug, Clone)] -pub struct CondBr { - pub op: Opcode, - pub cond: Value, - pub then_dest: Successor, - pub else_dest: Successor, -} - -/// Multi-way Branch w/Selector -#[derive(Debug, Clone)] -pub struct Switch { - pub op: Opcode, - pub arg: Value, - pub arms: Vec, - pub default: Successor, -} - -#[derive(Debug, Clone)] -pub struct SwitchArm { - pub value: u32, - pub successor: Successor, -} - -#[derive(Debug, Clone)] -pub struct Successor { - pub destination: Block, - /// NOTE: Block arguments are always in stack order, i.e. the top operand on - /// the stack is the first block argument - pub args: ValueList, -} -impl Successor { - pub fn deep_clone(&self, pool: &mut ValueListPool) -> Self { - Self { - destination: self.destination, - args: self.args.deep_clone(pool), - } - } -} - -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct SuccessorInfo<'a> { - pub destination: Block, - pub args: &'a [Value], -} -impl<'a> SuccessorInfo<'a> { - pub fn new(successor: &Successor, pool: &'a ValueListPool) -> Self { - Self { - destination: successor.destination, - args: successor.args.as_slice(pool), - } - } -} -impl<'a> formatter::PrettyPrint for SuccessorInfo<'a> { - fn render(&self) -> miden_core::prettier::Document { - use crate::formatter::*; - - if self.args.is_empty() { - return self.destination.render(); - } - - let args = self - .args - .iter() - .copied() - .map(display) - .reduce(|acc, arg| acc + const_text(" ") + arg) - .map(|args| const_text(" ") + args) - .unwrap_or_default(); - const_text("(") - + const_text("block") - + const_text(" ") - + display(self.destination.as_u32()) - + args - + const_text(")") - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct SuccessorInfoMut<'a> { - pub destination: Block, - pub args: &'a mut [Value], -} -impl<'a> SuccessorInfoMut<'a> { - pub fn new(successor: &'a mut Successor, pool: &'a mut ValueListPool) -> Self { - Self { - destination: successor.destination, - args: successor.args.as_mut_slice(pool), - } - } -} - -/// Return -#[derive(Debug, Clone)] -pub struct Ret { - pub op: Opcode, - /// NOTE: Return arguments are always in stack order, i.e. `ret a, b` - /// will appear on the stack as `[a, b]` - pub args: ValueList, -} - -/// Return an immediate -#[derive(Debug, Clone)] -pub struct RetImm { - pub op: Opcode, - pub arg: Immediate, -} - -/// Test -#[derive(Debug, Clone)] -pub struct Test { - pub op: Opcode, - pub arg: Value, - pub ty: Type, -} - -/// Load a value of type `ty` from `addr` -#[derive(Debug, Clone)] -pub struct LoadOp { - pub op: Opcode, - pub addr: Value, - pub ty: Type, -} - -/// A primop/intrinsic that takes a variable number of arguments -#[derive(Debug, Clone)] -pub struct PrimOp { - pub op: Opcode, - /// NOTE: Primops should be defined such that their arguments are in stack order - /// when they correspond to a MASM instruction, i.e. `assert_eq a, b` should appear `[b, a]` - pub args: ValueList, -} - -/// A primop that takes an immediate for its first argument, followed by a variable number of -/// arguments -#[derive(Debug, Clone)] -pub struct PrimOpImm { - pub op: Opcode, - pub imm: Immediate, - /// NOTE: Primops should be defined such that their arguments are in stack order - /// when they correspond to a MASM instruction, i.e. `assert_eq a, b` should appear `[b, a]`, - /// not counting the immediate argument - pub args: ValueList, -} - -#[doc(hidden)] -pub struct InstructionWithValueListPool<'a> { - pub inst: &'a Instruction, - pub value_lists: &'a ValueListPool, -} -impl<'a> PartialEq for InstructionWithValueListPool<'a> { - fn eq(&self, other: &Self) -> bool { - if core::mem::discriminant(self.inst) != core::mem::discriminant(other.inst) { - return false; - } - - if self.inst.opcode() != other.inst.opcode() { - return false; - } - - match (self.inst, other.inst) { - (Instruction::GlobalValue(l), Instruction::GlobalValue(r)) => l.global == r.global, - (Instruction::BinaryOp(l), Instruction::BinaryOp(r)) => { - l.overflow == r.overflow && l.args == r.args - } - (Instruction::BinaryOpImm(l), Instruction::BinaryOpImm(r)) => { - l.arg == r.arg && l.imm == r.imm && l.overflow == r.overflow - } - (Instruction::UnaryOp(l), Instruction::UnaryOp(r)) => { - l.arg == r.arg && l.overflow == r.overflow - } - (Instruction::UnaryOpImm(l), Instruction::UnaryOpImm(r)) => { - l.imm == r.imm && l.overflow == r.overflow - } - (Instruction::Call(l), Instruction::Call(r)) => { - l.callee == r.callee - && l.args.as_slice(self.value_lists) == r.args.as_slice(self.value_lists) - } - (Instruction::Br(l), Instruction::Br(r)) => { - let l = SuccessorInfo::new(&l.successor, self.value_lists); - let r = SuccessorInfo::new(&r.successor, self.value_lists); - l == r - } - (Instruction::CondBr(l), Instruction::CondBr(r)) => { - let l_then = SuccessorInfo::new(&l.then_dest, self.value_lists); - let l_else = SuccessorInfo::new(&l.else_dest, self.value_lists); - let r_then = SuccessorInfo::new(&r.then_dest, self.value_lists); - let r_else = SuccessorInfo::new(&r.else_dest, self.value_lists); - l.cond == r.cond && l_then == r_then && l_else == r_else - } - (Instruction::Switch(l), Instruction::Switch(r)) => { - if l.arg != r.arg { - return false; - } - let l_arms = BTreeSet::from_iter( - l.arms.iter().map(|arm| SuccessorInfo::new(&arm.successor, self.value_lists)), - ); - let r_arms = BTreeSet::from_iter( - r.arms.iter().map(|arm| SuccessorInfo::new(&arm.successor, self.value_lists)), - ); - let same_arms = l_arms == r_arms; - let same_default = SuccessorInfo::new(&l.default, self.value_lists) - == SuccessorInfo::new(&r.default, self.value_lists); - same_arms && same_default - } - (Instruction::Ret(l), Instruction::Ret(r)) => { - l.args.as_slice(self.value_lists) == r.args.as_slice(other.value_lists) - } - (Instruction::RetImm(l), Instruction::RetImm(r)) => l.arg == r.arg, - (Instruction::Load(l), Instruction::Load(r)) => l.addr == r.addr && l.ty == r.ty, - (Instruction::PrimOp(l), Instruction::PrimOp(r)) => { - l.args.as_slice(self.value_lists) == r.args.as_slice(other.value_lists) - } - (Instruction::PrimOpImm(l), Instruction::PrimOpImm(r)) => { - l.imm == r.imm - && l.args.as_slice(self.value_lists) == r.args.as_slice(other.value_lists) - } - (Instruction::Test(l), Instruction::Test(r)) => l.arg == r.arg && l.ty == r.ty, - (Instruction::InlineAsm(l), Instruction::InlineAsm(r)) => { - l.args.as_slice(self.value_lists) == r.args.as_slice(other.value_lists) - && l.results == r.results - && l.body == r.body - && l.blocks == r.blocks - } - (..) => unreachable!(), - } - } -} - -#[doc(hidden)] -pub struct InstPrettyPrinter<'a> { - pub current_function: FunctionIdent, - pub id: Inst, - pub dfg: &'a DataFlowGraph, -} -impl<'a> fmt::Display for InstPrettyPrinter<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} -impl<'a> formatter::PrettyPrint for InstPrettyPrinter<'a> { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let inst_data = self.dfg.inst(self.id); - let mut results = vec![]; - for result in self.dfg.inst_results(self.id) { - let v = const_text("(") + display(*result) + const_text(" "); - let t = text(format!("{}", self.dfg.value_type(*result))); - results.push(v + t + const_text(")")); - } - - let wrapper = match results.len() { - 0 => None, - 1 => Some( - const_text("(") - + const_text("let") - + const_text(" ") - + results.pop().unwrap() - + const_text(" "), - ), - _ => { - let open = const_text("(") + const_text("let") + const_text(" ") + const_text("["); - let bindings = - results.into_iter().reduce(|acc, doc| acc + const_text(" ") + doc).unwrap(); - let close = const_text("]") + const_text(" "); - Some(open + bindings + close) - } - }; - - let inner = const_text("(") + display(inst_data.opcode()); - - let (attrs, operands) = match inst_data { - Instruction::BinaryOp(BinaryOp { - overflow: None, - args, - .. - }) => { - let lhs = display(args[1]); - let rhs = display(args[0]); - (vec![], vec![lhs, rhs]) - } - Instruction::BinaryOp(BinaryOp { - overflow: Some(overflow), - args, - .. - }) => { - let lhs = display(args[1]); - let rhs = display(args[0]); - (vec![display(*overflow)], vec![lhs, rhs]) - } - Instruction::BinaryOpImm(BinaryOpImm { - overflow: None, - arg, - imm, - .. - }) => { - let lhs = display(*arg); - let rhs = display(*imm); - (vec![], vec![lhs, rhs]) - } - Instruction::BinaryOpImm(BinaryOpImm { - overflow: Some(overflow), - arg, - imm, - .. - }) => { - let lhs = display(*arg); - let rhs = display(*imm); - (vec![display(*overflow)], vec![lhs, rhs]) - } - Instruction::UnaryOp(UnaryOp { - overflow: None, - arg, - .. - }) => (vec![], vec![display(*arg)]), - Instruction::UnaryOp(UnaryOp { - overflow: Some(overflow), - arg, - .. - }) => (vec![display(*overflow)], vec![display(*arg)]), - Instruction::UnaryOpImm(UnaryOpImm { - overflow: None, - imm, - .. - }) => (vec![], vec![display(*imm)]), - Instruction::UnaryOpImm(UnaryOpImm { - overflow: Some(overflow), - imm, - .. - }) => (vec![display(*overflow)], vec![display(*imm)]), - Instruction::Ret(Ret { args, .. }) => { - let args = - args.as_slice(&self.dfg.value_lists).iter().copied().map(display).collect(); - (vec![], args) - } - Instruction::RetImm(RetImm { arg, .. }) => (vec![], vec![display(*arg)]), - Instruction::Call(Call { callee, args, .. }) => { - let mut operands = if callee.module == self.current_function.module { - vec![display(callee.function)] - } else { - vec![ - const_text("(") - + display(callee.module) - + const_text(" ") - + display(callee.function) - + const_text(")"), - ] - }; - operands.extend(args.as_slice(&self.dfg.value_lists).iter().copied().map(display)); - (vec![], operands) - } - Instruction::CondBr(CondBr { - cond, - ref then_dest, - ref else_dest, - .. - }) => ( - vec![], - vec![ - display(*cond), - SuccessorInfo::new(then_dest, &self.dfg.value_lists).render(), - SuccessorInfo::new(else_dest, &self.dfg.value_lists).render(), - ], - ), - Instruction::Br(Br { ref successor, .. }) => { - (vec![], vec![SuccessorInfo::new(successor, &self.dfg.value_lists).render()]) - } - Instruction::Switch(Switch { - arg, - arms, - ref default, - .. - }) => { - let default = const_text("(") - + const_text("_") - + const_text(" ") - + const_text(".") - + const_text(" ") - + SuccessorInfo::new(default, &self.dfg.value_lists).render() - + const_text(")"); - let arms = arms - .iter() - .map(|arm| { - const_text("(") - + display(arm.value) - + const_text(" ") - + const_text(".") - + const_text(" ") - + SuccessorInfo::new(&arm.successor, &self.dfg.value_lists).render() - + const_text(")") - }) - .chain(core::iter::once(default)) - .reduce(|acc, arm| acc + nl() + arm) - .unwrap(); - return inner + display(*arg) + indent(4, nl() + arms) + const_text(")"); - } - Instruction::Test(Test { arg, ref ty, .. }) => { - (vec![text(format!("{}", ty))], vec![display(*arg)]) - } - Instruction::PrimOp(PrimOp { args, .. }) => { - let args = - args.as_slice(&self.dfg.value_lists).iter().copied().map(display).collect(); - (vec![], args) - } - Instruction::PrimOpImm(PrimOpImm { imm, args, .. }) => { - let mut operands = vec![display(*imm)]; - operands.extend(args.as_slice(&self.dfg.value_lists).iter().copied().map(display)); - (vec![], operands) - } - Instruction::Load(LoadOp { addr, .. }) => (vec![], vec![display(*addr)]), - Instruction::InlineAsm(ref asm) => { - let inner = asm.render(self.current_function, self.dfg); - return match wrapper { - None => inner, - Some(wrapper) => wrapper + inner + const_text(")"), - }; - } - Instruction::LocalVar(LocalVarOp { local, args, .. }) => { - let args = core::iter::once(display(local)) - .chain(args.as_slice(&self.dfg.value_lists).iter().copied().map(display)) - .collect(); - (vec![const_text("local")], args) - } - Instruction::GlobalValue(GlobalValueOp { global, .. }) => { - let inner = self.dfg.global_value(*global).render(self.dfg); - return match wrapper { - None => inner, - Some(wrapper) => wrapper + inner + const_text(")"), - }; - } - }; - - let inner = attrs.into_iter().fold(inner, |acc, attr| acc + const_text(".") + attr); - let inner = - operands.into_iter().fold(inner, |acc, operand| acc + const_text(" ") + operand) - + const_text(")"); - - match wrapper { - None => inner, - Some(wrapper) => wrapper + inner + const_text(")"), - } - } -} diff --git a/hir/src/ir.rs b/hir/src/ir.rs new file mode 100644 index 000000000..00441f32c --- /dev/null +++ b/hir/src/ir.rs @@ -0,0 +1,85 @@ +mod block; +mod builder; +mod callable; +pub mod cfg; +mod component; +mod context; +mod dialect; +pub mod dominance; +pub mod effects; +pub mod entity; +mod ident; +mod immediates; +pub mod loops; +mod op; +mod operands; +mod operation; +pub mod print; +mod region; +mod successor; +pub(crate) mod symbols; +pub mod traits; +mod types; +mod usable; +mod value; +pub mod verifier; +mod visit; + +pub use midenc_hir_symbol as interner; +pub use midenc_session::diagnostics::{Report, SourceSpan, Span, Spanned}; + +pub use self::{ + block::{ + Block, BlockCursor, BlockCursorMut, BlockId, BlockList, BlockOperand, BlockOperandRef, + BlockRef, PostOrderBlockIter, PreOrderBlockIter, + }, + builder::{Builder, BuilderExt, InsertionGuard, Listener, ListenerType, OpBuilder}, + callable::*, + context::Context, + dialect::{Dialect, DialectInfo, DialectRegistration, DialectRegistrationHook}, + entity::{ + Entity, EntityCursor, EntityCursorMut, EntityGroup, EntityId, EntityIter, EntityList, + EntityListItem, EntityMut, EntityParent, EntityProjection, EntityProjectionMut, + EntityRange, EntityRangeMut, EntityRef, EntityStorage, EntityWithId, EntityWithParent, + MaybeDefaultEntityIter, RawEntityRef, StorableEntity, UnsafeEntityRef, + UnsafeIntrusiveEntityRef, + }, + ident::{FunctionIdent, Ident}, + immediates::{Felt, FieldElement, Immediate, StarkField}, + op::{BuildableOp, Op, OpExt, OpRegistration}, + operands::{ + OpOperand, OpOperandImpl, OpOperandList, OpOperandRange, OpOperandRangeMut, + OpOperandStorage, + }, + operation::{ + equivalence, OpCursor, OpCursorMut, OpList, Operation, OperationBuilder, OperationName, + OperationRef, + }, + print::{AttrPrinter, OpPrinter, OpPrintingFlags}, + region::{ + InvocationBounds, LoopLikeOpInterface, Region, RegionBranchOpInterface, RegionBranchPoint, + RegionBranchTerminatorOpInterface, RegionCursor, RegionCursorMut, RegionKind, + RegionKindInterface, RegionList, RegionRef, RegionSuccessor, RegionSuccessorInfo, + RegionSuccessorIter, RegionTransformFailed, + }, + successor::{ + KeyedSuccessor, KeyedSuccessorRange, KeyedSuccessorRangeMut, OpSuccessor, OpSuccessorMut, + OpSuccessorRange, OpSuccessorRangeMut, OpSuccessorStorage, SuccessorInfo, SuccessorOperand, + SuccessorOperandRange, SuccessorOperandRangeMut, SuccessorOperands, SuccessorWithKey, + SuccessorWithKeyMut, + }, + symbols::*, + traits::{FoldResult, OpFoldResult}, + types::*, + usable::Usable, + value::{ + AsValueRange, BlockArgument, BlockArgumentRange, BlockArgumentRangeMut, BlockArgumentRef, + OpResult, OpResultRange, OpResultRangeMut, OpResultRef, OpResultStorage, StackOperand, + Value, ValueId, ValueOrAlias, ValueRange, ValueRef, + }, + verifier::{OpVerifier, Verify}, + visit::{ + OpVisitor, OperationVisitor, RawWalk, ReverseBlock, Searcher, SymbolVisitor, Visitor, Walk, + WalkMut, WalkOrder, WalkResult, WalkStage, + }, +}; diff --git a/hir/src/ir/block.rs b/hir/src/ir/block.rs new file mode 100644 index 000000000..1cc60a887 --- /dev/null +++ b/hir/src/ir/block.rs @@ -0,0 +1,1305 @@ +use alloc::{format, vec::Vec}; +use core::{fmt, sync::atomic::AtomicBool}; + +use smallvec::SmallVec; + +use super::{entity::EntityParent, *}; +use crate::traits::SingleRegion; + +/// A pointer to a [Block] +pub type BlockRef = UnsafeIntrusiveEntityRef; +/// An intrusive, doubly-linked list of [Block] +pub type BlockList = EntityList; +/// A cursor into a [BlockList] +pub type BlockCursor<'a> = EntityCursor<'a, Block>; +/// A mutable cursor into a [BlockList] +pub type BlockCursorMut<'a> = EntityCursorMut<'a, Block>; +/// An iterator over blocks produced by a depth-first, pre-order visit of the CFG +pub type PreOrderBlockIter = cfg::PreOrderIter; +/// An iterator over blocks produced by a depth-first, post-order visit of the CFG +pub type PostOrderBlockIter = cfg::PostOrderIter; + +/// The unique identifier for a [Block] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct BlockId(u32); +impl BlockId { + pub const fn from_u32(id: u32) -> Self { + Self(id) + } + + pub const fn as_u32(&self) -> u32 { + self.0 + } +} +impl EntityId for BlockId { + #[inline(always)] + fn as_usize(&self) -> usize { + self.0 as usize + } +} +impl fmt::Debug for BlockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "^block{}", &self.0) + } +} +impl fmt::Display for BlockId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "^block{}", &self.0) + } +} + +/// Represents a basic block in the IR. +/// +/// Basic blocks are used in SSA regions to provide the structure of the control-flow graph. +/// Operations within a basic block appear in the order they will be executed. +/// +/// A block must have a [traits::Terminator], an operation which transfers control to another block +/// in the same region, or out of the containing operation (e.g. returning from a function). +/// +/// Blocks have _predecessors_ and _successors_, representing the inbound and outbound edges +/// (respectively) formed by operations that transfer control between blocks. A block can have +/// zero or more predecessors and/or successors. A well-formed region will generally only have a +/// single block (the entry block) with no predecessors (i.e. no unreachable blocks), and no blocks +/// with both multiple predecessors _and_ multiple successors (i.e. no critical edges). It is valid +/// to have both unreachable blocks and critical edges in the IR, but they must be removed during +/// the course of compilation. +pub struct Block { + /// The unique id of this block + id: BlockId, + /// Flag that indicates whether the ops in this block have a valid ordering + valid_op_ordering: AtomicBool, + /// The set of uses of this block + uses: BlockOperandList, + /// The list of [Operation]s that comprise this block + body: OpList, + /// The parameter list for this block + arguments: Vec, +} + +impl Eq for Block {} +impl PartialEq for Block { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} +impl core::hash::Hash for Block { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl fmt::Debug for Block { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Block") + .field("id", &self.id) + .field_with("region", |f| match self.parent() { + None => f.write_str("None"), + Some(r) => write!(f, "Some({r:p})"), + }) + .field_with("arguments", |f| { + let mut list = f.debug_list(); + for arg in self.arguments.iter() { + list.entry_with(|f| f.write_fmt(format_args!("{}", &arg.borrow()))); + } + list.finish() + }) + .finish_non_exhaustive() + } +} + +impl fmt::Display for Block { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.id) + } +} + +impl Block { + pub fn print(&self, flags: &OpPrintingFlags) -> crate::formatter::Document { + use crate::formatter::PrettyPrint; + + let printer = BlockPrinter { block: self, flags }; + printer.render() + } +} + +struct BlockPrinter<'a> { + block: &'a Block, + flags: &'a OpPrintingFlags, +} + +impl crate::formatter::PrettyPrint for BlockPrinter<'_> { + fn render(&self) -> crate::formatter::Document { + use crate::{formatter::*, traits::SingleBlock}; + + let is_entry_block = self.block.is_entry_block() && !self.flags.print_entry_block_headers; + let mut is_parent_op_single_block_single_region = false; + let mut is_parent_op_symbol = false; + if let Some(parent_op) = self.block.parent_op() { + let parent_op = parent_op.borrow(); + is_parent_op_single_block_single_region = parent_op.implements::() + && parent_op.implements::(); + is_parent_op_symbol = parent_op.implements::(); + } + + let block = self.block.body.iter().fold(Document::Empty, |acc, op| { + let context = op.context(); + let doc = op.print(self.flags, context); + if acc.is_empty() { + doc + } else if is_parent_op_single_block_single_region && is_parent_op_symbol { + // For blocks that serve as containers for symbol ops, e.g. module/component, + // add extra spacing between symbol ops to aid in readability + acc + nl() + nl() + doc + } else { + acc + nl() + doc + } + }); + + if is_parent_op_single_block_single_region { + block + } else if !is_entry_block || self.flags.print_entry_block_headers { + let args = self.block.arguments().iter().fold(Document::Empty, |acc, arg| { + let arg = *arg; + let doc = text(format!("{arg}: {}", arg.borrow().ty())); + if acc.is_empty() { + doc + } else { + acc + const_text(", ") + doc + } + }); + let args = if self.block.has_arguments() { + const_text("(") + args + const_text(")") + } else { + args + }; + + let header = display(self.block.id) + args + const_text(":"); + header + indent(4, nl() + block) + } else { + block + } + } +} + +impl crate::formatter::PrettyPrint for Block { + fn render(&self) -> crate::formatter::Document { + let flags = OpPrintingFlags::default(); + + self.print(&flags) + } +} + +impl Spanned for Block { + fn span(&self) -> SourceSpan { + self.body.front().get().map(|op| op.span()).unwrap_or_default() + } +} + +impl Entity for Block {} + +impl EntityWithId for Block { + type Id = BlockId; + + fn id(&self) -> Self::Id { + self.id + } +} + +impl EntityWithParent for Block { + type Parent = Region; +} + +impl EntityListItem for Block {} + +impl EntityParent for Block { + fn offset() -> usize { + core::mem::offset_of!(Block, body) + } +} + +impl EntityParent for Block { + fn offset() -> usize { + core::mem::offset_of!(Block, uses) + } +} + +impl Usable for Block { + type Use = BlockOperand; + + #[inline(always)] + fn uses(&self) -> &BlockOperandList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut BlockOperandList { + &mut self.uses + } +} + +impl cfg::Graph for Block { + type ChildEdgeIter = BlockSuccessorEdgesIter; + type ChildIter = BlockSuccessorIter; + type Edge = BlockOperandRef; + type Node = BlockRef; + + fn size(&self) -> usize { + if let Some(term) = self.terminator() { + term.borrow().num_successors() + } else { + 0 + } + } + + fn entry_node(&self) -> Self::Node { + self.as_block_ref() + } + + fn children(parent: Self::Node) -> Self::ChildIter { + BlockSuccessorIter::new(parent) + } + + fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter { + BlockSuccessorEdgesIter::new(parent) + } + + fn edge_dest(edge: Self::Edge) -> Self::Node { + edge.borrow().successor() + } +} + +impl<'a> cfg::InvertibleGraph for &'a Block { + type Inverse = cfg::Inverse<&'a Block>; + type InvertibleChildEdgeIter = BlockPredecessorEdgesIter; + type InvertibleChildIter = BlockPredecessorIter; + + fn inverse(self) -> Self::Inverse { + cfg::Inverse::new(self) + } + + fn inverse_children(parent: Self::Node) -> Self::InvertibleChildIter { + BlockPredecessorIter::new(parent) + } + + fn inverse_children_edges(parent: Self::Node) -> Self::InvertibleChildEdgeIter { + BlockPredecessorEdgesIter::new(parent) + } +} + +impl cfg::Graph for BlockRef { + type ChildEdgeIter = BlockSuccessorEdgesIter; + type ChildIter = BlockSuccessorIter; + type Edge = BlockOperandRef; + type Node = BlockRef; + + fn size(&self) -> usize { + if let Some(term) = self.borrow().terminator() { + term.borrow().num_successors() + } else { + 0 + } + } + + fn entry_node(&self) -> Self::Node { + *self + } + + fn children(parent: Self::Node) -> Self::ChildIter { + BlockSuccessorIter::new(parent) + } + + fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter { + BlockSuccessorEdgesIter::new(parent) + } + + fn edge_dest(edge: Self::Edge) -> Self::Node { + edge.borrow().successor() + } +} + +impl cfg::InvertibleGraph for BlockRef { + type Inverse = cfg::Inverse; + type InvertibleChildEdgeIter = BlockPredecessorEdgesIter; + type InvertibleChildIter = BlockPredecessorIter; + + fn inverse(self) -> Self::Inverse { + cfg::Inverse::new(self) + } + + fn inverse_children(parent: Self::Node) -> Self::InvertibleChildIter { + BlockPredecessorIter::new(parent) + } + + fn inverse_children_edges(parent: Self::Node) -> Self::InvertibleChildEdgeIter { + BlockPredecessorEdgesIter::new(parent) + } +} + +#[doc(hidden)] +pub struct BlockSuccessorIter { + iter: BlockSuccessorEdgesIter, +} +impl BlockSuccessorIter { + pub fn new(parent: BlockRef) -> Self { + Self { + iter: BlockSuccessorEdgesIter::new(parent), + } + } +} +impl ExactSizeIterator for BlockSuccessorIter { + #[inline] + fn len(&self) -> usize { + self.iter.len() + } + + #[inline] + fn is_empty(&self) -> bool { + self.iter.is_empty() + } +} +impl Iterator for BlockSuccessorIter { + type Item = BlockRef; + + fn next(&mut self) -> Option { + self.iter.next().map(|bo| bo.borrow().successor()) + } + + #[inline] + fn collect>(self) -> B + where + Self: Sized, + { + let Some(terminator) = self.iter.terminator.as_ref() else { + return B::from_iter([]); + }; + let terminator = terminator.borrow(); + let successors = terminator.successors(); + B::from_iter( + successors.all().as_slice()[self.iter.index..self.iter.num_successors] + .iter() + .map(|succ| succ.block.borrow().successor()), + ) + } + + fn collect_into>(self, collection: &mut E) -> &mut E + where + Self: Sized, + { + let Some(terminator) = self.iter.terminator.as_ref() else { + return collection; + }; + let terminator = terminator.borrow(); + let successors = terminator.successors(); + collection.extend( + successors.all().as_slice()[self.iter.index..self.iter.num_successors] + .iter() + .map(|succ| succ.block.borrow().successor()), + ); + collection + } +} + +#[doc(hidden)] +pub struct BlockSuccessorEdgesIter { + terminator: Option, + num_successors: usize, + index: usize, +} +impl BlockSuccessorEdgesIter { + pub fn new(parent: BlockRef) -> Self { + let terminator = parent.borrow().terminator(); + let num_successors = terminator.as_ref().map(|t| t.borrow().num_successors()).unwrap_or(0); + Self { + terminator, + num_successors, + index: 0, + } + } +} +impl ExactSizeIterator for BlockSuccessorEdgesIter { + #[inline] + fn len(&self) -> usize { + self.num_successors.saturating_sub(self.index) + } + + #[inline] + fn is_empty(&self) -> bool { + self.index >= self.num_successors + } +} +impl Iterator for BlockSuccessorEdgesIter { + type Item = BlockOperandRef; + + fn next(&mut self) -> Option { + if self.index >= self.num_successors { + return None; + } + + // SAFETY: We'll never have a none terminator if we have non-zero number of successors + let terminator = unsafe { self.terminator.as_ref().unwrap_unchecked() }; + let index = self.index; + self.index += 1; + Some(terminator.borrow().successor(index).dest) + } + + fn collect>(self) -> B + where + Self: Sized, + { + let Some(terminator) = self.terminator.as_ref() else { + return B::from_iter([]); + }; + let terminator = terminator.borrow(); + let successors = terminator.successors(); + B::from_iter( + successors.all().as_slice()[self.index..self.num_successors] + .iter() + .map(|succ| succ.block), + ) + } + + fn collect_into>(self, collection: &mut E) -> &mut E + where + Self: Sized, + { + let Some(terminator) = self.terminator.as_ref() else { + return collection; + }; + let terminator = terminator.borrow(); + let successors = terminator.successors(); + collection.extend( + successors.all().as_slice()[self.index..self.num_successors] + .iter() + .map(|succ| succ.block), + ); + collection + } +} + +#[doc(hidden)] +pub struct BlockPredecessorIter { + preds: SmallVec<[BlockRef; 4]>, + index: usize, +} +impl BlockPredecessorIter { + pub fn new(child: BlockRef) -> Self { + let preds = child.borrow().predecessors().map(|bo| bo.predecessor()).collect(); + Self { preds, index: 0 } + } + + #[inline(always)] + pub fn into_inner(self) -> SmallVec<[BlockRef; 4]> { + self.preds + } +} +impl ExactSizeIterator for BlockPredecessorIter { + #[inline] + fn len(&self) -> usize { + self.preds.len().saturating_sub(self.index) + } + + #[inline] + fn is_empty(&self) -> bool { + self.index >= self.preds.len() + } +} +impl Iterator for BlockPredecessorIter { + type Item = BlockRef; + + #[inline] + fn next(&mut self) -> Option { + if self.is_empty() { + return None; + } + let index = self.index; + self.index += 1; + Some(self.preds[index]) + } + + fn collect>(self) -> B + where + Self: Sized, + { + B::from_iter(self.preds) + } + + fn collect_into>(self, collection: &mut E) -> &mut E + where + Self: Sized, + { + collection.extend(self.preds); + collection + } +} + +#[doc(hidden)] +pub struct BlockPredecessorEdgesIter { + preds: SmallVec<[BlockOperandRef; 4]>, + index: usize, +} +impl BlockPredecessorEdgesIter { + pub fn new(child: BlockRef) -> Self { + let preds = child + .borrow() + .predecessors() + .map(|bo| unsafe { BlockOperandRef::from_raw(&*bo) }) + .collect(); + Self { preds, index: 0 } + } +} +impl ExactSizeIterator for BlockPredecessorEdgesIter { + #[inline] + fn len(&self) -> usize { + self.preds.len().saturating_sub(self.index) + } + + #[inline] + fn is_empty(&self) -> bool { + self.index >= self.preds.len() + } +} +impl Iterator for BlockPredecessorEdgesIter { + type Item = BlockOperandRef; + + #[inline] + fn next(&mut self) -> Option { + if self.is_empty() { + return None; + } + let index = self.index; + self.index += 1; + Some(self.preds[index]) + } + + fn collect>(self) -> B + where + Self: Sized, + { + B::from_iter(self.preds) + } + + fn collect_into>(self, collection: &mut E) -> &mut E + where + Self: Sized, + { + collection.extend(self.preds); + collection + } +} + +impl Block { + pub fn new(id: BlockId) -> Self { + Self { + id, + valid_op_ordering: AtomicBool::new(true), + uses: Default::default(), + body: Default::default(), + arguments: Default::default(), + } + } + + #[inline] + pub fn as_block_ref(&self) -> BlockRef { + unsafe { BlockRef::from_raw(self) } + } + + /// Get a handle to the containing [Region] of this block, if it is attached to one + pub fn parent(&self) -> Option { + self.as_block_ref().parent() + } + + /// Get a handle to the containing [Operation] of this block, if it is attached to one + pub fn parent_op(&self) -> Option { + self.parent().and_then(|region| region.parent()) + } + + /// Get a handle to the ancestor [Block] of this block, if one is present + pub fn parent_block(&self) -> Option { + self.parent_op().and_then(|op| op.parent()) + } + + /// Returns true if this block is the entry block for its containing region + pub fn is_entry_block(&self) -> bool { + if let Some(parent) = self.parent().map(|r| r.borrow()) { + parent.entry_block_ref().is_some_and(|entry| entry == self.as_block_ref()) + } else { + false + } + } + + /// Get the first operation in the body of this block + #[inline] + pub fn front(&self) -> Option { + self.body.front().as_pointer() + } + + /// Get the last operation in the body of this block + #[inline] + pub fn back(&self) -> Option { + self.body.back().as_pointer() + } + + /// Get the list of [Operation] comprising the body of this block + #[inline(always)] + pub fn body(&self) -> &OpList { + &self.body + } + + /// Get a mutable reference to the list of [Operation] comprising the body of this block + #[inline(always)] + pub fn body_mut(&mut self) -> &mut OpList { + &mut self.body + } +} + +/// Arguments +impl Block { + #[inline] + pub fn has_arguments(&self) -> bool { + !self.arguments.is_empty() + } + + #[inline] + pub fn num_arguments(&self) -> usize { + self.arguments.len() + } + + #[inline(always)] + pub fn arguments(&self) -> &[BlockArgumentRef] { + self.arguments.as_slice() + } + + #[inline(always)] + pub fn arguments_mut(&mut self) -> &mut Vec { + &mut self.arguments + } + + pub fn argument_values(&self) -> impl ExactSizeIterator + '_ { + self.arguments.iter().copied().map(|arg| arg as ValueRef) + } + + pub fn argument_types(&self) -> impl ExactSizeIterator + '_ { + self.arguments.iter().copied().map(|arg| arg.borrow().ty().clone()) + } + + #[inline] + pub fn get_argument(&self, index: usize) -> BlockArgumentRef { + self.arguments[index] + } + + /// Erase the block argument at `index` + /// + /// Panics if the argument still has uses. + pub fn erase_argument(&mut self, index: usize) { + assert!( + !self.arguments[index].borrow().is_used(), + "cannot erase block arguments with uses" + ); + self.arguments.remove(index); + } + + /// Erase every parameter of this block for which `should_erase` returns true. + /// + /// Panics if any argument to be erased still has uses. + pub fn erase_arguments(&mut self, should_erase: F) + where + F: Fn(&BlockArgument) -> bool, + { + self.arguments.retain(|arg| { + let arg = arg.borrow(); + let keep = !should_erase(&arg); + assert!(keep || !arg.is_used(), "cannot erase block arguments with uses"); + keep + }); + } + + pub fn erase(&mut self) { + if let Some(mut region) = self.parent() { + let mut region = region.borrow_mut(); + let body = region.body_mut(); + let mut cursor = unsafe { body.cursor_mut_from_ptr(self.as_block_ref()) }; + cursor.remove(); + } + } +} + +/// Placement +impl Block { + /// Insert this block after `after` in its containing region. + /// + /// Panics if this block is already attached to a region, or if `after` is not attached. + #[track_caller] + pub fn insert_after(&mut self, after: BlockRef) { + assert!( + self.parent().is_none(), + "cannot insert block that is already attached to another region" + ); + let mut region = after.parent().expect("'after' block is not attached to a region"); + { + let mut region = region.borrow_mut(); + let region_body = region.body_mut(); + let mut cursor = unsafe { region_body.cursor_mut_from_ptr(after) }; + cursor.insert_after(self.as_block_ref()); + } + } + + /// Insert this block before `before` in its containing region. + /// + /// Panics if this block is already attached to a region, or if `before` is not attached. + #[track_caller] + pub fn insert_before(&mut self, before: BlockRef) { + assert!( + self.parent().is_none(), + "cannot insert block that is already attached to another region" + ); + let mut region = before.parent().expect("'before' block is not attached to a region"); + { + let mut region = region.borrow_mut(); + let region_body = region.body_mut(); + let mut cursor = unsafe { region_body.cursor_mut_from_ptr(before) }; + cursor.insert_before(self.as_block_ref()); + } + } + + /// Insert this block at the end of `region`. + /// + /// Panics if this block is already attached to a region. + #[track_caller] + pub fn insert_at_end(&mut self, mut region: RegionRef) { + assert!( + self.parent().is_none(), + "cannot insert block that is already attached to another region" + ); + region.borrow_mut().body_mut().push_back(self.as_block_ref()); + } + + /// Unlink this block from its current region and insert it right before `before` + #[track_caller] + pub fn move_before(&mut self, before: BlockRef) { + self.unlink(); + self.insert_before(before); + } + + /// Remove this block from its containing region + fn unlink(&mut self) { + if let Some(mut region) = self.parent() { + let mut region = region.borrow_mut(); + unsafe { + let mut cursor = region.body_mut().cursor_mut_from_ptr(self.as_block_ref()); + cursor.remove(); + } + } + } + + /// Splice the body of `block` to the end of `self`, updating the parent of all spliced ops. + /// + /// It is up to the caller to ensure that this operation produces valid IR. + pub fn splice_block(&mut self, block: &mut Self) { + let ops = block.body_mut().take(); + self.body.back_mut().splice_after(ops); + } + + /// Splice the body of `block` to `self` before `ip`, updating the parent of all spliced ops. + /// + /// It is up to the caller to ensure that this operation produces valid IR. + pub fn splice_block_before(&mut self, block: &mut Self, ip: OperationRef) { + assert_eq!(ip.parent().unwrap(), block.as_block_ref()); + + let ops = block.body_mut().take(); + let mut cursor = unsafe { self.body.cursor_mut_from_ptr(ip) }; + cursor.splice_before(ops); + } + + /// Splice the body of `block` to `self` after `ip`, updating the parent of all spliced ops. + /// + /// It is up to the caller to ensure that this operation produces valid IR. + pub fn splice_block_after(&mut self, block: &mut Self, ip: OperationRef) { + assert_eq!(ip.parent().unwrap(), block.as_block_ref()); + + let ops = block.body_mut().take(); + let mut cursor = unsafe { self.body.cursor_mut_from_ptr(ip) }; + cursor.splice_after(ops); + } + + /// Split this block into two blocks before the specified operation + /// + /// Note that all operations in the block prior to `before` stay as part of the original block, + /// and the rest are moved to the new block, including the old terminator. The original block is + /// thus left without a terminator. + /// + /// Returns the newly created block. + pub fn split_block(&mut self, before: OperationRef) -> BlockRef { + let this = self.as_block_ref(); + assert!( + BlockRef::ptr_eq( + &this, + &before.parent().expect("'before' op is not attached to a block") + ), + "cannot split block using an operation that does not belong to the block being split" + ); + + // We need the parent op so we can get access to the current Context, but this also tells us + // that this block is attached to a region and operation. + let mut region = self.parent().expect("block is not attached to a region"); + let parent = region.parent().expect("parent region is not attached to an operation"); + // Create a new empty block + let mut new_block = parent.borrow().context().create_block(); + // Insert the block in the same region as `self`, immediately after `self` + { + let mut region_mut = region.borrow_mut(); + let blocks = region_mut.body_mut(); + let mut cursor = unsafe { blocks.cursor_mut_from_ptr(this) }; + cursor.insert_after(new_block); + } + // Split the body of `self` at `before`, and splice everything after `before`, including + // `before` itself, into the new block we created. + let ops = { + let mut cursor = unsafe { self.body.cursor_mut_from_ptr(before) }; + // Move the cursor before 'before' so that the split we get contains it + cursor.move_prev(); + cursor.split_after() + }; + new_block.borrow_mut().body_mut().back_mut().splice_after(ops); + new_block + } + + pub fn clear(&mut self) { + // Drop all references from within this block + self.drop_all_references(); + + // Drop all operations within this block + self.body_mut().clear(); + } +} + +/// Ancestors +impl Block { + pub fn is_legal_to_hoist_into(&self) -> bool { + use crate::traits::ReturnLike; + + // No terminator means the block is under construction, and thus legal to hoist into + let Some(terminator) = self.terminator() else { + return true; + }; + + // If the block has no successors, it can never be legal to hoist into it, there is nothing + // to hoist! + if self.num_successors() == 0 { + return false; + } + + // Instructions should not be hoisted across effectful or return-like terminators. This is + // typically only exception handling intrinsics, which HIR doesn't really have, but which + // we may nevertheless want to represent in the future. + // + // NOTE: Most return-like terminators would have no successors, but in LLVM, for example, + // there are instructions like `catch_ret`, which semantically are return-like, but which + // have a successor block (the landing pad). + let terminator = terminator.borrow(); + !terminator.is_memory_effect_free() || terminator.implements::() + } + + pub fn has_ssa_dominance(&self) -> bool { + self.parent_op() + .and_then(|op| { + op.borrow() + .as_trait::() + .map(|rki| rki.has_ssa_dominance()) + }) + .unwrap_or(true) + } + + /// Walk up the ancestor blocks of `block`, until `f` returns `true` for a block. + /// + /// NOTE: `block` is visited before any of its ancestors. + pub fn traverse_ancestors(block: BlockRef, mut f: F) -> Option + where + F: FnMut(BlockRef) -> bool, + { + let mut block = Some(block); + while let Some(current) = block.take() { + if f(current) { + return Some(current); + } + block = current.borrow().parent_block(); + } + + None + } + + /// Try to get a pair of blocks, starting with the given pair, which live in the same region, + /// by exploring the relationships of both blocks with respect to their regions. + /// + /// The returned block pair will either be the same input blocks, or some combination of those + /// blocks or their ancestors. + pub fn get_blocks_in_same_region(a: BlockRef, b: BlockRef) -> Option<(BlockRef, BlockRef)> { + // If both blocks do not live in the same region, we will have to check their parent + // operations. + let a_region = a.parent().unwrap(); + let b_region = b.parent().unwrap(); + if a_region == b_region { + return Some((a, b)); + } + + // Iterate over all ancestors of `a`, counting the depth of `a`. + // + // If one of `a`'s ancestors are in the same region as `b`, then we stop early because we + // found our nearest common ancestor. + let mut a_depth = 0; + let result = Self::traverse_ancestors(a, |block| { + a_depth += 1; + block.parent().is_some_and(|r| r == b_region) + }); + if let Some(a) = result { + return Some((a, b)); + } + + // Iterate over all ancestors of `b`, counting the depth of `b`. + // + // If one of `b`'s ancestors are in the same region as `a`, then we stop early because we + // found our nearest common ancestor. + let mut b_depth = 0; + let result = Self::traverse_ancestors(b, |block| { + b_depth += 1; + block.parent().is_some_and(|r| r == a_region) + }); + if let Some(b) = result { + return Some((a, b)); + } + + // Otherwise, we found two blocks that are siblings at some level. Walk the deepest one + // up until we reach the top or find a nearest common ancestor. + let mut a = Some(a); + let mut b = Some(b); + loop { + use core::cmp::Ordering; + + match a_depth.cmp(&b_depth) { + Ordering::Greater => { + a = a.and_then(|a| a.grandparent().and_then(|gp| gp.parent())); + a_depth -= 1; + } + Ordering::Less => { + b = b.and_then(|b| b.grandparent().and_then(|gp| gp.parent())); + b_depth -= 1; + } + Ordering::Equal => break, + } + } + + // If we found something with the same level, then we can march both up at the same time + // from here on out. + while let Some(next_a) = a.take() { + // If they are at the same level, and have the same parent region, then we succeeded. + let next_a_parent = next_a.parent(); + let b_parent = b.as_ref().and_then(|b| b.parent()); + if next_a_parent == b_parent { + return Some((next_a, b.unwrap())); + } + + a = next_a_parent.and_then(|r| r.grandparent()); + b = b_parent.and_then(|r| r.grandparent()); + } + + // They don't share a nearest common ancestor, perhaps they are in different modules or + // something. + None + } +} + +/// Predecessors and Successors +impl Block { + /// Returns true if this block has predecessors + #[inline(always)] + pub fn has_predecessors(&self) -> bool { + self.is_used() + } + + /// Get an iterator over the predecessors of this block + #[inline(always)] + pub fn predecessors(&self) -> BlockOperandIter<'_> { + self.iter_uses() + } + + /// If this block has exactly one predecessor, return it, otherwise `None` + /// + /// NOTE: A predecessor block with multiple edges, e.g. a conditional branch that has this block + /// as the destination for both true/false branches is _not_ considered a single predecessor by + /// this function. + pub fn get_single_predecessor(&self) -> Option { + let front = self.uses.front().as_pointer()?; + let back = self.uses.back().as_pointer().unwrap(); + if BlockOperandRef::ptr_eq(&front, &back) { + Some(front.borrow().predecessor()) + } else { + None + } + } + + /// If this block has a unique predecessor, i.e. all incoming edges originate from one block, + /// return it, otherwise `None` + pub fn get_unique_predecessor(&self) -> Option { + let mut front = self.uses.front(); + let block_operand = front.get()?; + let block = block_operand.predecessor(); + loop { + front.move_next(); + if let Some(bo) = front.get() { + if !BlockRef::ptr_eq(&block, &bo.predecessor()) { + break None; + } + } else { + break Some(block); + } + } + } + + /// Returns true if this block has any successors + #[inline] + pub fn has_successors(&self) -> bool { + self.num_successors() > 0 + } + + /// Get the number of successors of this block in the CFG + pub fn num_successors(&self) -> usize { + self.terminator().map(|op| op.borrow().num_successors()).unwrap_or(0) + } + + /// Get the `index`th successor of this block's terminator operation + #[track_caller] + pub fn get_successor(&self, index: usize) -> BlockRef { + let op = self.terminator().expect("this block has no terminator"); + op.borrow().successor(index).dest.borrow().successor() + } + + /// This drops all operand uses from operations within this block, which is an essential step in + /// breaking cyclic dependences between references when they are to be deleted. + pub fn drop_all_references(&mut self) { + let mut cursor = self.body.front_mut(); + while let Some(mut op) = cursor.as_pointer() { + op.borrow_mut().drop_all_references(); + cursor.move_next(); + } + } + + /// This drops all uses of values defined in this block or in the blocks of nested regions + /// wherever the uses are located. + pub fn drop_all_defined_value_uses(&mut self) { + let mut cursor = self.body.back_mut(); + while let Some(mut op) = cursor.as_pointer() { + op.borrow_mut().drop_all_defined_value_uses(); + cursor.move_prev(); + } + for arg in self.arguments.iter_mut() { + let mut arg = arg.borrow_mut(); + arg.uses_mut().clear(); + } + self.drop_all_uses(); + } + + /// Drop all uses of this block via [BlockOperand] + #[inline] + pub fn drop_all_uses(&mut self) { + self.uses_mut().clear(); + } + + pub fn replace_all_uses_with(&mut self, mut replacement: BlockRef) { + if BlockRef::ptr_eq(&self.as_block_ref(), &replacement) { + return; + } + + let mut replacement_block = replacement.borrow_mut(); + + let uses = self.uses_mut().take(); + replacement_block.uses_mut().back_mut().splice_after(uses); + } + + #[inline(always)] + pub(super) fn is_op_order_valid(&self) -> bool { + use core::sync::atomic::Ordering; + + self.valid_op_ordering.load(Ordering::Acquire) + } + + #[inline(always)] + pub(super) fn mark_op_order_valid(&self) { + use core::sync::atomic::Ordering; + + self.valid_op_ordering.store(true, Ordering::Release); + } + + pub(super) fn invalidate_op_order(&mut self) { + use core::sync::atomic::Ordering; + + // Validate the current ordering + assert!(self.verify_op_order()); + self.valid_op_ordering.store(false, Ordering::Release); + } + + /// Returns true if the current operation ordering in this block is valid + pub(super) fn verify_op_order(&self) -> bool { + // The order is already known to be invalid + if !self.is_op_order_valid() { + return false; + } + + // The order is valid if there are less than 2 operations + if self.body.is_empty() + || OperationRef::ptr_eq( + &self.body.front().as_pointer().unwrap(), + &self.body.back().as_pointer().unwrap(), + ) + { + return true; + } + + let mut cursor = self.body.front(); + let mut prev = None; + while let Some(op) = cursor.as_pointer() { + cursor.move_next(); + if prev.is_none() { + prev = Some(op); + continue; + } + + // The previous operation must have a smaller order index than the next + let prev_order = prev.take().unwrap().borrow().order(); + let current_order = op.borrow().order().unwrap_or(u32::MAX); + if prev_order.is_some_and(|o| o >= current_order) { + return false; + } + prev = Some(op); + } + + true + } + + /// Get the terminator operation of this block, or `None` if the block does not have one. + pub fn terminator(&self) -> Option { + if !self.has_terminator() { + None + } else { + self.body.back().as_pointer() + } + } + + /// Returns true if this block has a terminator + pub fn has_terminator(&self) -> bool { + use crate::traits::Terminator; + !self.body.is_empty() + && self.body.back().get().is_some_and(|op| op.implements::()) + } +} + +pub type BlockOperandRef = UnsafeIntrusiveEntityRef; +/// An intrusive, doubly-linked list of [BlockOperand] +pub type BlockOperandList = EntityList; +#[allow(unused)] +pub type BlockOperandCursor<'a> = EntityCursor<'a, BlockOperand>; +#[allow(unused)] +pub type BlockOperandCursorMut<'a> = EntityCursorMut<'a, BlockOperand>; +pub type BlockOperandIter<'a> = EntityIter<'a, BlockOperand>; + +/// A [BlockOperand] represents a use of a [Block] by an [Operation] +pub struct BlockOperand { + /// The owner of this operand, i.e. the operation it is an operand of + pub owner: OperationRef, + /// The index of this operand in the set of block operands of the operation + pub index: u8, +} + +impl Entity for BlockOperand {} +impl EntityWithParent for BlockOperand { + type Parent = Block; +} +impl EntityListItem for BlockOperand { + #[track_caller] + fn on_inserted(this: UnsafeIntrusiveEntityRef, _cursor: &mut EntityCursorMut<'_, Self>) { + assert!(this.parent().is_some()); + } +} + +impl BlockOperand { + #[inline] + pub fn new(owner: OperationRef, index: u8) -> Self { + Self { owner, index } + } + + pub fn as_block_operand_ref(&self) -> BlockOperandRef { + unsafe { BlockOperandRef::from_raw(self) } + } + + pub fn block_id(&self) -> BlockId { + self.successor().borrow().id + } + + /// Get the block from which this block operand originates, i.e. the predecessor block + pub fn predecessor(&self) -> BlockRef { + self.owner.parent().expect("owning operation is not attached to a block") + } + + /// Get the block this operand references + #[inline] + pub fn successor(&self) -> BlockRef { + self.as_block_operand_ref().parent().unwrap_or_else(|| { + panic!( + "block operand is dead at index {} in {}", + self.index, + &self.owner.borrow().name() + ) + }) + } + + /// Set the successor block to `block`, removing the block operand from the use list of the + /// previous block, and adding it to the use list of `block`. + /// + /// NOTE: This requires a mutable borrow of `block` when mutating its use list. + pub fn set(&mut self, mut block: BlockRef) { + self.unlink(); + block.borrow_mut().insert_use(self.as_block_operand_ref()); + } +} + +impl fmt::Debug for BlockOperand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BlockOperand") + .field("block", &self.successor()) + .field_with("owner", |f| write!(f, "{:p}", &self.owner)) + .field("index", &self.index) + .finish() + } +} +impl fmt::Display for BlockOperand { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.block_id()) + } +} +impl StorableEntity for BlockOperand { + #[inline(always)] + fn index(&self) -> usize { + self.index as usize + } + + unsafe fn set_index(&mut self, index: usize) { + self.index = index.try_into().expect("too many successors"); + } + + /// Remove this use of `block` + fn unlink(&mut self) { + let this = self.as_block_operand_ref(); + if !this.is_linked() { + return; + } + let Some(mut parent) = this.parent() else { + return; + }; + + let mut block = parent.borrow_mut(); + let uses = block.uses_mut(); + unsafe { + let mut cursor = uses.cursor_mut_from_ptr(this); + cursor.remove(); + } + } +} diff --git a/hir/src/ir/builder.rs b/hir/src/ir/builder.rs new file mode 100644 index 000000000..e8c69ac7a --- /dev/null +++ b/hir/src/ir/builder.rs @@ -0,0 +1,533 @@ +use alloc::{boxed::Box, rc::Rc}; + +use crate::{ + BlockArgument, BlockRef, BuildableOp, Context, OperationRef, ProgramPoint, RegionRef, + SourceSpan, Type, Value, +}; + +/// The [Builder] trait encompasses all of the functionality needed to construct and insert blocks +/// and operations into the IR. +pub trait Builder: Listener { + fn context(&self) -> &Context; + fn context_rc(&self) -> Rc; + /// Returns the current insertion point of the builder + fn insertion_point(&self) -> &ProgramPoint; + /// Clears the current insertion point + fn clear_insertion_point(&mut self) -> ProgramPoint; + /// Restores the current insertion point to `ip` + fn restore_insertion_point(&mut self, ip: ProgramPoint); + /// Sets the current insertion point to `ip` + fn set_insertion_point(&mut self, ip: ProgramPoint); + + /// Sets the insertion point before `op`, causing subsequent insertions to be placed before it. + #[inline] + fn set_insertion_point_before(&mut self, op: OperationRef) { + self.set_insertion_point(ProgramPoint::before(op)); + } + + /// Sets the insertion point after `op`, causing subsequent insertions to be placed after it. + #[inline] + fn set_insertion_point_after(&mut self, op: OperationRef) { + self.set_insertion_point(ProgramPoint::after(op)); + } + + /// Sets the insertion point to the node after the specified value is defined. + /// + /// If value has a defining operation, this sets the insertion point after that operation, so + /// that all insertions are placed following the definition. + /// + /// Otherwise, a value must be a block argument, so the insertion point is placed at the start + /// of the block, causing insertions to be placed starting at the front of the block. + fn set_insertion_point_after_value(&mut self, value: &dyn Value) { + let pp = if let Some(op) = value.get_defining_op() { + ProgramPoint::after(op) + } else { + let block_argument = value.downcast_ref::().unwrap(); + ProgramPoint::at_start_of(block_argument.owner()) + }; + self.set_insertion_point(pp); + } + + /// Sets the current insertion point to the start of `block`. + /// + /// Operations inserted will be placed starting at the beginning of the block. + #[inline] + fn set_insertion_point_to_start(&mut self, block: BlockRef) { + self.set_insertion_point(ProgramPoint::at_start_of(block)); + } + + /// Sets the current insertion point to the end of `block`. + /// + /// Operations inserted will be placed starting at the end of the block. + #[inline] + fn set_insertion_point_to_end(&mut self, block: BlockRef) { + self.set_insertion_point(ProgramPoint::at_end_of(block)); + } + + /// Return the block the current insertion point belongs to. + /// + /// NOTE: The insertion point is not necessarily at the end of the block. + /// + /// Returns `None` if the insertion point is unset, or is pointing at an operation which is + /// detached from a block. + fn insertion_block(&self) -> Option { + self.insertion_point().block() + } + + /// Add a new block with `args` arguments, and set the insertion point to the end of it. + /// + /// The block is inserted after the provided insertion point `ip`, or at the end of `parent` if + /// not. + /// + /// Panics if `ip` is in a different region than `parent`, or if the position it refers to is no + /// longer valid. + fn create_block(&mut self, parent: RegionRef, ip: Option, args: &[Type]) -> BlockRef { + let mut block = self.context().create_block_with_params(args.iter().cloned()); + if let Some(at) = ip { + let region = at.parent().unwrap(); + assert!( + RegionRef::ptr_eq(&parent, ®ion), + "insertion point region differs from 'parent'" + ); + + block.borrow_mut().insert_after(at); + } else { + block.borrow_mut().insert_at_end(parent); + } + + self.notify_block_inserted(block, None, None); + + self.set_insertion_point_to_end(block); + + block + } + + /// Add a new block with `args` arguments, and set the insertion point to the end of it. + /// + /// The block is inserted before `before`. + fn create_block_before(&mut self, before: BlockRef, args: &[Type]) -> BlockRef { + let mut block = self.context().create_block_with_params(args.iter().cloned()); + block.borrow_mut().insert_before(before); + self.notify_block_inserted(block, None, None); + self.set_insertion_point_to_end(block); + block + } + + /// Insert `op` at the current insertion point. + /// + /// If the insertion point is inserting after the current operation, then after calling this + /// function, the insertion point will have been moved to the newly inserted operation. This + /// ensures that subsequent calls to `insert` will place operations in the block in the same + /// sequence as they were inserted. The other insertion point placements already have more or + /// less intuitive behavior, e.g. inserting _before_ the current operation multiple times will + /// result in operations being placed in the same sequence they were inserted, just before the + /// current op. + /// + /// This function will panic if no insertion point is set. + fn insert(&mut self, op: OperationRef) { + let ip = self.insertion_point(); + + match *ip { + ProgramPoint::Block { + block, + position: point, + } => match point { + crate::Position::Before => op.insert_at_start(block), + crate::Position::After => op.insert_at_end(block), + }, + ProgramPoint::Op { + op: other_op, + position: point, + .. + } => match point { + crate::Position::Before => op.insert_before(other_op), + crate::Position::After => { + op.insert_after(other_op); + self.set_insertion_point(ProgramPoint::after(op)); + } + }, + ProgramPoint::Invalid => panic!("insertion point is invalid/unset"), + } + self.notify_operation_inserted(op, *self.insertion_point()); + } +} + +pub trait BuilderExt: Builder { + /// Returns a specialized builder for a concrete [Op], `T`, which can be called like a closure + /// with the arguments required to create an instance of the specified operation. + /// + /// # How it works + /// + /// The set of arguments which are valid for the specialized builder returned by `create`, are + /// determined by what implementations of the [BuildableOp] trait exist for `T`. The specific + /// impl that is chosen will depend on the types of the arguments given to it. Typically, there + /// should only be one implementation, or if there are multiple, they should not overlap in + /// ways that may confuse type inference, or you will be forced to specify the full type of the + /// argument pack. + /// + /// This mechanism for constructing ops using arbitrary arguments is essentially a workaround + /// for the lack of variadic generics in Rust, and isn't quite as nice as what you can acheive + /// in C++ with varidadic templates and `std::forward` and such, but is close enough so that + /// the ergonomics are still a significant improvement over the alternative approaches. + /// + /// The nice thing about this is that we can generate all of the boilerplate, and hide all of + /// the sensitive/unsafe parts of initializing operations. Alternative approaches require + /// exposing more unsafe APIs for use by builders, whereas this approach can conceal those + /// details within this crate. + /// + /// ## Example + /// + /// ```text,ignore + /// // Get an OpBuilder + /// let builder = context.builder(); + /// // Obtain a builder for AddOp + /// let add_builder = builder.create::(span); + /// // Consume the builder by creating the op with the given arguments + /// let add = add_builder(lhs, rhs, Overflow::Wrapping).expect("invalid add op"); + /// ``` + /// + /// Or, simplified/collapsed: + /// + /// ```text,ignore + /// let builder = context.builder(); + /// let add = builder.create::(span)(lhs, rhs, Overflow::Wrapping) + /// .expect("invalid add op"); + /// ``` + #[inline(always)] + fn create(&mut self, span: SourceSpan) -> >::Builder<'_, Self> + where + Args: core::marker::Tuple, + T: BuildableOp, + { + >::builder(self, span) + } +} + +impl BuilderExt for B {} + +pub struct OpBuilder { + context: Rc, + listener: Option, + ip: ProgramPoint, +} + +impl OpBuilder { + pub fn new(context: Rc) -> Self { + Self { + context, + listener: None, + ip: ProgramPoint::default(), + } + } +} + +impl OpBuilder { + /// Sets the listener of this builder to `listener` + pub fn with_listener(self, listener: L2) -> OpBuilder + where + L2: Listener, + { + OpBuilder { + context: self.context, + listener: Some(listener), + ip: self.ip, + } + } + + pub fn listener(&self) -> Option<&L> { + self.listener.as_ref() + } + + #[inline] + pub fn into_parts(self) -> (Rc, Option, ProgramPoint) { + (self.context, self.listener, self.ip) + } +} + +impl Listener for OpBuilder { + fn kind(&self) -> ListenerType { + self.listener.as_ref().map(|l| l.kind()).unwrap_or(ListenerType::Builder) + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_operation_inserted(op, prev); + } + } + + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_block_inserted(block, prev, ip); + } + } +} + +impl Builder for OpBuilder { + #[inline(always)] + fn context(&self) -> &Context { + self.context.as_ref() + } + + #[inline(always)] + fn context_rc(&self) -> Rc { + self.context.clone() + } + + #[inline(always)] + fn insertion_point(&self) -> &ProgramPoint { + &self.ip + } + + #[inline] + fn clear_insertion_point(&mut self) -> ProgramPoint { + let ip = self.ip; + self.ip = ProgramPoint::Invalid; + ip + } + + #[inline] + fn restore_insertion_point(&mut self, ip: ProgramPoint) { + self.ip = ip; + } + + #[inline(always)] + fn set_insertion_point(&mut self, ip: ProgramPoint) { + self.ip = ip; + } +} + +#[derive(Debug, Copy, Clone)] +pub enum ListenerType { + Builder, + Rewriter, +} + +#[allow(unused_variables)] +pub trait Listener { + fn kind(&self) -> ListenerType; + /// Notify the listener that the specified operation was inserted. + /// + /// * If the operation was moved, then `prev` is the previous location of the op + /// * If the operation was unlinked before it was inserted, then `prev` is `Invalid` + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) {} + /// Notify the listener that the specified block was inserted. + /// + /// * If the block was moved, then `prev` and `ip` represent the previous location of the block. + /// * If the block was unlinked before it was inserted, then `prev` and `ip` are `None` + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + } +} + +impl Listener for Option { + fn kind(&self) -> ListenerType { + ListenerType::Builder + } + + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + if let Some(listener) = self.as_ref() { + listener.notify_block_inserted(block, prev, ip); + } + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + if let Some(listener) = self.as_ref() { + listener.notify_operation_inserted(op, prev); + } + } +} + +impl Listener for Box { + #[inline] + fn kind(&self) -> ListenerType { + (**self).kind() + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + (**self).notify_operation_inserted(op, prev) + } + + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + (**self).notify_block_inserted(block, prev, ip) + } +} + +impl Listener for Rc { + #[inline] + fn kind(&self) -> ListenerType { + (**self).kind() + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + (**self).notify_operation_inserted(op, prev) + } + + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + (**self).notify_block_inserted(block, prev, ip) + } +} + +/// A listener of kind `Builder` that does nothing +pub struct NoopBuilderListener; +impl Listener for NoopBuilderListener { + #[inline] + fn kind(&self) -> ListenerType { + ListenerType::Builder + } +} + +/// This is used to allow [InsertionGuard] to be agnostic about the type of builder/rewriter it +/// wraps, while still performing the necessary insertion point restoration on drop. Without this, +/// we would be required to specify a `B: Builder` bound on the definition of [InsertionGuard]. +#[doc(hidden)] +#[allow(unused_variables)] +trait RestoreInsertionPointOnDrop { + fn restore_insertion_point_on_drop(&mut self, ip: ProgramPoint); +} +impl RestoreInsertionPointOnDrop for InsertionGuard<'_, B> { + #[inline(always)] + default fn restore_insertion_point_on_drop(&mut self, _ip: ProgramPoint) {} +} +impl RestoreInsertionPointOnDrop for InsertionGuard<'_, B> { + fn restore_insertion_point_on_drop(&mut self, ip: ProgramPoint) { + self.builder.restore_insertion_point(ip); + } +} + +pub struct InsertionGuard<'a, B: ?Sized> { + builder: &'a mut B, + ip: ProgramPoint, +} +impl<'a, B> InsertionGuard<'a, B> +where + B: ?Sized + Builder, +{ + #[allow(unused)] + pub fn new(builder: &'a mut B) -> Self { + let ip = *builder.insertion_point(); + Self { builder, ip } + } +} +impl core::ops::Deref for InsertionGuard<'_, B> { + type Target = B; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.builder + } +} +impl core::ops::DerefMut for InsertionGuard<'_, B> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.builder + } +} +impl Drop for InsertionGuard<'_, B> { + fn drop(&mut self) { + let ip = self.ip; + self.restore_insertion_point_on_drop(ip); + } +} +impl Listener for InsertionGuard<'_, B> { + fn kind(&self) -> ListenerType { + self.builder.kind() + } + + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + self.builder.notify_block_inserted(block, prev, ip); + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + self.builder.notify_operation_inserted(op, prev); + } +} +impl Builder for InsertionGuard<'_, B> { + fn context(&self) -> &Context { + self.builder.context() + } + + fn context_rc(&self) -> Rc { + self.builder.context_rc() + } + + fn insertion_point(&self) -> &ProgramPoint { + self.builder.insertion_point() + } + + fn clear_insertion_point(&mut self) -> ProgramPoint { + self.builder.clear_insertion_point() + } + + fn restore_insertion_point(&mut self, ip: ProgramPoint) { + self.builder.restore_insertion_point(ip); + } + + fn set_insertion_point(&mut self, ip: ProgramPoint) { + self.builder.set_insertion_point(ip); + } + + fn set_insertion_point_before(&mut self, op: OperationRef) { + self.builder.set_insertion_point_before(op); + } + + fn set_insertion_point_after(&mut self, op: OperationRef) { + self.builder.set_insertion_point_after(op); + } + + fn set_insertion_point_after_value(&mut self, value: &dyn Value) { + self.builder.set_insertion_point_after_value(value); + } + + fn set_insertion_point_to_start(&mut self, block: BlockRef) { + self.builder.set_insertion_point_to_start(block); + } + + fn set_insertion_point_to_end(&mut self, block: BlockRef) { + self.builder.set_insertion_point_to_end(block); + } + + fn insertion_block(&self) -> Option { + self.builder.insertion_block() + } + + fn create_block(&mut self, parent: RegionRef, ip: Option, args: &[Type]) -> BlockRef { + self.builder.create_block(parent, ip, args) + } + + fn create_block_before(&mut self, before: BlockRef, args: &[Type]) -> BlockRef { + self.builder.create_block_before(before, args) + } + + fn insert(&mut self, op: OperationRef) { + self.builder.insert(op); + } +} diff --git a/hir/src/ir/callable.rs b/hir/src/ir/callable.rs new file mode 100644 index 000000000..c76b08241 --- /dev/null +++ b/hir/src/ir/callable.rs @@ -0,0 +1,401 @@ +use alloc::{format, vec::Vec}; +use core::fmt; + +use super::SymbolPathAttr; +use crate::{ + formatter, CallConv, EntityRef, Op, OpOperandRange, OpOperandRangeMut, RegionRef, Symbol, + SymbolPath, SymbolRef, Type, UnsafeIntrusiveEntityRef, Value, ValueRef, Visibility, +}; + +/// A call-like operation is one that transfers control from one function to another. +/// +/// These operations may be traditional static calls, e.g. `call @foo`, or indirect calls, e.g. +/// `call_indirect v1`. An operation that uses this interface cannot _also_ implement the +/// `CallableOpInterface`. +pub trait CallOpInterface: Op { + /// Get the callee of this operation. + /// + /// A callee is either a symbol, or a reference to an SSA value. + fn callable_for_callee(&self) -> Callable; + /// Sets the callee for this operation. + fn set_callee(&mut self, callable: Callable); + /// Get the operands of this operation that are used as arguments for the callee + fn arguments(&self) -> OpOperandRange<'_>; + /// Get a mutable reference to the operands of this operation that are used as arguments for the + /// callee + fn arguments_mut(&mut self) -> OpOperandRangeMut<'_>; + /// Resolve the callable operation for the current callee to a `CallableOpInterface`, or `None` + /// if a valid callable was not resolved, using the provided symbol table. + /// + /// This method is used to perform callee resolution using a cached symbol table, rather than + /// traversing the operation hierarchy looking for symbol tables to try resolving with. + fn resolve_in_symbol_table(&self, symbols: &dyn crate::SymbolTable) -> Option; + /// Resolve the callable operation for the current callee to a `CallableOpInterface`, or `None` + /// if a valid callable was not resolved. + fn resolve(&self) -> Option; +} + +/// A callable operation is one who represents a potential function, and may be a target for a call- +/// like operation (i.e. implementations of `CallOpInterface`). These operations may be traditional +/// function ops (i.e. `Function`), as well as function reference-producing operations, such as an +/// op that creates closures, or captures a function by reference. +/// +/// These operations may only contain a single region. +pub trait CallableOpInterface: Op { + /// Returns the region on the current operation that is callable. + /// + /// This may return `None` in the case of an external callable object, e.g. an externally- + /// defined function reference. + fn get_callable_region(&self) -> Option; + /// Returns the signature of the callable + fn signature(&self) -> &Signature; +} + +#[doc(hidden)] +pub trait AsCallableSymbolRef { + fn as_callable_symbol_ref(&self) -> SymbolRef; +} +impl AsCallableSymbolRef for T { + #[inline(always)] + fn as_callable_symbol_ref(&self) -> SymbolRef { + unsafe { SymbolRef::from_raw(self as &dyn Symbol) } + } +} +impl AsCallableSymbolRef for UnsafeIntrusiveEntityRef { + #[inline(always)] + fn as_callable_symbol_ref(&self) -> SymbolRef { + let t_ptr = Self::as_ptr(self); + unsafe { SymbolRef::from_raw(t_ptr as *const dyn Symbol) } + } +} + +/// A [Callable] represents a symbol or a value which can be used as a valid _callee_ for a +/// [CallOpInterface] implementation. +/// +/// Symbols are not SSA values, but there are situations where we want to treat them as one, such +/// as indirect calls. Abstracting over whether the callable is a symbol or an SSA value allows us +/// to focus on the call semantics, rather than the difference between the type types of value. +#[derive(Debug, Clone)] +pub enum Callable { + Symbol(SymbolPath), + Value(ValueRef), +} +impl fmt::Display for Callable { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Symbol(path) => fmt::Display::fmt(path, f), + Self::Value(value) => fmt::Display::fmt(value, f), + } + } +} +impl From<&SymbolPathAttr> for Callable { + fn from(value: &SymbolPathAttr) -> Self { + Self::Symbol(value.path.clone()) + } +} +impl From<&SymbolPath> for Callable { + fn from(value: &SymbolPath) -> Self { + Self::Symbol(value.clone()) + } +} +impl From for Callable { + fn from(value: SymbolPath) -> Self { + Self::Symbol(value) + } +} +impl From for Callable { + fn from(value: ValueRef) -> Self { + Self::Value(value) + } +} +impl Callable { + #[inline(always)] + pub fn new(callable: impl Into) -> Self { + callable.into() + } + + pub fn is_symbol(&self) -> bool { + matches!(self, Self::Symbol(_)) + } + + pub fn is_value(&self) -> bool { + matches!(self, Self::Value(_)) + } + + pub fn as_symbol_path(&self) -> Option<&SymbolPath> { + match self { + Self::Symbol(ref name) => Some(name), + _ => None, + } + } + + pub fn as_value(&self) -> Option> { + match self { + Self::Value(ref value_ref) => Some(value_ref.borrow()), + _ => None, + } + } + + pub fn unwrap_symbol_path(self) -> SymbolPath { + match self { + Self::Symbol(name) => name, + Self::Value(value_ref) => panic!("expected symbol, got {}", value_ref.borrow().id()), + } + } + + pub fn unwrap_value_ref(self) -> ValueRef { + match self { + Self::Value(value) => value, + Self::Symbol(ref name) => panic!("expected value, got {name}"), + } + } +} + +/// Represents whether an argument or return value has a special purpose in +/// the calling convention of a function. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash)] +#[repr(u8)] +pub enum ArgumentPurpose { + /// No special purpose, the argument is passed/returned by value + #[default] + Default, + /// Used for platforms where the calling convention expects return values of + /// a certain size to be written to a pointer passed in by the caller. + StructReturn, +} +impl fmt::Display for ArgumentPurpose { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Default => f.write_str("default"), + Self::StructReturn => f.write_str("sret"), + } + } +} + +/// Represents how to extend a small integer value to native machine integer width. +/// +/// For Miden, native integrals are unsigned 64-bit field elements, but it is typically +/// going to be the case that we are targeting the subset of Miden Assembly where integrals +/// are unsigned 32-bit integers with a standard twos-complement binary representation. +/// +/// It is for the latter scenario that argument extension is really relevant. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash)] +#[repr(u8)] +pub enum ArgumentExtension { + /// Do not perform any extension, high bits have undefined contents + #[default] + None, + /// Zero-extend the value + Zext, + /// Sign-extend the value + Sext, +} +impl fmt::Display for ArgumentExtension { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::None => f.write_str("none"), + Self::Zext => f.write_str("zext"), + Self::Sext => f.write_str("sext"), + } + } +} + +/// Describes a function parameter or result. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AbiParam { + /// The type associated with this value + pub ty: Type, + /// The special purpose, if any, of this parameter or result + pub purpose: ArgumentPurpose, + /// The desired approach to extending the size of this value to + /// a larger bit width, if applicable. + pub extension: ArgumentExtension, +} +impl AbiParam { + pub fn new(ty: Type) -> Self { + Self { + ty, + purpose: ArgumentPurpose::default(), + extension: ArgumentExtension::default(), + } + } + + pub fn sret(ty: Type) -> Self { + assert!(ty.is_pointer(), "sret parameters must be pointers"); + Self { + ty, + purpose: ArgumentPurpose::StructReturn, + extension: ArgumentExtension::default(), + } + } +} +impl formatter::PrettyPrint for AbiParam { + fn render(&self) -> formatter::Document { + use crate::formatter::*; + + let mut doc = const_text("(") + const_text("param") + const_text(" "); + if !matches!(self.purpose, ArgumentPurpose::Default) { + doc += const_text("(") + display(self.purpose) + const_text(")") + const_text(" "); + } + if !matches!(self.extension, ArgumentExtension::None) { + doc += const_text("(") + display(self.extension) + const_text(")") + const_text(" "); + } + doc + text(format!("{}", &self.ty)) + const_text(")") + } +} + +impl fmt::Display for AbiParam { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = f.debug_map(); + builder.entry(&"ty", &format_args!("{}", &self.ty)); + if !matches!(self.purpose, ArgumentPurpose::Default) { + builder.entry(&"purpose", &format_args!("{}", &self.purpose)); + } + if !matches!(self.extension, ArgumentExtension::None) { + builder.entry(&"extension", &format_args!("{}", &self.extension)); + } + builder.finish() + } +} + +/// A [Signature] represents the type, ABI, and linkage of a function. +/// +/// A function signature provides us with all of the necessary detail to correctly +/// validate and emit code for a function, whether from the perspective of a caller, +/// or the callee. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Signature { + /// The arguments expected by this function + pub params: Vec, + /// The results returned by this function + pub results: Vec, + /// The calling convention that applies to this function + pub cc: CallConv, + /// The linkage/visibility that should be used for this function + pub visibility: Visibility, +} + +crate::define_attr_type!(Signature); + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_map() + .key(&"params") + .value_with(|f| { + let mut builder = f.debug_list(); + for param in self.params.iter() { + builder.entry(&format_args!("{param}")); + } + builder.finish() + }) + .key(&"results") + .value_with(|f| { + let mut builder = f.debug_list(); + for param in self.params.iter() { + builder.entry(&format_args!("{param}")); + } + builder.finish() + }) + .entry(&"cc", &format_args!("{}", &self.cc)) + .entry(&"visibility", &format_args!("{}", &self.visibility)) + .finish() + } +} + +impl Signature { + /// Create a new signature with the given parameter and result types, + /// for a public function using the `SystemV` calling convention + pub fn new, R: IntoIterator>( + params: P, + results: R, + ) -> Self { + Self { + params: params.into_iter().collect(), + results: results.into_iter().collect(), + cc: CallConv::SystemV, + visibility: Visibility::Public, + } + } + + /// Returns true if this function is externally visible + pub fn is_public(&self) -> bool { + matches!(self.visibility, Visibility::Public) + } + + /// Returns true if this function is only visible within it's containing module + pub fn is_private(&self) -> bool { + matches!(self.visibility, Visibility::Public) + } + + /// Returns true if this function is a kernel function + pub fn is_kernel(&self) -> bool { + matches!(self.cc, CallConv::Kernel) + } + + /// Returns the number of arguments expected by this function + pub fn arity(&self) -> usize { + self.params().len() + } + + /// Returns a slice containing the parameters for this function + pub fn params(&self) -> &[AbiParam] { + self.params.as_slice() + } + + /// Returns the parameter at `index`, if present + #[inline] + pub fn param(&self, index: usize) -> Option<&AbiParam> { + self.params.get(index) + } + + /// Returns a slice containing the results of this function + pub fn results(&self) -> &[AbiParam] { + match self.results.as_slice() { + [AbiParam { + ty: Type::Never, .. + }] => &[], + results => results, + } + } +} +impl formatter::PrettyPrint for Signature { + fn render(&self) -> formatter::Document { + use crate::formatter::*; + + let cc = if matches!(self.cc, CallConv::SystemV) { + None + } else { + Some( + const_text("(") + + const_text("cc") + + const_text(" ") + + display(self.cc) + + const_text(")"), + ) + }; + + let params = self.params.iter().fold(cc.unwrap_or(Document::Empty), |acc, param| { + if acc.is_empty() { + param.render() + } else { + acc + const_text(" ") + param.render() + } + }); + + if self.results.is_empty() { + params + } else { + let open = const_text("(") + const_text("result"); + let results = self + .results + .iter() + .fold(open, |acc, e| acc + const_text(" ") + text(format!("{}", &e.ty))) + + const_text(")"); + if matches!(params, Document::Empty) { + results + } else { + params + const_text(" ") + results + } + } + } +} diff --git a/hir/src/ir/cfg.rs b/hir/src/ir/cfg.rs new file mode 100644 index 000000000..37d473a64 --- /dev/null +++ b/hir/src/ir/cfg.rs @@ -0,0 +1,247 @@ +mod diff; +mod scc; +mod visit; + +pub use self::{ + diff::{CfgDiff, CfgUpdate, CfgUpdateKind, GraphDiff}, + scc::StronglyConnectedComponents, + visit::{DefaultGraphVisitor, GraphVisitor, LazyDfsVisitor, PostOrderIter, PreOrderIter}, +}; + +/// This is an abstraction over graph-like structures used in the IR: +/// +/// * The CFG of a region, i.e. graph of blocks +/// * The CFG reachable from a single block, i.e. graph of blocks +/// * The dominator graph of a region, i.e. graph of dominator nodes +/// * The call graph of a program +/// * etc... +/// +/// It isn't strictly necessary, but it provides some uniformity, and is useful particularly +/// for implementation of various analyses. +pub trait Graph { + /// The type of node represented in the graph. + /// + /// Typically this should be a pointer-like reference type, cheap to copy/clone. + type Node: Clone; + /// Type used to iterate over children of a node in the graph. + type ChildIter: ExactSizeIterator; + /// The type used to represent an edge in the graph. + /// + /// This should be cheap to copy/clone. + type Edge; + /// Type used to iterate over child edges of a node in the graph. + type ChildEdgeIter: ExactSizeIterator; + + /// An empty graph has no nodes. + #[inline] + fn is_empty(&self) -> bool { + self.size() == 0 + } + /// Get the number of nodes in this graph + fn size(&self) -> usize; + /// Get the entry node of the graph. + /// + /// It is expected that a graph always has an entry. As such, this function will panic if + /// called on an "empty" graph. You should check whether the graph is empty _first_, if you + /// are working with a possibly-empty graph. + fn entry_node(&self) -> Self::Node; + /// Get an iterator over the children of `parent` + fn children(parent: Self::Node) -> Self::ChildIter; + /// Get an iterator over the children edges of `parent` + fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter; + /// Return the destination node of an edge. + fn edge_dest(edge: Self::Edge) -> Self::Node; +} + +impl Graph for &G { + type ChildEdgeIter = ::ChildEdgeIter; + type ChildIter = ::ChildIter; + type Edge = ::Edge; + type Node = ::Node; + + fn is_empty(&self) -> bool { + (**self).is_empty() + } + + fn size(&self) -> usize { + (**self).size() + } + + fn entry_node(&self) -> Self::Node { + (**self).entry_node() + } + + fn children(parent: Self::Node) -> Self::ChildIter { + ::children(parent) + } + + fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter { + ::children_edges(parent) + } + + fn edge_dest(edge: Self::Edge) -> Self::Node { + ::edge_dest(edge) + } +} + +/// An [InvertibleGraph] is a [Graph] which can be "inverted", i.e. edges are reversed. +/// +/// Technically, any graph is invertible, however we are primarily interested in supporting graphs +/// for which an inversion of itself has some semantic value. For example, visiting a CFG in +/// reverse is useful in various contexts, such as constructing dominator trees. +/// +/// This is primarily consumed via [Inverse]. +pub trait InvertibleGraph: Graph { + /// The type of this graph's inversion + /// + /// This is primarily useful in cases where you inverse the inverse of a graph - by allowing + /// the types to differ, you can recover the original graph, rather than having to emulate + /// both uninverted graphs using the inverse type. + /// + /// See [Inverse] for an example of how this is used. + type Inverse: Graph; + /// The type of iterator used to visit "inverted" children of a node in this graph, i.e. + /// the predecessors. + type InvertibleChildIter: ExactSizeIterator; + /// The type of iterator used to obtain the set of "inverted" children edges of a node in this + /// graph, i.e. the predecessor edges. + type InvertibleChildEdgeIter: ExactSizeIterator; + + /// Get an iterator over the predecessors of `parent`. + /// + /// NOTE: `parent` in this case will actually be a child of the nodes in the iterator, but we + /// preserve the naming so as to make it apparent we are working with an inversion of the + /// original graph. + fn inverse_children(parent: Self::Node) -> Self::InvertibleChildIter; + /// Get an iterator over the predecessor edges of `parent`. + fn inverse_children_edges(parent: Self::Node) -> Self::InvertibleChildEdgeIter; + /// Obtain the inversion of this graph + fn inverse(self) -> Self::Inverse; +} + +/// This is a wrapper type for [Graph] implementations, used to indicate that iterating a +/// graph should be iterated in "inverse" order, the semantics of which depend on the graph. +/// +/// If used with an [InvertibleGraph], it uses the graph impls inverse iterators. If used with a +/// graph that is _not_ invertible, it uses the graph impls normal iterators. Effectively, this is +/// a specialization marker type. +pub struct Inverse { + graph: G, +} + +impl Inverse { + /// Construct an inversion over `graph` + #[inline] + pub fn new(graph: G) -> Self { + Self { graph } + } +} + +impl Graph for Inverse { + type ChildEdgeIter = InverseChildEdgeIter<::InvertibleChildEdgeIter>; + type ChildIter = InverseChildIter<::InvertibleChildIter>; + type Edge = ::Edge; + type Node = ::Node; + + fn is_empty(&self) -> bool { + self.graph.is_empty() + } + + fn size(&self) -> usize { + self.graph.size() + } + + fn entry_node(&self) -> Self::Node { + self.graph.entry_node() + } + + fn children(parent: Self::Node) -> Self::ChildIter { + InverseChildIter::new(::inverse_children(parent)) + } + + fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter { + InverseChildEdgeIter::new(::inverse_children_edges(parent)) + } + + fn edge_dest(edge: Self::Edge) -> Self::Node { + ::edge_dest(edge) + } +} + +impl InvertibleGraph for Inverse { + type Inverse = G; + type InvertibleChildEdgeIter = ::ChildEdgeIter; + type InvertibleChildIter = ::ChildIter; + + fn inverse_children(parent: Self::Node) -> Self::InvertibleChildIter { + ::children(parent) + } + + fn inverse_children_edges(parent: Self::Node) -> Self::InvertibleChildEdgeIter { + ::children_edges(parent) + } + + fn inverse(self) -> Self::Inverse { + self.graph + } +} + +/// An iterator returned by `children` that iterates over `inverse_children` of the underlying graph +#[doc(hidden)] +pub struct InverseChildIter { + iter: I, +} + +impl InverseChildIter { + pub fn new(iter: I) -> Self { + Self { iter } + } +} +impl ExactSizeIterator for InverseChildIter { + #[inline] + fn len(&self) -> usize { + self.iter.len() + } + + #[inline] + fn is_empty(&self) -> bool { + self.iter.is_empty() + } +} +impl Iterator for InverseChildIter { + type Item = ::Item; + + default fn next(&mut self) -> Option { + self.iter.next() + } +} + +/// An iterator returned by `children_edges` that iterates over `inverse_children_edges` of the +/// underlying graph. +#[doc(hidden)] +pub struct InverseChildEdgeIter { + iter: I, +} +impl InverseChildEdgeIter { + pub fn new(iter: I) -> Self { + Self { iter } + } +} +impl ExactSizeIterator for InverseChildEdgeIter { + #[inline] + fn len(&self) -> usize { + self.iter.len() + } + + #[inline] + fn is_empty(&self) -> bool { + self.iter.is_empty() + } +} +impl Iterator for InverseChildEdgeIter { + type Item = ::Item; + + default fn next(&mut self) -> Option { + self.iter.next() + } +} diff --git a/hir/src/ir/cfg/diff.rs b/hir/src/ir/cfg/diff.rs new file mode 100644 index 000000000..fd02878f2 --- /dev/null +++ b/hir/src/ir/cfg/diff.rs @@ -0,0 +1,289 @@ +use core::fmt; + +use smallvec::SmallVec; + +use crate::{adt::SmallDenseMap, BlockRef}; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum CfgUpdateKind { + Insert, + Delete, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct CfgUpdate { + kind: CfgUpdateKind, + from: BlockRef, + to: BlockRef, +} +impl CfgUpdate { + #[inline(always)] + pub const fn kind(&self) -> CfgUpdateKind { + self.kind + } + + #[inline(always)] + pub const fn from(&self) -> BlockRef { + self.from + } + + #[inline(always)] + pub const fn to(&self) -> BlockRef { + self.to + } +} +impl fmt::Debug for CfgUpdate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(match self.kind { + CfgUpdateKind::Insert => "Insert", + CfgUpdateKind::Delete => "Delete", + }) + .field("from", &self.from) + .field("to", &self.to) + .finish() + } +} + +#[derive(Default, Clone)] +struct DeletesInserts { + deletes: SmallVec<[BlockRef; 2]>, + inserts: SmallVec<[BlockRef; 2]>, +} +impl DeletesInserts { + pub fn di(&self, is_insert: bool) -> &SmallVec<[BlockRef; 2]> { + if is_insert { + &self.inserts + } else { + &self.deletes + } + } + + pub fn di_mut(&mut self, is_insert: bool) -> &mut SmallVec<[BlockRef; 2]> { + if is_insert { + &mut self.inserts + } else { + &mut self.deletes + } + } +} + +pub trait GraphDiff { + fn is_empty(&self) -> bool; + fn legalized_updates(&self) -> &[CfgUpdate]; + fn num_legalized_updates(&self) -> usize { + self.legalized_updates().len() + } + fn pop_update_for_incremental_updates(&mut self) -> CfgUpdate; + fn get_children(&self, node: BlockRef) -> SmallVec<[BlockRef; 8]>; +} + +/// GraphDiff defines a CFG snapshot: given a set of Update, provides +/// a getChildren method to get a Node's children based on the additional updates +/// in the snapshot. The current diff treats the CFG as a graph rather than a +/// multigraph. Added edges are pruned to be unique, and deleted edges will +/// remove all existing edges between two blocks. +/// +/// Two booleans are used to define orders in graphs: +/// InverseGraph defines when we need to reverse the whole graph and is as such +/// also equivalent to applying updates in reverse. +/// InverseEdge defines whether we want to change the edges direction. E.g., for +/// a non-inversed graph, the children are naturally the successors when +/// InverseEdge is false and the predecessors when InverseEdge is true. +#[derive(Clone)] +pub struct CfgDiff { + succ: SmallDenseMap, + pred: SmallDenseMap, + /// By default, it is assumed that, given a CFG and a set of updates, we wish + /// to apply these updates as given. If UpdatedAreReverseApplied is set, the + /// updates will be applied in reverse: deleted edges are considered re-added + /// and inserted edges are considered deleted when returning children. + updated_are_reverse_applied: bool, + /// Keep the list of legalized updates for a deterministic order of updates + /// when using a GraphDiff for incremental updates in the DominatorTree. + /// The list is kept in reverse to allow popping from end. + legalized_updates: SmallVec<[CfgUpdate; 4]>, +} + +impl Default for CfgDiff { + fn default() -> Self { + Self { + succ: Default::default(), + pred: Default::default(), + updated_are_reverse_applied: false, + legalized_updates: Default::default(), + } + } +} + +impl CfgDiff { + pub fn new(updates: I, reverse_apply_updates: bool) -> Self + where + I: ExactSizeIterator, + { + let mut this = Self { + legalized_updates: legalize_updates(updates, INVERSE_GRAPH, false), + ..Default::default() + }; + for update in this.legalized_updates.iter() { + let is_insert = matches!(update.kind(), CfgUpdateKind::Insert) || reverse_apply_updates; + this.succ.entry(update.from).or_default().di_mut(is_insert).push(update.to); + this.pred.entry(update.to).or_default().di_mut(is_insert).push(update.from); + } + this.updated_are_reverse_applied = reverse_apply_updates; + this + } +} + +impl GraphDiff for CfgDiff { + fn is_empty(&self) -> bool { + self.succ.is_empty() && self.pred.is_empty() && self.legalized_updates.is_empty() + } + + #[inline(always)] + fn legalized_updates(&self) -> &[CfgUpdate] { + &self.legalized_updates + } + + fn pop_update_for_incremental_updates(&mut self) -> CfgUpdate { + assert!(!self.legalized_updates.is_empty(), "no updates to apply"); + let update = self.legalized_updates.pop().unwrap(); + let is_insert = + matches!(update.kind(), CfgUpdateKind::Insert) || self.updated_are_reverse_applied; + let succ_di_list = &mut self.succ[&update.from]; + let is_empty = { + let succ_list = succ_di_list.di_mut(is_insert); + assert_eq!(succ_list.last(), Some(&update.to)); + succ_list.pop(); + succ_list.is_empty() + }; + if is_empty && succ_di_list.di(!is_insert).is_empty() { + self.succ.remove(&update.from); + } + + let pred_di_list = &mut self.pred[&update.to]; + let pred_list = pred_di_list.di_mut(is_insert); + assert_eq!(pred_list.last(), Some(&update.from)); + pred_list.pop(); + if pred_list.is_empty() && pred_di_list.di(!is_insert).is_empty() { + self.pred.remove(&update.to); + } + update + } + + fn get_children(&self, node: BlockRef) -> SmallVec<[BlockRef; 8]> { + let mut r = crate::dominance::nca::get_children::(node); + if !INVERSE_EDGE { + r.reverse(); + } + + let children = if INVERSE_EDGE != INVERSE_GRAPH { + &self.pred + } else { + &self.succ + }; + let Some(found) = children.get(&node) else { + return r; + }; + + // Remove children present in the CFG but not in the snapshot. + for child in found.di(false) { + r.retain(|c| c != child); + } + + // Add children present in the snapshot for not in the real CFG. + r.extend(found.di(true).iter().cloned()); + + r + } +} + +/// `legalize_updates` simplifies updates assuming a graph structure. +/// +/// This function serves double purpose: +/// +/// 1. It removes redundant updates, which makes it easier to reverse-apply them when traversing +/// CFG. +/// 2. It optimizes away updates that cancel each other out, as the end result is the same. +fn legalize_updates( + all_updates: I, + inverse_graph: bool, + reverse_result_order: bool, +) -> SmallVec<[CfgUpdate; 4]> +where + I: ExactSizeIterator, +{ + #[derive(Default, Copy, Clone)] + struct UpdateOp { + num_insertions: i32, + index: u32, + } + + // Count the total number of inserions of each edge. + // Each insertion adds 1 and deletion subtracts 1. The end number should be one of: + // + // * `-1` (deletion) + // * `0` (NOP), + // * `1` (insertion). + // + // Otherwise, the sequence of updates contains multiple updates of the same kind and we assert + // for that case. + let mut operations = + SmallDenseMap::<(BlockRef, BlockRef), UpdateOp, 4>::with_capacity(all_updates.len()); + + for ( + i, + CfgUpdate { + kind, + mut from, + mut to, + }, + ) in all_updates.enumerate() + { + if inverse_graph { + // Reverse edge for post-dominators + core::mem::swap(&mut from, &mut to); + } + + operations + .entry((from, to)) + .or_insert_with(|| UpdateOp { + num_insertions: 0, + index: i as u32, + }) + .num_insertions += match kind { + CfgUpdateKind::Insert => 1, + CfgUpdateKind::Delete => -1, + }; + } + + let mut result = SmallVec::<[CfgUpdate; 4]>::with_capacity(operations.len()); + for (&(from, to), update_op) in operations.iter() { + assert!(update_op.num_insertions.abs() <= 1, "unbalanced operations!"); + if update_op.num_insertions == 0 { + continue; + } + let kind = if update_op.num_insertions > 0 { + CfgUpdateKind::Insert + } else { + CfgUpdateKind::Delete + }; + result.push(CfgUpdate { kind, from, to }); + } + + // Make the order consistent by not relying on pointer values within the set. Reuse the old + // operations map. + // + // In the future, we should sort by something else to minimize the amount of work needed to + // perform the series of updates. + result.sort_by(|a, b| { + let op_a = &operations[&(a.from, a.to)]; + let op_b = &operations[&(b.from, b.to)]; + if reverse_result_order { + op_a.index.cmp(&op_b.index) + } else { + op_a.index.cmp(&op_b.index).reverse() + } + }); + + result +} diff --git a/hir/src/ir/cfg/scc.rs b/hir/src/ir/cfg/scc.rs new file mode 100644 index 000000000..7dc42ec48 --- /dev/null +++ b/hir/src/ir/cfg/scc.rs @@ -0,0 +1,279 @@ +use alloc::vec::Vec; + +use super::*; +use crate::FxHashMap; + +#[derive(Clone)] +pub struct StronglyConnectedComponent { + nodes: Vec<::Node>, +} + +impl core::fmt::Debug for StronglyConnectedComponent +where + G: Graph, + ::Node: Eq + Clone + core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("StronglyConnectedComponent") + .field("nodes", &self.nodes) + .finish() + } +} + +impl Default for StronglyConnectedComponent +where + G: Graph, +{ + fn default() -> Self { + Self { + nodes: Default::default(), + } + } +} + +impl StronglyConnectedComponent +where + N: Clone + Eq, + G: Graph, +{ + #[inline] + pub fn is_empty(&self) -> bool { + self.nodes.is_empty() + } + + #[inline] + pub fn len(&self) -> usize { + self.nodes.len() + } + + #[inline] + pub fn as_slice(&self) -> &[N] { + self.nodes.as_slice() + } + + pub fn clear(&mut self) { + self.nodes.clear(); + } + + pub fn push(&mut self, node: N) { + self.nodes.push(node); + } + + pub fn iter(&self) -> core::slice::Iter<'_, N> { + self.nodes.iter() + } + + /// Test if the current SCC has a cycle. + /// + /// If the SCC has more than one node, this is trivially true. If not, it may still contain a + /// cycle if the node has an edge back to itself. + pub fn has_cycle(&self) -> bool { + assert!(!self.is_empty()); + + if self.len() > 1 { + return true; + } + + let node = self.nodes[0].clone(); + for child_node in ::children(node.clone()) { + if child_node == node { + return true; + } + } + + false + } +} + +impl IntoIterator for StronglyConnectedComponent { + type IntoIter = alloc::vec::IntoIter; + type Item = ::Node; + + fn into_iter(self) -> Self::IntoIter { + self.nodes.into_iter() + } +} + +pub struct StronglyConnectedComponents { + /// Global visit counter + next_visit_num: usize, + /// The per-node visit counters used to detect when a complete SCC is on the stack. + /// + /// The counters are also used as DFS flags + visit_numbers: FxHashMap<::Node, usize>, + /// Stack holding nodes of the SCC + node_stack: Vec<::Node>, + /// The current SCC + current: StronglyConnectedComponent, + /// DFS stack, used to maintain the ordering. + /// + /// The top contains the current node, the next child to visit, and the minimum uplink value + /// of all children. + visit_stack: Vec>, +} + +impl core::fmt::Debug for StronglyConnectedComponents +where + G: Graph, + ::Node: Eq + Clone + core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("StronglyConnectedComponents") + .field("next_visit_num", &self.next_visit_num) + .field("visit_numbers", &self.visit_numbers) + .field("node_stack", &self.node_stack) + .field("current", &self.current) + .field("visit_stack", &self.visit_stack) + .finish() + } +} + +struct StackElement { + // Current node pointer + node: ::Node, + // The next child, modified in-place during DFS + next_child: ::ChildIter, + // Minimum uplink value of all children of `node` + min_visited: usize, +} + +impl core::fmt::Debug for StackElement +where + G: Graph, + ::Node: Eq + Clone + core::fmt::Debug, +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("StackElement") + .field("node", &self.node) + .field("min_visited", &self.min_visited) + .finish_non_exhaustive() + } +} + +impl StronglyConnectedComponents +where + N: Clone + Eq + core::hash::Hash + core::fmt::Debug, + G: Graph, +{ + pub fn new(graph: &G) -> Self { + Self::init(graph.entry_node()) + } + + fn init(node: N) -> Self { + let mut this = Self { + next_visit_num: 0, + visit_numbers: Default::default(), + node_stack: Default::default(), + current: Default::default(), + visit_stack: Default::default(), + }; + + this.visit_one(node); + this.next_scc(); + + this + } + + fn is_at_end(&self) -> bool { + assert!(!self.current.is_empty() || self.visit_stack.is_empty()); + self.current.is_empty() + } + + /// Inform the iterator that the specified old node has been deleted, and `new` is to be used + /// in its place. + #[allow(unused)] + pub fn replace_node(&mut self, old: N, new: N) { + let count = self.visit_numbers.remove(&old).expect("'old' not in scc iterator"); + *self.visit_numbers.entry(new).or_default() = count; + } + + /// A single "visit" within the non-recursive DFS traversal + fn visit_one(&mut self, node: N) { + let visit_num = self.next_visit_num; + self.next_visit_num += 1; + self.visit_numbers.insert(node.clone(), visit_num); + self.node_stack.push(node.clone()); + let next_child = ::children(node.clone()); + self.visit_stack.push(StackElement { + node, + next_child, + min_visited: visit_num, + }); + } + + /// The stack-based DFS traversal + fn visit_children(&mut self) { + assert!(!self.visit_stack.is_empty()); + + while let Some(child_node) = self.visit_stack.last_mut().unwrap().next_child.next() { + let visited = self.visit_numbers.get(&child_node).copied(); + match visited { + None => { + // This node has never been seen + self.visit_one(child_node); + continue; + } + Some(child_num) => { + let tos = self.visit_stack.last_mut().unwrap(); + tos.min_visited = core::cmp::min(tos.min_visited, child_num); + } + } + } + } + + /// Compute the next SCC using the DFS traversal + fn next_scc(&mut self) { + self.current.clear(); + + while !self.visit_stack.is_empty() { + self.visit_children(); + + // Pop the leaf on top of the visit stack + let mut visiting = self.visit_stack.pop().unwrap(); + assert!(visiting.next_child.next().is_none()); + + // Propagate min_visited to parent so we can detect the SCC starting node + if let Some(last) = self.visit_stack.last_mut() { + last.min_visited = core::cmp::min(last.min_visited, visiting.min_visited); + } + + if visiting.min_visited != self.visit_numbers[&visiting.node] { + continue; + } + + // A full SCC is on the node stack! It includes all nodes below `visiting` on the stack. + // Copy those nodes to `self.current`, reset their `min_visited` values, and return ( + // this suspends the DFS traversal till a subsequent call to `next`) + loop { + let node = self.node_stack.pop().unwrap(); + let should_continue = node != visiting.node; + *self.visit_numbers.get_mut(&node).unwrap() = usize::MAX; + self.current.push(node); + + if !should_continue { + return; + } + } + } + } +} + +impl Iterator for StronglyConnectedComponents +where + N: Clone + Eq + core::hash::Hash + core::fmt::Debug, + G: Graph, +{ + type Item = StronglyConnectedComponent; + + fn next(&mut self) -> Option { + if self.is_at_end() { + return None; + } + + let scc = core::mem::take(&mut self.current); + + self.next_scc(); + + Some(scc) + } +} diff --git a/hir/src/ir/cfg/visit.rs b/hir/src/ir/cfg/visit.rs new file mode 100644 index 000000000..69c4ade14 --- /dev/null +++ b/hir/src/ir/cfg/visit.rs @@ -0,0 +1,313 @@ +use core::ops::ControlFlow; + +use smallvec::SmallVec; + +use super::Graph; +use crate::adt::SmallSet; + +/// By implementing this trait, you can refine the traversal performed by [DfsVisitor], as well as +/// hook in custom behavior to be executed upon reaching a node in both pre-order and post-order +/// visits. +/// +/// There are two callbacks, both with default implementations that align with the default +/// semantics of a depth-first traversal. +/// +/// If you wish to prune the search, the best place to do so is [GraphVisitor::on_node_reached], +/// as it provides the opportunity to control whether or not the visitor will visit any of the +/// node's successors as well as emit the node during iteration. +#[allow(unused_variables)] +pub trait GraphVisitor { + type Node; + + /// Called when a node is first reached during a depth-first traversal, i.e. pre-order + /// + /// If this function returns `ControlFlow::Break`, none of `node`'s successors will be visited, + /// and `node` will not be emitted by the visitor. This can be used to prune the traversal, + /// e.g. confining a visit to a specific loop in a CFG. + fn on_node_reached(&mut self, from: Option<&Self::Node>, node: &Self::Node) -> ControlFlow<()> { + ControlFlow::Continue(()) + } + + /// Called when all successors of a node have been visited by the depth-first traversal, i.e. + /// post-order. + fn on_block_visited(&mut self, node: &Self::Node) {} +} + +/// A useful no-op visitor for when you want the default behavior. +pub struct DefaultGraphVisitor(core::marker::PhantomData); +impl Default for DefaultGraphVisitor { + fn default() -> Self { + Self(core::marker::PhantomData) + } +} +impl GraphVisitor for DefaultGraphVisitor { + type Node = T; +} + +/// A basic iterator over a depth-first traversal of nodes in a graph, producing them in pre-order. +#[repr(transparent)] +pub struct PreOrderIter(LazyDfsVisitor::Node>>) +where + G: Graph; +impl PreOrderIter +where + G: Graph, + ::Node: Eq, +{ + /// Visit all nodes reachable from `root` in pre-order + pub fn new(root: ::Node) -> Self { + Self(LazyDfsVisitor::new(root, DefaultGraphVisitor::default())) + } + + /// Visit all nodes reachable from `root` in pre-order, treating the nodes in `visited` as + /// already visited, skipping them (and their successors) during the traversal. + pub fn new_with_visited( + root: ::Node, + visited: impl IntoIterator::Node>, + ) -> Self { + Self(LazyDfsVisitor::new_with_visited(root, DefaultGraphVisitor::default(), visited)) + } +} +impl core::iter::FusedIterator for PreOrderIter +where + G: Graph, + ::Node: Eq, +{ +} +impl Iterator for PreOrderIter +where + G: Graph, + ::Node: Eq, +{ + type Item = ::Node; + + #[inline] + fn next(&mut self) -> Option { + self.0.next::() + } +} + +/// A basic iterator over a depth-first traversal of nodes in a graph, producing them in post-order. +#[repr(transparent)] +pub struct PostOrderIter(LazyDfsVisitor::Node>>) +where + G: Graph; +impl PostOrderIter +where + G: Graph, + ::Node: Eq, +{ + /// Visit all nodes reachable from `root` in post-order + #[inline] + pub fn new(root: ::Node) -> Self { + Self(LazyDfsVisitor::new(root, DefaultGraphVisitor::default())) + } + + /// Visit all nodes reachable from `root` in post-order, treating the nodes in `visited` as + /// already visited, skipping them (and their successors) during the traversal. + pub fn new_with_visited( + root: ::Node, + visited: impl IntoIterator::Node>, + ) -> Self { + Self(LazyDfsVisitor::new_with_visited(root, DefaultGraphVisitor::default(), visited)) + } +} +impl core::iter::FusedIterator for PostOrderIter +where + G: Graph, + ::Node: Eq, +{ +} +impl Iterator for PostOrderIter +where + G: Graph, + ::Node: Eq, +{ + type Item = ::Node; + + #[inline] + fn next(&mut self) -> Option { + self.0.next::() + } +} + +/// This type is an iterator over a depth-first traversal of a graph, with customization hooks +/// provided via the [GraphVisitor] trait. +/// +/// The order in which nodes are produced by the iterator depends on how you invoke the `next` +/// method - it must be instantiated with a constant boolean that indicates whether or not the +/// iteration is to produce nodes in post-order. +/// +/// As a result, this type does not implement `Iterator` itself - it is meant to be consumed as +/// an internal detail of higher-level iterator types. Two such types are provided in this module +/// for common pre- and post-order iterations: +/// +/// * [PreOrderIter], for iterating in pre-order +/// * [PostOrderIter], for iterating in post-order +/// +pub struct LazyDfsVisitor { + /// The nodes we have already visited, or wish to consider visited + visited: SmallSet<::Node, 32>, + /// The stack of discovered nodes currently being visited + stack: SmallVec<[VisitNode<::Node>; 8]>, + /// A [GraphVisitor] implementation used to hook into the traversal machinery + visitor: V, +} + +/// Represents a node in the graph which has been reached during traversal, and is in the process of +/// being visited. +struct VisitNode { + /// The parent node in the graph from which this node was drived + parent: Option, + /// The node in the underlying graph being visited + node: T, + /// The successors of this node + successors: SmallVec<[T; 2]>, + /// Set to `true` once this node has been handled by [GraphVisitor::on_node_reached] + reached: bool, +} +impl VisitNode +where + T: Clone, +{ + #[inline] + pub fn node(&self) -> T { + self.node.clone() + } + + /// Returns true if no successors remain to be visited under this node + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.successors.is_empty() + } + + /// Get the next successor of this node, and advance the `next_child` index + /// + /// It is expected that the caller has already checked [is_empty]. Failing to do so + /// will cause this function to panic once all successors have been visited. + pub fn pop_successor(&mut self) -> T { + self.successors.pop().unwrap() + } +} + +impl LazyDfsVisitor +where + G: Graph, + ::Node: Eq, + V: GraphVisitor::Node>, +{ + /// Visit the graph rooted under `from`, using the provided visitor for customization hooks. + pub fn new(from: ::Node, visitor: V) -> Self { + Self::new_with_visited(from, visitor, None::<::Node>) + } + + /// Visit the graph rooted under `from`, using the provided visitor for customization hooks. + /// + /// The initial set of "visited" nodes is seeded with `visited`. Any node in this set (and their + /// children) will be skipped during iteration and by the traversal itself. If `from` is in this + /// set, then the resulting iterator will be empty (i.e. produce no nodes, and perform no + /// traversal). + pub fn new_with_visited( + from: ::Node, + visitor: V, + visited: impl IntoIterator::Node>, + ) -> Self { + use smallvec::smallvec; + + let visited = visited.into_iter().collect::>(); + if visited.contains(&from) { + // The root node itself is being ignored, return an empty iterator + return Self { + visited, + stack: smallvec![], + visitor, + }; + } + + let successors = SmallVec::from_iter(G::children(from.clone())); + Self { + visited, + stack: smallvec![VisitNode { + parent: None, + node: from, + successors, + reached: false, + }], + visitor, + } + } + + /// Step the visitor forward one step. + /// + /// The semantics of a step depend on the value of `POSTORDER`: + /// + /// * If `POSTORDER == true`, then we resume traversal of the graph until the next node that + /// has had all of its successors visited is on top of the visit stack. + /// * If `POSTORDER == false`, then we resume traversal of the graph until the next unvisited + /// node is reached for the first time. + /// + /// In both cases, the node we find by the search is what is returned. If no more nodes remain + /// to be visited, this returns `None`. + /// + /// This function invokes the associated [GraphVisitor] callbacks during the traversal, at the + /// appropriate time. + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Option<::Node> { + loop { + let Some(node) = self.stack.last_mut() else { + break None; + }; + + if !node.reached { + node.reached = true; + let unvisited = self.visited.insert(node.node()); + if !unvisited { + let _ = unsafe { self.stack.pop().unwrap_unchecked() }; + continue; + } + + // Handle pre-order visit + let should_visit = + self.visitor.on_node_reached(node.parent.as_ref(), &node.node).is_continue(); + if !should_visit { + // It was indicated we shouldn't visit this node, so move to the next + let _ = unsafe { self.stack.pop().unwrap_unchecked() }; + continue; + } + + if POSTORDER { + // We need to visit this node's successors first + continue; + } else { + // We're going to visit this node's successors on the next call + break Some(node.node.clone()); + } + } + + // Otherwise, we're visiting a successor of this node. + // + // If this node has no successors, we're done visiting it + // If we've visited all successors of this node, we've got our next item + if node.is_empty() { + let node = unsafe { self.stack.pop().unwrap_unchecked() }; + self.visitor.on_block_visited(&node.node); + if POSTORDER { + break Some(node.node); + } else { + continue; + } + } + + // Otherwise, continue visiting successors + let parent = node.node(); + let successor = node.pop_successor(); + let successors = SmallVec::from_iter(G::children(successor.clone())); + self.stack.push(VisitNode { + parent: Some(parent), + node: successor, + successors, + reached: false, + }); + } + } +} diff --git a/hir/src/ir/component.rs b/hir/src/ir/component.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/hir/src/ir/component.rs @@ -0,0 +1 @@ + diff --git a/hir/src/ir/context.rs b/hir/src/ir/context.rs new file mode 100644 index 000000000..0fc55323d --- /dev/null +++ b/hir/src/ir/context.rs @@ -0,0 +1,346 @@ +use alloc::{boxed::Box, rc::Rc, sync::Arc, vec::Vec}; +use core::{ + cell::{Cell, RefCell}, + mem::MaybeUninit, +}; + +use blink_alloc::Blink; +use midenc_session::Session; +use traits::BranchOpInterface; + +use super::{traits::BuildableTypeConstraint, *}; +use crate::{ + constants::{ConstantData, ConstantId, ConstantPool}, + FxHashMap, +}; + +/// Represents the shared state of the IR, used during a compilation session. +/// +/// The primary purpose(s) of the context are: +/// +/// * Provide storage/memory for all allocated IR entities for the lifetime of the session. +/// * Provide unique value and block identifiers for printing the IR +/// * Provide a uniqued constant pool +/// * Provide configuration used during compilation +/// +/// # Safety +/// +/// The [Context] _must_ live as long as any reference to an IR entity may be dereferenced. +pub struct Context { + session: Rc, + allocator: Rc, + registered_dialects: RefCell>>, + dialect_hooks: RefCell>>, + constants: RefCell, + type_cache: RefCell>>, + next_block_id: Cell, + next_value_id: Cell, +} + +impl Default for Context { + fn default() -> Self { + use alloc::sync::Arc; + + use midenc_session::diagnostics::DefaultSourceManager; + + let target_dir = std::env::current_dir().unwrap(); + let options = midenc_session::Options::default(); + let source_manager = Arc::new(DefaultSourceManager::default()); + let session = + Rc::new(Session::new([], None, None, target_dir, options, None, source_manager)); + Self::new(session) + } +} + +impl Context { + /// Create a new [Context] for the given [Session] + pub fn new(session: Rc) -> Self { + let allocator = Rc::new(Blink::new()); + Self { + session, + allocator, + registered_dialects: Default::default(), + dialect_hooks: Default::default(), + constants: Default::default(), + type_cache: Default::default(), + next_block_id: Cell::new(0), + next_value_id: Cell::new(0), + } + } + + #[inline] + pub fn session(&self) -> &Session { + &self.session + } + + #[inline] + pub fn session_rc(&self) -> Rc { + self.session.clone() + } + + #[inline] + pub fn diagnostics(&self) -> &::midenc_session::DiagnosticsHandler { + &self.session.diagnostics + } + + pub fn registered_dialects( + &self, + ) -> core::cell::Ref<'_, FxHashMap>> { + self.registered_dialects.borrow() + } + + pub fn get_registered_dialect(&self, dialect: impl Into) -> Rc { + let dialect = dialect.into(); + self.registered_dialects.borrow()[&dialect].clone() + } + + pub fn get_or_register_dialect(&self) -> Rc + where + T: DialectRegistration, + { + let dialect_name = ::NAMESPACE.into(); + if let Some(dialect) = self.registered_dialects.borrow().get(&dialect_name).cloned() { + return dialect; + } + + let mut info = DialectInfo::new::(); + + let dialect_hooks = self.dialect_hooks.borrow(); + if let Some(hooks) = dialect_hooks.get(&dialect_name) { + for hook in hooks { + hook(&mut info, self); + } + } + + ::register_operations(&mut info); + + let dialect = Rc::new(T::init(info)) as Rc; + self.registered_dialects.borrow_mut().insert(dialect_name, Rc::clone(&dialect)); + dialect + } + + pub fn register_dialect_hook(&self, hook: F) + where + T: DialectRegistration, + F: Fn(&mut DialectInfo, &Context) + 'static, + { + let dialect_name = ::NAMESPACE.into(); + let mut dialect_hooks = self.dialect_hooks.borrow_mut(); + let registered_hooks = + dialect_hooks.entry(dialect_name).or_insert_with(|| Vec::with_capacity(1)); + registered_hooks.push(Box::new(hook)); + } + + pub fn create_constant(&self, data: impl Into) -> ConstantId { + let mut constants = self.constants.borrow_mut(); + constants.insert(data.into()) + } + + pub fn get_constant(&self, id: ConstantId) -> Arc { + self.constants.borrow().get(id) + } + + pub fn get_constant_size_in_bytes(&self, id: ConstantId) -> usize { + self.constants.borrow().get_by_ref(id).len() + } + + pub fn get_cached_type(&self) -> Option> { + self.type_cache.borrow().get(&core::any::TypeId::of::()).cloned() + } + + pub fn get_or_insert_type(&self) -> Arc { + match self.get_cached_type::() { + Some(ty) => ty, + None => { + let ty = Arc::new(::build(self)); + let mut types = self.type_cache.borrow_mut(); + types.insert(core::any::TypeId::of::(), Arc::clone(&ty)); + ty + } + } + } + + /// Get a new [OpBuilder] for this context + pub fn builder(self: Rc) -> OpBuilder { + OpBuilder::new(Rc::clone(&self)) + } + + /// Create a new, detached and empty [Block] with no parameters + pub fn create_block(&self) -> BlockRef { + let block = Block::new(self.alloc_block_id()); + self.alloc_tracked(block) + } + + /// Create a new, detached and empty [Block], with parameters corresponding to the given types + pub fn create_block_with_params(&self, tys: I) -> BlockRef + where + I: IntoIterator, + { + let block = Block::new(self.alloc_block_id()); + let mut block = self.alloc_tracked(block); + let owner = block; + let args = tys.into_iter().enumerate().map(|(index, ty)| { + let id = self.alloc_value_id(); + let arg = BlockArgument::new( + SourceSpan::default(), + id, + ty, + owner, + index.try_into().expect("too many block arguments"), + ); + self.alloc(arg) + }); + block.borrow_mut().arguments_mut().extend(args); + block + } + + /// Append a new [BlockArgument] to `block`, with the given type and source location + /// + /// Returns the block argument as a `dyn Value` reference + pub fn append_block_argument( + &self, + mut block: BlockRef, + ty: Type, + span: SourceSpan, + ) -> ValueRef { + let next_index = block.borrow().num_arguments(); + let id = self.alloc_value_id(); + let arg = BlockArgument::new( + span, + id, + ty, + block, + next_index.try_into().expect("too many block arguments"), + ); + let arg = self.alloc(arg); + block.borrow_mut().arguments_mut().push(arg); + arg.upcast() + } + + /// Create a new [OpOperand] with the given value, owner, and index. + /// + /// NOTE: This inserts the operand as a user of `value`, but does _not_ add the operand to + /// `owner`'s operand storage, the caller is expected to do that. This makes this function a + /// more useful primitive. + pub fn make_operand(&self, mut value: ValueRef, owner: OperationRef, index: u8) -> OpOperand { + let op_operand = self.alloc_tracked(OpOperandImpl::new(value, owner, index)); + let mut value = value.borrow_mut(); + value.insert_use(op_operand); + op_operand + } + + /// Create a new [BlockOperand] with the given block, owner, and index. + /// + /// NOTE: This inserts the block operand as a user of `block`, but does _not_ add the block + /// operand to `owner`'s successor storage, the caller is expected to do that. This makes this + /// function a more useful primitive. + pub fn make_block_operand( + &self, + mut block: BlockRef, + owner: OperationRef, + index: u8, + ) -> BlockOperandRef { + let block_operand = self.alloc_tracked(BlockOperand::new(owner, index)); + let mut block = block.borrow_mut(); + block.insert_use(block_operand); + block_operand + } + + /// Create a new [OpResult] with the given type, owner, and index + /// + /// NOTE: This does not attach the result to the operation, it is expected that the caller will + /// do so. + pub fn make_result( + &self, + span: SourceSpan, + ty: Type, + owner: OperationRef, + index: u8, + ) -> OpResultRef { + let id = self.alloc_value_id(); + self.alloc(OpResult::new(span, id, ty, owner, index)) + } + + /// Appends `value` as an argument to the `branch_inst` instruction arguments list if the + /// destination block of the `branch_inst` is `dest`. + /// + /// NOTE: Panics if `branch_inst` is not a branch instruction. + pub fn append_branch_destination_argument( + &self, + mut branch_inst: OperationRef, + dest: BlockRef, + value: ValueRef, + ) { + let mut borrow = branch_inst.borrow_mut(); + let op = borrow.as_mut().as_operation_mut(); + assert!( + op.as_trait::().is_some(), + "expected branch instruction, got {branch_inst:?}" + ); + let dest_operand_groups: Vec = op + .successors() + .iter() + .filter(|succ| succ.block.borrow().successor() == dest) + .map(|succ| succ.operand_group as usize) + .collect(); + for dest_group in dest_operand_groups { + let current_dest_operands_len = op.operands.group(dest_group).len(); + let operand = self.make_operand( + value, + op.as_operation_ref(), + (current_dest_operands_len + 1) as u8, + ); + op.operands_mut().extend_group(dest_group, [operand]); + } + } + + /// Allocate a new uninitialized entity of type `T` + /// + /// In general, you can probably prefer [Context::alloc] instead, but for use cases where you + /// need to allocate the space for `T` first, and then perform initialization, this can be + /// used. + pub fn alloc_uninit(&self) -> UnsafeEntityRef> { + UnsafeEntityRef::new_uninit(&self.allocator) + } + + /// Allocate a new uninitialized entity of type `T`, which needs to be tracked in an intrusive + /// doubly-linked list. + /// + /// In general, you can probably prefer [Context::alloc_tracked] instead, but for use cases + /// where you need to allocate the space for `T` first, and then perform initialization, + /// this can be used. + pub fn alloc_uninit_tracked(&self) -> UnsafeIntrusiveEntityRef> { + UnsafeIntrusiveEntityRef::::new_uninit_with_metadata(Default::default(), &self.allocator) + } + + /// Allocate a new `EntityHandle`. + /// + /// [EntityHandle] is a smart-pointer type for IR entities, which behaves like a ref-counted + /// pointer with dynamically-checked borrow checking rules. It is designed to play well with + /// entities allocated from a [Context], and with the somewhat cyclical nature of the IR. + pub fn alloc(&self, value: T) -> UnsafeEntityRef { + UnsafeEntityRef::new(value, &self.allocator) + } + + /// Allocate a new `TrackedEntityHandle`. + /// + /// [TrackedEntityHandle] is like [EntityHandle], except that it is specially designed for + /// entities which are meant to be tracked in intrusive linked lists. For example, the blocks + /// in a region, or the ops in a block. It does this without requiring the entity to know about + /// the link at all, while still making it possible to access the link from the entity. + pub fn alloc_tracked(&self, value: T) -> UnsafeIntrusiveEntityRef { + UnsafeIntrusiveEntityRef::new_with_metadata(value, Default::default(), &self.allocator) + } + + fn alloc_block_id(&self) -> BlockId { + let id = self.next_block_id.get(); + self.next_block_id.set(id + 1); + BlockId::from_u32(id) + } + + fn alloc_value_id(&self) -> ValueId { + let id = self.next_value_id.get(); + self.next_value_id.set(id + 1); + ValueId::from_u32(id) + } +} diff --git a/hir/src/ir/dialect.rs b/hir/src/ir/dialect.rs new file mode 100644 index 000000000..7b60934cc --- /dev/null +++ b/hir/src/ir/dialect.rs @@ -0,0 +1,114 @@ +mod info; + +use alloc::boxed::Box; +use core::ptr::{DynMetadata, Pointee}; + +pub use self::info::DialectInfo; +use crate::{ + any::AsAny, interner, AttributeValue, Builder, OperationName, OperationRef, SourceSpan, Type, +}; + +pub type DialectRegistrationHook = Box; + +/// A [Dialect] represents a collection of IR entities that are used in conjunction with one +/// another. Multiple dialects can co-exist _or_ be mutually exclusive. Converting between dialects +/// is the job of the conversion infrastructure, using a process called _legalization_. +pub trait Dialect { + /// Get metadata about this dialect (it's operations, interfaces, etc.) + fn info(&self) -> &DialectInfo; + + /// Get the name(space) of this dialect + fn name(&self) -> interner::Symbol { + self.info().name() + } + + /// Get the set of registered operations associated with this dialect + fn registered_ops(&self) -> &[OperationName] { + self.info().operations() + } + + /// A hook to materialize a single constant operation from a given attribute value and type. + /// + /// This method should use the provided builder to create the operation without changing the + /// insertion point. The generated operation is expected to be constant-like, i.e. single result + /// zero operands, no side effects, etc. + /// + /// Returns `None` if a constant cannot be materialized for the given attribute. + #[allow(unused_variables)] + #[inline] + fn materialize_constant( + &self, + builder: &mut dyn Builder, + attr: Box, + ty: &Type, + span: SourceSpan, + ) -> Option { + None + } +} + +impl dyn Dialect { + /// Get the [OperationName] of the operation type `T`, if registered with this dialect. + pub fn registered_name(&self) -> Option + where + T: crate::OpRegistration, + { + let opcode = ::name(); + self.registered_ops().iter().find(|op| op.name() == opcode).cloned() + } + + /// Get the [OperationName] of the operation type `T`. + /// + /// Panics if the operation is not registered with this dialect. + pub fn expect_registered_name(&self) -> OperationName + where + T: crate::OpRegistration, + { + self.registered_name::().unwrap_or_else(|| { + panic!( + "{} is not registered with dialect '{}'", + core::any::type_name::(), + self.name() + ) + }) + } + + /// Attempt to cast this operation reference to an implementation of `Trait` + pub fn as_registered_interface(&self) -> Option<&Trait> + where + Trait: ?Sized + Pointee> + 'static, + { + let this = self as *const dyn Dialect; + let (ptr, _) = this.to_raw_parts(); + let info = self.info(); + info.upcast(ptr) + } +} + +/// A [DialectRegistration] must be implemented for any implementation of [Dialect], to allow the +/// dialect to be registered with a [crate::Context] and instantiated on demand when building ops +/// in the IR. +/// +/// This is not part of the [Dialect] trait itself, as that trait must be object safe, and this +/// trait is _not_ object safe. +pub trait DialectRegistration: AsAny + Dialect { + /// The namespace of the dialect to register + /// + /// A dialect namespace serves both as a way to namespace the operations of that dialect, as + /// well as a way to uniquely name/identify the dialect itself. Thus, no two dialects can have + /// the same namespace at the same time. + const NAMESPACE: &'static str; + + /// Initialize an instance of this dialect to be stored (uniqued) in the current + /// [crate::Context]. + /// + /// A dialect will only ever be initialized once per context. A dialect must use interior + /// mutability to satisfy the requirements of the [Dialect] trait, and to allow the context to + /// store the returned instance in a reference-counted smart pointer. + fn init(info: DialectInfo) -> Self; + + /// This is called when registering a dialect, to register operations of the dialect. + /// + /// This is called _before_ [DialectRegistration::init]. + fn register_operations(info: &mut DialectInfo); +} diff --git a/hir/src/ir/dialect/info.rs b/hir/src/ir/dialect/info.rs new file mode 100644 index 000000000..39f175536 --- /dev/null +++ b/hir/src/ir/dialect/info.rs @@ -0,0 +1,160 @@ +use alloc::vec::Vec; +use core::{ + any::TypeId, + marker::Unsize, + ptr::{DynMetadata, Pointee}, +}; + +use super::{Dialect, DialectRegistration}; +use crate::{interner, traits::TraitInfo, FxHashMap, OpRegistration, OperationName}; + +pub struct DialectInfo { + /// The namespace of this dialect + name: interner::Symbol, + /// The concrete type id of the dialect implementation + type_id: TypeId, + /// The set of operations registered to this dialect + registered_ops: Vec, + /// The set of dialect interfaces (traits) implemented by this dialect + registered_interfaces: Vec, + /// The set of trait implementations for operations of this dialect which for one reason or + /// another could not be attached to the operation definition itself. These traits are instead + /// late-bound at dialect registration time. This field is only used during dialect registration. + late_bound_traits: FxHashMap>, +} + +impl DialectInfo { + pub(crate) fn new() -> Self + where + T: DialectRegistration, + { + let type_id = TypeId::of::(); + Self { + name: ::NAMESPACE.into(), + type_id, + registered_ops: Default::default(), + registered_interfaces: Default::default(), + late_bound_traits: Default::default(), + } + } + + pub const fn name(&self) -> interner::Symbol { + self.name + } + + pub const fn dialect_type_id(&self) -> &TypeId { + &self.type_id + } + + pub fn operations(&self) -> &[OperationName] { + &self.registered_ops + } + + pub fn register_operation(&mut self) -> OperationName + where + T: OpRegistration, + { + let opcode = ::name(); + match self.registered_ops.binary_search_by_key(&opcode, |op| op.name()) { + Ok(index) => self.registered_ops[index].clone(), + Err(index) => { + let extra_traits = self.late_bound_traits.remove(&opcode).unwrap_or_default(); + let name = OperationName::new::(self.name, extra_traits); + self.registered_ops.insert(index, name.clone()); + name + } + } + } + + pub fn register_operation_trait(&mut self) + where + T: OpRegistration + Unsize + 'static, + Trait: ?Sized + Pointee> + 'static, + { + let opcode = ::name(); + let traits = self.late_bound_traits.entry(opcode).or_default(); + let trait_type_id = TypeId::of::(); + if let Err(index) = traits.binary_search_by(|ti| ti.type_id().cmp(&trait_type_id)) { + traits.insert(index, TraitInfo::new::()); + } + } + + pub fn register_interface(&mut self) + where + T: Dialect + Unsize + 'static, + Trait: ?Sized + Pointee> + 'static, + { + let type_id = TypeId::of::(); + assert_eq!( + type_id, self.type_id, + "cannot register implementation of Trait for T, for another type" + ); + + let trait_type_id = TypeId::of::(); + if let Err(index) = self + .registered_interfaces + .binary_search_by(|ti| ti.type_id().cmp(&trait_type_id)) + { + self.registered_interfaces.insert(index, TraitInfo::new::()); + } + } + + /// Returns true if this operation implements `Trait` + pub fn implements(&self) -> bool + where + Trait: ?Sized + Pointee> + 'static, + { + let type_id = TypeId::of::(); + self.registered_interfaces + .binary_search_by(|ti| ti.type_id().cmp(&type_id)) + .is_ok() + } + + pub(super) fn upcast<'a, Trait>(&self, ptr: *const ()) -> Option<&'a Trait> + where + Trait: ?Sized + Pointee> + 'static, + { + let type_id = TypeId::of::(); + let metadata = self + .registered_interfaces + .binary_search_by(|ti| ti.type_id().cmp(&type_id)) + .ok() + .map(|index| unsafe { + self.registered_interfaces[index].metadata_unchecked::() + })?; + Some(unsafe { &*core::ptr::from_raw_parts(ptr, metadata) }) + } +} + +impl Eq for DialectInfo {} +impl PartialEq for DialectInfo { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} +impl Ord for DialectInfo { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.name.cmp(&other.name) + } +} +impl PartialOrd for DialectInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl core::hash::Hash for DialectInfo { + fn hash(&self, state: &mut H) { + self.name.hash(state); + } +} + +impl core::fmt::Debug for DialectInfo { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.name.as_str()) + } +} +impl core::fmt::Display for DialectInfo { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(self.name.as_str()) + } +} diff --git a/hir/src/ir/dominance.rs b/hir/src/ir/dominance.rs new file mode 100644 index 000000000..09b49f29b --- /dev/null +++ b/hir/src/ir/dominance.rs @@ -0,0 +1,19 @@ +mod frontier; +mod info; +pub mod nca; +mod traits; +mod tree; + +pub use self::{ + frontier::DominanceFrontier, + info::{DominanceInfo, PostDominanceInfo, RegionDominanceInfo}, + traits::{Dominates, PostDominates}, + tree::{ + DomTreeBase, DomTreeError, DomTreeNode, DomTreeVerificationLevel, DominanceTree, + PostDominanceTree, PostOrderDomTreeIter, PreOrderDomTreeIter, + }, +}; +use self::{ + nca::{BatchUpdateInfo, SemiNCA}, + tree::DomTreeRoots, +}; diff --git a/hir/src/ir/dominance/frontier.rs b/hir/src/ir/dominance/frontier.rs new file mode 100644 index 000000000..6fe701701 --- /dev/null +++ b/hir/src/ir/dominance/frontier.rs @@ -0,0 +1,235 @@ +use alloc::collections::VecDeque; + +use super::*; +use crate::{ + adt::{SmallDenseMap, SmallSet}, + BlockArgument, BlockRef, ValueRef, +}; + +/// Calculates the dominance frontier for every block in a given `DominatorTree` +/// +/// The dominance frontier of a block `B` is the set of blocks `DF` where for each block `Y` in `DF` +/// `B` dominates some predecessor of `Y`, but does not strictly dominate `Y`. +/// +/// Dominance frontiers are useful in the construction of SSA form, as well as identifying control +/// dependent dataflow (for example, a variable in a program that has a different value depending +/// on what branch of an `if` statement is taken). +/// +/// A dominance frontier can also be computed for a set of blocks, by taking the union of the +/// dominance frontiers of each block in the set. +/// +/// An iterated dominance frontier is given by computing the dominance frontier for some set `X`, +/// i.e. `DF(X)`, then computing the dominance frontier on that, i.e. `DF(DF(X))`, taking the union +/// of the results, and repeating this process until fixpoint is reached. This is often represented +/// in literature as `DF+(X)`. +/// +/// Iterated dominance frontiers are of particular usefulness to us, because they correspond to the +/// set of blocks in which we need to place phi nodes for some variable, in order to properly handle +/// control dependent dataflow for that variable. +/// +/// Consider the following example (not in SSA form): +/// +/// +/// ```text,ignore +/// block0(x): +/// v = 0 +/// cond_br x, block1, block2 +/// +/// block1(): +/// v = 1 +/// br block3 +/// +/// block2(): +/// v = 2 +/// br block3 +/// +/// block3: +/// ret v +/// ``` +/// +/// In this example, we have a variable, `v`, which is assigned new values later in the program +/// depending on which path through the program is taken. To transform this program into SSA form, +/// we take the set `V`, containing all of the assignments to `v`, and compute `DF+(V)`. Given +/// the program above, that would give us the set `{block3}`: +/// +/// * The dominance frontier of the assignment in `block0` is empty, because `block0` strictly +/// dominates all other blocks in the program. +/// * The dominance frontier of the assignment in `block1` contains `block3`, because `block1` +/// dominates a predecessor of `block3` (itself), but does not strictly dominate that predecessor, +/// because a node cannot strictly dominate itself. +/// * The dominance frontier of the assignment in `block2` contains `block3`, for the same reasons +/// as `block1`. +/// * The dominance frontier of `block3` is empty, because it has no successors and thus cannot +/// dominate any other blocks. +/// * The union of all the dominance frontiers is simply `{block3}` +/// +/// So this tells us that we need to place a phi node (a block parameter) at `block3`, and rewrite +/// all uses of `v` strictly dominated by the phi node to use the value associated with the phi +/// instead. In every predecessor of `block3`, we must pass `v` as a new block argument. Lastly, to +/// obtain SSA form, we rewrite assignments to `v` as defining new variables instead, and walk up +/// the dominance tree from each use of `v` until we find the nearest dominating definition for that +/// use, and rewrite the usage of `v` to use the value produced by that definition. Performing these +/// steps gives us the following program: +/// +/// ```text,ignore +/// block0(x): +/// v0 = 0 +/// cond_br x, block1, block2 +/// +/// block1(): +/// v2 = 1 +/// br block3(v2) +/// +/// block2(): +/// v3 = 2 +/// br block3(v3) +/// +/// block3(v1): +/// ret v1 +/// ``` +/// +/// This program is in SSA form, and the dataflow for `v` is now explicit. An interesting +/// consequence of the transformation we performed, is that we are able to trivially recognize +/// that the definition of `v` in `block0` is unused, allowing us to eliminate it entirely. +#[derive(Default)] +pub struct DominanceFrontier { + /// The dominance frontier for each block, as a set of blocks + dfs: SmallDenseMap, 8>, +} + +impl DominanceFrontier { + pub fn new(domtree: &DominanceTree) -> Self { + let mut this = Self::default(); + + for node in domtree.postorder() { + let Some(node_block) = node.block() else { + continue; + }; + + let block = node_block.borrow(); + let has_multiple_predecessors = block.predecessors().enumerate().any(|(i, _)| i > 1); + if !has_multiple_predecessors { + continue; + } + + let idom = node + .idom() + .expect("expected immediate dominator for block with multiple predecessors"); + let idom_block = idom.block().unwrap(); + for pred in block.predecessors() { + let mut p = pred.predecessor(); + while p != idom_block { + this.dfs.entry(p).or_default().insert(node_block); + let node_p = domtree.get(Some(p)).unwrap(); + let Some(idom_p) = node_p.idom() else { + break; + }; + p = idom_p.block().unwrap(); + } + } + } + + this + } + + /// Compute the iterated dominance frontier for `block` + pub fn iterate(&self, block: BlockRef) -> SmallSet { + self.iterate_all([block]) + } + + /// Compute the iterated dominance frontier for `blocks` + pub fn iterate_all(&self, blocks: I) -> SmallSet + where + I: IntoIterator, + { + let mut block_q = VecDeque::default(); + let mut idf = SmallSet::<_, 4>::default(); + + let mut visit_block = |block: BlockRef, block_q: &mut VecDeque| { + // If `block` has an empty dominance frontier, there is nothing to add. + let Some(df) = self.dfs.get(&block) else { + return; + }; + + let added = df.difference(&idf); + if added.is_empty() { + return; + } + + // Extend `idf` and add the new blocks to the queue + for block in added { + idf.insert(block); + if !block_q.contains(&block) { + block_q.push_back(block); + } + } + }; + + // Process the initial set of blocks + for block in blocks { + visit_block(block, &mut block_q); + } + + // Process any newly queued blocks + while let Some(block) = block_q.pop_front() { + visit_block(block, &mut block_q); + } + + idf + } + + /// Get an iterator over the dominance frontier of `block` + pub fn iter(&self, block: BlockRef) -> impl Iterator + '_ { + DominanceFrontierIter { + df: self.dfs.get(&block).map(|set| set.iter().copied()), + } + } + + /// Get an iterator over the dominance frontier of `value` + pub fn iter_by_value(&self, value: ValueRef) -> impl Iterator + '_ { + let v = value.borrow(); + let defining_block = match v.get_defining_op() { + Some(op) => op.parent().unwrap(), + None => v.downcast_ref::().unwrap().owner(), + }; + DominanceFrontierIter { + df: self.dfs.get(&defining_block).map(|set| set.iter().copied()), + } + } + + /// Get the set of blocks in the dominance frontier of `block`, or `None` if `block` has an + /// empty dominance frontier. + #[inline] + pub fn get(&self, block: &BlockRef) -> Option<&SmallSet> { + self.dfs.get(block) + } + + /// Get the set of blocks in the dominance frontier of `value`, or `None` if `value` has an + /// empty dominance frontier. + pub fn get_by_value(&self, value: ValueRef) -> Option<&SmallSet> { + let v = value.borrow(); + let defining_block = match v.get_defining_op() { + Some(op) => op.parent().unwrap(), + None => v.downcast_ref::().unwrap().owner(), + }; + self.dfs.get(&defining_block) + } +} + +struct DominanceFrontierIter { + df: Option, +} +impl Iterator for DominanceFrontierIter +where + I: Iterator, +{ + type Item = BlockRef; + + fn next(&mut self) -> Option { + if let Some(i) = self.df.as_mut() { + i.next() + } else { + None + } + } +} diff --git a/hir/src/ir/dominance/info.rs b/hir/src/ir/dominance/info.rs new file mode 100644 index 000000000..e5247a84c --- /dev/null +++ b/hir/src/ir/dominance/info.rs @@ -0,0 +1,588 @@ +use alloc::rc::Rc; +use core::cell::{LazyCell, Ref, RefCell}; + +use smallvec::SmallVec; + +use super::*; +use crate::{ + adt::SmallDenseMap, pass::Analysis, Block, BlockRef, Operation, OperationRef, + RegionKindInterface, RegionRef, Report, +}; + +/// [DominanceInfo] provides a high-level API for querying dominance information. +/// +/// Note that this type is aware of the different types of regions, and returns a region-kind +/// specific notion of dominance. See [RegionKindInterface] for details. +#[derive(Debug, Default, Clone)] +pub struct DominanceInfo { + info: DominanceInfoBase, +} + +impl Analysis for DominanceInfo { + type Target = Operation; + + fn name(&self) -> &'static str { + "dominance" + } + + fn analyze( + &mut self, + op: &Self::Target, + _analysis_manager: crate::pass::AnalysisManager, + ) -> Result<(), Report> { + if op.has_regions() { + self.info.recompute(op); + } + + Ok(()) + } + + fn invalidate(&self, _preserved_analyses: &mut crate::pass::PreservedAnalyses) -> bool { + true + } +} + +impl DominanceInfo { + /// Compute the dominance information for `op` + pub fn new(op: &Operation) -> Self { + Self { + info: DominanceInfoBase::new(op), + } + } + + #[doc(hidden)] + #[inline(always)] + pub(crate) fn info(&self) -> &DominanceInfoBase { + &self.info + } + + /// Recompute dominance information for all of the regions in `op`. + pub fn recompute(&mut self, op: &Operation) { + self.info.recompute(op) + } + + /// Invalidate all dominance info. + /// + /// This can be used by clients that make major changes to the CFG and don't have a good way to + /// update it. + pub fn invalidate(&mut self) { + self.info.invalidate(); + } + + /// Invalidate dominance info for the given region. + /// + /// This can be used by clients that make major changes to the CFG and don't have a good way to + /// update it. + pub fn invalidate_region(&mut self, region: RegionRef) { + self.info.invalidate_region(region); + } + + /// Get the dominance tree node for `block`, if one exists. + pub fn node(&self, block: BlockRef) -> Option> { + self.info.node(block) + } + + /// Get the dominance tree for `region` + pub fn dominance(&self, region: RegionRef) -> Rc> { + self.info.dominance(region) + } + + /// Returns true if `a` dominates `b`. + /// + /// Note that if `a == b`, this returns true, if you want strict dominance, see + /// [Self::properly_dominates] instead. + /// + /// The specific details of how dominance is computed is specific to the types involved. See + /// the implementations of the [Dominates] trait for that information. + pub fn dominates(&self, a: &A, b: &B) -> bool + where + A: Dominates, + { + a.dominates(b, self) + } + + /// Returns true if `a` properly dominates `b`. + /// + /// This always returns false if `a == b`. + /// + /// The specific details of how dominance is computed is specific to the types involved. See + /// the implementations of the [Dominates] trait for that information. + pub fn properly_dominates(&self, a: &A, b: &B) -> bool + where + A: Dominates, + { + a.properly_dominates(b, self) + } + + /// An implementation of `properly_dominates` for operations, where we sometimes wish to treat + /// `a` as dominating `b`, if `b` is enclosed by a region of `a`. This behavior is controlled + /// by the `enclosing_op_ok` flag. + pub fn properly_dominates_with_options( + &self, + a: OperationRef, + mut b: OperationRef, + enclosing_op_ok: bool, + ) -> bool { + let a_block = a.parent().expect("`a` must be in a block"); + let mut b_block = b.parent().expect("`b` must be in a block"); + + // An instruction dominates itself, but does not properly dominate itself, unless this is + // a graph region. + if a == b { + return !a_block.borrow().has_ssa_dominance(); + } + + // If these ops are in different regions, then normalize one into the other. + let a_region = a_block.parent().unwrap(); + if a_region != b_block.parent().unwrap() { + // Walk up `b`'s region tree until we find an operation in `a`'s region that encloses + // it. If this fails, then we know there is no post-dominance relation. + let Some(found) = a_region.borrow().find_ancestor_op(b) else { + return false; + }; + b = found; + b_block = b.parent().expect("`b` must be in a block"); + assert!(b_block.parent().unwrap() == a_region); + + // If `a` encloses `b`, then we consider it to dominate. + if a == b && enclosing_op_ok { + return true; + } + } + + // Ok, they are in the same region now. + if a_block == b_block { + // Dominance changes based on the region type. In a region with SSA dominance, uses + // insde the same block must follow defs. In other region kinds, uses and defs can + // come in any order inside a block. + return if a_block.borrow().has_ssa_dominance() { + // If the blocks are the same, then check if `b` is before `a` in the block. + a.borrow().is_before_in_block(&b) + } else { + true + }; + } + + // If the blocks are different, use the dominance tree to resolve the query + self.info.dominance(a_region).properly_dominates(Some(a_block), Some(b_block)) + } +} + +/// [PostDominanceInfo] provides a high-level API for querying post-dominance information. +/// +/// Note that this type is aware of the different types of regions, and returns a region-kind +/// specific notion of dominance. See [RegionKindInterface] for details. +#[derive(Default, Clone)] +pub struct PostDominanceInfo { + info: DominanceInfoBase, +} + +impl Analysis for PostDominanceInfo { + type Target = Operation; + + fn name(&self) -> &'static str { + "post-dominance" + } + + fn analyze( + &mut self, + op: &Self::Target, + _analysis_manager: crate::pass::AnalysisManager, + ) -> Result<(), Report> { + if !op.has_regions() { + self.info.recompute(op); + } + + Ok(()) + } + + fn invalidate(&self, _preserved_analyses: &mut crate::pass::PreservedAnalyses) -> bool { + true + } +} + +impl PostDominanceInfo { + /// Compute the post-dominance information for `op` + pub fn new(op: &Operation) -> Self { + Self { + info: DominanceInfoBase::new(op), + } + } + + #[doc(hidden)] + #[inline(always)] + pub(crate) fn info(&self) -> &DominanceInfoBase { + &self.info + } + + /// Returns true if `a` post-dominates `b`. + /// + /// Note that if `a == b`, this returns true, if you want strict post-dominance, see + /// [Self::properly_post_dominates] instead. + /// + /// The specific details of how dominance is computed is specific to the types involved. See + /// the implementations of the [PostDominates] trait for that information. + pub fn post_dominates(&self, a: &A, b: &B) -> bool + where + A: PostDominates, + { + a.post_dominates(b, self) + } + + /// Returns true if `a` properly post-dominates `b`. + /// + /// This always returns false if `a == b`. + /// + /// The specific details of how dominance is computed is specific to the types involved. See + /// the implementations of the [PostDominates] trait for that information. + pub fn properly_post_dominates(&self, a: &A, b: &B) -> bool + where + A: PostDominates, + { + a.properly_post_dominates(b, self) + } +} + +/// This type carries the dominance information for a single region, lazily computed on demand. +pub struct RegionDominanceInfo { + /// The dominator tree for this region + domtree: LazyCell>>, RegionDomTreeCtor>, + /// A flag that indicates where blocks in this region have SSA dominance + has_ssa_dominance: bool, +} + +impl core::fmt::Debug for RegionDominanceInfo { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("RegionDominanceInfo") + .field("has_ssa_dominance", &self.has_ssa_dominance) + .field_with("domtree", |f| { + if let Some(domtree) = self.domtree.as_ref() { + core::fmt::Debug::fmt(domtree, f) + } else { + f.write_str("None") + } + }) + .finish() + } +} + +impl Clone for RegionDominanceInfo { + fn clone(&self) -> Self { + let domtree = self.domtree.clone(); + Self { + domtree: LazyCell::new(RegionDomTreeCtor::Clone(domtree)), + has_ssa_dominance: self.has_ssa_dominance, + } + } +} + +impl RegionDominanceInfo { + /// Construct a new [RegionDominanceInfo] for `region` + pub fn new(region: RegionRef) -> Self { + let r = region.borrow(); + let parent_op = r.parent().unwrap(); + // A region has SSA dominance if it tells us one way or the other, otherwise we must assume + // that it does. + let has_ssa_dominance = parent_op + .borrow() + .as_trait::() + .map(|rki| rki.has_ssa_dominance()) + .unwrap_or(true); + + Self::create(region, has_ssa_dominance, r.has_one_block()) + } + + fn create(region: RegionRef, has_ssa_dominance: bool, has_one_block: bool) -> Self { + // We only create a dominator tree for multi-block regions + if has_one_block { + Self { + domtree: LazyCell::new(RegionDomTreeCtor::Compute(None)), + has_ssa_dominance, + } + } else { + Self { + domtree: LazyCell::new(RegionDomTreeCtor::Compute(Some(region))), + has_ssa_dominance, + } + } + } + + /// Get the dominance tree for this region. + /// + /// Returns `None` if the region was empty or had only a single block. + pub fn dominance(&self) -> Option>> { + self.domtree.clone() + } +} + +/// This type provides shared functionality to both [DominanceInfo] and [PostDominanceInfo]. +#[derive(Default)] +pub(crate) struct DominanceInfoBase { + /// A mapping of regions to their dominator tree and a flag that indicates whether or not they + /// have SSA dominance. + /// + /// This map does not contain dominator trees for empty or single block regions, however we + /// still compute whether or not they have SSA dominance regardless. + dominance_infos: RefCell>>, +} + +impl core::fmt::Debug for DominanceInfoBase { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let infos = self.dominance_infos.borrow(); + + let mut builder = f.debug_map(); + for (region, region_info) in infos.iter() { + builder.key_with(|f| write!(f, "{region}")).value(region_info); + } + builder.finish() + } +} + +impl Clone for DominanceInfoBase { + fn clone(&self) -> Self { + let infos = self.dominance_infos.borrow(); + Self { + dominance_infos: RefCell::new(infos.clone()), + } + } +} + +#[allow(unused)] +impl DominanceInfoBase { + /// Compute dominance information for all of the regions in `op`. + pub fn new(op: &Operation) -> Self { + let mut this = Self::default(); + this.recompute(op); + this + } + + /// Recompute dominance information for all of the regions in `op`. + pub fn recompute(&mut self, op: &Operation) { + let dominance_infos = self.dominance_infos.get_mut(); + dominance_infos.clear(); + + let has_ssa_dominance = op + .as_trait::() + .is_none_or(|rki| rki.has_ssa_dominance()); + for region in op.regions() { + let has_one_block = region.has_one_block(); + let region = region.as_region_ref(); + let info = RegionDominanceInfo::::create( + region, + has_ssa_dominance, + has_one_block, + ); + dominance_infos.insert(region, info); + } + } + + /// Invalidate all dominance info. + /// + /// This can be used by clients that make major changes to the CFG and don't have a good way to + /// update it. + pub fn invalidate(&mut self) { + self.dominance_infos.get_mut().clear(); + } + + /// Invalidate dominance info for the given region. + /// + /// This can be used by clients that make major changes to the CFG and don't have a good way to + /// update it. + pub fn invalidate_region(&mut self, region: RegionRef) { + self.dominance_infos.get_mut().remove(®ion); + } + + /// Finds the nearest common dominator block for the two given blocks `a` and `b`. + /// + /// If no common dominator can be found, this function will return `None`. + pub fn find_nearest_common_dominator_of( + &self, + a: Option, + b: Option, + ) -> Option { + // If either `a` or `b` are `None`, then conservatively return `None` + let a = a?; + let b = b?; + + // If they are the same block, then we are done. + if a == b { + return Some(a); + } + + // Try to find blocks that are in the same region. + let (a, b) = Block::get_blocks_in_same_region(a, b)?; + + // If the common ancestor in a common region is the same block, then return it. + if a == b { + return Some(a); + } + + // Otherwise, there must be multiple blocks in the region, check the dominance tree + self.dominance(a.parent().unwrap()).find_nearest_common_dominator(a, b) + } + + /// Finds the nearest common dominator block for the given range of blocks. + /// + /// If no common dominator can be found, this function will return `None`. + pub fn find_nearest_common_dominator_of_all( + &self, + mut blocks: impl ExactSizeIterator, + ) -> Option { + let mut dom = blocks.next(); + + for block in blocks { + dom = self.find_nearest_common_dominator_of(dom, Some(block)); + } + + dom + } + + /// Get the root dominance node of the given region. + /// + /// Panics if `region` is not a multi-block region. + pub fn root_node(&self, region: RegionRef) -> Rc { + self.get_dominance_info(region) + .domtree + .as_deref() + .expect("`region` isn't multi-block") + .root_node() + .expect("expected region to have a root node") + } + + /// Return the dominance node for the region containing `block`. + /// + /// Panics if `block` is not a member of a multi-block region. + pub fn node(&self, block: BlockRef) -> Option> { + self.get_dominance_info(block.parent().expect("block isn't attached to region")) + .domtree + .as_deref() + .expect("`block` isn't in a multi-block region") + .get(Some(block)) + } + + /// Return true if the specified block is reachable from the entry block of its region. + pub fn is_reachable_from_entry(&self, block: BlockRef) -> bool { + // If this is the first block in its region, then it is trivially reachable. + if block.borrow().is_entry_block() { + return true; + } + + let region = block.parent().expect("block isn't attached to region"); + self.dominance(region).is_reachable_from_entry(block) + } + + /// Return true if operations in the specified block are known to obey SSA dominance rules. + /// + /// Returns false if the block is a graph region or unknown. + pub fn block_has_ssa_dominance(&self, block: BlockRef) -> bool { + let region = block.parent().expect("block isn't attached to region"); + self.get_dominance_info(region).has_ssa_dominance + } + + /// Return true if operations in the specified region are known to obey SSA dominance rules. + /// + /// Returns false if the region is a graph region or unknown. + pub fn region_has_ssa_dominance(&self, region: RegionRef) -> bool { + self.get_dominance_info(region).has_ssa_dominance + } + + /// Returns the dominance tree for `region`. + /// + /// Panics if `region` is a single-block region. + pub fn dominance(&self, region: RegionRef) -> Rc> { + self.get_dominance_info(region) + .dominance() + .expect("cannot get dominator tree for single block regions") + } + + /// Return the dominance information for `region`. + /// + /// NOTE: The dominance tree for single-block regions will be `None` + fn get_dominance_info(&self, region: RegionRef) -> Ref<'_, RegionDominanceInfo> { + // Check to see if we already have this information. + self.dominance_infos + .borrow_mut() + .entry(region) + .or_insert_with(|| RegionDominanceInfo::new(region)); + + Ref::map(self.dominance_infos.borrow(), |di| &di[®ion]) + } + + /// Return true if the specified block A properly dominates block B. + pub fn properly_dominates(&self, a: BlockRef, mut b: BlockRef) -> bool { + // A block dominates itself, but does not properly dominate itself. + if a == b { + return false; + } + + // If both blocks are not in the same region, `a` properly dominates `b` if `b` is defined + // in an operation region that (recursively) ends up being dominated by `a`. Walk up the + // ancestors of `b`. + let a_region = a.parent(); + if a_region != b.parent() { + // If we could not find a valid block `b` then it is not a dominator. + let Some(found) = a_region.as_ref().and_then(|r| r.borrow().find_ancestor_block(b)) + else { + return false; + }; + + b = found; + + // Check to see if the ancestor of `b` is the same block as `a`. `a` properly dominates + // `b` if it contains an op that contains the `b` block + if a == b { + return true; + } + } + + // Otherwise, they are two different blocks in the same region, use dominance tree + self.dominance(a_region.unwrap()).properly_dominates(Some(a), Some(b)) + } +} + +impl DominanceInfoBase { + #[allow(unused)] + pub fn root_nodes(&self, region: RegionRef) -> SmallVec<[BlockRef; 4]> { + self.dominance_infos + .borrow() + .get(®ion) + .and_then(|dominfo| dominfo.domtree.as_deref()) + .map(|domtree| domtree.roots().iter().filter_map(|maybe_root| *maybe_root).collect()) + .unwrap_or_default() + } +} + +/// A faux-constructor for [RegionDominanceInfo] for use with [LazyCell] without boxing. +enum RegionDomTreeCtor { + Compute(Option), + Clone(Option>>), +} +impl FnOnce<()> for RegionDomTreeCtor { + type Output = Option>>; + + extern "rust-call" fn call_once(self, _args: ()) -> Self::Output { + match self { + Self::Compute(None) => None, + Self::Compute(Some(region)) => DomTreeBase::new(region).ok().map(Rc::new), + Self::Clone(domtree) => domtree, + } + } +} +impl FnMut<()> for RegionDomTreeCtor { + extern "rust-call" fn call_mut(&mut self, _args: ()) -> Self::Output { + match self { + Self::Compute(None) => None, + Self::Compute(Some(region)) => DomTreeBase::new(*region).ok().map(Rc::new), + Self::Clone(domtree) => domtree.clone(), + } + } +} +impl Fn<()> for RegionDomTreeCtor { + extern "rust-call" fn call(&self, _args: ()) -> Self::Output { + match self { + Self::Compute(None) => None, + Self::Compute(Some(region)) => DomTreeBase::new(*region).ok().map(Rc::new), + Self::Clone(domtree) => domtree.clone(), + } + } +} diff --git a/hir/src/ir/dominance/nca.rs b/hir/src/ir/dominance/nca.rs new file mode 100644 index 000000000..c79e5643d --- /dev/null +++ b/hir/src/ir/dominance/nca.rs @@ -0,0 +1,1540 @@ +use alloc::{collections::BTreeMap, rc::Rc}; +use core::cell::{Cell, Ref, RefCell}; + +use smallvec::{smallvec, SmallVec}; + +use super::{DomTreeBase, DomTreeNode, DomTreeRoots}; +use crate::{ + cfg::{self, Graph, GraphDiff, Inverse}, + formatter::{DisplayOptional, DisplayValues}, + BlockRef, EntityId, EntityWithId, Region, +}; + +/// [SemiNCAInfo] provides functionality for constructing a dominator tree for a control-flow graph +/// based on the Semi-NCA algorithm described in the following dissertation: +/// +/// [1] Linear-Time Algorithms for Dominators and Related Problems +/// Loukas Georgiadis, Princeton University, November 2005, pp. 21-23: +/// ftp://ftp.cs.princeton.edu/reports/2005/737.pdf +/// +/// The Semi-NCA algorithm runs in O(n^2) worst-case time but usually slightly faster than Simple +/// Lengauer-Tarjan in practice. +/// +/// O(n^2) worst cases happen when the computation of nearest common ancestors requires O(n) average +/// time, which is very unlikely in real world. If this ever turns out to be an issue, consider +/// implementing a hybrid algorithm that uses SLT to perform full constructions and SemiNCA for +/// incremental updates. +/// +/// The file uses the Depth Based Search algorithm to perform incremental updates (insertion and +/// deletions). The implemented algorithm is based on this publication: +/// +/// [2] An Experimental Study of Dynamic Dominators +/// Loukas Georgiadis, et al., April 12 2016, pp. 5-7, 9-10: +/// https://arxiv.org/pdf/1604.02711.pdf +pub struct SemiNCA { + /// Number to node mapping is 1-based. + num_to_node: SmallVec<[Option; 64]>, + /// Infos are mapped to nodes using block indices + node_infos: RefCell>, + batch_updates: Option>, +} + +/// Get the successors (or predecessors, if `INVERSED == true`) of `node`, incorporating insertions +/// and deletions from `bui` if available. +/// +/// The use of "children" here changes meaning depending on: +/// +/// * Whether or not the graph traversal is `INVERSED` +/// * Whether or not the graph is a post-dominator tree (i.e. `IS_POST_DOM`) +/// +/// If we're traversing a post-dominator tree, then the "children" of a node are actually +/// predecessors of the block in the CFG. However, if the traversal is _also_ `INVERSED`, then the +/// children actually are successors of the block in the CFG. +/// +/// For a forward-dominance tree, "children" do correspond to successors in the CFG, but again, if +/// the traversal is `INVERSED`, then the children are actually predecessors. +/// +/// This function (and others in this module) are written in such a way that we can abstract over +/// whether the underlying dominator tree is a forward- or post-dominance tree, as much of the +/// implementation is identical. +pub fn get_children_with_batch_updates( + node: BlockRef, + bui: Option<&BatchUpdateInfo>, +) -> SmallVec<[BlockRef; 8]> { + use crate::cfg::GraphDiff; + + if let Some(bui) = bui { + bui.pre_cfg_view.get_children::(node) + } else { + get_children::(node) + } +} + +/// Get the successors (or predecessors, if `INVERSED == true`) of `node`. +pub fn get_children(node: BlockRef) -> SmallVec<[BlockRef; 8]> { + if INVERSED { + Inverse::::children(node).collect() + } else { + let mut r = BlockRef::children(node).collect::>(); + r.reverse(); + r + } +} + +#[derive(Default)] +pub struct NodeInfo { + num: Cell, + parent: Cell, + semi: Cell, + label: Cell, + idom: Cell>, + reverse_children: SmallVec<[u32; 4]>, +} +impl NodeInfo { + pub fn idom(&self) -> Option { + self.idom.get() + } + + #[inline] + pub fn num(&self) -> u32 { + self.num.get() + } + + #[inline] + pub fn parent(&self) -> u32 { + self.parent.get() + } + + #[inline] + pub fn semi(&self) -> u32 { + self.semi.get() + } + + #[inline] + pub fn label(&self) -> u32 { + self.label.get() + } + + #[inline] + pub fn reverse_children(&self) -> &[u32] { + &self.reverse_children + } +} + +/// [BatchUpdateInfo] represents a batch of insertion/deletion operations that have been applied to +/// the CFG. This information is used to incrementally update the dominance tree as changes are +/// made to the CFG. +#[derive(Default, Clone)] +pub struct BatchUpdateInfo { + pub pre_cfg_view: cfg::CfgDiff, + pub post_cfg_view: cfg::CfgDiff, + pub num_legalized: usize, + // Remembers if the whole tree was recomputed at some point during the current batch update + pub is_recalculated: bool, +} + +impl BatchUpdateInfo { + pub fn new( + pre_cfg_view: cfg::CfgDiff, + post_cfg_view: Option>, + ) -> Self { + let num_legalized = pre_cfg_view.num_legalized_updates(); + Self { + pre_cfg_view, + post_cfg_view: post_cfg_view.unwrap_or_default(), + num_legalized, + is_recalculated: false, + } + } +} + +impl SemiNCA { + /// Obtain a fresh [SemiNCA] instance, using the provided set of [BatchUpdateInfo]. + pub fn new(batch_updates: Option>) -> Self { + Self { + num_to_node: smallvec![None], + node_infos: Default::default(), + batch_updates, + } + } + + /// Reset the [SemiNCA] state so it can be used to compute a dominator tree from scratch. + pub fn clear(&mut self) { + // Don't reset the pointer to BatchUpdateInfo here -- if there's an update in progress, + // we need this information to continue it. + self.num_to_node.clear(); + self.num_to_node.push(None); + self.node_infos.get_mut().clear(); + } + + /// Look up information about a block in the Semi-NCA state + pub fn node_info(&self, block: Option) -> Ref<'_, NodeInfo> { + match block { + None => Ref::map(self.node_infos.borrow(), |ni| { + ni.first().expect("no virtual node present") + }), + Some(block) => { + let index = block.borrow().id().as_usize() + 1; + + if index >= self.node_infos.borrow().len() { + self.node_infos.borrow_mut().resize_with(index + 1, NodeInfo::default); + } + + Ref::map(self.node_infos.borrow(), |ni| unsafe { ni.get_unchecked(index) }) + } + } + } + + /// Get a mutable reference to the stored informaton for `block` + pub fn node_info_mut(&mut self, block: Option) -> &mut NodeInfo { + match block { + None => self.node_infos.get_mut().get_mut(0).expect("no virtual node present"), + Some(block) => { + let index = block.borrow().id().as_usize() + 1; + + let node_infos = self.node_infos.get_mut(); + if index >= node_infos.len() { + node_infos.resize_with(index + 1, NodeInfo::default); + } + + unsafe { node_infos.get_unchecked_mut(index) } + } + } + } + + /// Look up the immediate dominator for `block`, if it has one. + /// + /// A value of `None` for `block` is meaningless, as virtual nodes only are present in post- + /// dominance graphs, and always post-dominate all other nodes in the graph. However, it is + /// convenient to have many of the APIs in this module take a `Option` for uniformity. + pub fn idom(&self, block: Option) -> Option { + self.node_info(block).idom() + } + + /// Get or compute the dominance tree node information for `block`, in `tree`, using the current + /// Semi-NCA state. + pub fn node_for_block( + &self, + block: Option, + tree: &mut DomTreeBase, + ) -> Option> { + let node = tree.get(block); + if node.is_some() { + return node; + } + + // Haven't calculated this node yet? Get or calculate the node for the immediate dominator + let idom = self.idom(block); + let idom_node = match idom { + None => Some(tree.get(None).expect("expected idom or virtual node")), + Some(idom_block) => self.node_for_block(Some(idom_block), tree), + }; + + // Add a new tree node for this node, and link it as a child of idom_node + Some(tree.create_node(block, idom_node)) + } + + /// Custom DFS implementation which can skip nodes based on a provided predicate. + /// + /// It also collects reverse children so that we don't have to spend time getting predecessors + /// in SemiNCA. + /// + /// If `IsReverse` is set to true, the DFS walk will be performed backwards relative to IS_POST_DOM + /// -- using reverse edges for dominators and forward edges for post-dominators. + /// + /// If `succ_order` is specified then that is the order in which the DFS traverses the children, + /// otherwise the order is implied by the results of `get_children`. + pub fn run_dfs( + &mut self, + v: Option, + mut last_num: u32, + mut condition: C, + attach_to_num: u32, + succ_order: Option<&BTreeMap>, + ) -> u32 + where + C: FnMut(Option, Option) -> bool, + { + let v = v.expect("expected valid root node for search"); + + let mut worklist = SmallVec::<[(BlockRef, u32); 64]>::from_iter([(v, attach_to_num)]); + + self.node_info_mut(Some(v)).parent.set(attach_to_num); + + while let Some((block, parent_num)) = worklist.pop() { + let block_info = self.node_info_mut(Some(block)); + block_info.reverse_children.push(parent_num); + + // Visited nodes always have positive DFS numbers. + if block_info.num.get() != 0 { + continue; + } + + block_info.parent.set(parent_num); + last_num += 1; + block_info.num.set(last_num); + block_info.semi.set(last_num); + block_info.label.set(last_num); + self.num_to_node.push(Some(block)); + + let mut successors = if const { REVERSE != IS_POST_DOM } { + get_children_with_batch_updates::( + block, + self.batch_updates.as_ref(), + ) + } else { + get_children_with_batch_updates::( + block, + self.batch_updates.as_ref(), + ) + }; + if let Some(succ_order) = succ_order { + if successors.len() > 1 { + successors.sort_by(|a, b| succ_order[a].cmp(&succ_order[b])); + } + } + + for succ in successors.into_iter().filter(|succ| condition(Some(block), Some(*succ))) { + worklist.push((succ, last_num)); + } + } + + last_num + } + + // V is a predecessor of W. eval() returns V if V < W, otherwise the minimum + // of sdom(U), where U > W and there is a virtual forest path from U to V. The + // virtual forest consists of linked edges of processed vertices. + // + // We can follow Parent pointers (virtual forest edges) to determine the + // ancestor U with minimum sdom(U). But it is slow and thus we employ the path + // compression technique to speed up to O(m*log(n)). Theoretically the virtual + // forest can be organized as balanced trees to achieve almost linear + // O(m*alpha(m,n)) running time. But it requires two auxiliary arrays (Size + // and Child) and is unlikely to be faster than the simple implementation. + // + // For each vertex V, its Label points to the vertex with the minimal sdom(U) + // (Semi) in its path from V (included) to NodeToInfo[V].Parent (excluded). + fn eval<'a, 'b: 'a>( + v: u32, + last_linked: u32, + eval_stack: &mut SmallVec<[&'a NodeInfo; 32]>, + num_to_info: &'b [Option>], + ) -> u32 { + let mut v_info = &**num_to_info[v as usize].as_ref().unwrap(); + if v_info.parent.get() < last_linked { + return v_info.label.get(); + } + + // Store ancestors except the last (root of a virtual tree) into a stack. + eval_stack.clear(); + loop { + let parent = &**num_to_info[v_info.parent.get() as usize].as_ref().unwrap(); + eval_stack.push(v_info); + v_info = parent; + if v_info.parent.get() < last_linked { + break; + } + } + + // Path compression. Point each vertex's `parent` to the root and update its `label` if any + // of its ancestors `label` has a smaller `semi` + let mut p_info = v_info; + let mut p_label_info = &**num_to_info[p_info.label.get() as usize].as_ref().unwrap(); + while let Some(info) = eval_stack.pop() { + v_info = info; + v_info.parent.set(p_info.parent.get()); + let v_label_info = &**num_to_info[v_info.label.get() as usize].as_ref().unwrap(); + if p_label_info.semi.get() < v_label_info.semi.get() { + v_info.label.set(p_info.label.get()); + } else { + p_label_info = v_label_info; + } + p_info = v_info; + } + + v_info.label.get() + } + + /// This function requires DFS to be run before calling it. + pub fn run(&mut self) { + let next_num = self.num_to_node.len(); + let mut num_to_info = SmallVec::<[Option>; 8]>::default(); + num_to_info.reserve(next_num); + num_to_info.push(None); + + // Initialize idoms to spanning tree parents + for i in 1..next_num { + let v = self.num_to_node[i].unwrap(); + let v_info = self.node_info(Some(v)); + v_info.idom.set(self.num_to_node[v_info.parent() as usize]); + assert_eq!(i, num_to_info.len()); + num_to_info.push(Some(v_info)); + } + + // Step 1: Calculate the semi-dominators of all vertices + let mut eval_stack = SmallVec::<[&NodeInfo; 32]>::default(); + for i in (2..next_num).rev() { + let w_info = num_to_info[i].as_ref().unwrap(); + + // Initialize the semi-dominator to point to the parent node. + w_info.semi.set(w_info.parent()); + for n in w_info.reverse_children.iter().copied() { + let semi_u = num_to_info + [Self::eval(n, i as u32 + 1, &mut eval_stack, &num_to_info) as usize] + .as_ref() + .unwrap() + .semi + .get(); + if semi_u < w_info.semi.get() { + w_info.semi.set(semi_u); + } + } + } + + // Step 2: Explicitly define the immediate dominator of each vertex. + // + // IDom[i] = NCA(SDom[i], SpanningTreeParent(i)) + // + // Note that the parents were stored in IDoms and later got invalidated during path + // compression in `eval` + for i in 2..next_num { + let w_info = num_to_info[i].as_ref().unwrap(); + assert_ne!(w_info.semi.get(), 0); + let s_dom_num = num_to_info[w_info.semi.get() as usize].as_ref().unwrap().num.get(); + let mut w_idom_candidate = w_info.idom(); + loop { + let w_idom_candidate_info = self.node_info(w_idom_candidate); + if w_idom_candidate_info.num.get() <= s_dom_num { + break; + } + w_idom_candidate = w_idom_candidate_info.idom(); + } + + w_info.idom.set(w_idom_candidate); + } + } + + /// [PostDominatorTree] always has a virtual root that represents a virtual CFG node that serves + /// as a single exit from the region. + /// + /// All the other exits (CFG nodes with terminators and nodes in infinite loops) are logically + /// connected to this virtual CFG exit node. + /// + /// This function maps a null CFG node to the virtual root tree node. + fn add_virtual_root(&mut self) { + if const { IS_POST_DOM } { + assert_eq!(self.num_to_node.len(), 1, "SemiNCAInfo must be freshly constructed"); + + let info = self.node_info_mut(None); + info.num.set(1); + info.semi.set(1); + info.label.set(1); + + // num_to_node[1] = None + self.num_to_node.push(None); + } + } + + /// For postdominators, nodes with no forward successors are trivial roots that + /// are always selected as tree roots. Roots with forward successors correspond + /// to CFG nodes within infinite loops. + fn has_forward_successors( + n: Option, + bui: Option<&BatchUpdateInfo>, + ) -> bool { + let n = n.expect("`n` must be a valid node"); + !get_children_with_batch_updates::(n, bui).is_empty() + } + + fn entry_node(tree: &DomTreeBase) -> BlockRef { + tree.parent() + .borrow() + .entry_block_ref() + .expect("expected region to have an entry block") + } + + pub fn find_roots( + tree: &DomTreeBase, + bui: Option<&BatchUpdateInfo>, + ) -> DomTreeRoots { + let mut roots = DomTreeRoots::default(); + + // For dominators, region entry CFG node is always a tree root node. + if !IS_POST_DOM { + roots.push(Some(Self::entry_node(tree))); + return roots; + } + + let mut snca = Self::new(bui.cloned()); + + // PostDominatorTree always has a virtual root. + snca.add_virtual_root(); + let mut num = 1u32; + + log::trace!("looking for trivial roots"); + + // Step 1: Find all the trivial roots that are going to definitely remain tree roots + let mut total = 0; + // It may happen that there are some new nodes in the CFG that are result of the ongoing + // batch update, but we cannot really pretend that they don't exist -- we won't see any + // outgoing or incoming edges to them, so it's fine to discover them here, as they would end + // up appearing in the CFG at some point anyway. + let region = tree.parent().borrow(); + let mut region_body = region.body().front(); + while let Some(n) = region_body.as_pointer() { + region_body.move_next(); + total += 1; + // If it has no successors, it is definitely a root + if !Self::has_forward_successors(Some(n), bui) { + roots.push(Some(n)); + // Run DFS not to walk this part of CFG later. + num = snca.run_dfs::(Some(n), num, always_descend, 1, None); + log::trace!("found a new trivial root: {}", n.borrow().id()); + match snca.num_to_node.get(num as usize) { + None => log::trace!("last visited node: None"), + Some(None) => { + log::trace!("last visited virtual node") + } + Some(Some(last_visited)) => { + log::trace!("last visited node: {}", last_visited.borrow().id()) + } + } + } + } + + log::trace!("looking for non-trivial roots"); + + // Step 2: Find all non-trivial root candidates. + // + // Those are CFG nodes that are reverse-unreachable were not visited by previous DFS walks + // (i.e. CFG nodes in infinite loops). + // + // Accounting for the virtual exit, see if we had any reverse-unreachable nodes. + let has_non_trivial_roots = total + 1 != num; + if has_non_trivial_roots { + // `succ_order` is the order of blocks in the region. It is needed to make the + // calculation of the `furthest_away` node and the whole PostDominanceTree immune to + // swapping successors (e.g. canonicalizing branch predicates). `succ_order` is + // initialized lazily only for successors of reverse unreachable nodes. + #[derive(Default)] + struct LazySuccOrder { + succ_order: BTreeMap, + initialized: bool, + } + impl LazySuccOrder { + pub fn get_or_init<'a, 'b: 'a, const IS_POST_DOM: bool>( + &'b mut self, + region: &Region, + bui: Option<&'a BatchUpdateInfo>, + snca: &SemiNCA, + ) -> &'a BTreeMap { + if !self.initialized { + let mut region_body = region.body().front(); + while let Some(n) = region_body.as_pointer() { + region_body.move_next(); + let n_num = snca.node_info(Some(n)).num.get(); + if n_num == 0 { + for succ in + get_children_with_batch_updates::(n, bui) + { + self.succ_order.insert(succ, 0); + } + } + } + + // Add mapping for all entries of succ_order + let mut node_num = 0; + let mut region_body = region.body().front(); + while let Some(n) = region_body.as_pointer() { + region_body.move_next(); + node_num += 1; + if let Some(order) = self.succ_order.get_mut(&n) { + assert_eq!(*order, 0); + *order = node_num; + } + } + self.initialized = true; + } + + &self.succ_order + } + } + + let mut succ_order = LazySuccOrder::default(); + + // Make another DFS pass over all other nodes to find the reverse-unreachable blocks, + // and find the furthest paths we'll be able to make. + // + // Note that this looks N^2, but it's really 2N worst case, if every node is unreachable. + // This is because we are still going to only visit each unreachable node once, we may + // just visit it in two directions, depending on how lucky we get. + let mut region_body = region.body().front(); + while let Some(n) = region_body.as_pointer() { + region_body.move_next(); + + if snca.node_info(Some(n)).num.get() == 0 { + log::trace!("visiting node {n}"); + + // Find the furthest away we can get by following successors, then + // follow them in reverse. This gives us some reasonable answer about + // the post-dom tree inside any infinite loop. In particular, it + // guarantees we get to the farthest away point along *some* + // path. This also matches the GCC's behavior. + // If we really wanted a totally complete picture of dominance inside + // this infinite loop, we could do it with SCC-like algorithms to find + // the lowest and highest points in the infinite loop. In theory, it + // would be nice to give the canonical backedge for the loop, but it's + // expensive and does not always lead to a minimal set of roots. + log::trace!("running forward DFS.."); + + let succ_order = succ_order.get_or_init(®ion, bui, &snca); + let new_num = snca.run_dfs::( + Some(n), + num, + always_descend, + num, + Some(succ_order), + ); + let furthest_away = snca.num_to_node[new_num as usize]; + match furthest_away { + None => log::trace!( + "found a new furthest away node (non-trivial root): virtual node" + ), + Some(furthest_away) => { + log::trace!( + "found a new furthest away node (non-trivial root): \ + {furthest_away}" + ); + } + } + roots.push(furthest_away); + log::trace!("previous `num`: {num}, new `num` {new_num}"); + log::trace!("removing DFS info.."); + for i in ((num + 1)..=new_num).rev() { + let n = snca.num_to_node[i as usize]; + match n { + None => log::trace!("removing DFS info for virtual node"), + Some(n) => log::trace!("removing DFS info for {n}"), + } + *snca.node_info_mut(n) = Default::default(); + snca.num_to_node.pop(); + } + let prev_num = num; + log::trace!("running reverse depth-first search"); + num = snca.run_dfs::(furthest_away, num, always_descend, 1, None); + for i in (prev_num + 1)..num { + match snca.num_to_node[i as usize] { + None => log::trace!("found virtual node"), + Some(n) => log::trace!("found node {n}"), + } + } + } + } + } + + log::trace!("total: {total}, num: {num}"); + log::trace!("discovered cfg nodes:"); + for i in 0..num { + match &snca.num_to_node[i as usize] { + None => log::trace!(" {i}: virtual node"), + Some(n) => log::trace!(" {i}: {n}"), + } + } + + assert_eq!(total + 1, num, "everything should have been visited"); + + // Step 3: If we found some non-trivial roots, make them non-redundant. + if has_non_trivial_roots { + Self::remove_redundant_roots(snca.batch_updates.as_ref(), &mut roots); + } + + log::trace!( + "found roots: {}", + DisplayValues::new(roots.iter().map(|v| DisplayOptional(v.as_ref()))) + ); + + roots + } + + // This function only makes sense for postdominators. + // + // We define roots to be some set of CFG nodes where (reverse) DFS walks have to start in order + // to visit all the CFG nodes (including the reverse-unreachable ones). + // + // When the search for non-trivial roots is done it may happen that some of the non-trivial + // roots are reverse-reachable from other non-trivial roots, which makes them redundant. This + // function removes them from the set of input roots. + fn remove_redundant_roots( + bui: Option<&BatchUpdateInfo>, + roots: &mut SmallVec<[Option; 4]>, + ) { + assert!(IS_POST_DOM, "this function is for post-dominators only"); + + log::trace!("removing redundant roots.."); + + let mut snca = Self::new(bui.cloned()); + + let mut root_index = 0; + 'roots: while root_index < roots.len() { + let root = roots[root_index]; + + // Trivial roots are never redundant + if !Self::has_forward_successors(root, bui) { + continue; + } + + log::trace!("checking if {} remains a root", DisplayOptional(root.as_ref())); + snca.clear(); + + // Do a forward walk looking for the other roots. + let num = snca.run_dfs::(root, 0, always_descend, 0, None); + // Skip the start node and begin from the second one (note that DFS uses 1-based indexing) + for x in 2..(num as usize) { + let n = snca.num_to_node[x].unwrap(); + + // If we found another root in a (forward) DFS walk, remove the current root from + // the set of roots, as it is reverse-reachable from the other one. + if roots.iter().any(|r| r.as_ref().is_some_and(|root| root == &n)) { + log::trace!("forward DFS walk found another root {n}"); + log::trace!("removing root {}", DisplayOptional(root.as_ref())); + roots.swap_remove(root_index); + + // Root at the back takes the current root's place, so revisit the same index on + // the next iteration + continue 'roots; + } + } + + root_index += 1; + } + } + + pub fn do_full_dfs_walk(&mut self, tree: &DomTreeBase, condition: C) + where + for<'a> C: Copy + Fn(Option, Option) -> bool + 'a, + { + if const { !IS_POST_DOM } { + assert_eq!(tree.num_roots(), 1, "dominators should have a single root"); + self.run_dfs::(tree.roots()[0], 0, condition, 0, None); + return; + } + + self.add_virtual_root(); + let mut num = 1; + for root in tree.roots().iter().copied() { + num = self.run_dfs::(root, num, condition, 1, None); + } + } + + pub fn attach_new_subtree( + &mut self, + tree: &mut DomTreeBase, + attach_to: Rc, + ) { + // Attach the first unreachable block to `attach_to` + self.node_info(self.num_to_node[1]).idom.set(attach_to.block()); + // Loop over all of the discovered blocks in the function... + for w in self.num_to_node.iter().copied().skip(1) { + if tree.get(w).is_some() { + // Already computed the node before + continue; + } + + let idom = self.idom(w); + + // Get or compute the node for the immediate dominator + let idom_node = self.node_for_block(idom, tree); + + // Add a new tree node for this basic block, and link it as a child of idom_node + tree.create_node(w, idom_node); + } + } + + pub fn reattach_existing_subtree( + &mut self, + tree: &mut DomTreeBase, + attach_to: Rc, + ) { + self.node_info(self.num_to_node[1]).idom.set(attach_to.block()); + for n in self.num_to_node.iter().copied().skip(1) { + let node = tree.get(n).unwrap(); + let idom = tree.get(self.node_info(n).idom()).unwrap(); + node.set_idom(idom); + } + } + + // Checks if a node has proper support, as defined on the page 3 and later + // explained on the page 7 of [2]. + pub fn has_proper_support( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + node: &DomTreeNode, + ) -> bool { + log::trace!("is reachable from idom {node}"); + + let Some(block) = node.block() else { + return false; + }; + + let preds = if IS_POST_DOM { + get_children_with_batch_updates::(block, bui) + } else { + get_children_with_batch_updates::(block, bui) + }; + + for pred in preds { + log::trace!("pred {pred}"); + if tree.get(Some(pred)).is_none() { + continue; + } + + let support = tree.find_nearest_common_dominator(block, pred); + log::trace!("support {}", DisplayOptional(support.as_ref())); + if support != Some(block) { + log::trace!( + "{node} is reachable from support {}", + DisplayOptional(support.as_ref()) + ); + return true; + } + } + + false + } +} + +#[derive(Eq, PartialEq)] +struct InsertionInfoItem { + node: Rc, +} +impl From> for InsertionInfoItem { + fn from(node: Rc) -> Self { + Self { node } + } +} +impl PartialOrd for InsertionInfoItem { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for InsertionInfoItem { + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.node.level().cmp(&other.node.level()) + } +} + +#[derive(Default)] +struct InsertionInfo { + bucket: crate::adt::SmallPriorityQueue, + visited: crate::adt::SmallSet, 8>, + affected: SmallVec<[Rc; 8]>, +} + +/// Insertion and Deletion +impl SemiNCA { + pub fn insert_edge( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + from: Option, + to: Option, + ) { + assert!( + from.as_ref().is_some() || IS_POST_DOM, + "'from' has to be a valid cfg node or a virtual root" + ); + let to = to.expect("expected a valid `to` node"); + + log::trace!("inserting edge {from:?} -> {to}"); + + let from_node = tree.get(from); + let from_node = if let Some(from_node) = from_node { + from_node + } else { + // Ignore edges from unreachable nodes for (forward) dominators. + if !IS_POST_DOM { + return; + } + + // The unreachable node becomes a new root -- a tree node for it. + let virtual_root = tree.get(None); + let from_node = tree.create_node(from, virtual_root); + tree.roots_mut().push(from); + from_node + }; + + tree.mark_invalid(); + + let to_node = tree.get(Some(to)); + match to_node { + None => Self::insert_unreachable(tree, bui, from_node, to), + Some(to_node) => Self::insert_reachable(tree, bui, from_node, to_node), + } + } + + fn insert_unreachable( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + from: Rc, + to: BlockRef, + ) { + log::trace!("inserting {from} -> {to} (unreachable)"); + + // Collect discovered edges to already reachable nodes + // Discover and connect nodes that became reachable with the insertion. + let mut discovered_edges_to_reachable = SmallVec::default(); + Self::compute_unreachable_dominators( + tree, + bui, + to, + from.clone(), + &mut discovered_edges_to_reachable, + ); + + log::trace!("inserted {from} -> {to} (prev unreachable)"); + + // Use the discovered edges and insert discovered connecting (incoming) edges + for (from_block_ref, to_node) in discovered_edges_to_reachable { + log::trace!("inserting discovered connecting edge {from_block_ref:?} -> {to_node}",); + let from_node = tree.get(from_block_ref).unwrap(); + Self::insert_reachable(tree, bui, from_node, to_node); + } + } + + fn insert_reachable( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + from: Rc, + to: Rc, + ) { + log::trace!("reachable {from} -> {to}"); + + if const { IS_POST_DOM } { + let rebuilt = SemiNCA::::update_roots_before_insertion( + unsafe { + core::mem::transmute::<&mut DomTreeBase, &mut DomTreeBase>( + tree, + ) + }, + bui.map(|bui| unsafe { + core::mem::transmute::<&BatchUpdateInfo, &BatchUpdateInfo>( + bui, + ) + }), + to.clone(), + ); + if rebuilt { + return; + } + } + + // find_nearest_common_dominator expects both pointers to be valid. When `from` is a virtual + // root, then its CFG block pointer is `None`, so we have to "compute" the NCD manually + let ncd_block = if from.block().is_some() && to.block().is_some() { + tree.find_nearest_common_dominator(from.block().unwrap(), to.block().unwrap()) + } else { + None + }; + assert!(ncd_block.is_some() || tree.is_post_dominator()); + let ncd = tree.get(ncd_block).unwrap(); + + log::trace!("nearest common dominator == {ncd}"); + + // Based on Lemma 2.5 from [2], after insertion of (from, to), `v` is affected iff + // depth(ncd) + 1 < depth(v) && a path `P` from `to` to `v` exists where every `w` on `P` + // s.t. depth(v) <= depth(w) + // + // This reduces to a widest path problem (maximizing the depth of the minimum vertex in + // the path) which can be solved by a modified version of Dijkstra with a bucket queue + // (named depth-based search in [2]). + // + // `to` is in the path, so depth(ncd) + 1 < depth(v) <= depth(to). Nothing affected if + // this does not hold. + let ncd_level = ncd.level(); + if ncd_level + 1 >= to.level() { + return; + } + + let mut insertion_info = InsertionInfo::default(); + let mut unaffected_on_current_level = SmallVec::<[Rc; 8]>::default(); + insertion_info.bucket.push(to.clone().into()); + insertion_info.visited.insert(to); + + while let Some(InsertionInfoItem { mut node }) = insertion_info.bucket.pop() { + insertion_info.affected.push(node.clone()); + + let current_level = node.level(); + log::trace!("mark {node} as affected, current level: {current_level}"); + + assert!(node.block().is_some() && insertion_info.visited.contains(&node)); + + loop { + // Unlike regular Dijkstra, we have an inner loop to expand more + // vertices. The first iteration is for the (affected) vertex popped + // from II.Bucket and the rest are for vertices in + // UnaffectedOnCurrentLevel, which may eventually expand to affected + // vertices. + // + // Invariant: there is an optimal path from `To` to TN with the minimum + // depth being CurrentLevel. + for succ in get_children_with_batch_updates::( + node.block().unwrap(), + bui, + ) { + let succ_node = tree + .get(Some(succ)) + .expect("unreachable successor found during reachable insertion"); + let succ_level = succ_node.level(); + log::trace!("successor {succ_node}, level = {succ_level}"); + + // There is an optimal path from `To` to Succ with the minimum depth + // being min(CurrentLevel, SuccLevel). + // + // If depth(NCD)+1 < depth(Succ) is not satisfied, Succ is unaffected + // and no affected vertex may be reached by a path passing through it. + // Stop here. Also, Succ may be visited by other predecessors but the + // first visit has the optimal path. Stop if Succ has been visited. + if succ_level <= ncd_level + 1 + || !insertion_info.visited.insert(succ_node.clone()) + { + continue; + } + + if succ_level > current_level { + // succ is unaffected, but it may (transitively) expand to affected vertices. + // Store it in unaffected_on_current_level + log::trace!("marking visiting not affected {succ}"); + unaffected_on_current_level.push(succ_node.clone()); + } else { + // The condition is satisfied (Succ is affected). Add Succ to the + // bucket queue. + log::trace!("add {succ} to a bucket"); + insertion_info.bucket.push(succ_node.clone().into()); + } + } + + if unaffected_on_current_level.is_empty() { + break; + } + + if let Some(n) = unaffected_on_current_level.pop() { + node = n; + } else { + break; + } + log::trace!("next: {node}"); + } + } + + // Finish by updating immediate dominators and levels. + Self::update_insertion(tree, bui, ncd, &insertion_info); + } + + pub fn delete_edge( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + from: Option, + to: Option, + ) { + let from = from.expect("cannot disconnect virtual node"); + let to = to.expect("cannot disconnect virtual node"); + + log::trace!("deleting edge {from} -> {to}"); + + // Deletion in an unreachable subtree -- nothing to do. + let Some(from_node) = tree.get(Some(from)) else { + return; + }; + + let Some(to_node) = tree.get(Some(to)) else { + log::trace!("to {to} already unreachable -- there is no edge to delete",); + return; + }; + + let ncd_block = tree.find_nearest_common_dominator(from, to); + let ncd = tree.get(ncd_block); + + // If to dominates from -- nothing to do. + if Some(&to_node) != ncd.as_ref() { + tree.mark_invalid(); + + let to_idom = to_node.idom(); + log::trace!( + "ncd {}, to_idom {}", + DisplayOptional(ncd.as_ref()), + DisplayOptional(to_idom.as_ref()) + ); + + // To remains reachable after deletion (based on caption under figure 4, from [2]) + if (Some(&from_node) != to_idom.as_ref()) + || Self::has_proper_support(tree, bui, &to_node) + { + Self::delete_reachable(tree, bui, from_node, to_node) + } else { + Self::delete_unreachable(tree, bui, to_node) + } + + if const { IS_POST_DOM } { + SemiNCA::::update_roots_after_update( + unsafe { + core::mem::transmute::<&mut DomTreeBase, &mut DomTreeBase>( + tree, + ) + }, + bui.map(|bui| unsafe { + core::mem::transmute::<&BatchUpdateInfo, &BatchUpdateInfo>( + bui, + ) + }), + ); + } + } + } + + /// Handles deletions that leave destination nodes reachable. + fn delete_reachable( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + from: Rc, + to: Rc, + ) { + log::trace!("deleting reachable {from} -> {to} - rebuilding subtree.."); + + // Find the top of the subtree that needs to be rebuilt (based on the lemma 2.6 from [2]) + let to_idom = + tree.find_nearest_common_dominator(from.block().unwrap(), to.block().unwrap()); + assert!(to_idom.is_some() || tree.is_post_dominator()); + let to_idom_node = tree.get(to_idom).unwrap(); + let prev_idom_subtree = to_idom_node.idom(); + // Top of the subtree to rebuild is the root node. Rebuild the tree from scratch. + let Some(prev_idom_subtree) = prev_idom_subtree else { + log::trace!("the entire tree needs to be rebuilt"); + Self::compute_from_scratch(tree, bui.cloned()); + return; + }; + + // Only visit nodes in the subtree starting at `to` + let level = to_idom_node.level(); + let descend_below = |_: Option, to: Option| -> bool { + tree.get(to).unwrap().level() > level + }; + + log::trace!("top of subtree {to_idom_node}"); + + let mut snca = Self::new(bui.cloned()); + snca.run_dfs::(to_idom, 0, descend_below, 0, None); + log::trace!("running Semi-NCA"); + snca.run(); + snca.reattach_existing_subtree(tree, prev_idom_subtree); + } + + /// Handle deletions that make destination node unreachable. + /// + /// (Based on the lemma 2.7 from the [2].) + fn delete_unreachable( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + to: Rc, + ) { + log::trace!("deleting unreachable subtree {to}"); + assert!(to.block().is_some()); + + if IS_POST_DOM { + // Deletion makes a region reverse-unreachable and creates a new root. + // + // Simulate that by inserting an edge from the virtual root to `to` and adding it as a new + // root. + log::trace!("deletion made a region reverse-unreachable, adding new root {to}"); + tree.roots_mut().push(to.block()); + Self::insert_reachable(tree, bui, tree.get(None).unwrap(), to); + return; + } + + let mut affected_queue = SmallVec::<[Option; 16]>::default(); + let level = to.level(); + + // Traverse destination node's descendants with greater level in the tree + // and collect visited nodes. + let descend_and_collect = |_: Option, to: Option| -> bool { + let node = tree.get(to).unwrap(); + if node.level() > level { + return true; + } + if !affected_queue.contains(&to) { + affected_queue.push(to) + } + false + }; + + let mut snca = Self::new(bui.cloned()); + let last_dfs_num = snca.run_dfs::(to.block(), 0, descend_and_collect, 0, None); + + let mut min_node = to.clone(); + // Identify the top of the subtree to rebuild by finding the NCD of all the affected nodes. + for n in affected_queue { + let node = tree.get(n).unwrap(); + let ncd_block = + tree.find_nearest_common_dominator(node.block().unwrap(), to.block().unwrap()); + assert!(ncd_block.is_some() || tree.is_post_dominator()); + let ncd = tree.get(ncd_block).unwrap(); + log::trace!( + "processing affected node {node} with: nearest common dominator = {ncd}, min node \ + = {min_node}" + ); + if ncd != node && ncd.level() < min_node.level() { + min_node = ncd; + } + } + + // Root reached, rebuild the whole tree from scratch. + if min_node.idom().is_none() { + log::trace!("the entire tree needs to be rebuilt"); + Self::compute_from_scratch(tree, bui.cloned()); + return; + } + + // Erase the unreachable subtree in reverse preorder to process all children before deleting + // their parent. + for i in (1..=(last_dfs_num as usize)).rev() { + if let Some(n) = snca.num_to_node[i] { + log::trace!("erasing node {n}"); + tree.erase_node(n); + } + } + + // The affected subtree start at the `to` node -- there's no extra work to do. + if min_node == to { + return; + } + + log::trace!("delete_unreachable: running dfs with min_node = {min_node}"); + let min_level = min_node.level(); + let prev_idom = min_node.idom().unwrap(); + snca.clear(); + + // Identify nodes that remain in the affected subtree. + let descend_below = |_: Option, to: Option| -> bool { + let to_node = tree.get(to); + to_node.is_some_and(|to_node| to_node.level() > min_level) + }; + snca.run_dfs::(min_node.block(), 0, descend_below, 0, None); + + log::trace!("previous idom(min_node) = {prev_idom}"); + log::trace!("running Semi-NCA"); + + // Rebuild the remaining part of affected subtree. + snca.run(); + snca.reattach_existing_subtree(tree, prev_idom); + } + + pub fn apply_updates( + tree: &mut DomTreeBase, + mut pre_view_cfg: cfg::CfgDiff, + post_view_cfg: cfg::CfgDiff, + ) { + // Note: the `post_view_cfg` is only used when computing from scratch. It's data should + // already included in the `pre_view_cfg` for incremental updates. + let num_updates = pre_view_cfg.num_legalized_updates(); + match num_updates { + 0 => (), + 1 => { + // Take the fast path for a single update and avoid running the batch update machinery. + let update = pre_view_cfg.pop_update_for_incremental_updates(); + let bui = if post_view_cfg.is_empty() { + None + } else { + Some(BatchUpdateInfo::new(post_view_cfg.clone(), Some(post_view_cfg))) + }; + match update.kind() { + cfg::CfgUpdateKind::Insert => { + Self::insert_edge( + tree, + bui.as_ref(), + Some(update.from()), + Some(update.to()), + ); + } + cfg::CfgUpdateKind::Delete => { + Self::delete_edge( + tree, + bui.as_ref(), + Some(update.from()), + Some(update.to()), + ); + } + } + } + _ => { + let mut bui = BatchUpdateInfo::new(pre_view_cfg, Some(post_view_cfg)); + // Recalculate the DominatorTree when the number of updates exceeds a threshold, + // which usually makes direct updating slower than recalculation. We select this + // threshold proportional to the size of the DominatorTree. The constant is selected + // by choosing the one with an acceptable performance on some real-world inputs. + + // Make unittests of the incremental algorithm work + // TODO(pauls): review this + if tree.len() <= 100 { + if bui.num_legalized > tree.len() { + Self::compute_from_scratch(tree, Some(bui.clone())); + } + } else if bui.num_legalized > tree.len() / 40 { + Self::compute_from_scratch(tree, Some(bui.clone())); + } + + // If the DominatorTree was recalculated at some point, stop the batch updates. Full + // recalculations ignore batch updates and look at the actual CFG. + for _ in 0..bui.num_legalized { + if bui.is_recalculated { + break; + } + + Self::apply_next_update(tree, &mut bui); + } + } + } + } + + fn apply_next_update( + tree: &mut DomTreeBase, + bui: &mut BatchUpdateInfo, + ) { + // Popping the next update, will move the `pre_view_cfg` to the next snapshot. + let current_update = bui.pre_cfg_view.pop_update_for_incremental_updates(); + log::trace!("applying update: {current_update:?}"); + + match current_update.kind() { + cfg::CfgUpdateKind::Insert => { + Self::insert_edge( + tree, + Some(bui), + Some(current_update.from()), + Some(current_update.to()), + ); + } + cfg::CfgUpdateKind::Delete => { + Self::delete_edge( + tree, + Some(bui), + Some(current_update.from()), + Some(current_update.to()), + ); + } + } + } + + pub fn compute(tree: &mut DomTreeBase) { + Self::compute_from_scratch(tree, None); + } + + pub fn compute_from_scratch( + tree: &mut DomTreeBase, + mut bui: Option>, + ) { + use crate::cfg::GraphDiff; + + tree.reset(); + + // If the update is using the actual CFG, `bui` is `None`. If it's using a view, `bui` is + // `Some` and the `pre_cfg_view` is used. When calculating from scratch, make the + // `pre_cfg_view` equal to the `post_cfg_view`, so `post` is used. + let post_view_bui = bui.clone().and_then(|mut bui| { + if !bui.post_cfg_view.is_empty() { + bui.pre_cfg_view = bui.post_cfg_view.clone(); + Some(bui) + } else { + None + } + }); + + // This is rebuilding the whole tree, not incrementally, but `post_view_bui` is used in case + // the caller needs a dominator tree update with a cfg view + let mut snca = Self::new(post_view_bui); + + // Step 0: Number blocks in depth-first order, and initialize variables used in later stages + // of the algorithm. + let roots = Self::find_roots(tree, bui.as_ref()); + *tree.roots_mut() = roots; + snca.do_full_dfs_walk(tree, always_descend); + + snca.run(); + if let Some(bui) = bui.as_mut() { + bui.is_recalculated = true; + log::trace!("dominator tree recalculated, skipping future batch updates"); + } + + if tree.roots().is_empty() { + return; + } + + // Add a node for the root. If the tree is a post-dominator tree, it will be the virtual + // exit (denoted by a block ref of `None`), which post-dominates all real exits (including + // multiple exit blocks, infinite loops). + let root = if IS_POST_DOM { None } else { tree.roots()[0] }; + + let new_root = tree.create_node(root, None); + tree.set_root(new_root); + let root_node = tree.root_node().expect("expected root node"); + snca.attach_new_subtree(tree, root_node); + } + + fn update_insertion( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + ncd: Rc, + insertion_info: &InsertionInfo, + ) { + log::trace!("updating nearest common dominator = {ncd}"); + + for to_node in insertion_info.affected.iter().cloned() { + log::trace!("idom({to_node}) = {ncd}"); + to_node.set_idom(ncd.clone()); + } + + if IS_POST_DOM { + SemiNCA::::update_roots_after_update( + unsafe { + core::mem::transmute::<&mut DomTreeBase, &mut DomTreeBase>( + tree, + ) + }, + bui.map(|bui| unsafe { + core::mem::transmute::<&BatchUpdateInfo, &BatchUpdateInfo>( + bui, + ) + }), + ); + } + } + + /// Connects nodes that become reachable with an insertion + fn compute_unreachable_dominators( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + root: BlockRef, + incoming: Rc, + discovered_connecting_edges: &mut SmallVec<[(Option, Rc); 8]>, + ) { + assert!(tree.get(Some(root)).is_none(), "root must not be reachable"); + + // Visit only previously unreachable nodes + let unreachable_descender = |from: Option, to: Option| -> bool { + let to_node = tree.get(to); + match to_node { + None => true, + Some(to_node) => { + discovered_connecting_edges.push((from, to_node)); + false + } + } + }; + + let mut snca = Self::new(bui.cloned()); + snca.run_dfs::(Some(root), 0, unreachable_descender, 0, None); + snca.run(); + snca.attach_new_subtree(tree, incoming); + + log::trace!("after adding unreachable nodes"); + } +} + +/// Verification +impl SemiNCA { + pub fn verify_roots(&self, _tree: &DomTreeBase) -> bool { + true + } + + pub fn verify_reachability(&self, _tree: &DomTreeBase) -> bool { + true + } + + pub fn verify_levels(&self, _tree: &DomTreeBase) -> bool { + true + } + + pub fn verify_dfs_numbers(&self, _tree: &DomTreeBase) -> bool { + true + } + + pub fn verify_parent_property(&self, _tree: &DomTreeBase) -> bool { + true + } + + pub fn verify_sibling_property(&self, _tree: &DomTreeBase) -> bool { + true + } +} + +impl SemiNCA { + /// Determines if some existing root becomes reverse-reachable after the insertion. + /// + /// Rebuilds the whole tree if that situation happens. + fn update_roots_before_insertion( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + to: Rc, + ) -> bool { + // Destination node is not attached to the virtual root, so it cannot be a root + if !tree.is_virtual_root(&to.idom().unwrap()) { + return false; + } + + if !tree.roots().contains(&to.block()) { + // To is not a root, nothing to update + return false; + } + + log::trace!("after the insertion, {to} is no longer a root - rebuilding the tree.."); + + Self::compute_from_scratch(tree, bui.cloned()); + true + } + + /// Updates the set of roots after insertion or deletion. + /// + /// This ensures that roots are the same when after a series of updates and when the tree would + /// be built from scratch. + fn update_roots_after_update( + tree: &mut DomTreeBase, + bui: Option<&BatchUpdateInfo>, + ) { + // The tree has only trivial roots -- nothing to update. + if !tree.roots().iter().copied().any(|n| Self::has_forward_successors(n, bui)) { + return; + } + + // Recalculate the set of roots + let roots = Self::find_roots(tree, bui); + if !is_permutation(tree.roots(), &roots) { + // The roots chosen in the CFG have changed. This is because the incremental algorithm + // does not really know or use the set of roots and can make a different (implicit) + // decision about which node within an infinite loop becomes a root. + log::trace!( + "roots are different in updated trees - the entire tree needs to be rebuilt" + ); + // It may be possible to update the tree without recalculating it, but we do not know + // yet how to do it, and it happens rarely in practice. + Self::compute_from_scratch(tree, bui.cloned()); + } + } +} + +fn is_permutation(a: &[Option], b: &[Option]) -> bool { + if a.len() != b.len() { + return false; + } + let set = crate::adt::SmallSet::<_, 4>::from_iter(a.iter().cloned()); + for n in b { + if !set.contains(n) { + return false; + } + } + true +} + +#[doc(hidden)] +#[inline(always)] +const fn always_descend(_: Option, _: Option) -> bool { + true +} diff --git a/hir/src/ir/dominance/traits.rs b/hir/src/ir/dominance/traits.rs new file mode 100644 index 000000000..e7acadb7e --- /dev/null +++ b/hir/src/ir/dominance/traits.rs @@ -0,0 +1,231 @@ +use super::{DominanceInfo, PostDominanceInfo}; +use crate::{Block, BlockRef, Operation, Value}; + +/// This trait is implemented on a type which has a dominance relationship with `Rhs`. +pub trait Dominates { + /// Returns true if `self` dominates `other`. + /// + /// In cases where `Rhs = Self`, implementations should return true when `self == other`. + /// + /// For a stricter form of dominance, use [Dominates::properly_dominates]. + fn dominates(&self, other: &Rhs, dom_info: &DominanceInfo) -> bool; + /// Returns true if `self` properly dominates `other`. + /// + /// In cases where `Rhs = Self`, implementations should return false when `self == other`. + fn properly_dominates(&self, other: &Rhs, dom_info: &DominanceInfo) -> bool; +} + +/// This trait is implemented on a type which has a post-dominance relationship with `Rhs`. +pub trait PostDominates { + /// Returns true if `self` post-dominates `other`. + /// + /// In cases where `Rhs = Self`, implementations should return true when `self == other`. + /// + /// For a stricter form of dominance, use [PostDominates::properly_dominates]. + fn post_dominates(&self, other: &Rhs, dom_info: &PostDominanceInfo) -> bool; + /// Returns true if `self` properly post-dominates `other`. + /// + /// In cases where `Rhs = Self`, implementations should return false when `self == other`. + fn properly_post_dominates(&self, other: &Rhs, dom_info: &PostDominanceInfo) -> bool; +} + +/// The dominance relationship between two blocks. +impl Dominates for Block { + /// Returns true if `a == b` or `a` properly dominates `b`. + fn dominates(&self, other: &Self, dom_info: &DominanceInfo) -> bool { + core::ptr::addr_eq(self, other) || self.properly_dominates(other, dom_info) + } + + /// Returns true if `a != b` and: + /// + /// * `a` is an ancestor of `b` + /// * The region containing `a` also contains `b` or some ancestor of `b`, and `a` dominates + /// that block in that kind of region. + /// * In SSA regions, `a` properly dominates `b` if all control flow paths from the entry + /// block to `b`, flow through `a`. + /// * In graph regions, all blocks dominate all other blocks. + fn properly_dominates(&self, other: &Self, dom_info: &DominanceInfo) -> bool { + dom_info.info().properly_dominates(self.as_block_ref(), other.as_block_ref()) + } +} + +/// The dominance relationship between two blocks. +impl Dominates for BlockRef { + /// Returns true if `a == b` or `a` properly dominates `b`. + fn dominates(&self, other: &Self, dom_info: &DominanceInfo) -> bool { + BlockRef::ptr_eq(self, other) || self.properly_dominates(other, dom_info) + } + + /// Returns true if `a != b` and: + /// + /// * `a` is an ancestor of `b` + /// * The region containing `a` also contains `b` or some ancestor of `b`, and `a` dominates + /// that block in that kind of region. + /// * In SSA regions, `a` properly dominates `b` if all control flow paths from the entry + /// block to `b`, flow through `a`. + /// * In graph regions, all blocks dominate all other blocks. + fn properly_dominates(&self, other: &Self, dom_info: &DominanceInfo) -> bool { + dom_info.info().properly_dominates(*self, *other) + } +} + +/// The post-dominance relationship between two blocks. +impl PostDominates for Block { + fn post_dominates(&self, other: &Self, dom_info: &PostDominanceInfo) -> bool { + core::ptr::addr_eq(self, other) || self.properly_post_dominates(other, dom_info) + } + + /// Returns true if `a != b` and: + /// + /// * `a` is an ancestor of `b` + /// * The region containing `a` also contains `b` or some ancestor of `b`, and `a` dominates + /// that block in that kind of region. + /// * In SSA regions, `a` properly post-dominates `b` if all control flow paths from `b` to + /// an exit node, flow through `a`. + /// * In graph regions, all blocks post-dominate all other blocks. + fn properly_post_dominates(&self, other: &Self, dom_info: &PostDominanceInfo) -> bool { + dom_info.info().properly_dominates(self.as_block_ref(), other.as_block_ref()) + } +} + +/// The post-dominance relationship between two blocks. +impl PostDominates for BlockRef { + fn post_dominates(&self, other: &Self, dom_info: &PostDominanceInfo) -> bool { + BlockRef::ptr_eq(self, other) || self.properly_post_dominates(other, dom_info) + } + + /// Returns true if `a != b` and: + /// + /// * `a` is an ancestor of `b` + /// * The region containing `a` also contains `b` or some ancestor of `b`, and `a` dominates + /// that block in that kind of region. + /// * In SSA regions, `a` properly post-dominates `b` if all control flow paths from `b` to + /// an exit node, flow through `a`. + /// * In graph regions, all blocks post-dominate all other blocks. + fn properly_post_dominates(&self, other: &Self, dom_info: &PostDominanceInfo) -> bool { + dom_info.info().properly_dominates(*self, *other) + } +} + +/// The dominance relationship for operations +impl Dominates for Operation { + fn dominates(&self, other: &Self, dom_info: &DominanceInfo) -> bool { + core::ptr::addr_eq(self, other) || self.properly_dominates(other, dom_info) + } + + /// Returns true if `a != b`, and: + /// + /// * `a` and `b` are in the same block, and `a` properly dominates `b` within the block, or + /// * the block that contains `a` properly dominates the block that contains `b`. + /// * `b` is enclosed in a region of `a` + /// + /// In any SSA region, `a` dominates `b` in the same block if `a` precedes `b`. In a graph + /// region all operations in a block dominate all other operations in the same block. + fn properly_dominates(&self, other: &Self, dom_info: &DominanceInfo) -> bool { + let a = self.as_operation_ref(); + let b = other.as_operation_ref(); + dom_info.properly_dominates_with_options(a, b, /*enclosing_op_ok= */ true) + } +} + +/// The post-dominance relationship for operations +impl PostDominates for Operation { + fn post_dominates(&self, other: &Self, dom_info: &PostDominanceInfo) -> bool { + core::ptr::addr_eq(self, other) || self.properly_post_dominates(other, dom_info) + } + + /// Returns true if `a != b`, and: + /// + /// * `a` and `b` are in the same block, and `a` properly post-dominates `b` within the block + /// * the block that contains `a` properly post-dominates the block that contains `b`. + /// * `b` is enclosed in a region of `a` + /// + /// In any SSA region, `a` post-dominates `b` in the same block if `b` precedes `a`. In a graph + /// region all operations in a block post-dominate all other operations in the same block. + fn properly_post_dominates(&self, other: &Self, dom_info: &PostDominanceInfo) -> bool { + let a_block = self.parent().expect("`self` must be in a block"); + let mut b_block = other.parent().expect("`other` must be in a block"); + + // An instruction post dominates, but does not properly post-dominate itself unless this is + // a graph region. + if core::ptr::addr_eq(self, other) { + return !a_block.borrow().has_ssa_dominance(); + } + + // If these ops are in different regions, then normalize one into the other. + let a_region = a_block.parent(); + let b_region = b_block.parent(); + let a = self.as_operation_ref(); + let mut b = other.as_operation_ref(); + if a_region != b_region { + // Walk up `b`'s region tree until we find an operation in `a`'s region that encloses + // it. If this fails, then we know there is no post-dominance relation. + let Some(found) = a_region.as_ref().and_then(|r| r.borrow().find_ancestor_op(b)) else { + return false; + }; + b = found; + b_block = b.parent().unwrap(); + assert!(b_block.parent() == a_region); + + // If `a` encloses `b`, then we consider it to post-dominate. + if a == b { + return true; + } + } + + // Ok, they are in the same region. If they are in the same block, check if `b` is before + // `a` in the block. + if a_block == b_block { + // Dominance changes based on the region type + return if a_block.borrow().has_ssa_dominance() { + // If the blocks are the same, then check if `b` is before `a` in the block. + b.borrow().is_before_in_block(&a) + } else { + true + }; + } + + // If the blocks are different, check if `a`'s block post-dominates `b`'s + dom_info + .info() + .dominance(a_region.unwrap()) + .properly_dominates(Some(a_block), Some(b_block)) + } +} + +/// The dominance relationship between a value and an operation, e.g. between a definition of a +/// value and a user of that same value. +impl Dominates for dyn Value { + /// Return true if the definition of `self` dominates a use by operation `other`. + fn dominates(&self, other: &Operation, dom_info: &DominanceInfo) -> bool { + self.get_defining_op().is_some_and(|op| op == other.as_operation_ref()) + || self.properly_dominates(other, dom_info) + } + + /// Returns true if the definition of `self` properly dominates `other`. + /// + /// This requires the value to either be a block argument, where the block containing `other` + /// is dominated by the block defining `self`, OR that the value is an operation result, and + /// the defining op of `self` properly dominates `other`. + /// + /// If the defining op of `self` encloses `b` in one of its regions, `a` does not dominate `b`. + fn properly_dominates(&self, other: &Operation, dom_info: &DominanceInfo) -> bool { + // Block arguments properly dominate all operations in their own block, so we use a + // dominates check here, not a properly_dominates check. + if let Some(block_arg) = self.downcast_ref::() { + return block_arg + .owner() + .borrow() + .dominates(&other.parent().unwrap().borrow(), dom_info); + } + + // `a` properly dominates `b` if the operation defining `a` properly dominates `b`, but `a` + // does not itself enclose `b` in one of its regions. + let defining_op = self.get_defining_op().unwrap(); + dom_info.properly_dominates_with_options( + defining_op, + other.as_operation_ref(), + /*enclosing_op_ok= */ false, + ) + } +} diff --git a/hir/src/ir/dominance/tree.rs b/hir/src/ir/dominance/tree.rs new file mode 100644 index 000000000..bc9b1292d --- /dev/null +++ b/hir/src/ir/dominance/tree.rs @@ -0,0 +1,1030 @@ +use alloc::{rc::Rc, vec::Vec}; +use core::{ + cell::{Cell, RefCell}, + fmt, + num::NonZeroU32, +}; + +use smallvec::{smallvec, SmallVec}; + +use super::{BatchUpdateInfo, SemiNCA}; +use crate::{ + cfg::{self, Graph, Inverse, InvertibleGraph}, + formatter::DisplayOptional, + BlockRef, EntityId, EntityWithId, RegionRef, +}; + +#[derive(Debug, thiserror::Error)] +pub enum DomTreeError { + /// Tried to compute a dominator tree for an empty region + #[error("unable to create dominance tree for empty region")] + EmptyRegion, +} + +/// The level of verification to use with [DominatorTreeBase::verify] +pub enum DomTreeVerificationLevel { + /// Checks basic tree structure and compares with a freshly constructed tree + /// + /// O(n^2) time worst case, but is faster in practice. + Fast, + /// Checks if the tree is correct, but compares it to a freshly constructed tree instead of + /// checking the sibling property. + /// + /// O(n^2) time. + Basic, + /// Verifies if the tree is correct by making sure all the properties, including the parent + /// and sibling property, hold. + /// + /// O(n^3) time. + Full, +} + +/// A forward dominance tree +pub type DominanceTree = DomTreeBase; + +/// A post (backward) dominance tree +pub type PostDominanceTree = DomTreeBase; + +pub type DomTreeRoots = SmallVec<[Option; 4]>; + +/// A dominator tree implementation that abstracts over the type of dominance it represents. +pub struct DomTreeBase { + /// The roots from which dominance is traced. + /// + /// For forward dominance trees, there is always a single root. For post-dominance trees, there + /// may be multiple, one for each exit from the region. + roots: DomTreeRoots, + /// The nodes represented in this dominance tree + #[allow(clippy::type_complexity)] + nodes: SmallVec<[Option<(Option, Rc)>; 64]>, + /// The root dominance tree node. + root: Option>, + /// The parent region for which this dominance tree was computed + parent: RegionRef, + /// Whether this dominance tree is valid (true), or outdated (false) + valid: Cell, + /// A counter for expensive queries that may cause us to perform some extra work in order to + /// speed up those queries after a certain point. + slow_queries: Cell, +} + +/// A node in a [DomTreeBase]. +pub struct DomTreeNode { + /// The block represented by this node + block: Option, + /// The immediate dominator of this node, if applicable + idom: Cell>>, + /// The children of this node in the tree + children: RefCell; 4]>>, + /// The depth of this node in the tree + level: Cell, + /// The DFS visitation order (forward) + num_in: Cell>, + /// The DFS visitation order (backward) + num_out: Cell>, +} + +impl fmt::Display for DomTreeNode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", DisplayOptional(self.block.as_ref().map(|b| b.borrow().id()).as_ref())) + } +} + +impl fmt::Debug for DomTreeNode { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use crate::EntityWithId; + + f.debug_struct("DomTreeNode") + .field_with("block", |f| match self.block.as_ref() { + None => f.write_str("None"), + Some(block_ref) => write!(f, "{}", block_ref.borrow().id()), + }) + .field("idom", &unsafe { &*self.idom.as_ptr() }.as_ref().map(|n| n.block)) + .field_with("children", |f| { + f.debug_list() + .entries(self.children.borrow().iter().map(|child| child.block)) + .finish() + }) + .field("level", &self.level.get()) + .field("num_in", &self.num_in.get()) + .field("num_out", &self.num_out.get()) + .finish() + } +} + +impl fmt::Debug for DomTreeBase { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = f.debug_struct("DomTreeBase"); + builder + .field("valid", &self.valid.get()) + .field("slow_queries", &self.slow_queries.get()) + .field_with("root", |f| match self.root.as_ref().and_then(|root| root.block) { + Some(root) => write!(f, "{root}"), + None => f.write_str(""), + }); + if IS_POST_DOM { + builder.field_with("roots", |f| { + let mut builder = f.debug_set(); + for root in self.roots.iter() { + builder.entry_with(|f| match root { + Some(root) => write!(f, "{root}"), + None => f.write_str(""), + }); + } + builder.finish() + }); + } + + builder.field_with("nodes", |f| { + f.debug_set() + .entries(self.nodes.iter().filter_map(|node| node.as_ref().map(|(_, n)| n.clone()))) + .finish() + }); + + builder.finish() + } +} + +/// An iterator over nodes in a dominance tree produced by a depth-first, pre-order traversal +pub type PreOrderDomTreeIter = cfg::PreOrderIter>; +/// An iterator over nodes in a dominance tree produced by a depth-first, post-order traversal +pub type PostOrderDomTreeIter = cfg::PostOrderIter>; + +impl Graph for Rc { + type ChildEdgeIter = DomTreeSuccessorIter; + type ChildIter = DomTreeSuccessorIter; + type Edge = Rc; + type Node = Rc; + + fn size(&self) -> usize { + self.children.borrow().len() + } + + fn children(parent: Self::Node) -> Self::ChildIter { + DomTreeSuccessorIter::new(parent) + } + + fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter { + DomTreeSuccessorIter::new(parent) + } + + fn edge_dest(edge: Self::Edge) -> Self::Node { + // The edge is the child node + edge + } + + fn entry_node(&self) -> Self::Node { + Rc::clone(self) + } +} + +pub struct DomTreeSuccessorIter { + node: Rc, + num_children: usize, + index: usize, +} +impl DomTreeSuccessorIter { + pub fn new(node: Rc) -> Self { + let num_children = node.num_children(); + Self { + node, + num_children, + index: 0, + } + } +} +impl core::iter::FusedIterator for DomTreeSuccessorIter {} +impl ExactSizeIterator for DomTreeSuccessorIter { + #[inline] + fn len(&self) -> usize { + self.num_children.saturating_sub(self.index) + } + + #[inline] + fn is_empty(&self) -> bool { + self.index >= self.num_children + } +} +impl Iterator for DomTreeSuccessorIter { + type Item = Rc; + + fn next(&mut self) -> Option { + if self.index >= self.num_children { + return None; + } + let index = self.index; + self.index += 1; + Some(self.node.children.borrow()[index].clone()) + } +} +impl DoubleEndedIterator for DomTreeSuccessorIter { + fn next_back(&mut self) -> Option { + if self.num_children == 0 { + return None; + } + let index = self.num_children; + self.num_children -= 1; + Some(self.node.children.borrow()[index].clone()) + } +} + +impl DomTreeNode { + /// Create a new node for `block`, with the specified immediate dominator. + /// + /// If `block` is `None`, this must be a node in a post-dominator tree, and the resulting node + /// is a virtual node that post-dominates all nodes in the tree + pub fn new(block: Option, idom: Option>) -> Self { + let this = Self { + block, + idom: Cell::new(None), + children: Default::default(), + level: Cell::new(0), + num_in: Cell::new(None), + num_out: Cell::new(None), + }; + if let Some(idom) = idom { + this.with_idom(idom) + } else { + this + } + } + + /// Build this node with the specified immediate dominator. + pub fn with_idom(self, idom: Rc) -> Self { + self.level.set(idom.level.get() + 1); + self.idom.set(Some(idom)); + self + } + + pub fn block(&self) -> Option { + self.block + } + + pub fn idom(&self) -> Option> { + unsafe { &*self.idom.as_ptr() }.clone() + } + + pub(super) fn set_idom(self: Rc, new_idom: Rc) { + let idom = self.idom.take().expect("no immediate dominator?"); + if idom == new_idom { + self.idom.set(Some(idom)); + return; + } + + { + let mut children = idom.children.borrow_mut(); + let child_index = children + .iter() + .position(|n| Rc::ptr_eq(n, &self)) + .expect("not in immediate dominator children!"); + children.remove(child_index); + } + + { + let mut children = new_idom.children.borrow_mut(); + children.push(Rc::clone(&self)); + } + self.idom.set(Some(new_idom)); + + self.update_level(); + } + + #[inline(always)] + pub fn level(&self) -> u32 { + self.level.get() + } + + pub fn is_leaf(&self) -> bool { + self.children.borrow().is_empty() + } + + pub fn num_children(&self) -> usize { + self.children.borrow().len() + } + + pub fn add_child(&self, child: Rc) { + self.children.borrow_mut().push(child); + } + + pub fn clear_children(&self) { + self.children.borrow_mut().clear(); + } + + /// Returns true if `self` is dominated by `other` in the tree. + pub fn is_dominated_by(&self, other: &Self) -> bool { + let num_in = self.num_in.get().expect("you forgot to call update_dfs_numbers").get(); + let other_num_in = other.num_in.get().expect("you forgot to call update_dfs_numbers").get(); + let num_out = self.num_out.get().unwrap().get(); + let other_num_out = other.num_out.get().unwrap().get(); + num_in >= other_num_in && num_out <= other_num_out + } + + /// Recomputes this node's depth in the dominator tree + fn update_level(self: Rc) { + let idom_level = self.idom().expect("expected to have an immediate dominator").level(); + if self.level() == idom_level + 1 { + return; + } + + let mut stack = SmallVec::<[Rc; 64]>::from_iter([self.clone()]); + while let Some(current) = stack.pop() { + current.level.set(current.idom().unwrap().level() + 1); + for child in current.children.borrow().iter() { + assert!(child.idom().is_some()); + if child.level() != child.idom().unwrap().level() + 1 { + stack.push(Rc::clone(child)); + } + } + } + } +} + +impl Eq for DomTreeNode {} +impl PartialEq for DomTreeNode { + fn eq(&self, other: &Self) -> bool { + self.block == other.block + } +} + +impl DomTreeBase { + #[inline] + pub fn root(&self) -> BlockRef { + self.roots[0].unwrap() + } + + /// Get all the nodes of this tree as a vector in pre-order visitation order + pub fn preorder(&self) -> Vec> { + let mut nodes = self + .nodes + .iter() + .filter_map(|entry| match entry { + Some((Some(_), node)) => Some(node.clone()), + _ => None, + }) + .collect::>(); + nodes.sort_by(|a, b| a.num_in.get().cmp(&b.num_in.get())); + nodes + } + + /// Get all the nodes of this tree as a vector in post-order visitation order + pub fn postorder(&self) -> Vec> { + let mut nodes = self + .nodes + .iter() + .filter_map(|entry| match entry { + Some((Some(_), node)) => Some(node.clone()), + _ => None, + }) + .collect::>(); + nodes.sort_by(|a, b| a.num_out.get().cmp(&b.num_out.get())); + nodes + } + + /// Get all the nodes of this tree as a vector in reverse post-order visitation order + /// + /// This differs from `preorder` in that it is the exact inverse of `postorder`. + /// Where `preorder` represents the order in which each node is first seen when traversing the + /// CFG from the entry point, `postorder` is the order in which nodes are visited, once all their + /// children are visited. Thus, a reverse post-order traversal is equivalent to a preorder + /// traversal of the dominance tree, where `preorder` corresponds to a traversal of the CFG. + pub fn reverse_postorder(&self) -> Vec> { + let mut nodes = self + .nodes + .iter() + .filter_map(|entry| match entry { + Some((Some(_), node)) => Some(node.clone()), + _ => None, + }) + .collect::>(); + nodes.sort_by(|a, b| a.num_out.get().cmp(&b.num_out.get()).reverse()); + nodes + } +} + +impl DomTreeBase { + /// Compute a dominator tree for `region` + pub fn new(region: RegionRef) -> Result { + let entry = region.borrow().entry_block_ref().ok_or(DomTreeError::EmptyRegion)?; + let root = Rc::new(DomTreeNode::new(Some(entry), None)); + let root_id = entry.borrow().id().as_usize() + 1; + let mut nodes = SmallVec::default(); + nodes.resize(root_id + 2, None); + nodes[root_id] = Some((Some(entry), root.clone())); + let roots = smallvec![Some(entry)]; + + let mut this = Self { + parent: region, + root: Some(root), + roots, + nodes, + valid: Cell::new(false), + slow_queries: Cell::new(0), + }; + + this.compute(); + + Ok(this) + } + + #[inline] + pub fn parent(&self) -> RegionRef { + self.parent + } + + pub fn len(&self) -> usize { + self.nodes.iter().filter(|entry| entry.is_some()).count() + } + + pub fn is_empty(&self) -> bool { + self.nodes.is_empty() || self.nodes.iter().all(|entry| entry.is_none()) + } + + #[inline] + pub fn num_roots(&self) -> usize { + self.roots.len() + } + + #[inline] + pub fn roots(&self) -> &[Option] { + &self.roots + } + + #[inline] + pub fn roots_mut(&mut self) -> &mut DomTreeRoots { + &mut self.roots + } + + pub(super) fn set_root(&mut self, root: Rc) { + self.root = Some(root); + } + + /// Returns true if this tree is a post-dominance tree. + #[inline(always)] + pub const fn is_post_dominator(&self) -> bool { + IS_POST_DOM + } + + pub(super) fn mark_invalid(&self) { + self.valid.set(false); + } + + /// Get the node for `block`, if one exists in the tree. + /// + /// Use `None` to get the virtual node, if this is a post-dominator tree + pub fn get(&self, block: Option) -> Option> { + let index = self.node_index(block); + self.nodes.get(index).and_then(|entry| match entry { + Some((_, node)) => Some(node.clone()), + _ => None, + }) + } + + #[inline] + fn node_index(&self, block: Option) -> usize { + assert!( + block.is_none_or(|block| block.parent().is_some_and(|parent| parent == self.parent)), + "cannot get dominance info of block with different parent" + ); + if let Some(block) = block { + block.borrow().id().as_usize() + 1 + } else { + // Reserve index 0 for None + 0 + } + } + + /// Returns the entry node for the CFG of the region. + /// + /// However, if this tree represents the post-dominance relations for a region, this root may be + /// a node with `block` set to `None`. This is the case when there are multiple exit nodes from + /// a particular function. Consumers of post-dominance information must be capable of dealing + /// with this possibility. + pub fn root_node(&self) -> Option> { + self.root.clone() + } + + /// Get all nodes dominated by `r`, including `r` itself + pub fn get_descendants(&self, r: BlockRef) -> SmallVec<[BlockRef; 2]> { + let mut results = SmallVec::default(); + let Some(rn) = self.get(Some(r)) else { + return results; + }; + let mut worklist = SmallVec::<[Rc; 8]>::default(); + worklist.push(rn); + + while let Some(n) = worklist.pop() { + let Some(n_block) = n.block() else { + continue; + }; + results.push(n_block); + worklist.extend(n.children.borrow().iter().cloned()); + } + + results + } + + /// Return true if `a` is dominated by the entry block of the region containing it. + pub fn is_reachable_from_entry(&self, a: BlockRef) -> bool { + assert!(!self.is_post_dominator(), "unimplemented for post dominator trees"); + + self.get(Some(a)).is_some() + } + + #[inline] + pub const fn is_reachable_from_entry_node(&self, a: Option<&Rc>) -> bool { + a.is_some() + } + + /// Returns true if and only if `a` dominates `b` and `a != b` + /// + /// Note that this is not a constant time operation. + pub fn properly_dominates(&self, a: Option, b: Option) -> bool { + if a == b { + return false; + } + let a = self.get(a); + let b = self.get(b); + if a.is_none() || b.is_none() { + return false; + } + self.properly_dominates_node(a, b) + } + + /// Returns true if and only if `a` dominates `b` and `a != b` + /// + /// Note that this is not a constant time operation. + pub fn properly_dominates_node( + &self, + a: Option>, + b: Option>, + ) -> bool { + a != b && self.dominates_node(a, b) + } + + /// Returns true iff `a` dominates `b`. + /// + /// Note that this is not a constant time operation + pub fn dominates(&self, a: Option, b: Option) -> bool { + if a == b { + return true; + } + let a = self.get(a); + let b = self.get(b); + self.dominates_node(a, b) + } + + /// Returns true iff `a` dominates `b`. + /// + /// Note that this is not a constant time operation + pub fn dominates_node(&self, a: Option>, b: Option>) -> bool { + // A trivially dominates itself + if a == b { + return true; + } + + // An unreachable node is dominated by anything + if b.is_none() { + return true; + } + + // And dominates nothing. + if a.is_none() { + return false; + } + + let a = a.unwrap(); + let b = b.unwrap(); + + if b.idom().is_some_and(|idom| idom == a) { + return true; + } + + if a.idom().is_some_and(|idom| idom == b) { + return false; + } + + // A can only dominate B if it is higher in the tree + if a.level() >= b.level() { + return false; + } + + if self.valid.get() { + return b.is_dominated_by(&a); + } + + // If we end up with too many slow queries, just update the DFS numbers on the assumption + // that we are going to keep querying + self.slow_queries.set(self.slow_queries.get() + 1); + if self.slow_queries.get() > 32 { + self.update_dfs_numbers(); + return b.is_dominated_by(&a); + } + + self.dominated_by_slow_tree_walk(a, b) + } + + /// Finds the nearest block which is a common dominator of both `a` and `b` + pub fn find_nearest_common_dominator(&self, a: BlockRef, b: BlockRef) -> Option { + assert!(a.parent() == b.parent(), "two blocks are not in same region"); + + // If either A or B is an entry block then it is nearest common dominator (for forward + // dominators). + if !self.is_post_dominator() { + let parent = a.parent().unwrap(); + let entry = parent.borrow().entry_block_ref().unwrap(); + if a == entry || b == entry { + return Some(entry); + } + } + + let mut a = self.get(Some(a)).expect("'a' must be in the tree"); + let mut b = self.get(Some(b)).expect("'b' must be in the tree"); + + // Use level information to go up the tree until the levels match. Then continue going up + // until we arrive at the same node. + while a != b { + if a.level() < b.level() { + core::mem::swap(&mut a, &mut b); + } + + a = a.idom().unwrap(); + } + + a.block() + } +} + +impl DomTreeBase { + pub fn insert_edge(&mut self, mut from: Option, mut to: Option) { + if self.is_post_dominator() { + core::mem::swap(&mut from, &mut to); + } + SemiNCA::::insert_edge(self, None, from, to) + } + + pub fn delete_edge(&mut self, mut from: Option, mut to: Option) { + if self.is_post_dominator() { + core::mem::swap(&mut from, &mut to); + } + SemiNCA::::delete_edge(self, None, from, to) + } + + pub fn apply_updates( + &mut self, + pre_view_cfg: cfg::CfgDiff, + post_view_cfg: cfg::CfgDiff, + ) { + SemiNCA::::apply_updates(self, pre_view_cfg, post_view_cfg); + } + + pub fn compute(&mut self) { + SemiNCA::::compute_from_scratch(self, None); + } + + pub fn compute_with_updates(&mut self, updates: impl ExactSizeIterator) { + // FIXME: Updated to use the PreViewCFG and behave the same as until now. + // This behavior is however incorrect; this actually needs the PostViewCFG. + let pre_view_cfg = cfg::CfgDiff::new(updates, true); + let bui = BatchUpdateInfo::new(pre_view_cfg, None); + SemiNCA::::compute_from_scratch(self, Some(bui)); + } + + pub fn verify(&self, level: DomTreeVerificationLevel) -> bool { + let snca = SemiNCA::new(None); + + // Simplest check is to compare against a new tree. This will also usefully print the old + // and ne3w trees, if they are different. + if !self.is_same_as_fresh_tree() { + return false; + } + + // Common checks to verify the properties of the tree. O(n log n) at worst. + if !snca.verify_roots(self) + || !snca.verify_reachability(self) + || !snca.verify_levels(self) + || !snca.verify_dfs_numbers(self) + { + return false; + } + + // Extra checks depending on verification level. Up to O(n^3) + match level { + DomTreeVerificationLevel::Basic => { + if !snca.verify_parent_property(self) { + return false; + } + } + DomTreeVerificationLevel::Full => { + if !snca.verify_parent_property(self) || !snca.verify_sibling_property(self) { + return false; + } + } + _ => (), + } + + true + } + + fn is_same_as_fresh_tree(&self) -> bool { + let fresh = Self::new(self.parent).unwrap(); + let is_same = self == &fresh; + if !is_same { + log::error!( + "{} is different than a freshly computed one!", + if IS_POST_DOM { + "post-dominator tree" + } else { + "dominator tree" + } + ); + log::error!("Current: {self}"); + log::error!("Fresh: {fresh}"); + } + + is_same + } + + pub fn is_virtual_root(&self, node: &DomTreeNode) -> bool { + self.is_post_dominator() && node.block.is_none() + } + + pub fn add_new_block(&mut self, block: BlockRef, idom: Option) -> Rc { + assert!(self.get(Some(block)).is_none(), "block already in dominator tree"); + let idom = self.get(idom).expect("no immediate dominator specified for `idom`"); + self.mark_invalid(); + self.create_node(Some(block), Some(idom)) + } + + pub fn set_new_root(&mut self, block: BlockRef) -> Rc { + assert!(self.get(Some(block)).is_none(), "block already in dominator tree"); + assert!(!self.is_post_dominator(), "cannot change root of post-dominator tree"); + + self.valid.set(false); + let node = self.create_node(Some(block), None); + if self.roots.is_empty() { + self.roots.push(Some(block)); + } else { + assert_eq!(self.roots.len(), 1); + let old_node = self.get(self.roots[0]).unwrap(); + node.add_child(old_node.clone()); + old_node.idom.set(Some(node.clone())); + old_node.update_level(); + self.roots[0] = Some(block); + } + self.root = Some(node.clone()); + node + } + + pub fn change_immediate_dominator(&mut self, n: BlockRef, idom: Option) { + let n = self.get(Some(n)).expect("expected `n` to be in tree"); + let idom = self.get(idom).expect("expected `idom` to be in tree"); + self.change_immediate_dominator_node(n, idom); + } + + pub fn change_immediate_dominator_node(&mut self, n: Rc, idom: Rc) { + self.valid.set(false); + n.idom.set(Some(idom)); + } + + /// Removes a node from the dominator tree. + /// + /// Block must not dominate any other blocks. + /// + /// Removes node from the children of its immediate dominator. Deletes dominator node associated + /// with `block`. + pub fn erase_node(&mut self, block: BlockRef) { + let node_index = self.node_index(Some(block)); + let entry = unsafe { self.nodes.get_unchecked_mut(node_index).take() }; + let Some((_, node)) = entry else { + panic!("no node in tree for {block}"); + }; + assert!(node.is_leaf(), "node is not a leaf node"); + + self.valid.set(false); + + // Remove node from immediate dominator's children + if let Some(idom) = node.idom() { + idom.children.borrow_mut().retain(|child| child != &node); + } + + if !IS_POST_DOM { + return; + } + + // Remember to update PostDominatorTree roots + if let Some(root_index) = self.roots.iter().position(|r| r.is_some_and(|r| r == block)) { + self.roots.remove(root_index); + } + } + + /// Assign in and out numbers to the nodes while walking the dominator tree in DFS order. + pub fn update_dfs_numbers(&self) { + if self.valid.get() { + self.slow_queries.set(0); + return; + } + + let mut worklist = SmallVec::<[(Rc, usize); 32]>::default(); + let this_root = self.root_node().unwrap(); + + // Both dominators and postdominators have a single root node. In the case of + // PostDominatorTree, this node is a virtual root. + this_root.num_in.set(NonZeroU32::new(1)); + worklist.push((this_root, 0)); + + let mut dfs_num = 1u32; + + while let Some((node, child_index)) = worklist.last_mut() { + // If we visited all of the children of this node, "recurse" back up the + // stack setting the DFOutNum. + if *child_index >= node.num_children() { + node.num_out.set(Some(unsafe { NonZeroU32::new_unchecked(dfs_num) })); + dfs_num += 1; + worklist.pop(); + } else { + // Otherwise, recursively visit this child. + let index = *child_index; + *child_index += 1; + let child = node.children.borrow()[index].clone(); + child.num_in.set(Some(unsafe { NonZeroU32::new_unchecked(dfs_num) })); + dfs_num += 1; + worklist.push((child, 0)); + } + } + + self.slow_queries.set(0); + self.valid.set(true); + } + + /// Reset the dominator tree state + pub fn reset(&mut self) { + self.nodes.clear(); + self.root.take(); + self.roots.clear(); + self.valid.set(false); + self.slow_queries.set(0); + } + + pub(super) fn create_node( + &mut self, + block: Option, + idom: Option>, + ) -> Rc { + let node = Rc::new(DomTreeNode::new(block, idom.clone())); + let node_index = self.node_index(block); + if node_index >= self.nodes.len() { + self.nodes.resize(node_index + 1, None); + } + self.nodes[node_index] = Some((block, node.clone())); + if let Some(idom) = idom { + idom.add_child(node.clone()); + } + node + } + + /// `block` is split and now it has one successor. + /// + /// Update dominator tree to reflect the change. + pub fn split_block(&mut self, block: BlockRef) { + if IS_POST_DOM { + self.split::>(block); + } else { + self.split::(block); + } + } + + // `block` is split and now it has one successor. Update dominator tree to reflect this change. + fn split(&mut self, block: ::Node) + where + G: InvertibleGraph, + { + let mut successors = G::children(block); + assert_eq!(successors.len(), 1, "`block` should have a single successor"); + + let succ = successors.next().unwrap(); + let predecessors = G::inverse_children(block).collect::>(); + + assert!(!predecessors.is_empty(), "expected at at least one predecessor"); + + let mut block_dominates_succ = true; + for pred in G::inverse_children(succ) { + if pred != block + && !self.dominates(Some(succ), Some(pred)) + && self.is_reachable_from_entry(pred) + { + block_dominates_succ = false; + break; + } + } + + // Find `block`'s immediate dominator and create new dominator tree node for `block`. + let idom = predecessors.iter().find(|p| self.is_reachable_from_entry(**p)).copied(); + + // It's possible that none of the predecessors of `block` are reachable; + // in that case, `block` itself is unreachable, so nothing needs to be + // changed. + let Some(idom) = idom else { + return; + }; + + let idom = predecessors.iter().copied().fold(idom, |idom, p| { + if self.is_reachable_from_entry(p) { + self.find_nearest_common_dominator(idom, p).expect("expected idom") + } else { + idom + } + }); + + // Create the new dominator tree node... and set the idom of `block`. + let node = self.add_new_block(block, Some(idom)); + + // If NewBB strictly dominates other blocks, then it is now the immediate + // dominator of NewBBSucc. Update the dominator tree as appropriate. + if block_dominates_succ { + let succ_node = self.get(Some(succ)).expect("expected 'succ' to be in dominator tree"); + self.change_immediate_dominator_node(succ_node, node); + } + } + + fn dominated_by_slow_tree_walk(&self, a: Rc, b: Rc) -> bool { + assert_ne!(a, b); + + let a_level = a.level(); + let mut b = b; + + // Don't walk nodes above A's subtree. When we reach A's level, we must + // either find A or be in some other subtree not dominated by A. + while b.level() > a_level { + match b.idom() { + Some(b_idom) => b = b_idom, + None => break, + } + } + + b == a + } +} + +impl fmt::Display for DomTreeBase { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use core::fmt::Write; + + f.write_str("=============================--------------------------------\n")?; + if IS_POST_DOM { + f.write_str("Inorder PostDominator Tree: ")?; + } else { + f.write_str("Inorder Dominator Tree: ")?; + } + if !self.valid.get() { + write!(f, "DFS numbers invalid: {} slow queries.", self.slow_queries.get())?; + } + f.write_char('\n')?; + + // The postdom tree can have a `None` root if there are no returns. + if let Some(root_node) = self.root_node() { + print_dom_tree(root_node, 1, f)? + } + f.write_str("Roots: ")?; + for (i, block) in self.roots.iter().enumerate() { + if i > 0 { + f.write_str(", ")?; + } + if let Some(block) = block { + write!(f, "{block}")?; + } else { + f.write_str("")?; + } + } + f.write_char('\n') + } +} + +fn print_dom_tree( + node: Rc, + level: usize, + f: &mut core::fmt::Formatter<'_>, +) -> core::fmt::Result { + write!(f, "{: <1$}", "", level)?; + writeln!(f, "[{level}] {node}")?; + for child_node in node.children.borrow().iter().cloned() { + print_dom_tree(child_node, level + 1, f)?; + } + Ok(()) +} + +impl Eq for DomTreeBase {} +impl PartialEq for DomTreeBase { + fn eq(&self, other: &Self) -> bool { + self.parent == other.parent + && self.roots.len() == other.roots.len() + && self.roots.iter().all(|root| other.roots.contains(root)) + && self.nodes.len() == other.nodes.len() + && self.nodes.iter().all(|entry| match entry { + Some((_, node)) => { + let block = node.block(); + other.get(block).is_some_and(|n| node == &n) + } + None => true, + }) + } +} diff --git a/hir/src/ir/effects.rs b/hir/src/ir/effects.rs new file mode 100644 index 000000000..f9cc0ba14 --- /dev/null +++ b/hir/src/ir/effects.rs @@ -0,0 +1,37 @@ +mod instance; +mod interface; +mod memory; +mod speculation; + +use core::{any::Any, fmt}; + +pub use self::{instance::EffectInstance, interface::*, memory::*, speculation::*}; +use crate::DynPartialEq; + +pub trait Effect: Any + fmt::Debug {} + +pub trait Resource: Any + DynPartialEq + fmt::Debug { + fn name(&self) -> &'static str; +} + +/// A conservative default resource kind +#[derive(Debug, Default, PartialEq, Eq)] +pub struct DefaultResource; +impl Resource for DefaultResource { + fn name(&self) -> &'static str { + "default" + } +} + +/// An automatic allocation-scope resource that is valid in the context of a parent +/// AutomaticAllocationScope trait. +#[derive(Debug, Default, PartialEq, Eq)] +pub struct AutomaticAllocationScopeResource; +impl Resource for AutomaticAllocationScopeResource { + fn name(&self) -> &'static str { + "automatic-allocation-scope" + } +} + +/// An operation trait for ops that are always speculatable and have no memory effects +pub trait Pure: AlwaysSpeculatable + MemoryEffectOpInterface {} diff --git a/hir/src/ir/effects/instance.rs b/hir/src/ir/effects/instance.rs new file mode 100644 index 000000000..991df041a --- /dev/null +++ b/hir/src/ir/effects/instance.rs @@ -0,0 +1,298 @@ +use alloc::{boxed::Box, rc::Rc}; +use core::fmt; + +use super::{DefaultResource, Effect, Resource}; +use crate::{ + interner, AttributeSet, AttributeValue, BlockArgument, BlockArgumentRef, EntityRef, OpOperand, + OpOperandImpl, OpResult, OpResultRef, SymbolRef, Value, ValueRef, +}; + +#[derive(Clone)] +pub struct EffectInstance { + /// The specific effect being applied + effect: T, + /// The resource that the given value resides in + resource: Rc, + /// The [Symbol], [OpOperand], [OpResult], or [BlockArgument] that the effect applies to. + value: Option, + /// Additional parameters of the effect instance. + parameters: AttributeSet, + /// The stage the side effect happens in. + /// + /// Side effects with a lower stage happen earlier than those with a higher stage. + stage: u8, + /// Indicates whether this side effect acts on every single value of the resource + effect_on_full_region: bool, +} + +impl EffectInstance { + pub fn new(effect: T) -> Self { + Self::new_with_resource(effect, DefaultResource) + } + + pub fn new_for_value(effect: T, value: impl Into) -> Self { + Self::new_for_value_with_resource(effect, value, DefaultResource) + } +} + +impl EffectInstance { + pub fn new_with_resource(effect: T, resource: impl Resource) -> Self { + Self { + effect, + resource: Rc::new(resource), + parameters: AttributeSet::new(), + value: None, + stage: 0, + effect_on_full_region: false, + } + } + + #[inline] + pub fn new_for_value_with_resource( + effect: T, + value: impl Into, + resource: impl Resource, + ) -> Self { + Self { + effect, + resource: Rc::new(resource), + parameters: Default::default(), + value: Some(value.into()), + stage: 0, + effect_on_full_region: false, + } + } + + #[inline(always)] + pub fn with_parameter( + mut self, + name: impl Into, + value: impl AttributeValue, + ) -> Self { + self.parameters.insert(name, Some(value)); + self + } + + #[inline(always)] + pub fn with_stage(mut self, stage: u8) -> Self { + self.stage = stage; + self + } + + #[inline(always)] + pub fn with_effect_on_full_region(mut self, yes: bool) -> Self { + self.effect_on_full_region = yes; + self + } + + /// Get the effect being applied + #[inline] + pub fn effect(&self) -> &T { + &self.effect + } + + /// Get the resource that the effect applies to + #[inline] + pub fn resource(&self) -> &dyn Resource { + self.resource.as_ref() + } + + /// Get the parameters of the effect. + #[inline] + pub const fn parameters(&self) -> &AttributeSet { + &self.parameters + } + + /// Get the stage at which the effect happens. + #[inline] + pub const fn stage(&self) -> u8 { + self.stage + } + + /// Returns whether this efffect acts on every single value of the resource. + #[inline] + pub const fn is_effect_on_full_region(&self) -> bool { + self.effect_on_full_region + } + + /// Get the value the effect is being applied on, or `None` if there isn't a known value + /// being affected. + pub fn value(&self) -> Option { + match self.value.as_ref()? { + EffectValue::Result(res) => Some(*res as ValueRef), + EffectValue::BlockArgument(arg) => Some(*arg as ValueRef), + EffectValue::Operand(operand) => Some(operand.borrow().as_value_ref()), + _ => None, + } + } + + /// Get the value the effect is being applied on, or `None` if there isn't a known value + /// being affected. + #[allow(unused)] + fn effect_value(&self) -> Option<&EffectValue> { + self.value.as_ref() + } + + /// Get the value the effect is being applied on, if it is of the specified type, or `None` if + /// there isn't a known value being affected. + pub fn value_of_kind<'a, 'b: 'a, V>(&'b self) -> Option> + where + V: Value, + EntityRef<'a, V>: TryFrom<&'b EffectValue>, + { + self.value.as_ref().and_then(|value| value.try_as_ref()) + } + + /// Get the symbol reference the effect is applied on, or `None` if there isn't a known symbol + /// being affected. + pub fn symbol(&self) -> Option { + match self.value.as_ref()? { + EffectValue::Symbol(symbol_use) => Some(*symbol_use), + _ => None, + } + } +} + +impl fmt::Debug for EffectInstance { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EffectInstance") + .field("effect", &self.effect) + .field("resource", &self.resource) + .field("value", &self.value) + .field("parameters", &self.parameters) + .field("stage", &self.stage) + .field("effect_on_full_region", &self.effect_on_full_region) + .finish() + } +} + +#[derive(PartialEq, Eq)] +pub enum EffectValue { + Attribute(Box), + Symbol(SymbolRef), + Operand(OpOperand), + Result(OpResultRef), + BlockArgument(BlockArgumentRef), +} +impl Clone for EffectValue { + fn clone(&self) -> Self { + match self { + Self::Attribute(attr) => Self::Attribute(attr.clone_value()), + Self::Symbol(value) => Self::Symbol(*value), + Self::Operand(value) => Self::Operand(*value), + Self::Result(value) => Self::Result(*value), + Self::BlockArgument(value) => Self::BlockArgument(*value), + } + } +} +impl fmt::Debug for EffectValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Attribute(attr) => f.debug_tuple("Attribute").field(attr).finish(), + Self::Symbol(symbol_use) => f + .debug_tuple("Symbol") + .field_with(|f| { + let symbol = symbol_use.borrow(); + write!(f, "{}", &symbol.path()) + }) + .finish(), + Self::Operand(operand) => { + let value = operand.borrow().as_value_ref(); + f.debug_tuple("Operand").field(&value).finish() + } + Self::Result(result) => { + let value = *result as ValueRef; + f.debug_tuple("Result").field(&value).finish() + } + Self::BlockArgument(arg) => { + let value = *arg as ValueRef; + f.debug_tuple("BlockArgument").field(&value).finish() + } + } + } +} + +impl From> for EffectValue { + fn from(value: Box) -> Self { + Self::Attribute(value) + } +} + +impl From for EffectValue { + fn from(value: SymbolRef) -> Self { + Self::Symbol(value) + } +} + +impl From for EffectValue { + fn from(value: OpOperand) -> Self { + Self::Operand(value) + } +} + +impl From for EffectValue { + fn from(value: OpResultRef) -> Self { + Self::Result(value) + } +} + +impl From for EffectValue { + fn from(value: BlockArgumentRef) -> Self { + Self::BlockArgument(value) + } +} + +impl From for EffectValue { + fn from(value: ValueRef) -> Self { + let value = value.borrow(); + if let Some(result) = value.downcast_ref::() { + Self::Result(result.as_op_result_ref()) + } else { + let arg = value.downcast_ref::().unwrap(); + Self::BlockArgument(arg.as_block_argument_ref()) + } + } +} + +impl EffectValue { + pub fn try_as_ref<'a, 'b: 'a, V>(&'b self) -> Option> + where + V: Value, + EntityRef<'a, V>: TryFrom<&'b Self>, + { + TryFrom::try_from(self).ok() + } +} + +impl<'a> core::convert::TryFrom<&'a EffectValue> for EntityRef<'a, OpOperandImpl> { + type Error = (); + + fn try_from(value: &'a EffectValue) -> Result { + match value { + EffectValue::Operand(operand) => Ok(operand.borrow()), + _ => Err(()), + } + } +} + +impl<'a> core::convert::TryFrom<&'a EffectValue> for EntityRef<'a, BlockArgument> { + type Error = (); + + fn try_from(value: &'a EffectValue) -> Result { + match value { + EffectValue::BlockArgument(operand) => Ok(operand.borrow()), + _ => Err(()), + } + } +} + +impl<'a> core::convert::TryFrom<&'a EffectValue> for EntityRef<'a, OpResult> { + type Error = (); + + fn try_from(value: &'a EffectValue) -> Result { + match value { + EffectValue::Result(operand) => Ok(operand.borrow()), + _ => Err(()), + } + } +} diff --git a/hir/src/ir/effects/interface.rs b/hir/src/ir/effects/interface.rs new file mode 100644 index 000000000..37ab0ca15 --- /dev/null +++ b/hir/src/ir/effects/interface.rs @@ -0,0 +1,178 @@ +use smallvec::SmallVec; + +use super::*; +use crate::{SymbolRef, ValueRef}; + +pub trait EffectOpInterface { + /// Return the set all of the operation's effects + fn effects(&self) -> EffectIterator; + /// Returns true if this operation has no effects + fn has_no_effect(&self) -> bool { + self.effects().is_empty() + } + /// Return the set of effect instances that operate on the provided value + fn effects_on_value(&self, value: ValueRef) -> ValueEffectIterator { + EffectIterator::for_value(self.effects(), value) + } + /// Return the set of effect instances that operate on the provided symbol + fn effects_on_symbol(&self, symbol: SymbolRef) -> SymbolEffectIterator { + EffectIterator::for_symbol(self.effects(), symbol) + } + /// Return the set of effect instances that operate on the provided resource + fn effects_on_resource<'a, 'b: 'a>( + &self, + resource: &'b dyn Resource, + ) -> ResourceEffectIterator<'b, T> { + EffectIterator::for_resource(self.effects(), resource) + } +} + +impl dyn EffectOpInterface { + /// Return the set all of the operation's effects that correspond to effect type `T` + pub fn effects_of_type(&self) -> impl Iterator> + '_ + where + E: Effect, + { + self.effects().filter(|instance| (instance.effect() as &dyn Any).is::()) + } + + /// Returns true if the operation exhibits the given effect. + pub fn has_effect(&self) -> bool + where + E: Any, + { + self.effects().any(|instance| (instance.effect() as &dyn Any).is::()) + } + + /// Returns true if the operation only exhibits the given effect. + pub fn only_has_effect(&self) -> bool + where + E: Any, + { + let mut effects = self.effects(); + !effects.is_empty() && effects.all(|instance| (instance.effect() as &dyn Any).is::()) + } +} + +pub struct EffectIterator { + effects: smallvec::IntoIter<[EffectInstance; 4]>, +} +impl EffectIterator { + pub fn from_smallvec(effects: SmallVec<[EffectInstance; 4]>) -> Self { + Self { + effects: effects.into_iter(), + } + } + + pub fn new(effects: impl IntoIterator>) -> Self { + let effects = effects.into_iter().collect::>(); + Self { + effects: effects.into_iter(), + } + } + + pub const fn for_value(effects: Self, value: ValueRef) -> ValueEffectIterator { + ValueEffectIterator { + iter: effects, + value, + } + } + + pub const fn for_symbol(effects: Self, symbol: SymbolRef) -> SymbolEffectIterator { + SymbolEffectIterator { + iter: effects, + symbol, + } + } + + pub const fn for_resource( + effects: Self, + resource: &dyn Resource, + ) -> ResourceEffectIterator<'_, T> { + ResourceEffectIterator { + iter: effects, + resource, + } + } + + #[inline] + pub fn as_slice(&self) -> &[EffectInstance] { + self.effects.as_slice() + } +} +impl core::iter::FusedIterator for EffectIterator {} +impl ExactSizeIterator for EffectIterator { + fn is_empty(&self) -> bool { + self.effects.is_empty() + } + + fn len(&self) -> usize { + self.effects.len() + } +} +impl Iterator for EffectIterator { + type Item = EffectInstance; + + #[inline] + fn next(&mut self) -> Option { + self.effects.next() + } +} + +pub struct ValueEffectIterator { + iter: EffectIterator, + value: ValueRef, +} +impl core::iter::FusedIterator for ValueEffectIterator {} +impl Iterator for ValueEffectIterator { + type Item = EffectInstance; + + fn next(&mut self) -> Option { + while let Some(instance) = self.iter.next() { + if instance.value().is_some_and(|v| v == self.value) { + return Some(instance); + } + } + + None + } +} + +pub struct SymbolEffectIterator { + iter: EffectIterator, + symbol: SymbolRef, +} +impl core::iter::FusedIterator for SymbolEffectIterator {} +impl Iterator for SymbolEffectIterator { + type Item = EffectInstance; + + fn next(&mut self) -> Option { + while let Some(instance) = self.iter.next() { + if instance.symbol().is_some_and(|s| s == self.symbol) { + return Some(instance); + } + } + + None + } +} + +pub struct ResourceEffectIterator<'a, T> { + iter: EffectIterator, + resource: &'a dyn Resource, +} +impl core::iter::FusedIterator for ResourceEffectIterator<'_, T> {} +impl Iterator for ResourceEffectIterator<'_, T> { + type Item = EffectInstance; + + fn next(&mut self) -> Option { + #[allow(clippy::while_let_on_iterator)] + while let Some(instance) = self.iter.next() { + if instance.resource().dyn_eq(self.resource) { + return Some(instance); + } + } + + None + } +} diff --git a/hir/src/ir/effects/memory.rs b/hir/src/ir/effects/memory.rs new file mode 100644 index 000000000..27be9c759 --- /dev/null +++ b/hir/src/ir/effects/memory.rs @@ -0,0 +1,33 @@ +use super::*; + +/// Marker trait for ops with recursive memory effects, i.e. the effects of the operation includes +/// the effects of operations nested within its regions. If the operation does not implement any +/// effect markers, e.g. `MemoryWrite`, then it can be assumed to have no memory effects itself. +pub trait HasRecursiveMemoryEffects {} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum MemoryEffect { + /// The following effect indicates that the operation reads from some resource. + /// + /// A 'read' effect implies only dereferencing of the resource, and not any visible mutation. + Read, + /// The following effect indicates that the operation writes to some resource. + /// + /// A 'write' effect implies only mutating a resource, and not any visible dereference or read. + Write, + /// The following effect indicates that the operation allocates from some resource. + /// + /// An 'allocate' effect implies only allocation of the resource, and not any visible mutation or + /// dereference. + Allocate, + /// The following effect indicates that the operation frees some resource that has been + /// allocated. + /// + /// An 'allocate' effect implies only de-allocation of the resource, and not any visible + /// allocation, mutation or dereference. + Free, +} + +impl Effect for MemoryEffect {} + +pub trait MemoryEffectOpInterface = EffectOpInterface; diff --git a/hir/src/ir/effects/speculation.rs b/hir/src/ir/effects/speculation.rs new file mode 100644 index 000000000..8464f38db --- /dev/null +++ b/hir/src/ir/effects/speculation.rs @@ -0,0 +1,41 @@ +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Speculatability { + /// The Operation in question cannot be speculatively executed. This could be + /// because it may invoke undefined behavior or have other side effects. + NotSpeculatable, + + /// The Operation in question can be speculatively executed. It does not have + /// any side effects or undefined behavior. + Speculatable, + + /// The Operation in question can be speculatively executed if all the + /// operations in all attached regions can also be speculatively executed. + RecursivelySpeculatable, +} + +/// This op interface enables one to inject custom logic to determine whether an Operation can be +/// speculatively executed. +/// +/// Ops that implement this interface need to implement the custom logic in the `speculatability` +/// method. +/// +/// For instance, the `speculatability` for a specific op may check the attributes or input types to +/// determine whether that specific operation is speculatable. +pub trait ConditionallySpeculatable { + /// Returns value indicating whether the specific operation in question can be speculatively + /// executed. + /// + /// Please see the [Speculatability] docs to know how to interpret the return value. + fn speculatability(&self) -> Speculatability; +} + +/// This trait marks an op (which must be tagged as implementing the [ConditionallySpeculatable] +/// interface) as being recursively speculatable. +/// +/// This means that said op can be speculated only if all the instructions in all the regions +/// attached to the op can be speculated. +pub trait RecursivelySpeculatable: ConditionallySpeculatable {} + +/// This trait marks an op (which must be tagged as implementing the [ConditionallySpeculatable] +/// interface) as being always speculatable. +pub trait AlwaysSpeculatable: ConditionallySpeculatable {} diff --git a/hir/src/ir/entity.rs b/hir/src/ir/entity.rs new file mode 100644 index 000000000..1eab88c38 --- /dev/null +++ b/hir/src/ir/entity.rs @@ -0,0 +1,1622 @@ +mod group; +mod list; +mod storage; + +use core::{ + alloc::{AllocError, Layout}, + any::Any, + cell::{Cell, UnsafeCell}, + fmt, + hash::Hash, + mem::MaybeUninit, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +pub use self::{ + group::EntityGroup, + list::{EntityCursor, EntityCursorMut, EntityIter, EntityList, MaybeDefaultEntityIter}, + storage::{EntityRange, EntityRangeMut, EntityStorage}, +}; +use crate::any::*; + +/// A trait implemented by an IR entity +pub trait Entity: Any {} + +/// A trait implemented by any [Entity] that is storeable in an [EntityList]. +/// +/// This trait defines callbacks that are executed any time the entity is added, removed, or +/// transferred between lists. +/// +/// By default, these callbacks are no-ops. +#[allow(unused_variables)] +pub trait EntityListItem: Sized + Entity { + /// Invoked when this entity type is inserted into an intrusive list + #[inline] + fn on_inserted(this: UnsafeIntrusiveEntityRef, cursor: &mut EntityCursorMut<'_, Self>) {} + /// Invoked when this entity type is removed from an intrusive list + #[inline] + fn on_removed(this: UnsafeIntrusiveEntityRef, list: &mut EntityCursorMut<'_, Self>) {} + /// Invoked when a set of entities is moved from one intrusive list to another + #[inline] + fn on_transfer( + this: UnsafeIntrusiveEntityRef, + from: &mut EntityList, + to: &mut EntityList, + ) { + } +} + +impl EntityListItem for T { + default fn on_inserted( + _this: UnsafeIntrusiveEntityRef, + _list: &mut EntityCursorMut<'_, Self>, + ) { + } + + default fn on_removed( + _this: UnsafeIntrusiveEntityRef, + _list: &mut EntityCursorMut<'_, Self>, + ) { + } + + default fn on_transfer( + _this: UnsafeIntrusiveEntityRef, + _from: &mut EntityList, + _to: &mut EntityList, + ) { + } +} + +/// A trait implemented by an [Entity] that is a parent to one or more other [Entity] types. +/// +/// Parents must implement this trait for each unique [EntityList] they contain. +pub trait EntityParent: Entity { + /// Statically compute the offset of the [EntityList] within `Self` that is used to store + /// children of type `Child`. + fn offset() -> usize; +} + +/// A trait implemented by an [Entity] that is a logical child of another entity type, and is stored +/// in the parent using an [EntityList]. +/// +/// This trait defines callbacks that are executed any time the entity is modified in relation to its +/// parent entity, i.e. inserted in a parent, removed from a parent, or moved from one to another. +/// +/// By default, these callbacks are no-ops. +pub trait EntityWithParent: Entity { + /// The parent entity that this entity logically belongs to. + type Parent: EntityParent; +} + +/// A trait implemented by an [Entity] that has a unique identifier +/// +/// Currently, this is used only for [Value]s and [Block]s. +pub trait EntityWithId: Entity { + type Id: EntityId; + + fn id(&self) -> Self::Id; +} + +/// A trait implemented by an IR entity that can be stored in [EntityStorage]. +pub trait StorableEntity { + /// Get the absolute index of this entity in its container. + fn index(&self) -> usize; + /// Set the absolute index of this entity in its container. + /// + /// # Safety + /// + /// This is intended to be called only by the [EntityStorage] implementation, as it is + /// responsible for maintaining indices of all items it is storing. However, entities commonly + /// want to know their own index in storage, so this trait allows them to conceptually own the + /// index, but delegate maintenance to [EntityStorage]. + unsafe fn set_index(&mut self, index: usize); + /// Called when this entity is removed from [EntityStorage] + #[inline(always)] + fn unlink(&mut self) {} +} + +/// A trait that must be implemented by the unique identifier for an [Entity] +pub trait EntityId: Copy + Clone + PartialEq + Eq + PartialOrd + Ord + Hash + fmt::Display { + fn as_usize(&self) -> usize; +} + +/// An error raised when an aliasing violation is detected in the use of [EntityHandle] +#[non_exhaustive] +pub struct AliasingViolationError { + #[cfg(debug_assertions)] + location: &'static core::panic::Location<'static>, + kind: AliasingViolationKind, +} + +#[derive(Debug)] +enum AliasingViolationKind { + /// Attempted to create an immutable alias for an entity that was mutably borrowed + Immutable, + /// Attempted to create a mutable alias for an entity that was immutably borrowed + Mutable, +} + +impl fmt::Display for AliasingViolationKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Immutable => f.write_str("already mutably borrowed"), + Self::Mutable => f.write_str("already borrowed"), + } + } +} + +impl fmt::Debug for AliasingViolationError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = f.debug_struct("AliasingViolationError"); + builder.field("kind", &self.kind); + #[cfg(debug_assertions)] + builder.field("location", &self.location); + builder.finish() + } +} +impl fmt::Display for AliasingViolationError { + #[cfg(debug_assertions)] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} in file '{}' at line {} and column {}", + &self.kind, + self.location.file(), + self.location.line(), + self.location.column() + ) + } + + #[cfg(not(debug_assertions))] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.kind) + } +} + +/// A raw pointer to an IR entity that has no associated metadata +pub type UnsafeEntityRef = RawEntityRef; + +/// A raw pointer to an IR entity that has an intrusive linked-list link as its metadata +pub type UnsafeIntrusiveEntityRef = RawEntityRef; + +pub struct IntrusiveLink { + link: intrusive_collections::LinkedListLink, + parent: Cell<*const ()>, +} + +impl Default for IntrusiveLink { + fn default() -> Self { + Self { + link: Default::default(), + parent: Cell::new(core::ptr::null()), + } + } +} + +impl IntrusiveLink { + #[inline] + pub fn is_linked(&self) -> bool { + self.link.is_linked() + } +} + +impl IntrusiveLink { + pub(self) fn set_parent(&self, parent: Option>) { + if let Some(parent) = parent { + assert!( + self.link.is_linked(), + "must add entity to parent entity list before setting parent" + ); + self.parent.set(UnsafeIntrusiveEntityRef::as_ptr(&parent).cast()); + } else if self.parent.get().is_null() { + panic!("no parent previously set"); + } else { + self.parent.set(core::ptr::null()); + } + } + + pub fn parent(&self) -> Option> { + let parent = self.parent.get(); + if parent.is_null() { + // EntityList is orphaned + None + } else { + Some(unsafe { UnsafeIntrusiveEntityRef::from_raw(parent.cast()) }) + } + } +} + +/// A [RawEntityRef] is an unsafe smart pointer type for IR entities allocated in a [Context]. +/// +/// Along with the type of entity referenced, it can be instantiated with extra metadata of any +/// type. For example, [UnsafeIntrusiveEntityRef] stores an intrusive link in the entity metadata, +/// so that the entity can be added to an intrusive linked list without the entity needing to +/// know about the link - and without violating aliasing rules when navigating the list. +/// +/// Unlike regular references, no reference to the underlying `T` is constructed until one is +/// needed, at which point the borrow (whether mutable or immutable) is dynamically checked to +/// ensure that it is valid according to Rust's aliasing rules. +/// +/// As a result, a [RawEntityRef] is not considered an alias, and it is possible to acquire a +/// mutable reference to the underlying data even while other copies of the handle exist. Any +/// attempt to construct invalid aliases (immutable reference while a mutable reference exists, or +/// vice versa), will result in a runtime panic. +/// +/// This is a tradeoff, as we do not get compile-time guarantees that such panics will not occur, +/// but in exchange we get a much more flexible and powerful IR structure. +/// +/// # SAFETY +/// +/// Unlike most smart-pointer types, e.g. `Rc`, [RAwEntityRef] does not provide any protection +/// against the underlying allocation being deallocated (i.e. the arena it points into is dropped). +/// This is by design, as the type is meant to be stored in objects inside the arena, and +/// _not_ dropped when the arena is dropped. This requires care when using it however, to ensure +/// that no [RawEntityRef] lives longer than the arena that allocated it. +/// +/// For a safe entity reference, see [EntityRef], which binds a [RawEntityRef] to the lifetime +/// of the arena. +pub struct RawEntityRef { + inner: NonNull>, +} +impl Copy for RawEntityRef {} +impl Clone for RawEntityRef { + fn clone(&self) -> Self { + *self + } +} +impl RawEntityRef { + /// Create a new [EntityHandle] from a raw pointer to the underlying [EntityObj]. + /// + /// # SAFETY + /// + /// [EntityHandle] is designed to operate like an owned smart-pointer type, ala `Rc`. As a + /// result, it expects that the underlying data _never moves_ after it is allocated, for as + /// long as any outstanding [EntityHandle]s exist that might be used to access that data. + /// + /// Additionally, it is expected that all accesses to the underlying data flow through an + /// [EntityHandle], as it is the foundation on which the soundness of [EntityHandle] is built. + /// You must ensure that there no other references to the underlying data exist, or can be + /// created, _except_ via [EntityHandle]. + /// + /// You should generally not be using this API, as it is meant solely for constructing an + /// [EntityHandle] immediately after allocating the underlying [EntityObj]. + #[inline] + unsafe fn from_inner(inner: NonNull>) -> Self { + Self { inner } + } + + #[inline] + unsafe fn from_ptr(ptr: *mut RawEntityMetadata) -> Self { + debug_assert!(!ptr.is_null()); + Self::from_inner(NonNull::new_unchecked(ptr)) + } + + #[inline] + fn into_inner(this: Self) -> NonNull> { + this.inner + } +} + +impl RawEntityRef { + /// Create a new [RawEntityRef] by allocating `value` with `metadata` in the given arena + /// allocator. + /// + /// # SAFETY + /// + /// The resulting [RawEntityRef] must not outlive the arena. This is not enforced statically, + /// it is up to the caller to uphold the invariants of this type. + pub fn new_with_metadata(value: T, metadata: Metadata, arena: &blink_alloc::Blink) -> Self { + unsafe { + Self::from_inner(NonNull::new_unchecked( + arena.put(RawEntityMetadata::new(value, metadata)), + )) + } + } + + /// Create a [RawEntityRef] for an entity which may not be fully initialized, using the provided + /// arena. + /// + /// # SAFETY + /// + /// The safety rules are much the same as [RawEntityRef::new], with the main difference + /// being that the `T` does not have to be initialized yet. No references to the `T` will + /// be created directly until [RawEntityRef::assume_init] is called. + pub fn new_uninit_with_metadata( + metadata: Metadata, + arena: &blink_alloc::Blink, + ) -> RawEntityRef, Metadata> { + unsafe { + RawEntityRef::from_ptr(RawEntityRef::allocate_for_layout( + metadata, + Layout::new::(), + |layout| arena.allocator().allocate(layout).map_err(|_| AllocError), + <*mut u8>::cast, + )) + } + } +} + +impl RawEntityRef { + pub fn new(value: T, arena: &blink_alloc::Blink) -> Self { + RawEntityRef::new_with_metadata(value, (), arena) + } + + pub fn new_uninit(arena: &blink_alloc::Blink) -> RawEntityRef, ()> { + RawEntityRef::new_uninit_with_metadata((), arena) + } +} + +impl RawEntityRef, Metadata> { + /// Converts to `RawEntityRef`. + /// + /// # Safety + /// + /// Just like with [MaybeUninit::assume_init], it is up to the caller to guarantee that the + /// value really is in an initialized state. Calling this when the content is not yet fully + /// initialized causes immediate undefined behavior. + #[inline] + pub unsafe fn assume_init(self) -> RawEntityRef { + let ptr = Self::into_inner(self); + unsafe { RawEntityRef::from_inner(ptr.cast()) } + } +} + +impl RawEntityRef { + /// Convert this handle into a raw pointer to the underlying entity. + /// + /// This should only be used in situations where the returned pointer will not be used to + /// actually access the underlying entity. Use [get] or [get_mut] for that. [RawEntityRef] + /// ensures that Rust's aliasing rules are not violated when using it, but if you use the + /// returned pointer to do so, no such guarantee is provided, and undefined behavior can + /// result. + /// + /// # Safety + /// + /// The returned pointer _must_ not be used to create a reference to the underlying entity + /// unless you can guarantee that such a reference does not violate Rust's aliasing rules. + /// + /// Do not use the pointer to create a mutable reference if other references exist, and do + /// not use the pointer to create an immutable reference if a mutable reference exists or + /// might be created while the immutable reference lives. + pub fn into_raw(this: Self) -> *const T { + Self::as_ptr(&this) + } + + pub fn as_ptr(this: &Self) -> *const T { + let ptr: *mut RawEntityMetadata = NonNull::as_ptr(this.inner); + + // SAFETY: This cannot go through Deref::deref or RawEntityRef::inner because this + // is required to retain raw/mut provenance such that e.g. `get_mut` can write through + // the pointer after the RawEntityRef is recovered through `from_raw` + let ptr = unsafe { core::ptr::addr_of_mut!((*ptr).entity.cell) }; + UnsafeCell::raw_get(ptr).cast_const() + } + + /// Convert a pointer returned by [RawEntityRef::into_raw] back into a [RawEntityRef]. + /// + /// # Safety + /// + /// * It is _only_ valid to call this method on a pointer returned by [RawEntityRef::into_raw]. + /// * The pointer must be a valid pointer for `T` + pub unsafe fn from_raw(ptr: *const T) -> Self { + let offset = unsafe { RawEntityMetadata::::data_offset(ptr) }; + + // Reverse the offset to find the original EntityObj + let entity_ptr = unsafe { ptr.byte_sub(offset) as *mut RawEntityMetadata }; + + unsafe { Self::from_ptr(entity_ptr) } + } + + /// Get a dynamically-checked immutable reference to the underlying `T` + #[track_caller] + pub fn borrow<'a, 'b: 'a>(&'a self) -> EntityRef<'b, T> { + let ptr: *mut RawEntityMetadata = NonNull::as_ptr(self.inner); + unsafe { (*core::ptr::addr_of!((*ptr).entity)).borrow() } + } + + /// Get a dynamically-checked mutable reference to the underlying `T` + #[track_caller] + pub fn borrow_mut<'a, 'b: 'a>(&'a mut self) -> EntityMut<'b, T> { + let ptr: *mut RawEntityMetadata = NonNull::as_ptr(self.inner); + unsafe { (*core::ptr::addr_of!((*ptr).entity)).borrow_mut() } + } + + /// Try to get a dynamically-checked mutable reference to the underlying `T` + /// + /// Returns `None` if the entity is already borrowed + pub fn try_borrow_mut<'a, 'b: 'a>(&'a mut self) -> Option> { + let ptr: *mut RawEntityMetadata = NonNull::as_ptr(self.inner); + unsafe { (*core::ptr::addr_of!((*ptr).entity)).try_borrow_mut().ok() } + } + + pub fn ptr_eq(this: &Self, other: &Self) -> bool { + core::ptr::addr_eq(this.inner.as_ptr(), other.inner.as_ptr()) + } + + unsafe fn allocate_for_layout( + metadata: Metadata, + value_layout: Layout, + allocate: F, + mem_to_metadata: F2, + ) -> *mut RawEntityMetadata + where + F: FnOnce(Layout) -> Result, AllocError>, + F2: FnOnce(*mut u8) -> *mut RawEntityMetadata, + { + use alloc::alloc::handle_alloc_error; + + let layout = raw_entity_metadata_layout_for_value_layout::(value_layout); + unsafe { + RawEntityRef::try_allocate_for_layout(metadata, value_layout, allocate, mem_to_metadata) + .unwrap_or_else(|_| handle_alloc_error(layout)) + } + } + + #[inline] + unsafe fn try_allocate_for_layout( + metadata: Metadata, + value_layout: Layout, + allocate: F, + mem_to_metadata: F2, + ) -> Result<*mut RawEntityMetadata, AllocError> + where + F: FnOnce(Layout) -> Result, AllocError>, + F2: FnOnce(*mut u8) -> *mut RawEntityMetadata, + { + let layout = raw_entity_metadata_layout_for_value_layout::(value_layout); + let ptr = allocate(layout)?; + let inner = mem_to_metadata(ptr.as_non_null_ptr().as_ptr()); + unsafe { + debug_assert_eq!(Layout::for_value_raw(inner), layout); + + core::ptr::addr_of_mut!((*inner).metadata).write(metadata); + core::ptr::addr_of_mut!((*inner).entity.borrow).write(Cell::new(BorrowFlag::UNUSED)); + #[cfg(debug_assertions)] + core::ptr::addr_of_mut!((*inner).entity.borrowed_at).write(Cell::new(None)); + } + + Ok(inner) + } +} + +impl RawEntityRef { + /// Casts this reference to the concrete type `T`, if the underlying value is a `T`. + /// + /// If the cast is not valid for this reference, `Err` is returned containing the original value. + #[inline] + pub fn try_downcast( + self, + ) -> Result, RawEntityRef> + where + To: DowncastFromRef + 'static, + From: 'static, + { + RawEntityRef::::try_downcast_from(self) + } + + /// Casts this reference to the concrete type `T`, if the underlying value is a `T`. + /// + /// If the cast is not valid for this reference, `Err` is returned containing the original value. + #[inline] + pub fn try_downcast_ref(&self) -> Option> + where + To: DowncastFromRef + 'static, + From: Is + AsAny + 'static, + Obj: ?Sized, + { + RawEntityRef::::try_downcast_from_ref(self) + } + + /// Casts this reference to the concrete type `T`, if the underlying value is a `T`. + /// + /// Panics if the cast is not valid for this reference. + #[inline] + #[track_caller] + pub fn downcast(self) -> RawEntityRef + where + To: DowncastFromRef + 'static, + From: Is + AsAny + 'static, + Obj: ?Sized, + { + RawEntityRef::::downcast_from(self) + } + + /// Casts this reference to the concrete type `T`, if the underlying value is a `T`. + /// + /// Panics if the cast is not valid for this reference. + #[inline] + #[track_caller] + pub fn downcast_ref(&self) -> RawEntityRef + where + To: DowncastFromRef + 'static, + From: Is + AsAny + 'static, + Obj: ?Sized, + { + RawEntityRef::::downcast_from_ref(self) + } +} + +impl RawEntityRef { + pub fn try_downcast_from( + from: RawEntityRef, + ) -> Result> + where + From: ?Sized + 'static, + To: DowncastFromRef + 'static, + { + let borrow = from.borrow(); + >::downcast_from_ref(&*borrow) + .map(|to| unsafe { RawEntityRef::from_raw(to) }) + .ok_or(from) + } + + pub fn try_downcast_from_ref(from: &RawEntityRef) -> Option + where + From: ?Sized + Is + AsAny + 'static, + To: DowncastFromRef + 'static, + Obj: ?Sized, + { + let borrow = from.borrow(); + borrow.as_any().downcast_ref().map(|to| unsafe { RawEntityRef::from_raw(to) }) + } + + #[track_caller] + pub fn downcast_from(from: RawEntityRef) -> Self + where + From: ?Sized + Is + AsAny + 'static, + To: DowncastFromRef + 'static, + Obj: ?Sized, + { + let borrow = from.borrow(); + unsafe { RawEntityRef::from_raw(borrow.as_any().downcast_ref().expect("invalid cast")) } + } + + #[track_caller] + pub fn downcast_from_ref(from: &RawEntityRef) -> Self + where + From: ?Sized + Is + AsAny + 'static, + To: DowncastFromRef + 'static, + Obj: ?Sized, + { + let borrow = from.borrow(); + unsafe { RawEntityRef::from_raw(borrow.as_any().downcast_ref().expect("invalid cast")) } + } +} + +impl RawEntityRef { + /// Casts this reference to the an unsized type `Trait`, if `From` implements `Trait` + /// + /// If the cast is not valid for this reference, `Err` is returned containing the original value. + #[inline] + pub fn upcast(self) -> RawEntityRef + where + To: ?Sized, + From: core::marker::Unsize + AsAny + 'static, + { + unsafe { RawEntityRef::::from_inner(self.inner) } + } +} + +impl RawEntityRef { + /// Get a entity ref for the underlying [crate::Operation] data of an [Op]. + pub fn as_operation_ref(self) -> crate::OperationRef { + // SAFETY: This relies on the fact that we generate Op implementations such that the first + // field is always the [crate::Operation], and that the containing struct is #[repr(C)]. + unsafe { + let ptr = Self::into_raw(self); + crate::OperationRef::from_raw(ptr.cast()) + } + } +} + +impl core::ops::CoerceUnsized> + for RawEntityRef +where + T: ?Sized + core::marker::Unsize, + U: ?Sized, +{ +} +impl Eq for RawEntityRef {} +impl PartialEq for RawEntityRef { + #[inline] + fn eq(&self, other: &Self) -> bool { + Self::ptr_eq(self, other) + } +} +impl PartialOrd for RawEntityRef { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for RawEntityRef { + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.borrow().id().cmp(&other.borrow().id()) + } +} +impl core::hash::Hash for RawEntityRef { + fn hash(&self, state: &mut H) { + self.inner.as_ptr().addr().hash(state); + } +} +impl fmt::Pointer for RawEntityRef { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&Self::as_ptr(self), f) + } +} +impl fmt::Display for RawEntityRef { + #[inline] + default fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.borrow()) + } +} +impl fmt::Display for RawEntityRef { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.borrow().id()) + } +} + +impl fmt::Debug for RawEntityRef { + #[inline] + default fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.borrow(), f) + } +} +impl fmt::Debug for RawEntityRef { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.borrow().id()) + } +} +impl crate::formatter::PrettyPrint + for RawEntityRef +{ + #[inline] + fn render(&self) -> crate::formatter::Document { + self.borrow().render() + } +} +impl StorableEntity for RawEntityRef { + #[inline] + fn index(&self) -> usize { + self.borrow().index() + } + + #[inline] + unsafe fn set_index(&mut self, index: usize) { + unsafe { + self.borrow_mut().set_index(index); + } + } + + #[inline] + fn unlink(&mut self) { + self.borrow_mut().unlink() + } +} +impl crate::Spanned for RawEntityRef { + #[inline] + fn span(&self) -> crate::SourceSpan { + self.borrow().span() + } +} + +/// A guard that ensures a reference to an IR entity cannot be mutably aliased +pub struct EntityRef<'b, T: ?Sized + 'b> { + value: NonNull, + borrow: BorrowRef<'b>, +} +impl core::ops::Deref for EntityRef<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: the value is accessible as long as we hold our borrow. + unsafe { self.value.as_ref() } + } +} +impl<'b, T: ?Sized> EntityRef<'b, T> { + /// Map this reference to a derived reference. + #[inline] + pub fn map(orig: Self, f: F) -> EntityRef<'b, U> + where + F: FnOnce(&T) -> &U, + { + EntityRef { + value: NonNull::from(f(&*orig)), + borrow: orig.borrow, + } + } + + /// Project this borrow into a owned (but semantically borrowed) value that inherits ownership + /// of the underlying borrow. + pub fn project<'to, 'from: 'to, To, F>( + orig: EntityRef<'from, T>, + f: F, + ) -> EntityProjection<'from, To> + where + F: FnOnce(&'to T) -> To, + To: 'to, + { + EntityProjection { + value: f(unsafe { orig.value.as_ref() }), + borrow: orig.borrow, + } + } + + /// Try to convert this immutable borrow into a mutable borrow, if it is the only immutable borrow + pub fn into_entity_mut(self) -> Result, Self> { + let value = self.value; + match self.borrow.try_into_mut() { + Ok(borrow) => Ok(EntityMut { + value, + borrow, + _marker: core::marker::PhantomData, + }), + Err(borrow) => Err(Self { value, borrow }), + } + } + + pub fn into_borrow_ref(self) -> BorrowRef<'b> { + self.borrow + } + + pub fn from_raw_parts(value: NonNull, borrow: BorrowRef<'b>) -> Self { + Self { value, borrow } + } +} + +impl<'b, T, U> core::ops::CoerceUnsized> for EntityRef<'b, T> +where + T: ?Sized + core::marker::Unsize, + U: ?Sized, +{ +} + +impl fmt::Debug for EntityRef<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} +impl fmt::Display for EntityRef<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} +impl crate::formatter::PrettyPrint for EntityRef<'_, T> { + #[inline] + fn render(&self) -> crate::formatter::Document { + (**self).render() + } +} +impl Eq for EntityRef<'_, T> {} +impl PartialEq for EntityRef<'_, T> { + fn eq(&self, other: &Self) -> bool { + **self == **other + } +} +impl PartialOrd for EntityRef<'_, T> { + fn partial_cmp(&self, other: &Self) -> Option { + (**self).partial_cmp(&**other) + } + + fn ge(&self, other: &Self) -> bool { + **self >= **other + } + + fn gt(&self, other: &Self) -> bool { + **self > **other + } + + fn le(&self, other: &Self) -> bool { + **self <= **other + } + + fn lt(&self, other: &Self) -> bool { + **self < **other + } +} +impl Ord for EntityRef<'_, T> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + (**self).cmp(&**other) + } +} +impl Hash for EntityRef<'_, T> { + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +/// A guard that provides exclusive access to an IR entity +pub struct EntityMut<'b, T: ?Sized> { + /// The raw pointer to the underlying data + /// + /// This is a pointer rather than a `&'b mut T` to avoid `noalias` violations, because a + /// `EntityMut` argument doesn't hold exclusivity for its whole scope, only until it drops. + value: NonNull, + /// This value provides the drop glue for tracking that the underlying allocation is + /// mutably borrowed, but it is otherwise not read. + #[allow(unused)] + borrow: BorrowRefMut<'b>, + /// `NonNull` is covariant over `T`, so we need to reintroduce invariance via phantom data + _marker: core::marker::PhantomData<&'b mut T>, +} +impl<'b, T: ?Sized> EntityMut<'b, T> { + /// Map this mutable reference to a derived mutable reference. + #[inline] + pub fn map(mut orig: Self, f: F) -> EntityMut<'b, U> + where + F: FnOnce(&mut T) -> &mut U, + { + let value = NonNull::from(f(&mut *orig)); + EntityMut { + value, + borrow: orig.borrow, + _marker: core::marker::PhantomData, + } + } + + /// Project this mutable borrow into a owned (but semantically borrowed) value that inherits + /// ownership of the underlying mutable borrow. + pub fn project<'to, 'from: 'to, To, F>( + mut orig: EntityMut<'from, T>, + f: F, + ) -> EntityProjectionMut<'from, To> + where + F: FnOnce(&'to mut T) -> To, + To: 'to, + { + EntityProjectionMut { + value: f(unsafe { orig.value.as_mut() }), + borrow: orig.borrow, + } + } + + /// Splits an `EntityMut` into multiple `EntityMut`s for different components of the borrowed + /// data. + /// + /// The underlying entity will remain mutably borrowed until both returned `EntityMut`s go out + /// of scope. + /// + /// The entity is already mutably borrowed, so this cannot fail. + /// + /// This is an associated function that needs to be used as `EntityMut::map_split(...)`, so as + /// to avoid conflicting with any method of the same name accessible via the `Deref` impl. + /// + /// # Examples + /// + /// ```rust + /// use midenc_hir::*; + /// use blink_alloc::Blink; + /// + /// let alloc = Blink::default(); + /// let mut entity = UnsafeEntityRef::new([1, 2, 3, 4], &alloc); + /// let borrow = entity.borrow_mut(); + /// let (mut begin, mut end) = EntityMut::map_split(borrow, |slice| slice.split_at_mut(2)); + /// assert_eq!(*begin, [1, 2]); + /// assert_eq!(*end, [3, 4]); + /// begin.copy_from_slice(&[4, 3]); + /// end.copy_from_slice(&[2, 1]); + /// ``` + #[inline] + pub fn map_split( + mut orig: Self, + f: F, + ) -> (EntityMut<'b, U>, EntityMut<'b, V>) + where + F: FnOnce(&mut T) -> (&mut U, &mut V), + { + let borrow = orig.borrow.clone(); + let (a, b) = f(&mut *orig); + ( + EntityMut { + value: NonNull::from(a), + borrow, + _marker: core::marker::PhantomData, + }, + EntityMut { + value: NonNull::from(b), + borrow: orig.borrow, + _marker: core::marker::PhantomData, + }, + ) + } + + /// Convert this mutable borrow into an immutable borrow + pub fn into_entity_ref(self) -> EntityRef<'b, T> { + let value = self.value; + let borrow = self.into_borrow_ref_mut(); + + EntityRef { + value, + borrow: borrow.into_borrow_ref(), + } + } + + #[doc(hidden)] + pub(crate) fn into_borrow_ref_mut(self) -> BorrowRefMut<'b> { + self.borrow + } + + #[allow(unused)] + pub(crate) fn from_raw_parts(value: NonNull, borrow: BorrowRefMut<'b>) -> Self { + Self { + value, + borrow, + _marker: core::marker::PhantomData, + } + } +} +impl Deref for EntityMut<'_, T> { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + // SAFETY: the value is accessible as long as we hold our borrow. + unsafe { self.value.as_ref() } + } +} +impl DerefMut for EntityMut<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut T { + // SAFETY: the value is accessible as long as we hold our borrow. + unsafe { self.value.as_mut() } + } +} + +impl<'b, T, U> core::ops::CoerceUnsized> for EntityMut<'b, T> +where + T: ?Sized + core::marker::Unsize, + U: ?Sized, +{ +} + +impl fmt::Debug for EntityMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} +impl fmt::Display for EntityMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} +impl crate::formatter::PrettyPrint for EntityMut<'_, T> { + #[inline] + fn render(&self) -> crate::formatter::Document { + (**self).render() + } +} +impl Eq for EntityMut<'_, T> {} +impl PartialEq for EntityMut<'_, T> { + fn eq(&self, other: &Self) -> bool { + **self == **other + } +} +impl PartialOrd for EntityMut<'_, T> { + fn partial_cmp(&self, other: &Self) -> Option { + (**self).partial_cmp(&**other) + } + + fn ge(&self, other: &Self) -> bool { + **self >= **other + } + + fn gt(&self, other: &Self) -> bool { + **self > **other + } + + fn le(&self, other: &Self) -> bool { + **self <= **other + } + + fn lt(&self, other: &Self) -> bool { + **self < **other + } +} +impl Ord for EntityMut<'_, T> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + (**self).cmp(&**other) + } +} +impl Hash for EntityMut<'_, T> { + fn hash(&self, state: &mut H) { + (**self).hash(state); + } +} + +/// Represents a projection of an [EntityRef] into an owned value. +/// +/// This retains the lifetime of the borrow, while allowing the projection itself to be owned. This +/// is useful in cases where you would like to return a value derived from an [EntityRef] from a +/// function, when the borrowed entity outlives the function itself. Since [EntityRef] can only +/// represent references, deriving owned (but semantically borrowed) values from an [EntityRef] +/// cannot be returned from an enclosing function, unlike if the value was a reference. +/// +/// NOTE: An [EntityProjection] takes ownership of the [EntityRef] from which it was derived, and +/// propagates the lifetime of the borrowed entity. When this type is dropped, so is the original +/// borrow. +pub struct EntityProjection<'b, T: 'b> { + borrow: BorrowRef<'b>, + value: T, +} +impl core::ops::Deref for EntityProjection<'_, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.value + } +} +impl core::ops::DerefMut for EntityProjection<'_, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} +impl<'b, T> EntityProjection<'b, T> { + pub fn map(orig: Self, f: F) -> EntityProjection<'b, U> + where + F: FnOnce(T) -> U, + { + EntityProjection { + value: f(orig.value), + borrow: orig.borrow, + } + } +} +impl fmt::Debug for EntityProjection<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.value, f) + } +} +impl fmt::Display for EntityProjection<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.value, f) + } +} +impl crate::formatter::PrettyPrint for EntityProjection<'_, T> { + #[inline] + fn render(&self) -> crate::formatter::Document { + crate::formatter::PrettyPrint::render(&self.value) + } +} +impl Eq for EntityProjection<'_, T> {} +impl PartialEq for EntityProjection<'_, T> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} +impl PartialOrd for EntityProjection<'_, T> { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } + + fn ge(&self, other: &Self) -> bool { + self.value.ge(&other.value) + } + + fn gt(&self, other: &Self) -> bool { + self.value.gt(&other.value) + } + + fn le(&self, other: &Self) -> bool { + self.value.le(&other.value) + } + + fn lt(&self, other: &Self) -> bool { + self.value.lt(&other.value) + } +} +impl Ord for EntityProjection<'_, T> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.value.cmp(&other.value) + } +} +impl Hash for EntityProjection<'_, T> { + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + +/// Represents a projection of an [EntityMut] into an owned value. +/// +/// This retains the lifetime of the borrow, while allowing the projection itself to be owned. This +/// is useful in cases where you would like to return a value derived from an [EntityMut] from a +/// function, when the borrowed entity outlives the function itself. Since [EntityMut] can only +/// represent references, deriving owned (but semantically borrowed) values from an [EntityMut] +/// cannot be returned from an enclosing function, unlike if the value was a reference. +/// +/// NOTE: An [EntityProjectionMut] takes ownership of the [EntityMut] from which it was derived, and +/// propagates the lifetime of the borrowed entity. When this type is dropped, so is the original +/// borrow. +pub struct EntityProjectionMut<'b, T: 'b> { + borrow: BorrowRefMut<'b>, + value: T, +} +impl core::ops::Deref for EntityProjectionMut<'_, T> { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.value + } +} +impl core::ops::DerefMut for EntityProjectionMut<'_, T> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.value + } +} +impl<'b, T> EntityProjectionMut<'b, T> { + pub fn map(orig: Self, f: F) -> EntityProjectionMut<'b, U> + where + F: FnOnce(T) -> U, + { + EntityProjectionMut { + value: f(orig.value), + borrow: orig.borrow, + } + } +} +impl fmt::Debug for EntityProjectionMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.value, f) + } +} +impl fmt::Display for EntityProjectionMut<'_, T> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(&self.value, f) + } +} +impl crate::formatter::PrettyPrint + for EntityProjectionMut<'_, T> +{ + #[inline] + fn render(&self) -> crate::formatter::Document { + crate::formatter::PrettyPrint::render(&self.value) + } +} +impl Eq for EntityProjectionMut<'_, T> {} +impl PartialEq for EntityProjectionMut<'_, T> { + fn eq(&self, other: &Self) -> bool { + self.value == other.value + } +} +impl PartialOrd for EntityProjectionMut<'_, T> { + fn partial_cmp(&self, other: &Self) -> Option { + self.value.partial_cmp(&other.value) + } + + fn ge(&self, other: &Self) -> bool { + self.value.ge(&other.value) + } + + fn gt(&self, other: &Self) -> bool { + self.value.gt(&other.value) + } + + fn le(&self, other: &Self) -> bool { + self.value.le(&other.value) + } + + fn lt(&self, other: &Self) -> bool { + self.value.lt(&other.value) + } +} +impl Ord for EntityProjectionMut<'_, T> { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.value.cmp(&other.value) + } +} +impl Hash for EntityProjectionMut<'_, T> { + fn hash(&self, state: &mut H) { + self.value.hash(state) + } +} + +// This type wraps the entity data with extra metadata we want to associate with the entity, but +// separately from it, so that pointers to the metadata do not cause aliasing violations if the +// entity itself is borrowed. +// +// The kind of metadata stored here is unconstrained, but in practice should be limited to things +// that you _need_ to be able to access from a `RawEntityRef`, without aliasing the entity. For now +// the main reason we use this is for the intrusive link used to store entities in an intrusive +// linked list. We don't want traversing the intrusive list to require borrowing the entity, only +// the link, unless we explicitly want to borrow the entity, thus we use the metadata field here +// to hold the link. +// +// This has to be `pub` for implementing the traits required for the intrusive collections +// integration, but its internals are hidden outside this module, and we hide it from the generated +// docs as well. +#[repr(C)] +#[doc(hidden)] +pub struct RawEntityMetadata { + metadata: Metadata, + entity: RawEntity, +} +impl RawEntityMetadata { + pub(crate) fn new(value: T, metadata: Metadata) -> Self { + Self { + metadata, + entity: RawEntity::new(value), + } + } +} +impl RawEntityMetadata { + #[track_caller] + pub(crate) fn borrow(&self) -> EntityRef<'_, T> { + let ptr = self as *const Self; + unsafe { (*core::ptr::addr_of!((*ptr).entity)).borrow() } + } + + #[track_caller] + pub(crate) fn borrow_mut(&self) -> EntityMut<'_, T> { + let ptr = (self as *const Self).cast_mut(); + unsafe { (*core::ptr::addr_of_mut!((*ptr).entity)).borrow_mut() } + } + + #[inline] + const fn metadata_offset() -> usize { + core::mem::offset_of!(RawEntityMetadata<(), Metadata>, metadata) + } + + /// Get the offset within a `RawEntityMetadata` for the payload behind a pointer. + /// + /// # Safety + /// + /// The pointer must point to (and have valid metadata for) a previously valid instance of T, but + /// the T is allowed to be dropped. + unsafe fn data_offset(ptr: *const T) -> usize { + use core::mem::align_of_val_raw; + + // Align the unsized value to the end of the RawEntityMetadata. + // Because RawEntityMetadata/RawEntity is repr(C), it will always be the last field in memory. + // + // SAFETY: since the only unsized types possible are slices, trait objects, and extern types, + // the input safety requirement is currently enough to satisfy the requirements of + // align_of_val_raw; but this is an implementation detail of the language that is unstable + unsafe { RawEntityMetadata::<(), Metadata>::data_offset_align(align_of_val_raw(ptr)) } + } + + #[inline] + fn data_offset_align(align: usize) -> usize { + let layout = Layout::new::>(); + layout.size() + layout.padding_needed_for(align) + } +} + +fn raw_entity_metadata_layout_for_value_layout(layout: Layout) -> Layout { + Layout::new::>() + .extend(layout) + .unwrap() + .0 + .pad_to_align() +} + +/// A [RawEntity] wraps an entity to be allocated in a [Context], and provides dynamic borrow- +/// checking functionality for [UnsafeEntityRef], thereby protecting the entity by ensuring that +/// all accesses adhere to Rust's aliasing rules. +#[repr(C)] +pub struct RawEntity { + borrow: Cell, + #[cfg(debug_assertions)] + borrowed_at: Cell>>, + cell: UnsafeCell, +} + +impl RawEntity { + pub fn new(value: T) -> Self { + Self { + borrow: Cell::new(BorrowFlag::UNUSED), + #[cfg(debug_assertions)] + borrowed_at: Cell::new(None), + cell: UnsafeCell::new(value), + } + } + + #[allow(unused)] + #[doc(hidden)] + #[inline(always)] + pub(crate) fn entity_addr(&self) -> *mut T { + self.cell.get() + } + + #[allow(unused)] + #[doc(hidden)] + #[inline(always)] + pub(crate) const fn entity_offset(&self) -> usize { + core::mem::offset_of!(Self, cell) + } +} + +impl core::ops::CoerceUnsized> for RawEntity where + T: core::ops::CoerceUnsized +{ +} + +impl RawEntity { + /// Construct a borrow of this [RawEntity], returning the raw borrow components. + /// + /// # Safety + /// + /// Callers may only use the returned pointer while holding the corresponding [BorrowRef], + /// otherwise there is no protection against mutable aliasing of the underlying data. + #[track_caller] + #[allow(unused)] + pub unsafe fn borrow_unsafe(&self) -> (NonNull, BorrowRef<'_>) { + match self.try_borrow() { + Ok(b) => (b.value, b.into_borrow_ref()), + Err(err) => panic_aliasing_violation(err), + } + } + + /// Construct a mutable borrow of this [RawEntity], returning the raw borrow components. + /// + /// # Safety + /// + /// Callers may only use the returned pointer while holding the corresponding [BorrowRefMut], + /// otherwise there is no protection against mutable aliasing of the underlying data. + #[track_caller] + pub unsafe fn borrow_mut_unsafe(&self) -> (NonNull, BorrowRefMut<'_>) { + match self.try_borrow_mut() { + Ok(b) => (b.value, b.into_borrow_ref_mut()), + Err(err) => panic_aliasing_violation(err), + } + } + + #[track_caller] + #[inline] + pub fn borrow(&self) -> EntityRef<'_, T> { + match self.try_borrow() { + Ok(b) => b, + Err(err) => panic_aliasing_violation(err), + } + } + + #[inline] + #[track_caller] + pub fn try_borrow(&self) -> Result, AliasingViolationError> { + match BorrowRef::new(&self.borrow) { + Some(b) => { + #[cfg(debug_assertions)] + { + // `borrowed_at` is always the *first* active borrow + if b.borrow.get() == BorrowFlag(1) { + self.borrowed_at.set(Some(core::panic::Location::caller())); + } + } + + // SAFETY: `BorrowRef` ensures that there is only immutable access to the value + // while borrowed. + let value = unsafe { NonNull::new_unchecked(self.cell.get()) }; + Ok(EntityRef { value, borrow: b }) + } + None => Err(AliasingViolationError { + #[cfg(debug_assertions)] + location: self.borrowed_at.get().unwrap(), + kind: AliasingViolationKind::Immutable, + }), + } + } + + #[inline] + #[track_caller] + pub fn borrow_mut(&self) -> EntityMut<'_, T> { + match self.try_borrow_mut() { + Ok(b) => b, + Err(err) => panic_aliasing_violation(err), + } + } + + #[inline] + #[track_caller] + pub fn try_borrow_mut(&self) -> Result, AliasingViolationError> { + match BorrowRefMut::new(&self.borrow) { + Some(b) => { + #[cfg(debug_assertions)] + { + self.borrowed_at.set(Some(core::panic::Location::caller())); + } + + // SAFETY: `BorrowRefMut` guarantees unique access. + let value = unsafe { NonNull::new_unchecked(self.cell.get()) }; + Ok(EntityMut { + value, + borrow: b, + _marker: core::marker::PhantomData, + }) + } + None => Err(AliasingViolationError { + // If a borrow occurred, then we must already have an outstanding borrow, + // so `borrowed_at` will be `Some` + #[cfg(debug_assertions)] + location: self.borrowed_at.get().unwrap(), + kind: AliasingViolationKind::Mutable, + }), + } + } +} + +#[doc(hidden)] +pub struct BorrowRef<'b> { + borrow: &'b Cell, +} +impl<'b> BorrowRef<'b> { + #[inline] + fn new(borrow: &'b Cell) -> Option { + let b = borrow.get().wrapping_add(1); + if !b.is_reading() { + // Incrementing borrow can result in a non-reading value (<= 0) in these cases: + // 1. It was < 0, i.e. there are writing borrows, so we can't allow a read borrow due to + // Rust's reference aliasing rules + // 2. It was isize::MAX (the max amount of reading borrows) and it overflowed into + // isize::MIN (the max amount of writing borrows) so we can't allow an additional + // read borrow because isize can't represent so many read borrows (this can only + // happen if you mem::forget more than a small constant amount of `EntityRef`s, which + // is not good practice) + None + } else { + // Incrementing borrow can result in a reading value (> 0) in these cases: + // 1. It was = 0, i.e. it wasn't borrowed, and we are taking the first read borrow + // 2. It was > 0 and < isize::MAX, i.e. there were read borrows, and isize is large + // enough to represent having one more read borrow + borrow.set(b); + Some(Self { borrow }) + } + } + + /// Convert this immutable borrow into a mutable one, if it is the sole immutable borrow. + pub fn try_into_mut(self) -> Result, Self> { + use core::mem::ManuallyDrop; + + // Ensure we don't try to modify the borrow flag when this BorrowRef goes out of scope + let this = ManuallyDrop::new(self); + let b = this.borrow.get(); + debug_assert!(b.is_reading()); + if (b - 1).is_unused() { + this.borrow.set(BorrowFlag::UNUSED - 1); + Ok(BorrowRefMut { + borrow: this.borrow, + }) + } else { + Err(ManuallyDrop::into_inner(this)) + } + } +} +impl Drop for BorrowRef<'_> { + #[inline] + fn drop(&mut self) { + let borrow = self.borrow.get(); + debug_assert!(borrow.is_reading()); + self.borrow.set(borrow - 1); + } +} +impl Clone for BorrowRef<'_> { + #[inline] + fn clone(&self) -> Self { + // Since this Ref exists, we know the borrow flag + // is a reading borrow. + let borrow = self.borrow.get(); + debug_assert!(borrow.is_reading()); + // Prevent the borrow counter from overflowing into + // a writing borrow. + assert!(borrow != BorrowFlag::MAX); + self.borrow.set(borrow + 1); + BorrowRef { + borrow: self.borrow, + } + } +} + +#[doc(hidden)] +pub struct BorrowRefMut<'b> { + borrow: &'b Cell, +} +impl Drop for BorrowRefMut<'_> { + #[inline] + fn drop(&mut self) { + let borrow = self.borrow.get(); + debug_assert!(borrow.is_writing()); + self.borrow.set(borrow + 1); + } +} +impl<'b> BorrowRefMut<'b> { + /// Consume this mutable borrow and convert it into a immutable one without releasing it + pub fn into_borrow_ref(self) -> BorrowRef<'b> { + let borrow = self.borrow; + debug_assert!(borrow.get().is_writing()); + borrow.set(borrow.get() + 2); + core::mem::forget(self); + BorrowRef { borrow } + } + + #[inline] + fn new(borrow: &'b Cell) -> Option { + // NOTE: Unlike BorrowRefMut::clone, new is called to create the initial + // mutable reference, and so there must currently be no existing + // references. Thus, while clone increments the mutable refcount, here + // we explicitly only allow going from UNUSED to UNUSED - 1. + match borrow.get() { + BorrowFlag::UNUSED => { + borrow.set(BorrowFlag::UNUSED - 1); + Some(Self { borrow }) + } + _ => None, + } + } + + // Clones a `BorrowRefMut`. + // + // This is only valid if each `BorrowRefMut` is used to track a mutable + // reference to a distinct, nonoverlapping range of the original object. + // This isn't in a Clone impl so that code doesn't call this implicitly. + #[inline] + fn clone(&self) -> Self { + let borrow = self.borrow.get(); + debug_assert!(borrow.is_writing()); + // Prevent the borrow counter from underflowing. + assert!(borrow != BorrowFlag::MIN); + self.borrow.set(borrow - 1); + Self { + borrow: self.borrow, + } + } +} + +/// Positive values represent the number of outstanding immutable borrows, while negative values +/// represent the number of outstanding mutable borrows. Multiple mutable borrows can only be +/// active simultaneously if they refer to distinct, non-overlapping components of an entity. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +struct BorrowFlag(isize); +impl BorrowFlag { + const MAX: Self = Self(isize::MAX); + const MIN: Self = Self(isize::MIN); + const UNUSED: Self = Self(0); + + #[allow(unused)] + pub fn is_unused(&self) -> bool { + self.0 == Self::UNUSED.0 + } + + pub fn is_writing(&self) -> bool { + self.0 < Self::UNUSED.0 + } + + pub fn is_reading(&self) -> bool { + self.0 > Self::UNUSED.0 + } + + #[inline] + pub const fn wrapping_add(self, rhs: isize) -> Self { + Self(self.0.wrapping_add(rhs)) + } +} +impl core::ops::Add for BorrowFlag { + type Output = BorrowFlag; + + #[inline] + fn add(self, rhs: isize) -> Self::Output { + Self(self.0 + rhs) + } +} +impl core::ops::Sub for BorrowFlag { + type Output = BorrowFlag; + + #[inline] + fn sub(self, rhs: isize) -> Self::Output { + Self(self.0 - rhs) + } +} + +// This ensures the panicking code is outlined from `borrow` and `borrow_mut` for `EntityObj`. +#[cfg_attr(not(panic = "abort"), inline(never))] +#[track_caller] +#[cold] +fn panic_aliasing_violation(err: AliasingViolationError) -> ! { + panic!("{err:?}") +} diff --git a/hir/src/ir/entity/group.rs b/hir/src/ir/entity/group.rs new file mode 100644 index 000000000..f3e8bb69c --- /dev/null +++ b/hir/src/ir/entity/group.rs @@ -0,0 +1,217 @@ +use core::fmt; + +/// Represents size and range information for a contiguous grouping of entities in a vector. +/// +/// This is used so that individual groups can be grown or shrunk, while maintaining stability +/// of references to items in other groups. +#[derive(Default, Copy, Clone)] +pub struct EntityGroup(u16); +impl fmt::Debug for EntityGroup { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("EntityGroup") + .field("range", &self.as_range()) + .field("len", &self.len()) + .finish() + } +} +impl EntityGroup { + const START_MASK: u16 = u8::MAX as u16; + + /// Create a new group of size `len`, starting at index `start` + pub fn new(start: usize, len: usize) -> Self { + let start = u16::try_from(start).expect("too many items"); + let len = u16::try_from(len).expect("group too large"); + let group = start | (len << 8); + + Self(group) + } + + /// Get the start index in the containing vector + #[inline] + pub fn start(&self) -> usize { + (self.0 & Self::START_MASK) as usize + } + + /// Get the end index (exclusive) in the containing vector + #[inline] + pub fn end(&self) -> usize { + self.start() + self.len() + } + + /// Returns true if this group is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Get the number of items in this group + #[inline] + pub fn len(&self) -> usize { + (self.0 >> 8) as usize + } + + /// Get the [core::ops::Range] equivalent of this group + pub fn as_range(&self) -> core::ops::Range { + let start = self.start(); + let len = self.len(); + start..(start + len) + } + + /// Increase the size of this group by `n` items + /// + /// Panics if `n` overflows `u16::MAX`, or if the resulting size overflows `u8::MAX` + pub fn grow(&mut self, n: usize) { + let n = u16::try_from(n).expect("group is too large"); + let start = self.0 & Self::START_MASK; + let len = (self.0 >> 8) + n; + assert!(len <= u8::MAX as u16, "group is too large"); + self.0 = start | (len << 8); + } + + /// Decrease the size of this group by `n` items + /// + /// Panics if `n` overflows `u16::MAX`, or if `n` is greater than the number of remaining items. + pub fn shrink(&mut self, n: usize) { + let n = u16::try_from(n).expect("cannot shrink by a size larger than the max group size"); + let start = self.0 & Self::START_MASK; + let len = (self.0 >> 8).saturating_sub(n); + self.0 = start | (len << 8); + } + + /// Shift the position of this group by `offset` + pub fn shift_start(&mut self, offset: isize) { + let offset = i16::try_from(offset).expect("offset too large"); + let mut start = self.0 & Self::START_MASK; + if offset >= 0 { + start += offset as u16; + } else { + start -= offset.unsigned_abs(); + } + assert!(start <= Self::START_MASK, "group offset cannot be larger than u8::MAX"); + self.0 &= !Self::START_MASK; + self.0 |= start; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn entity_group_empty() { + let group = EntityGroup::new(0, 0); + assert_eq!(group.start(), 0); + assert_eq!(group.end(), 0); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 0..0); + + let group = EntityGroup::new(101, 0); + assert_eq!(group.start(), 101); + assert_eq!(group.end(), 101); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 101..101); + } + + #[test] + fn entity_group_non_empty() { + let group = EntityGroup::new(0, 1); + assert_eq!(group.start(), 0); + assert_eq!(group.end(), 1); + assert_eq!(group.len(), 1); + assert!(!group.is_empty()); + assert_eq!(group.as_range(), 0..1); + + let group = EntityGroup::new(255, 255); + assert_eq!(group.start(), 255); + assert_eq!(group.end(), 510); + assert_eq!(group.len(), 255); + assert!(!group.is_empty()); + assert_eq!(group.as_range(), 255..510); + } + + #[test] + fn entity_group_grow() { + let mut group = EntityGroup::new(10, 0); + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 10); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 10..10); + + group.grow(1); + + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 11); + assert_eq!(group.len(), 1); + assert!(!group.is_empty()); + assert_eq!(group.as_range(), 10..11); + + group.grow(3); + + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 14); + assert_eq!(group.len(), 4); + assert!(!group.is_empty()); + assert_eq!(group.as_range(), 10..14); + } + + #[test] + fn entity_group_shrink() { + let mut group = EntityGroup::new(10, 4); + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 14); + assert_eq!(group.len(), 4); + assert!(!group.is_empty()); + assert_eq!(group.as_range(), 10..14); + + group.shrink(3); + + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 11); + assert_eq!(group.len(), 1); + assert!(!group.is_empty()); + assert_eq!(group.as_range(), 10..11); + + group.shrink(1); + + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 10); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 10..10); + + group.shrink(1); + + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 10); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 10..10); + } + + #[test] + fn entity_group_shift_start() { + let mut group = EntityGroup::new(10, 0); + assert_eq!(group.start(), 10); + assert_eq!(group.end(), 10); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 10..10); + + group.shift_start(10); + assert_eq!(group.start(), 20); + assert_eq!(group.end(), 20); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 20..20); + + group.shift_start(-5); + assert_eq!(group.start(), 15); + assert_eq!(group.end(), 15); + assert_eq!(group.len(), 0); + assert!(group.is_empty()); + assert_eq!(group.as_range(), 15..15); + } +} diff --git a/hir/src/ir/entity/list.rs b/hir/src/ir/entity/list.rs new file mode 100644 index 000000000..f42ef8330 --- /dev/null +++ b/hir/src/ir/entity/list.rs @@ -0,0 +1,1256 @@ +use core::{fmt, mem::MaybeUninit, ptr::NonNull}; + +use super::{ + EntityListItem, EntityMut, EntityParent, EntityRef, EntityWithParent, IntrusiveLink, + RawEntityMetadata, RawEntityRef, UnsafeIntrusiveEntityRef, +}; + +pub struct EntityList { + list: intrusive_collections::linked_list::LinkedList>, +} +impl Default for EntityList { + fn default() -> Self { + Self { + list: Default::default(), + } + } +} +impl EntityList { + /// Construct a new, empty [EntityList] + pub fn new() -> Self { + Self::default() + } + + /// Returns true if this list is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.list.is_empty() + } + + /// Returns the number of entities in this list + pub fn len(&self) -> usize { + let mut cursor = self.list.front(); + let mut usize = 0; + while !cursor.is_null() { + usize += 1; + cursor.move_next(); + } + usize + } + + #[doc(hidden)] + pub fn cursor(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.list.cursor(), + } + } + + /// Get an [EntityCursor] pointing to the first entity in the list, or the null object if + /// the list is empty + pub fn front(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.list.front(), + } + } + + /// Get an [EntityCursor] pointing to the last entity in the list, or the null object if + /// the list is empty + pub fn back(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.list.back(), + } + } + + /// Get an iterator over the entities in this list + /// + /// The iterator returned produces [EntityRef]s for each item in the list, with their lifetime + /// bound to the list itself, not the iterator. + pub fn iter(&self) -> EntityIter<'_, T> { + EntityIter { + cursor: self.cursor(), + started: false, + } + } + + /// Get a cursor to the item pointed to by `ptr`. + /// + /// # Safety + /// + /// This function may only be called when it is known that `ptr` refers to an entity which is + /// linked into this list. This operation will panic if the entity is not linked into any list, + /// and may result in undefined behavior if the operation is linked into a different list. + pub unsafe fn cursor_from_ptr(&self, ptr: UnsafeIntrusiveEntityRef) -> EntityCursor<'_, T> { + unsafe { + let raw = UnsafeIntrusiveEntityRef::into_inner(ptr).as_ptr(); + EntityCursor { + cursor: self.list.cursor_from_ptr(raw), + } + } + } +} + +impl EntityList +where + T: EntityWithParent, +{ + pub(crate) fn parent(&self) -> UnsafeIntrusiveEntityRef<::Parent> { + let offset = <::Parent as EntityParent>::offset(); + let ptr = self as *const EntityList; + unsafe { + let parent = ptr.byte_sub(offset).cast::<::Parent>(); + UnsafeIntrusiveEntityRef::from_raw(parent) + } + } +} + +trait EntityListTraits: Sized { + fn cursor_mut(&mut self) -> EntityCursorMut<'_, T>; + + /// Get a mutable cursor to the item pointed to by `ptr`. + /// + /// # Safety + /// + /// This function may only be called when it is known that `ptr` refers to an entity which is + /// linked into this list. This operation will panic if the entity is not linked into any list, + /// and may result in undefined behavior if the operation is linked into a different list. + unsafe fn cursor_mut_from_ptr( + &mut self, + ptr: UnsafeIntrusiveEntityRef, + ) -> EntityCursorMut<'_, T>; + + /// Get an [EntityCursorMut] pointing to the first entity in the list, or the null object if + /// the list is empty + fn front_mut(&mut self) -> EntityCursorMut<'_, T>; + + /// Get an [EntityCursorMut] pointing to the last entity in the list, or the null object if + /// the list is empty + fn back_mut(&mut self) -> EntityCursorMut<'_, T>; + + fn remove(cursor: &mut EntityCursorMut<'_, T>) -> Option>; + + fn replace_with( + cursor: &mut EntityCursorMut<'_, T>, + entity: UnsafeIntrusiveEntityRef, + ) -> Result, UnsafeIntrusiveEntityRef>; + + /// Prepend `entity` to this list + fn push_front(list: &mut EntityList, entity: UnsafeIntrusiveEntityRef); + + /// Append `entity` to this list + fn push_back(list: &mut EntityList, entity: UnsafeIntrusiveEntityRef); + + fn insert_after(cursor: &mut EntityCursorMut<'_, T>, entity: UnsafeIntrusiveEntityRef); + + fn insert_before(cursor: &mut EntityCursorMut<'_, T>, entity: UnsafeIntrusiveEntityRef); + + /// This splices `list` into the underlying list of `self` by inserting the elements of `list` + /// after the current cursor position. + /// + /// For example, let's say we have the following list and cursor position: + /// + /// ```text,ignore + /// [A, B, C] + /// ^-- cursor + /// ``` + /// + /// Splicing a new list, `[D, E, F]` after the cursor would result in: + /// + /// ```text,ignore + /// [A, B, D, E, F, C] + /// ^-- cursor + /// ``` + /// + /// If the cursor is pointing at the null object, then `list` is appended to the start of the + /// underlying [EntityList] for this cursor. + fn splice_after(cursor: &mut EntityCursorMut<'_, T>, list: EntityList); + + /// This splices `list` into the underlying list of `self` by inserting the elements of `list` + /// before the current cursor position. + /// + /// For example, let's say we have the following list and cursor position: + /// + /// ```text,ignore + /// [A, B, C] + /// ^-- cursor + /// ``` + /// + /// Splicing a new list, `[D, E, F]` before the cursor would result in: + /// + /// ```text,ignore + /// [A, D, E, F, B, C] + /// ^-- cursor + /// ``` + /// + /// If the cursor is pointing at the null object, then `list` is appended to the end of the + /// underlying [EntityList] for this cursor. + fn splice_before(cursor: &mut EntityCursorMut<'_, T>, list: EntityList); + + /// Splits the list into two after the current cursor position. + /// + /// This will return a new list consisting of everything after the cursor, with the original + /// list retaining everything before. + /// + /// If the cursor is pointing at the null object then the entire contents of the [EntityList] + /// are moved. + fn split_after(cursor: &mut EntityCursorMut<'_, T>) -> EntityList; + + /// Splits the list into two before the current cursor position. + /// + /// This will return a new list consisting of everything before the cursor, with the original + /// list retaining everything after. + /// + /// If the cursor is pointing at the null object then the entire contents of the [EntityList] + /// are moved. + fn split_before(cursor: &mut EntityCursorMut<'_, T>) -> EntityList; + + /// Remove the entity at the front of the list, returning its [TrackedEntityHandle] + /// + /// Returns `None` if the list is empty. + fn pop_front(&mut self) -> Option>; + + /// Remove the entity at the back of the list, returning its [TrackedEntityHandle] + /// + /// Returns `None` if the list is empty. + fn pop_back(&mut self) -> Option>; + + /// Removes all items from this list. + /// + /// This will unlink all entities currently in the list, which requires iterating through all + /// elements in the list. If the entities may be used again, this ensures that their intrusive + /// link is properly unlinked. + fn clear(&mut self); + + /// Takes all the elements out of the [EntityList], leaving it empty. + /// + /// The taken elements are returned as a new [EntityList]. + fn take(&mut self) -> Self; +} + +impl EntityListTraits for EntityList { + default fn cursor_mut(&mut self) -> EntityCursorMut<'_, T> { + EntityCursorMut { + cursor: self.list.cursor_mut(), + parent: core::ptr::null(), + } + } + + default unsafe fn cursor_mut_from_ptr( + &mut self, + ptr: UnsafeIntrusiveEntityRef, + ) -> EntityCursorMut<'_, T> { + let raw = UnsafeIntrusiveEntityRef::into_inner(ptr).as_ptr(); + unsafe { + EntityCursorMut { + cursor: self.list.cursor_mut_from_ptr(raw), + parent: core::ptr::null(), + } + } + } + + /// Get an [EntityCursorMut] pointing to the first entity in the list, or the null object if + /// the list is empty + default fn front_mut(&mut self) -> EntityCursorMut<'_, T> { + EntityCursorMut { + cursor: self.list.front_mut(), + parent: core::ptr::null(), + } + } + + /// Get an [EntityCursorMut] pointing to the last entity in the list, or the null object if + /// the list is empty + default fn back_mut(&mut self) -> EntityCursorMut<'_, T> { + EntityCursorMut { + cursor: self.list.back_mut(), + parent: core::ptr::null(), + } + } + + default fn remove(cursor: &mut EntityCursorMut<'_, T>) -> Option> { + let entity = cursor.cursor.remove()?; + ::on_removed(entity, cursor); + Some(entity) + } + + default fn replace_with( + cursor: &mut EntityCursorMut<'_, T>, + entity: UnsafeIntrusiveEntityRef, + ) -> Result, UnsafeIntrusiveEntityRef> { + let removed = cursor.cursor.replace_with(entity)?; + ::on_removed(removed, cursor); + ::on_inserted(entity, cursor); + Ok(removed) + } + + default fn push_front(list: &mut EntityList, entity: UnsafeIntrusiveEntityRef) { + list.list.push_front(entity); + ::on_inserted(entity, &mut list.front_mut()); + } + + default fn push_back(list: &mut EntityList, entity: UnsafeIntrusiveEntityRef) { + list.list.push_back(entity); + ::on_inserted(entity, &mut list.back_mut()); + } + + default fn insert_after( + cursor: &mut EntityCursorMut<'_, T>, + entity: UnsafeIntrusiveEntityRef, + ) { + cursor.cursor.insert_after(entity); + ::on_inserted(entity, cursor); + } + + default fn insert_before( + cursor: &mut EntityCursorMut<'_, T>, + entity: UnsafeIntrusiveEntityRef, + ) { + cursor.cursor.insert_before(entity); + ::on_inserted(entity, cursor); + } + + default fn splice_after(cursor: &mut EntityCursorMut<'_, T>, mut list: EntityList) { + while let Some(entity) = list.list.pop_back() { + Self::insert_after(cursor, entity) + } + } + + default fn splice_before(cursor: &mut EntityCursorMut<'_, T>, mut list: EntityList) { + while let Some(entity) = list.list.pop_front() { + Self::insert_before(cursor, entity) + } + } + + default fn split_after(cursor: &mut EntityCursorMut<'_, T>) -> EntityList { + let list = cursor.cursor.split_after(); + let mut list_cursor = list.front(); + while let Some(entity) = list_cursor.clone_pointer() { + ::on_removed(entity, cursor); + list_cursor.move_next(); + } + Self { list } + } + + default fn split_before(cursor: &mut EntityCursorMut<'_, T>) -> EntityList { + let list = cursor.cursor.split_before(); + let mut list_cursor = list.front(); + while let Some(entity) = list_cursor.clone_pointer() { + ::on_removed(entity, cursor); + list_cursor.move_next(); + } + Self { list } + } + + default fn pop_front(&mut self) -> Option> { + let removed = self.list.pop_front()?; + ::on_removed(removed, &mut self.front_mut()); + Some(removed) + } + + default fn pop_back(&mut self) -> Option> { + let removed = self.list.pop_back()?; + ::on_removed(removed, &mut self.back_mut()); + Some(removed) + } + + default fn clear(&mut self) { + while self.pop_front().is_some() {} + } + + default fn take(&mut self) -> Self { + let list = self.list.take(); + let mut list_cursor = list.front(); + while let Some(entity) = list_cursor.clone_pointer() { + ::on_removed(entity, &mut self.front_mut()); + list_cursor.move_next(); + } + Self { list } + } +} + +impl EntityListTraits for EntityList +where + T: EntityListItem + EntityWithParent, + ::Parent: EntityParent, +{ + fn cursor_mut(&mut self) -> EntityCursorMut<'_, T> { + let parent = UnsafeIntrusiveEntityRef::into_raw(self.parent()).cast(); + EntityCursorMut { + cursor: self.list.cursor_mut(), + parent, + } + } + + unsafe fn cursor_mut_from_ptr( + &mut self, + ptr: UnsafeIntrusiveEntityRef, + ) -> EntityCursorMut<'_, T> { + let parent = UnsafeIntrusiveEntityRef::into_raw(self.parent()).cast(); + let raw = UnsafeIntrusiveEntityRef::into_inner(ptr).as_ptr(); + EntityCursorMut { + cursor: self.list.cursor_mut_from_ptr(raw), + parent, + } + } + + fn back_mut(&mut self) -> EntityCursorMut<'_, T> { + let parent = UnsafeIntrusiveEntityRef::into_raw(self.parent()).cast(); + EntityCursorMut { + cursor: self.list.back_mut(), + parent, + } + } + + fn front_mut(&mut self) -> EntityCursorMut<'_, T> { + let parent = UnsafeIntrusiveEntityRef::into_raw(self.parent()).cast(); + EntityCursorMut { + cursor: self.list.front_mut(), + parent, + } + } + + #[track_caller] + fn remove(cursor: &mut EntityCursorMut<'_, T>) -> Option> { + let entity = cursor.cursor.remove()?; + entity.set_parent(None); + ::on_removed(entity, cursor); + Some(entity) + } + + fn replace_with( + cursor: &mut EntityCursorMut<'_, T>, + entity: UnsafeIntrusiveEntityRef, + ) -> Result, UnsafeIntrusiveEntityRef> { + let removed = cursor.cursor.replace_with(entity)?; + removed.set_parent(None); + ::on_removed(removed, cursor); + let parent = cursor.parent().expect("cannot insert items in an orphaned entity list"); + entity.set_parent(Some(parent)); + ::on_inserted(entity, cursor); + Ok(removed) + } + + fn push_front(list: &mut EntityList, entity: UnsafeIntrusiveEntityRef) { + let parent = list.parent(); + list.list.push_front(entity); + entity.set_parent(Some(parent)); + ::on_inserted(entity, &mut list.front_mut()); + } + + fn push_back(list: &mut EntityList, entity: UnsafeIntrusiveEntityRef) { + let parent = list.parent(); + list.list.push_back(entity); + entity.set_parent(Some(parent)); + ::on_inserted(entity, &mut list.back_mut()); + } + + fn insert_after(cursor: &mut EntityCursorMut<'_, T>, entity: UnsafeIntrusiveEntityRef) { + cursor.cursor.insert_after(entity); + let parent = cursor.parent().expect("cannot insert items in an orphaned entity list"); + entity.set_parent(Some(parent)); + ::on_inserted(entity, cursor); + } + + fn insert_before(cursor: &mut EntityCursorMut<'_, T>, entity: UnsafeIntrusiveEntityRef) { + cursor.cursor.insert_before(entity); + let parent = cursor.parent().expect("cannot insert items in an orphaned entity list"); + entity.set_parent(Some(parent)); + ::on_inserted(entity, cursor); + } + + fn splice_after(cursor: &mut EntityCursorMut<'_, T>, mut list: EntityList) { + let parent = cursor.parent().expect("cannot insert items in an orphaned entity list"); + while let Some(entity) = list.list.pop_back() { + cursor.cursor.insert_after(entity); + entity.set_parent(Some(parent)); + ::on_inserted(entity, cursor); + } + } + + fn splice_before(cursor: &mut EntityCursorMut<'_, T>, mut list: EntityList) { + let parent = cursor.parent().expect("cannot insert items in an orphaned entity list"); + while let Some(entity) = list.list.pop_front() { + cursor.cursor.insert_before(entity); + entity.set_parent(Some(parent)); + ::on_inserted(entity, cursor); + } + } + + fn split_after(cursor: &mut EntityCursorMut<'_, T>) -> EntityList { + let list = cursor.cursor.split_after(); + let mut list_cursor = list.front(); + while let Some(entity) = list_cursor.clone_pointer() { + entity.set_parent(None); + ::on_removed(entity, cursor); + list_cursor.move_next(); + } + Self { list } + } + + fn split_before(cursor: &mut EntityCursorMut<'_, T>) -> EntityList { + let list = cursor.cursor.split_before(); + let mut list_cursor = list.front(); + while let Some(entity) = list_cursor.clone_pointer() { + entity.set_parent(None); + ::on_removed(entity, cursor); + list_cursor.move_next(); + } + Self { list } + } + + fn pop_front(&mut self) -> Option> { + let removed = self.list.pop_front()?; + removed.set_parent(None); + ::on_removed(removed, &mut self.front_mut()); + Some(removed) + } + + fn pop_back(&mut self) -> Option> { + let removed = self.list.pop_back()?; + removed.set_parent(None); + ::on_removed(removed, &mut self.back_mut()); + Some(removed) + } + + fn clear(&mut self) { + while self.pop_front().is_some() {} + } + + #[track_caller] + fn take(&mut self) -> Self { + let list = self.list.take(); + let mut list_cursor = list.front(); + while let Some(entity) = list_cursor.clone_pointer() { + entity.set_parent(None); + ::on_removed(entity, &mut self.front_mut()); + list_cursor.move_next(); + } + Self { list } + } +} + +impl EntityList { + #[doc(hidden)] + pub fn cursor_mut(&mut self) -> EntityCursorMut<'_, T> { + >::cursor_mut(self) + } + + /// Get a mutable cursor to the item pointed to by `ptr`. + /// + /// # Safety + /// + /// This function may only be called when it is known that `ptr` refers to an entity which is + /// linked into this list. This operation will panic if the entity is not linked into any list, + /// and may result in undefined behavior if the operation is linked into a different list. + pub unsafe fn cursor_mut_from_ptr( + &mut self, + ptr: UnsafeIntrusiveEntityRef, + ) -> EntityCursorMut<'_, T> { + >::cursor_mut_from_ptr(self, ptr) + } + + /// Get an [EntityCursorMut] pointing to the first entity in the list, or the null object if + /// the list is empty + pub fn front_mut(&mut self) -> EntityCursorMut<'_, T> { + >::front_mut(self) + } + + /// Get an [EntityCursorMut] pointing to the last entity in the list, or the null object if + /// the list is empty + pub fn back_mut(&mut self) -> EntityCursorMut<'_, T> { + >::back_mut(self) + } + + /// Prepend `entity` to this list + pub fn push_front(&mut self, entity: UnsafeIntrusiveEntityRef) { + >::push_front(self, entity) + } + + /// Append `entity` to this list + pub fn push_back(&mut self, entity: UnsafeIntrusiveEntityRef) { + >::push_back(self, entity) + } + + /// Remove the entity at the front of the list, returning its [TrackedEntityHandle] + /// + /// Returns `None` if the list is empty. + pub fn pop_front(&mut self) -> Option> { + >::pop_front(self) + } + + /// Remove the entity at the back of the list, returning its [TrackedEntityHandle] + /// + /// Returns `None` if the list is empty. + pub fn pop_back(&mut self) -> Option> { + >::pop_back(self) + } + + /// Removes all items from this list. + /// + /// This will unlink all entities currently in the list, which requires iterating through all + /// elements in the list. If the entities may be used again, this ensures that their intrusive + /// link is properly unlinked. + pub fn clear(&mut self) { + >::clear(self) + } + + /// Empties the list without properly unlinking the intrusive links of the items in the list. + /// + /// Since this does not unlink any objects, any attempts to link these objects into another + /// [EntityList] will fail but will not cause any memory unsafety. To unlink those objects + /// manually, you must call the `force_unlink` function on the link. + pub fn fast_clear(&mut self) { + self.list.fast_clear(); + } + + /// Takes all the elements out of the [EntityList], leaving it empty. + /// + /// The taken elements are returned as a new [EntityList]. + #[track_caller] + pub fn take(&mut self) -> Self { + >::take(self) + } +} + +impl fmt::Debug for EntityList { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut builder = f.debug_list(); + for entity in self.iter() { + builder.entry(&entity); + } + builder.finish() + } +} + +impl FromIterator> for EntityList { + fn from_iter(iter: I) -> Self + where + I: IntoIterator>, + { + let mut list = EntityList::::default(); + for handle in iter { + list.push_back(handle); + } + list + } +} + +impl IntoIterator for EntityList { + type IntoIter = intrusive_collections::linked_list::IntoIter>; + type Item = UnsafeIntrusiveEntityRef; + + fn into_iter(self) -> Self::IntoIter { + self.list.into_iter() + } +} + +impl<'a, T> IntoIterator for &'a EntityList { + type IntoIter = EntityIter<'a, T>; + type Item = EntityRef<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +/// A cursor which provides read-only access to an [EntityList]. +pub struct EntityCursor<'a, T> { + cursor: intrusive_collections::linked_list::Cursor<'a, EntityAdapter>, +} +impl<'a, T> EntityCursor<'a, T> { + /// Returns true if this cursor is pointing to the null object + #[inline] + pub fn is_null(&self) -> bool { + self.cursor.is_null() + } + + /// Get a shared reference to the entity under the cursor. + /// + /// Returns `None` if the cursor is currently pointing to the null object. + /// + /// NOTE: This returns an [EntityRef] whose lifetime is bound to the underlying [EntityList], + /// _not_ the [EntityCursor], since the cursor cannot mutate the list. + #[track_caller] + pub fn get(&self) -> Option> { + Some(self.cursor.get()?.entity.borrow()) + } + + /// Get the [TrackedEntityHandle] corresponding to the entity under the cursor. + /// + /// Returns `None` if the cursor is pointing to the null object. + #[inline] + pub fn as_pointer(&self) -> Option> { + self.cursor.clone_pointer() + } + + /// Consume the cursor and convert it into a borrow of the current entity, or `None` if null. + #[inline] + #[track_caller] + pub fn into_borrow(self) -> Option> { + Some(self.cursor.get()?.borrow()) + } + + /// Moves the cursor to the next element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will move it to the front of the + /// [EntityList]. If it is pointing to the back of the [EntityList] then this will move it to + /// the null object. + #[inline] + pub fn move_next(&mut self) { + self.cursor.move_next(); + } + + /// Moves the cursor to the previous element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will move it to the back of the + /// [EntityList]. If it is pointing to the front of the [EntityList] then this will move it to + /// the null object. + #[inline] + pub fn move_prev(&mut self) { + self.cursor.move_prev(); + } + + /// Returns a cursor pointing to the next element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will return a cursor pointing to the + /// front of the [EntityList]. If it is pointing to the last entity of the [EntityList] then + /// this will return a null cursor. + #[inline] + pub fn peek_next(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.cursor.peek_next(), + } + } + + /// Returns a cursor pointing to the previous element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will return a cursor pointing to + /// the last entity in the [EntityList]. If it is pointing to the front of the [EntityList] then + /// this will return a null cursor. + #[inline] + pub fn peek_prev(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.cursor.peek_prev(), + } + } +} + +/// A cursor which provides mutable access to an [EntityList]. +pub struct EntityCursorMut<'a, T> { + cursor: intrusive_collections::linked_list::CursorMut<'a, EntityAdapter>, + parent: *const (), +} + +impl EntityCursorMut<'_, T> +where + T: EntityWithParent, +{ + fn parent(&self) -> Option::Parent>> { + if self.parent.is_null() { + None + } else { + Some(unsafe { UnsafeIntrusiveEntityRef::from_raw(self.parent.cast()) }) + } + } +} + +impl<'a, T: EntityListItem> EntityCursorMut<'a, T> { + /// Returns true if this cursor is pointing to the null object + #[inline] + pub fn is_null(&self) -> bool { + self.cursor.is_null() + } + + /// Get a shared reference to the entity under the cursor. + /// + /// Returns `None` if the cursor is currently pointing to the null object. + /// + /// NOTE: This binds the lifetime of the [EntityRef] to the cursor, to ensure that the cursor + /// is frozen while the entity is being borrowed. This ensures that only one reference at a + /// time is being handed out by this cursor. + pub fn get(&self) -> Option> { + self.cursor.get().map(|obj| obj.entity.borrow()) + } + + /// Get a mutable reference to the entity under the cursor. + /// + /// Returns `None` if the cursor is currently pointing to the null object. + /// + /// Not only does this mutably borrow the cursor, the lifetime of the [EntityMut] is bound to + /// that of the cursor, which means it cannot outlive the cursor, and also prevents the cursor + /// from being accessed in any way until the mutable reference is dropped. This makes it + /// impossible to try and alias the underlying entity using the cursor. + pub fn get_mut(&mut self) -> Option> { + self.cursor.get().map(|obj| obj.entity.borrow_mut()) + } + + /// Returns a read-only cursor pointing to the current element. + /// + /// The lifetime of the returned [EntityCursor] is bound to that of the [EntityCursorMut], which + /// means it cannot outlive the [EntityCursorMut] and that the [EntityCursorMut] is frozen for + /// the lifetime of the [EntityCursor]. + pub fn as_cursor(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.cursor.as_cursor(), + } + } + + /// Get the [TrackedEntityHandle] corresponding to the entity under the cursor. + /// + /// Returns `None` if the cursor is pointing to the null object. + #[inline] + pub fn as_pointer(&self) -> Option> { + self.cursor.as_cursor().clone_pointer() + } + + /// Consume the cursor and convert it into a borrow of the current entity, or `None` if null. + #[inline] + pub fn into_borrow(self) -> Option> { + self.cursor.into_ref().map(|item| item.borrow()) + } + + /// Consume the cursor and convert it into a mutable borrow of the current entity, or `None` if null. + #[inline] + pub fn into_borrow_mut(self) -> Option> { + self.cursor.into_ref().map(|item| item.borrow_mut()) + } + + /// Moves the cursor to the next element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will move it to the front of the + /// [EntityList]. If it is pointing to the back of the [EntityList] then this will move it to + /// the null object. + #[inline] + pub fn move_next(&mut self) { + self.cursor.move_next(); + } + + /// Moves the cursor to the previous element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will move it to the back of the + /// [EntityList]. If it is pointing to the front of the [EntityList] then this will move it to + /// the null object. + #[inline] + pub fn move_prev(&mut self) { + self.cursor.move_prev(); + } + + /// Returns a cursor pointing to the next element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will return a cursor pointing to the + /// front of the [EntityList]. If it is pointing to the last entity of the [EntityList] then + /// this will return a null cursor. + #[inline] + pub fn peek_next(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.cursor.peek_next(), + } + } + + /// Returns a cursor pointing to the previous element of the [EntityList]. + /// + /// If the cursor is pointing to the null object then this will return a cursor pointing to + /// the last entity in the [EntityList]. If it is pointing to the front of the [EntityList] then + /// this will return a null cursor. + #[inline] + pub fn peek_prev(&self) -> EntityCursor<'_, T> { + EntityCursor { + cursor: self.cursor.peek_prev(), + } + } + + /// Removes the current entity from the [EntityList]. + /// + /// A pointer to the element that was removed is returned, and the cursor is moved to point to + /// the next element in the [Entitylist]. + /// + /// If the cursor is currently pointing to the null object then nothing is removed and `None` is + /// returned. + #[inline] + #[track_caller] + pub fn remove(&mut self) -> Option> { + as EntityListTraits>::remove(self) + } + + /// Removes the current entity from the [EntityList] and inserts another one in its place. + /// + /// A pointer to the entity that was removed is returned, and the cursor is modified to point to + /// the newly added entity. + /// + /// If the cursor is currently pointing to the null object then `Err` is returned containing the + /// entity we failed to insert. + /// + /// # Panics + /// Panics if the new entity is already linked to a different intrusive collection. + #[inline] + pub fn replace_with( + &mut self, + value: UnsafeIntrusiveEntityRef, + ) -> Result, UnsafeIntrusiveEntityRef> { + as EntityListTraits>::replace_with(self, value) + } + + /// Inserts a new entity into the [EntityList], after the current cursor position. + /// + /// If the cursor is pointing at the null object then the entity is inserted at the start of the + /// underlying [EntityList]. + /// + /// # Panics + /// + /// Panics if the entity is already linked to a different [EntityList] + #[inline] + pub fn insert_after(&mut self, value: UnsafeIntrusiveEntityRef) { + as EntityListTraits>::insert_after(self, value) + } + + /// Inserts a new entity into the [EntityList], before the current cursor position. + /// + /// If the cursor is pointing at the null object then the entity is inserted at the end of the + /// underlying [EntityList]. + /// + /// # Panics + /// + /// Panics if the entity is already linked to a different [EntityList] + #[inline] + pub fn insert_before(&mut self, value: UnsafeIntrusiveEntityRef) { + as EntityListTraits>::insert_before(self, value) + } + + /// This splices `list` into the underlying list of `self` by inserting the elements of `list` + /// after the current cursor position. + /// + /// For example, let's say we have the following list and cursor position: + /// + /// ```text,ignore + /// [A, B, C] + /// ^-- cursor + /// ``` + /// + /// Splicing a new list, `[D, E, F]` after the cursor would result in: + /// + /// ```text,ignore + /// [A, B, D, E, F, C] + /// ^-- cursor + /// ``` + /// + /// If the cursor is pointing at the null object, then `list` is appended to the start of the + /// underlying [EntityList] for this cursor. + #[inline] + pub fn splice_after(&mut self, list: EntityList) { + as EntityListTraits>::splice_after(self, list) + } + + /// This splices `list` into the underlying list of `self` by inserting the elements of `list` + /// before the current cursor position. + /// + /// For example, let's say we have the following list and cursor position: + /// + /// ```text,ignore + /// [A, B, C] + /// ^-- cursor + /// ``` + /// + /// Splicing a new list, `[D, E, F]` before the cursor would result in: + /// + /// ```text,ignore + /// [A, D, E, F, B, C] + /// ^-- cursor + /// ``` + /// + /// If the cursor is pointing at the null object, then `list` is appended to the end of the + /// underlying [EntityList] for this cursor. + #[inline] + pub fn splice_before(&mut self, list: EntityList) { + as EntityListTraits>::splice_before(self, list) + } + + /// Splits the list into two after the current cursor position. + /// + /// This will return a new list consisting of everything after the cursor, with the original + /// list retaining everything before. + /// + /// If the cursor is pointing at the null object then the entire contents of the [EntityList] + /// are moved. + pub fn split_after(&mut self) -> EntityList { + as EntityListTraits>::split_after(self) + } + + /// Splits the list into two before the current cursor position. + /// + /// This will return a new list consisting of everything before the cursor, with the original + /// list retaining everything after. + /// + /// If the cursor is pointing at the null object then the entire contents of the [EntityList] + /// are moved. + pub fn split_before(&mut self) -> EntityList { + as EntityListTraits>::split_before(self) + } +} + +pub struct EntityIter<'a, T> { + cursor: EntityCursor<'a, T>, + started: bool, +} +impl core::iter::FusedIterator for EntityIter<'_, T> {} +impl<'a, T> Iterator for EntityIter<'a, T> { + type Item = EntityRef<'a, T>; + + fn next(&mut self) -> Option { + // If we haven't started iterating yet, then we're on the null cursor, so move to the + // front of the list now that we have started iterating. + if !self.started { + self.started = true; + self.cursor.move_next(); + } + let item = self.cursor.get()?; + self.cursor.move_next(); + Some(item) + } +} +impl DoubleEndedIterator for EntityIter<'_, T> { + fn next_back(&mut self) -> Option { + // If we haven't started iterating yet, then we're on the null cursor, so move to the + // back of the list now that we have started iterating. + if !self.started { + self.started = true; + self.cursor.move_prev(); + } + let item = self.cursor.get()?; + self.cursor.move_prev(); + Some(item) + } +} + +pub struct MaybeDefaultEntityIter<'a, T> { + iter: Option>, +} +impl Default for MaybeDefaultEntityIter<'_, T> { + fn default() -> Self { + Self { iter: None } + } +} +impl core::iter::FusedIterator for MaybeDefaultEntityIter<'_, T> {} +impl<'a, T> Iterator for MaybeDefaultEntityIter<'a, T> { + type Item = EntityRef<'a, T>; + + #[inline] + fn next(&mut self) -> Option { + self.iter.as_mut().and_then(|iter| iter.next()) + } +} +impl DoubleEndedIterator for MaybeDefaultEntityIter<'_, T> { + #[inline] + fn next_back(&mut self) -> Option { + self.iter.as_mut().and_then(|iter| iter.next_back()) + } +} + +impl RawEntityRef { + /// Create a new [UnsafeIntrusiveEntityRef] by allocating `value` in `arena` + /// + /// # SAFETY + /// + /// This function has the same requirements around safety as [RawEntityRef::new]. + pub fn new(value: T, arena: &blink_alloc::Blink) -> Self { + RawEntityRef::new_with_metadata(value, IntrusiveLink::default(), arena) + } + + pub fn new_uninit(arena: &blink_alloc::Blink) -> RawEntityRef, IntrusiveLink> { + RawEntityRef::new_uninit_with_metadata(IntrusiveLink::default(), arena) + } +} + +impl RawEntityRef +where + T: EntityWithParent, +{ + /// Returns the parent entity this entity is linked to, if linked. + pub fn parent(&self) -> Option::Parent>> { + unsafe { + let offset = core::mem::offset_of!(RawEntityMetadata, metadata); + let current = self.inner.byte_add(offset).cast::(); + current.as_ref().parent() + } + } + + pub(self) fn set_parent( + &self, + parent: Option::Parent>>, + ) { + unsafe { + let offset = core::mem::offset_of!(RawEntityMetadata, metadata); + let current = self.inner.byte_add(offset).cast::(); + current.as_ref().set_parent(parent); + } + } +} + +impl RawEntityRef +where + T: EntityWithParent, + ::Parent: EntityWithParent, +{ + pub fn grandparent( + &self, + ) -> Option< + UnsafeIntrusiveEntityRef<<::Parent as EntityWithParent>::Parent>, + > { + self.parent().and_then(|parent| parent.parent()) + } +} + +impl RawEntityRef { + /// Returns true if this entity is linked into an intrusive list + pub fn is_linked(&self) -> bool { + unsafe { + let offset = core::mem::offset_of!(RawEntityMetadata, metadata); + let current = self.inner.byte_add(offset).cast::(); + current.as_ref().is_linked() + } + } + + /// Get the previous entity in the list of `T` containing the current entity + /// + /// For example, in a list of `Operation` in a `Block`, this would return the handle of the + /// previous operation in the block, or `None` if there are no other ops before this one. + pub fn prev(&self) -> Option { + use intrusive_collections::linked_list::{LinkOps, LinkedListOps}; + unsafe { + let offset = core::mem::offset_of!(RawEntityMetadata, metadata); + let current = self.inner.byte_add(offset).cast::(); + if !current.as_ref().is_linked() { + return None; + } + LinkOps.prev(current.cast()).map(|link_ptr| { + let offset = core::mem::offset_of!(IntrusiveLink, link); + let link_ptr = link_ptr.byte_sub(offset).cast::(); + Self::from_link_ptr(link_ptr) + }) + } + } + + /// Get the next entity in the list of `T` containing the current entity + /// + /// For example, in a list of `Operation` in a `Block`, this would return the handle of the + /// next operation in the block, or `None` if there are no other ops after this one. + pub fn next(&self) -> Option { + use intrusive_collections::linked_list::{LinkOps, LinkedListOps}; + unsafe { + let offset = core::mem::offset_of!(RawEntityMetadata, metadata); + let current = self.inner.byte_add(offset).cast::(); + if !current.as_ref().is_linked() { + return None; + } + LinkOps.next(current.cast()).map(|link_ptr| { + let offset = core::mem::offset_of!(IntrusiveLink, link); + let link_ptr = link_ptr.byte_sub(offset).cast::(); + Self::from_link_ptr(link_ptr) + }) + } + } + + #[inline] + unsafe fn from_link_ptr(link: NonNull) -> Self { + let offset = core::mem::offset_of!(RawEntityMetadata, metadata); + let ptr = link.byte_sub(offset).cast::>(); + Self { inner: ptr } + } +} + +#[doc(hidden)] +pub struct DefaultPointerOps(core::marker::PhantomData); +impl Copy for DefaultPointerOps {} +impl Clone for DefaultPointerOps { + fn clone(&self) -> Self { + *self + } +} +impl Default for DefaultPointerOps { + fn default() -> Self { + Self::new() + } +} +impl DefaultPointerOps { + const fn new() -> Self { + Self(core::marker::PhantomData) + } +} + +unsafe impl intrusive_collections::PointerOps + for DefaultPointerOps> +{ + type Pointer = UnsafeIntrusiveEntityRef; + type Value = RawEntityMetadata; + + #[inline] + unsafe fn from_raw(&self, value: *const Self::Value) -> Self::Pointer { + debug_assert!(!value.is_null() && value.is_aligned()); + UnsafeIntrusiveEntityRef::from_ptr(value.cast_mut()) + } + + #[inline] + fn into_raw(&self, ptr: Self::Pointer) -> *const Self::Value { + UnsafeIntrusiveEntityRef::into_inner(ptr).as_ptr().cast_const() + } +} + +/// An adapter for storing any `Entity` impl in a [intrusive_collections::LinkedList] +pub struct EntityAdapter { + link_ops: intrusive_collections::linked_list::LinkOps, + ptr_ops: DefaultPointerOps>, + marker: core::marker::PhantomData, +} +impl Copy for EntityAdapter {} +impl Clone for EntityAdapter { + fn clone(&self) -> Self { + *self + } +} +impl Default for EntityAdapter { + fn default() -> Self { + Self::new() + } +} +impl EntityAdapter { + pub const fn new() -> Self { + Self { + link_ops: intrusive_collections::linked_list::LinkOps, + ptr_ops: DefaultPointerOps::new(), + marker: core::marker::PhantomData, + } + } +} + +unsafe impl intrusive_collections::Adapter for EntityAdapter { + type LinkOps = intrusive_collections::linked_list::LinkOps; + type PointerOps = DefaultPointerOps>; + + unsafe fn get_value( + &self, + link: ::LinkPtr, + ) -> *const ::Value { + let offset = core::mem::offset_of!(IntrusiveLink, link); + let link_ptr = link.byte_sub(offset).cast::(); + let raw_entity_ref = UnsafeIntrusiveEntityRef::::from_link_ptr(link_ptr); + raw_entity_ref.inner.as_ptr().cast_const() + } + + unsafe fn get_link( + &self, + value: *const ::Value, + ) -> ::LinkPtr { + let raw_entity_ref = UnsafeIntrusiveEntityRef::from_ptr(value.cast_mut()); + let offset = RawEntityMetadata::::metadata_offset(); + raw_entity_ref.inner.byte_add(offset).cast() + } + + fn link_ops(&self) -> &Self::LinkOps { + &self.link_ops + } + + fn link_ops_mut(&mut self) -> &mut Self::LinkOps { + &mut self.link_ops + } + + fn pointer_ops(&self) -> &Self::PointerOps { + &self.ptr_ops + } +} diff --git a/hir/src/ir/entity/storage.rs b/hir/src/ir/entity/storage.rs new file mode 100644 index 000000000..99478d811 --- /dev/null +++ b/hir/src/ir/entity/storage.rs @@ -0,0 +1,1038 @@ +use core::fmt; + +use smallvec::{smallvec, SmallVec}; + +use super::{EntityGroup, StorableEntity}; + +/// [EntityStorage] provides an abstraction over storing IR entities in an [crate::Operation]. +/// +/// Specifically, it provides an abstraction for storing IR entities in a flat vector, while +/// retaining the ability to semantically group the entities, access them by group or individually, +/// and grow or shrink the group or overall set. +/// +/// The implementation expects the types stored in it to implement the [StorableEntity] trait, which +/// provides it the ability to ensure the entity is kept up to date with its position in the +/// set. Additionally, it ensures that removing an entity will unlink that entity from any +/// dependents or dependencies that it needs to maintain links for. +/// +/// Users can control the number of entities stored inline via the `INLINE` const parameter. By +/// default, only a single entity is stored inline, but sometimes more may be desired if you know +/// that a particular entity always has a particular cardinality. +pub struct EntityStorage { + /// The items being stored + items: SmallVec<[T; INLINE]>, + /// The semantic grouping information for this instance. + /// + /// There is always at least one group, and more can be explicitly added/removed. + groups: SmallVec<[EntityGroup; 2]>, +} + +impl fmt::Debug for EntityStorage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(core::any::type_name::()) + .field_with("groups", |f| { + let mut builder = f.debug_list(); + for group in self.groups.iter() { + let range = group.as_range(); + let items = &self.items[range.clone()]; + builder.entry_with(|f| { + f.debug_map().entry(&"range", &range).entry(&"items", &items).finish() + }); + } + builder.finish() + }) + .finish() + } +} + +impl Default for EntityStorage { + fn default() -> Self { + Self { + items: Default::default(), + groups: smallvec![EntityGroup::default()], + } + } +} + +impl EntityStorage { + /// Returns true if there are no items in storage. + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } + + /// Returns the total number of items in storage. + #[inline] + pub fn len(&self) -> usize { + self.items.len() + } + + /// Returns the number of groups with allocated storage. + #[inline] + pub fn num_groups(&self) -> usize { + self.groups.len() + } + + /// Get an iterator over all of the items in storage + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, T> { + self.items.iter() + } + + /// Get a mutable iterator over all of the items in storage + #[inline] + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { + self.items.iter_mut() + } + + /// Returns an iterator over the groups in storage + pub fn groups(&self) -> impl Iterator> + '_ { + self.groups.iter().map(|group| EntityRange { + range: group.as_range(), + items: self.items.as_slice(), + }) + } +} + +impl EntityStorage { + /// Get an empty [EntityRangeMut] derived from this storage + pub fn empty_mut(&mut self) -> EntityRangeMut<'_, T, INLINE> { + EntityRangeMut { + group: 0, + range: 0..0, + groups: &mut self.groups, + items: &mut self.items, + } + } + + /// Push an item to the last group + pub fn push(&mut self, mut item: T) { + let index = self.items.len(); + unsafe { + item.set_index(index); + } + self.items.push(item); + let group = self.groups.last_mut().unwrap(); + group.grow(1); + } + + /// Extend the last group with `items` + pub fn extend(&mut self, items: I) + where + I: IntoIterator, + { + let mut group = self.group_mut(self.groups.len() - 1); + group.extend(items); + } + + /// Push `items` as a new group, and return the group index + #[inline] + pub fn push_group(&mut self, items: impl IntoIterator) -> usize { + let group = self.groups.len(); + self.extend_group(group, items); + group + } + + /// Push `item` to the specified group + pub fn push_to_group(&mut self, group: usize, item: T) { + if self.groups.len() <= group { + let next_offset = self.groups.last().map(|group| group.as_range().end).unwrap_or(0); + self.groups.resize(group + 1, EntityGroup::new(next_offset, 0)); + } + let mut group = self.group_mut(group); + group.push(item); + } + + /// Pushes `items` to the given group, creating it if necessary, and allocating any intervening + /// implied groups if they have not been created it. + pub fn extend_group(&mut self, group: usize, items: I) + where + I: IntoIterator, + { + if self.groups.len() <= group { + let next_offset = self.groups.last().map(|group| group.as_range().end).unwrap_or(0); + self.groups.resize(group + 1, EntityGroup::new(next_offset, 0)); + } + let mut group = self.group_mut(group); + group.extend(items); + } + + /// Clear all items in storage + pub fn clear(&mut self) { + for mut item in self.items.drain(..) { + item.unlink(); + } + self.groups.clear(); + self.groups.push(EntityGroup::default()); + } + + /// Get all the items in storage + pub fn all(&self) -> EntityRange<'_, T> { + EntityRange { + range: 0..self.items.len(), + items: self.items.as_slice(), + } + } + + /// Get an [EntityRange] covering items in the specified group + pub fn group(&self, group: usize) -> EntityRange<'_, T> { + EntityRange { + range: self.groups[group].as_range(), + items: self.items.as_slice(), + } + } + + /// Get an [EntityRangeMut] covering items in the specified group + pub fn group_mut(&mut self, group: usize) -> EntityRangeMut<'_, T, INLINE> { + let range = self.groups[group].as_range(); + EntityRangeMut { + group, + range, + groups: &mut self.groups, + items: &mut self.items, + } + } +} + +impl core::ops::Index for EntityStorage { + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + &self.items[index] + } +} +impl core::ops::IndexMut for EntityStorage { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.items[index] + } +} + +/// A reference to a range of items in [EntityStorage] +#[derive(Clone)] +pub struct EntityRange<'a, T> { + range: core::ops::Range, + items: &'a [T], +} +impl<'a, T> EntityRange<'a, T> { + pub fn new(range: core::ops::Range, items: &'a [T]) -> Self { + assert!(range.end <= items.len()); + + Self { range, items } + } + + /// Get an empty range + pub fn empty() -> Self { + Self { + range: 0..0, + items: &[], + } + } + + /// Returns true if this range is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.as_slice().is_empty() + } + + /// Returns the size of this range + #[inline] + pub fn len(&self) -> usize { + self.as_slice().len() + } + + /// Returns a reference to the index range covered by this EntityRange + #[inline(always)] + pub const fn range(&self) -> &core::ops::Range { + &self.range + } + + /// Get this range as a slice + #[inline] + pub fn as_slice(&self) -> &'a [T] { + if self.range.is_empty() { + &[] + } else { + &self.items[self.range.start..self.range.end] + } + } + + /// Get an iterator over the items in this range + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, T> { + self.as_slice().iter() + } + + /// Get an item at the specified index relative to this range, or `None` if the index is out of bounds. + #[inline] + pub fn get(&self, index: usize) -> Option<&T> { + self.as_slice().get(index) + } +} +impl core::ops::Index for EntityRange<'_, T> { + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + &self.as_slice()[index] + } +} +impl<'a, T> IntoIterator for EntityRange<'a, T> { + type IntoIter = core::slice::Iter<'a, T>; + type Item = &'a T; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.items[self.range.start..self.range.end].iter() + } +} + +/// A mutable range of items in [EntityStorage] +/// +/// Items outside the range are not modified, however the range itself can have its size change, +/// which as a result will shift other items around. Any other groups in [EntityStorage] will +/// be updated to reflect such changes, so in general this should be transparent. +pub struct EntityRangeMut<'a, T, const INLINE: usize = 1> { + group: usize, + range: core::ops::Range, + groups: &'a mut [EntityGroup], + items: &'a mut SmallVec<[T; INLINE]>, +} +impl EntityRangeMut<'_, T, INLINE> { + /// Returns true if this range is empty + #[inline] + pub fn is_empty(&self) -> bool { + self.as_slice().is_empty() + } + + /// Get the number of items covered by this range + #[inline] + pub fn len(&self) -> usize { + self.as_slice().len() + } + + /// Returns a reference to the index range covered by this EntityRangeMut + #[inline(always)] + pub const fn range(&self) -> &core::ops::Range { + &self.range + } + + /// Temporarily borrow this range immutably + pub fn as_immutable(&self) -> EntityRange<'_, T> { + EntityRange { + range: self.range.clone(), + items: self.items.as_slice(), + } + } + + /// Get this range as a slice + #[inline] + pub fn as_slice(&self) -> &[T] { + if self.range.is_empty() { + &[] + } else { + &self.items[self.range.start..self.range.end] + } + } + + /// Get this range as a mutable slice + #[inline] + pub fn as_slice_mut(&mut self) -> &mut [T] { + if self.range.is_empty() { + &mut [] + } else { + &mut self.items[self.range.start..self.range.end] + } + } + + /// Get an iterator over the items covered by this range + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, T> { + self.as_slice().iter() + } + + /// Get a mutable iterator over the items covered by this range + #[inline] + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { + self.as_slice_mut().iter_mut() + } + + /// Get a reference to the item at `index`, relative to the start of this range. + #[inline] + pub fn get(&self, index: usize) -> Option<&T> { + self.as_slice().get(index) + } + + /// Get a mutable reference to the item at `index`, relative to the start of this range. + #[inline] + pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { + self.as_slice_mut().get_mut(index) + } +} + +impl EntityRangeMut<'_, T, INLINE> { + /// Append `item` to storage at the end of this range + #[inline] + pub fn push(&mut self, item: T) { + self.extend([item]); + } + + /// Append `items` to storage at the end of this range + pub fn extend(&mut self, operands: I) + where + I: IntoIterator, + { + // Handle edge case where group is the last group + let is_last = self.groups.len() == self.group + 1; + if is_last { + self.extend_last(operands); + } else { + self.extend_within(operands); + } + } + + fn extend_last(&mut self, items: I) + where + I: IntoIterator, + { + let prev_len = self.items.len(); + self.items.extend(items.into_iter().enumerate().map(|(i, mut item)| { + unsafe { + item.set_index(prev_len + i); + } + item + })); + let num_inserted = self.items.len().abs_diff(prev_len); + if num_inserted == 0 { + return; + } + self.groups[self.group].grow(num_inserted); + self.range = self.groups[self.group].as_range(); + } + + fn extend_within(&mut self, items: I) + where + I: IntoIterator, + { + let prev_len = self.items.len(); + let start = self.range.end; + self.items.insert_many( + start, + items.into_iter().enumerate().map(|(i, mut item)| { + unsafe { + item.set_index(start + i); + } + item + }), + ); + let num_inserted = self.items.len().abs_diff(prev_len); + if num_inserted == 0 { + return; + } + self.groups[self.group].grow(num_inserted); + self.range = self.groups[self.group].as_range(); + + // Shift groups + for group in self.groups[(self.group + 1)..].iter_mut() { + group.shift_start(num_inserted as isize); + } + + // Shift item indices + let shifted = self.range.end; + for (offset, item) in self.items[shifted..].iter_mut().enumerate() { + unsafe { + item.set_index(shifted + offset); + } + } + } + + /// Remove the item at `index` in this group, and return it. + /// + /// NOTE: This will panic if `index` is out of bounds of the group. + pub fn erase(&mut self, index: usize) -> T { + assert!(self.range.len() > index, "index out of bounds"); + self.range.end -= 1; + self.groups[self.group].shrink(1); + let mut removed = self.items.remove(self.range.start + index); + { + removed.unlink(); + } + + // Shift groups + let next_group = self.group + 1; + if next_group < self.groups.len() { + for group in self.groups[next_group..].iter_mut() { + group.shift_start(-1); + } + } + + // Shift item indices + let next_item = index; + if next_item < self.items.len() { + for (offset, item) in self.items[next_item..].iter_mut().enumerate() { + unsafe { + item.set_index(next_item + offset); + } + } + } + + removed + } + + /// Remove the last item from this group, or `None` if empty + pub fn pop(&mut self) -> Option { + if self.range.is_empty() { + return None; + } + let index = self.range.end - 1; + self.range.end = index; + self.groups[self.group].shrink(1); + let mut removed = self.items.remove(index); + { + removed.unlink(); + } + + // Shift groups + let next_group = self.group + 1; + if next_group < self.groups.len() { + for group in self.groups[next_group..].iter_mut() { + group.shift_start(-1); + } + } + + // Shift item indices + let next_item = index; + if next_item < self.items.len() { + for (offset, item) in self.items[next_item..].iter_mut().enumerate() { + unsafe { + item.set_index(next_item + offset); + } + } + } + + Some(removed) + } +} + +impl EntityRangeMut<'_, T, INLINE> { + /// Remove all items in this range from storage, unlinking them in the process + pub fn clear(&mut self) { + if self.range.is_empty() { + return; + } + + let total_len = self.items.len(); + let len = self.range.len(); + let end = self.range.end; + self.range.end = self.range.start; + self.groups[self.group].shrink(len); + + for item in self.items[self.range.start..end].iter_mut() { + item.unlink(); + } + + let trailing_items = total_len - end; + if trailing_items > 0 { + self.items.copy_within(end.., self.range.start); + self.items.truncate(self.range.start + trailing_items); + } else { + self.items.truncate(self.range.start); + } + + // Shift groups + let next_group = self.group + 1; + if next_group < self.groups.len() { + let shift = -(len as isize); + for group in self.groups[next_group..].iter_mut() { + group.shift_start(shift); + } + } + + // Shift item indices + if trailing_items > 0 { + for (offset, item) in self.items[self.range.start..(self.range.start + trailing_items)] + .iter_mut() + .enumerate() + { + unsafe { + item.set_index(self.range.start + offset); + } + } + } + } + + /// Remove all items in this range from storage into an owned [SmallVec] for further processing. + /// + /// NOTE: This does not unlink the items removed from storage, the caller is expected to handle + /// this themselves. + pub fn take(&mut self) -> SmallVec<[T; INLINE]> { + let mut taken = SmallVec::<[T; INLINE]>::with_capacity(self.len()); + if self.range.is_empty() { + return taken; + } + let total_len = self.items.len(); + let len = self.range.len(); + let end = self.range.end; + self.range.end = self.range.start; + self.groups[self.group].shrink(len); + taken.extend_from_slice(&self.items[self.range.start..end]); + let trailing_items = total_len - end; + if trailing_items > 0 { + self.items.copy_within(end.., self.range.start); + self.items.truncate(self.range.start + trailing_items); + } else { + self.items.truncate(self.range.start); + } + + // Shift groups + let next_group = self.group + 1; + if next_group < self.groups.len() { + let shift = -(len as isize); + for group in self.groups[next_group..].iter_mut() { + group.shift_start(shift); + } + } + + // Shift item indices + if trailing_items > 0 { + for (offset, item) in self.items[self.range.start..(self.range.start + trailing_items)] + .iter_mut() + .enumerate() + { + unsafe { + item.set_index(self.range.start + offset); + } + } + } + + taken + } +} +impl core::ops::Index for EntityRangeMut<'_, T, INLINE> { + type Output = T; + + #[inline] + fn index(&self, index: usize) -> &Self::Output { + &self.as_slice()[index] + } +} +impl core::ops::IndexMut for EntityRangeMut<'_, T, INLINE> { + #[inline] + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.as_slice_mut()[index] + } +} +impl<'a, T> IntoIterator for EntityRangeMut<'a, T> { + type IntoIter = core::slice::IterMut<'a, T>; + type Item = &'a mut T; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.items[self.range.start..self.range.end].iter_mut() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + struct Item { + index: usize, + value: usize, + linked: bool, + } + impl Item { + pub fn new(value: usize) -> Self { + Self { + index: 0, + value, + linked: true, + } + } + } + impl StorableEntity for Item { + fn index(&self) -> usize { + self.index + } + + unsafe fn set_index(&mut self, index: usize) { + self.index = index; + } + + fn unlink(&mut self) { + self.linked = false; + } + } + + type ItemStorage = EntityStorage; + #[allow(unused)] + type ItemRange<'a> = EntityRange<'a, Item>; + #[allow(unused)] + type ItemRangeMut<'a> = EntityRangeMut<'a, Item, 1>; + + #[test] + fn entity_storage_empty_operations() { + let mut storage = ItemStorage::default(); + + // No items, but always have at least one group + assert_eq!(storage.len(), 0); + assert!(storage.is_empty()); + assert_eq!(storage.num_groups(), 1); + + { + let range = storage.all(); + assert_eq!(range.len(), 0); + assert!(range.is_empty()); + assert_eq!(range.as_slice(), &[]); + assert_eq!(range.iter().next(), None); + } + + // No items, two groups + let group = storage.push_group(None); + assert_eq!(group, 1); + assert_eq!(storage.num_groups(), 2); + assert_eq!(storage.len(), 0); + assert!(storage.is_empty()); + + { + let range = storage.group(0); + assert_eq!(range.len(), 0); + assert!(range.is_empty()); + assert_eq!(range.as_slice(), &[]); + assert_eq!(range.iter().next(), None); + } + } + + #[test] + fn entity_storage_push_to_empty_group_entity_range() { + let mut storage = ItemStorage::default(); + + // Get group as mutable range + let mut group_range = storage.group_mut(0); + + // Verify handling of empty group in EntityRangeMut + assert_eq!(group_range.len(), 0); + assert!(group_range.is_empty()); + assert_eq!(group_range.as_slice(), &[]); + assert_eq!(group_range.iter().next(), None); + + // Push items to range + group_range.push(Item::new(0)); + group_range.push(Item::new(1)); + + assert_eq!(group_range[0].value, 0); + assert!(group_range[0].linked); + assert_eq!(group_range[1].value, 1); + assert!(group_range[1].linked); + + // Verify range reflects changes + assert_eq!(group_range.len(), 2); + assert!(!group_range.is_empty()); + assert_eq!( + group_range.as_slice(), + &[ + Item { + index: 0, + value: 0, + linked: true + }, + Item { + index: 1, + value: 1, + linked: true + } + ] + ); + assert_eq!( + group_range.iter().next(), + Some(&Item { + index: 0, + value: 0, + linked: true + }) + ); + } + + #[test] + fn entity_storage_extend_empty_group_entity_range() { + let mut storage = ItemStorage::default(); + + // Get group as mutable range + storage.push_to_group(0, Item::new(0)); + let group_id = storage.push_group(None); + let mut group_range = storage.group_mut(group_id); + + group_range.extend([Item::new(1), Item::new(2)]); + + // Verify handling of empty group in EntityRangeMut + assert_eq!(group_range.len(), 2); + assert_eq!(group_range.range().start, 1); + assert_eq!(group_range.range().end, 3); + assert_eq!( + group_range.as_slice(), + &[ + Item { + index: 1, + value: 1, + linked: true + }, + Item { + index: 2, + value: 2, + linked: true + } + ] + ); + assert_eq!( + group_range.iter().next(), + Some(&Item { + index: 1, + value: 1, + linked: true + }) + ); + + assert_eq!(group_range[0].value, 1); + assert!(group_range[0].linked); + assert_eq!(group_range[1].value, 2); + assert!(group_range[1].linked); + } + + #[test] + fn entity_storage_pop_from_non_empty_group_entity_range() { + let mut storage = ItemStorage::default(); + + assert_eq!(storage.num_groups(), 1); + storage.push_to_group(0, Item::new(0)); + assert_eq!(storage.len(), 1); + assert!(!storage.is_empty()); + + // Get group as mutable range + let mut group_range = storage.group_mut(0); + assert_eq!(group_range.len(), 1); + assert!(!group_range.is_empty()); + assert_eq!( + group_range.as_slice(), + &[Item { + index: 0, + value: 0, + linked: true + }] + ); + assert_eq!( + group_range.iter().next(), + Some(&Item { + index: 0, + value: 0, + linked: true + }) + ); + + // Pop item from range + let item = group_range.pop(); + assert_eq!( + item, + Some(Item { + index: 0, + value: 0, + linked: false + }) + ); + assert_eq!(group_range.len(), 0); + assert!(group_range.is_empty()); + assert_eq!(group_range.as_slice(), &[]); + assert_eq!(group_range.iter().next(), None); + assert_eq!(group_range.range.clone(), 0..0); + + // Pop from empty range should have no effect + let item = group_range.pop(); + assert_eq!(item, None); + assert_eq!(group_range.len(), 0); + assert!(group_range.is_empty()); + assert_eq!(group_range.as_slice(), &[]); + assert_eq!(group_range.iter().next(), None); + assert_eq!(group_range.range.clone(), 0..0); + } + + #[test] + fn entity_storage_push_to_empty_group_entity_range_before_other_groups() { + let mut storage = ItemStorage::default(); + + storage.extend_group(0, [Item::new(0), Item::new(1)]); + let group1 = storage.push_group(None); + let group2 = storage.push_group(None); + let group3 = storage.push_group([Item::new(4), Item::new(5)]); + + assert!(!storage.is_empty()); + assert_eq!(storage.len(), 4); + assert_eq!(storage.num_groups(), 4); + + assert_eq!(storage.group(0).range.clone(), 0..2); + assert_eq!(storage.group(1).range.clone(), 2..2); + assert_eq!(storage.group(2).range.clone(), 2..2); + assert_eq!(storage.group(3).range.clone(), 2..4); + + // Insert items into first non-empty group + { + let mut group_range = storage.group_mut(group1); + + // Verify handling of empty group in EntityRangeMut + assert_eq!(group_range.len(), 0); + assert!(group_range.is_empty()); + assert_eq!(group_range.as_slice(), &[]); + assert_eq!(group_range.iter().next(), None); + + // Push items to range + group_range.push(Item::new(2)); + group_range.push(Item::new(3)); + + // Verify range reflects changes + assert_eq!(group_range.len(), 2); + assert!(!group_range.is_empty()); + assert_eq!( + group_range.as_slice(), + &[ + Item { + index: 2, + value: 2, + linked: true + }, + Item { + index: 3, + value: 3, + linked: true + } + ] + ); + assert_eq!( + group_range.iter().next(), + Some(&Item { + index: 2, + value: 2, + linked: true + }) + ); + } + + // The subsequent empty group should still be empty, but at a new offset + let group_range = storage.group(group2); + assert_eq!(group_range.range.clone(), 4..4); + assert_eq!(group_range.len(), 0); + assert!(group_range.is_empty()); + assert_eq!(group_range.as_slice(), &[]); + assert_eq!(group_range.iter().next(), None); + + // The trailing non-empty group should have updated offsets + let group_range = storage.group(group3); + assert_eq!(group_range.range.clone(), 4..6); + assert_eq!(group_range.len(), 2); + assert!(!group_range.is_empty()); + assert_eq!( + group_range.as_slice(), + &[ + Item { + index: 4, + value: 4, + linked: true + }, + Item { + index: 5, + value: 5, + linked: true + } + ] + ); + assert_eq!( + group_range.iter().next(), + Some(&Item { + index: 4, + value: 4, + linked: true + }) + ); + } + + #[test] + fn entity_storage_pop_from_non_empty_group_entity_range_before_other_groups() { + let mut storage = ItemStorage::default(); + + storage.extend_group(0, [Item::new(0), Item::new(1)]); + let group1 = storage.push_group(None); + let group2 = storage.push_group(None); + let group3 = storage.push_group([Item::new(4), Item::new(5)]); + + assert!(!storage.is_empty()); + assert_eq!(storage.len(), 4); + assert_eq!(storage.num_groups(), 4); + + assert_eq!(storage.group(0).range.clone(), 0..2); + assert_eq!(storage.group(1).range.clone(), 2..2); + assert_eq!(storage.group(2).range.clone(), 2..2); + assert_eq!(storage.group(3).range.clone(), 2..4); + + // Pop from group0 + { + let mut group_range = storage.group_mut(0); + let item = group_range.pop(); + assert_eq!( + item, + Some(Item { + index: 1, + value: 1, + linked: false + }) + ); + assert_eq!(group_range.len(), 1); + assert!(!group_range.is_empty()); + assert_eq!( + group_range.as_slice(), + &[Item { + index: 0, + value: 0, + linked: true + }] + ); + } + + // The subsequent empty group(s) should still be empty, but at a new offset + for group_index in [group1, group2] { + let group_range = storage.group(group_index); + assert_eq!(group_range.range.clone(), 1..1); + assert_eq!(group_range.len(), 0); + assert!(group_range.is_empty()); + assert_eq!(group_range.as_slice(), &[]); + assert_eq!(group_range.iter().next(), None); + } + + // The trailing non-empty group should have updated offsets + let group_range = storage.group(group3); + assert_eq!(group_range.range.clone(), 1..3); + assert_eq!(group_range.len(), 2); + assert!(!group_range.is_empty()); + assert_eq!( + group_range.as_slice(), + &[ + Item { + index: 1, + value: 4, + linked: true + }, + Item { + index: 2, + value: 5, + linked: true + } + ] + ); + assert_eq!( + group_range.iter().next(), + Some(&Item { + index: 1, + value: 4, + linked: true + }) + ); + } +} diff --git a/hir/src/ident.rs b/hir/src/ir/ident.rs similarity index 87% rename from hir/src/ident.rs rename to hir/src/ir/ident.rs index 7108c2b23..e542defd9 100644 --- a/hir/src/ident.rs +++ b/hir/src/ir/ident.rs @@ -1,3 +1,4 @@ +use alloc::format; use core::{ cmp::Ordering, fmt, @@ -7,34 +8,23 @@ use core::{ use anyhow::anyhow; +use super::{ + interner::{symbols, Symbol}, + SourceSpan, Spanned, +}; use crate::{ - diagnostics::{SourceSpan, Spanned}, + define_attr_type, formatter::{self, PrettyPrint}, - symbols, Symbol, }; -/// Demangle `name`, where `name` was mangled using Rust's mangling scheme -#[inline] -pub fn demangle>(name: S) -> String { - demangle_impl(name.as_ref()) -} - -fn demangle_impl(name: &str) -> String { - let mut input = name.as_bytes(); - let mut demangled = Vec::with_capacity(input.len() * 2); - rustc_demangle::demangle_stream(&mut input, &mut demangled, /* include_hash= */ false) - .expect("failed to write demangled identifier"); - String::from_utf8(demangled).expect("demangled identifier contains invalid utf-8") -} - /// Represents a globally-unique module/function name pair, with corresponding source spans. #[derive(Copy, Clone, PartialEq, Eq, Hash, Spanned)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct FunctionIdent { pub module: Ident, #[span] pub function: Ident, } +define_attr_type!(FunctionIdent); impl FunctionIdent { pub fn display(&self) -> impl fmt::Display + '_ { use crate::formatter::*; @@ -98,13 +88,12 @@ impl Ord for FunctionIdent { /// /// An identifier is some string, along with an associated source span #[derive(Copy, Clone, Eq, Spanned)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serde", serde(into = "Symbol", from = "Symbol"))] pub struct Ident { pub name: Symbol, #[span] pub span: SourceSpan, } +define_attr_type!(Ident); impl Default for Ident { fn default() -> Self { Self { diff --git a/hir/src/ir/immediates.rs b/hir/src/ir/immediates.rs new file mode 100644 index 000000000..1a913fb49 --- /dev/null +++ b/hir/src/ir/immediates.rs @@ -0,0 +1,1034 @@ +use core::{ + fmt, + hash::{Hash, Hasher}, +}; + +pub use miden_core::{Felt, FieldElement, StarkField}; + +use crate::{formatter::PrettyPrint, Type}; + +#[derive(Debug, Copy, Clone)] +pub enum Immediate { + I1(bool), + U8(u8), + I8(i8), + U16(u16), + I16(i16), + U32(u32), + I32(i32), + U64(u64), + I64(i64), + U128(u128), + I128(i128), + F64(f64), + Felt(Felt), +} + +impl Immediate { + pub fn ty(&self) -> Type { + match self { + Self::I1(_) => Type::I1, + Self::U8(_) => Type::U8, + Self::I8(_) => Type::I8, + Self::U16(_) => Type::U16, + Self::I16(_) => Type::I16, + Self::U32(_) => Type::U32, + Self::I32(_) => Type::I32, + Self::U64(_) => Type::U64, + Self::I64(_) => Type::I64, + Self::U128(_) => Type::U128, + Self::I128(_) => Type::I128, + Self::F64(_) => Type::F64, + Self::Felt(_) => Type::Felt, + } + } + + /// Returns true if this immediate is a non-negative value + pub fn is_non_negative(&self) -> bool { + match self { + Self::I1(i) => *i, + Self::I8(i) => *i > 0, + Self::U8(i) => *i > 0, + Self::I16(i) => *i > 0, + Self::U16(i) => *i > 0, + Self::I32(i) => *i > 0, + Self::U32(i) => *i > 0, + Self::I64(i) => *i > 0, + Self::U64(i) => *i > 0, + Self::U128(i) => *i > 0, + Self::I128(i) => *i > 0, + Self::F64(f) => f.is_sign_positive(), + Self::Felt(_) => true, + } + } + + /// Returns true if this immediate can represent negative values + pub fn is_signed(&self) -> bool { + matches!( + self, + Self::I8(_) | Self::I16(_) | Self::I32(_) | Self::I64(_) | Self::I128(_) | Self::F64(_) + ) + } + + /// Returns true if this immediate can only represent non-negative values + pub fn is_unsigned(&self) -> bool { + matches!( + self, + Self::I1(_) + | Self::U8(_) + | Self::U16(_) + | Self::U32(_) + | Self::U64(_) + | Self::U128(_) + | Self::Felt(_) + ) + } + + /// Returns true if this immediate is an odd integer, otherwise false + /// + /// If the immediate is not an integer, returns `None` + pub fn is_odd(&self) -> Option { + match self { + Self::I1(b) => Some(*b), + Self::U8(i) => Some((*i).is_multiple_of(2)), + Self::I8(i) => Some(*i % 2 == 0), + Self::U16(i) => Some((*i).is_multiple_of(2)), + Self::I16(i) => Some(*i % 2 == 0), + Self::U32(i) => Some((*i).is_multiple_of(2)), + Self::I32(i) => Some(*i % 2 == 0), + Self::U64(i) => Some((*i).is_multiple_of(2)), + Self::I64(i) => Some(*i % 2 == 0), + Self::Felt(i) => Some(i.as_int().is_multiple_of(2)), + Self::U128(i) => Some((*i).is_multiple_of(2)), + Self::I128(i) => Some(*i % 2 == 0), + Self::F64(_) => None, + } + } + + /// Returns true if this immediate is a non-zero integer, otherwise false + /// + /// If the immediate is not an integer, returns `None` + pub fn as_bool(self) -> Option { + match self { + Self::I1(b) => Some(b), + Self::U8(i) => Some(i != 0), + Self::I8(i) => Some(i != 0), + Self::U16(i) => Some(i != 0), + Self::I16(i) => Some(i != 0), + Self::U32(i) => Some(i != 0), + Self::I32(i) => Some(i != 0), + Self::U64(i) => Some(i != 0), + Self::I64(i) => Some(i != 0), + Self::Felt(i) => Some(i.as_int() != 0), + Self::U128(i) => Some(i != 0), + Self::I128(i) => Some(i != 0), + Self::F64(_) => None, + } + } + + /// Attempts to convert this value to a u8 regardless of signedness + pub fn bitcast_u8(self) -> Option { + match self { + Self::I1(b) => Some(b as u8), + Self::U8(b) => Some(b), + Self::I8(b) => Some(b as u8), + Self::U16(b) => u8::try_from(b).ok(), + Self::I16(b) => i8::try_from(b).ok().map(|v| v as u8), + Self::U32(b) => u8::try_from(b).ok(), + Self::I32(b) => i8::try_from(b).ok().map(|v| v as u8), + Self::U64(b) => u8::try_from(b).ok(), + Self::I64(b) => i8::try_from(b).ok().map(|v| v as u8), + Self::Felt(i) => u8::try_from(i.as_int()).ok(), + Self::U128(b) if b <= (u8::MAX as u128) => Some(b as u8), + Self::U128(_) => None, + Self::I128(b) if b < (i8::MIN as i128) || b > (i8::MAX as i128) => None, + Self::I128(b) => Some(b as u8), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a i8 regardless of signedness + pub fn bitcast_i8(self) -> Option { + match self { + Self::I1(b) => Some(b as i8), + Self::U8(b) => Some(b as i8), + Self::I8(b) => Some(b), + Self::U16(b) => i8::try_from(b as i16).ok(), + Self::I16(b) => i8::try_from(b).ok(), + Self::U32(b) => i8::try_from(b as i32).ok(), + Self::I32(b) => i8::try_from(b).ok(), + Self::U64(b) => i8::try_from(b as i64).ok(), + Self::I64(b) => i8::try_from(b).ok(), + Self::Felt(i) => i8::try_from(i.as_int() as i64).ok(), + Self::U128(b) if b <= (u8::MAX as u128) => Some(b as u8 as i8), + Self::U128(_) => None, + Self::I128(b) if b < (i8::MIN as i128) || b > (i8::MAX as i128) => None, + Self::I128(b) => Some(b as i8), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a u16 regardless of signedness + pub fn bitcast_u16(self) -> Option { + match self { + Self::I1(b) => Some(b as u16), + Self::U8(b) => Some(b as u16), + Self::I8(b) => Some(b as u16), + Self::U16(b) => Some(b), + Self::I16(b) => Some(b as u16), + Self::U32(b) => u16::try_from(b).ok(), + Self::I32(b) => i16::try_from(b).ok().map(|v| v as u16), + Self::U64(b) => u16::try_from(b).ok(), + Self::I64(b) => i16::try_from(b).ok().map(|v| v as u16), + Self::Felt(i) => u16::try_from(i.as_int()).ok(), + Self::U128(b) if b <= (u16::MAX as u128) => Some(b as u16), + Self::U128(_) => None, + Self::I128(b) if b < (i16::MIN as i128) || b > (i16::MAX as i128) => None, + Self::I128(b) => Some(b as u16), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a i16 regardless of signedness + pub fn bitcast_i16(self) -> Option { + match self { + Self::I1(b) => Some(b as i16), + Self::U8(b) => Some(b as i16), + Self::I8(b) => Some(b as i16), + Self::U16(b) => Some(b as i16), + Self::I16(b) => Some(b), + Self::U32(b) => u16::try_from(b).ok().map(|v| v as i16), + Self::I32(b) => i16::try_from(b).ok(), + Self::U64(b) => u16::try_from(b).ok().map(|v| v as i16), + Self::I64(b) => i16::try_from(b).ok(), + Self::Felt(i) => u16::try_from(i.as_int()).ok().map(|v| v as i16), + Self::U128(b) if b <= (u16::MAX as u128) => Some(b as i16), + Self::U128(_) => None, + Self::I128(b) if b < (i16::MIN as i128) || b > (i16::MAX as i128) => None, + Self::I128(b) => Some(b as i16), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a u32 regardless of signedness + pub fn bitcast_u32(self) -> Option { + match self { + Self::I1(b) => Some(b as u32), + Self::U8(b) => Some(b as u32), + Self::I8(b) => Some(b as u32), + Self::U16(b) => Some(b as u32), + Self::I16(b) => Some(b as u32), + Self::U32(b) => Some(b), + Self::I32(b) => Some(b as u32), + Self::U64(b) => u32::try_from(b).ok(), + Self::I64(b) => i32::try_from(b).ok().map(|v| v as u32), + Self::Felt(i) => u32::try_from(i.as_int()).ok(), + Self::U128(b) if b <= (u32::MAX as u128) => Some(b as u32), + Self::U128(_) => None, + Self::I128(b) if b < (i32::MIN as i128) || b > (i32::MAX as i128) => None, + Self::I128(b) => Some(b as u32), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a i32 regardless of signedness + pub fn bitcast_i32(self) -> Option { + match self { + Self::I1(b) => Some(b as i32), + Self::U8(b) => Some(b as i32), + Self::I8(b) => Some(b as i32), + Self::U16(b) => Some(b as i32), + Self::I16(b) => Some(b as i32), + Self::U32(b) => Some(b as i32), + Self::I32(b) => Some(b), + Self::U64(b) => u32::try_from(b).ok().map(|v| v as i32), + Self::I64(b) => i32::try_from(b).ok(), + Self::Felt(i) => u32::try_from(i.as_int()).ok().map(|v| v as i32), + Self::U128(b) if b <= (u32::MAX as u128) => Some(b as i32), + Self::U128(_) => None, + Self::I128(b) if b < (i32::MIN as i128) || b > (i32::MAX as i128) => None, + Self::I128(b) => Some(b as i32), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a u64 regardless of signedness + pub fn bitcast_u64(self) -> Option { + match self { + Self::I1(b) => Some(b as u64), + Self::U8(b) => Some(b as u64), + Self::I8(b) => Some(b as u64), + Self::U16(b) => Some(b as u64), + Self::I16(b) => Some(b as u64), + Self::U32(b) => Some(b as u64), + Self::I32(b) => Some(b as u64), + Self::U64(b) => Some(b), + Self::I64(b) => Some(b as u64), + Self::Felt(i) => Some(i.as_int()), + Self::U128(b) if b <= (u64::MAX as u128) => Some(b as u64), + Self::U128(_) => None, + Self::I128(b) if b < (i64::MIN as i128) || b > (i64::MAX as i128) => None, + Self::I128(b) => Some(b as u64), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a i64 regardless of signedness + pub fn bitcast_i64(self) -> Option { + match self { + Self::I1(b) => Some(b as i64), + Self::U8(b) => Some(b as i64), + Self::I8(b) => Some(b as i64), + Self::U16(b) => Some(b as i64), + Self::I16(b) => Some(b as i64), + Self::U32(b) => Some(b as i64), + Self::I32(b) => Some(b as i64), + Self::U64(b) => Some(b as i64), + Self::I64(b) => Some(b), + Self::Felt(i) => Some(i.as_int() as i64), + Self::U128(b) if b <= (u64::MAX as u128) => Some(b as i64), + Self::U128(_) => None, + Self::I128(b) if b < (i64::MIN as i128) || b > (i64::MAX as i128) => None, + Self::I128(b) => Some(b as i64), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a u128 regardless of signedness + pub fn bitcast_u128(self) -> Option { + match self { + Self::I1(b) => Some(b as u128), + Self::U8(b) => Some(b as u128), + Self::I8(b) => Some(b as u128), + Self::U16(b) => Some(b as u128), + Self::I16(b) => Some(b as u128), + Self::U32(b) => Some(b as u128), + Self::I32(b) => Some(b as u128), + Self::U64(b) => Some(b as u128), + Self::I64(b) => Some(b as u128), + Self::Felt(i) => Some(i.as_int() as u128), + Self::U128(b) => Some(b), + Self::I128(b) => Some(b as u128), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a i128 regardless of signedness + pub fn bitcast_i128(self) -> Option { + match self { + Self::I1(b) => Some(b as i128), + Self::U8(b) => Some(b as i128), + Self::I8(b) => Some(b as i128), + Self::U16(b) => Some(b as i128), + Self::I16(b) => Some(b as i128), + Self::U32(b) => Some(b as i128), + Self::I32(b) => Some(b as i128), + Self::U64(b) => Some(b as i128), + Self::I64(b) => Some(b as i128), + Self::Felt(i) => Some(i.as_int() as i128), + Self::U128(b) => Some(b as i128), + Self::I128(b) => Some(b), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a felt regardless of signedness + pub fn bitcast_felt(self) -> Option { + match self { + Self::Felt(value) => Some(value), + imm => imm.bitcast_u64().map(Felt::new), + } + } + + /// Attempts to convert this value to a f64 + pub fn bitcast_f64(self) -> Option { + match self { + Self::I1(b) => Some(f64::from(b as u32)), + Self::U8(b) => Some(f64::from(b as u32)), + Self::I8(b) => Some(f64::from(b as i32)), + Self::U16(b) => Some(f64::from(b as u32)), + Self::I16(b) => Some(f64::from(b as i32)), + Self::U32(b) => Some(f64::from(b)), + Self::I32(b) => Some(f64::from(b)), + Self::U64(b) => Some(b as f64), + Self::I64(b) => Some(b as f64), + Self::Felt(i) => Some(i.as_int() as f64), + Self::U128(b) => Some(b as f64), + Self::I128(b) => Some(b as f64), + Self::F64(f) => Some(f), + } + } + + /// Attempts to convert this value to a u32 + pub fn as_u32(self) -> Option { + match self { + Self::I1(b) => Some(b as u32), + Self::U8(b) => Some(b as u32), + Self::I8(b) if b >= 0 => Some(b as u32), + Self::I8(_) => None, + Self::U16(b) => Some(b as u32), + Self::I16(b) if b >= 0 => Some(b as u32), + Self::I16(_) => None, + Self::U32(b) => Some(b), + Self::I32(b) if b >= 0 => Some(b as u32), + Self::I32(_) => None, + Self::U64(b) => u32::try_from(b).ok(), + Self::I64(b) if b >= 0 => u32::try_from(b as u64).ok(), + Self::I64(_) => None, + Self::Felt(i) => u32::try_from(i.as_int()).ok(), + Self::U128(b) if b <= (u32::MAX as u64 as u128) => Some(b as u32), + Self::U128(_) => None, + Self::I128(b) if b >= 0 && b <= (u32::MAX as u64 as i128) => Some(b as u32), + Self::I128(_) => None, + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to i32 + pub fn as_i32(self) -> Option { + match self { + Self::I1(b) => Some(b as i32), + Self::U8(i) => Some(i as i32), + Self::I8(i) => Some(i as i32), + Self::U16(i) => Some(i as i32), + Self::I16(i) => Some(i as i32), + Self::U32(i) => i.try_into().ok(), + Self::I32(i) => Some(i), + Self::U64(i) => i.try_into().ok(), + Self::I64(i) => i.try_into().ok(), + Self::Felt(i) => i.as_int().try_into().ok(), + Self::U128(i) if i <= (i32::MAX as u32 as u128) => Some(i as u32 as i32), + Self::U128(_) => None, + Self::I128(i) if i >= (i32::MIN as i128) && i <= (i32::MAX as i128) => Some(i as i32), + Self::I128(_) => None, + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to a field element + pub fn as_felt(self) -> Option { + match self { + Self::I1(b) => Some(Felt::new(b as u64)), + Self::U8(b) => Some(Felt::new(b as u64)), + Self::I8(b) => u64::try_from(b).ok().map(Felt::new), + Self::U16(b) => Some(Felt::new(b as u64)), + Self::I16(b) => u64::try_from(b).ok().map(Felt::new), + Self::U32(b) => Some(Felt::new(b as u64)), + Self::I32(b) => u64::try_from(b).ok().map(Felt::new), + Self::U64(b) => Some(Felt::new(b)), + Self::I64(b) => u64::try_from(b).ok().map(Felt::new), + Self::Felt(i) => Some(i), + Self::U128(b) => u64::try_from(b).ok().map(Felt::new), + Self::I128(b) => u64::try_from(b).ok().map(Felt::new), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to u64 + pub fn as_u64(self) -> Option { + match self { + Self::I1(b) => Some(b as u64), + Self::U8(i) => Some(i as u64), + Self::I8(i) if i >= 0 => Some(i as u64), + Self::I8(_) => None, + Self::U16(i) => Some(i as u64), + Self::I16(i) if i >= 0 => Some(i as u16 as u64), + Self::I16(_) => None, + Self::U32(i) => Some(i as u64), + Self::I32(i) if i >= 0 => Some(i as u32 as u64), + Self::I32(_) => None, + Self::U64(i) => Some(i), + Self::I64(i) if i >= 0 => Some(i as u64), + Self::I64(_) => None, + Self::Felt(i) => Some(i.as_int()), + Self::U128(i) => (i).try_into().ok(), + Self::I128(i) if i >= 0 => (i).try_into().ok(), + Self::I128(_) => None, + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to i64 + pub fn as_i64(self) -> Option { + match self { + Self::I1(b) => Some(b as i64), + Self::U8(i) => Some(i as i64), + Self::I8(i) => Some(i as i64), + Self::U16(i) => Some(i as i64), + Self::I16(i) => Some(i as i64), + Self::U32(i) => Some(i as i64), + Self::I32(i) => Some(i as i64), + Self::U64(i) => (i).try_into().ok(), + Self::I64(i) => Some(i), + Self::Felt(i) => i.as_int().try_into().ok(), + Self::U128(i) if i <= i64::MAX as u128 => Some(i as u64 as i64), + Self::U128(_) => None, + Self::I128(i) => (i).try_into().ok(), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to u128 + pub fn as_u128(self) -> Option { + match self { + Self::I1(b) => Some(b as u128), + Self::U8(i) => Some(i as u128), + Self::I8(i) if i >= 0 => Some(i as u128), + Self::I8(_) => None, + Self::U16(i) => Some(i as u128), + Self::I16(i) if i >= 0 => Some(i as u16 as u128), + Self::I16(_) => None, + Self::U32(i) => Some(i as u128), + Self::I32(i) if i >= 0 => Some(i as u32 as u128), + Self::I32(_) => None, + Self::U64(i) => Some(i as u128), + Self::I64(i) if i >= 0 => Some(i as u128), + Self::I64(_) => None, + Self::Felt(i) => Some(i.as_int() as u128), + Self::U128(i) => Some(i), + Self::I128(i) if i >= 0 => (i).try_into().ok(), + Self::I128(_) => None, + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } + + /// Attempts to convert this value to i128 + pub fn as_i128(self) -> Option { + match self { + Self::I1(b) => Some(b as i128), + Self::U8(i) => Some(i as i128), + Self::I8(i) => Some(i as i128), + Self::U16(i) => Some(i as i128), + Self::I16(i) => Some(i as i128), + Self::U32(i) => Some(i as i128), + Self::I32(i) => Some(i as i128), + Self::U64(i) => Some(i as i128), + Self::I64(i) => Some(i as i128), + Self::Felt(i) => Some(i.as_int() as i128), + Self::U128(i) if i <= i128::MAX as u128 => Some(i as i128), + Self::U128(_) => None, + Self::I128(i) => Some(i), + Self::F64(f) => FloatToInt::::to_int(f).ok(), + } + } +} +impl fmt::Display for Immediate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::I1(i) => write!(f, "{i}"), + Self::U8(i) => write!(f, "{i}"), + Self::I8(i) => write!(f, "{i}"), + Self::U16(i) => write!(f, "{i}"), + Self::I16(i) => write!(f, "{i}"), + Self::U32(i) => write!(f, "{i}"), + Self::I32(i) => write!(f, "{i}"), + Self::U64(i) => write!(f, "{i}"), + Self::I64(i) => write!(f, "{i}"), + Self::U128(i) => write!(f, "{i}"), + Self::I128(i) => write!(f, "{i}"), + Self::F64(n) => write!(f, "{n}"), + Self::Felt(i) => write!(f, "{i}"), + } + } +} +impl PrettyPrint for Immediate { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + display(self) + } +} +impl Hash for Immediate { + fn hash(&self, state: &mut H) { + let d = core::mem::discriminant(self); + d.hash(state); + match self { + Self::I1(i) => i.hash(state), + Self::U8(i) => i.hash(state), + Self::I8(i) => i.hash(state), + Self::U16(i) => i.hash(state), + Self::I16(i) => i.hash(state), + Self::U32(i) => i.hash(state), + Self::I32(i) => i.hash(state), + Self::U64(i) => i.hash(state), + Self::I64(i) => i.hash(state), + Self::U128(i) => i.hash(state), + Self::I128(i) => i.hash(state), + Self::F64(f) => { + let bytes = f.to_be_bytes(); + bytes.hash(state) + } + Self::Felt(i) => i.as_int().hash(state), + } + } +} +impl Eq for Immediate {} +impl PartialEq for Immediate { + fn eq(&self, other: &Self) -> bool { + match (*self, *other) { + (Self::I1(x), Self::I1(y)) => x == y, + (Self::I8(x), Self::I8(y)) => x == y, + (Self::U8(x), Self::U8(y)) => x == y, + (Self::U16(x), Self::U16(y)) => x == y, + (Self::I16(x), Self::I16(y)) => x == y, + (Self::U32(x), Self::U32(y)) => x == y, + (Self::I32(x), Self::I32(y)) => x == y, + (Self::U64(x), Self::U64(y)) => x == y, + (Self::I64(x), Self::I64(y)) => x == y, + (Self::U128(x), Self::U128(y)) => x == y, + (Self::I128(x), Self::I128(y)) => x == y, + (Self::F64(x), Self::F64(y)) => x == y, + (Self::Felt(x), Self::Felt(y)) => x == y, + _ => false, + } + } +} +impl PartialEq for Immediate { + fn eq(&self, other: &isize) -> bool { + let y = *other; + match *self { + Self::I1(x) => x == (y == 1), + Self::U8(_) if y < 0 => false, + Self::U8(x) => x as isize == y, + Self::I8(x) => x as isize == y, + Self::U16(_) if y < 0 => false, + Self::U16(x) => x as isize == y, + Self::I16(x) => x as isize == y, + Self::U32(_) if y < 0 => false, + Self::U32(x) => x as isize == y, + Self::I32(x) => x as isize == y, + Self::U64(_) if y < 0 => false, + Self::U64(x) => x == y as i64 as u64, + Self::I64(x) => x == y as i64, + Self::U128(_) if y < 0 => false, + Self::U128(x) => x == y as i128 as u128, + Self::I128(x) => x == y as i128, + Self::F64(_) => false, + Self::Felt(_) if y < 0 => false, + Self::Felt(x) => x.as_int() == y as i64 as u64, + } + } +} +impl PartialOrd for Immediate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for Immediate { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + use core::cmp::Ordering; + + match (self, other) { + // Floats require special treatment + (Self::F64(x), Self::F64(y)) => x.total_cmp(y), + // Here we're attempting to compare against any integer immediate, + // so we must attempt to convert the float to the largest possible + // integer representation, i128, and then promote the integer immediate + // to i128 for comparison + // + // If the float is not an integer value, truncate it and compare, then + // adjust the result to account for the truncation + (Self::F64(x), y) => { + let y = y + .as_i128() + .expect("expected rhs to be an integer capable of fitting in an i128"); + if let Ok(x) = FloatToInt::::to_int(*x) { + x.cmp(&y) + } else { + let is_positive = x.is_sign_positive(); + if let Ok(x) = FloatToInt::::to_int((*x).trunc()) { + // Edge case for equality: the float must be bigger due to truncation + match x.cmp(&y) { + Ordering::Equal if is_positive => Ordering::Greater, + Ordering::Equal => Ordering::Less, + o => o, + } + } else { + // The float is larger than i128 can represent, the sign tells us in what + // direction + if is_positive { + Ordering::Greater + } else { + Ordering::Less + } + } + } + } + (x, y @ Self::F64(_)) => y.cmp(x).reverse(), + // u128 immediates require separate treatment + (Self::U128(x), Self::U128(y)) => x.cmp(y), + (Self::U128(x), y) => { + let y = y.as_u128().expect("expected rhs to be an integer in the range of u128"); + x.cmp(&y) + } + (x, Self::U128(y)) => { + let x = x.as_u128().expect("expected lhs to be an integer in the range of u128"); + x.cmp(y) + } + // i128 immediates require separate treatment + (Self::I128(x), Self::I128(y)) => x.cmp(y), + // We're only comparing against values here which are u64, i64, or smaller than 64-bits + (Self::I128(x), y) => { + let y = y.as_i128().expect("expected rhs to be an integer smaller than i128"); + x.cmp(&y) + } + (x, Self::I128(y)) => { + let x = x.as_i128().expect("expected lhs to be an integer smaller than i128"); + x.cmp(y) + } + // u64 immediates may not fit in an i64 + (Self::U64(x), Self::U64(y)) => x.cmp(y), + // We're only comparing against values here which are i64, or smaller than 64-bits + (Self::U64(x), y) => { + let y = + y.as_i64().expect("expected rhs to be an integer capable of fitting in an i64") + as u64; + x.cmp(&y) + } + (x, Self::U64(y)) => { + let x = + x.as_i64().expect("expected lhs to be an integer capable of fitting in an i64") + as u64; + x.cmp(y) + } + // All immediates at this point are i64 or smaller + (x, y) => { + let x = + x.as_i64().expect("expected lhs to be an integer capable of fitting in an i64"); + let y = + y.as_i64().expect("expected rhs to be an integer capable of fitting in an i64"); + x.cmp(&y) + } + } + } +} +impl From for Type { + #[inline] + fn from(imm: Immediate) -> Self { + imm.ty() + } +} +impl From<&Immediate> for Type { + #[inline(always)] + fn from(imm: &Immediate) -> Self { + imm.ty() + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: bool) -> Self { + Self::I1(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: i8) -> Self { + Self::I8(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: u8) -> Self { + Self::U8(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: i16) -> Self { + Self::I16(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: u16) -> Self { + Self::U16(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: i32) -> Self { + Self::I32(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: u32) -> Self { + Self::U32(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: i64) -> Self { + Self::I64(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: u64) -> Self { + Self::U64(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: u128) -> Self { + Self::U128(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: i128) -> Self { + Self::I128(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: f64) -> Self { + Self::F64(value) + } +} +impl From for Immediate { + #[inline(always)] + fn from(value: char) -> Self { + Self::I32(value as u32 as i32) + } +} + +trait FloatToInt: Sized { + const ZERO: T; + + fn upper_bound() -> Self; + fn lower_bound() -> Self; + fn to_int(self) -> Result; + unsafe fn to_int_unchecked(self) -> T; +} +impl FloatToInt for f64 { + const ZERO: i8 = 0; + + fn upper_bound() -> Self { + f64::from(i8::MAX) + 1.0 + } + + fn lower_bound() -> Self { + f64::from(i8::MIN) - 1.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> i8 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: u8 = 0; + + fn upper_bound() -> Self { + f64::from(u8::MAX) + 1.0 + } + + fn lower_bound() -> Self { + 0.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> u8 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: i16 = 0; + + fn upper_bound() -> Self { + f64::from(i16::MAX) + 1.0 + } + + fn lower_bound() -> Self { + f64::from(i16::MIN) - 1.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> i16 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: u16 = 0; + + fn upper_bound() -> Self { + f64::from(u16::MAX) + 1.0 + } + + fn lower_bound() -> Self { + 0.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> u16 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: i32 = 0; + + fn upper_bound() -> Self { + f64::from(i32::MAX) + 1.0 + } + + fn lower_bound() -> Self { + f64::from(i32::MIN) - 1.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> i32 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: u32 = 0; + + fn upper_bound() -> Self { + f64::from(u32::MAX) + 1.0 + } + + fn lower_bound() -> Self { + 0.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> u32 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: i64 = 0; + + fn upper_bound() -> Self { + 63.0f64.exp2() + } + + fn lower_bound() -> Self { + -63.0f64.exp2() - 1.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> i64 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: u64 = 0; + + fn upper_bound() -> Self { + 64.0f64.exp2() + } + + fn lower_bound() -> Self { + 0.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> u64 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: Felt = Felt::ZERO; + + fn upper_bound() -> Self { + 64.0f64.exp2() - 32.0f64.exp2() + 1.0 + } + + fn lower_bound() -> Self { + 0.0 + } + + fn to_int(self) -> Result { + float_to_int(self).map(Felt::new) + } + + unsafe fn to_int_unchecked(self) -> Felt { + Felt::new(f64::to_int_unchecked::(self)) + } +} +impl FloatToInt for f64 { + const ZERO: u128 = 0; + + fn upper_bound() -> Self { + 128.0f64.exp2() + } + + fn lower_bound() -> Self { + 0.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> u128 { + f64::to_int_unchecked(self) + } +} +impl FloatToInt for f64 { + const ZERO: i128 = 0; + + fn upper_bound() -> Self { + f64::from(i128::BITS - 1).exp2() + } + + fn lower_bound() -> Self { + (-f64::from(i128::BITS - 1)).exp2() - 1.0 + } + + fn to_int(self) -> Result { + float_to_int(self) + } + + unsafe fn to_int_unchecked(self) -> i128 { + f64::to_int_unchecked(self) + } +} + +fn float_to_int(f: f64) -> Result +where + I: Copy, + f64: FloatToInt, +{ + use core::num::FpCategory; + match f.classify() { + FpCategory::Nan | FpCategory::Infinite | FpCategory::Subnormal => Err(()), + FpCategory::Zero => Ok(>::ZERO), + FpCategory::Normal => { + if f == f.trunc() + && f > >::lower_bound() + && f < >::upper_bound() + { + // SAFETY: We know that x must be integral, and within the bounds of its type + Ok(unsafe { >::to_int_unchecked(f) }) + } else { + Err(()) + } + } + } +} diff --git a/hir/src/ir/loops.rs b/hir/src/ir/loops.rs new file mode 100644 index 000000000..394e9ec8d --- /dev/null +++ b/hir/src/ir/loops.rs @@ -0,0 +1,1384 @@ +use alloc::{collections::BTreeMap, format, rc::Rc}; +use core::{ + cell::{Cell, Ref, RefCell, RefMut}, + fmt, +}; + +use smallvec::SmallVec; + +use super::{ + dominance::{DominanceInfo, DominanceTree, PostOrderDomTreeIter}, + RegionKindInterface, RegionRef, +}; +use crate::{ + adt::{SmallDenseMap, SmallSet}, + cfg::{Graph, Inverse, InvertibleGraph}, + pass::Analysis, + BlockRef, Operation, OperationRef, PostOrderBlockIter, Report, +}; + +/// Represents the results of analyzing an [Operation] and computing the [LoopForest] for each of +/// the op's regions. +/// +/// This type implements [Analysis], so can be used in conjunction with other passes. +#[derive(Default)] +pub struct LoopInfo { + per_region: SmallVec<[IntraRegionLoopInfo; 2]>, +} + +/// Represents loop information for a intra-region CFG contained in `region` +struct IntraRegionLoopInfo { + /// The region which contains the CFG + pub region: RegionRef, + /// The loop forest for the CFG in `region` + pub forest: LoopForest, +} + +impl Analysis for LoopInfo { + type Target = Operation; + + fn name(&self) -> &'static str { + "loops" + } + + fn analyze( + &mut self, + op: &Self::Target, + analysis_manager: crate::pass::AnalysisManager, + ) -> Result<(), Report> { + // If the op has no regions, or it does, but they are graph regions, do not compute the + // forest, as it cannot succeed. + if !op.has_regions() + || op + .as_trait::() + .is_some_and(|rki| rki.has_graph_regions()) + { + return Ok(()); + } + + // First, obtain the dominance info for this op + let dominfo = analysis_manager.get_analysis::()?; + // Then compute the forests for each region of the op + for region in op.regions() { + // If this region has a single block, the loop forest is empty + if region.has_one_block() { + self.per_region.push(IntraRegionLoopInfo { + region: region.as_region_ref(), + forest: LoopForest::default(), + }); + continue; + } + + // Otherwise, compute it for this region + let region = region.as_region_ref(); + let forest = LoopForest::new(&dominfo.info().dominance(region)); + self.per_region.push(IntraRegionLoopInfo { region, forest }); + } + + Ok(()) + } + + fn invalidate(&self, preserved_analyses: &mut crate::pass::PreservedAnalyses) -> bool { + // Don't invalidate the LoopForest analysis unless the dominance tree was invalidated + !preserved_analyses.is_preserved::() + } +} + +impl LoopInfo { + /// Returns true if the op this info was derived from contains any loops + pub fn has_loops(&self) -> bool { + !self.per_region.is_empty() && !self.per_region.iter().any(|info| !info.forest.is_empty()) + } + + /// Returns true if `region` has loops according to this loop info + pub fn region_has_loops(&self, region: &RegionRef) -> bool { + self.per_region + .iter() + .find_map(|info| { + if &info.region == region { + Some(!info.forest.is_empty()) + } else { + None + } + }) + .unwrap_or(false) + } + + /// Get the [LoopForest] for `region` + pub fn get(&self, region: &RegionRef) -> Option<&LoopForest> { + self.per_region.iter().find_map(|info| { + if &info.region == region { + Some(&info.forest) + } else { + None + } + }) + } +} + +/// [LoopForest] represents all of the top-level loop structures in a specified region. +/// +/// The [LoopForest] analysis is used to identify natural loops and determine the loop depth of +/// various nodes in a generic graph of blocks. A natural loop has exactly one entry-point, which +/// is called the header. Note that natural loops may actually be several loops that share the same +/// header node. +/// +/// This analysis calculates the nesting structure of loops in a function. For each natural loop +/// identified, this analysis identifies natural loops contained entirely within the loop and the +/// basic blocks that make up the loop. +/// +/// It can calculate on the fly various bits of information, for example: +/// +/// * Whether there is a preheader for the loop +/// * The number of back edges to the header +/// * Whether or not a particular block branches out of the loop +/// * The successor blocks of the loop +/// * The loop depth +/// * etc... +/// +/// Note that this analysis specifically identifies _loops_ not cycles or SCCs in the graph. There +/// can be strongly connected components in the graph which this analysis will not recognize and +/// that will not be represented by a loop instance. In particular, a loop might be inside such a +/// non-loop SCC, or a non-loop SCC might contain a sub-SCC which is a loop. +/// +/// For an overview of terminology used in this API (and thus all related loop analyses or +/// transforms), see [Loop Terminology](https://llvm.org/docs/LoopTerminology.html). +#[derive(Default)] +pub struct LoopForest { + /// The set of top-level loops in the forest + top_level_loops: SmallVec<[Rc; 4]>, + /// Mapping of basic blocks to the inner most loop they occur in + block_map: BTreeMap>, +} + +impl LoopForest { + /// Compute a new [LoopForest] from the given dominator tree. + pub fn new(tree: &DominanceTree) -> Self { + let mut forest = Self::default(); + forest.analyze(tree); + forest + } + + /// Returns true if there are no loops in the forest + pub fn is_empty(&self) -> bool { + self.top_level_loops.is_empty() + } + + /// Returns the number of loops in the forest + pub fn len(&self) -> usize { + self.top_level_loops.len() + } + + /// Returns true if `block` is in this loop forest + #[inline] + pub fn contains_block(&self, block: BlockRef) -> bool { + self.block_map.contains_key(&block) + } + + /// Get the set of top-level/outermost loops in the forest + pub fn top_level_loops(&self) -> &[Rc] { + &self.top_level_loops + } + + /// Return all of the loops in the function in preorder across the loop nests, with siblings in + /// forward program order. + /// + /// Note that because loops form a forest of trees, preorder is equivalent to reverse postorder. + pub fn loops_in_preorder(&self) -> SmallVec<[Rc; 4]> { + // The outer-most loop actually goes into the result in the same relative order as we walk + // it. But LoopForest stores the top level loops in reverse program order so for here we + // reverse it to get forward program order. + // + // FIXME: If we change the order of LoopForest we will want to remove the reverse here. + let mut preorder_loops = SmallVec::<[Rc; 4]>::default(); + for l in self.top_level_loops.iter().cloned().rev() { + let mut loops_in_preorder = l.loops_in_preorder(); + preorder_loops.append(&mut loops_in_preorder); + } + preorder_loops + } + + /// Return all of the loops in the function in preorder across the loop nests, with siblings in + /// _reverse_ program order. + /// + /// Note that because loops form a forest of trees, preorder is equivalent to reverse postorder. + /// + /// Also note that this is _not_ a reverse preorder. Only the siblings are in reverse program + /// order. + pub fn loops_in_reverse_sibling_preorder(&self) -> SmallVec<[Rc; 4]> { + // The outer-most loop actually goes into the result in the same relative order as we walk + // it. LoopForest stores the top level loops in reverse program order so we walk in order + // here. + // + // FIXME: If we change the order of LoopInfo we will want to add a reverse here. + let mut preorder_loops = SmallVec::<[Rc; 4]>::default(); + let mut preorder_worklist = SmallVec::<[Rc; 4]>::default(); + for l in self.top_level_loops.iter().cloned() { + assert!(preorder_worklist.is_empty()); + preorder_worklist.push(l); + while let Some(l) = preorder_worklist.pop() { + // Sub-loops are stored in forward program order, but will process the worklist + // backwards so we can just append them in order. + preorder_worklist.extend(l.nested().iter().cloned()); + preorder_loops.push(l); + } + } + + preorder_loops + } + + /// Return the inner most loop that `block` lives in. + /// + /// If a basic block is in no loop (for example the entry node), `None` is returned. + pub fn loop_for(&self, block: BlockRef) -> Option> { + self.block_map.get(&block).cloned() + } + + /// Return the loop nesting level of the specified block. + /// + /// A depth of 0 means the block is not inside any loop. + pub fn loop_depth(&self, block: BlockRef) -> usize { + self.loop_for(block).map(|l| l.depth()).unwrap_or(0) + } + + /// Returns true if the block is a loop header + pub fn is_loop_header(&self, block: BlockRef) -> bool { + self.loop_for(block).map(|l| l.header() == block).unwrap_or(false) + } + + /// This removes the specified top-level loop from this loop info object. + /// + /// The loop is not deleted, as it will presumably be inserted into another loop. + /// + /// # Panics + /// + /// This function will panic if the given loop is not a top-level loop + pub fn remove_loop(&mut self, l: &Loop) -> Option> { + assert!(l.is_outermost(), "`l` is not an outermost loop"); + let index = self.top_level_loops.iter().position(|tll| core::ptr::addr_eq(&**tll, l))?; + Some(self.top_level_loops.swap_remove(index)) + } + + /// Change the top-level loop that contains `block` to the specified loop. + /// + /// This should be used by transformations that restructure the loop hierarchy tree. + pub fn change_loop_for(&mut self, block: BlockRef, l: Option>) { + if let Some(l) = l { + self.block_map.insert(block, l); + } else { + self.block_map.remove(&block); + } + } + + /// Replace the specified loop in the top-level loops list with the indicated loop. + pub fn change_top_level_loop(&mut self, old: Rc, new: Rc) { + assert!( + new.parent_loop().is_none() && old.parent_loop().is_none(), + "loops already embedded into a subloop" + ); + let index = self + .top_level_loops + .iter() + .position(|tll| Rc::ptr_eq(tll, &old)) + .expect("`old` loop is not a top-level loop"); + self.top_level_loops[index] = new; + } + + /// This adds the specified loop to the collection of top-level loops. + pub fn add_top_level_loop(&mut self, l: Rc) { + assert!(l.is_outermost(), "loop already in subloop"); + self.top_level_loops.push(l); + } + + /// This method completely removes `block` from all data structures, including all of the loop + /// objects it is nested in and our mapping from basic blocks to loops. + pub fn remove_block(&mut self, block: BlockRef) { + if let Some(l) = self.block_map.remove(&block) { + let mut next_l = Some(l); + while let Some(l) = next_l.take() { + next_l = l.parent_loop(); + l.remove_block_from_loop(block); + } + } + } + + pub fn is_not_already_contained_in(sub_loop: Option<&Loop>, parent: Option<&Loop>) -> bool { + let Some(sub_loop) = sub_loop else { + return true; + }; + if parent.is_some_and(|parent| parent == sub_loop) { + return false; + } + Self::is_not_already_contained_in(sub_loop.parent_loop().as_deref(), parent) + } + + /// Analyze the given dominance tree to discover loops. + /// + /// The analysis discovers loops during a post-order traversal of the given dominator tree, + /// interleaved with backward CFG traversals within each subloop + /// (see `discover_and_map_subloop`). The backward traversal skips inner subloops, so this part + /// of the algorithm is linear in the number of CFG edges. Subloop and block vectors are then + /// populated during a single forward CFG traversal. + /// + /// During the two CFG traversals each block is seen three times: + /// + /// 1. Discovered and mapped by a reverse CFG traversal. + /// 2. Visited during a forward DFS CFG traversal. + /// 3. Reverse-inserted in the loop in postorder following forward DFS. + /// + /// The block vectors are inclusive, so step 3 requires loop-depth number of insertions per + /// block. + pub fn analyze(&mut self, tree: &DominanceTree) { + // Postorder traversal of the dominator tree. + let Some(root) = tree.root_node() else { + return; + }; + for node in PostOrderDomTreeIter::new(root.clone()) { + let header = node.block().expect("expected header block"); + let mut backedges = SmallVec::<[BlockRef; 4]>::default(); + + // Check each predecessor of the potential loop header. + for backedge in BlockRef::inverse_children(header) { + // If `header` dominates `pred`, this is a new loop. Collect the backedges. + let backedge_node = tree.get(Some(backedge)); + if backedge_node.is_some() && tree.dominates_node(Some(node.clone()), backedge_node) + { + backedges.push(backedge); + } + } + + // Perform a backward CFG traversal to discover and map blocks in this loop. + if !backedges.is_empty() { + let l = Rc::new(Loop::new(header)); + self.discover_and_map_sub_loop(l, backedges, tree); + } + } + + // Perform a single forward CFG traversal to populate blocks and subloops for all loops. + for block in PostOrderBlockIter::new(root.block().unwrap()) { + self.insert_into_loop(block); + } + } + + /// Discover a subloop with the specified backedges such that: + /// + /// * All blocks within this loop are mapped to this loop or a subloop. + /// * All subloops within this loop have their parent loop set to this loop or a subloop. + fn discover_and_map_sub_loop( + &mut self, + l: Rc, + backedges: SmallVec<[BlockRef; 4]>, + tree: &DominanceTree, + ) { + let mut num_blocks = 0usize; + let mut num_subloops = 0usize; + + // Perform a backward CFG traversal using a worklist. + let mut reverse_cfg_worklist = backedges; + while let Some(pred) = reverse_cfg_worklist.pop() { + match self.loop_for(pred) { + None if !tree.is_reachable_from_entry(pred) => continue, + None => { + // This is an undiscovered block. Map it to the current loop. + self.change_loop_for(pred, Some(l.clone())); + num_blocks += 1; + if pred == l.header() { + continue; + } + + // Push all block predecessors on the worklist + reverse_cfg_worklist.extend(Inverse::::children(pred)); + } + Some(subloop) => { + // This is a discovered block. Find its outermost discovered loop. + let subloop = subloop.outermost_loop(); + + // If it is already discovered to be a subloop of this loop, continue. + if subloop == l { + continue; + } + + // Discover a subloop of this loop. + subloop.set_parent_loop(Some(l.clone())); + num_subloops += 1; + num_blocks += subloop.num_blocks(); + + // Continue traversal along predecessors that are not loop-back edges from + // within this subloop tree itself. Note that a predecessor may directly reach + // another subloop that is not yet discovered to be a subloop of this loop, + // which we must traverse. + for pred in BlockRef::inverse_children(subloop.header()) { + if self.loop_for(pred).is_none_or(|l| l != subloop) { + reverse_cfg_worklist.push(pred); + } + } + } + } + } + + l.nested.borrow_mut().reserve(num_subloops); + l.reserve(num_blocks); + } + + /// Add a single block to its ancestor loops in post-order. + /// + /// If the block is a subloop header, add the subloop to its parent in post-order, then reverse + /// the block and subloop vectors of the now complete subloop to achieve RPO. + fn insert_into_loop(&mut self, block: BlockRef) { + let mut subloop = self.loop_for(block); + if let Some(sl) = subloop.clone().filter(|sl| sl.header() == block) { + let parent = sl.parent_loop(); + // We reach this point once per subloop after processing all the blocks in the subloop. + if sl.is_outermost() { + self.add_top_level_loop(sl.clone()); + } else { + parent.as_ref().unwrap().nested.borrow_mut().push(sl.clone()); + } + + // For convenience, blocks and subloops are inserted in postorder. Reverse the lists, + // except for the loop header, which is always at the beginning. + sl.reverse_blocks(1); + sl.nested.borrow_mut().reverse(); + subloop = parent; + } + + while let Some(sl) = subloop.take() { + sl.add_block_entry(block); + subloop = sl.parent_loop(); + } + } + + /// Verify the loop forest structure using the provided [DominanceTree] + pub fn verify(&self, tree: &DominanceTree) -> Result<(), Report> { + let mut loops = SmallSet::, 2>::default(); + for l in self.top_level_loops.iter().cloned() { + if !l.is_outermost() { + return Err(Report::msg("top-level loop has a parent")); + } + l.verify_loop_nest(&mut loops)?; + } + + if cfg!(debug_assertions) { + // Verify that blocks are mapped to valid loops. + for (block, block_loop) in self.block_map.iter() { + let block = *block; + if !loops.contains(block_loop) { + return Err(Report::msg("orphaned loop")); + } + if !block_loop.contains_block(block) { + return Err(Report::msg("orphaned block")); + } + for child_loop in block_loop.nested().iter() { + if child_loop.contains_block(block) { + return Err(Report::msg( + "expected block map to reflect the innermost loop containing `block`", + )); + } + } + } + + // Recompute forest to verify loops structure. + let other = LoopForest::new(tree); + + // Build a map we can use to move from our forest to the newly computed one. This allows + // us to ignore the particular order in any layer of the loop forest while still + // comparing the structure. + let mut other_headers = SmallDenseMap::, 8>::default(); + + fn add_inner_loops_to_headers_map( + headers: &mut SmallDenseMap, 8>, + l: &Rc, + ) { + let header = l.header(); + headers.insert(header, Rc::clone(l)); + for sl in l.nested().iter() { + add_inner_loops_to_headers_map(headers, sl); + } + } + + for l in other.top_level_loops() { + add_inner_loops_to_headers_map(&mut other_headers, l); + } + + // Walk the top level loops and ensure there is a corresponding top-level loop in the + // computed version and then recursively compare those loop nests. + for l in self.top_level_loops() { + let header = l.header(); + let other_l = other_headers.remove(&header); + match other_l { + None => { + return Err(Report::msg( + "top level loop is missing in computed loop forest", + )) + } + Some(other_l) => { + // Recursively compare the loops + Self::compare_loops(l.clone(), other_l, &mut other_headers)?; + } + } + } + + // Any remaining entries in the map are loops which were found when computing a fresh + // loop forest but not present in the current one. + if !other_headers.is_empty() { + for (_header, header_loop) in other_headers { + log::trace!("Found new loop {header_loop:?}"); + } + return Err(Report::msg("found new loops when recomputing loop forest")); + } + } + + Ok(()) + } + + #[cfg(debug_assertions)] + fn compare_loops( + l: Rc, + other_l: Rc, + other_loop_headers: &mut SmallDenseMap, 8>, + ) -> Result<(), Report> { + use crate::EntityWithId; + + let header = l.header(); + let other_header = other_l.header(); + if header != other_header { + return Err(Report::msg( + "mismatched headers even though found under the same map entry", + )); + } + + if l.depth() != other_l.depth() { + return Err(Report::msg("mismatched loop depth")); + } + + { + let mut parent_l = Some(l.clone()); + let mut other_parent_l = Some(other_l.clone()); + while let Some(pl) = parent_l.take() { + if let Some(opl) = other_parent_l.take() { + if pl.header() != opl.header() { + return Err(Report::msg("mismatched parent loop headers")); + } + parent_l = pl.parent_loop(); + other_parent_l = opl.parent_loop(); + } else { + return Err(Report::msg( + "`other_l` misreported its depth: expected a parent and got none", + )); + } + } + } + + for sl in l.nested().iter() { + let sl_header = sl.header(); + let other_sl = other_loop_headers.remove(&sl_header); + match other_sl { + None => return Err(Report::msg("inner loop is missing in computed loop forest")), + Some(other_sl) => { + Self::compare_loops(sl.clone(), other_sl, other_loop_headers)?; + } + } + } + + let mut blocks = l.blocks.borrow().clone(); + let mut other_blocks = other_l.blocks.borrow().clone(); + blocks.sort_by_key(|b| b.borrow().id()); + other_blocks.sort_by_key(|b| b.borrow().id()); + if blocks != other_blocks { + log::trace!("blocks: {}", crate::formatter::DisplayValues::new(blocks.iter())); + log::trace!( + "other_blocks: {}", + crate::formatter::DisplayValues::new(other_blocks.iter()) + ); + return Err(Report::msg("loops report mismatched blocks")); + } + + let block_set = l.block_set(); + let other_block_set = other_l.block_set(); + let diff = block_set.symmetric_difference(&other_block_set); + if block_set.len() != other_block_set.len() || !diff.is_empty() { + log::trace!( + "block_set: {}", + crate::formatter::DisplayValues::new(block_set.iter()) + ); + log::trace!( + "other_block_set: {}", + crate::formatter::DisplayValues::new(other_block_set.iter()) + ); + log::trace!("diff: {}", crate::formatter::DisplayValues::new(diff.iter())); + return Err(Report::msg("loops report mismatched block sets")); + } + + Ok(()) + } + + #[cfg(not(debug_assertions))] + fn compare_loops( + _l: Rc, + _other_l: Rc, + _other_loop_headers: &mut SmallDenseMap, 8>, + ) -> Result<(), Report> { + Ok(()) + } +} + +impl fmt::Debug for LoopForest { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("LoopInfo") + .field("top_level_loops", &self.top_level_loops) + .field("block_map", &self.block_map) + .finish() + } +} + +/// Edge type. +pub type LoopEdge = (BlockRef, BlockRef); + +/// [Loop] is used to represent loops that are detected in the control-flow graph. +#[derive(Default)] +pub struct Loop { + /// If this loop is an outermost loop, this field is `None`. + /// + /// Otherwise, it holds a handle to the parent loop which transfers control to this loop. + parent_loop: Cell>>, + /// Loops contained entirely within this one. + /// + /// All of the loops in this set will have their `parent` set to this loop + nested: RefCell; 2]>>, + /// The list of blocks in this loop. + /// + /// The header block is always at index 0. + blocks: RefCell>, + /// The uniqued set of blocks present in this loop + block_set: RefCell>, +} + +impl Eq for Loop {} +impl PartialEq for Loop { + fn eq(&self, other: &Self) -> bool { + core::ptr::addr_eq(self, other) + } +} + +impl Loop { + /// Create a new [Loop] with `block` as its header. + pub fn new(block: BlockRef) -> Self { + let mut this = Self::default(); + this.blocks.get_mut().push(block); + this.block_set.get_mut().insert(block); + this + } + + /// Get the nesting level of this loop. + /// + /// An outer-most loop has depth 1, for consistency with loop depth values used for basic + /// blocks, where depth 0 is used for blocks not inside any loops. + pub fn depth(&self) -> usize { + let mut depth = 1; + let mut current_loop = self.parent_loop(); + while let Some(curr) = current_loop.take() { + depth += 1; + current_loop = curr.parent_loop(); + } + depth + } + + /// Get the header block of this loop + pub fn header(&self) -> BlockRef { + self.blocks.borrow()[0] + } + + /// Return the parent loop of this loop, if it has one, or `None` if it is a top-level loop. + /// + /// A loop is either top-level in a function (that is, it is not contained in any other loop) or + /// it is entirely enclosed in some other loop. If a loop is top-level, it has no parent, + /// otherwise its parent is the innermost loop in which it is enclosed. + pub fn parent_loop(&self) -> Option> { + unsafe { (*self.parent_loop.as_ptr()).clone() } + } + + /// This is a low-level API for bypassing [add_child_loop]. + pub fn set_parent_loop(&self, parent: Option>) { + self.parent_loop.set(parent); + } + + /// Discover the outermost loop that contains `self` + pub fn outermost_loop(self: Rc) -> Rc { + let mut l = self; + while let Some(parent) = l.parent_loop() { + l = parent; + } + l + } + + /// Return true if the specified loop is contained within in this loop. + pub fn contains(&self, l: Rc) -> bool { + if core::ptr::addr_eq(self, &*l) { + return true; + } + + let Some(parent) = l.parent_loop() else { + return false; + }; + + self.contains(parent) + } + + /// Returns true if the specified basic block is in this loop + pub fn contains_block(&self, block: BlockRef) -> bool { + self.block_set.borrow().contains(&block) + } + + /// Returns true if the specified operation is in this loop + pub fn contains_op(&self, op: &OperationRef) -> bool { + let Some(block) = op.parent() else { + return false; + }; + self.contains_block(block) + } + + /// Return the loops contained entirely within this loop. + pub fn nested(&self) -> Ref<'_, [Rc]> { + Ref::map(self.nested.borrow(), |nested| nested.as_slice()) + } + + /// Return true if the loop does not contain any (natural) loops. + /// + /// [Loop] does not detect irreducible control flow, just natural loops. That is, it is possible + /// that there is cyclic control flow within the innermost loop or around the outermost loop. + pub fn is_innermost(&self) -> bool { + self.nested.borrow().is_empty() + } + + /// Return true if the loop does not have a parent (natural) loop (i.e. it is outermost, which + /// is the same as top-level). + pub fn is_outermost(&self) -> bool { + unsafe { (*self.parent_loop.as_ptr()).is_none() } + } + + /// Get a list of the basic blocks which make up this loop. + pub fn blocks(&self) -> Ref<'_, [BlockRef]> { + Ref::map(self.blocks.borrow(), |blocks| blocks.as_slice()) + } + + /// Get a mutable reference to the basic blocks which make up this loop. + pub fn blocks_mut(&self) -> RefMut<'_, SmallVec<[BlockRef; 32]>> { + self.blocks.borrow_mut() + } + + /// Return the number of blocks contained in this loop + pub fn num_blocks(&self) -> usize { + self.blocks.borrow().len() + } + + /// Return a reference to the blocks set. + pub fn block_set(&self) -> Ref<'_, SmallSet> { + self.block_set.borrow() + } + + /// Return a mutable reference to the blocks set. + pub fn block_set_mut(&self) -> RefMut<'_, SmallSet> { + self.block_set.borrow_mut() + } + + /// Returns true if the terminator of `block` can branch to another block that is outside of the + /// current loop. + /// + /// # Panics + /// + /// This function will panic if `block` is not inside this loop. + pub fn is_loop_exiting(&self, block: BlockRef) -> bool { + assert!(self.contains_block(block), "exiting block must be part of the loop"); + BlockRef::children(block).any(|succ| !self.contains_block(succ)) + } + + /// Returns true if `block` is a loop-latch. + /// + /// A latch block is a block that contains a branch back to the header. + /// + /// This function is useful when there are multiple latches in a loop because `get_loop_latch` + /// will return `None` in that case. + pub fn is_loop_latch(&self, block: BlockRef) -> bool { + assert!(self.contains_block(block), "block does not belong to the loop"); + BlockRef::inverse_children(self.header()).any(|pred| pred == block) + } + + /// Calculate the number of back edges to the loop header + pub fn num_backedges(&self) -> usize { + BlockRef::inverse_children(self.header()) + .filter(|pred| self.contains_block(*pred)) + .count() + } +} + +/// Loop Analysis +/// +/// Note that all of these methods can fail on general loops (ie, there may not be a preheader, +/// etc). For best success, the loop simplification and induction variable canonicalization pass +/// should be used to normalize loops for easy analysis. These methods assume canonical loops. +impl Loop { + /// Get all blocks inside the loop that have successors outside of the loop. + /// + /// These are the blocks _inside of the current loop_ which branch out. The returned list is + /// always unique. + pub fn exiting_blocks(&self) -> SmallVec<[BlockRef; 2]> { + let mut exiting_blocks = SmallVec::default(); + for block in self.blocks.borrow().iter().copied() { + for succ in BlockRef::children(block) { + // A block must be an exit block if it is not contained in the current loop + if !self.contains_block(succ) { + exiting_blocks.push(block); + break; + } + } + } + exiting_blocks + } + + /// If [Self::exiting_blocks] would return exactly one block, return it, otherwise `None`. + pub fn exiting_block(&self) -> Option { + let mut exiting_block = None; + for block in self.blocks.borrow().iter().copied() { + for succ in BlockRef::children(block) { + if !self.contains_block(succ) { + if exiting_block.is_some() { + return None; + } else { + exiting_block = Some(block); + } + break; + } + } + } + exiting_block + } + + /// Get all of the successor blocks of this loop. + /// + /// These are the blocks _outside of the current loop_ which are branched to. + pub fn exit_blocks(&self) -> SmallVec<[BlockRef; 2]> { + let mut exit_blocks = SmallVec::default(); + for block in self.blocks.borrow().iter().copied() { + for succ in BlockRef::children(block) { + if !self.contains_block(succ) { + exit_blocks.push(succ); + } + } + } + exit_blocks + } + + /// If [Self::exit_blocks] would return exactly one block, return it, otherwise `None`. + pub fn exit_block(&self) -> Option { + let mut exit_block = None; + for block in self.blocks.borrow().iter().copied() { + for succ in BlockRef::children(block) { + if !self.contains_block(succ) { + if exit_block.is_some() { + return None; + } else { + exit_block = Some(succ); + } + } + } + } + exit_block + } + + /// Returns true if no exit block for the loop has a predecessor that is outside the loop. + pub fn has_dedicated_exits(&self) -> bool { + // Each predecessor of each exit block of a normal loop is contained within the loop. + for exit_block in self.unique_exit_blocks() { + for pred in BlockRef::inverse_children(exit_block) { + if !self.contains_block(pred) { + return false; + } + } + } + + // All the requirements are met. + true + } + + /// Return all unique successor blocks of this loop. + /// + /// These are the blocks _outside of the current loop_ which are branched to. + pub fn unique_exit_blocks(&self) -> SmallVec<[BlockRef; 2]> { + let mut unique_exits = SmallVec::default(); + unique_exit_blocks_helper(self, &mut unique_exits, |_| true); + unique_exits + } + + /// Return all unique successor blocks of this loop, except successors from the latch block + /// which are not considered. If an exit that comes from the latch block, but also has a non- + /// latch predecessor in the loop, it will be included. + /// + /// These are the blocks _outside of the current loop_ which are branched to. + pub fn unique_non_latch_exit_blocks(&self) -> SmallVec<[BlockRef; 2]> { + let latch_block = self.loop_latch().expect("latch must exist"); + let mut unique_exits = SmallVec::default(); + unique_exit_blocks_helper(self, &mut unique_exits, |block| block != latch_block); + unique_exits + } + + /// If [Self::unique_exit_blocks] would return exactly one block, return it, otherwise `None`. + #[inline] + pub fn unique_exit_block(&self) -> Option { + self.exit_block() + } + + /// Return true if this loop does not have any exit blocks. + pub fn has_no_exit_blocks(&self) -> bool { + for block in self.blocks.borrow().iter().copied() { + for succ in BlockRef::children(block) { + if !self.contains_block(succ) { + return false; + } + } + } + true + } + + /// Return all pairs of (_inside_block_, _outside_block_). + pub fn exit_edges(&self) -> SmallVec<[LoopEdge; 2]> { + let mut exit_edges = SmallVec::default(); + for block in self.blocks.borrow().iter().copied() { + for succ in BlockRef::children(block) { + if !self.contains_block(succ) { + exit_edges.push((block, succ)); + } + } + } + exit_edges + } + + /// Returns the pre-header for this loop, if there is one. + /// + /// A loop has a pre-header if there is only one edge to the header of the loop from outside of + /// the loop. If this is the case, the block branching to the header of the loop is the + /// pre-header node. + /// + /// This returns `None` if there is no pre-header for the loop. + pub fn preheader(&self) -> Option { + use crate::IteratorExt; + + // Keep track of nodes outside the loop branching to the header... + let out = self.loop_predecessor()?; + + // Make sure we are allowed to hoist instructions into the predecessor. + if !out.borrow().is_legal_to_hoist_into() { + return None; + } + + // Make sure there is only one exit out of the preheader. + if !BlockRef::children(out).has_single_element() { + // Multiple exits from the block, must not be a preheader. + return None; + } + + // The predecessor has exactly one successor, so it is a preheader. + Some(out) + } + + /// If the given loop's header has exactly one unique predecessor outside the loop, return it. + /// + /// This is less strict than the loop "preheader" concept, which requires the predecessor to + /// have exactly one successor. + pub fn loop_predecessor(&self) -> Option { + // Keep track of nodes outside the loop branching to the header... + let mut out = None; + // Loop over the predecessors of the header node... + let header = self.header(); + for pred in BlockRef::inverse_children(header) { + if !self.contains_block(pred) { + if out.as_ref().is_some_and(|out| out != &pred) { + // Multiple predecessors outside the loop + return None; + } + out = Some(pred); + } + } + out + } + + /// If there is a single latch block for this loop, return it. + /// + /// A latch block is a block that contains a branch back to the header. + pub fn loop_latch(&self) -> Option { + let header = self.header(); + let mut latch_block = None; + for pred in BlockRef::inverse_children(header) { + if self.contains_block(pred) { + if latch_block.is_some() { + return None; + } + latch_block = Some(pred); + } + } + latch_block + } + + /// Get all loop latch blocks of this loop. + /// + /// A latch block is a block that contains a branch back to the header. + pub fn loop_latches(&self) -> SmallVec<[BlockRef; 2]> { + BlockRef::inverse_children(self.header()) + .filter(|pred| self.contains_block(*pred)) + .collect() + } + + /// Return all inner loops in the loop nest rooted by the loop in preorder, with siblings in + /// forward program order. + pub fn inner_loops_in_preorder(&self) -> SmallVec<[Rc; 2]> { + let mut worklist = SmallVec::<[Rc; 4]>::default(); + worklist.extend(self.nested().iter().rev().cloned()); + + let mut results = SmallVec::default(); + while let Some(l) = worklist.pop() { + // Sub-loops are stored in forward program order, but will process the + // worklist backwards so append them in reverse order. + worklist.extend(l.nested().iter().rev().cloned()); + results.push(l); + } + + results + } + + /// Return all loops in the loop nest rooted by the loop in preorder, with siblings in forward + /// program order. + pub fn loops_in_preorder(self: Rc) -> SmallVec<[Rc; 2]> { + let mut loops = self.inner_loops_in_preorder(); + loops.insert(0, self); + loops + } +} + +fn unique_exit_blocks_helper( + l: &Loop, + exit_blocks: &mut SmallVec<[BlockRef; 2]>, + mut predicate: F, +) where + F: FnMut(BlockRef) -> bool, +{ + let mut visited = SmallSet::::default(); + for block in l.blocks.borrow().iter().copied().filter(|b| predicate(*b)) { + for succ in BlockRef::children(block) { + if !l.contains_block(succ) && visited.insert(succ) { + exit_blocks.push(succ); + } + } + } +} + +/// Updates +impl Loop { + /// Add `block` to this loop, and as a member of all parent loops. + /// + /// It is not valid to replace the loop header using this function. + /// + /// This is intended for use by analyses which need to update loop information. + pub fn add_block_to_loop(self: Rc, block: BlockRef, forest: &mut LoopForest) { + assert!(!forest.contains_block(block), "`block` is already in this loop"); + + // Add the loop mapping to the LoopForest object... + forest.block_map.insert(block, self.clone()); + + // Add the basic block to this loop and all parent loops... + let mut next_l = Some(self); + while let Some(l) = next_l.take() { + l.add_block_entry(block); + next_l = l.parent_loop(); + } + } + + /// Replace `prev` with `new` in the set of children of this loop, updating the parent pointer + /// of `prev` to `None`, and of `new` to `self`. + /// + /// This also updates the loop depth of the new child. + /// + /// This is intended for use when splitting loops up. + pub fn replace_child_loop_with(self: Rc, prev: Rc, new: Rc) { + assert_eq!(prev.parent_loop().as_ref(), Some(&self), "this loop is already broken"); + assert!(new.parent_loop().is_none(), "`new` already has a parent"); + + // Set the parent of `new` to `self` + new.set_parent_loop(Some(self.clone())); + // Replace `prev` in `self.nested` with `new` + let mut nested = self.nested.borrow_mut(); + let entry = nested.iter_mut().find(|l| Rc::ptr_eq(l, &prev)).expect("`prev` not in loop"); + let _ = core::mem::replace(entry, new); + // Set the parent of `prev` to `None` + prev.set_parent_loop(None); + } + + /// Add the specified loop to be a child of this loop. + /// + /// This updates the loop depth of the new child. + pub fn add_child_loop(self: Rc, child: Rc) { + assert!(child.parent_loop().is_none(), "child already has a parent"); + child.set_parent_loop(Some(self.clone())); + self.nested.borrow_mut().push(child); + } + + /// This removes subloops of this loop based on the provided predicate, and returns them in a + /// vector. + /// + /// The loops are not deleted, as they will presumably be inserted into another loop. + pub fn take_child_loops(&self, should_remove: F) -> SmallVec<[Rc; 2]> + where + F: Fn(&Loop) -> bool, + { + let mut taken = SmallVec::default(); + self.nested.borrow_mut().retain(|l| { + if should_remove(l) { + l.set_parent_loop(None); + taken.push(Rc::clone(l)); + false + } else { + true + } + }); + taken + } + + /// This removes the specified child from being a subloop of this loop. + /// + /// The loop is not deleted, as it will presumably be inserted into another loop. + pub fn take_child_loop(&self, child: &Loop) -> Option> { + let mut nested = self.nested.borrow_mut(); + let index = nested.iter().position(|l| core::ptr::addr_eq(&**l, child))?; + Some(nested.swap_remove(index)) + } + + /// This adds a basic block directly to the basic block list. + /// + /// This should only be used by transformations that create new loops. Other transformations + /// should use [add_block_to_loop]. + pub fn add_block_entry(&self, block: BlockRef) { + self.blocks.borrow_mut().push(block); + self.block_set.borrow_mut().insert(block); + } + + /// Reverse the order of blocks in this loop starting from `index` to the end. + pub fn reverse_blocks(&self, index: usize) { + self.blocks.borrow_mut()[index..].reverse(); + } + + /// Reserve capacity for `capacity` blocks + pub fn reserve(&self, capacity: usize) { + self.blocks.borrow_mut().reserve(capacity); + } + + /// This method is used to move `block` (which must be part of this loop) to be the loop header + /// of the loop (the block that dominates all others). + pub fn move_to_header(&self, block: BlockRef) { + let mut blocks = self.blocks.borrow_mut(); + let index = blocks.iter().position(|b| *b == block).expect("loop does not contain `block`"); + if index == 0 { + return; + } + unsafe { + blocks.swap_unchecked(0, index); + } + } + + /// This removes the specified basic block from the current loop, updating the `self.blocks` as + /// appropriate. This does not update the mapping in the corresponding [LoopInfo]. + pub fn remove_block_from_loop(&self, block: BlockRef) { + let mut blocks = self.blocks.borrow_mut(); + let index = blocks.iter().position(|b| *b == block).expect("loop does not contain `block`"); + blocks.swap_remove(index); + self.block_set.borrow_mut().remove(&block); + } + + /// Verify loop structure + #[cfg(debug_assertions)] + pub fn verify_loop(&self) -> Result<(), Report> { + use crate::PreOrderBlockIter; + + if self.blocks.borrow().is_empty() { + return Err(Report::msg("loop header is missing")); + } + + // Setup for using a depth-first iterator to visit every block in the loop. + let exit_blocks = self.exit_blocks(); + let mut visit_set = SmallSet::::default(); + visit_set.extend(exit_blocks.iter().cloned()); + + // Keep track of the BBs visited. + let mut visited_blocks = SmallSet::::default(); + + // Check the individual blocks. + let header = self.header(); + for block in PreOrderBlockIter::new_with_visited(header, exit_blocks.iter().cloned()) { + let has_in_loop_successors = BlockRef::children(block).any(|b| self.contains_block(b)); + if !has_in_loop_successors { + return Err(Report::msg("loop block has no in-loop successors")); + } + + let has_in_loop_predecessors = + BlockRef::inverse_children(block).any(|b| self.contains_block(b)); + if !has_in_loop_predecessors { + return Err(Report::msg("loop block has no in-loop predecessors")); + } + + let outside_loop_preds = BlockRef::inverse_children(block) + .filter(|b| !self.contains_block(*b)) + .collect::>(); + + if block == header && outside_loop_preds.is_empty() { + return Err(Report::msg("loop is unreachable")); + } else if !outside_loop_preds.is_empty() { + // A non-header loop shouldn't be reachable from outside the loop, though it is + // permitted if the predecessor is not itself actually reachable. + let entry = block.parent().unwrap().borrow().entry_block_ref().unwrap(); + for child_block in PreOrderBlockIter::new(entry) { + if outside_loop_preds.iter().any(|pred| &child_block == pred) { + return Err(Report::msg("loop has multiple entry points")); + } + } + } + if block != header.parent().unwrap().borrow().entry_block_ref().unwrap() { + return Err(Report::msg("loop contains region entry block")); + } + visited_blocks.insert(block); + } + + if visited_blocks.len() != self.num_blocks() { + log::trace!("The following blocks are unreachable in the loop: "); + for block in self.blocks().iter() { + if !visited_blocks.contains(block) { + log::trace!("{block}"); + } + } + return Err(Report::msg("unreachable block in loop")); + } + + // Check the subloops + for subloop in self.nested().iter() { + // Each block in each subloop should be contained within this loop. + for block in subloop.blocks().iter() { + if !self.contains_block(*block) { + return Err(Report::msg( + "loop does not contain all the blocks of its subloops", + )); + } + } + } + + // Check the parent loop pointer. + if let Some(parent) = self.parent_loop() { + if !parent.nested().contains(&parent) { + return Err(Report::msg("loop is not a subloop of its parent")); + } + } + + Ok(()) + } + + #[cfg(not(debug_assertions))] + pub fn verify_loop(&self) -> Result<(), Report> { + Ok(()) + } + + /// Verify loop structure of this loop and all nested loops. + pub fn verify_loop_nest( + self: Rc, + loops: &mut SmallSet, 2>, + ) -> Result<(), Report> { + loops.insert(self.clone()); + + // Verify this loop. + self.verify_loop()?; + + // Verify the subloops. + for l in self.nested.borrow().iter().cloned() { + l.verify_loop_nest(loops)?; + } + + Ok(()) + } + + /// Print loop with all the blocks inside it. + pub fn print(&self, verbose: bool) -> impl fmt::Display + '_ { + PrintLoop { + loop_info: self, + nested: true, + verbose, + } + } +} + +struct PrintLoop<'a> { + loop_info: &'a Loop, + nested: bool, + verbose: bool, +} + +impl crate::formatter::PrettyPrint for PrintLoop<'_> { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + let mut doc = const_text("loop containing: "); + let header = self.loop_info.header(); + for (i, block) in self.loop_info.blocks().iter().copied().enumerate() { + if !self.verbose { + if i > 0 { + doc += const_text(", "); + } + doc += display(block); + } else { + doc += nl(); + } + + if block == header { + doc += const_text("

"); + } else if self.loop_info.is_loop_latch(block) { + doc += const_text(""); + } else if self.loop_info.is_loop_exiting(block) { + doc += const_text(""); + } + + if self.verbose { + doc += text(format!("{:?}", &block.borrow())); + } + } + + if self.nested { + let nested = self.loop_info.nested().iter().fold(Document::Empty, |acc, l| { + let printer = PrintLoop { + loop_info: l, + nested: true, + verbose: self.verbose, + }; + acc + nl() + printer.render() + }); + doc + indent(2, nested) + } else { + doc + } + } +} + +impl fmt::Display for PrintLoop<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use crate::formatter::PrettyPrint; + self.pretty_print(f) + } +} + +impl fmt::Display for Loop { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", self.print(false)) + } +} +impl fmt::Debug for Loop { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("Loop") + .field("parent_loop", &self.parent_loop()) + .field("nested", &self.nested()) + .field("blocks", &self.blocks()) + .field("block_set", &self.block_set()) + .finish() + } +} diff --git a/hir/src/ir/op.rs b/hir/src/ir/op.rs new file mode 100644 index 000000000..42a60c522 --- /dev/null +++ b/hir/src/ir/op.rs @@ -0,0 +1,172 @@ +use alloc::{boxed::Box, format}; + +use super::*; +use crate::{any::AsAny, traits::TraitInfo, AttributeValue}; + +pub trait OpRegistration: Op { + /// The name of the dialect this op is declared part of + fn dialect_name() -> ::midenc_hir_symbol::Symbol; + /// The name of the operation (i.e. its opcode) + fn name() -> ::midenc_hir_symbol::Symbol; + /// The fully-qualified name of the operation (i.e. `.`) + fn full_name() -> ::midenc_hir_symbol::Symbol { + ::midenc_hir_symbol::Symbol::intern(format!( + "{}.{}", + Self::dialect_name(), + ::name() + )) + } + /// The set of statically known traits for this op + fn traits() -> Box<[TraitInfo]>; +} + +pub trait BuildableOp: Op { + type Builder<'a, T>: FnOnce, crate::Report>> + + 'a + where + T: ?Sized + Builder + 'a; + fn builder<'b, B>(builder: &'b mut B, span: SourceSpan) -> Self::Builder<'b, B> + where + B: ?Sized + Builder + 'b; +} + +pub trait Op: AsAny + OpVerifier { + /// The name of this operation's opcode + /// + /// The opcode must be distinct from all other opcodes in the same dialect + fn name(&self) -> OperationName; + fn as_operation(&self) -> &Operation; + fn as_operation_mut(&mut self) -> &mut Operation; + + fn print(&self, flags: &OpPrintingFlags) -> crate::formatter::Document { + let operation = self.as_operation(); + operation.print(flags, operation.context()) + } + + #[inline] + fn as_operation_ref(&self) -> OperationRef { + self.as_operation().as_operation_ref() + } + fn set_span(&mut self, span: SourceSpan) { + self.as_operation_mut().set_span(span); + } + fn parent(&self) -> Option { + self.as_operation().parent() + } + fn parent_region(&self) -> Option { + self.as_operation().parent_region() + } + fn parent_op(&self) -> Option { + self.as_operation().parent_op() + } + fn num_regions(&self) -> usize { + self.as_operation().num_regions() + } + fn regions(&self) -> &RegionList { + self.as_operation().regions() + } + fn regions_mut(&mut self) -> &mut RegionList { + self.as_operation_mut().regions_mut() + } + fn region(&self, index: usize) -> EntityRef<'_, Region> { + self.as_operation().region(index) + } + fn region_mut(&mut self, index: usize) -> EntityMut<'_, Region> { + self.as_operation_mut().region_mut(index) + } + fn has_successors(&self) -> bool { + self.as_operation().has_successors() + } + fn num_successors(&self) -> usize { + self.as_operation().num_successors() + } + fn has_operands(&self) -> bool { + self.as_operation().has_operands() + } + fn num_operands(&self) -> usize { + self.as_operation().num_operands() + } + fn operands(&self) -> &OpOperandStorage { + self.as_operation().operands() + } + fn operands_mut(&mut self) -> &mut OpOperandStorage { + self.as_operation_mut().operands_mut() + } + fn has_results(&self) -> bool { + self.as_operation().has_results() + } + fn num_results(&self) -> usize { + self.as_operation().num_results() + } + fn results(&self) -> &OpResultStorage { + self.as_operation().results() + } + fn results_mut(&mut self) -> &mut OpResultStorage { + self.as_operation_mut().results_mut() + } + fn successors(&self) -> &OpSuccessorStorage { + self.as_operation().successors() + } + fn successors_mut(&mut self) -> &mut OpSuccessorStorage { + self.as_operation_mut().successors_mut() + } +} + +impl Spanned for dyn Op { + fn span(&self) -> SourceSpan { + self.as_operation().span + } +} + +pub trait OpExt { + /// Return the value associated with attribute `name` for this function + fn get_attribute(&self, name: impl Into) -> Option<&dyn AttributeValue>; + + /// Return true if this function has an attributed named `name` + fn has_attribute(&self, name: impl Into) -> bool; + + /// Set the attribute `name` with `value` for this function. + fn set_attribute( + &mut self, + name: impl Into, + value: Option, + ); + + /// Remove any attribute with the given name from this function + fn remove_attribute(&mut self, name: impl Into); + + /// Returns a handle to the nearest containing [Operation] of type `T` for this operation, if it + /// is attached to one + fn nearest_parent_op(&self) -> Option>; +} + +impl OpExt for T { + #[inline] + fn get_attribute(&self, name: impl Into) -> Option<&dyn AttributeValue> { + self.as_operation().get_attribute(name) + } + + #[inline] + fn has_attribute(&self, name: impl Into) -> bool { + self.as_operation().has_attribute(name) + } + + #[inline] + fn set_attribute( + &mut self, + name: impl Into, + value: Option, + ) { + self.as_operation_mut().set_attribute(name, value); + } + + #[inline] + fn remove_attribute(&mut self, name: impl Into) { + self.as_operation_mut().remove_attribute(name); + } + + #[inline] + fn nearest_parent_op(&self) -> Option> { + self.as_operation().nearest_parent_op() + } +} diff --git a/hir/src/ir/operands.rs b/hir/src/ir/operands.rs new file mode 100644 index 000000000..84ad15013 --- /dev/null +++ b/hir/src/ir/operands.rs @@ -0,0 +1,178 @@ +use core::fmt; + +use super::Context; +use crate::{EntityRef, OperationRef, Type, UnsafeIntrusiveEntityRef, Value, ValueId, ValueRef}; + +pub type OpOperand = UnsafeIntrusiveEntityRef; +pub type OpOperandList = crate::EntityList; +#[allow(unused)] +pub type OpOperandIter<'a> = crate::EntityIter<'a, OpOperandImpl>; +#[allow(unused)] +pub type OpOperandCursor<'a> = crate::EntityCursor<'a, OpOperandImpl>; +#[allow(unused)] +pub type OpOperandCursorMut<'a> = crate::EntityCursorMut<'a, OpOperandImpl>; + +/// An [OpOperand] represents a use of a [Value] by an [Operation] +pub struct OpOperandImpl { + /// The operand value + pub value: Option, + /// The owner of this operand, i.e. the operation it is an operand of + pub owner: OperationRef, + /// The index of this operand in the operand list of an operation + pub index: u8, +} +impl OpOperandImpl { + #[inline] + pub fn new(value: ValueRef, owner: OperationRef, index: u8) -> Self { + Self { + value: Some(value), + owner, + index, + } + } + + #[track_caller] + pub fn value(&self) -> EntityRef<'_, dyn Value> { + self.value.as_ref().expect("operand is unlinked").borrow() + } + + #[inline] + pub const fn as_value_ref(&self) -> ValueRef { + self.value.unwrap() + } + + #[inline] + pub fn as_operand_ref(&self) -> OpOperand { + unsafe { OpOperand::from_raw(self) } + } + + pub fn owner(&self) -> EntityRef<'_, crate::Operation> { + self.owner.borrow() + } + + pub fn ty(&self) -> crate::Type { + self.value().ty().clone() + } + + pub fn operand_group(&self) -> u8 { + let owner = self.owner.borrow(); + let operands = owner.operands(); + let operand_index = self.index as usize; + let group_index = operands + .groups() + .position(|group| group.range().contains(&operand_index)) + .expect("broken operand reference!"); + group_index as u8 + } + + /// Set the operand value to `value`, removing the operand from the use list of the previous + /// value, and adding it to the use list of `value`. + pub fn set(&mut self, mut value: ValueRef) { + let this = self.as_operand_ref(); + if let Some(mut prev) = self.value.take() { + unsafe { + let mut prev = prev.borrow_mut(); + prev.uses_mut().cursor_mut_from_ptr(this).remove(); + } + } + self.value = Some(value); + value.borrow_mut().insert_use(this); + } +} +impl fmt::Debug for OpOperand { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Debug::fmt(&*self.borrow(), f) + } +} +impl fmt::Debug for OpOperandImpl { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + #[derive(Debug)] + #[allow(unused)] + struct ValueInfo<'a> { + id: ValueId, + ty: &'a Type, + } + + let value = self.value.map(|value| value.borrow()); + let value = value.as_ref().map(|value| ValueInfo { + id: value.id(), + ty: value.ty(), + }); + f.debug_struct("OpOperand") + .field("index", &self.index) + .field("value", &value) + .finish_non_exhaustive() + } +} +impl crate::Spanned for OpOperandImpl { + fn span(&self) -> crate::SourceSpan { + self.value().span() + } +} +impl crate::Entity for OpOperandImpl {} +impl crate::EntityListItem for OpOperandImpl {} +impl crate::StorableEntity for OpOperandImpl { + #[inline(always)] + fn index(&self) -> usize { + self.index as usize + } + + unsafe fn set_index(&mut self, index: usize) { + self.index = index.try_into().expect("too many operands"); + } + + fn unlink(&mut self) { + if !self.as_operand_ref().is_linked() { + return; + } + if let Some(mut value) = self.value.take() { + let ptr = self.as_operand_ref(); + let mut value = value.borrow_mut(); + let uses = value.uses_mut(); + unsafe { + let mut cursor = uses.cursor_mut_from_ptr(ptr); + cursor.remove(); + } + } + } +} + +pub type OpOperandStorage = crate::EntityStorage; +pub type OpOperandRange<'a> = crate::EntityRange<'a, OpOperand>; +pub type OpOperandRangeMut<'a> = crate::EntityRangeMut<'a, OpOperand, 1>; + +impl OpOperandRangeMut<'_> { + pub fn set_operands(&mut self, operands: I, owner: OperationRef, context: &Context) + where + I: IntoIterator, + { + let mut operands = operands.into_iter().enumerate(); + let mut num_operands = 0; + while let Some((index, value)) = operands.next() { + if let Some(operand_ref) = self.get_mut(index) { + num_operands += 1; + let mut operand = operand_ref.borrow_mut(); + // If the new operand value and the existing one are the same, no change is required + if operand.value.is_some_and(|v| v == value) { + continue; + } + // Otherwise, set the operand value to the new value + operand.set(value); + } else { + // The operand group is being extended + self.extend(core::iter::once((index, value)).chain(operands).map(|(_, value)| { + num_operands += 1; + context.make_operand(value, owner, 0) + })); + break; + } + } + + // Remove excess operands + if num_operands < self.len() { + for _ in 0..(self.len() - num_operands) { + let _ = self.pop(); + } + } + } +} diff --git a/hir/src/ir/operation.rs b/hir/src/ir/operation.rs new file mode 100644 index 000000000..036bcf658 --- /dev/null +++ b/hir/src/ir/operation.rs @@ -0,0 +1,1471 @@ +mod builder; +pub mod equivalence; +mod name; + +use alloc::{boxed::Box, rc::Rc}; +use core::{ + fmt, + ptr::{DynMetadata, NonNull, Pointee}, + sync::atomic::AtomicU32, +}; + +use smallvec::SmallVec; + +pub use self::{builder::OperationBuilder, name::OperationName}; +use super::{ + effects::{HasRecursiveMemoryEffects, MemoryEffect, MemoryEffectOpInterface}, + *, +}; +use crate::{ + adt::SmallSet, patterns::RewritePatternSet, AttributeSet, AttributeValue, Forward, ProgramPoint, +}; + +pub type OperationRef = UnsafeIntrusiveEntityRef; +pub type OpList = EntityList; +pub type OpCursor<'a> = EntityCursor<'a, Operation>; +pub type OpCursorMut<'a> = EntityCursorMut<'a, Operation>; + +/// The [Operation] struct provides the common foundation for all [Op] implementations. +/// +/// It provides: +/// +/// * Support for casting between the concrete operation type `T`, `dyn Op`, the underlying +/// `Operation`, and any of the operation traits that the op implements. Not only can the casts +/// be performed, but an [Operation] can be queried to see if it implements a specific trait at +/// runtime to conditionally perform some behavior. This makes working with operations in the IR +/// very flexible and allows for adding or modifying operations without needing to change most of +/// the compiler, which predominately works on operation traits rather than concrete ops. +/// * Storage for all IR entities attached to an operation, e.g. operands, results, nested regions, +/// attributes, etc. +/// * Navigation of the IR graph; navigate up to the containing block/region/op, down to nested +/// regions/blocks/ops, or next/previous sibling operations in the same block. Additionally, you +/// can navigate directly to the definitions of operands used, to users of results produced, and +/// to successor blocks. +/// * Many utility functions related to working with operations, many of which are also accessible +/// via the [Op] trait, so that working with an [Op] or an [Operation] are largely +/// indistinguishable. +/// +/// All [Op] implementations can be cast to the underlying [Operation], but most of the +/// fucntionality is re-exported via default implementations of methods on the [Op] trait. The main +/// benefit is avoiding any potential overhead of casting when going through the trait, rather than +/// calling the underlying [Operation] method directly. +/// +/// # Safety +/// +/// [Operation] is implemented as part of a larger structure that relies on assumptions which depend +/// on IR entities being allocated via [Context], i.e. the arena. Those allocations produce an +/// [UnsafeIntrusiveEntityRef] or [UnsafeEntityRef], which allocate the pointee type inside a struct +/// that provides metadata about the pointee that can be accessed without aliasing the pointee +/// itself - in particular, links for intrusive collections. This is important, because while these +/// pointer types are a bit like raw pointers in that they lack any lifetime information, and are +/// thus unsafe to dereference in general, they _do_ ensure that the pointee can be safely reified +/// as a reference without violating Rust's borrow checking rules, i.e. they are dynamically borrow- +/// checked. +/// +/// The reason why we are able to generally treat these "unsafe" references as safe, is because we +/// require that all IR entities be allocated via [Context]. This makes it essential to keep the +/// context around in order to work with the IR, and effectively guarantees that no [RawEntityRef] +/// will be dereferenced after the context is dropped. This is not a guarantee provided by the +/// compiler however, but one that is imposed in practice, as attempting to work with the IR in +/// any capacity without a [Context] is almost impossible. We must ensure however, that we work +/// within this set of rules to uphold the safety guarantees. +/// +/// This "fragility" is a tradeoff - we get the performance characteristics of an arena-allocated +/// IR, with the flexibility and power of using pointers rather than indexes as handles, while also +/// maintaining the safety guarantees of Rust's borrowing system. The downside is that we can't just +/// allocate IR entities wherever we want and use them the same way. +#[derive(Spanned)] +pub struct Operation { + /// The [Context] in which this [Operation] was allocated. + context: NonNull, + /// The dialect and opcode name for this operation, as well as trait implementation metadata + name: OperationName, + /// The offset of the field containing this struct inside the concrete [Op] it represents. + /// + /// This is required in order to be able to perform casts from [Operation]. An [Operation] + /// cannot be constructed without providing it to the `uninit` function, and callers of that + /// function are required to ensure that it is correct. + offset: usize, + /// The order of this operation in its containing block + /// + /// This is atomic to ensure that even if a mutable reference to this operation is held, loads + /// of this field cannot be elided, as the value can still be mutated at any time. In practice, + /// the only time this is ever written, is when all operations in a block have their orders + /// recomputed, or when a single operation is updating its own order. + order: AtomicU32, + #[span] + pub span: SourceSpan, + /// Attributes that apply to this operation + pub attrs: AttributeSet, + /// The set of operands for this operation + /// + /// NOTE: If the op supports immediate operands, the storage for the immediates is handled + /// by the op, rather than here. Additionally, the semantics of the immediate operands are + /// determined by the op, e.g. whether the immediate operands are always applied first, or + /// what they are used for. + pub operands: OpOperandStorage, + /// The set of values produced by this operation. + pub results: OpResultStorage, + /// If this operation represents control flow, this field stores the set of successors, + /// and successor operands. + pub successors: OpSuccessorStorage, + /// The set of regions belonging to this operation, if any + pub regions: RegionList, +} + +/// Equality over operations is determined by reference identity, i.e. two operations are only equal +/// if they refer to the same address in memory, regardless of the content of the operation itself. +impl Eq for Operation {} +impl PartialEq for Operation { + fn eq(&self, other: &Self) -> bool { + core::ptr::addr_eq(self, other) + } +} + +/// The Hash implementation for operations is defined to match the equality implementation, i.e. +/// the hash of an operation is the hash of its address in memory. +impl core::hash::Hash for Operation { + fn hash(&self, state: &mut H) { + core::ptr::hash(self, state) + } +} + +impl fmt::Debug for Operation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Operation") + .field_with("name", |f| write!(f, "{}", &self.name())) + .field("offset", &self.offset) + .field("order", &self.order) + .field("attrs", &self.attrs) + .field("block", &self.parent().as_ref().map(|b| b.borrow().id())) + .field_with("operands", |f| { + let mut list = f.debug_list(); + for operand in self.operands().all() { + list.entry(&operand.borrow()); + } + list.finish() + }) + .field("results", &self.results) + .field("successors", &self.successors) + .finish_non_exhaustive() + } +} + +impl fmt::Debug for OperationRef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Debug::fmt(&self.borrow(), f) + } +} + +impl fmt::Display for OperationRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.borrow().name()) + } +} + +impl AsRef for Operation { + fn as_ref(&self) -> &dyn Op { + self.name.upcast(self.container()).unwrap() + } +} + +impl AsMut for Operation { + fn as_mut(&mut self) -> &mut dyn Op { + self.name.upcast_mut(self.container().cast_mut()).unwrap() + } +} + +impl Entity for Operation {} +impl EntityWithParent for Operation { + type Parent = Block; +} +impl EntityListItem for Operation { + fn on_inserted(this: OperationRef, _cursor: &mut EntityCursorMut<'_, Self>) { + // NOTE: We use OperationName, instead of the Operation itself, to avoid borrowing. + if this.name().implements::() { + let parent = this.nearest_symbol_table(); + if let Some(mut parent) = parent { + if parent.name().implements::() { + // NOTE: We call `unwrap()` here because we are confident that these function calls + // are valid thanks to the `implements` check above. + let mut symbol_table = parent.borrow_mut(); + let sym_manager = symbol_table.as_trait_mut::().unwrap(); + let mut sym_manager = sym_manager.symbol_manager_mut(); + + let symbol_ref = this.borrow().as_symbol_ref().unwrap(); + + let is_new = sym_manager.insert_new(symbol_ref, ProgramPoint::Invalid); + assert!( + is_new, + "Unable to insert {} in symbol table of {}: symbol {} is already \ + registered to another operation {}.", + this.name(), + parent.name(), + symbol_ref.borrow().name(), + sym_manager.lookup(symbol_ref.borrow().name()).unwrap().borrow().name() + ); + } + } + } + let order_offset = core::mem::offset_of!(Operation, order); + unsafe { + let ptr = UnsafeIntrusiveEntityRef::as_ptr(&this); + let order_ptr = ptr.byte_add(order_offset).cast::(); + (*order_ptr).store(Self::INVALID_ORDER, core::sync::atomic::Ordering::Release); + } + } + + fn on_transfer(_this: OperationRef, _from: &mut EntityList, to: &mut EntityList) { + // Invalidate the ordering of the new parent block + let mut to = to.parent(); + to.borrow_mut().invalidate_op_order(); + } + + fn on_removed(this: OperationRef, _list: &mut EntityCursorMut<'_, Self>) { + // NOTE: We use OperationName, instead of the Operation itself, to avoid borrowing. + if this.name().implements::() { + let parent = this.nearest_symbol_table(); + if let Some(mut parent) = parent { + // NOTE: We use OperationName, instead of the Operation itself, to avoid borrowing. + if parent.name().implements::() { + let mut symbol_table = parent.borrow_mut(); + let sym_manager = symbol_table.as_trait_mut::().unwrap(); + let mut sym_manager = sym_manager.symbol_manager_mut(); + + let symbol_ref = this.borrow().as_symbol_ref().unwrap(); + + sym_manager.remove(symbol_ref); + }; + } + } + } +} + +impl EntityParent for Operation { + fn offset() -> usize { + core::mem::offset_of!(Operation, regions) + } +} + +/// Construction +impl Operation { + #[doc(hidden)] + pub unsafe fn uninit(context: Rc, name: OperationName, offset: usize) -> Self { + assert!(name.is::()); + + Self { + context: unsafe { NonNull::new_unchecked(Rc::as_ptr(&context).cast_mut()) }, + name, + offset, + order: AtomicU32::new(0), + span: Default::default(), + attrs: Default::default(), + operands: Default::default(), + results: Default::default(), + successors: Default::default(), + regions: Default::default(), + } + } +} + +/// Insertion +impl OperationRef { + pub fn insert_at_start(self, mut block: BlockRef) { + assert!( + self.parent().is_none(), + "cannot insert operation that is already attached to another block" + ); + { + let mut block = block.borrow_mut(); + block.body_mut().push_front(self); + } + } + + pub fn insert_at_end(self, mut block: BlockRef) { + assert!( + self.parent().is_none(), + "cannot insert operation that is already attached to another block" + ); + { + let mut block = block.borrow_mut(); + block.body_mut().push_back(self); + } + } + + pub fn insert_before(self, before: OperationRef) { + assert!( + self.parent().is_none(), + "cannot insert operation that is already attached to another block" + ); + let mut block = before.parent().expect("'before' block is not attached to a block"); + { + let mut block = block.borrow_mut(); + let block_body = block.body_mut(); + let mut cursor = unsafe { block_body.cursor_mut_from_ptr(before) }; + cursor.insert_before(self); + } + } + + pub fn insert_after(self, after: OperationRef) { + assert!( + self.parent().is_none(), + "cannot insert operation that is already attached to another block" + ); + let mut block = after.parent().expect("'after' block is not attached to a block"); + { + let mut block = block.borrow_mut(); + let block_body = block.body_mut(); + let mut cursor = unsafe { block_body.cursor_mut_from_ptr(after) }; + cursor.insert_after(self); + } + } +} + +/// Read-only Metadata +impl OperationRef { + pub fn name(&self) -> OperationName { + let ptr = OperationRef::as_ptr(self); + // SAFETY: The `name` field of Operation is read-only after an op is allocated, and the + // safety guarantees of OperationRef require that the allocation never moves for the + // lifetime of the ref. So it is always safe to read this field via direct pointer, even + // if a mutable borrow of the containing op exists, because the field is never written to + // after allocation. + unsafe { + let name_ptr = core::ptr::addr_of!((*ptr).name); + OperationName::clone(&*name_ptr) + } + } + + /// Returns a handle to the nearest containing [Operation] of this operation, if it is attached + /// to one + pub fn parent_op(&self) -> Option { + self.parent_region().and_then(|region| region.parent()) + } + + /// Returns a handle to the containing [Region] of this operation, if it is attached to one + pub fn parent_region(&self) -> Option { + self.grandparent() + } + + /// Returns the nearest [SymbolTable] from this operation. + /// + /// Returns `None` if no parent of this operation is a valid symbol table. + pub fn nearest_symbol_table(&self) -> Option { + let mut parent = self.parent_op(); + while let Some(parent_op) = parent.take() { + if parent_op.name().implements::() { + return Some(parent_op); + } + parent = parent_op.parent_op(); + } + None + } +} + +/// Metadata +impl Operation { + /// Get the name of this operation + /// + /// An operation name consists of both its dialect, and its opcode. + pub fn name(&self) -> OperationName { + self.name.clone() + } + + /// Get the dialect associated with this operation + pub fn dialect(&self) -> Rc { + self.context().get_registered_dialect(self.name.dialect()) + } + + /// Set the source location associated with this operation + #[inline] + pub fn set_span(&mut self, span: SourceSpan) { + self.span = span; + } + + /// Get a borrowed reference to the owning [Context] of this operation + #[inline(always)] + pub fn context(&self) -> &Context { + // SAFETY: This is safe so long as this operation is allocated in a Context, since the + // Context by definition outlives the allocation. + unsafe { self.context.as_ref() } + } + + /// Get a owned reference to the owning [Context] of this operation + pub fn context_rc(&self) -> Rc { + // SAFETY: This is safe so long as this operation is allocated in a Context, since the + // Context by definition outlives the allocation. + // + // Additionally, constructing the Rc from a raw pointer is safe here, as the pointer was + // obtained using `Rc::as_ptr`, so the only requirement to call `Rc::from_raw` is to + // increment the strong count, as `as_ptr` does not preserve the count for the reference + // held by this operation. Incrementing the count first is required to manufacture new + // clones of the `Rc` safely. + unsafe { + let ptr = self.context.as_ptr().cast_const(); + Rc::increment_strong_count(ptr); + Rc::from_raw(ptr) + } + } +} + +/// Verification +impl Operation { + /// Run any verifiers for this operation + pub fn verify(&self) -> Result<(), Report> { + let dyn_op: &dyn Op = self.as_ref(); + dyn_op.verify(self.context()) + } + + /// Run any verifiers for this operation, and all of its nested operations, recursively. + /// + /// The verification is performed in post-order, so that when the verifier(s) for `self` are + /// run, it is known that all of its children have successfully verified. + pub fn recursively_verify(&self) -> Result<(), Report> { + self.postwalk(|op: &Operation| op.verify().into()).into_result() + } +} + +/// Traits/Casts +impl Operation { + pub(super) const fn container(&self) -> *const () { + unsafe { + let ptr = self as *const Self; + ptr.byte_sub(self.offset).cast() + } + } + + #[inline(always)] + pub fn as_operation_ref(&self) -> OperationRef { + // SAFETY: This is safe under the assumption that we always allocate Operations using the + // arena, i.e. it is a child of a RawEntityMetadata structure. + // + // Additionally, this relies on the fact that Op implementations are #[repr(C)] and ensure + // that their Operation field is always first in the generated struct + unsafe { OperationRef::from_raw(self) } + } + + /// Returns true if the concrete type of this operation is `T` + #[inline] + pub fn is(&self) -> bool { + self.name.is::() + } + + /// Returns true if this operation implements `Trait` + #[inline] + pub fn implements(&self) -> bool + where + Trait: ?Sized + Pointee> + 'static, + { + self.name.implements::() + } + + /// Attempt to downcast to the concrete [Op] type of this operation + pub fn downcast_ref(&self) -> Option<&T> { + self.name.downcast_ref::(self.container()) + } + + /// Attempt to downcast to the concrete [Op] type of this operation + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.name.downcast_mut::(self.container().cast_mut()) + } + + /// Attempt to cast this operation reference to an implementation of `Trait` + pub fn as_trait(&self) -> Option<&Trait> + where + Trait: ?Sized + Pointee> + 'static, + { + self.name.upcast(self.container()) + } + + /// Attempt to cast this operation reference to an implementation of `Trait` + pub fn as_trait_mut(&mut self) -> Option<&mut Trait> + where + Trait: ?Sized + Pointee> + 'static, + { + self.name.upcast_mut(self.container().cast_mut()) + } +} + +/// Attributes +impl Operation { + /// Get the underlying attribute set for this operation + #[inline(always)] + pub fn attributes(&self) -> &AttributeSet { + &self.attrs + } + + /// Get a mutable reference to the underlying attribute set for this operation + #[inline(always)] + pub fn attributes_mut(&mut self) -> &mut AttributeSet { + &mut self.attrs + } + + /// Return the value associated with attribute `name` for this function + pub fn get_attribute(&self, name: impl Into) -> Option<&dyn AttributeValue> { + self.attrs.get_any(name.into()) + } + + /// Return the value associated with attribute `name` for this function + pub fn get_attribute_mut( + &mut self, + name: impl Into, + ) -> Option<&mut dyn AttributeValue> { + self.attrs.get_any_mut(name.into()) + } + + /// Return the value associated with attribute `name` for this function, as its concrete type + /// `T`, _if_ the attribute by that name, is of that type. + pub fn get_typed_attribute(&self, name: impl Into) -> Option<&T> + where + T: AttributeValue, + { + self.attrs.get(name.into()) + } + + /// Return the value associated with attribute `name` for this function, as its concrete type + /// `T`, _if_ the attribute by that name, is of that type. + pub fn get_typed_attribute_mut( + &mut self, + name: impl Into, + ) -> Option<&mut T> + where + T: AttributeValue, + { + self.attrs.get_mut(name.into()) + } + + /// Return true if this function has an attributed named `name` + pub fn has_attribute(&self, name: impl Into) -> bool { + self.attrs.has(name.into()) + } + + /// Set the attribute `name` with `value` for this function. + pub fn set_attribute( + &mut self, + name: impl Into, + value: Option, + ) { + self.attrs.insert(name, value); + } + + /// Set the intrinsic attribute `name` with `value` for this function. + pub fn set_intrinsic_attribute( + &mut self, + name: impl Into, + value: Option, + ) { + self.attrs.set(crate::Attribute { + name: name.into(), + value: value.map(|v| Box::new(v) as Box), + intrinsic: true, + }); + } + + /// Remove any attribute with the given name from this function + pub fn remove_attribute(&mut self, name: impl Into) { + self.attrs.remove(name.into()); + } +} + +/// Symbol Attributes +impl Operation { + pub fn set_symbol_attribute( + &mut self, + attr_name: impl Into, + symbol: impl AsSymbolRef, + ) { + let attr_name = attr_name.into(); + let mut symbol = symbol.as_symbol_ref(); + + // Do not allow self-references + // + // NOTE: We are using this somewhat convoluted way to check identity of the symbol, + // so that we do not attempt to borrow `self` again if `symbol` and `self` are the + // same operation. That would fail due to the mutable reference to `self` we are + // already holding. + let (data_ptr, _) = SymbolRef::as_ptr(&symbol).to_raw_parts(); + assert!( + !core::ptr::addr_eq(data_ptr, self.container()), + "a symbol cannot use itself, except via nested operations" + ); + + // Track the usage of `symbol` by `self` + let user = self.context().alloc_tracked(SymbolUse { + owner: self.as_operation_ref(), + attr: attr_name, + }); + + // Store the underlying attribute value + if self.has_attribute(attr_name) { + let attr = self.get_typed_attribute_mut::(attr_name).unwrap(); + let symbol = symbol.borrow(); + assert!( + !attr.user.is_linked(), + "attempted to replace symbol use without unlinking the previously used symbol \ + first" + ); + attr.user = user; + attr.path = symbol.path(); + } else { + let attr = { + let symbol = symbol.borrow(); + SymbolPathAttr { + user, + path: symbol.path(), + } + }; + self.set_attribute(attr_name, Some(attr)); + } + + symbol.borrow_mut().insert_use(user); + } +} + +/// Navigation +impl Operation { + /// Returns a handle to the containing [Block] of this operation, if it is attached to one + #[inline] + pub fn parent(&self) -> Option { + self.as_operation_ref().parent() + } + + /// Returns a handle to the containing [Region] of this operation, if it is attached to one + pub fn parent_region(&self) -> Option { + self.as_operation_ref().parent_region() + } + + /// Returns a handle to the nearest containing [Operation] of this operation, if it is attached + /// to one + pub fn parent_op(&self) -> Option { + self.as_operation_ref().parent_op() + } + + /// Returns a handle to the nearest containing [Operation] of type `T` for this operation, if it + /// is attached to one + pub fn nearest_parent_op(&self) -> Option> { + let mut parent = self.parent_op(); + while let Some(op) = parent.take() { + parent = + op.parent().and_then(|block| block.parent()).and_then(|region| region.parent()); + let op = op.borrow(); + if let Some(t_ref) = op.downcast_ref::() { + return Some(unsafe { UnsafeIntrusiveEntityRef::from_raw(t_ref) }); + } + } + None + } +} + +/// Traversal +impl Operation { + pub fn prewalk_all(&self, callback: F) + where + F: FnMut(&Operation), + { + Walk::::prewalk_all::(self, callback); + } + + pub fn prewalk(&self, callback: F) -> WalkResult + where + F: FnMut(&Operation) -> WalkResult, + { + Walk::::prewalk::(self, callback) + } + + pub fn postwalk_all(&self, callback: F) + where + F: FnMut(&Operation), + { + Walk::::postwalk_all::(self, callback); + } + + pub fn postwalk(&self, callback: F) -> WalkResult + where + F: FnMut(&Operation) -> WalkResult, + { + Walk::::postwalk::(self, callback) + } +} + +/// Regions +impl Operation { + /// Returns true if this operation has any regions + #[inline] + pub fn has_regions(&self) -> bool { + !self.regions.is_empty() + } + + /// Returns the number of regions owned by this operation. + /// + /// NOTE: This does not include regions of nested operations, just those directly attached + /// to this operation. + #[inline] + pub fn num_regions(&self) -> usize { + self.regions.len() + } + + /// Get a reference to the region list for this operation + #[inline(always)] + pub fn regions(&self) -> &RegionList { + &self.regions + } + + /// Get a mutable reference to the region list for this operation + #[inline(always)] + pub fn regions_mut(&mut self) -> &mut RegionList { + &mut self.regions + } + + /// Get a reference to a specific region, given its index. + /// + /// This function will panic if the index is invalid. + pub fn region(&self, index: usize) -> EntityRef<'_, Region> { + let mut cursor = self.regions.front(); + let mut count = 0; + while !cursor.is_null() { + if index == count { + return cursor.into_borrow().unwrap(); + } + cursor.move_next(); + count += 1; + } + panic!("invalid region index {index}: out of bounds"); + } + + /// Get a mutable reference to a specific region, given its index. + /// + /// This function will panic if the index is invalid. + pub fn region_mut(&mut self, index: usize) -> EntityMut<'_, Region> { + let mut cursor = self.regions.front_mut(); + let mut count = 0; + while !cursor.is_null() { + if index == count { + return cursor.into_borrow_mut().unwrap(); + } + cursor.move_next(); + count += 1; + } + panic!("invalid region index {index}: out of bounds"); + } +} + +/// Successors +impl Operation { + /// Returns true if this operation has any successor blocks + #[inline] + pub fn has_successors(&self) -> bool { + !self.successors.is_empty() + } + + /// Returns the number of successor blocks this operation may transfer control to + #[inline] + pub fn num_successors(&self) -> usize { + self.successors.len() + } + + /// Get a reference to the successors of this operation + #[inline(always)] + pub fn successors(&self) -> &OpSuccessorStorage { + &self.successors + } + + /// Get a mutable reference to the successors of this operation + #[inline(always)] + pub fn successors_mut(&mut self) -> &mut OpSuccessorStorage { + &mut self.successors + } + + /// Get a reference to the successor group at `index` + #[inline] + pub fn successor_group(&self, index: usize) -> OpSuccessorRange<'_> { + self.successors.group(index) + } + + /// Get a mutable reference to the successor group at `index` + #[inline] + pub fn successor_group_mut(&mut self, index: usize) -> OpSuccessorRangeMut<'_> { + self.successors.group_mut(index) + } + + /// Get a reference to the keyed successor group at `index` + #[inline] + pub fn keyed_successor_group(&self, index: usize) -> KeyedSuccessorRange<'_, T> + where + T: KeyedSuccessor, + { + let range = self.successors.group(index); + KeyedSuccessorRange::new(range, &self.operands) + } + + /// Get a mutable reference to the keyed successor group at `index` + #[inline] + pub fn keyed_successor_group_mut(&mut self, index: usize) -> KeyedSuccessorRangeMut<'_, T> + where + T: KeyedSuccessor, + { + let range = self.successors.group_mut(index); + KeyedSuccessorRangeMut::new(range, &mut self.operands) + } + + /// Get a reference to the successor at `index` in the group at `group_index` + #[inline] + pub fn successor_in_group(&self, group_index: usize, index: usize) -> OpSuccessor<'_> { + let info = &self.successors.group(group_index)[index]; + OpSuccessor { + dest: info.block, + arguments: self.operands.group(info.operand_group as usize), + } + } + + /// Get a mutable reference to the successor at `index` in the group at `group_index` + #[inline] + pub fn successor_in_group_mut( + &mut self, + group_index: usize, + index: usize, + ) -> OpSuccessorMut<'_> { + let info = &self.successors.group(group_index)[index]; + OpSuccessorMut { + dest: info.block, + arguments: self.operands.group_mut(info.operand_group as usize), + } + } + + /// Get a reference to the successor at `index` + #[inline] + #[track_caller] + pub fn successor(&self, index: usize) -> OpSuccessor<'_> { + let info = &self.successors[index]; + OpSuccessor { + dest: info.block, + arguments: self.operands.group(info.operand_group as usize), + } + } + + /// Get a mutable reference to the successor at `index` + #[inline] + #[track_caller] + pub fn successor_mut(&mut self, index: usize) -> OpSuccessorMut<'_> { + let info = self.successors[index]; + OpSuccessorMut { + dest: info.block, + arguments: self.operands.group_mut(info.operand_group as usize), + } + } + + /// Get an iterator over the successors of this operation + pub fn successor_iter(&self) -> impl DoubleEndedIterator> + '_ { + self.successors.iter().map(|info| OpSuccessor { + dest: info.block, + arguments: self.operands.group(info.operand_group as usize), + }) + } +} + +/// Operands +impl Operation { + /// Returns true if this operation has at least one operand + #[inline] + pub fn has_operands(&self) -> bool { + !self.operands.is_empty() + } + + /// Returns the number of operands given to this operation + #[inline] + pub fn num_operands(&self) -> usize { + self.operands.len() + } + + /// Get a reference to the operand storage for this operation + #[inline] + pub fn operands(&self) -> &OpOperandStorage { + &self.operands + } + + /// Get a mutable reference to the operand storage for this operation + #[inline] + pub fn operands_mut(&mut self) -> &mut OpOperandStorage { + &mut self.operands + } + + /// Replace the current operands of this operation with the ones provided in `operands`. + pub fn set_operands(&mut self, operands: impl IntoIterator) { + self.operands.clear(); + let context = self.context_rc(); + let owner = self.as_operation_ref(); + self.operands.extend( + operands + .into_iter() + .enumerate() + .map(|(index, value)| context.make_operand(value, owner, index as u8)), + ); + } + + /// Replace any uses of `from` with `to` within this operation + pub fn replaces_uses_of_with(&mut self, from: ValueRef, to: ValueRef) { + if ValueRef::ptr_eq(&from, &to) { + return; + } + + for operand in self.operands.iter_mut() { + debug_assert!(operand.is_linked()); + if ValueRef::ptr_eq(&from, &operand.borrow().value.unwrap()) { + operand.borrow_mut().set(to); + } + } + } + + /// Replace all uses of this operation's results with `values` + /// + /// The number of results and the number of values in `values` must be exactly the same, + /// otherwise this function will panic. + pub fn replace_all_uses_with(&mut self, values: impl ExactSizeIterator) { + assert_eq!(self.num_results(), values.len()); + for (result, replacement) in self.results.iter_mut().zip(values) { + if (*result as ValueRef) == replacement { + continue; + } + result.borrow_mut().replace_all_uses_with(replacement); + } + } + + /// Replace uses of this operation's results with `values`, for each use which, when provided + /// to the given callback, returns true. + /// + /// The number of results and the number of values in `values` must be exactly the same, + /// otherwise this function will panic. + pub fn replace_uses_with_if(&mut self, values: V, should_replace: F) + where + V: ExactSizeIterator, + F: Fn(&OpOperandImpl) -> bool, + { + assert_eq!(self.num_results(), values.len()); + for (result, replacement) in self.results.iter_mut().zip(values) { + let mut result = *result as ValueRef; + if result == replacement { + continue; + } + result.borrow_mut().replace_uses_with_if(replacement, &should_replace); + } + } +} + +/// Results +impl Operation { + /// Returns true if this operation produces any results + #[inline] + pub fn has_results(&self) -> bool { + !self.results.is_empty() + } + + /// Returns the number of results produced by this operation + #[inline] + pub fn num_results(&self) -> usize { + self.results.len() + } + + /// Get a reference to the result set of this operation + #[inline] + pub fn results(&self) -> &OpResultStorage { + &self.results + } + + /// Get a mutable reference to the result set of this operation + #[inline] + pub fn results_mut(&mut self) -> &mut OpResultStorage { + &mut self.results + } + + /// Get a reference to the result at `index` among all results of this operation + #[inline] + pub fn get_result(&self, index: usize) -> &OpResultRef { + &self.results[index] + } + + /// Returns true if the results of this operation are used + pub fn is_used(&self) -> bool { + self.results.iter().any(|result| result.borrow().is_used()) + } + + /// Returns true if the results of this operation have exactly one user + pub fn has_exactly_one_use(&self) -> bool { + let mut used_by = None; + for result in self.results.iter() { + let result = result.borrow(); + if !result.is_used() { + continue; + } + + for used in result.iter_uses() { + if used_by.as_ref().is_some_and(|user| !OperationRef::eq(user, &used.owner)) { + // We found more than one user + return false; + } else if used_by.is_none() { + used_by = Some(used.owner); + } + } + } + + // If we reach here, and we have a `used_by` set, we have exactly one user + used_by.is_some() + } + + /// Returns true if the results of this operation are used outside of the given block + pub fn is_used_outside_of_block(&self, block: &BlockRef) -> bool { + self.results + .iter() + .any(|result| result.borrow().is_used_outside_of_block(block)) + } + + /// Returns true if this operation is unused and has no side effects that prevent it being erased + pub fn is_trivially_dead(&self) -> bool { + !self.is_used() && self.would_be_trivially_dead() + } + + /// Returns true if this operation would be dead if unused, and has no side effects that would + /// prevent erasing it. This is equivalent to checking `is_trivially_dead` if `self` is unused. + /// + /// NOTE: Terminators and symbols are never considered to be trivially dead by this function. + pub fn would_be_trivially_dead(&self) -> bool { + if self.implements::() || self.implements::() { + false + } else { + self.would_be_trivially_dead_even_if_terminator() + } + } + + /// Implementation of `would_be_trivially_dead` that also considers terminator operations as + /// dead if they have no side effects. This allows for marking region operations as trivially + /// dead without always being conservative about terminators. + pub fn would_be_trivially_dead_even_if_terminator(&self) -> bool { + // The set of operations to consider when checking for side effects + let mut effecting_ops = SmallVec::<[OperationRef; 4]>::from_iter([self.as_operation_ref()]); + while let Some(op) = effecting_ops.pop() { + let op = op.borrow(); + + // If the operation has recursive effects, push all of the nested operations on to the + // stack to consider + let has_recursive_effects = op.implements::(); + if has_recursive_effects { + for region in op.regions() { + for block in region.body() { + for op in block.body() { + effecting_ops.push(op.as_operation_ref()); + } + } + } + } + + // If the op has memory effects, try to characterize them to see if the op is trivially + // dead here. + if let Some(effect_interface) = op.as_trait::() { + let mut effects = effect_interface.effects(); + + // Gather all results of this op that are allocated + let mut alloc_results = SmallSet::::default(); + for effect in effects.as_slice() { + let allocates = matches!(effect.effect(), MemoryEffect::Allocate); + if let Some(value) = effect.value() { + let is_defined_by_op = value + .borrow() + .get_defining_op() + .is_some_and(|op| self.as_operation_ref() == op); + if allocates && is_defined_by_op { + alloc_results.insert(value); + } + } + } + + if !effects.all(|effect| { + // We can drop effects if the value is an allocation and is a result of + // the operation + if effect.value().is_some_and(|v| alloc_results.contains(&v)) { + true + } else { + // Otherwise, the effect must be a read + matches!(effect.effect(), MemoryEffect::Read) + } + }) { + return false; + } + continue; + } + + // Otherwise, if the op has recursive side effects we can treat the operation itself + // as having no effects + if has_recursive_effects { + continue; + } + + // If there were no effect interfaces, we treat this op as conservatively having + // effects + return false; + } + + // If we get here, none of the operations had effects that prevented marking this operation + // as dead. + true + } + + /// Returns true if the given operation is free of memory effects. + /// + /// An operation is free of memory effects if its implementation of `MemoryEffectOpInterface` + /// indicates that it has no memory effects. For example, it may implement `NoMemoryEffect`. + /// Alternatively, if the operation has the `HasRecursiveMemoryEffects` trait, then it is free + /// of memory effects if all of its nested operations are free of memory effects. + /// + /// If the operation has both, then it is free of memory effects if both conditions are + /// satisfied. + pub fn is_memory_effect_free(&self) -> bool { + if let Some(mem_interface) = self.as_trait::() { + if !mem_interface.has_no_effect() { + return false; + } + + // If the op does not have recursive side effects, then it is memory effect free + if !self.implements::() { + return true; + } + } else if !self.implements::() { + // Otherwise, if the op does not implement the memory effect interface and it does not + // have recursive side effects, then it cannot be known that the op is moveable. + return false; + } + + // Recurse into the regions and ensure that all nested ops are memory effect free + for region in self.regions() { + let walk_result = region.prewalk(|op| { + if !op.is_memory_effect_free() { + WalkResult::Break(()) + } else { + WalkResult::Continue(()) + } + }); + if walk_result.was_interrupted() { + return false; + } + } + + true + } +} + +/// Movement +impl Operation { + /// Remove this operation (and its descendants) from its containing block, and delete them + #[inline] + pub fn erase(&mut self) { + // We don't delete entities currently, so for now this is just an alias for `remove` + self.remove(); + + self.successors.clear(); + self.operands.clear(); + } + + /// Remove the operation from its parent block, but don't delete it. + pub fn remove(&mut self) { + if let Some(mut parent) = self.parent() { + let mut block = parent.borrow_mut(); + let body = block.body_mut(); + let mut cursor = unsafe { body.cursor_mut_from_ptr(self.as_operation_ref()) }; + cursor.remove(); + } + } + + /// Unlink this operation from its current block and insert it at `ip`, which may be in the same + /// or another block in the same function. + /// + /// # Panics + /// + /// This function will panic if the given program point is unset, or refers to an orphaned op, + /// i.e. an op that has no parent block. + pub fn move_to(&mut self, mut ip: ProgramPoint) { + let this = self.as_operation_ref(); + if let Some(op) = ip.operation() { + if op == this { + // The move is a no-op + return; + } + + assert!(ip.block().is_some(), "cannot insert an operation relative to an orphaned op"); + } + + // Detach `self` + self.remove(); + + { + // Move `self` to `ip` + let mut cursor = ip.cursor_mut().expect("insertion point is invalid/unset"); + // NOTE: We use `insert_after` here because the cursor we get is positioned such that + // insert_after will always insert at the precise point specified. + cursor.insert_after(self.as_operation_ref()); + } + } + + /// This drops all operand uses from this operation, which is used to break cyclic dependencies + /// between references when they are to be deleted + pub fn drop_all_references(&mut self) { + self.operands.clear(); + + { + let mut region_cursor = self.regions.front_mut(); + while let Some(mut region) = region_cursor.as_pointer() { + region.borrow_mut().drop_all_references(); + region_cursor.move_next(); + } + } + + self.successors.clear(); + } + + /// This drops all uses of any values defined by this operation or its nested regions, + /// wherever they are located. + pub fn drop_all_defined_value_uses(&mut self) { + for result in self.results.iter_mut() { + let mut res = result.borrow_mut(); + res.uses_mut().clear(); + } + + let mut regions = self.regions.front_mut(); + while let Some(mut region) = regions.as_pointer() { + let mut region = region.borrow_mut(); + let blocks = region.body_mut(); + let mut cursor = blocks.front_mut(); + while let Some(mut block) = cursor.as_pointer() { + block.borrow_mut().drop_all_defined_value_uses(); + cursor.move_next(); + } + regions.move_next(); + } + } + + /// Drop all uses of results of this operation + pub fn drop_all_uses(&mut self) { + for result in self.results.iter_mut() { + result.borrow_mut().uses_mut().clear(); + } + } +} + +/// Ordering +impl Operation { + /// This value represents an invalid index ordering for an operation within its containing block + const INVALID_ORDER: u32 = u32::MAX; + /// This value represents the stride to use when computing a new order for an operation + const ORDER_STRIDE: u32 = 5; + + /// Returns true if this operation is an ancestor of `other`. + /// + /// An operation is considered its own ancestor, use [Self::is_proper_ancestor_of] if you do not + /// want this behavior. + pub fn is_ancestor_of(&self, other: &Self) -> bool { + core::ptr::addr_eq(self, other) || Self::is_a_proper_ancestor_of_b(self, other) + } + + /// Returns true if this operation is a proper ancestor of `other` + pub fn is_proper_ancestor_of(&self, other: &Self) -> bool { + Self::is_a_proper_ancestor_of_b(self, other) + } + + /// Returns true if operation `a` is a proper ancestor of operation `b` + fn is_a_proper_ancestor_of_b(a: &Self, b: &Self) -> bool { + let a = a.as_operation_ref(); + let mut next = b.parent_op(); + while let Some(b) = next.take() { + if OperationRef::ptr_eq(&a, &b) { + return true; + } + } + false + } + + /// Given an operation `other` that is within the same parent block, return whether the current + /// operation is before it in the operation list. + /// + /// NOTE: This function has an average complexity of O(1), but worst case may take O(N) where + /// N is the number of operations within the parent block. + pub fn is_before_in_block(&self, other: &OperationRef) -> bool { + use core::sync::atomic::Ordering; + + let block = self.parent().expect("operations without parent blocks have no order"); + let other = other.borrow(); + assert!( + other + .parent() + .as_ref() + .is_some_and(|other_block| BlockRef::ptr_eq(&block, other_block)), + "expected both operations to have the same parent block" + ); + + // If the order of the block is already invalid, directly recompute the parent + if !block.borrow().is_op_order_valid() { + Self::recompute_block_order(block); + } else { + // Update the order of either operation if necessary. + self.update_order_if_necessary(); + other.update_order_if_necessary(); + } + + self.order.load(Ordering::Relaxed) < other.order.load(Ordering::Relaxed) + } + + /// Update the order index of this operation of this operation if necessary, + /// potentially recomputing the order of the parent block. + fn update_order_if_necessary(&self) { + use core::sync::atomic::Ordering; + + assert!(self.parent().is_some(), "expected valid parent"); + + // If the order is valid for this operation there is nothing to do. + let block = self.parent().unwrap(); + if self.has_valid_order() || block.borrow().body().iter().count() == 1 { + return; + } + + let this = self.as_operation_ref(); + let prev = this.prev(); + let next = this.next(); + assert!(prev.is_some() || next.is_some(), "expected more than one operation in block"); + + // If the operation is at the end of the block. + if next.is_none() { + let prev = prev.unwrap(); + let prev = prev.borrow(); + let prev_order = prev.order.load(Ordering::Acquire); + if prev_order == Self::INVALID_ORDER { + return Self::recompute_block_order(block); + } + + // Add the stride to the previous operation. + self.order.store(prev_order + Self::ORDER_STRIDE, Ordering::Release); + return; + } + + // If this is the first operation try to use the next operation to compute the + // ordering. + if prev.is_none() { + let next = next.unwrap(); + let next = next.borrow(); + let next_order = next.order.load(Ordering::Acquire); + match next_order { + Self::INVALID_ORDER | 0 => { + return Self::recompute_block_order(block); + } + // If we can't use the stride, just take the middle value left. This is safe + // because we know there is at least one valid index to assign to. + order if order <= Self::ORDER_STRIDE => { + self.order.store(order / 2, Ordering::Release); + } + _ => { + self.order.store(Self::ORDER_STRIDE, Ordering::Release); + } + } + return; + } + + // Otherwise, this operation is between two others. Place this operation in + // the middle of the previous and next if possible. + let prev = prev.unwrap().borrow().order.load(Ordering::Acquire); + let next = next.unwrap().borrow().order.load(Ordering::Acquire); + if prev == Self::INVALID_ORDER || next == Self::INVALID_ORDER { + return Self::recompute_block_order(block); + } + + // Check to see if there is a valid order between the two. + if prev + 1 == next { + return Self::recompute_block_order(block); + } + self.order.store(prev + ((next - prev) / 2), Ordering::Release); + } + + fn recompute_block_order(block: BlockRef) { + use core::sync::atomic::Ordering; + + let block = block.borrow(); + let mut cursor = block.body().front(); + let mut index = 0; + while let Some(op) = cursor.as_pointer() { + index += Self::ORDER_STRIDE; + cursor.move_next(); + let ptr = OperationRef::as_ptr(&op); + unsafe { + let order_addr = core::ptr::addr_of!((*ptr).order); + (*order_addr).store(index, Ordering::Release); + } + } + + block.mark_op_order_valid(); + } + + /// Returns `None` if this operation has invalid ordering + #[inline] + pub(crate) fn order(&self) -> Option { + use core::sync::atomic::Ordering; + match self.order.load(Ordering::Acquire) { + Self::INVALID_ORDER => None, + order => Some(order), + } + } + + /// Returns `None` if this operation has invalid ordering + #[inline] + #[allow(unused)] + pub(crate) fn get_or_compute_order(&self) -> u32 { + use core::sync::atomic::Ordering; + + if let Some(order) = self.order() { + return order; + } + + Self::recompute_block_order( + self.parent().expect("cannot compute block ordering for orphaned operation"), + ); + + self.order.load(Ordering::Acquire) + } + + /// Returns true if this operation has a valid order + #[inline(always)] + pub(super) fn has_valid_order(&self) -> bool { + self.order().is_some() + } +} + +/// Canonicalization +impl Operation { + /// Populates `rewrites` with the set of canonicalization patterns registered for this operation + #[inline] + pub fn populate_canonicalization_patterns( + &self, + rewrites: &mut RewritePatternSet, + context: Rc, + ) { + self.name.populate_canonicalization_patterns(rewrites, context); + } +} + +impl crate::traits::Foldable for Operation { + fn fold(&self, results: &mut smallvec::SmallVec<[OpFoldResult; 1]>) -> FoldResult { + use crate::traits::Foldable; + + if let Some(foldable) = self.as_trait::() { + foldable.fold(results) + } else { + FoldResult::Failed + } + } + + fn fold_with<'operands>( + &self, + operands: &[Option>], + results: &mut smallvec::SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult { + use crate::traits::Foldable; + + if let Some(foldable) = self.as_trait::() { + foldable.fold_with(operands, results) + } else { + FoldResult::Failed + } + } +} diff --git a/hir/src/ir/operation/builder.rs b/hir/src/ir/operation/builder.rs new file mode 100644 index 000000000..dbd5b66e0 --- /dev/null +++ b/hir/src/ir/operation/builder.rs @@ -0,0 +1,266 @@ +use alloc::{boxed::Box, vec, vec::Vec}; + +use midenc_session::diagnostics::Severity; + +use crate::{ + traits::Terminator, AsCallableSymbolRef, AsSymbolRef, AttributeValue, BlockRef, Builder, + KeyedSuccessor, Op, OpBuilder, OperationRef, Region, Report, Spanned, SuccessorInfo, Type, + UnsafeIntrusiveEntityRef, ValueRef, +}; + +/// The [OperationBuilder] is a primitive for imperatively constructing an [Operation]. +/// +/// Currently, this is primarily used by our `#[operation]` macro infrastructure, to finalize +/// construction of the underlying [Operation] of an [Op] implementation, after both have been +/// allocated and initialized with only basic metadata. This builder is then used to add all of +/// the data under the op, e.g. operands, results, attributes, etc. Once complete, verification is +/// run on the constructed op. +/// +/// Using this directly is possible, see [OperationBuilder::new] for details. You may also find it +/// useful to examine the expansion of the `#[operation]` macro for existing ops to understand what goes +/// on behind the scenes for most ops. +pub struct OperationBuilder<'a, T, B: ?Sized = OpBuilder> { + builder: &'a mut B, + op: OperationRef, + _marker: core::marker::PhantomData, +} +impl<'a, T, B> OperationBuilder<'a, T, B> +where + T: Op, + B: ?Sized + Builder, +{ + /// Create a new [OperationBuilder] for `op` using the provided [Builder]. + /// + /// The [Operation] underlying `op` must have been initialized correctly: + /// + /// * Allocated via the same context as `builder` + /// * Initialized via [crate::Operation::uninit] + /// * All op traits implemented by `T` must have been registered with its [OperationName] + /// * All fields of `T` must have been initialized to actual or default values. This builder + /// will invoke verification at the end, and if `T` is not correctly initialized, it will + /// result in undefined behavior. + pub fn new(builder: &'a mut B, op: UnsafeIntrusiveEntityRef) -> Self { + let op = op.as_operation_ref(); + Self { + builder, + op, + _marker: core::marker::PhantomData, + } + } + + /// Set attribute `name` on this op to `value` + #[inline] + pub fn with_attr(&mut self, name: &'static str, value: A) + where + A: AttributeValue, + { + self.op.borrow_mut().set_attribute(name, Some(value)); + } + + /// Set attribute `name` on this op to `value` + #[inline] + pub fn with_hidden_attr(&mut self, name: &'static str, value: A) + where + A: AttributeValue, + { + self.op.borrow_mut().set_intrinsic_attribute(name, Some(value)); + } + + /// Set symbol `attr_name` on this op to `symbol`. + /// + /// Symbol references are stored as attributes, and have similar semantics to operands, i.e. + /// they require tracking uses. + #[inline] + pub fn with_symbol(&mut self, attr_name: &'static str, symbol: impl AsSymbolRef) { + let mut op = self.op.borrow_mut(); + op.set_symbol_attribute(attr_name, symbol); + } + + /// Like [with_symbol], but further constrains the range of valid input symbols to those which + /// are valid [CallableOpInterface] implementations. + #[inline] + pub fn with_callable_symbol( + &mut self, + attr_name: &'static str, + callable: impl AsCallableSymbolRef, + ) { + let callable = callable.as_callable_symbol_ref(); + let mut op = self.op.borrow_mut(); + op.set_symbol_attribute(attr_name, callable); + } + + /// Add a new [Region] to this operation. + /// + /// NOTE: You must ensure this is called _after_ [Self::with_operands], and [Self::implements] + /// if the op implements the [traits::NoRegionArguments] trait. Otherwise, the inserted region + /// may not be valid for this op. + pub fn create_region(&mut self) { + let region = Region::default(); + let region = self.builder.context().alloc_tracked(region); + let mut op = self.op.borrow_mut(); + op.regions.push_back(region); + } + + pub fn with_successor( + &mut self, + dest: BlockRef, + arguments: impl IntoIterator, + ) { + let owner = self.op; + // Insert operand group for this successor + let mut op = self.op.borrow_mut(); + let operand_group = op.operands.push_group( + arguments + .into_iter() + .enumerate() + .map(|(index, arg)| self.builder.context().make_operand(arg, owner, index as u8)), + ); + // Record SuccessorInfo for this successor in the op + let succ_index = u8::try_from(op.successors.len()).expect("too many successors"); + let successor = self.builder.context().make_block_operand(dest, owner, succ_index); + op.successors.push(SuccessorInfo { + block: successor, + key: None, + operand_group: operand_group.try_into().expect("too many operand groups"), + }); + } + + pub fn with_successors(&mut self, succs: I) + where + I: IntoIterator)>, + { + let owner = self.op; + let mut op = self.op.borrow_mut(); + let mut group = vec![]; + for (i, (block, args)) in succs.into_iter().enumerate() { + let block = self.builder.context().make_block_operand(block, owner, i as u8); + let operands = args + .into_iter() + .map(|value_ref| self.builder.context().make_operand(value_ref, owner, 0)); + let operand_group = op.operands.push_group(operands); + group.push(SuccessorInfo { + block, + key: None, + operand_group: operand_group.try_into().expect("too many operand groups"), + }); + } + if op.successors.is_empty() { + // Extend the empty default group + op.successors.extend_group(0, group); + } else { + // Create new group + op.successors.push_group(group); + } + } + + pub fn with_keyed_successors(&mut self, succs: I) + where + S: KeyedSuccessor, + I: IntoIterator, + { + let owner = self.op; + let mut op = self.op.borrow_mut(); + let mut group = vec![]; + for (i, successor) in succs.into_iter().enumerate() { + let (key, block, args) = successor.into_parts(); + let block = self.builder.context().make_block_operand(block, owner, i as u8); + let operands = args + .into_iter() + .map(|value_ref| self.builder.context().make_operand(value_ref, owner, 0)); + let operand_group = op.operands.push_group(operands); + let key = Box::new(key); + let key = unsafe { core::ptr::NonNull::new_unchecked(Box::into_raw(key)) }; + group.push(SuccessorInfo { + block, + key: Some(key.cast()), + operand_group: operand_group.try_into().expect("too many operand groups"), + }); + } + if op.successors.is_empty() { + // Extend the empty default group + op.successors.extend_group(0, group); + } else { + // Create new group + op.successors.push_group(group); + } + } + + /// Append operands to the set of operands given to this op so far. + pub fn with_operands(&mut self, operands: I) + where + I: IntoIterator, + { + let owner = self.op; + let operands = operands + .into_iter() + .enumerate() + .map(|(index, value)| self.builder.context().make_operand(value, owner, index as u8)); + let mut op = self.op.borrow_mut(); + op.operands.extend(operands); + } + + /// Append operands to the set of operands in operand group `group` + pub fn with_operands_in_group(&mut self, group: usize, operands: I) + where + I: IntoIterator, + { + let owner = self.op; + let operands = operands + .into_iter() + .enumerate() + .map(|(index, value)| self.builder.context().make_operand(value, owner, index as u8)); + let mut op = self.op.borrow_mut(); + op.operands.extend_group(group, operands); + } + + /// Allocate `n` results for this op, of unknown type, to be filled in later + pub fn with_results(&mut self, n: usize) { + let span = self.op.borrow().span; + let owner = self.op; + let results = (0..n) + .map(|idx| self.builder.context().make_result(span, Type::Unknown, owner, idx as u8)); + let mut op = self.op.borrow_mut(); + op.results.clear(); + op.results.extend(results); + } + + /// Consume this builder, verify the op, and return a handle to it, or an error if validation + /// failed. + pub fn build(mut self) -> Result, Report> { + let op = { + let mut op = self.op.borrow_mut(); + + // Infer result types and apply any associated validation + if let Some(interface) = op.as_trait_mut::() { + interface.infer_return_types(self.builder.context())?; + } + + // Verify things that would require negative trait impls + if !op.implements::() && op.has_successors() { + return Err(self + .builder + .context() + .session() + .diagnostics + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label( + op.span(), + "this operation has successors, but does not implement the 'Terminator' \ + trait", + ) + .with_help("operations with successors must implement the 'Terminator' trait") + .into_report()); + } + + unsafe { UnsafeIntrusiveEntityRef::from_raw(op.container().cast()) } + }; + + // Insert op at current insertion point, if set + if self.builder.insertion_point().is_valid() { + self.builder.insert(self.op); + } + + Ok(op) + } +} diff --git a/hir/src/ir/operation/equivalence.rs b/hir/src/ir/operation/equivalence.rs new file mode 100644 index 000000000..c445f6bae --- /dev/null +++ b/hir/src/ir/operation/equivalence.rs @@ -0,0 +1,247 @@ +use core::hash::Hasher; + +use bitflags::bitflags; +use smallvec::SmallVec; + +use super::Operation; +use crate::{traits::Commutative, OpOperand, Region, Value, ValueRef}; + +bitflags! { + #[derive(Copy, Clone)] + pub struct OperationEquivalenceFlags : u8 { + const NONE = 0; + const IGNORE_LOCATIONS = 1; + } +} + +impl Default for OperationEquivalenceFlags { + fn default() -> Self { + Self::NONE + } +} + +pub trait OperationHasher { + fn hash_operation(&self, op: &Operation, hasher: &mut H); +} + +#[derive(Default)] +pub struct DefaultOperationHasher; + +impl OperationHasher for DefaultOperationHasher { + fn hash_operation(&self, op: &Operation, hasher: &mut H) { + op.hash_with_options( + OperationEquivalenceFlags::default(), + DefaultValueHasher, + DefaultValueHasher, + hasher, + ); + } +} + +#[derive(Default)] +pub struct IgnoreValueEquivalenceOperationHasher; + +impl OperationHasher for IgnoreValueEquivalenceOperationHasher { + fn hash_operation(&self, op: &Operation, hasher: &mut H) { + op.hash_with_options( + OperationEquivalenceFlags::IGNORE_LOCATIONS, + IgnoreValueHasher, + IgnoreValueHasher, + hasher, + ); + } +} + +pub trait ValueHasher { + fn hash_value(&self, value: ValueRef, hasher: &mut H); +} + +/// A [ValueHasher] impl that hashes a value based on its address in memory. +/// +/// This is generally used with [OperationEquivalence] to require operands/results between two +/// operations to be exactly the same. +#[derive(Default)] +pub struct DefaultValueHasher; + +impl ValueHasher for DefaultValueHasher { + fn hash_value(&self, value: ValueRef, hasher: &mut H) { + core::ptr::hash(ValueRef::as_ptr(&value), hasher); + } +} + +/// A [ValueHasher] impl that ignores operands/results, i.e. the hash is unchanged +#[derive(Default)] +pub struct IgnoreValueHasher; + +impl ValueHasher for IgnoreValueHasher { + fn hash_value(&self, _value: ValueRef, _hasher: &mut H) {} +} + +impl Operation { + pub fn hash_with_options( + &self, + flags: OperationEquivalenceFlags, + operand_hasher: impl ValueHasher, + result_hasher: impl ValueHasher, + hasher: &mut H, + ) where + H: core::hash::Hasher, + { + use core::hash::Hash; + + // Hash operations based upon their: + // + // - Operation name + // - Attributes + // - Result types + self.name.hash(hasher); + self.attrs.hash(hasher); + for result in self.results().iter() { + let result = result.borrow(); + result.ty().hash(hasher); + } + + if !flags.contains(OperationEquivalenceFlags::IGNORE_LOCATIONS) { + self.span.hash(hasher); + } + + // Operands + self.operands().len().hash(hasher); + for operand in self.operands().iter() { + let operand = operand.borrow(); + operand_hasher.hash_value(operand.as_value_ref(), hasher); + } + + // Results + self.results().len().hash(hasher); + for result in self.results().iter() { + let result = result.borrow(); + result_hasher.hash_value(result.as_value_ref(), hasher); + } + } + + pub fn is_equivalent(&self, rhs: &Operation, flags: OperationEquivalenceFlags) -> bool { + self.is_equivalent_with_options(rhs, flags, |l, r| core::ptr::addr_eq(l, r)) + } + + pub fn is_equivalent_with_options( + &self, + rhs: &Operation, + flags: OperationEquivalenceFlags, + check_equivalent: F, + ) -> bool + where + F: Fn(&dyn Value, &dyn Value) -> bool, + { + if core::ptr::addr_eq(self, rhs) { + return true; + } + + // 1. Compare operation properties + if self.name != rhs.name + || self.num_regions() != rhs.num_regions() + || self.num_successors() != rhs.num_successors() + || self.num_operands() != rhs.num_operands() + || self.num_results() != rhs.num_results() + || self.attrs != rhs.attrs + { + return false; + } + + if !flags.contains(OperationEquivalenceFlags::IGNORE_LOCATIONS) && self.span != rhs.span { + return false; + } + + // 2. Compare operands + if self.implements::() { + let lhs_operands = self.operands().all(); + let rhs_operands = rhs.operands().all(); + let mut lhs_operands = SmallVec::<[_; 2]>::from_slice(lhs_operands.as_slice()); + lhs_operands.sort_by(sort_operands); + let mut rhs_operands = SmallVec::<[_; 2]>::from_slice(rhs_operands.as_slice()); + rhs_operands.sort_by(sort_operands); + if !are_operands_equivalent(&lhs_operands, &rhs_operands, &check_equivalent) { + return false; + } + } else { + // Check pair-wise for equivalence + let lhs = self.operands.all(); + let rhs = rhs.operands.all(); + if !are_operands_equivalent(lhs.as_slice(), rhs.as_slice(), &check_equivalent) { + return false; + } + } + + // 3. Compare result types + for (lhs_r, rhs_r) in + self.results().all().iter().copied().zip(rhs.results().all().iter().copied()) + { + let lhs_r = lhs_r.borrow(); + let rhs_r = rhs_r.borrow(); + if lhs_r.ty() != rhs_r.ty() { + return false; + } + } + + // 4. Compare regions + for (lhs_region, rhs_region) in self.regions().iter().zip(rhs.regions().iter()) { + if !is_region_equivalent_to(&lhs_region, &rhs_region, flags, &check_equivalent) { + return false; + } + } + + true + } +} + +pub fn ignore_value_equivalence(_lhs: &dyn Value, _rhs: &dyn Value) -> bool { + true +} + +pub fn exact_value_match(lhs: &dyn Value, rhs: &dyn Value) -> bool { + core::ptr::addr_eq(lhs, rhs) +} + +fn is_region_equivalent_to( + _lhs: &Region, + _rhs: &Region, + _flags: OperationEquivalenceFlags, + _check_equivalent: F, +) -> bool +where + F: Fn(&dyn Value, &dyn Value) -> bool, +{ + todo!() +} + +fn sort_operands(a: &OpOperand, b: &OpOperand) -> core::cmp::Ordering { + let a = a.borrow().as_value_ref(); + let b = b.borrow().as_value_ref(); + let a = ValueRef::as_ptr(&a).addr(); + let b = ValueRef::as_ptr(&b).addr(); + a.cmp(&b) +} + +fn are_operands_equivalent(a: &[OpOperand], b: &[OpOperand], check_equivalent: F) -> bool +where + F: Fn(&dyn Value, &dyn Value) -> bool, +{ + // Check pair-wise for equivalence + for (a, b) in a.iter().copied().zip(b.iter().copied()) { + let a = a.borrow(); + let b = b.borrow(); + let a = a.value(); + let b = b.value(); + if core::ptr::addr_eq(&*a, &*b) { + continue; + } + if a.ty() != b.ty() { + return false; + } + if !check_equivalent(&*a, &*b) { + return false; + } + } + + true +} diff --git a/hir/src/ir/operation/name.rs b/hir/src/ir/operation/name.rs new file mode 100644 index 000000000..087856466 --- /dev/null +++ b/hir/src/ir/operation/name.rs @@ -0,0 +1,218 @@ +use alloc::{boxed::Box, rc::Rc, vec::Vec}; +use core::{ + any::TypeId, + fmt, + ptr::{DynMetadata, Pointee}, +}; + +use super::OpRegistration; +use crate::{ + interner, + patterns::RewritePatternSet, + traits::{Canonicalizable, TraitInfo}, + Context, +}; + +/// The operation name, or mnemonic, that uniquely identifies an operation. +/// +/// The operation name consists of its dialect name, and the opcode name within the dialect. +/// +/// No two operation names can share the same fully-qualified operation name. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct OperationName(Rc); + +struct OperationInfo { + /// The dialect of this operation + dialect: interner::Symbol, + /// The opcode name for this operation + name: interner::Symbol, + /// The type id of the concrete type that implements this operation + type_id: TypeId, + /// Details of the traits implemented by this operation, used to answer questions about what + /// traits are implemented, as well as reconstruct `&dyn Trait` references given a pointer to + /// the data of a specific operation instance. + traits: Box<[TraitInfo]>, + /// The implementation of `Canonicalizable::get_canonicalization_patterns` for this type + get_canonicalization_patterns: fn(&mut RewritePatternSet, Rc), +} + +impl OperationName { + pub fn new(dialect: interner::Symbol, mut extra_traits: Vec) -> Self + where + T: OpRegistration, + { + let type_id = TypeId::of::(); + let mut traits = Vec::from(::traits()); + traits.append(&mut extra_traits); + traits.sort_by_key(|ti| *ti.type_id()); + let get_canonicalization_patterns = ::get_canonicalization_patterns; + let info = Rc::new(OperationInfo::new( + dialect, + ::name(), + type_id, + traits, + get_canonicalization_patterns, + )); + Self(info) + } + + /// Returns the dialect name of this operation + pub fn dialect(&self) -> interner::Symbol { + self.0.dialect + } + + /// Returns the name/opcode of this operation + pub fn name(&self) -> interner::Symbol { + self.0.name + } + + /// Returns the [TypeId] of the operation type + #[inline] + pub fn id(&self) -> &TypeId { + &self.0.type_id + } + + /// Populates `rewrites` with the set of canonicalization patterns registered for this operation + pub fn populate_canonicalization_patterns( + &self, + rewrites: &mut RewritePatternSet, + context: Rc, + ) { + (self.0.get_canonicalization_patterns)(rewrites, context) + } + + /// Returns true if `T` is the concrete type that implements this operation + #[inline] + pub fn is(&self) -> bool { + TypeId::of::() == self.0.type_id + } + + /// Returns true if this operation implements `Trait` + pub fn implements(&self) -> bool + where + Trait: ?Sized + Pointee> + 'static, + { + let type_id = TypeId::of::(); + self.implements_trait_id(&type_id) + } + + /// Returns true if this operation implements `trait`, where `trait` is the `TypeId` of a + /// `dyn Trait` type. + pub fn implements_trait_id(&self, trait_id: &TypeId) -> bool { + self.0.traits.binary_search_by(|ti| ti.type_id().cmp(trait_id)).is_ok() + } + + #[inline] + pub(super) fn downcast_ref(&self, ptr: *const ()) -> Option<&T> { + if self.is::() { + Some(unsafe { self.downcast_ref_unchecked(ptr) }) + } else { + None + } + } + + #[inline(always)] + unsafe fn downcast_ref_unchecked(&self, ptr: *const ()) -> &T { + &*core::ptr::from_raw_parts(ptr.cast::(), ()) + } + + #[inline] + pub(super) fn downcast_mut(&mut self, ptr: *mut ()) -> Option<&mut T> { + if self.is::() { + Some(unsafe { self.downcast_mut_unchecked(ptr) }) + } else { + None + } + } + + #[inline(always)] + unsafe fn downcast_mut_unchecked(&mut self, ptr: *mut ()) -> &mut T { + &mut *core::ptr::from_raw_parts_mut(ptr.cast::(), ()) + } + + pub(super) fn upcast(&self, ptr: *const ()) -> Option<&Trait> + where + Trait: ?Sized + Pointee> + 'static, + { + let metadata = self + .get::() + .map(|trait_impl| unsafe { trait_impl.metadata_unchecked::() })?; + Some(unsafe { &*core::ptr::from_raw_parts(ptr, metadata) }) + } + + pub(super) fn upcast_mut(&mut self, ptr: *mut ()) -> Option<&mut Trait> + where + Trait: ?Sized + Pointee> + 'static, + { + let metadata = self + .get::() + .map(|trait_impl| unsafe { trait_impl.metadata_unchecked::() })?; + Some(unsafe { &mut *core::ptr::from_raw_parts_mut(ptr, metadata) }) + } + + #[inline] + fn get(&self) -> Option<&TraitInfo> { + let type_id = TypeId::of::(); + let traits = self.0.traits.as_ref(); + traits + .binary_search_by(|ti| ti.type_id().cmp(&type_id)) + .ok() + .map(|index| &traits[index]) + } +} +impl fmt::Debug for OperationName { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} +impl fmt::Display for OperationName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}", &self.dialect(), &self.name()) + } +} + +impl OperationInfo { + pub fn new( + dialect: interner::Symbol, + name: interner::Symbol, + type_id: TypeId, + traits: Vec, + get_canonicalization_patterns: fn(&mut RewritePatternSet, Rc), + ) -> Self { + Self { + dialect, + name, + type_id, + traits: traits.into_boxed_slice(), + get_canonicalization_patterns, + } + } +} + +impl Eq for OperationInfo {} +impl PartialEq for OperationInfo { + fn eq(&self, other: &Self) -> bool { + self.dialect == other.dialect && self.name == other.name && self.type_id == other.type_id + } +} +impl PartialOrd for OperationInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for OperationInfo { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.dialect + .cmp(&other.dialect) + .then_with(|| self.name.cmp(&other.name)) + .then_with(|| self.type_id.cmp(&other.type_id)) + } +} +impl core::hash::Hash for OperationInfo { + fn hash(&self, state: &mut H) { + self.dialect.hash(state); + self.name.hash(state); + self.type_id.hash(state); + } +} diff --git a/hir/src/ir/print.rs b/hir/src/ir/print.rs new file mode 100644 index 000000000..ca3bfb3ea --- /dev/null +++ b/hir/src/ir/print.rs @@ -0,0 +1,289 @@ +use alloc::format; +use core::fmt; + +use super::{Context, Operation}; +use crate::{ + formatter::{Document, PrettyPrint}, + matchers::Matcher, + traits::BranchOpInterface, + AttributeValue, EntityWithId, SuccessorOperands, Value, +}; + +#[derive(Debug)] +pub struct OpPrintingFlags { + pub print_entry_block_headers: bool, + pub print_intrinsic_attributes: bool, +} + +impl Default for OpPrintingFlags { + fn default() -> Self { + Self { + print_entry_block_headers: true, + print_intrinsic_attributes: false, + } + } +} + +/// The `OpPrinter` trait is expected to be implemented by all [Op] impls as a prequisite. +/// +/// The actual implementation is typically generated as part of deriving [Op]. +pub trait OpPrinter { + fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document; +} + +impl OpPrinter for Operation { + #[inline] + fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document { + if let Some(op_printer) = self.as_trait::() { + op_printer.print(flags, context) + } else { + let printer = OperationPrinter { + op: self, + flags, + context, + }; + printer.render() + } + } +} + +impl fmt::Display for Operation { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let flags = OpPrintingFlags::default(); + let context = self.context(); + let doc = self.print(&flags, context); + write!(f, "{doc}") + } +} + +pub trait AttrPrinter { + fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document; +} + +impl AttrPrinter for T { + default fn print(&self, _flags: &OpPrintingFlags, _context: &Context) -> Document { + PrettyPrint::render(self) + } +} + +impl AttrPrinter for crate::Attribute { + fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document { + use crate::formatter::*; + + match self.value() { + None => text(format!("#[{}]", self.name.as_str())), + Some(value) => { + const_text("#[") + + text(self.name.as_str()) + + const_text(" = ") + + value.print(flags, context) + + const_text("]") + } + } + } +} + +impl AttrPrinter for crate::OpFoldResult { + fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document { + use crate::formatter::*; + + match self { + Self::Attribute(attr) => attr.print(flags, context), + Self::Value(value) => display(value.borrow().id()), + } + } +} + +impl AttrPrinter for [T] { + fn print(&self, flags: &OpPrintingFlags, context: &Context) -> Document { + use crate::formatter::*; + + let mut doc = Document::Empty; + for (i, item) in self.iter().enumerate() { + if i == 0 { + doc += const_text(", "); + } + + doc += item.print(flags, context); + } + doc + } +} + +pub fn render_operation_results(op: &Operation) -> crate::formatter::Document { + use crate::formatter::*; + + let results = op.results(); + let doc = results.iter().fold(Document::Empty, |acc, result| { + if acc.is_empty() { + display(result.borrow().id()) + } else { + acc + const_text(", ") + display(result.borrow().id()) + } + }); + if doc.is_empty() { + doc + } else { + doc + const_text(" = ") + } +} + +pub fn render_operation_operands(op: &Operation) -> crate::formatter::Document { + use crate::formatter::*; + + let operands = op.operands(); + operands.iter().fold(Document::Empty, |acc, operand| { + let operand = operand.borrow(); + let value = operand.value(); + if acc.is_empty() { + display(value.id()) + } else { + acc + const_text(", ") + display(value.id()) + } + }) +} + +pub fn render_operation_result_types(op: &Operation) -> crate::formatter::Document { + use crate::formatter::*; + + let results = op.results(); + let result_types = results.iter().fold(Document::Empty, |acc, result| { + if acc.is_empty() { + text(format!("{}", result.borrow().ty())) + } else { + acc + const_text(", ") + text(format!("{}", result.borrow().ty())) + } + }); + if result_types.is_empty() { + result_types + } else { + const_text(" : ") + result_types + } +} + +pub fn render_regions(op: &Operation, flags: &OpPrintingFlags) -> crate::formatter::Document { + use crate::formatter::*; + const_text(" ") + + op.regions.iter().fold(Document::Empty, |acc, region| { + let doc = region.print(flags); + if acc.is_empty() { + doc + } else { + acc + const_text(" ") + doc + } + }) + + const_text(";") +} + +struct OperationPrinter<'a> { + op: &'a Operation, + flags: &'a OpPrintingFlags, + context: &'a Context, +} + +/// The generic format for printed operations is: +/// +/// <%result..> = .(%operand : , ..) : #.. { +/// // Region +/// ^(<%block_argument...>): +/// // Block +/// }; +/// +/// Special handling is provided for SingleRegionSingleBlock and CallableOpInterface ops: +/// +/// * SingleRegionSingleBlock ops with no operands will have the block header elided +impl PrettyPrint for OperationPrinter<'_> { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + let doc = render_operation_results(self.op) + display(self.op.name()) + const_text(" "); + let doc = if let Some(value) = crate::matchers::constant().matches(self.op) { + doc + value.print(self.flags, self.context) + } else if let Some(branch) = self.op.as_trait::() { + // Print non-successor operands + let operands = branch.operands().group(0); + let doc = if !operands.is_empty() { + operands.iter().enumerate().fold(doc, |doc, (i, operand)| { + let operand = operand.borrow(); + let value = operand.value(); + if i > 0 { + doc + const_text(", ") + display(value.id()) + } else { + doc + display(value.id()) + } + }) + const_text(" ") + } else { + doc + }; + // Print successors + branch.successors().iter().enumerate().fold(doc, |doc, (succ_index, succ)| { + let doc = if succ_index > 0 { + doc + const_text(", ") + display(succ.block.borrow().successor()) + } else { + doc + display(succ.block.borrow().successor()) + }; + + let operands = branch.get_successor_operands(succ_index); + if !operands.is_empty() { + let doc = doc + const_text("("); + operands.forwarded().iter().enumerate().fold(doc, |doc, (i, operand)| { + if !operand.is_linked() { + if i > 0 { + doc + const_text(", ") + const_text("") + } else { + doc + const_text("") + } + } else { + let operand = operand.borrow(); + let value = operand.value(); + if i > 0 { + doc + const_text(", ") + display(value.id()) + } else { + doc + display(value.id()) + } + } + }) + const_text(")") + } else { + doc + } + }) + } else { + doc + render_operation_operands(self.op) + }; + + let doc = doc + render_operation_result_types(self.op); + + let attrs = self.op.attrs.iter().fold(Document::Empty, |acc, attr| { + // Do not print intrinsic attributes unless explicitly configured + if !self.flags.print_intrinsic_attributes && attr.intrinsic { + return acc; + } + let doc = if let Some(value) = attr.value() { + const_text("#[") + + display(attr.name) + + const_text(" = ") + + value.print(self.flags, self.context) + + const_text("]") + } else { + text(format!("#[{}]", &attr.name)) + }; + if acc.is_empty() { + doc + } else { + acc + const_text(" ") + doc + } + }); + + let doc = if attrs.is_empty() { + doc + } else { + doc + const_text(" ") + attrs + }; + + if self.op.has_regions() { + doc + render_regions(self.op, self.flags) + } else { + doc + const_text(";") + } + } +} diff --git a/hir/src/ir/region.rs b/hir/src/ir/region.rs new file mode 100644 index 000000000..3915da1f3 --- /dev/null +++ b/hir/src/ir/region.rs @@ -0,0 +1,749 @@ +mod branch_point; +mod interfaces; +mod invocation_bounds; +mod kind; +mod successor; +mod transforms; + +use smallvec::{smallvec, SmallVec}; + +pub use self::{ + branch_point::RegionBranchPoint, + interfaces::{ + LoopLikeOpInterface, RegionBranchOpInterface, RegionBranchTerminatorOpInterface, + RegionKindInterface, + }, + invocation_bounds::InvocationBounds, + kind::RegionKind, + successor::{RegionSuccessor, RegionSuccessorInfo, RegionSuccessorIter}, + transforms::RegionTransformFailed, +}; +use super::*; +use crate::{ + adt::SmallSet, + patterns::RegionSimplificationLevel, + traits::{SingleBlock, SingleRegion}, + Forward, +}; + +pub type RegionRef = UnsafeIntrusiveEntityRef; +/// An intrusive, doubly-linked list of [Region]s +pub type RegionList = EntityList; +/// A cursor in a [RegionList] +pub type RegionCursor<'a> = EntityCursor<'a, Region>; +/// A mutable cursor in a [RegionList] +pub type RegionCursorMut<'a> = EntityCursorMut<'a, Region>; + +/// A region is a container for [Block], in one of two forms: +/// +/// * Graph-like, in which the region consists of a single block, and the order of operations in +/// that block does not dictate any specific control flow semantics. It is up to the containing +/// operation to define. +/// * SSA-form, in which the region consists of one or more blocks that must obey the usual rules +/// of SSA dominance, and where operations in a block reflect the order in which those operations +/// are to be executed. Values defined by an operation must dominate any uses of those values in +/// the region. +/// +/// The first block in a region is the _entry_ block, and its argument list corresponds to the +/// arguments expected by the region itself. +/// +/// A region is only valid when it is attached to an [Operation], whereas the inverse is not true, +/// i.e. an operation without a parent region is a top-level operation, e.g. `Module`. +#[derive(Default)] +pub struct Region { + /// The list of [Block]s that comprise this region + body: BlockList, +} + +impl Entity for Region {} + +impl EntityWithParent for Region { + type Parent = Operation; +} + +impl EntityListItem for Region {} + +impl EntityParent for Region { + fn offset() -> usize { + core::mem::offset_of!(Region, body) + } +} + +impl core::fmt::Debug for Region { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self, f) + } +} +impl core::fmt::Display for Region { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let region_number = self.region_number(); + write!(f, "region({region_number})") + } +} + +impl cfg::Graph for Region { + type ChildEdgeIter = block::BlockSuccessorEdgesIter; + type ChildIter = block::BlockSuccessorIter; + type Edge = BlockOperandRef; + type Node = BlockRef; + + fn is_empty(&self) -> bool { + self.body.is_empty() + } + + fn size(&self) -> usize { + self.body.len() + } + + fn children(parent: Self::Node) -> Self::ChildIter { + block::BlockSuccessorIter::new(parent) + } + + fn children_edges(parent: Self::Node) -> Self::ChildEdgeIter { + block::BlockSuccessorEdgesIter::new(parent) + } + + fn edge_dest(edge: Self::Edge) -> Self::Node { + edge.parent().unwrap() + } + + fn entry_node(&self) -> Self::Node { + self.body.front().as_pointer().expect("empty region") + } +} + +impl<'a> cfg::InvertibleGraph for &'a Region { + type Inverse = cfg::Inverse<&'a Region>; + type InvertibleChildEdgeIter = block::BlockPredecessorEdgesIter; + type InvertibleChildIter = block::BlockPredecessorIter; + + fn inverse(self) -> Self::Inverse { + cfg::Inverse::new(self) + } + + fn inverse_children(parent: Self::Node) -> Self::InvertibleChildIter { + block::BlockPredecessorIter::new(parent) + } + + fn inverse_children_edges(parent: Self::Node) -> Self::InvertibleChildEdgeIter { + block::BlockPredecessorEdgesIter::new(parent) + } +} + +/// Blocks +impl Region { + /// Returns true if this region is empty (has no blocks) + pub fn is_empty(&self) -> bool { + self.body.is_empty() + } + + /// Get a handle to the entry block for this region + pub fn entry(&self) -> EntityRef<'_, Block> { + self.body.front().into_borrow().unwrap() + } + + /// Get a mutable handle to the entry block for this region + pub fn entry_mut(&mut self) -> EntityMut<'_, Block> { + self.body.front_mut().into_borrow_mut().unwrap() + } + + /// Get the [BlockRef] of the entry block of this region, if it has one + #[inline] + pub fn entry_block_ref(&self) -> Option { + self.body.front().as_pointer() + } + + /// Get the list of blocks comprising the body of this region + pub fn body(&self) -> &BlockList { + &self.body + } + + /// Get a mutable reference to the list of blocks comprising the body of this region + pub fn body_mut(&mut self) -> &mut BlockList { + &mut self.body + } +} + +/// Traversal +impl Region { + pub fn prewalk_all(&self, callback: F) + where + F: FnMut(&Operation), + { + Walk::::prewalk_all::(self, callback); + } + + pub fn prewalk(&self, callback: F) -> WalkResult + where + F: FnMut(&Operation) -> WalkResult, + { + Walk::::prewalk::(self, callback) + } + + pub fn postwalk_all(&self, callback: F) + where + F: FnMut(&Operation), + { + Walk::::postwalk_all::(self, callback); + } + + pub fn postwalk(&self, callback: F) -> WalkResult + where + F: FnMut(&Operation) -> WalkResult, + { + Walk::::postwalk::(self, callback) + } +} + +/// Metadata +impl Region { + #[inline] + pub fn as_region_ref(&self) -> RegionRef { + unsafe { RegionRef::from_raw(self) } + } + + pub fn region_number(&self) -> usize { + let op = self.parent().unwrap().borrow(); + op.regions() + .iter() + .position(|r| core::ptr::addr_eq(self, &*r)) + .expect("invalid region parent") + } + + /// Returns true if this region is an ancestor of `other`, i.e. it contains it. + /// + /// NOTE: This returns true if `self == other`, see [Self::is_proper_ancestor] if you do not + /// want this behavior. + pub fn is_ancestor(&self, other: &RegionRef) -> bool { + let this = self.as_region_ref(); + &this == other || Self::is_proper_ancestor_of(&this, other) + } + + /// Returns true if this region is a proper ancestor of `other`, i.e. `other` is contained by it + /// + /// NOTE: This returns false if `self == other`, see [Self::is_ancestor] if you do not want this + /// behavior. + pub fn is_proper_ancestor(&self, other: &RegionRef) -> bool { + let this = self.as_region_ref(); + Self::is_proper_ancestor_of(&this, other) + } + + fn is_proper_ancestor_of(this: &RegionRef, other: &RegionRef) -> bool { + if this == other { + return false; + } + + let mut parent = other.borrow().parent_region(); + while let Some(parent_region) = parent.take() { + if this == &parent_region { + return true; + } + parent = parent_region.borrow().parent_region(); + } + + false + } + + /// Returns true if this region may be a graph region without SSA dominance + pub fn may_be_graph_region(&self) -> bool { + if let Some(owner) = self.parent() { + owner + .borrow() + .as_trait::() + .is_some_and(|rki| rki.has_graph_regions()) + } else { + true + } + } + + /// Returns true if this region has only one block + pub fn has_one_block(&self) -> bool { + !self.body.is_empty() + && BlockRef::ptr_eq( + &self.body.front().as_pointer().unwrap(), + &self.body.back().as_pointer().unwrap(), + ) + } + + /// Get the defining [Operation] for this region, if the region is attached to one. + pub fn parent(&self) -> Option { + self.as_region_ref().parent() + } + + /// Get the region which contains the parent operation of this region, if there is one. + pub fn parent_region(&self) -> Option { + self.parent().and_then(|op| op.grandparent()) + } +} + +/// Region Graph +impl Region { + /// Traverse the region graph starting at `begin`. + /// + /// The traversal is interrupted if `stop` evaluates to `true` for a successor region. The + /// first argument given to the callback is the successor region, and the second argument is + /// the set of successor regions visited thus far. + /// + /// Returns `true` if traversal was interrupted, otherwise false. + /// + /// # Panics + /// + /// This function will panic if `begin` is a region that does not belong to an operation, or + /// if that operation does not implement `RegionBranchOpInterface`. + pub fn traverse_region_graph(begin: &Self, mut stop: F) -> bool + where + F: FnMut(&Region, &SmallSet) -> bool, + { + let op = begin.parent().expect("cannot traverse an orphaned region"); + let op = op.borrow(); + let branch = op + .as_trait::() + .expect("expected parent op to implement RegionBranchOpInterface"); + + let mut visited = SmallSet::::default(); + visited.insert(begin.as_region_ref()); + + let mut worklist = SmallVec::<[RegionRef; 4]>::default(); + for successor in + branch.get_successor_regions(RegionBranchPoint::Child(begin.as_region_ref())) + { + if let Some(successor) = successor.into_successor() { + worklist.push(successor); + } + } + + while let Some(next_region_ref) = worklist.pop() { + let next_region = next_region_ref.borrow(); + + if stop(&next_region, &visited) { + return true; + } + + if visited.insert(next_region_ref) { + for successor in + branch.get_successor_regions(RegionBranchPoint::Child(next_region_ref)) + { + if let Some(successor) = successor.into_successor() { + worklist.push(successor); + } + } + } + } + + false + } + + /// Returns true if `self` is reachable from `begin` in the region graph of the containing + /// operation, which must implement the `RegionBranchOpInterface` trait. + pub fn is_reachable_from(&self, begin: &Region) -> bool { + assert_eq!(self.parent(), begin.parent(), "expected both regions to belong to the same op"); + // We interrupted the traversal if we find `self` in the region graph + Self::traverse_region_graph(begin, |region, _| core::ptr::addr_eq(self, region)) + } + + /// Returns true if `self` is reachable from itself in the region graph of the containing + /// operation, which must implement the `RegionBranchOpInterface` trait. + /// + /// The implication of this returning `true`, is that the region graph contains a loop, and + /// `self` participates in that loop. + pub fn is_repetitive_region(&self) -> bool { + Self::traverse_region_graph(self, |region, _| core::ptr::addr_eq(self, region)) + } + + /// Returns a vector of regions in the region graph rooted at `begin`, following a post-order + /// traversal of the graph, i.e. successors appear before their predecessors. + /// + /// NOTE: Backedges encountered during the traversal are ignored. + /// + /// Like [Self::traverse_region_graph], this requires the parent op to implement + /// [RegionBranchOpInterface]. + pub fn postorder_region_graph(begin: &Self) -> SmallVec<[RegionRef; 4]> { + struct RegionNode { + region: RegionRef, + children: SmallVec<[RegionRef; 2]>, + } + impl RegionNode { + pub fn new(region: RegionRef, branch: &dyn RegionBranchOpInterface) -> Self { + // Collect unvisited children + let children = branch + .get_successor_regions(RegionBranchPoint::Child(region)) + .filter_map(|s| s.into_successor()) + .collect(); + Self { region, children } + } + } + + let op = begin.parent().expect("cannot traverse an orphaned region"); + let op = op.borrow(); + let branch = op + .as_trait::() + .expect("expected parent op to implement RegionBranchOpInterface"); + + let mut postorder = SmallVec::<[RegionRef; 4]>::default(); + let mut visited = SmallSet::::default(); + let mut worklist = SmallVec::<[(RegionNode, usize); 4]>::default(); + + let root = begin.as_region_ref(); + visited.insert(root); + let root = RegionNode::new(root, branch); + worklist.push((root, 0)); + + while let Some((node, child_index)) = worklist.last_mut() { + // If we visited all of the children of this node, "recurse" back up the stack + if *child_index >= node.children.len() { + postorder.push(node.region); + worklist.pop(); + } else { + // Otherwise, recursively visit the given child + let index = *child_index; + *child_index += 1; + let child = RegionNode::new(node.children[index], branch); + if worklist.iter().any(|(node, _)| node.region == child.region) { + // `child` forms a backedge to a node we're still visiting, so ignore it + continue; + } else if visited.insert(child.region) { + worklist.push((child, 0)); + } + } + } + + postorder + } + + /// Returns a vector of regions in the region graph rooted at `root`, following a post-order + /// traversal of the graph, i.e. successors appear before their predecessors. + /// + /// NOTE: Backedges encountered during the traversal are ignored. + pub fn postorder_region_graph_for( + root: &dyn RegionBranchOpInterface, + ) -> SmallVec<[RegionRef; 4]> { + struct RegionNode { + region: RegionRef, + children: SmallVec<[RegionRef; 2]>, + } + impl RegionNode { + pub fn new(region: RegionRef, branch: &dyn RegionBranchOpInterface) -> Self { + // Collect unvisited children + let children = branch + .get_successor_regions(RegionBranchPoint::Child(region)) + .filter_map(|s| s.into_successor()) + .collect(); + Self { region, children } + } + } + + let mut postorder = SmallVec::<[RegionRef; 4]>::default(); + let mut visited = SmallSet::::default(); + let mut worklist = SmallVec::<[(RegionNode, usize); 4]>::default(); + + for succ in root.get_successor_regions(RegionBranchPoint::Parent) { + let Some(region) = succ.into_successor() else { + continue; + }; + + if visited.insert(region) { + worklist.push((RegionNode::new(region, root), 0)); + } + } + + while let Some((node, child_index)) = worklist.last_mut() { + // If we visited all of the children of this node, "recurse" back up the stack + if *child_index >= node.children.len() { + postorder.push(node.region); + worklist.pop(); + } else { + // Otherwise, recursively visit the given child + let index = *child_index; + *child_index += 1; + let child = RegionNode::new(node.children[index], root); + if worklist.iter().any(|(node, _)| node.region == child.region) { + // `child` forms a backedge to a node we're still visiting, so ignore it + continue; + } else if visited.insert(child.region) { + worklist.push((child, 0)); + } + } + } + + postorder + } +} + +/// Mutation +impl Region { + /// Push `block` to the start of this region + #[inline] + pub fn push_front(&mut self, block: BlockRef) { + self.body.push_front(block); + } + + /// Push `block` to the end of this region + #[inline] + pub fn push_back(&mut self, block: BlockRef) { + self.body.push_back(block); + } + + pub fn take_body(&mut self, mut from_region: RegionRef) { + self.drop_all_references(); + self.body.clear(); + + // Take blocks from `from_region`, update the parent of all the blocks, then splice to the + // end of this region's body + let blocks = from_region.borrow_mut().body_mut().take(); + self.body.back_mut().splice_after(blocks); + } + + /// Drop all operand uses from operations within this region, which is an essential step + /// in breaking cyclic dependencies between references when they are to be deleted. + pub fn drop_all_references(&mut self) { + let mut cursor = self.body_mut().front_mut(); + while let Some(mut op) = cursor.as_pointer() { + op.borrow_mut().drop_all_references(); + cursor.move_next(); + } + } +} + +/// Values +impl Region { + /// Check if every value in `values` is defined above this region, i.e. they are defined in a + /// region which is a proper ancestor of `self`. + pub fn values_are_defined_above(&self, values: &[ValueRef]) -> bool { + let this = self.as_region_ref(); + for value in values { + if !value + .borrow() + .parent_region() + .is_some_and(|value_region| Self::is_proper_ancestor_of(&value_region, &this)) + { + return false; + } + } + true + } + + /// Replace all uses of `value` with `replacement`, within this region. + pub fn replace_all_uses_in_region_with(&mut self, _value: ValueRef, _replacement: ValueRef) { + todo!("RegionUtils.h") + } + + /// Visit each use of a value in this region (and its descendants), where that value was defined + /// in an ancestor of `limit`. + pub fn visit_used_values_defined_above(&self, _limit: &RegionRef, _callback: F) + where + F: FnMut(OpOperand), + { + todo!("RegionUtils.h") + } + + /// Visit each use of a value in any of the provided regions (or their descendants), where that + /// value was defined in an ancestor of that region. + pub fn visit_used_values_defined_above_any(_regions: &[RegionRef], _callback: F) + where + F: FnMut(OpOperand), + { + todo!("RegionUtils.h") + } + + /// Return a vector of values used in this region (and its descendants), and defined in an + /// ancestor of the `limit` region. + pub fn get_used_values_defined_above(&self, _limit: &RegionRef) -> SmallVec<[ValueRef; 1]> { + todo!("RegionUtils.h") + } + + /// Return a vector of values used in any of the provided regions, but defined in an ancestor. + pub fn get_used_values_defined_above_any(_regions: &[RegionRef]) -> SmallVec<[ValueRef; 1]> { + todo!("RegionUtils.h") + } + + /// Make this region isolated from above. + /// + /// * Capture the values that are defined above the region and used within it. + /// * Append block arguments to the entry block that represent each captured value. + /// * Replace all uses of the captured values within the region, with the new block arguments + /// * `clone_into_region` is called with the defining op of a captured value. If it returns + /// true, it indicates that the op needs to be cloned into the region. As a result, the + /// operands of that operation become part of the captured value set (unless the operations + /// that define the operand values themselves are to be cloned). The cloned operations are + /// added to the entry block of the region. + /// + /// Returns the set of captured values. + pub fn make_isolated_from_above( + &mut self, + _rewriter: &mut R, + _clone_into_region: F, + ) -> SmallVec<[ValueRef; 1]> + where + R: crate::Rewriter, + F: Fn(&Operation) -> bool, + { + todo!("RegionUtils.h") + } +} + +/// Queries +impl Region { + pub fn find_common_ancestor(ops: &[OperationRef]) -> Option { + use bitvec::prelude::*; + + match ops.len() { + 0 => None, + 1 => unsafe { ops.get_unchecked(0) }.borrow().parent_region(), + num_ops => { + let (first, rest) = unsafe { ops.split_first().unwrap_unchecked() }; + let mut region = first.borrow().parent_region(); + let mut remaining_ops = bitvec![1; num_ops - 1]; + while let Some(r) = region.take() { + while let Some(index) = remaining_ops.first_one() { + // Is this op contained in `region`? + if r.borrow().find_ancestor_op(rest[index]).is_some() { + unsafe { + remaining_ops.set_unchecked(index, false); + } + } + } + if remaining_ops.not_any() { + break; + } + region = r.borrow().parent_region(); + } + region + } + } + } + + /// Returns `block` if `block` lies in this region, or otherwise finds the ancestor of `block` + /// that lies in this region. + /// + /// Returns `None` if the latter fails. + pub fn find_ancestor_block(&self, block: BlockRef) -> Option { + let this = self.as_region_ref(); + let mut current = Some(block); + while let Some(current_block) = current.take() { + let parent = current_block.parent()?; + if parent == this { + return Some(current_block); + } + current = parent.grandparent(); + } + current + } + + /// Returns `op` if `op` lies in this region, or otherwise finds the ancestor of `op` that lies + /// in this region. + /// + /// Returns `None` if the latter fails. + pub fn find_ancestor_op(&self, op: OperationRef) -> Option { + let this = self.as_region_ref(); + let mut current = Some(op); + while let Some(current_op) = current.take() { + let parent = current_op.borrow().parent_region()?; + if parent == this { + return Some(current_op); + } + current = parent.parent(); + } + current + } +} + +/// Transforms +impl Region { + /// Run a set of structural simplifications over the regions in `regions`. + /// + /// This includes transformations like unreachable block elimination, dead argument elimination, + /// as well as some other DCE. + /// + /// This function returns `Ok` if any of the regions were simplified, `Err` otherwise. + /// + /// The provided rewriter is used to notify callers of operation and block deletion. + /// + /// The provided [RegionSimplificationLevel] will be used to determine whether to apply more + /// aggressive simplifications, namely block merging. Note that when block merging is enabled, + /// this can lead to merged blocks with extra arguments. + pub fn simplify_all( + regions: &[RegionRef], + rewriter: &mut dyn crate::Rewriter, + simplification_level: RegionSimplificationLevel, + ) -> Result<(), RegionTransformFailed> { + let merge_blocks = matches!(simplification_level, RegionSimplificationLevel::Aggressive); + + log::debug!(target: "region-simplify", "running region simplification on {} regions", regions.len()); + log::debug!(target: "region-simplify", " simplification level = {simplification_level:?}"); + log::debug!(target: "region-simplify", " merge_blocks = {merge_blocks}"); + + let eliminated_blocks = Self::erase_unreachable_blocks(regions, rewriter).is_ok(); + let eliminated_ops_or_args = Self::dead_code_elimination(regions, rewriter).is_ok(); + + let mut merged_identical_blocks = false; + let mut dropped_redundant_arguments = false; + if merge_blocks { + merged_identical_blocks = Self::merge_identical_blocks(regions, rewriter).is_ok(); + dropped_redundant_arguments = Self::drop_redundant_arguments(regions, rewriter).is_ok(); + } + + if eliminated_blocks + || eliminated_ops_or_args + || merged_identical_blocks + || dropped_redundant_arguments + { + Ok(()) + } else { + Err(RegionTransformFailed) + } + } +} + +/// Printing +impl Region { + pub fn print(&self, flags: &OpPrintingFlags) -> crate::formatter::Document { + use crate::formatter::PrettyPrint; + + let printer = RegionPrinter { + region: self, + flags, + }; + printer.render() + } +} + +struct RegionPrinter<'a> { + region: &'a Region, + flags: &'a OpPrintingFlags, +} + +impl crate::formatter::PrettyPrint for RegionPrinter<'_> { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + if self.region.is_empty() { + return const_text("{ }"); + } + + let is_parent_op_single_block_single_region = self.region.parent().is_some_and(|op| { + let op = op.borrow(); + op.implements::() && op.implements::() + }); + self.region.body.iter().fold(Document::Empty, |acc, block| { + if acc.is_empty() { + if is_parent_op_single_block_single_region || !self.flags.print_entry_block_headers + { + const_text("{") + indent(4, nl() + block.print(self.flags)) + } else { + const_text("{") + nl() + block.print(self.flags) + } + } else { + acc + nl() + block.print(self.flags) + } + }) + nl() + + const_text("}") + } +} + +impl crate::formatter::PrettyPrint for Region { + fn render(&self) -> crate::formatter::Document { + let flags = OpPrintingFlags::default(); + + self.print(&flags) + } +} diff --git a/hir/src/ir/region/branch_point.rs b/hir/src/ir/region/branch_point.rs new file mode 100644 index 000000000..d89b9d5dc --- /dev/null +++ b/hir/src/ir/region/branch_point.rs @@ -0,0 +1,60 @@ +use core::fmt; + +use super::*; + +/// This type represents a point being branched from in the methods of `RegionBranchOpInterface`. +/// +/// One can branch from one of two different kinds of places: +/// +/// * The parent operation (i.e. the op implementing `RegionBranchOpInterface`). +/// * A region within the parent operation (where the parent implements `RegionBranchOpInterface`). +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum RegionBranchPoint { + /// A branch from the current operation to one of its regions + Parent, + /// A branch from the given region, within a parent `RegionBranchOpInterface` op + Child(RegionRef), +} +impl fmt::Display for RegionBranchPoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Parent => f.write_str("parent"), + Self::Child(ref region) => { + write!(f, "child({})", region.borrow().region_number()) + } + } + } +} +impl fmt::Debug for RegionBranchPoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Parent => f.write_str("Parent"), + Self::Child(ref region) => { + f.debug_tuple("Child").field(&format_args!("{region:p}")).finish() + } + } + } +} +impl RegionBranchPoint { + /// Returns true if branching from the parent op. + #[inline] + pub fn is_parent(&self) -> bool { + matches!(self, Self::Parent) + } + + /// Returns the region if branching from a region, otherwise `None`. + pub fn region(&self) -> Option { + match self { + Self::Child(region) => Some(*region), + Self::Parent => None, + } + } +} +impl<'a> From> for RegionBranchPoint { + fn from(succ: RegionSuccessor<'a>) -> Self { + match succ.into_successor() { + None => Self::Parent, + Some(succ) => Self::Child(succ), + } + } +} diff --git a/hir/src/ir/region/interfaces.rs b/hir/src/ir/region/interfaces.rs new file mode 100644 index 000000000..e166af2a2 --- /dev/null +++ b/hir/src/ir/region/interfaces.rs @@ -0,0 +1,385 @@ +use alloc::boxed::Box; + +use super::*; +use crate::{ + attributes::AttributeValue, traits::Terminator, Op, SuccessorOperandRange, + SuccessorOperandRangeMut, Type, +}; + +/// An op interface that indicates what types of regions it holds +pub trait RegionKindInterface { + /// Get the [RegionKind] for this operation + fn kind(&self) -> RegionKind; + /// Returns true if the kind of this operation's regions requires SSA dominance + #[inline] + fn has_ssa_dominance(&self) -> bool { + matches!(self.kind(), RegionKind::SSA) + } + #[inline] + fn has_graph_regions(&self) -> bool { + matches!(self.kind(), RegionKind::Graph) + } +} + +// TODO(pauls): Implement verifier +/// This interface provides information for region operations that exhibit branching behavior +/// between held regions. I.e., this interface allows for expressing control flow information for +/// region holding operations. +/// +/// This interface is meant to model well-defined cases of control-flow and value propagation, +/// where what occurs along control-flow edges is assumed to be side-effect free. +/// +/// A "region branch point" indicates a point from which a branch originates. It can indicate either +/// a region of this op or [RegionBranchPoint::Parent]. In the latter case, the branch originates +/// from outside of the op, i.e., when first executing this op. +/// +/// A "region successor" indicates the target of a branch. It can indicate either a region of this +/// op or this op. In the former case, the region successor is a region pointer and a range of block +/// arguments to which the "successor operands" are forwarded to. In the latter case, the control +/// flow leaves this op and the region successor is a range of results of this op to which the +/// successor operands are forwarded to. +/// +/// By default, successor operands and successor block arguments/successor results must have the +/// same type. `areTypesCompatible` can be implemented to allow non-equal types. +/// +/// ## Example +/// +/// ```hir,ignore +/// %r = scf.for %iv = %lb to %ub step %step iter_args(%a = %b) +/// -> tensor<5xf32> { +/// ... +/// scf.yield %c : tensor<5xf32> +/// } +/// ``` +/// +/// `scf.for` has one region. The region has two region successors: the region itself and the +/// `scf.for` op. `%b` is an entry successor operand. `%c` is a successor operand. `%a` is a +/// successor block argument. `%r` is a successor result. +pub trait RegionBranchOpInterface: Op { + /// Returns the operands of this operation that are forwarded to the region successor's block + /// arguments or this operation's results when branching to `point`. `point` is guaranteed to + /// be among the successors that are returned by `get_entry_succcessor_regions` or + /// `get_successor_regions(parent_op())`. + /// + /// ## Example + /// + /// In the example in the top-level docs of this trait, this function returns the operand `%b` + /// of the `scf.for` op, regardless of the value of `point`, i.e. this op always forwards the + /// same operands, regardless of whether the loop has 0 or more iterations. + #[inline] + #[allow(unused_variables)] + fn get_entry_successor_operands(&self, point: RegionBranchPoint) -> SuccessorOperandRange<'_> { + crate::SuccessorOperandRange::empty() + } + /// Returns the potential region successors when first executing the op. + /// + /// Unlike [get_successor_regions], this method also passes along the constant operands of this + /// op. Based on these, the implementation may filter out certain successors. By default, it + /// simply dispatches to `get_successor_regions`. `operands` contains an entry for every operand + /// of this op, with `None` representing if the operand is non-constant. + /// + /// NOTE: The control flow does not necessarily have to enter any region of this op. + /// + /// ## Example + /// + /// In the example in the top-level docs of this trait, this function may return two region + /// successors: the single region of the `scf.for` op and the `scf.for` operation (that + /// implements this interface). If `%lb`, `%ub`, `%step` are constants and it can be determined + /// the loop does not have any iterations, this function may choose to return only this + /// operation. Similarly, if it can be determined that the loop has at least one iteration, this + /// function may choose to return only the region of the loop. + #[inline] + #[allow(unused_variables)] + fn get_entry_successor_regions( + &self, + operands: &[Option>], + ) -> RegionSuccessorIter<'_> { + self.get_successor_regions(RegionBranchPoint::Parent) + } + /// Returns the potential region successors when branching from `point`. + /// + /// These are the regions that may be selected during the flow of control. + /// + /// When `point` is [RegionBranchPoint::Parent], this function returns the region successors + /// when entering the operation. Otherwise, this method returns the successor regions when + /// branching from the region indicated by `point`. + /// + /// ## Example + /// + /// In the example in the top-level docs of this trait, this function returns the region of the + /// `scf.for` and this operation for either region branch point (`parent` and the region of the + /// `scf.for`). An implementation may choose to filter out region successors when it is + /// statically known (e.g., by examining the operands of this op) that those successors are not + /// branched to. + fn get_successor_regions(&self, point: RegionBranchPoint) -> RegionSuccessorIter<'_>; + /// Returns a set of invocation bounds, representing the minimum and maximum number of times + /// this operation will invoke each attached region (assuming the regions yield normally, i.e. + /// do not abort or invoke an infinite loop). The minimum number of invocations is at least 0. + /// If the maximum number of invocations cannot be statically determined, then it will be set to + /// [InvocationBounds::unknown]. + /// + /// This function also passes along the constant operands of this op. `operands` contains an + /// entry for every operand of this op, with `None` representing if the operand is non-constant. + /// + /// This function may be called speculatively on operations where the provided operands are not + /// necessarily the same as the operation's current operands. This may occur in analyses that + /// wish to determine "what would be the region invocations if these were the operands?" + #[inline] + #[allow(unused_variables)] + fn get_region_invocation_bounds( + &self, + operands: &[Option>], + ) -> SmallVec<[InvocationBounds; 1]> { + use smallvec::smallvec; + + smallvec![InvocationBounds::Unknown; self.num_regions()] + } + /// This function is called to compare types along control-flow edges. + /// + /// By default, the types are check for exact equality. + #[inline] + fn are_types_compatible(&self, lhs: &Type, rhs: &Type) -> bool { + lhs == rhs + } + /// Returns `true` if control flow originating from the region at `index` may eventually branch + /// back to the same region, either from itself, or after passing through other regions first. + fn is_repetitive_region(&self, index: usize) -> bool { + self.region(index).is_repetitive_region() + } + /// Returns `true` if there is a loop in the region branching graph. + /// + /// Only reachable regions (starting from the entry region) are considered. + fn has_loop(&self) -> bool { + self.get_successor_regions(RegionBranchPoint::Parent) + .filter_map(|entry| entry.into_successor()) + .any(|region| { + Region::traverse_region_graph(®ion.borrow(), |r, visited| { + // Interrupted traversal if the region was already visited + visited.contains(&r.as_region_ref()) + }) + }) + } +} + +// TODO(pauls): Implement verifier (should have no results and no successors) +/// This interface provides information for branching terminator operations in the presence of a +/// parent [RegionBranchOpInterface] implementation. It specifies which operands are passed to which +/// successor region. +pub trait RegionBranchTerminatorOpInterface: Op + Terminator { + /// Get a range of operands corresponding to values that are semantically "returned" by passing + /// them to the region successor indicated by `point`. + fn get_successor_operands(&self, point: RegionBranchPoint) -> SuccessorOperandRange<'_>; + /// Get a mutable range of operands corresponding to values that are semantically "returned" by + /// passing them to the region successor indicated by `point`. + fn get_mutable_successor_operands( + &mut self, + point: RegionBranchPoint, + ) -> SuccessorOperandRangeMut<'_>; + /// Returns the potential region successors that are branched to after this terminator based on + /// the given constant operands. + /// + /// This method also passes along the constant operands of this op. `operands` contains an entry + /// for every operand of this op, with `None` representing non-constant values. + /// + /// The default implementation simply dispatches to the parent `RegionBranchOpInterface`'s + /// `get_successor_regions` implementation. + #[allow(unused_variables)] + fn get_successor_regions( + &self, + operands: &[Option>], + ) -> SmallVec<[RegionSuccessorInfo; 2]> { + let parent_region = + self.parent_region().expect("expected operation to have a parent region"); + let parent_op = parent_region.parent().expect("expected operation to have a parent op"); + parent_op + .borrow() + .as_trait::() + .expect("invalid region terminator parent: must implement RegionBranchOpInterface") + .get_successor_regions(RegionBranchPoint::Child(parent_region)) + .into_successor_infos() + } +} + +/// This trait is implemented by operations which have loop-like semantics. +/// +/// It provides useful helpers and access to properties of the loop represented, and is used in +/// order to perform transformations on the loop. Implementors will be considered by loop-invariant +/// code motion. +/// +/// Loop-carried variables can be exposed through this interface. There are 3 components to a +/// loop-carried variable: +/// +/// - The "region iter_arg" is the block argument of the entry block that represents the loop- +/// carried variable in each iteration. +/// - The "init value" is an operand of the loop op that serves as the initial region iter_arg value +/// for the first iteration (if any). +/// - The "yielded" value is the value that is forwarded from one iteration to serve as the region +/// iter_arg of the next iteration. +/// +/// If one of the respective interface methods is implemented, so must the other two. The interface +/// verifier ensures that the number of types of the region iter_args, init values and yielded +/// values match. +/// +/// Optionally, "loop results" can be exposed through this interface. These are the values that are +/// returned from the loop op when there are no more iterations. The number and types of the loop +/// results must match with the region iter_args. Note: Loop results are optional because some loops +/// (e.g., `scf.while`) may produce results that do match 1-to-1 with the region iter_args. +#[allow(unused_variables)] +#[allow(clippy::result_unit_err)] +pub trait LoopLikeOpInterface: Op { + /// Returns true if the given value is defined outside of the loop. + /// + /// A sensible implementation could be to check whether the value's defining operation lies + /// outside of the loops body region. If the loop uses explicit capture of dependencies, an + /// implementation could check whether the value corresponds to a captured dependency. + fn is_defined_outside_of_loop(&self, value: ValueRef) -> bool { + let value = value.borrow(); + if let Some(defining_op) = value.get_defining_op() { + self.as_operation().is_ancestor_of(&defining_op.borrow()) + } else { + let block_arg = value + .downcast_ref::() + .expect("invalid value reference: defining op is orphaned"); + let defining_region = block_arg.parent_region().unwrap(); + let defining_op = defining_region.parent().unwrap(); + self.as_operation().is_ancestor_of(&defining_op.borrow()) + } + } + + /// Returns the entry region for this loop, which is expected to also play the role of loop + /// header. + /// + /// NOTE: It is expected that if the loop has iteration arguments, that the values returned + /// from `Self::get_region_iter_args` correspond to block arguments of the header region. + /// Additionally, it is presumed that initialization variables expected by the op are provided + /// to the loop body via block arguments of this region. + fn get_loop_header_region(&self) -> RegionRef; + + /// Returns the regions that make up the body of the loop, and should be inspected for loop- + /// invariant operations. + fn get_loop_regions(&self) -> SmallVec<[RegionRef; 2]>; + + /// Moves the given loop-invariant operation out of the loop. + fn move_out_of_loop(&mut self, mut op: OperationRef) { + op.borrow_mut().move_to(crate::ProgramPoint::before(self.as_operation())); + } + + /// Promotes the loop body to its containing block if the loop is known to have a single + /// iteration. + /// + /// Returns `Ok` if the promotion was successful + fn promote_if_single_iteration( + &mut self, + rewriter: &mut dyn crate::Rewriter, + ) -> Result<(), ()> { + Err(()) + } + + /// Return all induction variables, if they exist. + /// + /// If the op has no notion of induction variable, then return `None`. If it does have a notion + /// but an instance doesn't have induction variables, then return an empty vector. + fn get_loop_induction_vars(&self) -> Option> { + None + } + + /// Return all lower bounds, if they exist. + /// + /// If the op has no notion of lower bounds, then return `None`. If it does have a notion but an + /// instance doesn't have lower bounds, then return an empty vector. + fn get_loop_lower_bounds(&self) -> Option> { + None + } + + /// Return all upper bounds, if they exist. + /// + /// If the op has no notion of upper bounds, then return `None`. If it does have a notion but an + /// instance doesn't have upper bounds, then return an empty vector. + fn get_loop_upper_bounds(&self) -> Option> { + None + } + + /// Return all steps, if they exist. + /// + /// If the op has no notion of steps, then return `None`. If it does have a notion but an + /// instance doesn't have steps, then return an empty vector. + fn get_loop_steps(&self) -> Option> { + None + } + + /// Return the mutable "init" operands that are used as initialization values for the region + /// "iter_args" of this loop. + fn get_inits_mut(&mut self) -> OpOperandRangeMut<'_> { + self.operands_mut().empty_mut() + } + + /// Return the region "iter_args" (block arguments) that correspond to the "init" operands. + /// + /// If the op has multiple regions, return the corresponding block arguments of the entry region. + fn get_region_iter_args(&self) -> Option> { + None + } + + /// Return the mutable operand range of values that are yielded to the next iteration by the + /// loop terminator. + /// + /// For loop operations that dont yield a value, this should return `None`. + fn get_yielded_values_mut(&mut self) -> Option>> { + None + } + + /// Return the range of results that are return from this loop and correspond to the "init" + /// operands. + /// + /// Note: This interface method is optional. If loop results are not exposed via this interface, + /// `None` should be returned. + /// + /// Otherwise, the number and types of results must match with the region iter_args, inits and + /// yielded values that are exposed via this interface. If loop results are exposed but this + /// loop op has no loop-carried variables, an empty result range (and not `None`) should be + /// returned. + fn get_loop_results(&self) -> Option> { + None + } +} + +impl dyn LoopLikeOpInterface { + /// If there is a single induction variable return it, otherwise return `None` + pub fn get_single_induction_var(&self) -> Option { + let vars = self.get_loop_induction_vars(); + if let Some([var]) = vars.as_deref() { + return Some(*var); + } + None + } + + /// Return the single lower bound value or attribute if it exists, otherwise return `None` + pub fn get_single_lower_bound(&self) -> Option { + let mut lower_bounds = self.get_loop_lower_bounds()?; + if lower_bounds.len() == 1 { + lower_bounds.pop() + } else { + None + } + } + + /// Return the single upper bound value or attribute if it exists, otherwise return `None` + pub fn get_single_upper_bound(&self) -> Option { + let mut upper_bounds = self.get_loop_upper_bounds()?; + if upper_bounds.len() == 1 { + upper_bounds.pop() + } else { + None + } + } + + /// Return the single step value or attribute if it exists, otherwise return `None` + pub fn get_single_step(&self) -> Option { + let mut steps = self.get_loop_steps()?; + if steps.len() == 1 { + steps.pop() + } else { + None + } + } +} diff --git a/hir/src/ir/region/invocation_bounds.rs b/hir/src/ir/region/invocation_bounds.rs new file mode 100644 index 000000000..574471747 --- /dev/null +++ b/hir/src/ir/region/invocation_bounds.rs @@ -0,0 +1,132 @@ +use core::ops::{Bound, RangeBounds}; + +/// This type represents upper and lower bounds on the number of times a region of a +/// `RegionBranchOpInterface` op can be invoked. The lower bound is at least zero, but the upper +/// bound may not be known. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +pub enum InvocationBounds { + /// The region can be invoked an unknown number of times, possibly never. + #[default] + Unknown, + /// The region can never be invoked + Never, + /// The region can be invoked exactly N times + Exact(u32), + /// The region can be invoked any number of times in the given range + Variable { min: u32, max: u32 }, + /// The region can be invoked at least N times, but an unknown number of times beyond that. + AtLeastN(u32), + /// The region can be invoked any number of times up to N + NoMoreThan(u32), +} +impl InvocationBounds { + #[inline] + pub fn new(bounds: impl Into) -> Self { + bounds.into() + } + + #[inline] + pub fn is_unknown(&self) -> bool { + matches!(self, Self::Unknown) + } + + pub fn min(&self) -> Bound<&u32> { + self.start_bound() + } + + pub fn max(&self) -> Bound<&u32> { + self.end_bound() + } +} +impl From for InvocationBounds { + fn from(value: u32) -> Self { + if value == 0 { + Self::Never + } else { + Self::Exact(value) + } + } +} +impl From> for InvocationBounds { + fn from(range: core::ops::Range) -> Self { + if range.start == range.end { + Self::Never + } else if range.end == range.start + 1 { + Self::Exact(range.start) + } else { + assert!(range.start < range.end); + Self::Variable { + min: range.start, + max: range.end, + } + } + } +} +impl From> for InvocationBounds { + fn from(value: core::ops::RangeFrom) -> Self { + if value.start == 0 { + Self::Unknown + } else { + Self::AtLeastN(value.start) + } + } +} +impl From> for InvocationBounds { + fn from(value: core::ops::RangeTo) -> Self { + if value.end == 1 { + Self::Never + } else if value.end == u32::MAX { + Self::Unknown + } else { + Self::NoMoreThan(value.end - 1) + } + } +} +impl From for InvocationBounds { + fn from(_value: core::ops::RangeFull) -> Self { + Self::Unknown + } +} +impl From> for InvocationBounds { + fn from(range: core::ops::RangeInclusive) -> Self { + let (start, end) = range.into_inner(); + if start == 0 && end == 0 { + Self::Never + } else if start == end { + Self::Exact(start) + } else { + Self::Variable { + min: start, + max: end + 1, + } + } + } +} +impl From> for InvocationBounds { + fn from(range: core::ops::RangeToInclusive) -> Self { + if range.end == 0 { + Self::Never + } else { + Self::NoMoreThan(range.end) + } + } +} +impl RangeBounds for InvocationBounds { + fn start_bound(&self) -> Bound<&u32> { + match self { + Self::Unknown | Self::NoMoreThan(_) => Bound::Unbounded, + Self::Never => Bound::Excluded(&0), + Self::Exact(n) | Self::Variable { min: n, .. } => Bound::Included(n), + Self::AtLeastN(n) => Bound::Included(n), + } + } + + fn end_bound(&self) -> Bound<&u32> { + match self { + Self::Unknown | Self::AtLeastN(_) => Bound::Unbounded, + Self::Never => Bound::Excluded(&0), + Self::Exact(n) | Self::Variable { max: n, .. } => Bound::Excluded(n), + Self::NoMoreThan(n) => Bound::Excluded(n), + } + } +} diff --git a/hir/src/ir/region/kind.rs b/hir/src/ir/region/kind.rs new file mode 100644 index 000000000..83eb6da8e --- /dev/null +++ b/hir/src/ir/region/kind.rs @@ -0,0 +1,22 @@ +/// Represents the types of regions that can be represented in the IR +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +pub enum RegionKind { + /// A graph region is one without control-flow semantics, i.e. dataflow between operations is + /// the only thing that dictates order, and operations can be conceptually executed in parallel + /// if the runtime supports it. + /// + /// As there is no control-flow in these regions, graph regions may only contain a single block. + Graph, + /// An SSA region is one where the strict control-flow semantics and properties of SSA (static + /// single assignment) form must be upheld. + /// + /// SSA regions must adhere to: + /// + /// * Values can only be defined once + /// * Definitions must dominate uses + /// * Ordering of operations in a block corresponds to execution order, i.e. operations earlier + /// in a block dominate those later in the block. + /// * Blocks must end with a terminator. + #[default] + SSA, +} diff --git a/hir/src/ir/region/successor.rs b/hir/src/ir/region/successor.rs new file mode 100644 index 000000000..6e972ec1d --- /dev/null +++ b/hir/src/ir/region/successor.rs @@ -0,0 +1,183 @@ +use core::fmt; + +use self::value::BlockArgumentRange; +use super::*; +use crate::ValueRange; + +/// This struct represents the metadata about a [RegionBranchOpInterface] successor, without +/// borrowing the op or the successor. +#[derive(Debug, Clone)] +pub enum RegionSuccessorInfo { + /// We're entering the given region, and its successor operands are the block arguments of its + /// entry block. + Entering(RegionRef), + /// We're exiting/returning to the parent op from one of its child regions. + /// + /// The given result group index is used to obtain the successor operands from the results of + /// the terminator operation which transfers control to the parent. + Returning(SmallVec<[ValueRef; 2]>), +} +impl RegionSuccessorInfo { + pub fn successor(&self) -> RegionBranchPoint { + match self { + Self::Entering(region) => RegionBranchPoint::Child(*region), + Self::Returning(_) => RegionBranchPoint::Parent, + } + } +} + +/// A [RegionSuccessor] represents the successor of a region. +/// +/// +/// A region successor can either be another region, or the parent operation. If the successor is a +/// region, this class represents the destination region, as well as a set of arguments from that +/// region that will be populated when control flows into the region. If the successor is the parent +/// operation, this class represents an optional set of results that will be populated when control +/// returns to the parent operation. +/// +/// This interface assumes that the values from the current region that are used to populate the +/// successor inputs are the operands of the return-like terminator operations in the blocks within +/// this region. +pub struct RegionSuccessor<'a> { + dest: RegionBranchPoint, + arguments: ValueRange<'a>, +} + +impl<'a> RegionSuccessor<'a> { + /// Creates a [RegionSuccessor] representing a branch to `dest` with `arguments` + pub fn new(dest: RegionBranchPoint, arguments: impl Into>) -> Self { + Self { + dest, + arguments: arguments.into(), + } + } + + /// Creates a [RegionSuccessor] representing a branch to another region of the parent operation. + pub fn child(region: RegionRef, inputs: BlockArgumentRange<'a>) -> Self { + Self { + dest: RegionBranchPoint::Child(region), + arguments: inputs.into(), + } + } + + /// Creates a [RegionSuccessor] representing a branch to/out of the parent operation. + pub fn parent(inputs: OpResultRange<'a>) -> Self { + Self { + dest: RegionBranchPoint::Parent, + arguments: inputs.into(), + } + } + + /// Get the underlying [RegionBranchPoint] + pub fn branch_point(&self) -> &RegionBranchPoint { + &self.dest + } + + /// Returns true if the successor is the parent op + pub fn is_parent(&self) -> bool { + self.dest.is_parent() + } + + pub fn successor(&self) -> Option { + self.dest.region() + } + + pub fn into_successor(self) -> Option { + self.dest.region() + } + + /// Return the inputs to the successor that are remapped by the exit values of the current + /// region. + pub fn successor_inputs(&self) -> &ValueRange<'a> { + &self.arguments + } +} + +impl fmt::Debug for RegionSuccessor<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("RegionSuccessor") + .field("dest", &self.dest) + .field_with("arguments", |f| { + let mut list = f.debug_list(); + for operand in self.arguments.iter() { + list.entry(&operand.borrow()); + } + list.finish() + }) + .finish() + } +} + +pub struct RegionSuccessorIter<'a> { + // TODO(pauls): See if we can get rid of [RegionSuccessorInfo] entirely and just use + // [RegionSuccessor] + #[allow(unused)] + op: &'a Operation, + successors: SmallVec<[RegionSuccessorInfo; 2]>, + index: usize, +} +impl<'a> RegionSuccessorIter<'a> { + pub fn new( + op: &'a Operation, + successors: impl IntoIterator, + ) -> Self { + Self { + op, + successors: SmallVec::from_iter(successors), + index: 0, + } + } + + pub fn empty(op: &'a Operation) -> Self { + Self { + op, + successors: Default::default(), + index: 0, + } + } + + pub fn get(&self, index: usize) -> Option> { + self.successors.get(index).map(|info| match info { + RegionSuccessorInfo::Entering(region) => { + let operands = ValueRange::Owned( + region + .borrow() + .entry() + .arguments() + .iter() + .map(|arg| arg.borrow().as_value_ref()) + .collect(), + ); + RegionSuccessor::new(RegionBranchPoint::Child(*region), operands) + } + RegionSuccessorInfo::Returning(results) => { + RegionSuccessor::new(RegionBranchPoint::Parent, results.clone()) + } + }) + } + + pub fn into_successor_infos(self) -> SmallVec<[RegionSuccessorInfo; 2]> { + self.successors + } +} +impl core::iter::FusedIterator for RegionSuccessorIter<'_> {} +impl ExactSizeIterator for RegionSuccessorIter<'_> { + fn len(&self) -> usize { + self.successors.len() + } +} +impl<'a> Iterator for RegionSuccessorIter<'a> { + type Item = RegionSuccessor<'a>; + + fn next(&mut self) -> Option { + if self.index >= self.successors.len() { + return None; + } + + let next = self.get(self.index)?; + + self.index += 1; + + Some(next) + } +} diff --git a/hir/src/ir/region/transforms.rs b/hir/src/ir/region/transforms.rs new file mode 100644 index 000000000..10f7d442d --- /dev/null +++ b/hir/src/ir/region/transforms.rs @@ -0,0 +1,6 @@ +mod block_merging; +mod dce; +mod drop_redundant_args; + +#[derive(Debug, Copy, Clone)] +pub struct RegionTransformFailed; diff --git a/hir/src/ir/region/transforms/block_merging.rs b/hir/src/ir/region/transforms/block_merging.rs new file mode 100644 index 000000000..57c38af91 --- /dev/null +++ b/hir/src/ir/region/transforms/block_merging.rs @@ -0,0 +1,263 @@ +#![allow(unused)] +use alloc::boxed::Box; + +use super::RegionTransformFailed; +use crate::{ + adt::SmallDenseMap, BlockArgument, BlockRef, DynHash, OpResult, Operation, OperationRef, + Region, RegionRef, Rewriter, ValueRef, +}; + +bitflags::bitflags! { + struct EquivalenceFlags: u8 { + const IGNORE_LOCATIONS = 1; + } +} + +struct OpEquivalence { + flags: EquivalenceFlags, + operand_hasher: OperandHasher, + result_hasher: ResultHasher, +} + +type ValueHasher = Box; + +impl OpEquivalence { + pub fn new() -> Self { + Self { + flags: EquivalenceFlags::empty(), + operand_hasher: DefaultValueHasher, + result_hasher: DefaultValueHasher, + } + } +} +impl OpEquivalence { + #[inline] + pub fn with_flags(mut self, flags: EquivalenceFlags) -> Self { + self.flags.insert(flags); + self + } + + /// Ignore op operands when computing equivalence for operations + pub fn ignore_operands(self) -> OpEquivalence<(), ResultHasher> { + OpEquivalence { + flags: self.flags, + operand_hasher: (), + result_hasher: self.result_hasher, + } + } + + /// Ignore op results when computing equivalence for operations + pub fn ignore_results(self) -> OpEquivalence { + OpEquivalence { + flags: self.flags, + operand_hasher: self.operand_hasher, + result_hasher: (), + } + } + + /// Specify a custom hasher for op operands + pub fn with_operand_hasher( + self, + hasher: impl Fn(&ValueRef, &mut dyn core::hash::Hasher) + 'static, + ) -> OpEquivalence { + OpEquivalence { + flags: self.flags, + operand_hasher: Box::new(hasher), + result_hasher: self.result_hasher, + } + } + + /// Specify a custom hasher for op results + pub fn with_result_hasher( + self, + hasher: impl Fn(&ValueRef, &mut dyn core::hash::Hasher) + 'static, + ) -> OpEquivalence { + OpEquivalence { + flags: self.flags, + operand_hasher: self.operand_hasher, + result_hasher: Box::new(hasher), + } + } + + /// Compare if two operations are equivalent using the current equivalence configuration. + /// + /// This is equivalent to calling [compute_equivalence] with `are_values_equivalent` set to + /// `ValueRef::ptr_eq`, and `on_value_equivalence` to a no-op. + #[inline] + pub fn are_equivalent(&self, lhs: &OperationRef, rhs: &OperationRef) -> bool { + #[inline(always)] + fn noop(_: &ValueRef, _: &ValueRef) {} + + self.compute_equivalence(lhs, rhs, ValueRef::ptr_eq, noop) + } + + /// Compare if two operations (and their regions) are equivalent using the current equivalence + /// configuration. + /// + /// * `are_values_equivalent` is a callback used to check if two values are equivalent. For + /// two operations to be equivalent, their operands must be the same SSA value, or this + /// callback must return `true`. + /// * `on_value_equivalence` is a callback to inform the caller that the analysis determined + /// that two values are equivalent. + /// + /// NOTE: Additional information regarding value equivalence can be injected into the analysis + /// via `are_values_equivalent`. Typically, callers may want values that were recorded as + /// equivalent via `on_value_equivalence` to be reflected in `are_values_equivalent`, but it + /// depends on the exact semantics desired by the caller. + pub fn compute_equivalence( + &self, + lhs: &OperationRef, + rhs: &OperationRef, + are_values_equivalent: VE, + on_value_equivalence: OVE, + ) -> bool + where + VE: Fn(&ValueRef, &ValueRef) -> bool, + OVE: FnMut(&ValueRef, &ValueRef), + { + todo!() + } + + /// Compare if two regions are equivalent using the current equivalence configuration. + /// + /// See [compute_equivalence] for more details. + pub fn compute_region_equivalence( + &self, + lhs: &RegionRef, + rhs: &RegionRef, + are_values_equivalent: VE, + on_value_equivalence: OVE, + ) -> bool + where + VE: Fn(&ValueRef, &ValueRef) -> bool, + OVE: FnMut(&ValueRef, &ValueRef), + { + todo!() + } + + /// Hashes an operation based on: + /// + /// * OperationName + /// * Attributes + /// * Result types + fn hash_operation(&self, op: &Operation, hasher: &mut impl core::hash::Hasher) { + use core::hash::Hash; + + use crate::Value; + + op.name().hash(hasher); + for attr in op.attributes().iter() { + attr.hash(hasher); + } + for result in op.results().iter() { + result.borrow().ty().hash(hasher); + } + } +} + +#[inline(always)] +pub fn ignore_value_equivalence(_lhs: &ValueRef, _rhs: &ValueRef) -> bool { + true +} + +struct DefaultValueHasher; +impl FnOnce<(&ValueRef, &mut dyn core::hash::Hasher)> for DefaultValueHasher { + type Output = (); + + extern "rust-call" fn call_once( + self, + args: (&ValueRef, &mut dyn core::hash::Hasher), + ) -> Self::Output { + use core::hash::Hash; + + let (value, hasher) = args; + value.dyn_hash(hasher); + } +} +impl FnMut<(&ValueRef, &mut dyn core::hash::Hasher)> for DefaultValueHasher { + extern "rust-call" fn call_mut( + &mut self, + args: (&ValueRef, &mut dyn core::hash::Hasher), + ) -> Self::Output { + use core::hash::Hash; + + let (value, hasher) = args; + value.dyn_hash(hasher); + } +} +impl Fn<(&ValueRef, &mut dyn core::hash::Hasher)> for DefaultValueHasher { + extern "rust-call" fn call( + &self, + args: (&ValueRef, &mut dyn core::hash::Hasher), + ) -> Self::Output { + use core::hash::Hash; + + let (value, hasher) = args; + value.dyn_hash(hasher); + } +} + +struct BlockEquivalenceData { + /// The block this data refers to + block: BlockRef, + /// The hash for this block + hash: u64, + /// A map of result producing operations to their relative orders within this block. The order + /// of an operation is the number of defined values that are produced within the block before + /// this operation. + op_order_index: SmallDenseMap, +} +impl BlockEquivalenceData { + pub fn new(block: BlockRef) -> Self { + use core::hash::Hasher; + + let mut op_order_index = SmallDenseMap::default(); + + let b = block.borrow(); + let mut order = b.num_arguments() as u32; + let mut op_equivalence = OpEquivalence::new() + .with_flags(EquivalenceFlags::IGNORE_LOCATIONS) + .ignore_operands() + .ignore_results(); + + let mut hasher = rustc_hash::FxHasher::default(); + for op in b.body() { + let num_results = op.num_results() as u32; + if num_results > 0 { + op_order_index.insert(op.as_operation_ref(), order); + order += num_results; + } + op_equivalence.hash_operation(&op, &mut hasher); + } + + Self { + block, + hash: hasher.finish(), + op_order_index, + } + } + + fn get_order_of(&self, value: &ValueRef) -> usize { + let value = value.borrow(); + assert!(value.parent_block().unwrap() == self.block, "expected value of this block"); + + if let Some(block_arg) = value.downcast_ref::() { + return block_arg.index(); + } + + let result = value.downcast_ref::().unwrap(); + let order = + *self.op_order_index.get(&result.owner()).expect("expected op to have an order"); + result.index() + (order as usize) + } +} + +impl Region { + // TODO(pauls) + pub(in crate::ir::region) fn merge_identical_blocks( + _regions: &[RegionRef], + _rewriter: &mut dyn Rewriter, + ) -> Result<(), RegionTransformFailed> { + Err(RegionTransformFailed) + } +} diff --git a/hir/src/ir/region/transforms/dce.rs b/hir/src/ir/region/transforms/dce.rs new file mode 100644 index 000000000..56d1a9817 --- /dev/null +++ b/hir/src/ir/region/transforms/dce.rs @@ -0,0 +1,401 @@ +use alloc::collections::VecDeque; + +use smallvec::SmallVec; + +use super::RegionTransformFailed; +use crate::{ + adt::SmallSet, + traits::{BranchOpInterface, Terminator}, + OpOperandImpl, OpResult, Operation, OperationRef, PostOrderBlockIter, Region, RegionRef, + Rewriter, SuccessorOperands, ValueRef, +}; + +/// Data structure used to track which values have already been proved live. +/// +/// Because operations can have multiple results, this data structure tracks liveness for both +/// values and operations to avoid having to look through all results when analyzing a use. +/// +/// This data structure essentially tracks the dataflow lattice. The set of values/ops proved live +/// increases monotonically to a fixed-point. +#[derive(Default)] +struct LiveMap { + values: SmallSet, + ops: SmallSet, + changed: bool, +} +impl LiveMap { + pub fn was_proven_live(&self, value: &ValueRef) -> bool { + // TODO(pauls): For results that are removable, e.g. for region based control flow, + // we could allow for these values to be tracked independently. + let val = value.borrow(); + if let Some(result) = val.downcast_ref::() { + self.ops.contains(&result.owner()) + } else { + self.values.contains(value) + } + } + + #[inline] + pub fn was_op_proven_live(&self, op: &OperationRef) -> bool { + self.ops.contains(op) + } + + pub fn set_proved_live(&mut self, value: ValueRef) { + // TODO(pauls): For results that are removable, e.g. for region based control flow, + // we could allow for these values to be tracked independently. + let val = value.borrow(); + if let Some(result) = val.downcast_ref::() { + self.changed |= self.ops.insert(result.owner()); + } else { + self.changed |= self.values.insert(value); + } + } + + pub fn set_op_proved_live(&mut self, op: OperationRef) { + self.changed |= self.ops.insert(op); + } + + #[inline(always)] + pub fn mark_unchanged(&mut self) { + self.changed = false; + } + + #[inline(always)] + pub const fn has_changed(&self) -> bool { + self.changed + } + + pub fn is_use_specially_known_dead(&self, user: &OpOperandImpl) -> bool { + // DCE generally treats all uses of an op as live if the op itself is considered live. + // However, for successor operands to terminators we need a finer-grained notion where we + // deduce liveness for operands individually. The reason for this is easiest to think about + // in terms of a classical phi node based SSA IR, where each successor operand is really an + // operand to a _separate_ phi node, rather than all operands to the branch itself as with + // the block argument representation that we use. + // + // And similarly, because each successor operand is really an operand to a phi node, rather + // than to the terminator op itself, a terminator op can't e.g. "print" the value of a + // successor operand. + let owner = &user.owner; + if owner.borrow().implements::() { + if let Some(branch_interface) = owner.borrow().as_trait::() { + if let Some(arg) = + branch_interface.get_successor_block_argument(user.index as usize) + { + return !self.was_proven_live(&arg.upcast()); + } + } + } + + false + } + + pub fn propagate_region_liveness(&mut self, region: &Region) { + if region.body().is_empty() { + return; + } + + for block in PostOrderBlockIter::new(region.body().front().as_pointer().unwrap()) { + // We process block arguments after the ops in the block, to promote faster convergence + // to a fixed point (we try to visit uses before defs). + let block = block.borrow(); + for op in block.body().iter().rev() { + self.propagate_liveness(&op); + } + + // We currently do not remove entry block arguments, so there is no need to track their + // liveness. + // + // TODO(pauls): We could track these and enable removing dead operands/arguments from + // region control flow operations in the future. + if block.is_entry_block() { + continue; + } + + for arg in block.arguments().iter().copied() { + let arg = arg as ValueRef; + if !self.was_proven_live(&arg) { + self.process_value(arg); + } + } + } + } + + pub fn propagate_liveness(&mut self, op: &Operation) { + // Recurse on any regions the op has + for region in op.regions() { + self.propagate_region_liveness(®ion); + } + + // We process terminator operations separately + if op.implements::() { + return self.propagate_terminator_liveness(op); + } + + // Don't reprocess live operations. + if self.was_op_proven_live(&op.as_operation_ref()) { + return; + } + + // Process this op + if !op.would_be_trivially_dead() { + self.set_op_proved_live(op.as_operation_ref()); + } + + // If the op isn't intrinsically alive, check it's results + for result in op.results().iter().copied() { + self.process_value(result as ValueRef); + } + } + + fn propagate_terminator_liveness(&mut self, op: &Operation) { + // Terminators are always live + self.set_op_proved_live(op.as_operation_ref()); + + // Check to see if we can reason about the successor operands + // + // If we can't reason about the operand to a successor, conservatively mark it as live + if let Some(branch_op) = op.as_trait::() { + let num_successors = branch_op.num_successors(); + for successor_idx in 0..num_successors { + let operands = branch_op.get_successor_operands(successor_idx); + let succ = op.successor(successor_idx).dest.borrow().successor(); + // Produced operands are always live if the terminator is live + for arg in succ.borrow().arguments().iter().copied().take(operands.num_produced()) { + self.set_proved_live(arg as ValueRef); + } + } + } else { + for successor in op.successors().iter() { + let successor = successor.block.borrow().successor(); + for arg in successor.borrow().arguments().iter().copied() { + self.set_proved_live(arg as ValueRef); + } + } + } + } + + fn process_value(&mut self, value: ValueRef) { + let proved_live = value.borrow().iter_uses().any(|user| { + if self.is_use_specially_known_dead(&user) { + return false; + } + self.was_op_proven_live(&user.owner) + }); + if proved_live { + self.set_proved_live(value); + } + } +} + +impl Region { + pub fn dead_code_elimination( + regions: &[RegionRef], + rewriter: &mut dyn Rewriter, + ) -> Result<(), RegionTransformFailed> { + log::debug!(target: "region-simplify", "starting region dead code elimination"); + let mut live_map = LiveMap::default(); + loop { + live_map.mark_unchanged(); + + log::trace!(target: "region-simplify", "propagating region liveness"); + + for region in regions { + live_map.propagate_region_liveness(®ion.borrow()); + } + + if !live_map.has_changed() { + log::trace!(target: "region-simplify", "liveness propagation has reached fixpoint"); + break; + } + } + + Self::cleanup_dead_code(regions, rewriter, &live_map) + } + + /// Erase the unreachable blocks within the regions in `regions`. + /// + /// Returns `Ok` if any blocks were erased, `Err` otherwise. + pub fn erase_unreachable_blocks( + regions: &[RegionRef], + rewriter: &mut dyn crate::Rewriter, + ) -> Result<(), RegionTransformFailed> { + let mut erased_dead_blocks = false; + let mut reachable = SmallSet::<_, 8>::default(); + let mut worklist = VecDeque::from_iter(regions.iter().cloned()); + while let Some(mut region) = worklist.pop_front() { + log::debug!(target: "region-simplify", "erasing unreachable blocks in region"); + let mut current_region = region.borrow_mut(); + let blocks = current_region.body_mut(); + if blocks.is_empty() { + log::debug!(target: "region-simplify", "skipping empty region"); + continue; + } + + // If this is a single block region, just collect nested regions. + let entry = blocks.front().as_pointer().unwrap(); + if entry.next().is_none() { + log::trace!(target: "region-simplify", "region is a single-block ({entry}) region: adding nested regions to worklist"); + for op in blocks.front().get().unwrap().body() { + worklist.extend(op.regions().iter().map(|r| r.as_region_ref())); + } + continue; + } + + // Mark all reachable blocks. + log::trace!(target: "region-simplify", "locating reachable blocks from {entry}"); + reachable.clear(); + let iter = PostOrderBlockIter::new(entry); + reachable.extend(iter); + + // Collect all of the dead blocks and push the live regions on the worklist + let mut cursor = entry.next(); + drop(current_region); + while let Some(mut block) = cursor.take() { + cursor = block.next(); + + if reachable.contains(&block) { + log::trace!(target: "region-simplify", "{block} is reachable - adding nested regions to worklist"); + // Walk any regions within this block + for op in block.borrow().body() { + worklist.extend(op.regions().iter().map(|r| r.as_region_ref())); + } + continue; + } + + // The block is unreachable, erase it + log::trace!(target: "region-simplify", "{block} is unreachable - erasing block"); + block.borrow_mut().drop_all_defined_value_uses(); + rewriter.erase_block(block); + erased_dead_blocks = true; + } + } + + if erased_dead_blocks { + Ok(()) + } else { + Err(RegionTransformFailed) + } + } + + fn cleanup_dead_code( + regions: &[RegionRef], + rewriter: &mut dyn Rewriter, + live_map: &LiveMap, + ) -> Result<(), RegionTransformFailed> { + log::debug!(target: "region-simplify", "cleaning up dead code"); + + let mut erased_anything = false; + for region in regions { + let current_region = region.borrow(); + if current_region.body().is_empty() { + log::trace!(target: "region-simplify", "skipping empty region"); + continue; + } + + let has_single_block = current_region.has_one_block(); + + // Delete every operation that is not live. Graph regions may have cycles in the use-def + // graph, so we must explicitly drop all uses from each operation as we erase it. + // Visiting the operations in post-order guarantees that in SSA CFG regions, value uses + // are removed before defs, which makes `drop_all_uses` a no-op. + let region_entry = current_region.entry_block_ref().unwrap(); + log::debug!(target: "region-simplify", "visiting reachable blocks from {region_entry}"); + let iter = PostOrderBlockIter::new(region_entry); + for block in iter { + log::trace!(target: "region-simplify", "visiting block {block}"); + if !has_single_block { + Self::erase_terminator_successor_operands( + block.borrow().terminator().expect("expected block to have terminator"), + live_map, + ); + } + log::trace!(target: "region-simplify", "visiting ops in {block} in post-order"); + let mut next_op = block.borrow().body().back().as_pointer(); + while let Some(mut child_op) = next_op.take() { + next_op = child_op.prev(); + if !live_map.was_op_proven_live(&child_op) { + log::trace!( + target: "region-simplify", "found '{}' that was not proven live - erasing", + child_op.name() + ); + erased_anything = true; + child_op.borrow_mut().drop_all_uses(); + rewriter.erase_op(child_op); + } else { + let child_op = child_op.borrow(); + if child_op.regions().is_empty() { + log::trace!(target: "region-simplify", "found '{}' that was proven live", child_op.name()); + continue; + } + let child_regions = child_op + .regions() + .iter() + .map(|r| r.as_region_ref()) + .collect::>(); + log::trace!( + target: "region-simplify", "found '{}' that was proven live - cleaning up {} child regions", + child_op.name(), + child_regions.len() + ); + erased_anything |= + Self::cleanup_dead_code(&child_regions, rewriter, live_map).is_ok(); + } + } + } + + // Delete block arguments. + // + // The entry block has an unknown contract with their enclosing block, so leave it alone. + drop(current_region); + let mut current_block = region_entry.next(); + while let Some(mut block) = current_block.take() { + log::debug!(target: "region-simplify", "deleting unused block arguments for {block}"); + current_block = block.next(); + block.borrow_mut().erase_arguments(|arg| { + let is_dead = !live_map.was_proven_live(&arg.as_value_ref()); + if is_dead { + log::trace!(target: "region-simplify", "{arg} was not proven live - erasing"); + } + is_dead + }); + } + } + + if erased_anything { + Ok(()) + } else { + Err(RegionTransformFailed) + } + } + + fn erase_terminator_successor_operands(mut terminator: OperationRef, live_map: &LiveMap) { + let mut op = terminator.borrow_mut(); + if !op.implements::() { + return; + } + + log::debug!( + target: "region-simplify", "erasing branch successor operands for {op} ({} successors)", + op.num_successors() + ); + + // Iterate successors in reverse to minimize the amount of operand shifting + for succ_index in (0..op.num_successors()).rev() { + let mut succ = op.successor_mut(succ_index); + let block = succ.dest.borrow().successor(); + // Iterate arguments in reverse so that erasing an argument does not shift the others + let num_arguments = succ.arguments.len(); + log::trace!(target: "region-simplify", "checking successor {block} for unused arguments"); + assert_eq!(num_arguments, block.borrow().num_arguments()); + for arg_index in (0..num_arguments).rev() { + let arg = block.borrow().get_argument(arg_index) as ValueRef; + let is_dead = !live_map.was_proven_live(&arg); + if is_dead { + log::trace!(target: "region-simplify", "{arg} was not proven live - erasing"); + succ.arguments.erase(arg_index); + } + } + } + } +} diff --git a/hir/src/ir/region/transforms/drop_redundant_args.rs b/hir/src/ir/region/transforms/drop_redundant_args.rs new file mode 100644 index 000000000..4c95a95dc --- /dev/null +++ b/hir/src/ir/region/transforms/drop_redundant_args.rs @@ -0,0 +1,150 @@ +use smallvec::SmallVec; + +use super::RegionTransformFailed; +use crate::{ + traits::BranchOpInterface, BlockArgumentRef, BlockRef, Region, RegionRef, Rewriter, + SuccessorOperands, Usable, +}; + +impl Region { + /// This optimization drops redundant argument to blocks. I.e., if a given argument to a block + /// receives the same value from each of the block predecessors, we can remove the argument from + /// the block and use directly the original value. + /// + /// ## Example + /// + /// A simple example: + /// + /// ```hir,ignore + /// %cond = llvm.call @rand() : () -> i1 + /// %val0 = llvm.mlir.constant(1 : i64) : i64 + /// %val1 = llvm.mlir.constant(2 : i64) : i64 + /// %val2 = llvm.mlir.constant(3 : i64) : i64 + /// llvm.cond_br %cond, ^bb1(%val0 : i64, %val1 : i64), ^bb2(%val0 : i64, %val2 + /// : i64) + /// + /// ^bb1(%arg0 : i64, %arg1 : i64): + /// llvm.call @foo(%arg0, %arg1) + /// ``` + /// + /// That can be rewritten as: + /// + /// ```hir,ignore + /// %cond = llvm.call @rand() : () -> i1 + /// %val0 = llvm.mlir.constant(1 : i64) : i64 + /// %val1 = llvm.mlir.constant(2 : i64) : i64 + /// %val2 = llvm.mlir.constant(3 : i64) : i64 + /// llvm.cond_br %cond, ^bb1(%val1 : i64), ^bb2(%val2 : i64) + /// + /// ^bb1(%arg0 : i64): + /// llvm.call @foo(%val0, %arg0) + /// ``` + pub(in crate::ir::region) fn drop_redundant_arguments( + regions: &[RegionRef], + rewriter: &mut dyn Rewriter, + ) -> Result<(), RegionTransformFailed> { + let mut worklist = SmallVec::<[RegionRef; 1]>::from_iter(regions.iter().cloned()); + + let mut any_changed = false; + while let Some(region) = worklist.pop() { + // Add any nested regions to the worklist + let region = region.borrow(); + let mut blocks = region.body().front(); + while let Some(block) = blocks.as_pointer() { + blocks.move_next(); + + any_changed |= Self::drop_redundant_block_arguments(block, rewriter).is_ok(); + + for op in block.borrow().body() { + let mut regions = op.regions().front(); + while let Some(region) = regions.as_pointer() { + worklist.push(region); + regions.move_next(); + } + } + } + } + + if any_changed { + Ok(()) + } else { + Err(RegionTransformFailed) + } + } + + /// If a block's argument is always the same across different invocations, then + /// drop the argument and use the value directly inside the block + fn drop_redundant_block_arguments( + mut block: BlockRef, + rewriter: &mut dyn Rewriter, + ) -> Result<(), RegionTransformFailed> { + let mut args_to_erase = SmallVec::<[usize; 4]>::default(); + + // Go through the arguments of the block. + let mut block_mut = block.borrow_mut(); + let block_args = + SmallVec::<[BlockArgumentRef; 2]>::from_iter(block_mut.arguments().iter().cloned()); + for (arg_index, block_arg) in block_args.into_iter().enumerate() { + let mut same_arg = true; + let mut common_value = None; + + // Go through the block predecessor and flag if they pass to the block different values + // for the same argument. + for pred in block_mut.predecessors() { + let pred_op = pred.owner.borrow(); + if let Some(branch_op) = pred_op.as_trait::() { + let succ_index = pred.index as usize; + let succ_operands = branch_op.get_successor_operands(succ_index); + let branch_operands = succ_operands.forwarded(); + let arg = branch_operands[arg_index].borrow().as_value_ref(); + if common_value.is_none() { + common_value = Some(arg); + continue; + } + if common_value.as_ref().is_some_and(|cv| cv != &arg) { + same_arg = false; + break; + } + } else { + same_arg = false; + break; + } + } + + // If they are passing the same value, drop the argument. + if let Some(common_value) = common_value { + if same_arg { + args_to_erase.push(arg_index); + + // Remove the argument from the block. + rewriter.replace_all_uses_of_value_with(block_arg, common_value); + } + } + } + + // Remove the arguments. + for arg_index in args_to_erase.iter().copied() { + block_mut.erase_argument(arg_index); + + // Remove the argument from the branch ops. + let mut preds = block_mut.uses_mut().front_mut(); + while let Some(mut pred) = preds.as_pointer() { + preds.move_next(); + + let mut pred = pred.borrow_mut(); + let mut pred_op = pred.owner.borrow_mut(); + if let Some(branch_op) = pred_op.as_trait_mut::() { + let succ_index = pred.index as usize; + let mut succ_operands = branch_op.get_successor_operands_mut(succ_index); + succ_operands.forwarded_mut().erase(arg_index); + } + } + } + + if !args_to_erase.is_empty() { + Ok(()) + } else { + Err(RegionTransformFailed) + } + } +} diff --git a/hir/src/ir/successor.rs b/hir/src/ir/successor.rs new file mode 100644 index 000000000..0d60bc82e --- /dev/null +++ b/hir/src/ir/successor.rs @@ -0,0 +1,565 @@ +use alloc::vec::Vec; +use core::fmt; + +use super::{OpOperandStorage, StorableEntity, Usable, ValueRange}; +use crate::{AttributeValue, BlockOperandRef, BlockRef, OpOperandRange, OpOperandRangeMut}; + +pub type OpSuccessorStorage = crate::EntityStorage; +pub type OpSuccessorRange<'a> = crate::EntityRange<'a, SuccessorInfo>; +pub type OpSuccessorRangeMut<'a> = crate::EntityRangeMut<'a, SuccessorInfo, 0>; + +/// This trait represents common behavior shared by any range of successor operands. +pub trait SuccessorOperands { + /// Returns true if there are no operands in this set + fn is_empty(&self) -> bool { + self.num_produced() == 0 && self.len() == 0 + } + /// Returns the total number of operands in this set + fn len(&self) -> usize; + /// Returns the number of internally produced operands in this set + fn num_produced(&self) -> usize; + /// Returns true if the operand at `index` is internally produced + #[inline] + fn is_operand_produced(&self, index: usize) -> bool { + index < self.num_produced() + } + /// Get the range of forwarded operands + fn forwarded(&self) -> OpOperandRange<'_>; + /// Get a [SuccessorOperand] representing the operand at `index` + /// + /// Returns `None` if the index is out of range. + fn get(&self, index: usize) -> Option { + if self.is_operand_produced(index) { + Some(SuccessorOperand::Produced) + } else { + self.forwarded() + .get(index) + .map(|op_operand| SuccessorOperand::Forwarded(op_operand.borrow().as_value_ref())) + } + } + + /// Get a [SuccessorOperand] representing the operand at `index`. + /// + /// Panics if the index is out of range. + fn get_unchecked(&self, index: usize) -> SuccessorOperand { + if self.is_operand_produced(index) { + SuccessorOperand::Produced + } else { + SuccessorOperand::Forwarded(self.forwarded()[index].borrow().as_value_ref()) + } + } + + /// Gets the index of the forwarded operand which maps to the given successor block argument + /// index. + /// + /// Panics if the given block argument index does not correspond to a forwarded operand. + fn get_operand_index(&self, block_argument_index: usize) -> usize { + assert!( + self.is_operand_produced(block_argument_index), + "cannot map operands produced by the operation" + ); + let base_index = self.forwarded()[0].borrow().index as usize; + base_index + (block_argument_index - self.num_produced()) + } +} + +/// This type models how operands are forwarded to block arguments in control flow. It consists of a +/// number, denoting how many of the successor block arguments are produced by the operation, +/// followed by a range of operands that are forwarded. The produced operands are passed to the +/// first few block arguments of the successor, followed by the forwarded operands. It is +/// unsupported to pass them in a different order. +/// +/// An example operation with both of these concepts would be a branch-on-error operation, that +/// internally produces an error object on the error path: +/// +/// ```hir,ignore +/// invoke %function(%0) +/// label ^success ^error(%1 : i32) +/// +/// ^error(%e: !error, %arg0 : i32): +/// ... +/// ``` +/// +/// This operation would return an instance of [SuccessorOperands] with a produced operand count of +/// 1 (mapped to `%e` in the successor) and forwarded operands consisting of `%1` in the example +/// above (mapped to `%arg0` in the successor). +pub struct SuccessorOperandRange<'a> { + /// The explicit op operands which are to be passed along to the successor + forwarded: OpOperandRange<'a>, + /// The number of operands that are produced internally within the operation and which are to + /// be passed to the successor before any forwarded operands. + num_produced: usize, +} +impl<'a> SuccessorOperandRange<'a> { + /// Create an empty successor operand set + pub fn empty() -> Self { + Self { + forwarded: OpOperandRange::empty(), + num_produced: 0, + } + } + + /// Create a successor operand set consisting solely of forwarded op operands + #[inline] + pub const fn forward(forwarded: OpOperandRange<'a>) -> Self { + Self { + forwarded, + num_produced: 0, + } + } + + /// Create a successor operand set consisting solely of `num_produced` internally produced + /// results + pub fn produced(num_produced: usize) -> Self { + Self { + forwarded: OpOperandRange::empty(), + num_produced, + } + } + + /// Create a new successor operand set with the given number of internally produced results, + /// and forwarded op operands. + #[inline] + pub const fn new(num_produced: usize, forwarded: OpOperandRange<'a>) -> Self { + Self { + forwarded, + num_produced, + } + } + + #[inline] + pub fn into_forwarded(self) -> OpOperandRange<'a> { + self.forwarded + } +} +impl SuccessorOperands for SuccessorOperandRange<'_> { + #[inline] + fn len(&self) -> usize { + self.num_produced + self.forwarded.len() + } + + #[inline(always)] + fn num_produced(&self) -> usize { + self.num_produced + } + + fn forwarded(&self) -> OpOperandRange<'_> { + self.forwarded.clone() + } +} + +/// The mutable variant of [SuccessorOperandsRange]. +pub struct SuccessorOperandRangeMut<'a> { + /// The explicit op operands which are to be passed along to the successor + forwarded: OpOperandRangeMut<'a>, + /// The number of operands that are produced internally within the operation and which are to + /// be passed to the successor before any forwarded operands. + num_produced: usize, +} +impl<'a> SuccessorOperandRangeMut<'a> { + /// Create a successor operand set consisting solely of forwarded op operands + #[inline] + pub const fn forward(forwarded: OpOperandRangeMut<'a>) -> Self { + Self { + forwarded, + num_produced: 0, + } + } + + /// Create a new successor operand set with the given number of internally produced results, + /// and forwarded op operands. + #[inline] + pub const fn new(num_produced: usize, forwarded: OpOperandRangeMut<'a>) -> Self { + Self { + forwarded, + num_produced, + } + } + + #[inline(always)] + pub fn forwarded_mut(&mut self) -> &mut OpOperandRangeMut<'a> { + &mut self.forwarded + } +} +impl SuccessorOperands for SuccessorOperandRangeMut<'_> { + #[inline] + fn len(&self) -> usize { + self.num_produced + self.forwarded.len() + } + + #[inline(always)] + fn num_produced(&self) -> usize { + self.num_produced + } + + #[inline(always)] + fn forwarded(&self) -> OpOperandRange<'_> { + self.forwarded.as_immutable() + } +} + +/// Represents an operand in a [SuccessorOperands] set. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SuccessorOperand { + /// This operand is internally produced by the operation, and passed to the successor before + /// any forwarded op operands. + Produced, + /// This operand is a forwarded operand of the operation. + Forwarded(crate::ValueRef), +} + +impl SuccessorOperand { + pub fn into_value_ref(self) -> Option { + match self { + Self::Produced => None, + Self::Forwarded(value) => Some(value), + } + } +} + +/// This trait represents successor-like values for operations, with support for control-flow +/// predicated on a "key", a sentinel value that must match in order for the successor block to be +/// taken. +/// +/// The ability to associate a successor with a user-defined key, is intended for modeling things +/// such as [crate::dialects::hir::Switch], which has one or more successors which are guarded by +/// an integer value that is matched against the input, or selector, value. Most importantly, doing +/// so in a way that keeps everything in sync as the IR is modified. +/// +/// When used as a successor argument to an operation, each successor gets its own operand group, +/// and if it has an associated key, keyed successors are stored in a special attribute which tracks +/// each key and its associated successor index. This allows requesting the successor details and +/// getting back the correct key, destination, and operands. +pub trait KeyedSuccessor { + /// The type of key this successor + type Key: AttributeValue + Clone + Eq; + /// The type of value which will represent a reference to this successor. + /// + /// You should use [OpSuccessor] if this successor is not keyed. + type Repr<'a>: 'a; + /// The type of value which will represent a mutable reference to this successor. + /// + /// You should use [OpSuccessorMut] if this successor is not keyed. + type ReprMut<'a>: 'a; + + /// The (optional) value of the key for this successor. + /// + /// Keys must be valid attribute values, as they will be encoded in the operation attributes. + /// + /// If `None` is returned, this successor is to be treated like a regular successor argument, + /// i.e. a destination block and associated operands. If a key is returned, the key must be + /// unique across the set of keyed successors. + fn key(&self) -> &Self::Key; + /// Convert this value into the raw parts comprising the successor information: + /// + /// * The (optional) key under which this successor is selected + /// * The destination block + /// * The destination operands + fn into_parts(self) -> (Self::Key, BlockRef, Vec); + fn into_repr( + key: Self::Key, + block: BlockOperandRef, + operands: OpOperandRange<'_>, + ) -> Self::Repr<'_>; + fn into_repr_mut( + key: Self::Key, + block: BlockOperandRef, + operands: OpOperandRangeMut<'_>, + ) -> Self::ReprMut<'_>; +} + +/// This struct tracks successor metadata needed by [crate::Operation] +#[derive(Copy, Clone)] +pub struct SuccessorInfo { + pub block: BlockOperandRef, + pub(crate) key: Option>, + pub(crate) operand_group: u8, +} + +impl SuccessorInfo { + pub fn successor(&self) -> BlockRef { + self.block.borrow().successor() + } + + pub fn successor_operand_group(&self) -> usize { + self.operand_group as usize + } + + pub fn successor_operands(&self) -> ValueRange<'static, 4> { + let owner = self.block.borrow().owner; + ValueRange::from(owner.borrow().operands().group(self.operand_group as usize)).into_owned() + } +} + +impl crate::StorableEntity for SuccessorInfo { + #[inline(always)] + fn index(&self) -> usize { + self.block.index() + } + + #[inline(always)] + unsafe fn set_index(&mut self, index: usize) { + self.block.set_index(index); + } + + #[inline(always)] + fn unlink(&mut self) { + if self.block.is_linked() { + self.block.unlink(); + } + } +} +impl fmt::Debug for SuccessorInfo { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SuccessorInfo") + .field("block", &self.block.borrow()) + .field("key", &self.key) + .field("operand_group", &self.operand_group) + .finish() + } +} + +/// An [OpSuccessor] is a BlockOperand + OpOperandRange for that block +pub struct OpSuccessor<'a> { + pub dest: BlockOperandRef, + pub arguments: OpOperandRange<'a>, +} + +impl OpSuccessor<'_> { + pub fn successor(&self) -> BlockRef { + self.dest.borrow().successor() + } +} + +impl fmt::Debug for OpSuccessor<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpSuccessor") + .field("block", &self.dest.borrow().block_id()) + .field_with("arguments", |f| { + let mut list = f.debug_list(); + for operand in self.arguments.iter() { + list.entry(&operand.borrow()); + } + list.finish() + }) + .finish() + } +} + +/// An [OpSuccessorMut] is a BlockOperand + OpOperandRangeMut for that block +pub struct OpSuccessorMut<'a> { + pub dest: BlockOperandRef, + pub arguments: OpOperandRangeMut<'a>, +} + +impl OpSuccessorMut<'_> { + pub fn successor(&self) -> BlockRef { + self.dest.borrow().successor() + } + + /// Rewrite the successor destination with `block`, updating the use list of each block. + /// + /// This is a no-op if the block has not changed. + pub fn set(&mut self, mut block: BlockRef) { + // Unlink from old destination + { + let mut dest = self.dest.borrow_mut(); + if BlockRef::ptr_eq(&block, &dest.successor()) { + return; + } + dest.unlink(); + } + + // Link to new destination + let mut block = block.borrow_mut(); + let uses = block.uses_mut(); + uses.push_back(self.dest); + + debug_assert!(self.dest.parent().is_some()); + } +} + +impl fmt::Debug for OpSuccessorMut<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OpSuccessorMut") + .field("block", &self.dest.borrow().block_id()) + .field_with("arguments", |f| { + let mut list = f.debug_list(); + for operand in self.arguments.iter() { + list.entry(&operand.borrow()); + } + list.finish() + }) + .finish() + } +} + +pub struct KeyedSuccessorRange<'a, T> { + range: OpSuccessorRange<'a>, + operands: &'a OpOperandStorage, + _marker: core::marker::PhantomData, +} +impl<'a, T> KeyedSuccessorRange<'a, T> { + pub fn new(range: OpSuccessorRange<'a>, operands: &'a OpOperandStorage) -> Self { + Self { + range, + operands, + _marker: core::marker::PhantomData, + } + } + + pub fn get(&self, index: usize) -> Option> { + self.range.get(index).map(|info| { + let operands = self.operands.group(info.operand_group as usize); + SuccessorWithKey { + info, + operands, + _marker: core::marker::PhantomData, + } + }) + } + + pub fn is_empty(&self) -> bool { + self.range.is_empty() + } + + pub fn len(&self) -> usize { + self.range.len() + } + + pub fn iter(&self) -> KeyedSuccessorRangeIter<'a, '_, T> { + KeyedSuccessorRangeIter { + range: self, + index: 0, + } + } +} + +pub struct KeyedSuccessorRangeMut<'a, T> { + range: OpSuccessorRangeMut<'a>, + operands: &'a mut OpOperandStorage, + _marker: core::marker::PhantomData, +} +impl<'a, T> KeyedSuccessorRangeMut<'a, T> { + pub fn new(range: OpSuccessorRangeMut<'a>, operands: &'a mut OpOperandStorage) -> Self { + Self { + range, + operands, + _marker: core::marker::PhantomData, + } + } + + pub fn get(&self, index: usize) -> Option> { + self.range.get(index).map(|info| { + let operands = self.operands.group(info.operand_group as usize); + SuccessorWithKey { + info, + operands, + _marker: core::marker::PhantomData, + } + }) + } + + pub fn get_mut(&mut self, index: usize) -> Option> { + self.range.get_mut(index).map(|info| { + let operands = self.operands.group_mut(info.operand_group as usize); + SuccessorWithKeyMut { + info, + operands, + _marker: core::marker::PhantomData, + } + }) + } + + pub fn remove(&mut self, index: usize) { + self.range.erase(index); + } +} + +pub struct KeyedSuccessorRangeIter<'a, 'b: 'a, T> { + range: &'b KeyedSuccessorRange<'a, T>, + index: usize, +} +impl<'a, 'b: 'a, T> Iterator for KeyedSuccessorRangeIter<'a, 'b, T> { + type Item = SuccessorWithKey<'b, T>; + + fn next(&mut self) -> Option { + if self.index >= self.range.range.len() { + return None; + } + + let idx = self.index; + self.index += 1; + self.range.get(idx) + } +} + +pub struct SuccessorWithKey<'a, T> { + info: &'a SuccessorInfo, + operands: OpOperandRange<'a>, + _marker: core::marker::PhantomData, +} +impl<'a, T: KeyedSuccessor> SuccessorWithKey<'a, T> { + #[inline] + pub fn info(&self) -> &SuccessorInfo { + self.info + } + + pub fn key(&self) -> Option<&::Key> { + self.info + .key + .map(|ptr| unsafe { &*(ptr.as_ptr() as *mut ::Key) }) + } + + pub fn block(&self) -> BlockRef { + self.info.block.borrow().successor() + } + + pub fn block_operand(&self) -> BlockOperandRef { + self.info.block + } + + pub fn operand_group(&self) -> usize { + self.info.operand_group as usize + } + + #[inline(always)] + pub fn arguments(&self) -> &OpOperandRange<'a> { + &self.operands + } +} + +pub struct SuccessorWithKeyMut<'a, T> { + info: &'a SuccessorInfo, + operands: OpOperandRangeMut<'a>, + _marker: core::marker::PhantomData, +} +impl<'a, T: KeyedSuccessor> SuccessorWithKeyMut<'a, T> { + pub fn key(&self) -> Option<&::Key> { + self.info + .key + .map(|ptr| unsafe { &*(ptr.as_ptr() as *mut ::Key) }) + } + + pub fn block(&self) -> BlockRef { + self.info.block.borrow().successor() + } + + pub fn block_operand(&self) -> BlockOperandRef { + self.info.block + } + + pub fn operand_group(&self) -> usize { + self.info.operand_group as usize + } + + #[inline(always)] + pub fn arguments(&self) -> &OpOperandRangeMut<'a> { + &self.operands + } + + #[inline(always)] + pub fn arguments_mut(&mut self) -> &mut OpOperandRangeMut<'a> { + &mut self.operands + } +} diff --git a/hir/src/ir/symbols.rs b/hir/src/ir/symbols.rs new file mode 100644 index 000000000..db68699fa --- /dev/null +++ b/hir/src/ir/symbols.rs @@ -0,0 +1,327 @@ +mod name; +mod path; +mod symbol; +mod symbol_use; +mod table; + +use alloc::{collections::VecDeque, format, vec}; + +use midenc_session::diagnostics::{miette, Diagnostic}; +use smallvec::SmallVec; + +pub use self::{ + name::*, + path::*, + symbol::{Symbol, SymbolRef}, + symbol_use::*, + table::*, +}; +use super::{Region, RegionRef, WalkResult}; +use crate::{Operation, OperationRef, UnsafeIntrusiveEntityRef}; + +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum InvalidSymbolRefError { + #[error("invalid symbol reference: no symbol table available")] + NoSymbolTable { + #[label("cannot resolve this symbol")] + symbol: crate::SourceSpan, + #[label( + "because this operation has no parent symbol table with which to resolve the reference" + )] + user: crate::SourceSpan, + }, + #[error("invalid symbol reference: undefined symbol")] + UnknownSymbol { + #[label("failed to resolve this symbol")] + symbol: crate::SourceSpan, + #[label("in the nearest symbol table from this operation")] + user: crate::SourceSpan, + }, + #[error("invalid symbol reference: undefined component '{component}' of symbol")] + UnknownSymbolComponent { + #[label("failed to resolve this symbol")] + symbol: crate::SourceSpan, + #[label("from the root symbol table of this operation")] + user: crate::SourceSpan, + component: &'static str, + }, + #[error("invalid symbol reference: expected callable")] + NotCallable { + #[label("expected this symbol to implement the CallableOpInterface")] + symbol: crate::SourceSpan, + }, + #[error("invalid symbol reference: symbol is not the correct type")] + InvalidType { + #[label( + "expected this symbol to be a '{expected}', but symbol referenced a '{got}' operation" + )] + symbol: crate::SourceSpan, + expected: &'static str, + got: crate::OperationName, + }, +} + +/// A trait which allows multiple types to be coerced into a [SymbolRef]. +/// +/// This is primarily intended for use in operation builders. +pub trait AsSymbolRef { + fn as_symbol_ref(&self) -> SymbolRef; +} +impl AsSymbolRef for &T { + #[inline] + fn as_symbol_ref(&self) -> SymbolRef { + unsafe { SymbolRef::from_raw(*self as &dyn Symbol) } + } +} +impl AsSymbolRef for UnsafeIntrusiveEntityRef { + #[inline] + fn as_symbol_ref(&self) -> SymbolRef { + let t_ptr = Self::as_ptr(self); + unsafe { SymbolRef::from_raw(t_ptr as *const dyn Symbol) } + } +} +impl AsSymbolRef for SymbolRef { + #[inline(always)] + fn as_symbol_ref(&self) -> SymbolRef { + Self::clone(self) + } +} + +impl Operation { + /// Returns true if this operation implements [Symbol] + #[inline] + pub fn is_symbol(&self) -> bool { + self.implements::() + } + + /// Returns the symbol name of this operation, if it implements [Symbol] + pub fn symbol_name_if_symbol(&self) -> Option { + self.as_symbol().map(|symbol| symbol.name()) + } + + /// Get this operation as a [Symbol], if this operation implements the trait. + #[inline] + pub fn as_symbol(&self) -> Option<&dyn Symbol> { + self.as_trait::() + } + + /// Get this operation as a [SymbolRef], if this operation implements the trait. + #[inline] + pub fn as_symbol_ref(&self) -> Option { + self.as_trait::() + .map(|symbol| unsafe { SymbolRef::from_raw(symbol) }) + } + + /// Get this operation as a [SymbolTable], if this operation implements the trait. + #[inline] + pub fn as_symbol_table(&self) -> Option<&dyn SymbolTable> { + self.as_trait::() + } + + /// Return the root symbol table in which this symbol is contained, if one exists. + /// + /// The root symbol table is always the top-level ancestor (i.e. has no parent). In general + /// when we refer to the root symbol table, we are referring to an anonymous symbol table that + /// represents the global namespace in which all symbols are rooted. However, it may be the + /// case that the top-level ancestor is actually a symbol, in which case it is presumed that + /// it is a symbol in the global namespace, and that only symbols nested within it are + /// resolvable. + /// + /// Callers are expected to know this difference. + pub fn root_symbol_table(&self) -> Option { + let mut parent = Some(self.as_operation_ref()); + while let Some(ancestor) = parent.take() { + let ancestor_op = ancestor.borrow(); + let next = ancestor_op.parent_op(); + if next.is_none() { + parent = if ancestor_op.implements::() { + Some(ancestor) + } else { + None + }; + break; + } else { + parent = next; + } + } + parent + } + + /// Returns the nearest [SymbolTable] from this operation. + /// + /// Returns `None` if no parent of this operation is a valid symbol table. + pub fn nearest_symbol_table(&self) -> Option { + self.as_operation_ref().nearest_symbol_table() + } + + /// Returns the operation registered with the given symbol name within the closest symbol table + /// including `self`. + /// + /// Returns `None` if the symbol is not found. + pub fn nearest_symbol(&self, symbol: SymbolName) -> Option { + if let Some(sym) = self.as_symbol() { + if sym.name() == symbol { + return Some(unsafe { UnsafeIntrusiveEntityRef::from_raw(sym) }); + } + } + let symbol_table_op = self.nearest_symbol_table()?; + let op = symbol_table_op.borrow(); + let symbol_table = op.as_trait::().unwrap(); + symbol_table.get(symbol) + } + + /// Walks all symbol table operations nested within this operation, including itself. + /// + /// For each symbol table operation, the provided callback is invoked with the op and a boolean + /// signifying if the symbols within that symbol table can be treated as if all uses within the + /// IR are visible to the caller. + pub fn walk_symbol_tables(&self, all_symbol_uses_visible: bool, mut callback: F) + where + F: FnMut(&dyn SymbolTable, bool), + { + self.prewalk_all(|op: &Operation| { + if let Some(sym) = op.as_symbol_table() { + callback(sym, all_symbol_uses_visible); + } + }); + } + + /// Walk all of the operations nested under, and including this operation, without traversing + /// into any nested symbol tables (including this operation, if it is a symbol table). + /// + /// Stops walking if the result of the callback is anything other than `WalkResult::Continue`. + pub fn walk_symbol_table(&self, mut callback: F) -> WalkResult + where + F: FnMut(&Operation) -> WalkResult, + { + callback(self)?; + if self.implements::() { + return WalkResult::Continue(()); + } + + for region in self.regions() { + Self::walk_symbol_table_region(®ion, &mut callback)?; + } + + WalkResult::Continue(()) + } + + /// Walk all of the operations within the given set of regions, without traversing into any + /// nested symbol tables. If `WalkResult::Skip` is returned for an op, none of that op's regions + /// will be visited. + pub fn walk_symbol_table_region(region: &Region, mut callback: F) -> WalkResult + where + F: FnMut(&Operation) -> WalkResult, + { + let mut regions = SmallVec::<[RegionRef; 4]>::from_iter([region.as_region_ref()]); + while let Some(region) = regions.pop() { + let region = region.borrow(); + for block in region.body() { + for op in block.body() { + match callback(&op) { + WalkResult::Continue(_) => { + // If this op defines a new symbol table scope, we can't traverse. Any symbol + // references nested within this op are different semantically. + if !op.implements::() { + regions.extend(op.regions().iter().map(|r| r.as_region_ref())); + } + } + err @ WalkResult::Break(_) => return err, + WalkResult::Skip => (), + } + } + } + } + + WalkResult::Continue(()) + } + + /// Walk all of the uses, for any symbol, that are nested within this operation, invoking the + /// provided callback for each use. + /// + /// This does not traverse into any nested symbol tables. + pub fn walk_symbol_uses(&self, mut callback: F) -> WalkResult + where + F: FnMut(SymbolUseRef) -> WalkResult, + { + // Walk the uses on this operation. + Self::walk_symbol_refs(self, &mut callback)?; + + // Only recurse if this operation is not a symbol table. A symbol table defines a new scope, + // so we can't walk the attributes from within the symbol table op. + if !self.implements::() { + for region in self.regions() { + Self::walk_symbol_table_region(®ion, |op| { + Self::walk_symbol_refs(op, &mut callback) + })?; + } + } + + WalkResult::Continue(()) + } + + /// Walk all of the uses, for any symbol, that are nested within the given region, invoking the + /// provided callback for each use. + /// + /// This does not traverse into any nested symbol tables. + pub fn walk_symbol_uses_in_region(from: &Region, mut callback: F) -> WalkResult + where + F: FnMut(SymbolUseRef) -> WalkResult, + { + Self::walk_symbol_table_region(from, |op| Self::walk_symbol_refs(op, &mut callback)) + } + + /// Get an iterator over all of the uses, for any symbol, that are nested within the current + /// operation. + /// + /// This does not traverse into any nested symbol tables, and will also only return uses on + /// the current operation if it does not also define a symbol table. This is because we treat + /// the region as the boundary of the symbol table, and not the op itself. + pub fn all_symbol_uses(&self) -> SymbolUseRefsIter { + let mut uses = VecDeque::new(); + if self.implements::() { + return SymbolUseRefsIter::from(uses); + } + let _ = Self::walk_symbol_refs(self, |symbol_use| { + uses.push_back(symbol_use); + WalkResult::Continue(()) + }); + for region in self.regions() { + let _ = Self::walk_symbol_uses_in_region(®ion, |symbol_use| { + uses.push_back(symbol_use); + WalkResult::Continue(()) + }); + } + SymbolUseRefsIter::from(uses) + } + + /// Get an iterator over all of the uses, for any symbol, that are nested within the given + /// region 'from'. + /// + /// This does not traverse into any nested symbol tables. + pub fn all_symbol_uses_in_region(from: &Region) -> SymbolUseRefsIter { + let mut uses = VecDeque::new(); + let _ = Self::walk_symbol_uses_in_region(from, |symbol_use| { + uses.push_back(symbol_use); + WalkResult::Continue(()) + }); + SymbolUseRefsIter::from(uses) + } + + /// Walk all of the symbol references within the given operation, invoking the provided callback + /// for each found use. + /// + /// The callbacks takes the symbol use. + pub fn walk_symbol_refs(op: &Operation, mut callback: F) -> WalkResult + where + F: FnMut(SymbolUseRef) -> WalkResult, + { + for attr in op.attrs.iter() { + if let Some(attr) = attr.value_as::() { + callback(attr.user)?; + } + } + + WalkResult::Continue(()) + } +} diff --git a/hir/src/ir/symbols/name.rs b/hir/src/ir/symbols/name.rs new file mode 100644 index 000000000..ffb078a0d --- /dev/null +++ b/hir/src/ir/symbols/name.rs @@ -0,0 +1,33 @@ +/// Represents the name of a [Symbol] in its local [SymbolTable] +pub type SymbolName = crate::interner::Symbol; + +/// Generate a unique symbol name. +/// +/// Iteratively increase `counter` and use it as a suffix for symbol names until `is_unique` does +/// not detect any conflict. +pub fn generate_symbol_name(name: SymbolName, counter: &mut usize, is_unique: F) -> SymbolName +where + F: Fn(&str) -> bool, +{ + use core::fmt::Write; + + use crate::SmallStr; + + if is_unique(name.as_str()) { + return name; + } + + let base_len = name.as_str().len(); + let mut buf = SmallStr::with_capacity(base_len + 2); + buf.push_str(name.as_str()); + loop { + *counter += 1; + buf.truncate(base_len); + buf.push('_'); + write!(&mut buf, "{counter}").unwrap(); + + if is_unique(buf.as_str()) { + break SymbolName::intern(buf); + } + } +} diff --git a/hir/src/ir/symbols/path.rs b/hir/src/ir/symbols/path.rs new file mode 100644 index 000000000..79293b9a1 --- /dev/null +++ b/hir/src/ir/symbols/path.rs @@ -0,0 +1,788 @@ +use alloc::{borrow::Cow, collections::VecDeque, format}; +use core::fmt; + +use midenc_session::diagnostics::{miette, Diagnostic}; +use smallvec::{smallvec, SmallVec}; + +use super::SymbolUseRef; +use crate::{define_attr_type, interner, FunctionIdent, SymbolName}; + +#[derive(Debug, thiserror::Error, Diagnostic)] +pub enum InvalidSymbolPathError { + #[error("invalid symbol path: cannot be empty")] + Empty, + #[error("invalid symbol path: invalid format")] + #[diagnostic(help( + "The grammar for symbols is `:[/]*[@]" + ))] + InvalidFormat, + #[error("invalid symbol path: missing package")] + #[diagnostic(help( + "A fully-qualified symbol must namespace packages, i.e. `:`, but \ + you've only provided one of these" + ))] + MissingPackage, + #[error("invalid symbol path: only fully-qualified symbols can be versioned")] + UnexpectedVersion, + #[error("invalid symbol path: unexpected character '{token}' at byte {pos}")] + UnexpectedToken { token: char, pos: usize }, + #[error("invalid symbol path: no leaf component was provided")] + MissingLeaf, + #[error("invalid symbol path: unexpected components found after leaf")] + UnexpectedTrailingComponents, + #[error("invalid symbol path: only one root component is allowed, and it must come first")] + UnexpectedRootPlacement, +} + +#[derive(Clone, PartialEq, Eq)] +pub struct SymbolPathAttr { + pub path: SymbolPath, + pub user: SymbolUseRef, +} + +impl fmt::Display for SymbolPathAttr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.path) + } +} + +impl crate::formatter::PrettyPrint for SymbolPathAttr { + fn render(&self) -> crate::formatter::Document { + crate::formatter::display(self) + } +} + +impl fmt::Debug for SymbolPathAttr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SymbolPathAttr") + .field("path", &self.path) + .field("user", &self.user.borrow()) + .finish() + } +} + +impl core::hash::Hash for SymbolPathAttr { + fn hash(&self, state: &mut H) { + self.path.hash(state); + self.user.hash(state); + } +} + +define_attr_type!(SymbolPathAttr); + +/// This type is a custom [Attribute] for [Symbol] references. +/// +/// A [SymbolPath] is represented much like a filesystem path, i.e. as a vector of components. +/// Each component refers to a distinct `Symbol` that must be resolvable, the details of which +/// depends on what style of path is used. +/// +/// Similar to filesystem paths, there are two types of paths supported: +/// +/// * Unrooted (i.e. relative) paths. These are resolved from the nearest parent `SymbolTable`, +/// and must terminate with `SymbolNameComponent::Leaf`. +/// * Absolute paths. The resolution rules for these depends on what the top-level operation is +/// as reachable from the containing operation, described in more detail below. These paths +/// must begin with `SymbolNameComponent::Root`. +/// +/// NOTE: There is no equivalent of the `.` or `..` nodes in a filesystem path in symbol paths, +/// at least at the moment. Thus there is no way to refer to symbols some arbitrary number of +/// parents above the current `SymbolTable`, they must be resolved to absolute paths by the +/// frontend for now. +/// +/// # Symbol Resolution +/// +/// Relative paths, as mentioned above, are resolved from the nearest parent `SymbolTable`; if +/// no `SymbolTable` is present, an error will be raised. +/// +/// Absolute paths are relatively simple, but supports two use cases, based on the _top-level_ +/// operation reachable from the current operation, i.e. the operation at the top of the +/// ancestor tree which has no parent: +/// +/// 1. If the top-level operation is an anonymous `SymbolTable` (i.e. it is not also a `Symbol`), +/// then that `SymbolTable` corresponds to the global (root) namespace, and symbols are +/// resolved recursively from there. +/// 2. If the top-level operation is a named `SymbolTable` (i.e. it is also a `Symbol`), then it +/// is presumed that the top-level operation is defined in the global (root) namespace, even +/// though we are unable to reach the global namespace directly. Thus, the symbol we're +/// trying to resolve _must_ be a descendant of the top-level operation. This implies that +/// the symbol path of the top-level operation must be a prefix of `path`. +/// +/// We support the second style to allow for working with more localized chunks of IR, when no +/// symbol references escape the top-level `SymbolTable`. This is mostly useful in testing +/// scenarios. +/// +/// Symbol resolution of absolute paths will fail if: +/// +/// * The top-level operation is not a `SymbolTable` +/// * The top-level operation is a `Symbol` whose path is not a prefix of `path` +/// * We are unable to resolve any component of the path, starting from the top-level +/// * Any intermediate symbol in the path refers to a `Symbol` which is not also a `SymbolTable` +#[derive(Clone)] +pub struct SymbolPath { + /// The underlying components of the symbol name (alternatively called the symbol path). + pub path: SmallVec<[SymbolNameComponent; 3]>, +} + +impl FromIterator for SymbolPath { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + Self { + path: SmallVec::from_iter(iter), + } + } +} + +impl SymbolPath { + pub fn new(components: I) -> Result + where + I: IntoIterator, + { + let mut path = SmallVec::default(); + + let mut components = components.into_iter(); + + match components.next() { + None => return Err(InvalidSymbolPathError::Empty), + Some(component @ (SymbolNameComponent::Root | SymbolNameComponent::Component(_))) => { + path.push(component); + } + Some(component @ SymbolNameComponent::Leaf(_)) => { + if components.next().is_some() { + return Err(InvalidSymbolPathError::UnexpectedTrailingComponents); + } + path.push(component); + return Ok(Self { path }); + } + }; + + while let Some(component) = components.next() { + match component { + SymbolNameComponent::Root => { + return Err(InvalidSymbolPathError::UnexpectedRootPlacement); + } + component @ SymbolNameComponent::Component(_) => { + path.push(component); + } + component @ SymbolNameComponent::Leaf(_) => { + path.push(component); + if components.next().is_some() { + return Err(InvalidSymbolPathError::UnexpectedTrailingComponents); + } + } + } + } + + Ok(Self { path }) + } + + /// Converts a [FunctionIdent] representing a fully-qualified Miden Assembly procedure path, + /// to it's equivalent [SymbolPath] representation. + /// + /// # Example + /// + /// ```rust + /// use midenc_hir::{SymbolPath, SymbolNameComponent, FunctionIdent}; + /// + /// let id = FunctionIdent { + /// module: "intrinsics::mem".into(), + /// function: "load_felt_unchecked".into(), + /// }; + /// assert_eq!( + /// SymbolPath::from_masm_function_id(id), + /// SymbolPath::from_iter([ + /// SymbolNameComponent::Root, + /// SymbolNameComponent::Component("intrinsics".into()), + /// SymbolNameComponent::Component("mem".into()), + /// SymbolNameComponent::Leaf("load_felt_unchecked".into()), + /// ]) + /// ); + /// ``` + pub fn from_masm_function_id(id: FunctionIdent) -> Self { + let mut path = Self::from_masm_module_id(id.module.as_str()); + path.path.push(SymbolNameComponent::Leaf(id.function.as_symbol())); + path + } + + /// Converts a [str] representing a fully-qualified Miden Assembly module path, to it's + /// equivalent [SymbolPath] representation. + /// + /// # Example + /// + /// ```rust + /// use midenc_hir::{SymbolPath, SymbolNameComponent}; + /// + /// assert_eq!( + /// SymbolPath::from_masm_module_id("intrinsics::mem"), + /// SymbolPath::from_iter([ + /// SymbolNameComponent::Root, + /// SymbolNameComponent::Component("intrinsics".into()), + /// SymbolNameComponent::Component("mem".into()), + /// ]) + /// ); + /// ``` + pub fn from_masm_module_id(id: &str) -> Self { + let parts = id.split("::"); + Self::from_iter( + core::iter::once(SymbolNameComponent::Root) + .chain(parts.map(SymbolName::intern).map(SymbolNameComponent::Component)), + ) + } + + /// Returns the leaf component of the symbol path + pub fn name(&self) -> SymbolName { + match self.path.last().expect("expected non-empty symbol path") { + SymbolNameComponent::Leaf(name) => *name, + component => panic!("invalid symbol path: expected leaf node, got: {component:?}"), + } + } + + /// Set the value of the leaf component of the path, or append it if not yet present + pub fn set_name(&mut self, name: SymbolName) { + match self.path.last_mut() { + Some(SymbolNameComponent::Leaf(ref mut prev_name)) => { + *prev_name = name; + } + _ => { + self.path.push(SymbolNameComponent::Leaf(name)); + } + } + } + + /// Returns the first non-root component of the symbol path, if the path is absolute + pub fn namespace(&self) -> Option { + if self.is_absolute() { + match self.path[1] { + SymbolNameComponent::Component(ns) => Some(ns), + SymbolNameComponent::Leaf(_) => None, + SymbolNameComponent::Root => unreachable!( + "malformed symbol path: root components may only occur at the start of a path" + ), + } + } else { + None + } + } + + /// Derive a Miden Assembly `LibraryPath` from this symbol path + pub fn to_library_path(&self) -> midenc_session::LibraryPath { + use midenc_session::{ + miden_assembly::{ast::Ident, SourceSpan, Span}, + LibraryNamespace, LibraryPath, + }; + + let mut components = self.path.iter(); + let mut parts = SmallVec::<[_; 3]>::default(); + if self.is_absolute() { + let _ = components.next(); + } + let ns = match components.next() { + None => { + return LibraryPath::new_from_components(LibraryNamespace::Anon, parts); + } + Some(component) => LibraryNamespace::from_ident_unchecked(Ident::from_raw_parts( + Span::new(SourceSpan::default(), component.as_symbol_name().as_str().into()), + )), + }; + + for component in components { + let id = Ident::from_raw_parts(Span::new( + SourceSpan::default(), + component.as_symbol_name().as_str().into(), + )); + parts.push(id); + } + + LibraryPath::new_from_components(ns, parts) + } + + /// Returns true if this symbol name is fully-qualified + pub fn is_absolute(&self) -> bool { + matches!(&self.path[0], SymbolNameComponent::Root) + } + + /// Returns true if this symbol name is nested + pub fn has_parent(&self) -> bool { + if self.is_absolute() { + self.path.len() > 2 + } else { + self.path.len() > 1 + } + } + + /// Returns true if `self` is a prefix of `other`, i.e. `other` is a further qualified symbol + /// reference. + /// + /// NOTE: If `self` and `other` are equal, `self` is considered a prefix. The caller should + /// check if the two references are identical if they wish to distinguish the two cases. + pub fn is_prefix_of(&self, other: &Self) -> bool { + other.is_prefixed_by(&self.path) + } + + /// Returns true if `prefix` is a prefix of `self`, i.e. `self` is a further qualified symbol + /// reference. + /// + /// NOTE: If `self` and `prefix` are equal, `prefix` is considered a valid prefix. The caller + /// should check if the two references are identical if they wish to distinguish the two cases. + pub fn is_prefixed_by(&self, prefix: &[SymbolNameComponent]) -> bool { + let mut a = prefix.iter(); + let mut b = self.path.iter(); + + let mut index = 0; + loop { + match (a.next(), b.next()) { + (Some(part_a), Some(part_b)) if part_a == part_b => { + index += 1; + } + (None, Some(_)) => break index > 0, + _ => break false, + } + } + } + + /// Returns an iterator over the path components of this symbol name + pub fn components(&self) -> impl ExactSizeIterator + '_ { + self.path.iter().copied() + } + + /// Get the parent of this path, i.e. all but the last component + pub fn parent(&self) -> Option { + match self.path.split_last()? { + (SymbolNameComponent::Root, []) => None, + (_, rest) => Some(SymbolPath { + path: SmallVec::from_slice(rest), + }), + } + } + + /// Get the portion of this path without the `Leaf` component, if present. + pub fn without_leaf(&self) -> Cow<'_, SymbolPath> { + match self.path.split_last() { + Some((SymbolNameComponent::Leaf(_), rest)) => Cow::Owned(SymbolPath { + path: SmallVec::from_slice(rest), + }), + _ => Cow::Borrowed(self), + } + } +} + +/// Print symbol path according to Wasm Component Model rules, i.e.: +/// +/// ```text,ignore +/// PATH ::= NAMESPACE ":" PACKAGE PACKAGE_PATH? VERSION? +/// +/// NAMESPACE ::= SYMBOL +/// PACKAGE ::= SYMBOL (":" SYMBOL)* +/// PACKAGE_PATH ::= ("/" SYMBOL)+ +/// VERSION ::= "@" VERSION_STRING +/// ``` +/// +/// The first component of an absolute path (ignoring the `Root` node) is expected to be the package +/// name, i.e. the `NAMESPACE ":" PACKAGE` part as a single symbol. +/// +/// The first component of a relative path is expected to be either `Component` or `Leaf` +impl fmt::Display for SymbolPath { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use core::fmt::Write; + + let mut components = self.path.iter(); + + if self.is_absolute() { + let _ = components.next(); + } + + match components.next() { + Some(component) => f.write_str(component.as_symbol_name().as_str())?, + None => return Ok(()), + } + for component in components { + f.write_char('/')?; + f.write_str(component.as_symbol_name().as_str())?; + } + Ok(()) + } +} + +impl fmt::Debug for SymbolPath { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("SymbolPath") + .field_with("path", |f| f.debug_list().entries(self.path.iter()).finish()) + .finish() + } +} +impl crate::formatter::PrettyPrint for SymbolPath { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + display(self) + } +} +impl Eq for SymbolPath {} +impl PartialEq for SymbolPath { + fn eq(&self, other: &Self) -> bool { + self.path == other.path + } +} +impl PartialOrd for SymbolPath { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for SymbolPath { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.path.cmp(&other.path) + } +} +impl core::hash::Hash for SymbolPath { + fn hash(&self, state: &mut H) { + self.path.hash(state); + } +} + +/// A component of a namespaced [SymbolName]. +/// +/// A component refers to one of the following: +/// +/// * The root/global namespace anchor, i.e. indicates that other components are to be resolved +/// relative to the root (possibly anonymous) symbol table. +/// * The name of a symbol table nested within another symbol table or root namespace +/// * The name of a symbol (which must always be the leaf component of a path) +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub enum SymbolNameComponent { + /// A component that signals the path is relative to the root symbol table + Root, + /// A component of the symbol name path + Component(SymbolName), + /// The name of the symbol in its local symbol table + Leaf(SymbolName), +} + +impl SymbolNameComponent { + pub fn as_symbol_name(&self) -> SymbolName { + match self { + Self::Root => interner::symbols::Empty, + Self::Component(name) | Self::Leaf(name) => *name, + } + } + + #[inline] + pub fn is_root(&self) -> bool { + matches!(self, Self::Root) + } + + #[inline] + pub fn is_leaf(&self) -> bool { + matches!(self, Self::Leaf(_)) + } +} + +impl fmt::Debug for SymbolNameComponent { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Root => f.write_str("Root"), + Self::Component(name) => { + f.debug_tuple("Component").field_with(|f| f.write_str(name.as_str())).finish() + } + Self::Leaf(name) => { + f.debug_tuple("Leaf").field_with(|f| f.write_str(name.as_str())).finish() + } + } + } +} + +impl Ord for SymbolNameComponent { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + use core::cmp::Ordering; + + if self == other { + return Ordering::Equal; + } + + match (self, other) { + (Self::Root, _) => Ordering::Less, + (_, Self::Root) => Ordering::Greater, + (Self::Component(x), Self::Component(y)) => x.cmp(y), + (Self::Component(_), _) => Ordering::Less, + (_, Self::Component(_)) => Ordering::Greater, + (Self::Leaf(x), Self::Leaf(y)) => x.cmp(y), + } + } +} + +impl PartialOrd for SymbolNameComponent { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +/// An iterator over [SymbolNameComponent] derived from a path symbol and leaf symbol. +pub struct SymbolNameComponents { + parts: VecDeque<&'static str>, + name: SymbolName, + absolute: bool, + done: bool, +} + +impl SymbolNameComponents { + /// Construct a new [SymbolNameComponents] iterator from a Wasm Component Model symbol. + /// + /// The syntax for such symbols are described by the following EBNF-style grammar: + /// + /// ```text,ignore + /// SYMBOL ::= ID + /// QUALIFIED_SYMBOL ::= NAMESPACE ("/" ID)* ("@" VERSION)? + /// NAMESPACE ::= (ID ":")+ ID + /// ID ::= ID_CHAR+ + /// ID_CHAR ::= 'A'..'Z' + /// | 'a'..'z' + /// | '0'..'z' + /// | '-' + /// ```text,ignore + /// + /// This corresponds to identifiers of the form: + /// + /// * `foo` (referencing `foo` in the current scope) + /// * `miden:base/foo` (importing `foo` from the `miden:base` package) + /// * `miden:base/foo/bar` (importing `bar` from the `foo` interface of `miden:base`) + /// * `miden:base/foo/bar@1.0.0` (same as above, but specifying an exact package version) + /// + /// The following are not permitted: + /// + /// * `foo@1.0.0` (cannot reference a different version of the current package) + /// * `miden/foo` (packages must be namespaced, i.e. `:`) + pub fn from_component_model_symbol(symbol: SymbolName) -> Result { + use core::{iter::Peekable, str::CharIndices}; + + let mut parts = VecDeque::default(); + if symbol == interner::symbols::Empty { + let done = symbol == interner::symbols::Empty; + return Ok(Self { + parts, + name: symbol, + done, + absolute: false, + }); + } + + #[inline(always)] + fn is_valid_id_char(c: char) -> bool { + c.is_ascii_alphanumeric() || c == '-' + } + + fn lex_id<'a>( + s: &'a str, + start: usize, + lexer: &mut Peekable>, + ) -> Option<(usize, &'a str)> { + let mut end = start; + while let Some((i, c)) = lexer.next_if(|(_, c)| is_valid_id_char(*c)) { + end = i + c.len_utf8(); + } + if end == start { + return None; + } + Some((end, unsafe { core::str::from_utf8_unchecked(&s.as_bytes()[start..end]) })) + } + + let input = symbol.as_str(); + let mut chars = input.char_indices().peekable(); + let mut pos = 0; + + // Parse the package name + let mut absolute = false; + let package_end = loop { + let (new_pos, _) = lex_id(input, pos, &mut chars).ok_or_else(|| { + crate::Report::msg(format!( + "invalid component model symbol: '{symbol}' contains invalid characters" + )) + })?; + pos = new_pos; + + if let Some((new_pos, c)) = chars.next_if(|(_, c)| *c == ':') { + pos = new_pos + c.len_utf8(); + absolute = true; + } else { + break pos; + } + }; + + // Check if this is just a local symbol or package name + if chars.peek().is_none() { + let symbol = + unsafe { core::str::from_utf8_unchecked(&input.as_bytes()[pos..package_end]) }; + return Ok(Self { + parts, + name: SymbolName::intern(symbol), + done: false, + absolute, + }); + } + + // Push the package name to `parts` + let package_name = + unsafe { core::str::from_utf8_unchecked(&input.as_bytes()[pos..package_end]) }; + parts.push_back(package_name); + + // The next character may be either a version (if absolute), or "/" + // + // Advance the lexer as appropriate + match chars.next_if(|(_, c)| *c == '/') { + None => { + // If the next char is not '@', the format is invalid + // If the char is '@', but the path is not absolute, the format is invalid + if chars.next_if(|(_, c)| *c == '@').is_some() { + if !absolute { + return Err(crate::Report::msg( + "invalid component model symbol: unqualified symbols cannot be \ + versioned", + )); + } + // TODO(pauls): Add support for version component + // + // For now we drop it + parts.clear(); + return Ok(Self { + parts, + name: SymbolName::intern(package_name), + done: false, + absolute, + }); + } else { + return Err(crate::Report::msg(format!( + "invalid component model symbol: unexpected character in '{symbol}' \ + starting at byte {pos}" + ))); + } + } + Some((new_pos, c)) => { + pos = new_pos + c.len_utf8(); + } + } + + // Parse `ID ("/" ID)*+` until we reach end of input, or `"@"` + loop { + let (new_pos, id) = lex_id(input, pos, &mut chars).ok_or_else(|| { + crate::Report::msg(format!( + "invalid component model symbol: '{symbol}' contains invalid characters" + )) + })?; + pos = new_pos; + + if let Some((new_pos, c)) = chars.next_if(|(_, c)| *c == '/') { + pos = new_pos + c.len_utf8(); + parts.push_back(id); + } else { + break; + } + } + + // If the next char is '@', we have a version + // + // TODO(pauls): Add support for version component + // + // For now, ignore it + if chars.next_if(|(_, c)| *c == '@').is_some() { + let name = SymbolName::intern(parts.pop_back().unwrap()); + return Ok(Self { + parts, + name, + done: false, + absolute, + }); + } + + // We should be at the end now, or the format is invalid + if chars.peek().is_none() { + let name = SymbolName::intern(parts.pop_back().unwrap()); + Ok(Self { + parts, + name, + done: false, + absolute, + }) + } else { + Err(crate::Report::msg(format!( + "invalid component model symbol: '{symbol}' contains invalid character starting \ + at byte {pos}" + ))) + } + } + + /// Convert this iterator into a single [Symbol] consisting of all components. + /// + /// Returns `None` if the input is empty. + pub fn into_symbol_name(self) -> Option { + let attr = self.into_symbol_path()?; + + Some(SymbolName::intern(attr)) + } + + /// Convert this iterator into a [SymbolPath]. + /// + /// + /// Returns `None` if the input is empty. + pub fn into_symbol_path(self) -> Option { + if self.name == interner::symbols::Empty { + return None; + } + + if self.parts.is_empty() { + return Some(SymbolPath { + path: smallvec![SymbolNameComponent::Leaf(self.name)], + }); + } + + // Pre-allocate the storage for the internal SymbolPath path + let mut path = SmallVec::<[_; 3]>::with_capacity(self.parts.len() + 1); + + // Handle the first path component which tells us whether or not the path is rooted + let mut parts = self.parts.into_iter(); + if let Some(part) = parts.next() { + if part == "::" { + path.push(SymbolNameComponent::Root); + } else { + path.push(SymbolNameComponent::Component(SymbolName::intern(part))); + } + } + + // Append the remaining parts as intermediate path components + path.extend(parts.map(SymbolName::intern).map(SymbolNameComponent::Component)); + + // Finish up with the leaf symbol + path.push(SymbolNameComponent::Leaf(self.name)); + + Some(SymbolPath { path }) + } +} + +impl core::iter::FusedIterator for SymbolNameComponents {} +impl Iterator for SymbolNameComponents { + type Item = SymbolNameComponent; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + if self.absolute { + self.absolute = false; + return Some(SymbolNameComponent::Root); + } + if let Some(part) = self.parts.pop_front() { + return Some(SymbolNameComponent::Component(part.into())); + } + self.done = true; + Some(SymbolNameComponent::Leaf(self.name)) + } +} +impl ExactSizeIterator for SymbolNameComponents { + fn len(&self) -> usize { + let is_empty = self.name == interner::symbols::Empty; + if is_empty { + assert_eq!(self.parts.len(), 0, "malformed symbol name components"); + 0 + } else { + self.parts.len() + 1 + } + } +} diff --git a/hir/src/ir/symbols/symbol.rs b/hir/src/ir/symbols/symbol.rs new file mode 100644 index 000000000..b92703f52 --- /dev/null +++ b/hir/src/ir/symbols/symbol.rs @@ -0,0 +1,269 @@ +use alloc::collections::VecDeque; +use core::fmt; + +use smallvec::SmallVec; + +use super::{ + SymbolName, SymbolNameComponent, SymbolPath, SymbolTable, SymbolUse, SymbolUseRefsIter, +}; +use crate::{ + Op, Operation, OperationRef, RegionRef, Report, UnsafeIntrusiveEntityRef, Usable, Visibility, +}; + +pub type SymbolRef = UnsafeIntrusiveEntityRef; + +impl fmt::Debug for SymbolRef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Display::fmt(self, f) + } +} +impl fmt::Display for SymbolRef { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}", &self.borrow().name()) + } +} + +/// A [Symbol] is an IR entity with an associated _symbol_, or name, which is expected to be unique +/// amongst all other symbols in the same namespace. +/// +/// For example, functions are named, and are expected to be unique within the same module, +/// otherwise it would not be possible to unambiguously refer to a function by name. Likewise +/// with modules in a program, etc. +pub trait Symbol: Usable + 'static { + fn as_symbol_operation(&self) -> &Operation; + fn as_symbol_operation_mut(&mut self) -> &mut Operation; + /// Get the name of this symbol + fn name(&self) -> SymbolName; + /// Get the fully-qualified (absolute) path of this symbol + /// + /// # Panics + /// + /// This function traverses the parents of this operation to the top level. If called while + /// mutably borrowing any of the ancestors of this operation, a panic will occur. + fn path(&self) -> SymbolPath { + let mut parts = VecDeque::from_iter([SymbolNameComponent::Leaf(self.name())]); + let mut symbol_table = self.as_symbol_operation().nearest_symbol_table(); + + while let Some(parent_symbol_table) = symbol_table.take() { + let sym_table_op = parent_symbol_table.borrow(); + if let Some(sym) = sym_table_op.as_symbol() { + parts.push_front(SymbolNameComponent::Component(sym.name())); + symbol_table = sym_table_op.nearest_symbol_table(); + } else { + // This is an anonymous symbol table - for now we require all symbol tables to be + // symbols unless it is the root symbol table + assert!( + sym_table_op.parent_op().is_none(), + "anonymous symbol tables cannot have parents" + ); + } + } + + parts.push_front(SymbolNameComponent::Root); + + SymbolPath::from_iter(parts) + } + /// Set the name of this symbol + fn set_name(&mut self, name: SymbolName); + /// Get the visibility of this symbol + fn visibility(&self) -> Visibility; + /// Returns true if this symbol has private visibility + #[inline] + fn is_private(&self) -> bool { + self.visibility().is_private() + } + /// Returns true if this symbol has public visibility + #[inline] + fn is_public(&self) -> bool { + self.visibility().is_public() + } + /// Sets the visibility of this symbol + fn set_visibility(&mut self, visibility: Visibility); + /// Sets the visibility of this symbol to private + fn set_private(&mut self) { + self.set_visibility(Visibility::Private); + } + /// Sets the visibility of this symbol to internal + fn set_internal(&mut self) { + self.set_visibility(Visibility::Internal); + } + /// Sets the visibility of this symbol to public + fn set_public(&mut self) { + self.set_visibility(Visibility::Public); + } + /// Get all of the uses of this symbol that are nested within `from` + fn symbol_uses_in(&self, from: OperationRef) -> SymbolUseRefsIter { + let mut uses = VecDeque::default(); + let from = from.borrow(); + let mut cursor = self.first_use(); + while let Some(user) = cursor.as_pointer() { + let owner = user.borrow().owner; + if from.is_ancestor_of(&owner.borrow()) { + uses.push_back(user); + } + cursor.move_next(); + } + SymbolUseRefsIter::from(uses) + } + /// Get all of the uses of this symbol that are nested within `from` + fn symbol_uses_in_region(&self, from: RegionRef) -> SymbolUseRefsIter { + let mut uses = VecDeque::default(); + let from = from.borrow(); + + // Filter the set of uses we wish to match to only those that occur within the parent op + // of `from`, as all other uses cannot, by definition, be in `from`. + let from_op = from.parent().unwrap(); + let from_op = from_op.borrow(); + let mut scoped_uses = SmallVec::<[_; 8]>::default(); + { + let mut cursor = self.first_use(); + while let Some(user) = cursor.as_pointer() { + let owner = user.borrow().owner; + if from_op.is_ancestor_of(&owner.borrow()) { + scoped_uses.push((owner, user)); + } + cursor.move_next(); + } + } + + // Don't bother looking in `from` if there aren't any uses to begin with + if scoped_uses.is_empty() { + return SymbolUseRefsIter::from(uses); + } + + // Visit the body of `from`, to determine which of the uses of this symbol that belong to + // the parent operation of `from`, occur in the `from` region itself. + for block in from.body() { + for op in block.body() { + // Find all uses of `self` which occur within `op`, and add them to the result set, + // while also removing them from the set of uses to match against, reducing the + // work needed by future iterations. + scoped_uses.retain(|(owner, user)| { + if op.is_ancestor_of(&owner.borrow()) { + uses.push_back(*user); + false + } else { + true + } + }); + + // If there are no more uses remaining, we're done, and can stop searching + if scoped_uses.is_empty() { + return SymbolUseRefsIter::from(uses); + } + } + } + + SymbolUseRefsIter::from(uses) + } + /// Return true if there are no uses of this symbol nested within `from` + fn symbol_uses_known_empty(&self, from: OperationRef) -> bool { + let from = from.borrow(); + !self.iter_uses().any(|user| from.is_ancestor_of(&user.owner.borrow())) + } + /// Attempt to replace all uses of this symbol nested within `from`, with the provided replacement + fn replace_all_uses( + &mut self, + replacement: SymbolRef, + from: OperationRef, + ) -> Result<(), Report> { + for user in self.symbol_uses_in(from) { + let SymbolUse { mut owner, attr } = *user.borrow(); + let mut owner = owner.borrow_mut(); + // Unlink previously used symbol + unsafe { + self.uses_mut().cursor_mut_from_ptr(user).remove(); + } + // Link replacement symbol + owner.set_symbol_attribute(attr, replacement); + } + + Ok(()) + } + /// Returns true if this operation can be discarded if it has no remaining symbol uses + /// + /// By default, if the visibility is non-public, a symbol is considered discardable + fn can_discard_when_unused(&self) -> bool { + !self.is_public() + } + /// Returns true if this operation is a declaration, rather than a definition, of a symbol + /// + /// The default implementation assumes that all operations are definitions + fn is_declaration(&self) -> bool { + false + } + /// Return the root symbol table in which this symbol is contained, if one exists. + /// + /// The root symbol table does not necessarily know about this symbol, rather the symbol table + /// which "owns" this symbol may itself be a symbol that belongs to another symbol table. This + /// function traces this chain as far as it goes, and returns the highest ancestor in the tree. + fn root_symbol_table(&self) -> Option { + self.as_symbol_operation().root_symbol_table() + } +} + +impl dyn Symbol { + pub fn is(&self) -> bool { + let op = self.as_symbol_operation(); + op.is::() + } + + pub fn downcast_ref(&self) -> Option<&T> { + let op = self.as_symbol_operation(); + op.downcast_ref::() + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> { + let op = self.as_symbol_operation_mut(); + op.downcast_mut::() + } + + /// Get an [OperationRef] for the operation underlying this symbol + /// + /// NOTE: This relies on the assumption that all ops are allocated via the arena, and that all + /// [Symbol] implementations are ops. + pub fn as_operation_ref(&self) -> OperationRef { + self.as_symbol_operation().as_operation_ref() + } +} + +impl crate::Verify for T +where + T: Op + Symbol, +{ + fn verify(&self, context: &crate::Context) -> Result<(), Report> { + verify_symbol(self, context) + } +} + +impl crate::Verify for Operation { + fn should_verify(&self, _context: &crate::Context) -> bool { + self.implements::() + } + + fn verify(&self, context: &crate::Context) -> Result<(), Report> { + verify_symbol( + self.as_trait::() + .expect("this operation does not implement the `Symbol` trait"), + context, + ) + } +} + +fn verify_symbol(symbol: &dyn Symbol, context: &crate::Context) -> Result<(), Report> { + use midenc_session::diagnostics::{Severity, Spanned}; + + // Symbols must either have no parent, or be an immediate child of a SymbolTable + let op = symbol.as_symbol_operation(); + let parent = op.parent_op(); + if !parent.is_none_or(|parent| parent.borrow().implements::()) { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label(op.span(), "expected parent of this operation to be a symbol table") + .with_help("required due to this operation implementing the 'Symbol' trait") + .into_report()); + } + Ok(()) +} diff --git a/hir/src/ir/symbols/symbol_use.rs b/hir/src/ir/symbols/symbol_use.rs new file mode 100644 index 000000000..21616c557 --- /dev/null +++ b/hir/src/ir/symbols/symbol_use.rs @@ -0,0 +1,130 @@ +use alloc::collections::VecDeque; +use core::fmt; + +use super::SymbolPathAttr; +use crate::{Entity, EntityListItem, EntityRef, OperationRef, UnsafeIntrusiveEntityRef}; + +pub type SymbolUseRef = UnsafeIntrusiveEntityRef; +pub type SymbolUseList = crate::EntityList; +pub type SymbolUseIter<'a> = crate::EntityIter<'a, SymbolUse>; +pub type SymbolUseCursor<'a> = crate::EntityCursor<'a, SymbolUse>; +pub type SymbolUseCursorMut<'a> = crate::EntityCursorMut<'a, SymbolUse>; + +/// A [SymbolUse] represents a use of a [Symbol] by an [Operation] +#[derive(Copy, Clone)] +pub struct SymbolUse { + /// The user of the symbol + pub owner: OperationRef, + /// The symbol attribute of the op that stores the symbol + pub attr: crate::interner::Symbol, +} +impl SymbolUse { + #[inline] + pub fn new(owner: OperationRef, symbol: crate::interner::Symbol) -> Self { + Self { + owner, + attr: symbol, + } + } + + pub fn symbol(&self) -> EntityRef<'_, SymbolPathAttr> { + EntityRef::map(self.owner.borrow(), |owner| { + owner.get_typed_attribute::(self.attr).expect("expected symbol") + }) + } +} +impl fmt::Debug for SymbolUse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let op = self.owner.borrow(); + let value = op.get_typed_attribute::(self.attr); + f.debug_struct("SymbolUse") + .field("attr", &self.attr) + .field("symbol", &value.as_ref().map(|value| &value.path)) + .finish_non_exhaustive() + } +} + +impl Entity for SymbolUse {} +impl EntityListItem for SymbolUse {} + +/// An iterator over [SymbolUse] which owns the collection it iterates over. +/// +/// This is primarily used in contexts where the set of symbol uses is being gathered from many +/// places, and thus [SymbolUseIter] is not able to be used. +pub struct SymbolUsesIter { + items: VecDeque, +} +impl SymbolUsesIter { + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } +} +impl ExactSizeIterator for SymbolUsesIter { + #[inline(always)] + fn len(&self) -> usize { + self.items.len() + } +} +impl From> for SymbolUsesIter { + fn from(items: VecDeque) -> Self { + Self { items } + } +} +impl FromIterator for SymbolUsesIter { + fn from_iter>(iter: T) -> Self { + Self { + items: iter.into_iter().map(|user| *user.borrow()).collect(), + } + } +} +impl core::iter::FusedIterator for SymbolUsesIter {} +impl Iterator for SymbolUsesIter { + type Item = SymbolUse; + + #[inline] + fn next(&mut self) -> Option { + self.items.pop_front() + } +} + +/// An iterator over [SymbolUseRef] which owns the collection it iterates over. +/// +/// This is primarily used in contexts where the set of symbol uses is being gathered from many +/// places, and thus [SymbolUseIter] is not able to be used. +pub struct SymbolUseRefsIter { + items: VecDeque, +} +impl SymbolUseRefsIter { + #[inline] + pub fn is_empty(&self) -> bool { + self.items.is_empty() + } +} +impl ExactSizeIterator for SymbolUseRefsIter { + #[inline(always)] + fn len(&self) -> usize { + self.items.len() + } +} +impl From> for SymbolUseRefsIter { + fn from(items: VecDeque) -> Self { + Self { items } + } +} +impl FromIterator for SymbolUseRefsIter { + fn from_iter>(iter: T) -> Self { + Self { + items: iter.into_iter().collect(), + } + } +} +impl core::iter::FusedIterator for SymbolUseRefsIter {} +impl Iterator for SymbolUseRefsIter { + type Item = SymbolUseRef; + + #[inline] + fn next(&mut self) -> Option { + self.items.pop_front() + } +} diff --git a/hir/src/ir/symbols/table.rs b/hir/src/ir/symbols/table.rs new file mode 100644 index 000000000..53caba022 --- /dev/null +++ b/hir/src/ir/symbols/table.rs @@ -0,0 +1,738 @@ +use super::{ + generate_symbol_name, Symbol, SymbolName, SymbolNameComponent, SymbolPath, SymbolPathAttr, + SymbolRef, +}; +use crate::{ + traits::{GraphRegionNoTerminator, NoTerminator, Terminator}, + FxHashMap, Op, Operation, OperationRef, ProgramPoint, Report, UnsafeIntrusiveEntityRef, +}; + +/// A type alias for [SymbolTable] implementations referenced via [UnsafeIntrusiveEntityRef] +pub type SymbolTableRef = UnsafeIntrusiveEntityRef; + +/// A [SymbolTable] is an IR entity which contains other IR entities, called _symbols_, each of +/// which has a name, aka symbol, that uniquely identifies it amongst all other entities in the +/// same [SymbolTable]. +/// +/// The symbols in a [SymbolTable] do not need to all refer to the same entity type, however the +/// concrete value type of the symbol itself, e.g. `String`, must be the same. This is enforced +/// in the way that the [SymbolTable] and [Symbol] traits interact. A [SymbolTable] has an +/// associated `Key` type, and a [Symbol] has an associated `Id` type - only types whose `Id` +/// type matches the `Key` type of the [SymbolTable], can be stored in that table. +pub trait SymbolTable { + /// Get a reference to the underlying [Operation] + fn as_symbol_table_operation(&self) -> &Operation; + + /// Get a mutable reference to the underlying [Operation] + fn as_symbol_table_operation_mut(&mut self) -> &mut Operation; + + /// Get a [SymbolManager] for this symbol table. + fn symbol_manager(&self) -> SymbolManager<'_>; + + /// Get a [SymbolManagerMut] for this symbol table. + fn symbol_manager_mut(&mut self) -> SymbolManagerMut<'_>; + + /// Get the entry for `name` in this table + fn get(&self, name: SymbolName) -> Option { + self.symbol_manager().lookup(name) + } + + /// Resolve the entry for `path` in this table, or via the root symbol table + fn resolve(&self, path: &SymbolPath) -> Option { + let found = self.symbol_manager().lookup_symbol_ref(path)?; + let op = found.borrow(); + let sym = op.as_symbol().expect("symbol table resolved to a non-symbol op!"); + Some(unsafe { SymbolRef::from_raw(sym) }) + } + + /// Insert `entry` in the symbol table, but only if no other symbol with the same name exists. + /// + /// If provided, the symbol will be inserted at the given insertion point in the body of the + /// symbol table operation. + /// + /// This function will panic if the symbol is attached to another symbol table. + /// + /// Returns `true` if successful, `false` if the symbol is already defined + fn insert_new(&mut self, entry: SymbolRef, ip: ProgramPoint) -> bool { + self.symbol_manager_mut().insert_new(entry, ip) + } + + /// Like [SymbolTable::insert_new], except the symbol is renamed to avoid collisions. + /// + /// Returns the name of the symbol after insertion. + fn insert(&mut self, entry: SymbolRef, ip: ProgramPoint) -> SymbolName { + self.symbol_manager_mut().insert(entry, ip) + } + + /// Remove the symbol `name`, and return the entry if one was present. + fn remove(&mut self, name: SymbolName) -> Option { + let mut manager = self.symbol_manager_mut(); + + if let Some(symbol) = manager.lookup(name) { + manager.remove(symbol); + Some(symbol) + } else { + None + } + } + + /// Renames the symbol named `from`, as `to`, as well as all uses of that symbol. + /// + /// Returns `Err` if unable to update all uses. + /// + /// # Panics + /// + /// This function will panic if no operation named `from` exists in this symbol table. + fn rename(&mut self, from: SymbolName, to: SymbolName) -> Result<(), Report> { + let mut manager = self.symbol_manager_mut(); + + let symbol = manager.lookup(from).unwrap_or_else(|| panic!("undefined symbol '{from}'")); + manager.rename_symbol(symbol, to) + } +} + +impl dyn SymbolTable { + /// Get an [OperationRef] for the operation underlying this symbol table + /// + /// NOTE: This relies on the assumption that all ops are allocated via the arena, and that all + /// [SymbolTable] implementations are ops. + pub fn as_operation_ref(&self) -> OperationRef { + self.as_symbol_table_operation().as_operation_ref() + } + + /// Look up a symbol with the given name and concrete type, returning `None` if no such symbol + /// exists + pub fn find(&self, name: SymbolName) -> Option> { + let op = self.get(name)?; + let op = op.borrow(); + let op = op.as_symbol_operation().downcast_ref::()?; + Some(unsafe { UnsafeIntrusiveEntityRef::from_raw(op) }) + } +} + +/// A [SymbolMap] is a low-level datastructure used in implementing a [SymbolTable] operation. +/// +/// It is primarily responsible for maintaining a mapping between symbol names, and the symbol +/// operations registered to those names, within the body of the containing [SymbolTable] op. +/// +/// In most circumstances, you will want to interact with this via [SymbolManager] or +/// [SymbolManagerMut], as the operations provided here are mostly low-level plumbing, and thus +/// incomplete without functionality provided by higher-level abstractions. +#[derive(Default, Debug)] +pub struct SymbolMap { + /// A low-level mapping of symbols to operations found in this table + symbols: FxHashMap, + /// Used to unique symbol names when conflicts are detected + uniquing_count: usize, +} +impl SymbolMap { + /// Build a [SymbolMap] on the fly from the given operation. + /// + /// It is assumed that the given operation is a [SymbolTable] op, but this is not checked, and + /// does not affect the correctness - however, it has limited utility for non-symbol table ops. + pub fn build(op: &Operation) -> Self { + let mut symbols = FxHashMap::default(); + + let region = op.regions().front().get().unwrap(); + for op in region.entry().body() { + if let Some(symbol) = op.as_trait::() { + let name = symbol.name(); + let symbol_ref = unsafe { SymbolRef::from_raw(symbol) }; + symbols + .try_insert(name, symbol_ref) + .expect("expected region to contain uniquely named symbol operations"); + } + } + + Self { + symbols, + uniquing_count: 0, + } + } + + /// Get the symbol named `name`, or `None` if undefined. + pub fn get(&self, name: impl Into) -> Option { + let name = name.into(); + self.symbols.get(&name).cloned() + } + + /// Get the symbol named `name` as an [OperationRef], or `None` if undefined. + pub fn get_op(&self, name: impl Into) -> Option { + let name = name.into(); + self.symbols.get(&name).map(|symbol| symbol.borrow().as_operation_ref()) + } + + /// Get the symbol referenced by `attr` as an [OperationRef], or `None` if undefined. + /// + /// This function will search for the symbol path according to whether the path is absolute or + /// relative: + /// + /// * Absolute paths will be resolved by traversing up the operation tree to the root operation, + /// which will be expected to be an anonymous SymbolTable, and then resolve path components + /// from there. + /// * Relative paths will be resolved from the current SymbolTable + /// + /// In the special case where a absolute path is given, but the root operation is also a Symbol, + /// it is presumed that what we have found is not the absolute root which represents the global + /// namespace, but rather a symbol defined in the global namespace. This means that only + /// children of that symbol are possibly resolvable (as we have no way to reach other symbols + /// defined in the global namespace). In short, we only attempt to resolve absolute paths where + /// the first component matches the root symbol. If it matches, then the symbol is resolved + /// normally from there, otherwise `None` is returned. + pub fn resolve(&self, symbol_table: &Operation, attr: &SymbolPath) -> Option { + let mut components = attr.components(); + + // Resolve absolute paths via the root symbol table + if attr.is_absolute() { + let _ = components.next(); + + // Locate the root operation + let root = if let Some(mut root) = symbol_table.parent_op() { + while let Some(ancestor) = root.borrow().parent_op() { + root = ancestor; + } + root + } else { + symbol_table.as_operation_ref() + }; + + let root_op = root.borrow(); + + // If the root is also a Symbol, then we aren't actually in the root namespace, but + // in one of the symbols within the root namespace. As a result, we can only resolve + // absolute symbol paths which are children of `root`, as we cannot reach any other + // symbols in the root namespace. + if let Some(root_symbol) = root_op.as_trait::() { + match components.next()? { + SymbolNameComponent::Leaf(name) => { + return if name == root_symbol.name() { + Some(root) + } else { + None + }; + } + SymbolNameComponent::Component(name) => { + if name != root_symbol.name() { + return None; + } + } + SymbolNameComponent::Root => unreachable!(), + } + } + + // Resolve the symbol from `root` + let root_symbol_table = root_op.as_trait::()?; + let symbol_manager = root_symbol_table.symbol_manager(); + symbol_manager.symbols().resolve_components(components) + } else { + self.resolve_components(components) + } + } + + fn resolve_components( + &self, + mut components: impl ExactSizeIterator, + ) -> Option { + match components.next()? { + super::SymbolNameComponent::Component(name) => { + let mut found = self.get_op(name); + loop { + let op_ref = found.take()?; + let op = op_ref.borrow(); + let symbol_table = op.as_trait::()?; + let manager = symbol_table.symbol_manager(); + match components.next() { + None => return Some(op_ref), + Some(super::SymbolNameComponent::Component(name)) => { + found = manager.lookup_op(name); + } + Some(super::SymbolNameComponent::Leaf(name)) => { + assert_eq!(components.next(), None); + break manager.lookup_op(name); + } + Some(super::SymbolNameComponent::Root) => unreachable!(), + } + } + } + super::SymbolNameComponent::Leaf(name) => self.get_op(name), + super::SymbolNameComponent::Root => { + unreachable!("root component should have already been consumed") + } + } + } + + /// Returns true if a symbol named `name` is in the map + #[inline] + pub fn contains_key(&self, name: &K) -> bool + where + K: ?Sized + core::hash::Hash + hashbrown::Equivalent, + { + self.symbols.contains_key(name) + } + + /// Remove the entry for `name` from this map, if present. + #[inline] + pub fn remove(&mut self, name: SymbolName) -> Option { + self.symbols.remove(&name) + } + + /// Inserts `symbol` in the map, as `name`, so long as `name` is not already in the map. + #[inline] + pub fn insert_new(&mut self, name: SymbolName, symbol: SymbolRef) -> bool { + self.symbols.try_insert(name, symbol).is_ok() + } + + /// Inserts `symbol` in the map, with `name` if that name is not already registered in the map. + /// Otherwise, a unique variation of `name` is generated, and `symbol` is inserted in the map + /// with that name instead. + /// + /// If `name` is modified to make it unique, `symbol` is updated with the new name on insertion. + /// + /// Returns the name `symbol` has after insertion. + /// + /// NOTE: If `symbol` is already in the map with `name`, this is a no-op. + pub fn insert(&mut self, name: SymbolName, mut symbol: SymbolRef) -> SymbolName { + // Add the symbol to the symbol map + // let sym = symbol.borrow(); + match self.symbols.try_insert(name, symbol) { + Ok(_) => { + symbol.borrow_mut().set_name(name); + name + } + Err(err) => { + // If this exact symbol was already in the table, do nothing + if err.entry.get() == &symbol { + assert_eq!( + symbol.borrow().name(), + name, + "name does not match what was registered with the symbol table" + ); + return name; + } + + // Otherwise, we need to make the symbol name unique + let uniqued = generate_symbol_name(name, &mut self.uniquing_count, |name| { + !self.symbols.contains_key(name) + }); + // drop(sym); + symbol.borrow_mut().set_name(uniqued); + // TODO: visit uses? symbol should be unused AFAICT + self.symbols.insert(uniqued, symbol); + uniqued + } + } + } + + /// Ensures that the given symbol name is unique within this symbol map, as well as all of the + /// provided symbol managers. + /// + /// Returns the unique name, but this function does not modify the map or rename the symbol + /// itself, that is expected to be done from [SymbolManagerMut]. + pub fn make_unique(&mut self, op: &SymbolRef, tables: &[SymbolManager<'_>]) -> SymbolName { + // Determine new name that is unique in all symbol tables. + let name = { op.borrow().name() }; + + generate_symbol_name(name, &mut self.uniquing_count, |name| { + if self.symbols.contains_key(name) { + return false; + } + !tables.iter().any(|t| t.symbols.contains_key(name)) + }) + } + + /// Get an iterator of [SymbolRef] corresponding to the [Symbol] operations in this map + pub fn symbols(&self) -> impl Iterator + '_ { + self.symbols.values().cloned() + } +} + +/// This type is used to abstract over ownership of an immutable [SymbolMap]. +pub enum Symbols<'a> { + /// The symbol map is owned by this struct, typically because the operation to which it + /// ostensibly belongs did not have one for us, so we were forced to compute the symbol + /// mapping for that operation on the fly. + Owned(SymbolMap), + /// The symbol map is being borrowed (typically from the [SymbolTable] operation) + Borrowed(&'a SymbolMap), +} +impl From for Symbols<'_> { + fn from(value: SymbolMap) -> Self { + Self::Owned(value) + } +} +impl core::ops::Deref for Symbols<'_> { + type Target = SymbolMap; + + fn deref(&self) -> &Self::Target { + match self { + Self::Owned(ref symbols) => symbols, + Self::Borrowed(symbols) => symbols, + } + } +} + +/// This type is used to abstract over ownership of an immutable [SymbolMap]. +pub enum SymbolsMut<'a> { + /// The symbol map is owned by this struct, typically because the operation to which it + /// ostensibly belongs did not have one for us, so we were forced to compute the symbol + /// mapping for that operation on the fly. + Owned(SymbolMap), + /// The symbol map is being borrowed (typically from the [SymbolTable] operation) + Borrowed(&'a mut SymbolMap), +} +impl From for SymbolsMut<'_> { + fn from(value: SymbolMap) -> Self { + Self::Owned(value) + } +} +impl core::ops::Deref for SymbolsMut<'_> { + type Target = SymbolMap; + + fn deref(&self) -> &Self::Target { + match self { + Self::Owned(ref symbols) => symbols, + Self::Borrowed(symbols) => symbols, + } + } +} +impl core::ops::DerefMut for SymbolsMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::Owned(symbols) => symbols, + Self::Borrowed(symbols) => symbols, + } + } +} + +/// This type provides high-level read-only symbol table operations for [SymbolTable] impls. +/// +/// It is designed to be able to handle both dynamically-computed symbol table mappings, or use +/// cached mappings provided by the [SymbolTable] op itself. +/// +/// See [SymbolManagerMut] for read/write use cases. +pub struct SymbolManager<'a> { + /// The [SymbolTable] operation we're managing + symbol_table: &'a Operation, + /// The symbols registered under `symbol_table`. + /// + /// This information can either be computed dynamically, or cached by the operation itself. + symbols: Symbols<'a>, +} + +impl<'a> SymbolManager<'a> { + /// Create a new [SymbolManager] from the given operation and symbol mappings + pub fn new(symbol_table: &'a Operation, symbols: Symbols<'a>) -> Self { + Self { + symbol_table, + symbols, + } + } + + /// Returns true if this symbol table corresponds to the root namespace + pub fn is_root(&self) -> bool { + self.symbol_table.parent().is_none() + } + + /// Returns a reference to the underlying symbol table [Operation] + pub fn symbol_table(&self) -> &Operation { + self.symbol_table + } + + pub fn symbols(&self) -> &SymbolMap { + &self.symbols + } + + /// Get the symbol named `name`, or `None` if undefined. + pub fn lookup(&self, name: impl Into) -> Option { + self.symbols.get(name) + } + + /// Get the symbol named `name` as an [OperationRef], or `None` if undefined. + pub fn lookup_op(&self, name: impl Into) -> Option { + self.symbols.get_op(name) + } + + /// Get the symbol referenced by `attr` as an [OperationRef], or `None` if undefined. + /// + /// See [SymbolMap::resolve] for more details about symbol resolution. + pub fn lookup_symbol_ref(&self, attr: &SymbolPath) -> Option { + self.symbols.resolve(self.symbol_table, attr) + } +} + +impl<'a> From<&'a Operation> for SymbolManager<'a> { + fn from(symbol_table: &'a Operation) -> Self { + Self { + symbol_table, + symbols: SymbolMap::build(symbol_table).into(), + } + } +} + +/// This type provides high-level read and write symbol table operations for [SymbolTable] impls. +/// +/// It is designed to be able to handle both dynamically-computed symbol table mappings, or use +/// cached mappings provided by the [SymbolTable] op itself. +pub struct SymbolManagerMut<'a> { + /// The [SymbolTable] operation we're managing + symbol_table: &'a mut Operation, + /// The symbols registered under `symbol_table`. + /// + /// This information can either be computed dynamically, or cached by the operation itself. + symbols: SymbolsMut<'a>, +} +impl<'a> SymbolManagerMut<'a> { + /// Create a new [SymbolManager] from the given operation and symbol mappings + pub fn new(symbol_table: &'a mut Operation, symbols: SymbolsMut<'a>) -> Self { + Self { + symbol_table, + symbols, + } + } + + /// Returns an immutable reference to the underlying symbol table [Operation] + /// + /// NOTE: This requires a mutable reference to `self`, because the underlying [Operation] + /// reference is a mutable one. + pub fn symbol_table(&mut self) -> &Operation { + self.symbol_table + } + + /// Returns a mutable reference to the underlying symbol table [Operation] + pub fn symbol_table_mut(&mut self) -> &mut Operation { + self.symbol_table + } + + /// Get the symbol named `name`, or `None` if undefined. + pub fn lookup(&self, name: impl Into) -> Option { + self.symbols.get(name) + } + + /// Get the symbol named `name` as an [OperationRef], or `None` if undefined. + pub fn lookup_op(&self, name: impl Into) -> Option { + self.symbols.get_op(name) + } + + /// Get the symbol referenced by `attr` as an [OperationRef], or `None` if undefined. + /// + /// See [SymbolMap::resolve] for more details about symbol resolution. + pub fn lookup_symbol_ref(&self, attr: &SymbolPath) -> Option { + self.symbols.resolve(self.symbol_table, attr) + } + + /// Remove the given [Symbol] op from the table + /// + /// NOTE: This does not remove users of `op`'s symbol, that is left up to callers + pub fn remove(&mut self, op: SymbolRef) { + let name = { + let symbol = op.borrow(); + let symbol_op = symbol.as_operation_ref(); + assert_eq!( + symbol_op.borrow().parent_op(), + Some(self.symbol_table.as_operation_ref()), + "expected `op` to be a child of this symbol table" + ); + symbol.name() + }; + + self.symbols.remove(name); + } + + /// Inserts a new symbol into the table, as long as the symbol name is unique. + /// + /// Returns `false` if an existing symbol with the same name is already in the table. + /// + /// # Panics + /// + /// This function will panic if `symbol` is already attached to another operation. + pub fn insert_new(&mut self, symbol: SymbolRef, ip: ProgramPoint) -> bool { + let name = symbol.borrow().name(); + if self.symbols.contains_key(&name) { + return false; + } + + assert_eq!(self.insert(symbol, ip), name, "expected insertion to preserve original name"); + + true + } + + /// Insert a new symbol into the table, renaming it as necessary to avoid name collisions. + /// + /// If `ip` is provided, the operation will be inserted at the specified program point. + /// Otherwise, the new symbol is inserted at the end of the body of the symbol table op. + /// + /// Returns the name of the symbol after insertion, which may not be the same as its original + /// name. + /// + /// # Panics + /// + /// This function will panic if `symbol` is already attached to another operation. + pub fn insert(&mut self, symbol: SymbolRef, mut ip: ProgramPoint) -> SymbolName { + // The symbol cannot be the child of another op, and must be the child of the symbol table + // after insertion. + let (name, symbol_op) = { + let sym = symbol.borrow(); + let symbol_op = sym.as_operation_ref(); + assert!( + symbol_op + .borrow() + .parent_op() + .is_none_or(|p| p == self.symbol_table.as_operation_ref()), + "symbol is already inserted in another op" + ); + (sym.name(), symbol_op) + }; + + if symbol_op.borrow().parent().is_none() { + let requires_terminator = !self.symbol_table.implements::() + && !self.symbol_table.implements::(); + let mut body = self.symbol_table.region_mut(0); + let mut block = body.entry_mut(); + + // If no terminator is required in the symbol table body, simply insert it at the + if requires_terminator { + let block_terminator = block + .terminator() + .expect("symbol table op requires a terminator, but one was not found"); + + if ip.is_unset() { + let ops = block.body_mut(); + unsafe { + let mut cursor = ops.cursor_mut_from_ptr(block_terminator); + cursor.insert_before(symbol_op); + } + } else { + let ip_block = ip.block(); + assert_eq!( + ip_block, + Some(block.as_block_ref()), + "invalid insertion point: not located in this symbol table" + ); + if ip.is_at_block_end() { + // If the insertion point would place the symbol after the region terminator + // it must be itself a valid region terminator, or the insertion point is + // not valid + assert!( + symbol_op.borrow().implements::(), + "cannot insert symbol after the region terminator" + ); + } + ip.cursor_mut().unwrap().insert_after(symbol_op); + } + } else if ip.is_unset() { + block.body_mut().push_back(symbol_op); + } else { + let ip_block = ip.block(); + assert_eq!( + ip_block, + Some(block.as_block_ref()), + "invalid insertion point: not located in this symbol table" + ); + ip.cursor_mut().unwrap().insert_after(symbol_op); + } + } + + // Add the symbol to the symbol map + self.symbols.insert(name, symbol) + } + + /// Renames the given operation, and updates the symbol table and all uses of the old name. + /// + /// Returns `Err` if not all uses could be updated. + pub fn rename_symbol(&mut self, mut op: SymbolRef, to: SymbolName) -> Result<(), Report> { + let name = { + let symbol = op.borrow(); + let name = symbol.name(); + let symbol_op = symbol.as_symbol_operation(); + assert!( + symbol_op + .parent_op() + .is_some_and(|parent| parent == self.symbol_table.as_operation_ref()), + "expected operation to be a child of this symbol table" + ); + assert!( + self.lookup(name).as_ref().is_some_and(|o| o == &op), + "current name does not resolve to `op`" + ); + assert!( + !self.symbols.contains_key(&to), + "new symbol name given by `to` is already in use" + ); + name + }; + + // Rename the name stored in all users of `op` + self.replace_all_symbol_uses(op, to)?; + + // Remove op with old name, change name, add with new name. + // + // The order is important here due to how `remove` and `insert` rely on the op name. + self.remove(op); + { + op.borrow_mut().set_name(to); + } + self.insert(op, ProgramPoint::default()); + + assert!( + self.lookup(to).is_some_and(|o| o == op), + "new name does not resolve to renamed op" + ); + assert!(!self.symbols.contains_key(&name), "old name still exists"); + + Ok(()) + } + + /// Replaces the symbol name stored in all uses of the symbol `op`. + /// + /// NOTE: This is not the same as replacing uses of one symbol with another, this used while + /// renaming the symbol name of `op`, while preserving its uses. + pub fn replace_all_symbol_uses( + &mut self, + mut op: SymbolRef, + to: SymbolName, + ) -> Result<(), Report> { + // Visit all users of `symbol`, and rewrite the name used with `to` + let mut symbol = op.borrow_mut(); + let mut users = symbol.uses_mut().front_mut(); + while let Some(mut user) = users.as_pointer() { + users.move_next(); + + let mut user = user.borrow_mut(); + let mut user_op = user.owner.borrow_mut(); + let symbol_name_attr = user_op + .get_typed_attribute_mut::(user.attr) + .expect("invalid symbol use"); + symbol_name_attr.path.set_name(to); + } + + Ok(()) + } + + /// Renames the given operation to a name that is unique within this and all of the provided + /// symbol tables, updating the symbol table and all uses of the old name. + /// + /// Returns the new name, or `Err` if renaming fails. + pub fn make_unique( + &mut self, + op: SymbolRef, + tables: &[SymbolManager<'_>], + ) -> Result { + // Determine new name that is unique in all symbol tables. + let uniqued = self.symbols.make_unique(&op, tables); + + // Rename the symbol to the new name + self.rename_symbol(op, uniqued)?; + + Ok(uniqued) + } +} + +impl<'a> From<&'a mut Operation> for SymbolManagerMut<'a> { + fn from(symbol_table: &'a mut Operation) -> Self { + let symbols = SymbolMap::build(&*symbol_table).into(); + Self { + symbol_table, + symbols, + } + } +} diff --git a/hir/src/ir/traits.rs b/hir/src/ir/traits.rs new file mode 100644 index 000000000..d82c6603c --- /dev/null +++ b/hir/src/ir/traits.rs @@ -0,0 +1,309 @@ +mod canonicalization; +mod foldable; +mod info; +mod types; + +use alloc::{boxed::Box, format}; + +use midenc_session::diagnostics::Severity; + +pub use self::{ + canonicalization::Canonicalizable, + foldable::{FoldResult, Foldable, OpFoldResult}, + info::TraitInfo, + types::*, +}; +use super::BlockRef; +use crate::{derive, AttributeValue, Context, Operation, Report, Spanned}; + +/// Marker trait for commutative ops, e.g. `X op Y == Y op X` +pub trait Commutative {} + +/// Marker trait for constant-like ops +pub trait ConstantLike {} + +/// Marker trait for return-like ops +pub trait ReturnLike {} + +/// Op is a terminator (i.e. it can be used to terminate a block) +pub trait Terminator {} + +/// Op's regions do not require blocks to end with a [Terminator] +pub trait NoTerminator {} + +/// Marker trait for idemptoent ops, i.e. `op op X == op X (unary) / X op X == X (binary)` +pub trait Idempotent {} + +/// Marker trait for ops that exhibit the property `op op X == X` +pub trait Involution {} + +/// Marker trait for ops which are not permitted to access values defined above them +pub trait IsolatedFromAbove {} + +/// Marker trait for ops which have only regions of [`RegionKind::Graph`] +pub trait HasOnlyGraphRegion {} + +/// Op's regions are all single-block graph regions, that not require a terminator +/// +/// This trait _cannot_ be derived via `derive!` +pub trait GraphRegionNoTerminator: + NoTerminator + SingleBlock + crate::RegionKindInterface + HasOnlyGraphRegion +{ +} + +// TODO(pauls): Implement verifier +/// This interface provides information for branching terminator operations, i.e. terminator +/// operations with successors. +/// +/// This interface is meant to model well-defined cases of control-flow of value propagation, where +/// what occurs along control-flow edges is assumed to be side-effect free. For example, +/// corresponding successor operands and successor block arguments may have different types. In such +/// cases, `are_types_compatible` can be implemented to compare types along control-flow edges. By +/// default, type equality is used. +pub trait BranchOpInterface: crate::Op { + /// Returns the operands that correspond to the arguments of the successor at `index`. + /// + /// It consists of a number of operands that are internally produced by the operation, followed + /// by a range of operands that are forwarded. An example operation making use of produced + /// operands would be: + /// + /// ```hir,ignore + /// invoke %function(%0) + /// label ^success ^error(%1 : i32) + /// + /// ^error(%e: !error, %arg0: i32): + /// ... + ///``` + /// + /// The operand that would map to the `^error`s `%e` operand is produced by the `invoke` + /// operation, while `%1` is a forwarded operand that maps to `%arg0` in the successor. + /// + /// Produced operands always map to the first few block arguments of the successor, followed by + /// the forwarded operands. Mapping them in any other order is not supported by the interface. + /// + /// By having the forwarded operands last allows users of the interface to append more forwarded + /// operands to the branch operation without interfering with other successor operands. + fn get_successor_operands(&self, index: usize) -> crate::SuccessorOperandRange<'_> { + let op = ::as_operation(self); + let operand_group = op.successors()[index].operand_group as usize; + crate::SuccessorOperandRange::forward(op.operands().group(operand_group)) + } + /// The mutable version of [Self::get_successor_operands]. + fn get_successor_operands_mut(&mut self, index: usize) -> crate::SuccessorOperandRangeMut<'_> { + let op = ::as_operation_mut(self); + let operand_group = op.successors()[index].operand_group as usize; + crate::SuccessorOperandRangeMut::forward(op.operands_mut().group_mut(operand_group)) + } + /// Returns the block argument of the successor corresponding to the operand at `operand_index`. + /// + /// Returns `None` if the specified operand is not a successor operand. + fn get_successor_block_argument( + &self, + operand_index: usize, + ) -> Option { + let op = ::as_operation(self); + let operand_groups = op.operands().num_groups(); + let mut next_index = 0usize; + for operand_group in 0..operand_groups { + let group_size = op.operands().group(operand_group).len(); + if (next_index..(next_index + group_size)).contains(&operand_index) { + let arg_index = operand_index - next_index; + // We found the operand group, now map that to a successor + let succ_info = + op.successors().iter().find(|s| operand_group == s.operand_group as usize)?; + return succ_info + .block + .borrow() + .successor() + .borrow() + .arguments() + .get(arg_index) + .cloned(); + } + + next_index += group_size; + } + + None + } + /// Returns the successor that would be chosen with the given constant operands. + /// + /// Each operand of this op has an entry in the `operands` slice. If the operand is non-constant, + /// the corresponding entry will be `None`. + /// + /// Returns `None` if a single successor could not be chosen. + #[inline] + #[allow(unused_variables)] + fn get_successor_for_operands( + &self, + operands: &[Option>], + ) -> Option { + None + } + /// This is called to compare types along control-flow edges. + /// + /// By default, types must be exactly equal to be compatible. + fn are_types_compatible(&self, lhs: &crate::Type, rhs: &crate::Type) -> bool { + lhs == rhs + } + + /// Changes the destination to `new_dest` if the current destination is `old_dest`. + fn change_branch_destination(&mut self, old_dest: BlockRef, new_dest: BlockRef) { + let op = ::as_operation_mut(self); + assert_eq!(old_dest.borrow().num_arguments(), new_dest.borrow().num_arguments()); + for successor_info in op.successors_mut().iter_mut() { + if successor_info.successor() == old_dest { + successor_info.block.borrow_mut().set(new_dest); + } + } + } +} + +/// This interface provides information for select-like operations, i.e., operations that forward +/// specific operands to the output, depending on a binary condition. +/// +/// If the value of the condition is 1, then the `true` operand is returned, and the third operand +/// is ignored, even if it was poison. +/// +/// If the value of the condition is 0, then the `false` operand is returned, and the second operand +/// is ignored, even if it was poison. +/// +/// If the condition is poison, then poison is returned. +/// +/// Implementing operations can also accept shaped conditions, in which case the operation works +/// element-wise. +pub trait SelectLikeOpInterface { + /// Returns the operand that represents the boolean condition for this select-like op. + fn get_condition(&self) -> crate::ValueRef; + /// Returns the operand that would be chosen for a true condition. + fn get_true_value(&self) -> crate::ValueRef; + /// Returns the operand that would be chosen for a false condition. + fn get_false_value(&self) -> crate::ValueRef; +} + +derive! { + /// Marker trait for unary ops, i.e. those which take a single operand + pub trait UnaryOp {} + + verify { + fn is_unary_op(op: &Operation, context: &Context) -> Result<(), Report> { + if op.num_operands() == 1 { + Ok(()) + } else { + Err( + context.diagnostics() + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label(op.span(), format!("incorrect number of operands, expected 1, got {}", op.num_operands())) + .with_help("this operator implements 'UnaryOp', which requires it to have exactly one operand") + .into_report() + ) + } + } + } +} + +derive! { + /// Marker trait for binary ops, i.e. those which take two operands + pub trait BinaryOp {} + + verify { + fn is_binary_op(op: &Operation, context: &Context) -> Result<(), Report> { + if op.num_operands() == 2 { + Ok(()) + } else { + Err( + context.diagnostics() + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label(op.span(), format!("incorrect number of operands, expected 2, got {}", op.num_operands())) + .with_help("this operator implements 'BinaryOp', which requires it to have exactly two operands") + .into_report() + ) + } + } + } +} + +derive! { + /// Op's regions have no arguments + pub trait NoRegionArguments {} + + verify { + fn no_region_arguments(op: &Operation, context: &Context) -> Result<(), Report> { + for region in op.regions().iter() { + if region.is_empty() { + continue + } + if region.entry().has_arguments() { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label( + op.span(), + "this operation does not permit regions with arguments, but one was found" + ) + .into_report()); + } + } + + Ok(()) + } + } +} + +derive! { + /// Op's regions have a single block + pub trait SingleBlock {} + + verify { + fn has_only_single_block_regions(op: &Operation, context: &Context) -> Result<(), Report> { + for region in op.regions().iter() { + if region.body().iter().count() > 1 { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label( + op.span(), + "this operation requires single-block regions, but regions with multiple \ + blocks were found", + ) + .into_report()); + } + } + + Ok(()) + } + } +} + +// pub trait SingleBlockImplicitTerminator {} + +derive! { + /// Op has a single region + pub trait SingleRegion {} + + verify { + fn has_exactly_one_region(op: &Operation, context: &Context) -> Result<(), Report> { + let num_regions = op.num_regions(); + if num_regions != 1 { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label( + op.span(), + format!("this operation requires exactly one region, but got {num_regions}") + ) + .into_report()); + } + + Ok(()) + } + } +} + +// pub trait HasParent {} +// pub trait ParentOneOf<(T,...)> {} diff --git a/hir/src/ir/traits/canonicalization.rs b/hir/src/ir/traits/canonicalization.rs new file mode 100644 index 000000000..aedb67807 --- /dev/null +++ b/hir/src/ir/traits/canonicalization.rs @@ -0,0 +1,82 @@ +use alloc::rc::Rc; + +use crate::{patterns::RewritePatternSet, Context, Op}; + +/// This trait represents an [Op] that has a canonical/normal form. +/// +/// Canonicalization patterns are applied via a single canonicalization pass, which iteratively +/// applies canonicalization patterns of all operations until either fixpoint is reached, or some +/// maximum number of iterations is reached. As a result, canonicalization is performed on a best- +/// effort basis, and the IR overall is not guaranteed to be in perfect canonical form. +/// +/// Canonicalization is intended to simplify subsequent analysis and optimization, by allowing them +/// to assume that all operations are in canonical form, and thus not needing to handle all the +/// many different variations of the same op that might occur in practice. This reduces the amount +/// of redundant work that must be done, and improves the performance of compilation overall. It +/// is important to stress though, that canonicalizations must not be required for correctness - +/// if an operation is not in canonical form, compilation should still produce correct output, +/// albeit less optimal output. +/// +/// +/// Operations which have a canonical/normal form must be able to define what it means for two +/// instances of the op to be equivalent. This is because the mathematical properties of +/// canonicalization are defined in terms of equivalence relations over all forms of an op in the +/// same _equivalence class_. An intuitive way of thinking about this in terms of operations, are +/// that two instances of an operation are in the same equivalence class if they are unique from +/// each other, but would produce identical results, i.e. they represent the same computation in +/// different forms. +/// +/// For operations which meet the above requirement, and do provide a set of canonicalization +/// patterns, those patterns must uphold the following properties: +/// +/// 1. _Idempotence_. After applying canonicalization pattern(s) to the op, applying again to the +/// resulting op will have no effect, i.e. `canonicalize(op) = canonicalize(canonicalize(op))`. +/// 2. _Decisiveness_. Applying the canonicalization patterns to any two instances of the operation +/// in the same equivalence class, produce an identical result. The result must be the canonical +/// form, must be unique for the equivalence class, and must be itself a member of the +/// equivalence class (i.e. the output is equivalent to the inputs). In other words, given a +/// equivalence class `{a, b, c}`, where `c` is the canonical representation for that equivalence +/// class, then: `canon(a) = canon(b) = c`. +/// 3. _Convergence_. Each canonicalization rewrite must either leave the IR unchanged, or rewrite +/// it such that the output is strictly _more_ canonical than the input. A rewrite which makes +/// the input less canonical "temporarily" so that another rewrite will apply, can easily result +/// in unstable or cyclic rewrites, causing canonicalization to never reach fixpoint. +/// +/// Additionally, there are some general rules that should be followed: +/// +/// * Canonicalization rewrites should be simple, and focused purely on reaching canonical form. +/// They should not be used to do unrelated optimizations/rewrites that do not pertain to the +/// task of canonicalization. +/// +/// * Canonicalization rewrites should never rewrite other operations with canonical forms. However, +/// it is fine to add or remove operations in order to reach canonical form. For example, if we +/// are canonicalizing an `if` expression, we might want to simplify the condition expression such +/// that a condition of the form `!x` is rewritten as just `x`, swapping the then/else blocks so +/// that the resulting `if` is equivalent to the original, but without the unnecessary inversion +/// of the condition. In this case, we removed an operation that became dead as the result of +/// canonicalizing the `if`. It would not, however, have been a good idea for us to try and do +/// more complex analysis/rewrite of the `x` expression itself - instead, the canonicalization +/// should assume that `x` is already in its canonical form. +/// +/// * It is best to canonicalize towards fewer uses of a value when operands are duplicated, as +/// some rewrite patterns only match when a value has a single use. For example, canonicalizing +/// `x + x` as `x * 2`, since this reduces the number of uses of `x` by one. +pub trait Canonicalizable { + /// Populate `rewrites` with the set of rewrite patterns that should be applied to canonicalize + /// this operation. + /// + /// NOTE: This should not be used to register _all_ possible rewrites for this operation, only + /// those used for canonicalization. + /// + /// An operation that has no canonicalization patterns may simply return without adding any + /// patterns to the set. This is the default behavior implemented for all [Op] impls. + fn get_canonicalization_patterns(rewrites: &mut RewritePatternSet, context: Rc); +} + +impl Canonicalizable for T { + default fn get_canonicalization_patterns( + _rewrites: &mut RewritePatternSet, + _context: Rc, + ) { + } +} diff --git a/hir/src/ir/traits/foldable.rs b/hir/src/ir/traits/foldable.rs new file mode 100644 index 000000000..e58a3b45a --- /dev/null +++ b/hir/src/ir/traits/foldable.rs @@ -0,0 +1,181 @@ +use alloc::boxed::Box; + +use smallvec::SmallVec; + +use crate::{AttributeValue, ValueRef}; + +/// Represents the outcome of an attempt to fold an operation. +#[must_use] +pub enum FoldResult { + /// The operation was folded and erased, and the given fold results were returned + Ok(T), + /// The operation was modified in-place, but not erased. + InPlace, + /// The operation could not be folded + Failed, +} +impl FoldResult { + /// Returns true if folding was successful + #[inline] + pub fn is_ok(&self) -> bool { + matches!(self, Self::Ok(_) | Self::InPlace) + } + + /// Returns true if folding was unsuccessful + #[inline] + pub fn is_failed(&self) -> bool { + matches!(self, Self::Failed) + } + + /// Convert this result to an `Option` representing a successful outcome, where `None` indicates + /// an in-place fold, and `Some(T)` indicates that the operation was folded away. + /// + /// Panics with the given message if the fold attempt failed. + #[inline] + #[track_caller] + pub fn expect(self, message: &'static str) -> Option { + match self { + Self::Ok(out) => Some(out), + Self::InPlace => None, + Self::Failed => unwrap_failed_fold_result(message), + } + } + + /// Convert this result to an `Option` representing a successful outcome, where `None` indicates + /// an in-place fold, and `Some(T)` indicates that the operation was folded away. + /// + /// Invokes the given callback if the fold attempt failed. + #[inline] + #[track_caller] + pub fn unwrap_or_else(self, callback: F) -> Option + where + F: FnOnce() -> !, + { + match self { + Self::Ok(out) => Some(out), + Self::InPlace => None, + Self::Failed => callback(), + } + } +} +impl From> for FoldResult { + fn from(value: Option) -> Self { + match value { + None => FoldResult::Failed, + Some(value) => FoldResult::Ok(value), + } + } +} +impl core::ops::FromResidual for FoldResult { + fn from_residual(residual: ::Residual) -> Self { + match residual { + FoldResult::Failed => FoldResult::Failed, + _ => unreachable!(), + } + } +} +impl core::ops::Residual for FoldResult { + type TryType = FoldResult; +} +impl core::ops::Try for FoldResult { + type Output = T; + type Residual = FoldResult; + + #[inline] + fn from_output(output: Self::Output) -> Self { + FoldResult::Ok(output) + } + + #[inline] + fn branch(self) -> core::ops::ControlFlow { + use core::ops::ControlFlow; + match self { + FoldResult::Ok(c) => ControlFlow::Continue(c), + FoldResult::InPlace => ControlFlow::Break(FoldResult::InPlace), + FoldResult::Failed => ControlFlow::Break(FoldResult::Failed), + } + } +} +#[cold] +#[track_caller] +#[inline(never)] +fn unwrap_failed_fold_result(message: &'static str) -> ! { + panic!("tried to unwrap failed fold result as successful: {message}") +} + +/// Represents a single result value of a folded operation. +pub enum OpFoldResult { + /// The value is constant + Attribute(Box), + /// The value is a non-constant SSA value + Value(ValueRef), +} +impl OpFoldResult { + #[inline] + pub fn is_constant(&self) -> bool { + matches!(self, Self::Attribute(_)) + } +} +impl Eq for OpFoldResult {} +impl PartialEq for OpFoldResult { + fn eq(&self, other: &Self) -> bool { + use core::hash::{Hash, Hasher}; + + match (self, other) { + (Self::Attribute(lhs), Self::Attribute(rhs)) => { + if lhs.as_any().type_id() != rhs.as_any().type_id() { + return false; + } + let lhs_hash = { + let mut hasher = rustc_hash::FxHasher::default(); + lhs.hash(&mut hasher); + hasher.finish() + }; + let rhs_hash = { + let mut hasher = rustc_hash::FxHasher::default(); + rhs.hash(&mut hasher); + hasher.finish() + }; + lhs_hash == rhs_hash + } + (Self::Value(lhs), Self::Value(rhs)) => ValueRef::ptr_eq(lhs, rhs), + _ => false, + } + } +} +impl core::fmt::Debug for OpFoldResult { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Attribute(attr) => core::fmt::Debug::fmt(attr, f), + Self::Value(ref value) => write!(f, "{}", value.borrow().id()), + } + } +} + +/// An operation that can be constant-folded must implement the folding logic via this trait. +/// +/// NOTE: Any `ConstantLike` operation must implement this trait as a no-op, i.e. returning the +/// value of the constant directly, as this is used by the pattern matching infrastructure to +/// extract the value of constant operations without knowing anything about the specific op. +pub trait Foldable { + /// Attempt to fold this operation using its current operand values. + /// + /// If folding was successful and the operation should be erased, `results` will contain the + /// folded results. See [FoldResult] for more details on what the various outcomes of folding + /// are. + fn fold(&self, results: &mut SmallVec<[OpFoldResult; 1]>) -> FoldResult; + + /// Attempt to fold this operation with the specified operand values. + /// + /// The elements in `operands` will correspond 1:1 with the operands of the operation, but will + /// be `None` if the value is non-constant. + /// + /// If folding was successful and the operation should be erased, `results` will contain the + /// folded results. See [FoldResult] for more details on what the various outcomes of folding + /// are. + fn fold_with( + &self, + operands: &[Option>], + results: &mut SmallVec<[OpFoldResult; 1]>, + ) -> FoldResult; +} diff --git a/hir/src/ir/traits/info.rs b/hir/src/ir/traits/info.rs new file mode 100644 index 000000000..38064f8bb --- /dev/null +++ b/hir/src/ir/traits/info.rs @@ -0,0 +1,84 @@ +use core::{ + any::{Any, TypeId}, + marker::Unsize, + ptr::{null, DynMetadata, Pointee}, +}; + +#[doc(hidden)] +pub struct TraitInfo { + /// The [TypeId] of the trait type, used as a unique key for [TraitImpl]s + type_id: TypeId, + /// Type-erased dyn metadata containing the trait vtable pointer for the concrete type + /// + /// This is transmuted to the correct trait type when reifying a `&dyn Trait` reference, + /// which is safe as `DynMetadata` is always the same size for all types. + metadata: DynMetadata, +} +impl TraitInfo { + pub fn new() -> Self + where + T: Any + Unsize + crate::verifier::Verifier, + Trait: ?Sized + Pointee> + 'static, + { + let type_id = TypeId::of::(); + let ptr = null::() as *const Trait; + let (_, metadata) = ptr.to_raw_parts(); + Self { + type_id, + metadata: unsafe { + core::mem::transmute::, DynMetadata>(metadata) + }, + } + } + + #[inline(always)] + pub const fn type_id(&self) -> &TypeId { + &self.type_id + } + + /// Obtain the dyn metadata for `Trait` from this info. + /// + /// # Safety + /// + /// This is highly unsafe - you must guarantee that `Trait` is the same type as the one used + /// to create this `TraitInfo` instance. In debug mode, errors like this will be caught, but + /// in release builds, no checks are performed, and absolute havoc will result if you use this + /// incorrectly. + /// + /// It is intended _only_ for use by generated code which has all of the type information + /// available to it statically. It must be public so that operations can be defined in other + /// crates. + pub unsafe fn metadata_unchecked(&self) -> DynMetadata + where + Trait: ?Sized + Pointee> + 'static, + { + debug_assert!(self.type_id == TypeId::of::()); + core::mem::transmute(self.metadata) + } +} +impl Eq for TraitInfo {} +impl PartialEq for TraitInfo { + fn eq(&self, other: &Self) -> bool { + self.type_id == other.type_id + } +} +impl PartialEq for TraitInfo { + fn eq(&self, other: &TypeId) -> bool { + self.type_id.eq(other) + } +} +impl PartialOrd for TraitInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl PartialOrd for TraitInfo { + fn partial_cmp(&self, other: &TypeId) -> Option { + Some(self.type_id.cmp(other)) + } +} +impl Ord for TraitInfo { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.type_id.cmp(&other.type_id) + } +} diff --git a/hir/src/ir/traits/types.rs b/hir/src/ir/traits/types.rs new file mode 100644 index 000000000..592777169 --- /dev/null +++ b/hir/src/ir/traits/types.rs @@ -0,0 +1,589 @@ +use alloc::format; +use core::fmt; + +use midenc_hir_type::PointerType; +use midenc_session::diagnostics::Severity; + +use crate::{derive, ir::value::Value, Context, Op, Operation, Report, Type}; + +/// OpInterface to compute the return type(s) of an operation. +pub trait InferTypeOpInterface: Op { + /// Run type inference for this op's results, using the current state, and apply any changes. + /// + /// Returns an error if unable to infer types, or if some type constraint is violated. + fn infer_return_types(&mut self, context: &Context) -> Result<(), Report>; + + /// Return whether the set sets of types are compatible + fn are_compatible_return_types(&self, lhs: &[Type], rhs: &[Type]) -> bool { + lhs == rhs + } +} + +derive! { + /// Op expects all operands to be of the same type + pub trait SameTypeOperands {} + + verify { + fn operands_are_the_same_type(op: &Operation, context: &Context) -> Result<(), Report> { + let mut operands = op.operands().iter(); + // If there are no operands, then it is trivially true that operands agree on type + let Some(first_operand) = operands.next() else { return Ok(()); }; + let (expected_ty, set_by) = { + let operand = first_operand.borrow(); + let value = operand.value(); + (value.ty().clone(), value.span()) + }; + + for operand in operands { + let operand = operand.borrow(); + let value = operand.value(); + let value_ty = value.ty(); + if value_ty != &expected_ty { + return Err(context + .session() + .diagnostics + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation {}", op.name())) + .with_primary_label( + op.span, + "this operation expects all operands to be of the same type" + ) + .with_secondary_label( + set_by, + "inferred the expected type from this value" + ) + .with_secondary_label( + value.span(), + "which differs from this value's type" + ) + .with_help(format!("expected '{expected_ty}', got '{value_ty}'")) + .into_report() + ); + } + } + + Ok(()) + } + } +} + +derive! { + /// Op expects all operands and results to be of the same type. + /// + /// **NOTE:** Operations that implement this trait must also explicitly implement + /// [`SameTypeOperands`]. This can be achieved by using the "traits" field in the [#operation] + /// macro, as shown below: + /// + /// ```rust,ignore + /// #[operation ( + /// dialect = ArithDialect, + /// traits(UnaryOp, SameTypeOperands, SameOperandsAndResultType), + /// implements(InferTypeOpInterface, MemoryEffectOpInterface) + /// )] + /// pub struct SomeOp { + /// # ... + /// } + /// ``` + pub trait SameOperandsAndResultType: SameTypeOperands {} + + verify { + fn operands_and_result_are_the_same_type(op: &Operation, context: &Context) -> Result<(), Report> { + let mut operands = op.operands().iter(); + // If there are no operands, then it is trivially true that operands and results agree + // on type + let Some(first_operand) = operands.next() else { return Ok(()); }; + let (expected_ty, set_by) = { + let operand = first_operand.borrow(); + let value = operand.value(); + (value.ty().clone(), value.span()) + }; + + let results = op.results(); + assert!(!results.is_empty(), + "Operation: {} was marked as having SameOperandsAndResultType, however it has no results.", op.name()); + + for result in results.iter() { + let result = result.borrow(); + let value = result.as_value_ref().borrow(); + let result_ty = result.ty(); + + if result_ty != &expected_ty { + return Err(context + .session() + .diagnostics + .diagnostic(Severity::Error) + .with_message(::alloc::format!("invalid operation result {}", op.name())) + .with_primary_label( + op.span, + "this operation expects the operands and the results to be of the same type" + ) + .with_secondary_label( + set_by, + "inferred the expected type from this value" + ) + .with_secondary_label( + value.span(), + "which differs from this value's type" + ) + .with_help(format!("expected '{expected_ty}', got '{result_ty}'")) + .into_report() + ); + } + } + + Ok(()) + } + } +} + +/// An operation trait that indicates it expects a variable number of operands, matching the given +/// type constraint, i.e. zero or more of the base type. +pub trait Variadic {} + +impl crate::Verify> for T +where + T: crate::Op + Variadic, +{ + fn verify(&self, _context: &Context) -> Result<(), Report> { + self.as_operation().verify() + } +} +impl crate::Verify> for Operation { + fn should_verify(&self, _context: &Context) -> bool { + self.implements::>() + } + + fn verify(&self, context: &Context) -> Result<(), Report> { + for operand in self.operands().iter() { + let operand = operand.borrow(); + let value = operand.value(); + let ty = value.ty(); + if ::matches(ty) { + continue; + } else { + let description = ::description(); + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid operand") + .with_primary_label( + value.span(), + format!("expected operand type to be {description}, but got {ty}"), + ) + .into_report()); + } + } + + Ok(()) + } +} + +pub trait TypeConstraint: 'static { + fn description() -> impl fmt::Display; + fn matches(ty: &crate::Type) -> bool; +} + +/// A type that can be constructed as a [crate::Type] +pub trait BuildableTypeConstraint: TypeConstraint { + fn build(context: &Context) -> crate::Type; +} + +macro_rules! type_constraint { + ($Constraint:ident, $description:literal, $matcher:literal) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct $Constraint; + impl TypeConstraint for $Constraint { + #[inline(always)] + fn description() -> impl core::fmt::Display { + $description + } + + #[inline(always)] + fn matches(_ty: &$crate::Type) -> bool { + $matcher + } + } + }; + + ($Constraint:ident, $description:literal, $matcher:path) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct $Constraint; + impl TypeConstraint for $Constraint { + #[inline(always)] + fn description() -> impl core::fmt::Display { + $description + } + + #[inline(always)] + fn matches(ty: &$crate::Type) -> bool { + $matcher(ty) + } + } + }; + + ($Constraint:ident, $description:literal, |$matcher_input:ident| $matcher:expr) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + pub struct $Constraint; + impl TypeConstraint for $Constraint { + #[inline(always)] + fn description() -> impl core::fmt::Display { + $description + } + + #[inline(always)] + fn matches($matcher_input: &$crate::Type) -> bool { + $matcher + } + } + }; +} + +type_constraint!(AnyType, "any type", true); +// TODO(pauls): Extend Type with new Function variant, we'll use that to represent function handles +//type_constraint!(AnyFunction, "a function type", crate::Type::is_function); +type_constraint!(AnyList, "any list type", crate::Type::is_list); +type_constraint!(AnyArray, "any array type", crate::Type::is_array); +type_constraint!(AnyStruct, "any struct type", crate::Type::is_struct); +type_constraint!(AnyPointer, "a pointer type", crate::Type::is_pointer); +type_constraint!(AnyInteger, "an integral type", crate::Type::is_integer); +type_constraint!(AnyPointerOrInteger, "an integral or pointer type", |ty| ty.is_pointer() + || ty.is_integer()); +type_constraint!(AnySignedInteger, "a signed integral type", crate::Type::is_signed_integer); +type_constraint!( + AnyUnsignedInteger, + "an unsigned integral type", + crate::Type::is_unsigned_integer +); +type_constraint!(IntFelt, "a field element", crate::Type::is_felt); + +/// A boolean +pub type Bool = SizedInt<1>; + +/// A signless 8-bit integer +pub type Int8 = SizedInt<8>; +/// A signed 8-bit integer +pub type SInt8 = And>; +/// An unsigned 8-bit integer +pub type UInt8 = And>; + +/// A signless 16-bit integer +pub type Int16 = SizedInt<16>; +/// A signed 16-bit integer +pub type SInt16 = And>; +/// An unsigned 16-bit integer +pub type UInt16 = And>; + +/// A signless 32-bit integer +pub type Int32 = SizedInt<32>; +/// A signed 16-bit integer +pub type SInt32 = And>; +/// An unsigned 16-bit integer +pub type UInt32 = And>; + +/// A signless 64-bit integer +pub type Int64 = SizedInt<64>; +/// A signed 64-bit integer +pub type SInt64 = And>; +/// An unsigned 64-bit integer +pub type UInt64 = And>; + +/// A signless 128-bit integer +pub type Int128 = SizedInt<128>; +/// A signed 128-bit integer +pub type SInt128 = And>; +/// An unsigned 128-bit integer +pub type UInt128 = And>; + +impl BuildableTypeConstraint for IntFelt { + fn build(_context: &Context) -> crate::Type { + crate::Type::Felt + } +} +impl BuildableTypeConstraint for Bool { + fn build(_context: &Context) -> crate::Type { + crate::Type::I1 + } +} +impl BuildableTypeConstraint for UInt8 { + fn build(_context: &Context) -> crate::Type { + crate::Type::U8 + } +} +impl BuildableTypeConstraint for SInt8 { + fn build(_context: &Context) -> crate::Type { + crate::Type::I8 + } +} +impl BuildableTypeConstraint for UInt16 { + fn build(_context: &Context) -> crate::Type { + crate::Type::U16 + } +} +impl BuildableTypeConstraint for SInt16 { + fn build(_context: &Context) -> crate::Type { + crate::Type::I16 + } +} +impl BuildableTypeConstraint for UInt32 { + fn build(_context: &Context) -> crate::Type { + crate::Type::U32 + } +} +impl BuildableTypeConstraint for SInt32 { + fn build(_context: &Context) -> crate::Type { + crate::Type::I32 + } +} +impl BuildableTypeConstraint for UInt64 { + fn build(_context: &Context) -> crate::Type { + crate::Type::U64 + } +} +impl BuildableTypeConstraint for SInt64 { + fn build(_context: &Context) -> crate::Type { + crate::Type::I64 + } +} +impl BuildableTypeConstraint for UInt128 { + fn build(_context: &Context) -> crate::Type { + crate::Type::U128 + } +} +impl BuildableTypeConstraint for SInt128 { + fn build(_context: &Context) -> crate::Type { + crate::Type::I128 + } +} + +/// Represents a fixed-width integer of `N` bits +pub struct SizedInt(core::marker::PhantomData<[(); N]>); +impl Copy for SizedInt {} +impl Clone for SizedInt { + fn clone(&self) -> Self { + *self + } +} +impl fmt::Debug for SizedInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(core::any::type_name::()) + } +} +impl fmt::Display for SizedInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{N}-bit integral type") + } +} +impl TypeConstraint for SizedInt { + fn description() -> impl fmt::Display { + Self(core::marker::PhantomData) + } + + fn matches(ty: &crate::Type) -> bool { + ty.is_integer() + } +} +impl BuildableTypeConstraint for SizedInt<8> { + fn build(_context: &Context) -> crate::Type { + crate::Type::I8 + } +} +impl BuildableTypeConstraint for SizedInt<16> { + fn build(_context: &Context) -> crate::Type { + crate::Type::I16 + } +} +impl BuildableTypeConstraint for SizedInt<32> { + fn build(_context: &Context) -> crate::Type { + crate::Type::I32 + } +} +impl BuildableTypeConstraint for SizedInt<64> { + fn build(_context: &Context) -> crate::Type { + crate::Type::I64 + } +} + +/// A type constraint for pointer values +pub struct PointerOf(core::marker::PhantomData); +impl Copy for PointerOf {} +impl Clone for PointerOf { + fn clone(&self) -> Self { + *self + } +} +impl fmt::Debug for PointerOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(core::any::type_name::()) + } +} +impl fmt::Display for PointerOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let pointee = ::description(); + write!(f, "a pointer to {pointee}") + } +} +impl TypeConstraint for PointerOf { + #[inline(always)] + fn description() -> impl fmt::Display { + Self(core::marker::PhantomData) + } + + fn matches(ty: &crate::Type) -> bool { + ty.pointee().is_some_and(|pointee| ::matches(pointee)) + } +} +impl BuildableTypeConstraint for PointerOf { + fn build(context: &Context) -> crate::Type { + crate::Type::from(PointerType::new(::build(context))) + } +} + +/// A type constraint for array values +pub struct AnyArrayOf(core::marker::PhantomData); +impl Copy for AnyArrayOf {} +impl Clone for AnyArrayOf { + fn clone(&self) -> Self { + *self + } +} +impl fmt::Debug for AnyArrayOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(core::any::type_name::()) + } +} +impl fmt::Display for AnyArrayOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let element = ::description(); + write!(f, "an array of {element}") + } +} +impl TypeConstraint for AnyArrayOf { + #[inline(always)] + fn description() -> impl fmt::Display { + Self(core::marker::PhantomData) + } + + fn matches(ty: &crate::Type) -> bool { + match ty { + crate::Type::Array(ty) => ::matches(ty.element_type()), + _ => false, + } + } +} + +/// A type constraint for array values +pub struct ArrayOf(core::marker::PhantomData<[T; N]>); +impl Copy for ArrayOf {} +impl Clone for ArrayOf { + fn clone(&self) -> Self { + *self + } +} +impl fmt::Debug for ArrayOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(core::any::type_name::()) + } +} +impl fmt::Display for ArrayOf { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let element = ::description(); + write!(f, "an array of {N} {element}") + } +} +impl TypeConstraint for ArrayOf { + #[inline(always)] + fn description() -> impl fmt::Display { + Self(core::marker::PhantomData) + } + + fn matches(ty: &crate::Type) -> bool { + match ty { + crate::Type::Array(ty) if ty.len() == N => { + ::matches(ty.element_type()) + } + _ => false, + } + } +} +impl BuildableTypeConstraint for ArrayOf { + fn build(context: &Context) -> crate::Type { + let element = ::build(context); + crate::Type::from(crate::ArrayType::new(element, N)) + } +} + +/// Represents a conjunction of two constraints as a concrete value +pub struct And { + _left: core::marker::PhantomData, + _right: core::marker::PhantomData, +} +impl Copy for And {} +impl Clone for And { + fn clone(&self) -> Self { + *self + } +} +impl fmt::Debug for And { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(core::any::type_name::()) + } +} +impl TypeConstraint for And { + fn description() -> impl fmt::Display { + struct Both { + left: L, + right: R, + } + impl fmt::Display for Both { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "both {} and {}", &self.left, &self.right) + } + } + let left = ::description(); + let right = ::description(); + Both { left, right } + } + + #[inline] + fn matches(ty: &crate::Type) -> bool { + ::matches(ty) && ::matches(ty) + } +} + +/// Represents a disjunction of two constraints as a concrete value +pub struct Or { + _left: core::marker::PhantomData, + _right: core::marker::PhantomData, +} +impl Copy for Or {} +impl Clone for Or { + fn clone(&self) -> Self { + *self + } +} +impl fmt::Debug for Or { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(core::any::type_name::()) + } +} +impl TypeConstraint for Or { + fn description() -> impl fmt::Display { + struct Either { + left: L, + right: R, + } + impl fmt::Display for Either { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "either {} or {}", &self.left, &self.right) + } + } + let left = ::description(); + let right = ::description(); + Either { left, right } + } + + #[inline] + fn matches(ty: &crate::Type) -> bool { + ::matches(ty) || ::matches(ty) + } +} diff --git a/hir/src/ir/types.rs b/hir/src/ir/types.rs new file mode 100644 index 000000000..aba5bea0a --- /dev/null +++ b/hir/src/ir/types.rs @@ -0,0 +1 @@ +pub use midenc_hir_type::*; diff --git a/hir/src/ir/usable.rs b/hir/src/ir/usable.rs new file mode 100644 index 000000000..846fab6fe --- /dev/null +++ b/hir/src/ir/usable.rs @@ -0,0 +1,58 @@ +use super::{ + entity::EntityIter, EntityCursor, EntityCursorMut, EntityList, EntityListItem, + UnsafeIntrusiveEntityRef, +}; + +/// The [Usable] trait is implemented for IR entities which are _defined_ and _used_, and as a +/// result, require a data structure called the _use-def list_. +/// +/// A _definition_ of an IR entity, is a unique instantiation of that entity, the result of which +/// is different from all other definitions, even if the data associated with that definition is +/// the same as another definition. For example, SSA values are defined as either block arguments +/// or operation results, and a given value can only be defined once. +/// +/// A _use_ represents a unique reference to a _definition_ of some IR entity. Each use is unique, +/// and can be used to obtain not only the _user_ of the reference, but the location of that use +/// within the user. Uses are tracked in a _use list_, also called the _use-def list_, which +/// associates all uses to the definition, or _def_, that they reference. For example, operations +/// in HIR _use_ SSA values defined previously in the program. +/// +/// A _user_ does not have to be of the same IR type as the _definition_, and the type representing +/// the _use_ is typically different than both, and represents the type of relationship between the +/// two. For example, an `OpOperand` represents a single use of a `Value` by an `Op`. The entity +/// being defined is a `Value`, the entity using that definition is an `Op`, and the data associated +/// with each use is represented by `OpOperand`. +pub trait Usable { + /// The type associated with each unique use, e.g. `OpOperand` + type Use: EntityListItem; + + /// Get a list of uses of this definition + fn uses(&self) -> &EntityList; + /// Get a mutable list of uses of this definition + fn uses_mut(&mut self) -> &mut EntityList; + + /// Returns true if this definition is used + #[inline] + fn is_used(&self) -> bool { + !self.uses().is_empty() + } + /// Get an iterator over the uses of this definition + #[inline] + fn iter_uses(&self) -> EntityIter<'_, Self::Use> { + self.uses().iter() + } + /// Get a cursor positioned on the first use of this definition, or the null cursor if unused. + fn first_use(&self) -> EntityCursor<'_, Self::Use> { + self.uses().front() + } + /// Get a mutable cursor positioned on the first use of this definition, or the null cursor if + /// unused. + #[inline] + fn first_use_mut(&mut self) -> EntityCursorMut<'_, Self::Use> { + self.uses_mut().front_mut() + } + /// Add `user` to the set of uses of this definition + fn insert_use(&mut self, user: UnsafeIntrusiveEntityRef) { + self.uses_mut().push_back(user); + } +} diff --git a/hir/src/ir/value.rs b/hir/src/ir/value.rs new file mode 100644 index 000000000..85f4e760d --- /dev/null +++ b/hir/src/ir/value.rs @@ -0,0 +1,454 @@ +mod aliasing; +mod range; +mod stack; + +use core::{any::Any, fmt}; + +pub use self::{ + aliasing::ValueOrAlias, + range::{AsValueRange, ValueRange}, + stack::StackOperand, +}; +use super::*; +use crate::{DynHash, DynPartialEq}; + +/// A unique identifier for a [Value] in the IR +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct ValueId(u32); +impl ValueId { + pub const fn from_u32(id: u32) -> Self { + Self(id) + } + + pub const fn as_u32(&self) -> u32 { + self.0 + } +} +impl EntityId for ValueId { + #[inline(always)] + fn as_usize(&self) -> usize { + self.0 as usize + } +} +impl fmt::Debug for ValueId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "v{}", &self.0) + } +} +impl fmt::Display for ValueId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "v{}", &self.0) + } +} + +/// Represents an SSA value in the IR. +/// +/// The data underlying a [Value] represents a _definition_, and thus implements [Usable]. The users +/// of a [Value] are operands (see [OpOperandImpl]). Operands are associated with an operation. Thus +/// the graph formed of the edges between values and operations via operands forms the data-flow +/// graph of the program. +pub trait Value: + Any + + EntityWithId + + Spanned + + Usable + + fmt::Debug + + fmt::Display + + DynPartialEq + + DynHash +{ + /// Set the source location of this value + fn set_span(&mut self, span: SourceSpan); + /// Get the type of this value + fn ty(&self) -> &Type; + /// Set the type of this value + fn set_type(&mut self, ty: Type); + /// Get the defining operation for this value, _if_ defined by an operation. + /// + /// Returns `None` if this value is defined by other means than an operation result. + fn get_defining_op(&self) -> Option; + /// Get the region which contains the definition of this value + fn parent_region(&self) -> Option { + self.parent_block().and_then(|block| block.parent()) + } + /// Get the block which contains the definition of this value + fn parent_block(&self) -> Option; + /// Returns true if this value is used outside of the given block + fn is_used_outside_of_block(&self, block: &BlockRef) -> bool { + self.iter_uses() + .any(|user| user.owner.parent().is_some_and(|blk| !BlockRef::ptr_eq(&blk, block))) + } + /// Replace all uses of `self` with `replacement` + fn replace_all_uses_with(&mut self, mut replacement: ValueRef) { + let mut cursor = self.uses_mut().front_mut(); + while let Some(mut user) = cursor.as_pointer() { + // Rewrite use of `self` with `replacement` + { + let mut user = user.borrow_mut(); + user.value = Some(replacement); + } + // Remove `user` from the use list of `self` + cursor.remove(); + // Add `user` to the use list of `replacement` + replacement.borrow_mut().insert_use(user); + } + } + /// Replace all uses of `self` with `replacement` unless the user is in `exceptions` + fn replace_all_uses_except(&mut self, mut replacement: ValueRef, exceptions: &[OperationRef]) { + let mut cursor = self.uses_mut().front_mut(); + while let Some(mut user) = cursor.as_pointer() { + // Rewrite use of `self` with `replacement` if user not in `exceptions` + { + let mut user = user.borrow_mut(); + if exceptions.contains(&user.owner) { + cursor.move_next(); + continue; + } + user.value = Some(replacement); + } + // Remove `user` from the use list of `self` + cursor.remove(); + // Add `user` to the use list of `replacement` + replacement.borrow_mut().insert_use(user); + } + } +} + +impl dyn Value { + #[inline] + pub fn is(&self) -> bool { + (self as &dyn Any).is::() + } + + #[inline] + pub fn downcast_ref(&self) -> Option<&T> { + (self as &dyn Any).downcast_ref::() + } + + #[inline] + pub fn downcast_mut(&mut self) -> Option<&mut T> { + (self as &mut dyn Any).downcast_mut::() + } + + /// Replace all uses of `self` with `replacement` if `should_replace` returns true + pub fn replace_uses_with_if(&mut self, mut replacement: ValueRef, should_replace: F) + where + F: Fn(&OpOperandImpl) -> bool, + { + let mut cursor = self.uses_mut().front_mut(); + while let Some(mut user) = cursor.as_pointer() { + // Rewrite use of `self` with `replacement` if `should_replace` returns true + { + let mut user = user.borrow_mut(); + if !should_replace(&user) { + cursor.move_next(); + continue; + } + user.value = Some(replacement); + } + // Remove `user` from the use list of `self` + cursor.remove(); + // Add `user` to the use list of `replacement` + replacement.borrow_mut().insert_use(user); + } + } +} + +/// Generates the boilerplate for a concrete [Value] type. +macro_rules! value_impl { + ( + $(#[$outer:meta])* + $vis:vis struct $ValueKind:ident { + $(#[doc $($owner_doc_args:tt)*])* + owner: $OwnerTy:ty, + $(#[doc $($index_doc_args:tt)*])* + index: u8, + $( + $(#[$inner:ident $($args:tt)*])* + $Field:ident: $FieldTy:ty, + )* + } + + fn get_defining_op(&$GetDefiningOpSelf:ident) -> Option $GetDefiningOp:block + + fn parent_block(&$ParentBlockSelf:ident) -> Option $ParentBlock:block + + $($t:tt)* + ) => { + $(#[$outer])* + #[derive(Spanned)] + $vis struct $ValueKind { + id: ValueId, + #[span] + span: SourceSpan, + ty: Type, + uses: OpOperandList, + owner: $OwnerTy, + index: u8, + $( + $(#[$inner $($args)*])* + $Field: $FieldTy + ),* + } + + + impl $ValueKind { + pub fn new( + span: SourceSpan, + id: ValueId, + ty: Type, + owner: $OwnerTy, + index: u8, + $( + $Field: $FieldTy + ),* + ) -> Self { + Self { + id, + ty, + span, + uses: Default::default(), + owner, + index, + $( + $Field + ),* + } + } + + $(#[doc $($owner_doc_args)*])* + pub fn owner(&self) -> $OwnerTy { + self.owner.clone() + } + + $(#[doc $($index_doc_args)*])* + pub fn index(&self) -> usize { + self.index as usize + } + } + + impl Value for $ValueKind { + fn ty(&self) -> &Type { + &self.ty + } + + fn set_span(&mut self, span: SourceSpan) { + self.span = span; + } + + fn set_type(&mut self, ty: Type) { + self.ty = ty; + } + + fn get_defining_op(&$GetDefiningOpSelf) -> Option $GetDefiningOp + + fn parent_block(&$ParentBlockSelf) -> Option $ParentBlock + } + + impl Entity for $ValueKind {} + impl EntityWithId for $ValueKind { + type Id = ValueId; + + #[inline(always)] + fn id(&self) -> Self::Id { + self.id + } + } + + impl EntityParent for $ValueKind { + fn offset() -> usize { + core::mem::offset_of!($ValueKind, uses) + } + } + + impl Usable for $ValueKind { + type Use = OpOperandImpl; + + #[inline(always)] + fn uses(&self) -> &OpOperandList { + &self.uses + } + + #[inline(always)] + fn uses_mut(&mut self) -> &mut OpOperandList { + &mut self.uses + } + } + + + impl Eq for $ValueKind {} + + impl PartialEq for $ValueKind { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } + } + + impl Ord for $ValueKind { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.id.cmp(&other.id) + } + } + + impl PartialOrd for $ValueKind { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl core::hash::Hash for $ValueKind { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } + } + + impl fmt::Display for $ValueKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use crate::formatter::PrettyPrint; + + self.pretty_print(f) + } + } + + impl fmt::Debug for $ValueKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut builder = f.debug_struct(stringify!($ValueKind)); + builder + .field("id", &self.id) + .field("ty", &self.ty) + .field("index", &self.index) + .field("is_used", &(!self.uses.is_empty())); + + $( + builder.field(stringify!($Field), &self.$Field); + )* + + builder.finish_non_exhaustive() + } + } + + $($t)* + } +} + +/// A pointer to a [Value] +pub type ValueRef = UnsafeEntityRef; +/// A pointer to a [BlockArgument] +pub type BlockArgumentRef = UnsafeEntityRef; +/// A pointer to a [OpResult] +pub type OpResultRef = UnsafeEntityRef; + +value_impl!( + /// A [BlockArgument] represents the definition of a [Value] by a block parameter + pub struct BlockArgument { + /// Get the [Block] to which this [BlockArgument] belongs + owner: BlockRef, + /// Get the index of this argument in the argument list of the owning [Block] + index: u8, + } + + fn get_defining_op(&self) -> Option { + None + } + + fn parent_block(&self) -> Option { + Some(self.owner) + } +); + +impl BlockArgument { + #[inline] + pub fn as_value_ref(&self) -> ValueRef { + self.as_block_argument_ref() as ValueRef + } + + #[inline] + pub fn as_block_argument_ref(&self) -> BlockArgumentRef { + unsafe { BlockArgumentRef::from_raw(self) } + } +} + +impl crate::formatter::PrettyPrint for BlockArgument { + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + display(self.id) + const_text(": ") + self.ty.render() + } +} + +impl StorableEntity for BlockArgument { + #[inline(always)] + fn index(&self) -> usize { + self.index as usize + } + + unsafe fn set_index(&mut self, index: usize) { + self.index = index.try_into().expect("too many block arguments"); + } +} + +pub type BlockArgumentRange<'a> = crate::EntityRange<'a, BlockArgumentRef>; +pub type BlockArgumentRangeMut<'a> = crate::EntityRangeMut<'a, BlockArgumentRef, 1>; + +value_impl!( + /// An [OpResult] represents the definition of a [Value] by the result of an [Operation] + pub struct OpResult { + /// Get the [Operation] to which this [OpResult] belongs + owner: OperationRef, + /// Get the index of this result in the result list of the owning [Operation] + index: u8, + } + + fn get_defining_op(&self) -> Option { + Some(self.owner) + } + + fn parent_block(&self) -> Option { + self.owner.parent() + } +); + +impl OpResult { + #[inline] + pub fn as_value_ref(&self) -> ValueRef { + unsafe { ValueRef::from_raw(self as &dyn Value) } + } + + #[inline] + pub fn as_op_result_ref(&self) -> OpResultRef { + unsafe { OpResultRef::from_raw(self) } + } +} + +impl crate::formatter::PrettyPrint for OpResult { + #[inline] + fn render(&self) -> crate::formatter::Document { + use crate::formatter::*; + + display(self.id) + } +} + +impl StorableEntity for OpResult { + #[inline(always)] + fn index(&self) -> usize { + self.index as usize + } + + unsafe fn set_index(&mut self, index: usize) { + self.index = index.try_into().expect("too many op results"); + } + + /// Unlink all users of this result + /// + /// The users will still refer to this result, but the use list of this value will be empty + fn unlink(&mut self) { + let uses = self.uses_mut(); + uses.clear(); + } +} + +pub type OpResultStorage = crate::EntityStorage; +pub type OpResultRange<'a> = crate::EntityRange<'a, OpResultRef>; +pub type OpResultRangeMut<'a> = crate::EntityRangeMut<'a, OpResultRef, 1>; diff --git a/hir/src/ir/value/aliasing.rs b/hir/src/ir/value/aliasing.rs new file mode 100644 index 000000000..2c2a82ac2 --- /dev/null +++ b/hir/src/ir/value/aliasing.rs @@ -0,0 +1,171 @@ +use core::{fmt, num::NonZeroU8}; + +use super::*; + +/// This is a essentialy a [ValueRef], but with a modified encoding that lets us uniquely identify +/// aliases of a value on the operand stack during analysis. +/// +/// Aliases of a value are treated as unique values for purposes of operand stack management, but +/// are associated with multiple copies of a value on the stack. +#[derive(Copy, Clone)] +pub struct ValueOrAlias { + /// The SSA value of this operand + value: ValueRef, + /// To avoid unnecessary borrowing of `value`, we cache the ValueId here + value_id: ValueId, + /// When an SSA value is used multiple times by an instruction, each use must be accounted for + /// on the operand stack as a unique value. The alias identifier is usually generated from a + /// counter of the unique instances of the value, but can be any unique integer value. + alias_id: u8, +} + +impl ValueOrAlias { + /// Create a new [ValueOrAlias] from the given [ValueRef] + pub fn new(value: ValueRef) -> Self { + let value_id = value.borrow().id(); + Self { + value, + value_id, + alias_id: 0, + } + } + + /// Gets the effective size of this type on the Miden operand stack + pub fn stack_size(&self) -> usize { + self.value.borrow().ty().size_in_felts() + } + + /// Create an aliased copy of this value, using `id` to uniquely identify the alias. + /// + /// NOTE: You must ensure that each alias of the same value gets a unique identifier, + /// or you may observe strange behavior due to two aliases that should be distinct, + /// being treated as if they have the same identity. + pub fn copy(mut self, id: NonZeroU8) -> Self { + self.alias_id = id.get(); + self + } + + /// Get an un-aliased copy of this value + pub fn unaliased(mut self) -> Self { + self.alias_id = 0; + self + } + + /// Convert this value into an alias, using `id` to uniquely identify the alias. + /// + /// NOTE: You must ensure that each alias of the same value gets a unique identifier, + /// or you may observe strange behavior due to two aliases that should be distinct, + /// being treated as if they have the same identity. + pub fn set_alias(&mut self, id: NonZeroU8) { + self.alias_id = id.get(); + } + + /// Get the underlying [ValueRef] + pub fn value(self) -> ValueRef { + self.value + } + + /// Borrow the underlying [Value] + pub fn borrow_value(&self) -> EntityRef<'_, dyn Value> { + self.value.borrow() + } + + /// Get the unique alias identifier for this value, if this value is an alias + pub fn alias(self) -> Option { + NonZeroU8::new(self.alias_id) + } + + /// Get the unique alias identifier for this value, if this value is an alias + pub fn unwrap_alias(self) -> NonZeroU8 { + NonZeroU8::new(self.alias_id).unwrap_or_else(|| panic!("expected {self:?} to be an alias")) + } + + /// Returns true if this value is an alias + pub fn is_alias(&self) -> bool { + self.alias_id != 0 + } +} + +impl core::borrow::Borrow for ValueOrAlias { + #[inline(always)] + fn borrow(&self) -> &ValueRef { + &self.value + } +} + +impl core::borrow::Borrow for &ValueOrAlias { + #[inline(always)] + fn borrow(&self) -> &ValueRef { + &self.value + } +} + +impl core::borrow::Borrow for &mut ValueOrAlias { + #[inline(always)] + fn borrow(&self) -> &ValueRef { + &self.value + } +} + +impl Eq for ValueOrAlias {} + +impl PartialEq for ValueOrAlias { + fn eq(&self, other: &Self) -> bool { + self.value_id == other.value_id && self.alias_id == other.alias_id + } +} + +impl core::hash::Hash for ValueOrAlias { + fn hash(&self, state: &mut H) { + self.value_id.hash(state); + self.alias_id.hash(state); + } +} + +impl Ord for ValueOrAlias { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.value_id.cmp(&other.value_id).then(self.alias_id.cmp(&other.alias_id)) + } +} + +impl PartialEq for ValueOrAlias { + fn eq(&self, other: &ValueRef) -> bool { + &self.value == other + } +} + +impl PartialOrd for ValueOrAlias { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for ValueOrAlias { + #[inline] + fn from(value: ValueRef) -> Self { + Self::new(value) + } +} + +impl From for ValueRef { + #[inline] + fn from(value: ValueOrAlias) -> Self { + value.value + } +} + +impl fmt::Display for ValueOrAlias { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.alias() { + None => write!(f, "{}", &self.value_id), + Some(alias) => write!(f, "{}.{alias}", &self.value_id), + } + } +} + +impl fmt::Debug for ValueOrAlias { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} diff --git a/hir/src/ir/value/range.rs b/hir/src/ir/value/range.rs new file mode 100644 index 000000000..c72e544d9 --- /dev/null +++ b/hir/src/ir/value/range.rs @@ -0,0 +1,466 @@ +use core::borrow::Borrow; + +use smallvec::{smallvec, SmallVec}; + +use super::*; +use crate::adt::SmallSet; + +/// An abstraction over the different type of [Value]-derived entity ranges, e.g. operands, results, +/// block arguments, vs type-erased collections. +/// +/// This range types supports fewer conveniences than [EntityRange] or raw slices provide, as we +/// are not able to handle all ranges exactly the same (for example, borrowing an element from the +/// range works for all but op operands, as those have an extra layer of indirection). +/// +/// In general, this should be used in only narrow circumstances where a more specific range type +/// cannot be used. +pub enum ValueRange<'a, const N: usize = 2> { + /// A default-initialized empty range + Empty, + /// The values in the range are type-erased, but owned + Owned(SmallVec<[ValueRef; N]>), + /// The values in the range are type-erased, but borrowed + Borrowed(&'a [ValueRef]), + /// The value range contains block arguments + BlockArguments(&'a [BlockArgumentRef]), + /// The value range contains operands + Operands(&'a [OpOperand]), + /// The value range contains results + Results(&'a [OpResultRef]), +} + +impl Default for ValueRange<'_, N> { + fn default() -> Self { + Self::Empty + } +} + +impl<'values, const N: usize> ValueRange<'values, N> { + /// Returns true if this range is empty + pub fn is_empty(&self) -> bool { + match self { + Self::Empty => true, + Self::Owned(values) => values.is_empty(), + Self::Borrowed(values) => values.is_empty(), + Self::BlockArguments(range) => range.is_empty(), + Self::Operands(range) => range.is_empty(), + Self::Results(range) => range.is_empty(), + } + } + + /// Returns the number of values in the range + pub fn len(&self) -> usize { + match self { + Self::Empty => 0, + Self::Owned(values) => values.len(), + Self::Borrowed(values) => values.len(), + Self::BlockArguments(range) => range.len(), + Self::Operands(range) => range.len(), + Self::Results(range) => range.len(), + } + } + + pub fn slice<'a, 'b: 'a + 'values, R>(&'b self, range: R) -> ValueRange<'a, N> + where + R: core::slice::SliceIndex<[ValueRef], Output = [ValueRef]>, + R: core::slice::SliceIndex<[BlockArgumentRef], Output = [BlockArgumentRef]>, + R: core::slice::SliceIndex<[OpOperand], Output = [OpOperand]>, + R: core::slice::SliceIndex<[OpResultRef], Output = [OpResultRef]>, + { + match self { + Self::Empty => ValueRange::Empty, + Self::Owned(values) => Self::Borrowed(&values[range]), + Self::Borrowed(values) => Self::Borrowed(&values[range]), + Self::BlockArguments(values) => Self::BlockArguments(&values[range]), + Self::Operands(values) => Self::Operands(&values[range]), + Self::Results(values) => Self::Results(&values[range]), + } + } + + /// Returns the value at `index` in the range + pub fn get(&self, index: usize) -> Option { + match self { + Self::Empty => None, + Self::Owned(values) => values.get(index).cloned(), + Self::Borrowed(values) => values.get(index).cloned(), + Self::BlockArguments(range) => { + range.get(index).map(|operand| operand.borrow().as_value_ref()) + } + Self::Operands(range) => { + range.get(index).map(|operand| operand.borrow().as_value_ref()) + } + Self::Results(range) => range.get(index).map(|result| result.borrow().as_value_ref()), + } + } + + /// Returns true if `value` is present in this range + pub fn contains(&self, value: V) -> bool + where + V: Borrow, + { + let value = value.borrow(); + match self { + Self::Empty => false, + Self::Owned(values) => values.contains(value), + Self::Borrowed(values) => values.contains(value), + Self::BlockArguments(args) => args.iter().copied().any(|arg| arg as ValueRef == *value), + Self::Operands(operands) => { + operands.iter().any(|operand| operand.borrow().as_value_ref() == *value) + } + Self::Results(results) => { + results.iter().copied().any(|result| result as ValueRef == *value) + } + } + } + + /// Iterate over the values in this range as [ValueRef]s + pub fn iter(&self) -> ValueRangeIter<'_, N> { + match self { + Self::Empty => ValueRangeIter::new(ValueRange::Borrowed(&[])), + Self::Owned(values) => ValueRangeIter::new(ValueRange::Borrowed(values.as_slice())), + Self::Borrowed(values) => ValueRangeIter::new(ValueRange::Borrowed(values)), + Self::BlockArguments(values) => ValueRangeIter::new(ValueRange::BlockArguments(values)), + Self::Operands(values) => ValueRangeIter::new(ValueRange::Operands(values)), + Self::Results(values) => ValueRangeIter::new(ValueRange::Results(values)), + } + } + + /// Convert this into an owned [ValueRange] with static lifetime + pub fn into_owned(self) -> ValueRange<'static, N> { + ValueRange::Owned(self.into_smallvec()) + } + + /// Convert this into an owned [SmallVec] of the underlying values. + pub fn into_smallvec(self) -> SmallVec<[ValueRef; N]> { + match self { + Self::Empty => smallvec![], + Self::Owned(values) => values, + Self::Borrowed(values) => SmallVec::from_slice(values), + Self::BlockArguments(args) => args.iter().copied().map(|arg| arg as ValueRef).collect(), + Self::Operands(operands) => { + operands.iter().map(|operand| operand.borrow().as_value_ref()).collect() + } + Self::Results(results) => { + results.iter().copied().map(|result| result as ValueRef).collect() + } + } + } + + /// Obtain a [alloc::vec::Vec] from this range. + pub fn to_vec(&self) -> alloc::vec::Vec { + match self { + Self::Empty => Default::default(), + Self::Owned(values) => values.to_vec(), + Self::Borrowed(values) => values.to_vec(), + Self::BlockArguments(args) => args.iter().copied().map(|arg| arg as ValueRef).collect(), + Self::Operands(operands) => { + operands.iter().map(|operand| operand.borrow().as_value_ref()).collect() + } + Self::Results(results) => { + results.iter().copied().map(|result| result as ValueRef).collect() + } + } + } +} + +impl core::fmt::Debug for ValueRange<'_, N> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl core::fmt::Display for ValueRange<'_, N> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut builder = f.debug_list(); + for value in self.iter() { + builder.entry_with(|f| write!(f, "{value}")); + } + + builder.finish() + } +} + +impl FromIterator for ValueRange<'static, N> { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + Self::Owned(SmallVec::from_iter(iter)) + } +} + +impl FromIterator for ValueRange<'static, N> { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + Self::from_iter(iter.into_iter().map(|arg| arg as ValueRef)) + } +} + +impl FromIterator for ValueRange<'static, N> { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + Self::from_iter(iter.into_iter().map(|result| result as ValueRef)) + } +} + +impl FromIterator for ValueRange<'static, N> { + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + Self::from_iter(iter.into_iter().map(|operand| operand.borrow().as_value_ref())) + } +} + +impl<'a, const N: usize> IntoIterator for ValueRange<'a, N> { + type IntoIter = ValueRangeIter<'a, N>; + type Item = ValueRef; + + fn into_iter(self) -> Self::IntoIter { + ValueRangeIter::new(self) + } +} + +impl<'a, 'b: 'a, const N: usize> IntoIterator for &'a ValueRange<'b, N> { + type IntoIter = ValueRangeIter<'a, N>; + type Item = ValueRef; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl From> for ValueRange<'static, N> { + fn from(values: SmallVec<[ValueRef; N]>) -> Self { + Self::Owned(SmallVec::from_slice(&values)) + } +} + +impl<'a, const M: usize, const N: usize> From<&'a SmallVec<[ValueRef; M]>> for ValueRange<'a, N> { + fn from(values: &'a SmallVec<[ValueRef; M]>) -> Self { + Self::Borrowed(values.as_slice()) + } +} + +impl From> for ValueRange<'static, N> { + fn from(values: SmallSet) -> Self { + Self::Owned(values.into_vec()) + } +} + +impl<'a, const M: usize, const N: usize> From<&'a SmallSet> for ValueRange<'a, N> { + fn from(values: &'a SmallSet) -> Self { + Self::Borrowed(values.as_slice()) + } +} + +impl<'a, const N: usize> From<&'a [ValueRef]> for ValueRange<'a, N> { + fn from(values: &'a [ValueRef]) -> Self { + Self::Borrowed(values) + } +} + +impl<'a, const N: usize> From> for ValueRange<'a, N> { + fn from(range: BlockArgumentRange<'a>) -> Self { + Self::BlockArguments(range.as_slice()) + } +} + +impl<'a, const N: usize> From<&'a [BlockArgumentRef]> for ValueRange<'a, N> { + fn from(range: &'a [BlockArgumentRef]) -> Self { + Self::BlockArguments(range) + } +} + +impl<'a, const N: usize> From> for ValueRange<'a, N> { + fn from(range: OpOperandRange<'a>) -> Self { + Self::Operands(range.as_slice()) + } +} + +impl<'a, const N: usize> From<&'a [OpOperand]> for ValueRange<'a, N> { + fn from(range: &'a [OpOperand]) -> Self { + Self::Operands(range) + } +} + +impl<'a, const N: usize> From> for ValueRange<'a, N> { + fn from(range: OpResultRange<'a>) -> Self { + Self::Results(range.as_slice()) + } +} + +impl<'a, const N: usize> From<&'a [OpResultRef]> for ValueRange<'a, N> { + fn from(range: &'a [OpResultRef]) -> Self { + Self::Results(range) + } +} + +impl<'a, const N: usize> From> for ValueRange<'a, N> { + fn from(range: SuccessorOperandRange<'a>) -> Self { + Self::from(range.into_forwarded()) + } +} + +pub trait AsValueRange { + fn as_value_range(&self) -> ValueRange<'_, 2>; +} + +impl AsValueRange for Option { + fn as_value_range(&self) -> ValueRange<'_, 2> { + match self.as_ref() { + Some(values) => values.as_value_range(), + None => ValueRange::Empty, + } + } +} + +impl AsValueRange for [ValueRef] { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Borrowed(self) + } +} + +impl AsValueRange for [BlockArgumentRef] { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::BlockArguments(self) + } +} + +impl AsValueRange for [OpOperand] { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Operands(self) + } +} + +impl AsValueRange for [OpResultRef] { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Results(self) + } +} + +impl AsValueRange for alloc::vec::Vec { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Borrowed(self.as_slice()) + } +} + +impl AsValueRange for SmallVec<[ValueRef; N]> { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Borrowed(self.as_slice()) + } +} + +impl AsValueRange for OpOperandStorage { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Operands(self.all().as_slice()) + } +} + +impl AsValueRange for OpResultStorage { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Results(self.all().as_slice()) + } +} + +impl AsValueRange for OpOperandRange<'_> { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Operands(self.as_slice()) + } +} + +impl AsValueRange for OpOperandRangeMut<'_> { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Operands(self.as_slice()) + } +} + +impl AsValueRange for OpResultRange<'_> { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Results(self.as_slice()) + } +} + +impl AsValueRange for OpResultRangeMut<'_> { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::Results(self.as_slice()) + } +} + +impl AsValueRange for BlockArgumentRange<'_> { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::BlockArguments(self.as_slice()) + } +} + +impl AsValueRange for BlockArgumentRangeMut<'_> { + fn as_value_range(&self) -> ValueRange<'_, 2> { + ValueRange::BlockArguments(self.as_slice()) + } +} + +/// An iterator consuming the contents of a [ValueRange] +pub struct ValueRangeIter<'a, const N: usize> { + range: ValueRange<'a, N>, + index: usize, +} + +impl<'a, const N: usize> ValueRangeIter<'a, N> { + pub fn new(range: ValueRange<'a, N>) -> Self { + Self { range, index: 0 } + } +} + +impl core::iter::FusedIterator for ValueRangeIter<'_, N> {} +impl ExactSizeIterator for ValueRangeIter<'_, N> { + fn len(&self) -> usize { + let len = self.range.len(); + len.saturating_sub(self.index) + } +} +impl Iterator for ValueRangeIter<'_, N> { + type Item = ValueRef; + + fn next(&mut self) -> Option { + let len = self.range.len(); + if self.index >= len { + return None; + } + + let index = self.index; + self.index += 1; + match &self.range { + ValueRange::Empty => None, + ValueRange::Owned(values) => values.get(index).cloned(), + ValueRange::Borrowed(values) => values.get(index).cloned(), + ValueRange::BlockArguments(range) => { + range.get(index).map(|o| o.borrow().as_value_ref()) + } + ValueRange::Operands(range) => range.get(index).map(|o| o.borrow().as_value_ref()), + ValueRange::Results(range) => range.get(index).map(|o| o.borrow().as_value_ref()), + } + } +} + +impl DoubleEndedIterator for ValueRangeIter<'_, N> { + fn next_back(&mut self) -> Option { + let len = self.range.len(); + let index = len.checked_sub(self.index + 1)?; + + self.index += 1; + match &self.range { + ValueRange::Empty => None, + ValueRange::Owned(values) => values.get(index).cloned(), + ValueRange::Borrowed(values) => values.get(index).cloned(), + ValueRange::BlockArguments(range) => { + range.get(index).map(|o| o.borrow().as_value_ref()) + } + ValueRange::Operands(range) => range.get(index).map(|o| o.borrow().as_value_ref()), + ValueRange::Results(range) => range.get(index).map(|o| o.borrow().as_value_ref()), + } + } +} diff --git a/hir/src/ir/value/stack.rs b/hir/src/ir/value/stack.rs new file mode 100644 index 000000000..fca8e13d8 --- /dev/null +++ b/hir/src/ir/value/stack.rs @@ -0,0 +1,45 @@ +use super::*; + +/// This is an simple representation of a [ValueRef] on the Miden operand stack +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct StackOperand { + /// The value this operand corresponds to + pub value: ValueOrAlias, + /// The position of this operand on the corresponding stack + pub pos: u8, +} + +impl core::fmt::Display for StackOperand { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{}:{}", &self.pos, &self.value) + } +} + +impl Ord for StackOperand { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.pos.cmp(&other.pos).then(self.value.cmp(&other.value)) + } +} + +impl PartialOrd for StackOperand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From<(usize, ValueOrAlias)> for StackOperand { + #[inline(always)] + fn from(pair: (usize, ValueOrAlias)) -> Self { + Self { + pos: pair.0 as u8, + value: pair.1, + } + } +} + +impl PartialEq for StackOperand { + #[inline(always)] + fn eq(&self, other: &ValueOrAlias) -> bool { + self.value.eq(other) + } +} diff --git a/hir/src/ir/verifier.rs b/hir/src/ir/verifier.rs new file mode 100644 index 000000000..0edd7fec3 --- /dev/null +++ b/hir/src/ir/verifier.rs @@ -0,0 +1,262 @@ +use super::{Context, Report}; + +/// The `OpVerifier` trait is expected to be implemented by all [Op] impls as a prequisite. +/// +/// The actual implementation is typically generated as part of deriving [Op]. +pub trait OpVerifier { + fn verify(&self, context: &Context) -> Result<(), Report>; +} + +/// The `Verify` trait represents verification logic associated with implementations of some trait. +/// +/// This is specifically used for automatically deriving verification checks for [Op] impls that +/// implement traits that imply constraints on the representation or behavior of that op. +/// +/// For example, if some [Op] derives an op trait like `SingleBlock`, this information is recorded +/// in the underlying [Operation] metadata, so that we can recover a trait object reference for the +/// trait when needed. However, just deriving the trait is not sufficient to guarantee that the op +/// actually adheres to the implicit constraints and behavior of that trait. For example, +/// `SingleBlock` implies that the implementing op contains only regions that consist of a single +/// [Block]. This cannot be checked statically. The first step to addressing this though, is to +/// reify the implicit validation rules as explicit checks - hence this trait. +/// +/// So we've established that some op traits, such as `SingleBlock` mentioned above, have implicit +/// validation rules, and we can implement [Verify] to make the implicit validation rules of such +/// traits explicit - but how do we ensure that when an op derives an op trait, that the [Verify] +/// impl is also derived, _and_ that it is called when the op is verified? +/// +/// The answer lies in the use of some tricky type-level code to accomplish the following goals: +/// +/// * Do not emit useless checks for op traits that have no verification rules +/// * Do not require storing data in each instance of an [Op] just to verify a trait +/// * Do not require emitting a bunch of redundant type checks for information we know statically +/// * Be able to automatically derive all of the verification machinery along with the op traits +/// +/// The way this works is as follows: +/// +/// * We `impl Verify for T where T: Op` for every trait `Trait` with validation rules. +/// * A blanket impl of [HasVerifier] exists for all `T: Verify`. This is a marker trait used +/// in conjunction with specialization. See the trait docs for more details on its purpose. +/// * The [Verifier] trait provides a default vacuous impl for all `Trait` and `T` pairs. However, +/// we also provided a specialized [Verifier] impl for all `T: Verify` using the +/// `HasVerifier` marker. The specialized impl applies the underlying `Verify` impl. +/// * When deriving the op traits for an `Op` impl, we generate a hidden type that encodes all of +/// the op traits implemented by the op. We then generate an `OpVerifier` impl for the op, which +/// uses the hidden type we generated to reify the `Verifier` impl for each trait. The +/// `OpVerifier` implementation uses const eval to strip out all of the vacuous verifier impls, +/// leaving behind just the "real" verification rules specific to the traits implemented by that +/// op. +/// * The `OpVerifier` impl is object-safe, and is in fact a required super-trait of `Op` to ensure +/// that verification is part of defining an `Op`, but also to ensure that `verify` is a method +/// of `Op`, and that we can cast an `Operation` to `&dyn OpVerifier` and call `verify` on that. +/// +/// As a result of all this, we end up with highly-specialized verifiers for each op, with no +/// dynamic dispatch, and automatically maintained as part of the `Op` definition. When a new +/// op trait is derived, the verifier for the op is automatically updated to verify the new trait. +pub trait Verify { + /// In cases where verification may be disabled via runtime configuration, or based on + /// dynamic properties of the type, this method can be overridden and used to signal to + /// the verification driver that verification should be skipped on this item. + #[inline(always)] + #[allow(unused_variables)] + fn should_verify(&self, context: &Context) -> bool { + true + } + /// Apply this verifier, but only if [Verify::should_verify] returns true. + #[inline] + fn maybe_verify(&self, context: &Context) -> Result<(), Report> { + if self.should_verify(context) { + self.verify(context) + } else { + Ok(()) + } + } + /// Apply this verifier to the current item. + fn verify(&self, context: &Context) -> Result<(), Report>; +} + +/// A marker trait used for verifier specialization. +/// +/// # Safety +/// +/// In order for the `#[rustc_unsafe_specialization_marker]` attribute to be used safely and +/// correctly, the following rules must hold: +/// +/// * No associated items +/// * No impls with lifetime constraints, as specialization will ignore them +/// +/// For our use case, which is specializing verification for a given type and trait combination, +/// by optimizing out verification-related code for type combinations which have no verifier, these +/// are easy rules to uphold. +/// +/// However, we must ensure that we continue to uphold these rules moving forward. +#[rustc_unsafe_specialization_marker] +unsafe trait HasVerifier: Verify {} + +// While at first glance, it appears we would be using this to specialize on the fact that a type +// _has_ a verifier, which is strictly-speaking true, the actual goal we're aiming to acheive is +// to be able to identify the _absence_ of a verifier, so that we can eliminate the boilerplate for +// verifying that trait. See `Verifier` for more information. +unsafe impl HasVerifier for T where T: Verify {} + +/// The `Verifier` trait is used to derive a verifier for a given trait and concrete type. +/// +/// It does this by providing a default implementation for all combinations of `Trait` and `T`, +/// which always succeeds, and then specializing that implementation for `T: HasVerifier`. +/// +/// This has the effect of making all traits "verifiable", but only actually doing any verification +/// for types which implement `Verify`. +/// +/// We go a step further and actually set things up so that `rustc` can eliminate all of the dead +/// code when verification is vacuous. This is done by using const eval in the hidden type generated +/// for an [Op] impls [OpVerifier] implementation, which will wrap verification in a const-evaluated +/// check of the `VACUOUS` associated const. It can also be used directly, but the general idea +/// behind all of this is that we don't need to directly touch any of this, it's all generated. +/// +/// NOTE: Because this trait provides a default blanket impl for all `T`, you should avoid bringing +/// it into scope unless absolutely needed. It is virtually always preferred to explicitly invoke +/// this trait using turbofish syntax, so as to avoid conflict with the [Verify] trait, and to +/// avoid polluting the namespace for all types in scope. +pub trait Verifier { + /// An implementation of `Verifier` sets this flag to true when its implementation is vacuous, + /// i.e. it always succeeds and is not dependent on runtime context. + /// + /// The default implementation of this trait sets this to `true`, since without a verifier for + /// the type, verification always succeeds. However, we can specialize on the presence of + /// a verifier and set this to `false`, which will result in all of the verification logic + /// being applied. + /// + /// ## Example Usage + /// + /// Shown below is an example of how one can use const eval to eliminate dead code branches + /// in verifier selection, so that the resulting implementation is specialized and able to + /// have more optimizations applied as a result. + /// + /// ```rust + /// use midenc_hir::{Context, Report, Verify, verifier::Verifier}; + /// + /// /// This trait is a marker for a type that should never validate, i.e. verifying it always + /// /// returns an error. + /// trait Nope {} + /// impl Verify for Any { + /// fn verify(&self, context: &Context) -> Result<(), Report> { + /// Err(Report::msg("nope")) + /// } + /// } + /// + /// /// We can't impl the `Verify` trait for all T outside of `midenc_hir`, so we newtype all T + /// /// to do so, in effect, we can mimic the effect of implementing for all T using this type. + /// struct Any(core::marker::PhantomData); + /// impl Any { + /// fn new() -> Self { + /// Self(core::marker::PhantomData) + /// } + /// } + /// + /// /// This struct implements `Nope`, so it has an explicit verifier, which always fails + /// struct AlwaysRejected; + /// impl Nope for AlwaysRejected {} + /// + /// /// This struct doesn't implement `Nope`, so it gets a vacuous verifier, which always + /// /// succeeds. + /// struct AlwaysAccepted; + /// + /// /// Our vacuous verifier impl + /// #[inline(always)] + /// fn noop(_: &Any, _: &Context) -> Result<(), Report> { Ok(()) } + /// + /// /// This block uses const-eval to select the verifier for Any statically + /// let always_accepted = const { + /// if as Verifier>::VACUOUS { + /// noop + /// } else { + /// as Verifier>::maybe_verify + /// } + /// }; + /// + /// /// This block uses const-eval to select the verifier for Any statically + /// let always_rejected = const { + /// if as Verifier>::VACUOUS { + /// noop + /// } else { + /// as Verifier>::maybe_verify + /// } + /// }; + /// + /// /// Verify that we got the correct impls. We can't verify that all of the abstraction was + /// /// eliminated, but from reviewing the assembly output, it appears that this is precisely + /// /// what happens. + /// let context = Context::default(); + /// assert!(always_accepted(&Any::new(), &context).is_ok()); + /// assert!(always_rejected(&Any::new(), &context).is_err()); + /// ``` + const VACUOUS: bool; + + /// Checks if this verifier is applicable for the current item + fn should_verify(&self, context: &Context) -> bool; + /// Applies the verifier for this item, if [Verifier::should_verify] returns `true` + fn maybe_verify(&self, context: &Context) -> Result<(), Report>; + /// Applies the verifier for this item + fn verify(&self, context: &Context) -> Result<(), Report>; +} + +/// The default blanket impl for all types and traits +impl Verifier for T { + default const VACUOUS: bool = true; + + #[inline(always)] + default fn should_verify(&self, _context: &Context) -> bool { + false + } + + #[inline(always)] + default fn maybe_verify(&self, _context: &Context) -> Result<(), Report> { + Ok(()) + } + + #[inline(always)] + default fn verify(&self, _context: &Context) -> Result<(), Report> { + Ok(()) + } +} + +/// THe specialized impl for types which implement `Verify` +impl Verifier for T +where + T: HasVerifier, +{ + const VACUOUS: bool = false; + + #[inline] + fn should_verify(&self, context: &Context) -> bool { + >::should_verify(self, context) + } + + #[inline(always)] + fn maybe_verify(&self, context: &Context) -> Result<(), Report> { + >::maybe_verify(self, context) + } + + #[inline] + fn verify(&self, context: &Context) -> Result<(), Report> { + >::verify(self, context) + } +} + +#[cfg(test)] +mod tests { + use core::hint::black_box; + + use super::*; + use crate::{traits::SingleBlock, Operation}; + + struct Vacuous; + + /// In this test, we're validating that a type that trivially verifies specializes as vacuous, + /// and that a type we know has a "real" verifier, specializes as _not_ vacuous + #[test] + fn verifier_specialization_concrete() { + assert!(black_box(>::VACUOUS)); + assert!(black_box(!>::VACUOUS)); + } +} diff --git a/hir/src/ir/visit.rs b/hir/src/ir/visit.rs new file mode 100644 index 000000000..93b3deca5 --- /dev/null +++ b/hir/src/ir/visit.rs @@ -0,0 +1,107 @@ +mod searcher; +mod visitor; +mod walkable; + +pub use core::ops::ControlFlow; + +pub use self::{ + searcher::Searcher, + visitor::{OpVisitor, OperationVisitor, SymbolVisitor, Visitor}, + walkable::{RawWalk, ReverseBlock, Walk, WalkMut, WalkOrder, WalkStage}, +}; +use crate::Report; + +/// A result-like type used to control traversals of a [Walkable] entity. +/// +/// It is comparable to [core::ops::ControlFlow], with an additional option to continue traversal, +/// but with a sibling, rather than visiting any further children of the current item. +/// +/// It is compatible with the `?` operator, however doing so will exit early on _both_ `Skip` and +/// `Break`, so you should be aware of that when using the try operator. +#[derive(Clone)] +#[must_use] +pub enum WalkResult { + /// Continue the traversal normally, optionally producing a value for the current item. + Continue(C), + /// Skip traversing the current item and any children that have not been visited yet, and + /// continue the traversal with the next sibling of the current item. + Skip, + /// Stop the traversal entirely, and optionally returning a value associated with the break. + // + /// This can be used to represent both errors, and the successful result of a search. + Break(B), +} +impl WalkResult { + /// Returns true if the walk should continue + pub fn should_continue(&self) -> bool { + matches!(self, Self::Continue(_)) + } + + /// Returns true if the walk was interrupted + pub fn was_interrupted(&self) -> bool { + matches!(self, Self::Break(_)) + } + + /// Returns true if the walk was skipped + pub fn was_skipped(&self) -> bool { + matches!(self, Self::Skip) + } +} +impl WalkResult { + /// Convert this [WalkResult] into an equivalent [Result] + #[inline] + pub fn into_result(self) -> Result<(), B> { + match self { + Self::Break(err) => Err(err), + Self::Skip | Self::Continue(_) => Ok(()), + } + } +} +impl From<()> for WalkResult { + fn from(_value: ()) -> Self { + Self::Continue(()) + } +} +impl From> for WalkResult { + fn from(value: Result<(), B>) -> Self { + match value { + Ok(_) => WalkResult::Continue(()), + Err(err) => WalkResult::Break(err), + } + } +} +impl From> for Result<(), B> { + #[inline(always)] + fn from(value: WalkResult) -> Self { + value.into_result() + } +} +impl core::ops::FromResidual for WalkResult { + fn from_residual(residual: ::Residual) -> Self { + match residual { + WalkResult::Break(b) => WalkResult::Break(b), + _ => unreachable!(), + } + } +} +impl core::ops::Residual for WalkResult { + type TryType = WalkResult; +} +impl core::ops::Try for WalkResult { + type Output = C; + type Residual = WalkResult; + + #[inline] + fn from_output(output: Self::Output) -> Self { + WalkResult::Continue(output) + } + + #[inline] + fn branch(self) -> ControlFlow { + match self { + WalkResult::Continue(c) => ControlFlow::Continue(c), + WalkResult::Skip => ControlFlow::Break(WalkResult::Skip), + WalkResult::Break(b) => ControlFlow::Break(WalkResult::Break(b)), + } + } +} diff --git a/hir/src/ir/visit/searcher.rs b/hir/src/ir/visit/searcher.rs new file mode 100644 index 000000000..6c5385743 --- /dev/null +++ b/hir/src/ir/visit/searcher.rs @@ -0,0 +1,56 @@ +use super::{OpVisitor, OperationVisitor, SymbolVisitor, Visitor, WalkResult}; +use crate::{Op, Operation, OperationRef, Symbol}; + +/// [Searcher] is a driver for [Visitor] impls as applied to some root [Operation]. +/// +/// The searcher traverses the object graph in depth-first preorder, from operations to regions to +/// blocks to operations, etc. All nested items of an entity are visited before its siblings, i.e. +/// a region is fully visited before the next region of the same containing operation. +/// +/// This is effectively control-flow order, from an abstract interpretation perspective, i.e. an +/// actual program might only execute one region of a multi-region op, but this traversal will visit +/// all of them unless otherwise directed by a `WalkResult`. +pub struct Searcher { + visitor: V, + root: OperationRef, + _marker: core::marker::PhantomData, +} +impl> Searcher { + pub fn new(root: OperationRef, visitor: V) -> Self { + Self { + visitor, + root, + _marker: core::marker::PhantomData, + } + } +} + +impl Searcher { + pub fn visit(&mut self) -> WalkResult<>::Output> { + self.root.borrow().prewalk(|op: &Operation| self.visitor.visit(op)) + } +} + +impl> Searcher { + pub fn visit(&mut self) -> WalkResult<>::Output> { + self.root.borrow().prewalk(|op: &Operation| { + if let Some(op) = op.downcast_ref::() { + self.visitor.visit(op) + } else { + WalkResult::Continue(()) + } + }) + } +} + +impl Searcher { + pub fn visit(&mut self) -> WalkResult<>::Output> { + self.root.borrow().prewalk(|op: &Operation| { + if let Some(sym) = op.as_symbol() { + self.visitor.visit(sym) + } else { + WalkResult::Continue(()) + } + }) + } +} diff --git a/hir/src/ir/visit/visitor.rs b/hir/src/ir/visit/visitor.rs new file mode 100644 index 000000000..4a061d734 --- /dev/null +++ b/hir/src/ir/visit/visitor.rs @@ -0,0 +1,36 @@ +use super::WalkResult; +use crate::{Op, Operation, Symbol}; + +/// A generic trait that describes visitors for all kinds +pub trait Visitor { + /// The type of output produced by visiting an item. + type Output; + + /// The function which is applied to each `T` as it is visited. + fn visit(&mut self, current: &T) -> WalkResult; +} + +/// We can automatically convert any closure of appropriate type to a `Visitor` +impl Visitor for F +where + F: FnMut(&T) -> WalkResult, +{ + type Output = U; + + #[inline] + fn visit(&mut self, op: &T) -> WalkResult { + self(op) + } +} + +/// Represents a visitor over [Operation] +pub trait OperationVisitor: Visitor {} +impl OperationVisitor for V where V: Visitor {} + +/// Represents a visitor over [Op] of type `T` +pub trait OpVisitor: Visitor {} +impl OpVisitor for V where V: Visitor {} + +/// Represents a visitor over [Symbol] +pub trait SymbolVisitor: Visitor {} +impl SymbolVisitor for V where V: Visitor {} diff --git a/hir/src/ir/visit/walkable.rs b/hir/src/ir/visit/walkable.rs new file mode 100644 index 000000000..a5d843099 --- /dev/null +++ b/hir/src/ir/visit/walkable.rs @@ -0,0 +1,741 @@ +use super::WalkResult; +use crate::{ + Block, BlockRef, Direction, Operation, OperationRef, Region, RegionRef, + UnsafeIntrusiveEntityRef, +}; + +/// The traversal order for a walk of a region, block, or operation +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum WalkOrder { + PreOrder, + PostOrder, +} + +/// Encodes the current walk stage for generic walkers. +/// +/// When walking an operation, we can either choose a pre- or post-traversal walker which invokes +/// the callback on an operation before/after all its attached regions have been visited, or choose +/// a generic walker where the callback is invoked on the operation N+1 times, where N is the number +/// of regions attached to that operation. [WalkStage] encodes the current stage of the walk, i.e. +/// which regions have already been visited, and the callback accepts an additional argument for +/// the current stage. Such generic walkers that accept stage-aware callbacks are only applicable +/// when the callback operations on an operation (i.e. doesn't apply to callbacks on blocks or +/// regions). +#[derive(Clone, PartialEq, Eq)] +pub struct WalkStage { + /// The number of regions in the operation + num_regions: usize, + /// The next region to visit in the operation + next_region: Option, +} +impl WalkStage { + pub fn new(op: OperationRef) -> Self { + let op = op.borrow(); + Self { + num_regions: op.num_regions(), + next_region: op.regions().front().as_pointer(), + } + } + + /// Returns true if the parent operation is being visited before all regions. + #[inline] + pub fn is_before_all_regions(&self) -> bool { + self.next_region.is_some_and(|r| r.prev().is_none()) + } + + /// Returns true if the parent operation is being visited just before visiting `region` + #[inline] + pub fn is_before_region(&self, region: RegionRef) -> bool { + self.next_region.is_some_and(|r| r.next().is_some_and(|next| next == region)) + } + + /// Returns true if the parent operation is being visited just after visiting `region` + #[inline] + pub fn is_after_region(&self, region: RegionRef) -> bool { + self.next_region.is_some_and(|r| r.prev().is_some_and(|prev| prev == region)) + } + + /// Returns true if the parent operation is being visited after all regions. + #[inline] + pub fn is_after_all_regions(&self) -> bool { + self.next_region.is_none() + } + + /// Advance the walk stage + #[inline] + pub fn advance(&mut self) { + if let Some(next_region) = self.next_region.take() { + self.next_region = next_region.next(); + } + } + + /// Returns the next region that will be visited + #[inline(always)] + pub const fn next_region(&self) -> Option { + self.next_region + } +} + +pub trait WalkDirection = + Walker + Walker + Walker; + +/// [Walk] represents an implementation of a depth-first traversal (pre- or post-order) from some +/// root object in the entity graph, to children of a given entity type. +/// +/// An implementation of this trait specifies a type, `T`, corresponding to the type of item being +/// walked, while `Self` is the root entity, possibly of the same type, which may contain `T`. Thus +/// traversing from the root to all of the leaves, we will visit all reachable `T` nested within +/// `Self`, possibly including itself. +/// +/// In cases where the root entity and the entity type being visited are the same, callbacks given +/// to this trait's methods are invoked on both the root entity and any children of that type. This +/// would require re-borrowing the root entity, so to distinguish between immutable and mutable +/// visits, this trait has a mutable variant, [WalkMut], which ensures that the root entity is not +/// borrowed during the traversal, and thus can be mutably borrowed by the visitor if needed. +pub trait Walk { + /// Walk all `T` in `self` in a specific order, applying the given callback to each. + /// + /// This is very similar to [Walkable::walk_interruptible], except the callback has no control + /// over the traversal, and must be infallible. + fn walk_all(&self, order: WalkOrder, mut callback: F) + where + D: WalkDirection, + F: FnMut(&T), + { + let _ = self.walk::(order, |t| { + callback(t); + + WalkResult::<()>::Continue(()) + }); + } + + /// Walk all `T` in `self` using a pre-order, depth-first traversal, applying the given callback + /// to each `T`. + #[inline] + fn prewalk_all(&self, callback: F) + where + D: WalkDirection, + F: FnMut(&T), + { + self.walk_all::(WalkOrder::PreOrder, callback) + } + + /// Walk all `T` in `self` using a post-order, depth-first traversal, applying the given callback + /// to each `T`. + #[inline] + fn postwalk_all(&self, callback: F) + where + D: WalkDirection, + F: FnMut(&T), + { + self.walk_all::(WalkOrder::PostOrder, callback) + } + + /// Walk `self` in the given order, visiting each `T` and applying the given callback to them. + /// + /// The given callback can control the traversal using the [WalkResult] it returns: + /// + /// * `WalkResult::Skip` will skip the walk of the current item and its nested elements that + /// have not been visited already, continuing with the next item. + /// * `WalkResult::Break` will interrupt the walk, and no more items will be visited + /// * `WalkResult::Continue` will continue the walk + fn walk(&self, order: WalkOrder, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&T) -> WalkResult; + + /// Walk all `T` in `self` using a pre-order, depth-first traversal, applying the given callback + /// to each `T`, and determining how to proceed based on the returned [WalkResult]. + #[inline] + fn prewalk(&self, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&T) -> WalkResult, + { + self.walk::(WalkOrder::PreOrder, callback) + } + + /// Walk all `T` in `self` using a post-order, depth-first traversal, applying the given callback + /// to each `T`, and determining how to proceed based on the returned [WalkResult]. + #[inline] + fn postwalk(&self, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&T) -> WalkResult, + { + self.walk::(WalkOrder::PostOrder, callback) + } +} + +/// A mutable variant of [Walk], for traversal which may mutate visited entities. +pub trait WalkMut { + /// Walk all `T` in `self` in a specific order, applying the given callback to each. + /// + /// This is very similar to [Walkable::walk_interruptible], except the callback has no control + /// over the traversal, and must be infallible. + fn walk_all_mut(&mut self, order: WalkOrder, mut callback: F) + where + D: WalkDirection, + F: FnMut(&mut T), + { + let _ = self.walk_mut::(order, |t| { + callback(t); + + WalkResult::<()>::Continue(()) + }); + } + + /// Walk all `T` in `self` using a pre-order, depth-first traversal, applying the given callback + /// to each `T`. + #[inline] + fn prewalk_all_mut(&mut self, callback: F) + where + D: WalkDirection, + F: FnMut(&mut T), + { + self.walk_all_mut::(WalkOrder::PreOrder, callback) + } + + /// Walk all `T` in `self` using a post-order, depth-first traversal, applying the given callback + /// to each `T`. + #[inline] + fn postwalk_all_mut(&mut self, callback: F) + where + D: WalkDirection, + F: FnMut(&mut T), + { + self.walk_all_mut::(WalkOrder::PostOrder, callback) + } + + /// Walk `self` in the given order, visiting each `T` and applying the given callback to them. + /// + /// The given callback can control the traversal using the [WalkResult] it returns: + /// + /// * `WalkResult::Skip` will skip the walk of the current item and its nested elements that + /// have not been visited already, continuing with the next item. + /// * `WalkResult::Break` will interrupt the walk, and no more items will be visited + /// * `WalkResult::Continue` will continue the walk + fn walk_mut(&mut self, order: WalkOrder, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&mut T) -> WalkResult; + + /// Walk all `T` in `self` using a pre-order, depth-first traversal, applying the given callback + /// to each `T`, and determining how to proceed based on the returned [WalkResult]. + #[inline] + fn prewalk_mut_interruptible(&mut self, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&mut T) -> WalkResult, + { + self.walk_mut::(WalkOrder::PreOrder, callback) + } + + /// Walk all `T` in `self` using a post-order, depth-first traversal, applying the given callback + /// to each `T`, and determining how to proceed based on the returned [WalkResult]. + #[inline] + fn postwalk_mut_interruptible(&mut self, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&mut T) -> WalkResult, + { + self.walk_mut::(WalkOrder::PostOrder, callback) + } +} + +/// [RawWalk] is a variation of [Walk]/[WalkMut] that performs the traversal while ensuring that +/// no entity is borrowed when visitor callbacks are invoked. This allows the visitor to freely +/// obtain mutable/immutable borrows without having to worry if the traversal is holding a borrow +/// somewhere. +pub trait RawWalk { + /// Walk all `T` in `self` in a specific order, applying the given callback to each. + /// + /// This is very similar to [Walkable::walk_interruptible], except the callback has no control + /// over the traversal, and must be infallible. + fn raw_walk_all(&self, order: WalkOrder, mut callback: F) + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef), + { + let _ = self.raw_walk::(order, |t| { + callback(t); + + WalkResult::<()>::Continue(()) + }); + } + + /// Walk all `T` in `self` using a pre-order, depth-first traversal, applying the given callback + /// to each `T`. + #[inline] + fn raw_prewalk_all(&self, callback: F) + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef), + { + self.raw_walk_all::(WalkOrder::PreOrder, callback) + } + + /// Walk all `T` in `self` using a post-order, depth-first traversal, applying the given callback + /// to each `T`. + #[inline] + fn raw_postwalk_all(&self, callback: F) + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef), + { + self.raw_walk_all::(WalkOrder::PostOrder, callback) + } + + /// Walk `self` in the given order, visiting each `T` and applying the given callback to them. + /// + /// The given callback can control the traversal using the [WalkResult] it returns: + /// + /// * `WalkResult::Skip` will skip the walk of the current item and its nested elements that + /// have not been visited already, continuing with the next item. + /// * `WalkResult::Break` will interrupt the walk, and no more items will be visited + /// * `WalkResult::Continue` will continue the walk + fn raw_walk(&self, order: WalkOrder, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef) -> WalkResult; + + /// Walk all `T` in `self` using a pre-order, depth-first traversal, applying the given callback + /// to each `T`, and determining how to proceed based on the returned [WalkResult]. + #[inline] + fn raw_prewalk(&self, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef) -> WalkResult, + { + self.raw_walk::(WalkOrder::PreOrder, callback) + } + + /// Walk all `T` in `self` using a post-order, depth-first traversal, applying the given callback + /// to each `T`, and determining how to proceed based on the returned [WalkResult]. + #[inline] + fn raw_postwalk(&self, callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef) -> WalkResult, + { + self.raw_walk::(WalkOrder::PostOrder, callback) + } +} + +/// Walking operations nested within an [Operation], including itself +impl RawWalk for OperationRef { + fn raw_walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef) -> WalkResult, + { + raw_walk_operations::(*self, order, &mut callback) + } +} + +impl Walk for OperationRef { + fn walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&Operation) -> WalkResult, + { + let mut wrapper = |op: OperationRef| callback(&op.borrow()); + raw_walk_operations::(*self, order, &mut wrapper) + } +} + +impl WalkMut for OperationRef { + fn walk_mut(&mut self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&mut Operation) -> WalkResult, + { + let mut wrapper = |mut op: OperationRef| callback(&mut op.borrow_mut()); + raw_walk_operations::(*self, order, &mut wrapper) + } +} + +impl Walk for Operation { + fn walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&Operation) -> WalkResult, + { + let mut wrapper = |op: OperationRef| callback(&op.borrow()); + raw_walk_operations::(self.as_operation_ref(), order, &mut wrapper) + } +} + +/// Walking regions of an [Operation], and those of all nested operations +impl RawWalk for OperationRef { + fn raw_walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef) -> WalkResult, + { + raw_walk_regions::(*self, order, &mut callback) + } +} + +impl Walk for OperationRef { + fn walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&Region) -> WalkResult, + { + let mut wrapper = |region: RegionRef| callback(®ion.borrow()); + raw_walk_regions::(*self, order, &mut wrapper) + } +} + +impl WalkMut for OperationRef { + fn walk_mut(&mut self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&mut Region) -> WalkResult, + { + let mut wrapper = |mut region: RegionRef| callback(&mut region.borrow_mut()); + raw_walk_regions::(*self, order, &mut wrapper) + } +} + +impl Walk for Operation { + fn walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&Region) -> WalkResult, + { + let mut wrapper = |region: RegionRef| callback(®ion.borrow()); + raw_walk_regions::(self.as_operation_ref(), order, &mut wrapper) + } +} + +#[allow(unused)] +pub fn raw_walk(op: OperationRef, callback: &mut F) -> WalkResult +where + D: WalkDirection, + F: FnMut(OperationRef, &WalkStage) -> WalkResult, +{ + let mut stage = WalkStage::new(op); + + let mut next_region = stage.next_region(); + while let Some(region) = next_region.take() { + // Invoke callback on the parent op before visiting each child region + let result = callback(op, &stage); + + match result { + WalkResult::Skip => return WalkResult::Continue(()), + err @ WalkResult::Break(_) => return err, + WalkResult::Continue(_) => { + stage.advance(); + + let mut next_block = D::start(&*region.borrow()); + while let Some(block) = next_block.take() { + next_block = D::continue_walk(block); + + let mut next_op = D::start(&*block.borrow()); + while let Some(op) = next_op.take() { + next_op = D::continue_walk(op); + + raw_walk::(op, callback)?; + } + } + } + } + } + + // Invoke callback after all regions have been visited + callback(op, &stage) +} + +fn raw_walk_regions(op: OperationRef, order: WalkOrder, callback: &mut F) -> WalkResult +where + D: WalkDirection, + F: FnMut(RegionRef) -> WalkResult, +{ + let mut next_region = D::start(&*op.borrow()); + while let Some(region) = next_region.take() { + next_region = D::continue_walk(region); + + if matches!(order, WalkOrder::PreOrder) { + let result = callback(region); + match result { + WalkResult::Skip => continue, + err @ WalkResult::Break(_) => return err, + _ => (), + } + } + + let mut next_block = D::start(&*region.borrow()); + while let Some(block) = next_block.take() { + next_block = D::continue_walk(block); + + let mut next_op = D::start(&*block.borrow()); + while let Some(op) = next_op.take() { + next_op = D::continue_walk(op); + + raw_walk_regions::(op, order, callback)?; + } + } + + if matches!(order, WalkOrder::PostOrder) { + callback(region)?; + } + } + + WalkResult::Continue(()) +} + +#[allow(unused)] +fn raw_walk_blocks(op: OperationRef, order: WalkOrder, callback: &mut F) -> WalkResult +where + D: WalkDirection, + F: FnMut(BlockRef) -> WalkResult, +{ + let mut next_region = D::start(&*op.borrow()); + while let Some(region) = next_region.take() { + next_region = D::continue_walk(region); + + let mut next_block = D::start(&*region.borrow()); + while let Some(block) = next_block.take() { + next_block = D::continue_walk(block); + + if matches!(order, WalkOrder::PreOrder) { + let result = callback(block); + match result { + WalkResult::Skip => continue, + err @ WalkResult::Break(_) => return err, + _ => (), + } + } + + let mut next_op = D::start(&*block.borrow()); + while let Some(op) = next_op.take() { + next_op = D::continue_walk(op); + + raw_walk_blocks::(op, order, callback)?; + } + + if matches!(order, WalkOrder::PostOrder) { + callback(block)?; + } + } + } + + WalkResult::Continue(()) +} + +fn raw_walk_operations( + op: OperationRef, + order: WalkOrder, + callback: &mut F, +) -> WalkResult +where + D: WalkDirection, + F: FnMut(OperationRef) -> WalkResult, +{ + if matches!(order, WalkOrder::PreOrder) { + let result = callback(op); + match result { + WalkResult::Skip => return WalkResult::Continue(()), + err @ WalkResult::Break(_) => return err, + _ => (), + } + } + + let mut next_region = D::start(&*op.borrow()); + while let Some(region) = next_region.take() { + next_region = D::continue_walk(region); + + let mut next_block = D::start(&*region.borrow()); + while let Some(block) = next_block.take() { + next_block = D::continue_walk(block); + + let mut next_op = D::start(&*block.borrow()); + while let Some(op) = next_op.take() { + next_op = D::continue_walk(op); + + raw_walk_operations::(op, order, callback)?; + } + } + } + + if matches!(order, WalkOrder::PostOrder) { + callback(op)?; + } + + WalkResult::Continue(()) +} + +fn raw_walk_region_operations( + region: RegionRef, + order: WalkOrder, + callback: &mut F, +) -> WalkResult +where + D: WalkDirection, + F: FnMut(OperationRef) -> WalkResult, +{ + let mut next_block = D::start(&*region.borrow()); + while let Some(block) = next_block.take() { + next_block = D::continue_walk(block); + + let mut next_op = D::start(&*block.borrow()); + while let Some(op) = next_op.take() { + next_op = D::continue_walk(op); + + raw_walk_operations::(op, order, callback)?; + } + } + + WalkResult::Continue(()) +} + +/// Walking operations nested within a [Region] +impl RawWalk for RegionRef { + fn raw_walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(UnsafeIntrusiveEntityRef) -> WalkResult, + { + raw_walk_region_operations::(*self, order, &mut callback) + } +} + +impl Walk for RegionRef { + fn walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&Operation) -> WalkResult, + { + let mut wrapper = |op: OperationRef| callback(&op.borrow()); + raw_walk_region_operations::(*self, order, &mut wrapper) + } +} + +impl WalkMut for RegionRef { + fn walk_mut(&mut self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&mut Operation) -> WalkResult, + { + let mut wrapper = |mut op: OperationRef| callback(&mut op.borrow_mut()); + raw_walk_region_operations::(*self, order, &mut wrapper) + } +} + +impl Walk for Region { + fn walk(&self, order: WalkOrder, mut callback: F) -> WalkResult + where + D: WalkDirection, + F: FnMut(&Operation) -> WalkResult, + { + let mut wrapper = |op: OperationRef| callback(&op.borrow()); + raw_walk_region_operations::(self.as_region_ref(), order, &mut wrapper) + } +} + +pub trait Walker { + fn start(entity: &Parent) -> Option; + fn continue_walk(child: Child) -> Option; +} + +/// A custom [WalkDirectionImpl] that is the same as [Forward], except the operations of each block +/// are visited bottom-up, i.e. as if [Backward] applied just to [Block]. +pub struct ReverseBlock; + +impl Walker for ReverseBlock { + #[inline(always)] + fn start(entity: &Region) -> Option { + entity.body().front().as_pointer() + } + + #[inline(always)] + fn continue_walk(child: BlockRef) -> Option { + child.next() + } +} + +impl Walker for ReverseBlock { + #[inline(always)] + fn start(entity: &Block) -> Option { + entity.body().back().as_pointer() + } + + #[inline(always)] + fn continue_walk(child: OperationRef) -> Option { + child.prev() + } +} + +impl Walker for ReverseBlock { + #[inline(always)] + fn start(entity: &Operation) -> Option { + entity.regions().front().as_pointer() + } + + #[inline(always)] + fn continue_walk(child: RegionRef) -> Option { + child.next() + } +} + +impl Walker for D { + #[inline(always)] + fn start(entity: &Region) -> Option { + if const { D::IS_FORWARD } { + entity.body().front().as_pointer() + } else { + entity.body().back().as_pointer() + } + } + + #[inline(always)] + fn continue_walk(child: BlockRef) -> Option { + if const { D::IS_FORWARD } { + child.next() + } else { + child.prev() + } + } +} + +impl Walker for D { + #[inline(always)] + fn start(entity: &Block) -> Option { + if const { D::IS_FORWARD } { + entity.body().front().as_pointer() + } else { + entity.body().back().as_pointer() + } + } + + #[inline(always)] + fn continue_walk(child: OperationRef) -> Option { + if const { D::IS_FORWARD } { + child.next() + } else { + child.prev() + } + } +} + +impl Walker for D { + #[inline(always)] + fn start(entity: &Operation) -> Option { + if const { D::IS_FORWARD } { + entity.regions().front().as_pointer() + } else { + entity.regions().back().as_pointer() + } + } + + #[inline(always)] + fn continue_walk(child: RegionRef) -> Option { + if const { D::IS_FORWARD } { + child.next() + } else { + child.prev() + } + } +} diff --git a/hir/src/itertools.rs b/hir/src/itertools.rs new file mode 100644 index 000000000..10e985f07 --- /dev/null +++ b/hir/src/itertools.rs @@ -0,0 +1,17 @@ +pub trait IteratorExt { + /// Returns true if the given iterator consists of exactly one element + fn has_single_element(&mut self) -> bool; +} + +impl IteratorExt for I { + default fn has_single_element(&mut self) -> bool { + self.next().is_some_and(|_| self.next().is_none()) + } +} + +impl IteratorExt for I { + #[inline] + fn has_single_element(&mut self) -> bool { + self.len() == 1 + } +} diff --git a/hir/src/layout.rs b/hir/src/layout.rs deleted file mode 100644 index cdb0d0116..000000000 --- a/hir/src/layout.rs +++ /dev/null @@ -1,485 +0,0 @@ -use core::{ - ops::{Index, IndexMut}, - ptr::NonNull, -}; - -use cranelift_entity::EntityRef; -use intrusive_collections::{ - intrusive_adapter, - linked_list::{Cursor, CursorMut, LinkedList}, - LinkedListLink, UnsafeRef, -}; -use typed_arena::Arena; - -/// This struct holds the data for each node in an ArenaMap/OrderedArenaMap -pub struct LayoutNode { - pub link: LinkedListLink, - key: K, - value: V, -} -impl Clone for LayoutNode { - fn clone(&self) -> Self { - Self { - link: LinkedListLink::new(), - key: self.key, - value: self.value.clone(), - } - } -} -impl LayoutNode { - pub fn new(key: K, value: V) -> Self { - Self { - link: LinkedListLink::default(), - key, - value, - } - } - - #[inline(always)] - pub fn key(&self) -> K { - self.key - } - - #[inline(always)] - pub fn value(&self) -> &V { - &self.value - } - - #[inline(always)] - pub fn value_mut(&mut self) -> &mut V { - &mut self.value - } -} - -intrusive_adapter!(pub LayoutAdapter = UnsafeRef>: LayoutNode { link: LinkedListLink } where K: EntityRef); - -/// ArenaMap provides similar functionality to other kinds of maps: -/// -/// # Pros -/// -/// * Once allocated, values stored in the map have a stable location, this can be useful for when -/// you expect to store elements of the map in an intrusive collection. -/// * Keys can be more efficiently sized, i.e. rather than pointers/usize keys, you can choose -/// arbitrarily small bitwidths, as long as there is sufficient keyspace for your use case. -/// * Attempt to keep data in the map as contiguous in memory as possible. This is again useful for -/// when the data is also linked into an intrusive collection, like a linked list, where -/// traversing the list will end up visiting many of the nodes in the map. If each node was its -/// own Box, this would cause thrashing of the cache - ArenaMap sidesteps this by allocating -/// values in chunks of memory that are friendlier to the cache. -/// -/// # Cons -/// -/// * Memory allocated for data stored in the map is not released until the map is dropped. This is -/// a tradeoff made to ensure that the data has a stable location in memory, but the flip side of -/// that is increased memory usage for maps that stick around for a long time. In our case, these -/// maps are relatively short-lived, so it isn't a problem in practice. -/// * It doesn't provide as rich of an API as HashMap and friends -pub struct ArenaMap { - keys: Vec>>, - arena: Arena, - _marker: core::marker::PhantomData, -} -impl Drop for ArenaMap { - fn drop(&mut self) { - self.keys.clear() - } -} -impl Clone for ArenaMap { - fn clone(&self) -> Self { - let mut cloned = Self::new(); - for opt in self.keys.iter() { - match opt { - None => cloned.keys.push(None), - Some(nn) => { - let value = unsafe { nn.as_ref() }; - cloned.push(value.clone()); - } - } - } - cloned - } -} -impl Default for ArenaMap { - #[inline] - fn default() -> Self { - Self::new() - } -} -impl ArenaMap { - /// Creates a new, empty ArenaMap - pub fn new() -> Self { - Self { - arena: Arena::default(), - keys: vec![], - _marker: core::marker::PhantomData, - } - } - - /// Returns true if this [ArenaMap] is empty - pub fn is_empty(&self) -> bool { - self.keys.is_empty() - } - - /// Returns the total number of actively linked items in the map - pub fn len(&self) -> usize { - self.keys.iter().filter(|item| item.is_some()).count() - } - - /// Returns true if this map contains `key` - pub fn contains(&self, key: K) -> bool { - self.keys.get(key.index()).map(|item| item.is_some()).unwrap_or(false) - } - - /// Adds a new entry to the map, returning the key it is associated to - pub fn push(&mut self, value: V) -> K { - let key = self.alloc_key(); - self.alloc_node(key, value); - key - } - - /// Used in conjunction with `alloc_key` to associate data with the allocated key - pub fn append(&mut self, key: K, value: V) { - self.alloc_node(key, value); - } - - /// Returns a reference to the value associated with the given key - pub fn get(&self, key: K) -> Option<&V> { - self.keys - .get(key.index()) - .and_then(|item| item.map(|nn| unsafe { nn.as_ref() })) - } - - /// Returns a mutable reference to the value associated with the given key - pub fn get_mut(&mut self, key: K) -> Option<&mut V> { - self.keys - .get_mut(key.index()) - .and_then(|item| item.map(|mut nn| unsafe { nn.as_mut() })) - } - - /// Returns a raw pointer to the value associated with the given key - /// - /// # Safety - /// - /// This function is unsafe, since the resulting pointer could outlive the arena itself, - /// or be used to incorrectly alias a value for which a mutable reference exists. - /// - /// To safely use this function, callers must never construct a reference from the pointer - /// unless they can guarantee that the data pointed to is immutable, or can be safely accessed - /// using atomic operations. No other uses are permitted, unless you want to shoot yourself - /// in the foot. - pub unsafe fn get_raw(&self, key: K) -> Option> { - self.keys.get(key.index()).copied().and_then(|item| item) - } - - /// Takes the value that was stored at the given key - pub fn take(&mut self, key: K) -> Option> { - self.keys[key.index()].take() - } - - pub fn iter(&self) -> impl Iterator>> + '_ { - self.keys.iter().copied() - } - - /// Removes the value associated with the given key - /// - /// NOTE: This function will panic if the key is invalid/unbound - pub fn remove(&mut self, key: K) { - self.keys[key.index()].take(); - } - - pub fn alloc_key(&mut self) -> K { - let id = self.keys.len(); - let key = K::new(id); - self.keys.push(None); - key - } - - fn alloc_node(&mut self, key: K, value: V) -> NonNull { - let len = key.index() + 1; - if len > self.keys.len() { - self.keys.resize(len, None); - } - let value = self.arena.alloc(value); - let nn = unsafe { NonNull::new_unchecked(value) }; - self.keys[key.index()].replace(nn); - nn - } -} -impl Index for ArenaMap { - type Output = V; - - #[inline] - fn index(&self, index: K) -> &Self::Output { - self.get(index).unwrap() - } -} -impl IndexMut for ArenaMap { - #[inline] - fn index_mut(&mut self, index: K) -> &mut Self::Output { - self.get_mut(index).unwrap() - } -} - -/// OrderedArenaMap is an extension of ArenaMap that provides for arbitrary ordering of keys/values -/// -/// This is done using an intrusive linked list alongside an ArenaMap. The list is used to link one -/// key/value pair to the next, so any ordering you wish to implement is possible. This is -/// particularly useful for layout of blocks in a function, or instructions within blocks, as you -/// can precisely position them relative to other blocks/instructions. -/// -/// Because the linked list is intrusive, it is virtually free in terms of space, but comes with the -/// standard overhead for traversals. That said, there are a couple of niceties that give it good -/// overall performance: -/// -/// * It is a doubly-linked list, so you can traverse equally efficiently front-to-back or -/// back-to-front, -/// * It has O(1) indexing; given a key, we can directly obtain a reference to a node, and with -/// that, obtain a cursor over the list starting at that node. -pub struct OrderedArenaMap { - list: LinkedList>, - map: ArenaMap>, -} -impl Drop for OrderedArenaMap { - fn drop(&mut self) { - self.list.fast_clear(); - } -} -impl Clone for OrderedArenaMap { - fn clone(&self) -> Self { - let mut cloned = Self::new(); - for opt in self.map.iter() { - match opt { - None => { - cloned.map.alloc_key(); - } - Some(nn) => { - let value = unsafe { nn.as_ref() }.value(); - cloned.push(value.clone()); - } - } - } - cloned - } -} -impl Default for OrderedArenaMap { - #[inline] - fn default() -> Self { - Self::new() - } -} -impl OrderedArenaMap { - pub fn new() -> Self { - Self { - map: ArenaMap::new(), - list: LinkedList::new(LayoutAdapter::new()), - } - } - - /// Returns true if this [OrderedArenaMap] is empty - pub fn is_empty(&self) -> bool { - self.list.is_empty() - } - - /// Returns the total number of actively linked items in the map - pub fn len(&self) -> usize { - self.list.iter().count() - } - - /// Returns true if this map contains the given key and its value has been linked - #[inline] - pub fn contains(&self, key: K) -> bool { - if let Some(node) = self.map.get(key) { - node.link.is_linked() - } else { - false - } - } - - /// Returns a reference to the value associated with the given key, if present and linked - pub fn get(&self, key: K) -> Option<&V> { - let node = self.map.get(key)?; - if node.link.is_linked() { - Some(node.value()) - } else { - None - } - } - - /// Returns a mutable reference to the value associated with the given key, if present and - /// linked - pub fn get_mut(&mut self, key: K) -> Option<&mut V> { - let node = self.map.get_mut(key)?; - if node.link.is_linked() { - Some(node.value_mut()) - } else { - None - } - } - - /// Allocates a key, but does not link the data - #[inline] - pub fn create(&mut self) -> K { - self.map.alloc_key() - } - - /// Used with `create` when ready to associate data with the allocated key, linking it in to the - /// end of the list - pub fn append(&mut self, key: K, value: V) { - debug_assert!(!self.contains(key)); - let data = self.alloc_node(key, value); - self.list.push_back(data); - } - - /// Like `append`, but inserts the node before `before` in the list - /// - /// NOTE: This function will panic if `before` is not present in the list - pub fn insert_before(&mut self, key: K, before: K, value: V) { - let value_opt = self.get_mut(key); - debug_assert!(value_opt.is_none()); - let data = self.alloc_node(key, value); - let mut cursor = self.cursor_mut_at(before); - cursor.insert_before(data); - } - - /// Like `append`, but inserts the node after `after` in the list - /// - /// NOTE: This function will panic if `after` is not present in the list - pub fn insert_after(&mut self, key: K, after: K, value: V) { - let value_opt = self.get_mut(key); - debug_assert!(value_opt.is_none()); - let data = self.alloc_node(key, value); - let mut cursor = self.cursor_mut_at(after); - cursor.insert_after(data); - } - - /// Allocates a key and links data in the same operation - pub fn push(&mut self, value: V) -> K { - let key = self.alloc_key(); - self.append(key, value); - key - } - - /// Like `push`, but inserts the node after `after` in the list - /// - /// NOTE: This function will panic if `after` is not present in the list - pub fn push_after(&mut self, after: K, value: V) -> K { - let key = self.alloc_key(); - self.insert_after(key, after, value); - key - } - - /// Unlinks the value associated with the given key from this map - /// - /// NOTE: Removal does not result in deallocation of the underlying data, this - /// happens when the map is dropped. To perform early garbage collection, you can - /// clone the map, and drop the original. - pub fn remove(&mut self, key: K) { - if let Some(value) = self.map.get(key) { - assert!(value.link.is_linked(), "cannot remove a value that is not linked"); - let mut cursor = unsafe { self.list.cursor_mut_from_ptr(value) }; - cursor.remove(); - } - } - - /// Returns the first node in the map - pub fn first(&self) -> Option<&LayoutNode> { - self.list.front().get() - } - - /// Returns the last node in the map - pub fn last(&self) -> Option<&LayoutNode> { - self.list.back().get() - } - - /// Returns a cursor which can be used to traverse the map in order (front to back) - pub fn cursor(&self) -> Cursor<'_, LayoutAdapter> { - self.list.front() - } - - /// Returns a cursor which can be used to traverse the map mutably, in order (front to back) - pub fn cursor_mut(&mut self) -> CursorMut<'_, LayoutAdapter> { - self.list.front_mut() - } - - /// Returns a cursor which can be used to traverse the map in order (front to back), starting - /// at the key given. - pub fn cursor_at(&self, key: K) -> Cursor<'_, LayoutAdapter> { - let ptr = &self.map[key] as *const LayoutNode; - unsafe { self.list.cursor_from_ptr(ptr) } - } - - /// Returns a cursor which can be used to traverse the map mutably, in order (front to back), - /// starting at the key given. - pub fn cursor_mut_at(&mut self, key: K) -> CursorMut<'_, LayoutAdapter> { - let ptr = &self.map[key] as *const LayoutNode; - unsafe { self.list.cursor_mut_from_ptr(ptr) } - } - - /// Returns an iterator over the key/value pairs in the map. - /// - /// The iterator is double-ended, so can be used to traverse the map front-to-back, or - /// back-to-front - pub fn iter(&self) -> OrderedArenaMapIter<'_, K, V> { - OrderedArenaMapIter(self.list.iter()) - } - - /// Returns an iterator over the keys in the map, in order (front to back) - pub fn keys(&self) -> impl Iterator + '_ { - self.list.iter().map(|item| item.key()) - } - - /// Returns an iterator over the values in the map, in order (front to back) - pub fn values(&self) -> impl Iterator { - self.list.iter().map(|item| item.value()) - } - - #[inline] - fn alloc_key(&mut self) -> K { - self.map.alloc_key() - } - - fn alloc_node(&mut self, key: K, value: V) -> UnsafeRef> { - let nn = self.map.alloc_node(key, LayoutNode::new(key, value)); - unsafe { UnsafeRef::from_raw(nn.as_ptr()) } - } -} -impl Index for OrderedArenaMap { - type Output = V; - - #[inline] - fn index(&self, index: K) -> &Self::Output { - self.map[index].value() - } -} -impl IndexMut for OrderedArenaMap { - #[inline] - fn index_mut(&mut self, index: K) -> &mut Self::Output { - self.map.get_mut(index).unwrap().value_mut() - } -} - -pub struct OrderedArenaMapIter<'a, K, V>( - intrusive_collections::linked_list::Iter<'a, LayoutAdapter>, -) -where - K: EntityRef; -impl<'a, K, V> Iterator for OrderedArenaMapIter<'a, K, V> -where - K: EntityRef, -{ - type Item = (K, &'a V); - - #[inline] - fn next(&mut self) -> Option { - self.0.next().map(|item| (item.key(), item.value())) - } -} -impl<'a, K, V> DoubleEndedIterator for OrderedArenaMapIter<'a, K, V> -where - K: EntityRef, -{ - #[inline] - fn next_back(&mut self) -> Option { - self.0.next_back().map(|item| (item.key(), item.value())) - } -} diff --git a/hir/src/lib.rs b/hir/src/lib.rs index 20c1d622d..51f0af058 100644 --- a/hir/src/lib.rs +++ b/hir/src/lib.rs @@ -1,230 +1,98 @@ +#![no_std] +#![feature(allocator_api)] +#![feature(alloc_layout_extra)] +#![feature(coerce_unsized)] +#![feature(unsize)] +#![feature(ptr_metadata)] +#![feature(ptr_as_uninit)] +#![feature(layout_for_ptr)] +#![feature(slice_ptr_get)] +#![feature(specialization)] +#![feature(rustc_attrs)] +#![feature(debug_closure_helpers)] +#![feature(trait_alias)] +#![feature(try_trait_v2)] +#![feature(try_trait_v2_residual)] +#![feature(tuple_trait)] +#![feature(fn_traits)] +#![feature(unboxed_closures)] +#![feature(box_into_inner)] +#![feature(const_type_id)] +#![feature(exact_size_is_empty)] +#![feature(generic_const_exprs)] +#![feature(clone_to_uninit)] +#![feature(new_range_api)] +// The following are used in impls of custom collection types based on SmallVec +#![feature(std_internals)] // for ByRefSized +#![feature(extend_one)] +#![feature(extend_one_unchecked)] +#![feature(iter_advance_by)] +#![feature(iter_next_chunk)] +#![feature(iter_collect_into)] +#![feature(trusted_len)] +#![feature(never_type)] +#![feature(maybe_uninit_slice)] +#![feature(maybe_uninit_array_assume_init)] +#![feature(maybe_uninit_uninit_array_transpose)] +#![feature(array_into_iter_constructors)] +#![feature(slice_range)] +#![feature(slice_swap_unchecked)] +#![feature(hasher_prefixfree_extras)] +// Some of the above features require us to disable these warnings +#![allow(incomplete_features)] +#![allow(internal_features)] #![deny(warnings)] -// TODO: Stabilized in 1.76, then de-stablized before release due to -// a soundness bug when interacting with #![feature(arbitrary_self_types)] -// so this got punted to a later release once they come up with a solution. -// -// Required for pass infrastructure, can be removed when it gets stabilized -// in an upcoming release, see https://github.com/rust-lang/rust/issues/65991 -// for details -#![feature(trait_upcasting)] -pub mod parser; + +extern crate alloc; #[cfg(feature = "std")] extern crate std; -#[macro_use] -extern crate alloc; - -#[macro_use] -extern crate lalrpop_util; +extern crate self as midenc_hir; -pub use intrusive_collections::UnsafeRef; -pub use miden_core::{FieldElement, StarkField}; -pub use midenc_hir_macros::*; -pub use midenc_hir_symbol::{symbols, Symbol}; -pub use midenc_hir_type::{ - self as types, AddressSpace, Alignable, FunctionType, StructType, Type, TypeRepr, +pub use compact_str::{ + CompactString as SmallStr, CompactStringExt as SmallStrExt, ToCompactString as ToSmallStr, }; -pub use midenc_session::diagnostics::{self, SourceSpan}; - -/// Represents a field element in Miden -pub type Felt = miden_core::Felt; - -/// Represents an offset from the base of linear memory in Miden -pub type Offset = u32; - -#[macro_export] -macro_rules! assert_matches { - ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)?) => { - match $left { - $( $pattern )|+ $( if $guard )? => {} - ref left_val => { - panic!(r#" -assertion failed: `(left matches right)` - left: `{:?}`, - right: `{}`"#, left_val, stringify!($($pattern)|+ $(if $guard)?)); - } - } - }; - - ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $msg:literal $(,)?) => { - match $left { - $( $pattern )|+ $( if $guard )? => {} - ref left_val => { - panic!(concat!(r#" -assertion failed: `(left matches right)` - left: `{:?}`, - right: `{}` -"#, $msg), left_val, stringify!($($pattern)|+ $(if $guard)?)); - } - } - }; +pub use hashbrown; +pub use smallvec::{smallvec, SmallVec, ToSmallVec}; - ($left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $msg:literal, $($arg:tt)+) => { - match $left { - $( $pattern )|+ $( if $guard )? => {} - ref left_val => { - panic!(concat!(r#" -assertion failed: `(left matches right)` - left: `{:?}`, - right: `{}` -"#, $msg), left_val, stringify!($($pattern)|+ $(if $guard)?), $($arg)+); - } - } - } -} - -#[macro_export] -macro_rules! diagnostic { - ($diagnostics:ident, $severity:expr, $msg:literal) => {{ - $diagnostics.diagnostic($severity).with_message($msg).emit(); - }}; - - ($diagnostics:ident, $severity:expr, $msg:literal, $span:expr, $label:expr) => {{ - let span = $span; - $diagnostics - .diagnostic($severity) - .with_message($msg) - .with_primary_label($span, $label) - .emit(); - }}; - - ($diagnostics:ident, $severity:expr, $msg:literal, $span:expr, $label:expr, $note:expr) => {{ - let span = $span; - $diagnostics - .diagnostic($severity) - .with_message($msg) - .with_primary_label(span, $label) - .with_note($note) - .emit(); - }}; - - ($diagnostics:ident, $severity:expr, $msg:literal, $span:expr, $label:expr, $span2:expr, $label2:expr) => {{ - let span = $span; - let span2 = $span2; - $diagnostics - .diagnostic($severity) - .with_message($msg) - .with_primary_label(span, $label) - .with_secondary_label(span2, $label2) - .emit(); - }}; - - ($diagnostics:ident, $severity:expr, $msg:literal, $span:expr, $label:expr, $span2:expr, $label2:expr, $note:expr) => {{ - let span = $span; - let span2 = $span2; - $diagnostics - .diagnostic($severity) - .with_message($msg) - .with_primary_label(span, $label) - .with_secondary_label(span2, $label2) - .with_help($note) - .emit(); - }}; -} +pub type FxHashMap = hashbrown::HashMap; +pub type FxHashSet = hashbrown::HashSet; +pub use rustc_hash::{FxBuildHasher, FxHasher}; pub mod adt; -mod asm; -mod attribute; -mod block; -mod builder; -mod component; -mod constants; -mod dataflow; -mod display; +mod any; +mod attributes; +pub mod constants; +pub mod demangle; +pub mod derive; +pub mod dialects; +mod direction; +mod eq; +mod folder; pub mod formatter; -mod function; -mod globals; -mod ident; -mod immediates; -mod insert; -mod instruction; -mod layout; -mod locals; -mod module; +mod hash; +mod ir; +pub mod itertools; +pub mod matchers; pub mod pass; -mod program; -mod segments; -pub mod testing; -#[cfg(test)] -mod tests; -mod value; - -use core::fmt; +pub mod patterns; +mod program_point; +pub mod version; -// Re-export cranelift_entity so that users don't have to hunt for the same version -pub use cranelift_entity; +pub use midenc_session::diagnostics; pub use self::{ - asm::*, - attribute::{attributes, Attribute, AttributeSet, AttributeValue}, - block::{Block, BlockData}, - builder::{DefaultInstBuilder, FunctionBuilder, InstBuilder, InstBuilderBase, ReplaceBuilder}, - component::*, - constants::{Constant, ConstantData, ConstantPool, IntoBytes}, - dataflow::DataFlowGraph, - display::{Decorator, DisplayValues}, - function::*, - globals::*, - ident::{demangle, FunctionIdent, Ident}, - immediates::Immediate, - insert::{Insert, InsertionPoint}, - instruction::*, - layout::{ArenaMap, LayoutAdapter, LayoutNode, OrderedArenaMap}, - locals::{Local, LocalId}, - module::*, - pass::{ - AnalysisKey, ConversionPassRegistration, ModuleRewritePassAdapter, PassInfo, - RewritePassRegistration, + attributes::{ + markers::*, ArrayAttr, Attribute, AttributeSet, AttributeValue, DictAttr, Overflow, + SetAttr, Visibility, }, - program::{Linker, Program, ProgramAnalysisKey, ProgramBuilder}, - segments::{DataSegment, DataSegmentAdapter, DataSegmentError, DataSegmentTable}, - value::{Value, ValueData, ValueList, ValueListPool}, + direction::{Backward, Direction, Forward}, + eq::DynPartialEq, + folder::OperationFolder, + hash::{DynHash, DynHasher}, + ir::*, + itertools::IteratorExt, + patterns::{Rewriter, RewriterExt}, + program_point::{Position, ProgramPoint}, }; - -/// A `ProgramPoint` represents a position in a function where the live range of an SSA value can -/// begin or end. It can be either: -/// -/// 1. An instruction or -/// 2. A block header. -/// -/// This corresponds more or less to the lines in the textual form of the IR. -#[derive(PartialEq, Eq, Clone, Copy, Hash)] -pub enum ProgramPoint { - /// An instruction in the function. - Inst(Inst), - /// A block header. - Block(Block), -} -impl ProgramPoint { - /// Get the instruction we know is inside. - pub fn unwrap_inst(self) -> Inst { - match self { - Self::Inst(x) => x, - Self::Block(x) => panic!("expected inst: {}", x), - } - } -} -impl From for ProgramPoint { - fn from(inst: Inst) -> Self { - Self::Inst(inst) - } -} -impl From for ProgramPoint { - fn from(block: Block) -> Self { - Self::Block(block) - } -} -impl fmt::Display for ProgramPoint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Self::Inst(x) => write!(f, "{}", x), - Self::Block(x) => write!(f, "{}", x), - } - } -} -impl fmt::Debug for ProgramPoint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "ProgramPoint({})", self) - } -} diff --git a/hir/src/locals.rs b/hir/src/locals.rs deleted file mode 100644 index 31431e7d7..000000000 --- a/hir/src/locals.rs +++ /dev/null @@ -1,99 +0,0 @@ -use alloc::alloc::Layout; -use core::fmt; - -use super::Type; - -/// A strongly typed identifier for referencing locals associated with a function -#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct LocalId(u16); -impl LocalId { - /// Create a new instance from a `u16`. - #[inline] - pub fn from_u16(x: u16) -> Self { - debug_assert!(x < u16::MAX, "invalid raw local id"); - Self(x) - } - - /// Return the underlying index value as a `usize`. - #[inline] - pub fn as_usize(self) -> usize { - self.0 as usize - } -} -impl cranelift_entity::EntityRef for LocalId { - #[inline] - fn new(index: usize) -> Self { - debug_assert!(index < (u16::MAX as usize)); - Self(index as u16) - } - - #[inline] - fn index(self) -> usize { - self.0 as usize - } -} -impl cranelift_entity::packed_option::ReservedValue for LocalId { - #[inline] - fn reserved_value() -> LocalId { - Self(u16::MAX) - } - - #[inline] - fn is_reserved_value(&self) -> bool { - self.0 == u16::MAX - } -} -impl fmt::Display for LocalId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "local{}", self.0) - } -} -impl fmt::Debug for LocalId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0, f) - } -} -impl From for u16 { - #[inline(always)] - fn from(id: LocalId) -> Self { - id.0 - } -} -impl From for miden_assembly::ast::Immediate { - #[inline(always)] - fn from(id: LocalId) -> Self { - miden_assembly::ast::Immediate::Value(miden_assembly::Span::unknown(id.0)) - } -} - -/// Represents a local allocated on the heap statically -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Local { - /// The unique identifier associated with this local - /// - /// It also represents the offset in the set of locals of a function - /// where this local will be allocated. - /// - /// NOTE: If a local's size is larger than a word, multiple consecutive - /// local allocations may be made to ensure there is enough memory starting - /// at the offset represented by `id` to hold the entire value - pub id: LocalId, - /// The type of the value stored in this local - pub ty: Type, -} -impl Local { - /// Returns the [Layout] for this local in memory - pub fn layout(&self) -> Layout { - self.ty.layout() - } - - /// Returns the size in bytes for this local, including necessary alignment padding - pub fn size_in_bytes(&self) -> usize { - self.ty.size_in_bytes() - } - - /// Returns the size in words for this local, including necessary alignment padding - pub fn size_in_words(&self) -> usize { - self.ty.size_in_words() - } -} diff --git a/hir/src/matchers.rs b/hir/src/matchers.rs new file mode 100644 index 000000000..1c3a0dc73 --- /dev/null +++ b/hir/src/matchers.rs @@ -0,0 +1,3 @@ +mod matcher; + +pub use self::matcher::*; diff --git a/hir/src/matchers/matcher.rs b/hir/src/matchers/matcher.rs new file mode 100644 index 000000000..ba04d4d9d --- /dev/null +++ b/hir/src/matchers/matcher.rs @@ -0,0 +1,772 @@ +use alloc::boxed::Box; +use core::ptr::{DynMetadata, Pointee}; + +use smallvec::SmallVec; + +use crate::{ + AttributeValue, Op, OpFoldResult, OpOperand, Operation, OperationRef, UnsafeIntrusiveEntityRef, + ValueRef, +}; + +/// [Matcher] is a pattern matching abstraction with support for expressing both matching and +/// capturing semantics. +/// +/// This is used to implement low-level pattern matching primitives for the IR for use in: +/// +/// * Folding +/// * Canonicalization +/// * Regionalized transformations and analyses +pub trait Matcher { + /// The value type produced as a result of a successful match + /// + /// Use `()` if this matcher does not capture any value, and simply signals whether or not + /// the pattern was matched. + type Matched; + + /// Check if `entity` is matched by this matcher, returning `Self::Matched` if successful. + fn matches(&self, entity: &T) -> Option; +} + +#[repr(transparent)] +pub struct MatchWith(pub F); +impl Matcher for MatchWith +where + F: Fn(&T) -> Option, +{ + type Matched = U; + + #[inline(always)] + fn matches(&self, entity: &T) -> Option { + (self.0)(entity) + } +} + +/// A match combinator representing the logical AND of two sub-matchers. +/// +/// Both patterns must match on the same IR entity, but only the matched value of `B` is returned, +/// i.e. the captured result of `A` is discarded. +/// +/// Returns the result of matching `B` if successful, otherwise `None` +pub struct AndMatcher { + a: A, + b: B, +} + +impl AndMatcher { + pub const fn new(a: A, b: B) -> Self { + Self { a, b } + } +} + +impl Matcher for AndMatcher +where + A: Matcher, + B: Matcher, +{ + type Matched = >::Matched; + + #[inline] + fn matches(&self, entity: &T) -> Option { + self.a.matches(entity).and_then(|_| self.b.matches(entity)) + } +} + +/// A match combinator representing a monadic bind of two patterns. +/// +/// In other words, given two patterns `A` and `B`: +/// +/// * `A` is matched, and if it fails, the entire match fails. +/// * `B` is then matched against the output of `A`, and if it fails, the entire match fails +/// * Both matches were successful, and the output of `B` is returned as the final result. +pub struct ChainMatcher { + a: A, + b: B, +} + +impl ChainMatcher { + pub const fn new(a: A, b: B) -> Self { + Self { a, b } + } +} + +impl Matcher for ChainMatcher +where + A: Matcher, + B: Matcher, +{ + type Matched = >::Matched; + + #[inline] + fn matches(&self, entity: &T) -> Option { + self.a.matches(entity).and_then(|matched| self.b.matches(&matched)) + } +} + +/// Matches operations which implement some trait `Trait`, capturing the match as a trait object. +/// +/// NOTE: `Trait` must be an object-safe trait. +pub struct OpTraitMatcher { + _marker: core::marker::PhantomData, +} + +impl Default for OpTraitMatcher +where + Trait: ?Sized + Pointee> + 'static, +{ + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl OpTraitMatcher +where + Trait: ?Sized + Pointee> + 'static, +{ + /// Create a new [OpTraitMatcher] from the given matcher. + pub const fn new() -> Self { + Self { + _marker: core::marker::PhantomData, + } + } +} + +impl Matcher for OpTraitMatcher +where + Trait: ?Sized + Pointee> + 'static, +{ + type Matched = UnsafeIntrusiveEntityRef; + + fn matches(&self, entity: &Operation) -> Option { + entity + .as_trait::() + .map(|op| unsafe { UnsafeIntrusiveEntityRef::from_raw(op) }) + } +} + +/// Matches operations which implement some trait `Trait`. +/// +/// Returns a type-erased operation ref, not a trait object like [OpTraitMatcher] +pub struct HasTraitMatcher { + _marker: core::marker::PhantomData, +} + +impl Default for HasTraitMatcher +where + Trait: ?Sized + Pointee> + 'static, +{ + #[inline] + fn default() -> Self { + Self::new() + } +} + +impl HasTraitMatcher +where + Trait: ?Sized + Pointee> + 'static, +{ + /// Create a new [HasTraitMatcher] from the given matcher. + pub const fn new() -> Self { + Self { + _marker: core::marker::PhantomData, + } + } +} + +impl Matcher for HasTraitMatcher +where + Trait: ?Sized + Pointee> + 'static, +{ + type Matched = OperationRef; + + fn matches(&self, entity: &Operation) -> Option { + if !entity.implements::() { + return None; + } + Some(entity.as_operation_ref()) + } +} + +/// Matches any operation with an attribute named `name`, the value of which matches a matcher of +/// type `M`. +pub struct OpAttrMatcher { + name: &'static str, + matcher: M, +} + +impl OpAttrMatcher +where + M: Matcher, +{ + /// Create a new [OpAttrMatcher] from the given attribute name and matcher. + pub const fn new(name: &'static str, matcher: M) -> Self { + Self { name, matcher } + } +} + +impl Matcher for OpAttrMatcher +where + M: Matcher, +{ + type Matched = >::Matched; + + fn matches(&self, entity: &Operation) -> Option { + entity.get_attribute(self.name).and_then(|value| self.matcher.matches(value)) + } +} + +/// Matches any operation with an attribute `name` and concrete value type of `A`. +/// +/// Binds the value as its concrete type `A`. +pub type TypedOpAttrMatcher = OpAttrMatcher>; + +/// Matches and binds any attribute value whose concrete type is `A`. +pub struct TypedAttrMatcher(core::marker::PhantomData); +impl Default for TypedAttrMatcher { + #[inline(always)] + fn default() -> Self { + Self(core::marker::PhantomData) + } +} +impl Matcher for TypedAttrMatcher { + type Matched = A; + + #[inline] + fn matches(&self, entity: &dyn AttributeValue) -> Option { + entity.downcast_ref::().cloned() + } +} + +/// A matcher for operations that always succeeds, binding the operation reference in the process. +struct AnyOpMatcher; +impl Matcher for AnyOpMatcher { + type Matched = OperationRef; + + #[inline(always)] + fn matches(&self, entity: &Operation) -> Option { + Some(entity.as_operation_ref()) + } +} + +/// A matcher for operations whose concrete type is `T`, binding the op with a strongly-typed +/// reference. +struct OneOpMatcher(core::marker::PhantomData); +impl OneOpMatcher { + pub const fn new() -> Self { + Self(core::marker::PhantomData) + } +} +impl Matcher for OneOpMatcher { + type Matched = UnsafeIntrusiveEntityRef; + + #[inline(always)] + fn matches(&self, entity: &Operation) -> Option { + entity + .downcast_ref::() + .map(|op| unsafe { UnsafeIntrusiveEntityRef::from_raw(op) }) + } +} + +/// A matcher for values that always succeeds, binding the value reference in the process. +struct AnyValueMatcher; +impl Matcher for AnyValueMatcher { + type Matched = ValueRef; + + #[inline(always)] + fn matches(&self, entity: &ValueRef) -> Option { + Some(*entity) + } +} + +/// A matcher that only succeeds if it matches exactly the provided value. +struct ExactValueMatcher(ValueRef); +impl Matcher for ExactValueMatcher { + type Matched = ValueRef; + + #[inline(always)] + fn matches(&self, entity: &ValueRef) -> Option { + if ValueRef::ptr_eq(&self.0, entity) { + Some(*entity) + } else { + None + } + } +} + +/// A matcher for operations that implement [crate::traits::ConstantLike] +type ConstantOpMatcher = HasTraitMatcher; + +/// Like [ConstantOpMatcher], this matcher matches constant operations, but rather than binding +/// the operation itself, it binds the constant value produced by the operation. +#[derive(Default)] +struct ConstantOpBinder; +impl Matcher for ConstantOpBinder { + type Matched = Box; + + fn matches(&self, entity: &Operation) -> Option { + use crate::traits::Foldable; + + if !entity.implements::() { + return None; + } + + let mut out = SmallVec::default(); + entity.fold(&mut out).unwrap_or_else(|| { + panic!("expected constant-like op '{}' to be foldable", entity.name()) + }); + let Some(OpFoldResult::Attribute(value)) = out.pop() else { + return None; + }; + + Some(value) + } +} + +/// An extension of [ConstantOpBinder] which only matches constant values of type `T` +struct TypedConstantOpBinder(core::marker::PhantomData); +impl TypedConstantOpBinder { + pub const fn new() -> Self { + Self(core::marker::PhantomData) + } +} +impl Matcher for TypedConstantOpBinder { + type Matched = T; + + fn matches(&self, entity: &Operation) -> Option { + ConstantOpBinder.matches(entity).and_then(|value| { + if !value.is::() { + None + } else { + Some(unsafe { + let raw = Box::into_raw(value); + *Box::from_raw(raw as *mut T) + }) + } + }) + } +} + +/// Matches operations which implement [crate::traits::UnaryOp] and binds the operand. +#[derive(Default)] +struct UnaryOpBinder; +impl Matcher for UnaryOpBinder { + type Matched = OpOperand; + + fn matches(&self, entity: &Operation) -> Option { + if !entity.implements::() { + return None; + } + + Some(entity.operands()[0].borrow().as_operand_ref()) + } +} + +/// Matches operations which implement [crate::traits::BinaryOp] and binds both operands. +#[derive(Default)] +struct BinaryOpBinder; +impl Matcher for BinaryOpBinder { + type Matched = [OpOperand; 2]; + + fn matches(&self, entity: &Operation) -> Option { + if !entity.implements::() { + return None; + } + + let operands = entity.operands(); + let lhs = operands[0].borrow().as_operand_ref(); + let rhs = operands[1].borrow().as_operand_ref(); + + Some([lhs, rhs]) + } +} + +/// Converts the output of [UnaryOpBinder] to an OpFoldResult, by checking if the operand definition +/// is a constant-like op, and either binding the constant value, or the SSA value used as the +/// operand. +/// +/// This can be used to set up for folding. +struct FoldResultBinder; +impl Matcher for FoldResultBinder { + type Matched = OpFoldResult; + + fn matches(&self, operand: &OpOperand) -> Option { + let operand = operand.borrow(); + let maybe_constant = operand + .value() + .get_defining_op() + .and_then(|defining_op| constant().matches(&defining_op.borrow())); + if let Some(const_operand) = maybe_constant { + Some(OpFoldResult::Attribute(const_operand)) + } else { + Some(OpFoldResult::Value(operand.as_value_ref())) + } + } +} + +/// Converts the output of [BinaryOpBinder] to a pair of OpFoldResults, by checking if the operand +/// definitions are constant, and either binding the constant values, or the SSA values used by each +/// operand. +/// +/// This can be used to set up for folding. +struct BinaryFoldResultBinder; +impl Matcher<[OpOperand; 2]> for BinaryFoldResultBinder { + type Matched = [OpFoldResult; 2]; + + fn matches(&self, operands: &[OpOperand; 2]) -> Option { + let binder = FoldResultBinder; + + let lhs = binder.matches(&operands[0]).unwrap(); + let rhs = binder.matches(&operands[1]).unwrap(); + + Some([lhs, rhs]) + } +} + +/// Matches the operand of a unary op to determine if it is a candidate for folding. +/// +/// A successful match binds the constant value of the operand for use by the [Foldable] impl. +struct FoldableOperandBinder; +impl Matcher for FoldableOperandBinder { + type Matched = Box; + + fn matches(&self, operand: &OpOperand) -> Option { + let operand = operand.borrow(); + let defining_op = operand.value().get_defining_op()?; + constant().matches(&defining_op.borrow()) + } +} + +struct TypedFoldableOperandBinder(core::marker::PhantomData); +impl Default for TypedFoldableOperandBinder { + fn default() -> Self { + Self(core::marker::PhantomData) + } +} +impl Matcher for TypedFoldableOperandBinder { + type Matched = Box; + + fn matches(&self, operand: &OpOperand) -> Option { + FoldableOperandBinder + .matches(operand) + .and_then(|value| value.downcast::().ok()) + } +} + +/// Matches the operands of a binary op to determine if it is a candidate for folding. +/// +/// A successful match binds the constant value of the operands for use by the [Foldable] impl. +/// +/// NOTE: Both operands must be constant for this to match. Use [BinaryFoldResultBinder] if you +/// wish to let the [Foldable] impl decide what to do in the presence of mixed constant and non- +/// constant operands. +struct FoldableBinaryOpBinder; +impl Matcher<[OpOperand; 2]> for FoldableBinaryOpBinder { + type Matched = [Box; 2]; + + fn matches(&self, operands: &[OpOperand; 2]) -> Option { + let binder = FoldableOperandBinder; + let lhs = binder.matches(&operands[0])?; + let rhs = binder.matches(&operands[1])?; + + Some([lhs, rhs]) + } +} + +// Match Combinators + +/// Matches both `a` and `b`, or fails +pub const fn match_both( + a: A, + b: B, +) -> impl Matcher>::Matched> +where + A: Matcher, + B: Matcher, +{ + AndMatcher::new(a, b) +} + +/// Matches `a` and if successful, matches `b` against the output of `a`, or fails. +pub const fn match_chain( + a: A, + b: B, +) -> impl Matcher>::Matched> +where + A: Matcher, + B: Matcher, +{ + ChainMatcher::new(a, b) +} + +// Operation Matchers + +/// Matches any operation, i.e. it always matches +/// +/// Returns a type-erased operation reference +pub const fn match_any() -> impl Matcher { + AnyOpMatcher +} + +/// Matches any operation whose concrete type is `T` +/// +/// Returns a strongly-typed op reference +pub const fn match_op() -> impl Matcher> { + OneOpMatcher::::new() +} + +/// Matches any operation that implements [crate::traits::ConstantLike]. +/// +/// These operations return a single result, and must be pure (no side effects) +pub const fn constant_like() -> impl Matcher { + ConstantOpMatcher::new() +} + +// Constant Value Binders + +/// Matches any operation that implements [crate::traits::ConstantLike], and binds the constant +/// value as the result of the match. +pub const fn constant() -> impl Matcher> { + ConstantOpBinder +} + +/// Like [constant], but only matches if the constant value has the concrete type `T`. +/// +/// Typically, constant values will be [crate::Immediate], but any attribute value can be matched. +pub const fn constant_of() -> impl Matcher { + TypedConstantOpBinder::new() +} + +// Value Binders + +/// Matches any unary operation (i.e. implements [crate::traits::UnaryOp]), and binds its operand. +pub const fn unary() -> impl Matcher { + UnaryOpBinder +} + +/// Matches any unary operation (i.e. implements [crate::traits::UnaryOp]), and binds its operand +/// as an [OpFoldResult]. +/// +/// This is done by examining the defining op of the operand to determine if it is a constant, and +/// if so, it binds the constant value, rather than the SSA value. +/// +/// This can be used to setup for folding. +pub const fn unary_fold_result() -> impl Matcher { + match_chain(UnaryOpBinder, FoldResultBinder) +} + +/// Matches any unary operation (i.e. implements [crate::traits::UnaryOp]) whose operand is a +/// materialized constant, and thus a prime candidate for folding. +/// +/// The constant value is bound by this matcher, so it can be used immediately for folding. +pub const fn unary_foldable() -> impl Matcher> { + match_chain(UnaryOpBinder, FoldableOperandBinder) +} + +/// Matches any binary operation (i.e. implements [crate::traits::BinaryOp]), and binds its operands. +pub const fn binary() -> impl Matcher { + BinaryOpBinder +} + +/// Matches any binary operation (i.e. implements [crate::traits::BinaryOp]), and binds its operands +/// as [OpFoldResult]s. +/// +/// This is done by examining the defining op of the operands to determine if they are constant, and +/// if so, binds the constant value, rather than the SSA value. +/// +/// This can be used to setup for folding. +pub const fn binary_fold_results() -> impl Matcher { + match_chain(BinaryOpBinder, BinaryFoldResultBinder) +} + +/// Matches any binary operation (i.e. implements [crate::traits::BinaryOp]) whose operands are +/// both materialized constants, and thus a prime candidate for folding. +/// +/// The constant values are bound by this matcher, so they can be used immediately for folding. +pub const fn binary_foldable() -> impl Matcher; 2]> { + match_chain(BinaryOpBinder, FoldableBinaryOpBinder) +} + +// Value Matchers + +/// Matches any value, i.e. it always matches +pub const fn match_any_value() -> impl Matcher { + AnyValueMatcher +} + +/// Matches any instance of `value`, i.e. it requires an exact match +pub const fn match_value(value: ValueRef) -> impl Matcher { + ExactValueMatcher(value) +} + +pub const fn foldable_operand() -> impl Matcher> { + FoldableOperandBinder +} + +pub const fn foldable_operand_of() -> impl Matcher> +where + T: AttributeValue + Clone, +{ + TypedFoldableOperandBinder(core::marker::PhantomData) +} + +#[cfg(test)] +mod tests { + use alloc::rc::Rc; + + use super::*; + use crate::{ + dialects::{builtin::*, test::*}, + *, + }; + + #[test] + fn matcher_match_any_value() { + let context = Rc::new(Context::default()); + + let (lhs, rhs, sum) = setup(context.clone()); + + // All three values should `match_any_value` + for value in [&lhs, &rhs, &sum] { + assert_eq!(match_any_value().matches(value).as_ref(), Some(value)); + } + } + + #[test] + fn matcher_match_value() { + let context = Rc::new(Context::default()); + + let (lhs, rhs, sum) = setup(context.clone()); + + // All three values should match themselves via `match_value` + for value in [&lhs, &rhs, &sum] { + assert_eq!(match_value(*value).matches(value).as_ref(), Some(value)); + } + } + + #[test] + fn matcher_match_any() { + let context = Rc::new(Context::default()); + + let (lhs, _rhs, sum) = setup(context.clone()); + + // We should be able to match `lhs` and `sum` ops using `match_any` + let lhs_op = lhs.borrow().get_defining_op().unwrap(); + let sum_op = sum.borrow().get_defining_op().unwrap(); + + for op in [&lhs_op, &sum_op] { + assert_eq!(match_any().matches(&op.borrow()).as_ref(), Some(op)); + } + } + + #[test] + fn matcher_match_op() { + let context = Rc::new(Context::default()); + + let (lhs, rhs, sum) = setup(context.clone()); + let lhs_op = lhs.borrow().get_defining_op().unwrap(); + let sum_op = sum.borrow().get_defining_op().unwrap(); + assert!(rhs.borrow().get_defining_op().is_none()); + + // Both `lhs` and `sum` ops should be matched as their respective operation types, and not + // as a different operation type + assert!(match_op::().matches(&lhs_op.borrow()).is_some()); + assert!(match_op::().matches(&sum_op.borrow()).is_none()); + assert!(match_op::().matches(&lhs_op.borrow()).is_none()); + assert!(match_op::().matches(&sum_op.borrow()).is_some()); + } + + #[test] + fn matcher_match_both() { + let context = Rc::new(Context::default()); + + let (lhs, _rhs, _sum) = setup(context.clone()); + let lhs_op = lhs.borrow().get_defining_op().unwrap(); + + // Ensure if the first matcher fails, then the whole match fails + assert!(match_both(match_op::(), constant_of::()) + .matches(&lhs_op.borrow()) + .is_none()); + // Ensure if the second matcher fails, then the whole match fails + assert!(match_both(constant_like(), constant_of::()) + .matches(&lhs_op.borrow()) + .is_none()); + // Ensure that if both matchers would succeed, then the whole match succeeds + assert!(match_both(constant_like(), constant_of::()) + .matches(&lhs_op.borrow()) + .is_some()); + } + + #[test] + fn matcher_match_chain() { + let context = Rc::new(Context::default()); + + let (_, rhs, sum) = setup(context.clone()); + let sum_op = sum.borrow().get_defining_op().unwrap(); + + let [lhs_fr, rhs_fr] = binary_fold_results() + .matches(&sum_op.borrow()) + .expect("expected to bind both operands of 'add'"); + assert_eq!(lhs_fr, OpFoldResult::Attribute(Box::new(Immediate::U32(1)))); + assert_eq!(rhs_fr, OpFoldResult::Value(rhs)); + } + + #[test] + fn matcher_constant_like() { + let context = Rc::new(Context::default()); + + let (lhs, _rhs, sum) = setup(context.clone()); + let lhs_op = lhs.borrow().get_defining_op().unwrap(); + let sum_op = sum.borrow().get_defining_op().unwrap(); + + // Only `lhs` should be matched by `constant_like` + assert!(constant_like().matches(&lhs_op.borrow()).is_some()); + assert!(constant_like().matches(&sum_op.borrow()).is_none()); + } + + #[test] + fn matcher_constant() { + let context = Rc::new(Context::default()); + + let (lhs, _rhs, sum) = setup(context.clone()); + let lhs_op = lhs.borrow().get_defining_op().unwrap(); + let sum_op = sum.borrow().get_defining_op().unwrap(); + + // Only `lhs` should produce a matching constant value + assert!(constant().matches(&lhs_op.borrow()).is_some()); + assert!(constant().matches(&sum_op.borrow()).is_none()); + } + + #[test] + fn matcher_constant_of() { + let context = Rc::new(Context::default()); + + let (lhs, _rhs, sum) = setup(context.clone()); + let lhs_op = lhs.borrow().get_defining_op().unwrap(); + let sum_op = sum.borrow().get_defining_op().unwrap(); + + // `lhs` should produce a matching constant value of the correct type and value + assert_eq!(constant_of::().matches(&lhs_op.borrow()), Some(Immediate::U32(1))); + assert!(constant_of::().matches(&sum_op.borrow()).is_none()); + } + + fn setup(context: Rc) -> (ValueRef, ValueRef, ValueRef) { + let mut builder = OpBuilder::new(Rc::clone(&context)); + + let function = { + let builder = builder.create::(SourceSpan::default()); + let name = Ident::new("test".into(), SourceSpan::default()); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + // Define function body + let mut builder = FunctionBuilder::new(function, &mut builder); + let lhs = builder.u32(1, SourceSpan::default()).unwrap(); + let block = builder.current_block(); + let rhs = block.borrow().arguments()[0].upcast(); + let sum = builder.add(lhs, rhs, SourceSpan::default()).unwrap(); + builder.ret(Some(sum), SourceSpan::default()).unwrap(); + + (lhs, rhs, sum) + } +} diff --git a/hir/src/module.rs b/hir/src/module.rs deleted file mode 100644 index ff90c3858..000000000 --- a/hir/src/module.rs +++ /dev/null @@ -1,967 +0,0 @@ -use alloc::collections::BTreeMap; - -use intrusive_collections::{ - intrusive_adapter, - linked_list::{Cursor, CursorMut}, - LinkedList, LinkedListLink, RBTreeLink, -}; -use rustc_hash::FxHashSet; - -use self::formatter::PrettyPrint; -use crate::{ - diagnostics::{miette, Diagnostic, DiagnosticsHandler, Report, Severity, Spanned}, - *, -}; - -/// This error is raised when two modules conflict with the same symbol name -#[derive(Debug, thiserror::Error, Diagnostic)] -#[error("module {} has already been declared", .name)] -#[diagnostic()] -pub struct ModuleConflictError { - #[label("duplicate declaration occurs here")] - pub span: SourceSpan, - pub name: Symbol, -} -impl ModuleConflictError { - pub fn new(name: Ident) -> Self { - Self { - span: name.span, - name: name.as_symbol(), - } - } -} - -pub type ModuleTree = intrusive_collections::RBTree; -pub type ModuleList = intrusive_collections::LinkedList; - -intrusive_adapter!(pub ModuleListAdapter = Box: Module { list_link: LinkedListLink }); -intrusive_adapter!(pub ModuleTreeAdapter = Box: Module { link: RBTreeLink }); -impl<'a> intrusive_collections::KeyAdapter<'a> for ModuleTreeAdapter { - type Key = Ident; - - #[inline] - fn get_key(&self, module: &'a Module) -> Ident { - module.name - } -} - -/// Represents a SSA IR module -/// -/// These correspond to MASM modules. -/// -/// This module is largely a container for functions, but it also provides -/// as the owner for pooled resources available to functions: -/// -/// * Mapping from Signature to FuncRef -/// * Mapping from FunctionName to FuncRef -#[derive(Spanned, AnalysisKey)] -pub struct Module { - /// The link used to attach this module to a [Program] - link: RBTreeLink, - /// The link used to store this module in a list of modules - list_link: LinkedListLink, - /// The name of this module - #[span] - #[analysis_key] - pub name: Ident, - /// Documentation attached to this module, to be passed through to - /// Miden Assembly during code generation. - pub docs: Option, - /// The size of the linear memory region (in pages) which is reserved by the module creator. - /// - /// For example, with rustc-compiled Wasm modules, it reserves 16 pages of memory for the - /// shadow stack, and if there is any static data, a minimum of 1 page for the static data. - /// As a result, we must ensure that we do not allocate any globals or other items in this - /// reserved region. - reserved_memory_pages: u32, - /// The page size (in bytes) used by this module. - /// - /// Set to 64k by default. - page_size: u32, - /// The set of data segments allocated in this module - pub(crate) segments: DataSegmentTable, - /// The set of global variables declared in this module - pub(crate) globals: GlobalVariableTable, - /// The set of functions which belong to this module, in the order - /// in which they were defined. - pub(crate) functions: LinkedList, - /// This flag indicates whether this module is a kernel module - /// - /// Kernel modules have additional constraints imposed on them that regular - /// modules do not, in exchange for some useful functionality: - /// - /// * Functions with external linkage are required to use the `Kernel` calling convention. - /// * A kernel module executes in the root context of the Miden VM, allowing one to expose - /// functionality that is protected from tampering by other non-kernel functions in the - /// program. - /// * Due to the above, you may not reference globals outside the kernel module, from within - /// kernel functions, as they are not available in the root context. - is_kernel: bool, -} -impl fmt::Display for Module { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.pretty_print(f) - } -} -impl formatter::PrettyPrint for Module { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let mut header = - const_text("(") + const_text("module") + const_text(" ") + display(self.name); - if self.is_kernel { - header += const_text(" ") + const_text("(") + const_text("kernel") + const_text(")"); - } - - let segments = self - .segments - .iter() - .map(PrettyPrint::render) - .reduce(|acc, doc| acc + nl() + doc) - .map(|doc| const_text(";; Data Segments") + nl() + doc) - .unwrap_or(Document::Empty); - - let constants = self - .globals - .constants() - .map(|(constant, constant_data)| { - const_text("(") - + const_text("const") - + const_text(" ") - + const_text("(") - + const_text("id") - + const_text(" ") - + display(constant.as_u32()) - + const_text(")") - + const_text(" ") - + text(format!("{:#x}", constant_data.as_ref())) - + const_text(")") - }) - .reduce(|acc, doc| acc + nl() + doc) - .map(|doc| const_text(";; Constants") + nl() + doc) - .unwrap_or(Document::Empty); - - let globals = self - .globals - .iter() - .map(PrettyPrint::render) - .reduce(|acc, doc| acc + nl() + doc) - .map(|doc| const_text(";; Global Variables") + nl() + doc) - .unwrap_or(Document::Empty); - - let mut external_functions = BTreeMap::::default(); - let functions = self - .functions - .iter() - .map(|fun| { - for import in fun.dfg.imports() { - // Don't print declarations for functions in this module - if import.id.module == self.name { - continue; - } - external_functions.entry(import.id).or_insert_with(|| import.signature.clone()); - } - fun.render() - }) - .reduce(|acc, doc| acc + nl() + nl() + doc) - .map(|doc| const_text(";; Functions") + nl() + doc) - .unwrap_or(Document::Empty); - - let imports = external_functions - .into_iter() - .map(|(id, signature)| ExternalFunction { id, signature }.render()) - .reduce(|acc, doc| acc + nl() + doc) - .map(|doc| const_text(";; Imports") + nl() + doc) - .unwrap_or(Document::Empty); - - let body = vec![segments, constants, globals, functions, imports] - .into_iter() - .filter(|section| !section.is_empty()) - .fold(nl(), |a, b| { - if matches!(a, Document::Newline) { - indent(4, a + b) - } else { - a + nl() + indent(4, nl() + b) - } - }); - - if body.is_empty() { - header + const_text(")") + nl() - } else { - header + body + nl() + const_text(")") + nl() - } - } -} -impl fmt::Debug for Module { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Module") - .field("name", &self.name) - .field("reserved_memory_pages", &self.reserved_memory_pages) - .field("page_size", &self.page_size) - .field("is_kernel", &self.is_kernel) - .field("docs", &self.docs) - .field("segments", &self.segments) - .field("globals", &self.globals) - .field("functions", &self.functions) - .finish() - } -} -impl midenc_session::Emit for Module { - fn name(&self) -> Option { - Some(self.name.as_symbol()) - } - - fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { - midenc_session::OutputType::Hir - } - - fn write_to( - &self, - mut writer: W, - mode: midenc_session::OutputMode, - _session: &midenc_session::Session, - ) -> std::io::Result<()> { - assert_eq!( - mode, - midenc_session::OutputMode::Text, - "binary mode is not supported for HIR modules" - ); - writer.write_fmt(format_args!("{}", self)) - } -} -impl Eq for Module {} -impl PartialEq for Module { - fn eq(&self, other: &Self) -> bool { - let is_eq = self.name == other.name - && self.is_kernel == other.is_kernel - && self.reserved_memory_pages == other.reserved_memory_pages - && self.page_size == other.page_size - && self.docs == other.docs - && self.segments.iter().eq(other.segments.iter()) - && self.globals.len() == other.globals.len() - && self.functions.iter().count() == other.functions.iter().count(); - if !is_eq { - return false; - } - - for global in self.globals.iter() { - let id = global.id(); - if !other.globals.contains_key(id) { - return false; - } - let other_global = other.globals.get(id); - if global != other_global { - return false; - } - } - - for function in self.functions.iter() { - if !other.contains(function.id.function) { - return false; - } - if let Some(other_function) = other.function(function.id.function) { - if function != other_function { - return false; - } - } else { - return false; - } - } - - true - } -} - -/// This macro asserts that a function is valid for insertion into a given module. -macro_rules! assert_valid_function { - ($module:ident, $function:ident) => { - assert_eq!($module.name, $function.id.module, "mismatched module identifiers"); - assert!( - $function.is_detached(), - "cannot attach a function to a module that is already attached to a module" - ); - // Validate the kernel rules - if $function.is_kernel() { - assert!($module.is_kernel, "cannot add kernel functions to a non-kernel module"); - } else if $module.is_kernel && $function.is_public() { - panic!( - "functions with external linkage in kernel modules must use the kernel calling \ - convention" - ); - } - }; -} - -impl Module { - /// Create a new, empty [Module] - pub fn new>(name: S) -> Self { - Self::make(name.into(), /* is_kernel= */ false) - } - - /// Create a new, empty [Module] with the given source location - pub fn new_with_span>(name: S, span: SourceSpan) -> Self { - let name = Ident::new(Symbol::intern(name.as_ref()), span); - Self::make(name, /* is_kernel= */ false) - } - - /// Create a new, empty kernel [Module] - pub fn new_kernel>(name: S) -> Self { - Self::make(name.into(), /* is_kernel= */ true) - } - - /// Create a new, empty kernel [Module] with the given source location - pub fn new_kernel_with_span>(name: S, span: SourceSpan) -> Self { - let name = Ident::new(Symbol::intern(name.as_ref()), span); - Self::make(name, /* is_kernel= */ true) - } - - fn make(name: Ident, is_kernel: bool) -> Self { - Self { - link: Default::default(), - list_link: Default::default(), - name, - docs: None, - reserved_memory_pages: 0, - page_size: 64 * 1024, - segments: Default::default(), - globals: GlobalVariableTable::new(ConflictResolutionStrategy::None), - functions: Default::default(), - is_kernel, - } - } - - /// Get the page size to use by default for this module. - #[inline] - pub const fn page_size(&self) -> u32 { - self.page_size - } - - /// Get the size (in pages) of the linear memory address space (starting from offset 0), which - /// is reserved for use by the caller. - #[inline] - pub const fn reserved_memory_pages(&self) -> u32 { - self.reserved_memory_pages - } - - /// Get the size (in bytes) of the linear memory address space (starting from offset 0), which - /// is reserved for use by the caller. - #[inline] - pub const fn reserved_memory_bytes(&self) -> u32 { - self.reserved_memory_pages * self.page_size - } - - /// Set the size of the reserved linear memory region. - /// - /// NOTE: Declared data segments can be placed in the reserved area, but global variables will - /// never be allocated in the reserved area. - pub fn set_reserved_memory_size(&mut self, size: u32) { - self.reserved_memory_pages = size; - } - - /// Returns true if this module is a kernel module - #[inline] - pub const fn is_kernel(&self) -> bool { - self.is_kernel - } - - /// Returns true if this module has yet to be attached to a [Program] - pub fn is_detached(&self) -> bool { - !self.link.is_linked() - } - - /// Return the table of data segments for this module - pub fn segments(&self) -> &DataSegmentTable { - &self.segments - } - - /// Declare a new [DataSegment] in this module, with the given offset, size, and data. - /// - /// Returns `Err` if the segment declaration is invalid, or conflicts with an existing segment - /// - /// Data segments are ordered by the address at which they are allocated, at link-time, all - /// segments from all modules are linked together, and they must either be disjoint, or exactly - /// identical in order to overlap - it is not permitted to have partially overlapping segments - /// with different views of the memory represented by that segment. - pub fn declare_data_segment( - &mut self, - offset: Offset, - size: u32, - init: ConstantData, - readonly: bool, - ) -> Result<(), DataSegmentError> { - self.segments.declare(offset, size, init, readonly) - } - - /// Return the table of global variables for this module - pub fn globals(&self) -> &GlobalVariableTable { - &self.globals - } - - /// Declare a new [GlobalVariable] in this module, with the given name, type, linkage, and - /// optional initializer. - /// - /// Returns `Err` if a symbol with the same name but conflicting declaration already exists, - /// or if the specification of the global variable is invalid in any way. - /// - /// NOTE: The [GlobalVariable] returned here is scoped to this module only, it cannot be used to - /// index into the global variable table of a [Program], which is constructed at link-time. - pub fn declare_global_variable( - &mut self, - name: Ident, - ty: Type, - linkage: Linkage, - init: Option, - ) -> Result { - self.globals.declare(name, ty, linkage, init) - } - - /// Set the initializer for a [GlobalVariable] to `init`. - /// - /// Returns `Err` if the initializer conflicts with the current definition of the global in any - /// way. - pub fn set_global_initializer( - &mut self, - gv: GlobalVariable, - init: ConstantData, - ) -> Result<(), GlobalVariableError> { - self.globals.set_initializer(gv, init) - } - - /// Get the data associated with the given [GlobalVariable] - #[inline] - pub fn global(&self, id: GlobalVariable) -> &GlobalVariableData { - self.globals.get(id) - } - - /// Look up a global by `name`. - pub fn find_global(&self, name: Ident) -> Option<&GlobalVariableData> { - self.globals.find(name).map(|gv| self.globals.get(gv)) - } - - /// Find the first function in this module marked with the `entrypoint` attribute - pub fn entrypoint(&self) -> Option { - self.functions.iter().find_map(|f| { - if f.has_attribute(&symbols::Entrypoint) { - Some(f.id) - } else { - None - } - }) - } - - /// Return an iterator over the functions in this module - /// - /// The iterator is double-ended, so can be used to traverse the module body in either direction - pub fn functions<'a, 'b: 'a>( - &'b self, - ) -> intrusive_collections::linked_list::Iter<'a, FunctionListAdapter> { - self.functions.iter() - } - - /// Get a [Function] in this module by name, if available - pub fn function<'a, 'b: 'a>(&'b self, id: Ident) -> Option<&'a Function> { - self.cursor_at(id).get() - } - - /// Compute the set of imports for this module, automatically aliasing modules when there - /// are namespace conflicts - pub fn imports(&self) -> ModuleImportInfo { - let mut imports = ModuleImportInfo::default(); - let locals = self.functions.iter().map(|f| f.id).collect::>(); - - for function in self.functions.iter() { - for import in function.imports() { - if !locals.contains(&import.id) { - imports.add(import.id); - } - } - } - imports - } - - /// Returns true if this module contains the function `name` - pub fn contains(&self, name: Ident) -> bool { - self.function(name).is_some() - } - - /// Unlinks the given function from this module - pub fn unlink(&mut self, id: Ident) -> Box { - let mut cursor = self.cursor_mut_at(id); - cursor - .remove() - .unwrap_or_else(|| panic!("cursor pointing to a null when removing function id: {id}")) - } - - /// Append `function` to the end of this module's body, returning the [FuncId] - /// assigned to it within this module. - /// - /// NOTE: This function will panic if either of the following rules are violated: - /// - /// * If this module is a kernel module, public functions must use the kernel calling - /// convention, however private functions can use any convention. - /// * If this module is not a kernel module, functions may not use the kernel calling convention - pub fn push(&mut self, function: Box) -> Result<(), SymbolConflictError> { - assert_valid_function!(self, function); - if let Some(prev) = self.function(function.id.function) { - return Err(SymbolConflictError(prev.id)); - } - self.functions.push_back(function); - Ok(()) - } - - /// Insert `function` in the module body before the function with id `before` - /// - /// If `before` is no longer attached to this module, `function` is added to - /// the end of the module body. - pub fn insert_before( - &mut self, - function: Box, - before: Ident, - ) -> Result<(), SymbolConflictError> { - assert_valid_function!(self, function); - if let Some(prev) = self.function(function.id.function) { - return Err(SymbolConflictError(prev.id)); - } - - let mut cursor = self.cursor_mut_at(before); - cursor.insert_before(function); - - Ok(()) - } - - /// Insert `function` in the module body after the function with id `after` - /// - /// If `after` is no longer attached to this module, `function` is added to - /// the end of the module body. - pub fn insert_after( - &mut self, - function: Box, - after: Ident, - ) -> Result<(), SymbolConflictError> { - assert_valid_function!(self, function); - if let Some(prev) = self.function(function.id.function) { - return Err(SymbolConflictError(prev.id)); - } - - let mut cursor = self.cursor_mut_at(after); - if cursor.is_null() { - cursor.insert_before(function); - } else { - cursor.insert_after(function); - } - - Ok(()) - } - - /// Remove the first function in the module, and return it, if present - pub fn pop_front(&mut self) -> Option> { - self.functions.pop_front() - } - - /// Returns a mutable cursor to the module body, starting at the first function. - /// - /// If the module body is empty, the returned cursor will point to the null object. - /// - /// NOTE: If one uses this cursor to insert a function that is invalid - #[inline] - pub fn cursor_mut<'a, 'b: 'a>(&'b mut self) -> ModuleCursor<'a> { - ModuleCursor { - cursor: self.functions.front_mut(), - name: self.name, - is_kernel: self.is_kernel, - } - } - - /// Returns a cursor to the module body, located at the function indicated by `id`. - /// - /// If no function with `id` is in the list, the returned cursor will point to the null object. - pub fn cursor_at<'a, 'b: 'a>(&'b self, id: Ident) -> Cursor<'a, FunctionListAdapter> { - let mut cursor = self.functions.front(); - while let Some(function) = cursor.get() { - if function.id.function == id { - break; - } - cursor.move_next(); - } - cursor - } - - /// Returns a mutable cursor to the module body, located at the function indicated by `id`. - /// - /// If no function with `id` is in the list, the returned cursor will point to the null object. - pub fn cursor_mut_at<'a, 'b: 'a>(&'b mut self, id: Ident) -> ModuleCursor<'a> { - let mut cursor = self.functions.front_mut(); - while let Some(function) = cursor.get() { - if function.id.function == id { - break; - } - cursor.move_next(); - } - ModuleCursor { - cursor, - name: self.name, - is_kernel: self.is_kernel, - } - } -} - -pub struct ModuleCursor<'a> { - cursor: CursorMut<'a, FunctionListAdapter>, - name: Ident, - is_kernel: bool, -} -impl<'a> ModuleCursor<'a> { - /// Returns true if this cursor is pointing to the null object - #[inline(always)] - pub fn is_null(&self) -> bool { - self.cursor.is_null() - } - - /// Return a reference to the function pointed to by this cursor - /// - /// If the cursor is pointing to the null object, `None` is returned - #[inline(always)] - pub fn get(&self) -> Option<&Function> { - self.cursor.get() - } - - /// Insert a new function into the module after the cursor. - /// - /// If the cursor is pointing to the null object, the insert happens at the front of the list. - /// - /// NOTE: This function will panic if the function violates the validation rules for - /// the module, i.e. must not be attached, follows kernel module rules when applicable. - pub fn insert_after(&mut self, function: Box) { - assert_valid_function!(self, function); - self.cursor.insert_after(function); - } - - /// Insert a new function into the module before the cursor. - /// - /// If the cursor is pointing to the null object, the insert happens at the end of the list. - /// - /// NOTE: This function will panic if the function violates the validation rules for - /// the module, i.e. must not be attached, follows kernel module rules when applicable. - pub fn insert_before(&mut self, function: Box) { - assert_valid_function!(self, function); - self.cursor.insert_before(function); - } - - /// Moves this cursor to the next function in the module. - /// - /// If the cursor is pointing to the null object, then this moves the cursor to the front - /// of the list. If at the end of the list, it moves to the null object. - #[inline(always)] - pub fn move_next(&mut self) { - self.cursor.move_next(); - } - - /// Moves this cursor to the previous function in the module. - /// - /// If the cursor is pointing to the null object, then this moves the cursor to the end - /// of the list. If at the front of the list, it moves to the null object. - #[inline(always)] - pub fn move_prev(&mut self) { - self.cursor.move_prev(); - } - - /// Return a cursor pointing to the next function in the module. - /// - /// If this cursor is on the null object, then the returned cursor will be on the - /// front of the list. If at the last element, then the returned cursor will on the - /// null object. - #[inline(always)] - pub fn peek_next(&self) -> Cursor<'_, FunctionListAdapter> { - self.cursor.peek_next() - } - - /// Return a cursor pointing to the previous function in the module. - /// - /// If this cursor is on the null object, then the returned cursor will be on the - /// end of the list. If at the first element, then the returned cursor will on the - /// null object. - #[inline(always)] - pub fn peek_prev(&self) -> Cursor<'_, FunctionListAdapter> { - self.cursor.peek_prev() - } - - /// Removes the current function from the module. - /// - /// The cursor will be moved to the next function in the module, or the null object - /// if we're at the end of the module. - #[inline(always)] - pub fn remove(&mut self) -> Option> { - self.cursor.remove() - } -} - -pub struct ModuleBuilder { - module: Box, -} -impl From> for ModuleBuilder { - fn from(module: Box) -> Self { - Self { module } - } -} -impl ModuleBuilder { - pub fn new>(name: S) -> Self { - Self { - module: Box::new(Module::new(name)), - } - } - - pub fn new_kernel>(name: S) -> Self { - Self { - module: Box::new(Module::new_kernel(name)), - } - } - - pub fn with_span(&mut self, span: SourceSpan) -> &mut Self { - self.module.name = Ident::new(self.module.name.as_symbol(), span); - self - } - - pub fn with_docs>(&mut self, docs: S) -> &mut Self { - self.module.docs = Some(docs.into()); - self - } - - pub fn with_page_size(&mut self, page_size: u32) -> &mut Self { - self.module.page_size = page_size; - self - } - - pub fn with_reserved_memory_pages(&mut self, num_pages: u32) -> &mut Self { - self.module.reserved_memory_pages = num_pages; - self - } - - pub fn name(&self) -> Ident { - self.module.name - } - - pub fn declare_global_variable>( - &mut self, - name: S, - ty: Type, - linkage: Linkage, - init: Option, - span: SourceSpan, - ) -> Result { - let name = Ident::new(Symbol::intern(name.as_ref()), span); - self.module.declare_global_variable(name, ty, linkage, init) - } - - pub fn set_global_initializer( - &mut self, - gv: GlobalVariable, - init: ConstantData, - ) -> Result<(), GlobalVariableError> { - self.module.set_global_initializer(gv, init) - } - - pub fn declare_data_segment>( - &mut self, - offset: Offset, - size: u32, - init: I, - readonly: bool, - ) -> Result<(), DataSegmentError> { - self.module.declare_data_segment(offset, size, init.into(), readonly) - } - - /// Start building a new function in this module - pub fn function<'a, 'b: 'a, S: Into>( - &'b mut self, - name: S, - signature: Signature, - ) -> Result, SymbolConflictError> { - let name = name.into(); - if let Some(prev) = self.module.function(name) { - return Err(SymbolConflictError(prev.id)); - } - - let id = FunctionIdent { - module: self.module.name, - function: name, - }; - let function = Box::new(Function::new(id, signature)); - let entry = function.dfg.entry; - - Ok(ModuleFunctionBuilder { - builder: self, - function, - position: entry, - }) - } - - pub fn build(self) -> Box { - self.module - } -} - -pub struct ModuleFunctionBuilder<'m> { - builder: &'m mut ModuleBuilder, - function: Box, - position: Block, -} -impl<'m> ModuleFunctionBuilder<'m> { - pub fn with_span(&mut self, span: SourceSpan) -> &mut Self { - self.function.id.function = Ident::new(self.function.id.function.as_symbol(), span); - self - } - - /// Get the fully-qualified name of the underlying function - pub fn id(&self) -> FunctionIdent { - self.function.id - } - - /// Get the signature of the underlying function - pub fn signature(&self) -> &Signature { - &self.function.signature - } - - pub fn module<'a, 'b: 'a>(&'b mut self) -> &'a mut ModuleBuilder { - self.builder - } - - #[inline(always)] - pub fn data_flow_graph(&self) -> &DataFlowGraph { - &self.function.dfg - } - - #[inline(always)] - pub fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph { - &mut self.function.dfg - } - - #[inline] - pub fn entry_block(&self) -> Block { - self.function.dfg.entry - } - - #[inline] - pub fn current_block(&self) -> Block { - self.position - } - - #[inline] - pub fn switch_to_block(&mut self, block: Block) { - self.position = block; - } - - pub fn create_block(&mut self) -> Block { - self.data_flow_graph_mut().create_block() - } - - pub fn block_params(&self, block: Block) -> &[Value] { - self.data_flow_graph().block_params(block) - } - - pub fn append_block_param(&mut self, block: Block, ty: Type, span: SourceSpan) -> Value { - self.data_flow_graph_mut().append_block_param(block, ty, span) - } - - pub fn inst_results(&self, inst: Inst) -> &[Value] { - self.data_flow_graph().inst_results(inst) - } - - pub fn first_result(&self, inst: Inst) -> Value { - self.data_flow_graph().first_result(inst) - } - - pub fn set_attribute(&mut self, name: impl Into, value: impl Into) { - self.data_flow_graph_mut().set_attribute(name, value); - } - - pub fn import_function( - &mut self, - module: M, - function: F, - signature: Signature, - ) -> Result - where - M: Into, - F: Into, - { - self.function.dfg.import_function(module.into(), function.into(), signature) - } - - pub fn ins<'a, 'b: 'a>(&'b mut self) -> DefaultInstBuilder<'a> { - DefaultInstBuilder::new(&mut self.function.dfg, self.position) - } - - pub fn build(self, diagnostics: &DiagnosticsHandler) -> Result { - let sig = self.function.signature(); - match sig.linkage { - Linkage::External | Linkage::Internal => (), - linkage => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function definition") - .with_primary_label( - self.function.span(), - format!("invalid linkage: '{linkage}'"), - ) - .with_help("Only 'external' and 'internal' linkage are valid for functions") - .into_report()); - } - } - - let is_kernel_module = self.builder.module.is_kernel; - let is_public = sig.is_public(); - - match sig.cc { - CallConv::Kernel if is_kernel_module => { - if !is_public { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function definition") - .with_primary_label( - self.function.span(), - format!("expected 'external' linkage, but got '{}'", &sig.linkage), - ) - .with_help( - "Functions declared with the 'kernel' calling convention must have \ - 'external' linkage", - ) - .into_report()); - } - } - CallConv::Kernel => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function definition") - .with_primary_label( - self.function.span(), - "unsupported use of 'kernel' calling convention", - ) - .with_help( - "The 'kernel' calling convention is only allowed in kernel modules, on \ - functions with external linkage", - ) - .into_report()); - } - cc if is_kernel_module && is_public => { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function definition") - .with_primary_label( - self.function.span(), - format!("unsupported use of '{cc}' calling convention"), - ) - .with_help( - "Functions with external linkage, must use the 'kernel' calling \ - convention when defined in a kernel module", - ) - .into_report()); - } - _ => (), - } - - let id = self.function.id; - self.builder.module.functions.push_back(self.function); - - Ok(id) - } -} diff --git a/hir/src/parser/ast/block.rs b/hir/src/parser/ast/block.rs deleted file mode 100644 index 7b764e8c2..000000000 --- a/hir/src/parser/ast/block.rs +++ /dev/null @@ -1,40 +0,0 @@ -use super::*; - -/// Represents a basic block in a [FunctionDeclaration] -#[derive(Spanned)] -pub struct Block { - #[span] - pub span: SourceSpan, - pub id: crate::Block, - pub params: Vec, - pub body: Vec, -} -impl Block { - pub fn new( - span: SourceSpan, - id: crate::Block, - params: Vec, - body: Vec, - ) -> Self { - Self { - span, - id, - params, - body, - } - } -} -impl fmt::Debug for Block { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Block") - .field("id", &format_args!("{}", self.id)) - .field("params", &self.params) - .field("body", &self.body) - .finish() - } -} -impl PartialEq for Block { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.params == other.params && self.body == other.body - } -} diff --git a/hir/src/parser/ast/convert.rs b/hir/src/parser/ast/convert.rs deleted file mode 100644 index 30a75c24f..000000000 --- a/hir/src/parser/ast/convert.rs +++ /dev/null @@ -1,1131 +0,0 @@ -use alloc::collections::{BTreeSet, VecDeque}; - -use cranelift_entity::packed_option::ReservedValue; -use either::Either::{Left, Right}; -use intrusive_collections::UnsafeRef; -use midenc_session::Session; - -use super::*; -use crate::{ - pass::{AnalysisManager, ConversionPass, ConversionResult}, - Immediate, Opcode, PassInfo, Signature, Type, -}; - -/// This pass converts the syntax tree of an HIR module to HIR -#[derive(PassInfo)] -pub struct ConvertAstToHir; -impl ConversionPass for ConvertAstToHir { - type From = Box; - type To = crate::Module; - - fn convert( - &mut self, - mut ast: Self::From, - _analyses: &mut AnalysisManager, - session: &Session, - ) -> ConversionResult { - use alloc::collections::btree_map::Entry; - - let mut module = if ast.is_kernel { - crate::Module::new_kernel(ast.name) - } else { - crate::Module::new(ast.name) - }; - - let mut is_valid = true; - - // Validate constants - let (constants_by_id, is_constants_valid) = - ast.take_and_validate_constants(&session.diagnostics); - is_valid &= is_constants_valid; - - let mut remapped_constants = RemappedConstants::default(); - for (constant_id, constant_data) in constants_by_id.into_iter() { - if let Entry::Vacant(entry) = remapped_constants.entry(constant_id) { - let new_constant_id = module.globals.insert_constant(constant_data.into_inner()); - entry.insert(new_constant_id); - } - } - - // Validate globals - let (globals_by_id, is_global_vars_valid) = - ast.take_and_validate_globals(&remapped_constants, &session.diagnostics); - is_valid &= is_global_vars_valid; - - for (_, gv_data) in globals_by_id.into_iter() { - unsafe { - module.globals.insert(gv_data.into_inner()); - } - } - - // Validate data segments - for segment_ast in ast.data_segments.drain(..) { - let span = segment_ast.span(); - if let Err(err) = module.declare_data_segment( - segment_ast.offset, - segment_ast.size, - segment_ast.data, - segment_ast.readonly, - ) { - is_valid = false; - session - .diagnostics - .diagnostic(Severity::Error) - .with_message("invalid data segment") - .with_primary_label(span, err) - .emit(); - } - } - - // Validate functions - let mut functions_by_id = BTreeMap::>::default(); - let mut worklist = Vec::with_capacity(ast.functions.len()); - for function in core::mem::take(&mut ast.functions).into_iter() { - match functions_by_id.entry(function.name) { - Entry::Vacant(entry) => { - entry.insert(Span::new(function.name.span(), function.signature.clone())); - worklist.push(function); - } - Entry::Occupied(entry) => { - let prev = entry.get().span(); - session - .diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function declaration") - .with_primary_label( - function.span(), - "a function with the same name has already been declared", - ) - .with_secondary_label(prev, "previously declared here") - .emit(); - is_valid = false; - } - } - } - - // Validate imports - let (imports_by_id, is_externals_valid) = - ast.take_and_validate_imports(&session.diagnostics); - is_valid &= is_externals_valid; - - let mut functions = crate::FunctionList::default(); - let mut values_by_id = ValuesById::default(); - let mut used_imports = BTreeSet::::default(); - let mut inst_results = InstResults::default(); - for mut function in worklist.into_iter() { - values_by_id.clear(); - inst_results.clear(); - - let id = FunctionIdent { - module: module.name, - function: function.name, - }; - - is_valid &= function.is_declaration_valid(&session.diagnostics); - let entry = function.blocks[0].id; - let mut blocks_by_id = match function.populate_block_map(&session.diagnostics) { - Ok(blocks) => blocks, - Err(blocks) => { - is_valid = false; - blocks - } - }; - let mut blockq = VecDeque::from([entry]); - - // Build the HIR function - let mut f = Box::new(crate::Function::new_uninit(id, function.signature)); - // Move attributes from the AST to the DFG - f.dfg.attrs = function.attrs; - // The entry block is always the first in the layout - f.dfg.entry = entry; - // Visit each block and build it, but do not yet write to the DataFlowGraph - let mut visited = BTreeSet::::default(); - while let Some(block_id) = blockq.pop_front() { - // Do not visit the same block twice - if !visited.insert(block_id) { - continue; - } - - let mut block_data = crate::BlockData::new(block_id); - let block = blocks_by_id.remove(&block_id).unwrap(); - - // Ensure block parameters are not yet defined - for (num, param) in block.params.into_iter().enumerate() { - match try_insert_param_value( - param.id, - param.span(), - block.id, - num as u16, - param.ty, - &mut values_by_id, - &session.diagnostics, - ) { - Some(id) => { - block_data.params.push(id, &mut f.dfg.value_lists); - } - None => { - is_valid = false; - } - } - } - - // Populate the block with instructions from the AST - for (num, inst) in block.body.into_iter().enumerate() { - is_valid &= try_insert_inst( - inst, - num as u16, - &mut block_data, - &mut blockq, - &blocks_by_id, - &visited, - &mut values_by_id, - &mut inst_results, - &mut used_imports, - &imports_by_id, - &functions_by_id, - &mut f, - &session.diagnostics, - ); - } - f.dfg.blocks.append(block_id, block_data); - } - - // Now that all of the blocks have been visited, - // we can record the value data in the DataFlowGraph - for (id, data) in core::mem::take(&mut values_by_id).into_iter() { - let next_id = f.dfg.values.next_key(); - let next_raw_id = next_id.as_u32(); - let raw_id = id.as_u32(); - assert!( - raw_id >= next_raw_id, - "expected that {id} is always ahead of, or equal to {next_id}" - ); - if raw_id == next_raw_id { - f.dfg.values.push(data.into_inner()); - continue; - } - - // If we reach here, we need to insert dummy data until the next - // key is the one that we have data for - while raw_id > f.dfg.values.next_key().as_u32() { - f.dfg.values.push(crate::ValueData::Inst { - ty: Type::Unknown, - num: 0, - inst: crate::Inst::reserved_value(), - }); - } - assert_eq!(f.dfg.values.push(data.into_inner()), id); - } - - // Also record all of the instruction results - for (inst, results) in core::mem::take(&mut inst_results).into_iter() { - f.dfg.results[inst].extend(results, &mut f.dfg.value_lists); - } - - functions.push_back(f); - } - - // If any of the imports are unused, add them to all functions in the module - if imports_by_id.keys().any(|id| !used_imports.contains(id)) { - while let Some(mut function) = functions.pop_front() { - function.dfg.imports.extend(imports_by_id.iter().filter_map(|(id, ext)| { - if used_imports.contains(id) { - None - } else { - Some((*id, ext.inner().clone())) - } - })); - module.functions.push_back(function); - } - } else { - module.functions = functions; - } - - if is_valid { - Ok(module) - } else { - Err(session - .diagnostics - .diagnostic(Severity::Error) - .with_message(format!("failed to validate '{}'", module.name)) - .with_help("One or more diagnostics have been emitted, see them for details") - .into_report()) - } - } -} - -#[allow(clippy::too_many_arguments)] -fn try_insert_inst( - mut inst: Inst, - num: u16, - block_data: &mut crate::BlockData, - blockq: &mut VecDeque, - blocks_by_id: &BlocksById, - visited_blocks: &BTreeSet, - values_by_id: &mut ValuesById, - inst_results: &mut InstResults, - used_imports: &mut BTreeSet, - imports_by_id: &ImportsById, - functions_by_id: &BTreeMap>, - function: &mut crate::Function, - diagnostics: &DiagnosticsHandler, -) -> bool { - use crate::{BinaryOp, BinaryOpImm, Instruction, PrimOp, PrimOpImm, UnaryOp, UnaryOpImm}; - - let id = function.dfg.insts.alloc_key(); - let span = inst.span; - let results = core::mem::take(&mut inst.outputs); - - // Translate instruction data from AST representation to HIR - let data = match inst.ty { - InstType::BinaryOp { - opcode: op, - overflow, - operands: [Operand::Value(lhs), Operand::Value(rhs)], - } => Some(Instruction::BinaryOp(BinaryOp { - op, - overflow, - args: [rhs.into_inner(), lhs.into_inner()], - })), - InstType::BinaryOp { - opcode: op, - overflow, - operands: [imm, Operand::Value(rhs)], - } => { - let imm_ty = values_by_id.get(&rhs).map(|v| v.ty().clone()).unwrap_or(Type::Unknown); - operand_to_immediate(imm, &imm_ty, diagnostics).map(|imm| { - Instruction::BinaryOpImm(BinaryOpImm { - op, - overflow, - arg: rhs.into_inner(), - imm, - }) - }) - } - InstType::BinaryOp { - operands: [_, rhs], .. - } => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label(rhs.span(), "unexpected immediate operand") - .with_secondary_label( - span, - "only the left-hand operand of a binary operator may be immediate", - ) - .emit(); - None - } - InstType::UnaryOp { - opcode: op, - overflow, - operand: Operand::Value(operand), - } => Some(Instruction::UnaryOp(UnaryOp { - op, - overflow, - arg: operand.into_inner(), - })), - InstType::UnaryOp { - opcode: op, - overflow, - operand, - } => { - let imm = match op { - Opcode::ImmI1 => operand_to_immediate(operand, &Type::I1, diagnostics), - Opcode::ImmI8 => operand_to_immediate(operand, &Type::I8, diagnostics), - Opcode::ImmU8 => operand_to_immediate(operand, &Type::U8, diagnostics), - Opcode::ImmI16 => operand_to_immediate(operand, &Type::I16, diagnostics), - Opcode::ImmU16 => operand_to_immediate(operand, &Type::U16, diagnostics), - Opcode::ImmI32 => operand_to_immediate(operand, &Type::I32, diagnostics), - Opcode::ImmU32 => operand_to_immediate(operand, &Type::U32, diagnostics), - Opcode::ImmI64 => operand_to_immediate(operand, &Type::I64, diagnostics), - Opcode::ImmU64 => operand_to_immediate(operand, &Type::U64, diagnostics), - Opcode::ImmF64 => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - "f64 support is not yet implemented in the parser", - ) - .emit(); - None - } - Opcode::ImmFelt => operand_to_immediate(operand, &Type::Felt, diagnostics), - _ => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - "this operation does not support immediate arguments", - ) - .with_secondary_label(operand.span(), "this operand cannot be immediate") - .emit(); - None - } - }; - imm.map(|imm| Instruction::UnaryOpImm(UnaryOpImm { op, overflow, imm })) - } - InstType::Br { - opcode: op, - successor, - } => { - match is_valid_successor( - &successor, - span, - blocks_by_id, - visited_blocks, - values_by_id, - diagnostics, - ) { - Ok(next) => { - if let Some(next) = next { - blockq.push_back(next); - } - let args = crate::ValueList::from_iter( - successor.args.iter().map(|arg| arg.into_inner()), - &mut function.dfg.value_lists, - ); - Some(Instruction::Br(crate::Br { - op, - successor: crate::Successor { - destination: successor.id, - args, - }, - })) - } - Err(_) => None, - } - } - InstType::CondBr { - opcode: op, - cond, - then_dest, - else_dest, - } => { - let mut is_valid = is_valid_value_reference(&cond, span, values_by_id, diagnostics); - - match is_valid_successor( - &then_dest, - span, - blocks_by_id, - visited_blocks, - values_by_id, - diagnostics, - ) { - Ok(next) => { - if let Some(next) = next { - blockq.push_back(next); - } - } - Err(_) => { - is_valid = false; - } - } - - match is_valid_successor( - &else_dest, - span, - blocks_by_id, - visited_blocks, - values_by_id, - diagnostics, - ) { - Ok(next) => { - if let Some(next) = next { - blockq.push_back(next); - } - } - Err(_) => { - is_valid = false; - } - } - - if is_valid { - let then_args = crate::ValueList::from_iter( - then_dest.args.iter().map(|arg| arg.into_inner()), - &mut function.dfg.value_lists, - ); - let else_args = crate::ValueList::from_iter( - else_dest.args.iter().map(|arg| arg.into_inner()), - &mut function.dfg.value_lists, - ); - Some(Instruction::CondBr(crate::CondBr { - op, - cond: cond.into_inner(), - then_dest: crate::Successor { - destination: then_dest.id, - args: then_args, - }, - else_dest: crate::Successor { - destination: else_dest.id, - args: else_args, - }, - })) - } else { - None - } - } - InstType::Switch { - opcode: op, - selector, - successors, - fallback, - } => { - let mut is_valid = is_valid_value_reference(&selector, span, values_by_id, diagnostics); - - let mut used_discriminants = BTreeSet::>::default(); - let mut arms = Vec::with_capacity(successors.len()); - for arm in successors.into_iter() { - let arm_span = arm.span(); - let discriminant = arm.inner().0; - if !used_discriminants.insert(Span::new(arm_span, discriminant)) { - let prev = used_discriminants - .get(&Span::new(SourceSpan::UNKNOWN, discriminant)) - .unwrap() - .span(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - arm_span, - "the discriminant for this switch case has already been used", - ) - .with_secondary_label(prev, "previously used here") - .emit(); - is_valid = false; - } - let successor = arm.into_inner().1; - let successor_args = crate::ValueList::from_iter( - successor.args.iter().map(|arg| arg.into_inner()), - &mut function.dfg.value_lists, - ); - arms.push(crate::SwitchArm { - value: discriminant, - successor: crate::Successor { - destination: successor.id, - args: successor_args, - }, - }); - match is_valid_successor( - &successor, - span, - blocks_by_id, - visited_blocks, - values_by_id, - diagnostics, - ) { - Ok(next) => { - if let Some(next) = next { - blockq.push_back(next); - } - } - Err(_) => { - is_valid = false; - } - } - } - - let fallback_args = crate::ValueList::from_iter( - fallback.args.iter().map(|arg| arg.into_inner()), - &mut function.dfg.value_lists, - ); - match is_valid_successor( - &fallback, - span, - blocks_by_id, - visited_blocks, - values_by_id, - diagnostics, - ) { - Ok(next) => { - if let Some(next) = next { - blockq.push_back(next); - } - } - Err(_) => { - is_valid = false; - } - } - - if is_valid { - Some(Instruction::Switch(crate::Switch { - op, - arg: selector.into_inner(), - arms, - default: crate::Successor { - destination: fallback.id, - args: fallback_args, - }, - })) - } else { - None - } - } - InstType::Ret { - opcode: op, - mut operands, - } => { - let expected_returns = function.signature.results.len(); - let actual_returns = operands.len(); - if actual_returns != expected_returns { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label( - span, - format!( - "the current function expects {expected_returns} values to be \ - returned, but {actual_returns} are returned here" - ), - ) - .emit(); - None - } else { - match actual_returns { - 0 => Some(Instruction::Ret(crate::Ret { - op, - args: Default::default(), - })), - 1 => match operands.pop().unwrap() { - Operand::Value(v) => { - if is_valid_value_reference(&v, span, values_by_id, diagnostics) { - let args = crate::ValueList::from_slice( - &[v.into_inner()], - &mut function.dfg.value_lists, - ); - Some(Instruction::Ret(crate::Ret { op, args })) - } else { - None - } - } - operand @ (Operand::Int(_) | Operand::BigInt(_)) => operand_to_immediate( - operand, - &function.signature.results[0].ty, - diagnostics, - ) - .map(|arg| Instruction::RetImm(crate::RetImm { op, arg })), - }, - _ => { - let mut is_valid = true; - let mut args = crate::ValueList::new(); - for operand in operands.iter() { - if let Operand::Value(ref v) = operand { - args.push(v.into_inner(), &mut function.dfg.value_lists); - is_valid &= - is_valid_value_reference(v, span, values_by_id, diagnostics); - } else { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid operand") - .with_primary_label( - operand.span(), - "expected an ssa value here, but got an immediate", - ) - .with_secondary_label(span, "occurs here") - .emit(); - is_valid = false; - } - } - if is_valid { - Some(Instruction::Ret(crate::Ret { op, args })) - } else { - None - } - } - } - } - } - InstType::Call { - opcode: op, - callee, - operands, - } => { - let mut is_valid = true; - let callee = match callee { - Left(local) => { - let callee = FunctionIdent { - function: local, - module: function.id.module, - }; - if let Some(sig) = functions_by_id.get(&local) { - use std::collections::hash_map::Entry; - if let Entry::Vacant(entry) = function.dfg.imports.entry(callee) { - entry.insert(ExternalFunction { - id: callee, - signature: sig.inner().clone(), - }); - } - } else { - diagnostics - .diagnostic(Severity::Error) - .with_message("reference to undefined local function") - .with_primary_label( - local.span(), - "this function is not defined in the current module, are you \ - missing an import?", - ) - .emit(); - is_valid = false; - } - callee - } - Right(external) => { - use std::collections::hash_map::Entry; - used_imports.insert(external); - if let Entry::Vacant(entry) = function.dfg.imports.entry(external) { - if let Some(ef) = imports_by_id.get(&external) { - entry.insert(ef.inner().clone()); - } else { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid call instruction") - .with_primary_label( - external.span(), - "this function is not imported in the current module, you \ - must do so in order to reference it", - ) - .with_secondary_label(span, "invalid callee for this instruction") - .emit(); - is_valid = false; - } - } - external - } - }; - - is_valid &= - is_valid_value_references(operands.as_slice(), span, values_by_id, diagnostics); - if is_valid { - let args = crate::ValueList::from_iter( - operands.iter().map(|arg| arg.into_inner()), - &mut function.dfg.value_lists, - ); - Some(Instruction::Call(crate::Call { op, callee, args })) - } else { - None - } - } - InstType::CallIndirect { .. } => { - unimplemented!("indirect calls are not implemented in the IR yet") - } - InstType::PrimOp { - opcode: op, - operands, - } => { - if operands.is_empty() { - Some(Instruction::PrimOp(PrimOp { - op, - args: Default::default(), - })) - } else { - let mut is_valid = true; - let mut imm = None; - let mut args = crate::ValueList::new(); - for (i, operand) in operands.iter().cloned().enumerate() { - let is_first = i == 0; - match operand { - Operand::Value(v) => { - is_valid &= - is_valid_value_reference(&v, span, values_by_id, diagnostics); - args.push(v.into_inner(), &mut function.dfg.value_lists); - } - operand @ (Operand::Int(_) | Operand::BigInt(_)) if is_first => { - imm = match op { - Opcode::AssertEq => { - if let Some(value) = operands[i + 1].as_value() { - match values_by_id.get(value.inner()).map(|vd| vd.ty()) { - Some(ty) => { - operand_to_immediate(operand, ty, diagnostics) - } - None => { - diagnostics - .diagnostic(Severity::Error) - .with_message("undefined value") - .with_primary_label( - operand.span(), - "this value is not defined yet", - ) - .with_secondary_label( - span, - "ensure the value is defined in a \ - dominating block of this instruction", - ) - .emit(); - None - } - } - } else { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid operand") - .with_primary_label( - operand.span(), - "expected an ssa value here, but got an immediate", - ) - .with_secondary_label( - span, - "only the first argument of this instruction may \ - be an immediate", - ) - .emit(); - None - } - } - Opcode::Store => { - operand_to_immediate(operand, &Type::U32, diagnostics) - } - opcode => { - unimplemented!("unsupported primop with immediate {opcode}") - } - }; - if imm.is_none() { - is_valid = false; - } - } - operand @ (Operand::Int(_) | Operand::BigInt(_)) => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label( - operand.span(), - "expected an ssa value here, but got an immediate", - ) - .with_secondary_label( - span, - "only the first argument of this instruction may be an \ - immediate", - ) - .emit(); - is_valid = false; - } - } - } - if is_valid { - if let Some(imm) = imm { - Some(Instruction::PrimOpImm(PrimOpImm { op, imm, args })) - } else { - Some(Instruction::PrimOp(crate::PrimOp { op, args })) - } - } else { - None - } - } - } - InstType::GlobalValue { - opcode: _op, - expr: _, - } => { - todo!() - } - }; - - // If the instruction data is invalid, we still need to handle the instruction results - // for subsequent instructions which may reference its results - if let Some(data) = data { - // Add instruction to DataFlowGraph - let node = crate::InstNode::new(id, block_data.id, Span::new(span, data)); - function.dfg.insts.append(id, node); - - // Add results to values_by_id map - let mut is_valid = true; - let results_vec = inst_results.entry(id).or_default(); - for tv in results.into_iter() { - if let Some(value) = - try_insert_result_value(tv.id, tv.span(), id, num, tv.ty, values_by_id, diagnostics) - { - results_vec.push(value); - } else { - is_valid = false; - } - } - - // Append instruction to block - let unsafe_ref = - unsafe { UnsafeRef::from_raw(function.dfg.insts.get_raw(id).unwrap().as_ptr()) }; - block_data.append(unsafe_ref); - - is_valid - } else { - for tv in results.into_iter() { - try_insert_result_value(tv.id, tv.span(), id, num, tv.ty, values_by_id, diagnostics); - } - - false - } -} - -fn is_valid_successor( - successor: &Successor, - parent_span: SourceSpan, - blocks_by_id: &BlocksById, - visited: &BTreeSet, - values_by_id: &ValuesById, - diagnostics: &DiagnosticsHandler, -) -> Result, crate::Block> { - let is_visited = visited.contains(&successor.id); - let is_valid = is_valid_value_references( - successor.args.as_slice(), - parent_span, - values_by_id, - diagnostics, - ); - if blocks_by_id.contains_key(&successor.id) || is_visited { - if is_valid { - if is_visited { - Ok(None) - } else { - Ok(Some(successor.id)) - } - } else { - Err(successor.id) - } - } else { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label(successor.span, "invalid successor: the named block does not exist") - .with_secondary_label(parent_span, "found in this instruction") - .emit(); - Err(successor.id) - } -} - -fn is_valid_value_references<'a, I: IntoIterator>>( - values: I, - parent_span: SourceSpan, - values_by_id: &ValuesById, - diagnostics: &DiagnosticsHandler, -) -> bool { - let mut is_valid = true; - let mut is_empty = true; - for value in values.into_iter() { - is_empty = false; - is_valid &= is_valid_value_reference(value, parent_span, values_by_id, diagnostics); - } - is_empty || is_valid -} - -fn is_valid_value_reference( - value: &Span, - parent_span: SourceSpan, - values_by_id: &ValuesById, - diagnostics: &DiagnosticsHandler, -) -> bool { - let is_valid = values_by_id.contains_key(value.inner()); - if !is_valid { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid value operand") - .with_primary_label( - value.span(), - "this value is either undefined, or its definition does not dominate this use", - ) - .with_secondary_label(parent_span, "invalid use occurs in this instruction") - .emit(); - } - is_valid -} - -fn try_insert_param_value( - id: crate::Value, - span: SourceSpan, - block: crate::Block, - num: u16, - ty: Type, - values: &mut BTreeMap>, - diagnostics: &DiagnosticsHandler, -) -> Option { - use alloc::collections::btree_map::Entry; - - match values.entry(id) { - Entry::Vacant(entry) => { - let data = crate::ValueData::Param { - ty, - num, - block, - span, - }; - entry.insert(Span::new(span, data)); - Some(id) - } - Entry::Occupied(entry) => { - let prev = entry.get().span(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label(span, "the name of this block parameter is already in use") - .with_secondary_label(prev, "previously declared here") - .emit(); - None - } - } -} - -fn try_insert_result_value( - id: crate::Value, - span: SourceSpan, - inst: crate::Inst, - num: u16, - ty: Type, - values: &mut BTreeMap>, - diagnostics: &DiagnosticsHandler, -) -> Option { - use alloc::collections::btree_map::Entry; - - match values.entry(id) { - Entry::Vacant(entry) => { - let data = crate::ValueData::Inst { ty, num, inst }; - entry.insert(Span::new(span, data)); - Some(id) - } - Entry::Occupied(entry) => { - let prev = entry.get().span(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid instruction") - .with_primary_label(span, "a value may only be declared a single time") - .with_secondary_label(prev, "previously declared here") - .emit(); - None - } - } -} - -fn operand_to_immediate( - operand: Operand, - ty: &Type, - diagnostics: &DiagnosticsHandler, -) -> Option { - match operand { - Operand::Int(i) => smallint_to_immediate(i.span(), i.into_inner(), ty, diagnostics), - Operand::BigInt(i) => bigint_to_immediate(i.span(), i.into_inner(), ty, diagnostics), - Operand::Value(_) => panic!("cannot convert ssa values to immediate"), - } -} - -fn smallint_to_immediate( - span: SourceSpan, - i: isize, - ty: &Type, - diagnostics: &DiagnosticsHandler, -) -> Option { - match ty { - Type::I1 => Some(Immediate::I1(i != 0)), - Type::I8 => try_convert_imm(i, span, ty, diagnostics).map(Immediate::I8), - Type::I16 => try_convert_imm(i, span, ty, diagnostics).map(Immediate::I16), - Type::I32 => try_convert_imm(i, span, ty, diagnostics).map(Immediate::I32), - Type::U8 | Type::U16 | Type::U32 | Type::U64 | Type::U128 | Type::U256 if i < 0 => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label(span, format!("expected a non-negative integer of type {ty}")) - .emit(); - None - } - Type::U8 => try_convert_imm(i as usize, span, ty, diagnostics).map(Immediate::U8), - Type::U16 => try_convert_imm(i as usize, span, ty, diagnostics).map(Immediate::U16), - Type::U32 => try_convert_imm(i as usize, span, ty, diagnostics).map(Immediate::U32), - Type::I64 => Some(Immediate::I64(i as i64)), - Type::U64 => Some(Immediate::U64(i as u64)), - Type::I128 => Some(Immediate::I128(i as i128)), - Type::U128 | Type::U256 | Type::F64 => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label(span, format!("immediates of type {ty} are not yet supported")) - .emit(); - None - } - ty => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label( - span, - format!("expected an immediate of type {ty}, but got an integer"), - ) - .emit(); - None - } - } -} - -fn bigint_to_immediate( - span: SourceSpan, - i: num_bigint::BigInt, - ty: &Type, - diagnostics: &DiagnosticsHandler, -) -> Option { - use num_traits::cast::ToPrimitive; - - let is_negative = i.sign() == num_bigint::Sign::Minus; - // NOTE: If we are calling this function, then `i` was too large to fit in an `isize`, so it - // must be a large integer type - let imm = match ty { - Type::U32 if !is_negative => i.to_u32().map(Immediate::U32), - Type::I64 => i.to_i64().map(Immediate::I64), - Type::U64 if !is_negative => i.to_u64().map(Immediate::U64), - Type::I128 => i.to_i128().map(Immediate::I128), - Type::U128 | Type::U256 | Type::F64 => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label(span, format!("immediates of type {ty} are not yet supported")) - .emit(); - return None; - } - ty => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label( - span, - format!("expected an immediate of type {ty}, but got an integer"), - ) - .emit(); - return None; - } - }; - if imm.is_none() { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label( - span, - format!( - "expected an immediate of type {ty}, but got {i}, which is out of range for \ - that type" - ), - ) - .emit(); - } - imm -} - -fn try_convert_imm( - i: T, - span: SourceSpan, - ty: &Type, - diagnostics: &DiagnosticsHandler, -) -> Option -where - U: TryFrom, - >::Error: fmt::Display, -{ - match U::try_from(i) { - Ok(value) => Some(value), - Err(err) => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid immediate operand") - .with_primary_label( - span, - format!("cannot interpret this as a value of type {ty}: {err}"), - ) - .emit(); - None - } - } -} diff --git a/hir/src/parser/ast/functions.rs b/hir/src/parser/ast/functions.rs deleted file mode 100644 index dd566b76d..000000000 --- a/hir/src/parser/ast/functions.rs +++ /dev/null @@ -1,129 +0,0 @@ -use super::*; -use crate::{diagnostics::DiagnosticsHandler, AttributeSet, Signature}; - -/// Represents the declaration of a function in a [Module] -#[derive(Spanned)] -pub struct FunctionDeclaration { - #[span] - pub span: SourceSpan, - pub attrs: AttributeSet, - pub name: Ident, - pub signature: Signature, - pub blocks: Vec, -} -impl FunctionDeclaration { - pub fn new( - span: SourceSpan, - name: Ident, - signature: Signature, - blocks: Vec, - attrs: AttributeSet, - ) -> Self { - Self { - span, - attrs, - name, - signature, - blocks, - } - } - - /// Returns true if the entry block and signature match for this declaration - pub fn is_declaration_valid(&self, diagnostics: &DiagnosticsHandler) -> bool { - let entry_block = &self.blocks[0]; - if entry_block.params.len() != self.signature.arity() { - let num_expected = entry_block.params.len(); - let num_declared = self.signature.arity(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function") - .with_primary_label( - entry_block.span, - "the parameter list of the entry block does not match the function signature", - ) - .with_secondary_label( - self.span, - format!("expected {num_expected} parameters, but got {num_declared}"), - ) - .emit(); - false - } else { - let mut is_valid = true; - for (expected, declared) in self.signature.params.iter().zip(entry_block.params.iter()) - { - let expected_ty = &expected.ty; - let declared_ty = &declared.ty; - if expected_ty != declared_ty { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid function") - .with_primary_label( - entry_block.span, - "the parameter list of the entry block does not match the function \ - signature", - ) - .with_secondary_label( - declared.span, - format!( - "expected a parameter of type {expected_ty}, but got {declared_ty}" - ), - ) - .emit(); - is_valid = false; - } - } - - is_valid - } - } - - pub(super) fn populate_block_map( - &mut self, - diagnostics: &DiagnosticsHandler, - ) -> Result { - use alloc::collections::btree_map::Entry; - - let mut blocks_by_id = BlocksById::default(); - let mut is_valid = true; - for block in core::mem::take(&mut self.blocks).into_iter() { - match blocks_by_id.entry(block.id) { - Entry::Vacant(entry) => { - entry.insert(block); - } - Entry::Occupied(entry) => { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid block") - .with_primary_label( - block.span, - "a block with the same name has already been declared", - ) - .with_secondary_label(entry.get().span, "previously declared here") - .emit(); - is_valid = false; - continue; - } - } - } - - if is_valid { - Ok(blocks_by_id) - } else { - Err(blocks_by_id) - } - } -} -impl fmt::Debug for FunctionDeclaration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("FunctionDeclaration") - .field("name", &self.name.as_symbol()) - .field("signature", &self.signature) - .field("blocks", &self.blocks) - .finish() - } -} -impl PartialEq for FunctionDeclaration { - fn eq(&self, other: &Self) -> bool { - self.name == other.name && self.signature == other.signature && self.blocks == other.blocks - } -} diff --git a/hir/src/parser/ast/globals.rs b/hir/src/parser/ast/globals.rs deleted file mode 100644 index b79788954..000000000 --- a/hir/src/parser/ast/globals.rs +++ /dev/null @@ -1,130 +0,0 @@ -use core::fmt; - -use super::{SourceSpan, Spanned}; -use crate::{ConstantData, Ident, Linkage, Type}; - -/// This represents the declaration of a global variable -#[derive(Spanned)] -pub struct GlobalVarDeclaration { - #[span] - pub span: SourceSpan, - pub id: crate::GlobalVariable, - pub name: Ident, - pub ty: Type, - pub linkage: Linkage, - pub init: Option, -} -impl GlobalVarDeclaration { - pub fn new( - span: SourceSpan, - id: crate::GlobalVariable, - name: Ident, - ty: Type, - linkage: Linkage, - init: Option, - ) -> Self { - Self { - span, - id, - name, - ty, - linkage, - init, - } - } -} -impl fmt::Debug for GlobalVarDeclaration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use crate::display::DisplayOptional; - - f.debug_struct("GlobalVarDeclaration") - .field("id", &format_args!("{}", &self.id)) - .field("name", &self.name.as_symbol()) - .field("ty", &self.ty) - .field("linkage", &self.linkage) - .field("init", &DisplayOptional(self.init.as_ref())) - .finish() - } -} -impl PartialEq for GlobalVarDeclaration { - fn eq(&self, other: &Self) -> bool { - self.id == other.id - && self.name == other.name - && self.ty == other.ty - && self.linkage == other.linkage - && self.init == other.init - } -} - -/// This represents the declaration of a constant -#[derive(Spanned)] -pub struct ConstantDeclaration { - #[span] - pub span: SourceSpan, - pub id: crate::Constant, - pub init: ConstantData, -} -impl ConstantDeclaration { - pub fn new(span: SourceSpan, id: crate::Constant, init: ConstantData) -> Self { - Self { span, id, init } - } -} -impl fmt::Debug for ConstantDeclaration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ConstantDeclaration") - .field("id", &format_args!("{}", &self.id)) - .field("init", &format_args!("{}", &self.init)) - .finish() - } -} -impl PartialEq for ConstantDeclaration { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.init == other.init - } -} - -/// This represents the declaration of a data segment -#[derive(Spanned)] -pub struct DataSegmentDeclaration { - #[span] - pub span: SourceSpan, - pub readonly: bool, - pub offset: u32, - pub size: u32, - pub data: ConstantData, -} -impl DataSegmentDeclaration { - pub fn new( - span: SourceSpan, - offset: u32, - size: u32, - readonly: bool, - data: ConstantData, - ) -> Self { - Self { - span, - readonly, - offset, - size, - data, - } - } -} -impl fmt::Debug for DataSegmentDeclaration { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("DataSegmentDeclaration") - .field("offset", &self.offset) - .field("size", &self.size) - .field("readonly", &self.readonly) - .field("data", &format_args!("{}", &self.data)) - .finish() - } -} -impl PartialEq for DataSegmentDeclaration { - fn eq(&self, other: &Self) -> bool { - self.offset == other.offset - && self.size == other.size - && self.readonly == other.readonly - && self.data == other.data - } -} diff --git a/hir/src/parser/ast/instruction.rs b/hir/src/parser/ast/instruction.rs deleted file mode 100644 index 7212ec28a..000000000 --- a/hir/src/parser/ast/instruction.rs +++ /dev/null @@ -1,210 +0,0 @@ -use either::Either; - -use super::*; -use crate::{Opcode, Overflow, Type}; - -/// Represents a single instruction. -#[derive(Spanned)] -pub struct Inst { - #[span] - pub span: SourceSpan, - /// The specific type of instruction and its data - pub ty: InstType, - /// If the instruction produces outputs, this will contain them, otherwise it is empty - pub outputs: Vec, -} -impl Inst { - pub fn new(span: SourceSpan, ty: InstType, outputs: Vec) -> Self { - Self { span, ty, outputs } - } -} -impl fmt::Debug for Inst { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Inst") - .field("ty", &self.ty) - .field("outputs", &self.outputs) - .finish() - } -} -impl PartialEq for Inst { - fn eq(&self, other: &Self) -> bool { - self.ty == other.ty && self.outputs == other.outputs - } -} - -/// This represents the various types of instructions which we can parse -#[derive(Debug, PartialEq, Eq)] -pub enum InstType { - BinaryOp { - opcode: Opcode, - overflow: Option, - operands: [Operand; 2], - }, - UnaryOp { - opcode: Opcode, - overflow: Option, - operand: Operand, - }, - Br { - opcode: Opcode, - successor: Successor, - }, - CondBr { - opcode: Opcode, - cond: Span, - then_dest: Successor, - else_dest: Successor, - }, - Switch { - opcode: Opcode, - selector: Span, - successors: Vec>, - fallback: Successor, - }, - Ret { - opcode: Opcode, - operands: Vec, - }, - Call { - opcode: Opcode, - callee: Either, - operands: Vec>, - }, - CallIndirect { - opcode: Opcode, - calle: Operand, - operands: Vec, - }, - PrimOp { - opcode: Opcode, - operands: Vec, - }, - GlobalValue { - opcode: Opcode, - expr: GlobalValueExpr, - }, -} - -/// An operand is an argument to an instruction -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum Operand { - Value(Span), - /// A small integer type, e.g. u32 - Int(Span), - /// A large integer type, e.g. i128 or u256 - BigInt(Span), -} -impl Operand { - pub fn span(&self) -> SourceSpan { - match self { - Self::Value(ref spanned) => spanned.span(), - Self::Int(ref spanned) => spanned.span(), - Self::BigInt(ref spanned) => spanned.span(), - } - } - - pub fn as_value(&self) -> Option> { - match self { - Self::Value(v) => Some(*v), - _ => None, - } - } -} - -/// Represents a value/type pair where applicable in the AST -#[derive(Spanned)] -pub struct TypedValue { - #[span] - pub span: SourceSpan, - pub id: crate::Value, - pub ty: Type, -} -impl TypedValue { - pub fn new(span: SourceSpan, id: crate::Value, ty: Type) -> Self { - Self { span, id, ty } - } -} -impl fmt::Display for TypedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.id) - } -} -impl fmt::Debug for TypedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("TypedValue") - .field("id", &self.id) - .field("ty", &self.ty) - .finish() - } -} -impl Eq for TypedValue {} -impl PartialEq for TypedValue { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.ty == other.ty - } -} - -/// This represents a branch destination and arguments -#[derive(Spanned)] -pub struct Successor { - #[span] - pub span: SourceSpan, - pub id: crate::Block, - pub args: Vec>, -} -impl fmt::Debug for Successor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Successor") - .field("id", &format_args!("{}", &self.id)) - .field( - "args", - &format_args!("{}", crate::display::DisplayValues::new(self.args.iter())), - ) - .finish() - } -} -impl Eq for Successor {} -impl PartialEq for Successor { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.args == other.args - } -} - -/// This represents access or a relative operation to a global variable -#[derive(Debug, PartialEq, Eq)] -pub enum GlobalValueExpr { - Symbol { - symbol: Ident, - offset: i32, - span: SourceSpan, - }, - Load { - base: Box, - offset: i32, - ty: Option, - span: SourceSpan, - }, - IAddImm { - base: Box, - offset: i32, - ty: Type, - span: SourceSpan, - }, -} -impl GlobalValueExpr { - pub fn ty(&self) -> Option { - match self { - Self::Symbol { .. } => None, - Self::Load { ref ty, .. } => ty.clone(), - Self::IAddImm { ref base, .. } => base.ty(), - } - } - - pub fn span(&self) -> SourceSpan { - match self { - Self::Symbol { span, .. } | Self::Load { span, .. } | Self::IAddImm { span, .. } => { - *span - } - } - } -} diff --git a/hir/src/parser/ast/mod.rs b/hir/src/parser/ast/mod.rs deleted file mode 100644 index 0ecd8ef49..000000000 --- a/hir/src/parser/ast/mod.rs +++ /dev/null @@ -1,265 +0,0 @@ -mod block; -mod convert; -mod functions; -mod globals; -mod instruction; - -use alloc::collections::BTreeMap; -use core::fmt; - -pub use self::{block::*, convert::ConvertAstToHir, functions::*, globals::*, instruction::*}; -use crate::{ - diagnostics::{DiagnosticsHandler, Severity, SourceSpan, Span, Spanned}, - ExternalFunction, FunctionIdent, Ident, -}; - -/// This represents the parsed contents of a single Miden IR module -#[derive(Spanned)] -pub struct Module { - #[span] - pub span: SourceSpan, - pub name: Ident, - pub constants: Vec, - pub global_vars: Vec, - pub data_segments: Vec, - pub functions: Vec, - pub externals: Vec>, - pub is_kernel: bool, -} -impl fmt::Debug for Module { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Module") - .field("name", &self.name.as_symbol()) - .field("constants", &self.constants) - .field("global_vars", &self.global_vars) - .field("data_segments", &self.data_segments) - .field("functions", &self.functions) - .field("externals", &self.externals) - .field("is_kernel", &self.is_kernel) - .finish() - } -} -impl midenc_session::Emit for Module { - fn name(&self) -> Option { - Some(self.name.as_symbol()) - } - - fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType { - midenc_session::OutputType::Ast - } - - fn write_to( - &self, - mut writer: W, - mode: midenc_session::OutputMode, - _session: &midenc_session::Session, - ) -> std::io::Result<()> { - assert_eq!( - mode, - midenc_session::OutputMode::Text, - "binary mode is not supported for HIR syntax trees" - ); - writer.write_fmt(format_args!("{:#?}", self)) - } -} - -type ConstantsById = BTreeMap>; -type RemappedConstants = BTreeMap; -type GlobalVariablesById = BTreeMap>; -type ImportsById = BTreeMap>; -type BlocksById = BTreeMap; -type ValuesById = BTreeMap>; -type InstResults = BTreeMap>; - -impl Module { - pub fn new(span: SourceSpan, name: Ident, is_kernel: bool, forms: Vec
) -> Self { - let mut module = Self { - span, - name, - constants: vec![], - functions: vec![], - externals: vec![], - global_vars: vec![], - data_segments: vec![], - is_kernel, - }; - for form in forms.into_iter() { - match form { - Form::Constant(constant) => { - module.constants.push(constant); - } - Form::Global(global) => { - module.global_vars.push(global); - } - Form::DataSegment(segment) => { - module.data_segments.push(segment); - } - Form::Function(function) => { - module.functions.push(function); - } - Form::ExternalFunction(external) => { - module.externals.push(external); - } - } - } - module - } - - fn take_and_validate_constants( - &mut self, - diagnostics: &DiagnosticsHandler, - ) -> (ConstantsById, bool) { - use alloc::collections::btree_map::Entry; - - let mut constants_by_id = ConstantsById::default(); - let constants = core::mem::take(&mut self.constants); - let mut is_valid = true; - for constant in constants.into_iter() { - match constants_by_id.entry(constant.id) { - Entry::Vacant(entry) => { - entry.insert(Span::new(constant.span, constant.init)); - } - Entry::Occupied(entry) => { - let prev = entry.get().span(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid constant declaration") - .with_primary_label( - constant.span, - "a constant with this identifier has already been declared", - ) - .with_secondary_label(prev, "previously declared here") - .emit(); - is_valid = false; - } - } - } - - (constants_by_id, is_valid) - } - - fn take_and_validate_globals( - &mut self, - remapped_constants: &RemappedConstants, - diagnostics: &DiagnosticsHandler, - ) -> (GlobalVariablesById, bool) { - use alloc::collections::btree_map::Entry; - - let mut globals_by_id = GlobalVariablesById::default(); - let global_vars = core::mem::take(&mut self.global_vars); - let mut is_valid = true; - for global in global_vars.into_iter() { - match globals_by_id.entry(global.id) { - Entry::Vacant(entry) => { - if let Some(id) = global.init { - if !remapped_constants.contains_key(&id) { - let id = id.as_u32(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid global variable declaration") - .with_primary_label( - global.span, - format!( - "invalid initializer: no constant named '{id}' in this \ - module" - ), - ) - .emit(); - is_valid = false; - } - } - let gv = crate::GlobalVariableData::new( - global.id, - global.name, - global.ty, - global.linkage, - global.init.map(|id| remapped_constants[&id]), - ); - entry.insert(Span::new(global.span, gv)); - } - Entry::Occupied(entry) => { - let prev = entry.get().span(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid global variable declaration") - .with_primary_label( - global.span, - "a global variable with the same id has already been declared", - ) - .with_secondary_label(prev, "previously declared here") - .emit(); - is_valid = false; - } - } - } - - (globals_by_id, is_valid) - } - - fn take_and_validate_imports( - &mut self, - diagnostics: &DiagnosticsHandler, - ) -> (ImportsById, bool) { - use alloc::collections::btree_map::Entry; - - let mut imports_by_id = ImportsById::default(); - let mut is_valid = true; - for external in core::mem::take(&mut self.externals).into_iter() { - if external.id.module == self.name { - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid external function declaration") - .with_primary_label( - external.span(), - "external function declarations may not reference functions in the \ - current module", - ) - .emit(); - is_valid = false; - continue; - } - - match imports_by_id.entry(external.id) { - Entry::Vacant(entry) => { - entry.insert(external); - } - Entry::Occupied(entry) => { - let prev = entry.get().span(); - diagnostics - .diagnostic(Severity::Error) - .with_message("invalid external function declaration") - .with_primary_label( - external.span(), - "an external function with the same name has already been declared", - ) - .with_secondary_label(prev, "previously declared here") - .emit(); - is_valid = false; - } - } - } - - (imports_by_id, is_valid) - } -} - -impl PartialEq for Module { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - && self.is_kernel == other.is_kernel - && self.global_vars == other.global_vars - && self.data_segments == other.data_segments - && self.functions == other.functions - && self.externals == other.externals - } -} - -/// This represents one of the top-level forms which a [Module] can contain -#[derive(Debug)] -pub enum Form { - Constant(ConstantDeclaration), - Global(GlobalVarDeclaration), - DataSegment(DataSegmentDeclaration), - Function(FunctionDeclaration), - ExternalFunction(Span), -} diff --git a/hir/src/parser/error.rs b/hir/src/parser/error.rs deleted file mode 100644 index 0d2b85294..000000000 --- a/hir/src/parser/error.rs +++ /dev/null @@ -1,151 +0,0 @@ -use super::lexer::{LexicalError, Token}; -use crate::{ - diagnostics::{miette, ByteIndex, Diagnostic, SourceSpan}, - DisplayValues, -}; - -#[derive(Debug, thiserror::Error, Diagnostic)] -pub enum ParseError { - #[diagnostic(transparent)] - #[error(transparent)] - Lexer(#[from] LexicalError), - #[error("error reading {path:?}: {source}")] - #[diagnostic()] - FileError { - #[source] - source: std::io::Error, - path: std::path::PathBuf, - }, - #[error("invalid token")] - #[diagnostic()] - InvalidToken(#[label] SourceSpan), - #[error("unexpected end of file")] - #[diagnostic()] - UnexpectedEof { - #[label("expected one of: {}", DisplayValues::new(expected.iter()))] - at: SourceSpan, - expected: Vec, - }, - #[error("unrecognized token '{token}'")] - UnrecognizedToken { - #[label("expected one of: {}", DisplayValues::new(expected.iter()))] - span: SourceSpan, - token: Token, - expected: Vec, - }, - #[error("extraneous token '{token}'")] - ExtraToken { - #[label] - span: SourceSpan, - token: Token, - }, - #[error("expected valid u32 immediate value, got '{value}'")] - InvalidU32 { - #[label] - span: SourceSpan, - value: isize, - }, - #[error("expected valid offset value, got '{value}'")] - InvalidOffset { - #[label] - span: SourceSpan, - value: isize, - }, - #[error("expected valid alignment value, got '{value}'")] - InvalidAlignment { - #[label] - span: SourceSpan, - value: isize, - }, - #[error("expected valid address space, got '{value}'")] - InvalidAddrSpace { - #[label] - span: SourceSpan, - value: isize, - }, - #[error("invalid function definition: cannot have empty body")] - EmptyFunction { - #[label] - span: SourceSpan, - }, - #[error("invalid function import declaration: cannot have body")] - ImportedFunctionWithBody { - #[label] - span: SourceSpan, - }, -} -impl Eq for ParseError {} -impl PartialEq for ParseError { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Lexer(l), Self::Lexer(r)) => l == r, - (Self::FileError { .. }, Self::FileError { .. }) => true, - (Self::InvalidToken(_), Self::InvalidToken(_)) => true, - ( - Self::UnexpectedEof { - expected: ref l, .. - }, - Self::UnexpectedEof { - expected: ref r, .. - }, - ) => l == r, - ( - Self::UnrecognizedToken { - token: lt, - expected: ref l, - .. - }, - Self::UnrecognizedToken { - token: rt, - expected: ref r, - .. - }, - ) => lt == rt && l == r, - (Self::ExtraToken { token: l, .. }, Self::ExtraToken { token: r, .. }) => l == r, - (Self::EmptyFunction { .. }, Self::EmptyFunction { .. }) => true, - (Self::ImportedFunctionWithBody { .. }, Self::ImportedFunctionWithBody { .. }) => true, - (Self::InvalidU32 { value: l, .. }, Self::InvalidU32 { value: r, .. }) => l == r, - (Self::InvalidOffset { value: l, .. }, Self::InvalidOffset { value: r, .. }) => l == r, - (Self::InvalidAlignment { value: l, .. }, Self::InvalidAlignment { value: r, .. }) => { - l == r - } - (Self::InvalidAddrSpace { value: l, .. }, Self::InvalidAddrSpace { value: r, .. }) => { - l == r - } - _ => false, - } - } -} -impl From> for ParseError { - fn from(err: lalrpop_util::ParseError) -> Self { - use lalrpop_util::ParseError as LError; - - match err { - LError::InvalidToken { location } => { - Self::InvalidToken(SourceSpan::at(Default::default(), location)) - } - LError::UnrecognizedEof { - location: at, - expected, - } => Self::UnexpectedEof { - at: SourceSpan::at(Default::default(), at), - expected, - }, - LError::UnrecognizedToken { - token: (l, token, r), - expected, - } => Self::UnrecognizedToken { - span: SourceSpan::new(Default::default(), l..r), - token, - expected, - }, - LError::ExtraToken { - token: (l, token, r), - } => Self::ExtraToken { - span: SourceSpan::new(Default::default(), l..r), - token, - }, - LError::User { error } => error, - } - } -} diff --git a/hir/src/parser/grammar.lalrpop b/hir/src/parser/grammar.lalrpop deleted file mode 100644 index 03bf44d9f..000000000 --- a/hir/src/parser/grammar.lalrpop +++ /dev/null @@ -1,825 +0,0 @@ -use core::num::NonZeroU16; - -use either::Either::{self, Left, Right}; - -use crate::diagnostics::{Span, SourceId, SourceSpan, ByteIndex}; -use crate::{AbiParam, ArgumentExtension, ArgumentPurpose}; -use crate::{CallConv, ConstantData, ExternalFunction, FunctionIdent}; -use crate::{Ident, Linkage, Opcode, Overflow, Signature, symbols, Symbol}; -use crate::{Type, TypeRepr, AddressSpace, StructType}; -use crate::{AttributeSet, Attribute, AttributeValue}; -use crate::parser::{ - ast::*, - lexer::Token, - ParseError -}; - -/// Overview of grammar (not authoritative, or exhaustive): -/// -/// MODULE ::= "(" "module" ID MODULE_ATTRS* MODULE_FORMS+ ")" -/// MODULE_ATTR ::= "(" "kernel" ")" -/// MODULE_FORMS ::= CONSTANT -/// | GLOBAL -/// | DATA_SEGMENT -/// | FUNC -/// -/// CONSTANT ::= "(" "const" INDEX HEX_BYTES ")" -/// CONST_ID ::= "(" "const" U32 ")" -/// -/// GLOBAL ::= "(" "global" GLOBAL_ID INDEX TYPE GLOBAL_INIT? ")" -/// GLOBAL_ID ::= ID -/// | "(" "export" NAME_OR_ID ")" -/// GLOBAL_INIT ::= CONST_ID -/// -/// DATA_SEGMENT ::= "(" "data" MUTABLE? OFFSET HEX_BYTES ")" -/// MUTABLE ::= "(" "mut" ")" -/// -/// FUNC ::= "(" "func" FUNC_ID FUNC_ATTR* PARAM* RESULT? BLOCK+ ")" -/// FUNC_ID ::= ID -/// | "(" "export" NAME_OR_ID ")" -/// | "(" "import" MODULE_NAME_OR_ID FUNCTION_NAME_OR_ID ")" -/// FUNC_ATTR ::= "(" "cc" CALL_CONV ")" -/// -/// PARAM ::= "(" "param" PARAM_ATTRS* TYPE_NAME ")" -/// PARAM_ATTR ::= "(" "sret" ")" -/// | "(" "zext" ")" -/// | "(" "sext" ")" -/// -/// RESULT ::= "(" "result" TYPE_NAME ")" -/// -/// BLOCK ::= "(" "block" BLOCK_ID BLOCK_PARAM* INST+ ") -/// BLOCK_PARAM ::= "(" "param" VALUE_ID TYPE_NAME ")" -/// -/// INST ::= NO_RESULTS_OP -/// | ONE_RESULT_OP -/// | MANY_RESULT_OP -/// | "(" "br" SUCCESSOR ")" -/// | "(" "condbr" VALUE_ID SUCCESSOR SUCCESSOR ")" -/// | "(" "switch" VALUE_ID SWITCH_ARM* "(" "_" "." SUCCESSOR ")" ")" -/// -/// NO_RESULTS_OP ::= "(" OPCODE OPERAND* ")" -/// ONE_RESULT_OP ::= "(" "let" VALUE_ID NO_RESULTS_OP ")" -/// MANY_RESULT_OP ::= "(" "let" "[" VALUE_ID+ "]" NO_RESULTS_OP ")" -/// -/// OPCODE ::= "unreachable" -/// | "ret" -/// | ... -/// -/// SWITCH_ARM ::= "(" U32 "." SUCCESSOR ")" -/// -/// SUCCESSOR ::= "(" BLOCK_ID VALUE_ID* ")" -/// -/// TYPE ::= "(" "type" TYPE_NAME ")" -/// TYPE_NAME ::= "i32" -/// | "(" "ptr" TYPE_NAME ")" -/// | ... -/// TYPED_VALUE_ID ::= (VALUE_ID TYPE_NAME) -/// -/// INDEX ::= "(" "id" U32 ")" -/// OFFSET ::= "(" "offset" I32 ")" -/// BLOCK_ID ::= [\d]+ -/// VALUE_ID ::= v[\d]+ -/// ID ::= "#" BARE_NAME -/// NAME ::= "\"" [^\s"]+ "\"" -/// BARE_NAME ::= [^[[:cntrl:]]:;,'"\s\\]+ - - -grammar(source_id: SourceId, next_var: &mut usize); - -// MACROS -// ================================================================================================ - -// Comma-delimited with at least one element -#[inline] -Comma: Vec = { - ",")*> => { - let mut v = v; - v.push(e); - v - } -}; - -// Comma-delimited, possibly empty, possibly with a trailing comma -#[inline] -CommaOpt: Vec = { - ",")*> => { - let mut v = v; - v.extend(e); - v - } -}; - -// AST NODE -// ================================================================================================ - -pub Module: Module = { - "(" "module" ")" => { - let is_kernel = attrs.iter().any(|attr| attr.name == symbols::Kernel && attr.value.as_bool().unwrap_or_default()); - Module::new(span!(source_id, l, r), name, is_kernel, forms) - }, -} - -ModuleAttr: Attribute = { - "(" "kernel" ")" => Attribute::new(symbols::Kernel, true), -} - -ModuleForm: Form = { - ConstantDeclaration => Form::Constant(<>), - GlobalVarDeclaration => Form::Global(<>), - DataSegmentDeclaration => Form::DataSegment(<>), - FunctionDeclaration, -} - -// GLOBALS -// ================================================================================================ - -ConstantDeclaration: ConstantDeclaration = { - "(" "const" ")" - => ConstantDeclaration::new(span!(source_id, l, r), crate::Constant::from_u32(id), init), -} - -GlobalVarDeclaration: GlobalVarDeclaration = { - "(" "global" ")" => { - let (name, linkage) = name_and_linkage; - GlobalVarDeclaration::new(span!(source_id, l, r), crate::GlobalVariable::from_u32(id), name, ty, linkage, init) - } -} - -DataSegmentDeclaration: DataSegmentDeclaration = { - "(" "data" ")")> ")")?> ")" => { - let readonly = is_mut.is_none(); - let size = size.unwrap_or(data.len().try_into().expect("invalid data segment: data cannot be more than 2^32 bytes")); - DataSegmentDeclaration::new(span!(source_id, l, r), offset, size, readonly, data) - } -} - -DataSegmentValue: crate::ConstantData = { - -} - -GlobalId: (Ident, Linkage) = { - "(" "export" ")" => (name, Linkage::External), - => (name, Linkage::Internal), -} - -GlobalInit: crate::Constant = { - "(" "const" ")" => crate::Constant::from_u32(id), -} - -// TYPES -// ============================================================================================== - -Type: Type = { - "(" "type" ")" -} - -TypeName: Type = { - "?" => Type::Unknown, - "!" => Type::Never, - "i1" => Type::I1, - "i8" => Type::I8, - "u8" => Type::U8, - "i16" => Type::I16, - "u16" => Type::U16, - "i32" => Type::I32, - "u32" => Type::U32, - "i64" => Type::I64, - "u64" => Type::U64, - "i128" => Type::I128, - "u128" => Type::U128, - "u256" => Type::U256, - "f64" => Type::F64, - "felt" => Type::Felt, - "(" ")" => Type::Unit, - PointerType, - StructType, - ArrayType, -} - -PointerType: Type = { - "(" "ptr" ")")?> ")" => { - match addrspace { - None => Type::Ptr(Box::new(pointee)), - Some(addrspace) => Type::NativePtr(Box::new(pointee), addrspace), - } - } -} - -StructType: Type = { - "(" "struct" ")" => { - if let Some(repr) = repr { - Type::Struct(StructType::new_with_repr(repr, fields)) - } else { - Type::Struct(StructType::new(fields)) - } - } -} - -TypeRepr: TypeRepr = { - "(" "repr" "transparent" ")" => TypeRepr::Transparent, - "(" "repr" ")")> ")" => TypeRepr::Align(align), - "(" "repr" ")")> ")" => TypeRepr::Packed(align), -} - -ArrayType: Type = { - "(" "array" ")" => Type::Array(Box::new(element), usize::try_from(len).unwrap()), -} - -AddressSpace: AddressSpace = { - "?" => AddressSpace::Unknown, - =>? { - match u16::try_from(i) { - Ok(0) => Ok(AddressSpace::Root), - Ok(v) => Ok(AddressSpace::Id(unsafe { NonZeroU16::new_unchecked(v) })), - Err(_) => Err(ParseError::InvalidAddrSpace { span: span!(source_id, l, r), value: i }.into()), - } - } -} - -// ATTRIBUTES -// ============================================================================================== - -Attributes: AttributeSet = { - "[" "]" => AttributeSet::from_iter(attrs), -} - -Attribute: Attribute = { - => Attribute { name: name.as_symbol(), value: AttributeValue::Unit }, - - "(" ")" => Attribute { name: name.as_symbol(), value }, -} - -AttributeValue: AttributeValue = { - => match name { - symbols::True => AttributeValue::Bool(true), - symbols::False => AttributeValue::Bool(false), - symbol => AttributeValue::String(symbol), - }, - - => AttributeValue::String(quoted), - - => AttributeValue::Int(n), - - => AttributeValue::Unit, -} - -// FUNCTIONS -// ============================================================================================== - -FunctionDeclaration: Form = { - "(" "func" ")" =>? { - let (name, linkage) = name_and_linkage; - let cc = cc.unwrap_or(CallConv::SystemV); - let results = result.map(|res| vec![res]).unwrap_or_default(); - let signature = Signature { - params, - results, - cc, - linkage, - }; - match name { - Left(name) if blocks.is_empty() => Err(ParseError::EmptyFunction { span: span!(source_id, l, r) }.into()), - Left(name) => { - Ok(Form::Function(FunctionDeclaration::new(span!(source_id, l, r), name, signature, blocks, AttributeSet::default()))) - } - Right(id) if !blocks.is_empty() => Err(ParseError::ImportedFunctionWithBody { span: span!(source_id, l, r) }.into()), - Right(id) => { - Ok(Form::ExternalFunction(Span::new(span!(source_id, l, r), ExternalFunction { - id, - signature, - }))) - } - } - }, -} - -FuncId: (Either, Linkage) = { - "(" "export" ")" => (Left(<>), Linkage::External), - "(" "import" ")" => (Right(FunctionIdent { module: m, function: f }), Linkage::External), - => (Left(<>), Linkage::Internal), -} - -FuncParam: AbiParam = { - "(" "param" "(" "sret" ")" ")" - => AbiParam { ty, purpose: ArgumentPurpose::StructReturn, extension: ArgumentExtension::None }, - "(" "param" "(" ")" ")" - => AbiParam { ty, purpose: ArgumentPurpose::Default, extension }, - "(" "param" ")" - => AbiParam { ty, purpose: ArgumentPurpose::Default, extension: ArgumentExtension::None }, -} - -FuncResult: AbiParam = { - "(" "result" ")" => AbiParam { ty, purpose: Default::default(), extension: Default::default() }, -} - -CallConvAttr: CallConv = { - "(" "cc" ")", -} - -CallConv: CallConv = { - "fast" => CallConv::Fast, - "kernel" => CallConv::Kernel, - "wasm" => CallConv::Wasm, -} - -ArgumentExtension: ArgumentExtension = { - "zext" => ArgumentExtension::Zext, - "sext" => ArgumentExtension::Sext, -} - - -// BLOCKS -// ================================================================================================ - -Block: Block = { - "(" "block" ")" => { - let id = crate::Block::from_u32(id); - Block::new(span!(source_id, l, r), id, params, insts) - } -} - -BlockParam: TypedValue = { - "(" "param" ")" => TypedValue::new(span!(source_id, l, r), id, ty), -} - -// INSTRUCTIONS -// ================================================================================================ - -TypedValueId: TypedValue = { - "(" ")" => TypedValue::new(value.span(), value.into_inner(), ty), -} - -Let: (TypedValue, Span) = { - "(" "let" ")" => { - (value, inst) - } -} - -LetMany: (Vec, Span) = { - "(" "let" "[" "]" ")" => { - (values, inst) - } -} - -Inst: Inst = { - => { - let (value, inst_ty) = let_expr; - Inst::new(span!(source_id, l, r), inst_ty.into_inner(), vec![value]) - }, - - => { - let (values, inst_ty) = let_expr; - Inst::new(span!(source_id, l, r), inst_ty.into_inner(), values) - }, - - "(" "call" ")" => { - Inst::new(span!(source_id, l, r), InstType::Call { opcode: Opcode::Call, callee, operands }, vec![]) - }, - - "(" "syscall" ")" => { - Inst::new(span!(source_id, l, r), InstType::Call { opcode: Opcode::Syscall, callee, operands }, vec![]) - }, - - "(" "unreachable" ")" => { - Inst::new(span!(source_id, l, r), InstType::PrimOp { opcode: Opcode::Unreachable, operands: vec![] }, vec![]) - }, - - "(" "ret" ")" => { - let operands = operand.map(|operand| vec![operand]).unwrap_or_default(); - Inst::new(span!(source_id, l, r), InstType::Ret { opcode: Opcode::Ret, operands }, vec![]) - }, - - "(" "br" ")" => { - Inst::new(span!(source_id, l, r), InstType::Br { opcode: Opcode::Br, successor }, vec![]) - }, - - "(" "condbr" ")" => { - Inst::new(span!(source_id, l, r), InstType::CondBr { opcode: Opcode::CondBr, cond, then_dest, else_dest }, vec![]) - }, - - "(" "switch" ")" =>? { - let mut arms = arms; - let fallback = match arms.pop().unwrap() { - (None, successor, _) => successor, - (Some(_), _, _) => panic!("invalid switch: default arm is required"), - }; - let mut successors = vec![]; - for arm in arms.into_iter() { - match arm { - (Some(id), successor, span) => successors.push(Span::new(span, (id, successor))), - (None, _, _) => panic!("invalid switch: only one default arm is allowed"), - } - } - Ok(Inst::new(span!(source_id, l, r), InstType::Switch { opcode: Opcode::Switch, selector, successors, fallback }, vec![])) - }, -} - -InstWithResult: Span = { - "(" ")" => { - let (opcode, overflow) = op_and_overflow; - Span::new(span!(source_id, l, r), InstType::UnaryOp { opcode, overflow, operand }) - }, - - "(" ")" => { - let (opcode, overflow) = op_and_overflow; - Span::new(span!(source_id, l, r), InstType::BinaryOp { opcode, overflow, operands: [lhs, rhs] }) - }, - - "(" "call" ")" => { - Span::new(span!(source_id, l, r), InstType::Call { opcode: Opcode::Call, callee, operands }) - }, - - "(" "syscall" ")" => { - Span::new(span!(source_id, l, r), InstType::Call { opcode: Opcode::Syscall, callee, operands }) - }, - - => Span::new(expr.span(), InstType::GlobalValue { opcode: Opcode::GlobalValue, expr }), -} - -InstWithManyResults: Span = { - "(" ")" => { - let overflow = Some(Overflow::Overflowing); - Span::new(span!(source_id, l, r), InstType::UnaryOp { opcode, overflow, operand }) - }, - - "(" ")" => { - let overflow = Some(Overflow::Overflowing); - Span::new(span!(source_id, l, r), InstType::BinaryOp { opcode, overflow, operands: [lhs, rhs] }) - }, - - "(" ")" => { - Span::new(span!(source_id, l, r), InstType::PrimOp { opcode, operands }) - } -} - -Successor: Successor = { - "(" "block" ")" - => Successor { span: span!(source_id, l, r), id: crate::Block::from_u32(id), args }, -} - -SwitchArm: (Option, Successor, SourceSpan) = { - "(" "." ")" => (Some(value), successor, span!(source_id, l, r)), - "(" "_" "." ")" => (None, successor, span!(source_id, l, r)), -} - -UnaryOpcode: (Opcode, Option) = { - "const.i1" => (Opcode::ImmI1, None), - "const.u8" => (Opcode::ImmU8, None), - "const.i8" => (Opcode::ImmU8, None), - "const.u16" => (Opcode::ImmU16, None), - "const.i16" => (Opcode::ImmI16, None), - "const.u32" => (Opcode::ImmU32, None), - "const.i32" => (Opcode::ImmI32, None), - "const.u64" => (Opcode::ImmU64, None), - "const.i64" => (Opcode::ImmI64, None), - "const.felt" => (Opcode::ImmFelt, None), - "neg" => (Opcode::Neg, None), - "inv" => (Opcode::Inv, None), - "incr.unchecked" => (Opcode::Incr, Some(Overflow::Unchecked)), - "incr.checked" => (Opcode::Incr, Some(Overflow::Checked)), - "incr.wrapping" => (Opcode::Incr, Some(Overflow::Wrapping)), - "ilog2" => (Opcode::Ilog2, None), - "pow2" => (Opcode::Pow2, None), - "not" => (Opcode::Not, None), - "bnot" => (Opcode::Bnot, None), - "popcnt" => (Opcode::Popcnt, None), - "clz" => (Opcode::Clz, None), - "ctz" => (Opcode::Ctz, None), - "clo" => (Opcode::Clo, None), - "cto" => (Opcode::Cto, None), - "ptrtoint" => (Opcode::PtrToInt, None), - "inttoptr" => (Opcode::IntToPtr, None), - "cast" => (Opcode::Cast, None), - "trunc" => (Opcode::Trunc, None), - "zext" => (Opcode::Zext, None), - "sext" => (Opcode::Sext, None), - "is_odd" => (Opcode::IsOdd, None), -} - -OverflowingUnaryOpcode: Opcode = { - "incr.overflowing" => Opcode::Incr, -} - -BinaryOpcode: (Opcode, Option) = { - "eq" => (Opcode::Eq, None), - "neq" => (Opcode::Neq, None), - "gt" => (Opcode::Gt, None), - "gte" => (Opcode::Gte, None), - "lt" => (Opcode::Lt, None), - "lte" => (Opcode::Lte, None), - "min" => (Opcode::Min, None), - "max" => (Opcode::Max, None), - "add.unchecked" => (Opcode::Add, Some(Overflow::Unchecked)), - "add.checked" => (Opcode::Add, Some(Overflow::Checked)), - "add.wrapping" => (Opcode::Add, Some(Overflow::Wrapping)), - "sub.unchecked" => (Opcode::Sub, Some(Overflow::Unchecked)), - "sub.checked" => (Opcode::Sub, Some(Overflow::Checked)), - "sub.wrapping" => (Opcode::Sub, Some(Overflow::Wrapping)), - "mul.unchecked" => (Opcode::Mul, Some(Overflow::Unchecked)), - "mul.checked" => (Opcode::Mul, Some(Overflow::Checked)), - "mul.wrapping" => (Opcode::Mul, Some(Overflow::Wrapping)), - "div.unchecked" => (Opcode::Div, Some(Overflow::Unchecked)), - "div.checked" => (Opcode::Div, Some(Overflow::Checked)), - "mod.unchecked" => (Opcode::Mod, Some(Overflow::Unchecked)), - "mod.checked" => (Opcode::Mod, Some(Overflow::Checked)), - "divmod.unchecked" => (Opcode::DivMod, Some(Overflow::Unchecked)), - "divmod.checked" => (Opcode::DivMod, Some(Overflow::Checked)), - "exp" => (Opcode::Exp, None), - "and" => (Opcode::And, None), - "band" => (Opcode::Band, None), - "or" => (Opcode::Or, None), - "bor" => (Opcode::Bor, None), - "xor" => (Opcode::Xor, None), - "bxor" => (Opcode::Bxor, None), - "shl.unchecked" => (Opcode::Shl, Some(Overflow::Unchecked)), - "shl.checked" => (Opcode::Shl, Some(Overflow::Checked)), - "shl.wrapping" => (Opcode::Shl, Some(Overflow::Wrapping)), - "shr.unchecked" => (Opcode::Shr, Some(Overflow::Unchecked)), - "shr.checked" => (Opcode::Shr, Some(Overflow::Checked)), - "shr.wrapping" => (Opcode::Shr, Some(Overflow::Wrapping)), - "rotl" => (Opcode::Rotl, None), - "rotr" => (Opcode::Rotr, None), -} - -OverflowingBinaryOpcode: Opcode = { - "add.overflowing" => Opcode::Add, - "sub.overflowing" => Opcode::Sub, - "mul.overflowing" => Opcode::Mul, - "shl.overflowing" => Opcode::Shl, - "shr.overflowing" => Opcode::Shr, -} - -PrimOpOpcode: Opcode = { - "assert" => Opcode::Assert, - "assertz" => Opcode::Assertz, - "assert.eq" => Opcode::AssertEq, - "alloca" => Opcode::Alloca, - "store" => Opcode::Store, - "load" => Opcode::Load, - "memcpy" => Opcode::MemCpy, - "memory.grow" => Opcode::MemGrow, - "select" => Opcode::Select, -} - -Operand: Operand = { - => Operand::Value(Span::new(span!(source_id, l, r), v)), - => Operand::Int(Span::new(span!(source_id, l, r), i)), - => Operand::BigInt(Span::new(span!(source_id, l, r), i)), -} - -GlobalValueExpr: GlobalValueExpr = { - "(" "global.symbol" ")" => { - GlobalValueExpr::Symbol { symbol, offset: offset.unwrap_or(0), span: span!(source_id, l, r) } - }, - - "(" "global.load" ")" => { - let offset = offset.unwrap_or(0); - GlobalValueExpr::Load { base: Box::new(base), offset, ty: Some(ty), span: span!(source_id, l, r) } - }, - - "(" "global.iadd" "(" "offset" "." ")" ")" => { - GlobalValueExpr::IAddImm { base: Box::new(base), offset, ty, span: span!(source_id, l, r) } - } -} - -// VALUES AND IDENTIFIERS -// ================================================================================================ - -HexString: ConstantData = { - data, -} - -Int: isize = { - int, -} - -Offset: i32 = { - "(" "offset" ")", -} - -Index: u32 = { - "(" "id" ")", -} - -Align: NonZeroU16 = { - =>? { - match u16::try_from(i) { - Ok(0) => Err(ParseError::InvalidAlignment { span: span!(source_id, l, r), value: i }.into()), - Ok(v) => Ok(unsafe { NonZeroU16::new_unchecked(v) }), - Err(_) => Err(ParseError::InvalidAlignment { span: span!(source_id, l, r), value: i }.into()), - } - } -} - -U32: u32 = { - =>? { - match u32::try_from(i) { - Ok(v) => Ok(v), - Err(_) => Err(ParseError::InvalidU32 { span: span!(source_id, l, r), value: i }.into()), - } - } -} - -RawOffset: i32 = { - =>? { - match i32::try_from(i) { - Ok(v) => Ok(v), - Err(_) => Err(ParseError::InvalidOffset { span: span!(source_id, l, r), value: i }.into()), - } - } -} - -Name: Ident = { - => Ident::new(id, span!(source_id, l, r)), -} - -Id: Ident = { - => Ident::new(id, span!(source_id, l, r)), -} - -NameOrId: Ident = { - Name, - Id, -} - -CalleeId: Either = { - => Left(<>), - "(" ")" => Right(FunctionIdent { module, function }), -} - -SpannedValueId: Span = { - => Span::new(span!(source_id, l, r), v), -} - -ValueId: crate::Value = { - value_id, -} - -// LEXER -// ================================================================================================ - -extern { - type Error = ParseError; - type Location = ByteIndex; - - enum Token { - ident => Token::Ident(), - string => Token::String(), - int => Token::Int(), - bigint => Token::BigInt(), - data => Token::Hex(), - value_id => Token::ValueId(), - "module" => Token::Module, - "kernel" => Token::Kernel, - "const" => Token::Const, - "global" => Token::Global, - "data" => Token::Data, - "type" => Token::Type, - "func" => Token::Func, - "block" => Token::Block, - "let" => Token::Let, - "export" => Token::Export, - "import" => Token::Import, - "id" => Token::Id, - "param" => Token::Param, - "result" => Token::Result, - "cc" => Token::Cc, - "fast" => Token::Fast, - "wasm" => Token::Wasm, - "sret" => Token::Sret, - "zext" => Token::Zext, - "sext" => Token::Sext, - "trunc" => Token::Trunc, - "ret" => Token::Ret, - "call" => Token::Call, - "call.indirect" => Token::CallIndirect, - "syscall" => Token::Syscall, - "br" => Token::Br, - "condbr" => Token::CondBr, - "switch" => Token::Switch, - "test" => Token::Test, - "load" => Token::Load, - "memcpy" => Token::MemCpy, - "asm" => Token::Asm, - "memory.grow" => Token::MemoryGrow, - "add.unchecked" => Token::AddUnchecked, - "add.checked" => Token::AddChecked, - "add.overflowing" => Token::AddOverflowing, - "add.wrapping" => Token::AddWrapping, - "sub.unchecked" => Token::SubUnchecked, - "sub.checked" => Token::SubChecked, - "sub.overflowing" => Token::SubOverflowing, - "sub.wrapping" => Token::SubWrapping, - "mul.unchecked" => Token::MulUnchecked, - "mul.checked" => Token::MulChecked, - "mul.overflowing" => Token::MulOverflowing, - "mul.wrapping" => Token::MulWrapping, - "div.unchecked" => Token::DivUnchecked, - "div.checked" => Token::DivChecked, - "mod.unchecked" => Token::ModUnchecked, - "mod.checked" => Token::ModChecked, - "divmod.unchecked" => Token::DivModUnchecked, - "divmod.checked" => Token::DivModChecked, - "min" => Token::Min, - "max" => Token::Max, - "exp" => Token::Exp, - "and" => Token::And, - "band" => Token::BAnd, - "or" => Token::Or, - "bor" => Token::BOr, - "xor" => Token::Xor, - "bxor" => Token::BXor, - "shl.unchecked" => Token::ShlUnchecked, - "shl.checked" => Token::ShlChecked, - "shl.wrapping" => Token::ShlWrapping, - "shl.overflowing" => Token::ShlOverflowing, - "shr.unchecked" => Token::ShrUnchecked, - "shr.checked" => Token::ShrChecked, - "shr.wrapping" => Token::ShrWrapping, - "shr.overflowing" => Token::ShrOverflowing, - "rotl" => Token::Rotl, - "rotr" => Token::Rotr, - "eq" => Token::Eq, - "neq" => Token::Neq, - "gt" => Token::Gt, - "gte" => Token::Gte, - "lt" => Token::Lt, - "lte" => Token::Lte, - "store" => Token::Store, - "inv" => Token::Inv, - "incr.unchecked" => Token::IncrUnchecked, - "incr.checked" => Token::IncrChecked, - "incr.wrapping" => Token::IncrWrapping, - "incr.overflowing" => Token::IncrOverflowing, - "ilog2" => Token::Ilog2, - "pow2" => Token::Pow2, - "not" => Token::Not, - "bnot" => Token::BNot, - "popcnt" => Token::Popcnt, - "clz" => Token::Clz, - "ctz" => Token::Ctz, - "clo" => Token::Clo, - "cto" => Token::Cto, - "is_odd" => Token::IsOdd, - "cast" => Token::Cast, - "ptrtoint" => Token::PtrToInt, - "inttoptr" => Token::IntToPtr, - "neg" => Token::Neg, - "const.i1" => Token::ConstI1, - "const.i8" => Token::ConstI8, - "const.u8" => Token::ConstU8, - "const.i16" => Token::ConstI16, - "const.u16" => Token::ConstU16, - "const.i32" => Token::ConstI32, - "const.u32" => Token::ConstU32, - "const.i64" => Token::ConstI64, - "const.u64" => Token::ConstU64, - "const.felt" => Token::ConstFelt, - "select" => Token::Select, - "assert" => Token::Assert, - "assertz" => Token::Assertz, - "assert.eq" => Token::AssertEq, - "alloca" => Token::Alloca, - "unreachable" => Token::Unreachable, - "i1" => Token::I1, - "i8" => Token::I8, - "u8" => Token::U8, - "i16" => Token::I16, - "u16" => Token::U16, - "i32" => Token::I32, - "u32" => Token::U32, - "i64" => Token::I64, - "u64" => Token::U64, - "i128" => Token::I128, - "u128" => Token::U128, - "u256" => Token::U256, - "f64" => Token::F64, - "felt" => Token::Felt, - "ptr" => Token::Ptr, - "addrspace" => Token::Addrspace, - "struct" => Token::Struct, - "array" => Token::Array, - "repr" => Token::Repr, - "transparent" => Token::Transparent, - "align" => Token::Align, - "size" => Token::Size, - "packed" => Token::Packed, - "mut" => Token::Mut, - "offset" => Token::Offset, - "global.symbol" => Token::GlobalSymbol, - "global.load" => Token::GlobalLoad, - "global.iadd" => Token::GlobalIAdd, - "symbol" => Token::Symbol, - "iadd" => Token::IAdd, - "+" => Token::Plus, - "-" => Token::Minus, - "_" => Token::Underscore, - "!" => Token::Bang, - "?" => Token::Question, - "[" => Token::LBracket, - "]" => Token::RBracket, - "(" => Token::LParen, - ")" => Token::RParen, - "." => Token::Dot, - } -} diff --git a/hir/src/parser/lexer/error.rs b/hir/src/parser/lexer/error.rs deleted file mode 100644 index 55a2319c0..000000000 --- a/hir/src/parser/lexer/error.rs +++ /dev/null @@ -1,113 +0,0 @@ -use core::{fmt, num::IntErrorKind}; - -use crate::diagnostics::{miette, Diagnostic, SourceSpan}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum InvalidEscapeKind { - Empty, - InvalidChars, - Invalid, -} -impl fmt::Display for InvalidEscapeKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Empty => f.write_str("cannot be empty"), - Self::InvalidChars => f.write_str("contained one or more invalid characters"), - Self::Invalid => f.write_str("is not recognized as a valid escape"), - } - } -} - -/// Errors that may occur during lexing of the source -#[derive(Clone, Debug, thiserror::Error, Diagnostic)] -pub enum LexicalError { - #[error("invalid integer value: {}", DisplayIntErrorKind(reason))] - #[diagnostic()] - InvalidInt { - #[label] - span: SourceSpan, - reason: IntErrorKind, - }, - #[error("encountered unexpected character '{found}'")] - #[diagnostic()] - UnexpectedCharacter { - #[label] - start: SourceSpan, - found: char, - }, - #[error("unclosed string")] - #[diagnostic()] - UnclosedString { - #[label] - span: SourceSpan, - }, - #[error("invalid unicode escape: {kind}")] - #[diagnostic()] - InvalidUnicodeEscape { - #[label] - span: SourceSpan, - kind: InvalidEscapeKind, - }, - #[error("invalid hex escape: {kind}")] - #[diagnostic()] - InvalidHexEscape { - #[label] - span: SourceSpan, - kind: InvalidEscapeKind, - }, - #[error("invalid module identifier")] - #[diagnostic(help( - "module names must be non-empty, start with 'a-z', and only contain ascii alpha-numeric \ - characters, '_', or '::' as a namespacing operator", - ))] - InvalidModuleIdentifier { - #[label] - span: SourceSpan, - }, - #[error("invalid function identifier")] - #[diagnostic(help("function names must be non-empty, and start with '_' or 'a-z'"))] - InvalidFunctionIdentifier { - #[label] - span: SourceSpan, - }, -} -impl PartialEq for LexicalError { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::InvalidInt { reason: lhs, .. }, Self::InvalidInt { reason: rhs, .. }) => { - lhs == rhs - } - ( - Self::UnexpectedCharacter { found: lhs, .. }, - Self::UnexpectedCharacter { found: rhs, .. }, - ) => lhs == rhs, - (Self::UnclosedString { .. }, Self::UnclosedString { .. }) => true, - ( - Self::InvalidUnicodeEscape { kind: k1, .. }, - Self::InvalidUnicodeEscape { kind: k2, .. }, - ) => k1 == k2, - (Self::InvalidHexEscape { kind: k1, .. }, Self::InvalidHexEscape { kind: k2, .. }) => { - k1 == k2 - } - (Self::InvalidModuleIdentifier { .. }, Self::InvalidModuleIdentifier { .. }) => true, - (Self::InvalidFunctionIdentifier { .. }, Self::InvalidFunctionIdentifier { .. }) => { - true - } - _ => false, - } - } -} - -struct DisplayIntErrorKind<'a>(&'a IntErrorKind); -impl<'a> fmt::Display for DisplayIntErrorKind<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.0 { - IntErrorKind::Empty => write!(f, "unable to parse empty string as integer"), - IntErrorKind::InvalidDigit => write!(f, "invalid digit"), - IntErrorKind::PosOverflow => write!(f, "value is too big"), - IntErrorKind::NegOverflow => write!(f, "value is too big"), - IntErrorKind::Zero => write!(f, "zero is not a valid value here"), - other => write!(f, "unable to parse integer value: {:?}", other), - } - } -} diff --git a/hir/src/parser/lexer/mod.rs b/hir/src/parser/lexer/mod.rs deleted file mode 100644 index 4b2923df5..000000000 --- a/hir/src/parser/lexer/mod.rs +++ /dev/null @@ -1,511 +0,0 @@ -mod error; -mod scanner; -mod token; - -use core::{num::IntErrorKind, ops::Range}; - -use num_traits::Num; - -pub use self::{ - error::{InvalidEscapeKind, LexicalError}, - scanner::Scanner, - token::Token, -}; -use crate::{ - diagnostics::{ByteIndex, ByteOffset, SourceId, SourceSpan}, - parser::ParseError, - Symbol, Value, -}; - -/// The value produced by the [Lexer] when iterated -pub type Lexed = Result<(ByteIndex, Token, ByteIndex), ParseError>; - -/// Pops a single token from the [Lexer] -macro_rules! pop { - ($lex:ident) => {{ - $lex.skip(); - }}; - ($lex:ident, $code:expr) => {{ - $lex.skip(); - $code - }}; -} - -/// Pops two tokens from the [Lexer] -#[allow(unused)] -macro_rules! pop2 { - ($lex:ident) => {{ - $lex.skip(); - $lex.skip(); - }}; - ($lex:ident, $code:expr) => {{ - $lex.skip(); - $lex.skip(); - $code - }}; -} - -/// The lexer that is used to perform lexical analysis on the Miden IR grammar. The lexer implements -/// the `Iterator` trait, so in order to retrieve the tokens, you simply have to iterate over it. -/// -/// # Errors -/// -/// Because the lexer is implemented as an iterator over tokens, this means that you can continue -/// to get tokens even if a lexical error occurs. The lexer will attempt to recover from an error -/// by injecting tokens it expects. -/// -/// If an error is unrecoverable, the lexer will continue to produce tokens, but there is no -/// guarantee that parsing them will produce meaningful results, it is primarily to assist in -/// gathering as many errors as possible. -pub struct Lexer<'a> { - source_id: SourceId, - - /// The scanner produces a sequence of chars + location, and can be controlled - /// The location type is SourceIndex - scanner: Scanner<'a>, - - /// The most recent token to be lexed. - /// At the start and end, this should be Token::Eof - token: Token, - - /// The position in the input where the current token starts - /// At the start this will be the byte index of the beginning of the input - token_start: ByteIndex, - - /// The position in the input where the current token ends - /// At the start this will be the byte index of the beginning of the input - token_end: ByteIndex, - - /// When we have reached true Eof, this gets set to true, and the only token - /// produced after that point is Token::Eof, or None, depending on how you are - /// consuming the lexer - eof: bool, -} -impl<'a> Lexer<'a> { - /// Produces an instance of the lexer with the lexical analysis to be performed on the `input` - /// string. Note that no lexical analysis occurs until the lexer has been iterated over. - pub fn new(source_id: SourceId, source: &'a str) -> Self { - let scanner = Scanner::new(source); - let mut lexer = Lexer { - source_id, - scanner, - token: Token::Eof, - token_start: 0.into(), - token_end: 0.into(), - eof: false, - }; - lexer.advance(); - lexer - } - - pub fn lex(&mut self) -> Option<::Item> { - if self.eof && self.token == Token::Eof { - return None; - } - - let token = std::mem::replace(&mut self.token, Token::Eof); - let start = self.token_start; - let end = self.token_end; - self.advance(); - match token { - Token::Error(err) => Some(Err(err.into())), - token => Some(Ok((start, token, end))), - } - } - - fn advance(&mut self) { - self.advance_start(); - self.token = self.tokenize(); - } - - #[inline] - fn advance_start(&mut self) { - let mut position: ByteIndex = self.scanner.position(); - loop { - let (pos, c) = self.scanner.read().unwrap_or((position, '\0')); - - position = pos; - - if c == '\0' { - self.eof = true; - return; - } - - if c.is_whitespace() { - self.scanner.next(); - continue; - } - - break; - } - - self.token_start = position; - } - - #[inline] - fn pop(&mut self) -> char { - let (pos, c) = self.scanner.next().unwrap_or((self.token_start, '\0')); - self.token_end = pos + ByteOffset::from_char_len(c); - c - } - - #[inline] - fn peek(&mut self) -> char { - self.scanner.peek().map(|(_, c)| c).unwrap_or('\0') - } - - #[inline] - fn read(&mut self) -> char { - self.scanner.read().map(|(_, c)| c).unwrap_or('\0') - } - - #[inline(always)] - fn skip(&mut self) { - self.pop(); - } - - /// Get the span for the current token in `Source`. - #[inline] - fn span(&self) -> SourceSpan { - SourceSpan::new(self.source_id, self.token_start..self.token_end) - } - - #[inline] - #[allow(unused)] - fn slice_span(&self, span: impl Into>) -> &str { - self.scanner.slice(span) - } - - /// Get a string slice of the current token. - #[inline] - fn slice(&self) -> &str { - self.scanner.slice(self.span()) - } - - #[inline] - fn skip_whitespace(&mut self) { - let mut c: char; - loop { - c = self.read(); - - if !c.is_whitespace() { - break; - } - - self.skip(); - } - } - - fn tokenize(&mut self) -> Token { - let c = self.read(); - - if c == ';' { - return self.lex_comment(); - } - - if c == '\0' { - self.eof = true; - return Token::Eof; - } - - if c.is_whitespace() { - self.skip_whitespace(); - } - - match self.read() { - '.' => pop!(self, Token::Dot), - '"' => self.lex_quoted_string(), - '(' => pop!(self, Token::LParen), - ')' => pop!(self, Token::RParen), - '[' => pop!(self, Token::LBracket), - ']' => pop!(self, Token::RBracket), - '+' => self.lex_number(), - '-' => self.lex_number(), - '!' => pop!(self, Token::Bang), - '?' => pop!(self, Token::Question), - '#' => self.lex_symbol(), - '0' => match self.peek() { - 'x' => { - self.skip(); - self.skip(); - self.lex_hex() - } - '0'..='9' => self.lex_number(), - _ => pop!(self, Token::Int(0)), - }, - '1'..='9' => self.lex_number(), - 'a'..='z' => self.lex_keyword_or_special_ident(), - '_' => pop!(self, Token::Underscore), - c => Token::Error(LexicalError::UnexpectedCharacter { - start: self.span(), - found: c, - }), - } - } - - fn lex_comment(&mut self) -> Token { - let mut c; - loop { - c = self.read(); - - if c == '\n' { - self.skip(); - break; - } - - if c == '\0' { - self.eof = true; - break; - } - - self.skip(); - } - - Token::Comment - } - - fn lex_symbol(&mut self) -> Token { - let c = self.pop(); - debug_assert_eq!(c, '#'); - - // A '#' followed by any sequence of printable ASCII characters, - // except whitespace, quotation marks, comma, semicolon, or params/brackets - loop { - match self.read() { - c if c.is_ascii_control() => break, - ' ' | '\'' | '"' | ',' | ';' | '[' | ']' | '(' | ')' => break, - c if c.is_ascii_graphic() => self.skip(), - _ => break, - } - } - - Token::Ident(Symbol::intern(&self.slice()[1..])) - } - - fn lex_keyword_or_special_ident(&mut self) -> Token { - let c = self.pop(); - debug_assert!(c.is_ascii_alphabetic() && c.is_lowercase()); - - loop { - match self.read() { - '_' => self.skip(), - '.' => { - // We only allow '.' when followed by a alpha character - match self.peek() { - c if c.is_ascii_lowercase() => self.skip(), - _ => break, - } - } - '0'..='9' => self.skip(), - c if c.is_ascii_lowercase() => self.skip(), - _ => break, - } - } - - let s = self.slice(); - if let Some(rest) = s.strip_prefix('v') { - return match rest.parse::() { - Ok(id) => Token::ValueId(Value::from_u32(id)), - Err(_) => Token::from_keyword(s).unwrap_or_else(|| Token::Ident(Symbol::intern(s))), - }; - } - Token::from_keyword(s).unwrap_or_else(|| Token::Ident(Symbol::intern(s))) - } - - fn lex_quoted_string(&mut self) -> Token { - let quote = self.pop(); - debug_assert!(quote == '"'); - - let mut buf = String::new(); - loop { - match self.read() { - '\0' => { - break Token::Error(LexicalError::UnclosedString { span: self.span() }); - } - '"' => { - self.skip(); - let symbol = Symbol::intern(&buf); - - break Token::String(symbol); - } - '\\' => { - self.skip(); - let start = self.token_end - 1; - match self.read() { - 't' => buf.push('\t'), - 'n' => buf.push('\n'), - 'r' => buf.push('\r'), - '"' => buf.push('"'), - '\\' => buf.push('\\'), - c if c.is_ascii_hexdigit() && self.peek().is_ascii_hexdigit() => { - self.skip(); - let c2 = self.read(); - self.skip(); - let n = c.to_digit(16).unwrap(); - let m = c2.to_digit(16).unwrap(); - match char::from_u32((16 * n) + m) { - Some(escaped) => buf.push(escaped), - None => { - break Token::Error(LexicalError::InvalidHexEscape { - span: SourceSpan::new( - self.source_id, - start..self.token_end, - ), - kind: InvalidEscapeKind::Invalid, - }); - } - } - } - 'u' if self.peek() == '{' => { - let mut escape = 0u32; - self.skip(); - self.skip(); - if self.read() == '}' { - break Token::Error(LexicalError::InvalidUnicodeEscape { - span: SourceSpan::new(self.source_id, start..self.token_end), - kind: InvalidEscapeKind::Empty, - }); - } - loop { - let c = self.read(); - if !c.is_ascii_hexdigit() { - match c { - '}' => { - self.skip(); - break; - } - '_' => { - self.skip(); - continue; - } - _ => { - return Token::Error( - LexicalError::InvalidUnicodeEscape { - span: SourceSpan::new( - self.source_id, - (self.token_end - 1)..self.token_end, - ), - kind: InvalidEscapeKind::InvalidChars, - }, - ) - } - } - } - escape *= 16; - escape += c.to_digit(16).unwrap(); - self.skip(); - } - match char::from_u32(escape) { - Some(escaped) => buf.push(escaped), - None => { - break Token::Error(LexicalError::InvalidUnicodeEscape { - span: SourceSpan::new( - self.source_id, - start..self.token_end, - ), - kind: InvalidEscapeKind::Invalid, - }); - } - } - } - _ => { - break Token::Error(LexicalError::InvalidHexEscape { - span: SourceSpan::new(self.source_id, start..self.token_end), - kind: InvalidEscapeKind::InvalidChars, - }); - } - } - } - c => { - buf.push(c); - self.skip(); - continue; - } - } - } - } - - fn lex_number(&mut self) -> Token { - use num_bigint::BigInt; - - let mut num = String::new(); - - // Expect the first character to be a digit or sign - let c = self.read(); - debug_assert!(c == '-' || c == '+' || c.is_ascii_digit()); - if c == '-' { - num.push(self.pop()); - } else if c == '+' { - self.skip(); - } - - while let '0'..='9' = self.read() { - num.push(self.pop()); - } - - match num.parse::() { - Ok(value) => Token::Int(value), - Err(err) => match err.kind() { - IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => { - let value = BigInt::from_str_radix(&num, 10).expect("invalid bigint"); - Token::BigInt(value) - } - reason => Token::Error(LexicalError::InvalidInt { - span: self.span(), - reason: reason.clone(), - }), - }, - } - } - - fn lex_hex(&mut self) -> Token { - let mut res: Vec = Vec::new(); - - // Expect the first character to be a valid hexadecimal digit - debug_assert!(self.read().is_ascii_hexdigit()); - - loop { - // If we hit a non-hex digit, we're done - let c1 = self.read(); - if !c1.is_ascii_hexdigit() { - break; - } - self.skip(); - - // All hex-encoded bytes are zero-padded, and thus occur - // in pairs, if we observe a non-hex digit at this point, - // it is invalid - let c2 = self.read(); - if !c2.is_ascii_hexdigit() { - return Token::Error(LexicalError::InvalidInt { - span: self.span(), - reason: IntErrorKind::InvalidDigit, - }); - } - self.skip(); - - // Each byte is represented by two hex digits, which can be converted - // to a value in the range 0..256 as by shifting the first left by 4 - // bits (equivalent to multiplying by 16), then adding the second digit - let byte = (c1.to_digit(16).unwrap() << 4) + c2.to_digit(16).unwrap(); - res.push(byte as u8); - } - - // We parse big-endian, but convert to little-endian - res.reverse(); - - Token::Hex(res.into()) - } -} - -impl<'a> Iterator for Lexer<'a> { - type Item = Lexed; - - fn next(&mut self) -> Option { - let mut res = self.lex(); - while let Some(Ok((_, Token::Comment, _))) = res { - res = self.lex(); - } - res - } -} diff --git a/hir/src/parser/lexer/scanner.rs b/hir/src/parser/lexer/scanner.rs deleted file mode 100644 index 05038835f..000000000 --- a/hir/src/parser/lexer/scanner.rs +++ /dev/null @@ -1,85 +0,0 @@ -use core::{ - iter::{FusedIterator, Peekable}, - ops::Range, -}; - -use crate::diagnostics::{ByteIndex, ByteOffset}; - -/// A simple raw character source for [super::Lexer]; -pub struct Scanner<'a> { - src: &'a str, - buf: Peekable>, - next: Option<(ByteIndex, char)>, - pos: ByteIndex, - eof: bool, -} -impl<'a> Scanner<'a> { - pub fn new(src: &'a str) -> Self { - let eof = src.is_empty(); - let mut buf = src.char_indices().peekable(); - let next = buf.next().map(|(i, c)| (ByteIndex::from(i as u32), c)); - Self { - src, - buf, - next, - pos: next.map(|(i, c)| i + ByteOffset::from_char_len(c)).unwrap_or_default(), - eof, - } - } - - #[inline(always)] - pub const fn read(&self) -> Option<(ByteIndex, char)> { - self.next - } - - pub fn peek(&mut self) -> Option<(ByteIndex, char)> { - self.buf.peek().and_then(|&(i, c)| match u32::try_from(i) { - Ok(i) => Some((ByteIndex::from(i), c)), - Err(_) => None, - }) - } - - #[inline] - fn advance(&mut self) { - match self.buf.next() { - Some((i, c)) if i < u32::MAX as usize => { - let i = ByteIndex::from(i as u32); - self.pos = i + ByteOffset::from_char_len(c); - self.next = Some((i, c)); - } - Some(_) => { - panic!("invalid source file: only files smaller than 2^32 bytes are supported") - } - None => { - self.eof = true; - self.next = None; - } - } - } - - pub fn position(&self) -> ByteIndex { - self.pos - } - - #[inline(always)] - pub fn slice(&self, span: impl Into>) -> &str { - &self.src[span.into()] - } -} - -impl<'a> Iterator for Scanner<'a> { - type Item = (ByteIndex, char); - - #[inline] - fn next(&mut self) -> Option { - if self.eof { - return None; - } - - let current = self.next.take(); - self.advance(); - current - } -} - -impl<'a> FusedIterator for Scanner<'a> {} diff --git a/hir/src/parser/lexer/token.rs b/hir/src/parser/lexer/token.rs deleted file mode 100644 index 2d712b148..000000000 --- a/hir/src/parser/lexer/token.rs +++ /dev/null @@ -1,541 +0,0 @@ -use core::{fmt, mem}; - -use num_bigint::BigInt; - -use super::LexicalError; -use crate::Symbol; - -/// The Token type produced by [Lexer] -#[derive(Debug, Clone)] -pub enum Token { - Eof, - Comment, - Error(LexicalError), - /// A named item, e.g. module, function, global - Ident(Symbol), - /// A quoted string or identifier - String(Symbol), - /// A value is an identifier of the form `v(0|[1-9][0-9]*)` - ValueId(crate::Value), - /// Fixed-width integers smaller than or equal to the platform native integer width - Int(isize), - /// Represents large integer types, such as i128 or u256 - BigInt(BigInt), - /// Hex strings are used to initialize global variables - Hex(crate::ConstantData), - Module, - Kernel, - Const, - Global, - Data, - Type, - Func, - Block, - Let, - Export, - Import, - Id, - Param, - Result, - Cc, - Fast, - Wasm, - Sret, - Sext, - Zext, - Trunc, - Ret, - Call, - CallIndirect, - Syscall, - Br, - CondBr, - Switch, - Test, - Load, - MemCpy, - Asm, - MemoryGrow, - AddUnchecked, - AddChecked, - AddOverflowing, - AddWrapping, - SubUnchecked, - SubChecked, - SubOverflowing, - SubWrapping, - MulUnchecked, - MulChecked, - MulOverflowing, - MulWrapping, - DivUnchecked, - DivChecked, - ModUnchecked, - ModChecked, - DivModUnchecked, - DivModChecked, - Min, - Max, - Exp, - And, - BAnd, - Or, - BOr, - Xor, - BXor, - ShlUnchecked, - ShlChecked, - ShlWrapping, - ShlOverflowing, - ShrUnchecked, - ShrChecked, - ShrWrapping, - ShrOverflowing, - Rotl, - Rotr, - Eq, - Neq, - Gt, - Gte, - Lt, - Lte, - Store, - Inv, - IncrUnchecked, - IncrChecked, - IncrWrapping, - IncrOverflowing, - Ilog2, - Pow2, - Not, - BNot, - Popcnt, - Clz, - Ctz, - Clo, - Cto, - IsOdd, - Cast, - PtrToInt, - IntToPtr, - Neg, - ConstI1, - ConstI8, - ConstU8, - ConstI16, - ConstU16, - ConstI32, - ConstU32, - ConstI64, - ConstU64, - ConstFelt, - Select, - Assert, - Assertz, - AssertEq, - Alloca, - Unreachable, - I1, - I8, - U8, - I16, - U16, - I32, - U32, - I64, - U64, - I128, - U128, - U256, - F64, - Felt, - Ptr, - Addrspace, - Struct, - Array, - Repr, - Transparent, - Align, - Size, - Packed, - Mut, - Offset, - GlobalSymbol, - GlobalLoad, - GlobalIAdd, - Symbol, - IAdd, - Plus, - Minus, - Underscore, - Bang, - Question, - Hash, - LBracket, - RBracket, - LParen, - RParen, - Dot, -} -impl Token { - pub fn from_keyword(s: &str) -> Option { - let tok = match s { - "module" => Self::Module, - "kernel" => Self::Kernel, - "const" => Self::Const, - "global" => Self::Global, - "data" => Self::Data, - "type" => Self::Type, - "func" => Self::Func, - "block" => Self::Block, - "let" => Self::Let, - "export" => Self::Export, - "import" => Self::Import, - "id" => Self::Id, - "param" => Self::Param, - "result" => Self::Result, - "cc" => Self::Cc, - "fast" => Self::Fast, - "wasm" => Self::Wasm, - "sret" => Self::Sret, - "zext" => Self::Zext, - "sext" => Self::Sext, - "trunc" => Self::Trunc, - "ret" => Self::Ret, - "call" => Self::Call, - "call.indirect" => Self::CallIndirect, - "syscall" => Self::Syscall, - "br" => Self::Br, - "condbr" => Self::CondBr, - "switch" => Self::Switch, - "test" => Self::Test, - "load" => Self::Load, - "memcpy" => Self::MemCpy, - "asm" => Self::Asm, - "memory.grow" => Self::MemoryGrow, - "add.unchecked" => Self::AddUnchecked, - "add.checked" => Self::AddChecked, - "add.overflowing" => Self::AddOverflowing, - "add.wrapping" => Self::AddWrapping, - "sub.unchecked" => Self::SubUnchecked, - "sub.checked" => Self::SubChecked, - "sub.overflowing" => Self::SubOverflowing, - "sub.wrapping" => Self::SubWrapping, - "mul.unchecked" => Self::MulUnchecked, - "mul.checked" => Self::MulChecked, - "mul.overflowing" => Self::MulOverflowing, - "mul.wrapping" => Self::MulWrapping, - "div.unchecked" => Self::DivUnchecked, - "div.checked" => Self::DivChecked, - "mod.unchecked" => Self::ModUnchecked, - "mod.checked" => Self::ModChecked, - "divmod.unchecked" => Self::DivModUnchecked, - "divmod.checked" => Self::DivModChecked, - "min" => Self::Min, - "max" => Self::Max, - "exp" => Self::Exp, - "and" => Self::And, - "band" => Self::BAnd, - "or" => Self::Or, - "bor" => Self::BOr, - "xor" => Self::Xor, - "bxor" => Self::BXor, - "shl.unchecked" => Self::ShlUnchecked, - "shl.checked" => Self::ShlChecked, - "shl.wrapping" => Self::ShlWrapping, - "shl.overflowing" => Self::ShlOverflowing, - "shr.unchecked" => Self::ShrUnchecked, - "shr.checked" => Self::ShrChecked, - "shr.wrapping" => Self::ShrWrapping, - "shr.overflowing" => Self::ShrOverflowing, - "rotl" => Self::Rotl, - "rotr" => Self::Rotr, - "eq" => Self::Eq, - "neq" => Self::Neq, - "gt" => Self::Gt, - "gte" => Self::Gte, - "lt" => Self::Lt, - "lte" => Self::Lte, - "store" => Self::Store, - "inv" => Self::Inv, - "incr.unchecked" => Self::IncrUnchecked, - "incr.checked" => Self::IncrChecked, - "incr.wrapping" => Self::IncrWrapping, - "incr.overflowing" => Self::IncrOverflowing, - "ilog2" => Self::Ilog2, - "pow2" => Self::Pow2, - "not" => Self::Not, - "bnot" => Self::BNot, - "popcnt" => Self::Popcnt, - "clz" => Self::Clz, - "ctz" => Self::Ctz, - "clo" => Self::Clo, - "cto" => Self::Cto, - "is_odd" => Self::IsOdd, - "cast" => Self::Cast, - "ptrtoint" => Self::PtrToInt, - "inttoptr" => Self::IntToPtr, - "neg" => Self::Neg, - "const.i1" => Self::ConstI1, - "const.i8" => Self::ConstI8, - "const.u8" => Self::ConstU8, - "const.i16" => Self::ConstI16, - "const.u16" => Self::ConstU16, - "const.i32" => Self::ConstI32, - "const.u32" => Self::ConstU32, - "const.i64" => Self::ConstI64, - "const.u64" => Self::ConstU64, - "const.felt" => Self::ConstFelt, - "select" => Self::Select, - "assert" => Self::Assert, - "assertz" => Self::Assertz, - "assert.eq" => Self::AssertEq, - "alloca" => Self::Alloca, - "unreachable" => Self::Unreachable, - "i1" => Self::I1, - "i8" => Self::I8, - "u8" => Self::U8, - "i16" => Self::I16, - "u16" => Self::U16, - "i32" => Self::I32, - "u32" => Self::U32, - "i64" => Self::I64, - "u64" => Self::U64, - "i128" => Self::I128, - "u128" => Self::U128, - "u256" => Self::U256, - "f64" => Self::F64, - "felt" => Self::Felt, - "ptr" => Self::Ptr, - "addrspace" => Self::Addrspace, - "struct" => Self::Struct, - "array" => Self::Array, - "repr" => Self::Repr, - "transparent" => Self::Transparent, - "align" => Self::Align, - "size" => Self::Size, - "packed" => Self::Packed, - "mut" => Self::Mut, - "offset" => Self::Offset, - "global.symbol" => Self::GlobalSymbol, - "global.load" => Self::GlobalLoad, - "global.iadd" => Self::GlobalIAdd, - "symbol" => Self::Symbol, - "iadd" => Self::IAdd, - _ => return None, - }; - Some(tok) - } -} -impl Eq for Token {} -impl PartialEq for Token { - fn eq(&self, other: &Token) -> bool { - match self { - Self::Int(i) => { - if let Self::Int(i2) = other { - return *i == *i2; - } - } - Self::BigInt(i) => { - if let Self::BigInt(i2) = other { - return *i == *i2; - } - } - Self::Error(_) => { - if let Self::Error(_) = other { - return true; - } - } - Self::Ident(i) => { - if let Self::Ident(i2) = other { - return i == i2; - } - } - Self::String(s) => { - if let Self::String(s2) = other { - return s == s2; - } - } - Self::Hex(a) => { - if let Self::Hex(b) = other { - return a == b; - } - } - Self::ValueId(a) => { - if let Self::ValueId(b) = other { - return a == b; - } - } - _ => return mem::discriminant(self) == mem::discriminant(other), - } - false - } -} -impl fmt::Display for Token { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::Eof => write!(f, "end of file"), - Self::Comment => write!(f, "comment"), - Self::Error(_) => write!(f, "invalid token"), - Self::Ident(_) => write!(f, "identifier"), - Self::String(_) => write!(f, "quoted string"), - Self::ValueId(_) => write!(f, "value"), - Self::Int(_) => write!(f, "integer literal"), - Self::BigInt(_) => write!(f, "big integer literal"), - Self::Hex(_) => write!(f, "big-endian hex string"), - Self::Module => write!(f, "module"), - Self::Kernel => write!(f, "kernel"), - Self::Const => write!(f, "const"), - Self::Global => write!(f, "global"), - Self::Data => write!(f, "data"), - Self::Type => write!(f, "type"), - Self::Func => write!(f, "func"), - Self::Block => write!(f, "block"), - Self::Let => write!(f, "let"), - Self::Export => write!(f, "export"), - Self::Import => write!(f, "import"), - Self::Id => write!(f, "id"), - Self::Param => write!(f, "param"), - Self::Result => write!(f, "result"), - Self::Cc => write!(f, "cc"), - Self::Fast => write!(f, "fast"), - Self::Wasm => write!(f, "wasm"), - Self::Sret => write!(f, "sret"), - Self::Zext => write!(f, "zext"), - Self::Sext => write!(f, "sext"), - Self::Trunc => write!(f, "trunc"), - Self::Ret => write!(f, "ret"), - Self::Call => write!(f, "call"), - Self::CallIndirect => write!(f, "call.indirect"), - Self::Syscall => write!(f, "syscall"), - Self::Br => write!(f, "br"), - Self::CondBr => write!(f, "condbr"), - Self::Switch => write!(f, "switch"), - Self::Test => write!(f, "test"), - Self::Load => write!(f, "load"), - Self::MemCpy => write!(f, "memcpy"), - Self::Asm => write!(f, "asm"), - Self::MemoryGrow => write!(f, "memory.grow"), - Self::AddUnchecked => write!(f, "add.unchecked"), - Self::AddChecked => write!(f, "add.checked"), - Self::AddOverflowing => write!(f, "add.overflowing"), - Self::AddWrapping => write!(f, "add.wrapping"), - Self::SubUnchecked => write!(f, "sub.unchecked"), - Self::SubChecked => write!(f, "sub.checked"), - Self::SubOverflowing => write!(f, "sub.overflowing"), - Self::SubWrapping => write!(f, "sub.wrapping"), - Self::MulUnchecked => write!(f, "mul.unchecked"), - Self::MulChecked => write!(f, "mul.checked"), - Self::MulOverflowing => write!(f, "mul.overflowing"), - Self::MulWrapping => write!(f, "mul.wrapping"), - Self::DivUnchecked => write!(f, "div.unchecked"), - Self::DivChecked => write!(f, "div.checked"), - Self::ModUnchecked => write!(f, "mod.unchecked"), - Self::ModChecked => write!(f, "mod.checked"), - Self::DivModUnchecked => write!(f, "divmod.unchecked"), - Self::DivModChecked => write!(f, "divmod.checked"), - Self::Min => write!(f, "min"), - Self::Max => write!(f, "max"), - Self::Exp => write!(f, "exp"), - Self::And => write!(f, "and"), - Self::BAnd => write!(f, "band"), - Self::Or => write!(f, "or"), - Self::BOr => write!(f, "bor"), - Self::Xor => write!(f, "xor"), - Self::BXor => write!(f, "bxor"), - Self::ShlUnchecked => write!(f, "shl.unchecked"), - Self::ShlChecked => write!(f, "shl.checked"), - Self::ShlWrapping => write!(f, "shl.wrapping"), - Self::ShlOverflowing => write!(f, "shl.overflowing"), - Self::ShrUnchecked => write!(f, "shr.unchecked"), - Self::ShrChecked => write!(f, "shr.checked"), - Self::ShrWrapping => write!(f, "shr.wrapping"), - Self::ShrOverflowing => write!(f, "shr.overflowing"), - Self::Rotl => write!(f, "rotl"), - Self::Rotr => write!(f, "rotr"), - Self::Eq => write!(f, "eq"), - Self::Neq => write!(f, "neq"), - Self::Gt => write!(f, "gt"), - Self::Gte => write!(f, "gte"), - Self::Lt => write!(f, "lt"), - Self::Lte => write!(f, "lte"), - Self::Store => write!(f, "store"), - Self::Inv => write!(f, "inv"), - Self::IncrUnchecked => write!(f, "incr.unchecked"), - Self::IncrChecked => write!(f, "incr.checked"), - Self::IncrWrapping => write!(f, "incr.wrapping"), - Self::IncrOverflowing => write!(f, "incr.overflowing"), - Self::Ilog2 => write!(f, "ilog2"), - Self::Pow2 => write!(f, "pow2"), - Self::Not => write!(f, "not"), - Self::BNot => write!(f, "bnot"), - Self::Popcnt => write!(f, "popcnt"), - Self::Clz => write!(f, "clz"), - Self::Ctz => write!(f, "ctz"), - Self::Clo => write!(f, "clo"), - Self::Cto => write!(f, "cto"), - Self::IsOdd => write!(f, "is_odd"), - Self::Cast => write!(f, "cast"), - Self::PtrToInt => write!(f, "ptrtoint"), - Self::IntToPtr => write!(f, "inttoptr"), - Self::Neg => write!(f, "neg"), - Self::ConstI1 => write!(f, "const.i1"), - Self::ConstI8 => write!(f, "const.i8"), - Self::ConstU8 => write!(f, "const.u8"), - Self::ConstI16 => write!(f, "const.i16"), - Self::ConstU16 => write!(f, "const.u16"), - Self::ConstI32 => write!(f, "const.i32"), - Self::ConstU32 => write!(f, "const.u32"), - Self::ConstI64 => write!(f, "const.i64"), - Self::ConstU64 => write!(f, "const.u64"), - Self::ConstFelt => write!(f, "const.felt"), - Self::Select => write!(f, "select"), - Self::Assert => write!(f, "assert"), - Self::Assertz => write!(f, "assertz"), - Self::AssertEq => write!(f, "assert.eq"), - Self::Alloca => write!(f, "alloca"), - Self::Unreachable => write!(f, "unreachable"), - Self::I1 => write!(f, "i1"), - Self::I8 => write!(f, "i8"), - Self::U8 => write!(f, "u8"), - Self::I16 => write!(f, "i16"), - Self::U16 => write!(f, "u16"), - Self::I32 => write!(f, "i32"), - Self::U32 => write!(f, "u32"), - Self::I64 => write!(f, "i64"), - Self::U64 => write!(f, "u64"), - Self::I128 => write!(f, "i128"), - Self::U128 => write!(f, "u128"), - Self::U256 => write!(f, "u256"), - Self::F64 => write!(f, "f64"), - Self::Felt => write!(f, "felt"), - Self::Ptr => write!(f, "ptr"), - Self::Addrspace => write!(f, "addrspace"), - Self::Struct => write!(f, "struct"), - Self::Array => write!(f, "array"), - Self::Repr => write!(f, "repr"), - Self::Transparent => write!(f, "transparent"), - Self::Align => write!(f, "align"), - Self::Size => write!(f, "size"), - Self::Packed => write!(f, "packed"), - Self::Mut => write!(f, "mut"), - Self::Offset => write!(f, "offset"), - Self::GlobalSymbol => write!(f, "global.symbol"), - Self::GlobalLoad => write!(f, "global.load"), - Self::GlobalIAdd => write!(f, "global.iadd"), - Self::Symbol => write!(f, "symbol"), - Self::IAdd => write!(f, "iadd"), - Self::Plus => write!(f, "+"), - Self::Minus => write!(f, "-"), - Self::Underscore => write!(f, "_"), - Self::Bang => write!(f, "!"), - Self::Question => write!(f, "?"), - Self::Hash => write!(f, "#"), - Self::LBracket => write!(f, "["), - Self::RBracket => write!(f, "]"), - Self::LParen => write!(f, "("), - Self::RParen => write!(f, ")"), - Self::Dot => write!(f, "."), - } - } -} diff --git a/hir/src/parser/mod.rs b/hir/src/parser/mod.rs deleted file mode 100644 index 4ae9442c1..000000000 --- a/hir/src/parser/mod.rs +++ /dev/null @@ -1,145 +0,0 @@ -/// Simple macro used in the grammar definition for constructing spans -macro_rules! span { - ($source_id:expr, $l:expr, $r:expr) => { - SourceSpan::new($source_id, $l..$r) - }; - ($source_id:expr, $i:expr) => { - SourceSpan::at($source_id, $i) - }; -} - -pub mod ast; -mod error; -mod lexer; -#[cfg(test)] -mod tests; - -lalrpop_mod!( - #[allow(clippy::all)] - grammar, - "/parser/grammar.rs" -); - -use alloc::sync::Arc; -use std::path::Path; - -pub use self::error::ParseError; -use self::{ - ast::ConvertAstToHir, - lexer::{Lexed, Lexer}, -}; -use crate::diagnostics::{Report, SourceFile, SourceManagerExt}; - -pub type ParseResult = Result; - -/// This is the parser for HIR text format -pub struct Parser<'a> { - session: &'a midenc_session::Session, -} -impl<'a> Parser<'a> { - /// Construct a new [Parser] - pub fn new(session: &'a midenc_session::Session) -> Self { - Self { session } - } - - /// Parse a `T` from a source file stored in the current code map - pub fn parse(&self, source: Arc) -> ParseResult - where - T: Parse, - { - ::parse(self, source) - } - - /// Parse a `T` from a string - pub fn parse_str(&self, source: impl AsRef) -> ParseResult - where - T: Parse, - { - let file = self.session.source_manager.load("nofile", source.as_ref().to_string()); - self.parse(file) - } - - /// Parse a `T` from the given file path - pub fn parse_file(&self, path: impl AsRef) -> ParseResult - where - T: Parse, - { - let path = path.as_ref(); - let file = self.session.source_manager.load_file(path).map_err(|err| { - Report::msg(err).wrap_err(format!("failed to load '{}' from disk", path.display())) - })?; - self.parse(file) - } -} - -pub trait Parse: Sized { - type Grammar; - - fn parse(parser: &Parser, source: Arc) -> ParseResult { - let lexer = Lexer::new(source.id(), source.as_str()); - - Self::parse_tokens(parser, source.clone(), lexer) - } - - fn parse_tokens( - parser: &Parser, - source: Arc, - tokens: impl IntoIterator, - ) -> ParseResult; -} -impl Parse for ast::Module { - type Grammar = grammar::ModuleParser; - - fn parse_tokens( - _parser: &Parser, - source: Arc, - tokens: impl IntoIterator, - ) -> ParseResult { - let source_id = source.id(); - let mut next_var = 0; - let result = ::Grammar::new().parse(source_id, &mut next_var, tokens); - match result { - Ok(ast) => Ok(ast), - Err(lalrpop_util::ParseError::User { error }) => { - Err(Report::from(error).with_source_code(source)) - } - Err(err) => { - let error = ParseError::from(err); - Err(Report::from(error).with_source_code(source)) - } - } - } -} -impl Parse for crate::Module { - type Grammar = grammar::ModuleParser; - - fn parse_tokens( - parser: &Parser, - source: Arc, - tokens: impl IntoIterator, - ) -> ParseResult { - use crate::pass::{AnalysisManager, ConversionPass}; - - let source_id = source.id(); - let mut next_var = 0; - let result = ::Grammar::new() - .parse(source_id, &mut next_var, tokens) - .map(Box::new); - match result { - Ok(ast) => { - let mut analyses = AnalysisManager::new(); - let mut convert_to_hir = ConvertAstToHir; - convert_to_hir - .convert(ast, &mut analyses, parser.session) - .map_err(|err| err.with_source_code(source)) - } - Err(lalrpop_util::ParseError::User { error }) => { - Err(Report::from(error).with_source_code(source)) - } - Err(err) => { - let error = ParseError::from(err); - Err(Report::from(error).with_source_code(source)) - } - } - } -} diff --git a/hir/src/parser/tests/input/test.hir b/hir/src/parser/tests/input/test.hir deleted file mode 100644 index cf76da8af..000000000 --- a/hir/src/parser/tests/input/test.hir +++ /dev/null @@ -1,21 +0,0 @@ -(module #test - ;; Constants - (const (id 0) 0xdeadbeef) - - ;; Global Variables - (global #DEADBEEF (id 0) (type u32) (const 0)) - - ;; Functions - (func (export #foo) (cc fast) (param u32) (param (sext) u32) (result u32) - (block 0 (param v1 u32) (param v2 u32) - (let (v3 u32) (add.unchecked v1 v2)) - (br (block 1))) - - (block 1 - (ret v3)) - ) - - ;; Imports - (func (import #tuple #make_pair) - (cc kernel) (param (sret) (ptr (struct u32 u32)))) -) diff --git a/hir/src/parser/tests/mod.rs b/hir/src/parser/tests/mod.rs deleted file mode 100644 index c99ed3af5..000000000 --- a/hir/src/parser/tests/mod.rs +++ /dev/null @@ -1,175 +0,0 @@ -use pretty_assertions::assert_eq; - -use crate::{ - diagnostics::{SourceSpan, Span}, - parser::ast::*, - AbiParam, ArgumentExtension, ArgumentPurpose, CallConv, ExternalFunction, FunctionIdent, Ident, - Linkage, Opcode, Overflow, Signature, StructType, Type, -}; - -macro_rules! ident { - ($name:ident) => { - Ident::new(crate::Symbol::intern(stringify!($name)), SourceSpan::UNKNOWN) - }; -} - -mod utils; -use self::utils::ParseTest; - -/// This test tries to exercise a broad swath of the IR syntax -#[test] -fn parser_integration_test() { - let dummy_sourcespan = SourceSpan::UNKNOWN; - - // global internal @DEADBEEF : u32 = 0xdeadbeef { id = 0 }; - let deadbeef_const_id = crate::Constant::from_u32(0); - let deadbeef_const = - ConstantDeclaration::new(dummy_sourcespan, deadbeef_const_id, "deadbeef".parse().unwrap()); - let deadbeef = GlobalVarDeclaration::new( - dummy_sourcespan, - crate::GlobalVariable::from_u32(0), - ident!(DEADBEEF), - Type::U32, - Linkage::Internal, - Some(deadbeef_const_id), - ); - - // pub cc(fast) fn foo(u32, sext u32) -> u32 { - let mut foo = FunctionDeclaration { - span: dummy_sourcespan, - attrs: Default::default(), - name: ident!(foo), - signature: Signature { - params: vec![ - AbiParam::new(Type::U32), - AbiParam { - ty: Type::U32, - purpose: Default::default(), - extension: ArgumentExtension::Sext, - }, - ], - results: vec![AbiParam::new(Type::U32)], - cc: CallConv::Fast, - linkage: Linkage::External, - }, - blocks: vec![], - }; - - // blk0(v1: u32, v2: u32): - let v1 = crate::Value::from_u32(1); - let v2 = crate::Value::from_u32(2); - let blk0_id = crate::Block::from_u32(0); - let mut blk0 = Block { - span: dummy_sourcespan, - id: blk0_id, - params: vec![ - TypedValue::new(dummy_sourcespan, v1, Type::U32), - TypedValue::new(dummy_sourcespan, v2, Type::U32), - ], - body: vec![], - }; - - // v3 = add.unchecked v1, v2 : u32 - let v3 = crate::Value::from_u32(3); - let inst1 = Inst { - span: dummy_sourcespan, - ty: InstType::BinaryOp { - opcode: Opcode::Add, - overflow: Some(Overflow::Unchecked), - operands: [ - Operand::Value(Span::new(dummy_sourcespan, v1)), - Operand::Value(Span::new(dummy_sourcespan, v2)), - ], - }, - outputs: vec![TypedValue::new(dummy_sourcespan, v3, Type::U32)], - }; - blk0.body.push(inst1); - - // br blk1 - let blk1_id = crate::Block::from_u32(1); - let inst2 = Inst { - span: dummy_sourcespan, - ty: InstType::Br { - opcode: Opcode::Br, - successor: Successor { - span: dummy_sourcespan, - id: blk1_id, - args: vec![], - }, - }, - outputs: vec![], - }; - blk0.body.push(inst2); - - // blk1: - let mut blk1 = Block { - span: dummy_sourcespan, - id: blk1_id, - params: vec![], - body: vec![], - }; - // ret v3 - let inst3 = Inst { - span: dummy_sourcespan, - ty: InstType::Ret { - opcode: Opcode::Ret, - operands: vec![Operand::Value(Span::new(dummy_sourcespan, v3))], - }, - outputs: vec![], - }; - blk1.body.push(inst3); - - foo.blocks.push(blk0); - foo.blocks.push(blk1); - - // cc(kernel) fn tuple::make_pair (sret *mut { u32, u32 }); - let tuple = StructType::new([Type::U32, Type::U32]); - let make_pair = ExternalFunction { - id: FunctionIdent { - module: ident!(tuple), - function: ident!(make_pair), - }, - signature: Signature { - params: vec![AbiParam { - ty: Type::Ptr(Box::new(Type::Struct(tuple))), - purpose: ArgumentPurpose::StructReturn, - extension: Default::default(), - }], - results: vec![], - cc: CallConv::Kernel, - linkage: Linkage::External, - }, - }; - - let expected = Module { - span: dummy_sourcespan, - name: ident!(test), - constants: vec![deadbeef_const], - global_vars: vec![deadbeef], - data_segments: vec![], - functions: vec![foo], - externals: vec![Span::new(dummy_sourcespan, make_pair)], - is_kernel: false, - }; - - ParseTest::new().expect_module_ast_from_file("src/parser/tests/input/test.hir", &expected); -} - -#[test] -fn parser_integration_test_roundtrip() { - let test = ParseTest::new(); - let module = test - .parse_module_from_file("src/parser/tests/input/test.hir") - .expect("parsing failed"); - - let formatted = module.to_string(); - let expected = std::fs::read_to_string("src/parser/tests/input/test.hir").unwrap(); - assert_eq!(formatted, expected); -} - -/// Round-trip an IR module through the textual format and assert that we get back the same module -#[allow(unused)] -fn roundtrip(module: &crate::Module) { - let formatted = module.to_string(); - ParseTest::new().expect_module(&formatted, module); -} diff --git a/hir/src/parser/tests/utils.rs b/hir/src/parser/tests/utils.rs deleted file mode 100644 index 907c1917e..000000000 --- a/hir/src/parser/tests/utils.rs +++ /dev/null @@ -1,168 +0,0 @@ -use std::{path::Path, sync::Arc}; - -use midenc_session::{Options, Verbosity, Warnings}; -use pretty_assertions::assert_eq; - -use crate::{ - diagnostics::{self, Buffer, CaptureEmitter, DefaultEmitter, Emitter, IntoDiagnostic, Report}, - parser::{ast::Module, Parser}, - testing::TestContext, -}; - -struct SplitEmitter { - capture: CaptureEmitter, - default: DefaultEmitter, -} -impl SplitEmitter { - #[inline] - pub fn new() -> Self { - use midenc_session::ColorChoice; - - Self { - capture: Default::default(), - default: DefaultEmitter::new(ColorChoice::Auto), - } - } - - #[allow(unused)] - pub fn captured(&self) -> String { - self.capture.captured() - } -} -impl Emitter for SplitEmitter { - #[inline] - fn buffer(&self) -> Buffer { - self.capture.buffer() - } - - #[inline] - fn print(&self, buffer: Buffer) -> Result<(), Report> { - use std::io::Write; - - let mut copy = self.capture.buffer(); - copy.write_all(buffer.as_slice()).into_diagnostic()?; - self.capture.print(buffer)?; - self.default.print(copy) - } -} - -/// [ParseTest] is a container for the data required to run parser tests. Used to build an AST from -/// the given source string and asserts that executing the test will result in the expected AST. -/// -/// # Errors: -/// - ScanError test: check that the source provided contains valid characters and keywords. -/// - ParseError test: check that the parsed values are valid. -/// * InvalidInt: This error is returned if the parsed number is not a valid u64. -pub struct ParseTest { - context: TestContext, - emitter: Arc, -} - -impl ParseTest { - /// Creates a new test, from the source string. - pub fn new() -> Self { - use midenc_session::{ProjectType, TargetEnv}; - - let emitter = Arc::new(SplitEmitter::new()); - let options = Options::new( - None, - TargetEnv::Base, - ProjectType::Library, - std::env::current_dir().unwrap(), - None, - ) - .with_verbosity(Verbosity::Warning) - .with_warnings(Warnings::Error); - let context = TestContext::default_with_opts_and_emitter(options, Some(emitter.clone())); - Self { context, emitter } - } - - /// This adds a new in-memory file to the [CodeMap] for this test. - /// - /// This is used when we want to write a test with imports, without having to place files on - /// disk - #[allow(unused)] - pub fn add_virtual_file>(&self, name: P, content: String) { - use diagnostics::SourceManager; - - let name = name.as_ref().to_str().unwrap(); - self.context.session.source_manager.load(name, content); - } - - pub fn parse_module_ast_from_file>(&self, path: P) -> Result { - let parser = Parser::new(&self.context.session); - parser.parse_file::(path) - } - - pub fn parse_module_ast(&self, source: &str) -> Result { - let parser = Parser::new(&self.context.session); - parser.parse_str::(source) - } - - pub fn parse_module_from_file>(&self, path: P) -> Result { - let parser = Parser::new(&self.context.session); - parser.parse_file::(path) - } - - pub fn parse_module(&self, source: &str) -> Result { - let parser = Parser::new(&self.context.session); - parser.parse_str::(source) - } - - #[track_caller] - #[allow(unused)] - pub fn expect_module_diagnostic(&self, source: &str, expected: &str) { - if let Err(err) = self.parse_module(source) { - self.context.session.diagnostics.emit(err); - assert!( - self.emitter.captured().contains(expected), - "expected diagnostic output to contain the string: '{}'", - expected - ); - } else { - panic!("expected parsing to fail, but it succeeded"); - } - } - - /// Parses a [Module] from the given source string and asserts that executing the test will - /// result in the expected IR. - #[track_caller] - pub fn expect_module(&self, source: &str, expected: &crate::Module) { - match self.parse_module(source) { - Err(err) => { - self.context.session.diagnostics.emit(err); - panic!("expected parsing to succeed, see diagnostics for details"); - } - Ok(parsed) => { - assert_eq!(&parsed, expected); - } - } - } - - /// Parses a [Module] from the given source string and asserts that executing the test will - /// result in the expected AST. - #[track_caller] - #[allow(unused)] - pub fn expect_module_ast(&self, source: &str, expected: &Module) { - match self.parse_module_ast(source) { - Err(err) => { - self.context.session.diagnostics.emit(err); - panic!("expected parsing to succeed, see diagnostics for details"); - } - Ok(ref ast) => assert_eq!(ast, expected), - } - } - - /// Parses a [Module] from the given source path and asserts that executing the test will result - /// in the expected AST. - #[track_caller] - pub fn expect_module_ast_from_file(&self, path: &str, expected: &Module) { - match self.parse_module_ast_from_file(path) { - Err(err) => { - self.context.session.diagnostics.emit(err); - panic!("expected parsing to succeed, see diagnostics for details"); - } - Ok(ref ast) => assert_eq!(ast, expected), - } - } -} diff --git a/hir/src/pass.rs b/hir/src/pass.rs new file mode 100644 index 000000000..0f2b9f0bb --- /dev/null +++ b/hir/src/pass.rs @@ -0,0 +1,249 @@ +mod analysis; +mod instrumentation; +mod manager; +#[allow(clippy::module_inception)] +mod pass; +pub mod registry; +mod specialization; +pub mod statistics; + +use alloc::string::String; + +pub use self::{ + analysis::{Analysis, AnalysisManager, OperationAnalysis, PreservedAnalyses}, + instrumentation::{PassInstrumentation, PassInstrumentor, PipelineParentInfo}, + manager::{IRPrintingConfig, Nesting, OpPassManager, PassDisplayMode, PassManager}, + pass::{OperationPass, Pass, PassExecutionState, PostPassStatus}, + registry::{PassInfo, PassPipelineInfo}, + specialization::PassTarget, + statistics::{PassStatistic, Statistic, StatisticValue}, +}; +use crate::{EntityRef, Operation, OperationName, OperationRef, SmallVec}; + +/// Handles IR printing, based on the [`IRPrintingConfig`] passed in +/// [Print::new]. Currently, this struct is managed by the [`PassManager`]'s [`PassInstrumentor`], +/// which calls the Print struct via its [`PassInstrumentation`] trait implementation. +/// +/// The configuration passed by [`IRPrintingConfig`] controls *when* the IR gets displayed, rather +/// than *how*. The display format itself depends on the `Display` implementation done by each +/// [`Operation`]. +/// +/// [`Print::selected_passes`] controls which passes are selected to be printable. This means that +/// those selected passes will run all the configured filters; which will determine whether +/// the pass displays the IR or not. The available options are [`SelectedPasses::All`] to enable all +/// the passes and [`SelectedPasses::Just`] to enable a select set of passes. +/// +/// The filters that run on the selected passes are: +/// - [`Print::only_when_modified`] will only print the IR if said pass modified the IR. +/// +/// - [`Print::op_filter`] will only display a specific subset of operations. +#[derive(Default)] +pub struct Print { + selected_passes: Option, + + only_when_modified: bool, + op_filter: Option, + + target: Option, +} + +/// Which passes are enabled for IR printing. +#[derive(Debug)] +enum SelectedPasses { + /// Enable all passes for IR Printing. + All, + /// Just select a subset of passes for IR printing. + Just(SmallVec<[String; 1]>), +} + +#[derive(Default, Debug)] +enum OpFilter { + /// Print all operations + #[default] + All, + /// Print any `Symbol` operation, optionally filtering by symbols whose name contains a given + /// string. + /// NOTE: Currently marked as `dead_code` since it is not configured via the CLI. See + /// [`Print::with_symbol_filter`] for more details. + #[allow(dead_code)] + Symbol(Option<&'static str>), + /// Print only operations of the given type + /// NOTE: Currently marked as `dead_code` since it is not configured via the CLI. See + /// [`Print::with_symbol_filter`] for more details. + #[allow(dead_code)] + Type { + dialect: crate::interner::Symbol, + op: crate::interner::Symbol, + }, +} + +impl Print { + pub fn new(config: &IRPrintingConfig) -> Option { + let print = if config.print_ir_after_all + || !config.print_ir_after_pass.is_empty() + || config.print_ir_after_modified + { + Some(Self::default()) + } else { + None + }; + print.map(|p| p.with_pass_filter(config)).map(|p| p.with_symbol_filter(config)) + } + + pub fn with_type_filter(mut self) -> Self { + let dialect = ::dialect_name(); + let op = ::name(); + self.op_filter = Some(OpFilter::Type { dialect, op }); + self + } + + #[allow(dead_code)] + /// Create a printer that only prints `Symbol` operations containing `name` + fn with_symbol_matching(mut self, name: &'static str) -> Self { + self.op_filter = Some(OpFilter::Symbol(Some(name))); + self + } + + /// Configure which operations are printed. This is set via the different variants present in + /// [`OpFilter`]. + /// + /// NOTE: At the moment, all operations are shown because symbol filtering is not processed by + /// the CLI. If added, this function could be expanded to process it. + fn with_symbol_filter(self, _config: &IRPrintingConfig) -> Self { + self.with_all_symbols() + } + + fn with_all_symbols(mut self) -> Self { + self.op_filter = Some(OpFilter::All); + self + } + + fn with_pass_filter(mut self, config: &IRPrintingConfig) -> Self { + let is_ir_filter_set = if config.print_ir_after_all { + self.selected_passes = Some(SelectedPasses::All); + true + } else if !config.print_ir_after_pass.is_empty() { + self.selected_passes = Some(SelectedPasses::Just(config.print_ir_after_pass.clone())); + true + } else { + false + }; + + if config.print_ir_after_modified { + self.only_when_modified = true; + // NOTE: If the user specified the "print after modification" flag, but didn't specify + // any IR pass filter flag; then we assume that the desired behavior is to set the "all + // pass" filter. + if !is_ir_filter_set { + self.selected_passes = Some(SelectedPasses::All); + } + }; + + self + } + + /// Specify the `log` target to write the IR output to. + /// + /// By default, the target is `printer`, unless the op is a `Symbol`, in which case it is the + /// `Symbol` name. + pub fn with_target(mut self, target: impl AsRef) -> Self { + let target = compact_str::CompactString::new(target.as_ref()); + self.target = Some(target); + self + } + + fn print_ir(&self, op: EntityRef<'_, Operation>) { + match self.op_filter { + Some(OpFilter::All) => { + let target = self.target.as_deref().unwrap_or("printer"); + log::trace!(target: target, "{op}"); + } + Some(OpFilter::Type { + dialect, + op: op_name, + }) => { + let name = op.name(); + if name.dialect() == dialect && name.name() == op_name { + let target = self.target.as_deref().unwrap_or("printer"); + log::trace!(target: target, "{op}"); + } + } + Some(OpFilter::Symbol(None)) => { + if let Some(sym) = op.as_symbol() { + let name = sym.name().as_str(); + let target = self.target.as_deref().unwrap_or(name); + log::trace!(target: target, "{}", sym.as_symbol_operation()); + } + } + Some(OpFilter::Symbol(Some(filter))) => { + if let Some(sym) = op.as_symbol().filter(|sym| sym.name().as_str().contains(filter)) + { + let target = self.target.as_deref().unwrap_or(filter); + log::trace!(target: target, "{}", sym.as_symbol_operation()); + } + } + None => (), + } + } + + fn pass_filter(&self, pass: &dyn OperationPass) -> bool { + match &self.selected_passes { + Some(SelectedPasses::All) => true, + Some(SelectedPasses::Just(passes)) => passes.iter().any(|p| pass.name() == *p), + None => false, + } + } + + fn should_print(&self, pass: &dyn OperationPass, ir_changed: &PostPassStatus) -> bool { + let pass_filter = self.pass_filter(pass); + + // Always print, unless "only_when_modified" has been set and there have not been changes. + let modification_filter = + !matches!(ir_changed, PostPassStatus::Unchanged if self.only_when_modified); + + pass_filter && modification_filter + } +} + +impl PassInstrumentation for Print { + fn run_before_pipeline( + &mut self, + _name: Option<&OperationName>, + _parent_info: &PipelineParentInfo, + op: OperationRef, + ) { + if !self.only_when_modified { + return; + } + + log::trace!("IR before the pass pipeline"); + let op = op.borrow(); + self.print_ir(op); + } + + fn run_before_pass(&mut self, pass: &dyn OperationPass, op: &OperationRef) { + if self.only_when_modified { + return; + } + if self.pass_filter(pass) { + log::trace!("Before the {} pass", pass.name()); + let op = op.borrow(); + self.print_ir(op); + } + } + + fn run_after_pass( + &mut self, + pass: &dyn OperationPass, + op: &OperationRef, + post_execution_state: &PassExecutionState, + ) { + let changed = post_execution_state.post_pass_status(); + + if self.should_print(pass, changed) { + log::trace!("After the {} pass", pass.name()); + let op = op.borrow(); + self.print_ir(op); + } + } +} diff --git a/hir/src/pass/analysis.rs b/hir/src/pass/analysis.rs index ab5a062b2..f70d1b255 100644 --- a/hir/src/pass/analysis.rs +++ b/hir/src/pass/analysis.rs @@ -1,460 +1,706 @@ use alloc::rc::Rc; use core::{ any::{Any, TypeId}, - hash::Hash, + cell::RefCell, }; -use midenc_session::Session; -use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; +use smallvec::SmallVec; -use crate::diagnostics::Report; +use super::{PassInstrumentor, PassTarget}; +use crate::{FxHashMap, Op, Operation, OperationRef, Report}; -type BuildFxHasher = std::hash::BuildHasherDefault; - -/// A convenient type alias for `Result` -pub type AnalysisResult = Result; +/// The [Analysis] trait is used to define an analysis over some operation. +/// +/// Analyses must be default-constructible, and `Sized + 'static` to support downcasting. +/// +/// An analysis, when requested, is first constructed via its `Default` implementation, and then +/// [Analysis::analyze] is called on the target type in order to compute the analysis results. +/// The analysis type also acts as storage for the analysis results. +/// +/// When the IR is changed, analyses are invalidated by default, unless they are specifically +/// preserved via the [PreservedAnalyses] set. When an analysis is being asked if it should be +/// invalidated, via [Analysis::invalidate], it has the opportunity to identify if it actually +/// needs to be invalidated based on what analyses were preserved. If dependent analyses of this +/// analysis haven't been invalidated, then this analysis may be able preserve itself as well, +/// and avoid redundant recomputation. +pub trait Analysis: Default + Any { + /// The specific type on which this analysis is performed. + /// + /// The analysis will only be run when an operation is of this type. + type Target: ?Sized + PassTarget; -#[doc(hidden)] -pub trait PreservableAnalysis: Any { - /// Called to determine if this analysis should be invalidated after a pass is run + /// The [TypeId] associated with the concrete underlying [Analysis] implementation /// - /// By default, all analyses are always invalidated after a pass, unless that pass - /// specifically marks an analysis as preserved. + /// This is automatically implemented for you, but in some cases, such as wrapping an + /// analysis in another type, you may want to implement this so that queries against the + /// type return the expected [TypeId] + #[inline] + fn analysis_id(&self) -> TypeId { + TypeId::of::() + } + + /// Get a `dyn Any` reference to the underlying [Analysis] implementation /// - /// If overridden, implementors must ensure that they use the provided - /// [PreservedAnalyses] set to determine if any dependencies were invalidated, - /// and return `true` if so. - fn is_invalidated(&self, _analyses: &PreservedAnalyses) -> bool { - true + /// This is automatically implemented for you, but in some cases, such as wrapping an + /// analysis in another type, you may want to implement this so that queries against the + /// type return the expected [TypeId] + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self as &dyn Any } -} -impl PreservableAnalysis for A { - fn is_invalidated(&self, analyses: &PreservedAnalyses) -> bool { - ::is_invalidated(self, analyses) + + /// Same as [Analysis::as_any], but used specifically for getting a reference-counted handle, + /// rather than a raw reference. + #[inline(always)] + fn as_any_rc(self: Rc) -> Rc { + self as Rc + } + + /// Returns the display name for this analysis + /// + /// By default this simply returns the name of the concrete implementation type. + fn name(&self) -> &'static str { + core::any::type_name::() } + + /// Analyze `op` using the provided [AnalysisManager]. + fn analyze( + &mut self, + op: &Self::Target, + analysis_manager: AnalysisManager, + ) -> Result<(), Report>; + + /// Query this analysis for invalidation. + /// + /// Given a preserved analysis set, returns true if it should truly be invalidated. This allows + /// for more fine-tuned invalidation in cases where an analysis wasn't explicitly marked + /// preserved, but may be preserved(or invalidated) based upon other properties such as analyses + /// sets. + fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool; } -/// An [Analysis] computes information about some compiler entity, e.g. a module. +/// A type-erased [Analysis]. /// -/// Analyses are cached, and associated with a unique key derived from the entity -/// to which they were applied. These cached analyses can then be queried via the -/// [AnalysisManager] by requesting a specific concrete [Analysis] type using that -/// key. +/// This is automatically derived for all [Analysis] implementations, and is the means by which +/// one can abstract over sets of analyses using dynamic dispatch. /// -/// For example, a module is typically associated with a unique identifier. Thus -/// to obtain the analysis for a specific module, you would request the specific -/// analysis using the module id, see [AnalysisManager::get]. -pub trait Analysis: Sized + Any { - /// The entity to which this analysis applies - type Entity: AnalysisKey; - - /// Analyze `entity`, using the provided [AnalysisManager] to query other - /// analyses on which this one depends; and the provided [Session] to - /// configure this analysis based on the current compilation session. - fn analyze( - entity: &Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> AnalysisResult; +/// This essentially just delegates to the underlying [Analysis] implementation, but it also handles +/// converting a raw [OperationRef] to the appropriate target type expected by the underlying +/// [Analysis]. +pub trait OperationAnalysis { + /// The unique type id of this analysis + fn analysis_id(&self) -> TypeId; + + /// Used for dynamic casting to the underlying [Analysis] type + fn as_any(&self) -> &dyn Any; - /// Called to determine if this analysis should be invalidated after a pass is run + /// Used for dynamic casting to the underlying [Analysis] type + fn as_any_rc(self: Rc) -> Rc; + + /// The name of this analysis + fn name(&self) -> &'static str; + + /// Runs this analysis over `op`. + /// + /// NOTE: This is only ever called once per instantiation of the analysis, but in theory can + /// support multiple calls to re-analyze `op`. Each call should reset any internal state to + /// ensure that if an analysis is reused in this way, that each analysis gets a clean slate. + fn analyze(&mut self, op: &OperationRef, am: AnalysisManager) -> Result<(), Report>; + + /// Query this analysis for invalidation. /// - /// By default, all analyses are always invalidated after a pass, unless that pass - /// specifically marks an analysis as preserved. + /// Given a preserved analysis set, returns true if it should truly be invalidated. This allows + /// for more fine-tuned invalidation in cases where an analysis wasn't explicitly marked + /// preserved, but may be preserved(or invalidated) based upon other properties such as analyses + /// sets. /// - /// If overridden, implementors must ensure that they use the provided - /// [PreservedAnalyses] set to determine if any dependencies were invalidated, - /// and return `true` if so. - fn is_invalidated(&self, _analyses: &PreservedAnalyses) -> bool { - true - } + /// Invalidated analyses must be removed from `preserved_analyses`. + fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool; } -/// The [AnalysisKey] trait is implemented for compiler entities that are targeted -/// for one or more [Analysis], and have a stable, unique identifier which can be -/// used to cache the results of each analysis computed for that entity. -/// -/// You must ensure that the key uniquely identifies the entity to which it applies, -/// or incorrect analysis results will be returned from the [AnalysisManager]. Note, -/// however, that it is not necessary to ensure that the key reflect changes to the -/// underlying entity (see [AnalysisManager::invalidate] for how invalidation is done). -pub trait AnalysisKey: 'static { - /// The type of the unique identifier associated with `Self` - type Key: Hash + PartialEq + Eq; - - /// Get the key to associate with the current entity - fn key(&self) -> Self::Key; +impl dyn OperationAnalysis { + /// Cast an reference-counted handle to this analysis to its concrete implementation type. + /// + /// Returns `None` if the underlying analysis is not of type `T` + #[inline] + pub fn downcast(self: Rc) -> Option> { + self.as_any_rc().downcast::().ok() + } } -/// This type is used as a cache key for analyses cached by the [AnalysisManager]. -/// -/// It pairs the [TypeId] of the [Analysis], with the hash of the entity type and -/// the unique key associated with the specific instance of the entity type to which -/// the analysis applies. This ensures that for any given analysis/entity combination, -/// no two cache entries will have the same key unless the analysis key for the entity -/// matches. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -struct CachedAnalysisKey { - ty: TypeId, - key: u64, -} -impl CachedAnalysisKey { - /// Get a new cache key from the given type information and analysis key - fn new(key: &<::Entity as AnalysisKey>::Key) -> Self - where - A: Analysis, - { - let ty = TypeId::of::(); - let key = Self::entity_key::<::Entity>(key); - Self { ty, key } +impl OperationAnalysis for A +where + A: Analysis, +{ + #[inline] + fn analysis_id(&self) -> TypeId { + ::analysis_id(self) } - fn entity_key(key: &::Key) -> u64 - where - T: AnalysisKey, - { - use core::hash::Hasher; + #[inline] + fn as_any(&self) -> &dyn Any { + ::as_any(self) + } - let mut hasher = FxHasher::default(); - let entity_ty = TypeId::of::(); - entity_ty.hash(&mut hasher); - key.hash(&mut hasher); - hasher.finish() + #[inline] + fn as_any_rc(self: Rc) -> Rc { + ::as_any_rc(self) + } + + #[inline] + fn name(&self) -> &'static str { + ::name(self) + } + + #[inline] + fn analyze(&mut self, op: &OperationRef, am: AnalysisManager) -> Result<(), Report> { + let op = <::Target as PassTarget>::into_target(op); + ::analyze(self, &op, am) + } + + #[inline] + fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool { + ::invalidate(self, preserved_analyses) } } -/// [PreservedAnalyses] represents the set of analyses which will be preserved for the next pass. -/// -/// You may mark an analysis as preserved using [AnalysisManager::mark_preserved]. +/// Represents a set of analyses that are known to be preserved after a rewrite has been applied. #[derive(Default)] pub struct PreservedAnalyses { - current_entity_key: u64, - preserved: FxHashMap>, + /// The set of preserved analysis type ids + preserved: SmallVec<[TypeId; 8]>, } impl PreservedAnalyses { - fn new( - current_entity_key: u64, - mut cached: FxHashMap>, - preserve: FxHashSet, - ) -> Self { - // Since we know which analyses are definitely preserved, - // build the initial preserved analyses set from those. - let mut preserved = Self::with_capacity(current_entity_key, preserve.len()); - for key in preserve.into_iter() { - if let Some(analysis) = cached.remove(&key) { - preserved.insert(key, analysis); - } + /// Mark all analyses as preserved. + /// + /// This is generally only useful when the IR is known not to have changed. + pub fn preserve_all(&mut self) { + self.insert(AllAnalyses::TYPE_ID); + } + + /// Mark the specified [Analysis] type as preserved. + pub fn preserve(&mut self) { + self.insert(TypeId::of::()); + } + + /// Mark a type as preserved using its raw [TypeId]. + /// + /// Typically it is best to use [Self::preserve] instead, but this can be useful in cases + /// where you can't express the type in Rust directly. + pub fn preserve_raw(&mut self, id: TypeId) { + self.insert(id); + } + + /// Returns true if the specified type is preserved. + /// + /// This will return true if all analyses are marked preserved, even if the specified type was + /// not explicitly preserved. + pub fn is_preserved(&self) -> bool { + self.preserved.contains(&TypeId::of::()) || self.is_all() + } + + /// Returns true if the specified [TypeId] is marked preserved. + /// + /// This will return true if all analyses are marked preserved, even if the specified type was + /// not explicitly preserved. + pub fn is_preserved_raw(&self, ty: &TypeId) -> bool { + self.preserved.contains(ty) || self.is_all() + } + + /// Mark a previously preserved type as invalidated. + /// + /// This will also remove the "all preserved" flag, if it had been set. + pub fn unpreserve(&mut self) { + // We must also remove the `all` marker, as we have invalidated one of the analyses + self.remove(&AllAnalyses::TYPE_ID); + self.remove(&TypeId::of::()); + } + + /// Mark a previously preserved [TypeId] as invalidated. + /// + /// This will also remove the "all preserved" flag, if it had been set. + pub fn unpreserve_raw(&mut self, ty: &TypeId) { + // We must also remove the `all` marker, as we have invalidated one of the analyses + self.remove(&AllAnalyses::TYPE_ID); + self.remove(ty) + } + + /// Returns true if all analyses are preserved + pub fn is_all(&self) -> bool { + self.preserved.contains(&AllAnalyses::TYPE_ID) + } + + /// Returns true if no analyses are being preserved + pub fn is_none(&self) -> bool { + self.preserved.is_empty() + } + + fn insert(&mut self, id: TypeId) { + match self.preserved.binary_search_by_key(&id, |probe| *probe) { + Ok(index) => self.preserved.insert(index, id), + Err(index) => self.preserved.insert(index, id), } + } - // Preserve all analyses for other entities - let mut worklist = vec![]; - for (key, analysis) in cached.into_iter() { - if key.key != current_entity_key { - preserved.insert(key, analysis); - continue; - } - worklist.push((key, analysis)); + fn remove(&mut self, id: &TypeId) { + if let Ok(index) = self.preserved.binary_search_by_key(&id, |probe| probe) { + self.preserved.remove(index); } + } +} - // Ask all remaining analyses if they should indeed be invalidated. - // - // We iterate to a fixpoint here to ensure that any new preserved analyses - // are taken into account by the remaining analyses pending invalidation - // when their `is_invalidated` method is called. If those analyses depend - // on a newly preserved analysis, they may be able to avoid being invalidated - // if they have no other invalidated dependencies. - let mut q = vec![]; - let mut changed = false; - loop { - while let Some((key, analysis)) = worklist.pop() { - if analysis.is_invalidated(&preserved) { - q.push((key, analysis)); - continue; - } else { - changed = true; - preserved.insert(key, analysis); - } - } - if !changed { - break; - } - changed = false; - core::mem::swap(&mut worklist, &mut q); +/// A marker type that is used to represent all possible [Analysis] types +pub struct AllAnalyses; +impl AllAnalyses { + const TYPE_ID: TypeId = TypeId::of::(); +} + +/// This type wraps all analyses stored in an [AnalysisMap], and handles some of the boilerplate +/// details around invalidation by intercepting calls to [Analysis::invalidate] and wrapping it +/// with extra logic. Notably, ensuring that invalidated analyses are removed from the +/// [PreservedAnalyses] set is handled by this wrapper. +/// +/// It is a transparent wrapper around `A`, and otherwise acts as a simple proxy to `A`'s +/// implementation of the [Analysis] trait. +#[repr(transparent)] +struct AnalysisWrapper { + analysis: A, +} +impl AnalysisWrapper { + fn new(op: &::Target, am: AnalysisManager) -> Result { + let mut analysis = A::default(); + analysis.analyze(op, am)?; + + Ok(Self { analysis }) + } +} +impl Default for AnalysisWrapper { + fn default() -> Self { + Self { + analysis: Default::default(), } + } +} +impl Analysis for AnalysisWrapper { + type Target = ::Target; - preserved + #[inline] + fn analysis_id(&self) -> TypeId { + self.analysis.analysis_id() } - /// Returns true if the analysis associated with the given type and key is preserved - pub fn is_preserved(&self) -> bool - where - A: Analysis, - { - let ty = TypeId::of::(); - let key = CachedAnalysisKey { - ty, - key: self.current_entity_key, - }; - self.preserved.contains_key(&key) + #[inline] + fn as_any(&self) -> &dyn Any { + self.analysis.as_any() } #[inline] - fn insert(&mut self, key: CachedAnalysisKey, analysis: Rc) { - self.preserved.insert(key, analysis); + fn as_any_rc(self: Rc) -> Rc { + // SAFETY: This transmute is safe because AnalysisWrapper is a transparent wrapper + // around A, so a pointer to the former is a pointer to the latter + let ptr = Rc::into_raw(self); + unsafe { Rc::::from_raw(ptr.cast()) as Rc } } - fn with_capacity(current_entity_key: u64, cap: usize) -> Self { - use std::collections::HashMap; + #[inline] + fn name(&self) -> &'static str { + self.analysis.name() + } - Self { - current_entity_key, - preserved: HashMap::with_capacity_and_hasher(cap, BuildFxHasher::default()), + #[inline] + fn analyze(&mut self, op: &Self::Target, am: AnalysisManager) -> Result<(), Report> { + self.analysis.analyze(op, am) + } + + fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool { + let invalidated = self.analysis.invalidate(preserved_analyses); + if invalidated { + preserved_analyses.unpreserve::(); } + invalidated } } -/// The [AnalysisManager] is used to query and compute analyses required during compilation. +/// An [AnalysisManager] is the primary entrypoint for performing analysis on a specific operation +/// instance that it is constructed for. /// -/// Each thread gets its own analysis manager, and may query any analysis, as long as the -/// caller has the key used for caching that analysis (e.g. module identifier). +/// It is used to manage and cache analyses for the operation, as well as those of child operations, +/// via nested [AnalysisManager] instances. /// -/// To compute an analysis, one must have a reference to the entity on which the analysis -/// is applied, and request that the analysis be computed. -/// -/// Analyses are cached, and assumed valid until explicitly invalidated. An analysis should -/// be invalidated any time the underlying entity changes, unless the analysis is known to -/// be preserved even with those changes. -#[derive(Default)] +/// This type is a thin wrapper around a pointer, and is meant to be passed by value. It can be +/// cheaply cloned. +#[derive(Clone)] +#[repr(transparent)] pub struct AnalysisManager { - /// We store the analysis results as `Rc` so that we can freely hand out references - /// to the analysis results without having to concern ourselves with too much lifetime - /// management. - /// - /// Since an [AnalysisManager] is scoped to a single thread, the reference counting - /// overhead is essentially irrelevant. - cached: FxHashMap>, - /// The set of analyses to preserve after the current pass is run - preserve: FxHashSet, - /// The set of entity keys that have had `preserve_none` set - preserve_none: FxHashSet, - /// The set of entity keys that have had `preserve_all` set - preserve_all: FxHashSet, + analyses: Rc, } impl AnalysisManager { - /// Get a new, empty [AnalysisManager]. - pub fn new() -> Self { - Self::default() + /// Create a new top-level [AnalysisManager] for `op` + pub fn new(op: OperationRef, instrumentor: Option>) -> Self { + Self { + analyses: Rc::new(NestedAnalysisMap::new(op, instrumentor)), + } } - /// Check if the given analysis has been computed and is in the cache - pub fn is_available(&self, key: &<::Entity as AnalysisKey>::Key) -> bool + /// Query for a cached analysis on the given parent operation. The analysis may not exist and if + /// it does it may be out-of-date. + pub fn get_cached_parent_analysis(&self, parent: OperationRef) -> Option> where A: Analysis, { - let key = CachedAnalysisKey::new::(key); - self.cached.contains_key(&key) + let mut current_parent = self.analyses.parent(); + while let Some(parent_am) = current_parent.take() { + if parent_am.get_operation() == parent { + return parent_am.analyses().get_cached::(); + } + current_parent = parent_am.parent(); + } + None } - /// Get a reference to the analysis of the requested type, for the given entity, if available - pub fn get(&self, key: &<::Entity as AnalysisKey>::Key) -> Option> + /// Query for the given analysis for the current operation. + pub fn get_analysis(&self) -> Result, Report> where - A: Analysis, + A: Analysis, { - let key = CachedAnalysisKey::new::(key); - self.cached.get(&key).cloned().map(preservable_analysis_to_concrete) + self.get_analysis_for::() } - /// Get a reference to the analysis of the requested type, for the given entity, or panics with - /// `msg` - pub fn expect(&self, key: &<::Entity as AnalysisKey>::Key, msg: &str) -> Rc + /// Query for the given analysis for the current operation of a specific derived operation type. + /// + /// NOTE: This will panic if the current operation is not of type `O`. + pub fn get_analysis_for(&self) -> Result, Report> where - A: Analysis, + A: Analysis, + O: 'static, { - let key = CachedAnalysisKey::new::(key); - self.cached.get(&key).cloned().map(preservable_analysis_to_concrete).expect(msg) + let op = { + let analysis_map = self.analyses.analyses.borrow(); + let cached = analysis_map.get_cached_for::(); + if let Some(cached) = cached { + return Ok(cached); + } + analysis_map.ir + }; + + // We have to construct the analysis without borrowing the AnalysisMap, otherwise we might + // try to re-borrow the map to construct a dependent analysis while holding a mutable ref. + let pi = self.pass_instrumentor(); + let am = self.clone(); + let ir = ::into_target(&op); + let analysis = AnalysisMap::compute_analysis_for::(pi, &*ir, &op, am)?; + + // Once the analysis is constructed, we can add it to the map + self.analyses + .analyses + .borrow_mut() + .analyses + .insert(TypeId::of::(), Rc::clone(&analysis) as Rc); + + Ok(analysis) } - /// Get a reference to the analysis of the requested type, or the default value, for the given - /// entity, if available - /// - /// If unavailable, and the default value is returned, that value is not cached. - pub fn get_or_default(&self, key: &<::Entity as AnalysisKey>::Key) -> Rc + /// Query for a cached entry of the given analysis on the current operation. + pub fn get_cached_analysis(&self) -> Option> where - A: Analysis + Default, + A: Analysis, { - let key = CachedAnalysisKey::new::(key); - self.cached - .get(&key) - .cloned() - .map(preservable_analysis_to_concrete) - .unwrap_or_else(|| Rc::new(A::default())) + self.analyses.analyses().get_cached::() } - /// Get a reference to the analysis of the requested type, computing it if necessary - /// - /// If computing the analysis fails, `Err` is returned. - pub fn get_or_compute( - &mut self, - entity: &::Entity, - session: &Session, - ) -> AnalysisResult> + /// Query for an analysis of a child operation, constructing it if necessary. + pub fn get_child_analysis(&self, op: OperationRef) -> Result, Report> where - A: Analysis, + A: Analysis, { - let key = CachedAnalysisKey::new::(&entity.key()); - if let Some(cached) = self.cached.get(&key).cloned() { - return Ok(preservable_analysis_to_concrete(cached)); - } - let analysis = Rc::new(A::analyze(entity, self, session)?); - let any = Rc::clone(&analysis); - self.cached.insert(key, any); - Ok(analysis) + self.clone().nest(op).get_analysis::() } - /// If an analysis of the requested type has been computed, take ownership of it, - /// and return the owned object to the caller. - /// - /// If there are outstanding references to the cached analysis data, then the data - /// will be cloned so that the caller gets an owning reference. + /// Query for an analysis of a child operation of a specific derived operation type, + /// constructing it if necessary. /// - /// If the analysis has not been computed, returns `None` - pub fn take(&mut self, key: &<::Entity as AnalysisKey>::Key) -> Option + /// NOTE: This will panic if `op` is not of type `O`. + pub fn get_child_analysis_for(&self, op: &O) -> Result, Report> where - A: Analysis + Clone, + A: Analysis, + O: Op, { - let key = CachedAnalysisKey::new::(key); - let cached = preservable_analysis_to_concrete(self.cached.remove(&key)?); - Some(match Rc::try_unwrap(cached) { - Ok(analysis) => analysis, - Err(cached) => (*cached).clone(), - }) + self.clone().nest(op.as_operation_ref()).get_analysis_for::() } - /// Insert an analysis into the manager with the given key - pub fn insert(&mut self, key: <::Entity as AnalysisKey>::Key, analysis: A) + /// Query for a cached analysis of a child operation, or return `None`. + pub fn get_cached_child_analysis(&self, child: &OperationRef) -> Option> where A: Analysis, { - let key = CachedAnalysisKey::new::(&key); - self.cached.insert(key, Rc::new(analysis)); + assert!(child.borrow().parent_op().unwrap() == self.analyses.get_operation()); + let child_analyses = self.analyses.child_analyses.borrow(); + let child_analyses = child_analyses.get(child)?; + let child_analyses = child_analyses.analyses.borrow(); + child_analyses.get_cached::() } - /// Mark all analyses as invalidated, unless otherwise preserved, forcing recomputation - /// of those analyses the next time they are requested. - /// - /// This clears any preservation markers that were set prior to calling this function, - /// e.g. with `mark_preserved`. When this function returns, all analyses are assumed - /// to be invalidated the next time this function is called, unless otherwise indicated. - pub fn invalidate(&mut self, key: &::Key) - where - T: AnalysisKey, - { - use std::collections::HashMap; - - let current_entity_key = CachedAnalysisKey::entity_key::(key); + /// Get an analysis manager for the given operation, which must be a proper descendant of the + /// current operation represented by this analysis manager. + pub fn nest(&self, op: OperationRef) -> AnalysisManager { + let current_op = self.analyses.get_operation(); + assert!( + current_op.borrow().is_proper_ancestor_of(&op.borrow()), + "expected valid descendant op" + ); + + // Check for the base case where the provided operation is immediately nested + if current_op == op.borrow().parent_op().expect("expected `op` to have a parent") { + return self.nest_immediate(op); + } - if self.preserve_none.remove(¤t_entity_key) { - self.cached.retain(|k, _| k.key != current_entity_key); - return; + // Otherwise, we need to collect all ancestors up to the current operation + let mut ancestors = SmallVec::<[OperationRef; 4]>::default(); + let mut next_op = op; + while next_op != current_op { + ancestors.push(next_op); + next_op = next_op.borrow().parent_op().unwrap(); } - if self.preserve_all.remove(¤t_entity_key) { - return; + let mut manager = self.clone(); + while let Some(op) = ancestors.pop() { + manager = manager.nest_immediate(op); } + manager + } - let mut to_preserve = vec![]; - for key in self.preserve.iter() { - if key.key == current_entity_key { - to_preserve.push(*key); + fn nest_immediate(&self, op: OperationRef) -> AnalysisManager { + use hashbrown::hash_map::Entry; + + assert!( + Some(self.analyses.get_operation()) == op.borrow().parent_op(), + "expected immediate child operation" + ); + let parent = self.analyses.clone(); + let mut child_analyses = self.analyses.child_analyses.borrow_mut(); + match child_analyses.entry(op) { + Entry::Vacant(entry) => { + let analyses = entry.insert(Rc::new(parent.nest(op))); + AnalysisManager { + analyses: Rc::clone(analyses), + } } + Entry::Occupied(entry) => AnalysisManager { + analyses: Rc::clone(entry.get()), + }, } + } - let mut to_invalidate = vec![]; - for key in self.cached.keys() { - if key.key == current_entity_key { - to_invalidate.push(*key); - } + /// Invalidate any non preserved analyses. + #[inline] + pub fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) { + Rc::clone(&self.analyses).invalidate(preserved_analyses) + } + + /// Clear any held analyses. + #[inline] + pub fn clear(&mut self) { + self.analyses.clear(); + } + + /// Clear any held analyses when the returned guard is dropped. + #[inline] + pub fn defer_clear(&self) -> ResetAnalysesOnDrop { + ResetAnalysesOnDrop { + analyses: self.analyses.clone(), + } + } + + /// Returns a [PassInstrumentor] for the current operation, if one was installed. + #[inline] + pub fn pass_instrumentor(&self) -> Option> { + self.analyses.pass_instrumentor() + } +} + +#[must_use] +#[doc(hidden)] +pub struct ResetAnalysesOnDrop { + analyses: Rc, +} +impl Drop for ResetAnalysesOnDrop { + fn drop(&mut self) { + self.analyses.clear() + } +} + +/// An analysis map that contains a map for the current operation, and a set of maps for any child +/// operations. +struct NestedAnalysisMap { + parent: Option>, + instrumentor: Option>, + analyses: RefCell, + child_analyses: RefCell>>, +} +impl NestedAnalysisMap { + /// Create a new top-level [NestedAnalysisMap] for `op`, with the given optional pass + /// instrumentor. + pub fn new(op: OperationRef, instrumentor: Option>) -> Self { + Self { + parent: None, + instrumentor, + analyses: RefCell::new(AnalysisMap::new(op)), + child_analyses: Default::default(), } + } - let mut preserve = FxHashSet::default(); - for key in to_preserve.into_iter() { - preserve.insert(self.preserve.take(&key).unwrap()); + /// Create a new [NestedAnalysisMap] for `op` nested under `self`. + pub fn nest(self: Rc, op: OperationRef) -> Self { + let instrumentor = self.instrumentor.clone(); + Self { + parent: Some(self), + instrumentor, + analyses: RefCell::new(AnalysisMap::new(op)), + child_analyses: Default::default(), } + } + + /// Get the parent [NestedAnalysisMap], or `None` if this is a top-level map. + pub fn parent(&self) -> Option> { + self.parent.clone() + } - let mut cached = - HashMap::with_capacity_and_hasher(to_invalidate.len(), BuildFxHasher::default()); - for key in to_invalidate.into_iter() { - let (key, value) = self.cached.remove_entry(&key).unwrap(); - cached.insert(key, value); + /// Return a [PassInstrumentor] for the current operation, if one was installed. + pub fn pass_instrumentor(&self) -> Option> { + self.instrumentor.clone() + } + + /// Get the operation for this analysis map. + #[inline] + pub fn get_operation(&self) -> OperationRef { + self.analyses.borrow().get_operation() + } + + fn analyses(&self) -> core::cell::Ref<'_, AnalysisMap> { + self.analyses.borrow() + } + + /// Invalidate any non preserved analyses. + pub fn invalidate(self: Rc, preserved_analyses: &mut PreservedAnalyses) { + // If all analyses were preserved, then there is nothing to do + if preserved_analyses.is_all() { + return; + } + + // Invalidate the analyses for the current operation directly + self.analyses.borrow_mut().invalidate(preserved_analyses); + + // If no analyses were preserved, then just simply clear out the child analysis results + if preserved_analyses.is_none() { + self.child_analyses.borrow_mut().clear(); } - let preserved = PreservedAnalyses::new(current_entity_key, cached, preserve); - self.cached.extend(preserved.preserved); + // Otherwise, invalidate each child analysis map + let mut to_invalidate = SmallVec::<[Rc; 8]>::from_iter([self]); + while let Some(map) = to_invalidate.pop() { + map.child_analyses.borrow_mut().retain(|_op, nested_analysis_map| { + Rc::clone(nested_analysis_map).invalidate(preserved_analyses); + if nested_analysis_map.child_analyses.borrow().is_empty() { + false + } else { + to_invalidate.push(Rc::clone(nested_analysis_map)); + true + } + }); + } } - /// Mark the given analysis as no longer valid (due to changes to the analyzed entity) - /// - /// You should invalidate analyses any time you modify the IR for that entity, unless - /// you can guarantee that the specific analysis is preserved. - pub fn mark_invalid(&mut self, key: &<::Entity as AnalysisKey>::Key) + pub fn clear(&self) { + self.child_analyses.borrow_mut().clear(); + self.analyses.borrow_mut().clear(); + } +} + +/// This class represents a cache of analyses for a single operation. +/// +/// All computation, caching, and invalidation of analyses takes place here. +struct AnalysisMap { + analyses: FxHashMap>, + ir: OperationRef, +} +impl AnalysisMap { + pub fn new(ir: OperationRef) -> Self { + Self { + analyses: Default::default(), + ir, + } + } + + /// Get a cached analysis instance if one exists, otherwise return `None`. + pub fn get_cached(&self) -> Option> where A: Analysis, { - let key = CachedAnalysisKey::new::(key); - self.preserve.remove(&key); - self.cached.remove(&key); + self.analyses.get(&TypeId::of::()).cloned().and_then(|a| a.downcast::()) } - /// When called, the current pass is signalling that all analyses should be invalidated - /// after it completes, regardless of any other configuration. - pub fn mark_none_preserved(&mut self, key: &::Key) + /// Get a cached analysis instance if one exists, otherwise return `None`. + pub fn get_cached_for(&self) -> Option> where - T: AnalysisKey, + A: Analysis, + O: 'static, { - let preserve_entity_key = CachedAnalysisKey::entity_key::(key); - self.preserve_none.insert(preserve_entity_key); - self.preserve_all.remove(&preserve_entity_key); + self.analyses.get(&TypeId::of::()).cloned().and_then(|a| a.downcast::()) } - /// When called, the current pass is signalling that all analyses will still be valid - /// after it completes, i.e. it makes no modifications that would invalidate an analysis. - /// - /// Care must be taken when doing this, to ensure that the pass actually does not do - /// anything that would invalidate any analysis results, or miscompiles are likely to - /// occur. - pub fn mark_all_preserved(&mut self, key: &::Key) + fn compute_analysis_for( + pi: Option>, + ir: &O, + op: &OperationRef, + am: AnalysisManager, + ) -> Result, Report> where - T: AnalysisKey, + A: Analysis, { - let preserve_entity_key = CachedAnalysisKey::entity_key::(key); - self.preserve_all.insert(preserve_entity_key); - self.preserve_none.remove(&preserve_entity_key); + let id = TypeId::of::(); + + // We don't have a cached analysis for the operation, compute it directly and + // add it to the cache. + if let Some(pi) = pi.as_deref() { + pi.run_before_analysis(core::any::type_name::(), &id, op); + } + + let analysis = Self::construct_analysis::(am, ir)?; + + if let Some(pi) = pi.as_deref() { + pi.run_after_analysis(core::any::type_name::(), &id, op); + } + + Ok(analysis.downcast::().unwrap()) } - /// When called, the current pass is signalling that the given analysis identified by `key`, - /// will still be valid after it completes. - /// - /// This should only be called when the caller can guarantee that the analysis is truly - /// preserved by the pass, otherwise miscompiles are likely to occur. - pub fn mark_preserved(&mut self, key: &<::Entity as AnalysisKey>::Key) + fn construct_analysis( + am: AnalysisManager, + op: &O, + ) -> Result, Report> where - A: Analysis, + A: Analysis, { - // If we're preserving everything, or preserving nothing, this is a no-op - let key = CachedAnalysisKey::new::(key); - if self.preserve_all.contains(&key.key) || self.preserve_none.contains(&key.key) { - return; - } + AnalysisWrapper::::new(op, am) + .map(|analysis| Rc::new(analysis) as Rc) + } - self.preserve.insert(key); + /// Returns the operation that this analysis map represents. + pub fn get_operation(&self) -> OperationRef { + self.ir } -} -fn preservable_analysis_to_concrete(pa: Rc) -> Rc -where - T: AnalysisKey, - A: Analysis, -{ - let any: Rc = pa; - any.downcast::().expect("invalid cached analysis key") + /// Clear any held analyses. + pub fn clear(&mut self) { + self.analyses.clear(); + } + + /// Invalidate any cached analyses based upon the given set of preserved analyses. + pub fn invalidate(&mut self, preserved_analyses: &mut PreservedAnalyses) { + // Remove any analyses that were invalidated. + // + // Using `retain`, we preserve the original insertion order, and dependencies always go + // before users, so we need only a single pass through. + self.analyses.retain(|_, a| !a.invalidate(preserved_analyses)); + } } diff --git a/hir/src/pass/conversion.rs b/hir/src/pass/conversion.rs deleted file mode 100644 index b3f43ec9d..000000000 --- a/hir/src/pass/conversion.rs +++ /dev/null @@ -1,114 +0,0 @@ -use midenc_session::Session; - -use super::{AnalysisManager, Chain, PassInfo}; -use crate::diagnostics::Report; - -/// A convenient type alias for `Result` -pub type ConversionResult = Result; - -/// This is a marker trait for [ConversionPass] impls which also implement [PassInfo] -/// -/// It is automatically implemented for you. -pub trait ConversionPassInfo: PassInfo + ConversionPass {} -impl

ConversionPassInfo for P where P: PassInfo + ConversionPass {} - -/// A [ConversionPass] is a pass which applies a change in representation to some compiler entity. -/// -/// Specifically, this is used to convert between intermediate representations/dialects in the -/// compiler. -/// -/// For example, a conversion pass would be used to lower a `midenc_hir::parser::ast::Module` -/// to a `midenc_hir::Module`. Each conversion between dialects like this can be thought of -/// as delineating compilation phases (e.g. parsing, semantic analysis, elaboration, optimization, -/// etc.). -pub trait ConversionPass { - type From; - type To; - - /// Apply this conversion to `entity` - fn convert( - &mut self, - entity: Self::From, - analyses: &mut AnalysisManager, - session: &Session, - ) -> ConversionResult; - - /// Chains two conversions together to form a new, fused conversion - fn chain

(self, next: P) -> Chain - where - Self: Sized, - P: ConversionPass, - { - Chain::new(self, next) - } -} -impl ConversionPass for Box

-where - P: ?Sized + ConversionPass, -{ - type From = T; - type To = U; - - fn convert( - &mut self, - entity: Self::From, - analyses: &mut AnalysisManager, - session: &Session, - ) -> ConversionResult { - (**self).convert(entity, analyses, session) - } -} - -type ConversionPassCtor = fn() -> Box>; - -#[doc(hidden)] -pub struct ConversionPassRegistration { - pub name: &'static str, - pub summary: &'static str, - pub description: &'static str, - ctor: ConversionPassCtor, -} -impl ConversionPassRegistration { - pub const fn new

() -> Self - where - P: ConversionPass + PassInfo + Default + 'static, - { - Self { - name:

::FLAG, - summary:

::SUMMARY, - description:

::DESCRIPTION, - ctor: dyn_conversion_pass_ctor::, - } - } - - /// Get the name of the registered pass - #[inline] - pub const fn name(&self) -> &'static str { - self.name - } - - /// Get a summary of the registered pass - #[inline] - pub const fn summary(&self) -> &'static str { - self.summary - } - - /// Get a rich description of the registered pass - #[inline] - pub const fn description(&self) -> &'static str { - self.description - } - - /// Get an instance of the registered pass - #[inline] - pub fn get(&self) -> Box> { - (self.ctor)() - } -} - -fn dyn_conversion_pass_ctor() -> Box> -where - P: Default + ConversionPass + 'static, -{ - Box::

::default() -} diff --git a/hir/src/pass/instrumentation.rs b/hir/src/pass/instrumentation.rs new file mode 100644 index 000000000..ccddaac05 --- /dev/null +++ b/hir/src/pass/instrumentation.rs @@ -0,0 +1,149 @@ +use alloc::boxed::Box; +use core::{any::TypeId, cell::RefCell}; + +use compact_str::CompactString; +use smallvec::SmallVec; + +use super::OperationPass; +use crate::{pass::PassExecutionState, OperationName, OperationRef}; + +#[allow(unused_variables)] +pub trait PassInstrumentation { + fn run_before_pipeline( + &mut self, + name: Option<&OperationName>, + parent_info: &PipelineParentInfo, + op: OperationRef, + ) { + } + fn run_after_pipeline( + &mut self, + name: Option<&OperationName>, + parent_info: &PipelineParentInfo, + ) { + } + fn run_before_pass(&mut self, pass: &dyn OperationPass, op: &OperationRef) {} + fn run_after_pass( + &mut self, + pass: &dyn OperationPass, + op: &OperationRef, + post_execution_state: &PassExecutionState, + ) { + } + fn run_after_pass_failed(&mut self, pass: &dyn OperationPass, op: &OperationRef) {} + fn run_before_analysis(&mut self, name: &str, id: &TypeId, op: &OperationRef) {} + fn run_after_analysis(&mut self, name: &str, id: &TypeId, op: &OperationRef) {} +} + +pub struct PipelineParentInfo { + /// The pass that spawned this pipeline, if any + pub pass: Option, +} + +impl PassInstrumentation for Box

{ + fn run_before_pipeline( + &mut self, + name: Option<&OperationName>, + parent_info: &PipelineParentInfo, + op: OperationRef, + ) { + (**self).run_before_pipeline(name, parent_info, op); + } + + fn run_after_pipeline( + &mut self, + name: Option<&OperationName>, + parent_info: &PipelineParentInfo, + ) { + (**self).run_after_pipeline(name, parent_info); + } + + fn run_before_pass(&mut self, pass: &dyn OperationPass, op: &OperationRef) { + (**self).run_before_pass(pass, op); + } + + fn run_after_pass( + &mut self, + pass: &dyn OperationPass, + op: &OperationRef, + post_execution_state: &PassExecutionState, + ) { + (**self).run_after_pass(pass, op, post_execution_state); + } + + fn run_after_pass_failed(&mut self, pass: &dyn OperationPass, op: &OperationRef) { + (**self).run_after_pass_failed(pass, op); + } + + fn run_before_analysis(&mut self, name: &str, id: &TypeId, op: &OperationRef) { + (**self).run_before_analysis(name, id, op); + } + + fn run_after_analysis(&mut self, name: &str, id: &TypeId, op: &OperationRef) { + (**self).run_after_analysis(name, id, op); + } +} + +#[derive(Default)] +pub struct PassInstrumentor { + instrumentations: RefCell; 1]>>, +} + +impl PassInstrumentor { + pub fn run_before_pipeline( + &self, + name: Option<&OperationName>, + parent_info: &PipelineParentInfo, + op: OperationRef, + ) { + self.instrument(|pi| pi.run_before_pipeline(name, parent_info, op)); + } + + pub fn run_after_pipeline( + &self, + name: Option<&OperationName>, + parent_info: &PipelineParentInfo, + ) { + self.instrument(|pi| pi.run_after_pipeline(name, parent_info)); + } + + pub fn run_before_pass(&self, pass: &dyn OperationPass, op: &OperationRef) { + self.instrument(|pi| pi.run_before_pass(pass, op)); + } + + pub fn run_after_pass( + &self, + pass: &dyn OperationPass, + op: &OperationRef, + post_execution_state: &PassExecutionState, + ) { + self.instrument(|pi| pi.run_after_pass(pass, op, post_execution_state)); + } + + pub fn run_after_pass_failed(&self, pass: &dyn OperationPass, op: &OperationRef) { + self.instrument(|pi| pi.run_after_pass_failed(pass, op)); + } + + pub fn run_before_analysis(&self, name: &str, id: &TypeId, op: &OperationRef) { + self.instrument(|pi| pi.run_before_analysis(name, id, op)); + } + + pub fn run_after_analysis(&self, name: &str, id: &TypeId, op: &OperationRef) { + self.instrument(|pi| pi.run_after_analysis(name, id, op)); + } + + pub fn add_instrumentation(&self, pi: Box) { + self.instrumentations.borrow_mut().push(pi); + } + + #[inline(always)] + fn instrument(&self, callback: F) + where + F: Fn(&mut dyn PassInstrumentation), + { + let mut instrumentations = self.instrumentations.borrow_mut(); + for pi in instrumentations.iter_mut() { + callback(pi); + } + } +} diff --git a/hir/src/pass/manager.rs b/hir/src/pass/manager.rs new file mode 100644 index 000000000..389411c2b --- /dev/null +++ b/hir/src/pass/manager.rs @@ -0,0 +1,1102 @@ +use alloc::{ + boxed::Box, + collections::BTreeMap, + format, + rc::Rc, + string::{String, ToString}, +}; + +use compact_str::{CompactString, ToCompactString}; +use midenc_session::{diagnostics::Severity, Options}; +use smallvec::{smallvec, SmallVec}; + +use super::{ + AnalysisManager, OperationPass, Pass, PassExecutionState, PassInstrumentation, + PassInstrumentor, PipelineParentInfo, Statistic, +}; +use crate::{ + pass::{PostPassStatus, Print}, + traits::IsolatedFromAbove, + Context, EntityMut, OpPrintingFlags, OpRegistration, Operation, OperationName, OperationRef, + Report, +}; + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub enum Nesting { + Implicit, + #[default] + Explicit, +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] +pub enum PassDisplayMode { + List, + #[default] + Pipeline, +} + +// TODO(pauls) +#[allow(unused)] +#[derive(Default, Debug)] +pub struct IRPrintingConfig { + pub print_module_scope: bool, + pub print_after_only_on_failure: bool, + // NOTE: Taken from the Options struct + pub print_ir_after_all: bool, + pub print_ir_after_pass: SmallVec<[String; 1]>, + pub print_ir_after_modified: bool, + pub flags: OpPrintingFlags, +} + +impl TryFrom<&Options> for IRPrintingConfig { + type Error = Report; + + fn try_from(options: &Options) -> Result { + let pass_filters = options.print_ir_after_pass.clone(); + + if options.print_ir_after_all && !pass_filters.is_empty() { + return Err(Report::msg( + "Flags `print_ir_after_all` and `print_ir_after_pass` are mutually exclusive. \ + Please select only one." + .to_string(), + )); + }; + + Ok(IRPrintingConfig { + print_ir_after_all: options.print_ir_after_all, + print_ir_after_pass: pass_filters.into(), + print_ir_after_modified: options.print_ir_after_modified, + ..Default::default() + }) + } +} + +/// The main pass manager and pipeline builder +pub struct PassManager { + context: Rc, + /// The underlying pass manager + pm: OpPassManager, + /// A manager for pass instrumentation + instrumentor: Rc, + /// An optional crash reproducer generator, if this pass manager is setup to + /// generate reproducers. + ///crash_reproducer_generator: Rc, + /// Indicates whether to print pass statistics + statistics: Option, + /// Indicates whether or not pass timing is enabled + timing: bool, + /// Indicates whether or not to run verification between passes + verification: bool, +} + +impl PassManager { + /// Create a new pass manager under the given context with a specific nesting style. The created + /// pass manager can schedule operations that match `name`. + pub fn new(context: Rc, name: impl AsRef, nesting: Nesting) -> Self { + let pm = OpPassManager::new(name.as_ref(), nesting, context.clone()); + Self { + context, + pm, + instrumentor: Default::default(), + statistics: None, + timing: false, + verification: true, + } + } + + /// Create a new pass manager under the given context with a specific nesting style. + /// + /// The created pass manager can schedule operations that match type `T`. + pub fn on(context: Rc, nesting: Nesting) -> Self { + Self::new(context, ::full_name(), nesting) + } + + /// Run the passes within this manager on the provided operation. The + /// specified operation must have the same name as the one provided the pass + /// manager on construction. + pub fn run(&mut self, op: OperationRef) -> Result<(), Report> { + use crate::Spanned; + + let op_name = op.borrow().name(); + let anchor = self.pm.name(); + if let Some(anchor) = anchor { + if anchor != &op_name { + return Err(self + .context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("failed to construct pass manager") + .with_primary_label( + op.borrow().span(), + format!("can't run '{anchor}' pass manager on '{op_name}'"), + ) + .into_report()); + } + } + + // Register all dialects for the current pipeline. + /* + let dependent_dialects = self.get_dependent_dialects(); + self.context.append_dialect_registry(dependent_dialects); + for dialect_name in dependent_dialects.names() { + self.context.get_or_register_dialect(dialect_name); + } + */ + + // Before running, make sure to finalize the pipeline pass list. + self.pm.finalize_pass_list()?; + + // Run pass initialization + self.pm.initialize()?; + + // Construct a top level analysis manager for the pipeline. + let analysis_manager = AnalysisManager::new(op, Some(self.instrumentor.clone())); + + // If reproducer generation is enabled, run the pass manager with crash handling enabled. + /* + let result = if self.crash_reproducer_generator.is_some() { + self.run_with_crash_recovery(op, analysis_manager); + } else { + self.run_passes(op, analysis_manager); + } + */ + let result = self.run_passes(op, analysis_manager); + + // Dump all of the pass statistics if necessary. + #[cfg(feature = "std")] + if self.statistics.is_some() { + let mut output = alloc::string::String::new(); + self.dump_statistics(&mut output).map_err(Report::msg)?; + std::println!("{output}"); + } + + result + } + + fn run_passes( + &mut self, + op: OperationRef, + analysis_manager: AnalysisManager, + ) -> Result<(), Report> { + OpToOpPassAdaptor::run_pipeline( + &mut self.pm, + op, + analysis_manager, + self.verification, + Some(self.instrumentor.clone()), + Some(&PipelineParentInfo { pass: None }), + ) + } + + #[inline] + pub fn context(&self) -> Rc { + self.context.clone() + } + + /// Runs the verifier after each individual pass. + pub fn enable_verifier(&mut self, yes: bool) -> &mut Self { + self.verification = yes; + self + } + + pub fn add_instrumentation(&mut self, pi: Box) -> &mut Self { + self.instrumentor.add_instrumentation(pi); + self + } + + pub fn enable_ir_printing(mut self, config: IRPrintingConfig) -> Self { + let print = Print::new(&config); + + if let Some(print) = print { + let print = Box::new(print); + self.add_instrumentation(print); + } + self + } + + pub fn enable_timing(&mut self, yes: bool) -> &mut Self { + self.timing = yes; + self + } + + pub fn enable_statistics(&mut self, mode: Option) -> &mut Self { + self.statistics = mode; + self + } + + fn dump_statistics(&mut self, out: &mut dyn core::fmt::Write) -> core::fmt::Result { + self.pm.print_statistics(out, self.statistics.unwrap_or_default()) + } + + pub fn print_as_textual_pipeline(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + self.pm.print_as_textual_pipeline(f) + } + + pub fn nest(&mut self) -> NestedOpPassManager<'_> { + self.pm.nest::() + } + + pub fn nest_pass_manager(&mut self, nested: OpPassManager) -> NestedOpPassManager<'_> { + self.pm.nest_pass_manager(nested) + } + + /// Nest a new op-specific pass manager (for the op with the given name), under this pass manager. + pub fn nest_with_type(&mut self, nested_name: &str) -> NestedOpPassManager<'_> { + self.pm.nest_with_type(nested_name) + } + + /// Nest a new op-agnostic ("any") pass manager under this pass manager. + pub fn nest_any(&mut self) -> NestedOpPassManager<'_> { + self.pm.nest_any() + } + + pub fn add_pass(&mut self, pass: Box) { + self.pm.add_pass(pass) + } + + pub fn add_nested_pass(&mut self, pass: Box) { + self.pm.add_nested_pass::(pass) + } +} + +/// This class represents a pass manager that runs passes on either a specific +/// operation type, or any isolated operation. This pass manager can not be run +/// on an operation directly, but must be run either as part of a top-level +/// `PassManager`(e.g. when constructed via `nest` calls), or dynamically within +/// a pass by using the `Pass::runPipeline` API. +pub struct OpPassManager { + /// The current context + context: Rc, + /// The name of the operation that passes of this pass manager operate on + name: Option, + /// The set of passes to run as part of this pass manager + passes: SmallVec<[Box; 8]>, + /// Control the implicit nesting of passes that mismatch the name set for this manager + nesting: Nesting, +} + +impl OpPassManager { + pub const ANY: &str = "any"; + + /// Construct a new op-agnostic ("any") pass manager with the given operation + /// type and nesting behavior. This is the same as invoking: + /// `OpPassManager(OpPassManager::ANY, nesting)`. + pub fn any(nesting: Nesting, context: Rc) -> Self { + Self { + context, + name: None, + passes: Default::default(), + nesting, + } + } + + pub fn new(name: &str, nesting: Nesting, context: Rc) -> Self { + if name == Self::ANY { + return Self::any(nesting, context); + } + + let (dialect_name, opcode) = name.split_once('.').expect( + "invalid operation name: expected format `.`, but missing `.`", + ); + let dialect_name = crate::interner::Symbol::intern(dialect_name); + let dialect = context.get_registered_dialect(dialect_name); + let ops = dialect.registered_ops(); + let name = + ops.iter() + .find(|name| name.name().as_str() == opcode) + .cloned() + .unwrap_or_else(|| { + panic!( + "invalid operation name: found dialect '{dialect_name}', but no operation \ + called '{opcode}' is registered to that dialect" + ) + }); + Self { + context, + name: Some(name), + passes: Default::default(), + nesting, + } + } + + pub fn on(nesting: Nesting, context: Rc) -> Self { + let dialect_name = ::dialect_name(); + let opcode = ::name(); + let dialect = context.get_registered_dialect(dialect_name); + let name = dialect + .registered_ops() + .iter() + .find(|n| n.name() == opcode) + .cloned() + .unwrap_or_else(|| { + panic!( + "invalid operation name: found dialect '{dialect_name}', but no operation \ + called '{opcode}' is registered to that dialect" + ); + }); + Self { + context, + name: Some(name), + passes: Default::default(), + nesting, + } + } + + pub fn for_operation(name: OperationName, nesting: Nesting, context: Rc) -> Self { + Self { + context, + name: Some(name), + passes: Default::default(), + nesting, + } + } + + pub fn context(&self) -> Rc { + self.context.clone() + } + + pub fn passes(&self) -> &[Box] { + &self.passes + } + + pub fn passes_mut(&mut self) -> &mut [Box] { + &mut self.passes + } + + pub fn is_empty(&self) -> bool { + self.passes.is_empty() + } + + pub fn len(&self) -> usize { + self.passes.len() + } + + pub fn is_op_agnostic(&self) -> bool { + self.name.is_none() + } + + pub fn clear(&mut self) { + self.passes.clear(); + } + + /// Nest a new op-specific pass manager (for the op with the given name), under this pass manager. + pub fn nest_with_type(&mut self, nested_name: &str) -> NestedOpPassManager<'_> { + self.nest_pass_manager(Self::new(nested_name, self.nesting, self.context.clone())) + } + + pub fn nest(&mut self) -> NestedOpPassManager<'_> { + self.nest_pass_manager(Self::on::(self.nesting, self.context.clone())) + } + + /// Nest a new op-agnostic ("any") pass manager under this pass manager. + pub fn nest_any(&mut self) -> NestedOpPassManager<'_> { + self.nest_pass_manager(Self::any(self.nesting, self.context.clone())) + } + + fn nest_for(&mut self, nested_name: OperationName) -> NestedOpPassManager<'_> { + self.nest_pass_manager(Self::for_operation(nested_name, self.nesting, self.context.clone())) + } + + pub fn add_pass(&mut self, pass: Box) { + // If this pass runs on a different operation than this pass manager, then implicitly + // nest a pass manager for this operation if enabled. + let pass_op_name = pass.target_name(&self.context); + if let Some(pass_op_name) = pass_op_name { + if self.name.as_ref().is_some_and(|name| name != &pass_op_name) { + if matches!(self.nesting, Nesting::Implicit) { + let mut nested = self.nest_for(pass_op_name); + nested.add_pass(pass); + return; + } + panic!( + "cannot add pass '{}' restricted to '{pass_op_name}' to a pass manager \ + intended to run on '{}', did you intend to nest?", + pass.name(), + self.name().unwrap(), + ); + } + } + + self.passes.push(pass); + } + + pub fn add_nested_pass(&mut self, pass: Box) { + let mut nested = self.nest::(); + nested.add_pass(pass); + } + + pub fn finalize_pass_list(&mut self) -> Result<(), Report> { + let finalize_adaptor = |adaptor: &mut OpToOpPassAdaptor| -> Result<(), Report> { + for pm in adaptor.pass_managers_mut() { + pm.finalize_pass_list()?; + } + + Ok(()) + }; + + // Walk the pass list and merge adjacent adaptors. + let num_passes = self.passes.len(); + let passes = core::mem::replace(&mut self.passes, SmallVec::with_capacity(num_passes)); + let prev_adaptor = None::>; + let (_, prev_adaptor) = passes.into_iter().try_fold( + (&mut self.passes, prev_adaptor), + |(passes, prev), mut pass| { + // Is this pass an adaptor? + match pass.as_any_mut().downcast_mut::() { + // Yes, merge it into the previous one if present, otherwise use this as the + // first adaptor in a potential chain of them + Some(adaptor) => match prev { + Some(mut prev_adaptor) => { + if adaptor.try_merge_into(&mut prev_adaptor) { + Ok::<_, Report>((passes, Some(prev_adaptor))) + } else { + let current = + pass.into_any().downcast::().unwrap(); + Ok((passes, Some(current))) + } + } + None => { + let current = pass.into_any().downcast::().unwrap(); + Ok((passes, Some(current))) + } + }, + // This pass isn't an adaptor, but if we have one, we need to finalize it + None => { + match prev { + Some(mut prev_adaptor) => { + finalize_adaptor(&mut prev_adaptor)?; + passes.push(prev_adaptor as Box); + passes.push(pass); + } + None => { + passes.push(pass); + } + } + Ok((passes, None)) + } + } + }, + )?; + + if let Some(prev_adaptor) = prev_adaptor { + self.passes.push(prev_adaptor); + } + + // If this is a op-agnostic pass manager, there is nothing left to do. + match self.name.as_ref() { + None => Ok(()), + // Otherwise, verify that all of the passes are valid for the current operation anchor. + Some(name) => { + for pass in self.passes.iter() { + if !pass.can_schedule_on(name) { + return Err(self + .context + .diagnostics() + .diagnostic(Severity::Error) + .with_message(format!( + "unable to schedule pass '{}' on pass manager intended for \ + '{name}'", + pass.name() + )) + .into_report()); + } + } + + Ok(()) + } + } + } + + pub fn name(&self) -> Option<&OperationName> { + self.name.as_ref() + } + + pub fn set_nesting(&mut self, nesting: Nesting) { + self.nesting = nesting; + } + + pub fn nesting(&self) -> Nesting { + self.nesting + } + + /// Indicate if this pass manager can be scheduled on the given operation + pub fn can_schedule_on(&self, name: &OperationName) -> bool { + // If this pass manager is op-specific, we simply check if the provided operation name + // is the same as this one. + if let Some(op_name) = self.name() { + return op_name == name; + } + + // Otherwise, this is an op-agnostic pass manager. Check that the operation can be + // scheduled on all passes within the manager. + if !name.implements::() { + return false; + } + self.passes.iter().all(|pass| pass.can_schedule_on(name)) + } + + fn initialize(&mut self) -> Result<(), Report> { + for pass in self.passes.iter_mut() { + // If this pass isn't an adaptor, directly initialize it + if let Some(adaptor) = pass.as_any_mut().downcast_mut::() { + for pm in adaptor.pass_managers_mut() { + pm.initialize()?; + } + } else { + pass.initialize(self.context.clone())?; + } + } + + Ok(()) + } + + #[allow(unused)] + fn merge_into(&mut self, rhs: &mut Self) { + assert_eq!(self.name, rhs.name, "merging unrelated pass managers"); + for pass in self.passes.drain(..) { + rhs.passes.push(pass); + } + } + + pub fn nest_pass_manager(&mut self, nested: Self) -> NestedOpPassManager<'_> { + let adaptor = Box::new(OpToOpPassAdaptor::new(nested)); + NestedOpPassManager { + parent: self, + nested: Some(adaptor), + } + } + + /// Prints out the passes of the pass manager as the textual representation of pipelines. + pub fn print_as_textual_pipeline(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if let Some(anchor) = self.name() { + write!(f, "{anchor}(")?; + } else { + f.write_str("any(")?; + } + for (i, pass) in self.passes().iter().enumerate() { + if i > 0 { + f.write_str(",")?; + } + pass.print_as_textual_pipeline(f)?; + } + f.write_str(")") + } + + pub fn print_statistics( + &self, + out: &mut dyn core::fmt::Write, + display_mode: PassDisplayMode, + ) -> core::fmt::Result { + const PASS_STATS_DESCRIPTION: &str = "... Pass statistics report ..."; + + // Print the stats header. + writeln!(out, "=={:-<73}==", "")?; + // Figure out how many spaces for the description name. + let padding = 80usize.saturating_sub(PASS_STATS_DESCRIPTION.len()); + writeln!(out, "{PASS_STATS_DESCRIPTION: self.print_statistics_as_list(out), + PassDisplayMode::Pipeline => self.print_statistics_as_pipeline(out), + } + } + + fn add_stats( + pass: &dyn OperationPass, + merged_stats: &mut BTreeMap<&str, SmallVec<[Box; 4]>>, + ) { + use alloc::collections::btree_map::Entry; + + if let Some(adaptor) = pass.as_any().downcast_ref::() { + // Recursively add each of the children. + for pass_manager in adaptor.pass_managers() { + for pass in pass_manager.passes() { + Self::add_stats(&**pass, merged_stats); + } + } + } else { + // If this is not an adaptor, add the stats to the list if there are any. + if !pass.has_statistics() { + return; + } + let statistics = SmallVec::<[Box; 4]>::from_iter( + pass.statistics().iter().map(|stat| Statistic::clone(&**stat)), + ); + match merged_stats.entry(pass.name()) { + Entry::Vacant(entry) => { + entry.insert(statistics); + } + Entry::Occupied(mut entry) => { + let prev_stats = entry.get_mut(); + assert_eq!(prev_stats.len(), statistics.len()); + for (index, mut stat) in statistics.into_iter().enumerate() { + let _ = prev_stats[index].try_merge(&mut stat); + } + } + } + } + } + + /// Print the statistics results in a list form, where each pass is sorted by name. + fn print_statistics_as_list(&self, out: &mut dyn core::fmt::Write) -> core::fmt::Result { + let mut merged_stats = BTreeMap::<&str, SmallVec<[Box; 4]>>::default(); + for pass in self.passes.iter() { + Self::add_stats(&**pass, &mut merged_stats); + } + + // Print the timing information sequentially. + for (pass, stats) in merged_stats.iter() { + self.print_pass_entry(out, 2, pass, stats)?; + } + + Ok(()) + } + + fn print_statistics_as_pipeline(&self, _out: &mut dyn core::fmt::Write) -> core::fmt::Result { + todo!() + } + + fn print_pass_entry( + &self, + out: &mut dyn core::fmt::Write, + indent: usize, + pass: &str, + stats: &[Box], + ) -> core::fmt::Result { + use core::fmt::Write; + + writeln!(out, "{pass: { + name: &'a str, + description: &'a str, + value: compact_str::CompactString, + } + + let mut largest_name = 0usize; + let mut largest_value = 0usize; + let mut rendered_stats = SmallVec::<[Rendered; 4]>::default(); + for stat in stats { + let mut value = compact_str::CompactString::default(); + let doc = stat.pretty_print(); + write!(&mut value, "{doc}")?; + let name = stat.name(); + largest_name = core::cmp::max(largest_name, name.len()); + largest_value = core::cmp::max(largest_value, value.len()); + rendered_stats.push(Rendered { + name, + description: stat.description(), + value, + }); + } + + // Sort the statistics by name. + rendered_stats.sort_by(|a, b| a.name.cmp(b.name)); + + // Print statistics + for stat in rendered_stats { + write!(out, "{: <1$} (S) ", "", indent)?; + write!(out, "{: <1$} ", &stat.value, largest_value)?; + write!(out, "{: <1$}", &stat.name, largest_name)?; + if stat.description.is_empty() { + out.write_char('\n')?; + } else { + writeln!(out, " - {}", &stat.description)?; + } + } + + Ok(()) + } +} + +pub struct NestedOpPassManager<'parent> { + parent: &'parent mut OpPassManager, + nested: Option>, +} + +impl core::ops::Deref for NestedOpPassManager<'_> { + type Target = OpPassManager; + + fn deref(&self) -> &Self::Target { + &self.nested.as_deref().unwrap().pass_managers()[0] + } +} +impl core::ops::DerefMut for NestedOpPassManager<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.nested.as_deref_mut().unwrap().pass_managers_mut()[0] + } +} + +impl Drop for NestedOpPassManager<'_> { + fn drop(&mut self) { + self.parent.add_pass(self.nested.take().unwrap() as Box); + } +} + +pub struct OpToOpPassAdaptor { + pms: SmallVec<[OpPassManager; 1]>, +} +impl OpToOpPassAdaptor { + pub fn new(pm: OpPassManager) -> Self { + Self { pms: smallvec![pm] } + } + + pub fn name(&self) -> CompactString { + use core::fmt::Write; + + let mut name = CompactString::default(); + let names = + crate::formatter::DisplayValues::new(self.pms.iter().map(|pm| match pm.name() { + None => alloc::borrow::Cow::Borrowed(OpPassManager::ANY), + Some(name) => alloc::borrow::Cow::Owned(name.to_string()), + })); + write!(&mut name, "Pipeline Collection: [{names}]").unwrap(); + name + } + + /// Try to merge the current pass adaptor into 'rhs'. + /// + /// This will try to append the pass managers of this adaptor into those within `rhs`, or return + /// failure if merging isn't possible. The main situation in which merging is not possible is if + /// one of the adaptors has an `any` pipeline that is not compatible with a pass manager in the + /// other adaptor. For example, if this adaptor has a `hir.function` pipeline and `rhs` has an + /// `any` pipeline that operates on a FunctionOpInterface. In this situation the pipelines have + /// a conflict (they both want to run on the same operations), so we can't merge. + pub fn try_merge_into(&mut self, rhs: &mut Self) -> bool { + // Functor used to detect if the given generic pass manager will have a potential schedule + // conflict with the given `pms`. + let has_schedule_conflict_with = |generic_pm: &OpPassManager, pms: &[OpPassManager]| { + pms.iter().any(|pm| { + // If this is a non-generic pass manager, a conflict will arise if a non-generic + // pass manager's operation name can be scheduled on the generic passmanager. + if let Some(name) = pm.name() { + generic_pm.can_schedule_on(name) + } else { + // Otherwise, this is a generic pass manager. We current can't determine when + // generic pass managers can be merged, so conservatively assume they conflict. + true + } + }) + }; + + // Check that if either adaptor has a generic pass manager, that pm is compatible within any + // non-generic pass managers. + // + // Check the current adaptor. + let lhs_generic = self.pass_managers().iter().find(|pm| pm.is_op_agnostic()); + if lhs_generic.is_some_and(|pm| has_schedule_conflict_with(pm, rhs.pass_managers())) { + return false; + } + + // Check the rhs adaptor. + let rhs_generic = self.pass_managers().iter().find(|pm| pm.is_op_agnostic()); + if rhs_generic.is_some_and(|pm| has_schedule_conflict_with(pm, self.pass_managers())) { + return false; + } + + for mut pm in self.pms.drain(..) { + // If an existing pass manager exists, then merge the given pass manager into it. + if let Some(existing) = + rhs.pass_managers_mut().iter_mut().find(|rpm| pm.name() == rpm.name()) + { + pm.merge_into(existing); + } else { + // Otherwise, add the given pass manager to the list. + rhs.pms.push(pm); + } + } + + // After coalescing, sort the pass managers within rhs by name. + rhs.pms.sort_by(|lhs, rhs| { + use core::cmp::Ordering; + // Order op-specific pass managers first and op-agnostic pass managers last. + match (lhs.name(), rhs.name()) { + (None, None) => Ordering::Equal, + (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Less, + (Some(lhs), Some(rhs)) => lhs.cmp(rhs), + } + }); + + true + } + + pub fn pass_managers(&self) -> &[OpPassManager] { + &self.pms + } + + pub fn pass_managers_mut(&mut self) -> &mut [OpPassManager] { + &mut self.pms + } + + /// Run the given operation and analysis manager on a provided op pass manager. + fn run_pipeline( + pm: &mut OpPassManager, + op: OperationRef, + analysis_manager: AnalysisManager, + verify: bool, + instrumentor: Option>, + parent_info: Option<&PipelineParentInfo>, + ) -> Result<(), Report> { + if verify { + // We run an initial recursive verification, since this is the first verification done + // to the operations + Self::verify(&op, true)?; + } + + assert!( + instrumentor.is_none() || parent_info.is_some(), + "expected parent info if instrumentor is provided" + ); + + // Clear out any computed operation analyses on exit. + // + // These analyses won't be used anymore in this pipeline, and this helps reduce the + // current working set of memory. If preserving these analyses becomes important in the + // future, we can re-evaluate. + let _clear = analysis_manager.defer_clear(); + + // Run the pipeline over the provided operation. + let mut op_name = None; + if let Some(instrumentor) = instrumentor.as_deref() { + op_name = pm.name().cloned(); + instrumentor.run_before_pipeline(op_name.as_ref(), parent_info.as_ref().unwrap(), op); + } + + for pass in pm.passes_mut() { + Self::run(&mut **pass, op, analysis_manager.clone(), verify)?; + } + + if let Some(instrumentor) = instrumentor.as_deref() { + instrumentor.run_after_pipeline(op_name.as_ref(), parent_info.as_ref().unwrap()); + } + + Ok(()) + } + + /// Run the given operation and analysis manager on a single pass. + fn run( + pass: &mut dyn OperationPass, + op: OperationRef, + analysis_manager: AnalysisManager, + verify: bool, + ) -> Result<(), Report> { + use crate::Spanned; + + let (op_name, span, context) = { + let op = op.borrow(); + (op.name(), op.span(), op.context_rc()) + }; + if !op_name.implements::() { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("failed to execute pass") + .with_primary_label( + span, + "trying to schedule a pass on an operation which does not implement \ + `IsolatedFromAbove`", + ) + .into_report()); + } + if !pass.can_schedule_on(&op_name) { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("failed to execute pass") + .with_primary_label(span, "trying to schedule a pass on an unsupported operation") + .into_report()); + } + + // Initialize the pass state with a callback for the pass to dynamically execute a pipeline + // on the currently visited operation. + let pi = analysis_manager.pass_instrumentor(); + let parent_info = PipelineParentInfo { + pass: Some(pass.name().to_compact_string()), + }; + let callback_op = op; + let callback_analysis_manager = analysis_manager.clone(); + let pipeline_callback: Box = Box::new( + move |pipeline: &mut OpPassManager, root: OperationRef| -> Result<(), Report> { + let pi = callback_analysis_manager.pass_instrumentor(); + let op = callback_op.borrow(); + let context = op.context_rc(); + let root_op = root.borrow(); + if !root_op.is_ancestor_of(&op) { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("failed to execute pass") + .with_primary_label( + root_op.span(), + "trying to schedule a dynamic pass pipeline on an operation that \ + isn't nested under the current operation the pass is processing", + ) + .into_report()); + } + assert!(pipeline.can_schedule_on(&root_op.name())); + // Before running, finalize the passes held by the pipeline + pipeline.finalize_pass_list()?; + + // Initialize the user-provided pipeline and execute the pipeline + pipeline.initialize()?; + + let nested_am = if root == callback_op { + callback_analysis_manager.clone() + } else { + callback_analysis_manager.nest(root) + }; + Self::run_pipeline(pipeline, root, nested_am, verify, pi, Some(&parent_info)) + }, + ); + + let mut execution_state = PassExecutionState::new( + op, + context.clone(), + analysis_manager.clone(), + Some(pipeline_callback), + ); + + // Instrument before the pass has run + if let Some(instrumentor) = pi.as_deref() { + instrumentor.run_before_pass(pass, &op); + } + + let mut result = + if let Some(adaptor) = pass.as_any_mut().downcast_mut::() { + adaptor.run_on_operation(op, &mut execution_state, verify) + } else { + pass.run_on_operation(op, &mut execution_state) + }; + + // Invalidate any non-preserved analyses + analysis_manager.invalidate(execution_state.preserved_analyses_mut()); + + // When `verify == true`, we run the verifier (unless the pass failed) + if result.is_ok() && verify { + // If the pass is an adaptor pass, we don't run the verifier recursively because the + // nested operations should have already been verified after nested passes had run + let run_verifier_recursively = !pass.as_any().is::(); + + // Reduce compile time by avoiding running the verifier if the pass didn't change the + // IR since the last time the verifier was run: + // + // * If the pass said that it preserved all analyses then it can't have permuted the IR + let run_verifier_now = !execution_state.preserved_analyses().is_all(); + + if run_verifier_now { + if let Err(verification_result) = Self::verify(&op, run_verifier_recursively) { + result = result.map_err(|_| verification_result); + } + } + } + + if let Some(instrumentor) = pi.as_deref() { + if result.is_err() { + instrumentor.run_after_pass_failed(pass, &op); + } else { + instrumentor.run_after_pass(pass, &op, &execution_state); + } + } + + // Return the pass result + result.map(|_| ()) + } + + fn verify(op: &OperationRef, verify_recursively: bool) -> Result<(), Report> { + let op = op.borrow(); + if verify_recursively { + op.recursively_verify() + } else { + op.verify() + } + } + + fn run_on_operation( + &mut self, + op: OperationRef, + state: &mut PassExecutionState, + verify: bool, + ) -> Result<(), Report> { + let analysis_manager = state.analysis_manager(); + let instrumentor = analysis_manager.pass_instrumentor(); + let parent_info = PipelineParentInfo { + pass: Some(self.name()), + }; + + // Collection region refs so we aren't holding borrows during pass execution + let mut next_region = op.borrow().regions().back().as_pointer(); + while let Some(region) = next_region.take() { + next_region = region.next(); + let mut next_block = region.borrow().body().front().as_pointer(); + while let Some(block) = next_block.take() { + next_block = block.next(); + let mut next_op = block.borrow().front(); + while let Some(op) = next_op.take() { + next_op = op.next(); + let op_name = op.borrow().name(); + if let Some(manager) = + self.pms.iter_mut().find(|pm| pm.can_schedule_on(&op_name)) + { + let am = analysis_manager.nest(op); + Self::run_pipeline( + manager, + op, + am, + verify, + instrumentor.clone(), + Some(&parent_info), + )?; + } + } + } + } + state.set_post_pass_status(PostPassStatus::Unchanged); + Ok(()) + } +} + +impl Pass for OpToOpPassAdaptor { + type Target = Operation; + + fn name(&self) -> &'static str { + crate::interner::Symbol::intern(self.name()).as_str() + } + + #[inline(always)] + fn target_name(&self, _context: &Context) -> Option { + None + } + + fn print_as_textual_pipeline(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let pms = self.pass_managers(); + for (i, pm) in pms.iter().enumerate() { + if i > 0 { + f.write_str(",")?; + } + pm.print_as_textual_pipeline(f)?; + } + + Ok(()) + } + + #[inline(always)] + fn can_schedule_on(&self, _name: &OperationName) -> bool { + true + } + + fn run_on_operation( + &mut self, + _op: EntityMut<'_, Operation>, + _state: &mut PassExecutionState, + ) -> Result<(), Report> { + unreachable!("unexpected call to `Pass::run_on_operation` for OpToOpPassAdaptor") + } +} diff --git a/hir/src/pass/mod.rs b/hir/src/pass/mod.rs deleted file mode 100644 index f91d6d02a..000000000 --- a/hir/src/pass/mod.rs +++ /dev/null @@ -1,212 +0,0 @@ -//! This module provides traits and associated types for use in compiler pass pipelines. -//use midenc_hir_pass::RewritePassRegistration; - -// Register rewrite passes for modules -//inventory::collect!(RewritePassRegistration); - -// Register rewrite passes for functions -//inventory::collect!(RewritePassRegistration); -/* -macro_rules! register_function_rewrite { - ($name:literal, $ty:ty) => { - impl $ty { - fn new( - _options: std::sync::Arc, - _diagnostics: std::sync::Arc, - ) -> Box { - Box::new(crate::ModuleRewritePassAdapter(Self)) - } - } - inventory::submit! { - crate::ModuleRewritePassRegistration::new($name, <$ty>::new) - } - }; -} - */ - -mod analysis; -mod conversion; -mod rewrite; - -use midenc_session::Session; - -pub use self::{analysis::*, conversion::*, rewrite::*}; - -/// This trait provides descriptive information about a pass -/// -/// This is primarily intended to assist in registering passes with the pass manager -pub trait PassInfo { - /// The string which should be used as the name of this pass on the command line. - /// - /// For example, for the InlineBlocks pass, this is set to `inline-blocks`. - /// - /// You can add `#[derive(PassInfo)]` to a pass type, and this will be derived from - /// the name of the type, and converted to kebab-case, e.g. `inline-blocks`. - const FLAG: &'static str; - /// A short, single-line description of what this pass does. - /// - /// You can add `#[derive(PassInfo)]` to a pass type, and this will be derived from - /// the first line of the documentation attached to the type. - const SUMMARY: &'static str; - /// A rich, potentially multi-line description of this pass and its configuration. - /// - /// You can add `#[derive(PassInfo)]` to a pass type, and this will be derived from - /// the documentation attached to the type. - const DESCRIPTION: &'static str; -} - -/// The [Pass] trait represents a fallible operation which takes an input of any type, and produces -/// an output of any type. This is intentionally abstract, and is intended as a building block for -/// compiler pipelines. -/// -/// [Pass] is in fact so abstract, that it is automatically implemented for any Rust function whose -/// type is representable by `FnMut(I) -> Result`. -/// -/// Implementations of [Pass] can be combined via [Pass::chain], which returns an instantiation of -/// the [Chain] type that itself implements [Pass]. This permits any number of passes to be -/// combined/chained together and passed around as a value. -pub trait Pass { - type Input<'a>; - type Output<'a>; - type Error; - - /// Runs the pass on the given input - /// - /// Passes should return `Err` to signal that the pass has failed - /// and compilation should be aborted - fn run<'a>( - &mut self, - input: Self::Input<'a>, - analyses: &mut AnalysisManager, - session: &Session, - ) -> Result, Self::Error>; - - /// Chains two passes together to form a new, fused pass - fn chain

(self, pass: P) -> Chain - where - Self: Sized, - P: for<'a> Pass = Self::Output<'a>, Error = Self::Error>, - { - Chain::new(self, pass) - } -} -impl Pass for &mut P -where - P: for<'a> Pass = T, Output<'a> = U, Error = E>, -{ - type Error = E; - type Input<'a> = T; - type Output<'a> = U; - - fn run<'a>( - &mut self, - input: Self::Input<'a>, - analyses: &mut AnalysisManager, - session: &Session, - ) -> Result, Self::Error> { - (*self).run(input, analyses, session) - } -} -impl Pass for Box

-where - P: ?Sized + for<'a> Pass = T, Output<'a> = U, Error = E>, -{ - type Error = E; - type Input<'a> = T; - type Output<'a> = U; - - fn run<'a>( - &mut self, - input: Self::Input<'a>, - analyses: &mut AnalysisManager, - session: &Session, - ) -> Result, Self::Error> { - (**self).run(input, analyses, session) - } -} -impl Pass for dyn FnMut(T, &mut AnalysisManager, &Session) -> Result { - type Error = E; - type Input<'a> = T; - type Output<'a> = U; - - #[inline] - fn run<'a>( - &mut self, - input: Self::Input<'a>, - analyses: &mut AnalysisManager, - session: &Session, - ) -> Result, Self::Error> { - self(input, analyses, session) - } -} - -/// [Chain] represents a pipeline of two or more passes whose inputs and outputs are linked -/// together into a "chain". If any pass in the pipeline raises an error, the rest of the -/// pipeline is skipped, and the error is returned. -/// -/// This is not meant to be constructed or referenced directly, as the type signature gets out -/// of hand quickly when combining multiple passes. Instead, you should invoke `chain` on a -/// [Pass] implementation, and use it as a trait object. In some cases this may require boxing -/// the `Chain`, depending on how you are using it in your compiler. -pub struct Chain { - a: A, - b: B, -} -impl Chain { - fn new(a: A, b: B) -> Self { - Self { a, b } - } -} -impl Copy for Chain -where - A: Copy, - B: Copy, -{ -} -impl Clone for Chain -where - A: Clone, - B: Clone, -{ - #[inline] - fn clone(&self) -> Self { - Self::new(self.a.clone(), self.b.clone()) - } -} -impl Pass for Chain -where - A: for<'a> Pass, - B: for<'a> Pass = ::Output<'a>, Error = E>, -{ - type Error = ::Error; - type Input<'a> = ::Input<'a>; - type Output<'a> = ::Output<'a>; - - fn run<'a>( - &mut self, - input: Self::Input<'a>, - analyses: &mut AnalysisManager, - session: &Session, - ) -> Result, Self::Error> { - let output = self.a.run(input, analyses, session)?; - self.b.run(output, analyses, session) - } -} -impl ConversionPass for Chain -where - A: ConversionPass, - B: ConversionPass::To>, -{ - type From = ::From; - type To = ::To; - - fn convert<'a>( - &mut self, - entity: Self::From, - analyses: &mut AnalysisManager, - session: &Session, - ) -> ConversionResult { - let output = self.a.convert(entity, analyses, session)?; - self.b.convert(output, analyses, session) - } -} diff --git a/hir/src/pass/pass.rs b/hir/src/pass/pass.rs new file mode 100644 index 000000000..224ee31c6 --- /dev/null +++ b/hir/src/pass/pass.rs @@ -0,0 +1,446 @@ +use alloc::{boxed::Box, rc::Rc}; +use core::{any::Any, fmt}; + +use super::*; +use crate::{Context, EntityMut, OperationName, OperationRef, Report}; + +/// A type-erased [Pass]. +/// +/// This is used to allow heterogenous passes to be operated on uniformly. +/// +/// Semantically, an [OperationPass] behaves like a `Pass`. +#[allow(unused_variables)] +pub trait OperationPass { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; + fn into_any(self: Box) -> Box; + fn name(&self) -> &'static str; + + fn argument(&self) -> &'static str { + // NOTE: Could we compute an argument string from the type name? + "" + } + fn description(&self) -> &'static str { + "" + } + fn info(&self) -> PassInfo { + PassInfo::lookup(self.argument()).expect("could not find pass information") + } + /// The name of the operation that this pass operates on, or `None` if this is a generic pass. + fn target_name(&self, context: &Context) -> Option; + fn initialize_options(&mut self, options: &str) -> Result<(), Report> { + Ok(()) + } + fn print_as_textual_pipeline(&self, f: &mut fmt::Formatter) -> fmt::Result; + fn has_statistics(&self) -> bool { + !self.statistics().is_empty() + } + fn statistics(&self) -> &[Box]; + fn statistics_mut(&mut self) -> &mut [Box]; + fn initialize(&mut self, context: Rc) -> Result<(), Report> { + Ok(()) + } + fn can_schedule_on(&self, name: &OperationName) -> bool; + fn run_on_operation( + &mut self, + op: OperationRef, + state: &mut PassExecutionState, + ) -> Result<(), Report>; + fn run_pipeline( + &mut self, + pipeline: &mut OpPassManager, + op: OperationRef, + state: &mut PassExecutionState, + ) -> Result<(), Report>; +} + +impl

OperationPass for P +where + P: Pass + 'static, +{ + fn as_any(&self) -> &dyn Any { +

::as_any(self) + } + + fn as_any_mut(&mut self) -> &mut dyn Any { +

::as_any_mut(self) + } + + fn into_any(self: Box) -> Box { +

::into_any(self) + } + + fn name(&self) -> &'static str { +

::name(self) + } + + fn argument(&self) -> &'static str { +

::argument(self) + } + + fn description(&self) -> &'static str { +

::description(self) + } + + fn info(&self) -> PassInfo { +

::info(self) + } + + fn target_name(&self, context: &Context) -> Option { +

::target_name(self, context) + } + + fn initialize_options(&mut self, options: &str) -> Result<(), Report> { +

::initialize_options(self, options) + } + + fn print_as_textual_pipeline(&self, f: &mut fmt::Formatter) -> fmt::Result { +

::print_as_textual_pipeline(self, f) + } + + fn has_statistics(&self) -> bool { +

::has_statistics(self) + } + + fn statistics(&self) -> &[Box] { +

::statistics(self) + } + + fn statistics_mut(&mut self) -> &mut [Box] { +

::statistics_mut(self) + } + + fn initialize(&mut self, context: Rc) -> Result<(), Report> { +

::initialize(self, context) + } + + fn can_schedule_on(&self, name: &OperationName) -> bool { +

::can_schedule_on(self, name) + } + + fn run_on_operation( + &mut self, + mut op: OperationRef, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + let op = <

::Target as PassTarget>::into_target_mut(&mut op); +

::run_on_operation(self, op, state) + } + + fn run_pipeline( + &mut self, + pipeline: &mut OpPassManager, + op: OperationRef, + state: &mut PassExecutionState, + ) -> Result<(), Report> { +

::run_pipeline(self, pipeline, op, state) + } +} + +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum PostPassStatus { + Unchanged, + Changed, +} + +impl From for PostPassStatus { + fn from(ir_was_changed: bool) -> Self { + if ir_was_changed { + PostPassStatus::Changed + } else { + PostPassStatus::Unchanged + } + } +} + +/// A compiler pass which operates on an [Operation] of some kind. +#[allow(unused_variables)] +pub trait Pass: Sized + Any { + /// The concrete/trait type targeted by this pass. + /// + /// Calls to `get_operation` will return a reference of this type. + type Target: ?Sized + PassTarget; + + /// Used for downcasting + #[inline(always)] + fn as_any(&self) -> &dyn Any { + self as &dyn Any + } + + /// Used for downcasting + #[inline(always)] + fn as_any_mut(&mut self) -> &mut dyn Any { + self as &mut dyn Any + } + + /// Used for downcasting + #[inline(always)] + fn into_any(self: Box) -> Box { + self as Box + } + + /// The display name of this pass + fn name(&self) -> &'static str; + /// The command line option name used to control this pass + fn argument(&self) -> &'static str { + // NOTE: Could we compute an argument string from the type name or `self.name()`? + "" + } + /// A description of what this pass does. + fn description(&self) -> &'static str { + "" + } + /// Obtain the underlying [PassInfo] object for this pass. + fn info(&self) -> PassInfo { + PassInfo::lookup(self.argument()).expect("pass is not currently registered") + } + /// The name of the operation that this pass operates on, or `None` if this is a generic pass. + fn target_name(&self, context: &Context) -> Option { + <::Target as PassTarget>::target_name(context) + } + /// If command-line options are provided for this pass, implementations must parse the raw + /// options here, returning `Err` if parsing fails for some reason. + /// + /// By default, this is a no-op. + fn initialize_options(&mut self, options: &str) -> Result<(), Report> { + Ok(()) + } + /// Prints out the pass in the textual representation of pipelines. + /// + /// If this is an adaptor pass, print its pass managers. + fn print_as_textual_pipeline(&self, f: &mut fmt::Formatter) -> fmt::Result { + let argument = self.argument(); + if !argument.is_empty() { + write!(f, "{argument}") + } else { + write!(f, "unknown<{}>", self.name()) + } + } + /// Returns true if this pass has associated statistics + fn has_statistics(&self) -> bool { + !self.statistics().is_empty() + } + /// Get pass statistics associated with this pass + fn statistics(&self) -> &[Box] { + &[] + } + /// Get mutable access to the pass statistics associated with this pass + fn statistics_mut(&mut self) -> &mut [Box] { + &mut [] + } + /// Initialize any complex state necessary for running this pass. + /// + /// This hook should not rely on any state accessible during the execution of a pass. For + /// example, `context`/`get_operation`/`get_analysis`/etc. should not be invoked within this + /// hook. + /// + /// This method is invoked after all dependent dialects for the pipeline are loaded, and is not + /// allowed to load any further dialects (override the `get_dependent_dialects()` hook for this + /// purpose instead). Returns `Err` with a diagnostic if initialization fails, in which case the + /// pass pipeline won't execute. + fn initialize(&mut self, context: Rc) -> Result<(), Report> { + Ok(()) + } + /// Query if this pass can be scheduled to run on the given operation type. + fn can_schedule_on(&self, name: &OperationName) -> bool; + /// Run this pass on the current operation + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report>; + /// Schedule an arbitrary pass pipeline on the provided operation. + /// + /// This can be invoke any time in a pass to dynamic schedule more passes. The provided + /// operation must be the current one or one nested below. + fn run_pipeline( + &mut self, + pipeline: &mut OpPassManager, + op: OperationRef, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + state.run_pipeline(pipeline, op) + } +} + +impl

Pass for Box

+where + P: Pass, +{ + type Target =

::Target; + + fn as_any(&self) -> &dyn Any { + (**self).as_any() + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + (**self).as_any_mut() + } + + fn into_any(self: Box) -> Box { + let pass = Box::into_inner(self); +

::into_any(pass) + } + + #[inline] + fn name(&self) -> &'static str { + (**self).name() + } + + #[inline] + fn argument(&self) -> &'static str { + (**self).argument() + } + + #[inline] + fn description(&self) -> &'static str { + (**self).description() + } + + #[inline] + fn info(&self) -> PassInfo { + (**self).info() + } + + #[inline] + fn target_name(&self, context: &Context) -> Option { + (**self).target_name(context) + } + + #[inline] + fn initialize_options(&mut self, options: &str) -> Result<(), Report> { + (**self).initialize_options(options) + } + + #[inline] + fn print_as_textual_pipeline(&self, f: &mut fmt::Formatter) -> fmt::Result { + (**self).print_as_textual_pipeline(f) + } + + #[inline] + fn has_statistics(&self) -> bool { + (**self).has_statistics() + } + + #[inline] + fn statistics(&self) -> &[Box] { + (**self).statistics() + } + + #[inline] + fn statistics_mut(&mut self) -> &mut [Box] { + (**self).statistics_mut() + } + + #[inline] + fn initialize(&mut self, context: Rc) -> Result<(), Report> { + (**self).initialize(context) + } + + #[inline] + fn can_schedule_on(&self, name: &OperationName) -> bool { + (**self).can_schedule_on(name) + } + + #[inline] + fn run_on_operation( + &mut self, + op: EntityMut<'_, Self::Target>, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + (**self).run_on_operation(op, state) + } + + #[inline] + fn run_pipeline( + &mut self, + pipeline: &mut OpPassManager, + op: OperationRef, + state: &mut PassExecutionState, + ) -> Result<(), Report> { + (**self).run_pipeline(pipeline, op, state) + } +} + +pub type DynamicPipelineExecutor = + dyn FnMut(&mut OpPassManager, OperationRef) -> Result<(), Report>; + +/// The state for a single execution of a pass. This provides a unified +/// interface for accessing and initializing necessary state for pass execution. +pub struct PassExecutionState { + /// The operation being transformed + op: OperationRef, + context: Rc, + analysis_manager: AnalysisManager, + /// The set of preserved analyses for the current execution + preserved_analyses: PreservedAnalyses, + // Callback in the pass manager that allows one to schedule dynamic pipelines that will be + // rooted at the provided operation. + #[allow(unused)] + pipeline_executor: Option>, + post_pass_status: PostPassStatus, +} +impl PassExecutionState { + pub fn new( + op: OperationRef, + context: Rc, + analysis_manager: AnalysisManager, + pipeline_executor: Option>, + ) -> Self { + Self { + op, + context, + analysis_manager, + preserved_analyses: Default::default(), + pipeline_executor, + post_pass_status: PostPassStatus::Unchanged, + } + } + + #[inline(always)] + pub fn context(&self) -> Rc { + self.context.clone() + } + + #[inline(always)] + pub const fn current_operation(&self) -> &OperationRef { + &self.op + } + + #[inline(always)] + pub const fn analysis_manager(&self) -> &AnalysisManager { + &self.analysis_manager + } + + #[inline(always)] + pub const fn preserved_analyses(&self) -> &PreservedAnalyses { + &self.preserved_analyses + } + + #[inline(always)] + pub fn preserved_analyses_mut(&mut self) -> &mut PreservedAnalyses { + &mut self.preserved_analyses + } + + #[inline(always)] + pub fn post_pass_status(&self) -> &PostPassStatus { + &self.post_pass_status + } + + #[inline(always)] + pub fn set_post_pass_status(&mut self, post_pass_status: PostPassStatus) { + self.post_pass_status = post_pass_status; + } + + pub fn run_pipeline( + &mut self, + pipeline: &mut OpPassManager, + op: OperationRef, + ) -> Result<(), Report> { + if let Some(pipeline_executor) = self.pipeline_executor.as_deref_mut() { + pipeline_executor(pipeline, op) + } else { + Ok(()) + } + } +} diff --git a/hir/src/pass/registry.rs b/hir/src/pass/registry.rs new file mode 100644 index 000000000..212c64a70 --- /dev/null +++ b/hir/src/pass/registry.rs @@ -0,0 +1,397 @@ +use alloc::{boxed::Box, collections::BTreeMap, format, sync::Arc}; +use core::any::TypeId; + +use midenc_hir_symbol::sync::{LazyLock, RwLock}; +use midenc_session::diagnostics::DiagnosticsHandler; + +use super::*; +use crate::Report; + +static PASS_REGISTRY: LazyLock = LazyLock::new(PassRegistry::new); + +/// A global, thread-safe pass and pass pipeline registry +/// +/// You should generally _not_ need to work with this directly. +pub struct PassRegistry { + passes: RwLock>, + pipelines: RwLock>, +} +impl Default for PassRegistry { + fn default() -> Self { + Self::new() + } +} +impl PassRegistry { + /// Create a new [PassRegistry] instance. + pub fn new() -> Self { + let mut passes = BTreeMap::default(); + let mut pipelines = BTreeMap::default(); + for pass in inventory::iter::() { + passes.insert( + pass.0.arg, + PassRegistryEntry { + arg: pass.0.arg, + description: pass.0.description, + type_id: pass.0.type_id, + builder: Arc::clone(&pass.0.builder), + }, + ); + } + for pipeline in inventory::iter::() { + pipelines.insert( + pipeline.0.arg, + PassRegistryEntry { + arg: pipeline.0.arg, + description: pipeline.0.description, + type_id: pipeline.0.type_id, + builder: Arc::clone(&pipeline.0.builder), + }, + ); + } + + Self { + passes: RwLock::new(passes), + pipelines: RwLock::new(pipelines), + } + } + + /// Get the pass information for the pass whose argument name is `name` + pub fn get_pass(&self, name: &str) -> Option { + self.passes.read().get(name).cloned().map(PassInfo) + } + + /// Get the pass pipeline information for the pipeline whose argument name is `name` + pub fn get_pipeline(&self, name: &str) -> Option { + self.pipelines.read().get(name).cloned().map(PassPipelineInfo) + } + + /// Register the given pass + pub fn register_pass(&self, info: PassInfo) { + use alloc::collections::btree_map::Entry; + + let mut passes = self.passes.write(); + match passes.entry(info.argument()) { + Entry::Vacant(entry) => { + entry.insert(info.0); + } + Entry::Occupied(entry) => { + assert_eq!( + entry.get().type_id, + info.0.type_id, + "cannot register pass '{}': name already registered by a different type", + info.argument() + ); + } + } + } + + /// Register the given pass pipeline + pub fn register_pipeline(&self, info: PassPipelineInfo) { + use alloc::collections::btree_map::Entry; + + let mut pipelines = self.pipelines.write(); + match pipelines.entry(info.argument()) { + Entry::Vacant(entry) => { + entry.insert(info.0); + } + Entry::Occupied(entry) => { + assert_eq!( + entry.get().type_id, + info.0.type_id, + "cannot register pass pipeline '{}': name already registered by a different \ + type", + info.argument() + ); + assert!(Arc::ptr_eq(&entry.get().builder, &info.0.builder)) + } + } + } +} + +inventory::collect!(PassInfo); +inventory::collect!(PassPipelineInfo); + +/// A type alias for the closure type for registering a pass with a pass manager +pub type PassRegistryFunction = dyn Fn(&mut OpPassManager, &str, &DiagnosticsHandler) -> Result<(), Report> + + Send + + Sync + + 'static; + +/// A type alias for the closure type used for type-erased pass constructors +pub type PassAllocatorFunction = dyn Fn() -> Box; + +/// A [RegistryEntry] is a registered pass or pass pipeline. +/// +/// This trait provides the common functionality shared by both passes and pipelines. +pub trait RegistryEntry { + /// Returns the command-line option that may be passed to `midenc` that will cause this pass + /// or pass pipeline to run. + fn argument(&self) -> &'static str; + /// Return a description for the pass or pass pipeline. + fn description(&self) -> &'static str; + /// Adds this entry to the given pass manager. + /// + /// Note: `options` is an opaque string that will be parsed by the builder. + /// + /// Returns `Err` if an error occurred parsing the given options. + fn add_to_pipeline( + &self, + pm: &mut OpPassManager, + options: &str, + diagnostics: &DiagnosticsHandler, + ) -> Result<(), Report>; +} + +/// Information about a pass or pass pipeline in the pass registry +#[derive(Clone)] +struct PassRegistryEntry { + /// The name of the compiler option for referencing on the command line + arg: &'static str, + /// A description of the pass or pass pipeline + description: &'static str, + /// The type id of the concrete pass type + type_id: Option, + /// Function that registers this entry with a pass manager pipeline + builder: Arc, +} +impl RegistryEntry for PassRegistryEntry { + #[inline] + fn add_to_pipeline( + &self, + pm: &mut OpPassManager, + options: &str, + diagnostics: &DiagnosticsHandler, + ) -> Result<(), Report> { + (self.builder)(pm, options, diagnostics) + } + + #[inline(always)] + fn argument(&self) -> &'static str { + self.arg + } + + #[inline(always)] + fn description(&self) -> &'static str { + self.description + } +} + +/// Information about a registered pass pipeline +pub struct PassPipelineInfo(PassRegistryEntry); +impl PassPipelineInfo { + pub fn new(arg: &'static str, description: &'static str, builder: B) -> Self + where + B: Fn(&mut OpPassManager, &str, &DiagnosticsHandler) -> Result<(), Report> + + Send + + Sync + + 'static, + { + Self(PassRegistryEntry { + arg, + description, + type_id: None, + builder: Arc::new(builder), + }) + } + + /// Find the [PassInfo] for a registered pass pipeline named `name` + pub fn lookup(name: &str) -> Option { + PASS_REGISTRY.get_pipeline(name) + } +} +impl RegistryEntry for PassPipelineInfo { + fn argument(&self) -> &'static str { + self.0.argument() + } + + fn description(&self) -> &'static str { + self.0.description() + } + + fn add_to_pipeline( + &self, + pm: &mut OpPassManager, + options: &str, + diagnostics: &DiagnosticsHandler, + ) -> Result<(), Report> { + self.0.add_to_pipeline(pm, options, diagnostics) + } +} + +/// Information about a registered pass +pub struct PassInfo(PassRegistryEntry); +impl PassInfo { + /// Create a new [PassInfo] from the given argument name and description, for a default- + /// constructible pass type `P`. + pub fn new(arg: &'static str, description: &'static str) -> Self { + let type_id = TypeId::of::

(); + Self(PassRegistryEntry { + arg, + description, + type_id: Some(type_id), + builder: Arc::new(default_registration::

), + }) + } + + /// Find the [PassInfo] for a registered pass named `name` + pub fn lookup(name: &str) -> Option { + PASS_REGISTRY.get_pass(name) + } +} +impl RegistryEntry for PassInfo { + fn argument(&self) -> &'static str { + self.0.argument() + } + + fn description(&self) -> &'static str { + self.0.description() + } + + fn add_to_pipeline( + &self, + pm: &mut OpPassManager, + options: &str, + diagnostics: &DiagnosticsHandler, + ) -> Result<(), Report> { + self.0.add_to_pipeline(pm, options, diagnostics) + } +} + +/// Register a specific dialect pipeline registry function with the system. +/// +/// # Example +/// +/// If your pipeline implements the [Default] trait, you can just do: +/// +/// ```text,ignore +/// register_pass_pipeline( +/// "my-pipeline", +/// "A simple test pipeline", +/// default_registration::(), +/// ) +/// ``` +/// +/// Otherwise, you need to pass a factor function which will be used to construct fresh instances +/// of the pipeline: +/// +/// ```text,ignore +/// register_pass_pipeline( +/// "my-pipeline", +/// "A simple test pipeline", +/// default_dyn_registration(|| MyPipeline::new(MyPipelineOptions::default())), +/// ) +/// ``` +/// +/// NOTE: The functions/closures passed above are required to be `Send + Sync + 'static`, as they +/// are stored in the global registry for the lifetime of the program, and may be accessed from any +/// thread. +pub fn register_pass_pipeline(arg: &'static str, description: &'static str, builder: B) +where + B: Fn(&mut OpPassManager, &str, &DiagnosticsHandler) -> Result<(), Report> + + Send + + Sync + + 'static, +{ + PASS_REGISTRY.register_pipeline(PassPipelineInfo(PassRegistryEntry { + arg, + description, + type_id: None, + builder: Arc::new(builder), + })); +} + +/// Register a specific dialect pass allocator function with the system. +/// +/// # Example +/// +/// ```text,ignore +/// register_pass(|| MyPass::default()) +/// ``` +/// +/// NOTE: The allocator function provided is required to be `Send + Sync + 'static`, as it is +/// stored in the global registry for the lifetime of the program, and may be accessed from any +/// thread. +pub fn register_pass(ctor: impl Fn() -> Box + Send + Sync + 'static) { + let pass = ctor(); + let type_id = pass.as_any().type_id(); + let arg = pass.argument(); + assert!( + !arg.is_empty(), + "attempted to register pass '{}' without specifying an argument name", + pass.name() + ); + let description = pass.description(); + PASS_REGISTRY.register_pass(PassInfo(PassRegistryEntry { + arg, + description, + type_id: Some(type_id), + builder: Arc::new(default_registration_factory(ctor)), + })); +} + +/// A default implementation of a pass pipeline registration function. +/// +/// It expects that `P` (the type of the pass or pass pipeline), implements `Default`, so that an +/// instance is default-constructible. It then initializes the pass with the provided options, +/// validates that the pass/pipeline is valid for the parent pipeline, and adds it if so. +pub fn default_registration( + pm: &mut OpPassManager, + options: &str, + diagnostics: &DiagnosticsHandler, +) -> Result<(), Report> { + use midenc_session::diagnostics::Severity; + + let mut pass = Box::

::default() as Box; + let result = pass.initialize_options(options); + let pm_op_name = pm.name(); + let pass_op_name = pass.target_name(&pm.context()); + let pass_op_name = pass_op_name.as_ref(); + if matches!(pm.nesting(), Nesting::Explicit) && pm_op_name != pass_op_name { + return Err(diagnostics + .diagnostic(Severity::Error) + .with_message(format!( + "registration error for pass '{}': can't add pass restricted to '{}' on a pass \ + manager intended to run on '{}', did you intend to nest?", + pass.name(), + crate::formatter::DisplayOptional(pass_op_name.as_ref()), + crate::formatter::DisplayOptional(pm_op_name), + )) + .into_report()); + } + pm.add_pass(pass); + result +} + +/// Like [default_registration], but takes an arbitrary constructor in the form of a zero-arity +/// closure, rather than relying on [Default]. Thus, this is actually a registration function +/// _factory_, rather than a registration function itself. +pub fn default_registration_factory Box + Send + Sync + 'static>( + builder: B, +) -> impl Fn(&mut OpPassManager, &str, &DiagnosticsHandler) -> Result<(), Report> + Send + Sync + 'static +{ + use midenc_session::diagnostics::Severity; + move |pm: &mut OpPassManager, + options: &str, + diagnostics: &DiagnosticsHandler| + -> Result<(), Report> { + let mut pass = builder(); + let result = pass.initialize_options(options); + let pm_op_name = pm.name(); + let pass_op_name = pass.target_name(&pm.context()); + let pass_op_name = pass_op_name.as_ref(); + if matches!(pm.nesting(), Nesting::Explicit) && pm_op_name != pass_op_name { + return Err(diagnostics + .diagnostic(Severity::Error) + .with_message(format!( + "registration error for pass '{}': can't add pass restricted to '{}' on a \ + pass manager intended to run on '{}', did you intend to nest?", + pass.name(), + crate::formatter::DisplayOptional(pass_op_name.as_ref()), + crate::formatter::DisplayOptional(pm_op_name), + )) + .into_report()); + } + pm.add_pass(pass); + result + } +} diff --git a/hir/src/pass/rewrite.rs b/hir/src/pass/rewrite.rs deleted file mode 100644 index ffdcae5eb..000000000 --- a/hir/src/pass/rewrite.rs +++ /dev/null @@ -1,373 +0,0 @@ -use midenc_session::Session; - -use super::{AnalysisKey, AnalysisManager, PassInfo}; -use crate::diagnostics::Report; - -/// A convenient type alias for `Result<(), Report>` -pub type RewriteResult = Result<(), Report>; - -/// A convenient type alias for closures which can be used as rewrite passes -pub type RewriteFn = dyn FnMut(&mut T, &mut AnalysisManager, &Session) -> RewriteResult; - -/// This is a marker trait for [RewritePass] impls which also implement [PassInfo] -/// -/// It is automatically implemented for you. -pub trait RewritePassInfo: PassInfo + RewritePass {} -impl

RewritePassInfo for P where P: PassInfo + RewritePass {} - -/// A [RewritePass] is a pass which transforms/rewrites an entity without converting it to a -/// new representation. For conversions, see [crate::ConversionPass]. -/// -/// For example, a rewrite rule which applies a mangling scheme to function names, does not -/// change the representation of a function, it simply changes things about the existing -/// representation (e.g. the name in this example). -/// -/// A rewrite is given access to the current [AnalysisManager], which can be used to obtain -/// the results of some [Analysis] needed to perform the rewrite, as well as indicate to the -/// [AnalysisManager] which analyses are preserved by the rewrite, if any. -/// -/// Additionally, the current [midenc_session::Session] is provided, which can be used as a -/// source of configuration for the rewrite, if needed. -pub trait RewritePass { - /// The entity type to which this rewrite applies - type Entity: AnalysisKey; - - /// Returns true if this rewrite should be applied to `entity` - fn should_apply(&self, _entity: &Self::Entity, _session: &Session) -> bool { - true - } - - /// Apply this rewrite to `entity` - fn apply( - &mut self, - entity: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult; - - /// Apply this rewrite, then `next` as a pipeline of rewrites - fn chain(self, next: R) -> RewriteSet - where - Self: Sized + 'static, - R: RewritePass + 'static, - { - RewriteSet::pair(self, next) - } -} - -impl RewritePass for Box

-where - T: AnalysisKey, - P: RewritePass, -{ - type Entity = T; - - fn should_apply(&self, entity: &Self::Entity, session: &Session) -> bool { - (**self).should_apply(entity, session) - } - - fn apply( - &mut self, - entity: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - (**self).apply(entity, analyses, session) - } - - fn chain(self, next: R) -> RewriteSet - where - Self: Sized + 'static, - R: RewritePass + 'static, - { - let mut rewrites = RewriteSet::from(self); - rewrites.push(next); - rewrites - } -} -impl RewritePass for Box> -where - T: AnalysisKey, -{ - type Entity = T; - - #[inline] - fn apply( - &mut self, - entity: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - (**self).apply(entity, analyses, session) - } -} -impl RewritePass for Box RewriteResult> -where - T: AnalysisKey, -{ - type Entity = T; - - #[inline] - fn apply( - &mut self, - entity: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - self(entity, analyses, session) - } -} -impl RewritePass for dyn FnMut(&mut T, &mut AnalysisManager, &Session) -> RewriteResult -where - T: AnalysisKey, -{ - type Entity = T; - - #[inline] - fn apply( - &mut self, - entity: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - self(entity, analyses, session) - } -} - -/// This type is used to adapt function [RewritePass] to apply against a module. -/// -/// When this is applied to a module, all functions in the module will be rewritten. -pub struct ModuleRewritePassAdapter(R); -impl Default for ModuleRewritePassAdapter -where - R: RewritePass + Default, -{ - fn default() -> Self { - Self(R::default()) - } -} -impl ModuleRewritePassAdapter -where - R: RewritePass, -{ - /// Adapt `R` to run against all functions in a [crate::Module] - pub const fn new(pass: R) -> Self { - Self(pass) - } -} -impl PassInfo for ModuleRewritePassAdapter { - const DESCRIPTION: &'static str = ::DESCRIPTION; - const FLAG: &'static str = ::FLAG; - const SUMMARY: &'static str = ::SUMMARY; -} -impl RewritePass for ModuleRewritePassAdapter -where - R: RewritePass, -{ - type Entity = crate::Module; - - fn apply( - &mut self, - module: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - // Removing a function via this cursor will move the cursor to - // the next function in the module. Once the end of the module - // is reached, the cursor will point to the null object, and - // `remove` will return `None`. - let mut cursor = module.cursor_mut(); - let mut dirty = false; - while let Some(mut function) = cursor.remove() { - // Apply rewrite - if self.0.should_apply(&function, session) { - dirty = true; - self.0.apply(&mut function, analyses, session)?; - analyses.invalidate::(&function.id); - } - - // Add the function back to the module - // - // We add it before the current position of the cursor - // to ensure that we don't interfere with our traversal - // of the module top to bottom - cursor.insert_before(function); - } - - if !dirty { - analyses.mark_all_preserved::(&module.name); - } - - Ok(()) - } -} - -/// A [RewriteSet] is used to compose two or more [RewritePass] impls for the same entity type, -/// to be applied as a single, fused [RewritePass]. -pub struct RewriteSet { - rewrites: Vec>>, -} -impl Default for RewriteSet { - fn default() -> Self { - Self { rewrites: vec![] } - } -} -impl RewriteSet -where - T: AnalysisKey, -{ - /// Create a new [RewriteSet] from a pair of [RewritePass] - pub fn pair(a: A, b: B) -> Self - where - A: RewritePass + 'static, - B: RewritePass + 'static, - { - Self { - rewrites: vec![Box::new(a), Box::new(b)], - } - } - - /// Append a new [RewritePass] to this set - pub fn push(&mut self, rewrite: R) - where - R: RewritePass + 'static, - { - self.rewrites.push(Box::new(rewrite)); - } - - /// Take all rewrites out of another [RewriteSet], and append them to this set - pub fn append(&mut self, other: &mut Self) { - self.rewrites.append(&mut other.rewrites); - } - - /// Extend this rewrite set with rewrites from `iter` - pub fn extend(&mut self, iter: impl IntoIterator>>) { - self.rewrites.extend(iter); - } -} -impl IntoIterator for RewriteSet -where - T: AnalysisKey, -{ - type IntoIter = alloc::vec::IntoIter; - type Item = Box>; - - #[inline] - fn into_iter(self) -> Self::IntoIter { - self.rewrites.into_iter() - } -} -impl From>> for RewriteSet -where - T: AnalysisKey, -{ - fn from(rewrite: Box>) -> Self { - Self { - rewrites: vec![rewrite], - } - } -} -impl + 'static> From> for RewriteSet -where - T: AnalysisKey, -{ - fn from(rewrite: Box) -> Self { - Self { - rewrites: vec![rewrite], - } - } -} -impl RewritePass for RewriteSet -where - T: AnalysisKey, -{ - type Entity = T; - - fn apply( - &mut self, - entity: &mut Self::Entity, - analyses: &mut AnalysisManager, - session: &Session, - ) -> RewriteResult { - for pass in self.rewrites.iter_mut() { - // Skip the rewrite if it shouldn't be applied - if !pass.should_apply(entity, session) { - continue; - } - - // Apply the rewrite - pass.apply(entity, analyses, session)?; - // Invalidate all analyses that were not marked preserved by `pass` - analyses.invalidate::(&entity.key()); - } - - Ok(()) - } - - fn chain(mut self, next: R) -> RewriteSet - where - Self: Sized + 'static, - R: RewritePass + 'static, - { - self.push(next); - self - } -} - -#[doc(hidden)] -pub struct RewritePassRegistration { - pub name: &'static str, - pub summary: &'static str, - pub description: &'static str, - ctor: fn() -> Box>, -} -impl RewritePassRegistration { - pub const fn new

() -> Self - where - P: RewritePass + PassInfo + Default + 'static, - { - Self { - name:

::FLAG, - summary:

::SUMMARY, - description:

::DESCRIPTION, - ctor: dyn_rewrite_pass_ctor::

, - } - } - - /// Get the name of the registered pass - #[inline] - pub const fn name(&self) -> &'static str { - self.name - } - - /// Get a summary of the registered pass - #[inline] - pub const fn summary(&self) -> &'static str { - self.summary - } - - /// Get a rich description of the registered pass - #[inline] - pub const fn description(&self) -> &'static str { - self.description - } - - /// Get an instance of the registered pass - #[inline] - pub fn get(&self) -> Box> { - (self.ctor)() - } -} - -fn dyn_rewrite_pass_ctor

() -> Box::Entity>> -where - P: RewritePass + Default + 'static, -{ - Box::

::default() -} - -// Register rewrite passes for modules -inventory::collect!(RewritePassRegistration); - -// Register rewrite passes for functions -inventory::collect!(RewritePassRegistration); diff --git a/hir/src/pass/specialization.rs b/hir/src/pass/specialization.rs new file mode 100644 index 000000000..502d08376 --- /dev/null +++ b/hir/src/pass/specialization.rs @@ -0,0 +1,152 @@ +use crate::{ + traits::BranchOpInterface, Context, EntityMut, EntityRef, Op, Operation, OperationName, + OperationRef, Symbol, SymbolTable, +}; + +pub trait PassTarget { + fn target_name(context: &Context) -> Option; + fn into_target(op: &OperationRef) -> EntityRef<'_, Self>; + fn into_target_mut(op: &mut OperationRef) -> EntityMut<'_, Self>; +} + +impl PassTarget for T { + default fn target_name(_context: &Context) -> Option { + None + } + + #[inline] + #[track_caller] + default fn into_target(op: &OperationRef) -> EntityRef<'_, T> { + EntityRef::map(op.borrow(), |t| { + t.downcast_ref::().unwrap_or_else(|| expected_type::(op)) + }) + } + + #[inline] + #[track_caller] + default fn into_target_mut(op: &mut OperationRef) -> EntityMut<'_, T> { + EntityMut::map(op.borrow_mut(), |t| { + t.downcast_mut::().unwrap_or_else(|| expected_type::(op)) + }) + } +} +impl PassTarget for Operation { + #[inline(always)] + fn target_name(_context: &Context) -> Option { + None + } + + #[inline] + #[track_caller] + fn into_target(op: &OperationRef) -> EntityRef<'_, Operation> { + op.borrow() + } + + #[inline] + #[track_caller] + fn into_target_mut(op: &mut OperationRef) -> EntityMut<'_, Operation> { + op.borrow_mut() + } +} +impl PassTarget for dyn Op { + #[inline(always)] + fn target_name(_context: &Context) -> Option { + None + } + + fn into_target(op: &OperationRef) -> EntityRef<'_, dyn Op> { + EntityRef::map(op.borrow(), |op| op.as_trait::().unwrap()) + } + + fn into_target_mut(op: &mut OperationRef) -> EntityMut<'_, dyn Op> { + EntityMut::map(op.borrow_mut(), |op| op.as_trait_mut::().unwrap()) + } +} +impl PassTarget for dyn BranchOpInterface { + #[inline(always)] + fn target_name(_context: &Context) -> Option { + None + } + + #[track_caller] + fn into_target(op: &OperationRef) -> EntityRef<'_, dyn BranchOpInterface> { + EntityRef::map(op.borrow(), |t| { + t.as_trait::() + .unwrap_or_else(|| expected_implementation::(op)) + }) + } + + #[track_caller] + fn into_target_mut(op: &mut OperationRef) -> EntityMut<'_, dyn BranchOpInterface> { + EntityMut::map(op.borrow_mut(), |t| { + t.as_trait_mut::() + .unwrap_or_else(|| expected_implementation::(op)) + }) + } +} +impl PassTarget for dyn Symbol { + #[inline(always)] + fn target_name(_context: &Context) -> Option { + None + } + + #[track_caller] + fn into_target(op: &OperationRef) -> EntityRef<'_, dyn Symbol> { + EntityRef::map(op.borrow(), |t| { + t.as_trait::() + .unwrap_or_else(|| expected_implementation::(op)) + }) + } + + #[track_caller] + fn into_target_mut(op: &mut OperationRef) -> EntityMut<'_, dyn Symbol> { + EntityMut::map(op.borrow_mut(), |t| { + t.as_trait_mut::() + .unwrap_or_else(|| expected_implementation::(op)) + }) + } +} +impl PassTarget for dyn SymbolTable + 'static { + #[inline(always)] + fn target_name(_context: &Context) -> Option { + None + } + + #[track_caller] + fn into_target(op: &OperationRef) -> EntityRef<'_, dyn SymbolTable + 'static> { + EntityRef::map(op.borrow(), |t| { + t.as_trait::() + .unwrap_or_else(|| expected_implementation::(op)) + }) + } + + #[track_caller] + fn into_target_mut(op: &mut OperationRef) -> EntityMut<'_, dyn SymbolTable + 'static> { + EntityMut::map(op.borrow_mut(), |t| { + t.as_trait_mut::() + .unwrap_or_else(|| expected_implementation::(op)) + }) + } +} + +#[cold] +#[inline(never)] +#[track_caller] +fn expected_type(op: &OperationRef) -> ! { + panic!( + "expected operation '{}' to be a `{}`", + op.borrow().name(), + core::any::type_name::(), + ) +} + +#[cold] +#[inline(never)] +#[track_caller] +fn expected_implementation(op: &OperationRef) -> ! { + panic!( + "expected '{}' to implement `{}`, but no vtable was found", + op.borrow().name(), + core::any::type_name::() + ) +} diff --git a/hir/src/pass/statistics.rs b/hir/src/pass/statistics.rs new file mode 100644 index 000000000..4bbe212c3 --- /dev/null +++ b/hir/src/pass/statistics.rs @@ -0,0 +1,463 @@ +use alloc::{boxed::Box, format, vec::Vec}; +use core::{any::Any, fmt}; + +use compact_str::CompactString; + +use crate::Report; + +/// A [Statistic] represents some stateful datapoint collected by and across passes. +/// +/// Statistics are named, have a description, and have a value. The value can be pretty printed, +/// and multiple instances of the same statistic can be merged together. +#[derive(Clone)] +pub struct PassStatistic { + pub name: CompactString, + pub description: CompactString, + pub value: V, +} +impl PassStatistic +where + V: StatisticValue, +{ + pub fn new(name: CompactString, description: CompactString, value: V) -> Self { + Self { + name, + description, + value, + } + } +} +impl Eq for PassStatistic {} +impl PartialEq for PassStatistic { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} +impl PartialOrd for PassStatistic { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for PassStatistic { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.name.cmp(&other.name) + } +} +impl Statistic for PassStatistic +where + V: Clone + StatisticValue + 'static, +{ + fn name(&self) -> &str { + &self.name + } + + fn description(&self) -> &str { + &self.description + } + + fn pretty_print(&self) -> crate::formatter::Document { + self.value.pretty_print() + } + + fn try_merge(&mut self, other: &mut dyn Any) -> Result<(), Report> { + let lhs = &mut self.value; + if let Some(rhs) = other.downcast_mut::<::Value>() { + lhs.merge(rhs); + Ok(()) + } else { + let name = &self.name; + let expected_ty = core::any::type_name::<::Value>(); + Err(Report::msg(format!( + "could not merge statistic '{name}': expected value of type '{expected_ty}', but \ + got a value of some other type" + ))) + } + } + + fn clone(&self) -> Box { + use core::clone::CloneToUninit; + let mut this = Box::::new_uninit(); + unsafe { + self.clone_to_uninit(this.as_mut_ptr().cast()); + this.assume_init() + } + } +} + +/// An abstraction over statistics that allows operating generically over statistics with different +/// types of values. +pub trait Statistic { + /// The display name of this statistic + fn name(&self) -> &str; + /// A description of what this statistic means and why it is significant + fn description(&self) -> &str; + /// Pretty prints this statistic as a value + fn pretty_print(&self) -> crate::formatter::Document; + /// Merges another instance of this statistic into this one, given a mutable reference to the + /// raw underlying value of the other instance. + /// + /// Returns `Err` if `other` is not a valid value type for this statistic + fn try_merge(&mut self, other: &mut dyn Any) -> Result<(), Report>; + /// Clones the underlying statistic + fn clone(&self) -> Box; +} + +pub trait StatisticValue { + type Value: Any + Clone; + + fn value(&self) -> &Self::Value; + fn value_mut(&mut self) -> &mut Self::Value; + fn value_as_any(&self) -> &dyn Any { + self.value() as &dyn Any + } + fn value_as_any_mut(&mut self) -> &mut dyn Any { + self.value_mut() as &mut dyn Any + } + fn expected_type(&self) -> &'static str { + core::any::type_name::<::Value>() + } + fn merge(&mut self, other: &mut Self::Value); + fn pretty_print(&self) -> crate::formatter::Document; +} + +impl dyn StatisticValue { + pub fn downcast_ref(&self) -> Option<&T> { + self.value_as_any().downcast_ref::() + } + + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.value_as_any_mut().downcast_mut::() + } +} + +/// Merges via OR +impl StatisticValue for bool { + type Value = bool; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + *self |= *other; + } + + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(*self) + } +} + +/// A boolean flag which evalutates to true, only if all observed values are false. +/// +/// Defaults to false, and merges by OR. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +pub struct FlagNone(bool); +impl From for bool { + #[inline(always)] + fn from(flag: FlagNone) -> Self { + !flag.0 + } +} +impl StatisticValue for FlagNone { + type Value = FlagNone; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + if !self.0 && !other.0 { + self.0 = true; + } else { + self.0 ^= other.0 + } + } + + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(bool::from(*self)) + } +} + +/// A boolean flag which evaluates to true, only if at least one true value was observed. +/// +/// Defaults to false, and merges by OR. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FlagAny(bool); +impl From for bool { + #[inline(always)] + fn from(flag: FlagAny) -> Self { + flag.0 + } +} +impl StatisticValue for FlagAny { + type Value = FlagAny; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + self.0 |= other.0; + } + + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(bool::from(*self)) + } +} + +/// A boolean flag which evaluates to true, only if all observed values were true. +/// +/// Defaults to true, and merges by AND. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct FlagAll(bool); +impl From for bool { + #[inline(always)] + fn from(flag: FlagAll) -> Self { + flag.0 + } +} +impl StatisticValue for FlagAll { + type Value = FlagAll; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + self.0 &= other.0 + } + + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(bool::from(*self)) + } +} + +macro_rules! numeric_statistic { + (#[cfg $($args:tt)*] $int_ty:ty) => { + /// Adds two numbers by saturating addition + #[cfg $($args)*] + impl StatisticValue for $int_ty { + type Value = $int_ty; + fn value(&self) -> &Self::Value { self } + fn value_mut(&mut self) -> &mut Self::Value { self } + fn merge(&mut self, other: &mut Self::Value) { + *self = self.saturating_add(*other); + } + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(*self) + } + } + }; + + (#[cfg $($args:tt)*] $int_ty:ty as $wrapper_ty:ty) => { + /// Adds two numbers by saturating addition + #[cfg $($args)*] + impl StatisticValue for $int_ty { + type Value = $int_ty; + fn value(&self) -> &Self::Value { self } + fn value_mut(&mut self) -> &mut Self::Value { self } + fn merge(&mut self, other: &mut Self::Value) { + *self = self.saturating_add(*other); + } + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(<$wrapper_ty>::from(*self)) + } + } + }; + + ($int_ty:ty) => { + /// Adds two numbers by saturating addition + impl StatisticValue for $int_ty { + type Value = $int_ty; + fn value(&self) -> &Self::Value { self } + fn value_mut(&mut self) -> &mut Self::Value { self } + fn merge(&mut self, other: &mut Self::Value) { + *self = self.saturating_add(*other); + } + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(*self) + } + } + } +} + +numeric_statistic!(u8); +numeric_statistic!(i8); +numeric_statistic!(u16); +numeric_statistic!(i16); +numeric_statistic!(u32); +numeric_statistic!(i32); +numeric_statistic!(u64); +numeric_statistic!(i64); +numeric_statistic!(usize); +numeric_statistic!(isize); +numeric_statistic!( + #[cfg(feature = "std")] + std::time::Duration as midenc_session::HumanDuration +); +numeric_statistic!( + #[cfg(feature = "std")] + midenc_session::HumanDuration +); + +impl StatisticValue for f64 { + type Value = f64; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + *self += *other; + } + + fn pretty_print(&self) -> crate::formatter::Document { + crate::formatter::display(*self) + } +} + +/// Merges an array of statistic values element-wise +impl StatisticValue for [T; N] +where + T: Any + StatisticValue + Clone, +{ + type Value = [T; N]; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + for index in 0..N { + self[index].merge(other[index].value_mut()); + } + } + + fn pretty_print(&self) -> crate::formatter::Document { + use crate::formatter::const_text; + + let doc = const_text("["); + self.iter().enumerate().fold(doc, |mut doc, (i, item)| { + if i > 0 { + doc += const_text(", "); + } + doc + item.pretty_print() + }) + const_text("]") + } +} + +/// Merges two vectors of statistics by appending +impl StatisticValue for Vec +where + T: Any + StatisticValue + Clone, +{ + type Value = Vec; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + self.append(other); + } + + fn pretty_print(&self) -> crate::formatter::Document { + use crate::formatter::const_text; + + let doc = const_text("["); + self.iter().enumerate().fold(doc, |mut doc, (i, item)| { + if i > 0 { + doc += const_text(", "); + } + doc + item.pretty_print() + }) + const_text("]") + } +} + +/// Merges two maps of statistics by merging values of identical keys, and appending missing keys +impl StatisticValue for alloc::collections::BTreeMap +where + K: Ord + Clone + fmt::Display + 'static, + V: Any + StatisticValue + Clone, +{ + type Value = alloc::collections::BTreeMap; + + fn value(&self) -> &Self::Value { + self + } + + fn value_mut(&mut self) -> &mut Self::Value { + self + } + + fn merge(&mut self, other: &mut Self::Value) { + use alloc::collections::btree_map::Entry; + + while let Some((k, mut v)) = other.pop_first() { + match self.entry(k) { + Entry::Vacant(entry) => { + entry.insert(v); + } + Entry::Occupied(mut entry) => { + entry.get_mut().merge(v.value_mut()); + } + } + } + } + + fn pretty_print(&self) -> crate::formatter::Document { + use crate::formatter::{const_text, indent, nl, text, Document}; + if self.is_empty() { + const_text("{}") + } else { + let single_line = const_text("{") + + self.iter().enumerate().fold(Document::Empty, |mut doc, (i, (k, v))| { + if i > 0 { + doc += const_text(", "); + } + doc + text(format!("{k}: ")) + v.pretty_print() + }) + + const_text("}"); + let multi_line = const_text("{") + + indent( + 4, + self.iter().enumerate().fold(nl(), |mut doc, (i, (k, v))| { + if i > 0 { + doc += const_text(",") + nl(); + } + doc + text(format!("{k}: ")) + v.pretty_print() + }) + nl(), + ) + + const_text("}"); + single_line | multi_line + } + } +} diff --git a/hir/src/patterns.rs b/hir/src/patterns.rs new file mode 100644 index 000000000..b70d11cc9 --- /dev/null +++ b/hir/src/patterns.rs @@ -0,0 +1,13 @@ +mod applicator; +mod driver; +mod pattern; +mod pattern_set; +mod rewriter; + +pub use self::{ + applicator::{PatternApplicationError, PatternApplicator}, + driver::*, + pattern::*, + pattern_set::{FrozenRewritePatternSet, RewritePatternSet}, + rewriter::*, +}; diff --git a/hir/src/patterns/applicator.rs b/hir/src/patterns/applicator.rs new file mode 100644 index 000000000..f11114ed7 --- /dev/null +++ b/hir/src/patterns/applicator.rs @@ -0,0 +1,226 @@ +use alloc::{collections::BTreeMap, rc::Rc}; + +use smallvec::SmallVec; + +use super::{FrozenRewritePatternSet, PatternBenefit, RewritePattern, Rewriter}; +use crate::{OperationName, OperationRef, ProgramPoint, Report}; + +pub enum PatternApplicationError { + NoMatchesFound, + Report(Report), +} + +/// This type manages the application of a group of rewrite patterns, with a user-provided cost model +pub struct PatternApplicator { + /// The list that owns the patterns used within this applicator + rewrite_patterns_set: Rc, + /// The set of patterns to match for each operation, stable sorted by benefit. + patterns: BTreeMap; 2]>>, + /// The set of patterns that may match against any operation type, stable sorted by benefit. + match_any_patterns: SmallVec<[Rc; 1]>, +} +impl PatternApplicator { + pub fn new(rewrite_patterns_set: Rc) -> Self { + Self { + rewrite_patterns_set, + patterns: Default::default(), + match_any_patterns: Default::default(), + } + } + + /// Apply a cost model to the patterns within this applicator. + pub fn apply_cost_model(&mut self, model: CostModel) + where + CostModel: Fn(&dyn RewritePattern) -> PatternBenefit, + { + // Clear the results computed by the previous cost model + self.match_any_patterns.clear(); + self.patterns.clear(); + + // Filter out op-specific patterns with no benefit, and order by highest benefit first + let mut benefits = SmallVec::<[_; 4]>::default(); + for (op, op_patterns) in self.rewrite_patterns_set.op_specific_patterns().iter() { + benefits + .extend(op_patterns.iter().filter_map(|p| filter_map_pattern_benefit(p, &model))); + benefits.sort_by_key(|(_, benefit)| *benefit); + self.patterns + .insert(op.clone(), benefits.drain(..).map(|(pat, _)| pat).collect()); + } + + // Filter out "match any" patterns with no benefit, and order by highest benefit first + benefits.extend( + self.rewrite_patterns_set + .any_op_patterns() + .iter() + .filter_map(|p| filter_map_pattern_benefit(p, &model)), + ); + benefits.sort_by_key(|(_, benefit)| *benefit); + self.match_any_patterns.extend(benefits.into_iter().map(|(pat, _)| pat)); + } + + /// Apply the default cost model that solely uses the pattern's static benefit + #[inline] + pub fn apply_default_cost_model(&mut self) { + log::debug!(target: "pattern-rewrite-driver", "applying default cost model"); + self.apply_cost_model(|pattern| *pattern.benefit()); + } + + /// Walk all of the patterns within the applicator. + pub fn walk_all_patterns(&self, mut callback: F) + where + F: FnMut(Rc), + { + for patterns in self.rewrite_patterns_set.op_specific_patterns().values() { + for pattern in patterns { + callback(Rc::clone(pattern)); + } + } + for pattern in self.rewrite_patterns_set.any_op_patterns() { + callback(Rc::clone(pattern)); + } + } + + pub fn match_and_rewrite( + &mut self, + op: OperationRef, + rewriter: &mut R, + can_apply: A, + mut on_failure: F, + mut on_success: S, + ) -> Result<(), PatternApplicationError> + where + A: for<'a> Fn(&'a dyn RewritePattern) -> bool, + F: for<'a> FnMut(&'a dyn RewritePattern), + S: for<'a> FnMut(&'a dyn RewritePattern) -> Result<(), Report>, + R: Rewriter, + { + // Check to see if there are patterns matching this specific operation type. + let op_name = { + let op = op.borrow(); + op.name() + }; + let op_specific_patterns = self.patterns.get(&op_name).map(|p| p.as_slice()).unwrap_or(&[]); + + if op_specific_patterns.is_empty() { + log::trace!(target: "pattern-rewrite-driver", "no op-specific patterns found for '{op_name}'"); + } else { + log::trace!( + target: "pattern-rewrite-driver", + "found {} op-specific patterns for '{op_name}'", + op_specific_patterns.len() + ); + } + + log::trace!(target: "pattern-rewrite-driver", "{} op-agnostic patterns available", self.match_any_patterns.len()); + + // Process the op-specific patterns and op-agnostic patterns in an interleaved fashion + let mut op_patterns = op_specific_patterns.iter().peekable(); + let mut any_op_patterns = self.match_any_patterns.iter().peekable(); + let mut result = Err(PatternApplicationError::NoMatchesFound); + loop { + // Find the next pattern with the highest benefit + // + // 1. Start with the assumption that we'll use the next op-specific pattern + let mut best_pattern = op_patterns.peek().copied(); + // 2. But take the next op-agnostic pattern instead, IF: + // a. There are no more op-specific patterns + // b. The benefit of the op-agnostic pattern is higher than the op-specific pattern + if let Some(next_any_pattern) = any_op_patterns + .next_if(|p| best_pattern.is_none_or(|bp| bp.benefit() < p.benefit())) + { + if let Some(best_pattern) = best_pattern { + log::trace!( + target: "pattern-rewrite-driver", + "selected op-agnostic pattern '{}' because its benefit is higher than the \ + next best op-specific pattern '{}'", + next_any_pattern.name(), + best_pattern.name() + ); + } else { + log::trace!( + target: "pattern-rewrite-driver", + "selected op-agnostic pattern '{}' because no op-specific pattern is \ + available", + next_any_pattern.name() + ); + } + best_pattern.replace(next_any_pattern); + } else { + // The op-specific pattern is best, if available, so actually consume it from the iterator + if let Some(best_pattern) = best_pattern { + log::trace!(target: "pattern-rewrite-driver", "selected op-specific pattern '{}'", best_pattern.name()); + } + best_pattern = op_patterns.next(); + } + + // Break if we have exhausted all patterns + let Some(best_pattern) = best_pattern else { + log::trace!(target: "pattern-rewrite-driver", "all patterns have been exhausted"); + break; + }; + + // Can we apply this pattern? + let applicable = can_apply(&**best_pattern); + if !applicable { + log::trace!(target: "pattern-rewrite-driver", "skipping pattern: can_apply returned false"); + continue; + } + + // Try to match and rewrite this pattern. + // + // The patterns are sorted by benefit, so if we match we can immediately rewrite. + rewriter.set_insertion_point(ProgramPoint::before(op)); + + // TODO: Save the nearest parent IsolatedFromAbove op of this op for use in debug + // messages/rendering, as the rewrite may invalidate `op` + log::debug!(target: "pattern-rewrite-driver", "trying to match '{}'", best_pattern.name()); + + match best_pattern.match_and_rewrite(op, rewriter) { + Ok(matched) => { + if matched { + log::trace!(target: "pattern-rewrite-driver", "pattern matched successfully"); + result = + on_success(&**best_pattern).map_err(PatternApplicationError::Report); + break; + } else { + log::trace!(target: "pattern-rewrite-driver", "failed to match pattern"); + on_failure(&**best_pattern); + } + } + Err(err) => { + log::error!(target: "pattern-rewrite-driver", "error occurred during match_and_rewrite: {err}"); + result = Err(PatternApplicationError::Report(err)); + on_failure(&**best_pattern); + } + } + } + + result + } +} + +fn filter_map_pattern_benefit( + pattern: &Rc, + cost_model: &CostModel, +) -> Option<(Rc, PatternBenefit)> +where + CostModel: Fn(&dyn RewritePattern) -> PatternBenefit, +{ + let benefit = if pattern.benefit().is_impossible_to_match() { + PatternBenefit::NONE + } else { + cost_model(&**pattern) + }; + if benefit.is_impossible_to_match() { + log::debug!( + target: "pattern-rewrite-driver", + "ignoring pattern '{}' ({}) because it is impossible to match or cannot lead to legal \ + IR (by cost model)", + pattern.name(), + pattern.kind(), + ); + None + } else { + Some((Rc::clone(pattern), benefit)) + } +} diff --git a/hir/src/patterns/driver.rs b/hir/src/patterns/driver.rs new file mode 100644 index 000000000..901f7f572 --- /dev/null +++ b/hir/src/patterns/driver.rs @@ -0,0 +1,1057 @@ +use alloc::{rc::Rc, vec::Vec}; +use core::cell::RefCell; + +use smallvec::SmallVec; + +use super::{ + ForwardingListener, FrozenRewritePatternSet, PatternApplicator, PatternRewriter, Rewriter, + RewriterListener, +}; +use crate::{ + adt::SmallSet, + patterns::{PatternApplicationError, RewritePattern}, + traits::{ConstantLike, Foldable, IsolatedFromAbove}, + AttrPrinter, BlockRef, Builder, Context, Forward, InsertionGuard, Listener, OpFoldResult, + OperationFolder, OperationRef, ProgramPoint, RawWalk, Region, RegionRef, Report, SourceSpan, + Spanned, Value, ValueRef, WalkResult, +}; + +/// Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the +/// highest benefit patterns in a greedy worklist driven manner until a fixpoint is reached. +/// +/// The greedy rewrite may prematurely stop after a maximum number of iterations, which can be +/// configured using [GreedyRewriteConfig]. +/// +/// This function also performs folding and simple dead-code elimination before attempting to match +/// any of the provided patterns. +/// +/// A region scope can be set using [GreedyRewriteConfig]. By default, the scope is set to the +/// specified region. Only in-scope ops are added to the worklist and only in-scope ops are allowed +/// to be modified by the patterns. +/// +/// Returns `Ok(changed)` if the iterative process converged (i.e., fixpoint was reached) and no +/// more patterns can be matched within the region. The `changed` flag is set to `true` if the IR +/// was modified at all. +/// +/// NOTE: This function does not apply patterns to the region's parent operation. +pub fn apply_patterns_and_fold_region_greedily( + region: RegionRef, + patterns: Rc, + mut config: GreedyRewriteConfig, +) -> Result { + // The top-level operation must be known to be isolated from above to prevent performing + // canonicalizations on operations defined at or above the region containing 'op'. + let context = { + let parent_op = region.parent().unwrap().borrow(); + assert!( + parent_op.implements::(), + "patterns can only be applied to operations which are isolated from above" + ); + parent_op.context_rc() + }; + + // Set scope if not specified + if config.scope.is_none() { + config.scope = Some(region); + } + + let mut driver = RegionPatternRewriteDriver::new(context, patterns, config, region); + let converged = driver.simplify(); + if converged.is_err() { + if let Some(max_iterations) = driver.driver.config.max_iterations { + log::trace!(target: "pattern-rewrite-driver", "pattern rewrite did not converge after scanning {max_iterations} times"); + } else { + log::trace!(target: "pattern-rewrite-driver", "pattern rewrite did not converge"); + } + } + converged +} + +/// Rewrite ops nested under the given operation, which must be isolated from above, by repeatedly +/// applying the highest benefit patterns in a greedy worklist driven manner until a fixpoint is +/// reached. +/// +/// The greedy rewrite may prematurely stop after a maximum number of iterations, which can be +/// configured using [GreedyRewriteConfig]. +/// +/// Also performs folding and simple dead-code elimination before attempting to match any of the +/// provided patterns. +/// +/// This overload runs a separate greedy rewrite for each region of the specified op. A region +/// scope can be set in the configuration parameter. By default, the scope is set to the region of +/// the current greedy rewrite. Only in-scope ops are added to the worklist and only in-scope ops +/// and the specified op itself are allowed to be modified by the patterns. +/// +/// NOTE: The specified op may be modified, but it may not be removed by the patterns. +/// +/// Returns `Ok(changed)` if the iterative process converged (i.e., fixpoint was reached) and no +/// more patterns can be matched within the region. The `changed` flag is set to `true` if the IR +/// was modified at all. +/// +/// NOTE: This function does not apply patterns to the given operation itself. +pub fn apply_patterns_and_fold_greedily( + op: OperationRef, + patterns: Rc, + config: GreedyRewriteConfig, +) -> Result { + let mut any_region_changed = false; + let mut failed = false; + let op = op.borrow(); + let mut cursor = op.regions().front(); + while let Some(region) = cursor.as_pointer() { + cursor.move_next(); + match apply_patterns_and_fold_region_greedily(region, patterns.clone(), config.clone()) { + Ok(region_changed) => { + any_region_changed |= region_changed; + } + Err(region_changed) => { + any_region_changed |= region_changed; + failed = true; + } + } + } + + if failed { + Err(any_region_changed) + } else { + Ok(any_region_changed) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum ApplyPatternsAndFoldEffect { + /// No effect, the IR remains unchanged + None, + /// The IR was modified + Changed, + /// The input IR was erased + Erased, +} + +pub type ApplyPatternsAndFoldResult = + Result; + +/// Rewrite the specified ops by repeatedly applying the highest benefit patterns in a greedy +/// worklist driven manner until a fixpoint is reached. +/// +/// The greedy rewrite may prematurely stop after a maximum number of iterations, which can be +/// configured using [GreedyRewriteConfig]. +/// +/// This function also performs folding and simple dead-code elimination before attempting to match +/// any of the provided patterns. +/// +/// Newly created ops and other pre-existing ops that use results of rewritten ops or supply +/// operands to such ops are also processed, unless such ops are excluded via `config.restrict`. +/// Any other ops remain unmodified (i.e., regardless of restrictions). +/// +/// In addition to op restrictions, a region scope can be specified. Only ops within the scope are +/// simplified. This is similar to [apply_patterns_and_fold_greedily], where only ops within the +/// given region/op are simplified by default. If no scope is specified, it is assumed to be the +/// first common enclosing region of the given ops. +/// +/// Note that ops in `ops` could be erased as result of folding, becoming dead, or via pattern +/// rewrites. If more far reaching simplification is desired, [apply_patterns_and_fold_greedily] +/// should be used. +/// +/// Returns `Ok(effect)` if the iterative process converged (i.e., fixpoint was reached) and no more +/// patterns can be matched. `effect` is set to `Changed` if the IR was modified, but at least one +/// operation was not erased. It is set to `Erased` if all of the input ops were erased. +pub fn apply_patterns_and_fold( + ops: &[OperationRef], + patterns: Rc, + mut config: GreedyRewriteConfig, +) -> ApplyPatternsAndFoldResult { + if ops.is_empty() { + return Ok(ApplyPatternsAndFoldEffect::None); + } + + // Determine scope of rewrite + if let Some(scope) = config.scope.as_ref() { + // If a scope was provided, make sure that all ops are in scope. + let all_ops_in_scope = ops.iter().all(|op| scope.borrow().find_ancestor_op(*op).is_some()); + assert!(all_ops_in_scope, "ops must be within the specified scope"); + } else { + // Compute scope if none was provided. The scope will remain `None` if there is a top-level + // op among `ops`. + config.scope = Region::find_common_ancestor(ops); + } + + // Start the pattern driver + let max_rewrites = config.max_rewrites.map(|max| max.get()).unwrap_or(u32::MAX); + let context = ops[0].borrow().context_rc(); + let mut driver = MultiOpPatternRewriteDriver::new(context, patterns, config, ops); + let converged = driver.simplify(ops); + let changed = match converged.as_ref() { + Ok(changed) | Err(changed) => *changed, + }; + let erased = driver.inner.surviving_ops.borrow().is_empty(); + let effect = if erased { + ApplyPatternsAndFoldEffect::Erased + } else if changed { + ApplyPatternsAndFoldEffect::Changed + } else { + ApplyPatternsAndFoldEffect::None + }; + if converged.is_ok() { + Ok(effect) + } else { + log::trace!(target: "pattern-rewrite-driver", "pattern rewrite did not converge after {max_rewrites} rewrites"); + Err(effect) + } +} + +/// This enum indicates which ops are put on the worklist during a greedy pattern rewrite +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +pub enum GreedyRewriteStrictness { + /// No restrictions on which ops are processed. + #[default] + Any, + /// Only pre-existing and newly created ops are processed. + /// + /// Pre-existing ops are those that were on the worklist at the very beginning. + ExistingAndNew, + /// Only pre-existing ops are processed. + /// + /// Pre-existing ops are those that were on the worklist at the very beginning. + Existing, +} + +/// This enum indicates the level of simplification to be applied to regions during a greedy +/// pattern rewrite. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +pub enum RegionSimplificationLevel { + /// Disable simplification. + None, + /// Perform basic simplifications (e.g. dead argument elimination) + #[default] + Normal, + /// Perform additional complex/expensive simplifications (e.g. block merging) + Aggressive, +} + +/// Configuration for [GreedyPatternRewriteDriver] +#[derive(Clone)] +pub struct GreedyRewriteConfig { + listener: Option>, + /// If set, only ops within the given region are added to the worklist. + /// + /// If no scope is specified, and no specific region is given when starting the greedy rewrite, + /// then the closest enclosing region of the initial list of operations is used. + scope: Option, + /// If set, specifies the maximum number of times the rewriter will iterate between applying + /// patterns and simplifying regions. + /// + /// NOTE: Only applicable when simplifying entire regions. + max_iterations: Option, + /// If set, specifies the maximum number of rewrites within an iteration. + max_rewrites: Option, + /// Perform control flow optimizations to the region tree after applying all patterns. + /// + /// NOTE: Only applicable when simplifying entire regions. + region_simplification: RegionSimplificationLevel, + /// The restrictions to apply, if any, to operations added to the worklist during the rewrite. + restrict: GreedyRewriteStrictness, + /// This flag specifies the order of initial traversal that populates the rewriter worklist. + /// + /// When true, operations are visited top-down, which is generally more efficient in terms of + /// compilation time. + /// + /// When false, the initial traversal of the region tree is bottom up on each block, which may + /// match larger patterns when given an ambiguous pattern set. + /// + /// NOTE: Only applicable when simplifying entire regions. + use_top_down_traversal: bool, +} +impl Default for GreedyRewriteConfig { + fn default() -> Self { + Self { + listener: None, + scope: None, + max_iterations: core::num::NonZeroU32::new(10), + max_rewrites: None, + region_simplification: Default::default(), + restrict: Default::default(), + use_top_down_traversal: false, + } + } +} +impl GreedyRewriteConfig { + pub fn new_with_listener(listener: impl RewriterListener + 'static) -> Self { + Self { + listener: Some(Rc::new(listener)), + ..Default::default() + } + } + + /// Scope rewrites to operations within `region` + pub fn with_scope(&mut self, region: RegionRef) -> &mut Self { + self.scope = Some(region); + self + } + + /// Set the maximum number of times the rewriter will iterate between applying patterns and + /// simplifying regions. + /// + /// If `0` is given, the number of iterations is unlimited. + /// + /// NOTE: Only applicable when simplifying entire regions. + pub fn with_max_iterations(&mut self, max: u32) -> &mut Self { + self.max_iterations = core::num::NonZeroU32::new(max); + self + } + + /// Set the maximum number of rewrites per iteration. + /// + /// If `0` is given, the number of rewrites is unlimited. + /// + /// NOTE: Only applicable when simplifying entire regions. + pub fn with_max_rewrites(&mut self, max: u32) -> &mut Self { + self.max_rewrites = core::num::NonZeroU32::new(max); + self + } + + /// Set the level of control flow optimizations to apply to the region tree. + /// + /// NOTE: Only applicable when simplifying entire regions. + pub fn with_region_simplification_level( + &mut self, + level: RegionSimplificationLevel, + ) -> &mut Self { + self.region_simplification = level; + self + } + + /// Set the level of restriction to apply to operations added to the worklist during the rewrite. + pub fn with_restrictions(&mut self, level: GreedyRewriteStrictness) -> &mut Self { + self.restrict = level; + self + } + + /// Specify whether or not to use a top-down traversal when initially adding operations to the + /// worklist. + pub fn with_top_down_traversal(&mut self, yes: bool) -> &mut Self { + self.use_top_down_traversal = yes; + self + } + + #[inline] + pub fn scope(&self) -> Option { + self.scope + } + + #[inline] + pub fn max_iterations(&self) -> Option { + self.max_iterations + } + + #[inline] + pub fn max_rewrites(&self) -> Option { + self.max_rewrites + } + + #[inline] + pub fn region_simplification_level(&self) -> RegionSimplificationLevel { + self.region_simplification + } + + #[inline] + pub fn strictness(&self) -> GreedyRewriteStrictness { + self.restrict + } + + #[inline] + pub fn use_top_down_traversal(&self) -> bool { + self.use_top_down_traversal + } +} + +pub struct GreedyPatternRewriteDriver { + context: Rc, + worklist: RefCell, + config: GreedyRewriteConfig, + /// Not maintained when `config.restrict` is `GreedyRewriteStrictness::Any` + filtered_ops: RefCell>, + matcher: RefCell, +} + +impl GreedyPatternRewriteDriver { + pub fn new( + context: Rc, + patterns: Rc, + config: GreedyRewriteConfig, + ) -> Self { + // Apply a simple cost model based solely on pattern benefit + let mut matcher = PatternApplicator::new(patterns); + matcher.apply_default_cost_model(); + + Self { + context, + worklist: Default::default(), + config, + filtered_ops: Default::default(), + matcher: RefCell::new(matcher), + } + } +} + +/// Worklist Managment +impl GreedyPatternRewriteDriver { + /// Add the given operation to the worklist + pub fn add_single_op_to_worklist(&self, op: OperationRef) { + if matches!(self.config.restrict, GreedyRewriteStrictness::Any) + || self.filtered_ops.borrow().contains(&op) + { + log::trace!(target: "pattern-rewrite-driver", "adding single op '{}' to worklist", op.name()); + self.worklist.borrow_mut().push(op); + } else { + log::trace!( + target: "pattern-rewrite-driver", "skipped adding single op '{}' to worklist due to strictness level", + op.name() + ); + } + } + + /// Add the given operation, and its ancestors, to the worklist + pub fn add_to_worklist(&self, op: OperationRef) { + // Gather potential ancestors while looking for a `scope` parent region + let mut ancestors = SmallVec::<[OperationRef; 8]>::default(); + let mut op = Some(op); + while let Some(ancestor_op) = op.take() { + let region = ancestor_op.grandparent(); + if self.config.scope.as_ref() == region.as_ref() { + ancestors.push(ancestor_op); + for op in ancestors { + self.add_single_op_to_worklist(op); + } + return; + } else { + log::trace!(target: "pattern-rewrite-driver", "gathering ancestors of '{}' for worklist", ancestor_op.name()); + ancestors.push(ancestor_op); + } + if let Some(region) = region { + op = region.parent(); + } else { + log::trace!(target: "pattern-rewrite-driver", "reached top level op while searching for ancestors"); + } + } + } + + /// Process operations until the worklist is empty, or `config.max_rewrites` is reached. + /// + /// Returns true if the IR was changed. + pub fn process_worklist(self: Rc) -> bool { + log::debug!(target: "pattern-rewrite-driver", "starting processing of greedy pattern rewrite driver worklist"); + let mut rewriter = + PatternRewriter::new_with_listener(self.context.clone(), Rc::clone(&self)); + + let mut changed = false; + let mut num_rewrites = 0u32; + while self.config.max_rewrites.is_none_or(|max| num_rewrites < max.get()) { + let Some(op) = self.worklist.borrow_mut().pop() else { + // Worklist is empty, we've converged + log::debug!(target: "pattern-rewrite-driver", "processing worklist complete, rewrites have converged"); + return changed; + }; + + if self.process_worklist_item(&mut rewriter, op) { + changed = true; + num_rewrites += 1; + } + } + + log::debug!( + target: "pattern-rewrite-driver", "processing worklist was canceled after {} rewrites without converging (reached max \ + rewrite limit)", + self.config.max_rewrites.map(|max| max.get()).unwrap_or(u32::MAX) + ); + + changed + } + + /// Process a single operation from the worklist. + /// + /// Returns true if the IR was changed. + fn process_worklist_item( + &self, + rewriter: &mut PatternRewriter>, + mut op_ref: OperationRef, + ) -> bool { + let op = op_ref.borrow(); + + log::trace!(target: "pattern-rewrite-driver", "processing operation '{op}'"); + + // If the operation is trivially dead - remove it. + if op.is_trivially_dead() { + drop(op); + rewriter.erase_op(op_ref); + log::trace!(target: "pattern-rewrite-driver", "processing complete: operation is trivially dead"); + return true; + } + + // Try to fold this op, unless it is a constant op, as that would lead to an infinite + // folding loop, since the folded result would be immediately materialized as a constant + // op, and then revisited. + if !op.implements::() { + // Re-borrow mutably since we're going to try and rewrite `op` now + drop(op); + let op = op_ref.borrow_mut(); + + let mut results = SmallVec::<[OpFoldResult; 1]>::default(); + log::trace!(target: "pattern-rewrite-driver", "attempting to fold operation.."); + if op.fold(&mut results).is_ok() { + if results.is_empty() { + // Op was modified in-place + self.notify_operation_modified(op_ref); + log::trace!( + target: "pattern-rewrite-driver", + "operation was succesfully folded/modified in-place" + ); + return true; + } else { + log::trace!( + target: "pattern-rewrite-driver", + "operation was succesfully folded away, to be replaced with: {}", + results.as_slice().print(&crate::OpPrintingFlags::default(), op.context()) + ); + } + + // Op results can be replaced with `results` + assert_eq!( + results.len(), + op.num_results(), + "folder produced incorrect number of results" + ); + let mut rewriter = InsertionGuard::new(&mut **rewriter); + rewriter.set_insertion_point(ProgramPoint::before(op_ref)); + + log::trace!(target: "pattern-rewrite-driver", "replacing op with fold results.."); + let mut replacements = SmallVec::<[Option; 2]>::default(); + let mut materialization_succeeded = true; + for (fold_result, result_ty) in results + .into_iter() + .zip(op.results().all().iter().map(|r| r.borrow().ty().clone())) + { + match fold_result { + OpFoldResult::Value(value) => { + assert_eq!( + value.borrow().ty(), + &result_ty, + "folder produced value of incorrect type" + ); + replacements.push(Some(value)); + } + OpFoldResult::Attribute(attr) => { + // Materialize attributes as SSA values using a constant op + let span = op.span(); + log::trace!( + target: "pattern-rewrite-driver", + "materializing constant for value '{}' and type '{result_ty}'", + attr.print(&crate::OpPrintingFlags::default(), op.context()) + ); + let constant_op = op.dialect().materialize_constant( + &mut *rewriter, + attr, + &result_ty, + span, + ); + match constant_op { + None => { + log::trace!( + target: "pattern-rewrite-driver", + "materialization failed: cleaning up any materialized ops \ + for {} previous results", + replacements.len() + ); + // If materialization fails, clean up any operations generated for the previous results + let mut replacement_ops = + SmallVec::<[OperationRef; 2]>::default(); + for replacement in replacements.iter().filter_map(|repl| *repl) + { + let replacement = replacement.borrow(); + assert!( + !replacement.is_used(), + "folder reused existing op for one result, but \ + constant materialization failed for another result" + ); + let replacement_op = replacement.get_defining_op().unwrap(); + if replacement_ops.contains(&replacement_op) { + continue; + } + replacement_ops.push(replacement_op); + } + for replacement_op in replacement_ops { + rewriter.erase_op(replacement_op); + } + materialization_succeeded = false; + break; + } + Some(constant_op) => { + let const_op = constant_op.borrow(); + assert!( + const_op.implements::(), + "materialize_constant produced op that does not implement \ + ConstantLike" + ); + let result: ValueRef = const_op.results().all()[0].upcast(); + assert_eq!( + result.borrow().ty(), + &result_ty, + "materialize_constant produced incorrect result type" + ); + log::trace!( + target: "pattern-rewrite-driver", + "successfully materialized constant as {}", + result.borrow().id() + ); + replacements.push(Some(result)); + } + } + } + } + } + + if materialization_succeeded { + log::trace!( + target: "pattern-rewrite-driver", + "materialization of fold results was successful, performing replacement.." + ); + drop(op); + rewriter.replace_op_with_values(op_ref, &replacements); + log::trace!( + target: "pattern-rewrite-driver", + "fold succeeded: operation was replaced with materialized constants" + ); + return true; + } else { + log::trace!( + target: "pattern-rewrite-driver", + "materialization of fold results failed, proceeding without folding" + ); + } + } + } else { + log::trace!(target: "pattern-rewrite-driver", "operation could not be folded"); + } + + // Try to match one of the patterns. + // + // The rewriter is automatically notified of any necessary changes, so there is nothing + // else to do here. + // TODO(pauls): if self.config.listener.is_some() { + // + // We need to trigger `notify_pattern_begin` in `can_apply`, and `notify_pattern_end` + // in `on_failure` and `on_success`, but we can't have multiple mutable aliases of + // the listener captured by these closures. + // + // This is another aspect of the listener infra that needs to be handled + log::trace!(target: "pattern-rewrite-driver", "attempting to match and rewrite one of the input patterns.."); + let result = if let Some(listener) = self.config.listener.as_deref() { + let op_name = op_ref.borrow().name(); + let can_apply = |pattern: &dyn RewritePattern| { + log::trace!(target: "pattern-rewrite-driver", "applying pattern {} to op {}", pattern.name(), &op_name); + listener.notify_pattern_begin(pattern, op_ref); + true + }; + let on_failure = |pattern: &dyn RewritePattern| { + log::trace!(target: "pattern-rewrite-driver", "pattern failed to match"); + listener.notify_pattern_end(pattern, false); + }; + let on_success = |pattern: &dyn RewritePattern| { + log::trace!(target: "pattern-rewrite-driver", "pattern applied successfully"); + listener.notify_pattern_end(pattern, true); + Ok(()) + }; + self.matcher.borrow_mut().match_and_rewrite( + op_ref, + &mut **rewriter, + can_apply, + on_failure, + on_success, + ) + } else { + self.matcher.borrow_mut().match_and_rewrite( + op_ref, + &mut **rewriter, + |_| true, + |_| {}, + |_| Ok(()), + ) + }; + + match result { + Ok(_) => { + log::trace!(target: "pattern-rewrite-driver", "processing complete: pattern matched and operation was rewritten"); + true + } + Err(PatternApplicationError::NoMatchesFound) => { + log::debug!(target: "pattern-rewrite-driver", "processing complete: exhausted all patterns without finding a match"); + false + } + Err(PatternApplicationError::Report(report)) => { + log::debug!( + target: "pattern-rewrite-driver", "processing complete: error occurred during match and rewrite: {report}" + ); + false + } + } + } + + /// Look over the operands of the provided op for any defining operations that should be re- + /// added to the worklist. This function should be called when an operation is modified or + /// removed, as it may trigger further simplifications. + fn add_operands_to_worklist(&self, op: OperationRef) { + let current_op = op.borrow(); + for operand in current_op.operands().all() { + // If this operand currently has at most 2 users, add its defining op to the worklist. + // After the op is deleted, then the operand will have at most 1 user left. If it has + // 0 users left, it can be deleted as well, and if it has 1 user left, there may be + // further canonicalization opportunities. + let operand = operand.borrow(); + let Some(def_op) = operand.value().get_defining_op() else { + continue; + }; + + let mut other_user = None; + let mut has_more_than_two_uses = false; + for user in operand.value().iter_uses() { + if user.owner == op || other_user.as_ref().is_some_and(|ou| ou == &user.owner) { + continue; + } + if other_user.is_none() { + other_user = Some(user.owner); + continue; + } + has_more_than_two_uses = true; + break; + } + if !has_more_than_two_uses { + self.add_to_worklist(def_op); + } + } + } +} + +/// Notifications +impl Listener for GreedyPatternRewriteDriver { + fn kind(&self) -> crate::ListenerType { + crate::ListenerType::Rewriter + } + + /// Notify the driver that the given block was inserted + fn notify_block_inserted( + &self, + block: crate::BlockRef, + prev: Option, + ip: Option, + ) { + if let Some(listener) = self.config.listener.as_deref() { + listener.notify_block_inserted(block, prev, ip); + } + } + + /// Notify the driver that the specified operation was inserted. + /// + /// Update the worklist as needed: the operation is enqueued depending on scope and strictness + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + if let Some(listener) = self.config.listener.as_deref() { + listener.notify_operation_inserted(op, prev); + } + if matches!(self.config.restrict, GreedyRewriteStrictness::ExistingAndNew) { + self.filtered_ops.borrow_mut().insert(op); + } + self.add_to_worklist(op); + } +} +impl RewriterListener for GreedyPatternRewriteDriver { + /// Notify the driver that the given block is about to be removed. + fn notify_block_erased(&self, block: BlockRef) { + if let Some(listener) = self.config.listener.as_deref() { + listener.notify_block_erased(block); + } + } + + /// Notify the driver that the sepcified operation may have been modified in-place. The + /// operation is added to the worklist. + fn notify_operation_modified(&self, op: OperationRef) { + if let Some(listener) = self.config.listener.as_deref() { + listener.notify_operation_modified(op); + } + self.add_to_worklist(op); + } + + /// Notify the driver that the specified operation was removed. + /// + /// Update the worklist as needed: the operation and its children are removed from the worklist + fn notify_operation_erased(&self, op: OperationRef) { + // Only ops that are within the configured scope are added to the worklist of the greedy + // pattern rewriter. + // + // A greedy pattern rewrite is not allowed to erase the parent op of the scope region, as + // that would break the worklist handling and some sanity checks. + if let Some(scope) = self.config.scope.as_ref() { + assert!( + scope.parent().is_some_and(|parent_op| parent_op != op), + "scope region must not be erased during greedy pattern rewrite" + ); + } + + if let Some(listener) = self.config.listener.as_deref() { + listener.notify_operation_erased(op); + } + + self.add_operands_to_worklist(op); + self.worklist.borrow_mut().remove(&op); + + if self.config.restrict != GreedyRewriteStrictness::Any { + self.filtered_ops.borrow_mut().remove(&op); + } + } + + /// Notify the driver that the specified operation was replaced. + /// + /// Update the worklist as needed: new users are enqueued + fn notify_operation_replaced_with_values( + &self, + op: OperationRef, + replacement: &[Option], + ) { + if let Some(listener) = self.config.listener.as_deref() { + listener.notify_operation_replaced_with_values(op, replacement); + } + } + + fn notify_match_failure(&self, span: SourceSpan, reason: Report) { + if let Some(listener) = self.config.listener.as_deref() { + listener.notify_match_failure(span, reason); + } + } +} + +pub struct RegionPatternRewriteDriver { + driver: Rc, + region: RegionRef, +} +impl RegionPatternRewriteDriver { + pub fn new( + context: Rc, + patterns: Rc, + config: GreedyRewriteConfig, + region: RegionRef, + ) -> Self { + let mut driver = GreedyPatternRewriteDriver::new(context, patterns, config); + // Populate strict mode ops, if applicable + if driver.config.restrict != GreedyRewriteStrictness::Any { + let filtered_ops = driver.filtered_ops.get_mut(); + region.raw_postwalk_all::(|op| { + filtered_ops.insert(op); + }); + } + Self { + driver: Rc::new(driver), + region, + } + } + + /// Simplify ops inside `self.region`, and simplify the region itself. + /// + /// Returns `Ok(changed)` if the transformation converged, with `changed` indicating whether or + /// not the IR was changed. Otherwise, `Err(changed)` is returned. + pub fn simplify(&mut self) -> Result { + use crate::matchers::Matcher; + + let mut continue_rewrites = false; + let mut iteration = 0; + + while self.driver.config.max_iterations.is_none_or(|max| iteration < max.get()) { + log::trace!(target: "pattern-rewrite-driver", "starting iteration {iteration} of region pattern rewrite driver"); + iteration += 1; + + // New iteration: start with an empty worklist + self.driver.worklist.borrow_mut().clear(); + + // `OperationFolder` CSE's constant ops (and may move them into parents regions to + // enable more aggressive CSE'ing). + let context = self.driver.context.clone(); + let mut folder = OperationFolder::new(context, Rc::clone(&self.driver)); + let mut insert_known_constant = |op: OperationRef| { + // Check for existing constants when populating the worklist. This avoids + // accidentally reversing the constant order during processing. + let operation = op.borrow(); + if let Some(const_value) = crate::matchers::constant().matches(&operation) { + drop(operation); + if !folder.insert_known_constant(op, Some(const_value)) { + return true; + } + } + false + }; + + if !self.driver.config.use_top_down_traversal { + // Add operations to the worklist in postorder. + log::trace!(target: "pattern-rewrite-driver", "adding operations in postorder"); + self.region.raw_postwalk_all::(|op| { + if !insert_known_constant(op) { + self.driver.add_to_worklist(op); + } + }); + } else { + // Add all nested operations to the worklist in preorder. + log::trace!(target: "pattern-rewrite-driver", "adding operations in preorder"); + self.region + .raw_prewalk::(|op| { + if !insert_known_constant(op) { + self.driver.add_to_worklist(op); + WalkResult::::Continue(()) + } else { + WalkResult::Skip + } + }) + .into_result() + .expect("unexpected error occurred while walking region"); + + // Reverse the list so our loop processes them in-order + self.driver.worklist.borrow_mut().reverse(); + } + + continue_rewrites = self.driver.clone().process_worklist(); + log::trace!( + target: "pattern-rewrite-driver", "processing of worklist for this iteration has completed, \ + changed={continue_rewrites}" + ); + + // After applying patterns, make sure that the CFG of each of the regions is kept up to + // date. + if self.driver.config.region_simplification != RegionSimplificationLevel::None { + let mut rewriter = PatternRewriter::new_with_listener( + self.driver.context.clone(), + Rc::clone(&self.driver), + ); + continue_rewrites |= Region::simplify_all( + &[self.region], + &mut *rewriter, + self.driver.config.region_simplification, + ) + .is_ok(); + } else { + log::debug!(target: "pattern-rewrite-driver", "region simplification was disabled, skipping simplification rewrites"); + } + + if !continue_rewrites { + log::trace!(target: "pattern-rewrite-driver", "region pattern rewrites have converged"); + break; + } + } + + // If `continue_rewrites` is false, then the rewrite converged, i.e. the IR wasn't changed + // in the last iteration. + if !continue_rewrites { + Ok(iteration > 1) + } else { + Err(iteration > 1) + } + } +} + +pub struct MultiOpPatternRewriteDriver { + driver: Rc, + inner: Rc, +} + +struct MultiOpPatternRewriteDriverImpl { + surviving_ops: RefCell>, +} + +impl MultiOpPatternRewriteDriver { + pub fn new( + context: Rc, + patterns: Rc, + mut config: GreedyRewriteConfig, + ops: &[OperationRef], + ) -> Self { + let surviving_ops = SmallSet::from_iter(ops.iter().copied()); + let inner = Rc::new(MultiOpPatternRewriteDriverImpl { + surviving_ops: RefCell::new(surviving_ops), + }); + let listener = Rc::new(ForwardingListener::new(config.listener.take(), Rc::clone(&inner))); + config.listener = Some(listener); + + let mut driver = GreedyPatternRewriteDriver::new(context.clone(), patterns, config); + if driver.config.restrict != GreedyRewriteStrictness::Any { + driver.filtered_ops.get_mut().extend(ops.iter().cloned()); + } + + Self { + driver: Rc::new(driver), + inner, + } + } + + pub fn simplify(&mut self, ops: &[OperationRef]) -> Result { + // Populate the initial worklist + for op in ops.iter().copied() { + self.driver.add_single_op_to_worklist(op); + } + + // Process ops on the worklist + let changed = self.driver.clone().process_worklist(); + if self.driver.worklist.borrow().is_empty() { + Ok(changed) + } else { + Err(changed) + } + } +} + +impl Listener for MultiOpPatternRewriteDriverImpl { + fn kind(&self) -> crate::ListenerType { + crate::ListenerType::Rewriter + } +} +impl RewriterListener for MultiOpPatternRewriteDriverImpl { + fn notify_operation_erased(&self, op: OperationRef) { + self.surviving_ops.borrow_mut().remove(&op); + } +} + +#[derive(Default)] +struct Worklist(Vec); +impl Worklist { + /// Clear all operations from the worklist + #[inline] + pub fn clear(&mut self) { + self.0.clear() + } + + /// Returns true if the worklist is empty + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Push an operation to the end of the worklist, unless it is already in the worklist. + pub fn push(&mut self, op: OperationRef) { + if self.0.contains(&op) { + return; + } + self.0.push(op); + } + + /// Pop the next operation from the worklist + #[inline] + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Remove `op` from the worklist + pub fn remove(&mut self, op: &OperationRef) { + if let Some(index) = self.0.iter().position(|o| o == op) { + self.0.remove(index); + } + } + + /// Reverse the worklist + pub fn reverse(&mut self) { + self.0.reverse(); + } +} diff --git a/hir/src/patterns/pattern.rs b/hir/src/patterns/pattern.rs new file mode 100644 index 000000000..277a93ae4 --- /dev/null +++ b/hir/src/patterns/pattern.rs @@ -0,0 +1,373 @@ +use alloc::rc::Rc; +use core::{any::TypeId, fmt}; + +use smallvec::SmallVec; + +use super::Rewriter; +use crate::{interner, Context, OperationName, OperationRef, Report}; + +#[derive(Debug)] +pub enum PatternKind { + /// The pattern root matches any operation + Any, + /// The pattern root matches a specific named operation + Operation(OperationName), + /// The pattern root matches a specific trait + Trait(TypeId), +} +impl fmt::Display for PatternKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Any => f.write_str("for any"), + Self::Operation(name) => write!(f, "for operation '{name}'"), + Self::Trait(_) => write!(f, "for trait"), + } + } +} + +/// Represents the benefit a pattern has. +/// +/// More beneficial patterns are preferred over those with lesser benefit, while patterns with no +/// benefit whatsoever can be discarded. +/// +/// This is used to evaluate which patterns to apply, and in what order. +#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct PatternBenefit(Option); +impl PatternBenefit { + /// Represents a pattern which is the most beneficial + pub const MAX: Self = Self(core::num::NonZeroU16::new(u16::MAX)); + /// Represents a pattern which is the least beneficial + pub const MIN: Self = Self(core::num::NonZeroU16::new(1)); + /// Represents a pattern which can never match, and thus should be discarded + pub const NONE: Self = Self(None); + + /// Create a new [PatternBenefit] from a raw [u16] value. + /// + /// A value of `u16::MAX` is treated as impossible to match, while values from `0..=65534` range + /// from the least beneficial to the most beneficial. + pub fn new(benefit: u16) -> Self { + if benefit == u16::MAX { + Self(None) + } else { + Self(core::num::NonZeroU16::new(benefit + 1)) + } + } + + /// Returns true if the pattern benefit indicates it can never match + #[inline] + pub fn is_impossible_to_match(&self) -> bool { + self.0.is_none() + } +} + +impl PartialOrd for PatternBenefit { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for PatternBenefit { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + use core::cmp::Ordering; + match (self.0, other.0) { + (None, None) => Ordering::Equal, + // Impossible to match is always last + (None, Some(_)) => Ordering::Greater, + (Some(_), None) => Ordering::Less, + // Benefits are ordered in reverse of integer order (higher benefit appears earlier) + (Some(a), Some(b)) => a.get().cmp(&b.get()).reverse(), + } + } +} + +pub trait Pattern { + fn info(&self) -> &PatternInfo; + /// A name used when printing diagnostics related to this pattern + #[inline(always)] + fn name(&self) -> &'static str { + self.info().name + } + /// The kind of value used to select candidate root operations for this pattern. + #[inline(always)] + fn kind(&self) -> &PatternKind { + &self.info().kind + } + /// Returns the benefit - the inverse of "cost" - of matching this pattern. + /// + /// The benefit of a [Pattern] is always static - rewrites that may have dynamic benefit can be + /// instantiated multiple times (different instances), for each benefit that they may return, + /// and be guarded by different match condition predicates. + #[inline(always)] + fn benefit(&self) -> &PatternBenefit { + &self.info().benefit + } + /// Returns true if this pattern is known to result in recursive application, i.e. this pattern + /// may generate IR that also matches this pattern, but is known to bound the recursion. This + /// signals to the rewrite driver that it is safe to apply this pattern recursively to the + /// generated IR. + #[inline(always)] + fn has_bounded_rewrite_recursion(&self) -> bool { + self.info().has_bounded_recursion + } + /// Return a list of operations that may be generated when rewriting an operation instance + /// with this pattern. + #[inline(always)] + fn generated_ops(&self) -> &[OperationName] { + &self.info().generated_ops + } + /// Return the root operation that this pattern matches. + /// + /// Patterns that can match multiple root types return `None` + #[inline(always)] + fn get_root_operation(&self) -> Option { + self.info().root_operation() + } + /// Return the trait id used to match the root operation of this pattern. + /// + /// If the pattern does not use a trait id for deciding the root match, this returns `None` + #[inline(always)] + fn get_root_trait(&self) -> Option { + self.info().get_root_trait() + } +} + +/// [PatternBase] describes all of the data related to a pattern, but does not express any actual +/// pattern logic, i.e. it is solely used for metadata about a pattern. +pub struct PatternInfo { + #[allow(unused)] + context: Rc, + name: &'static str, + kind: PatternKind, + #[allow(unused)] + labels: SmallVec<[interner::Symbol; 1]>, + benefit: PatternBenefit, + has_bounded_recursion: bool, + generated_ops: SmallVec<[OperationName; 0]>, +} + +impl PatternInfo { + /// Create a new [Pattern] from its component parts. + pub fn new( + context: Rc, + name: &'static str, + kind: PatternKind, + benefit: PatternBenefit, + ) -> Self { + Self { + context, + name, + kind, + labels: SmallVec::default(), + benefit, + has_bounded_recursion: false, + generated_ops: SmallVec::default(), + } + } + + /// Set whether or not this pattern has bounded rewrite recursion + #[inline(always)] + pub fn with_bounded_rewrite_recursion(&mut self, yes: bool) -> &mut Self { + self.has_bounded_recursion = yes; + self + } + + /// Return the root operation that this pattern matches. + /// + /// Patterns that can match multiple root types return `None` + pub fn root_operation(&self) -> Option { + match self.kind { + PatternKind::Operation(ref name) => Some(name.clone()), + _ => None, + } + } + + /// Return the trait id used to match the root operation of this pattern. + /// + /// If the pattern does not use a trait id for deciding the root match, this returns `None` + pub fn root_trait(&self) -> Option { + match self.kind { + PatternKind::Trait(type_id) => Some(type_id), + _ => None, + } + } +} + +impl Pattern for PatternInfo { + #[inline(always)] + fn info(&self) -> &PatternInfo { + self + } +} + +/// A [RewritePattern] represents two things: +/// +/// * A pattern which matches some IR that we're interested in, typically to replace with something +/// else. +/// * A rewrite which replaces IR that maches the pattern, with new IR, i.e. a DAG-to-DAG +/// replacement +pub trait RewritePattern: Pattern { + /// Attempt to match this pattern against the IR rooted at the specified operation, and rewrite + /// it if the match is successful. + /// + /// If applied, this rewrites the IR rooted at the matched operation, using the provided + /// [Rewriter] to generate new blocks and/or operations, or apply any modifications. + /// + /// If an unexpected error is encountered, i.e. an internal compiler error, it is emitted + /// through the normal diagnostic system, and the IR is left in a valid state. + fn match_and_rewrite( + &self, + op: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result; +} + +#[cfg(test)] +mod tests { + use alloc::{rc::Rc, string::ToString}; + + use pretty_assertions::{assert_eq, assert_str_eq}; + + use super::*; + use crate::{ + dialects::{builtin::*, test::*}, + patterns::*, + *, + }; + + /// In Miden, `n << 1` is vastly inferior to `n * 2` in cost, so reverse it + /// + /// NOTE: These two ops have slightly different semantics, a real implementation would have + /// to handle the edge cases. + struct ConvertShiftLeftBy1ToMultiply { + info: PatternInfo, + } + impl ConvertShiftLeftBy1ToMultiply { + pub fn new(context: Rc) -> Self { + let dialect = context.get_or_register_dialect::(); + let op_name = dialect.expect_registered_name::(); + let mut info = PatternInfo::new( + context, + "convert-shl1-to-mul2", + PatternKind::Operation(op_name), + PatternBenefit::new(1), + ); + info.with_bounded_rewrite_recursion(true); + Self { info } + } + } + impl Pattern for ConvertShiftLeftBy1ToMultiply { + fn info(&self) -> &PatternInfo { + &self.info + } + } + impl RewritePattern for ConvertShiftLeftBy1ToMultiply { + fn match_and_rewrite( + &self, + op: OperationRef, + rewriter: &mut dyn Rewriter, + ) -> Result { + use crate::matchers::{self, match_chain, match_op, MatchWith, Matcher}; + + let binder = MatchWith(|op: &UnsafeIntrusiveEntityRef| { + log::trace!( + "found matching 'hir.shl' operation, checking if `shift` operand is foldable" + ); + let op = op.borrow(); + let shift = op.shift().as_operand_ref(); + let matched = matchers::foldable_operand_of::().matches(&shift); + matched.and_then(|imm| { + log::trace!("`shift` operand is an immediate: {imm}"); + let imm = imm.as_u64(); + if imm.is_none() { + log::trace!("`shift` operand is not a valid u64 value"); + } + if imm.is_some_and(|imm| imm == 1) { + Some(()) + } else { + None + } + }) + }); + log::trace!("attempting to match '{}'", self.name()); + let matched = match_chain(match_op::(), binder).matches(&op.borrow()).is_some(); + log::trace!("'{}' matched: {matched}", self.name()); + + if !matched { + return Ok(false); + } + + log::trace!("found match, rewriting '{}'", op.borrow().name()); + let (span, lhs) = { + let shl = op.borrow(); + let shl = shl.downcast_ref::().unwrap(); + let span = shl.span(); + let lhs = shl.lhs().as_value_ref(); + (span, lhs) + }; + let constant_builder = rewriter.create::(span); + let constant: UnsafeIntrusiveEntityRef = + constant_builder(Immediate::U32(2)).unwrap(); + let shift = constant.borrow().result().as_value_ref(); + let mul_builder = rewriter.create::(span); + let mul = mul_builder(lhs, shift, Overflow::Wrapping).unwrap(); + let mul = mul.as_operation_ref(); + log::trace!("replacing shl with mul"); + rewriter.replace_op(op, mul); + + Ok(true) + } + } + + #[test] + fn rewrite_pattern_api_test() { + let mut builder = env_logger::Builder::from_env("MIDENC_TRACE"); + builder.init(); + + let context = Rc::new(Context::default()); + let pattern = ConvertShiftLeftBy1ToMultiply::new(Rc::clone(&context)); + + let mut builder = OpBuilder::new(Rc::clone(&context)); + let function = { + let builder = builder.create::(SourceSpan::default()); + let name = Ident::new("test".into(), SourceSpan::default()); + let signature = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); + builder(name, signature).unwrap() + }; + + // Define function body + { + let mut builder = FunctionBuilder::new(function, &mut builder); + let shift = builder.u32(1, SourceSpan::default()).unwrap(); + let block = builder.current_block(); + let lhs = block.borrow().arguments()[0].upcast(); + let result = builder.shl(lhs, shift, SourceSpan::default()).unwrap(); + builder.ret(Some(result), SourceSpan::default()).unwrap(); + } + + // Construct pattern set + let mut rewrites = RewritePatternSet::new(builder.context_rc()); + rewrites.push(pattern); + let rewrites = Rc::new(FrozenRewritePatternSet::new(rewrites)); + + // Execute pattern driver + let mut config = GreedyRewriteConfig::default(); + config.with_region_simplification_level(RegionSimplificationLevel::None); + let result = + apply_patterns_and_fold_greedily(function.as_operation_ref(), rewrites, config); + + // The rewrite should converge and modify the IR + assert_eq!(result, Ok(true)); + + // Confirm that the expected rewrite occurred + let func = function.borrow(); + let output = func.as_operation().to_string(); + let expected = "\ +public builtin.function @test(v0: u32) -> u32 { +^block0(v0: u32): + v3 = test.constant 2 : u32; + v4 = test.mul v0, v3 : u32 #[overflow = wrapping]; + builtin.ret v4; +};"; + assert_str_eq!(output.as_str(), expected); + } +} diff --git a/hir/src/patterns/pattern_set.rs b/hir/src/patterns/pattern_set.rs new file mode 100644 index 000000000..da72289c9 --- /dev/null +++ b/hir/src/patterns/pattern_set.rs @@ -0,0 +1,117 @@ +use alloc::{boxed::Box, collections::BTreeMap, rc::Rc, vec, vec::Vec}; + +use smallvec::SmallVec; + +use super::*; +use crate::{Context, OperationName}; + +pub struct RewritePatternSet { + context: Rc, + patterns: Vec>, +} +impl RewritePatternSet { + pub fn new(context: Rc) -> Self { + Self { + context, + patterns: vec![], + } + } + + pub fn from_iter

(context: Rc, patterns: P) -> Self + where + P: IntoIterator>, + { + Self { + context, + patterns: patterns.into_iter().collect(), + } + } + + #[inline] + pub fn context(&self) -> Rc { + Rc::clone(&self.context) + } + + #[inline] + pub fn patterns(&self) -> &[Box] { + &self.patterns + } + + pub fn push(&mut self, pattern: impl RewritePattern + 'static) { + self.patterns.push(Box::new(pattern)); + } + + pub fn extend

(&mut self, patterns: P) + where + P: IntoIterator>, + { + self.patterns.extend(patterns); + } +} + +pub struct FrozenRewritePatternSet { + context: Rc, + patterns: Vec>, + op_specific_patterns: BTreeMap; 2]>>, + any_op_patterns: SmallVec<[Rc; 1]>, +} +impl FrozenRewritePatternSet { + pub fn new(patterns: RewritePatternSet) -> Self { + let RewritePatternSet { context, patterns } = patterns; + let mut this = Self { + context, + patterns: Default::default(), + op_specific_patterns: Default::default(), + any_op_patterns: Default::default(), + }; + + for pattern in patterns { + let pattern = Rc::::from(pattern); + match pattern.kind() { + PatternKind::Operation(name) => { + this.op_specific_patterns + .entry(name.clone()) + .or_default() + .push(Rc::clone(&pattern)); + this.patterns.push(pattern); + } + PatternKind::Trait(ref trait_id) => { + for dialect in this.context.registered_dialects().values() { + for op in dialect.registered_ops().iter() { + if op.implements_trait_id(trait_id) { + this.op_specific_patterns + .entry(op.clone()) + .or_default() + .push(Rc::clone(&pattern)); + } + } + } + this.patterns.push(pattern); + } + PatternKind::Any => { + this.any_op_patterns.push(Rc::clone(&pattern)); + this.patterns.push(pattern); + } + } + } + + this + } + + #[inline] + pub fn patterns(&self) -> &[Rc] { + &self.patterns + } + + #[inline] + pub fn op_specific_patterns( + &self, + ) -> &BTreeMap; 2]>> { + &self.op_specific_patterns + } + + #[inline] + pub fn any_op_patterns(&self) -> &[Rc] { + &self.any_op_patterns + } +} diff --git a/hir/src/patterns/rewriter.rs b/hir/src/patterns/rewriter.rs new file mode 100644 index 000000000..05de52388 --- /dev/null +++ b/hir/src/patterns/rewriter.rs @@ -0,0 +1,1213 @@ +use alloc::{boxed::Box, format, rc::Rc}; +use core::ops::{Deref, DerefMut}; + +use smallvec::SmallVec; + +use crate::{ + patterns::Pattern, BlockRef, Builder, Context, InsertionGuard, Listener, ListenerType, + OpBuilder, OpOperandImpl, OperationRef, PostOrderBlockIter, ProgramPoint, RegionRef, Report, + SourceSpan, Usable, ValueRef, +}; + +/// A [Rewriter] is a [Builder] extended with additional functionality that is of primary use when +/// rewriting the IR after it is initially constructed. It is the basis on which the pattern +/// rewriter infrastructure is built. +pub trait Rewriter: Builder + RewriterListener { + /// Returns true if this rewriter has a listener attached. + /// + /// When no listener is present, fast paths can be taken when rewriting the IR, whereas a + /// listener requires breaking mutations up into individual actions so that the listener can + /// be made aware of all of them, in the order they occur. + fn has_listener(&self) -> bool; + + /// Replace the results of the given operation with the specified list of values (replacements). + /// + /// The result types of the given op and the replacements must match. The original op is erased. + fn replace_op_with_values(&mut self, op: OperationRef, values: &[Option]) { + assert_eq!(op.borrow().num_results(), values.len()); + + // Replace all result uses, notifies listener of the modifications + self.replace_all_op_uses_with_values(op, values); + + // Erase the op and notify the listener + self.erase_op(op); + } + + /// Replace the results of the given operation with the specified replacement op. + /// + /// The result types of the two ops must match. The original op is erased. + fn replace_op(&mut self, op: OperationRef, new_op: OperationRef) { + assert_eq!(op.borrow().num_results(), new_op.borrow().num_results()); + + // Replace all result uses, notifies listener of the modifications + self.replace_all_op_uses_with(op, new_op); + + // Erase the op and notify the listener + self.erase_op(op); + } + + /// This method erases an operation that is known to have no uses. + fn erase_op(&mut self, mut op: OperationRef) { + assert!(!op.borrow().is_used(), "expected op to have no uses"); + + // If no listener is attached, the op can be dropped all at once. + if !self.has_listener() { + op.borrow_mut().erase(); + return; + } + + // Helper function that erases a single operation + fn erase_single_op( + mut operation: OperationRef, + rewrite_listener: &mut R, + ) { + let op = operation.borrow(); + if cfg!(debug_assertions) { + // All nested ops should have been erased already + assert!(op.regions().iter().all(|r| r.is_empty()), "expected empty regions"); + // All users should have been erased already if the op is in a region with SSA dominance + if op.is_used() { + if let Some(region) = op.parent_region() { + assert!( + region.borrow().may_be_graph_region(), + "expected that op has no uses" + ); + } + } + } + + rewrite_listener.notify_operation_erased(operation); + + // Explicitly drop all uses in case the op is in a graph region + drop(op); + let mut op = operation.borrow_mut(); + op.drop_all_uses(); + op.erase(); + } + + // Nested ops must be erased one-by-one, so that listeners have a consistent view of the + // IR every time a notification is triggered. Users must be erased before definitions, i.e. + // in post-order, reverse dominance. + fn erase_tree(op: OperationRef, rewriter: &mut R) { + // Erase nested ops + let mut next_region = op.borrow().regions().front().as_pointer(); + while let Some(region) = next_region.take() { + next_region = region.next(); + // Erase all blocks in the right order. Successors should be erased before + // predecessors because successor blocks may use values defined in predecessor + // blocks. A post-order traversal of blocks within a region visits successors before + // predecessors. Repeat the traversal until the region is empty. (The block graph + // could be disconnected.) + let mut erased_blocks = SmallVec::<[BlockRef; 4]>::default(); + let mut region_entry = region.borrow().entry_block_ref(); + while let Some(entry) = region_entry.take() { + erased_blocks.clear(); + for block in PostOrderBlockIter::new(entry) { + let mut next_op = block.borrow().body().front().as_pointer(); + while let Some(op) = next_op.take() { + next_op = op.next(); + erase_tree(op, rewriter); + } + erased_blocks.push(block); + } + for mut block in erased_blocks.drain(..) { + // Explicitly drop all uses in case there is a cycle in the block + // graph. + for arg in block.borrow_mut().arguments_mut() { + arg.borrow_mut().uses_mut().clear(); + } + block.borrow_mut().drop_all_uses(); + rewriter.erase_block(block); + } + + region_entry = region.borrow().entry_block_ref(); + } + } + erase_single_op(op, rewriter); + } + + erase_tree(op, self); + } + + /// This method erases all operations in a block. + fn erase_block(&mut self, block: BlockRef) { + assert!(!block.borrow().is_used(), "expected 'block' to be unused"); + + let mut next_op = block.borrow().body().back().as_pointer(); + while let Some(op) = next_op.take() { + next_op = op.prev(); + assert!(!op.borrow().is_used(), "expected 'op' to be unused"); + self.erase_op(op); + } + + // Notify the listener that the block is about to be removed. + self.notify_block_erased(block); + + // Remove block from parent region + let mut region = block.parent().expect("expected 'block' to have a parent region"); + let mut region_mut = region.borrow_mut(); + let mut cursor = unsafe { region_mut.body_mut().cursor_mut_from_ptr(block) }; + cursor.remove(); + } + + /// Move the blocks that belong to `region` before the given insertion point in another region, + /// `ip`. The two regions must be different. The caller is responsible for creating or + /// updating the operation transferring flow of control to the region, and passing it the + /// correct block arguments. + fn inline_region_before(&mut self, mut region: RegionRef, mut ip: RegionRef) { + assert!(!RegionRef::ptr_eq(®ion, &ip), "cannot inline a region into itself"); + let region_body = region.borrow_mut().body_mut().take(); + if !self.has_listener() { + let mut parent_region = ip.borrow_mut(); + let parent_body = parent_region.body_mut(); + let mut cursor = parent_body.front_mut(); + cursor.splice_before(region_body); + } else { + // Move blocks from beginning of the region one-by-one + let ip = ip.borrow().entry_block_ref().unwrap(); + for block in region_body { + self.move_block_before(block, ip); + } + } + } + + /// Inline the operations of block `src` before the given insertion point in `dest`. + /// + /// If the insertion point is `None`, the block will be inlined at the end of the target block. + /// + /// The source block will be deleted and must have no uses. The `args` values, if provided, are + /// used to replace the block arguments of `src`, with `None` used to signal that an argument + /// should be ignored. + /// + /// If the source block is inserted at the end of the dest block, the dest block must have no + /// successors. Similarly, if the source block is inserted somewhere in the middle (or + /// beginning) of the dest block, the source block must have no successors. Otherwise, the + /// resulting IR would have unreachable operations. + fn inline_block_before( + &mut self, + mut src: BlockRef, + mut dest: BlockRef, + ip: Option, + args: &[Option], + ) { + assert!( + args.len() == src.borrow().num_arguments(), + "incorrect # of argument replacement values" + ); + + // The source block will be deleted, so it should not have any users (i.e., there should be + // no predecessors). + assert!(!src.borrow().has_predecessors(), "expected 'src' to have no predecessors"); + + // Ensure insertion point belongs to destination block if present + let insert_at_block_end = if let Some(ip) = ip { + let ip_block = ip.parent().expect("expected 'ip' to belong to a block"); + assert_eq!(ip_block, dest, "invalid insertion point: must be an op in 'dest'"); + ip.next().is_none() + } else { + true + }; + + if insert_at_block_end { + // The source block will be inserted at the end of the dest block, so the + // dest block should have no successors. Otherwise, the inserted operations + // will be unreachable. + assert!(!dest.borrow().has_successors(), "expected 'dest' to have no successors"); + } else { + // The source block will be inserted in the middle of the dest block, so + // the source block should have no successors. Otherwise, the remainder of + // the dest block would be unreachable. + assert!(!src.borrow().has_successors(), "expected 'src' to have no successors"); + } + + // Replace all of the successor arguments with the provided values. + for (arg, replacement) in src.borrow().arguments().iter().copied().zip(args.iter().copied()) + { + if let Some(replacement) = replacement { + self.replace_all_uses_of_value_with(arg.upcast(), replacement); + } + } + + // Move operations from the source block to the dest block and erase the source block. + if self.has_listener() { + let mut src_mut = src.borrow_mut(); + let mut src_cursor = src_mut.body_mut().front_mut(); + while let Some(op) = src_cursor.remove() { + if insert_at_block_end { + self.insert_op_at_end(op, dest); + } else { + self.insert_op_before(op, ip.unwrap()); + } + } + } else { + // Fast path: If no listener is attached, move all operations at once. + let mut dest_block = dest.borrow_mut(); + if let Some(ip) = ip { + dest_block.splice_block_before(&mut src.borrow_mut(), ip); + } else { + dest_block.splice_block(&mut src.borrow_mut()); + } + } + + // Erase the source block. + assert!(src.borrow().body().is_empty(), "expected 'src' to be empty"); + self.erase_block(src); + } + + /// Inline the operations of block `src` into the end of block `dest`. The source block will be + /// deleted and must have no uses. The `args` values, if present, are used to replace the block + /// arguments of `src`, where any `None` values are ignored. + /// + /// The dest block must have no successors. Otherwise, the resulting IR will have unreachable + /// operations. + fn merge_blocks(&mut self, src: BlockRef, dest: BlockRef, args: &[Option]) { + let ip = dest.borrow().body().back().as_pointer(); + self.inline_block_before(src, dest, ip, args); + } + + /// Split the operations starting at `ip` (inclusive) out of the given block into a new block, + /// and return it. + fn split_block(&mut self, mut block: BlockRef, ip: OperationRef) -> BlockRef { + // Fast path: if no listener is attached, split the block directly + if !self.has_listener() { + return block.borrow_mut().split_block(ip); + } + + assert_eq!( + block, + ip.parent().expect("expected 'ip' to be attached to a block"), + "expected 'ip' to be in 'block'" + ); + + let region = + block.parent().expect("cannot split a block which is not attached to a region"); + + // `create_block` sets the insertion point to the start of the new block + let mut guard = InsertionGuard::new(self); + let new_block = guard.create_block(region, Some(block), &[]); + + // If `ip` points to the end of the block, no ops should be moved + if ip.next().is_none() { + return new_block; + } + + // Move ops one-by-one from the end of `block` to the start of `new_block`. + // Stop when the operation pointed to by `ip` has been moved. + let mut block_mut = block.borrow_mut(); + let mut cursor = block_mut.body_mut().back_mut(); + let ip = new_block.borrow().body().front().as_pointer().unwrap(); + while let Some(op) = cursor.remove() { + let is_last_move = OperationRef::ptr_eq(&op, &ip); + guard.insert_op_before(op, ip); + if is_last_move { + break; + } + } + + new_block + } + + /// Unlink this block and insert it right before `ip`. + fn move_block_before(&mut self, mut block: BlockRef, ip: BlockRef) { + let current_region = block.parent(); + if current_region.is_none() { + block.borrow_mut().insert_before(ip); + } else { + block.borrow_mut().move_before(ip); + } + self.notify_block_inserted(block, current_region, Some(ip)); + } + + /// Unlink this operation from its current block and insert it right before `ip`, which + /// may be in the same or another block in the same function. + fn move_op_before(&mut self, mut op: OperationRef, ip: OperationRef) { + let prev = ProgramPoint::before(op); + op.borrow_mut().move_to(ProgramPoint::before(ip)); + self.notify_operation_inserted(op, prev); + } + + /// Unlink this operation from its current block and insert it right after `ip`, which may be + /// in the same or another block in the same function. + fn move_op_after(&mut self, mut op: OperationRef, ip: OperationRef) { + let prev = ProgramPoint::before(op); + op.borrow_mut().move_to(ProgramPoint::after(ip)); + self.notify_operation_inserted(op, prev); + } + + /// Unlink this operation from its current block and insert it at the end of `ip`. + fn move_op_to_end(&mut self, mut op: OperationRef, ip: BlockRef) { + let prev = ProgramPoint::before(op); + op.borrow_mut().move_to(ProgramPoint::at_end_of(ip)); + self.notify_operation_inserted(op, prev); + } + + /// Insert an unlinked operation right before `ip` + fn insert_op_before(&mut self, mut op: OperationRef, ip: OperationRef) { + let prev = ProgramPoint::before(op); + op.borrow_mut().as_operation_ref().insert_before(ip); + self.notify_operation_inserted(op, prev); + } + + /// Insert an unlinked operation right after `ip` + fn insert_op_after(&mut self, mut op: OperationRef, ip: OperationRef) { + let prev = ProgramPoint::before(op); + op.borrow_mut().as_operation_ref().insert_after(ip); + self.notify_operation_inserted(op, prev); + } + + /// Insert an unlinked operation at the end of `ip` + fn insert_op_at_end(&mut self, op: OperationRef, ip: BlockRef) { + let prev = ProgramPoint::before(op); + op.insert_at_end(ip); + self.notify_operation_inserted(op, prev); + } + + /// Find uses of `from` and replace them with `to`. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + fn replace_all_uses_of_value_with(&mut self, mut from: ValueRef, mut to: ValueRef) { + let mut from_val = from.borrow_mut(); + let from_uses = from_val.uses_mut(); + let mut cursor = from_uses.front_mut(); + while let Some(mut operand) = cursor.remove() { + let op = operand.borrow().owner; + self.notify_operation_modification_started(&op); + operand.borrow_mut().value = Some(to); + to.borrow_mut().insert_use(operand); + self.notify_operation_modified(op); + } + } + + /// Find uses of `from` and replace them with `to`. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + fn replace_all_uses_of_block_with(&mut self, mut from: BlockRef, mut to: BlockRef) { + let mut from_block = from.borrow_mut(); + let from_uses = from_block.uses_mut(); + let mut cursor = from_uses.front_mut(); + while let Some(operand) = cursor.remove() { + let op = operand.borrow().owner; + self.notify_operation_modification_started(&op); + to.borrow_mut().insert_use(operand); + self.notify_operation_modified(op); + } + } + + /// Find uses of `from` and replace them with `to`. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + fn replace_all_uses_with(&mut self, from: &[ValueRef], to: &[Option]) { + assert_eq!(from.len(), to.len(), "incorrect number of replacements"); + for (from, to) in from.iter().cloned().zip(to.iter().cloned()) { + if let Some(to) = to { + self.replace_all_uses_of_value_with(from, to); + } + } + } + + /// Find uses of `from` and replace them with `to`. + /// + /// Notifies the listener about every in-place modification (for every use that was replaced), + /// and that the `from` operation is about to be replaced. + fn replace_all_op_uses_with_values(&mut self, from: OperationRef, to: &[Option]) { + self.notify_operation_replaced_with_values(from, to); + + let results = from + .borrow() + .results() + .all() + .iter() + .copied() + .map(|result| result as ValueRef) + .collect::>(); + + self.replace_all_uses_with(&results, to); + } + + /// Find uses of `from` and replace them with `to`. + /// + /// Notifies the listener about every in-place modification (for every use that was replaced), + /// and that the `from` operation is about to be replaced. + fn replace_all_op_uses_with(&mut self, from: OperationRef, to: OperationRef) { + self.notify_operation_replaced(from, to); + + let from_results = from + .borrow() + .results() + .all() + .iter() + .copied() + .map(|result| result as ValueRef) + .collect::>(); + + let to_results = to + .borrow() + .results() + .all() + .iter() + .copied() + .map(|result| Some(result as ValueRef)) + .collect::; 2]>>(); + + self.replace_all_uses_with(&from_results, &to_results); + } + + /// Find uses of `from` within `block` and replace them with `to`. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + /// + /// Returns true if all uses were replaced, otherwise false. + fn replace_op_uses_within_block( + &mut self, + from: OperationRef, + to: &[ValueRef], + block: BlockRef, + ) -> bool { + let parent_op = block.grandparent(); + self.maybe_replace_op_uses_with(from, to, |operand| { + !parent_op + .as_ref() + .is_some_and(|op| op.borrow().is_proper_ancestor_of(&operand.owner.borrow())) + }) + } + + /// Find uses of `from` and replace them with `to`, except if the user is in `exceptions`. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + fn replace_all_uses_except( + &mut self, + from: ValueRef, + to: ValueRef, + exceptions: &[OperationRef], + ) { + self.maybe_replace_uses_of_value_with(from, to, |operand| { + !exceptions.contains(&operand.owner) + }); + } +} + +/// An extension trait for [Rewriter] implementations. +/// +/// This trait contains functionality that is not object safe, and would prevent using [Rewriter] as +/// a trait object. It is automatically implemented for all [Rewriter] impls. +pub trait RewriterExt: Rewriter { + /// This is a utility function that wraps an in-place modification of an operation, such that + /// the rewriter is guaranteed to be notified when the modifications start and stop. + fn modify_op_in_place(&mut self, op: OperationRef) -> InPlaceModificationGuard<'_, Self> { + InPlaceModificationGuard::new(self, op) + } + + /// Find uses of `from` and replace them with `to`, if `should_replace` returns true. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + /// + /// Returns true if all uses were replaced, otherwise false. + fn maybe_replace_uses_of_value_with

( + &mut self, + mut from: ValueRef, + mut to: ValueRef, + should_replace: P, + ) -> bool + where + P: Fn(&OpOperandImpl) -> bool, + { + let mut all_replaced = true; + let mut from = from.borrow_mut(); + let from_uses = from.uses_mut(); + let mut cursor = from_uses.front_mut(); + while let Some(user) = cursor.as_pointer() { + if should_replace(&user.borrow()) { + let owner = user.borrow().owner; + self.notify_operation_modification_started(&owner); + let operand = cursor.remove().unwrap(); + to.borrow_mut().insert_use(operand); + self.notify_operation_modified(owner); + } else { + all_replaced = false; + cursor.move_next(); + } + } + all_replaced + } + + /// Find uses of `from` and replace them with `to`, if `should_replace` returns true. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + /// + /// Returns true if all uses were replaced, otherwise false. + fn maybe_replace_uses_with

( + &mut self, + from: &[ValueRef], + to: &[ValueRef], + should_replace: P, + ) -> bool + where + P: Fn(&OpOperandImpl) -> bool, + { + assert_eq!(from.len(), to.len(), "incorrect number of replacements"); + let mut all_replaced = true; + for (from, to) in from.iter().cloned().zip(to.iter().cloned()) { + all_replaced &= self.maybe_replace_uses_of_value_with(from, to, &should_replace); + } + all_replaced + } + + /// Find uses of `from` and replace them with `to`, if `should_replace` returns true. + /// + /// Notifies the listener about every in-place op modification (for every use that was replaced). + /// + /// Returns true if all uses were replaced, otherwise false. + fn maybe_replace_op_uses_with

( + &mut self, + from: OperationRef, + to: &[ValueRef], + should_replace: P, + ) -> bool + where + P: Fn(&OpOperandImpl) -> bool, + { + let results = SmallVec::<[ValueRef; 2]>::from_iter( + from.borrow().results.all().iter().cloned().map(|result| result as ValueRef), + ); + self.maybe_replace_uses_with(&results, to, should_replace) + } +} + +impl RewriterExt for R {} + +#[allow(unused_variables)] +pub trait RewriterListener: Listener { + /// Notify the listener that the specified block is about to be erased. + /// + /// At this point, the block has zero uses. + fn notify_block_erased(&self, block: BlockRef) {} + + /// Notify the listener that an in-place modification of the specified operation has started + fn notify_operation_modification_started(&self, op: &OperationRef) {} + + /// Notify the listener that an in-place modification of the specified operation was canceled + fn notify_operation_modification_canceled(&self, op: &OperationRef) {} + + /// Notify the listener that the specified operation was modified in-place. + fn notify_operation_modified(&self, op: OperationRef) {} + + /// Notify the listener that all uses of the specified operation's results are about to be + /// replaced with the results of another operation. This is called before the uses of the old + /// operation have been changed. + /// + /// By default, this function calls the "operation replaced with values" notification. + fn notify_operation_replaced(&self, op: OperationRef, replacement: OperationRef) { + let replacement = replacement.borrow(); + let values = replacement + .results() + .all() + .iter() + .cloned() + .map(|result| Some(result as ValueRef)) + .collect::; 2]>>(); + self.notify_operation_replaced_with_values(op, &values); + } + + /// Notify the listener that all uses of the specified operation's results are about to be + /// replaced with the given range of values, potentially produced by other operations. This is + /// called before the uses of the operation have been changed. + fn notify_operation_replaced_with_values( + &self, + op: OperationRef, + replacement: &[Option], + ) { + } + + /// Notify the listener that the specified operation is about to be erased. At this point, the + /// operation has zero uses. + /// + /// NOTE: This notification is not triggered when unlinking an operation. + fn notify_operation_erased(&self, op: OperationRef) {} + + /// Notify the listener that the specified pattern is about to be applied at the specified root + /// operation. + fn notify_pattern_begin(&self, pattern: &dyn Pattern, op: OperationRef) {} + + /// Notify the listener that a pattern application finished with the specified status. + /// + /// `true` indicates that the pattern was applied successfully. `false` indicates that the + /// pattern could not be applied. The pattern may have communicated the reason for the failure + /// with `notify_match_failure` + fn notify_pattern_end(&self, pattern: &dyn Pattern, success: bool) {} + + /// Notify the listener that the pattern failed to match, and provide a diagnostic explaining + /// the reason why the failure occurred. + fn notify_match_failure(&self, span: SourceSpan, reason: Report) {} +} + +impl RewriterListener for Option { + fn notify_block_erased(&self, block: BlockRef) { + if let Some(listener) = self.as_ref() { + listener.notify_block_erased(block); + } + } + + fn notify_operation_modification_started(&self, op: &OperationRef) { + if let Some(listener) = self.as_ref() { + listener.notify_operation_modification_started(op); + } + } + + fn notify_operation_modification_canceled(&self, op: &OperationRef) { + if let Some(listener) = self.as_ref() { + listener.notify_operation_modification_canceled(op); + } + } + + fn notify_operation_modified(&self, op: OperationRef) { + if let Some(listener) = self.as_ref() { + listener.notify_operation_modified(op); + } + } + + fn notify_operation_replaced(&self, op: OperationRef, replacement: OperationRef) { + if let Some(listener) = self.as_ref() { + listener.notify_operation_replaced(op, replacement); + } + } + + fn notify_operation_replaced_with_values( + &self, + op: OperationRef, + replacement: &[Option], + ) { + if let Some(listener) = self.as_ref() { + listener.notify_operation_replaced_with_values(op, replacement); + } + } + + fn notify_operation_erased(&self, op: OperationRef) { + if let Some(listener) = self.as_ref() { + listener.notify_operation_erased(op); + } + } + + fn notify_pattern_begin(&self, pattern: &dyn Pattern, op: OperationRef) { + if let Some(listener) = self.as_ref() { + listener.notify_pattern_begin(pattern, op); + } + } + + fn notify_pattern_end(&self, pattern: &dyn Pattern, success: bool) { + if let Some(listener) = self.as_ref() { + listener.notify_pattern_end(pattern, success); + } + } + + fn notify_match_failure(&self, span: SourceSpan, reason: Report) { + if let Some(listener) = self.as_ref() { + listener.notify_match_failure(span, reason); + } + } +} + +impl RewriterListener for Box { + fn notify_block_erased(&self, block: BlockRef) { + (**self).notify_block_erased(block); + } + + fn notify_operation_modification_started(&self, op: &OperationRef) { + (**self).notify_operation_modification_started(op); + } + + fn notify_operation_modification_canceled(&self, op: &OperationRef) { + (**self).notify_operation_modification_canceled(op); + } + + fn notify_operation_modified(&self, op: OperationRef) { + (**self).notify_operation_modified(op); + } + + fn notify_operation_replaced(&self, op: OperationRef, replacement: OperationRef) { + (**self).notify_operation_replaced(op, replacement); + } + + fn notify_operation_replaced_with_values( + &self, + op: OperationRef, + replacement: &[Option], + ) { + (**self).notify_operation_replaced_with_values(op, replacement); + } + + fn notify_operation_erased(&self, op: OperationRef) { + (**self).notify_operation_erased(op) + } + + fn notify_pattern_begin(&self, pattern: &dyn Pattern, op: OperationRef) { + (**self).notify_pattern_begin(pattern, op); + } + + fn notify_pattern_end(&self, pattern: &dyn Pattern, success: bool) { + (**self).notify_pattern_end(pattern, success); + } + + fn notify_match_failure(&self, span: SourceSpan, reason: Report) { + (**self).notify_match_failure(span, reason); + } +} + +impl RewriterListener for Rc { + fn notify_block_erased(&self, block: BlockRef) { + (**self).notify_block_erased(block); + } + + fn notify_operation_modification_started(&self, op: &OperationRef) { + (**self).notify_operation_modification_started(op); + } + + fn notify_operation_modification_canceled(&self, op: &OperationRef) { + (**self).notify_operation_modification_canceled(op); + } + + fn notify_operation_modified(&self, op: OperationRef) { + (**self).notify_operation_modified(op); + } + + fn notify_operation_replaced(&self, op: OperationRef, replacement: OperationRef) { + (**self).notify_operation_replaced(op, replacement); + } + + fn notify_operation_replaced_with_values( + &self, + op: OperationRef, + replacement: &[Option], + ) { + (**self).notify_operation_replaced_with_values(op, replacement); + } + + fn notify_operation_erased(&self, op: OperationRef) { + (**self).notify_operation_erased(op) + } + + fn notify_pattern_begin(&self, pattern: &dyn Pattern, op: OperationRef) { + (**self).notify_pattern_begin(pattern, op); + } + + fn notify_pattern_end(&self, pattern: &dyn Pattern, success: bool) { + (**self).notify_pattern_end(pattern, success); + } + + fn notify_match_failure(&self, span: SourceSpan, reason: Report) { + (**self).notify_match_failure(span, reason); + } +} + +/// A listener of kind `Rewriter` that does nothing +pub struct NoopRewriterListener; +impl Listener for NoopRewriterListener { + #[inline] + fn kind(&self) -> ListenerType { + ListenerType::Rewriter + } + + #[inline(always)] + fn notify_operation_inserted(&self, _op: OperationRef, _prev: ProgramPoint) {} + + #[inline(always)] + fn notify_block_inserted( + &self, + _block: BlockRef, + _prev: Option, + _ip: Option, + ) { + } +} +impl RewriterListener for NoopRewriterListener { + fn notify_operation_replaced(&self, _op: OperationRef, _replacement: OperationRef) {} +} + +pub struct ForwardingListener { + base: Base, + derived: Derived, +} +impl ForwardingListener { + pub fn new(base: Base, derived: Derived) -> Self { + Self { base, derived } + } +} +impl Listener for ForwardingListener { + fn kind(&self) -> ListenerType { + self.derived.kind() + } + + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + self.base.notify_block_inserted(block, prev, ip); + self.derived.notify_block_inserted(block, prev, ip); + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + self.base.notify_operation_inserted(op, prev); + self.derived.notify_operation_inserted(op, prev); + } +} +impl RewriterListener + for ForwardingListener +{ + fn notify_block_erased(&self, block: BlockRef) { + self.base.notify_block_erased(block); + self.derived.notify_block_erased(block); + } + + fn notify_operation_modification_started(&self, op: &OperationRef) { + self.base.notify_operation_modification_started(op); + self.derived.notify_operation_modification_started(op); + } + + fn notify_operation_modification_canceled(&self, op: &OperationRef) { + self.base.notify_operation_modification_canceled(op); + self.derived.notify_operation_modification_canceled(op); + } + + fn notify_operation_modified(&self, op: OperationRef) { + self.base.notify_operation_modified(op); + self.derived.notify_operation_modified(op); + } + + fn notify_operation_replaced(&self, op: OperationRef, replacement: OperationRef) { + self.base.notify_operation_replaced(op, replacement); + self.derived.notify_operation_replaced(op, replacement); + } + + fn notify_operation_replaced_with_values( + &self, + op: OperationRef, + replacement: &[Option], + ) { + self.base.notify_operation_replaced_with_values(op, replacement); + self.derived.notify_operation_replaced_with_values(op, replacement); + } + + fn notify_operation_erased(&self, op: OperationRef) { + self.base.notify_operation_erased(op); + self.derived.notify_operation_erased(op); + } + + fn notify_pattern_begin(&self, pattern: &dyn Pattern, op: OperationRef) { + self.base.notify_pattern_begin(pattern, op); + self.derived.notify_pattern_begin(pattern, op); + } + + fn notify_pattern_end(&self, pattern: &dyn Pattern, success: bool) { + self.base.notify_pattern_end(pattern, success); + self.derived.notify_pattern_end(pattern, success); + } + + fn notify_match_failure(&self, span: SourceSpan, reason: Report) { + let err = Report::msg(format!("{reason}")); + self.base.notify_match_failure(span, reason); + self.derived.notify_match_failure(span, err); + } +} + +/// Wraps an in-place modification of an [Operation] to ensure the rewriter is properly notified +/// about the progress and outcome of the in-place notification. +/// +/// This is a minor efficiency win, as it avoids creating a new operation, and removing the old one, +/// but also often allows simpler code in the client. +pub struct InPlaceModificationGuard<'a, R: ?Sized + Rewriter> { + rewriter: &'a mut R, + op: OperationRef, + canceled: bool, +} +impl<'a, R> InPlaceModificationGuard<'a, R> +where + R: ?Sized + Rewriter, +{ + pub fn new(rewriter: &'a mut R, op: OperationRef) -> Self { + rewriter.notify_operation_modification_started(&op); + Self { + rewriter, + op, + canceled: false, + } + } + + #[inline] + pub fn rewriter(&mut self) -> &mut R { + self.rewriter + } + + #[inline] + pub fn op(&self) -> &OperationRef { + &self.op + } + + /// Cancels the pending in-place modification. + pub fn cancel(mut self) { + self.canceled = true; + } + + /// Signals the end of an in-place modification of the current operation. + pub fn finalize(self) {} +} +impl core::ops::Deref for InPlaceModificationGuard<'_, R> { + type Target = R; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.rewriter + } +} +impl core::ops::DerefMut for InPlaceModificationGuard<'_, R> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + self.rewriter + } +} +impl Drop for InPlaceModificationGuard<'_, R> { + fn drop(&mut self) { + if self.canceled { + self.rewriter.notify_operation_modification_canceled(&self.op); + } else { + self.rewriter.notify_operation_modified(self.op); + } + } +} + +/// A special type of `RewriterBase` that coordinates the application of a rewrite pattern on the +/// current IR being matched, providing a way to keep track of any mutations made. +/// +/// This type should be used to perform all necessary IR mutations within a rewrite pattern, as +/// the pattern driver may be tracking various state that would be invalidated when a mutation takes +/// place. +pub struct PatternRewriter { + rewriter: RewriterImpl, + recoverable: bool, +} + +impl PatternRewriter { + pub fn new(context: Rc) -> Self { + let rewriter = RewriterImpl::new(context); + Self { + rewriter, + recoverable: false, + } + } + + pub fn from_builder(builder: OpBuilder) -> Self { + let (context, _, ip) = builder.into_parts(); + let mut rewriter = RewriterImpl::new(context); + rewriter.restore_insertion_point(ip); + Self { + rewriter, + recoverable: false, + } + } +} + +impl PatternRewriter { + pub fn new_with_listener(context: Rc, listener: L) -> Self { + let rewriter = RewriterImpl::::new(context).with_listener(listener); + Self { + rewriter, + recoverable: false, + } + } + + #[inline] + pub const fn can_recover_from_rewrite_failure(&self) -> bool { + self.recoverable + } +} +impl Deref for PatternRewriter { + type Target = RewriterImpl; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + &self.rewriter + } +} +impl DerefMut for PatternRewriter { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.rewriter + } +} + +pub struct RewriterImpl { + context: Rc, + listener: Option, + ip: ProgramPoint, +} + +impl RewriterImpl { + pub fn new(context: Rc) -> Self { + Self { + context, + listener: None, + ip: ProgramPoint::default(), + } + } + + pub fn with_listener(self, listener: L2) -> RewriterImpl + where + L2: Listener, + { + RewriterImpl { + context: self.context, + listener: Some(listener), + ip: self.ip, + } + } +} + +impl From> for RewriterImpl { + #[inline] + fn from(builder: OpBuilder) -> Self { + let (context, listener, ip) = builder.into_parts(); + Self { + context, + listener, + ip, + } + } +} + +impl Builder for RewriterImpl { + #[inline(always)] + fn context(&self) -> &Context { + &self.context + } + + #[inline(always)] + fn context_rc(&self) -> Rc { + self.context.clone() + } + + #[inline(always)] + fn insertion_point(&self) -> &ProgramPoint { + &self.ip + } + + #[inline(always)] + fn clear_insertion_point(&mut self) -> ProgramPoint { + let ip = self.ip; + self.ip = ProgramPoint::Invalid; + ip + } + + #[inline(always)] + fn restore_insertion_point(&mut self, ip: ProgramPoint) { + self.ip = ip; + } + + #[inline(always)] + fn set_insertion_point(&mut self, ip: ProgramPoint) { + self.ip = ip; + } +} + +impl Rewriter for RewriterImpl { + #[inline(always)] + fn has_listener(&self) -> bool { + self.listener.is_some() + } +} + +impl Listener for RewriterImpl { + fn kind(&self) -> ListenerType { + ListenerType::Rewriter + } + + fn notify_operation_inserted(&self, op: OperationRef, prev: ProgramPoint) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_operation_inserted(op, prev); + } + } + + fn notify_block_inserted( + &self, + block: BlockRef, + prev: Option, + ip: Option, + ) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_block_inserted(block, prev, ip); + } + } +} + +impl RewriterListener for RewriterImpl { + fn notify_block_erased(&self, block: BlockRef) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_block_erased(block); + } + } + + fn notify_operation_modification_started(&self, op: &OperationRef) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_operation_modification_started(op); + } + } + + fn notify_operation_modification_canceled(&self, op: &OperationRef) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_operation_modification_canceled(op); + } + } + + fn notify_operation_modified(&self, op: OperationRef) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_operation_modified(op); + } + } + + fn notify_operation_replaced(&self, op: OperationRef, replacement: OperationRef) { + if self.listener.is_some() { + let replacement = replacement.borrow(); + let values = replacement + .results() + .all() + .iter() + .cloned() + .map(|result| Some(result.upcast())) + .collect::; 2]>>(); + self.notify_operation_replaced_with_values(op, &values); + } + } + + fn notify_operation_replaced_with_values( + &self, + op: OperationRef, + replacement: &[Option], + ) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_operation_replaced_with_values(op, replacement); + } + } + + fn notify_operation_erased(&self, op: OperationRef) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_operation_erased(op); + } + } + + fn notify_pattern_begin(&self, pattern: &dyn Pattern, op: OperationRef) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_pattern_begin(pattern, op); + } + } + + fn notify_pattern_end(&self, pattern: &dyn Pattern, success: bool) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_pattern_end(pattern, success); + } + } + + fn notify_match_failure(&self, span: SourceSpan, reason: Report) { + if let Some(listener) = self.listener.as_ref() { + listener.notify_match_failure(span, reason); + } + } +} diff --git a/hir/src/program/linker.rs b/hir/src/program/linker.rs deleted file mode 100644 index 5aba0cf82..000000000 --- a/hir/src/program/linker.rs +++ /dev/null @@ -1,765 +0,0 @@ -use std::{ - borrow::Cow, - collections::{BTreeMap, BTreeSet}, -}; - -use miden_assembly::Library as CompiledLibrary; -use petgraph::{prelude::DiGraphMap, Direction}; - -use crate::{ - diagnostics::{DiagnosticsHandler, Report, Severity, Spanned}, - *, -}; - -/// Represents a node in the global variable dependency graph -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -enum Node { - /// A global symbol that was defined or has been referenced - Global(Ident), - /// A function which refers to one or more global symbols - Function(FunctionIdent), -} - -/// Represents an object input to the [Linker] -pub enum Object { - /// The object is an HIR module - Hir(Box), - /// The object is a compiled Miden Assembly module - Masm { name: Ident, exports: Vec }, -} -impl Object { - /// Return the identifier associated with this object - pub fn id(&self) -> Ident { - match self { - Self::Hir(module) => module.name, - Self::Masm { name, .. } => *name, - } - } - - /// Return the set of exported functions/procedures from this object - pub fn exports(&self) -> Box<(dyn Iterator + '_)> { - match self { - Self::Hir(module) => Box::new(module.functions().map(|f| f.id)), - Self::Masm { name, ref exports } => { - let name = *name; - Box::new(exports.iter().copied().map(move |function| FunctionIdent { - module: name, - function, - })) - } - } - } -} -impl From> for Object { - fn from(module: Box) -> Self { - Self::Hir(module) - } -} -impl From<(Ident, Vec)> for Object { - fn from(module: (Ident, Vec)) -> Self { - Self::Masm { - name: module.0, - exports: module.1, - } - } -} - -/// The [Linker] performs a similar role in conjunction with the Miden compiler, as the system -/// linker does (e.g. `ld`) when used with compilers like `clang` or `rustc`. -/// -/// As a (very) rough overview, a typical linker is given a set of object files containing machine -/// code and data, one for every translation unit participating in the link (e.g. for Rust the -/// translation unit is a single crate). The linker will also be informed when dependencies are -/// expected to be provided at runtime, i.e. dynamic libraries. With this, a linker does the -/// following: -/// -/// * Determines the final layout of all code and data in the executable or library being produced, -/// this allows the linker to know the absolute and/or relative address for every symbol in the -/// program. -/// * Ensures that all referenced symbols (functions/globals) are defined, or that there are runtime -/// dependencies that will satisfy the missing symbols (in practice, what actually happens is the -/// static linker, i.e. `ld`, assumes missing symbols will be provided by the runtime -/// dependencies, and it is the runtime dynamic linker, i.e. `rtdyld`, which handles the case -/// where those symbols cannot be located when the program is starting up). -/// * Rewrites instructions with symbol references to use the absolute/relative addressing once the -/// layout of the program in memory is known. -/// * Emits the linked program in binary form, either as an executable or as a library -/// -/// However, there a couple of things that make [Linker] somewhat different than your typical system -/// linker: -/// -/// * We do not emit assembly/run the assembler prior to linking. This is because Miden Assembly -/// (MASM) does not have a way to represent things like data segments or global variables -/// natively. Instead, the linker is responsible for laying those out in memory ahead of time, and -/// then all operations involving them are lowered to use absolute addresses. -/// * [Linker] does not emit the final binary form of the program. It still plans the layout of -/// program data in memory, and performs the same type of validations as a typical linker, but the -/// output of the linker is a [Program], which must be emitted as Miden Assembly in a separate -/// step _after_ being linked. -/// * We cannot guarantee that the [Program] we emit constitutes a closed set of modules/functions, -/// even accounting for functions whose definitions will be provided at runtime. This is because -/// the Miden VM acts as the final assembler of the programs it runs, and if the [Program] we emit -/// is used as a library, we can't know what other modules might end up being linked into the -/// final program run by the VM. As a result, it is assumed that any code introduced separately is -/// either: -/// 1. memory-agnostic, i.e. it doesn't use the heap and/or make any assumptions about the heap -/// layout. -/// 2. compatible with the layout decided upon by the linker, i.e. it uses well-known allocator -/// functions like `malloc`; or it places its memory in the range 2^30-2^31 for user contexts, -/// or 2^30-(2^32 - 2^30) for root contexts (the latter allocates a separate region for syscall -/// locals). The linker will always reserve memory starting at address 2^30 for locals and -/// "unmanaged" memory allocations, to support scenarios whereby a linked library is used with -/// a program that needs its own region of heap to manage. -/// * Miden has separate address spaces depending on the context in which a function is executed, -/// i.e. the root vs user context distinction. Currently, all programs are assumed to be executed -/// in the root context, and we do not provide instructions for executing calls in another -/// context. However, we will eventually be linking programs which have a potentially unbounded -/// number of address spaces, which is an additional complication that your typical linker doesn't -/// have to deal with -pub struct Linker<'a> { - diagnostics: &'a DiagnosticsHandler, - /// This is the program being constructed by the linker - program: Box, - /// This is the set of named objects which have yet to be linked - pending: BTreeMap, - /// This is the set of patterns that symbol names will be matched against when determining - /// whether or not to raise an error when a reference to any symbol whose name starts with - /// that pattern cannot be found. - /// - /// In practice, this is used to allow certain library modules to be referenced without - /// requiring them to be loaded into the linker. - allow_missing: BTreeSet>, - /// This is the dependency graph for all functions in the program. - /// - /// This graph is used to obtain a topological ordering of the - /// functions in the program, so that we may emit Miden Assembly - /// such that all procedure definitions occur before their uses. - /// - /// It is allowed for there to be cyclical module dependencies, but - /// we do not permit cyclical function dependencies (i.e. recursive - /// function calls). - /// - /// The edge weight is unused. - callgraph: DiGraphMap, - /// This is a subset of `callgraph` for a single module. - /// - /// This is only used when preprocessing a module, and is reset on each call to `add` - local_callgraph: DiGraphMap, - /// This is the dependency graph for all globals in the program. - /// - /// This graph is used to identify what global symbols are used, from where, - /// and whether or not those dependencies are satisfied by the set of modules - /// being linked into the program. - globals: DiGraphMap, - /// The set of renamed global symbols for a single module. - /// - /// This is only used when preprocessing a module, and is reset on each call to `add` - renamed: BTreeMap, -} -impl<'a> Linker<'a> { - /// Create a [Linker] for a new, empty [Program]. - pub fn new(diagnostics: &'a DiagnosticsHandler) -> Self { - let program = Box::new(Program::new()); - - Self { - diagnostics, - program, - pending: Default::default(), - allow_missing: BTreeSet::from_iter([ - "std::".into(), - "intrinsics::".into(), - "miden::account".into(), - "miden::tx".into(), - "miden::note".into(), - ]), - callgraph: DiGraphMap::new(), - local_callgraph: DiGraphMap::new(), - globals: DiGraphMap::new(), - renamed: Default::default(), - } - } - - pub fn reserve_memory(&mut self, size: u32) { - self.program.reserved_memory_pages = size; - } - - pub fn with_page_size(&mut self, page_size: u32) -> &mut Self { - if self.program.page_size > 0 { - assert!(page_size.is_power_of_two()); - self.program.page_size = page_size; - } - self - } - - /// Set the entrypoint for the linked program - /// - /// Returns a [Report] if a different entrypoint was already declared. - pub fn with_entrypoint(&mut self, id: FunctionIdent) -> Result<(), Report> { - if let Some(prev) = self.program.entrypoint() { - if prev != id { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - id.function.span, - "this entrypoint conflicts with a previously declared entrypoint", - ) - .with_secondary_label(prev.function.span, "previous entrypoint declared here") - .into_report()); - } - } - - self.program.entrypoint = Some(id); - - Ok(()) - } - - /// Specify a pattern that will be matched against undefined symbols that determines whether or - /// or not it should be treated as an error. It is assumed that the referenced symbol will be - /// resolved during assembly to MAST. - pub fn allow_missing(&mut self, name: impl Into>) { - self.allow_missing.insert(name.into()); - } - - /// Add a compiled library to the set of libraries to link against - pub fn add_library(&mut self, lib: CompiledLibrary) { - // Add all of the exported objects to the callgraph - for export in lib.exports() { - let module = Ident::with_empty_span(Symbol::intern(export.module.path())); - let name: &str = export.name.as_ref(); - let function = Ident::with_empty_span(Symbol::intern(name)); - self.callgraph.add_node(FunctionIdent { module, function }); - } - self.program.add_library(lib); - } - - /// Add multiple libraries to the set of libraries to link against - pub fn add_libraries(&mut self, libs: I) - where - I: IntoIterator, - { - for lib in libs { - self.add_library(lib); - } - } - - /// Add an object to link as part of the resulting [Program]. - /// - /// There are different types of objects, see [Object] for details. - /// - /// # Errors - /// - /// The following conditions can cause an error to be raised, if applicable to the object given: - /// - /// * The object is invalid - /// * The object introduces recursion into the call graph - /// * Two or more objects export a module with the same name - /// * Two or more objects contain conflicting data segment declarations - /// * Two or more objects contain conflicting global variable declarations - pub fn add_object(&mut self, object: impl Into) -> Result<(), Report> { - let object = object.into(); - let id = object.id(); - - // Raise an error if we've already got a module by this name pending - if self.pending.contains_key(&id) { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - id.span, - "this module conflicts with a previous module of the same name", - ) - .into_report()); - } - - // Register functions in the callgraph - for export in object.exports() { - self.callgraph.add_node(export); - } - - match object { - Object::Hir(module) => self.add_hir_object(module), - object @ Object::Masm { .. } => { - // We're done preprocessing, so add the module to the pending set - self.pending.insert(object.id(), object); - - Ok(()) - } - } - } - - /// Add `module` to the set of objects to be linked - /// - /// This preprocesses the module for the linker, and will catch the following issues: - /// - /// * Multiple modules with the same name - /// * Conflicting data segment declarations - /// * Conflicting global variable declarations - /// * Recursion in the local call graph of the module (global analysis comes later) - /// - /// If any of the above errors occurs, a [Report] is returned. - fn add_hir_object(&mut self, mut module: Box) -> Result<(), Report> { - let id = module.name; - - // Reset the auxiliary data structures used for preprocessing - self.local_callgraph.clear(); - self.renamed.clear(); - - // Raise an error if we've already got a module by this name pending - if self.pending.contains_key(&id) { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - id.span, - "this module conflicts with a previous module of the same name", - ) - .into_report()); - } - - // Import all data segments - while let Some(segment) = module.segments.pop_front() { - self.program.segments.insert(segment)?; - } - - // Import all globals, and in the process: - // - // * Record all global variable definitions in the dependency graph - // * Track renamed symbols, if any, produced by the import - for global in module.globals.iter() { - let mut imported_global = global.clone(); - // If an initializer was set, we need to import the constant data too - if let Some(init) = imported_global.init.take() { - let data = module.globals.get_constant(init).clone(); - imported_global.init = Some(self.program.globals.insert_refcounted_constant(data)); - } - let (id, renamed) = self.program.globals.try_insert(imported_global)?; - let name = self.program.globals.get(id).name; - if renamed { - self.renamed.insert(global.name, name); - } - self.globals.add_node(Node::Global(name)); - } - - // Compute a subset of the call graph just for this module - for function in module.functions.iter() { - // While we're here, update the global call graph as well - let caller = self.callgraph.add_node(function.id); - let caller = self.local_callgraph.add_node(caller); - for import in function.imports() { - let callee = self.callgraph.add_node(import.id); - self.callgraph.add_edge(caller, callee, ()); - // For the toposort, we only care about functions in the same module - if import.id.module == module.name { - let callee = self.local_callgraph.add_node(callee); - self.local_callgraph.add_edge(caller, callee, ()); - } - } - } - - // Compute the topographical ordering of functions in this module - let topography = petgraph::algo::toposort(&self.local_callgraph, None).map_err(|_| { - // The error here only gives us the caller, but we'd like to know - // the callee for our error diagnostics. To get it, we call the - // call graph validation routine which does a traversal specifically - // designed to obtain that information. - validate_callgraph(&self.local_callgraph, self.diagnostics) - .expect_err("expected call graph to contain a cycle") - })?; - - // Preprocess all functions in this module by: - // - // * Record dependencies on global symbols in the dependency graph - // * Rewrite any references to renamed global symbols with internal/odr linkage - // * Reorder the function list according to the topological order computed above - // - // We do this in a single pass, by draining the list of sorted function ids, and - // then unlinking each function from the module in that order, processing it, and - // then inserting it at the end of the function list of the module. After all the - // ids have been visited, the function list of the module will be in topographical - // order. This is about as efficient as we can make it, roughly `O(N * M)`, where - // `N` is the number of functions in the module, and `M` is the number of items - // we must skip before we find the next function to process. If the module is in - // topographical order already, the `M` factor is 1. - for id in topography.into_iter() { - // Find and remove the function with the given name from the module - let mut function = module.unlink(id.function); - - // Process the global values of this function, i.e. references to global symbols - for gvalue in function.dfg.globals.values_mut() { - match gvalue { - // This global value type is sensitive to renaming - GlobalValueData::Symbol { - name: ref mut global_name, - .. - } => { - if let Some(new_name) = self.renamed.get(global_name).copied() { - *global_name = new_name; - } - // Make sure the symbol is in the graph - let global_node = self.globals.add_node(Node::Global(*global_name)); - let dependent_node = self.globals.add_node(Node::Function(id)); - // Record a dependency from this function to the named global - self.globals.add_edge(dependent_node, global_node, ()); - } - // These global values are stable even in the face of symbol renaming - GlobalValueData::Load { .. } | GlobalValueData::IAddImm { .. } => continue, - } - } - - // Insert the function back in the list - module.functions.push_back(function); - } - - // We're done preprocessing, so add the module to the pending set - self.pending.insert(id, Object::Hir(module)); - - Ok(()) - } - - /// Links all of the modules which were added, producing a [Program] if no issues are found. - /// - /// Returns a [Report] if the link fails for any reason. - /// - /// When called, all of the added modules have been preprocessed, and what remains are the - /// following tasks: - /// - /// * Verify that all referenced modules exist, or are known to be provided at runtime - /// * Verify that all referenced functions exist, or are known to be provided at runtime, and - /// that the signature known to the caller matches the actual definition. - /// * Verifies that the entrypoint, if set, is valid - /// * Verify that there are no cycles in the call graph, i.e. that there is no recursion present - /// * Verify that all references to global symbols have corresponding definitions - /// * Perform garbage collection of unreferenced globals - /// * TODO: If linking an executable program, garbage collect unused modules/functions - /// - /// Once linked, a [Program] can be emitted to Miden Assembly using the code generation passes. - pub fn link(mut self) -> Result, Report> { - // Look for cycles in the call graph - validate_callgraph(&self.callgraph, self.diagnostics)?; - - // Verify the entrypoint, if declared - if let Some(entry) = self.program.entrypoint() { - // NOTE(pauls): Currently, we always raise an error here, but since we do allow - // missing symbols in other situations, perhaps we should allow it here as well. - // For now though, we assume this is a mistake, since presumably you are compiling - // the code that contains the entrypoint. - let object = self.pending.get(&entry.module).ok_or_else(|| { - self.diagnostics - .diagnostic(Severity::Error) - .with_message(format!("linker error: undefined module '{}'", &entry.module)) - .into_report() - })?; - match object { - Object::Hir(module) => { - let function = module.function(entry.function).ok_or_else(|| { - self.diagnostics - .diagnostic(Severity::Error) - .with_message(format!("linker error: undefined function '{}'", &entry)) - .into_report() - })?; - if !function.is_public() { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - entry.function.span, - "entrypoint must have external linkage", - ) - .into_report()); - } - } - Object::Masm { ref exports, .. } => { - if !exports.contains(&entry.function) { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message(format!("linker error: undefined function '{}'", &entry)) - .into_report()); - } - } - } - } - - // Verify module/function references - for node in self.callgraph.nodes() { - // If the module is pending, it is being linked - let object = self.pending.get(&node.module); - let is_allowed_missing = self - .allow_missing - .iter() - .any(|pattern| node.module.as_str().starts_with(pattern.as_ref())); - - // If a referenced module is not present for the link, raise an error, unless it is - // specifically allowed to be missing at this point. - if object.is_none() { - if is_allowed_missing { - continue; - } - - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message(format!("linker error: undefined module '{}'", &node.module)) - .into_report()); - } - - // The module is present, so we must verify that the function is defined in that module - let object = unsafe { object.unwrap_unchecked() }; - let (is_externally_linkable, signature) = match object { - Object::Hir(ref module) => match module.function(node.function) { - Some(function) => (function.is_public(), Some(&function.signature)), - None if is_allowed_missing => (true, None), - None => { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message(format!("linker error: undefined function '{}'", &node)) - .into_report()) - } - }, - Object::Masm { ref exports, .. } => { - if !exports.contains(&node.function) && !is_allowed_missing { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message(format!("linker error: undefined function '{}'", &node)) - .into_report()); - } - (true, None) - } - }; - - // Next, visit all of the dependent functions, and ensure their signatures match - for dependent_id in self.callgraph.neighbors_directed(node, Direction::Incoming) { - // If the dependent is in another module, but the function has internal linkage, - // raise an error - if dependent_id.module != node.module && !is_externally_linkable { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - dependent_id.function.span, - format!( - "this function contains an invalid reference to '{}'", - &node.function - ), - ) - .with_help( - "Only functions with external linkage can be referenced across modules", - ) - .into_report()); - } - // Otherwise, make sure the signatures match (if we have signatures available) - let dependent_object = &self.pending[&dependent_id.module]; - match (signature, dependent_object) { - (Some(signature), Object::Hir(ref dependent_module)) => { - let dependent_function = dependent_module - .function(dependent_id.function) - .expect("dependency graph is outdated"); - let external_ref = dependent_function - .dfg - .get_import(&node) - .expect("dependency graph is outdated"); - let external_span = external_ref.id.span(); - verify_matching_signature( - node, - external_span, - signature, - &external_ref.signature, - self.diagnostics, - )?; - } - // If we don't have a signature for the dependency, we presume it matches the - // dependent - (None, Object::Hir(_)) => (), - // If the dependent is MASM, we don't know what signature it used, so we - // presume it is correct - (_, Object::Masm { .. }) => (), - } - } - } - - // Verify global symbol references, and garbage collect unused globals - for node in self.globals.nodes() { - // Skip nodes in the graph which aren't globals - let Node::Global(name) = node else { - continue; - }; - - // If this global has no incoming edges, it's dead, so garbage collect it - let mut dependents = self.globals.neighbors_directed(node, Direction::Incoming); - let is_dead = dependents.next().is_none(); - if is_dead { - let id = self - .program - .globals - .find(name) - .expect("expected global to be in table when dead"); - self.program.globals.remove(id); - } - - // If it has dependents, but isn't defined anywhere, raise an error - if !self.program.globals.exists(name) { - return Err(self - .diagnostics - .diagnostic(Severity::Error) - .with_message(format!("linker error: undefined global variable '{name}'")) - .into_report()); - } - } - - // Run the garbage collector - self.garbage_collect(); - - // We're finished processing all pending modules, so add them to the program - for object in self.pending.into_values() { - match object { - Object::Hir(module) => { - self.program.modules.insert(module); - } - Object::Masm { .. } => { - // These objects are provided to the assembler directly - continue; - } - } - } - - Ok(self.program) - } - - /// If an executable is being linked, discover unused functions and garbage collect them. - /// - /// Once a function has been identified as dead and is collected, any transitive items it - /// references may also be collected if they are orphaned as a result of the collection. - /// - /// If a library is being linked, this function is a no-op, as it is not known what will - /// be needed at runtime once used in the context of a program. - fn garbage_collect(&mut self) { - // TODO: See https://github.com/0xPolygonMiden/miden-ir/issues/26 - } -} - -/// Verifies that the actual signature of the given function matches what was expected. -/// -/// Here, `actual` is the defined signature of the function; while `expected` is the signature -/// associated with an [ExternalFunction], i.e. it is the signature expected by a prospective -/// caller. -fn verify_matching_signature( - id: FunctionIdent, - expected_span: SourceSpan, - actual: &Signature, - expected: &Signature, - diagnostics: &DiagnosticsHandler, -) -> Result<(), Report> { - // If the number of parameters differs, raise an error - if expected.arity() != actual.arity() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label(id.span(), "the arity of this function declaration is incorrect") - .with_secondary_label( - expected_span, - format!("the actual arity of the definition is {}", expected.arity()), - ) - .into_report()); - } - - // If the type or specification of any parameters differs, raise an error - for (i, (ep, ap)) in expected.params().iter().zip(actual.params().iter()).enumerate() { - if !is_matching_param(ep, ap) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - id.span(), - "the type signature of this function declaration is incorrect", - ) - .with_secondary_label(expected_span, "it does not match the signature defined here") - .with_help(format!("The parameter at index {i} is defined as {}", &ep.ty)) - .into_report()); - } - } - - // If the number of results differs, raise an error - let expected_results = expected.results(); - let actual_results = actual.results(); - if expected_results.len() != actual_results.len() { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - id.span(), - "the return arity of this function declaration is incorrect", - ) - .with_secondary_label( - expected_span, - format!("the actual number of return values is {}", expected_results.len()), - ) - .into_report()); - } - - // If the type of results differs, raise an error - for (i, (er, ar)) in expected_results.iter().zip(actual_results.iter()).enumerate() { - if !is_matching_param(er, ar) { - return Err(diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label( - id.span(), - "the type signature of this function declaration is incorrect", - ) - .with_secondary_label(expected_span, "it does not match the signature defined here") - .with_help(format!("The result at index {i} is defined as {}", &er.ty)) - .into_report()); - } - } - - Ok(()) -} - -/// Determines if the actual ABI of a parameter matches the expected ABI. -fn is_matching_param(expected: &AbiParam, actual: &AbiParam) -> bool { - if expected.purpose != actual.purpose { - return false; - } - - match actual.extension { - // If the actual definition has no extension requirement, - // it is ok if the caller is more strict, however we may - // want to raise a warning in such cases - ArgumentExtension::None => expected.ty == actual.ty, - ArgumentExtension::Zext if expected.extension != ArgumentExtension::Zext => false, - ArgumentExtension::Sext if expected.extension != ArgumentExtension::Sext => false, - ArgumentExtension::Zext | ArgumentExtension::Sext => expected.ty == actual.ty, - } -} - -/// Validate the given call graph by looking for cycles caused by recursion. -/// -/// Returns a [Report] if a cycle is found. -fn validate_callgraph( - callgraph: &DiGraphMap, - diagnostics: &DiagnosticsHandler, -) -> Result<(), Report> { - use petgraph::visit::{depth_first_search, DfsEvent, IntoNodeIdentifiers}; - - depth_first_search(callgraph, callgraph.node_identifiers(), |event| match event { - DfsEvent::BackEdge(caller, callee) => Err(diagnostics - .diagnostic(Severity::Error) - .with_message("linker error") - .with_primary_label(caller.span(), "this function contains recursion") - .with_secondary_label(callee.span(), "due to one or more calls to this function") - .with_help( - "If you need to make the call recursive, you may need to use indirect calls to \ - acheive this", - ) - .into_report()), - _ => Ok(()), - }) -} diff --git a/hir/src/program/mod.rs b/hir/src/program/mod.rs deleted file mode 100644 index 0e0425c28..000000000 --- a/hir/src/program/mod.rs +++ /dev/null @@ -1,404 +0,0 @@ -mod linker; - -use alloc::collections::BTreeMap; -use core::ops::{Deref, DerefMut}; - -use intrusive_collections::RBTree; -use miden_assembly::Library as CompiledLibrary; -use miden_core::crypto::hash::RpoDigest; - -pub use self::linker::Linker; -use crate::{ - diagnostics::{DiagnosticsHandler, Report}, - *, -}; - -/// A [Program] is a collection of [Module]s that are being compiled together as a package. -/// -/// This is primarily used for storing/querying data which must be shared across modules: -/// -/// * The set of global variables which will be allocated on the global heap -/// * The set of modules and functions which have been defined -/// -/// When translating to Miden Assembly, we need something like this to allow us to perform some -/// basic linker tasks prior to emitting the textual MASM which will be fed to the Miden VM. -/// -/// This structure is intended to be allocated via [std::sync::Arc], so that it can be shared -/// across multiple threads which are emitting/compiling modules at the same time. It is designed -/// so that individual fields are locked, rather than the structure as a whole, to minimize -/// contention. The intuition is that, in general, changes at the [Program] level are relatively -/// infrequent, i.e. only when declaring a new [Module], or [GlobalVariable], do we actually need to -/// mutate the structure. In all other situations, changes are scoped at the [Module] level. -pub struct Program { - /// This tree stores all of the modules being compiled as part of the current program. - modules: RBTree, - /// The set of compiled libraries this program links against - libraries: BTreeMap, - /// If set, this field is used to determine which function is the entrypoint for the program. - /// - /// When generating Miden Assembly, this will determine whether or not we're emitting - /// a program or just a collection of modules; and in the case of the former, what code - /// to emit in the root code block. - /// - /// If not present, but there is a function in the program with the `entrypoint` attribute, - /// that function will be used instead. If there are multiple functions with the `entrypoint` - /// attribute, and this field is `None`, the linker will raise an error. - entrypoint: Option, - /// The page size used by this program. - /// - /// If multiple modules with different page sizes are present, the largest size is used. - page_size: u32, - /// The size (in pages) of the reserved linear memory region (starting from offset 0) - reserved_memory_pages: u32, - /// The data segments gathered from all modules in the program, and laid out in address order. - segments: DataSegmentTable, - /// The global variable table produced by linking the global variable tables of all - /// modules in this program. The layout of this table corresponds to the layout of - /// global variables in the linear memory heap at runtime. - globals: GlobalVariableTable, -} - -impl Default for Program { - fn default() -> Self { - Self { - page_size: 64 * 1024, - reserved_memory_pages: 0, - modules: Default::default(), - libraries: Default::default(), - entrypoint: Default::default(), - segments: Default::default(), - globals: Default::default(), - } - } -} - -impl Program { - /// Create a new, empty [Program]. - #[inline(always)] - pub fn new() -> Self { - Self::default() - } - - /// Get the default page size for this program - #[inline] - pub const fn page_size(&self) -> u32 { - self.page_size - } - - /// Get the size of the reserved linear memory (in pages) region for this program - #[inline] - pub const fn reserved_memory_pages(&self) -> u32 { - self.reserved_memory_pages - } - - /// Get the size of the reserved linear memory (in bytes) region for this program - #[inline] - pub const fn reserved_memory_bytes(&self) -> u32 { - self.reserved_memory_pages * self.page_size - } - - /// Add to the set of libraries this [Program] will be assembled with - pub fn add_library(&mut self, lib: CompiledLibrary) { - self.libraries.insert(*lib.digest(), lib); - } - - /// Returns true if this program has a defined entrypoint - pub fn has_entrypoint(&self) -> bool { - self.entrypoint().is_some() - } - - /// Returns true if this program is executable. - /// - /// An executable program is one which has an entrypoint that will be called - /// after the program is loaded. - pub fn is_executable(&self) -> bool { - self.has_entrypoint() - } - - /// Returns the [FunctionIdent] corresponding to the program entrypoint - pub fn entrypoint(&self) -> Option { - self.entrypoint.or_else(|| self.modules.iter().find_map(|m| m.entrypoint())) - } - - /// Return a reference to the module table for this program - pub fn modules(&self) -> &RBTree { - &self.modules - } - - /// Return a mutable reference to the module table for this program - pub fn modules_mut(&mut self) -> &mut RBTree { - &mut self.modules - } - - /// Return the set of libraries this program links against - pub fn libraries(&self) -> &BTreeMap { - &self.libraries - } - - /// Return the set of libraries this program links against as a mutable reference - pub fn libraries_mut(&mut self) -> &mut BTreeMap { - &mut self.libraries - } - - /// Return a reference to the data segment table for this program - pub fn segments(&self) -> &DataSegmentTable { - &self.segments - } - - /// Get a reference to the global variable table for this program - pub fn globals(&self) -> &GlobalVariableTable { - &self.globals - } - - /// Get a mutable reference to the global variable table for this program - pub fn globals_mut(&mut self) -> &mut GlobalVariableTable { - &mut self.globals - } - - /// Returns true if `name` is defined in this program. - pub fn contains(&self, name: Ident) -> bool { - !self.modules.find(&name).is_null() - } - - /// Look up the signature of a function in this program by `id` - pub fn signature(&self, id: &FunctionIdent) -> Option<&Signature> { - let module = self.modules.find(&id.module).get()?; - module.function(id.function).map(|f| &f.signature) - } -} - -#[doc(hidden)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct ProgramAnalysisKey; -impl crate::pass::AnalysisKey for Program { - type Key = ProgramAnalysisKey; - - fn key(&self) -> Self::Key { - ProgramAnalysisKey - } -} - -/// This struct provides an ergonomic way to construct a [Program] in an imperative fashion. -/// -/// Simply create the builder, add/build one or more modules, then call `link` to obtain a -/// [Program]. -pub struct ProgramBuilder<'a> { - /// The set of HIR modules to link into the program - modules: BTreeMap>, - /// The set of modules defined externally, which will be linked during assembly - extern_modules: BTreeMap>, - /// The set of libraries we're linking against - libraries: BTreeMap, - entry: Option, - page_size: u32, - reserved_memory_size: u32, - diagnostics: &'a DiagnosticsHandler, -} -impl<'a> ProgramBuilder<'a> { - pub fn new(diagnostics: &'a DiagnosticsHandler) -> Self { - Self { - modules: Default::default(), - extern_modules: Default::default(), - libraries: Default::default(), - entry: None, - page_size: 64 * 1024, - reserved_memory_size: 0, - diagnostics, - } - } - - /// Set the entrypoint for the [Program] being built. - #[inline] - pub fn with_entrypoint(mut self, id: FunctionIdent) -> Self { - self.entry = Some(id); - self - } - - /// Add `module` to the set of modules to link into the final [Program] - /// - /// Unlike `add_module`, this function consumes the current builder state - /// and returns a new one, to allow for chaining builder calls together. - /// - /// Returns `Err` if a module with the same name already exists - pub fn with_module(mut self, module: Box) -> Result { - self.add_module(module).map(|_| self) - } - - /// Add `module` to the set of modules to link into the final [Program] - /// - /// Returns `Err` if a module with the same name already exists - pub fn add_module(&mut self, module: Box) -> Result<(), ModuleConflictError> { - let module_name = module.name; - if self.modules.contains_key(&module_name) || self.extern_modules.contains_key(&module_name) - { - return Err(ModuleConflictError::new(module_name)); - } - - self.page_size = core::cmp::max(self.page_size, module.page_size()); - self.reserved_memory_size = - core::cmp::max(self.reserved_memory_size, module.reserved_memory_pages()); - self.modules.insert(module_name, module); - - Ok(()) - } - - /// Make the linker aware that `module` (with the given set of exports), is available to be - /// linked against, but is already compiled to Miden Assembly, so has no HIR representation. - /// - /// Returns `Err` if a module with the same name already exists - pub fn add_extern_module( - &mut self, - module: Ident, - exports: E, - ) -> Result<(), ModuleConflictError> - where - E: IntoIterator, - { - if self.modules.contains_key(&module) || self.extern_modules.contains_key(&module) { - return Err(ModuleConflictError::new(module)); - } - - self.extern_modules.insert(module, exports.into_iter().collect()); - - Ok(()) - } - - /// Make the linker aware of the objects contained in the given library. - /// - /// Duplicate libraries/objects are ignored. - pub fn add_library(&mut self, library: CompiledLibrary) { - self.libraries.insert(*library.digest(), library); - } - - /// Start building a [Module] with the given name. - /// - /// When the builder is done, the resulting [Module] will be inserted - /// into the set of modules to be linked into the final [Program]. - pub fn module>(&mut self, name: S) -> ProgramModuleBuilder<'_, 'a> { - let name = name.into(); - let module = match self.modules.remove(&name) { - None => Box::new(Module::new(name)), - Some(module) => module, - }; - ProgramModuleBuilder { - pb: self, - mb: ModuleBuilder::from(module), - } - } - - /// Link a [Program] from the current [ProgramBuilder] state - pub fn link(self) -> Result, Report> { - let mut linker = Linker::new(self.diagnostics); - linker.with_page_size(self.page_size); - linker.reserve_memory(self.reserved_memory_size); - - let entrypoint = self.entry.or_else(|| self.modules.values().find_map(|m| m.entrypoint())); - if let Some(entry) = entrypoint { - linker.with_entrypoint(entry)?; - } - - linker.add_libraries(self.libraries.into_values()); - - self.extern_modules.into_iter().try_for_each(|obj| linker.add_object(obj))?; - self.modules.into_values().try_for_each(|obj| linker.add_object(obj))?; - - linker.link() - } -} - -/// This is used to build a [Module] from a [ProgramBuilder]. -/// -/// It is basically just a wrapper around [ModuleBuilder], but overrides two things: -/// -/// * `build` will add the module to the [ProgramBuilder] directly, rather than returning it -/// * `function` will delegate to [ProgramFunctionBuilder] which plays a similar role to this -/// struct, but for [ModuleFunctionBuilder]. -pub struct ProgramModuleBuilder<'a, 'b: 'a> { - pb: &'a mut ProgramBuilder<'b>, - mb: ModuleBuilder, -} -impl<'a, 'b: 'a> ProgramModuleBuilder<'a, 'b> { - /// Start building a [Function] wwith the given name and signature. - pub fn function<'c, 'd: 'c, S: Into>( - &'d mut self, - name: S, - signature: Signature, - ) -> Result, SymbolConflictError> { - Ok(ProgramFunctionBuilder { - diagnostics: self.pb.diagnostics, - fb: self.mb.function(name, signature)?, - }) - } - - /// Build the current [Module], adding it to the [ProgramBuilder]. - /// - /// Returns `err` if a module with that name already exists. - pub fn build(self) -> Result<(), ModuleConflictError> { - let pb = self.pb; - let mb = self.mb; - - pb.add_module(mb.build())?; - Ok(()) - } -} -impl<'a, 'b: 'a> Deref for ProgramModuleBuilder<'a, 'b> { - type Target = ModuleBuilder; - - fn deref(&self) -> &Self::Target { - &self.mb - } -} -impl<'a, 'b: 'a> DerefMut for ProgramModuleBuilder<'a, 'b> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.mb - } -} -impl<'a, 'b: 'a> AsRef for ProgramModuleBuilder<'a, 'b> { - fn as_ref(&self) -> &ModuleBuilder { - &self.mb - } -} -impl<'a, 'b: 'a> AsMut for ProgramModuleBuilder<'a, 'b> { - fn as_mut(&mut self) -> &mut ModuleBuilder { - &mut self.mb - } -} - -/// This is used to build a [Function] from a [ProgramModuleBuilder]. -/// -/// It is basically just a wrapper around [ModuleFunctionBuilder], but overrides -/// `build` to use the [DiagnosticsHandler] of the parent -/// [ProgramBuilder]. -pub struct ProgramFunctionBuilder<'a, 'b: 'a> { - diagnostics: &'b DiagnosticsHandler, - fb: ModuleFunctionBuilder<'a>, -} -impl<'a, 'b: 'a> ProgramFunctionBuilder<'a, 'b> { - /// Build the current function - pub fn build(self) -> Result { - let diagnostics = self.diagnostics; - self.fb.build(diagnostics) - } -} -impl<'a, 'b: 'a> Deref for ProgramFunctionBuilder<'a, 'b> { - type Target = ModuleFunctionBuilder<'a>; - - fn deref(&self) -> &Self::Target { - &self.fb - } -} -impl<'a, 'b: 'a> DerefMut for ProgramFunctionBuilder<'a, 'b> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.fb - } -} -impl<'a, 'b: 'a> AsRef> for ProgramFunctionBuilder<'a, 'b> { - fn as_ref(&self) -> &ModuleFunctionBuilder<'a> { - &self.fb - } -} -impl<'a, 'b: 'a> AsMut> for ProgramFunctionBuilder<'a, 'b> { - fn as_mut(&mut self) -> &mut ModuleFunctionBuilder<'a> { - &mut self.fb - } -} diff --git a/hir/src/program_point.rs b/hir/src/program_point.rs new file mode 100644 index 000000000..23b945123 --- /dev/null +++ b/hir/src/program_point.rs @@ -0,0 +1,523 @@ +use core::fmt; + +use crate::{ + entity::{EntityProjection, EntityProjectionMut}, + Block, BlockRef, EntityCursor, EntityCursorMut, EntityMut, EntityRef, Operation, OperationRef, + Spanned, +}; + +/// [ProgramPoint] represents a specific location in the execution of a program. +/// +/// A program point consists of two parts: +/// +/// * An anchor, either a block or operation +/// * A position, i.e. the direction relative to the anchor to which the program point refers +/// +/// A program point can be reified as a cursor within a block, such that an operation inserted at +/// the cursor will be placed at the specified position relative to the anchor. +#[derive(Default, Copy, Clone)] +pub enum ProgramPoint { + /// A program point which refers to nothing, and is always invalid if used + #[default] + Invalid, + /// A program point referring to the entry or exit of a block + Block { + /// The block this program point refers to + block: BlockRef, + /// The placement of the cursor relative to `block` + position: Position, + }, + /// A program point referring to the entry or exit of an operation + Op { + /// The operation this program point refers to + op: OperationRef, + /// The placement of the cursor relative to `op` + position: Position, + }, +} + +/// Represents the placement of inserted items relative to a [ProgramPoint] +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Position { + /// New items will be inserted before the current program point + Before, + /// New items will be inserted after the current program point + After, +} + +impl From> for ProgramPoint +where + for<'a> ProgramPoint: From<&'a T>, +{ + #[inline] + fn from(entity: EntityRef<'_, T>) -> Self { + Self::from(&*entity) + } +} + +impl From> for ProgramPoint +where + for<'a> ProgramPoint: From<&'a T>, +{ + #[inline] + fn from(entity: EntityMut<'_, T>) -> Self { + Self::from(&*entity) + } +} + +/// Construct a ProgramPoint referring to the point at entry to `op` +impl From<&Operation> for ProgramPoint { + #[inline] + fn from(op: &Operation) -> Self { + Self::from(op.as_operation_ref()) + } +} + +/// Construct a ProgramPoint referring to the point at entry to `op` +impl From for ProgramPoint { + #[inline] + fn from(op: OperationRef) -> Self { + Self::Op { + op, + position: Position::Before, + } + } +} + +/// Construct a ProgramPoint referring to the point at entry to `block` +impl From<&Block> for ProgramPoint { + #[inline] + fn from(block: &Block) -> Self { + Self::at_start_of(block.as_block_ref()) + } +} + +/// Construct a ProgramPoint referring to the point at entry to `block` +impl From for ProgramPoint { + #[inline] + fn from(block: BlockRef) -> Self { + Self::Block { + block, + position: Position::Before, + } + } +} + +#[doc(hidden)] +#[derive(Copy, Clone)] +pub struct BlockPoint { + block: BlockRef, + point: Position, +} +impl From for ProgramPoint { + fn from(point: BlockPoint) -> Self { + ProgramPoint::Block { + block: point.block, + position: point.point, + } + } +} +impl From for BlockPoint { + fn from(block: BlockRef) -> Self { + Self { + block, + point: Position::Before, + } + } +} +impl From<&Block> for BlockPoint { + fn from(block: &Block) -> Self { + Self { + block: block.as_block_ref(), + point: Position::Before, + } + } +} + +impl ProgramPoint { + /// Create a [ProgramPoint] at entry to `entity`, i.e. "before" + #[inline] + pub fn before(entity: impl Into) -> Self { + entity.into() + } + + /// Create a [ProgramPoint] at exit from `entity`, i.e. "after" + pub fn after(entity: impl Into) -> Self { + let mut pp = entity.into(); + match &mut pp { + Self::Invalid => (), + Self::Op { + position: ref mut point, + .. + } + | Self::Block { + position: ref mut point, + .. + } => { + *point = Position::After; + } + } + pp + } + + /// Create a [ProgramPoint] at entry to `block`, i.e. "before" + pub fn at_start_of(block: impl Into) -> Self { + let BlockPoint { block, .. } = block.into(); + Self::Block { + block, + position: Position::Before, + } + } + + /// Create a [ProgramPoint] at exit from `block`, i.e. "after" + pub fn at_end_of(block: impl Into) -> Self { + let BlockPoint { block, .. } = block.into(); + Self::Block { + block, + position: Position::After, + } + } + + /// Returns true if this program point is at the start of the containing block + pub fn is_at_block_start(&self) -> bool { + self.operation().is_some_and(|op| { + op.parent().is_some() && op.prev().is_none() && self.placement() == Position::Before + }) || matches!(self, Self::Block { position: Position::Before, block, .. } if block.borrow().body().is_empty()) + } + + /// Returns true if this program point is at the end of the containing block + pub fn is_at_block_end(&self) -> bool { + self.operation().is_some_and(|op| { + op.parent().is_some() && op.next().is_none() && self.placement() == Position::After + }) || matches!(self, Self::Block { position: Position::After, block, .. } if block.borrow().body().is_empty()) + } + + /// Returns the block of the program point anchor. + /// + /// Returns `None`, if the program point is either invalid, or pointing to an orphaned operation + pub fn block(&self) -> Option { + match self { + Self::Invalid => None, + Self::Block { block, .. } => Some(*block), + Self::Op { op, .. } => op.parent(), + } + } + + /// Returns the program point anchor as an operation. + /// + /// Returns `None` if the program point is either invalid, or not pointing to a specific op + pub fn operation(&self) -> Option { + match self { + Self::Invalid => None, + Self::Block { + position: Position::Before, + block, + .. + } => block.borrow().body().front().as_pointer(), + Self::Block { + position: Position::After, + block, + .. + } => block.borrow().body().back().as_pointer(), + Self::Op { op, .. } => Some(*op), + } + } + + /// Returns the operation after [Self::operation], relative to this program point. + /// + /// If the current program point is in an orphaned operation, this will return the current op. + /// + /// Returns `None` if the program point is either invalid, or not pointing to a specific op + #[track_caller] + pub fn next_operation(&self) -> Option { + assert!(!self.is_at_block_end()); + match self { + Self::Op { + position: Position::After, + op, + .. + } if op.parent().is_some() => op.next(), + Self::Op { op, .. } => Some(*op), + Self::Block { + position: Position::Before, + block, + } => block.borrow().front(), + Self::Block { .. } | Self::Invalid => None, + } + } + + /// Returns the operation preceding [Self::operation], relative to this program point. + /// + /// If the current program point is in an orphaned operation, this will return the current op. + /// + /// Returns `None` if the program point is either invalid, or not pointing to a specific op + #[track_caller] + pub fn prev_operation(&self) -> Option { + assert!(!self.is_at_block_start()); + match self { + Self::Op { + position: Position::Before, + op, + .. + } if op.parent().is_some() => op.prev(), + Self::Op { op, .. } => Some(*op), + Self::Block { + position: Position::After, + block, + } => block.borrow().back(), + Self::Block { .. } | Self::Invalid => None, + } + } + + /// Returns true if this program point refers to a valid program point + #[inline] + pub fn is_valid(&self) -> bool { + !self.is_unset() + } + + /// Returns true if this program point is invalid/unset + #[inline] + pub fn is_unset(&self) -> bool { + matches!(self, Self::Invalid) + } + + /// The positioning relative to the program point anchor + pub fn placement(&self) -> Position { + match self { + Self::Invalid => Position::After, + Self::Block { + position: point, .. + } + | Self::Op { + position: point, .. + } => *point, + } + } + + /// Obtain an immutable cursor in the block corresponding to this program point. + /// + /// The resulting cursor can have `as_pointer` or `get` called on it to get the operation to + /// which this point is relative. The intuition around where the cursor is placed for a given + /// program point can be understood as answering the question of "where does the cursor need + /// to be, such that if I inserted an op at that cursor, that the insertion would be placed at + /// the referenced program point (semantically before or after an operation or block). The + /// specific rules are as follows: + /// + /// * If "before" a block, the resulting cursor is the null cursor for the containing block, + /// since an insertion at the null cursor will be placed at the start of the block. + /// * If "after" a block, the cursor is placed on the last operation in the block, as insertion + /// will place the inserted op at the end of the block + /// * If "before" an operation, the cursor is placed on the operation immediately preceding + /// `self`, or a null cursor is returned. In both cases, an insertion at the returned cursor + /// would be placed immediately before `self` + /// * If "after" an operation, the cursor is placed on the operation in `self`, so that + /// insertion will place the inserted op immediately after `self`. + /// + /// NOTE: The block to which this program point refers will be borrowed for the lifetime of the + /// returned [EntityProjection]. + pub fn cursor<'a, 'b: 'a, 'c: 'b>( + &'c self, + ) -> Option>> { + match self { + Self::Invalid => None, + Self::Block { + block, + position: point, + } => Some(EntityRef::project(block.borrow(), |block| match point { + Position::Before => block.body().front(), + Position::After => block.body().back(), + })), + Self::Op { + op, + position: point, + } => { + let block = op.parent()?; + Some(EntityRef::project(block.borrow(), |block| match point { + Position::Before => { + if let Some(placement) = op.prev() { + unsafe { block.body().cursor_from_ptr(placement) } + } else { + block.body().cursor() + } + } + Position::After => unsafe { block.body().cursor_from_ptr(*op) }, + })) + } + } + } + + /// Same as [Self::cursor], but obtains a mutable cursor instead. + /// + /// NOTE: The block to which this program point refers will be borrowed mutably for the lifetime + /// of the returned [EntityProjectionMut]. + pub fn cursor_mut<'a, 'b: 'a, 'c: 'b>( + &'c mut self, + ) -> Option>> { + match self { + Self::Invalid => None, + Self::Block { + block, + position: point, + } => Some(EntityMut::project(block.borrow_mut(), |block| match point { + Position::Before => block.body_mut().cursor_mut(), + Position::After => block.body_mut().back_mut(), + })), + Self::Op { + op, + position: point, + } => { + let mut block = op.parent()?; + Some(EntityMut::project(block.borrow_mut(), |block| match point { + Position::Before => { + if let Some(placement) = op.prev() { + unsafe { block.body_mut().cursor_mut_from_ptr(placement) } + } else { + block.body_mut().cursor_mut() + } + } + Position::After => unsafe { block.body_mut().cursor_mut_from_ptr(*op) }, + })) + } + } + } +} + +impl Eq for ProgramPoint {} + +impl PartialEq for ProgramPoint { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Invalid, Self::Invalid) => true, + (Self::Invalid, _) | (_, Self::Invalid) => false, + ( + Self::Block { + block: x, + position: xp, + }, + Self::Block { + block: y, + position: yp, + }, + ) => x == y && xp == yp, + ( + Self::Op { + op: x, + position: xp, + .. + }, + Self::Op { + op: y, + position: yp, + .. + }, + ) => x == y && xp == yp, + (..) => false, + } + } +} + +impl core::hash::Hash for ProgramPoint { + fn hash(&self, state: &mut H) { + core::mem::discriminant(self).hash(state); + match self { + Self::Invalid => (), + Self::Block { + block, + position: point, + } => { + core::ptr::hash(BlockRef::as_ptr(block), state); + point.hash(state); + } + Self::Op { + op, + position: point, + .. + } => { + core::ptr::hash(OperationRef::as_ptr(op), state); + point.hash(state); + } + } + } +} + +impl Spanned for ProgramPoint { + fn span(&self) -> crate::SourceSpan { + use crate::SourceSpan; + + match self { + Self::Invalid => SourceSpan::UNKNOWN, + Self::Block { + block, + position: point, + } => match point { + Position::Before => { + block.borrow().body().front().get().map(|op| op.span()).unwrap_or_default() + } + Position::After => { + block.borrow().body().back().get().map(|op| op.span()).unwrap_or_default() + } + }, + Self::Op { op, .. } => op.borrow().span(), + } + } +} + +impl fmt::Display for ProgramPoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use crate::EntityWithId; + match self { + Self::Invalid => f.write_str(""), + Self::Block { + block, + position: point, + } => match point { + Position::Before => write!(f, "start({})", &block.borrow().id()), + Position::After => write!(f, "end({})", &block.borrow().id()), + }, + Self::Op { + op, + position: point, + } => { + use crate::formatter::{const_text, display}; + let block = op + .parent() + .map(|blk| display(blk.borrow().id())) + .unwrap_or_else(|| const_text("null")); + match point { + Position::Before => { + write!(f, "before({} in {block})", &op.borrow().name()) + } + Position::After => { + write!(f, "after({} in {block})", &op.borrow().name()) + } + } + } + } + } +} + +impl fmt::Debug for ProgramPoint { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + use crate::EntityWithId; + match self { + Self::Invalid => f.write_str("Invalid"), + Self::Block { + block, + position: point, + } => f + .debug_struct("Block") + .field("block", &block.borrow().id()) + .field("point", point) + .finish(), + Self::Op { + op, + position: point, + } => f + .debug_struct("Op") + .field("block", &op.parent().map(|blk| blk.borrow().id())) + .field("point", point) + .field("op", &op.borrow()) + .finish(), + } + } +} diff --git a/hir/src/segments.rs b/hir/src/segments.rs deleted file mode 100644 index ac4ccea3d..000000000 --- a/hir/src/segments.rs +++ /dev/null @@ -1,356 +0,0 @@ -use alloc::sync::Arc; -use core::{ - fmt, - hash::{Hash, Hasher}, -}; - -use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink, UnsafeRef}; - -intrusive_adapter!(pub DataSegmentAdapter = UnsafeRef: DataSegment { link: LinkedListLink }); - -use crate::{ - diagnostics::{miette, Diagnostic}, - formatter, Alignable, ConstantData, Offset, -}; - -/// This error is raised when attempting to declare a [DataSegment] -/// that in some way conflicts with previously declared data segments. -#[derive(Debug, thiserror::Error, Diagnostic)] -pub enum DataSegmentError { - /// The current segment overlaps with a previously allocated segment - #[error( - "invalid data segment: segment of {size1} bytes at {offset1:#x} overlaps with segment of \ - {size2} bytes at {offset2:#x}" - )] - #[diagnostic()] - OverlappingSegments { - offset1: Offset, - size1: u32, - offset2: Offset, - size2: u32, - }, - /// The current segment and a previous definition of that segment do - /// not agree on the data or read/write properties of the memory they - /// represent. - #[error( - "invalid data segment: segment at {0:#x} conflicts with a previous segment declaration at \ - this address" - )] - #[diagnostic()] - Mismatch(Offset), - /// The current segment and size do not fall in the boundaries of the heap - /// which is allocatable to globals and other heap allocations. - /// - /// For example, Miden reserves some amount of memory for procedure locals - /// at a predetermined address, and we do not permit segments to be allocated - /// past that point. - #[error( - "invalid data segment: segment of {size} bytes at {offset:#x} would extend beyond the end \ - of the usable heap" - )] - #[diagnostic()] - OutOfBounds { offset: Offset, size: u32 }, - /// The initializer for the current segment has a size greater than `u32::MAX` bytes - #[error( - "invalid data segment: segment at {0:#x} was declared with an initializer larger than \ - 2^32 bytes" - )] - #[diagnostic()] - InitTooLarge(Offset), - /// The initializer for the current segment has a size greater than the declared segment size - #[error( - "invalid data segment: segment of {size} bytes at {offset:#x} has an initializer of \ - {actual} bytes" - )] - #[diagnostic()] - InitOutOfBounds { - offset: Offset, - size: u32, - actual: u32, - }, -} - -/// Similar to [GlobalVariableTable], this structure is used to track data segments in a module or -/// program. -#[derive(Default)] -pub struct DataSegmentTable { - segments: LinkedList, -} -impl Clone for DataSegmentTable { - fn clone(&self) -> Self { - let mut table = Self::default(); - for segment in self.segments.iter() { - table.segments.push_back(UnsafeRef::from_box(Box::new(segment.clone()))); - } - table - } -} -impl DataSegmentTable { - /// Returns true if the table has no segments defined - pub fn is_empty(&self) -> bool { - self.segments.is_empty() - } - - /// Returns the offset in linear memory where the last data segment ends - pub fn next_available_offset(&self) -> u32 { - if let Some(last_segment) = self.last() { - let next_offset = last_segment.offset() + last_segment.size(); - // Ensure the start of the globals segment is word-aligned - next_offset.align_up(32) - } else { - 0 - } - } - - /// Declare a new [DataSegment], with the given offset, size, and data. - /// - /// Returns `Err` if the declared segment overlaps/conflicts with an existing segment. - pub fn declare( - &mut self, - offset: Offset, - size: u32, - init: ConstantData, - readonly: bool, - ) -> Result<(), DataSegmentError> { - self.insert(Box::new(DataSegment::new(offset, size, init, readonly)?)) - } - - /// Insert a [DataSegment] into this table, while preserving the order of the table. - /// - /// This will fail if the segment is invalid, or overlaps/conflicts with an existing segment. - pub fn insert(&mut self, segment: Box) -> Result<(), DataSegmentError> { - let mut cursor = self.segments.front_mut(); - let end = segment.offset + segment.size; - while let Some(current_segment) = cursor.get() { - let segment_end = current_segment.offset + current_segment.size; - // If this segment starts after the segment we're declaring, - // we do not need to continue searching for conflicts, and - // can go a head and perform the insert - if current_segment.offset >= end { - cursor.insert_before(UnsafeRef::from_box(segment)); - return Ok(()); - } - // If this segment starts at the same place as the one we're - // declaring that's a guaranteed conflict - if current_segment.offset == segment.offset { - // If the two segments have the same size and offset, then - // if they match in all other respects, we're done. If they - // don't match, then we raise a mismatch error. - if current_segment.size == segment.size - && current_segment.init == segment.init - && current_segment.readonly == segment.readonly - { - return Ok(()); - } - return Err(DataSegmentError::Mismatch(segment.offset)); - } - // This segment starts before the segment we're declaring, - // make sure that this segment ends before our segment starts - if segment_end > segment.offset { - return Err(DataSegmentError::OverlappingSegments { - offset1: segment.offset, - size1: segment.size, - offset2: current_segment.offset, - size2: current_segment.size, - }); - } - - cursor.move_next(); - } - - self.segments.push_back(UnsafeRef::from_box(segment)); - - Ok(()) - } - - /// Traverse the data segments in the table in ascending order by offset - pub fn iter<'a, 'b: 'a>( - &'b self, - ) -> intrusive_collections::linked_list::Iter<'a, DataSegmentAdapter> { - self.segments.iter() - } - - /// Remove the first data segment from the table - #[inline] - pub fn pop_front(&mut self) -> Option> { - self.segments - .pop_front() - .map(|unsafe_ref| unsafe { UnsafeRef::into_box(unsafe_ref) }) - } - - /// Return a reference to the last [DataSegment] in memory - #[inline] - pub fn last(&self) -> Option<&DataSegment> { - self.segments.back().get() - } -} -impl fmt::Debug for DataSegmentTable { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_list().entries(self.segments.iter()).finish() - } -} - -/// A [DataSegment] represents a region of linear memory that should be initialized -/// with a given vector of bytes. -/// -/// This is distinct from [GlobalVariableData], which can be referenced by name, -/// and participates in linkage. Furthermore, [GlobalVariableData] is only as large -/// as it's type/initializer and alignment require, they cannot be arbitrarily sized. -/// -/// A data segment has an offset from the start of linear memory, i.e. address 0x0, -/// and a fixed size, which must be at least as large as the initializer data for -/// the segment. If the size is larger than the initializer data, then it is implied -/// that the remaining bytes will be zeroed. -/// -/// A read-only data segment is used to determine whether a given operation is permitted -/// on addresses falling in that segment - e.g. loads are allowed, stores are not. Miden -/// currently does not have any form of memory protection, so this validation is best -/// effort. -#[derive(Clone)] -pub struct DataSegment { - link: LinkedListLink, - /// The offset from the start of linear memory where this segment starts - offset: Offset, - /// The size, in bytes, of this data segment. - /// - /// By default this will be the same size as `init`, unless explicitly given. - size: u32, - /// The data to initialize this segment with, may not be larger than `size` - init: Arc, - /// Whether or not this segment is intended to be read-only data - readonly: bool, - /// Whether or not this segment starts as all zeros - zeroed: bool, -} -impl fmt::Debug for DataSegment { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("DataSegment") - .field("offset", &self.offset) - .field("size", &self.size) - .field("init", &format_args!("{}", &self.init)) - .field("readonly", &self.readonly) - .field("zeroed", &self.zeroed) - .finish() - } -} -impl formatter::PrettyPrint for DataSegment { - fn render(&self) -> formatter::Document { - use crate::formatter::*; - - let readonly = if self.readonly { - const_text("(") + const_text("mut") + const_text(")") - } else { - Document::Empty - }; - - let offset = const_text("(") - + const_text("offset") - + const_text(" ") - + display(self.offset) - + const_text(")"); - - let size = if self.size as usize != self.init.len() { - const_text("(") - + const_text("size") - + const_text(" ") - + display(self.size) - + const_text(")") - } else { - Document::Empty - }; - - let value = text(format!("{:#x}", DisplayHex(self.init.as_slice()))); - - let content = vec![readonly, offset, size, value] - .into_iter() - .filter(|section| !section.is_empty()) - .reduce(|acc, e| acc + const_text(" ") + e) - .expect("expected segment to have at least an offset and data (or size)"); - - const_text("(") + const_text("data") + const_text(" ") + content + const_text(")") - } -} -impl DataSegment { - /// Create a new [DataSegment] with the given offset, size, initializer, and readonly flag. - /// - /// If the declared size and the size of the initializer differ, then the greater of the two is - /// used. However, if the declared size is smaller than the initializer, an error is returned. - /// - /// If the offset and/or size are invalid, an error is returned. - pub(crate) fn new( - offset: Offset, - size: u32, - init: ConstantData, - readonly: bool, - ) -> Result { - // Require the initializer data to be no larger than 2^32 bytes - let init_size = - init.len().try_into().map_err(|_| DataSegmentError::InitTooLarge(offset))?; - - // Require the initializer to fit within the declared bounds - if size < init_size { - return Err(DataSegmentError::InitOutOfBounds { - offset, - size, - actual: init_size, - }); - } - - // Require the entire segment to fit within the linear memory address space - let size = core::cmp::max(size, init_size); - offset.checked_add(size).ok_or(DataSegmentError::OutOfBounds { offset, size })?; - - let zeroed = init.is_empty() || init.as_slice().iter().all(|byte| byte == &0u8); - - Ok(Self { - link: Default::default(), - offset, - size, - init: Arc::new(init), - readonly, - zeroed, - }) - } - - /// Get the offset from the base of linear memory where this segment starts - pub const fn offset(&self) -> Offset { - self.offset - } - - /// Get the size, in bytes, of this segment - pub const fn size(&self) -> u32 { - self.size - } - - /// Get a reference to this segment's initializer data - pub fn init(&self) -> Arc { - Arc::clone(&self.init) - } - - /// Returns true if this segment is intended to be read-only - pub const fn is_readonly(&self) -> bool { - self.readonly - } - - /// Returns true if this segment is zeroed at initialization - pub const fn is_zeroed(&self) -> bool { - self.zeroed - } -} -impl Eq for DataSegment {} -impl PartialEq for DataSegment { - fn eq(&self, other: &Self) -> bool { - self.offset == other.offset - && self.size == other.size - && self.init == other.init - && self.readonly == other.readonly - } -} -impl Hash for DataSegment { - fn hash(&self, state: &mut H) { - self.offset.hash(state); - self.size.hash(state); - self.init.hash(state); - self.readonly.hash(state); - } -} diff --git a/hir/src/testing.rs b/hir/src/testing.rs deleted file mode 100644 index 1eb656496..000000000 --- a/hir/src/testing.rs +++ /dev/null @@ -1,1067 +0,0 @@ -use alloc::sync::Arc; -use core::{mem, slice}; -use std::path::Path; - -use midenc_session::{Options, Session}; - -use crate::{ - diagnostics::{ - DefaultSourceManager, Emitter, SourceFile, SourceId, SourceManagerExt, SourceSpan, - }, - *, -}; - -const PAGE_SIZE: u32 = 64 * 1024; - -fn setup_diagnostics() { - use crate::diagnostics::reporting::{self, ReportHandlerOpts}; - - let result = reporting::set_hook(Box::new(|_| Box::new(ReportHandlerOpts::new().build()))); - if result.is_ok() { - reporting::set_panic_hook(); - } -} - -/// The base context used by all IR tests -pub struct TestContext { - pub session: Session, -} -impl Default for TestContext { - fn default() -> Self { - Self::default_with_emitter(None) - } -} -impl TestContext { - /// Create a new test context with the given [Session] - pub fn new(session: Session) -> Self { - setup_diagnostics(); - - Self { session } - } - - pub fn default_with_emitter(emitter: Option>) -> Self { - Self::default_with_opts_and_emitter(Default::default(), emitter) - } - - pub fn default_with_opts_and_emitter( - options: Options, - emitter: Option>, - ) -> Self { - use midenc_session::InputFile; - - setup_diagnostics(); - - let source_manager = Arc::new(DefaultSourceManager::default()); - let session = Session::new( - [InputFile::from_path("test.hir").unwrap()], - None, - None, - std::env::temp_dir(), - options, - emitter, - source_manager, - ); - - Self { session } - } - - /// Add a source file to this context - pub fn add>(&self, path: P) -> Arc { - self.session - .source_manager - .load_file(path.as_ref()) - .expect("invalid source file") - } - - /// Get a [SourceSpan] corresponding to the callsite of this function - #[track_caller] - #[inline(never)] - pub fn current_span(&self) -> SourceSpan { - let caller = core::panic::Location::caller(); - let caller_file = - Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().join(caller.file()); - let source_file = self.add(caller_file); - source_file - .line_column_to_span(caller.line(), caller.column()) - .expect("could not resolve source location") - } - - /// Get a [SourceSpan] representing the location in the given source file (by id), line and - /// column - /// - /// It is expected that line and column are 1-indexed, so they will be shifted to be 0-indexed, - /// make sure to add 1 if you already have a 0-indexed line/column on hand - pub fn span(&self, source_id: SourceId, line: u32, column: u32) -> SourceSpan { - self.session - .source_manager - .get(source_id) - .ok() - .and_then(|file| file.line_column_to_span(line - 1, column - 1)) - .unwrap_or_default() - } -} - -#[macro_export] -macro_rules! current_file { - () => { - std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().join(file!()) - }; -} - -#[macro_export] -macro_rules! span { - ($source_manager:ident, $src:ident) => { - $source_manager - .get($src) - .ok() - .and_then(|file| file.line_column_to_span(line!() - 1, column!() - 1)) - .unwrap() - }; -} - -/// pub fn issue_56(i32, i32) -> i32 { -/// block0(v0: i32, v1: i32): -/// v3 = lt v0, v1 : i1; -/// v4 = cast v3 : i32; -/// v5 = neq 0, v4 : i1; -/// v6 = select v5, v0, v1 : i32; -/// v7 = const.i32 0 -/// cond_br v7, block1(v6), block2(v6) -/// -/// block1(v8: i32): -/// v9 = add.wrapping v7, v8 -/// ret v9; -/// -/// block2(v10: i32): -/// v11 = add.wrapping v7, v10 -/// ret v11 -///} -pub fn issue56(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionIdent { - // Declare the `fib` function, with the appropriate type signature - let sig = Signature { - params: vec![AbiParam::new(Type::I32), AbiParam::new(Type::I32)], - results: vec![AbiParam::new(Type::I32)], - cc: CallConv::SystemV, - linkage: Linkage::External, - }; - let mut fb = builder.function("entrypoint", sig).expect("unexpected symbol conflict"); - - let entry = fb.current_block(); - // Get the value for `v0` and `v1` - let (v0, v1) = { - let args = fb.block_params(entry); - (args[0], args[1]) - }; - - let v3 = fb.ins().lt(v0, v1, context.current_span()); - let v4 = fb.ins().cast(v3, Type::I32, context.current_span()); - let v5 = fb.ins().neq_imm(v4, Immediate::I32(0), context.current_span()); - let v6 = fb.ins().select(v5, v0, v1, context.current_span()); - let v7 = fb.ins().i32(0, context.current_span()); - let cond = fb.ins().i1(true, context.current_span()); - - let block1 = fb.create_block(); - let block2 = fb.create_block(); - let v8 = fb.append_block_param(block1, Type::I32, context.current_span()); - let v10 = fb.append_block_param(block2, Type::I32, context.current_span()); - - fb.ins().cond_br(cond, block1, &[v6], block2, &[v6], context.current_span()); - - fb.switch_to_block(block1); - let v9 = fb.ins().add_wrapping(v7, v8, context.current_span()); - fb.ins().ret(Some(v9), context.current_span()); - - fb.switch_to_block(block2); - let v11 = fb.ins().add_wrapping(v7, v10, context.current_span()); - fb.ins().ret(Some(v11), context.current_span()); - - // We're done - fb.build(&context.session.diagnostics) - .expect("unexpected validation error, see diagnostics output") -} - -/// Construct an implementation of a function which computes the sum -/// of a Fibonnaci sequence of length `n`, using the provided builder. -/// -/// This function is very simple, does not contain any memory operations, -/// any local variables, or function calls. It makes for a good sanity -/// check. -/// -/// In simple pseudocode, this is the function we're building: -/// -/// ```text,ignore -/// fn fib(n: u32) -> u32 { -/// let mut a = 0; -/// let mut b = 1; -/// for _ in 0..=n { -/// let c = a + b; -/// a = b; -/// b = c; -/// } -/// a -/// } -/// ``` -/// -/// Expressed as IR, we're looking for: -/// -/// ```text,ignore -/// module test -/// -/// pub fn fib(u32) -> u32 { -/// entry(n: u32): -/// a0 = const.u32 0 : u32; -/// b0 = const.u32 1 : u32; -/// n0 = const.u32 0 : u32; -/// br blk0(a0, b0, n0); -/// -/// blk0(a1: u32, b1: u32, n1: u32): -/// continue = lt n1, n : i1; -/// cond_br continue, blk1, blk2(a1); -/// -/// blk1: -/// b2 = add.checked a1, b1 : u32; -/// n2 = incr.wrapping n1 : u32; -/// br blk0(b1, b2, n2); -/// -/// blk2(result: u32): -/// ret result; -/// } -/// ``` -pub fn fib1(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionIdent { - // Declare the `fib` function, with the appropriate type signature - let sig = Signature { - params: vec![AbiParam::new(Type::U32)], - results: vec![AbiParam::new(Type::U32)], - cc: CallConv::SystemV, - linkage: Linkage::External, - }; - let mut fb = builder.function("fib", sig).expect("unexpected symbol conflict"); - - let entry = fb.current_block(); - // Get the value for `n` - let n = { - let args = fb.block_params(entry); - args[0] - }; - - // This block corresponds to `blk0` in the example - let loop_header = fb.create_block(); - let a1 = fb.append_block_param(loop_header, Type::U32, context.current_span()); - let b1 = fb.append_block_param(loop_header, Type::U32, context.current_span()); - let n1 = fb.append_block_param(loop_header, Type::U32, context.current_span()); - - // This block corresponds to `blk1` in the example - let loop_body = fb.create_block(); - - // This block corresponds to `blk2` in the example - let loop_exit = fb.create_block(); - let result = fb.append_block_param(loop_exit, Type::U32, context.current_span()); - - // Now, starting from the entry block, we build out the rest of the function in control flow - // order - fb.switch_to_block(entry); - let a0 = fb.ins().u32(0, context.current_span()); - let b0 = fb.ins().u32(1, context.current_span()); - let i0 = fb.ins().u32(0, context.current_span()); - fb.ins().br(loop_header, &[a0, b0, i0], context.current_span()); - - fb.switch_to_block(loop_header); - let continue_flag = fb.ins().lt(n1, n, context.current_span()); - fb.ins() - .cond_br(continue_flag, loop_body, &[], loop_exit, &[a1], context.current_span()); - - fb.switch_to_block(loop_body); - let b2 = fb.ins().add_checked(a1, b1, context.current_span()); - let n2 = fb.ins().incr_wrapping(n1, context.current_span()); - fb.ins().br(loop_header, &[b1, b2, n2], context.current_span()); - - fb.switch_to_block(loop_exit); - fb.ins().ret(Some(result), context.current_span()); - - // We're done - fb.build(&context.session.diagnostics) - .expect("unexpected validation error, see diagnostics output") -} - -/// Construct an implementation of a function which computes the sum -/// of a matrix of u32 values, with dimensions `rows` by `cols`. -/// -/// This helper does not take a [ModuleBuilder] because it is used in some -/// simpler tests where we just want the function, you can simply add the -/// function to the [ModuleBuilder] directly. -/// -/// This function is very simple, does not contain any memory operations, -/// any local variables, or function calls. It makes for a good sanity -/// check. -/// -/// In simple pseudocode, this is the function we're building: -/// -/// ```text,ignore -/// pub fn sum_matrix(ptr: *mut u32, rows: u32, cols: u32) -> u32 { -/// let mut sum = 0; -/// if ptr.is_null() { return sum; } -/// for r in 0..rows { -/// for col in 0..cols { -/// let value = *ptr[row][col]; -/// sum += value; -/// } -/// } -/// sum -/// } -/// ``` -/// -/// This corresponds to the following IR: -/// -/// ```text,ignore -/// pub fn test(*mut u32, u32, u32) -> *mut u32 { -/// entry(ptr0: *mut u32, rows: u32, cols: u32): -/// sum0 = const.u32 0 : u32 -/// ptr1 = ptrtoint ptr0 : u32 -/// not_null = neq ptr1, 0 : i1 -/// condbr not_null, blk1, blk0(sum0) -/// -/// blk0(result0: u32): -/// ret result0 -/// -/// blk1: -/// rows0 = const.u32 0 : u32 -/// cols0 = const.u32 0 : u32 -/// row_size = mul.checked cols, 4 -/// br blk2(sum0, rows0, cols0) -/// -/// blk2(sum1: u32, rows1: u32, cols1: u32): -/// has_more_rows = lt rows1, rows -/// row_skip = mul.checked rows1, row_size -/// condbr has_more_rows, blk3(sum1, rows1, cols1), blk0(sum1) -/// -/// blk3(sum3: u32, rows3: u32, cols3: u32): -/// has_more_cols = lt cols3, cols -/// condbr has_more_cols, blk4, blk5 -/// -/// blk4: -/// col_skip = mul.checked cols3, 4 -/// skip = add.checked row_skip, col_skip -/// ptr4i = add.checked ptr1, skip -/// ptr4 = inttoptr ptr4i : *mut u32 -/// value = load ptr4 : u32 -/// sum4 = add.checked sum3, value -/// cols4 = incr.wrapping cols3 -/// br blk3(sum4, rows3, cols4) -/// -/// blk5: -/// rows5 = incr.wrapping rows3 -/// br blk2(sum3, rows5, cols3) -/// } -/// ``` -pub fn sum_matrix(builder: &mut ModuleBuilder, context: &TestContext) -> FunctionIdent { - let sig = Signature::new( - [ - AbiParam::new(Type::Ptr(Box::new(Type::U32))), - AbiParam::new(Type::U32), - AbiParam::new(Type::U32), - ], - [AbiParam::new(Type::U32)], - ); - let id = Ident::new(Symbol::intern("sum_matrix"), context.current_span()); - let mut fb = builder.function(id, sig).expect("unexpected symbol conflict"); - - let entry = fb.current_block(); - - let a = fb.create_block(); // blk0(result0: u32) - let result0 = fb.append_block_param(a, Type::U32, context.current_span()); - let b = fb.create_block(); // blk1 - let c = fb.create_block(); // blk2(sum1: u32, rows1: u32, cols1: u32) - let sum1 = fb.append_block_param(c, Type::U32, context.current_span()); - let rows1 = fb.append_block_param(c, Type::U32, context.current_span()); - let cols1 = fb.append_block_param(c, Type::U32, context.current_span()); - let d = fb.create_block(); // blk3(sum3: u32, rows3: u32, cols3: u32) - let sum3 = fb.append_block_param(d, Type::U32, context.current_span()); - let rows3 = fb.append_block_param(d, Type::U32, context.current_span()); - let cols3 = fb.append_block_param(d, Type::U32, context.current_span()); - let e = fb.create_block(); // blk4 - let f = fb.create_block(); // blk5 - - let (ptr0, rows, cols) = { - let args = fb.block_params(entry); - (args[0], args[1], args[2]) - }; - // entry - let sum0 = fb.ins().u32(0, context.current_span()); - let ptr1 = fb.ins().ptrtoint(ptr0, Type::U32, context.current_span()); - let not_null = fb.ins().neq_imm(ptr1, Immediate::U32(0), context.current_span()); - fb.ins().cond_br(not_null, b, &[], a, &[sum0], context.current_span()); - - // blk0 - fb.switch_to_block(a); - fb.ins().ret(Some(result0), context.current_span()); - - // blk1 - fb.switch_to_block(b); - let rows0 = fb.ins().u32(0, context.current_span()); - let cols0 = fb.ins().u32(0, context.current_span()); - let row_size = fb.ins().mul_imm_checked(cols, Immediate::U32(4), context.current_span()); - fb.ins().br(c, &[sum0, rows0, cols0], context.current_span()); - - // blk2(sum1, rows1, cols1) - fb.switch_to_block(c); - let has_more_rows = fb.ins().lt(rows1, rows, context.current_span()); - let row_skip = fb.ins().mul_checked(rows1, row_size, context.current_span()); - fb.ins() - .cond_br(has_more_rows, d, &[sum1, rows1, cols1], a, &[sum1], context.current_span()); - - // blk3(sum3, rows3, cols3) - fb.switch_to_block(d); - let has_more_cols = fb.ins().lt(cols3, cols, context.current_span()); - fb.ins().cond_br(has_more_cols, e, &[], f, &[], context.current_span()); - - // blk4 - fb.switch_to_block(e); - let col_skip = fb.ins().mul_imm_checked(cols3, Immediate::U32(4), context.current_span()); - let skip = fb.ins().add_checked(row_skip, col_skip, context.current_span()); - let ptr4i = fb.ins().add_checked(ptr1, skip, context.current_span()); - let ptr4 = fb.ins().inttoptr(ptr4i, Type::Ptr(Box::new(Type::U32)), context.current_span()); - let value = fb.ins().load(ptr4, context.current_span()); - let sum4 = fb.ins().add_checked(sum3, value, context.current_span()); - let cols4 = fb.ins().incr_wrapping(cols3, context.current_span()); - fb.ins().br(d, &[sum4, rows3, cols4], context.current_span()); - - // blk5 - fb.switch_to_block(f); - let rows5 = fb.ins().incr_wrapping(rows3, context.current_span()); - let cols5 = fb.ins().u32(0, context.current_span()); - fb.ins().br(c, &[sum3, rows5, cols5], context.current_span()); - - // We're done - fb.build(&context.session.diagnostics) - .expect("unexpected validation error, see diagnostics output") -} - -/// Add a predefined set of intrinsics to a given [ProgramBuilder], making -/// them available for use by other modules in that program. -/// -/// This defines the following modules and functions: -/// -/// * The `mem` module, containing memory-management intrinsics, see [mem_intrinsics] for details. -/// * The `str` module, containing string-related intrinsics, see [str_intrinsics] for details -pub fn intrinsics(builder: &mut ProgramBuilder, context: &TestContext) -> anyhow::Result<()> { - mem_intrinsics(builder, context)?; - str_intrinsics(builder, context) -} - -/// Adds a `mem` module to the given [ProgramBuilder], containing a handful of -/// useful memory-management intrinsics: -/// -/// * `malloc(u32) -> *mut u8`, allocates from the usable heap -/// * `memory_size() -> u32`, returns the amount of allocated memory, in pages -/// * `memory_grow(u32) -> u32`, grows memory by a given number of pages, returning the previous -/// memory size -/// -/// Expressed as pseudocode, the `mem` module is as follows: -/// -/// ```text,ignore -/// module mem -/// -/// /// These three globals are provided by the linker, based on where the unreserved -/// /// heap memory segment begins and ends. -/// extern { -/// #[no_mangle] -/// static mut HEAP_BASE: *mut u8; -/// #[no_mangle] -/// static mut HEAP_END: *mut u8; -/// #[no_mangle] -/// static mut HEAP_TOP: *mut u8; -/// } -/// -/// pub const PAGE_SIZE: usize = 64 * 1024; -/// -/// /// Allocate `size` bytes from the memory reserved for heap allocations -/// pub fn malloc(size: usize) -> *mut u8 { -/// let top = HEAP_TOP as usize; -/// let available = (HEAP_END as usize - top); -/// if size > available { -/// let needed = size - available; -/// let pages = (needed / PAGE_SIZE) + ((needed % PAGE_SIZE) > 0) as usize; -/// assert_ne!(memory_grow(pages), usize::MAX); -/// } -/// let addr = top + size; -/// let mut ptr: *mut u8 = addr as *mut u8; -/// // Require a min alignment of 8 bytes -/// let align_offset = addr % 8; -/// if align_offset != 0 { -/// ptr = (addr + (8 - align_offset)) as *mut u8; -/// } -/// HEAP_TOP = ptr; -/// ptr -/// } -/// -/// /// Get the size, in pages, of the current heap -/// pub fn memory_size() -> usize { -/// (HEAP_END as usize - HEAP_BASE as usize) / PAGE_SIZE -/// } -/// -/// /// Grow the number of pages reserved for the heap by `num_pages`, returning the previous page count -/// /// -/// /// Returns `usize::MAX` if unable to allocate the requested number of pages -/// pub fn memory_grow(num_pages: usize) -> usize { -/// const HEAP_MAX: usize = 2u32.pow(30); -/// let end = HEAP_END as usize; -/// let remaining = (HEAP_MAX - end) / PAGE_SIZE; -/// if num_pages > remaining { -/// usize::MAX -/// } else { -/// let prev = (end - HEAP_BASE as usize) / PAGE_SIZE; -/// HEAP_END = (end + (num_pages * PAGE_SIZE)) as *mut u8; -/// prev -/// } -/// } -/// ``` -pub fn mem_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> anyhow::Result<()> { - // Next up, the `mem` module - let mut mb = builder.module("mem"); - - // This module knows about the stack segment, but no others - mb.declare_data_segment(0, PAGE_SIZE, vec![], false) - .expect("unexpected data segment error"); - - // pub const PAGE_SIZE: usize = 64 * 1024; - mb.declare_global_variable( - "PAGE_SIZE", - Type::U32, - Linkage::External, - Some(PAGE_SIZE.to_le_bytes().into()), - SourceSpan::UNKNOWN, - ) - .expect("unexpected global variable error"); - - mb.declare_global_variable( - "HEAP_BASE", - Type::Ptr(Box::new(Type::U8)), - Linkage::External, - Some((2 * 65536u32).to_le_bytes().into()), - SourceSpan::UNKNOWN, - ) - .expect("unexpected global variable error"); - - mb.declare_global_variable( - "HEAP_TOP", - Type::Ptr(Box::new(Type::U8)), - Linkage::External, - Some((2 * 65536u32).to_le_bytes().into()), - SourceSpan::UNKNOWN, - ) - .expect("unexpected global variable error"); - - mb.declare_global_variable( - "HEAP_END", - Type::Ptr(Box::new(Type::U8)), - Linkage::External, - Some((4096 * 65536u32).to_le_bytes().into()), - SourceSpan::UNKNOWN, - ) - .expect("unexpected global variable error"); - - // Define the alloc function - let mut fb = mb.function("alloc", malloc_signature()).expect("unexpected symbol conflict"); - - let memory_grow_sig = Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::U32)]); - let memory_grow = fb.import_function("mem", "memory_grow", memory_grow_sig.clone()).unwrap(); - - // pub fn alloc(size: usize) -> *mut u8 { - // let top = HEAP_TOP as usize; - // let available = (HEAP_END as usize - top); - // if size > available { - // let needed = size - available; - // let pages = (needed / PAGE_SIZE) + ((needed % PAGE_SIZE) > 0) as usize; - // assert_ne!(memory_grow(pages), usize::MAX); - // } - // let addr = top + size; - // let mut ptr: *mut u8 = addr as *mut u8; - // // Require a min alignment of 8 bytes - // let align_offset = addr % 8; - // if align_offset != 0 { - // ptr = (addr + (8 - align_offset)) as *mut u8; - // } - // HEAP_TOP = ptr; - // ptr - // } - let raw_ptr_ty = Type::Ptr(Box::new(Type::U8)); - let size = { - let args = fb.block_params(fb.current_block()); - args[0] - }; - let heap_top = fb.ins().load_symbol("HEAP_TOP", Type::U32, SourceSpan::UNKNOWN); - let heap_end = fb.ins().load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); - let available = fb.ins().sub_checked(heap_end, heap_top, SourceSpan::UNKNOWN); - let requires_growth = fb.ins().gt(size, available, SourceSpan::UNKNOWN); - let grow_mem_block = fb.create_block(); - let alloc_block = fb.create_block(); - fb.ins() - .cond_br(requires_growth, grow_mem_block, &[], alloc_block, &[], SourceSpan::UNKNOWN); - - fb.switch_to_block(grow_mem_block); - let needed = fb.ins().sub_checked(size, available, SourceSpan::UNKNOWN); - let need_pages = - fb.ins().div_imm_checked(needed, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - let need_extra = - fb.ins().mod_imm_checked(needed, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - let extra_page = fb.ins().gt_imm(need_extra, Immediate::U32(0), SourceSpan::UNKNOWN); - let extra_count = fb.ins().zext(extra_page, Type::U32, SourceSpan::UNKNOWN); - let num_pages = fb.ins().add_checked(need_pages, extra_count, SourceSpan::UNKNOWN); - let prev_pages = { - let call = fb.ins().call(memory_grow, &[num_pages], SourceSpan::UNKNOWN); - fb.first_result(call) - }; - let usize_max = fb.ins().u32(u32::MAX, SourceSpan::UNKNOWN); - fb.ins().assert_eq(prev_pages, usize_max, SourceSpan::UNKNOWN); - fb.ins().br(alloc_block, &[], SourceSpan::UNKNOWN); - - fb.switch_to_block(alloc_block); - let addr = fb.ins().add_checked(heap_top, size, SourceSpan::UNKNOWN); - let align_offset = fb.ins().mod_imm_checked(addr, Immediate::U32(8), SourceSpan::UNKNOWN); - let is_aligned = fb.ins().eq_imm(align_offset, Immediate::U32(0), SourceSpan::UNKNOWN); - let align_block = fb.create_block(); - let aligned_block = fb.create_block(); - let new_heap_top_ptr = - fb.append_block_param(aligned_block, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); - - let ptr = fb.ins().inttoptr(addr, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); - fb.ins() - .cond_br(is_aligned, aligned_block, &[ptr], align_block, &[], SourceSpan::UNKNOWN); - - fb.switch_to_block(align_block); - let aligned_addr = fb.ins().add_imm_checked(addr, Immediate::U32(8), SourceSpan::UNKNOWN); - let aligned_addr = fb.ins().sub_checked(aligned_addr, align_offset, SourceSpan::UNKNOWN); - let aligned_ptr = fb.ins().inttoptr(aligned_addr, raw_ptr_ty.clone(), SourceSpan::UNKNOWN); - fb.ins().br(aligned_block, &[aligned_ptr], SourceSpan::UNKNOWN); - - fb.switch_to_block(aligned_block); - let heap_top_addr = fb.ins().symbol_addr( - "HEAP_TOP", - Type::Ptr(Box::new(raw_ptr_ty.clone())), - SourceSpan::UNKNOWN, - ); - fb.ins().store(heap_top_addr, new_heap_top_ptr, SourceSpan::UNKNOWN); - fb.ins().ret(Some(new_heap_top_ptr), SourceSpan::UNKNOWN); - - let _alloc = fb.build().expect("unexpected validation error, see diagnostics output"); - - // Define the memory_size function - let memory_size_sig = Signature::new([], [AbiParam::new(Type::U32)]); - let mut fb = mb.function("memory_size", memory_size_sig).expect("unexpected symbol conflict"); - - // pub fn memory_size() -> usize { - // (HEAP_END as usize - HEAP_BASE as usize) / PAGE_SIZE - // } - let heap_base_addr = fb.ins().load_symbol("HEAP_BASE", Type::U32, SourceSpan::UNKNOWN); - let heap_end_addr = fb.ins().load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); - let used = fb.ins().sub_checked(heap_end_addr, heap_base_addr, SourceSpan::UNKNOWN); - let used_pages = fb.ins().div_imm_checked(used, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - fb.ins().ret(Some(used_pages), SourceSpan::UNKNOWN); - - let _memory_size = fb.build().expect("unexpected validation error, see diagnostics output"); - - // Define the memory_grow function - let mut fb = mb.function("memory_grow", memory_grow_sig).expect("unexpected symbol conflict"); - - // pub fn memory_grow(num_pages: usize) -> usize { - // const HEAP_MAX: usize = 2u32.pow(30); - // let end = HEAP_END as usize; - // let remaining = (HEAP_MAX - end) / PAGE_SIZE; - // if num_pages > remaining { - // usize::MAX - // } else { - // let prev = (end - HEAP_BASE as usize) / PAGE_SIZE; - // HEAP_END = (end + (num_pages * PAGE_SIZE)) as *mut u8; - // prev - // } - // } - let num_pages = { - let args = fb.block_params(fb.current_block()); - args[0] - }; - let heap_end = fb.ins().load_symbol("HEAP_END", Type::U32, SourceSpan::UNKNOWN); - let heap_max = fb.ins().u32(u32::MAX, SourceSpan::UNKNOWN); - let remaining_bytes = fb.ins().sub_checked(heap_max, heap_end, SourceSpan::UNKNOWN); - let remaining_pages = - fb.ins() - .div_imm_checked(remaining_bytes, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - let out_of_memory = fb.ins().gt(num_pages, remaining_pages, SourceSpan::UNKNOWN); - let out_of_memory_block = fb.create_block(); - let grow_memory_block = fb.create_block(); - fb.ins().cond_br( - out_of_memory, - out_of_memory_block, - &[], - grow_memory_block, - &[], - SourceSpan::UNKNOWN, - ); - - fb.switch_to_block(out_of_memory_block); - fb.ins().ret_imm(Immediate::U32(u32::MAX), SourceSpan::UNKNOWN); - - fb.switch_to_block(grow_memory_block); - let heap_base = fb.ins().load_symbol("HEAP_BASE", Type::U32, SourceSpan::UNKNOWN); - let prev_bytes = fb.ins().sub_checked(heap_end, heap_base, SourceSpan::UNKNOWN); - let prev_pages = - fb.ins() - .div_imm_checked(prev_bytes, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - let num_bytes = - fb.ins() - .mul_imm_checked(num_pages, Immediate::U32(PAGE_SIZE), SourceSpan::UNKNOWN); - let new_heap_end = fb.ins().add_checked(heap_end, num_bytes, SourceSpan::UNKNOWN); - let heap_end_addr = - fb.ins() - .symbol_addr("HEAP_END", Type::Ptr(Box::new(Type::U32)), SourceSpan::UNKNOWN); - fb.ins().store(heap_end_addr, new_heap_end, SourceSpan::UNKNOWN); - fb.ins().ret(Some(prev_pages), SourceSpan::UNKNOWN); - - let _memory_grow = fb.build().expect("unexpected validation error, see diagnostics output"); - - mb.build()?; - - Ok(()) -} - -/// Adds a `str` module to the given [ProgramBuilder], containing a handful of -/// useful string-related intrinsics: -/// -/// * `from_raw_parts(*mut u8, u32)`, gets a &str from a pointer + length -/// * `compare(&str, &str) -> i8`, compares two strings, returning -1, 0, or 1 -/// -/// Expressed as pseudocode, the `str` module is as follows: -/// -/// ```text,ignore -/// module str -/// -/// // This is to illustrate the type layout of a string reference -/// type &str = { *mut u8, usize }; -/// -/// /// Convert a raw pointer and length to a `&str`, assert the pointer is non-null -/// pub fn from_raw_parts(ptr: *mut u8, len: usize) -> &str { -/// assert!(ptr as usize); -/// // This intrinsic represents construction of the fat pointer for the &str reference -/// let ptr = intrinsics::ptr::from_raw_parts(ptr as *mut (), len); -/// &*ptr -/// } -/// -/// /// Compare two `&str`, returning one of the following: -/// /// -/// /// * 1 if `a` is greater than `b` -/// /// * 0 if `a` is equal to `b` -/// /// * -1 if `a` is less than `b` -/// pub fn compare(a: &str, b: &str) -> i8 { -/// let len = max(a.len, b.len); -/// let mut i = 0; -/// while i < len { -/// let a_char = a.as_bytes()[i]; -/// let b_char = b.as_bytes()[i]; -/// if a_char > b_char { -/// return 1; -/// } else if a_char < b_char { -/// return -1; -/// } else { -/// i += 1; -/// } -/// } -/// -/// if a.len > b.len { -/// 1 -/// } else if a.len < b.len { -/// -1 -/// } else { -/// 0 -/// } -/// } -/// ``` -pub fn str_intrinsics(builder: &mut ProgramBuilder, _context: &TestContext) -> anyhow::Result<()> { - // Next up, the `str` module - let mut mb = builder.module("str"); - - // Define the from_raw_parts function - let mut fb = mb - .function("from_raw_parts", str_from_raw_parts_signature()) - .expect("unexpected symbol conflict"); - - // Unlike the high-level pseudocode, the actual implementation uses an sret parameter, i.e.: - // - // pub fn from_raw_parts(sret result: *mut str, ptr: *mut u8, len: usize) { - // assert!(ptr as usize); - // *result = { ptr, len }; - // } - let (result, ptr, len) = { - let args = fb.block_params(fb.current_block()); - (args[0], args[1], args[2]) - }; - let addr = fb.ins().ptrtoint(ptr, Type::U32, SourceSpan::UNKNOWN); - let is_nonnull_addr = fb.ins().gt_imm(addr, Immediate::U32(0), SourceSpan::UNKNOWN); - fb.ins().assert(is_nonnull_addr, SourceSpan::UNKNOWN); - let ptr_ptr = fb.ins().getelementptr(result, &[0], SourceSpan::UNKNOWN); - fb.ins().store(ptr_ptr, ptr, SourceSpan::UNKNOWN); - let len_ptr = fb.ins().getelementptr(result, &[1], SourceSpan::UNKNOWN); - fb.ins().store(len_ptr, len, SourceSpan::UNKNOWN); - fb.ins().ret(None, SourceSpan::UNKNOWN); - - let _from_raw_parts = fb.build().expect("unexpected validation error, see diagnostics output"); - - // Define the compare function - let mut fb = mb - .function("compare", str_compare_signature()) - .expect("unexpected symbol conflict"); - - // pub fn compare(a: &str, b: &str) -> i8 { - // let len = max(a.len, b.len); - // let mut i = 0; - // while i < len { - // let a_char = a.as_bytes()[i]; - // let b_char = b.as_bytes()[i]; - // if a_char > b_char { - // return 1; - // } else if a_char < b_char { - // return -1; - // } else { - // i += 1; - // } - // } - // - // if a.len > b.len { - // 1 - // } else if a.len < b.len { - // -1 - // } else { - // 0 - // } - // } - let (a, b) = { - let args = fb.block_params(fb.current_block()); - (args[0], args[1]) - }; - let a_ptr_ptr = fb.ins().getelementptr(a, &[0], SourceSpan::UNKNOWN); - let a_ptr = fb.ins().load(a_ptr_ptr, SourceSpan::UNKNOWN); - let a_addr = fb.ins().ptrtoint(a_ptr, Type::U32, SourceSpan::UNKNOWN); - let b_ptr_ptr = fb.ins().getelementptr(b, &[0], SourceSpan::UNKNOWN); - let b_ptr = fb.ins().load(b_ptr_ptr, SourceSpan::UNKNOWN); - let b_addr = fb.ins().ptrtoint(b_ptr, Type::U32, SourceSpan::UNKNOWN); - - let a_len_ptr = fb.ins().getelementptr(a, &[1], SourceSpan::UNKNOWN); - let a_len = fb.ins().load(a_len_ptr, SourceSpan::UNKNOWN); - let b_len_ptr = fb.ins().getelementptr(b, &[1], SourceSpan::UNKNOWN); - let b_len = fb.ins().load(b_len_ptr, SourceSpan::UNKNOWN); - let len = fb.ins().max(a_len, b_len, SourceSpan::UNKNOWN); - - let loop_header = fb.create_block(); - let i = fb.append_block_param(loop_header, Type::U32, SourceSpan::UNKNOWN); - let loop_body = fb.create_block(); - let loop_exit = fb.create_block(); - let exit_block = fb.create_block(); - let result = fb.append_block_param(exit_block, Type::I8, SourceSpan::UNKNOWN); - let zero = fb.ins().u32(0, SourceSpan::UNKNOWN); - fb.ins().br(loop_header, &[zero], SourceSpan::UNKNOWN); - - fb.switch_to_block(loop_header); - let done = fb.ins().lt(i, len, SourceSpan::UNKNOWN); - fb.ins().cond_br(done, loop_exit, &[], loop_body, &[], SourceSpan::UNKNOWN); - - fb.switch_to_block(loop_body); - let a_char_addr = fb.ins().incr_wrapping(a_addr, SourceSpan::UNKNOWN); - let a_char_ptr = - fb.ins() - .inttoptr(a_char_addr, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); - let a_char = fb.ins().load(a_char_ptr, SourceSpan::UNKNOWN); - let b_char_addr = fb.ins().incr_wrapping(b_addr, SourceSpan::UNKNOWN); - let b_char_ptr = - fb.ins() - .inttoptr(b_char_addr, Type::Ptr(Box::new(Type::U8)), SourceSpan::UNKNOWN); - let b_char = fb.ins().load(b_char_ptr, SourceSpan::UNKNOWN); - let is_eq = fb.ins().eq(a_char, b_char, SourceSpan::UNKNOWN); - let is_gt = fb.ins().gt(a_char, b_char, SourceSpan::UNKNOWN); - let zero = fb.ins().i8(0, SourceSpan::UNKNOWN); - let one = fb.ins().i8(1, SourceSpan::UNKNOWN); - let neg_one = fb.ins().i8(-1, SourceSpan::UNKNOWN); - let is_ne_result = fb.ins().select(is_gt, one, neg_one, SourceSpan::UNKNOWN); - let i_incr = fb.ins().incr_wrapping(i, SourceSpan::UNKNOWN); - fb.ins().cond_br( - is_eq, - loop_header, - &[i_incr], - exit_block, - &[is_ne_result], - SourceSpan::UNKNOWN, - ); - - fb.switch_to_block(loop_exit); - let is_len_eq = fb.ins().eq(a_len, b_len, SourceSpan::UNKNOWN); - let is_len_gt = fb.ins().gt(a_len, b_len, SourceSpan::UNKNOWN); - let len_gt_result = fb.ins().select(is_len_gt, one, neg_one, SourceSpan::UNKNOWN); - let len_eq_result = fb.ins().select(is_len_eq, zero, len_gt_result, SourceSpan::UNKNOWN); - fb.ins().br(exit_block, &[len_eq_result], SourceSpan::UNKNOWN); - - fb.switch_to_block(exit_block); - fb.ins().ret(Some(result), SourceSpan::UNKNOWN); - - let _compare = fb.build().expect("unexpected validation error, see diagnostics output"); - - mb.build()?; - - Ok(()) -} - -/// This function uses the provided [ProgramBuilder] to define a module which exercises -/// a variety of fundamental functionality in the IR, and in Miden Assembly: -/// -/// * Memory access, management -/// * Global variables, data segments -/// * Function calls -/// * Assertions -/// -/// NOTE: This module builds on the [intrinsics] helper. -/// -/// The following is pseudocode representing the module we define and the program entrypoint. -/// -/// ```text,ignore -/// module test -/// -/// use mem; -/// use str; -/// -/// /// This is here solely to ensure that globals are linked correctly -/// extern { -/// const PAGE_SIZE: usize; -/// } -/// -/// pub fn main() -> i32 { -/// const HELLO: &str = "hello"; -/// -/// let len = HELLO.as_bytes().len(); -/// let ptr: *mut u8 = mem::alloc(len); -/// memcpy(HELLO.as_bytes().as_ptr(), ptr, len); -/// let greeting = str::from_raw_parts(ptr, len); -/// -/// assert_eq!(PAGE_SIZE, 64 * 1024); -/// assertz!(str::compare(HELLO, greeting)); -/// -/// 0 -/// } -/// ``` -pub fn hello_world(builder: &mut ProgramBuilder, context: &TestContext) -> anyhow::Result<()> { - let mut mb = builder.module("test"); - - // Every module is going to have the same data segment for the shadow stack, - // and this module will additionally have a data segment for read-only data, - // i.e. constants - mb.declare_data_segment(0, PAGE_SIZE, vec![], false) - .expect("unexpected data segment error"); - mb.declare_data_segment(PAGE_SIZE, PAGE_SIZE, b"hello\0".to_vec(), true) - .expect("unexpected data segment error"); - - // Declare the `main` function, with the appropriate type signature - let sig = Signature::new([], [AbiParam::new(Type::I32)]); - - let mut fb = mb.function("main", sig).expect("unexpected symbol conflict"); - - let raw_ptr_ty = Type::Ptr(Box::new(Type::U8)); - let malloc = fb.import_function("mem", "alloc", malloc_signature()).unwrap(); - let str_from_raw_parts = fb - .import_function("str", "from_raw_parts", str_from_raw_parts_signature()) - .unwrap(); - let str_compare = fb.import_function("str", "compare", str_compare_signature()).unwrap(); - - // const HELLO: &str = "hello"; - // - // let len = HELLO.as_bytes().len(); - // let ptr: *mut u8 = mem::alloc(len); - // memcpy(HELLO.as_bytes().as_ptr(), ptr, len); - // let greeting = str::from_raw_parts(ptr, len); - // - // assert_eq!(PAGE_SIZE, 64 * 1024); - // assertz!(str::compare(HELLO, greeting)); - // - // 0 - let hello_data = [PAGE_SIZE, 5u32]; - let hello_data = unsafe { - let slice = hello_data.as_slice(); - ConstantData::from(slice::from_raw_parts( - slice.as_ptr() as *const u8, - mem::size_of::() * 2, - )) - }; - fb.module() - .declare_global_variable( - "HELLO", - str_type(), - Linkage::Odr, - Some(hello_data), - SourceSpan::UNKNOWN, - ) - .expect("unexpected global variable error"); - let len = fb.ins().load_symbol_relative( - "HELLO", - Type::U32, - mem::size_of::() as i32, - SourceSpan::UNKNOWN, - ); - let ptr = { - let call = fb.ins().call(malloc, &[len], SourceSpan::UNKNOWN); - fb.first_result(call) - }; - let hello_gv = fb.ins().symbol("HELLO", SourceSpan::UNKNOWN); - let hello_data_ptr = fb.ins().load_global_relative( - hello_gv, - raw_ptr_ty.clone(), - mem::size_of::() as i32, - SourceSpan::UNKNOWN, - ); - //let hello_data_ptr = fb.ins().load_symbol_relative("HELLO", raw_ptr_ty.clone(), - // mem::size_of::(), SourceSpan::UNKNOWN); - fb.ins().memcpy(hello_data_ptr, ptr, len, SourceSpan::UNKNOWN); - let greeting_ptr = fb.ins().alloca(str_type(), SourceSpan::UNKNOWN); - fb.ins() - .call(str_from_raw_parts, &[greeting_ptr, ptr, len], SourceSpan::UNKNOWN); - let page_size = fb.ins().load_symbol("PAGE_SIZE", Type::U32, SourceSpan::UNKNOWN); - let expected_page_size = fb.ins().u32(PAGE_SIZE, SourceSpan::UNKNOWN); - fb.ins().assert_eq(page_size, expected_page_size, SourceSpan::UNKNOWN); - let compared = { - let hello_ptr = - fb.ins() - .symbol_addr("HELLO", Type::Ptr(Box::new(str_type())), SourceSpan::UNKNOWN); - let call = fb.ins().call(str_compare, &[hello_ptr, greeting_ptr], SourceSpan::UNKNOWN); - fb.first_result(call) - }; - let compared = fb.ins().trunc(compared, Type::I1, SourceSpan::UNKNOWN); - fb.ins().assertz(compared, SourceSpan::UNKNOWN); - fb.ins().ret_imm(Immediate::I32(0), SourceSpan::UNKNOWN); - - // Finalize 'test::main' - fb.build().expect("unexpected validation error, see diagnostics output"); - mb.build()?; - - // Add intrinsics - intrinsics(builder, context) -} - -#[inline] -fn str_type() -> Type { - Type::Struct(StructType::new([Type::Ptr(Box::new(Type::U8)), Type::U32])) -} - -fn malloc_signature() -> Signature { - Signature::new([AbiParam::new(Type::U32)], [AbiParam::new(Type::Ptr(Box::new(Type::U8)))]) -} - -fn str_from_raw_parts_signature() -> Signature { - Signature::new( - [ - AbiParam::sret(Type::Ptr(Box::new(str_type()))), - AbiParam::new(Type::Ptr(Box::new(Type::U8))), - AbiParam::new(Type::U32), - ], - [], - ) -} - -fn str_compare_signature() -> Signature { - let str_ptr_ty = Type::Ptr(Box::new(str_type())); - let a = AbiParam::new(str_ptr_ty); - let b = a.clone(); - Signature::new([a, b], [AbiParam::new(Type::I8)]) -} diff --git a/hir/src/tests.rs b/hir/src/tests.rs deleted file mode 100644 index 2e1d44bfd..000000000 --- a/hir/src/tests.rs +++ /dev/null @@ -1,144 +0,0 @@ -use super::{testing::TestContext, *}; - -/// Test that we can construct a basic module and function and validate it -#[test] -fn simple_builder_test() { - let context = TestContext::default(); - - let mut builder = ProgramBuilder::new(&context.session.diagnostics); - { - let mut mb = builder.module("test"); - testing::fib1(mb.as_mut(), &context); - mb.build().expect("unexpected error building test module"); - } - builder.link().expect("failed to link program"); -} - -/// Test that we can emit inline assembly within a function, and correctly validate it -#[test] -fn inline_asm_builders_test() { - let context = TestContext::default(); - - // Define the 'test' module - let mut builder = ModuleBuilder::new("test"); - - // Declare the `sum` function, with the appropriate type signature - let sig = Signature { - params: vec![AbiParam::new(Type::Ptr(Box::new(Type::Felt))), AbiParam::new(Type::U32)], - results: vec![AbiParam::new(Type::Felt)], - cc: CallConv::SystemV, - linkage: Linkage::External, - }; - let mut fb = builder.function("sum", sig).expect("unexpected symbol conflict"); - - let entry = fb.current_block(); - let (ptr, len) = { - let args = fb.block_params(entry); - (args[0], args[1]) - }; - - let mut asm_builder = fb.ins().inline_asm(&[ptr, len], [Type::Felt], SourceSpan::UNKNOWN); - asm_builder.ins().push(Felt::ZERO, SourceSpan::UNKNOWN); // [sum, ptr, len] - asm_builder.ins().push_u32(0, SourceSpan::UNKNOWN); // [i, sum, ptr, len] - asm_builder.ins().dup(0, SourceSpan::UNKNOWN); // [i, i, sum, ptr, len] - asm_builder.ins().dup(4, SourceSpan::UNKNOWN); // [len, i, i, sum, ptr, len] - asm_builder.ins().lt_u32(SourceSpan::UNKNOWN); // [i < len, i, sum, ptr, len] - - // Now, build the loop body - // - // The state of the stack on entry is: [i, sum, ptr, len] - let mut lb = asm_builder.ins().while_true(SourceSpan::UNKNOWN); - - // Calculate `i / 4` - lb.ins().dup(0, SourceSpan::UNKNOWN); // [i, i, sum, ptr, len] - lb.ins().div_imm_u32(4, SourceSpan::UNKNOWN); // [word_offset, i, sum, ptr, len] - - // Calculate the address for `array[i / 4]` - lb.ins().dup(3, SourceSpan::UNKNOWN); // [ptr, word_offset, ..] - lb.ins().swap(1, SourceSpan::UNKNOWN); - lb.ins().add_u32(Overflow::Checked, SourceSpan::UNKNOWN); // [ptr + word_offset, i, sum, ptr, len] - - // Calculate the `i % 4` - lb.ins().dup(1, SourceSpan::UNKNOWN); // [i, ptr + word_offset, i, sum, ptr, len] - lb.ins().mod_imm_u32(4, SourceSpan::UNKNOWN); // [element_offset, ptr + word_offset, ..] - - // Precalculate what elements of the word to drop, so that - // we are only left with the specific element we wanted - lb.ins().push_u32(4, SourceSpan::UNKNOWN); // [n, element_offset, ..] - let mut rb = lb.ins().repeat(3, SourceSpan::UNKNOWN); - rb.ins().sub_imm_u32(1, Overflow::Checked, SourceSpan::UNKNOWN); // [n = n - 1, element_offset] - rb.ins().dup(1, SourceSpan::UNKNOWN); // [element_offset, n, element_offset, ..] - rb.ins().dup(1, SourceSpan::UNKNOWN); // [n, element_offset, n, element_offset, ..] - rb.ins().lt_u32(SourceSpan::UNKNOWN); // [element_offset < n, n, element_offset, ..] - rb.ins().movdn(2, SourceSpan::UNKNOWN); // [n, element_offset, element_offset < n] - rb.build(); // [0, element_offset, element_offset < 1, element_offset < 2, ..] - - // Clean up the now unused operands we used to calculate which element we want - lb.ins().drop(SourceSpan::UNKNOWN); // [element_offset, ..] - lb.ins().drop(SourceSpan::UNKNOWN); // [element_offset < 1, ..] - - // Load the word - lb.ins().movup(3, SourceSpan::UNKNOWN); // [ptr + word_offset, element_offset < 1] - lb.ins().loadw(SourceSpan::UNKNOWN); // [word[0], word[1], word[2], word[3], element_offset < 1] - - // Select the element, `E`, that we want by conditionally dropping - // elements on the operand stack with a carefully chosen sequence - // of conditionals: E < N forall N in 0..=3 - lb.ins().movup(4, SourceSpan::UNKNOWN); // [element_offset < 1, word[0], ..] - lb.ins().cdrop(SourceSpan::UNKNOWN); // [word[0 or 1], word[2], word[3], element_offset < 2] - lb.ins().movup(3, SourceSpan::UNKNOWN); // [element_offset < 2, word[0 or 1], ..] - lb.ins().cdrop(SourceSpan::UNKNOWN); // [word[0 or 1 or 2], word[3], element_offset < 3] - lb.ins().movup(2, SourceSpan::UNKNOWN); // [element_offset < 3, ..] - lb.ins().cdrop(SourceSpan::UNKNOWN); // [array[i], i, sum, ptr, len] - lb.ins().movup(2, SourceSpan::UNKNOWN); // [sum, array[i], i, ptr, len] - lb.ins().add(SourceSpan::UNKNOWN); // [sum + array[i], i, ptr, len] - lb.ins().swap(1, SourceSpan::UNKNOWN); // [i, sum + array[i], ptr, len] - - // We've reached the end of the loop, but we need a copy of the - // loop header here in order to use the expression `i < len` as - // the condition for the loop - lb.ins().dup(0, SourceSpan::UNKNOWN); // [i, i, sum + array[i], ptr, len] - lb.ins().dup(4, SourceSpan::UNKNOWN); // [len, i, i, sum + array[i], ptr, len] - lb.ins().lt_u32(SourceSpan::UNKNOWN); // [i < len, i, sum + array[i], ptr, len] - - // Finalize, it is at this point that validation will occur - lb.build(); - - // Clean up the operand stack and return the sum - // - // The stack here is: [i, sum, ptr, len] - asm_builder.ins().swap(1, SourceSpan::UNKNOWN); // [sum, i, ptr, len] - asm_builder.ins().movdn(3, SourceSpan::UNKNOWN); // [i, ptr, len, sum] - let mut rb = asm_builder.ins().repeat(3, SourceSpan::UNKNOWN); - rb.ins().drop(SourceSpan::UNKNOWN); - rb.build(); // [sum] - - // Finish the inline assembly block - let asm = asm_builder.build(); - // Extract the result from the inline assembly block - let sum = fb.data_flow_graph().first_result(asm); - fb.ins().ret(Some(sum), SourceSpan::default()); - - // Finish building the function, getting back the function identifier - let _sum = fb - .build(&context.session.diagnostics) - .expect("unexpected validation error, see diagnostics output"); - - // Finalize the module - builder.build(); -} - -/// Test that we can construct and link a set of modules correctly -#[test] -fn linker_test() { - let context = TestContext::default(); - - let mut builder = ProgramBuilder::new(&context.session.diagnostics); - testing::hello_world(&mut builder, &context) - .expect("unexpected error constructing test modules"); - - let _program = builder - .with_entrypoint("test::main".parse().unwrap()) - .link() - .expect("failed to link program"); -} diff --git a/hir/src/value.rs b/hir/src/value.rs deleted file mode 100644 index 24ffa6090..000000000 --- a/hir/src/value.rs +++ /dev/null @@ -1,112 +0,0 @@ -use cranelift_entity::{self as entity, entity_impl}; - -use crate::{diagnostics::SourceSpan, Block, Inst, Type}; - -pub type ValueList = entity::EntityList; -pub type ValueListPool = entity::ListPool; - -/// A handle to a single SSA value -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Value(u32); -entity_impl!(Value, "v"); -impl Default for Value { - #[inline] - fn default() -> Self { - use cranelift_entity::packed_option::ReservedValue; - - Self::reserved_value() - } -} - -/// Data associated with a `Value`. -/// -/// Values are either block arguments, instructions or aliases, and -/// in addition to being linked to a `Inst` or a `Block`, they -/// have an associated type, position, and in some cases, a `SourceSpan`. -#[derive(Debug, Clone)] -pub enum ValueData { - Inst { - ty: Type, - num: u16, - inst: Inst, - }, - Param { - ty: Type, - num: u16, - block: Block, - span: SourceSpan, - }, -} -impl ValueData { - pub fn ty(&self) -> &Type { - match self { - Self::Inst { ref ty, .. } | Self::Param { ref ty, .. } => ty, - } - } - - pub fn span(&self) -> SourceSpan { - match self { - Self::Inst { .. } => SourceSpan::UNKNOWN, - Self::Param { span, .. } => *span, - } - } - - /// Update the block to which a block parameter belongs - /// - /// NOTE: This function will panic if the value is not a block parameter - /// - /// # Safety - /// - /// This function is marked unsafe because changing the block associated - /// with a value could cause unexpected results if the other pieces of the - /// DataFlowGraph are not updated correctly. Callers must ensure that this - /// is _only_ called when a block parameter has been moved to another block. - pub unsafe fn set_block(&mut self, block: Block) { - match self { - Self::Param { - block: ref mut orig, - .. - } => { - *orig = block; - } - _ => panic!("expected block parameter, got instruction result"), - } - } - - pub fn set_type(&mut self, ty: Type) { - match self { - Self::Inst { - ty: ref mut prev_ty, - .. - } => *prev_ty = ty, - Self::Param { - ty: ref mut prev_ty, - .. - } => *prev_ty = ty, - } - } - - pub fn unwrap_inst(&self) -> Inst { - match self { - Self::Inst { inst, .. } => *inst, - _ => panic!("expected instruction result value, got block parameter"), - } - } - - pub fn num(&self) -> u16 { - match self { - Self::Inst { num, .. } | Self::Param { num, .. } => *num, - } - } -} - -pub struct Values<'a> { - pub(super) inner: entity::Iter<'a, Value, ValueData>, -} -impl<'a> Iterator for Values<'a> { - type Item = Value; - - fn next(&mut self) -> Option { - self.inner.by_ref().next().map(|kv| kv.0) - } -} diff --git a/hir/src/version.rs b/hir/src/version.rs new file mode 100644 index 000000000..593963513 --- /dev/null +++ b/hir/src/version.rs @@ -0,0 +1,82 @@ +use core::{fmt, str::FromStr}; + +pub use semver::{self, VersionReq}; + +use crate::{define_attr_type, formatter}; + +/// Represents a Semantic Versioning version string. +/// +/// This is a newtype wrapper around [semver::Version], in order to make it representable as an +/// attribute value in the IR. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Version(semver::Version); + +impl Version { + /// Create a new [Version] from the given components, with empty pre-release and build metadata. + pub const fn new(major: u64, minor: u64, patch: u64) -> Self { + Self(semver::Version::new(major, minor, patch)) + } + + /// Create Version by parsing from string representation. + /// + /// # Errors + /// + /// Possible reasons for the parse to fail include: + /// + /// * `1.0` — too few numeric components. A SemVer version must have exactly three. If you are + /// looking at something that has fewer than three numbers in it, it’s possible it is a + /// [semver::VersionReq] instead (with an implicit default ^ comparison operator). + /// * `1.0.01` — a numeric component has a leading zero. + /// * `1.0.unknown` — unexpected character in one of the components. + /// * `1.0.0- or 1.0.0+` — the pre-release or build metadata are indicated present but empty. + /// * `1.0.0-alpha_123` — pre-release or build metadata have something outside the allowed characters, which are 0-9, A-Z, a-z, -, and . (dot). + /// * `23456789999999999999.0.0` — overflow of a u64. + pub fn parse(version: impl AsRef) -> Result { + semver::Version::parse(version.as_ref()).map(Self) + } +} + +define_attr_type!(Version); + +impl FromStr for Version { + type Err = semver::Error; + + #[inline] + fn from_str(s: &str) -> Result { + Self::parse(s) + } +} + +impl core::ops::Deref for Version { + type Target = semver::Version; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl core::ops::DerefMut for Version { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl fmt::Debug for Version { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Debug::fmt(&self.0, f) + } +} + +impl fmt::Display for Version { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + fmt::Display::fmt(&self.0, f) + } +} + +impl formatter::PrettyPrint for Version { + fn render(&self) -> formatter::Document { + use formatter::*; + + display(&self.0) + } +} diff --git a/midenc-compile/CHANGELOG.md b/midenc-compile/CHANGELOG.md index 72fe84aa9..59a1818c5 100644 --- a/midenc-compile/CHANGELOG.md +++ b/midenc-compile/CHANGELOG.md @@ -6,6 +6,85 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-compile-v0.4.0...midenc-compile-v0.4.1) - 2025-09-03 + +### Other + +- Add 128-bit wide arithmetic support to the compiler. + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-compile-v0.1.5...midenc-compile-v0.4.0) - 2025-08-15 + +### Added + +- implement advice map API in Miden SDK +- add `crypto::hmerge()` in Miden SDK (`hmerge` VM intruction); + +### Other + +- rename `io` to `advice`, export modules in stdlib SDK + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-compile-v0.1.0...midenc-compile-v0.1.5) - 2025-07-01 + +### Fixed + +- allow linking MASM modules without procedures type info to + +### Other + +- add format for entrypoint option +- remove unused `LiftExportsCrossCtxStage` + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-compile-v0.0.7...midenc-compile-v0.0.8) - 2025-04-24 + +### Added +- *(frontend)* Low-level account storage API in Miden SDK +- *(frontend)* generate `CanonLower` synthetic functions for +- *(frontend)* generate `CanonLift` synthetic functions for exports +- break out arith, ub, cf, and scf dialects from hir dialect +- *(driver)* improve targeting of logs/tracing +- add `CallConv::CrossCtx` calling convention for cross-context +- implement CanonABI type flattening for app relevant `Type` variants +- draft Wasm CM function type flattening, checks for scalar type +- on Miden CCABI lifting/lowering get import/export function signature from +- store the imported function type in the component import +- rewrite the function calls from the imported function (Miden CCABI) to +- draft lifting function generation in `LiftImportsCrossCtxStage` +- draft `LiftImportsCrossCtxStage` scaffolding +- draft `LowerExportsCrossCtxStage` implementation with a lot of + +### Fixed +- missing wasmparser feature causing cargo build failure +- *(driver)* ensure codegen dialect hooks are registered +- use import/export core function's span for the generated + +### Other +- treat warnings as compiler errors, +- *(codegen)* implement initial tests for load_sw/load_dw intrinsics +- add some missing log targets +- Move the new Wasm frontend to `frontend/wasm` and remove the old +- update rust toolchain, clean up deps +- rename hir2 crates +- avoid unnecessary recompilation of artifacts +- spills implementation, a variety of bug fixes, work on is_prime test +- expose subset of compiler pipeline for emitting optimized ir in tests +- *(driver)* specify region simplification level in rewrite stage +- switch uses of hir crates to hir2 +- switch compiler to hir2 +- switch from recognizing intrinsics module by name(substring) +- update to the latest `miden-mast-package` (renamed from +- update the Miden VM with updated `miden-package` crate +- update rust toolchain to 1-16 nightly @ 1.86.0 +- add comments for the handling of the lifting/lowering in the future +- replace `CallConv::CrossCtx` with `CanonLift` and `CanonLower` +- rename `LowerExportsCrossCtxStage` -> `LiftExportsCrossCtxStage` +- remove the hard-coded `cross-ctx*` checks in Miden CCABI +- rename `CanonAbiImport::interface_function_ty` +- move `LiftImportsCrossCtxStage`, `LowerExportsCrossCtxStage` to the new `cross_ctx` module +- add stages scaffolding for lifting/lowering cross-context calls +- switch from passing `Module` to `Component` in the compiler stages +- switch to `Package` without rodata, +- [**breaking**] move `Package` to `miden-package` in the VM repo + ## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/midenc-compile-v0.0.5...midenc-compile-v0.0.6) - 2024-09-06 ### Fixed diff --git a/midenc-compile/Cargo.toml b/midenc-compile/Cargo.toml index dce139c35..5ee330626 100644 --- a/midenc-compile/Cargo.toml +++ b/midenc-compile/Cargo.toml @@ -13,18 +13,30 @@ license.workspace = true readme.workspace = true edition.workspace = true +[features] +default = ["std"] +std = [ + "miden-assembly/std", + "midenc-codegen-masm/std", + "midenc-frontend-wasm/std", + "midenc-hir/std", + "midenc-session/std", + "dep:clap", + "dep:wat", +] + [dependencies] -clap.workspace = true -either.workspace = true +clap = { workspace = true, optional = true } log.workspace = true -intrusive-collections.workspace = true inventory.workspace = true midenc-codegen-masm.workspace = true -miden-assembly = { workspace = true, features = ["std"] } +miden-assembly.workspace = true +miden-mast-package.workspace = true midenc-frontend-wasm.workspace = true +midenc-dialect-scf.workspace = true +midenc-dialect-hir.workspace = true midenc-hir.workspace = true -midenc-hir-analysis.workspace = true midenc-hir-transform.workspace = true midenc-session.workspace = true thiserror.workspace = true -wat.workspace = true +wat = { workspace = true, optional = true } diff --git a/midenc-compile/src/compiler.rs b/midenc-compile/src/compiler.rs index bd9fb6ad7..ccf197415 100644 --- a/midenc-compile/src/compiler.rs +++ b/midenc-compile/src/compiler.rs @@ -1,71 +1,97 @@ -use std::{ffi::OsString, path::PathBuf, sync::Arc}; +#[cfg(feature = "std")] +use alloc::{borrow::ToOwned, format, string::ToString, vec}; +use alloc::{string::String, sync::Arc, vec::Vec}; +#[cfg(feature = "std")] +use std::ffi::OsString; +#[cfg(feature = "std")] use clap::{builder::ArgPredicate, Parser}; use midenc_session::{ + add_target_link_libraries, diagnostics::{DefaultSourceManager, Emitter}, ColorChoice, DebugInfo, InputFile, LinkLibrary, OptLevel, Options, OutputFile, OutputType, - OutputTypeSpec, OutputTypes, ProjectType, Session, TargetEnv, Verbosity, Warnings, + OutputTypeSpec, OutputTypes, Path, PathBuf, ProjectType, Session, TargetEnv, Verbosity, + Warnings, }; /// Compile a program from WebAssembly or Miden IR, to Miden Assembly. -#[derive(Debug, Parser)] -#[command(name = "midenc")] +#[derive(Debug)] +#[cfg_attr(feature = "std", derive(Parser))] +#[cfg_attr(feature = "std", command(name = "midenc"))] pub struct Compiler { /// Write all intermediate compiler artifacts to `` /// /// Defaults to a directory named `target/midenc` in the current working directory - #[arg( - long, - value_name = "DIR", - env = "MIDENC_TARGET_DIR", - default_value = "target/midenc", - help_heading = "Output" + #[cfg_attr( + feature = "std", + arg( + long, + value_name = "DIR", + env = "MIDENC_TARGET_DIR", + default_value = "target/midenc", + help_heading = "Output" + ) )] pub target_dir: PathBuf, /// The working directory for the compiler /// /// By default this will be the working directory the compiler is executed from - #[arg(long, value_name = "DIR", help_heading = "Output")] + #[cfg_attr( + feature = "std", + arg(long, value_name = "DIR", help_heading = "Output") + )] pub working_dir: Option, /// The path to the root directory of the Miden toolchain libraries /// /// By default this is assumed to be ~/.miden/toolchains/ - #[arg( - long, - value_name = "DIR", - env = "MIDENC_SYSROOT", - help_heading = "Compiler" + #[cfg_attr( + feature = "std", + arg( + long, + value_name = "DIR", + env = "MIDENC_SYSROOT", + help_heading = "Compiler" + ) )] pub sysroot: Option, /// Write compiled output to compiler-chosen filename in `` - #[arg( - long, - short = 'O', - value_name = "DIR", - env = "MIDENC_OUT_DIR", - help_heading = "Output" + #[cfg_attr( + feature = "std", + arg( + long, + short = 'O', + value_name = "DIR", + env = "MIDENC_OUT_DIR", + help_heading = "Output" + ) )] pub output_dir: Option, /// Write compiled output to `` - #[arg(long, short = 'o', value_name = "FILENAME", help_heading = "Output")] + #[cfg_attr( + feature = "std", + arg(long, short = 'o', value_name = "FILENAME", help_heading = "Output") + )] pub output_file: Option, /// Write output to stdout - #[arg(long, conflicts_with("output_file"), help_heading = "Output")] + #[cfg_attr( + feature = "std", + arg(long, conflicts_with("output_file"), help_heading = "Output") + )] pub stdout: bool, /// Specify the name of the project being compiled /// /// The default is derived from the name of the first input file, or if reading from stdin, /// the base name of the working directory. - #[arg( + #[cfg_attr(feature = "std", arg( long, short = 'n', value_name = "NAME", default_value = None, help_heading = "Diagnostics" - )] + ))] pub name: Option, /// Specify what type and level of informational output to emit - #[arg( + #[cfg_attr(feature = "std", arg( long = "verbose", short = 'v', value_enum, @@ -74,10 +100,10 @@ pub struct Compiler { default_missing_value = "debug", num_args(0..=1), help_heading = "Diagnostics" - )] + ))] pub verbosity: Verbosity, /// Specify how warnings should be treated by the compiler. - #[arg( + #[cfg_attr(feature = "std", arg( long, short = 'W', value_enum, @@ -86,33 +112,34 @@ pub struct Compiler { default_missing_value = "all", num_args(0..=1), help_heading = "Diagnostics" - )] + ))] pub warn: Warnings, /// Whether, and how, to color terminal output - #[arg( + #[cfg_attr(feature = "std", arg( long, value_enum, default_value_t = ColorChoice::Auto, default_missing_value = "auto", num_args(0..=1), help_heading = "Diagnostics" - )] + ))] pub color: ColorChoice, /// The target environment to compile for - #[arg( + #[cfg_attr(feature = "std", arg( long, value_name = "TARGET", default_value_t = TargetEnv::Base, help_heading = "Compiler" - )] + ))] pub target: TargetEnv, /// Specify the function to call as the entrypoint for the program - #[arg(long, help_heading = "Compiler", hide(true))] + /// in the format `::` + #[cfg_attr(feature = "std", arg(long, help_heading = "Compiler", hide(true)))] pub entrypoint: Option, /// Tells the compiler to produce an executable Miden program /// /// Implied by `--entrypoint`, defaults to true for non-rollup targets. - #[arg( + #[cfg_attr(feature = "std", arg( long = "exe", default_value_t = true, default_value_ifs([ @@ -122,12 +149,12 @@ pub struct Compiler { ("entrypoint", ArgPredicate::IsPresent, Some("true")), ]), help_heading = "Linker" - )] + ))] pub is_program: bool, /// Tells the compiler to produce a Miden library /// /// Implied by `--target rollup`, defaults to false. - #[arg( + #[cfg_attr(feature = "std", arg( long = "lib", conflicts_with("is_program"), conflicts_with("entrypoint"), @@ -139,14 +166,17 @@ pub struct Compiler { ("target", "rollup".into(), Some("true")), ]), help_heading = "Linker" - )] + ))] pub is_library: bool, /// Specify one or more search paths for link libraries requested via `-l` - #[arg( - long = "search-path", - short = 'L', - value_name = "PATH", - help_heading = "Linker" + #[cfg_attr( + feature = "std", + arg( + long = "search-path", + short = 'L', + value_name = "PATH", + help_heading = "Linker" + ) )] pub search_path: Vec, /// Link compiled projects to the specified library NAME. @@ -159,17 +189,16 @@ pub struct Compiler { /// while the latter will be located in the search path based on its KIND. /// /// See below for valid KINDs: - #[arg( - long = "link-library", - short = 'l', - value_name = "[KIND=]NAME", - value_delimiter = ',', - default_value_ifs([ - ("target", "base", "std"), - ("target", "rollup", "std,base"), - ]), - next_line_help(true), - help_heading = "Linker" + #[cfg_attr( + feature = "std", + arg( + long = "link-library", + short = 'l', + value_name = "[KIND=]NAME", + value_delimiter = ',', + next_line_help(true), + help_heading = "Linker" + ) )] pub link_libraries: Vec, /// Specify one or more output types for the compiler to emit @@ -179,16 +208,20 @@ pub struct Compiler { /// multiple times. /// /// PATH must be a directory in which to place the outputs, or `-` for stdout. - #[arg( - long = "emit", - value_name = "SPEC", - value_delimiter = ',', - next_line_help(true), - help_heading = "Output" + #[cfg_attr( + feature = "std", + arg( + long = "emit", + value_name = "SPEC", + value_delimiter = ',', + env = "MIDENC_EMIT", + next_line_help(true), + help_heading = "Output" + ) )] pub output_types: Vec, /// Specify what level of debug information to emit in compilation artifacts - #[arg( + #[cfg_attr(feature = "std", arg( long, value_enum, value_name = "LEVEL", @@ -197,11 +230,11 @@ pub struct Compiler { default_missing_value = "full", num_args(0..=1), help_heading = "Output" - )] + ))] pub debug: DebugInfo, /// Specify what type, and to what degree, of optimizations to apply to code during /// compilation. - #[arg( + #[cfg_attr(feature = "std", arg( long = "optimize", value_enum, value_name = "LEVEL", @@ -210,87 +243,115 @@ pub struct Compiler { default_missing_value = "balanced", num_args(0..=1), help_heading = "Output" - )] + ))] pub opt_level: OptLevel, /// Set a codegen option /// /// Use `-C help` to print available options - #[arg( - long, - short = 'C', - value_name = "OPT[=VALUE]", - help_heading = "Compiler" + #[cfg_attr( + feature = "std", + arg( + long, + short = 'C', + value_name = "OPT[=VALUE]", + help_heading = "Compiler" + ) )] pub codegen: Vec, /// Set an unstable compiler option /// /// Use `-Z help` to print available options - #[arg( - long, - short = 'Z', - value_name = "OPT[=VALUE]", - help_heading = "Compiler" + #[cfg_attr( + feature = "std", + arg( + long, + short = 'Z', + value_name = "OPT[=VALUE]", + help_heading = "Compiler" + ) )] pub unstable: Vec, } -#[derive(Debug, Clone, Parser)] -#[command(name = "-C")] +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "std", derive(Parser))] +#[cfg_attr(feature = "std", command(name = "-C"))] pub struct CodegenOptions { /// Tell the compiler to exit after it has parsed the inputs - #[arg( + #[cfg_attr(feature = "std", arg( long, conflicts_with_all(["analyze_only", "link_only"]), default_value_t = false, - )] + ))] pub parse_only: bool, /// Tell the compiler to exit after it has performed semantic analysis on the inputs - #[arg( + #[cfg_attr(feature = "std", arg( long, conflicts_with_all(["parse_only", "link_only"]), default_value_t = false, - )] + ))] pub analyze_only: bool, /// Tell the compiler to exit after linking the inputs, without generating Miden Assembly - #[arg( + #[cfg_attr(feature = "std", arg( long, conflicts_with_all(["no_link"]), default_value_t = false, - )] + ))] pub link_only: bool, /// Tell the compiler to generate Miden Assembly from the inputs without linking them - #[arg(long, default_value_t = false)] + #[cfg_attr(feature = "std", arg(long, default_value_t = false))] pub no_link: bool, } -#[derive(Debug, Clone, Parser)] -#[command(name = "-Z")] +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "std", derive(Parser))] +#[cfg_attr(feature = "std", command(name = "-Z"))] pub struct UnstableOptions { /// Print the CFG after each HIR pass is applied - #[arg(long, default_value_t = false, help_heading = "Passes")] + #[cfg_attr( + feature = "std", + arg(long, default_value_t = false, help_heading = "Passes") + )] pub print_cfg_after_all: bool, /// Print the CFG after running a specific HIR pass - #[arg( - long, - value_name = "PASS", - value_delimiter = ',', - help_heading = "Passes" + #[cfg_attr( + feature = "std", + arg( + long, + value_name = "PASS", + value_delimiter = ',', + help_heading = "Passes" + ) )] pub print_cfg_after_pass: Vec, /// Print the IR after each pass is applied - #[arg(long, default_value_t = false, help_heading = "Passes")] + #[cfg_attr( + feature = "std", + arg(long, default_value_t = false, help_heading = "Passes") + )] pub print_ir_after_all: bool, /// Print the IR after running a specific pass - #[arg( - long, - value_name = "PASS", - value_delimiter = ',', - help_heading = "Passes" + #[cfg_attr( + feature = "std", + arg( + long, + value_name = "PASS", + value_delimiter = ',', + help_heading = "Passes" + ) )] pub print_ir_after_pass: Vec, + /// Only print the IR if the pass modified the IR structure. If this flag is set, and no IR + /// filter flag is; then the default behavior is to print the IR after every pass. + #[cfg_attr( + feature = "std", + arg(long, default_value_t = false, help_heading = "Passes") + )] + pub print_ir_after_modified: bool, } impl CodegenOptions { + #[cfg(feature = "std")] fn parse_argv(argv: Vec) -> Self { let command = ::command() .no_binary_name(true) @@ -324,9 +385,15 @@ NOTE: When specifying these options, strip the leading '--'", .map_err(format_error::) .unwrap_or_else(|err| err.exit()) } + + #[cfg(not(feature = "std"))] + fn parse_argv(_argv: Vec) -> Self { + Self::default() + } } impl UnstableOptions { + #[cfg(feature = "std")] fn parse_argv(argv: Vec) -> Self { let command = ::command() .no_binary_name(true) @@ -360,15 +427,21 @@ NOTE: When specifying these options, strip the leading '--'", .map_err(format_error::) .unwrap_or_else(|err| err.exit()) } + + #[cfg(not(feature = "std"))] + fn parse_argv(_argv: Vec) -> Self { + Self::default() + } } impl Compiler { /// Construct a [Compiler] programatically + #[cfg(feature = "std")] pub fn new_session(inputs: I, emitter: Option>, argv: A) -> Session where I: IntoIterator, A: IntoIterator, - S: Into + Clone, + S: Into + Clone, { let argv = [OsString::from("midenc")] .into_iter() @@ -392,9 +465,9 @@ impl Compiler { inputs: Vec, emitter: Option>, ) -> Session { - let cwd = self - .working_dir - .unwrap_or_else(|| std::env::current_dir().expect("no working directory available")); + let cwd = self.working_dir.unwrap_or_else(current_dir); + + log::trace!(target: "driver", "current working directory = {}", cwd.display()); // Determine if a specific output file has been requested let output_file = match self.output_file { @@ -431,7 +504,8 @@ impl Compiler { .with_optimization(self.opt_level) .with_output_types(output_types); options.search_paths = self.search_path; - options.link_libraries = self.link_libraries; + let link_libraries = add_target_link_libraries(self.link_libraries, &self.target); + options.link_libraries = link_libraries; options.entrypoint = self.entrypoint; options.parse_only = codegen.parse_only; options.analyze_only = codegen.analyze_only; @@ -441,6 +515,7 @@ impl Compiler { options.print_cfg_after_pass = unstable.print_cfg_after_pass; options.print_ir_after_all = unstable.print_ir_after_all; options.print_ir_after_pass = unstable.print_ir_after_pass; + options.print_ir_after_modified = unstable.print_ir_after_modified; // Establish --target-dir let target_dir = if self.target_dir.is_absolute() { @@ -448,9 +523,7 @@ impl Compiler { } else { options.current_dir.join(&self.target_dir) }; - std::fs::create_dir_all(&target_dir).unwrap_or_else(|err| { - panic!("unable to create --target-dir '{}': {err}", target_dir.display()) - }); + create_target_dir(target_dir.as_path()); let source_manager = Arc::new(DefaultSourceManager::default()); Session::new( @@ -465,7 +538,27 @@ impl Compiler { } } +#[cfg(feature = "std")] fn format_error(err: clap::Error) -> clap::Error { let mut cmd = I::command(); err.format(&mut cmd) } + +#[cfg(feature = "std")] +fn current_dir() -> PathBuf { + std::env::current_dir().expect("no working directory available") +} + +#[cfg(not(feature = "std"))] +fn current_dir() -> PathBuf { + >::as_ref(".").to_path_buf() +} + +#[cfg(feature = "std")] +fn create_target_dir(path: &Path) { + std::fs::create_dir_all(path) + .unwrap_or_else(|err| panic!("unable to create --target-dir '{}': {err}", path.display())); +} + +#[cfg(not(feature = "std"))] +fn create_target_dir(_path: &Path) {} diff --git a/midenc-compile/src/lib.rs b/midenc-compile/src/lib.rs index 19cc5b39b..a85797b37 100644 --- a/midenc-compile/src/lib.rs +++ b/midenc-compile/src/lib.rs @@ -1,18 +1,27 @@ +#![no_std] +#![deny(warnings)] + +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; + mod compiler; mod stage; mod stages; -use std::rc::Rc; +use alloc::{rc::Rc, vec::Vec}; -use either::Either::{self, Left, Right}; -use midenc_codegen_masm::{self as masm, MasmArtifact}; -use midenc_hir::{ - diagnostics::{miette, Diagnostic, IntoDiagnostic, Report, WrapErr}, - pass::AnalysisManager, +pub use midenc_hir::Context; +use midenc_hir::Op; +use midenc_session::{ + diagnostics::{miette, Diagnostic, Report, WrapErr}, + OutputMode, }; -use midenc_session::{OutputMode, Session}; -pub use self::compiler::Compiler; +pub use self::{ + compiler::Compiler, + stages::{CodegenOutput, LinkOutput}, +}; use self::{stage::Stage, stages::*}; pub type CompilerResult = Result; @@ -24,94 +33,129 @@ pub type CompilerResult = Result; pub struct CompilerStopped; /// Run the compiler using the provided [Session] -pub fn compile(session: Rc) -> CompilerResult<()> { +pub fn compile(context: Rc) -> CompilerResult<()> { use midenc_hir::formatter::DisplayHex; - let mut analyses = AnalysisManager::new(); + log::info!("starting compilation session"); - match compile_inputs(session.inputs.clone(), &mut analyses, &session)? { - Artifact::Assembled(ref mast) => { + + midenc_codegen_masm::register_dialect_hooks(&context); + + let session = context.session(); + match compile_inputs(session.inputs.clone(), context.clone())? { + Artifact::Assembled(ref package) => { log::info!( "succesfully assembled mast package '{}' with digest {}", - mast.name, - DisplayHex::new(&mast.digest.as_bytes()) + package.name, + DisplayHex::new(&package.digest().as_bytes()) ); session - .emit(OutputMode::Text, mast) - .into_diagnostic() + .emit(OutputMode::Text, package) + .map_err(Report::msg) .wrap_err("failed to pretty print 'mast' artifact")?; session - .emit(OutputMode::Binary, mast) - .into_diagnostic() + .emit(OutputMode::Binary, package) + .map_err(Report::msg) .wrap_err("failed to serialize 'mast' artifact") } - Artifact::Linked(_) => { - log::debug!("no outputs requested by user: pipeline stopped after linking"); - Ok(()) - } Artifact::Lowered(_) => { - log::debug!("no outputs requested by user: pipeline stopped before linking"); + log::debug!("no outputs requested by user: pipeline stopped before assembly"); Ok(()) } } } /// Same as `compile`, but return compiled artifacts to the caller -pub fn compile_to_memory(session: Rc) -> CompilerResult { - let mut analyses = AnalysisManager::new(); - compile_inputs(session.inputs.clone(), &mut analyses, &session) +pub fn compile_to_memory(context: Rc) -> CompilerResult { + let inputs = context.session().inputs.clone(); + compile_inputs(inputs, context) } /// Same as `compile_to_memory`, but allows registering a callback which will be used as an extra /// compiler stage immediately after code generation and prior to assembly, if the linker was run. pub fn compile_to_memory_with_pre_assembly_stage( - session: Rc, - stage: &mut F, + context: Rc, + pre_assembly_stage: &mut F, ) -> CompilerResult where - F: FnMut(MasmArtifact, &mut AnalysisManager, &Session) -> CompilerResult, + F: FnMut(CodegenOutput, Rc) -> CompilerResult, { - type AssemblyInput = Either; - - let mut analyses = AnalysisManager::new(); - - let mut pre_assembly_stage = move |output: AssemblyInput, - analysis: &mut AnalysisManager, - session: &Session| { - match output { - Left(artifact) => stage(artifact, analysis, session).map(Left), - right @ Right(_) => Ok(right), - } - }; let mut stages = ParseStage - .next(SemanticAnalysisStage) + .collect(LinkStage) .next_optional(ApplyRewritesStage) - .collect(LinkerStage) .next(CodegenStage) .next( - &mut pre_assembly_stage - as &mut (dyn FnMut( - AssemblyInput, - &mut AnalysisManager, - &Session, - ) -> CompilerResult + pre_assembly_stage + as &mut (dyn FnMut(CodegenOutput, Rc) -> CompilerResult + + '_), + ) + .next(AssembleStage); + + let inputs = context.session().inputs.clone(); + stages.run(inputs, context) +} + +/// Compile the current inputs without lowering to Miden Assembly. +/// +/// Returns the translated pre-link outputs of the compiler's link stage. +pub fn compile_to_optimized_hir(context: Rc) -> CompilerResult { + let mut stages = ParseStage.collect(LinkStage).next_optional(ApplyRewritesStage); + + let inputs = context.session().inputs.clone(); + stages.run(inputs, context) +} + +/// Compile the current inputs without lowering to Miden Assembly and without any IR transformations. +/// +/// Returns the translated pre-link outputs of the compiler's link stage. +pub fn compile_to_unoptimized_hir(context: Rc) -> CompilerResult { + let mut stages = ParseStage.collect(LinkStage); + + let inputs = context.session().inputs.clone(); + stages.run(inputs, context) +} + +/// Lowers previously-generated pre-link outputs of the compiler to Miden Assembly/MAST. +/// +/// Returns the compiled artifact, just like `compile_to_memory` would. +pub fn compile_link_output_to_masm(link_output: LinkOutput) -> CompilerResult { + let mut stages = CodegenStage.next(AssembleStage); + + let context = link_output.component.borrow().as_operation().context_rc(); + stages.run(link_output, context) +} + +/// Lowers previously-generated pre-link outputs of the compiler to Miden Assembly/MAST, but with +/// an provided callback that will be used as an extra compiler stage just prior to assembly. +/// +/// Returns the compiled artifact, just like `compile_to_memory` would. +pub fn compile_link_output_to_masm_with_pre_assembly_stage( + link_output: LinkOutput, + pre_assembly_stage: &mut F, +) -> CompilerResult +where + F: FnMut(CodegenOutput, Rc) -> CompilerResult, +{ + let mut stages = CodegenStage + .next( + pre_assembly_stage + as &mut (dyn FnMut(CodegenOutput, Rc) -> CompilerResult + '_), ) .next(AssembleStage); - stages.run(session.inputs.clone(), &mut analyses, &session) + let context = link_output.component.borrow().as_operation().context_rc(); + stages.run(link_output, context) } fn compile_inputs( inputs: Vec, - analyses: &mut AnalysisManager, - session: &Session, + context: Rc, ) -> CompilerResult { let mut stages = ParseStage - .next(SemanticAnalysisStage) + .collect(LinkStage) .next_optional(ApplyRewritesStage) - .collect(LinkerStage) .next(CodegenStage) .next(AssembleStage); - stages.run(inputs, analyses, session) + stages.run(inputs, context) } diff --git a/midenc-compile/src/stage.rs b/midenc-compile/src/stage.rs index 5111377c5..e42981177 100644 --- a/midenc-compile/src/stage.rs +++ b/midenc-compile/src/stage.rs @@ -1,5 +1,6 @@ -use midenc_hir::pass::AnalysisManager; -use midenc_session::Session; +use alloc::{boxed::Box, rc::Rc, vec::Vec}; + +use midenc_hir::Context; use crate::{CompilerResult, CompilerStopped}; @@ -9,17 +10,12 @@ pub trait Stage { type Output; /// Return true if this stage is disabled - fn enabled(&self, _session: &Session) -> bool { + fn enabled(&self, _context: &Context) -> bool { true } /// Run this stage - fn run( - &mut self, - input: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult; + fn run(&mut self, input: Self::Input, context: Rc) -> CompilerResult; fn next(self, stage: S) -> Chain where @@ -47,33 +43,23 @@ pub trait Stage { } } -impl<'a, I, O> Stage for &'a mut dyn FnMut(I, &mut AnalysisManager, &Session) -> CompilerResult { +impl Stage for &mut dyn FnMut(I, Rc) -> CompilerResult { type Input = I; type Output = O; #[inline] - fn run( - &mut self, - input: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { - (*self)(input, analyses, session) + fn run(&mut self, input: Self::Input, context: Rc) -> CompilerResult { + (*self)(input, context) } } -impl Stage for Box CompilerResult> { +impl Stage for Box) -> CompilerResult> { type Input = I; type Output = O; #[inline] - fn run( - &mut self, - input: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { - self(input, analyses, session) + fn run(&mut self, input: Self::Input, context: Rc) -> CompilerResult { + self(input, context) } } @@ -98,17 +84,16 @@ where fn run<'a>( &mut self, input: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, + context: Rc, ) -> CompilerResult { - if !self.a.enabled(session) { + if !self.a.enabled(&context) { return Err(CompilerStopped.into()); } - let output = self.a.run(input, analyses, session)?; - if !self.b.enabled(session) { + let output = self.a.run(input, context.clone())?; + if !self.b.enabled(&context) { return Err(CompilerStopped.into()); } - self.b.run(output, analyses, session) + self.b.run(output, context) } } @@ -133,17 +118,16 @@ where fn run<'a>( &mut self, input: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, + context: Rc, ) -> CompilerResult { - if !self.a.enabled(session) { + if !self.a.enabled(&context) { return Err(CompilerStopped.into()); } - let output = self.a.run(input, analyses, session)?; - if !self.b.enabled(session) { + let output = self.a.run(input, context.clone())?; + if !self.b.enabled(&context) { Ok(output) } else { - self.b.run(output, analyses, session) + self.b.run(output, context) } } } @@ -177,16 +161,11 @@ where type Input = I; type Output = ::Output; - fn run( - &mut self, - inputs: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { - let mut outputs = vec![]; + fn run(&mut self, inputs: Self::Input, context: Rc) -> CompilerResult { + let mut outputs = Vec::default(); for input in inputs.into_iter() { - outputs.push(self.spread.run(input, analyses, session)?); + outputs.push(self.spread.run(input, context.clone())?); } - self.join.run(outputs, analyses, session) + self.join.run(outputs, context) } } diff --git a/midenc-compile/src/stages/assemble.rs b/midenc-compile/src/stages/assemble.rs index 82d7306ac..32159467a 100644 --- a/midenc-compile/src/stages/assemble.rs +++ b/midenc-compile/src/stages/assemble.rs @@ -1,23 +1,24 @@ +use alloc::{string::ToString, vec::Vec}; + +use miden_assembly::ast::QualifiedProcedureName; +use miden_mast_package::{Dependency, MastArtifact, Package, PackageExport}; +use midenc_session::{diagnostics::IntoDiagnostic, Session}; + use super::*; /// The artifact produced by the full compiler pipeline. /// /// The type of artifact depends on what outputs were requested, and what options were specified. pub enum Artifact { - /// The user requested MASM outputs, but - Lowered(masm::ModuleTree), - Linked(masm::MasmArtifact), - Assembled(masm::Package), + Lowered(CodegenOutput), + Assembled(Package), } impl Artifact { - pub fn unwrap_mast(self) -> masm::Package { + pub fn unwrap_mast(self) -> Package { match self { Self::Assembled(mast) => mast, - Self::Linked(_) => { - panic!("expected 'mast' artifact, but got linked 'masm' artifact instead") - } Self::Lowered(_) => { - panic!("expected 'mast' artifact, but got unlinked 'masm' artifact instead") + panic!("expected 'mast' artifact, but assembler stage was not run") } } } @@ -25,43 +26,76 @@ impl Artifact { /// Perform assembly of the generated Miden Assembly, producing MAST pub struct AssembleStage; + impl Stage for AssembleStage { - type Input = Either; + type Input = CodegenOutput; type Output = Artifact; - fn run( - &mut self, - input: Self::Input, - _analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { + fn run(&mut self, input: Self::Input, context: Rc) -> CompilerResult { use midenc_hir::formatter::DisplayHex; - match input { - Left(masm_artifact) if session.should_assemble() => { - let mast = masm_artifact.assemble(session)?; - log::debug!( - "successfully assembled mast artifact with digest {}", - DisplayHex::new(&mast.digest().as_bytes()) - ); - session.emit(OutputMode::Text, &mast).into_diagnostic()?; - session.emit(OutputMode::Binary, &mast).into_diagnostic()?; - Ok(Artifact::Assembled(masm::Package::new(mast, &masm_artifact, session))) - } - Left(masm_artifact) => { - log::debug!( - "skipping assembly of mast package from masm artifact (should-assemble=false)" - ); - Ok(Artifact::Linked(masm_artifact)) - } - Right(_masm_modules) if session.should_assemble() => todo!(), /* Ok(Artifact::Assembled(todo!())), */ - Right(masm_modules) => { - log::debug!( - "skipping assembly of mast package from unlinked modules \ - (should-assemble=false)" - ); - Ok(Artifact::Lowered(masm_modules)) + let session = context.session(); + if session.should_assemble() { + log::debug!("assembling mast artifact"); + let mast = + input.component.assemble(&input.link_libraries, &input.link_packages, session)?; + log::debug!( + "successfully assembled mast artifact with digest {}", + DisplayHex::new(&mast.digest().as_bytes()) + ); + session.emit(OutputMode::Text, &mast).into_diagnostic()?; + session.emit(OutputMode::Binary, &mast).into_diagnostic()?; + Ok(Artifact::Assembled(build_package(mast, &input, session))) + } else { + log::debug!( + "skipping assembly of mast package from masm artifact (should-assemble=false)" + ); + Ok(Artifact::Lowered(input)) + } + } +} + +fn build_package(mast: MastArtifact, outputs: &CodegenOutput, session: &Session) -> Package { + let name = session.name.clone(); + + let mut dependencies = Vec::new(); + for (link_lib, lib) in session.options.link_libraries.iter().zip(outputs.link_libraries.iter()) + { + let dependency = Dependency { + name: link_lib.name.to_string().into(), + digest: *lib.digest(), + }; + dependencies.push(dependency); + } + + // Gather all of the procedure metadata for exports of this package + let mut exports: Vec = Vec::new(); + if let MastArtifact::Library(ref lib) = mast { + assert!(outputs.component.entrypoint.is_none(), "expect masm component to be a library"); + for module_info in lib.module_infos() { + for (_, proc_info) in module_info.procedures() { + let name = + QualifiedProcedureName::new(module_info.path().clone(), proc_info.name.clone()); + let digest = proc_info.digest; + let signature = proc_info.signature.as_deref().cloned(); + exports.push(miden_mast_package::PackageExport { + name, + digest, + signature, + }); } } } + + let manifest = + miden_mast_package::PackageManifest::new(exports).with_dependencies(dependencies); + + let account_component_metadata_bytes = outputs.account_component_metadata_bytes.clone(); + + miden_mast_package::Package { + name, + mast, + manifest, + account_component_metadata_bytes, + } } diff --git a/midenc-compile/src/stages/codegen.rs b/midenc-compile/src/stages/codegen.rs index 3f2d94f35..f3b8e858f 100644 --- a/midenc-compile/src/stages/codegen.rs +++ b/midenc-compile/src/stages/codegen.rs @@ -1,82 +1,108 @@ +use alloc::{boxed::Box, collections::BTreeMap, sync::Arc, vec::Vec}; + +use miden_assembly::{ast::Module, Library}; +use miden_mast_package::Package; +use midenc_codegen_masm::{ + self as masm, + intrinsics::{ + ADVICE_INTRINSICS_MODULE_NAME, CRYPTO_INTRINSICS_MODULE_NAME, I128_INTRINSICS_MODULE_NAME, + I32_INTRINSICS_MODULE_NAME, I64_INTRINSICS_MODULE_NAME, MEM_INTRINSICS_MODULE_NAME, + }, + MasmComponent, ToMasmComponent, +}; +use midenc_hir::{interner::Symbol, pass::AnalysisManager}; use midenc_session::OutputType; use super::*; +pub struct CodegenOutput { + pub component: Arc, + pub link_libraries: Vec>, + pub link_packages: BTreeMap>, + /// The serialized AccountComponentMetadata (name, description, storage layout, etc.) + pub account_component_metadata_bytes: Option>, +} + /// Perform code generation on the possibly-linked output of previous stages pub struct CodegenStage; + impl Stage for CodegenStage { - type Input = LinkerOutput; - type Output = Either; + type Input = LinkOutput; + type Output = CodegenOutput; - fn enabled(&self, session: &Session) -> bool { - session.should_codegen() + fn enabled(&self, context: &Context) -> bool { + context.session().should_codegen() } fn run( &mut self, linker_output: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, + context: Rc, ) -> CompilerResult { - let LinkerOutput { - linked, - masm: mut masm_modules, + let LinkOutput { + component, + masm: masm_modules, + mast: link_libraries, + packages: link_packages, + .. } = linker_output; - match linked { - Left(program) => { - log::debug!("lowering hir program to masm"); - let mut convert_to_masm = masm::ConvertHirToMasm::::default(); - let mut artifact = convert_to_masm.convert(program, analyses, session)?; - if session.should_emit(OutputType::Masm) { - for module in artifact.modules() { - session.emit(OutputMode::Text, module).into_diagnostic()?; - } - } + log::debug!("lowering hir program to masm"); - // Ensure intrinsics modules are linked - for intrinsics_module in required_intrinsics_modules(session) { - log::debug!( - "adding required intrinsic module '{}' to masm program", - intrinsics_module.id - ); - artifact.insert(Box::new(intrinsics_module)); - } - // Link in any MASM inputs provided to the compiler - for module in masm_modules.into_iter() { - log::debug!("adding external masm module '{}' to masm program", module.id); - artifact.insert(module); - } + let analysis_manager = AnalysisManager::new(component.as_operation_ref(), None); + let mut masm_component = + component.borrow().to_masm_component(analysis_manager).map(Box::new)?; - Ok(Left(artifact)) + let session = context.session(); + if session.should_emit(OutputType::Masm) { + for module in masm_component.modules.iter() { + session.emit(OutputMode::Text, module).into_diagnostic()?; } - Right(ir) => { - log::debug!("lowering unlinked hir modules to masm"); - let mut convert_to_masm = masm::ConvertHirToMasm::::default(); - for module in ir.into_iter() { - let masm_module = convert_to_masm.convert(module, analyses, session)?; - session - .emit(OutputMode::Text, masm_module.as_ref()) - .into_diagnostic() - .wrap_err_with(|| { - format!("failed to emit 'masm' output for '{}'", masm_module.id) - })?; - masm_modules.insert(masm_module); - } + } - Ok(Right(masm_modules)) - } + // Ensure intrinsics modules are linked + for intrinsics_module in required_intrinsics_modules(session) { + log::debug!( + "adding required intrinsic module '{}' to masm program", + intrinsics_module.path() + ); + masm_component.modules.push(intrinsics_module); } + + // Link in any MASM inputs provided to the compiler + for module in masm_modules { + log::debug!("adding external masm module '{}' to masm program", module.path()); + masm_component.modules.push(module); + } + + Ok(CodegenOutput { + component: Arc::from(masm_component), + link_libraries, + link_packages, + account_component_metadata_bytes: linker_output.account_component_metadata_bytes, + }) } } -fn required_intrinsics_modules(session: &Session) -> Vec { - vec![ - masm::intrinsics::load("intrinsics::mem", &session.source_manager) +fn required_intrinsics_modules(session: &Session) -> impl IntoIterator> { + [ + masm::intrinsics::load(MEM_INTRINSICS_MODULE_NAME, &session.source_manager) + .map(Arc::from) + .expect("undefined intrinsics module"), + masm::intrinsics::load(I32_INTRINSICS_MODULE_NAME, &session.source_manager) + .map(Arc::from) + .expect("undefined intrinsics module"), + masm::intrinsics::load(I64_INTRINSICS_MODULE_NAME, &session.source_manager) + .map(Arc::from) + .expect("undefined intrinsics module"), + masm::intrinsics::load(I128_INTRINSICS_MODULE_NAME, &session.source_manager) + .map(Arc::from) .expect("undefined intrinsics module"), - masm::intrinsics::load("intrinsics::i32", &session.source_manager) + masm::intrinsics::load(CRYPTO_INTRINSICS_MODULE_NAME, &session.source_manager) + .map(Arc::from) .expect("undefined intrinsics module"), - masm::intrinsics::load("intrinsics::i64", &session.source_manager) + masm::intrinsics::load(ADVICE_INTRINSICS_MODULE_NAME, &session.source_manager) + .map(Arc::from) .expect("undefined intrinsics module"), ] } diff --git a/midenc-compile/src/stages/link.rs b/midenc-compile/src/stages/link.rs index cbd55d44f..2e5fcaac0 100644 --- a/midenc-compile/src/stages/link.rs +++ b/midenc-compile/src/stages/link.rs @@ -1,94 +1,204 @@ +use alloc::{borrow::ToOwned, collections::BTreeMap, sync::Arc, vec::Vec}; + +use midenc_frontend_wasm::FrontendOutput; +use midenc_hir::{interner::Symbol, BuilderExt, OpBuilder, SourceSpan}; +#[cfg(feature = "std")] +use midenc_session::Path; +use midenc_session::{ + diagnostics::{Severity, Spanned}, + InputType, ProjectType, +}; + use super::*; -pub enum LinkerInput { - Hir(Box), - Masm(Box), +#[derive(Clone)] +pub struct LinkOutput { + /// The IR world in which all components/modules are represented as declarations or definitions. + pub world: builtin::WorldRef, + /// The IR component which is the primary input being compiled + pub component: builtin::ComponentRef, + /// The serialized AccountComponentMetadata (name, description, storage layout, etc.) + pub account_component_metadata_bytes: Option>, + /// The set of Miden Assembly sources to be provided to the assembler to satisfy link-time + /// dependencies + pub masm: Vec>, + /// The set of MAST libraries to be provided to the assembler to satisfy link-time dependencies + /// + /// These are either given via `-l`, or as inputs + pub mast: Vec>, + /// The set of link libraries provided to the compiler as MAST packages + pub packages: BTreeMap>, } -pub struct LinkerOutput { - /// The linked HIR program, or the unlinked HIR modules - pub linked: Either, hir::ModuleList>, - /// The set of MASM inputs to the linker - pub masm: masm::ModuleTree, +impl LinkOutput { + // Load link libraries from the given [midenc_session::Session] + pub fn link_libraries_from(&mut self, session: &Session) -> Result<(), Report> { + assert!(self.mast.is_empty(), "link libraries already loaded!"); + for link_lib in session.options.link_libraries.iter() { + log::debug!( + "registering link library '{}' ({}, from {:#?}) with linker", + link_lib.name, + link_lib.kind, + link_lib.path.as_ref() + ); + let lib = link_lib.load(session).map(Arc::new)?; + self.mast.push(lib); + } + + Ok(()) + } } -/// Link together one or more HIR modules into an HIR program -pub struct LinkerStage; -impl Stage for LinkerStage { - type Input = Vec; - type Output = LinkerOutput; - - fn run( - &mut self, - inputs: Self::Input, - _analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { - let mut ir = hir::ModuleList::default(); - let mut masm = masm::ModuleTree::default(); +/// This stage gathers together the parsed inputs, constructs a [World] representing all of the +/// parsed non-Wasm inputs and specified link libraries, and then parses the Wasm input(s) in the +/// context of that world. If successful, there are no undefined symbols present in the program. +/// +/// This stage also ensures that any builtins/intrinsics are represented in the IR. +pub struct LinkStage; + +impl Stage for LinkStage { + type Input = Vec; + type Output = LinkOutput; + + fn run(&mut self, inputs: Self::Input, context: Rc) -> CompilerResult { + // Construct an empty world + let world = { + let mut builder = OpBuilder::new(context.clone()); + let world_builder = builder.create::(SourceSpan::default()); + world_builder()? + }; + + // Construct the empty linker outputs + let mut masm = Vec::default(); + let mut mast = Vec::default(); + let mut packages = BTreeMap::default(); + + // Visit each input, validate it, and update the linker outputs accordingly + let mut component_wasm = None; for input in inputs { match input { - LinkerInput::Hir(module) => { - ir.push_back(module); + ParseOutput::Wasm(wasm) => { + if component_wasm.is_some() { + return Err(Report::msg( + "only a single wasm input can be provided at a time", + )); + } + component_wasm = Some(wasm); } - LinkerInput::Masm(module) => { - masm.insert(module); + ParseOutput::Module(module) => { + if matches!(context.session().options.project_type, ProjectType::Library if module.is_executable()) + { + return Err(context + .diagnostics() + .diagnostic(Severity::Error) + .with_message("invalid input") + .with_primary_label( + module.span(), + "cannot pass executable modules as input when compiling a library", + ) + .into_report()); + } else if module.is_executable() { + // If a module is executable, we do not need to represent it in the world + // as it is by definition unreachable from any symbols outside of itself. + masm.push(module); + } else { + // We represent library modules in the world so that the symbols are + // resolvable. + // TODO: need type information for masm procedures. + // In the meantime assume the caller is responsible for the ABI transformation + // i.e. we're passing tx kernel function mocks in the integration tests. + masm.push(module); + } } - } - } - if session.should_link() { - log::debug!("linking hir program"); - - // Construct a new [Program] builder - let mut builder = match session.options.entrypoint.as_deref() { - Some(entrypoint) => { - log::debug!("overriding entrypoint with '{entrypoint}'"); - let entrypoint = entrypoint - .parse::() - .map_err(|err| Report::msg(format!("invalid --entrypoint: {err}")))?; - hir::ProgramBuilder::new(&session.diagnostics).with_entrypoint(entrypoint) + ParseOutput::Library(lib) => { + mast.push(lib); + } + ParseOutput::Package(package) => { + packages.insert(Symbol::intern(&package.name), package); } - None => hir::ProgramBuilder::new(&session.diagnostics), - }; - - // Add our HIR modules - for module in ir.into_iter() { - log::debug!("adding '{}' to linker inputs", module.name); - builder.add_module(module)?; } + } - // Handle linking against ad-hoc MASM sources - for module in masm.iter() { - log::debug!("adding external module '{}' to linker inputs", module.name); - builder - .add_extern_module(module.id, module.functions().map(|f| f.name.function))?; + // Parse and translate the component WebAssembly using the constructed World + let component_wasm = + component_wasm.ok_or_else(|| Report::msg("expected at least one wasm input"))?; + let FrontendOutput { + component, + account_component_metadata_bytes, + } = match component_wasm { + #[cfg(feature = "std")] + InputType::Real(path) => parse_hir_from_wasm_file(&path, world, context.clone())?, + #[cfg(not(feature = "std"))] + InputType::Real(_path) => unimplemented!(), + InputType::Stdin { name, input } => { + let config = wasm::WasmTranslationConfig { + source_name: name.file_stem().unwrap().to_owned().into(), + world: Some(world), + ..Default::default() + }; + parse_hir_from_wasm_bytes(&input, context.clone(), &config)? } + }; - // Load link libraries now - for link_lib in session.options.link_libraries.iter() { - log::debug!( - "registering link library '{}' ({}, from {:#?}) with linker", - link_lib.name, - link_lib.kind, - link_lib.path.as_ref() - ); - builder.add_library(link_lib.load(session)?); - } + let mut link_output = LinkOutput { + world, + component, + account_component_metadata_bytes, + masm, + mast: Vec::with_capacity(context.session().options.link_libraries.len()), + packages, + }; - let linked = Left(builder.link()?); + link_output.link_libraries_from(context.session())?; - if session.options.link_only { - log::debug!("stopping compiler early (link-only=true)"); - Err(Report::from(CompilerStopped)) - } else { - Ok(LinkerOutput { linked, masm }) - } - } else { - log::debug!("skipping hir linker (should-link=false)"); - Ok(LinkerOutput { - linked: Right(ir), - masm, - }) + if context.session().parse_only() { + log::debug!("stopping compiler early (parse-only=true)"); + return Err(CompilerStopped.into()); + } else if context.session().analyze_only() { + log::debug!("stopping compiler early (analyze-only=true)"); + return Err(CompilerStopped.into()); + } else if context.session().options.link_only { + log::debug!("stopping compiler early (link-only=true)"); + return Err(CompilerStopped.into()); } + + Ok(link_output) } } + +#[cfg(feature = "std")] +fn parse_hir_from_wasm_file( + path: &Path, + world: builtin::WorldRef, + context: Rc, +) -> CompilerResult { + use std::io::Read; + + log::debug!("parsing hir from wasm at {}", path.display()); + let mut file = std::fs::File::open(path) + .into_diagnostic() + .wrap_err("could not open input for reading")?; + let mut bytes = Vec::with_capacity(1024); + file.read_to_end(&mut bytes).into_diagnostic()?; + let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned(); + let config = wasm::WasmTranslationConfig { + source_name: file_name.into(), + world: Some(world), + ..Default::default() + }; + parse_hir_from_wasm_bytes(&bytes, context, &config) +} + +fn parse_hir_from_wasm_bytes( + bytes: &[u8], + context: Rc, + config: &wasm::WasmTranslationConfig, +) -> CompilerResult { + let outpub = wasm::translate(bytes, config, context.clone())?; + log::debug!( + "parsed hir component from wasm bytes with first module name: {}", + outpub.component.borrow().id() + ); + + Ok(outpub) +} diff --git a/midenc-compile/src/stages/mod.rs b/midenc-compile/src/stages/mod.rs index d60f86c54..264cf1661 100644 --- a/midenc-compile/src/stages/mod.rs +++ b/midenc-compile/src/stages/mod.rs @@ -1,13 +1,11 @@ -use either::Either::{self, *}; -use midenc_codegen_masm as masm; +use alloc::rc::Rc; + use midenc_frontend_wasm as wasm; -use midenc_hir::{ - self as hir, - parser::ast, - pass::{AnalysisManager, ConversionPass, RewritePass}, -}; +use midenc_hir::{dialects::builtin, Context}; +#[cfg(feature = "std")] +use midenc_session::diagnostics::WrapErr; use midenc_session::{ - diagnostics::{IntoDiagnostic, Report, WrapErr}, + diagnostics::{IntoDiagnostic, Report}, OutputMode, Session, }; @@ -19,13 +17,11 @@ mod codegen; mod link; mod parse; mod rewrite; -mod sema; pub use self::{ assemble::{Artifact, AssembleStage}, - codegen::CodegenStage, - link::{LinkerInput, LinkerOutput, LinkerStage}, + codegen::{CodegenOutput, CodegenStage}, + link::{LinkOutput, LinkStage}, parse::{ParseOutput, ParseStage}, rewrite::ApplyRewritesStage, - sema::SemanticAnalysisStage, }; diff --git a/midenc-compile/src/stages/parse.rs b/midenc-compile/src/stages/parse.rs index 1d188c75c..291bd3a02 100644 --- a/midenc-compile/src/stages/parse.rs +++ b/midenc-compile/src/stages/parse.rs @@ -1,174 +1,153 @@ -use std::path::Path; +#[cfg(feature = "std")] +use alloc::string::ToString; +use alloc::{format, rc::Rc, sync::Arc}; +use miden_assembly::utils::Deserializable; +#[cfg(feature = "std")] +use miden_assembly::utils::ReadAdapter; use midenc_session::{ - diagnostics::{IntoDiagnostic, Spanned, WrapErr}, - InputFile, + diagnostics::{IntoDiagnostic, WrapErr}, + InputFile, InputType, }; -use wasm::WasmTranslationConfig; +#[cfg(feature = "std")] +use midenc_session::{FileName, Path}; use super::*; -/// This represents the output of the parser, depending on the type -/// of input that was parsed/loaded. +/// This represents the output of the parser, depending on the type of input that was parsed/loaded. pub enum ParseOutput { - /// We parsed HIR into the AST from text - Ast(Box), - /// We parsed HIR from a Wasm module or other binary format - Hir(Box), - /// We parsed MASM from a Miden Assembly module or other binary format - Masm(Box), + /// We found a WebAssembly binary representing a component or core module. + /// + /// This input type is processed in a later stage, here we are only interested in other input + /// types. + Wasm(InputType), + /// A single Miden Assembly module was given as an input + Module(Arc), + /// A MAST library was given as an input + Library(Arc), + /// A Miden package was given as an input + Package(Arc), } -/// This stage of compilation is where we parse input files into the -/// earliest representation supported by the input file type. Later -/// stages will handle lowering as needed. +/// This stage of compilation is where we parse input files into the earliest representation +/// supported by the input file type. Later stages will handle lowering as needed. pub struct ParseStage; + impl Stage for ParseStage { type Input = InputFile; type Output = ParseOutput; - fn run( - &mut self, - input: Self::Input, - _analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { + fn run(&mut self, input: Self::Input, context: Rc) -> CompilerResult { use midenc_session::{FileType, InputType}; - // Track when compilation began + let file_type = input.file_type(); - match &input.file { - InputType::Real(ref path) => match file_type { - FileType::Hir => self.parse_ast_from_file(path.as_ref(), session), - FileType::Wasm => self.parse_hir_from_wasm_file(path.as_ref(), session), - FileType::Wat => self.parse_hir_from_wat_file(path.as_ref(), session), - FileType::Masm => self.parse_masm_from_file(path.as_ref(), session), - FileType::Mast => Err(Report::msg( - "invalid input: mast libraries are not supported as inputs, did you mean to \ - use '-l'?", - )), - FileType::Masp => Err(Report::msg( - "invalid input: mast packages are not supported as inputs, did you mean to \ - use '-l'?", - )), + let parsed = match input.file { + #[cfg(not(feature = "std"))] + InputType::Real(_path) => unimplemented!(), + #[cfg(feature = "std")] + InputType::Real(path) => match file_type { + FileType::Hir => { + Err(Report::msg("invalid input: hir parsing is temporarily unsupported")) + } + FileType::Wasm => Ok(ParseOutput::Wasm(InputType::Real(path))), + #[cfg(not(feature = "std"))] + FileType::Wat => unimplemented!(), + #[cfg(feature = "std")] + FileType::Wat => self.parse_wasm_from_wat_file(path.as_ref()), + FileType::Masm => self.parse_masm_from_file(path.as_ref(), context.clone()), + FileType::Mast => miden_assembly::Library::deserialize_from_file(&path) + .map(Arc::new) + .map(ParseOutput::Library) + .map_err(|err| { + Report::msg(format!( + "invalid input: could not deserialize mast library: {err}" + )) + }), + FileType::Masp => { + let mut file = std::fs::File::open(&path).map_err(|err| { + Report::msg(format!("cannot open {} for reading: {err}", path.display())) + })?; + let mut adapter = ReadAdapter::new(&mut file); + miden_mast_package::Package::read_from(&mut adapter) + .map(Arc::new) + .map(ParseOutput::Package) + .map_err(|err| { + Report::msg(format!( + "failed to load mast package from {}: {err}", + path.display() + )) + }) + } }, - InputType::Stdin { name, ref input } => match file_type { - FileType::Hir => self.parse_ast_from_bytes(input, session), - FileType::Wasm => self.parse_hir_from_wasm_bytes( - input, - session, - &WasmTranslationConfig { - source_name: name.as_str().to_string().into(), - ..Default::default() - }, - ), - FileType::Wat => self.parse_hir_from_wat_bytes( - input, - session, - &WasmTranslationConfig { - source_name: name.as_str().to_string().into(), - ..Default::default() - }, - ), - FileType::Masm => self.parse_masm_from_bytes(name.as_str(), input, session), - FileType::Mast => Err(Report::msg( - "invalid input: mast libraries are not supported as inputs, did you mean to \ - use '-l'?", - )), - FileType::Masp => Err(Report::msg( - "invalid input: mast packages are not supported as inputs, did you mean to \ - use '-l'?", - )), + InputType::Stdin { name, input } => match file_type { + FileType::Hir => { + Err(Report::msg("invalid input: hir parsing is temporarily unsupported")) + } + FileType::Wasm => Ok(ParseOutput::Wasm(InputType::Stdin { name, input })), + #[cfg(not(feature = "std"))] + FileType::Wat => unimplemented!(), + #[cfg(feature = "std")] + FileType::Wat => { + let wasm = wat::parse_bytes(&input) + .into_diagnostic() + .wrap_err("failed to parse wat")?; + Ok(ParseOutput::Wasm(InputType::Stdin { + name, + input: wasm.into_owned(), + })) + } + FileType::Masm => { + self.parse_masm_from_bytes(name.as_str(), &input, context.clone()) + } + FileType::Mast => miden_assembly::Library::read_from_bytes(&input) + .map(Arc::new) + .map(ParseOutput::Library) + .map_err(|err| { + Report::msg(format!( + "invalid input: could not deserialize mast library: {err}" + )) + }), + FileType::Masp => miden_mast_package::Package::read_from_bytes(&input) + .map(Arc::new) + .map(ParseOutput::Package) + .map_err(|err| { + Report::msg(format!( + "invalid input: failed to load mast package from {name}: {err}" + )) + }), }, + }?; + + match parsed { + ParseOutput::Module(ref module) => { + context.session().emit(OutputMode::Text, module).into_diagnostic()?; + } + ParseOutput::Wasm(_) | ParseOutput::Library(_) | ParseOutput::Package(_) => (), } + + Ok(parsed) } } impl ParseStage { - fn parse_ast_from_file(&self, path: &Path, session: &Session) -> CompilerResult { - use std::io::Read; - - let mut file = std::fs::File::open(path).into_diagnostic()?; - let mut bytes = Vec::with_capacity(1024); - file.read_to_end(&mut bytes).into_diagnostic()?; - self.parse_ast_from_bytes(&bytes, session) - } - - fn parse_ast_from_bytes(&self, bytes: &[u8], session: &Session) -> CompilerResult { - use midenc_hir::parser::Parser; - - let source = core::str::from_utf8(bytes) - .into_diagnostic() - .wrap_err("input is not valid utf-8")?; - let parser = Parser::new(session); - parser.parse_str(source).map(Box::new).map(ParseOutput::Ast) - } - - fn parse_hir_from_wasm_file( - &self, - path: &Path, - session: &Session, - ) -> CompilerResult { - use std::io::Read; - - log::debug!("parsing hir from wasm at {}", path.display()); - let mut file = std::fs::File::open(path) - .into_diagnostic() - .wrap_err("could not open input for reading")?; - let mut bytes = Vec::with_capacity(1024); - file.read_to_end(&mut bytes).into_diagnostic()?; - let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned(); - let config = wasm::WasmTranslationConfig { - source_name: file_name.into(), - ..Default::default() - }; - self.parse_hir_from_wasm_bytes(&bytes, session, &config) - } - - fn parse_hir_from_wasm_bytes( - &self, - bytes: &[u8], - session: &Session, - config: &WasmTranslationConfig, - ) -> CompilerResult { - let module = wasm::translate(bytes, config, session)?.unwrap_one_module(); - log::debug!("parsed hir module from wasm bytes: {}", module.name.as_str()); - - Ok(ParseOutput::Hir(module)) - } - - fn parse_hir_from_wat_file( - &self, - path: &Path, - session: &Session, - ) -> CompilerResult { - let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned(); - let config = WasmTranslationConfig { - source_name: file_name.into(), - ..Default::default() - }; + #[cfg(feature = "std")] + fn parse_wasm_from_wat_file(&self, path: &Path) -> CompilerResult { let wasm = wat::parse_file(path).into_diagnostic().wrap_err("failed to parse wat")?; - let module = wasm::translate(&wasm, &config, session)?.unwrap_one_module(); - - Ok(ParseOutput::Hir(module)) + Ok(ParseOutput::Wasm(InputType::Stdin { + name: FileName::from(path.to_path_buf()), + input: wasm, + })) } - fn parse_hir_from_wat_bytes( + #[cfg(feature = "std")] + fn parse_masm_from_file( &self, - bytes: &[u8], - session: &Session, - config: &WasmTranslationConfig, + path: &Path, + context: Rc, ) -> CompilerResult { - let wasm = wat::parse_bytes(bytes).into_diagnostic().wrap_err("failed to parse wat")?; - let module = wasm::translate(&wasm, config, session)?.unwrap_one_module(); - - Ok(ParseOutput::Hir(module)) - } - - fn parse_masm_from_file(&self, path: &Path, session: &Session) -> CompilerResult { use miden_assembly::{ ast::{self, Ident, ModuleKind}, LibraryNamespace, LibraryPath, }; - use midenc_codegen_masm as masm; // Construct library path for MASM module let module_name = Ident::new(path.file_stem().unwrap().to_str().unwrap()) @@ -189,24 +168,21 @@ impl ParseStage { // Parse AST let mut parser = ast::Module::parser(ModuleKind::Library); - let ast = parser.parse_file(name, path, &session.source_manager)?; - let span = ast.span(); + let ast = parser.parse_file(name, path, &context.session().source_manager)?; - // Convert to MASM IR representation - Ok(ParseOutput::Masm(Box::new(masm::Module::from_ast(&ast, span)))) + Ok(ParseOutput::Module(Arc::from(ast))) } fn parse_masm_from_bytes( &self, name: &str, bytes: &[u8], - session: &Session, + context: Rc, ) -> CompilerResult { use miden_assembly::{ ast::{self, ModuleKind}, LibraryPath, }; - use midenc_codegen_masm as masm; let source = core::str::from_utf8(bytes) .into_diagnostic() @@ -217,10 +193,8 @@ impl ParseStage { // Parse AST let mut parser = ast::Module::parser(ModuleKind::Library); - let ast = parser.parse_str(name, source, &session.source_manager)?; - let span = ast.span(); + let ast = parser.parse_str(name, source, &context.session().source_manager)?; - // Convert to MASM IR representation - Ok(ParseOutput::Masm(Box::new(masm::Module::from_ast(&ast, span)))) + Ok(ParseOutput::Module(Arc::from(ast))) } } diff --git a/midenc-compile/src/stages/rewrite.rs b/midenc-compile/src/stages/rewrite.rs index 3b9c16fda..a7d8ffa06 100644 --- a/midenc-compile/src/stages/rewrite.rs +++ b/midenc-compile/src/stages/rewrite.rs @@ -1,66 +1,101 @@ -use midenc_hir::RewritePassRegistration; +use alloc::boxed::Box; + +use midenc_dialect_hir::transforms::TransformSpills; +use midenc_dialect_scf::transforms::LiftControlFlowToSCF; +use midenc_hir::{ + pass::{IRPrintingConfig, Nesting, PassManager}, + patterns::{GreedyRewriteConfig, RegionSimplificationLevel}, + Op, +}; +use midenc_hir_transform::{Canonicalizer, ControlFlowSink, SinkOperandDefs}; use super::*; /// This stage applies all registered (and enabled) module-scoped rewrites to input HIR module(s) pub struct ApplyRewritesStage; impl Stage for ApplyRewritesStage { - type Input = LinkerInput; - type Output = LinkerInput; + type Input = LinkOutput; + type Output = LinkOutput; - fn enabled(&self, session: &Session) -> bool { - !session.analyze_only() + fn enabled(&self, context: &Context) -> bool { + !context.session().options.link_only } - fn run( - &mut self, - input: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { - let output = match input { - input @ LinkerInput::Masm(_) => { - log::debug!("skipping rewrites for masm input"); - input - } - LinkerInput::Hir(mut input) => { - log::debug!("applying rewrite passes to '{}'", input.name.as_str()); - // Get all registered module rewrites and apply them in the order they appear - let mut registered = vec![]; - let matches = session.matches(); - for rewrite in inventory::iter::> { - log::trace!("checking if flag for rewrite pass '{}' is enabled", rewrite.name); - let flag = rewrite.name(); - if matches.try_contains_id(flag).is_ok() { - if let Some(index) = matches.index_of(flag) { - let is_enabled = matches.get_flag(flag); - if is_enabled { - log::debug!( - "rewrite pass '{}' is registered and enabled", - rewrite.name - ); - registered.push((index, rewrite.get())); - } - } + fn run(&mut self, input: Self::Input, context: Rc) -> CompilerResult { + let ir_print_config: IRPrintingConfig = (&context.as_ref().session().options).try_into()?; + log::debug!(target: "driver", "applying rewrite passes"); + // TODO(pauls): Set up pass registration for new pass infra + /* + // Get all registered module rewrites and apply them in the order they appear + let mut registered = vec![]; + let matches = context.session().matches(); + for rewrite in inventory::iter::> { + log::trace!("checking if flag for rewrite pass '{}' is enabled", rewrite.name); + let flag = rewrite.name(); + if matches.try_contains_id(flag).is_ok() { + if let Some(index) = matches.index_of(flag) { + let is_enabled = matches.get_flag(flag); + if is_enabled { + log::debug!( + "rewrite pass '{}' is registered and enabled", + rewrite.name + ); + registered.push((index, rewrite.get())); } } - registered.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + } + } + registered.sort_unstable_by(|(a, _), (b, _)| a.cmp(b)); + */ + + // Construct a pass manager with the default pass pipeline + let mut pm = PassManager::on::(context.clone(), Nesting::Implicit) + .enable_ir_printing(ir_print_config); - // Populate the set of rewrite passes with default transformations, if there are no - // specific passes selected. - let mut rewrites = - masm::default_rewrites(registered.into_iter().map(|(_, r)| r), session); - rewrites.apply(&mut input, analyses, session)?; + let mut rewrite_config = GreedyRewriteConfig::default(); + rewrite_config.with_region_simplification_level(RegionSimplificationLevel::Normal); - log::debug!("rewrites successful"); - LinkerInput::Hir(input) + // Component passes + { + let mut component_pm = pm.nest::(); + // Function passes for module-level functions + { + let mut module_pm = component_pm.nest::(); + let mut func_pm = module_pm.nest::(); + func_pm.add_pass(Canonicalizer::create_with_config(&rewrite_config)); + func_pm.add_pass(Box::new(LiftControlFlowToSCF)); + // Re-run canonicalization to clean up generated structured control flow + func_pm.add_pass(Canonicalizer::create_with_config(&rewrite_config)); + func_pm.add_pass(Box::new(SinkOperandDefs)); + func_pm.add_pass(Box::new(ControlFlowSink)); + func_pm.add_pass(Box::new(TransformSpills)); + } + // Function passes for component-level functions + { + let mut func_pm = component_pm.nest::(); + func_pm.add_pass(Canonicalizer::create_with_config(&rewrite_config)); + func_pm.add_pass(Box::new(LiftControlFlowToSCF)); + // Re-run canonicalization to clean up generated structured control flow + func_pm.add_pass(Canonicalizer::create_with_config(&rewrite_config)); + func_pm.add_pass(Box::new(SinkOperandDefs)); + func_pm.add_pass(Box::new(ControlFlowSink)); + func_pm.add_pass(Box::new(TransformSpills)); } - }; - if session.rewrite_only() { - log::debug!("stopping compiler early (rewrite-only=true)"); - Err(Report::from(CompilerStopped)) + } + + log::trace!(target: "driver", "before rewrites: {}", input.world.borrow().as_operation()); + + // Run pass pipeline + pm.run(input.world.as_operation_ref())?; + + log::trace!(target: "driver", "after rewrites: {}", input.world.borrow().as_operation()); + log::debug!(target: "driver", "rewrites successful"); + + if context.session().rewrite_only() { + log::debug!(target: "driver", "stopping compiler early (rewrite-only=true)"); + Err(CompilerStopped.into()) } else { - Ok(output) + Ok(input) } } } diff --git a/midenc-compile/src/stages/sema.rs b/midenc-compile/src/stages/sema.rs deleted file mode 100644 index 118222cf8..000000000 --- a/midenc-compile/src/stages/sema.rs +++ /dev/null @@ -1,66 +0,0 @@ -use super::*; - -/// This stage of compilation takes the output of the parsing -/// stage and loads it into an HIR module for later stages. -/// -/// This may involve additional validation/semantic analysis, hence the name. -pub struct SemanticAnalysisStage; -impl Stage for SemanticAnalysisStage { - type Input = ParseOutput; - type Output = LinkerInput; - - fn run( - &mut self, - input: Self::Input, - analyses: &mut AnalysisManager, - session: &Session, - ) -> CompilerResult { - let parse_only = session.parse_only(); - let output = match input { - ParseOutput::Ast(ast) if parse_only => { - log::debug!("skipping semantic analysis (parse-only=true)"); - session.emit(OutputMode::Text, &ast).into_diagnostic()?; - return Err(CompilerStopped.into()); - } - ParseOutput::Ast(ast) => { - log::debug!("performing semantic analysis on ast module '{}'", ast.name.as_str()); - session.emit(OutputMode::Text, &ast).into_diagnostic()?; - let mut convert_to_hir = ast::ConvertAstToHir; - let module = Box::new(convert_to_hir.convert(ast, analyses, session)?); - LinkerInput::Hir(module) - } - ParseOutput::Hir(module) if parse_only => { - log::debug!("skipping semantic analysis (parse-only=true)"); - session.emit(OutputMode::Text, &module).into_diagnostic()?; - return Err(CompilerStopped.into()); - } - ParseOutput::Hir(module) => { - log::debug!( - "no semantic analysis required, '{}' is already valid hir", - module.name.as_str() - ); - session.emit(OutputMode::Text, &module).into_diagnostic()?; - LinkerInput::Hir(module) - } - ParseOutput::Masm(masm) if parse_only => { - log::debug!("skipping semantic analysis (parse-only=true)"); - session.emit(OutputMode::Text, &masm).into_diagnostic()?; - return Err(CompilerStopped.into()); - } - ParseOutput::Masm(masm) => { - log::debug!( - "no semantic analysis required, '{}' is already valid hir", - masm.id.as_str() - ); - session.emit(OutputMode::Text, &masm).into_diagnostic()?; - LinkerInput::Masm(masm) - } - }; - if session.analyze_only() { - log::debug!("stopping compiler early (analyze-only=true)"); - Err(CompilerStopped.into()) - } else { - Ok(output) - } - } -} diff --git a/midenc-debug/CHANGELOG.md b/midenc-debug/CHANGELOG.md index c5f890fa4..c3734fa69 100644 --- a/midenc-debug/CHANGELOG.md +++ b/midenc-debug/CHANGELOG.md @@ -6,6 +6,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1](https://github.com/0xMiden/compiler/compare/midenc-debug-v0.4.0...midenc-debug-v0.4.1) - 2025-09-03 + +### Other + +- Add 128-bit wide arithmetic support to the compiler. + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-debug-v0.1.5...midenc-debug-v0.4.0) - 2025-08-15 + +### Fixed + +- handle empty iterator returned by `into_remainder()` +- remove incorrect(order) `FromMidenRepr::from_words()` for `[Felt; 4]` + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) +- add `test_hmerge` integration test for `hmerge` Rust API + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-debug-v0.1.0...midenc-debug-v0.1.5) - 2025-07-01 + +### Fixed + +- invoke `init` in the lifting function prologue, load the advice + +### Other + +- add format for entrypoint option + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-debug-v0.0.7...midenc-debug-v0.0.8) - 2025-04-24 + +### Added +- *(types)* clean up hir-type for use outside the compiler +- *(codegen)* migrate to element-addressable vm +- add custom dependencies to `Executor` resolver, +- *(cargo-miden)* support building Wasm component from a Cargo project + +### Fixed +- *(codegen)* incomplete global/data segment lowering + +### Other +- *(codegen)* implement initial tests for load_sw/load_dw intrinsics +- update rust toolchain, clean up deps +- enrich Miden package loading error with the file path +- rename hir2 crates +- disable compilation of old hir crates, clean up deps +- switch uses of hir crates to hir2 +- update to the latest `miden-mast-package` (renamed from +- update the Miden VM with updated `miden-package` crate +- update rust toolchain to 1-16 nightly @ 1.86.0 +- Update midenc-debug/src/exec/executor.rs +- fix doc test false positive +- switch to `Package` without rodata, +- [**breaking**] move `Package` to `miden-package` in the VM repo + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-debug-v0.0.6...midenc-debug-v0.0.7) - 2024-09-17 ### Other diff --git a/midenc-debug/Cargo.toml b/midenc-debug/Cargo.toml index 72f24e0fd..c96335432 100644 --- a/midenc-debug/Cargo.toml +++ b/midenc-debug/Cargo.toml @@ -19,8 +19,10 @@ log.workspace = true glob = "0.3.1" miden-assembly.workspace = true miden-core.workspace = true +miden-lib.workspace = true +miden-debug-types.workspace = true +miden-mast-package.workspace = true miden-processor.workspace = true -miden-stdlib.workspace = true midenc-session.workspace = true midenc-codegen-masm.workspace = true midenc-hir.workspace = true @@ -28,10 +30,10 @@ thiserror.workspace = true toml.workspace = true proptest.workspace = true serde.workspace = true -ratatui = "0.28.0" +ratatui = "0.29.0" crossterm = { version = "0.28.1", features = ["event-stream"] } -tui-input = "0.10" -tokio = { version = "1.39.2", features = ["rt", "time", "macros"] } +tui-input = "0.11" +tokio.workspace = true tokio-util = "0.7.11" futures = "0.3.30" signal-hook = "0.3.17" diff --git a/midenc-debug/src/cli.rs b/midenc-debug/src/cli.rs index 920ce79e6..b21194542 100644 --- a/midenc-debug/src/cli.rs +++ b/midenc-debug/src/cli.rs @@ -2,6 +2,7 @@ use std::{ffi::OsString, path::PathBuf, sync::Arc}; use clap::{ColorChoice, Parser}; use midenc_session::{ + add_target_link_libraries, diagnostics::{DefaultSourceManager, Emitter}, ColorChoice as MDColorChoice, InputFile, LinkLibrary, Options, ProjectType, Session, TargetEnv, }; @@ -44,6 +45,7 @@ pub struct Debugger { )] pub target: TargetEnv, /// Specify the function to call as the entrypoint for the program + /// in the format `::` #[arg(long, help_heading = "Compiler", hide(true))] pub entrypoint: Option, /// Specify one or more search paths for link libraries requested via `-l` @@ -69,10 +71,6 @@ pub struct Debugger { short = 'l', value_name = "[KIND=]NAME", value_delimiter = ',', - default_value_ifs([ - ("target", "base", "std"), - ("target", "rollup", "std,base"), - ]), next_line_help(true), help_heading = "Linker" )] @@ -123,7 +121,10 @@ impl Debugger { let mut options = Options::new(None, self.target, ProjectType::Program, cwd, self.sysroot) .with_color(color); options.search_paths = self.search_path; - options.link_libraries = self.link_libraries; + + let link_libraries = add_target_link_libraries(self.link_libraries, &self.target); + options.link_libraries = link_libraries; + options.entrypoint = self.entrypoint; let target_dir = std::env::temp_dir(); diff --git a/midenc-debug/src/config.rs b/midenc-debug/src/config.rs index 224760c4f..af6a28cd7 100644 --- a/midenc-debug/src/config.rs +++ b/midenc-debug/src/config.rs @@ -93,7 +93,7 @@ struct Advice { #[derive(Debug, Clone, Deserialize)] struct AdviceMapEntry { - digest: Digest, + digest: Word, /// Values that will be pushed to the advice stack when this entry is requested #[serde(default)] values: Vec, @@ -143,14 +143,14 @@ impl clap::builder::TypedValueParser for DebuggerConfigParser { } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -struct Digest(miden_processor::Digest); -impl<'de> Deserialize<'de> for Digest { +struct Word(miden_core::Word); +impl<'de> Deserialize<'de> for Word { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let digest = String::deserialize(deserializer)?; - miden_processor::Digest::try_from(&digest) + miden_core::Word::try_from(&digest) .map_err(|err| serde::de::Error::custom(format!("invalid digest: {err}"))) .map(Self) } @@ -160,7 +160,7 @@ fn deserialize_rodata_bytes<'de, D>(deserializer: D) -> Result, D::Error where D: serde::Deserializer<'de>, { - use midenc_hir::ConstantData; + use midenc_hir::constants::ConstantData; String::deserialize(deserializer).and_then(|hex| { ConstantData::from_str_be(hex.as_str()) @@ -185,8 +185,9 @@ where opts.max_cycles, opts.expected_cycles, /* enable_tracing= */ true, + /* enable_debugging= */ true, ) - .map(|exec_opts| exec_opts.with_debugging()) + .map(|exec_opts| exec_opts.with_debugging(true)) .map_err(|err| serde::de::Error::custom(format!("invalid execution options: {err}"))) }) } @@ -206,11 +207,12 @@ mod tests { .unwrap(); let file = toml::from_str::(&text).unwrap(); - assert!(file.inputs.values().is_empty()); - assert!(file.advice_inputs.stack().is_empty()); + let expected_inputs = StackInputs::new(vec![]).unwrap(); + assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref()); + assert!(file.advice_inputs.stack.is_empty()); assert!(file.options.enable_tracing()); assert!(file.options.enable_debugging()); - assert_eq!(file.options.max_cycles(), u32::MAX); + assert_eq!(file.options.max_cycles(), ExecutionOptions::MAX_CYCLES); assert_eq!(file.options.expected_cycles(), 64); } @@ -224,8 +226,9 @@ mod tests { .unwrap(); let file = DebuggerConfig::parse_str(&text).unwrap(); - assert!(file.inputs.values().is_empty()); - assert!(file.advice_inputs.stack().is_empty()); + let expected_inputs = StackInputs::new(vec![]).unwrap(); + assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref()); + assert!(file.advice_inputs.stack.is_empty()); assert!(file.options.enable_tracing()); assert!(file.options.enable_debugging()); assert_eq!(file.options.max_cycles(), 1000); @@ -244,8 +247,10 @@ mod tests { .unwrap(); let file = DebuggerConfig::parse_str(&text).unwrap(); - assert_eq!(file.inputs.values(), &[RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]); - assert!(file.advice_inputs.stack().is_empty()); + let expected_inputs = + StackInputs::new(vec![RawFelt::new(1), RawFelt::new(2), RawFelt::new(3)]).unwrap(); + assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref()); + assert!(file.advice_inputs.stack.is_empty()); assert!(file.options.enable_tracing()); assert!(file.options.enable_debugging()); assert_eq!(file.options.max_cycles(), 1000); @@ -269,18 +274,20 @@ mod tests { max_cycles = 1000 }) .unwrap(); - let digest = miden_processor::Digest::try_from( + let digest = miden_core::Word::try_from( "0x3cff5b58a573dc9d25fd3c57130cc57e5b1b381dc58b5ae3594b390c59835e63", ) .unwrap(); let file = DebuggerConfig::parse_str(&text).unwrap_or_else(|err| panic!("{err}")); - assert_eq!(file.inputs.values(), &[RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)]); + let expected_inputs = + StackInputs::new(vec![RawFelt::new(1), RawFelt::new(2), RawFelt::new(3)]).unwrap(); + assert_eq!(file.inputs.as_ref(), expected_inputs.as_ref()); assert_eq!( - file.advice_inputs.stack(), + file.advice_inputs.stack, &[RawFelt::new(4), RawFelt::new(3), RawFelt::new(2), RawFelt::new(1)] ); assert_eq!( - file.advice_inputs.mapped_values(&digest), + file.advice_inputs.map.get(&digest).map(|value| value.as_ref()), Some([RawFelt::new(1), RawFelt::new(2), RawFelt::new(3), RawFelt::new(4)].as_slice()) ); assert!(file.options.enable_tracing()); diff --git a/midenc-debug/src/debug/breakpoint.rs b/midenc-debug/src/debug/breakpoint.rs index 8c83ec19c..4e7c74de8 100644 --- a/midenc-debug/src/debug/breakpoint.rs +++ b/midenc-debug/src/debug/breakpoint.rs @@ -1,4 +1,4 @@ -use std::{ops::Deref, str::FromStr}; +use std::{ops::Deref, path::Path, str::FromStr}; use glob::Pattern; use miden_processor::VmState; @@ -87,9 +87,11 @@ impl BreakpointType { /// Return true if this breakpoint indicates we should break at `loc` pub fn should_break_at(&self, loc: &ResolvedLocation) -> bool { match self { - Self::File(pattern) => pattern.matches_path(loc.source_file.path()), + Self::File(pattern) => { + pattern.matches_path(Path::new(loc.source_file.deref().content().uri().as_str())) + } Self::Line { pattern, line } if line == &loc.line => { - pattern.matches_path(loc.source_file.path()) + pattern.matches_path(Path::new(loc.source_file.deref().content().uri().as_str())) } _ => false, } diff --git a/midenc-debug/src/debug/memory.rs b/midenc-debug/src/debug/memory.rs index ab52bb2ec..133e7bb83 100644 --- a/midenc-debug/src/debug/memory.rs +++ b/midenc-debug/src/debug/memory.rs @@ -2,11 +2,12 @@ use std::{ ffi::{OsStr, OsString}, fmt, str::FromStr, + sync::Arc, }; use clap::{Parser, ValueEnum}; use midenc_codegen_masm::NativePtr; -use midenc_hir::Type; +use midenc_hir::{ArrayType, PointerType, Type}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ReadMemoryExpr { @@ -23,9 +24,9 @@ impl FromStr for ReadMemoryExpr { let argv = s.split_whitespace(); let args = Read::parse(argv)?; - let ty = args.ty.unwrap_or_else(|| Type::Array(Box::new(Type::Felt), 4)); + let ty = args.ty.unwrap_or_else(|| Type::from(ArrayType::new(Type::Felt, 4))); let addr = match args.mode { - MemoryMode::Word => NativePtr::new(args.addr, 0, 0), + MemoryMode::Word => NativePtr::new(args.addr, 0), MemoryMode::Byte => NativePtr::from_ptr(args.addr), }; Ok(Self { @@ -121,8 +122,8 @@ impl clap::builder::TypedValueParser for TypeParser { "u64" => Type::U64, "u128" => Type::U128, "felt" => Type::Felt, - "word" => Type::Array(Box::new(Type::Felt), 4), - "ptr" | "pointer" => Type::Ptr(Box::new(Type::U32)), + "word" => Type::from(ArrayType::new(Type::Felt, 4)), + "ptr" | "pointer" => Type::from(PointerType::new(Type::U32)), _ => { return Err(Error::raw( ErrorKind::InvalidValue, diff --git a/midenc-debug/src/debug/stacktrace.rs b/midenc-debug/src/debug/stacktrace.rs index fe88bbfcd..32aeb08b9 100644 --- a/midenc-debug/src/debug/stacktrace.rs +++ b/midenc-debug/src/debug/stacktrace.rs @@ -8,9 +8,10 @@ use std::{ sync::Arc, }; -use miden_core::{debuginfo::Location, AssemblyOp}; +use miden_core::AssemblyOp; +use miden_debug_types::Location; use miden_processor::{Operation, RowIndex, VmState}; -use midenc_hir::demangle; +use midenc_hir::demangle::demangle; use midenc_session::{ diagnostics::{SourceFile, SourceSpan}, Session, @@ -422,18 +423,18 @@ impl OpDetail { .. } => resolved .get_or_init(|| { - let path = Path::new(loc.path.as_ref()); + let path = Path::new(loc.uri().as_str()); let source_file = if path.exists() { session.source_manager.load_file(path).ok()? } else { - session.source_manager.get_by_path(loc.path.as_ref())? + session.source_manager.get_by_uri(loc.uri())? }; let span = SourceSpan::new(source_file.id(), loc.start..loc.end); let file_line_col = source_file.location(span); Some(ResolvedLocation { source_file, - line: file_line_col.line, - col: file_line_col.column, + line: file_line_col.line.to_u32(), + col: file_line_col.column.to_u32(), span, }) }) @@ -446,13 +447,14 @@ impl OpDetail { #[derive(Debug, Clone)] pub struct ResolvedLocation { pub source_file: Arc, + // TODO(fabrio): Use LineNumber and ColumnNumber instead of raw `u32`. pub line: u32, pub col: u32, pub span: SourceSpan, } impl fmt::Display for ResolvedLocation { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}:{}:{}", self.source_file.path().display(), self.line, self.col) + write!(f, "{}:{}:{}", self.source_file.uri().as_str(), self.line, self.col) } } @@ -495,7 +497,7 @@ impl<'a> StackTrace<'a> { } } -impl<'a> fmt::Display for StackTrace<'a> { +impl fmt::Display for StackTrace<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use std::fmt::Write; diff --git a/midenc-debug/src/exec/executor.rs b/midenc-debug/src/exec/executor.rs index b92a9fd2d..d37c7e0ec 100644 --- a/midenc-debug/src/exec/executor.rs +++ b/midenc-debug/src/exec/executor.rs @@ -1,24 +1,31 @@ use std::{ cell::RefCell, collections::{BTreeMap, BTreeSet, VecDeque}, + ops::Deref, rc::Rc, + sync::Arc, }; use miden_assembly::Library as CompiledLibrary; use miden_core::{Program, StackInputs, Word}; +use miden_debug_types::SourceManagerExt; +use miden_mast_package::{ + Dependency, DependencyName, DependencyResolver, LocalResolvedDependency, MastArtifact, + MemDependencyResolverByDigest, ResolvedDependency, +}; use miden_processor::{ - AdviceInputs, ContextId, ExecutionError, Felt, MastForest, MemAdviceProvider, Process, + AdviceInputs, AdviceProvider, ContextId, ExecutionError, Felt, MastForest, Process, ProcessState, RowIndex, StackOutputs, VmState, VmStateIterator, }; -use midenc_codegen_masm::{NativePtr, Package}; +use midenc_codegen_masm::{NativePtr, Rodata}; use midenc_hir::Type; use midenc_session::{ diagnostics::{IntoDiagnostic, Report}, - Session, + LinkLibrary, Session, STDLIB, }; use super::{DebugExecutor, DebuggerHost, ExecutionTrace, TraceEvent}; -use crate::{debug::CallStack, felt::PopFromStack, TestFelt}; +use crate::{debug::CallStack, FromMidenRepr, TestFelt}; /// The [Executor] is responsible for executing a program with the Miden VM. /// @@ -29,58 +36,82 @@ use crate::{debug::CallStack, felt::PopFromStack, TestFelt}; pub struct Executor { stack: StackInputs, advice: AdviceInputs, - libraries: Vec, + libraries: Vec>, + dependency_resolver: MemDependencyResolverByDigest, } impl Executor { /// Construct an executor with the given arguments on the operand stack pub fn new(args: Vec) -> Self { + let mut resolver = MemDependencyResolverByDigest::default(); + resolver.add(*STDLIB.digest(), STDLIB.clone().into()); + let base_lib = miden_lib::MidenLib::default().as_ref().clone(); + resolver.add(*base_lib.digest(), Arc::new(base_lib).into()); Self { stack: StackInputs::new(args).expect("invalid stack inputs"), advice: AdviceInputs::default(), libraries: Default::default(), + dependency_resolver: resolver, } } - pub fn for_package( - package: &Package, - args: Vec, + /// Construct the executor with the given inputs and adds dependencies from the given package + pub fn for_package( + package: &miden_mast_package::Package, + args: I, session: &Session, - ) -> Result { + ) -> Result + where + I: IntoIterator, + { use midenc_hir::formatter::DisplayHex; log::debug!( "creating executor for package '{}' (digest={})", package.name, - DisplayHex::new(&package.digest.as_bytes()) + DisplayHex::new(&package.digest().as_bytes()) ); + let mut exec = Self::new(args.into_iter().collect()); + let dependencies = package.manifest.dependencies(); + exec.with_dependencies(dependencies)?; + log::debug!("executor created"); + Ok(exec) + } - let mut exec = Self::new(args); - - for link_library in package.manifest.link_libraries.iter() { - log::debug!( - "loading link library from package manifest: {} (kind = {}, from = {:#?})", - link_library.name.as_ref(), - link_library.kind, - link_library.path.as_ref().map(|p| p.display()) - ); - let library = link_library.load(session)?; - log::debug!("library loaded succesfully"); - exec.with_library(&library); - } + /// Adds dependencies to the executor + pub fn with_dependencies<'a>( + &mut self, + dependencies: impl Iterator, + ) -> Result<&mut Self, Report> { + use midenc_hir::formatter::DisplayHex; - for rodata in package.rodata.iter() { - log::debug!( - "adding rodata segment for offset {} (size {}) to advice map: {}", - rodata.start.as_ptr(), - rodata.size_in_bytes(), - DisplayHex::new(&rodata.digest.as_bytes()) - ); - exec.advice - .extend_map([(rodata.digest, rodata.to_elements().map_err(Report::msg)?)]); + for dep in dependencies { + match self.dependency_resolver.resolve(dep) { + Some(resolution) => { + log::debug!("dependency {dep:?} resolved to {resolution:?}"); + log::debug!("loading library from package dependency: {dep:?}"); + match resolution { + ResolvedDependency::Local(LocalResolvedDependency::Library(lib)) => { + let library = lib.as_ref(); + self.with_mast_forest(lib.mast_forest().clone()); + } + ResolvedDependency::Local(LocalResolvedDependency::Package(pkg)) => { + if let MastArtifact::Library(lib) = &pkg.mast { + self.with_mast_forest(lib.mast_forest().clone()); + } else { + Err(Report::msg(format!( + "expected package {} to contain library", + pkg.name + )))?; + } + } + } + } + None => panic!("{dep:?} not found in resolver"), + } } log::debug!("executor created"); - Ok(exec) + Ok(self) } /// Set the contents of memory for the shadow stack frame of the entrypoint @@ -95,13 +126,19 @@ impl Executor { self } + /// Add a [MastForest] to the execution context + pub fn with_mast_forest(&mut self, mast: Arc) -> &mut Self { + self.libraries.push(mast); + self + } + /// Convert this [Executor] into a [DebugExecutor], which captures much more information /// about the program being executed, and must be stepped manually. pub fn into_debug(mut self, program: &Program, session: &Session) -> DebugExecutor { log::debug!("creating debug executor"); - let advice_provider = MemAdviceProvider::from(self.advice); - let mut host = DebuggerHost::new(advice_provider); + let advice_provider = AdviceProvider::from(self.advice.clone()); + let mut host = DebuggerHost::new(advice_provider, session.source_manager.clone()); for lib in core::mem::take(&mut self.libraries) { host.load_mast_forest(lib); } @@ -120,14 +157,16 @@ impl Executor { assertion_events.borrow_mut().insert(clk, event); }); - let mut process = Process::new_debug(program.kernel().clone(), self.stack, host); - let root_context = process.ctx(); - let result = process.execute(program); - let mut iter = VmStateIterator::new(process, result.clone()); + let mut process = Process::new_debug(program.kernel().clone(), self.stack, self.advice); + let process_state: ProcessState = (&mut process).into(); + let root_context = process_state.ctx(); + let result = process.execute(program, &mut host); + let stack_outputs = result.as_ref().map(|so| so.clone()).unwrap_or_default(); + let mut iter = VmStateIterator::new(process, result); let mut callstack = CallStack::new(trace_events); DebugExecutor { iter, - result, + stack_outputs, contexts: Default::default(), root_context, current_context: root_context, @@ -159,6 +198,29 @@ impl Executor { render_execution_error(err, &executor, session); } + if log::log_enabled!(target: "executor", log::Level::Trace) { + let step = step.unwrap(); + if let Some(op) = step.op.as_ref() { + if let Some(asmop) = step.asmop.as_ref() { + dbg!(&step.stack); + let source_loc = asmop.as_ref().location().map(|loc| { + let path = std::path::Path::new(loc.uri().path()); + let file = + session.diagnostics.source_manager_ref().load_file(path).unwrap(); + (file, loc.start) + }); + if let Some((source_file, line_start)) = source_loc { + let line_number = source_file.content().line_index(line_start).number(); + log::trace!(target: "executor", "in {} (located at {}:{})", asmop.context_name(), source_file.deref().uri().as_str(), &line_number); + } else { + log::trace!(target: "executor", "in {} (no source location available)", asmop.context_name()); + } + log::trace!(target: "executor", " executed `{op:?}` of `{}` (cycle {}/{})", asmop.op(), asmop.cycle_idx(), asmop.num_cycles()); + log::trace!(target: "executor", " stack state: {:#?}", &step.stack); + } + } + } + /* if let Some(op) = state.op { match op { @@ -273,11 +335,15 @@ impl Executor { /// Execute a program, parsing the operand stack outputs as a value of type `T` pub fn execute_into(self, program: &Program, session: &Session) -> T where - T: PopFromStack + PartialEq, + T: FromMidenRepr + PartialEq, { let out = self.execute(program, session); out.parse_result().expect("invalid result") } + + pub fn dependency_resolver_mut(&mut self) -> &mut MemDependencyResolverByDigest { + &mut self.dependency_resolver + } } #[track_caller] @@ -286,7 +352,7 @@ fn render_execution_error( execution_state: &DebugExecutor, session: &Session, ) -> ! { - use midenc_hir::diagnostics::{miette::miette, reporting::PrintDiagnostic, LabeledSpan}; + use midenc_session::diagnostics::{miette::miette, reporting::PrintDiagnostic, LabeledSpan}; let stacktrace = execution_state.callstack.stacktrace(&execution_state.recent, session); @@ -294,7 +360,7 @@ fn render_execution_error( if let Some(last_state) = execution_state.last.as_ref() { let stack = last_state.stack.iter().map(|elem| elem.as_int()); - let stack = midenc_hir::DisplayValues::new(stack); + let stack = midenc_hir::formatter::DisplayValues::new(stack); let fmp = last_state.fmp.as_int(); eprintln!( "\nLast Known State (at most recent instruction which succeeded): diff --git a/midenc-debug/src/exec/host.rs b/midenc-debug/src/exec/host.rs index 576526e19..f59899f4a 100644 --- a/midenc-debug/src/exec/host.rs +++ b/midenc-debug/src/exec/host.rs @@ -1,31 +1,40 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::{collections::BTreeMap, num::NonZeroU32, sync::Arc}; -use miden_core::crypto::hash::RpoDigest; +use miden_assembly::SourceManager; +use miden_core::Word; +use miden_debug_types::{Location, SourceFile, SourceSpan}; use miden_processor::{ - AdviceExtractor, AdviceInjector, AdviceProvider, ExecutionError, Host, HostResponse, - MastForest, MastForestStore, MemAdviceProvider, MemMastForestStore, ProcessState, RowIndex, + AdviceInputs, AdviceProvider, BaseHost, EventHandlerRegistry, ExecutionError, KvMap, + MastForest, MastForestStore, MemMastForestStore, ProcessState, RowIndex, SyncHost, }; use super::{TraceEvent, TraceHandler}; -/// This is an implementation of [Host] which is essentially [miden_processor::DefaultHost], +/// This is an implementation of [BaseHost] which is essentially [miden_processor::DefaultHost], /// but extended with additional functionality for debugging, in particular it manages trace /// events that record the entry or exit of a procedure call frame. #[derive(Default)] -pub struct DebuggerHost { - adv_provider: MemAdviceProvider, +pub struct DebuggerHost { + adv_provider: AdviceProvider, store: MemMastForestStore, tracing_callbacks: BTreeMap>>, + event_handlers: EventHandlerRegistry, on_assert_failed: Option>, + source_manager: Arc, } -impl DebuggerHost { +impl DebuggerHost +where + S: SourceManager, +{ /// Construct a new instance of [DebuggerHost] with the given advice provider. - pub fn new(adv_provider: MemAdviceProvider) -> Self { + pub fn new(adv_provider: AdviceProvider, source_manager: S) -> Self { Self { adv_provider, store: Default::default(), tracing_callbacks: Default::default(), + event_handlers: EventHandlerRegistry::default(), on_assert_failed: None, + source_manager: Arc::new(source_manager), } } @@ -50,37 +59,33 @@ impl DebuggerHost { } /// Load `forest` into the MAST store for this host - pub fn load_mast_forest(&mut self, forest: MastForest) { + pub fn load_mast_forest(&mut self, forest: Arc) { self.store.insert(forest); } } -impl Host for DebuggerHost { - fn get_advice( - &mut self, - process: &P, - extractor: AdviceExtractor, - ) -> Result { - self.adv_provider.get_advice(process, &extractor) - } - - fn set_advice( - &mut self, - process: &P, - injector: AdviceInjector, - ) -> Result { - self.adv_provider.set_advice(process, &injector) +impl BaseHost for DebuggerHost +where + S: SourceManager, +{ + fn get_mast_forest(&self, node_digest: &Word) -> Option> { + self.store.get(node_digest) } - fn get_mast_forest(&self, node_digest: &RpoDigest) -> Option> { - self.store.get(node_digest) + fn get_label_and_source_file( + &self, + location: &Location, + ) -> (SourceSpan, Option>) { + let maybe_file = self.source_manager.get_by_uri(location.uri()); + let span = self.source_manager.location_to_span(location.clone()).unwrap_or_default(); + (span, maybe_file) } - fn on_trace( + fn on_trace( &mut self, - process: &S, + process: &mut ProcessState, trace_id: u32, - ) -> Result { + ) -> Result<(), ExecutionError> { let event = TraceEvent::from(trace_id); let clk = process.clk(); if let Some(handlers) = self.tracing_callbacks.get_mut(&trace_id) { @@ -88,26 +93,27 @@ impl Host for DebuggerHost { handler(clk, event); } } - Ok(HostResponse::None) + Ok(()) } - fn on_assert_failed(&mut self, process: &S, err_code: u32) -> ExecutionError { + fn on_assert_failed(&mut self, process: &ProcessState, err_code: miden_core::Felt) { let clk = process.clk(); if let Some(handler) = self.on_assert_failed.as_mut() { - handler(clk, TraceEvent::AssertionFailed(core::num::NonZeroU32::new(err_code))); - } - let err_msg = match err_code { - midenc_hir::ASSERT_FAILED_ALIGNMENT => Some( - "failed alignment: use of memory address violates minimum alignment requirements \ - for that use" - .to_string(), - ), - _ => None, - }; - ExecutionError::FailedAssertion { - clk, - err_code, - err_msg, + // TODO: We're truncating the error code here, but we may need to handle the full range + handler(clk, TraceEvent::AssertionFailed(NonZeroU32::new(err_code.as_int() as u32))); } } } + +impl SyncHost for DebuggerHost +where + S: SourceManager, +{ + fn on_event( + &mut self, + process: &ProcessState, + event_id: u32, + ) -> Result, miden_processor::EventError> { + Ok(Vec::new()) + } +} diff --git a/midenc-debug/src/exec/mod.rs b/midenc-debug/src/exec/mod.rs index 4e2d8b354..0b0aa9829 100644 --- a/midenc-debug/src/exec/mod.rs +++ b/midenc-debug/src/exec/mod.rs @@ -6,6 +6,6 @@ mod trace; pub use self::{ executor::Executor, host::DebuggerHost, - state::{Chiplets, DebugExecutor}, + state::{DebugExecutor, MemoryChiplet}, trace::{ExecutionTrace, TraceEvent, TraceHandler}, }; diff --git a/midenc-debug/src/exec/state.rs b/midenc-debug/src/exec/state.rs index 5576194fc..bcb36f601 100644 --- a/midenc-debug/src/exec/state.rs +++ b/midenc-debug/src/exec/state.rs @@ -1,8 +1,12 @@ -use std::collections::{BTreeSet, VecDeque}; +use std::{ + collections::{BTreeSet, VecDeque}, + rc::Rc, +}; use miden_core::Word; use miden_processor::{ - ContextId, ExecutionError, Operation, RowIndex, StackOutputs, VmState, VmStateIterator, + ContextId, ExecutionError, MemoryAddress, MemoryError, Operation, RowIndex, StackOutputs, + VmState, VmStateIterator, }; use super::ExecutionTrace; @@ -18,7 +22,7 @@ pub struct DebugExecutor { /// The underlying [VmStateIterator] being driven pub iter: VmStateIterator, /// The final outcome of the program being executed - pub result: Result, + pub stack_outputs: StackOutputs, /// The set of contexts allocated during execution so far pub contexts: BTreeSet, /// The root context @@ -46,7 +50,7 @@ impl DebugExecutor { /// Returns the call frame exited this cycle, if any pub fn step(&mut self) -> Result, ExecutionError> { if self.stopped { - return self.result.as_ref().map(|_| None).map_err(|err| err.clone()); + return Ok(None); } match self.iter.next() { Some(Ok(state)) => { @@ -85,12 +89,25 @@ impl DebugExecutor { let last_cycle = self.cycle; let trace_len_summary = *self.iter.trace_len_summary(); let (_, _, _, chiplets, _) = self.iter.into_parts(); - let outputs = self.result.unwrap_or_default(); + let chiplets = Rc::new(chiplets); + + let chiplets0 = chiplets.clone(); + let get_state_at = move |context, clk| chiplets0.memory.get_state_at(context, clk); + let chiplets1 = chiplets.clone(); + let get_word = move |context, addr| chiplets1.memory.get_word(context, addr); + let get_value = move |context, addr| chiplets.memory.get_value(context, addr); + + let memory = MemoryChiplet { + get_value: Box::new(get_value), + get_word: Box::new(get_word), + get_state_at: Box::new(get_state_at), + }; + ExecutionTrace { root_context: self.root_context, last_cycle: RowIndex::from(last_cycle), - chiplets: Chiplets::new(move |context, clk| chiplets.get_mem_state_at(context, clk)), - outputs, + memory, + outputs: self.stack_outputs, trace_len_summary, } } @@ -112,17 +129,30 @@ impl Iterator for DebugExecutor { } // Dirty, gross, horrible hack until miden_processor::chiplets::Chiplets is exported -#[allow(clippy::type_complexity)] -pub struct Chiplets(Box Vec<(u64, Word)>>); -impl Chiplets { - pub fn new(callback: F) -> Self - where - F: Fn(ContextId, RowIndex) -> Vec<(u64, Word)> + 'static, - { - Self(Box::new(callback)) +pub struct MemoryChiplet { + get_value: Box Option>, + get_word: Box Result, MemoryError>>, + #[allow(clippy::type_complexity)] + get_state_at: Box Vec<(MemoryAddress, miden_core::Felt)>>, +} + +impl MemoryChiplet { + #[inline] + pub fn get_value(&self, context: ContextId, addr: u32) -> Option { + (self.get_value)(context, addr) } - pub fn get_mem_state_at(&self, context: ContextId, clk: RowIndex) -> Vec<(u64, Word)> { - (self.0)(context, clk) + #[inline] + pub fn get_word(&self, context: ContextId, addr: u32) -> Result, MemoryError> { + (self.get_word)(context, addr) + } + + #[inline] + pub fn get_mem_state_at( + &self, + context: ContextId, + clk: RowIndex, + ) -> Vec<(MemoryAddress, miden_core::Felt)> { + (self.get_state_at)(context, clk) } } diff --git a/midenc-debug/src/exec/trace.rs b/midenc-debug/src/exec/trace.rs index 4e3040b80..f385e3d30 100644 --- a/midenc-debug/src/exec/trace.rs +++ b/midenc-debug/src/exec/trace.rs @@ -5,18 +5,18 @@ use std::{ }; use miden_assembly::Library as CompiledLibrary; -use miden_core::{Program, StackInputs, Word}; +use miden_core::{FieldElement, Program, StackInputs, Word}; use miden_processor::{ - AdviceInputs, ContextId, ExecutionError, Felt, MastForest, MemAdviceProvider, Process, - ProcessState, RowIndex, StackOutputs, TraceLenSummary, VmState, VmStateIterator, + AdviceInputs, ContextId, ExecutionError, Felt, MastForest, Process, ProcessState, RowIndex, + StackOutputs, TraceLenSummary, VmState, VmStateIterator, }; use midenc_codegen_masm::NativePtr; -pub use midenc_hir::TraceEvent; -use midenc_hir::Type; +pub use midenc_codegen_masm::TraceEvent; +use midenc_hir::{SmallVec, ToSmallVec, Type}; use midenc_session::Session; -use super::Chiplets; -use crate::{debug::CallStack, felt::PopFromStack, DebuggerHost, TestFelt}; +use super::MemoryChiplet; +use crate::{debug::CallStack, DebuggerHost, FromMidenRepr, TestFelt}; /// A callback to be executed when a [TraceEvent] occurs at a given clock cycle pub type TraceHandler = dyn FnMut(RowIndex, TraceEvent); @@ -38,7 +38,7 @@ pub enum MemoryReadError { pub struct ExecutionTrace { pub(super) root_context: ContextId, pub(super) last_cycle: RowIndex, - pub(super) chiplets: Chiplets, + pub(super) memory: MemoryChiplet, pub(super) outputs: StackOutputs, pub(super) trace_len_summary: TraceLenSummary, } @@ -47,11 +47,18 @@ impl ExecutionTrace { /// Parse the program outputs on the operand stack as a value of type `T` pub fn parse_result(&self) -> Option where - T: PopFromStack, + T: FromMidenRepr, { - let mut stack = - VecDeque::from_iter(self.outputs.clone().stack().iter().copied().map(TestFelt)); - T::try_pop(&mut stack) + let size = ::size_in_felts(); + let stack = self.outputs.stack_truncated(size); + if stack.len() < size { + return None; + } + dbg!(stack); + let mut stack = stack.to_vec(); + stack.reverse(); + dbg!(&stack); + Some(::pop_from_stack(&mut stack)) } /// Consume the [ExecutionTrace], extracting just the outputs on the operand stack @@ -86,18 +93,20 @@ impl ExecutionTrace { ) -> Option { use miden_core::FieldElement; - let words = self.chiplets.get_mem_state_at(ctx, clk); - let addr = addr as u64; - match words.binary_search_by_key(&addr, |item| item.0) { - Ok(index) => Some(words[index].1), - Err(_) => Some([Felt::ZERO; 4]), - } + const ZERO: Word = Word::new([Felt::ZERO; 4]); + + Some( + self.memory + .get_word(ctx, addr) + .unwrap_or_else(|err| panic!("{err}")) + .unwrap_or(ZERO), + ) } /// Read the word at the given Miden memory address and element offset #[track_caller] - pub fn read_memory_element(&self, addr: u32, index: u8) -> Option { - self.read_memory_element_in_context(addr, index, self.root_context, self.last_cycle) + pub fn read_memory_element(&self, addr: u32) -> Option { + self.memory.get_value(self.root_context, addr) } /// Read the word at the given Miden memory address and element offset, under `ctx`, at cycle @@ -106,13 +115,10 @@ impl ExecutionTrace { pub fn read_memory_element_in_context( &self, addr: u32, - index: u8, ctx: ContextId, - clk: RowIndex, + _clk: RowIndex, ) -> Option { - assert!(index < 4, "invalid element index"); - self.read_memory_word_in_context(addr, ctx, clk) - .map(|word| word[index as usize]) + self.memory.get_value(ctx, addr) } /// Read a raw byte vector from `addr`, under `ctx`, at cycle `clk`, sufficient to hold a value @@ -128,32 +134,13 @@ impl ExecutionTrace { let size = ty.size_in_bytes(); let mut buf = Vec::with_capacity(size); - let size_in_words = ty.size_in_words(); - let mut elems = Vec::with_capacity(size_in_words); + let size_in_felts = ty.size_in_felts(); + let mut elems = Vec::with_capacity(size_in_felts); - if addr.is_word_aligned() { - for i in 0..size_in_words { - let addr = addr.waddr.checked_add(i as u32).ok_or(MemoryReadError::OutOfBounds)?; - elems.extend(self.read_memory_word_in_context(addr, ctx, clk).unwrap_or_default()); - } - } else if addr.is_element_aligned() { - let leading = - self.read_memory_word_in_context(addr.waddr, ctx, clk).unwrap_or_default(); - for item in leading.into_iter().skip(addr.index as usize) { - elems.push(item); - } - for i in 1..size_in_words { - let addr = addr.waddr.checked_add(i as u32).ok_or(MemoryReadError::OutOfBounds)?; - elems.extend(self.read_memory_word_in_context(addr, ctx, clk).unwrap_or_default()); - } - let trailing_addr = addr - .waddr - .checked_add(size_in_words as u32) - .ok_or(MemoryReadError::OutOfBounds)?; - let trailing = - self.read_memory_word_in_context(trailing_addr, ctx, clk).unwrap_or_default(); - for item in trailing.into_iter().take(4 - addr.index as usize) { - elems.push(item); + if addr.is_element_aligned() { + for i in 0..size_in_felts { + let addr = addr.addr.checked_add(i as u32).ok_or(MemoryReadError::OutOfBounds)?; + elems.push(self.read_memory_element_in_context(addr, ctx, clk).unwrap_or_default()); } } else { return Err(MemoryReadError::UnalignedRead); @@ -174,7 +161,7 @@ impl ExecutionTrace { #[track_caller] pub fn read_from_rust_memory(&self, addr: u32) -> Option where - T: core::any::Any + PopFromStack, + T: core::any::Any + FromMidenRepr, { self.read_from_rust_memory_in_context(addr, self.root_context, self.last_cycle) } @@ -189,96 +176,44 @@ impl ExecutionTrace { clk: RowIndex, ) -> Option where - T: core::any::Any + PopFromStack, + T: core::any::Any + FromMidenRepr, { use core::any::TypeId; let ptr = NativePtr::from_ptr(addr); if TypeId::of::() == TypeId::of::() { assert_eq!(ptr.offset, 0, "cannot read values of type Felt from unaligned addresses"); - let elem = self.read_memory_element_in_context(ptr.waddr, ptr.index, ctx, clk)?; - let mut stack = VecDeque::from([TestFelt(elem)]); - return Some(T::try_pop(&mut stack).unwrap_or_else(|| { - panic!( - "could not decode a value of type {} from {}", - core::any::type_name::(), - addr - ) - })); } - match core::mem::size_of::() { - n if n < 4 => { - if (4 - ptr.offset as usize) < n { - todo!("unaligned, split read") - } - let elem = self.read_memory_element_in_context(ptr.waddr, ptr.index, ctx, clk)?; - let elem = if ptr.offset > 0 { - let mask = 2u64.pow(32 - (ptr.offset as u32 * 8)) - 1; - let elem = elem.as_int() & mask; - Felt::new(elem << (ptr.offset as u64 * 8)) - } else { - elem - }; - let mut stack = VecDeque::from([TestFelt(elem)]); - Some(T::try_pop(&mut stack).unwrap_or_else(|| { - panic!( - "could not decode a value of type {} from {}", - core::any::type_name::(), - addr - ) - })) - } - 4 if ptr.offset > 0 => { - todo!("unaligned, split read") - } - 4 => { - let elem = self.read_memory_element_in_context(ptr.waddr, ptr.index, ctx, clk)?; - let mut stack = VecDeque::from([TestFelt(elem)]); - Some(T::try_pop(&mut stack).unwrap_or_else(|| { - panic!( - "could not decode a value of type {} from {}", - core::any::type_name::(), - addr - ) - })) + assert_eq!(ptr.offset, 0, "support for unaligned reads is not yet implemented"); + match ::size_in_felts() { + 1 => { + let felt = self.read_memory_element_in_context(ptr.addr, ctx, clk)?; + Some(T::from_felts(&[felt])) } - n if n <= 16 && ptr.offset > 0 => { - todo!("unaligned, split read") + 2 => { + let lo = self.read_memory_element_in_context(ptr.addr, ctx, clk)?; + let hi = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?; + Some(T::from_felts(&[lo, hi])) } - n if n <= 16 => { - let word = self.read_memory_word_in_context(ptr.waddr, ctx, clk)?; - let mut stack = VecDeque::from_iter(word.into_iter().map(TestFelt)); - Some(T::try_pop(&mut stack).unwrap_or_else(|| { - panic!( - "could not decode a value of type {} from {}", - core::any::type_name::(), - addr - ) - })) + 3 => { + let lo_l = self.read_memory_element_in_context(ptr.addr, ctx, clk)?; + let lo_h = self.read_memory_element_in_context(ptr.addr + 1, ctx, clk)?; + let hi_l = self.read_memory_element_in_context(ptr.addr + 2, ctx, clk)?; + Some(T::from_felts(&[lo_l, lo_h, hi_l])) } n => { - let mut buf = VecDeque::default(); - let chunks_needed = ((n / 4) as u32) + ((n % 4) > 0) as u32; - if ptr.offset > 0 { - todo!() - } else { - for i in 0..chunks_needed { - let abs_i = i + ptr.index as u32; - let word = ptr.waddr + (abs_i / 4); - let index = (abs_i % 4) as u8; - let elem = self - .read_memory_element_in_context(word, index, ctx, clk) - .expect("invalid memory access"); - buf.push_back(TestFelt(elem)); - } + assert_ne!(n, 0); + let num_words = n.next_multiple_of(4) / 4; + let mut words = SmallVec::<[_; 2]>::with_capacity(num_words); + for word_index in 0..(num_words as u32) { + let addr = ptr.addr + (word_index * 4); + let mut word = self.read_memory_word(addr)?; + word.reverse(); + dbg!(word_index, word); + words.push(word); } - Some(T::try_pop(&mut buf).unwrap_or_else(|| { - panic!( - "could not decode a value of type {} from {}", - core::any::type_name::(), - addr - ) - })) + words.resize(num_words, Word::new([Felt::ZERO; 4])); + Some(T::from_words(&words)) } } } diff --git a/midenc-debug/src/felt.rs b/midenc-debug/src/felt.rs index ac77d4d97..497dc94da 100644 --- a/midenc-debug/src/felt.rs +++ b/midenc-debug/src/felt.rs @@ -1,280 +1,635 @@ use std::collections::VecDeque; -use miden_core::StarkField; +use miden_core::{FieldElement, StarkField, Word}; use miden_processor::Felt as RawFelt; +use midenc_hir::{smallvec, SmallVec}; use proptest::{ arbitrary::Arbitrary, strategy::{BoxedStrategy, Strategy}, }; use serde::Deserialize; -pub trait PushToStack: Sized { - fn try_push(&self, stack: &mut Vec) { - let mut ptr = self as *const Self as *const u8; - let mut num_bytes = core::mem::size_of::(); - let mut buf = Vec::with_capacity(num_bytes / core::mem::size_of::()); - while num_bytes > 0 { - let mut next = [0u8; 4]; - let consume = core::cmp::min(4, num_bytes); - unsafe { - ptr.copy_to_nonoverlapping(next.as_mut_ptr(), consume); - ptr = ptr.byte_add(consume); +pub trait ToMidenRepr { + /// Convert this type into its raw byte representation + /// + /// The order of bytes in the resulting vector should be little-endian, i.e. the least + /// significant bytes come first. + fn to_bytes(&self) -> SmallVec<[u8; 16]>; + /// Convert this type into one or more field elements, where the order of the elements is such + /// that the byte representation of `self` is in little-endian order, i.e. the least significant + /// bytes come first. + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + let bytes = self.to_bytes(); + let num_felts = bytes.len().next_multiple_of(4) / 4; + let mut felts = SmallVec::<[RawFelt; 4]>::with_capacity(num_felts); + let mut chunks = bytes.into_iter().array_chunks::<4>(); + for chunk in chunks.by_ref() { + felts.push(RawFelt::new(u32::from_ne_bytes(chunk) as u64)); + } + if let Some(remainder) = chunks.into_remainder().filter(|r| r.len() > 0) { + if remainder.len() > 0 { + let mut chunk = [0u8; 4]; + for (i, byte) in remainder.enumerate() { + chunk[i] = byte; + } + felts.push(RawFelt::new(u32::from_ne_bytes(chunk) as u64)); } - num_bytes -= consume; - buf.push(RawFelt::new(u32::from_be_bytes(next) as u64)); } - - for item in buf.into_iter().rev() { - stack.push(item); + felts + } + /// Convert this type into one or more words, zero-padding as needed, such that: + /// + /// * The field elements within each word is in little-endian order, i.e. the least significant + /// bytes of come first. + /// * Each word, if pushed on the operand stack element-by-element, would leave the element + /// with the most significant bytes on top of the stack (including padding) + fn to_words(&self) -> SmallVec<[Word; 1]> { + let felts = self.to_felts(); + let num_words = felts.len().next_multiple_of(4) / 4; + let mut words = SmallVec::<[Word; 1]>::with_capacity(num_words); + let mut chunks = felts.into_iter().array_chunks::<4>(); + for mut word in chunks.by_ref() { + word.reverse(); + words.push(Word::new(word)); + } + if let Some(remainder) = chunks.into_remainder().filter(|r| r.len() > 0) { + if remainder.len() > 0 { + let mut word = [RawFelt::ZERO; 4]; + for (i, felt) in remainder.enumerate() { + word[i] = felt; + } + word.reverse(); + words.push(Word::new(word)); + } } + words } -} -pub trait PopFromStack: Sized { - fn try_pop(stack: &mut VecDeque) -> Option { - use core::mem::MaybeUninit; + /// Push this value on the given operand stack using [Self::to_felts] representation + fn push_to_operand_stack(&self, stack: &mut Vec) { + let felts = self.to_felts(); + for felt in felts.into_iter().rev() { + stack.push(felt); + } + } - let mut num_bytes = core::mem::size_of::(); - let mut result = MaybeUninit::::uninit(); - let mut ptr = result.as_mut_ptr() as *mut u8; - while num_bytes > 0 { - let next = stack.pop_front().expect("expected more operand stack elements"); - let next_bytes = (next.0.as_int() as u32).to_be_bytes(); - let consume = core::cmp::min(4, num_bytes); - unsafe { - next_bytes.as_ptr().copy_to_nonoverlapping(ptr, consume); - ptr = ptr.byte_add(consume); + /// Push this value in its [Self::to_words] representation, on the given stack. + /// + /// This function is designed for encoding values that will be placed on the advice stack and + /// copied into Miden VM memory by the compiler-emitted test harness. + /// + /// Returns the number of words that were pushed on the stack + fn push_words_to_advice_stack(&self, stack: &mut Vec) -> usize { + let words = self.to_words(); + let num_words = words.len(); + for word in words.into_iter().rev() { + for felt in word.into_iter() { + stack.push(felt); } - num_bytes -= consume; } - Some(unsafe { result.assume_init() }) + num_words } } -impl PushToStack for bool { - fn try_push(&self, stack: &mut Vec) { - stack.push(RawFelt::new(*self as u64)) +pub trait FromMidenRepr: Sized { + /// Returns the size of this type as encoded by [ToMidenRepr::to_felts] + fn size_in_felts() -> usize; + /// Extract a value of this type from `bytes`, where: + /// + /// * It is assumed that bytes is always padded out to 4 byte alignment + /// * It is assumed that the bytes are in little-endian order, as encoded by [ToMidenRepr] + fn from_bytes(bytes: &[u8]) -> Self; + /// Extract a value of this type as encoded in a vector of field elements, where: + /// + /// * The order of the field elements is little-endian, i.e. the element holding the least + /// significant bytes comes first. + fn from_felts(felts: &[RawFelt]) -> Self { + let mut bytes = SmallVec::<[u8; 16]>::with_capacity(felts.len() * 4); + for felt in felts { + let chunk = (felt.as_int() as u32).to_ne_bytes(); + bytes.extend(chunk); + } + Self::from_bytes(&bytes) + } + /// Extract a value of this type as encoded in a vector of words, where: + /// + /// * The order of the words is little-endian, i.e. the word holding the least significant + /// bytes comes first. + /// * The order of the field elements in each word is in big-endian order, i.e. the element + /// with the most significant byte is at the start of the word, and the element with the + /// least significant byte is at the end of the word. This corresponds to the order in + /// which elements are placed on the operand stack when preparing to read or write them + /// from Miden's memory. + fn from_words(words: &[Word]) -> Self { + let mut felts = SmallVec::<[RawFelt; 4]>::with_capacity(words.len() * 4); + for word in words { + for felt in word.iter().copied().rev() { + felts.push(felt); + } + } + Self::from_felts(&felts) + } + + /// Pop a value of this type from `stack` based on the canonical representation of this type + /// on the operand stack when writing it to memory (and as read from memory). + fn pop_from_stack(stack: &mut Vec) -> Self { + let needed = Self::size_in_felts(); + let mut felts = SmallVec::<[RawFelt; 4]>::with_capacity(needed); + for _ in 0..needed { + felts.push(stack.pop().unwrap()); + } + Self::from_felts(&felts) } } -impl PopFromStack for bool { - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front().unwrap().0.as_int() != 0) + +impl ToMidenRepr for bool { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + smallvec![*self as u8] + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![RawFelt::new(*self as u64)] + } + + fn push_to_operand_stack(&self, stack: &mut Vec) { + stack.push(RawFelt::new(*self as u64)); } } -impl PushToStack for u8 { - fn try_push(&self, stack: &mut Vec) { - stack.push(RawFelt::new(*self as u64)) +impl FromMidenRepr for bool { + #[inline(always)] + fn size_in_felts() -> usize { + 1 + } + + fn from_bytes(bytes: &[u8]) -> Self { + match bytes[0] { + 0 => false, + 1 => true, + n => panic!("invalid byte representation for boolean: {n:0x}"), + } + } + + fn from_felts(felts: &[RawFelt]) -> Self { + match felts[0].as_int() { + 0 => false, + 1 => true, + n => panic!("invalid byte representation for boolean: {n:0x}"), + } + } + + fn pop_from_stack(stack: &mut Vec) -> Self { + match stack.pop().unwrap().as_int() { + 0 => false, + 1 => true, + n => panic!("invalid byte representation for boolean: {n:0x}"), + } } } -impl PopFromStack for u8 { - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front().unwrap().0.as_int() as u8) + +impl ToMidenRepr for u8 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + smallvec![*self] + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![RawFelt::new(*self as u64)] + } + + fn push_to_operand_stack(&self, stack: &mut Vec) { + stack.push(RawFelt::new(*self as u64)); } } -impl PushToStack for i8 { - fn try_push(&self, stack: &mut Vec) { - stack.push(RawFelt::new(*self as u8 as u64)) +impl FromMidenRepr for u8 { + #[inline(always)] + fn size_in_felts() -> usize { + 1 + } + + #[inline(always)] + fn from_bytes(bytes: &[u8]) -> Self { + bytes[0] + } + + fn from_felts(felts: &[RawFelt]) -> Self { + felts[0].as_int() as u8 + } + + fn pop_from_stack(stack: &mut Vec) -> Self { + stack.pop().unwrap().as_int() as u8 } } -impl PopFromStack for i8 { - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front().unwrap().0.as_int() as i8) + +impl ToMidenRepr for i8 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + smallvec![*self as u8] + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![RawFelt::new(*self as u8 as u64)] + } + + fn push_to_operand_stack(&self, stack: &mut Vec) { + stack.push(RawFelt::new(*self as u8 as u64)); } } -impl PushToStack for u16 { - fn try_push(&self, stack: &mut Vec) { - stack.push(RawFelt::new(*self as u64)) +impl FromMidenRepr for i8 { + #[inline(always)] + fn size_in_felts() -> usize { + 1 + } + + #[inline(always)] + fn from_bytes(bytes: &[u8]) -> Self { + bytes[0] as i8 + } + + fn from_felts(felts: &[RawFelt]) -> Self { + felts[0].as_int() as u8 as i8 + } + + fn pop_from_stack(stack: &mut Vec) -> Self { + stack.pop().unwrap().as_int() as u8 as i8 } } -impl PopFromStack for u16 { - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front().unwrap().0.as_int() as u16) + +impl ToMidenRepr for u16 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_ne_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![RawFelt::new(*self as u64)] + } + + fn push_to_operand_stack(&self, stack: &mut Vec) { + stack.push(RawFelt::new(*self as u64)); } } -impl PushToStack for i16 { - fn try_push(&self, stack: &mut Vec) { - stack.push(RawFelt::new(*self as u16 as u64)) +impl FromMidenRepr for u16 { + #[inline(always)] + fn size_in_felts() -> usize { + 1 + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 2); + u16::from_ne_bytes([bytes[0], bytes[1]]) + } + + fn from_felts(felts: &[RawFelt]) -> Self { + felts[0].as_int() as u16 + } + + fn pop_from_stack(stack: &mut Vec) -> Self { + stack.pop().unwrap().as_int() as u16 } } -impl PopFromStack for i16 { - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front().unwrap().0.as_int() as i16) + +impl ToMidenRepr for i16 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_ne_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![RawFelt::new(*self as u16 as u64)] + } + + fn push_to_operand_stack(&self, stack: &mut Vec) { + stack.push(RawFelt::new(*self as u16 as u64)); } } -impl PushToStack for u32 { - fn try_push(&self, stack: &mut Vec) { - stack.push(RawFelt::new(*self as u64)) +impl FromMidenRepr for i16 { + #[inline(always)] + fn size_in_felts() -> usize { + 1 + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 2); + i16::from_ne_bytes([bytes[0], bytes[1]]) + } + + fn from_felts(felts: &[RawFelt]) -> Self { + felts[0].as_int() as u16 as i16 + } + + fn pop_from_stack(stack: &mut Vec) -> Self { + stack.pop().unwrap().as_int() as u16 as i16 } } -impl PopFromStack for u32 { - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front().unwrap().0.as_int() as u32) + +impl ToMidenRepr for u32 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_ne_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![RawFelt::new(*self as u64)] + } + + fn push_to_operand_stack(&self, stack: &mut Vec) { + stack.push(RawFelt::new(*self as u64)); } } -impl PushToStack for i32 { - fn try_push(&self, stack: &mut Vec) { - stack.push(RawFelt::new(*self as u32 as u64)) +impl FromMidenRepr for u32 { + #[inline(always)] + fn size_in_felts() -> usize { + 1 + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 4); + u32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + } + + fn from_felts(felts: &[RawFelt]) -> Self { + felts[0].as_int() as u32 + } + + fn pop_from_stack(stack: &mut Vec) -> Self { + stack.pop().unwrap().as_int() as u32 } } -impl PopFromStack for i32 { - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front().unwrap().0.as_int() as i32) + +impl ToMidenRepr for i32 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_ne_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![RawFelt::new(*self as u32 as u64)] + } + + fn push_to_operand_stack(&self, stack: &mut Vec) { + stack.push(RawFelt::new(*self as u32 as u64)); } } -impl PushToStack for u64 { - fn try_push(&self, stack: &mut Vec) { - let lo = self.rem_euclid(2u64.pow(32)); - let hi = self.div_euclid(2u64.pow(32)); - stack.push(RawFelt::new(lo)); - stack.push(RawFelt::new(hi)); +impl FromMidenRepr for i32 { + #[inline(always)] + fn size_in_felts() -> usize { + 1 + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 4); + i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) + } + + fn from_felts(felts: &[RawFelt]) -> Self { + felts[0].as_int() as u32 as i32 + } + + fn pop_from_stack(stack: &mut Vec) -> Self { + stack.pop().unwrap().as_int() as u32 as i32 } } -impl PopFromStack for u64 { - fn try_pop(stack: &mut VecDeque) -> Option { - let hi = stack.pop_front().unwrap().0.as_int() * 2u64.pow(32); - let lo = stack.pop_front().unwrap().0.as_int(); - Some(hi + lo) + +impl ToMidenRepr for u64 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_be_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + let bytes = self.to_be_bytes(); + let hi = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]); + let lo = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]); + smallvec![RawFelt::new(hi as u64), RawFelt::new(lo as u64)] } } -impl PushToStack for i64 { - fn try_push(&self, stack: &mut Vec) { - (*self as u64).try_push(stack) +impl FromMidenRepr for u64 { + #[inline(always)] + fn size_in_felts() -> usize { + 2 + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 8); + u64::from_be_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ]) + } + + fn from_felts(felts: &[RawFelt]) -> Self { + assert!(felts.len() >= 2); + let hi = (felts[0].as_int() as u32).to_be_bytes(); + let lo = (felts[1].as_int() as u32).to_be_bytes(); + u64::from_be_bytes([hi[0], hi[1], hi[2], hi[3], lo[0], lo[1], lo[2], lo[3]]) } } -impl PopFromStack for i64 { - fn try_pop(stack: &mut VecDeque) -> Option { - u64::try_pop(stack).map(|value| value as i64) + +impl ToMidenRepr for i64 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_be_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + (*self as u64).to_felts() } } -impl PushToStack for u128 { - fn try_push(&self, stack: &mut Vec) { - let lo = self.rem_euclid(2u128.pow(64)); - let hi = self.div_euclid(2u128.pow(64)); - (lo as u64).try_push(stack); - (hi as u64).try_push(stack); +impl FromMidenRepr for i64 { + #[inline(always)] + fn size_in_felts() -> usize { + 2 + } + + fn from_bytes(bytes: &[u8]) -> Self { + u64::from_bytes(bytes) as i64 + } + + fn from_felts(felts: &[RawFelt]) -> Self { + u64::from_felts(felts) as i64 } } -impl PopFromStack for u128 { - fn try_pop(stack: &mut VecDeque) -> Option { - let hi = (u64::try_pop(stack).unwrap() as u128) * 2u128.pow(64); - let lo = u64::try_pop(stack).unwrap() as u128; - Some(hi + lo) + +impl ToMidenRepr for u128 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_be_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + let bytes = self.to_be_bytes(); + let hi_h = + RawFelt::new(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64); + let hi_l = + RawFelt::new(u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as u64); + let lo_h = + RawFelt::new(u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]) as u64); + let lo_l = + RawFelt::new(u32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]) as u64); + + // The 64-bit limbs are little endian, (lo, hi), but the 32-bit limbs of those 64-bit + // values are big endian, (lo_h, lo_l) and (hi_h, hi_l). + smallvec![lo_h, lo_l, hi_h, hi_l] } } -impl PushToStack for i128 { - fn try_push(&self, stack: &mut Vec) { - (*self as u128).try_push(stack) +impl FromMidenRepr for u128 { + #[inline(always)] + fn size_in_felts() -> usize { + 4 + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= 16); + u128::from_be_bytes([ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], + ]) + } + + fn from_felts(felts: &[RawFelt]) -> Self { + assert!(felts.len() >= 4); + let hi_h = (felts[0].as_int() as u32).to_be_bytes(); + let hi_l = (felts[1].as_int() as u32).to_be_bytes(); + let lo_h = (felts[2].as_int() as u32).to_be_bytes(); + let lo_l = (felts[3].as_int() as u32).to_be_bytes(); + u128::from_be_bytes([ + hi_h[0], hi_h[1], hi_h[2], hi_h[3], hi_l[0], hi_l[1], hi_l[2], hi_l[3], lo_h[0], + lo_h[1], lo_h[2], lo_h[3], lo_l[0], lo_l[1], lo_l[2], lo_l[3], + ]) } } -impl PopFromStack for i128 { - fn try_pop(stack: &mut VecDeque) -> Option { - u128::try_pop(stack).map(|value| value as i128) + +impl ToMidenRepr for i128 { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(&self.to_be_bytes()) + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + (*self as u128).to_felts() } } -impl PushToStack for RawFelt { +impl FromMidenRepr for i128 { #[inline(always)] - fn try_push(&self, stack: &mut Vec) { - stack.push(*self); + fn size_in_felts() -> usize { + 4 + } + + fn from_bytes(bytes: &[u8]) -> Self { + u128::from_bytes(bytes) as i128 + } + + fn from_felts(felts: &[RawFelt]) -> Self { + u128::from_felts(felts) as i128 } } -impl PopFromStack for RawFelt { - #[inline(always)] - fn try_pop(stack: &mut VecDeque) -> Option { - Some(stack.pop_front()?.0) + +impl ToMidenRepr for RawFelt { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + panic!("field elements have no canonical byte representation") + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![*self] + } + + fn to_words(&self) -> SmallVec<[Word; 1]> { + let mut word = [RawFelt::ZERO; 4]; + word[0] = *self; + smallvec![Word::new(word)] } } -impl PushToStack for [RawFelt; 4] { +impl FromMidenRepr for RawFelt { #[inline(always)] - fn try_push(&self, stack: &mut Vec) { - stack.extend(self.iter().copied().rev()); + fn size_in_felts() -> usize { + 1 } -} -impl PopFromStack for [RawFelt; 4] { + + fn from_bytes(bytes: &[u8]) -> Self { + panic!("field elements have no canonical byte representation") + } + #[inline(always)] - fn try_pop(stack: &mut VecDeque) -> Option { - let a = stack.pop_front()?; - let b = stack.pop_front()?; - let c = stack.pop_front()?; - let d = stack.pop_front()?; - Some([a.0, b.0, c.0, d.0]) + fn from_felts(felts: &[RawFelt]) -> Self { + felts[0] } -} -impl PushToStack for Felt { #[inline(always)] - fn try_push(&self, stack: &mut Vec) { - stack.push(self.0); + fn from_words(words: &[Word]) -> Self { + words[0][0] } } -impl PopFromStack for Felt { + +impl ToMidenRepr for Felt { + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + panic!("field elements have no canonical byte representation") + } + + fn to_felts(&self) -> SmallVec<[RawFelt; 4]> { + smallvec![self.0] + } + + fn to_words(&self) -> SmallVec<[Word; 1]> { + let mut word = [RawFelt::ZERO; 4]; + word[0] = self.0; + smallvec![Word::new(word)] + } +} + +impl FromMidenRepr for Felt { #[inline(always)] - fn try_pop(stack: &mut VecDeque) -> Option { - stack.pop_front() + fn size_in_felts() -> usize { + 1 + } + + fn from_bytes(bytes: &[u8]) -> Self { + panic!("field elements have no canonical byte representation") + } + + #[inline(always)] + fn from_felts(felts: &[RawFelt]) -> Self { + Felt(felts[0]) + } + + #[inline(always)] + fn from_words(words: &[Word]) -> Self { + Felt(words[0][0]) + } +} + +impl ToMidenRepr for [u8; N] { + #[inline] + fn to_bytes(&self) -> SmallVec<[u8; 16]> { + SmallVec::from_slice(self) } } -impl PushToStack for [Felt; 4] { +impl FromMidenRepr for [u8; N] { #[inline(always)] - fn try_push(&self, stack: &mut Vec) { - stack.extend(self.iter().map(|f| f.0).rev()); + fn size_in_felts() -> usize { + N.next_multiple_of(4) / 4 + } + + fn from_bytes(bytes: &[u8]) -> Self { + assert!(bytes.len() >= N, "insufficient bytes"); + Self::try_from(&bytes[..N]).unwrap() } } -impl PopFromStack for [Felt; 4] { + +impl FromMidenRepr for [Felt; 4] { #[inline(always)] - fn try_pop(stack: &mut VecDeque) -> Option { - let a = stack.pop_front()?; - let b = stack.pop_front()?; - let c = stack.pop_front()?; - let d = stack.pop_front()?; - Some([a, b, c, d]) - } -} - -impl PopFromStack for [u8; N] { - fn try_pop(stack: &mut VecDeque) -> Option { - use midenc_hir::FieldElement; - let mut out = [0u8; N]; - - let chunk_size = (out.len() / 4) + (out.len() % 4 > 0) as usize; - for i in 0..chunk_size { - let elem: u32 = PopFromStack::try_pop(stack)?; - let bytes = elem.to_le_bytes(); - let offset = i * 4; - if offset + 3 < N { - out[offset] = bytes[0]; - out[offset + 1] = bytes[1]; - out[offset + 2] = bytes[2]; - out[offset + 3] = bytes[3]; - } else if offset + 2 < N { - out[offset] = bytes[0]; - out[offset + 1] = bytes[1]; - out[offset + 2] = bytes[2]; - break; - } else if offset + 1 < N { - out[offset] = bytes[0]; - out[offset + 1] = bytes[1]; - break; - } else if offset < N { - out[offset] = bytes[0]; - break; - } else { - break; - } - } + fn size_in_felts() -> usize { + 4 + } + + fn from_bytes(bytes: &[u8]) -> Self { + panic!("field elements have no canonical byte representation") + } - Some(out) + #[inline(always)] + fn from_felts(felts: &[RawFelt]) -> Self { + [Felt(felts[0]), Felt(felts[1]), Felt(felts[2]), Felt(felts[3])] } } @@ -282,41 +637,53 @@ impl PopFromStack for [u8; N] { /// /// Given a byte slice laid out like so: /// -/// [b0, b1, b2, b3, b4, b5, b6, b7, .., b31] +/// [b0, b1, b2, b3, b4, b5, b6, b7, .., b31] /// /// This will produce a vector of words laid out like so: /// -/// [[{b0, ..b3}, {b4, ..b7}, {b8..b11}, {b12, ..b15}], ..] +/// [[{b12, ..b15}, {b8..b11}, {b4, ..b7}, {b0, ..b3}], [{b31, ..}, ..]] /// -/// In other words, it produces words that when placed on the stack and written to memory -/// word-by-word, that memory will be laid out in the correct byte order. +/// In short, it produces words that when placed on the stack and written to memory word-by-word, +/// the original bytes will be laid out in Miden's memory in the correct order. pub fn bytes_to_words(bytes: &[u8]) -> Vec<[RawFelt; 4]> { // 1. Chunk bytes up into felts let mut iter = bytes.iter().array_chunks::<4>(); - let buf_size = (bytes.len() / 4) + (bytes.len() % 4 > 0) as usize; - let padding = buf_size % 8; - let mut buf = Vec::with_capacity(buf_size + padding); + let padded_bytes = bytes.len().next_multiple_of(16); + let num_felts = padded_bytes / 4; + let mut buf = Vec::with_capacity(num_felts); for chunk in iter.by_ref() { - let n = u32::from_le_bytes([*chunk[0], *chunk[1], *chunk[2], *chunk[3]]); + let n = u32::from_ne_bytes([*chunk[0], *chunk[1], *chunk[2], *chunk[3]]); buf.push(n); } // Zero-pad the buffer to nearest whole element - if let Some(rest) = iter.into_remainder() { - let mut n_buf = [0u8; 4]; - for (i, byte) in rest.into_iter().enumerate() { - n_buf[i] = *byte; + if let Some(rest) = iter.into_remainder().filter(|r| r.len() > 0) { + if rest.len() > 0 { + let mut n_buf = [0u8; 4]; + for (i, byte) in rest.into_iter().enumerate() { + n_buf[i] = *byte; + } + buf.push(u32::from_ne_bytes(n_buf)); } - buf.push(u32::from_le_bytes(n_buf)); } // Zero-pad the buffer to nearest whole word - let padded_buf_size = buf_size + padding; - buf.resize(padded_buf_size, 0); + buf.resize(num_felts, 0); // Chunk into words, and push them in largest-address first order - let word_size = (padded_buf_size / 4) + (padded_buf_size % 4 > 0) as usize; - let mut words = Vec::with_capacity(word_size); - for mut word_chunk in buf.into_iter().map(|elem| RawFelt::new(elem as u64)).array_chunks::<4>() - { - words.push(word_chunk); + let num_words = num_felts / 4; + let mut words = Vec::with_capacity(num_words); + let mut iter = buf.into_iter().map(|elem| RawFelt::new(elem as u64)).array_chunks::<4>(); + for mut word in iter.by_ref() { + word.reverse(); + words.push(word); + } + if let Some(extra) = iter.into_remainder().filter(|r| r.len() > 0) { + if extra.len() > 0 { + let mut word = [RawFelt::ZERO; 4]; + for (i, felt) in extra.enumerate() { + word[i] = felt; + } + word.reverse(); + words.push(word); + } } words } @@ -527,7 +894,147 @@ impl Arbitrary for Felt { mod tests { use std::collections::VecDeque; - use super::{bytes_to_words, PopFromStack}; + use miden_core::Word; + + use super::{bytes_to_words, FromMidenRepr, ToMidenRepr}; + + #[test] + fn bool_roundtrip() { + let encoded = true.to_bytes(); + let decoded = ::from_bytes(&encoded); + assert!(decoded); + + let encoded = true.to_felts(); + let decoded = ::from_felts(&encoded); + assert!(decoded); + + let encoded = true.to_words(); + let decoded = ::from_words(&encoded); + assert!(decoded); + + let mut stack = Vec::default(); + true.push_to_operand_stack(&mut stack); + let popped = ::pop_from_stack(&mut stack); + assert!(popped); + } + + #[test] + fn u8_roundtrip() { + let encoded = u8::MAX.to_bytes(); + let decoded = ::from_bytes(&encoded); + assert_eq!(decoded, u8::MAX); + + let encoded = u8::MAX.to_felts(); + let decoded = ::from_felts(&encoded); + assert_eq!(decoded, u8::MAX); + + let encoded = u8::MAX.to_words(); + let decoded = ::from_words(&encoded); + assert_eq!(decoded, u8::MAX); + + let mut stack = Vec::default(); + u8::MAX.push_to_operand_stack(&mut stack); + let popped = ::pop_from_stack(&mut stack); + assert_eq!(popped, u8::MAX); + } + + #[test] + fn u16_roundtrip() { + let encoded = u16::MAX.to_bytes(); + let decoded = ::from_bytes(&encoded); + assert_eq!(decoded, u16::MAX); + + let encoded = u16::MAX.to_felts(); + let decoded = ::from_felts(&encoded); + assert_eq!(decoded, u16::MAX); + + let encoded = u16::MAX.to_words(); + let decoded = ::from_words(&encoded); + assert_eq!(decoded, u16::MAX); + + let mut stack = Vec::default(); + u16::MAX.push_to_operand_stack(&mut stack); + let popped = ::pop_from_stack(&mut stack); + assert_eq!(popped, u16::MAX); + } + + #[test] + fn u32_roundtrip() { + let encoded = u32::MAX.to_bytes(); + let decoded = ::from_bytes(&encoded); + assert_eq!(decoded, u32::MAX); + + let encoded = u32::MAX.to_felts(); + let decoded = ::from_felts(&encoded); + assert_eq!(decoded, u32::MAX); + + let encoded = u32::MAX.to_words(); + let decoded = ::from_words(&encoded); + assert_eq!(decoded, u32::MAX); + + let mut stack = Vec::default(); + u32::MAX.push_to_operand_stack(&mut stack); + let popped = ::pop_from_stack(&mut stack); + assert_eq!(popped, u32::MAX); + } + + #[test] + fn u64_roundtrip() { + let encoded = u64::MAX.to_bytes(); + let decoded = ::from_bytes(&encoded); + assert_eq!(decoded, u64::MAX); + + let encoded = u64::MAX.to_felts(); + let decoded = ::from_felts(&encoded); + assert_eq!(decoded, u64::MAX); + + let encoded = u64::MAX.to_words(); + let decoded = ::from_words(&encoded); + assert_eq!(decoded, u64::MAX); + + let mut stack = Vec::default(); + u64::MAX.push_to_operand_stack(&mut stack); + let popped = ::pop_from_stack(&mut stack); + assert_eq!(popped, u64::MAX); + } + + #[test] + fn u128_roundtrip() { + let encoded = u128::MAX.to_bytes(); + let decoded = ::from_bytes(&encoded); + assert_eq!(decoded, u128::MAX); + + let encoded = u128::MAX.to_felts(); + let decoded = ::from_felts(&encoded); + assert_eq!(decoded, u128::MAX); + + let encoded = u128::MAX.to_words(); + let decoded = ::from_words(&encoded); + assert_eq!(decoded, u128::MAX); + + let mut stack = Vec::default(); + u128::MAX.push_to_operand_stack(&mut stack); + let popped = ::pop_from_stack(&mut stack); + assert_eq!(popped, u128::MAX); + } + + #[test] + fn byte_array_roundtrip() { + let bytes = [0, 1, 2, 3, 4, 5, 6, 7]; + + let encoded = bytes.to_felts(); + let decoded = <[u8; 8] as FromMidenRepr>::from_felts(&encoded); + assert_eq!(decoded, bytes); + + let encoded = bytes.to_words(); + let decoded = <[u8; 8] as FromMidenRepr>::from_words(&encoded); + assert_eq!(decoded, bytes); + + let mut stack = Vec::default(); + bytes.push_to_operand_stack(&mut stack); + let popped = <[u8; 8] as FromMidenRepr>::pop_from_stack(&mut stack); + assert_eq!(popped, bytes); + } #[test] fn bytes_to_words_test() { @@ -537,10 +1044,15 @@ mod tests { ]; let words = bytes_to_words(&bytes); assert_eq!(words.len(), 2); - assert_eq!(words[0][0].as_int() as u32, u32::from_le_bytes([1, 2, 3, 4])); - assert_eq!(words[0][1].as_int() as u32, u32::from_le_bytes([5, 6, 7, 8])); - assert_eq!(words[0][2].as_int() as u32, u32::from_le_bytes([9, 10, 11, 12])); - assert_eq!(words[0][3].as_int() as u32, u32::from_le_bytes([13, 14, 15, 16])); + // Words should be in little-endian order, elements of the word should be in big-endian + assert_eq!(words[0][3].as_int() as u32, u32::from_ne_bytes([1, 2, 3, 4])); + assert_eq!(words[0][2].as_int() as u32, u32::from_ne_bytes([5, 6, 7, 8])); + assert_eq!(words[0][1].as_int() as u32, u32::from_ne_bytes([9, 10, 11, 12])); + assert_eq!(words[0][0].as_int() as u32, u32::from_ne_bytes([13, 14, 15, 16])); + + // Make sure bytes_to_words and to_words agree + let to_words_output = bytes.to_words(); + assert_eq!(Word::new(words[0]), to_words_output[0]); } #[test] @@ -549,9 +1061,12 @@ mod tests { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, ]; - let words = bytes_to_words(&bytes); - let mut stack = VecDeque::from_iter(words.into_iter().flatten().map(super::Felt)); - let out: [u8; 32] = PopFromStack::try_pop(&mut stack).unwrap(); + let words_as_bytes = bytes_to_words(&bytes); + + let words = vec![Word::new(words_as_bytes[0]), Word::new(words_as_bytes[1])]; + + let out = <[u8; 32] as FromMidenRepr>::from_words(&words); + assert_eq!(&out, &bytes); } } diff --git a/midenc-debug/src/lib.rs b/midenc-debug/src/lib.rs index b410f0732..f76d9119f 100644 --- a/midenc-debug/src/lib.rs +++ b/midenc-debug/src/lib.rs @@ -21,7 +21,7 @@ pub use self::{ config::DebuggerConfig, debug::*, exec::*, - felt::{bytes_to_words, Felt, Felt as TestFelt, PopFromStack, PushToStack}, + felt::{bytes_to_words, Felt, Felt as TestFelt, FromMidenRepr, ToMidenRepr}, }; pub type ExecutionResult = Result; @@ -55,7 +55,7 @@ pub fn run_noninteractively( println!( "Executed program with hash {} in {}", - state.package.digest.to_hex(), + state.package.digest().to_hex(), HumanDuration::from(state.execution_duration), ); diff --git a/midenc-debug/src/ui/panes/breakpoints.rs b/midenc-debug/src/ui/panes/breakpoints.rs index 42a1b9e36..4405cc125 100644 --- a/midenc-debug/src/ui/panes/breakpoints.rs +++ b/midenc-debug/src/ui/panes/breakpoints.rs @@ -232,8 +232,7 @@ impl Pane for BreakpointsPane { pane.title_bottom( Line::styled( format!( - " {} of {} hit this cycle", - user_breakpoints_hit, user_created_breakpoints, + " {user_breakpoints_hit} of {user_created_breakpoints} hit this cycle", ), Style::default().add_modifier(Modifier::ITALIC), ) diff --git a/midenc-debug/src/ui/panes/disasm.rs b/midenc-debug/src/ui/panes/disasm.rs index c3ae525f7..d7c75315a 100644 --- a/midenc-debug/src/ui/panes/disasm.rs +++ b/midenc-debug/src/ui/panes/disasm.rs @@ -69,9 +69,7 @@ impl Pane for DisassemblyPane { .executor .recent .iter() - .map(|op| { - Line::from(vec![Span::styled(format!(" | {}", op), Color::White)]) - }) + .map(|op| Line::from(vec![Span::styled(format!(" | {op}"), Color::White)])) .collect::>(), ) } diff --git a/midenc-debug/src/ui/panes/source_code.rs b/midenc-debug/src/ui/panes/source_code.rs index 4b1d2089d..7187a0357 100644 --- a/midenc-debug/src/ui/panes/source_code.rs +++ b/midenc-debug/src/ui/panes/source_code.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeMap, sync::Arc}; +use std::{collections::BTreeMap, ops::Deref, sync::Arc}; use miden_assembly::diagnostics::SourceCode; use midenc_session::diagnostics::{LineIndex, Report, SourceFile, SourceId, SourceSpan}; @@ -57,12 +57,12 @@ impl SourceCodePane { let resolved_span = resolved.span.into_slice_index(); let content = resolved.source_file.content(); let last_line = content.last_line_index(); - let max_line_no = last_line.number().get() as usize; + let max_line_no = last_line.number().to_usize(); let gutter_width = max_line_no.ilog10() as u8; let lines = (0..(max_line_no - 1)) .map(|line_index| { - let line_index = miden_core::debuginfo::LineIndex::from(line_index as u32); - let line_no = line_index.number().get(); + let line_index = miden_debug_types::LineIndex::from(line_index as u32); + let line_no = line_index.number().to_u32(); let span = content.line_range(line_index).expect("invalid line index"); let span = span.start.to_usize()..span.end.to_usize(); @@ -483,7 +483,7 @@ impl Pane for SourceCodePane { ) .title( Line::styled( - resolved.source_file.path().to_string_lossy(), + resolved.source_file.deref().uri().as_str(), Style::default().add_modifier(Modifier::ITALIC), ) .right_aligned(), diff --git a/midenc-debug/src/ui/panes/stacktrace.rs b/midenc-debug/src/ui/panes/stacktrace.rs index f6f630b12..17b9bc882 100644 --- a/midenc-debug/src/ui/panes/stacktrace.rs +++ b/midenc-debug/src/ui/panes/stacktrace.rs @@ -87,7 +87,7 @@ impl Pane for StackTracePane { parts.push(name); if let Some(resolved) = frame.last_resolved(&state.session) { parts.push(Span::styled(" in ", Color::DarkGray)); - let path = resolved.source_file.path(); + let path = std::path::Path::new(resolved.source_file.as_ref().uri().as_str()); let path = path .strip_prefix(state.session.options.current_dir.as_path()) .ok() diff --git a/midenc-debug/src/ui/state.rs b/midenc-debug/src/ui/state.rs index 17aff4d39..2ea4d0295 100644 --- a/midenc-debug/src/ui/state.rs +++ b/midenc-debug/src/ui/state.rs @@ -3,7 +3,6 @@ use std::{rc::Rc, sync::Arc}; use miden_assembly::Library; use miden_core::{utils::Deserializable, FieldElement}; use miden_processor::{Felt, Program, StackInputs}; -use midenc_codegen_masm::Package; use midenc_session::{ diagnostics::{IntoDiagnostic, Report, SourceSpan, Span, WrapErr}, InputType, Session, @@ -14,7 +13,7 @@ use crate::{ }; pub struct State { - pub package: Arc, + pub package: Arc, pub inputs: DebuggerConfig, pub executor: DebugExecutor, pub execution_trace: ExecutionTrace, @@ -47,10 +46,10 @@ impl State { args.reverse(); inputs.inputs = StackInputs::new(args).into_diagnostic()?; } - let args = inputs.inputs.values().iter().copied().rev().collect::>(); + let args = inputs.inputs.iter().copied().rev().collect::>(); let package = load_package(&session)?; - let mut executor = crate::Executor::for_package(&package, args.clone(), &session)?; + let mut executor = crate::Executor::for_package(&package.clone(), args.clone(), &session)?; executor.with_advice_inputs(inputs.advice_inputs.clone()); for link_library in session.options.link_libraries.iter() { let lib = link_library.load(&session)?; @@ -91,7 +90,7 @@ impl State { pub fn reload(&mut self) -> Result<(), Report> { log::debug!("reloading program"); let package = load_package(&self.session)?; - let args = self.inputs.inputs.values().iter().copied().rev().collect::>(); + let args = self.inputs.inputs.iter().copied().rev().collect::>(); let mut executor = crate::Executor::for_package(&package, args.clone(), &self.session)?; executor.with_advice_inputs(self.inputs.advice_inputs.clone()); @@ -193,15 +192,17 @@ impl State { } let felt = self .execution_trace - .read_memory_element_in_context(expr.addr.waddr, expr.addr.index, context, cycle) + .read_memory_element_in_context(expr.addr.addr, context, cycle) .unwrap_or(Felt::ZERO); write_with_format_type!(output, expr, felt.as_int()); - } else if matches!(expr.ty, Type::Array(ref elem_ty, 4) if elem_ty.as_ref() == &Type::Felt) - { + } else if matches!( + expr.ty, + Type::Array(ref array_ty) if array_ty.element_type() == &Type::Felt && array_ty.len() == 4 + ) { if !expr.addr.is_word_aligned() { return Err("read failed: type 'word' must be aligned to a word boundary".into()); } - let word = self.execution_trace.read_memory_word(expr.addr.waddr).unwrap_or_default(); + let word = self.execution_trace.read_memory_word(expr.addr.addr).unwrap_or_default(); output.push('['); for (i, elem) in word.iter().enumerate() { if i > 0 { @@ -265,20 +266,30 @@ impl State { } } -fn load_package(session: &Session) -> Result, Report> { +fn load_package(session: &Session) -> Result, Report> { let package = match &session.inputs[0].file { InputType::Real(ref path) => { - Package::read_from_file(path).map(Arc::new).into_diagnostic()? + let bytes = std::fs::read(path).into_diagnostic()?; + miden_mast_package::Package::read_from_bytes(&bytes) + .map(Arc::new) + .map_err(|e| { + Report::msg(format!( + "failed to load Miden package from {}: {e}", + path.display() + )) + })? } InputType::Stdin { input, .. } => { - Package::read_from_bytes(input.as_slice()).map(Arc::new)? + miden_mast_package::Package::read_from_bytes(input.as_slice()) + .map(Arc::new) + .map_err(|e| Report::msg(format!("failed to load Miden package from stdin: {e}")))? } }; if let Some(entry) = session.options.entrypoint.as_ref() { // Input must be a library, not a program let id = entry - .parse::() + .parse::() .map_err(|_| Report::msg(format!("invalid function identifier: '{entry}'")))?; if !package.is_library() { return Err(Report::msg("cannot use --entrypoint with executable packages")); diff --git a/midenc-debug/src/ui/syntax_highlighting.rs b/midenc-debug/src/ui/syntax_highlighting.rs index cd495a455..5b55babe9 100644 --- a/midenc-debug/src/ui/syntax_highlighting.rs +++ b/midenc-debug/src/ui/syntax_highlighting.rs @@ -182,9 +182,9 @@ impl SyntectHighlighter { } } // finally, attempt to guess syntax based on first line - return self.syntax_set.find_syntax_by_first_line( + self.syntax_set.find_syntax_by_first_line( core::str::from_utf8(contents.data()).ok()?.split('\n').next()?, - ); + ) } } @@ -198,7 +198,7 @@ pub(crate) struct SyntectHighlighterState<'h> { use_bg_color: bool, } -impl<'h> HighlighterState for SyntectHighlighterState<'h> { +impl HighlighterState for SyntectHighlighterState<'_> { fn highlight_line<'a>(&mut self, line: Cow<'a, str>) -> Vec> { if let Ok(ops) = self.parse_state.parse_line(&line, &self.syntax_set) { let use_bg_color = self.use_bg_color; diff --git a/midenc-driver/CHANGELOG.md b/midenc-driver/CHANGELOG.md index 6f2d1624c..63c71aaea 100644 --- a/midenc-driver/CHANGELOG.md +++ b/midenc-driver/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-driver-v0.0.7...midenc-driver-v0.0.8) - 2025-04-24 + +### Other +- treat warnings as compiler errors, +- rename hir2 crates +- switch uses of hir crates to hir2 +- add `midenc run` inputs file example + ## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/midenc-driver-v0.0.5...midenc-driver-v0.0.6) - 2024-09-06 ### Added diff --git a/midenc-driver/Cargo.toml b/midenc-driver/Cargo.toml index 747946137..2af53fc82 100644 --- a/midenc-driver/Cargo.toml +++ b/midenc-driver/Cargo.toml @@ -17,7 +17,7 @@ edition.workspace = true default = ["all"] all = ["std", "debug"] debug = ["dep:midenc-debug"] -std = ["alloc", "log/std", "clap/std", "clap/color", "clap/env"] +std = ["alloc", "log/std", "clap/color"] alloc = ["clap/help", "clap/usage", "clap/error-context", "clap/suggestions"] [dependencies] diff --git a/midenc-driver/src/lib.rs b/midenc-driver/src/lib.rs index bfaecd72c..1a980b4df 100644 --- a/midenc-driver/src/lib.rs +++ b/midenc-driver/src/lib.rs @@ -1,3 +1,5 @@ +#![deny(warnings)] + mod midenc; pub use clap::Error as ClapError; diff --git a/midenc-driver/src/midenc.rs b/midenc-driver/src/midenc.rs index 10ffadf53..bbecfdfd4 100644 --- a/midenc-driver/src/midenc.rs +++ b/midenc-driver/src/midenc.rs @@ -5,6 +5,7 @@ use log::Log; use midenc_compile as compile; #[cfg(feature = "debug")] use midenc_debug as debugger; +use midenc_hir::Context; use midenc_session::{ diagnostics::{Emitter, Report}, InputFile, @@ -47,6 +48,13 @@ enum Commands { /// Program inputs are stack and advice provider values which the program can /// access during execution. The inputs file is a TOML file which describes /// what the inputs are, or where to source them from. + /// + /// Example: + /// + /// [inputs] + /// + /// stack = [1, 2, 0x3] + /// #[arg(long, value_name = "FILE")] inputs: Option, /// Number of outputs on the operand stack to print @@ -154,9 +162,11 @@ impl Midenc { if options.working_dir.is_none() { options.working_dir = Some(cwd); } - let session = - options.into_session(vec![input], emitter).with_extra_flags(matches.into()); - compile::compile(Rc::new(session)) + let session = Rc::new( + options.into_session(vec![input], emitter).with_extra_flags(matches.into()), + ); + let context = Rc::new(Context::new(session)); + compile::compile(context) } #[cfg(feature = "debug")] Commands::Run { diff --git a/midenc-session/CHANGELOG.md b/midenc-session/CHANGELOG.md index 3ec61bea9..b4b7b795a 100644 --- a/midenc-session/CHANGELOG.md +++ b/midenc-session/CHANGELOG.md @@ -6,6 +6,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-session-v0.1.5...midenc-session-v0.4.0) - 2025-08-15 + +### Added + +- add `project-kind` with `account`, `note-script` and +- rename note script rollup target into script + +### Other + +- update VM to `v0.15.0` +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-session-v0.0.7...midenc-session-v0.0.8) - 2025-04-24 + +### Added +- implement pass infrastructure + +### Fixed +- return error (instead of panic) when package contain a Program + +### Other +- treat warnings as compiler errors, +- add some missing log targets +- update rust toolchain, clean up deps +- enrich Miden package loading error with the file path +- rename hir2 crates +- switch compiler to hir2 +- update to the latest `miden-mast-package` (renamed from +- update the Miden VM with updated `miden-package` crate +- update rust toolchain to 1-16 nightly @ 1.86.0 +- fix formatting after rebase +- finish initial rewrite of backend using hir2 +- [**breaking**] move `Package` to `miden-package` in the VM repo +- add note script compilation test; + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-session-v0.0.6...midenc-session-v0.0.7) - 2024-09-17 ### Other diff --git a/midenc-session/Cargo.toml b/midenc-session/Cargo.toml index 5062d75d5..b867f2874 100644 --- a/midenc-session/Cargo.toml +++ b/midenc-session/Cargo.toml @@ -15,23 +15,29 @@ edition.workspace = true [features] default = ["std"] -std = ["dep:termcolor", "dep:parking_lot", "dep:clap"] -serde = ["dep:serde", "dep:serde_repr", "midenc-hir-symbol/serde"] +std = [ + "dep:termcolor", + "dep:parking_lot", + "dep:clap", + "anyhow/std", + "miden-assembly/std", + "miden-lib/std", +] [dependencies] +anyhow.workspace = true clap = { workspace = true, optional = true } inventory.workspace = true log.workspace = true miden-assembly.workspace = true +miden-assembly-syntax.workspace = true miden-core.workspace = true +miden-debug-types.workspace = true +miden-mast-package.workspace = true miden-stdlib.workspace = true midenc-hir-symbol.workspace = true midenc-hir-macros.workspace = true -miden-base-sys = { version = "0.0.7", path = "../sdk/base-sys", features = [ - "masl-lib", -] } +miden-lib.workspace = true parking_lot = { workspace = true, optional = true } termcolor = { version = "1.4.1", optional = true } thiserror.workspace = true -serde = { workspace = true, optional = true } -serde_repr = { workspace = true, optional = true } diff --git a/midenc-session/src/color.rs b/midenc-session/src/color.rs index dfe039da7..cdc657ee9 100644 --- a/midenc-session/src/color.rs +++ b/midenc-session/src/color.rs @@ -130,4 +130,18 @@ impl ColorChoice { } } } + + /// Returns true if this choice should forcefully use ANSI color codes. + /// + /// It's possible that ANSI is still the correct choice even if this + /// returns false. + #[cfg(not(feature = "std"))] + pub fn should_ansi(&self) -> bool { + match *self { + ColorChoice::Always => false, + ColorChoice::AlwaysAnsi => true, + ColorChoice::Never => false, + ColorChoice::Auto => false, + } + } } diff --git a/midenc-session/src/diagnostics.rs b/midenc-session/src/diagnostics.rs index 11ff71bdf..ee15c6598 100644 --- a/midenc-session/src/diagnostics.rs +++ b/midenc-session/src/diagnostics.rs @@ -14,10 +14,10 @@ pub use miden_assembly::diagnostics::{ miette::MietteDiagnostic as AdHocDiagnostic, reporting, reporting::{PrintDiagnostic, ReportHandlerOpts}, - Diagnostic, IntoDiagnostic, Label, LabeledSpan, RelatedError, RelatedLabel, Report, Severity, - WrapErr, + Diagnostic, Label, LabeledSpan, RelatedError, RelatedLabel, Report, Severity, WrapErr, }; -pub use miden_core::debuginfo::*; +pub use miden_core::*; +pub use miden_debug_types::*; pub use midenc_hir_macros::Spanned; #[cfg(feature = "std")] @@ -33,7 +33,7 @@ pub struct DiagnosticsConfig { pub struct DiagnosticsHandler { emitter: Arc, - source_manager: Arc, + source_manager: Arc, err_count: AtomicUsize, verbosity: Verbosity, warnings: Warnings, @@ -43,7 +43,8 @@ pub struct DiagnosticsHandler { impl Default for DiagnosticsHandler { fn default() -> Self { let emitter = Arc::new(DefaultEmitter::new(ColorChoice::Auto)); - let source_manager = Arc::new(DefaultSourceManager::default()); + let source_manager = + Arc::new(DefaultSourceManager::default()) as Arc; Self::new(Default::default(), source_manager, emitter) } } @@ -58,7 +59,7 @@ impl DiagnosticsHandler { /// [CodeMap], and [Emitter] implementation. pub fn new( config: DiagnosticsConfig, - source_manager: Arc, + source_manager: Arc, emitter: Arc, ) -> Self { let warnings = match config.warnings { @@ -77,7 +78,7 @@ impl DiagnosticsHandler { } #[inline] - pub fn source_manager(&self) -> Arc { + pub fn source_manager(&self) -> Arc { self.source_manager.clone() } @@ -177,8 +178,12 @@ impl DiagnosticsHandler { #[cfg(not(feature = "std"))] fn write_report(&self, diagnostic: Report) { - let out = PrintDiagnostic::new(diagnostic).to_string(); - self.emitter.print(out).unwrap(); + use core::fmt::Write; + + let mut buffer = self.emitter.buffer(); + let printer = PrintDiagnostic::new(diagnostic); + write!(&mut buffer, "{printer}").expect("failed to write diagnostic to buffer"); + self.emitter.print(buffer).unwrap(); } } @@ -395,3 +400,69 @@ impl Diagnostic for InFlightDiagnostic { None } } + +pub use self::into_diagnostic::{DiagnosticError, IntoDiagnostic}; + +mod into_diagnostic { + use alloc::boxed::Box; + + /// Convenience [`Diagnostic`] that can be used as an "anonymous" wrapper for + /// Errors. This is intended to be paired with [`IntoDiagnostic`]. + #[derive(Debug)] + pub struct DiagnosticError(Box); + impl DiagnosticError { + pub fn new(error: E) -> Self { + Self(Box::new(error)) + } + } + impl miden_assembly::diagnostics::Diagnostic + for DiagnosticError + { + } + impl core::fmt::Display for DiagnosticError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(self.0.as_ref(), f) + } + } + impl core::error::Error for DiagnosticError { + default fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + None + } + + default fn cause(&self) -> Option<&dyn core::error::Error> { + self.source() + } + } + impl core::error::Error for DiagnosticError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + self.0.source() + } + } + unsafe impl Send for DiagnosticError {} + unsafe impl Sync for DiagnosticError {} + + /** + Convenience trait that adds a [`.into_diagnostic()`](IntoDiagnostic::into_diagnostic) method that converts a type implementing + [`std::error::Error`] to a [`Result`]. + + ## Warning + + Calling this on a type implementing [`Diagnostic`] will reduce it to the common denominator of + [`std::error::Error`]. Meaning all extra information provided by [`Diagnostic`] will be + inaccessible. If you have a type implementing [`Diagnostic`] consider simply returning it or using + [`Into`] or the [`Try`](std::ops::Try) operator (`?`). + */ + pub trait IntoDiagnostic { + /// Converts [`Result`] types that return regular [`std::error::Error`]s + /// into a [`Result`] that returns a [`Diagnostic`]. + fn into_diagnostic(self) -> Result; + } + + impl IntoDiagnostic + for Result + { + fn into_diagnostic(self) -> Result { + self.map_err(|e| DiagnosticError::new(e).into()) + } + } +} diff --git a/midenc-session/src/duration.rs b/midenc-session/src/duration.rs index 34aae7d9a..99d053ddf 100644 --- a/midenc-session/src/duration.rs +++ b/midenc-session/src/duration.rs @@ -3,6 +3,7 @@ use std::{ time::{Duration, Instant}, }; +#[derive(Copy, Clone)] pub struct HumanDuration(Duration); impl HumanDuration { pub fn since(i: Instant) -> Self { @@ -14,6 +15,41 @@ impl HumanDuration { pub fn as_secs_f64(&self) -> f64 { self.0.as_secs_f64() } + + /// Adds two [HumanDuration], using saturating arithmetic + #[inline] + pub fn saturating_add(self, rhs: Self) -> Self { + Self(self.0.saturating_add(rhs.0)) + } +} +impl core::ops::Add for HumanDuration { + type Output = HumanDuration; + + fn add(self, rhs: Self) -> Self::Output { + Self(self.0 + rhs.0) + } +} +impl core::ops::Add for HumanDuration { + type Output = HumanDuration; + + fn add(self, rhs: Duration) -> Self::Output { + Self(self.0 + rhs) + } +} +impl core::ops::AddAssign for HumanDuration { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} +impl core::ops::AddAssign for HumanDuration { + fn add_assign(&mut self, rhs: Duration) { + self.0 += rhs; + } +} +impl From for Duration { + fn from(d: HumanDuration) -> Self { + d.0 + } } impl From for HumanDuration { fn from(d: Duration) -> Self { diff --git a/midenc-session/src/emit.rs b/midenc-session/src/emit.rs index 62fec41d5..1fb8a9b35 100644 --- a/midenc-session/src/emit.rs +++ b/midenc-session/src/emit.rs @@ -1,7 +1,7 @@ use alloc::{boxed::Box, fmt, format, string::ToString, sync::Arc, vec}; -use std::{fs::File, io::Write, path::Path}; use miden_core::{prettier::PrettyPrint, utils::Serializable}; +use miden_mast_package::MastArtifact; use midenc_hir_symbol::Symbol; use crate::{OutputMode, OutputType, Session}; @@ -11,9 +11,33 @@ pub trait Emit { fn name(&self) -> Option; /// The output type associated with this item and the given `mode` fn output_type(&self, mode: OutputMode) -> OutputType; + /// Write this item to the given [std::io::Write] handle, using `mode` to determine the output + /// type + fn write_to( + &self, + writer: W, + mode: OutputMode, + session: &Session, + ) -> anyhow::Result<()>; +} + +#[cfg(feature = "std")] +pub trait EmitExt: Emit { /// Write this item to standard output, inferring the best [OutputMode] based on whether or not /// stdout is a tty or not - fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { + fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()>; + /// Write this item to the given file path, using `mode` to determine the output type + fn write_to_file( + &self, + path: &std::path::Path, + mode: OutputMode, + session: &Session, + ) -> anyhow::Result<()>; +} + +#[cfg(feature = "std")] +impl EmitExt for T { + default fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()> { use std::io::IsTerminal; let stdout = std::io::stdout().lock(); let mode = if stdout.is_terminal() { @@ -23,67 +47,99 @@ pub trait Emit { }; self.write_to(stdout, mode, session) } - /// Write this item to the given file path, using `mode` to determine the output type - fn write_to_file( + + default fn write_to_file( &self, - path: &Path, + path: &std::path::Path, mode: OutputMode, session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { if let Some(dir) = path.parent() { std::fs::create_dir_all(dir)?; } - let file = File::create(path)?; + let file = std::fs::File::create(path)?; self.write_to(file, mode, session) } - /// Write this item to the given [std::io::Write] handle, using `mode` to determine the output - /// type - fn write_to( - &self, - writer: W, - mode: OutputMode, - session: &Session, - ) -> std::io::Result<()>; } -impl<'a, T: Emit> Emit for &'a T { - #[inline] - fn name(&self) -> Option { - (**self).name() +/// A trait that provides a subset of the [std::io::Write] functionality that is usable in no-std +/// contexts. +pub trait Writer { + fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()>; + fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()>; +} + +#[cfg(feature = "std")] +impl Writer for W { + fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()> { + ::write_fmt(self, fmt).map_err(|err| err.into()) } - #[inline] - fn output_type(&self, mode: OutputMode) -> OutputType { - (**self).output_type(mode) + fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()> { + ::write_all(self, buf).map_err(|err| err.into()) + } +} + +#[cfg(not(feature = "std"))] +impl Writer for alloc::vec::Vec { + fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()> { + if let Some(s) = fmt.as_str() { + self.extend(s.as_bytes()); + } else { + let formatted = fmt.to_string(); + self.extend(formatted.as_bytes()); + } + Ok(()) + } + + fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()> { + self.extend(buf); + Ok(()) + } +} + +#[cfg(not(feature = "std"))] +impl Writer for alloc::string::String { + fn write_fmt(&mut self, fmt: core::fmt::Arguments<'_>) -> anyhow::Result<()> { + if let Some(s) = fmt.as_str() { + self.push_str(s); + } else { + let formatted = fmt.to_string(); + self.push_str(&formatted); + } + Ok(()) } + fn write_all(&mut self, buf: &[u8]) -> anyhow::Result<()> { + let s = core::str::from_utf8(buf)?; + self.push_str(s); + Ok(()) + } +} + +impl Emit for &T { #[inline] - fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { - (**self).write_to_stdout(session) + fn name(&self) -> Option { + (**self).name() } #[inline] - fn write_to_file( - &self, - path: &Path, - mode: OutputMode, - session: &Session, - ) -> std::io::Result<()> { - (**self).write_to_file(path, mode, session) + fn output_type(&self, mode: OutputMode) -> OutputType { + (**self).output_type(mode) } #[inline] - fn write_to( + fn write_to( &self, writer: W, mode: OutputMode, session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { (**self).write_to(writer, mode, session) } } -impl<'a, T: Emit> Emit for &'a mut T { +impl Emit for &mut T { #[inline] fn name(&self) -> Option { (**self).name() @@ -95,27 +151,12 @@ impl<'a, T: Emit> Emit for &'a mut T { } #[inline] - fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { - (**self).write_to_stdout(session) - } - - #[inline] - fn write_to_file( - &self, - path: &Path, - mode: OutputMode, - session: &Session, - ) -> std::io::Result<()> { - (**self).write_to_file(path, mode, session) - } - - #[inline] - fn write_to( + fn write_to( &self, writer: W, mode: OutputMode, session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { (**self).write_to(writer, mode, session) } } @@ -132,27 +173,12 @@ impl Emit for Box { } #[inline] - fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { - (**self).write_to_stdout(session) - } - - #[inline] - fn write_to_file( - &self, - path: &Path, - mode: OutputMode, - session: &Session, - ) -> std::io::Result<()> { - (**self).write_to_file(path, mode, session) - } - - #[inline] - fn write_to( + fn write_to( &self, writer: W, mode: OutputMode, session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { (**self).write_to(writer, mode, session) } } @@ -169,27 +195,12 @@ impl Emit for Arc { } #[inline] - fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { - (**self).write_to_stdout(session) - } - - #[inline] - fn write_to_file( - &self, - path: &Path, - mode: OutputMode, - session: &Session, - ) -> std::io::Result<()> { - (**self).write_to_file(path, mode, session) - } - - #[inline] - fn write_to( + fn write_to( &self, writer: W, mode: OutputMode, session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { (**self).write_to(writer, mode, session) } } @@ -203,30 +214,31 @@ impl Emit for miden_assembly::ast::Module { OutputType::Masm } - fn write_to( + fn write_to( &self, mut writer: W, mode: OutputMode, _session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { assert_eq!(mode, OutputMode::Text, "masm syntax trees do not support binary mode"); - writer.write_fmt(format_args!("{}\n", self)) + writer.write_fmt(format_args!("{self}\n")) } } +#[cfg(feature = "std")] macro_rules! serialize_into { - ($serializable:ident, $writer:ident) => { + ($serializable:ident, $writer:expr) => { // NOTE: We're protecting against unwinds here due to i/o errors that will get turned into // panics if writing to the underlying file fails. This is because ByteWriter does not have // fallible APIs, thus WriteAdapter has to panic if writes fail. This could be fixed, but // that has to happen upstream in winterfell std::panic::catch_unwind(move || { - let mut writer = $writer; + let mut writer = ByteWriterAdapter($writer); $serializable.write_into(&mut writer) }) .map_err(|p| { - match p.downcast::() { - // SAFETY: It is guaranteed to be safe to read Box + match p.downcast::() { + // SAFETY: It is guaranteed to be safe to read Box Ok(err) => unsafe { core::ptr::read(&*err) }, // Propagate unknown panics Err(err) => std::panic::resume_unwind(err), @@ -235,6 +247,17 @@ macro_rules! serialize_into { }; } +struct ByteWriterAdapter<'a, W>(&'a mut W); +impl miden_assembly::utils::ByteWriter for ByteWriterAdapter<'_, W> { + fn write_u8(&mut self, value: u8) { + self.0.write_all(&[value]).unwrap() + } + + fn write_bytes(&mut self, values: &[u8]) { + self.0.write_all(values).unwrap() + } +} + impl Emit for miden_assembly::Library { fn name(&self) -> Option { None @@ -247,14 +270,14 @@ impl Emit for miden_assembly::Library { } } - fn write_to( + fn write_to( &self, mut writer: W, mode: OutputMode, _session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { struct LibraryTextFormatter<'a>(&'a miden_assembly::Library); - impl<'a> miden_core::prettier::PrettyPrint for LibraryTextFormatter<'a> { + impl miden_core::prettier::PrettyPrint for LibraryTextFormatter<'_> { fn render(&self) -> miden_core::prettier::Document { use miden_core::prettier::*; @@ -298,7 +321,7 @@ impl Emit for miden_assembly::Library { library_doc } } - impl<'a> fmt::Display for LibraryTextFormatter<'a> { + impl fmt::Display for LibraryTextFormatter<'_> { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.pretty_print(f) @@ -308,6 +331,7 @@ impl Emit for miden_assembly::Library { match mode { OutputMode::Text => writer.write_fmt(format_args!("{}", LibraryTextFormatter(self))), OutputMode::Binary => { + let mut writer = ByteWriterAdapter(&mut writer); self.write_into(&mut writer); Ok(()) } @@ -327,23 +351,43 @@ impl Emit for miden_core::Program { } } + fn write_to( + &self, + mut writer: W, + mode: OutputMode, + _session: &Session, + ) -> anyhow::Result<()> { + match mode { + //OutputMode::Text => writer.write_fmt(format_args!("{}", self)), + OutputMode::Text => unimplemented!("emitting mast in text form is currently broken"), + OutputMode::Binary => { + let mut writer = ByteWriterAdapter(&mut writer); + self.write_into(&mut writer); + Ok(()) + } + } + } +} + +#[cfg(feature = "std")] +impl EmitExt for miden_core::Program { fn write_to_file( &self, - path: &Path, + path: &std::path::Path, mode: OutputMode, session: &Session, - ) -> std::io::Result<()> { + ) -> anyhow::Result<()> { if let Some(dir) = path.parent() { std::fs::create_dir_all(dir)?; } let mut file = std::fs::File::create(path)?; match mode { OutputMode::Text => self.write_to(&mut file, mode, session), - OutputMode::Binary => serialize_into!(self, file), + OutputMode::Binary => serialize_into!(self, &mut file), } } - fn write_to_stdout(&self, session: &Session) -> std::io::Result<()> { + fn write_to_stdout(&self, session: &Session) -> anyhow::Result<()> { use std::io::IsTerminal; let mut stdout = std::io::stdout().lock(); let mode = if stdout.is_terminal() { @@ -353,23 +397,66 @@ impl Emit for miden_core::Program { }; match mode { OutputMode::Text => self.write_to(&mut stdout, mode, session), - OutputMode::Binary => serialize_into!(self, stdout), + OutputMode::Binary => serialize_into!(self, &mut stdout), + } + } +} + +impl Emit for miden_mast_package::Package { + fn name(&self) -> Option { + Some(Symbol::intern(&self.name)) + } + + fn output_type(&self, mode: OutputMode) -> OutputType { + match mode { + OutputMode::Text => OutputType::Mast, + OutputMode::Binary => OutputType::Masp, } } - fn write_to( + fn write_to( &self, mut writer: W, mode: OutputMode, - _session: &Session, - ) -> std::io::Result<()> { + session: &Session, + ) -> anyhow::Result<()> { match mode { - //OutputMode::Text => writer.write_fmt(format_args!("{}", self)), - OutputMode::Text => unimplemented!("emitting mast in text form is currently broken"), + OutputMode::Text => match self.mast { + miden_mast_package::MastArtifact::Executable(ref prog) => { + prog.write_to(writer, mode, session) + } + miden_mast_package::MastArtifact::Library(ref lib) => { + lib.write_to(writer, mode, session) + } + }, OutputMode::Binary => { - self.write_into(&mut writer); - Ok(()) + let bytes = self.to_bytes(); + writer.write_all(bytes.as_slice()) } } } } + +impl Emit for MastArtifact { + fn name(&self) -> Option { + None + } + + fn output_type(&self, mode: OutputMode) -> OutputType { + match mode { + OutputMode::Text => OutputType::Mast, + OutputMode::Binary => OutputType::Masl, + } + } + + fn write_to( + &self, + mut writer: W, + _mode: OutputMode, + _session: &Session, + ) -> anyhow::Result<()> { + let mut writer = ByteWriterAdapter(&mut writer); + self.write_into(&mut writer); + Ok(()) + } +} diff --git a/midenc-session/src/emitter.rs b/midenc-session/src/emitter.rs index abeb153cd..acc3be2c3 100644 --- a/midenc-session/src/emitter.rs +++ b/midenc-session/src/emitter.rs @@ -1,10 +1,16 @@ -use alloc::{string::String, vec::Vec}; +#[cfg(feature = "std")] +use alloc::string::String; +#[cfg(not(feature = "std"))] +use alloc::vec; +use alloc::vec::Vec; use core::ops::Deref; -use crate::{ - diagnostics::{IntoDiagnostic, Report}, - ColorChoice, -}; +#[cfg(not(feature = "std"))] +use midenc_hir_symbol::sync::RwLock; + +#[cfg(feature = "std")] +use crate::diagnostics::IntoDiagnostic; +use crate::{diagnostics::Report, ColorChoice}; /// The [Emitter] trait is used for controlling how diagnostics are displayed. /// @@ -59,7 +65,7 @@ pub struct DefaultEmitterImpl { #[cfg(not(feature = "std"))] #[doc(hidden)] pub struct DefaultEmitterImpl { - writer: Vec, + writer: RwLock>, ansi: bool, } @@ -90,7 +96,7 @@ impl DefaultEmitterImpl { fn new(color: ColorChoice) -> Self { Self { ansi: color.should_ansi(), - writer: vec![], + writer: Default::default(), } } } @@ -108,8 +114,9 @@ impl Emitter for DefaultEmitterImpl { #[inline(always)] fn print(&self, buffer: Buffer) -> Result<(), Report> { - self.0.push(b'\n'); - self.0.extend(buffer.into_inner()); + let mut writer = self.writer.write(); + writer.push(b'\n'); + writer.extend(buffer.into_inner()); Ok(()) } } @@ -302,9 +309,10 @@ impl Buffer { #[cfg(not(feature = "std"))] impl core::fmt::Write for Buffer { - fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Result> { - use core::fmt::Write; - self.0.write_str(s); + fn write_str(&mut self, s: &str) -> core::fmt::Result { + use miden_assembly::utils::ByteWriter; + self.0.write_bytes(s.as_bytes()); + Ok(()) } } diff --git a/midenc-session/src/flags/arg_matches.rs b/midenc-session/src/flags/arg_matches.rs new file mode 100644 index 000000000..03ceeb578 --- /dev/null +++ b/midenc-session/src/flags/arg_matches.rs @@ -0,0 +1,478 @@ +#[cfg(feature = "std")] +pub use clap::ArgMatches; + +#[cfg(not(feature = "std"))] +pub use self::fallback::ArgMatches; + +mod fallback { + #![allow(unused)] + use alloc::{ + borrow::Cow, + collections::{BTreeMap, VecDeque}, + format, + sync::Arc, + vec, + vec::Vec, + }; + use core::any::Any; + + use crate::{diagnostics::Report, CompileFlag, FlagAction}; + + /// Violation of [`ArgMatches`] assumptions + #[derive(Clone, Debug, thiserror::Error)] + #[non_exhaustive] + pub enum MatchesError { + /// Failed to downcast `AnyValue` to the specified type + #[non_exhaustive] + #[error("could not downcast to {expected:?}, need to downcast to {actual:?}")] + Downcast { + /// Type for value stored in [`ArgMatches`][crate::ArgMatches] + actual: AnyValueId, + /// The target type to downcast to + expected: AnyValueId, + }, + /// Argument not defined in [`Command`][crate::Command] + #[non_exhaustive] + #[error( + "unknown argument or group id. Make sure you are using the argument id and not the \ + short or long flags" + )] + UnknownArgument { + // Missing `id` but blocked on a public id type which will hopefully come with `unstable-v4` + }, + } + + impl MatchesError { + #[cfg_attr(debug_assertions, track_caller)] + pub(crate) fn unwrap(id: &str, r: Result) -> T { + let err = match r { + Ok(t) => { + return t; + } + Err(err) => err, + }; + panic!("Mismatch between definition and access of `{id}`. {err}",) + } + } + + #[derive(Default)] + pub struct ArgMatches { + #[cfg(debug_assertions)] + valid_args: Vec<&'static str>, + args: BTreeMap<&'static str, MatchedArg>, + } + + impl ArgMatches { + pub fn parse( + mut argv: VecDeque>, + flags: &BTreeMap<&'static str, &'static CompileFlag>, + ) -> Result { + let mut this = Self::default(); + + for flag in flags.values().copied() { + this.register_flag(flag); + } + + let mut trailing = false; + while let Some(arg) = argv.pop_front() { + if trailing { + this.args.get_mut("").unwrap().raw_vals.last_mut().unwrap().push(arg); + continue; + } + + let flag = match arg.strip_prefix("--") { + Some("") => { + // Start a new trailing argument group + trailing = true; + this.args.insert( + "", + MatchedArg { + source: None, + indices: vec![], + type_id: None, + vals: vec![], + raw_vals: vec![], + ignore_case: false, + }, + ); + continue; + } + Some(name) => flags.get(name).copied(), + None => match arg.strip_prefix("-") { + Some("") => { + return Err(Report::msg(format!( + "unexpected positional argument: '{arg}'" + ))); + } + Some(short) => { + let short = short.chars().next().unwrap(); + flags + .values() + .copied() + .find(|flag| flag.short.is_some_and(|c| c == short)) + } + None => { + return Err(Report::msg(format!( + "unexpected positional argument: '{arg}'" + ))) + } + }, + }; + + let flag = flag.ok_or_else(|| Report::msg(MatchesError::UnknownArgument {}))?; + let flag_matches = this + .args + .get_mut(flag.name) + .ok_or_else(|| Report::msg(MatchesError::UnknownArgument {}))?; + match flag.action { + FlagAction::Set => { + let value = argv + .pop_front() + .or(flag.default_missing_value.map(Cow::Borrowed)) + .or(flag.default_value.map(Cow::Borrowed)); + if let Some(value) = value { + flag_matches.raw_vals.push(vec![value]); + } else { + return Err(Report::msg(format!( + "missing required value for '--{}'", + flag.name + ))); + } + } + FlagAction::Count => { + let vals = flag_matches.vals.last_mut().unwrap(); + let count = vals.pop().unwrap().downcast_into::().unwrap(); + vals.push(AnyValue::new(count + 1)); + flag_matches.raw_vals.push(vec![Cow::Borrowed("")]); + } + FlagAction::Append => { + let value = argv + .pop_front() + .or(flag.default_missing_value.map(Cow::Borrowed)) + .or(flag.default_value.map(Cow::Borrowed)); + if let Some(value) = value { + flag_matches.raw_vals.push(vec![value]); + } else { + return Err(Report::msg(format!( + "missing required value for '--{}'", + flag.name + ))); + } + } + FlagAction::SetTrue | FlagAction::SetFalse => { + let vals = flag_matches.vals.last_mut().unwrap(); + vals.pop(); + vals.push(AnyValue::new(flag.action.as_boolean_value())); + flag_matches.raw_vals.push(vec![Cow::Borrowed("")]); + } + } + } + + Ok(this) + } + + pub fn iter(&self) -> impl Iterator>])> + '_ { + self.args.iter().map(|(k, matched)| (*k, matched.raw_vals.as_slice())) + } + + fn register_flag(&mut self, flag: &CompileFlag) { + assert!( + !self.args.contains_key(flag.name), + "command line flag {} is already registered", + flag.name + ); + + #[cfg(debug_assertions)] + { + self.valid_args.push(flag.name); + } + + let default_value = match flag.action { + FlagAction::Count => { + vec![AnyValue::new(0u8)] + } + FlagAction::SetTrue => { + let default = flag + .default_value + .or(flag.default_missing_value) + .map(|default_value| default_value == "true") + .unwrap_or_default(); + vec![AnyValue::new(default)] + } + FlagAction::SetFalse => { + vec![AnyValue::new( + flag.default_value + .or(flag.default_missing_value) + .map(|default_value| default_value == "true") + .unwrap_or(true), + )] + } + _ => vec![], + }; + self.args.insert( + flag.name, + MatchedArg { + source: None, + indices: vec![], + type_id: None, + vals: vec![default_value], + raw_vals: vec![], + ignore_case: false, + }, + ); + } + + fn append_value(&mut self, id: &'static str, val: AnyValue, raw: Cow<'static, str>) { + let arg = self.args.get_mut(id).unwrap(); + arg.vals.last_mut().unwrap().push(val); + arg.raw_vals.last_mut().unwrap().push(raw); + } + } + + #[derive(Default, Debug, Clone)] + struct MatchedArg { + source: Option, + indices: Vec, + type_id: Option, + vals: Vec>, + raw_vals: Vec>>, + ignore_case: bool, + } + + impl MatchedArg { + pub fn first(&self) -> Option<&AnyValue> { + self.vals.iter().flatten().next() + } + + pub fn type_id(&self) -> Option { + self.type_id + } + + pub fn infer_type_id(&self, expected: AnyValueId) -> AnyValueId { + self.type_id() + .or_else(|| { + self.vals + .iter() + .flatten() + .map(|v| v.type_id()) + .find(|actual| *actual != expected) + }) + .unwrap_or(expected) + } + } + + impl ArgMatches { + #[cfg_attr(debug_assertions, track_caller)] + pub fn get_one(&self, id: &str) -> Option<&T> { + MatchesError::unwrap(id, self.try_get_one(id)) + } + + #[cfg_attr(debug_assertions, track_caller)] + pub fn get_count(&self, id: &str) -> u8 { + *self.get_one::(id).unwrap_or_else(|| { + panic!("arg `{id}`'s `ArgAction` should be `Count` which should provide a default") + }) + } + + #[cfg_attr(debug_assertions, track_caller)] + pub fn get_flag(&self, id: &str) -> bool { + *self.get_one::(id).unwrap_or_else(|| { + panic!( + "arg `{id}`'s `ArgAction` should be one of `SetTrue`, `SetFalse` which should \ + provide a default" + ) + }) + } + + /// Non-panicking version of [`ArgMatches::get_one`] + pub fn try_get_one( + &self, + id: &str, + ) -> Result, MatchesError> { + let arg = self.try_get_arg_t::(id)?; + let value = match arg.and_then(|a| a.first()) { + Some(value) => value, + None => { + return Ok(None); + } + }; + Ok(value.downcast_ref::().map(Some).unwrap()) + } + + #[inline] + fn try_get_arg_t( + &self, + arg: &str, + ) -> Result, MatchesError> { + let arg = match self.try_get_arg(arg)? { + Some(arg) => arg, + None => { + return Ok(None); + } + }; + self.verify_arg_t::(arg)?; + Ok(Some(arg)) + } + + #[inline] + fn try_get_arg(&self, arg: &str) -> Result, MatchesError> { + self.verify_arg(arg)?; + Ok(self.args.get(arg)) + } + + fn verify_arg_t( + &self, + arg: &MatchedArg, + ) -> Result<(), MatchesError> { + let expected = AnyValueId::of::(); + let actual = arg.infer_type_id(expected); + if expected == actual { + Ok(()) + } else { + Err(MatchesError::Downcast { actual, expected }) + } + } + + #[inline] + fn verify_arg(&self, _arg: &str) -> Result<(), MatchesError> { + #[cfg(debug_assertions)] + { + if _arg.is_empty() || self.valid_args.contains(&_arg) { + } else { + log::debug!( + target: "driver", + "`{_arg:?}` is not an id of an argument or a group.\nMake sure you're using \ + the name of the argument itself and not the name of short or long flags." + ); + return Err(MatchesError::UnknownArgument {}); + } + } + Ok(()) + } + } + + #[derive(Clone)] + struct AnyValue { + inner: Arc, + id: AnyValueId, + } + + impl AnyValue { + fn new(inner: V) -> Self { + let id = AnyValueId::of::(); + let inner = Arc::new(inner); + Self { inner, id } + } + + pub(crate) fn downcast_ref( + &self, + ) -> Option<&T> { + self.inner.downcast_ref::() + } + + pub(crate) fn downcast_into( + self, + ) -> Result { + let id = self.id; + let value = Arc::downcast::(self.inner).map_err(|inner| Self { inner, id })?; + let value = Arc::try_unwrap(value).unwrap_or_else(|arc| (*arc).clone()); + Ok(value) + } + + pub(crate) fn type_id(&self) -> AnyValueId { + self.id + } + } + + impl core::fmt::Debug for AnyValue { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + f.debug_struct("AnyValue").field("inner", &self.id).finish() + } + } + + #[derive(Copy, Clone)] + pub struct AnyValueId { + type_id: core::any::TypeId, + #[cfg(debug_assertions)] + type_name: &'static str, + } + + impl AnyValueId { + pub(crate) fn of() -> Self { + Self { + type_id: core::any::TypeId::of::(), + #[cfg(debug_assertions)] + type_name: core::any::type_name::(), + } + } + } + + impl PartialEq for AnyValueId { + fn eq(&self, other: &Self) -> bool { + self.type_id == other.type_id + } + } + + impl Eq for AnyValueId {} + + impl PartialOrd for AnyValueId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + impl PartialEq for AnyValueId { + fn eq(&self, other: &core::any::TypeId) -> bool { + self.type_id == *other + } + } + + impl Ord for AnyValueId { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.type_id.cmp(&other.type_id) + } + } + + impl core::hash::Hash for AnyValueId { + fn hash(&self, state: &mut H) { + self.type_id.hash(state); + } + } + + impl core::fmt::Debug for AnyValueId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + #[cfg(not(debug_assertions))] + { + self.type_id.fmt(f) + } + #[cfg(debug_assertions)] + { + f.debug_struct(self.type_name).finish() + } + } + } + + impl<'a, A: ?Sized + 'static> From<&'a A> for AnyValueId { + fn from(_: &'a A) -> Self { + Self::of::() + } + } + + /// Origin of the argument's value + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[non_exhaustive] + pub enum ValueSource { + /// Value came [`Arg::default_value`][crate::Arg::default_value] + DefaultValue, + /// Value came [`Arg::env`][crate::Arg::env] + EnvVariable, + /// Value was passed in on the command-line + CommandLine, + } + + impl ValueSource { + pub(crate) fn is_explicit(self) -> bool { + self != Self::DefaultValue + } + } +} diff --git a/midenc-session/src/flags/mod.rs b/midenc-session/src/flags/mod.rs index a4bc902cd..aef81cfe9 100644 --- a/midenc-session/src/flags/mod.rs +++ b/midenc-session/src/flags/mod.rs @@ -1,40 +1,38 @@ +mod arg_matches; mod flag; +#[cfg(not(feature = "std"))] +use alloc::borrow::Cow; use alloc::vec::Vec; use core::fmt; -pub use self::flag::{CompileFlag, FlagAction}; -use crate::diagnostics::{IntoDiagnostic, Report}; +pub use self::{ + arg_matches::ArgMatches, + flag::{CompileFlag, FlagAction}, +}; +use crate::diagnostics::Report; -#[cfg(feature = "std")] -pub struct CompileFlags { - flags: Vec, - arg_matches: clap::ArgMatches, -} - -#[cfg(not(feature = "std"))] pub struct CompileFlags { flags: Vec, - args: alloc::collections::BTreeMap, + arg_matches: ArgMatches, } #[cfg(feature = "std")] impl Default for CompileFlags { fn default() -> Self { - Self::new(None::).unwrap() + Self::new(None::).unwrap() } } #[cfg(not(feature = "std"))] impl Default for CompileFlags { fn default() -> Self { - Self::new(None::) + Self::new(None::).unwrap() } } -#[cfg(feature = "std")] -impl From for CompileFlags { - fn from(arg_matches: clap::ArgMatches) -> Self { +impl From for CompileFlags { + fn from(arg_matches: ArgMatches) -> Self { let flags = inventory::iter::.into_iter().cloned().collect(); Self { flags, arg_matches } } @@ -48,6 +46,8 @@ impl CompileFlags { I: IntoIterator, V: Into + Clone, { + use crate::diagnostics::IntoDiagnostic; + let flags = inventory::iter::.into_iter().cloned().collect(); fake_compile_command() .try_get_matches_from(argv) @@ -60,96 +60,21 @@ impl CompileFlags { pub fn new(argv: I) -> Result where I: IntoIterator, - V: Into + Clone, + V: Into> + Clone, { use alloc::collections::{BTreeMap, VecDeque}; - let mut argv = argv.into_iter().map(|arg| arg.into()).collect::>(); + let argv = argv.into_iter().map(|arg| arg.into()).collect::>(); let flags = inventory::iter:: .into_iter() - .map(|flag| (flag.name.clone(), flag)) + .map(|flag| (flag.name, flag)) .collect::>(); + + let arg_matches = ArgMatches::parse(argv, &flags)?; let this = Self { - flags: flags.values().cloned().collect(), - arg_matches: Default::default(), + flags: flags.values().copied().cloned().collect(), + arg_matches, }; - let mut this = flags.values().fold(this, |this, flag| { - if let Some(default_value) = flag.default_value { - this.arg_matches.insert(flag.name, Some(vec![default_value])); - } else { - this.arg_matches.insert(flag.name, None); - } - }); - - while let Some(arg) = argv.pop_front() { - let Some(name) = arg.strip_prefix("--").or_else(|| { - arg.strip_prefix("-").and_then(|name| { - flags.values().find_map(|flag| { - if flag.short == name { - Some(flag.name.clone()) - } else { - None - } - }) - }) - }) else { - return Err(Report::msg(format!("unexpected positional argument: '{arg}'"))); - }; - if let Some(flag) = flags.get(&name) { - match flag.action { - FlagAction::Set => { - let value = argv - .pop_front() - .or_else(flag.default_missing_value.clone()) - .or_else(flag.default_value.clone()); - if let Some(value) = value { - this.arg_matches.insert(name.to_string(), Some(vec![value])) - } else { - return Err(Report::msg(format!( - "missing required value for '--{name}'" - ))); - } - } - FlagAction::Count => { - match this.arg_matches.entry(name.to_string()).or_insert_default() { - Some(ref mut values) => { - values.push("".to_string()); - } - ref mut entry => { - *entry = Some(vec!["".to_string()]); - } - } - } - FlagAction::Append => { - let value = argv - .pop_front() - .or_else(flag.default_missing_value.clone()) - .or_else(flag.default_value.clone()); - if let Some(value) = value { - match this.arg_matches.entry(name.to_string()).or_insert_default() { - Some(ref mut values) => { - values.push(value); - } - ref mut entry => { - *entry = Some(vec![value]); - } - } - } else { - return Err(Report::msg(format!( - "missing required value for '--{name}'" - ))); - } - } - FlagAction::SetTrue | FlagAction::SetFalse => { - this.arg_matches.insert( - name.to_string(), - Some(vec![flag.action.as_boolean_value().to_string()]), - ); - continue; - } - } - } - } Ok(this) } @@ -159,127 +84,17 @@ impl CompileFlags { } /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse` - #[cfg(feature = "std")] pub fn get_flag(&self, name: &str) -> bool { self.arg_matches.get_flag(name) } - /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse` - #[cfg(not(feature = "std"))] - pub fn get_flag(&self, name: &str) -> bool { - let flag = - self.flags.iter().find(|flag| flag.name == name).unwrap_or_else(|| { - panic!("invalid flag '--{name}', did you forget to register it?") - }); - self.arg_matches - .get(name) - .and_then(|maybe_values| match maybe_values { - Some(values) => Some(values[0].as_str() == "true"), - None => match flag.default_missing_value.as_deref() { - None => None, - Some("true") => Some(true), - Some("false") => Some(false), - Some(other) => unreachable!( - "should not be possible to set '{other}' for boolean flag '--{name}'" - ), - }, - }) - .unwrap_or_else(|| match flag.default_value.as_deref() { - None => { - if flag.action == FlagAction::SetTrue { - false - } else { - true - } - } - Some("true") => Some(true), - Some("false") => Some(false), - Some(other) => { - panic!("invalid default_value for boolean flag '--{name}': '{other}'") - } - }) - } - /// Get the count of a specific custom flag with action `FlagAction::Count` - #[cfg(feature = "std")] pub fn get_flag_count(&self, name: &str) -> usize { self.arg_matches.get_count(name) as usize } - /// Get the count of a specific custom flag with action `FlagAction::Count` - #[cfg(not(feature = "std"))] - pub fn get_flag_count(&self, name: &str) -> usize { - let flag = - self.flags.iter().find(|flag| flag.name == name).unwrap_or_else(|| { - panic!("invalid flag '--{name}', did you forget to register it?") - }); - self.arg_matches - .get(name) - .map(|maybe_values| match maybe_values { - Some(values) => values.len(), - None => match flag.default_missing_value.as_deref() { - None => 0, - Some(n) => n - .parse::() - .expect("invalid default_missing_value for '--{name}': '{n}'"), - }, - }) - .unwrap_or_else(|| match flag.default_value.as_deref() { - None => 0, - Some(n) => n.parse::().expect("invalid default_value for '--{name}': '{n}'"), - }) - } - - /// Get the value of a specific custom flag - #[cfg(feature = "std")] - pub fn get_flag_value(&self, name: &str) -> Option<&T> - where - T: core::any::Any + Clone + Send + Sync + 'static, - { - self.arg_matches.get_one(name) - } - - /// Get the value of a specific custom flag - #[cfg(not(feature = "std"))] - pub fn get_flag_value(&self, name: &str) -> Option<&T> - where - T: core::any::Any + FromStr + Clone + Send + Sync + 'static, - { - let flag = - self.flags.iter().find(|flag| flag.name == name).unwrap_or_else(|| { - panic!("invalid flag '--{name}', did you forget to register it?") - }); - match self.arg_matches.get(name)? { - Some(values) => values - .last() - .as_deref()? - .parse::() - .unwrap_or_else(|err| panic!("failed to parse value for --{name}: {err}")), - None => match flag.default_missing_value.as_deref() { - None => flag - .default_value - .as_deref()? - .parse::() - .unwrap_or_else(|err| panic!("invalid default_value for --{name}: {err}")), - Some(value) => value.parse::().unwrap_or_else(|err| { - panic!("invalid default_missing_value for --{name}: {err}") - }), - }, - } - } - - /// Iterate over values of a specific custom flag - #[cfg(feature = "std")] - pub fn get_flag_values(&self, name: &str) -> Option> - where - T: core::any::Any + Clone + Send + Sync + 'static, - { - self.arg_matches.get_many(name) - } - - /// Get the remaining [clap::ArgMatches] left after parsing the base session configuration - #[cfg(feature = "std")] - pub fn matches(&self) -> &clap::ArgMatches { + /// Get the remaining [ArgMatches] left after parsing the base session configuration + pub fn matches(&self) -> &ArgMatches { &self.arg_matches } } @@ -315,16 +130,9 @@ impl fmt::Debug for CompileFlags { #[cfg(not(feature = "std"))] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut map = f.debug_map(); - for (name, value) in self.arg_matches.iter() { - let flag = self.flags.iter().find(|flag| flag.name == name).unwrap(); - match value { - Some(values) => { - map.key(&name).value_with(|f| f.debug_list().entries(values.iter()).finish()); - } - None => { - map.key(&name).value(&None::<&str>); - } - } + for (name, raw_values) in self.arg_matches.iter() { + map.key(&name) + .value_with(|f| f.debug_list().entries(raw_values.iter()).finish()); } map.finish() } diff --git a/midenc-session/src/inputs.rs b/midenc-session/src/inputs.rs index a0578ee03..29dbe6eab 100644 --- a/midenc-session/src/inputs.rs +++ b/midenc-session/src/inputs.rs @@ -1,9 +1,9 @@ -use alloc::{borrow::Cow, format, string::String, vec, vec::Vec}; +#[cfg(feature = "std")] +use alloc::format; +use alloc::{borrow::Cow, string::String, vec, vec::Vec}; use core::fmt; -use std::{ - ffi::OsStr, - path::{Path, PathBuf}, -}; + +use crate::{Path, PathBuf}; #[derive(Clone)] pub struct FileName { @@ -37,15 +37,13 @@ impl fmt::Display for FileName { write!(f, "{}", self.as_str()) } } -#[cfg(feature = "std")] -impl AsRef for FileName { - fn as_ref(&self) -> &std::path::Path { - std::path::Path::new(self.name.as_ref()) +impl AsRef for FileName { + fn as_ref(&self) -> &Path { + self.name.as_ref().as_ref() } } -#[cfg(feature = "std")] -impl From for FileName { - fn from(path: std::path::PathBuf) -> Self { +impl From for FileName { + fn from(path: PathBuf) -> Self { Self { name: path.to_string_lossy().into_owned().into(), is_path: true, @@ -78,8 +76,7 @@ impl FileName { self.is_path } - #[cfg(feature = "std")] - pub fn as_path(&self) -> &std::path::Path { + pub fn as_path(&self) -> &Path { self.as_ref() } @@ -87,17 +84,12 @@ impl FileName { self.name.as_ref() } - #[cfg(feature = "std")] pub fn file_name(&self) -> Option<&str> { self.as_path().file_name().and_then(|name| name.to_str()) } - #[cfg(not(feature = "std"))] - pub fn file_name(&self) -> Option<&str> { - match self.name.rsplit_once('/') { - Some((_, name)) => Some(name), - None => Some(self.name.as_ref()), - } + pub fn file_stem(&self) -> Option<&str> { + self.as_path().file_stem().and_then(|name| name.to_str()) } } @@ -106,11 +98,12 @@ impl FileName { pub enum InvalidInputError { /// Occurs if an unsupported file type is given as an input #[error("invalid input file '{}': unsupported file type", .0.display())] - UnsupportedFileType(std::path::PathBuf), + UnsupportedFileType(PathBuf), /// We attempted to detecth the file type from the raw bytes, but failed #[error("could not detect file type of input")] UnrecognizedFileType, /// Unable to read input file + #[cfg(feature = "std")] #[error(transparent)] Io(#[from] std::io::Error), } @@ -161,6 +154,7 @@ impl InputFile { /// Get an [InputFile] representing the contents received from standard input. /// /// This function returns an error if the contents are not a valid supported file type. + #[cfg(feature = "std")] pub fn from_stdin(name: FileName) -> Result { use std::io::Read; @@ -206,6 +200,8 @@ impl InputFile { } } } + +#[cfg(feature = "std")] impl clap::builder::ValueParserFactory for InputFile { type Parser = InputFileParser; @@ -216,7 +212,10 @@ impl clap::builder::ValueParserFactory for InputFile { #[doc(hidden)] #[derive(Clone)] +#[cfg(feature = "std")] pub struct InputFileParser; + +#[cfg(feature = "std")] impl clap::builder::TypedValueParser for InputFileParser { type Value = InputFile; @@ -224,7 +223,7 @@ impl clap::builder::TypedValueParser for InputFileParser { &self, _cmd: &clap::Command, _arg: Option<&clap::Arg>, - value: &OsStr, + value: &std::ffi::OsStr, ) -> Result { use clap::error::{Error, ErrorKind}; diff --git a/midenc-session/src/lib.rs b/midenc-session/src/lib.rs index 6adf6854f..1fb1742cc 100644 --- a/midenc-session/src/lib.rs +++ b/midenc-session/src/lib.rs @@ -1,14 +1,22 @@ -#![feature(debug_closure_helpers)] #![no_std] +#![feature(debug_closure_helpers)] +#![feature(specialization)] +#![feature(slice_split_once)] +// Specialization +#![allow(incomplete_features)] +#![deny(warnings)] extern crate alloc; #[cfg(feature = "std")] extern crate std; + use alloc::{ borrow::ToOwned, + format, string::{String, ToString}, vec::Vec, }; +use core::str::FromStr; mod color; pub mod diagnostics; @@ -21,11 +29,11 @@ mod inputs; mod libs; mod options; mod outputs; +mod path; #[cfg(feature = "std")] mod statistics; use alloc::{fmt, sync::Arc}; -use std::path::{Path, PathBuf}; /// The version associated with the current compiler toolchain pub const MIDENC_BUILD_VERSION: &str = env!("MIDENC_BUILD_VERSION"); @@ -33,24 +41,28 @@ pub const MIDENC_BUILD_VERSION: &str = env!("MIDENC_BUILD_VERSION"); /// The git revision associated with the current compiler toolchain pub const MIDENC_BUILD_REV: &str = env!("MIDENC_BUILD_REV"); -use clap::ValueEnum; +pub use miden_assembly; use midenc_hir_symbol::Symbol; pub use self::{ color::ColorChoice, diagnostics::{DiagnosticsHandler, Emitter, SourceManager}, - duration::HumanDuration, - emit::Emit, - flags::{CompileFlag, CompileFlags, FlagAction}, - inputs::{FileType, InputFile, InputType, InvalidInputError}, - libs::{LibraryKind, LinkLibrary}, + emit::{Emit, Writer}, + flags::{ArgMatches, CompileFlag, CompileFlags, FlagAction}, + inputs::{FileName, FileType, InputFile, InputType, InvalidInputError}, + libs::{ + add_target_link_libraries, LibraryKind, LibraryNamespace, LibraryPath, + LibraryPathComponent, LinkLibrary, STDLIB, + }, options::*, outputs::{OutputFile, OutputFiles, OutputMode, OutputType, OutputTypeSpec, OutputTypes}, - statistics::Statistics, + path::{Path, PathBuf}, }; +#[cfg(feature = "std")] +pub use self::{duration::HumanDuration, emit::EmitExt, statistics::Statistics}; /// The type of project being compiled -#[derive(Debug, Copy, Clone, Default)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] pub enum ProjectType { /// Compile a Miden program that can be run on the Miden VM #[default] @@ -63,7 +75,7 @@ impl ProjectType { match target { // We default to compiling a program unless we find later // that we do not have an entrypoint. - TargetEnv::Base | TargetEnv::Rollup => Self::Program, + TargetEnv::Base | TargetEnv::Rollup { .. } => Self::Program, // The emulator can run either programs or individual library functions, // so we compile as a library and delegate the choice of how to run it // to the emulator @@ -80,7 +92,7 @@ pub struct Session { /// Configuration for the current compiler session pub options: Options, /// The current source manager - pub source_manager: Arc, + pub source_manager: Arc, /// The current diagnostics handler pub diagnostics: Arc, /// The inputs being compiled @@ -112,7 +124,7 @@ impl Session { target_dir: PathBuf, options: Options, emitter: Option>, - source_manager: Arc, + source_manager: Arc, ) -> Self where I: IntoIterator, @@ -129,14 +141,15 @@ impl Session { target_dir: PathBuf, options: Options, emitter: Option>, - source_manager: Arc, + source_manager: Arc, ) -> Self { - log::debug!("creating session for {} inputs:", inputs.len()); - if log::log_enabled!(log::Level::Debug) { + log::debug!(target: "driver", "creating session for {} inputs:", inputs.len()); + if log::log_enabled!(target: "driver", log::Level::Debug) { for input in inputs.iter() { - log::debug!(" - {} ({})", input.file_name(), input.file_type()); + log::debug!(target: "driver", " - {} ({})", input.file_name(), input.file_type()); } log::debug!( + target: "driver", " | outputs_dir = {}", output_dir .as_ref() @@ -144,10 +157,11 @@ impl Session { .unwrap_or("".to_string()) ); log::debug!( + target: "driver", " | output_file = {}", output_file.as_ref().map(|of| of.to_string()).unwrap_or("".to_string()) ); - log::debug!(" | target_dir = {}", target_dir.display()); + log::debug!(target: "driver", " | target_dir = {}", target_dir.display()); } let diagnostics = Arc::new(DiagnosticsHandler::new( options.diagnostics, @@ -160,49 +174,71 @@ impl Session { .or_else(|| output_file.as_ref().and_then(|of| of.parent())) .map(|path| path.to_path_buf()); + if let Some(output_dir) = output_dir.as_deref() { + log::debug!(target: "driver", " | output dir = {}", output_dir.display()); + } else { + log::debug!(target: "driver", " | output dir = "); + } + + log::debug!(target: "driver", " | target = {}", &options.target); + log::debug!(target: "driver", " | type = {:?}", &options.project_type); + if log::log_enabled!(target: "driver", log::Level::Debug) { + for lib in options.link_libraries.iter() { + if let Some(path) = lib.path.as_deref() { + log::debug!(target: "driver", " | linking {} library '{}' from {}", &lib.kind, &lib.name, path.display()); + } else { + log::debug!(target: "driver", " | linking {} library '{}'", &lib.kind, &lib.name); + } + } + } + let name = options .name .clone() .or_else(|| { - output_file - .as_ref() - .and_then(|of| of.filestem().map(|stem| stem.to_string_lossy().into_owned())) + log::debug!(target: "driver", "no name specified, attempting to derive from output file"); + output_file.as_ref().and_then(|of| of.filestem().map(|stem| stem.to_string())) }) - .unwrap_or_else(|| match inputs.first() { - Some(InputFile { - file: InputType::Real(ref path), - .. - }) => path - .file_stem() - .and_then(|stem| stem.to_str()) - .or_else(|| path.extension().and_then(|stem| stem.to_str())) - .unwrap_or_else(|| { - panic!( - "invalid input path: '{}' has no file stem or extension", - path.display() - ) - }) - .to_string(), - Some( - input @ InputFile { - file: InputType::Stdin { ref name, .. }, + .unwrap_or_else(|| { + log::debug!(target: "driver", "unable to derive name from output file, deriving from input"); + match inputs.first() { + Some(InputFile { + file: InputType::Real(ref path), .. - }, - ) => { - let name = name.as_str(); - if matches!(name, "empty" | "stdin") { - options - .current_dir - .file_stem() - .and_then(|stem| stem.to_str()) - .unwrap_or(name) - .to_string() - } else { - input.filestem().to_owned() + }) => path + .file_stem() + .and_then(|stem| stem.to_str()) + .or_else(|| path.extension().and_then(|stem| stem.to_str())) + .unwrap_or_else(|| { + panic!( + "invalid input path: '{}' has no file stem or extension", + path.display() + ) + }) + .to_string(), + Some( + input @ InputFile { + file: InputType::Stdin { ref name, .. }, + .. + }, + ) => { + let name = name.as_str(); + if matches!(name, "empty" | "stdin") { + log::debug!(target: "driver", "no good input file name to use, using current directory base name"); + options + .current_dir + .file_stem() + .and_then(|stem| stem.to_str()) + .unwrap_or(name) + .to_string() + } else { + input.filestem().to_owned() + } } + None => "out".to_owned(), } - None => "out".to_owned(), }); + log::debug!(target: "driver", "artifact name set to '{name}'"); let output_files = OutputFiles::new( name.clone(), @@ -220,6 +256,7 @@ impl Session { diagnostics, inputs, output_files, + #[cfg(feature = "std")] statistics: Default::default(), } } @@ -254,29 +291,9 @@ impl Session { self.options.flags.get_flag_count(name) } - /// Get the value of a specific custom flag + /// Get the remaining [ArgMatches] left after parsing the base session configuration #[inline] - pub fn get_flag_value(&self, name: &str) -> Option<&T> - where - T: core::any::Any + Clone + Send + Sync + 'static, - { - self.options.flags.get_flag_value(name) - } - - /// Iterate over values of a specific custom flag - #[inline] - #[cfg(feature = "std")] - pub fn get_flag_values(&self, name: &str) -> Option> - where - T: core::any::Any + Clone + Send + Sync + 'static, - { - self.options.flags.get_flag_values(name) - } - - /// Get the remaining [clap::ArgMatches] left after parsing the base session configuration - #[inline] - #[cfg(feature = "std")] - pub fn matches(&self) -> &clap::ArgMatches { + pub fn matches(&self) -> &ArgMatches { self.options.flags.matches() } @@ -296,6 +313,16 @@ impl Session { out_file } + #[cfg(not(feature = "std"))] + fn check_file_is_writeable(&self, file: &Path) { + panic!( + "Compiler exited with a fatal error: cannot write '{}' - compiler was built without \ + standard library", + file.display() + ); + } + + #[cfg(feature = "std")] fn check_file_is_writeable(&self, file: &Path) { if let Ok(m) = file.metadata() { if m.permissions().readonly() { @@ -356,7 +383,8 @@ impl Session { } /// Print the given emittable IR to stdout, as produced by a pass with name `pass` - pub fn print(&self, ir: impl Emit, pass: &str) -> std::io::Result<()> { + #[cfg(feature = "std")] + pub fn print(&self, ir: impl Emit, pass: &str) -> anyhow::Result<()> { if self.should_print_ir(pass) { ir.write_to_stdout(self)?; } @@ -376,7 +404,8 @@ impl Session { } /// Emit an item to stdout/file system depending on the current configuration - pub fn emit(&self, mode: OutputMode, item: &E) -> std::io::Result<()> { + #[cfg(feature = "std")] + pub fn emit(&self, mode: OutputMode, item: &E) -> anyhow::Result<()> { let output_type = item.output_type(mode); if self.should_emit(output_type) { let name = item.name().map(|n| n.as_str()); @@ -393,11 +422,15 @@ impl Session { Ok(()) } + + #[cfg(not(feature = "std"))] + pub fn emit(&self, _mode: OutputMode, _item: &E) -> anyhow::Result<()> { + Ok(()) + } } /// This enum describes the different target environments targetable by the compiler -#[derive(Debug, Copy, Clone, Default)] -#[cfg_attr(feature = "std", derive(ValueEnum))] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] pub enum TargetEnv { /// The emulator environment, which has a more restrictive instruction set Emu, @@ -405,14 +438,65 @@ pub enum TargetEnv { #[default] Base, /// The Miden Rollup environment, using the Rollup kernel - Rollup, + Rollup { target: RollupTarget }, } impl fmt::Display for TargetEnv { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::Emu => f.write_str("emu"), Self::Base => f.write_str("base"), - Self::Rollup => f.write_str("rollup"), + Self::Rollup { target } => f.write_str(&format!("rollup:{target}")), + } + } +} + +impl FromStr for TargetEnv { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "emu" => Ok(Self::Emu), + "base" => Ok(Self::Base), + "rollup" => Ok(Self::Rollup { + target: RollupTarget::default(), + }), + "rollup:account" => Ok(Self::Rollup { + target: RollupTarget::Account, + }), + "rollup:note-script" => Ok(Self::Rollup { + target: RollupTarget::NoteScript, + }), + "rollup:transaction-script" => Ok(Self::Rollup { + target: RollupTarget::TransactionScript, + }), + "rollup:authentication-component" => Ok(Self::Rollup { + target: RollupTarget::AuthComponent, + }), + _ => Err(anyhow::anyhow!("invalid target environment: {}", s)), + } + } +} + +/// This enum describes the different rollup targets +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] +pub enum RollupTarget { + #[default] + Account, + NoteScript, + TransactionScript, + /// Authentication `AccountComponent` that has exactly one procedure named `auth__*` that + /// accepts a `Word` (authentication arguments) and throws an error in case of a failed + /// authentication + AuthComponent, +} + +impl fmt::Display for RollupTarget { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Account => f.write_str("account"), + Self::NoteScript => f.write_str("note-script"), + Self::TransactionScript => f.write_str("transaction-script"), + Self::AuthComponent => f.write_str("authentication-component"), } } } diff --git a/midenc-session/src/libs.rs b/midenc-session/src/libs.rs index e55847fb7..fde87fbd1 100644 --- a/midenc-session/src/libs.rs +++ b/midenc-session/src/libs.rs @@ -1,42 +1,45 @@ -use alloc::{borrow::Cow, boxed::Box, format, str::FromStr, string::ToString}; +#![deny(warnings)] + +use alloc::{borrow::Cow, format, str::FromStr, sync::Arc, vec::Vec}; +#[cfg(feature = "std")] +use alloc::{boxed::Box, string::ToString}; use core::fmt; -use std::{ - ffi::OsStr, - path::{Path, PathBuf}, - sync::LazyLock, -}; -use miden_assembly::{Library as CompiledLibrary, LibraryNamespace}; -use miden_base_sys::masl::tx::MidenTxKernelLibrary; +pub use miden_assembly_syntax::{ + Library as CompiledLibrary, LibraryNamespace, LibraryPath, LibraryPathComponent, +}; +#[cfg(feature = "std")] +use miden_core::utils::Deserializable; use miden_stdlib::StdLibrary; +use midenc_hir_symbol::sync::LazyLock; +use crate::{diagnostics::Report, PathBuf, Session, TargetEnv}; +#[cfg(feature = "std")] use crate::{ - diagnostics::{IntoDiagnostic, Report, WrapErr}, - Session, + diagnostics::{IntoDiagnostic, WrapErr}, + Path, }; -static STDLIB: LazyLock = LazyLock::new(StdLibrary::default); -static BASE: LazyLock = LazyLock::new(MidenTxKernelLibrary::default); +pub static STDLIB: LazyLock> = + LazyLock::new(|| Arc::new(StdLibrary::default().into())); /// The types of libraries that can be linked against during compilation #[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -#[cfg_attr( - feature = "serde", - derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) -)] -#[repr(u8)] pub enum LibraryKind { /// A compiled MAST library #[default] Mast, /// A source-form MASM library, using the standard project layout Masm, + // A Miden package (MASP) + Masp, } impl fmt::Display for LibraryKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Mast => f.write_str("mast"), Self::Masm => f.write_str("masm"), + Self::Masp => f.write_str("masp"), } } } @@ -47,6 +50,7 @@ impl FromStr for LibraryKind { match s { "mast" | "masl" => Ok(Self::Mast), "masm" => Ok(Self::Masm), + "masp" => Ok(Self::Masp), _ => Err(()), } } @@ -54,7 +58,6 @@ impl FromStr for LibraryKind { /// A library requested by the user to be linked against during compilation #[derive(Debug, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct LinkLibrary { /// The name of the library. /// @@ -64,7 +67,6 @@ pub struct LinkLibrary { /// will be the basename of the file specified in the path. pub name: Cow<'static, str>, /// If specified, the path from which this library should be loaded - #[cfg_attr(feature = "serde", serde(default))] pub path: Option, /// The kind of library to load. /// @@ -73,6 +75,38 @@ pub struct LinkLibrary { pub kind: LibraryKind, } impl LinkLibrary { + /// Construct a LinkLibrary for Miden stdlib + pub fn std() -> Self { + LinkLibrary { + name: "std".into(), + path: None, + kind: LibraryKind::Mast, + } + } + + /// Construct a LinkLibrary for Miden base(rollup/tx kernel) library + pub fn base() -> Self { + LinkLibrary { + name: "base".into(), + path: None, + kind: LibraryKind::Mast, + } + } + + #[cfg(not(feature = "std"))] + pub fn load(&self, _session: &Session) -> Result { + // Handle libraries shipped with the compiler, or via Miden crates + match self.name.as_ref() { + "std" => Ok((*STDLIB).as_ref().clone()), + "base" => Ok(miden_lib::MidenLib::default().as_ref().clone()), + name => Err(Report::msg(format!( + "link library '{name}' cannot be loaded: compiler was built without standard \ + library" + ))), + } + } + + #[cfg(feature = "std")] pub fn load(&self, session: &Session) -> Result { if let Some(path) = self.path.as_deref() { return self.load_from_path(path, session); @@ -81,7 +115,7 @@ impl LinkLibrary { // Handle libraries shipped with the compiler, or via Miden crates match self.name.as_ref() { "std" => return Ok((*STDLIB).as_ref().clone()), - "base" => return Ok((*BASE).as_ref().clone()), + "base" => return Ok(miden_lib::MidenLib::default().as_ref().clone()), _ => (), } @@ -91,15 +125,17 @@ impl LinkLibrary { self.load_from_path(&path, session) } + #[cfg(feature = "std")] fn load_from_path(&self, path: &Path, session: &Session) -> Result { match self.kind { LibraryKind::Masm => { let ns = LibraryNamespace::new(&self.name) .into_diagnostic() .wrap_err_with(|| format!("invalid library namespace '{}'", &self.name))?; - let assembler = miden_assembly::Assembler::new(session.source_manager.clone()) - .with_debug_mode(true); - CompiledLibrary::from_dir(path, ns, assembler) + + miden_assembly::Assembler::new(session.source_manager.clone()) + .with_debug_mode(true) + .assemble_library_from_dir(path, ns) } LibraryKind::Mast => CompiledLibrary::deserialize_from_file(path).map_err(|err| { Report::msg(format!( @@ -107,9 +143,30 @@ impl LinkLibrary { path.display() )) }), + LibraryKind::Masp => { + let bytes = std::fs::read(path).into_diagnostic()?; + let package = + miden_mast_package::Package::read_from_bytes(&bytes).map_err(|e| { + Report::msg(format!( + "failed to load Miden package from {}: {e}", + path.display() + )) + })?; + let lib = match package.mast { + miden_mast_package::MastArtifact::Executable(_) => { + return Err(Report::msg(format!( + "Expected Miden package to contain a Library, got Program: '{}'", + path.display() + ))) + } + miden_mast_package::MastArtifact::Library(lib) => lib.clone(), + }; + Ok((*lib).clone()) + } } } + #[cfg(feature = "std")] fn find(&self, session: &Session) -> Result { use std::fs; @@ -149,6 +206,14 @@ impl LinkLibrary { ))); } } + LibraryKind::Masp => { + if !path.is_file() { + return Err(Report::msg(format!( + "unable to load Miden Assembly package from '{}': not a file", + path.display() + ))); + } + } } return Ok(path); } @@ -161,6 +226,7 @@ impl LinkLibrary { } } +#[cfg(feature = "std")] impl clap::builder::ValueParserFactory for LinkLibrary { type Parser = LinkLibraryParser; @@ -169,9 +235,12 @@ impl clap::builder::ValueParserFactory for LinkLibrary { } } +#[cfg(feature = "std")] #[doc(hidden)] #[derive(Clone)] pub struct LinkLibraryParser; + +#[cfg(feature = "std")] impl clap::builder::TypedValueParser for LinkLibraryParser { type Value = LinkLibrary; @@ -199,7 +268,7 @@ impl clap::builder::TypedValueParser for LinkLibraryParser { &self, _cmd: &clap::Command, _arg: Option<&clap::Arg>, - value: &OsStr, + value: &std::ffi::OsStr, ) -> Result { use clap::error::{Error, ErrorKind}; @@ -290,3 +359,29 @@ impl clap::builder::TypedValueParser for LinkLibraryParser { } } } + +/// Add libraries required by the target environment to the list of libraries to link against only +/// if they are not already present. +pub fn add_target_link_libraries( + link_libraries_in: Vec, + target: &TargetEnv, +) -> Vec { + let mut link_libraries_out = link_libraries_in; + match target { + TargetEnv::Base | TargetEnv::Emu => { + if !link_libraries_out.iter().any(|ll| ll.name == "std") { + link_libraries_out.push(LinkLibrary::std()); + } + } + TargetEnv::Rollup { .. } => { + if !link_libraries_out.iter().any(|ll| ll.name == "std") { + link_libraries_out.push(LinkLibrary::std()); + } + + if !link_libraries_out.iter().any(|ll| ll.name == "base") { + link_libraries_out.push(LinkLibrary::base()); + } + } + } + link_libraries_out +} diff --git a/midenc-session/src/options/mod.rs b/midenc-session/src/options/mod.rs index 11dbdf2f2..359e21ee7 100644 --- a/midenc-session/src/options/mod.rs +++ b/midenc-session/src/options/mod.rs @@ -1,9 +1,10 @@ use alloc::{fmt, str::FromStr, string::String, sync::Arc, vec, vec::Vec}; -use std::path::{Path, PathBuf}; +#[cfg(feature = "std")] +use crate::Path; use crate::{ diagnostics::{DiagnosticsConfig, Emitter}, - ColorChoice, CompileFlags, LinkLibrary, OutputTypes, ProjectType, TargetEnv, + ColorChoice, CompileFlags, LinkLibrary, OutputTypes, PathBuf, ProjectType, TargetEnv, }; /// This struct contains all of the configuration options for the compiler @@ -51,6 +52,8 @@ pub struct Options { pub print_ir_after_all: bool, /// Print IR to stdout each time the named passes are applied pub print_ir_after_pass: Vec, + /// Only print the IR if the pass modified the IR structure. + pub print_ir_after_modified: bool, /// Save intermediate artifacts in memory during compilation pub save_temps: bool, /// We store any leftover argument matches in the session options for use @@ -60,13 +63,38 @@ pub struct Options { impl Default for Options { fn default() -> Self { - let current_dir = std::env::current_dir().expect("could not get working directory"); + let current_dir = current_dir(); let target = TargetEnv::default(); let project_type = ProjectType::default_for_target(target); Self::new(None, target, project_type, current_dir, None) } } +#[cfg(feature = "std")] +fn current_dir() -> PathBuf { + std::env::current_dir().expect("could not get working directory") +} + +#[cfg(not(feature = "std"))] +fn current_dir() -> PathBuf { + PathBuf::from(".") +} + +#[cfg(feature = "std")] +fn current_sysroot() -> Option { + std::env::var("HOME").ok().map(|home| { + Path::new(&home) + .join(".miden") + .join("toolchains") + .join(crate::MIDENC_BUILD_VERSION) + }) +} + +#[cfg(not(feature = "std"))] +fn current_sysroot() -> Option { + None +} + impl Options { pub fn new( name: Option, @@ -75,14 +103,7 @@ impl Options { current_dir: PathBuf, sysroot: Option, ) -> Self { - let sysroot = sysroot.or_else(|| { - std::env::var("HOME").ok().map(|home| { - Path::new(&home) - .join(".miden") - .join("toolchains") - .join(crate::MIDENC_BUILD_VERSION) - }) - }); + let sysroot = sysroot.or_else(current_sysroot); Self { name, @@ -107,6 +128,7 @@ impl Options { print_cfg_after_pass: vec![], print_ir_after_all: false, print_ir_after_pass: vec![], + print_ir_after_modified: false, flags: CompileFlags::default(), } } diff --git a/midenc-session/src/outputs.rs b/midenc-session/src/outputs.rs index b76e6e578..ed75cdf1a 100644 --- a/midenc-session/src/outputs.rs +++ b/midenc-session/src/outputs.rs @@ -1,11 +1,13 @@ use alloc::{ - borrow::ToOwned, boxed::Box, collections::BTreeMap, fmt, format, str::FromStr, string::String, -}; -use std::{ - ffi::OsStr, - path::{Path, PathBuf}, + borrow::{Cow, ToOwned}, + collections::BTreeMap, + fmt, format, + str::FromStr, + string::String, }; +use crate::{Path, PathBuf}; + /// The type of output to produce for a given [OutputType], when multiple options are available #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum OutputMode { @@ -114,9 +116,9 @@ impl OutputFile { } } - pub fn filestem(&self) -> Option<&OsStr> { + pub fn filestem(&self) -> Option> { match self { - Self::Real(ref path) => path.file_stem(), + Self::Real(ref path) => path.file_stem().map(|stem| stem.to_string_lossy()), Self::Stdout => None, } } @@ -301,6 +303,7 @@ impl OutputFiles { #[derive(Debug, Clone, Default)] pub struct OutputTypes(BTreeMap>); impl OutputTypes { + #[cfg(feature = "std")] pub fn new>(entries: I) -> Result { let entries = entries.into_iter(); let mut map = BTreeMap::default(); @@ -362,18 +365,16 @@ impl OutputTypes { self.0.contains_key(key) } - pub fn iter(&self) -> std::collections::btree_map::Iter<'_, OutputType, Option> { + pub fn iter(&self) -> impl Iterator)> + '_ { self.0.iter() } - pub fn keys(&self) -> std::collections::btree_map::Keys<'_, OutputType, Option> { - self.0.keys() + pub fn keys(&self) -> impl Iterator + '_ { + self.0.keys().copied() } - pub fn values( - &self, - ) -> std::collections::btree_map::Values<'_, OutputType, Option> { - self.0.values() + pub fn values(&self) -> impl Iterator> { + self.0.values().map(|v| v.as_ref()) } #[inline(always)] @@ -422,6 +423,8 @@ pub enum OutputTypeSpec { path: Option, }, } + +#[cfg(feature = "std")] impl clap::builder::ValueParserFactory for OutputTypeSpec { type Parser = OutputTypeParser; @@ -432,13 +435,18 @@ impl clap::builder::ValueParserFactory for OutputTypeSpec { #[doc(hidden)] #[derive(Clone)] +#[cfg(feature = "std")] pub struct OutputTypeParser; + +#[cfg(feature = "std")] impl clap::builder::TypedValueParser for OutputTypeParser { type Value = OutputTypeSpec; fn possible_values( &self, - ) -> Option + '_>> { + ) -> Option + '_>> { + use alloc::boxed::Box; + use clap::builder::PossibleValue; Some(Box::new( [ @@ -458,7 +466,7 @@ impl clap::builder::TypedValueParser for OutputTypeParser { &self, _cmd: &clap::Command, _arg: Option<&clap::Arg>, - value: &OsStr, + value: &std::ffi::OsStr, ) -> Result { use clap::error::{Error, ErrorKind}; @@ -485,12 +493,18 @@ impl clap::builder::TypedValueParser for OutputTypeParser { } } +#[cfg(feature = "std")] trait PathMut { - fn with_stem(self, stem: impl AsRef) -> PathBuf; - fn with_stem_and_extension(self, stem: impl AsRef, ext: impl AsRef) -> PathBuf; + fn with_stem(self, stem: impl AsRef) -> PathBuf; + fn with_stem_and_extension( + self, + stem: impl AsRef, + ext: impl AsRef, + ) -> PathBuf; } -impl PathMut for &Path { - fn with_stem(self, stem: impl AsRef) -> PathBuf { +#[cfg(feature = "std")] +impl PathMut for &std::path::Path { + fn with_stem(self, stem: impl AsRef) -> std::path::PathBuf { let mut path = self.with_file_name(stem); if let Some(ext) = self.extension() { path.set_extension(ext); @@ -498,14 +512,19 @@ impl PathMut for &Path { path } - fn with_stem_and_extension(self, stem: impl AsRef, ext: impl AsRef) -> PathBuf { + fn with_stem_and_extension( + self, + stem: impl AsRef, + ext: impl AsRef, + ) -> std::path::PathBuf { let mut path = self.with_file_name(stem); path.set_extension(ext); path } } -impl PathMut for PathBuf { - fn with_stem(mut self, stem: impl AsRef) -> PathBuf { +#[cfg(feature = "std")] +impl PathMut for std::path::PathBuf { + fn with_stem(mut self, stem: impl AsRef) -> std::path::PathBuf { if let Some(ext) = self.extension() { let ext = ext.to_string_lossy().into_owned(); self.with_stem_and_extension(stem, ext) @@ -517,9 +536,9 @@ impl PathMut for PathBuf { fn with_stem_and_extension( mut self, - stem: impl AsRef, - ext: impl AsRef, - ) -> PathBuf { + stem: impl AsRef, + ext: impl AsRef, + ) -> std::path::PathBuf { self.set_file_name(stem); self.set_extension(ext); self diff --git a/midenc-session/src/path.rs b/midenc-session/src/path.rs new file mode 100644 index 000000000..efc058248 --- /dev/null +++ b/midenc-session/src/path.rs @@ -0,0 +1,280 @@ +#[cfg(feature = "std")] +pub use std::path::{Path, PathBuf}; + +#[cfg(not(feature = "std"))] +pub use self::fallback::{Path, PathBuf}; + +#[cfg(not(feature = "std"))] +mod fallback { + use alloc::{borrow::Cow, boxed::Box, vec::Vec}; + use core::{borrow::Borrow, fmt::Display, ops::Deref}; + + #[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub struct PathBuf(Vec); + + impl PathBuf { + pub const fn new() -> Self { + Self(Vec::new()) + } + + #[inline] + pub fn as_path(&self) -> &Path { + // SAFETY: Path just wraps [u8] and &*self.0 is &[u8], which is safe to transmute to &Path + unsafe { core::mem::transmute(&*self.0) } + } + + #[inline] + pub fn into_boxed_path(self) -> Box { + unsafe { core::mem::transmute(self.0.into_boxed_slice()) } + } + } + + impl From<&str> for PathBuf { + fn from(s: &str) -> Self { + Self(s.as_bytes().to_vec()) + } + } + + impl core::fmt::Debug for PathBuf { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let s = self.as_path().to_string_lossy(); + write!(f, "{s}") + } + } + + impl Deref for PathBuf { + type Target = Path; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_path() + } + } + + impl AsRef for PathBuf { + #[inline] + fn as_ref(&self) -> &Path { + self.as_path() + } + } + + impl Borrow for PathBuf { + #[inline] + fn borrow(&self) -> &Path { + self.as_path() + } + } + + #[derive(PartialEq, Eq, PartialOrd, Ord)] + #[repr(transparent)] + pub struct Path([u8]); + + impl core::fmt::Debug for Path { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let s = self.to_string_lossy(); + write!(f, "{s}") + } + } + + impl Path { + pub fn to_str(&self) -> Option<&str> { + core::str::from_utf8(&self.0).ok() + } + + pub fn to_string_lossy(&self) -> Cow<'_, str> { + alloc::string::String::from_utf8_lossy(&self.0) + } + + pub fn to_path_buf(&self) -> PathBuf { + PathBuf(self.0.to_vec()) + } + + pub fn display(&self) -> impl Display + '_ { + self.to_string_lossy() + } + + pub fn is_absolute(&self) -> bool { + self.0.starts_with(b"/") + } + + pub fn parent(&self) -> Option<&Self> { + if self.0.is_empty() || self.0.ends_with(b"/") { + return None; + } + match self.to_str() { + None => match self.0.rsplit_once(|b| *b == b'/') { + None => Some(Self::from_bytes(&self.0)), + Some((before, _)) => Some(Self::from_bytes(before)), + }, + Some(s) => match s.rsplit_once('/') { + None => Some(s.as_ref()), + Some((before, _)) => Some(before.as_ref()), + }, + } + } + + pub fn file_name(&self) -> Option<&Self> { + if self.0.ends_with(b"..") { + return None; + } + match self.to_str() { + None => match self.0.rsplit_once(|b| *b == b'/') { + None => Some(Self::from_bytes(&self.0)), + Some((_, after)) => Some(Self::from_bytes(after)), + }, + Some(s) => match s.rsplit_once('/') { + None => Some(s.as_ref()), + Some((_, after)) => Some(after.as_ref()), + }, + } + } + + pub fn file_stem(&self) -> Option<&Self> { + let file_name = self.file_name()?; + match file_name.0.rsplit_once(|b| *b == b'.') { + None => Some(file_name), + Some(([], _)) => Some(file_name), + Some((stem, _)) => Some(Self::from_bytes(stem)), + } + } + + pub fn extension(&self) -> Option<&Self> { + let file_name = self.file_name()?; + match file_name.0.rsplit_once(|b| *b == b'.') { + None => None, + Some(([], _)) => None, + Some((_, ext)) => Some(Self::from_bytes(ext)), + } + } + + pub fn is_dir(&self) -> bool { + self.extension().is_none() + } + + pub fn join

(&self, path: P) -> PathBuf + where + P: AsRef, + { + let path = path.as_ref(); + if self.0.is_empty() { + return path.to_path_buf(); + } + + let mut buf = Vec::with_capacity(self.0.len() + path.0.len() + 1); + buf.extend_from_slice(&self.0); + buf.push(b'/'); + buf.extend_from_slice(&path.0); + + PathBuf(buf) + } + + pub fn with_stem(&self, stem: S) -> PathBuf + where + S: AsRef, + { + let stem = stem.as_ref().as_bytes(); + match self.file_name() { + None => { + let mut buf = Vec::with_capacity(self.0.len() + stem.len()); + buf.extend_from_slice(&self.0); + buf.extend_from_slice(stem); + PathBuf(buf) + } + Some(file_name) => { + let (prefix, ext) = match file_name.0.rsplit_once(|b| *b == b'.') { + None => (self.0.strip_suffix(&file_name.0).unwrap(), None), + Some((name, ext)) => { + let len = self.0.len() - name.len() - 1 - ext.len(); + (&self.0[..len], Some(ext)) + } + }; + let mut buf = Vec::with_capacity( + prefix.len() + ext.map(|ext| ext.len()).unwrap_or_default() + stem.len(), + ); + buf.extend_from_slice(prefix); + buf.extend_from_slice(stem); + if let Some(ext) = ext { + buf.push(b'.'); + buf.extend_from_slice(ext) + } + PathBuf(buf) + } + } + } + + pub fn with_extension(&self, extension: S) -> PathBuf + where + S: AsRef, + { + let extension = extension.as_ref().as_bytes(); + match self.extension() { + None => { + let mut buf = self.to_path_buf(); + buf.0.push(b'.'); + buf.0.extend_from_slice(extension); + buf + } + Some(prev) => { + let bytes = self.0.strip_suffix(&prev.0).unwrap(); + let mut buf = Vec::with_capacity(bytes.len() + extension.len()); + buf.extend_from_slice(bytes); + buf.extend_from_slice(extension); + PathBuf(buf) + } + } + } + + pub fn with_stem_and_extension(&self, stem: S, extension: E) -> PathBuf + where + S: AsRef, + E: AsRef, + { + let stem = stem.as_ref().as_bytes(); + let extension = extension.as_ref().as_bytes(); + match self.file_name() { + None => { + let mut buf = + Vec::with_capacity(self.0.len() + stem.len() + 1 + extension.len()); + buf.extend_from_slice(&self.0); + buf.extend_from_slice(stem); + buf.push(b'.'); + buf.extend_from_slice(extension); + PathBuf(buf) + } + Some(file_name) => { + let bytes = self.0.strip_suffix(&file_name.0).unwrap(); + let mut buf = + Vec::with_capacity(bytes.len() + stem.len() + 1 + extension.len()); + buf.extend_from_slice(bytes); + buf.extend_from_slice(stem); + buf.push(b'.'); + buf.extend_from_slice(extension); + PathBuf(buf) + } + } + } + + #[inline(always)] + fn from_bytes(bytes: &[u8]) -> &Self { + unsafe { core::mem::transmute(bytes) } + } + } + + impl AsRef for str { + fn as_ref(&self) -> &Path { + unsafe { core::mem::transmute(self.as_bytes()) } + } + } + + impl AsRef for alloc::string::String { + fn as_ref(&self) -> &Path { + unsafe { core::mem::transmute(self.as_bytes()) } + } + } + + impl Clone for Box { + fn clone(&self) -> Self { + self.to_path_buf().into_boxed_path() + } + } +} diff --git a/midenc/CHANGELOG.md b/midenc/CHANGELOG.md index 889664b5c..ae140d339 100644 --- a/midenc/CHANGELOG.md +++ b/midenc/CHANGELOG.md @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-v0.1.5...midenc-v0.4.0) - 2025-08-15 + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/midenc-v0.0.7...midenc-v0.0.8) - 2025-04-24 + +### Other +- update Cargo.lock dependencies + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/midenc-v0.0.6...midenc-v0.0.7) - 2024-09-17 ### Other diff --git a/midenc/src/main.rs b/midenc/src/main.rs index fd71bf532..a5c63bcfd 100644 --- a/midenc/src/main.rs +++ b/midenc/src/main.rs @@ -23,8 +23,7 @@ pub fn main() -> Result<(), Report> { other => { return Err(Report::msg(format!( "invalid MIDENC_TRACE_TIMING precision, expected one of [s, ms, us, ns], got \ - '{}'", - other + '{other}'" ))); } }; diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index fb9334e2d..000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,92 +0,0 @@ -site_name: Miden Compiler Docs -theme: - name: material - features: - - search.suggest - - search.highlight - - search.share - # - navigation.instant - - navigation.instant.progress - - navigation.tracking - - navigation.integration - #- navigation.tabs - #- navigation.tabs.sticky - - navigation.indexes - #- navigation.sections - - navigation.path - - navigation.top - - navigation.footer - - toc.follow - - content.code.copy - - content.action.edit - -nav: - - Getting started: index.md - - Usage: - - midenc: usage/midenc.md - - cargo miden: usage/cargo-miden.md - - Guides: - - Rust To WebAssembly: guides/rust_to_wasm.md - - WebAssembly To Miden Assembly: guides/wasm_to_masm.md - - Developing Miden programs In Rust: guides/develop_miden_in_rust.md - - Developing Miden rollup accounts and note scripts In Rust: guides/develop_miden_rollup_accounts_and_note_scripts_in_rust.md - - Debugging programs: usage/debugger.md - - Compiler architecture: - - Overview: design/overview.md - - Supported front ends: design/frontends.md - #- HIR: - #- Code Generation: - #- Testing: - - Appendices: - - Known limitations: appendix/known-limitations.md - - Calling conventions: appendix/calling-conventions.md - - Canonical ABI vs Miden ABI: appendix/canonabi-adhocabi-mismatch.md - -markdown_extensions: - - toc: - permalink: true - permalink_title: Link to this section - toc_depth: 4 - - codehilite - - markdown_include.include: - base_path: src - - admonition - - footnotes - - def_list - - attr_list - - abbr - - pymdownx.tabbed - - pymdownx.superfences - - pymdownx.arithmatex: - generic: true - - pymdownx.betterem: - smart_enable: all - - pymdownx.keys - - pymdownx.details - - pymdownx.magiclink - - pymdownx.mark - - pymdownx.smartsymbols - - pymdownx.tasklist: - custom_checkbox: true - - pymdownx.tilde - - pymdownx.caret - - meta - - smarty - - pymdownx.extra - -plugins: - - search - - open-in-new-tab - -validation: - absolute_links: warn - -extra_javascript: - - https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?version=4.8.0 - - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js - - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.js - - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/contrib/auto-render.min.js - -extra_css: - - https://fonts.googleapis.com/icon?family=Material+Icons - - https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.7/katex.min.css diff --git a/release-plz.toml b/release-plz.toml index e97025318..335132758 100644 --- a/release-plz.toml +++ b/release-plz.toml @@ -1,10 +1,16 @@ [workspace] -# Only publish when the release PR is merged (starts with `release-plz-`) -# https://release-plz.ieni.dev/docs/config#the-release_always-field +pr_branch_prefix = "release-plz-" +# Only publish when the release PR is merged +# https://release-plz.dev/docs/config#the-release_always-field release_always = false -# Do not create a github release -# https://release-plz.ieni.dev/docs/config#the-git_release_enable-field -git_release_enable = false -# Does not create a git tag -# https://release-plz.ieni.dev/docs/config#the-git_tag_enable-field -git_tag_enable = false \ No newline at end of file +# Skip GitHub releases and git tags by default, individual crates can opt in +git_release_enable = false +git_tag_enable = false + +[[package]] +name = "midenc" +git_release_enable = true +git_tag_enable = true +git_tag_name = "{{ version }}" +git_release_name = "{{ version }}" + diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 20984a060..79c890449 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,5 +1,8 @@ [toolchain] -channel = "nightly-2024-08-06" +# REMINDER: After updating the channel, update: +# - docs/src/usage/cargo-miden.md +# - docs/src/usage/midenc.md +channel = "nightly-2025-07-20" components = ["rustfmt", "rust-src", "clippy"] -targets = ["wasm32-unknown-unknown", "wasm32-wasip1"] +targets = ["wasm32-unknown-unknown", "wasm32-wasip1", "wasm32-wasip2"] profile = "minimal" diff --git a/sdk/alloc/CHANGELOG.md b/sdk/alloc/CHANGELOG.md index a108f2699..78ee75ed3 100644 --- a/sdk/alloc/CHANGELOG.md +++ b/sdk/alloc/CHANGELOG.md @@ -6,6 +6,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0](https://github.com/0xMiden/compiler/compare/miden-sdk-alloc-v0.1.5...miden-sdk-alloc-v0.4.0) - 2025-08-15 + +### Fixed + +- make `HEAP_END` in `BumpAlloc` to be `u32::MAX` +- HEAP_END to represent the Miden limit in the Rust address space + +## [0.1.5](https://github.com/0xMiden/compiler/compare/miden-sdk-alloc-v0.1.0...miden-sdk-alloc-v0.1.5) - 2025-07-01 + +### Fixed + +- `BumpAlloc` to make all allocations minimally VM word-aligned (16 bytes) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/miden-sdk-alloc-v0.0.7...miden-sdk-alloc-v0.0.8) - 2025-04-24 + +### Added +- *(cargo-miden)* support building Wasm component from a Cargo project + +### Other +- treat warnings as compiler errors, + ## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/miden-sdk-alloc-v0.0.5...miden-sdk-alloc-v0.0.6) - 2024-09-06 ### Other diff --git a/sdk/alloc/Cargo.toml b/sdk/alloc/Cargo.toml index 351a8d82e..1fe399400 100644 --- a/sdk/alloc/Cargo.toml +++ b/sdk/alloc/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "miden-sdk-alloc" description = "A simple bump allocator for Miden SDK programs" -version.workspace = true +version = "0.7.0" rust-version.workspace = true authors.workspace = true repository.workspace = true diff --git a/sdk/alloc/build.rs b/sdk/alloc/build.rs new file mode 100644 index 000000000..a9287caab --- /dev/null +++ b/sdk/alloc/build.rs @@ -0,0 +1,71 @@ +// Build the Miden alloc stubs and link them for dependents. +// +// We produce a native static library (.a) that contains only the stub object +// files (no panic handler) to avoid duplicate panic symbols in downstream +// component builds. We do this by compiling a single rlib with rustc and naming +// the output `.a` so dependents pick it up via the native link search path. +// +// Why not an rlib? +// - `cargo:rustc-link-lib`/`cargo:rustc-link-search` are for native archives; +// .rlib doesn’t fit that model and attempts to use `rustc-link-arg` don’t +// propagate to dependents. +// Why not a staticlib via rustc directly? +// - A no_std staticlib usually requires a `#[panic_handler]`, which then +// collides at link time with other crates that also define panic symbols. +// - Packaging a single object keeps the archive minimal and free of panic +// symbols. + +use std::{env, path::PathBuf, process::Command}; + +fn main() { + let target = env::var("TARGET").unwrap_or_else(|_| "wasm32-wasip1".to_string()); + + // Only build the wasm stub when targeting wasm32 + if !target.starts_with("wasm32") { + println!("cargo:rerun-if-changed=stubs/heap_base.rs"); + return; + } + + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + + println!("cargo:rerun-if-env-changed=TARGET"); + println!("cargo:rerun-if-env-changed=RUSTUP_TOOLCHAIN"); + println!("cargo:rerun-if-env-changed=RUSTFLAGS"); + println!("cargo:rerun-if-changed={}", manifest_dir.join("stubs/heap_base.rs").display()); + + let out_rlib = out_dir.join("libmiden_alloc_intrinsics.a"); + + // Compile the stub crate into an rlib archive + let status = Command::new("rustc") + .arg("--crate-name") + .arg("miden_alloc_heap_base_stub") + .arg("--edition=2021") + .arg("--crate-type=rlib") + .arg("--target") + .arg(&target) + .arg("-C") + .arg("opt-level=1") + .arg("-C") + .arg("panic=abort") + .arg("-C") + .arg("codegen-units=1") + .arg("-C") + .arg("debuginfo=0") + .arg("-Z") + .arg("merge-functions=disabled") + .arg("-C") + .arg("target-feature=+bulk-memory,+wide-arithmetic") + .arg("-o") + .arg(&out_rlib) + .arg(manifest_dir.join("stubs/heap_base.rs")) + .status() + .expect("failed to spawn rustc for heap_base stub object"); + if !status.success() { + panic!("failed to compile heap_base stub object: {status}"); + } + + // Link for dependents of this crate + println!("cargo:rustc-link-search=native={}", out_dir.display()); + println!("cargo:rustc-link-lib=static=miden_alloc_intrinsics"); +} diff --git a/sdk/alloc/src/lib.rs b/sdk/alloc/src/lib.rs index 045312d64..37d789f01 100644 --- a/sdk/alloc/src/lib.rs +++ b/sdk/alloc/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![deny(warnings)] extern crate alloc; @@ -12,12 +13,13 @@ use core::{ #[cfg(target_family = "wasm")] const PAGE_SIZE: usize = 2usize.pow(16); -/// We require all allocations to be minimally word-aligned, i.e. 32 byte alignment -const MIN_ALIGN: usize = 32; +/// We require all allocations to be minimally word-aligned, i.e. 16 byte alignment +const MIN_ALIGN: usize = 16; -/// The linear memory heap must not spill over into the region reserved for procedure -/// locals, which begins at 2^30 in Miden's address space. -const HEAP_END: *mut u8 = (2usize.pow(30) / 4) as *mut u8; +/// The linear memory heap must not spill over into the region reserved for procedure locals, which +/// begins at 2^30 in Miden's address space. In Rust address space it should be 2^30 * 4 but since +/// it overflows the usize which is 32-bit on wasm32 we use u32::MAX. +const HEAP_END: *mut u8 = u32::MAX as *mut u8; /// A very simple allocator for Miden SDK-based programs. /// @@ -123,7 +125,7 @@ unsafe impl GlobalAlloc for BumpAlloc { } #[cfg(target_family = "wasm")] -#[link(wasm_import_module = "intrinsics::mem")] extern "C" { + #[link_name = "intrinsics::mem::heap_base"] fn heap_base() -> *mut u8; } diff --git a/sdk/alloc/stubs/heap_base.rs b/sdk/alloc/stubs/heap_base.rs new file mode 100644 index 000000000..54789a1da --- /dev/null +++ b/sdk/alloc/stubs/heap_base.rs @@ -0,0 +1,10 @@ +#![no_std] + +// Provide a local definition for the allocator to link against, and export it +// under the MASM intrinsic path so the frontend recognizes and lowers it. + +#[export_name = "intrinsics::mem::heap_base"] +pub extern "C" fn __intrinsics_mem_heap_base_stub() -> *mut u8 { + unsafe { core::hint::unreachable_unchecked() } +} + diff --git a/sdk/base-macros/CHANGELOG.md b/sdk/base-macros/CHANGELOG.md new file mode 100644 index 000000000..a9dd19485 --- /dev/null +++ b/sdk/base-macros/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.0](https://github.com/0xMiden/compiler/compare/miden-base-macros-v0.1.5...miden-base-macros-v0.4.0) - 2025-08-15 + +### Fixed + +- add empty `struct` support in `#[component]` attribute macro + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.1.0](https://github.com/0xMiden/compiler/releases/tag/miden-base-macros-v0.1.0) - 2025-05-23 + +### Added + +- add `package.metadata.miden.supported-types` +- *(sdk)* `component` attribute macros parses `name`, `description` +- *(sdk)* store type attribute name, update `miden-objects` to +- *(frontend)* parse AccountComponentMetadata in the frontend +- *(frontend)* store `AccountComponentMetadata` in custom link section +- *(sdk)* implement an account instantiation in `component` macro +- *(sdk)* introduce `miden-base` with high-level account storage API + +### Fixed + +- handle empty call site span for `component` macro under + +### Other + +- update dependencies +- 0.1.0 +- update url +- fixup miden-base facade in sdk +- formatting and cleanup +- remove the component_macro_test since macro under test +- split `component` attribute macro +- make account storage API polymorphic for key and value types +- add expected TOML test for `component` macro and compiled Miden package +- make `component` macro implement `Default` for the type +- fix clippy warnings +- add macros generated AccountComponentMetadata test +- parse description and implement AccountComponentMetadataBuilder, +- fix typos ([#243](https://github.com/0xMiden/compiler/pull/243)) +- a few minor improvements +- set up mdbook deploy +- add guides for compiling rust->masm +- add mdbook skeleton +- provide some initial usage instructions +- Initial commit diff --git a/sdk/base-macros/Cargo.toml b/sdk/base-macros/Cargo.toml new file mode 100644 index 000000000..61e35ac8e --- /dev/null +++ b/sdk/base-macros/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "miden-base-macros" +description = "Provides proc macro support for Miden rollup SDK" +version = "0.7.0" +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[lib] +proc-macro = true + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +miden-objects.workspace = true +semver.workspace = true +toml.workspace = true +syn.workspace = true +heck.workspace = true + +[dev-dependencies] +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging +miden-objects = { workspace = true, features = ["std"] } diff --git a/sdk/base-macros/src/account_component_metadata.rs b/sdk/base-macros/src/account_component_metadata.rs new file mode 100644 index 000000000..601419a02 --- /dev/null +++ b/sdk/base-macros/src/account_component_metadata.rs @@ -0,0 +1,105 @@ +use std::collections::BTreeSet; + +use miden_objects::account::{ + component::FieldIdentifier, AccountComponentMetadata, AccountType, MapRepresentation, + StorageEntry, StorageValueName, TemplateType, WordRepresentation, +}; +use semver::Version; + +pub struct AccountComponentMetadataBuilder { + /// The human-readable name of the component. + name: String, + + /// A brief description of what this component is and how it works. + description: String, + + /// The version of the component using semantic versioning. + /// This can be used to track and manage component upgrades. + version: Version, + + /// A set of supported target account types for this component. + supported_types: BTreeSet, + + /// A list of storage entries defining the component's storage layout and initialization + /// values. + storage: Vec, +} + +impl AccountComponentMetadataBuilder { + /// Adds a supported account type to this component metadata. + pub fn add_supported_type(&mut self, account_type: AccountType) { + self.supported_types.insert(account_type); + } + + pub fn new(name: String, version: Version, description: String) -> Self { + AccountComponentMetadataBuilder { + name, + description, + version, + supported_types: BTreeSet::new(), + storage: Vec::new(), + } + } + + pub fn add_storage_entry( + &mut self, + name: &str, + description: Option, + slot: u8, + field_type: &syn::Type, + field_type_attr: Option, + ) { + let type_path = if let syn::Type::Path(type_path) = field_type { + type_path + } else { + panic!("failed to get type path {field_type:?}") + }; + + if let Some(segment) = type_path.path.segments.last() { + let type_name = segment.ident.to_string(); + let storage_value_name = + StorageValueName::new(name).expect("well formed storage value name"); + match type_name.as_str() { + "StorageMap" => { + let mut map_repr = MapRepresentation::new(vec![], storage_value_name); + if let Some(description) = description { + map_repr = map_repr.with_description(description); + } + self.storage.push(StorageEntry::new_map(slot, map_repr)); + } + "Value" => { + let r#type = if let Some(field_type) = field_type_attr { + TemplateType::new(&field_type) + .unwrap_or_else(|_| panic!("well formed attribute type {field_type}")) + } else { + TemplateType::native_word() + }; + self.storage.push(StorageEntry::new_value( + slot, + WordRepresentation::Template { + r#type, + identifier: FieldIdentifier { + name: storage_value_name, + description, + }, + }, + )); + } + _ => panic!("unexpected field type: {type_name}"), + } + } else { + panic!("failed to get last segment of the type path {type_path:?}") + } + } + + pub fn build(self) -> AccountComponentMetadata { + AccountComponentMetadata::new( + self.name, + self.description, + self.version, + self.supported_types, + self.storage, + ) + .expect("failed to build AccountComponentMetadata") + } +} diff --git a/sdk/base-macros/src/boilerplate.rs b/sdk/base-macros/src/boilerplate.rs new file mode 100644 index 000000000..9f4f82ab4 --- /dev/null +++ b/sdk/base-macros/src/boilerplate.rs @@ -0,0 +1,19 @@ +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; + +/// Generates the shared runtime scaffolding required by no_std +pub(crate) fn runtime_boilerplate() -> TokenStream2 { + quote! { + #[doc = "Global allocator for Miden VM"] + #[global_allocator] + static __MIDEN_RUNTIME_ALLOCATOR: ::miden::BumpAlloc = ::miden::BumpAlloc::new(); + + #[cfg(not(test))] + #[doc = "Panic handler used when building for Miden VM"] + #[panic_handler] + #[allow(clippy::empty_loop)] + fn __miden_runtime_panic_handler(_info: &::core::panic::PanicInfo) -> ! { + loop {} + } + } +} diff --git a/sdk/base-macros/src/component_macro/generate_wit.rs b/sdk/base-macros/src/component_macro/generate_wit.rs new file mode 100644 index 000000000..0ee2bb746 --- /dev/null +++ b/sdk/base-macros/src/component_macro/generate_wit.rs @@ -0,0 +1,207 @@ +use std::{ + collections::{BTreeSet, HashSet}, + fmt::Write, + fs, + io::ErrorKind, +}; + +use proc_macro::Span; +use semver::Version; +use syn::spanned::Spanned; + +use crate::{ + component_macro::{to_kebab_case, ComponentMethod, MethodReturn, CORE_TYPES_PACKAGE}, + types::{ensure_custom_type_defined, ExportedTypeDef, ExportedTypeKind}, + util::generated_wit_folder, +}; + +/// Writes the generated component WIT to the crate's `wit` directory so that dependent targets can +/// reference it via manifest metadata. +pub fn write_component_wit_file( + call_site_span: Span, + wit_source: &str, + package_name: &str, +) -> Result<(), syn::Error> { + let sanitized_package_name = sanitize_package_name(package_name); + let autogenerated_wit_folder = generated_wit_folder()?; + let wit_path = autogenerated_wit_folder.join(format!("{sanitized_package_name}.wit")); + + let needs_write = match fs::read_to_string(&wit_path) { + Ok(existing) => existing != wit_source, + Err(err) if err.kind() == ErrorKind::NotFound => true, + Err(err) => { + return Err(syn::Error::new( + call_site_span.into(), + format!("failed to read existing WIT file '{}': {err}", wit_path.display()), + )); + } + }; + + if needs_write { + fs::write(&wit_path, wit_source).map_err(|err| { + syn::Error::new( + call_site_span.into(), + format!("failed to write WIT file '{}': {err}", wit_path.display()), + ) + })?; + } + + Ok(()) +} + +/// Renders the inline WIT source describing the component interface exported by the `impl` block. +pub fn build_component_wit( + component_package: &str, + component_version: &Version, + interface_name: &str, + world_name: &str, + type_imports: &BTreeSet, + methods: &[ComponentMethod], + exported_types: &[ExportedTypeDef], +) -> Result { + let package_with_version = if component_package.contains('@') { + component_package.to_string() + } else { + format!("{component_package}@{component_version}") + }; + + let mut wit_source = String::new(); + let _ = writeln!(wit_source, "// This file is auto-generated by the `#[component]` macro."); + let _ = writeln!(wit_source, "// Do not edit this file manually."); + wit_source.push('\n'); + let _ = writeln!(wit_source, "package {package_with_version};"); + wit_source.push('\n'); + let _ = writeln!(wit_source, "use {CORE_TYPES_PACKAGE};"); + wit_source.push('\n'); + + let exported_type_names: HashSet = + exported_types.iter().map(|def| def.wit_name.clone()).collect(); + + let mut combined_core_imports = type_imports.clone(); + for exported in exported_types { + match &exported.kind { + ExportedTypeKind::Record { fields } => { + for field in fields { + ensure_custom_type_defined( + &field.ty, + &exported_type_names, + Span::call_site().into(), + )?; + if !field.ty.is_custom { + combined_core_imports.insert(field.ty.wit_name.clone()); + } + } + } + ExportedTypeKind::Variant { variants } => { + for variant in variants { + if let Some(payload) = &variant.payload { + ensure_custom_type_defined( + payload, + &exported_type_names, + Span::call_site().into(), + )?; + if !payload.is_custom { + combined_core_imports.insert(payload.wit_name.clone()); + } + } + } + } + } + } + + let _ = writeln!(wit_source, "interface {interface_name} {{"); + + if !combined_core_imports.is_empty() { + let imports = combined_core_imports.iter().cloned().collect::>().join(", "); + let _ = writeln!(wit_source, " use core-types.{{{imports}}};"); + wit_source.push('\n'); + } + + for exported in exported_types { + match &exported.kind { + ExportedTypeKind::Record { fields } => { + let _ = writeln!(wit_source, " record {} {{", exported.wit_name); + for field in fields { + let field_name = to_kebab_case(&field.name); + let _ = writeln!(wit_source, " {}: {},", field_name, field.ty.wit_name); + } + let _ = writeln!(wit_source, " }}\n"); + } + ExportedTypeKind::Variant { variants } => { + let _ = writeln!(wit_source, " variant {} {{", exported.wit_name); + for variant in variants { + if let Some(payload) = &variant.payload { + let _ = writeln!( + wit_source, + " {}({}),", + variant.wit_name, payload.wit_name + ); + } else { + let _ = writeln!(wit_source, " {},", variant.wit_name); + } + } + let _ = writeln!(wit_source, " }}\n"); + } + } + } + + for method in methods { + for param in &method.params { + ensure_custom_type_defined( + ¶m.type_ref, + &exported_type_names, + param.user_ty.span(), + )?; + } + if let MethodReturn::Type { type_ref, user_ty } = &method.return_info { + ensure_custom_type_defined(type_ref, &exported_type_names, user_ty.span())?; + } + + let signature = if method.params.is_empty() { + match &method.return_info { + MethodReturn::Unit => format!(" {}: func();", method.wit_name), + MethodReturn::Type { type_ref, .. } => { + format!(" {}: func() -> {};", method.wit_name, type_ref.wit_name) + } + } + } else { + let params = method + .params + .iter() + .map(|param| format!("{}: {}", param.wit_param_name, param.type_ref.wit_name)) + .collect::>() + .join(", "); + match &method.return_info { + MethodReturn::Unit => format!(" {}: func({});", method.wit_name, params), + MethodReturn::Type { type_ref, .. } => { + format!(" {}: func({}) -> {};", method.wit_name, params, type_ref.wit_name) + } + } + }; + let _ = writeln!(wit_source, "{signature}"); + } + + let _ = writeln!(wit_source, "}}"); + wit_source.push('\n'); + let _ = writeln!(wit_source, "world {world_name} {{"); + let _ = writeln!(wit_source, " export {interface_name};"); + let _ = writeln!(wit_source, "}}"); + + Ok(wit_source) +} + +fn sanitize_package_name(package_name: &str) -> String { + let mut sanitized = package_name + .chars() + .map(|ch| match ch { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' => ch, + _ => '-', + }) + .collect::(); + + if sanitized.is_empty() { + sanitized.push_str("component"); + } + + sanitized +} diff --git a/sdk/base-macros/src/component_macro/metadata.rs b/sdk/base-macros/src/component_macro/metadata.rs new file mode 100644 index 000000000..f10391086 --- /dev/null +++ b/sdk/base-macros/src/component_macro/metadata.rs @@ -0,0 +1,135 @@ +use std::{fs, path::Path}; + +use proc_macro::Span; +use semver::Version; +use toml::Value; + +/// Cargo metadata relevant for the `#[component]` macro expansion. +pub struct CargoMetadata { + pub name: String, + pub version: Version, + pub description: String, + pub supported_types: Vec, + pub component_package: Option, +} + +/// Reads component metadata (name/description/version/supported types) from the enclosing package +/// manifest. +pub fn get_package_metadata(call_site_span: Span) -> Result { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".to_string()); + let current_dir = Path::new(&manifest_dir); + + let cargo_toml_path = current_dir.join("Cargo.toml"); + if !cargo_toml_path.is_file() { + return Ok(CargoMetadata { + name: String::new(), + version: Version::new(0, 0, 1), + description: String::new(), + supported_types: vec![], + component_package: None, + }); + } + + let cargo_toml_content = fs::read_to_string(&cargo_toml_path).map_err(|e| { + syn::Error::new( + call_site_span.into(), + format!("Failed to read {}: {}", cargo_toml_path.display(), e), + ) + })?; + let cargo_toml: Value = cargo_toml_content.parse::().map_err(|e| { + syn::Error::new( + call_site_span.into(), + format!("Failed to parse {}: {}", cargo_toml_path.display(), e), + ) + })?; + + let package_table = cargo_toml.get("package").ok_or_else(|| { + syn::Error::new( + call_site_span.into(), + format!( + "Cargo.toml ({}) does not contain a [package] table", + cargo_toml_path.display() + ), + ) + })?; + + let name = package_table + .get("name") + .and_then(|n| n.as_str()) + .map(String::from) + .ok_or_else(|| { + syn::Error::new( + call_site_span.into(), + format!("Missing 'name' field in [package] table of {}", cargo_toml_path.display()), + ) + })?; + + let version_str = package_table + .get("version") + .and_then(|v| v.as_str()) + .or_else(|| { + let base = env!("CARGO_MANIFEST_DIR"); + if base.ends_with(cargo_toml_path.parent().unwrap().to_str().unwrap()) { + Some("0.0.0") + } else { + None + } + }) + .ok_or_else(|| { + syn::Error::new( + call_site_span.into(), + format!( + "Missing 'version' field in [package] table of {} (version.workspace = true \ + is not yet supported for external crates)", + cargo_toml_path.display() + ), + ) + })?; + + let version = Version::parse(version_str).map_err(|e| { + syn::Error::new( + call_site_span.into(), + format!( + "Failed to parse version '{}' from {}: {}", + version_str, + cargo_toml_path.display(), + e + ), + ) + })?; + + let description = package_table + .get("description") + .and_then(|d| d.as_str()) + .map(String::from) + .unwrap_or_default(); + + let supported_types = cargo_toml + .get("package") + .and_then(|pkg| pkg.get("metadata")) + .and_then(|m| m.get("miden")) + .and_then(|m| m.get("supported-types")) + .and_then(|st| st.as_array()) + .map(|arr| { + arr.iter() + .filter_map(|v| v.as_str().map(|s| s.to_string())) + .collect::>() + }) + .unwrap_or_default(); + + let component_package = cargo_toml + .get("package") + .and_then(|pkg| pkg.get("metadata")) + .and_then(|meta| meta.get("component")) + .and_then(|component| component.get("package")) + .and_then(|pkg_val| pkg_val.as_str()) + .map(|pkg| pkg.to_string()); + + Ok(CargoMetadata { + name, + version, + description, + supported_types, + component_package, + }) +} diff --git a/sdk/base-macros/src/component_macro/mod.rs b/sdk/base-macros/src/component_macro/mod.rs new file mode 100644 index 000000000..db6444b8c --- /dev/null +++ b/sdk/base-macros/src/component_macro/mod.rs @@ -0,0 +1,853 @@ +use std::{ + collections::{BTreeSet, HashMap}, + env, + str::FromStr, +}; + +use heck::{ToKebabCase, ToSnakeCase}; +use miden_objects::{account::AccountType, utils::Serializable}; +use proc_macro::Span; +use proc_macro2::{Ident, Literal, TokenStream as TokenStream2}; +use quote::{format_ident, quote}; +use syn::{ + spanned::Spanned, Attribute, FnArg, ImplItem, ImplItemFn, ItemImpl, ItemStruct, ReturnType, + Type, Visibility, +}; + +use crate::{ + account_component_metadata::AccountComponentMetadataBuilder, + boilerplate::runtime_boilerplate, + component_macro::{ + generate_wit::{build_component_wit, write_component_wit_file}, + metadata::get_package_metadata, + storage::process_storage_fields, + }, + types::{ + map_type_to_type_ref, registered_export_types, ExportedTypeDef, ExportedTypeKind, TypeRef, + }, +}; + +mod generate_wit; +mod metadata; +mod storage; + +/// Fully-qualified identifier for the core types package used by exported component interfaces. +const CORE_TYPES_PACKAGE: &str = "miden:base/core-types@1.0.0"; + +/// Receiver kinds supported by the derived guest trait implementation. +#[derive(Clone, Copy)] +enum ReceiverKind { + /// The method receives `&self`. + Ref, + /// The method receives `&mut self`. + RefMut, + /// The method receives `self` by value. + Value, +} + +/// Metadata describing a WIT function parameter generated from a Rust method argument. +struct MethodParam { + ident: syn::Ident, + user_ty: syn::Type, + type_ref: TypeRef, + wit_param_name: String, +} + +enum MethodReturn { + Unit, + Type { + user_ty: Box, + type_ref: TypeRef, + }, +} + +/// Captures all information required to render WIT signatures and the guest trait implementation +/// for a single exported method. +struct ComponentMethod { + /// Method identifier in Rust. + fn_ident: syn::Ident, + /// Documentation attributes carried over to the guest trait implementation. + doc_attrs: Vec, + /// Method parameters metadata. + params: Vec, + /// Receiver mode required by the method. + receiver_kind: ReceiverKind, + /// Return type metadata. + return_info: MethodReturn, + /// Method name rendered in kebab-case for WIT output. + wit_name: String, +} + +/// Expands the `#[component]` attribute applied to either a struct declaration or an inherent +/// implementation block. +pub fn component( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + if !attr.is_empty() { + return syn::Error::new(Span::call_site().into(), "#[component] does not accept arguments") + .into_compile_error() + .into(); + } + + let call_site_span = Span::call_site(); + let item_tokens: TokenStream2 = item.into(); + + if let Ok(item_struct) = syn::parse2::(item_tokens.clone()) { + match expand_component_struct(call_site_span, item_struct) { + Ok(expanded) => expanded.into(), + Err(err) => err.to_compile_error().into(), + } + } else if let Ok(item_impl) = syn::parse2::(item_tokens) { + match expand_component_impl(call_site_span, item_impl) { + Ok(expanded) => expanded.into(), + Err(err) => err.to_compile_error().into(), + } + } else { + syn::Error::new( + call_site_span.into(), + "The `component` macro only supports structs and inherent impl blocks.", + ) + .into_compile_error() + .into() + } +} + +/// Expands the `#[component]` attribute applied to a struct by wiring storage metadata and link +/// section exports. +fn expand_component_struct( + call_site_span: Span, + mut input_struct: ItemStruct, +) -> Result { + let struct_name = &input_struct.ident; + + let metadata = get_package_metadata(call_site_span)?; + let mut acc_builder = AccountComponentMetadataBuilder::new( + metadata.name.clone(), + metadata.version.clone(), + metadata.description.clone(), + ); + + for st in &metadata.supported_types { + match AccountType::from_str(st) { + Ok(account_type) => acc_builder.add_supported_type(account_type), + Err(err) => { + return Err(syn::Error::new( + call_site_span.into(), + format!("Invalid account type '{st}' in supported-types: {err}"), + )); + } + } + } + + let default_impl = match &mut input_struct.fields { + syn::Fields::Named(fields) => { + let field_inits = process_storage_fields(fields, &mut acc_builder)?; + generate_default_impl(struct_name, &field_inits) + } + syn::Fields::Unit => quote! { + impl Default for #struct_name { + fn default() -> Self { + Self + } + } + }, + _ => { + return Err(syn::Error::new( + input_struct.fields.span(), + "The `component` macro only supports unit structs or structs with named fields.", + )); + } + }; + + let component_metadata = acc_builder.build(); + + let mut metadata_bytes = component_metadata.to_bytes(); + let padded_len = metadata_bytes.len().div_ceil(16) * 16; + metadata_bytes.resize(padded_len, 0); + + let link_section = generate_link_section(&metadata_bytes); + let runtime_boilerplate = runtime_boilerplate(); + + Ok(quote! { + #runtime_boilerplate + #input_struct + #default_impl + #link_section + }) +} + +/// Expands the `#[component]` attribute applied to an inherent implementation block by generating +/// the inline WIT interface, invoking `miden::generate!`, and wiring the guest trait implementation. +fn expand_component_impl( + call_site_span: Span, + impl_block: ItemImpl, +) -> Result { + if impl_block.trait_.is_some() { + return Err(syn::Error::new( + impl_block.span(), + "The `component` macro does not support trait implementations.", + )); + } + + let component_type = (*impl_block.self_ty).clone(); + if extract_type_ident(&component_type).is_none() { + return Err(syn::Error::new( + impl_block.self_ty.span(), + "Failed to determine the struct name targeted by this implementation.", + )); + } + + let metadata = get_package_metadata(call_site_span)?; + let component_package = metadata.component_package.clone().ok_or_else(|| { + syn::Error::new( + call_site_span.into(), + "package.metadata.component.package in Cargo.toml is required to derive the component \ + interface", + ) + })?; + + let interface_name = metadata.name.to_kebab_case(); + let interface_module = { + let name: &str = &interface_name; + name.to_snake_case() + }; + let world_name = format!("{interface_name}-world"); + + let mut exported_types = registered_export_types(); + exported_types.sort_by(|a, b| a.wit_name.cmp(&b.wit_name)); + let exported_types_by_rust: HashMap<_, _> = + exported_types.iter().map(|def| (def.rust_name.clone(), def.clone())).collect(); + let mut methods = Vec::new(); + let mut type_imports = BTreeSet::new(); + + for item in &impl_block.items { + if let ImplItem::Fn(method) = item { + if !matches!(method.vis, Visibility::Public(_)) { + continue; + } + + let (parsed_method, imports) = parse_component_method(method, &exported_types_by_rust)?; + type_imports.extend(imports); + methods.push(parsed_method); + } + } + + if methods.is_empty() { + return Err(syn::Error::new( + call_site_span.into(), + "Component `impl` is missing `pub` methods. A component cannot have emty exports.", + )); + } + + let wit_source = build_component_wit( + &component_package, + &metadata.version, + &interface_name, + &world_name, + &type_imports, + &methods, + &exported_types, + )?; + write_component_wit_file(call_site_span, &wit_source, &component_package)?; + let inline_literal = Literal::string(&wit_source); + + let guest_trait_path = build_guest_trait_path(&component_package, &interface_module)?; + let guest_methods: Vec = methods + .iter() + .map(|method| render_guest_method(method, &component_type)) + .collect(); + + let interface_path = format!("{}/{}@{}", component_package, interface_name, metadata.version); + let module_prefix = component_module_prefix(&component_type); + let module_prefix_segments: Option> = module_prefix + .as_ref() + .map(|path| path.segments.iter().map(|segment| segment.ident.to_string()).collect()); + + let custom_type_paths = + collect_custom_type_paths(&exported_types, &methods, module_prefix_segments.as_deref()); + + let (custom_with_entries, debug_with_entries) = build_custom_with_entries( + &exported_types, + &interface_path, + module_prefix.as_ref(), + &custom_type_paths, + ); + + if env::var_os("MIDEN_COMPONENT_DEBUG_WITH").is_some() { + eprintln!( + "[miden::component] with mappings for {}: {}", + component_package, + debug_with_entries.join(", ") + ); + } + + Ok(quote! { + ::miden::generate!(inline = #inline_literal, with = { #(#custom_with_entries)* }); + #impl_block + impl #guest_trait_path for #component_type { + #(#guest_methods)* + } + // Use the fully-qualified component type here so the export macro works even when + // the impl block was declared through a module-qualified path (e.g. `impl super::Foo`). + self::bindings::export!(#component_type); + }) +} + +/// Synthesizes the guest trait path exposed by `wit-bindgen` for the generated interface. +fn build_guest_trait_path( + component_package: &str, + interface_module: &str, +) -> Result { + let package_without_version = + component_package.split('@').next().unwrap_or(component_package).trim(); + + let segments: Vec<_> = package_without_version + .split([':', '/']) + .filter(|segment| !segment.is_empty()) + .map(to_snake_case) + .collect(); + + if segments.is_empty() { + return Err(syn::Error::new( + Span::call_site().into(), + "Invalid component package identifier provided in manifest metadata.", + )); + } + + let module_idents: Vec<_> = + segments.iter().map(|segment| format_ident!("{}", segment)).collect(); + let interface_ident = format_ident!("{}", to_snake_case(interface_module)); + + Ok(quote! { self::bindings::exports #( :: #module_idents)* :: #interface_ident :: Guest }) +} + +/// Emits the guest trait method forwarding logic invoking the user-defined implementation. +fn render_guest_method(method: &ComponentMethod, component_type: &Type) -> TokenStream2 { + let fn_ident = &method.fn_ident; + let doc_attrs = &method.doc_attrs; + let component_ident = format_ident!("__component_instance"); + + let mut param_tokens = Vec::new(); + let mut call_args = Vec::new(); + + for param in &method.params { + let ident = ¶m.ident; + call_args.push(quote!(#ident)); + + let param_ty = ¶m.user_ty; + param_tokens.push(quote!(#ident: #param_ty)); + } + + let fn_inputs = if param_tokens.is_empty() { + quote!() + } else { + quote!(#(#param_tokens),*) + }; + + let component_init = match method.receiver_kind { + ReceiverKind::Ref => quote! { let #component_ident = #component_type::default(); }, + ReceiverKind::RefMut | ReceiverKind::Value => { + quote! { let mut #component_ident = #component_type::default(); } + } + }; + + let call_expr = quote! { #component_ident.#fn_ident(#(#call_args),*) }; + + let output = match &method.return_info { + MethodReturn::Unit => quote!(), + MethodReturn::Type { user_ty, .. } => { + let user_ty = user_ty.as_ref(); + quote!(-> #user_ty) + } + }; + + let body = match &method.return_info { + MethodReturn::Unit => quote! { + #component_init + #call_expr; + }, + MethodReturn::Type { .. } => { + quote! { + #component_init + #call_expr + } + } + }; + + quote! { + #(#doc_attrs)* + fn #fn_ident(#fn_inputs) #output { + #body + } + } +} + +fn build_custom_with_entries( + exported_types: &[ExportedTypeDef], + interface_path: &str, + module_prefix: Option<&syn::Path>, + custom_type_paths: &HashMap>, +) -> (Vec, Vec) { + let mut tokens = Vec::new(); + let mut debug = Vec::new(); + + for def in exported_types { + let wit_path_str = format!("{interface_path}/{}", def.wit_name); + let wit_path = Literal::string(&wit_path_str); + let type_ident = format_ident!("{}", def.rust_name); + // Prefer the fully-qualified path discovered while scanning method signatures or exported + // fields. These paths already include any crate/module prefixes, so they work even when + // the type lives outside the component's module. + let type_tokens = if let Some(segments) = custom_type_paths.get(&def.wit_name) { + build_path_tokens(segments, &type_ident) + } else if let Some(prefix) = module_prefix { + // Fallback to the component's module prefix when no explicit path was collected. This + // preserves the old behaviour for types declared alongside the component. + quote!(#prefix :: #type_ident) + } else { + quote!(crate :: #type_ident) + }; + + debug.push(format!("{wit_path_str} => {type_tokens}")); + tokens.push(quote! { #wit_path: #type_tokens, }); + } + + (tokens, debug) +} + +fn component_module_prefix(component_type: &Type) -> Option { + if let Type::Path(type_path) = component_type { + let mut path = type_path.path.clone(); + if path.segments.len() <= 1 { + return None; + } + path.segments.pop(); + Some(path) + } else { + None + } +} + +fn record_type_path( + paths: &mut HashMap>, + type_ref: &TypeRef, + module_prefix_segments: Option<&[String]>, +) { + if !type_ref.is_custom { + return; + } + + let mut segments = type_ref.path.clone(); + // Normalise `self::` and `super::` prefixes relative to the module where the component impl + // lives so the generated path points at the original user type rather than the generated + // bindings module. + if let Some(first) = segments.first().cloned() { + match first.as_str() { + "self" => { + segments.remove(0); + if let Some(prefix) = module_prefix_segments { + let mut resolved = prefix.to_vec(); + resolved.extend(segments); + segments = resolved; + } + } + "super" => { + let super_count = segments.iter().take_while(|segment| *segment == "super").count(); + let mut resolved = + module_prefix_segments.map(|prefix| prefix.to_vec()).unwrap_or_default(); + if super_count > resolved.len() { + resolved.clear(); + } else { + for _ in 0..super_count { + let _ = resolved.pop(); + } + } + segments = + resolved.into_iter().chain(segments.into_iter().skip(super_count)).collect(); + } + "crate" => {} + _ => {} + } + } + + // Give single-segment paths a module prefix so we don't generate bare identifiers that fail to + // resolve outside the component module. + if segments.len() <= 1 { + if let Some(last) = segments.last().cloned() { + if let Some(prefix) = module_prefix_segments { + let mut resolved = prefix.to_vec(); + resolved.push(last); + segments = resolved; + } + } + } + + paths.entry(type_ref.wit_name.clone()).or_insert(segments); +} + +fn collect_custom_type_paths( + exported_types: &[ExportedTypeDef], + methods: &[ComponentMethod], + module_prefix_segments: Option<&[String]>, +) -> HashMap> { + let mut paths = HashMap::new(); + + for def in exported_types { + match &def.kind { + ExportedTypeKind::Record { fields } => { + for field in fields { + record_type_path(&mut paths, &field.ty, module_prefix_segments); + } + } + ExportedTypeKind::Variant { variants } => { + for variant in variants { + if let Some(payload) = &variant.payload { + record_type_path(&mut paths, payload, module_prefix_segments); + } + } + } + } + } + + for method in methods { + for param in &method.params { + record_type_path(&mut paths, ¶m.type_ref, module_prefix_segments); + } + if let MethodReturn::Type { type_ref, .. } = &method.return_info { + record_type_path(&mut paths, type_ref, module_prefix_segments); + } + } + + paths +} + +fn build_path_tokens(segments: &[String], type_ident: &Ident) -> TokenStream2 { + if segments.is_empty() { + return quote!(crate :: #type_ident); + } + + let mut modules: Vec = segments.to_vec(); + let type_name = type_ident.to_string(); + if modules.last().map(|seg| seg == &type_name).unwrap_or(false) { + modules.pop(); + } + + let mut iter = modules.iter(); + let mut tokens: Option = None; + + if let Some(first) = iter.next() { + tokens = Some(match first.as_str() { + "crate" => quote!(crate), + "self" => quote!(self), + "super" => quote!(super), + other => { + let ident = format_ident!("{}", other); + quote!(crate :: #ident) + } + }); + } + + for segment in iter { + let ident = format_ident!("{}", segment); + tokens = Some(match tokens { + Some(existing) => quote!(#existing :: #ident), + None => quote!(crate :: #ident), + }); + } + + let base = tokens.unwrap_or_else(|| quote!(crate)); + quote!(#base :: #type_ident) +} + +/// Parses a public inherent method and extracts the metadata necessary to export it via WIT. +fn parse_component_method( + method: &ImplItemFn, + exported_types: &HashMap, +) -> Result<(ComponentMethod, BTreeSet), syn::Error> { + if method.sig.constness.is_some() { + return Err(syn::Error::new( + method.sig.ident.span(), + "component methods cannot be `const`", + )); + } + if method.sig.asyncness.is_some() { + return Err(syn::Error::new( + method.sig.ident.span(), + "component methods cannot be `async`", + )); + } + if method.sig.unsafety.is_some() { + return Err(syn::Error::new( + method.sig.ident.span(), + "component methods cannot be `unsafe`", + )); + } + if method.sig.abi.is_some() { + return Err(syn::Error::new( + method.sig.ident.span(), + "component methods cannot specify an `extern` ABI", + )); + } + if !method.sig.generics.params.is_empty() { + return Err(syn::Error::new( + method.sig.generics.span(), + "component methods cannot be generic", + )); + } + if method.sig.variadic.is_some() { + return Err(syn::Error::new( + method.sig.ident.span(), + "variadic component methods are unsupported", + )); + } + + let mut inputs_iter = method.sig.inputs.iter(); + let receiver = inputs_iter.next().ok_or_else(|| { + syn::Error::new( + method.sig.span(), + "component methods must accept `self`, `&self`, or `&mut self` as the first argument", + ) + })?; + + let receiver_kind = match receiver { + FnArg::Receiver(recv) => match (&recv.reference, recv.mutability) { + (Some(_), Some(_)) => ReceiverKind::RefMut, + (Some(_), None) => ReceiverKind::Ref, + (None, _) => ReceiverKind::Value, + }, + FnArg::Typed(other) => { + return Err(syn::Error::new( + other.span(), + "component methods must use an explicit receiver", + )); + } + }; + + let mut params = Vec::new(); + let mut type_imports = BTreeSet::new(); + + for arg in inputs_iter { + match arg { + FnArg::Typed(pat_type) => { + let ident = match pat_type.pat.as_ref() { + syn::Pat::Ident(pat_ident) => pat_ident.ident.clone(), + other => { + return Err(syn::Error::new( + other.span(), + "component method arguments must be simple identifiers", + )); + } + }; + + let user_ty = (*pat_type.ty).clone(); + let type_ref = map_type_to_type_ref(&pat_type.ty, exported_types)?; + if !type_ref.is_custom { + type_imports.insert(type_ref.wit_name.clone()); + } + + params.push(MethodParam { + ident: ident.clone(), + user_ty, + type_ref, + wit_param_name: to_kebab_case(&ident.to_string()), + }); + } + FnArg::Receiver(other) => { + return Err(syn::Error::new( + other.span(), + "component methods support a single receiver argument", + )); + } + } + } + + let return_info = match &method.sig.output { + ReturnType::Default => MethodReturn::Unit, + ReturnType::Type(_, ty) if is_unit_type(ty) => MethodReturn::Unit, + ReturnType::Type(_, ty) => { + let type_ref = map_type_to_type_ref(ty, exported_types)?; + if !type_ref.is_custom { + type_imports.insert(type_ref.wit_name.clone()); + } + MethodReturn::Type { + user_ty: ty.clone(), + type_ref, + } + } + }; + + let doc_attrs = method + .attrs + .iter() + .filter(|attr| attr.path().is_ident("doc")) + .cloned() + .collect(); + + let component_method = ComponentMethod { + fn_ident: method.sig.ident.clone(), + doc_attrs, + params, + receiver_kind, + return_info, + wit_name: to_kebab_case(&method.sig.ident.to_string()), + }; + + Ok((component_method, type_imports)) +} + +/// Attempts to recover the final identifier from a type path for use with `bindings::export!`. +fn extract_type_ident(ty: &Type) -> Option { + match ty { + Type::Path(path) => path.path.segments.last().map(|segment| segment.ident.clone()), + Type::Group(group) => extract_type_ident(&group.elem), + Type::Paren(paren) => extract_type_ident(&paren.elem), + _ => None, + } +} + +/// Maps a Rust type used in the public interface to the corresponding WIT core-types identifier. +/// Determines whether a type represents the unit type `()`. +fn is_unit_type(ty: &Type) -> bool { + matches!(ty, Type::Tuple(tuple) if tuple.elems.is_empty()) +} + +/// Converts a snake_case identifier into kebab-case. +fn to_kebab_case(name: &str) -> String { + name.to_kebab_case() +} + +/// Converts a kebab-case identifier into snake_case. +fn to_snake_case(name: &str) -> String { + name.to_snake_case() +} + +/// Synthesizes the `Default` implementation for the component struct using the collected storage +/// initializers. +fn generate_default_impl( + struct_name: &syn::Ident, + field_inits: &[proc_macro2::TokenStream], +) -> proc_macro2::TokenStream { + quote! { + impl Default for #struct_name { + fn default() -> Self { + Self { + #(#field_inits),* + } + } + } + } +} + +/// Emits the static metadata blob inside the `rodata,miden_account` link section. +fn generate_link_section(metadata_bytes: &[u8]) -> proc_macro2::TokenStream { + let link_section_bytes_len = metadata_bytes.len(); + let encoded_bytes_str = Literal::byte_string(metadata_bytes); + + quote! { + #[unsafe( + // to test it in the integration(this crate) tests the section name needs to make mach-o section + // specifier happy and to have "segment and section separated by comma" + link_section = "rodata,miden_account" + )] + #[doc(hidden)] + #[allow(clippy::octal_escapes)] + pub static __MIDEN_ACCOUNT_COMPONENT_METADATA_BYTES: [u8; #link_section_bytes_len] = *#encoded_bytes_str; + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use super::*; + + #[test] + fn record_type_path_defaults_to_crate_root() { + let mut paths = HashMap::new(); + let type_ref = TypeRef { + wit_name: "struct-a".into(), + is_custom: true, + path: vec!["StructA".into()], + }; + + record_type_path(&mut paths, &type_ref, None); + + assert_eq!(paths.get("struct-a"), Some(&vec!["StructA".to_string()])); + } + + #[test] + fn record_type_path_applies_module_prefix() { + let mut paths = HashMap::new(); + let type_ref = TypeRef { + wit_name: "struct-a".into(), + is_custom: true, + path: vec!["StructA".into()], + }; + let prefix = vec!["foo".to_string(), "bar".to_string()]; + + record_type_path(&mut paths, &type_ref, Some(prefix.as_slice())); + + assert_eq!( + paths.get("struct-a"), + Some(&vec!["foo".to_string(), "bar".to_string(), "StructA".to_string()]) + ); + } + + #[test] + fn record_type_path_resolves_super_segments() { + let mut paths = HashMap::new(); + let type_ref = TypeRef { + wit_name: "struct-a".into(), + is_custom: true, + path: vec!["super".into(), "StructA".into()], + }; + let prefix = vec!["foo".to_string(), "bar".to_string()]; + + record_type_path(&mut paths, &type_ref, Some(prefix.as_slice())); + + assert_eq!(paths.get("struct-a"), Some(&vec!["foo".to_string(), "StructA".to_string()])); + } + + #[test] + fn build_path_tokens_generates_absolute_path() { + let segments = vec!["foo".to_string(), "bar".to_string(), "StructA".to_string()]; + let ident = format_ident!("StructA"); + let tokens = build_path_tokens(&segments, &ident).to_string(); + assert_eq!(tokens, "crate :: foo :: bar :: StructA"); + } + + #[test] + fn build_path_tokens_defaults_to_crate_root_for_single_segment() { + let segments = vec!["StructA".to_string()]; + let ident = format_ident!("StructA"); + let tokens = build_path_tokens(&segments, &ident).to_string(); + assert_eq!(tokens, "crate :: StructA"); + } + + #[test] + fn build_custom_with_entries_prefers_custom_paths() { + let exported_types = vec![ExportedTypeDef { + rust_name: "StructA".into(), + wit_name: "struct-a".into(), + kind: ExportedTypeKind::Record { fields: Vec::new() }, + }]; + let interface_path = "miden:component/path"; + let module_prefix: syn::Path = syn::parse_quote!(module::account); + let mut custom_paths = HashMap::new(); + custom_paths.insert("struct-a".into(), vec!["types".into(), "StructA".into()]); + + let (entries, _) = build_custom_with_entries( + &exported_types, + interface_path, + Some(&module_prefix), + &custom_paths, + ); + + assert_eq!(entries.len(), 1); + assert_eq!( + entries[0].to_string(), + "\"miden:component/path/struct-a\" : crate :: types :: StructA ," + ); + } +} diff --git a/sdk/base-macros/src/component_macro/storage.rs b/sdk/base-macros/src/component_macro/storage.rs new file mode 100644 index 000000000..bbb1eb484 --- /dev/null +++ b/sdk/base-macros/src/component_macro/storage.rs @@ -0,0 +1,122 @@ +use quote::quote; +use syn::spanned::Spanned; + +use crate::account_component_metadata::AccountComponentMetadataBuilder; + +/// Parsed arguments collected from a `#[storage(...)]` attribute. +struct StorageAttributeArgs { + slot: u8, + description: Option, + type_attr: Option, +} + +/// Attempts to parse a `#[storage(...)]` attribute and returns the extracted arguments. +fn parse_storage_attribute( + attr: &syn::Attribute, +) -> Result, syn::Error> { + if !attr.path().is_ident("storage") { + return Ok(None); + } + + let mut slot_value = None; + let mut description_value = None; + let mut type_value = None; + + let list = match &attr.meta { + syn::Meta::List(list) => list, + _ => return Err(syn::Error::new(attr.span(), "Expected #[storage(...)]")), + }; + + let parser = syn::meta::parser(|meta| { + if meta.path.is_ident("slot") { + let value_stream; + syn::parenthesized!(value_stream in meta.input); + let lit: syn::LitInt = value_stream.parse()?; + slot_value = Some(lit.base10_parse::()?); + Ok(()) + } else if meta.path.is_ident("description") { + let value = meta.value()?; + let lit: syn::LitStr = value.parse()?; + description_value = Some(lit.value()); + Ok(()) + } else if meta.path.is_ident("type") { + let value = meta.value()?; + let lit: syn::LitStr = value.parse()?; + type_value = Some(lit.value()); + Ok(()) + } else { + Err(meta.error("unrecognized storage attribute argument")) + } + }); + + list.parse_args_with(parser)?; + + let slot = slot_value.ok_or_else(|| { + syn::Error::new(attr.span(), "missing required `slot(N)` argument in `storage` attribute") + })?; + + Ok(Some(StorageAttributeArgs { + slot, + description: description_value, + type_attr: type_value, + })) +} + +/// Processes component struct fields, recording storage metadata and building default +/// initializers. +pub fn process_storage_fields( + fields: &mut syn::FieldsNamed, + builder: &mut AccountComponentMetadataBuilder, +) -> Result, syn::Error> { + let mut field_inits = Vec::new(); + let mut errors = Vec::new(); + + for field in fields.named.iter_mut() { + let field_name = field.ident.as_ref().expect("Named field must have an identifier"); + let field_type = &field.ty; + let mut storage_args = None; + let mut attr_indices_to_remove = Vec::new(); + + for (attr_idx, attr) in field.attrs.iter().enumerate() { + match parse_storage_attribute(attr) { + Ok(Some(args)) => { + if storage_args.is_some() { + errors.push(syn::Error::new(attr.span(), "duplicate `storage` attribute")); + } + storage_args = Some(args); + attr_indices_to_remove.push(attr_idx); + } + Ok(None) => {} + Err(e) => errors.push(e), + } + } + + for (removed_count, idx_to_remove) in attr_indices_to_remove.into_iter().enumerate() { + field.attrs.remove(idx_to_remove - removed_count); + } + + if let Some(args) = storage_args { + let slot = args.slot; + field_inits.push(quote! { + #field_name: #field_type { slot: #slot } + }); + + builder.add_storage_entry( + &field_name.to_string(), + args.description, + args.slot, + field_type, + args.type_attr, + ); + } else { + errors + .push(syn::Error::new(field.span(), "field is missing the `#[storage]` attribute")); + } + } + + if let Some(first_error) = errors.into_iter().next() { + Err(first_error) + } else { + Ok(field_inits) + } +} diff --git a/sdk/base-macros/src/export_type.rs b/sdk/base-macros/src/export_type.rs new file mode 100644 index 000000000..575313966 --- /dev/null +++ b/sdk/base-macros/src/export_type.rs @@ -0,0 +1,46 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, Item}; + +use crate::types::{exported_type_from_enum, exported_type_from_struct, register_export_type}; + +pub(crate) fn expand(attr: TokenStream, item: TokenStream) -> TokenStream { + if !attr.is_empty() { + return syn::Error::new_spanned( + proc_macro2::TokenStream::from(attr), + "#[export_type] does not accept arguments", + ) + .into_compile_error() + .into(); + } + + let item = parse_macro_input!(item as Item); + + match item { + Item::Struct(item_struct) => { + let span = item_struct.ident.span(); + match exported_type_from_struct(&item_struct) { + Ok(def) => match register_export_type(def, span) { + Ok(()) => quote! { #item_struct }.into(), + Err(err) => err.to_compile_error().into(), + }, + Err(err) => err.to_compile_error().into(), + } + } + Item::Enum(item_enum) => { + let span = item_enum.ident.span(); + match exported_type_from_enum(&item_enum) { + Ok(def) => match register_export_type(def, span) { + Ok(()) => quote! { #item_enum }.into(), + Err(err) => err.to_compile_error().into(), + }, + Err(err) => err.to_compile_error().into(), + } + } + other => { + syn::Error::new_spanned(other, "#[export_type] may only be applied to structs or enums") + .into_compile_error() + .into() + } + } +} diff --git a/sdk/base-macros/src/generate.rs b/sdk/base-macros/src/generate.rs new file mode 100644 index 000000000..eca0bff50 --- /dev/null +++ b/sdk/base-macros/src/generate.rs @@ -0,0 +1,442 @@ +use std::{ + env, fs, + io::ErrorKind, + path::{Path, PathBuf}, +}; + +use proc_macro2::{Literal, Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{ + parse::{Parse, ParseStream}, + Error, LitStr, Token, +}; + +/// File name for the embedded Miden SDK WIT . +const SDK_WIT_FILE_NAME: &str = "miden.wit"; +/// Embedded Miden SDK WIT source. +pub(crate) const SDK_WIT_SOURCE: &str = include_str!("../wit/miden.wit"); + +#[derive(Default)] +struct GenerateArgs { + inline: Option, + with: Option, +} + +impl Parse for GenerateArgs { + fn parse(input: ParseStream<'_>) -> syn::Result { + let mut args = GenerateArgs::default(); + + while !input.is_empty() { + let ident: syn::Ident = input.parse()?; + let name = ident.to_string(); + input.parse::()?; + + if name == "inline" { + if args.inline.is_some() { + return Err(syn::Error::new(ident.span(), "duplicate `inline` argument")); + } + args.inline = Some(input.parse()?); + } else if name == "with" { + if args.with.is_some() { + return Err(syn::Error::new(ident.span(), "duplicate `with` argument")); + } + let content; + syn::braced!(content in input); + let tokens = content.parse::()?; + args.with = Some(tokens); + } else { + return Err(syn::Error::new( + ident.span(), + format!("unsupported generate! argument `{name}`"), + )); + } + + if input.peek(Token![,]) { + let _ = input.parse::()?; + } + } + + Ok(args) + } +} + +/// Implements the expansion logic for the `generate!` macro. +pub(crate) fn expand(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input_tokens: proc_macro2::TokenStream = input.into(); + let args = if input_tokens.is_empty() { + GenerateArgs::default() + } else { + match syn::parse2::(input_tokens) { + Ok(parsed) => parsed, + Err(err) => return err.to_compile_error().into(), + } + }; + + let resolve_opts = manifest_paths::ResolveOptions { + allow_missing_local_wit: args.inline.is_some(), + }; + + match manifest_paths::resolve_wit_paths(resolve_opts) { + Ok(config) => { + if config.paths.is_empty() { + return Error::new( + Span::call_site(), + "no WIT dependencies declared under \ + [package.metadata.component.target.dependencies]", + ) + .to_compile_error() + .into(); + } + + let path_literals: Vec<_> = + config.paths.iter().map(|path| Literal::string(path)).collect(); + + let inline_clause = args.inline.as_ref().map(|src| quote! { inline: #src, }); + let custom_with_entries = args.with.unwrap_or_else(TokenStream2::new); + + let inline_world = args + .inline + .as_ref() + .and_then(|src| manifest_paths::extract_world_name(&src.value())); + let world_value = inline_world.or_else(|| config.world.clone()); + + if args.inline.is_some() && world_value.is_none() { + return Error::new( + Span::call_site(), + "failed to detect world name for inline WIT provided to generate!", + ) + .to_compile_error() + .into(); + } + + let world_clause = world_value.as_ref().map(|world| { + let literal = Literal::string(world); + quote! { world: #literal, } + }); + + quote! { + // Wrap the bindings in the `bindings` module since `generate!` makes a top level + // module named after the package namespace which is `miden` for all our projects + // so its conflicts with the `miden` crate (SDK) + #[doc(hidden)] + #[allow(dead_code)] + pub mod bindings { + ::miden::wit_bindgen::generate!({ + #inline_clause + #world_clause + path: [#(#path_literals),*], + generate_all, + runtime_path: "::miden::wit_bindgen::rt", + // path to use in the generated `export!` macro + default_bindings_module: "bindings", + with: { + #custom_with_entries + "miden:base/core-types@1.0.0": generate, + "miden:base/core-types@1.0.0/felt": ::miden::Felt, + "miden:base/core-types@1.0.0/word": ::miden::Word, + "miden:base/core-types@1.0.0/asset": ::miden::Asset, + "miden:base/core-types@1.0.0/account-id": ::miden::AccountId, + "miden:base/core-types@1.0.0/tag": ::miden::Tag, + "miden:base/core-types@1.0.0/note-type": ::miden::NoteType, + "miden:base/core-types@1.0.0/recipient": ::miden::Recipient, + "miden:base/core-types@1.0.0/note-idx": ::miden::NoteIdx, + }, + }); + } + } + .into() + } + Err(err) => err.to_compile_error().into(), + } +} + +mod manifest_paths { + use toml::Value; + + use super::*; + use crate::util::bundled_wit_folder; + + /// WIT metadata extracted from the consuming crate. + pub struct ResolvedWit { + pub paths: Vec, + pub world: Option, + } + + #[derive(Default)] + pub struct ResolveOptions { + pub allow_missing_local_wit: bool, + } + + /// Collects WIT search paths and the target world from `Cargo.toml` + local files. + pub fn resolve_wit_paths(options: ResolveOptions) -> Result { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|err| { + Error::new(Span::call_site(), format!("failed to read CARGO_MANIFEST_DIR: {err}")) + })?; + let manifest_path = Path::new(&manifest_dir).join("Cargo.toml"); + + let manifest_content = fs::read_to_string(&manifest_path).map_err(|err| { + Error::new( + Span::call_site(), + format!("failed to read manifest '{}': {err}", manifest_path.display()), + ) + })?; + + let manifest: Value = manifest_content.parse().map_err(|err| { + Error::new( + Span::call_site(), + format!("failed to parse manifest '{}': {err}", manifest_path.display()), + ) + })?; + + let canonical_prelude_dir = ensure_sdk_wit()?; + + let mut resolved = Vec::new(); + + let prelude_dir = canonical_prelude_dir + .to_str() + .ok_or_else(|| { + Error::new( + Span::call_site(), + format!("path '{}' contains invalid UTF-8", canonical_prelude_dir.display()), + ) + })? + .to_owned(); + + resolved.push(prelude_dir); + + if let Some(dependencies) = manifest + .get("package") + .and_then(Value::as_table) + .and_then(|package| package.get("metadata")) + .and_then(Value::as_table) + .and_then(|metadata| metadata.get("component")) + .and_then(Value::as_table) + .and_then(|component| component.get("target")) + .and_then(Value::as_table) + .and_then(|target| target.get("dependencies")) + .and_then(Value::as_table) + { + for (name, dependency) in dependencies.iter() { + let table = dependency.as_table().ok_or_else(|| { + Error::new( + Span::call_site(), + format!( + "dependency '{name}' under \ + [package.metadata.component.target.dependencies] must be a table" + ), + ) + })?; + + let path_value = table.get("path").and_then(Value::as_str).ok_or_else(|| { + Error::new( + Span::call_site(), + format!("dependency '{name}' is missing a 'path' entry"), + ) + })?; + + let raw_path = PathBuf::from(path_value); + let absolute = if raw_path.is_absolute() { + raw_path + } else { + Path::new(&manifest_dir).join(&raw_path) + }; + + let canonical = fs::canonicalize(&absolute).unwrap_or_else(|_| absolute.clone()); + + let metadata = fs::metadata(&canonical).map_err(|err| { + Error::new( + Span::call_site(), + format!( + "failed to read metadata for dependency '{name}' path '{}': {err}", + canonical.display() + ), + ) + })?; + + let search_path = if metadata.is_dir() { + canonical + } else if let Some(parent) = canonical.parent() { + parent.to_path_buf() + } else { + return Err(Error::new( + Span::call_site(), + format!( + "dependency '{name}' path '{}' does not have a parent directory", + canonical.display() + ), + )); + }; + + let path_str = search_path.to_str().ok_or_else(|| { + Error::new( + Span::call_site(), + format!("dependency '{name}' path contains invalid UTF-8"), + ) + })?; + + if !resolved.iter().any(|existing| existing == path_str) { + resolved.push(path_str.to_owned()); + } + } + } + + let local_wit_root = Path::new(&manifest_dir).join("wit"); + let mut world = None; + + if local_wit_root.exists() && !options.allow_missing_local_wit { + let local_root = fs::canonicalize(&local_wit_root).unwrap_or(local_wit_root); + let local_root_str = local_root.to_str().ok_or_else(|| { + Error::new( + Span::call_site(), + format!("path '{}' contains invalid UTF-8", local_root.display()), + ) + })?; + if !resolved.iter().any(|existing| existing == local_root_str) { + resolved.push(local_root_str.to_owned()); + } + world = detect_world_name(&local_root)?; + } + + Ok(ResolvedWit { + paths: resolved, + world, + }) + } + + /// Ensures the embedded Miden SDK WIT is materialized in the project's folder. + fn ensure_sdk_wit() -> Result { + let autogenerated_wit_folder = bundled_wit_folder()?; + + let sdk_wit_path = autogenerated_wit_folder.join(super::SDK_WIT_FILE_NAME); + let sdk_version: &str = env!("CARGO_PKG_VERSION"); + let expected_source = format!( + "/// NOTE: This file is auto-generated from the Miden SDK.\n/// Version: \ + v{sdk_version}\n/// Any manual edits will be overwritten.\n\n{SDK_WIT_SOURCE}" + ); + let should_write_wit = match fs::read_to_string(&sdk_wit_path) { + Ok(existing) => existing != expected_source, + Err(err) if err.kind() == ErrorKind::NotFound => true, + Err(err) => { + return Err(Error::new( + Span::call_site(), + format!("failed to read '{}': {err}", sdk_wit_path.display()), + )); + } + }; + + if should_write_wit { + fs::write(&sdk_wit_path, expected_source).map_err(|err| { + Error::new( + Span::call_site(), + format!("failed to write '{}': {err}", sdk_wit_path.display()), + ) + })?; + } + + Ok(fs::canonicalize(&autogenerated_wit_folder).unwrap_or(autogenerated_wit_folder)) + } + + /// Scans the component's `wit` directory to find the default world. + fn detect_world_name(wit_root: &Path) -> Result, Error> { + let mut entries = fs::read_dir(wit_root) + .map_err(|err| { + Error::new( + Span::call_site(), + format!("failed to read '{}': {err}", wit_root.display()), + ) + })? + .collect::, _>>() + .map_err(|err| { + Error::new( + Span::call_site(), + format!("failed to iterate '{}': {err}", wit_root.display()), + ) + })?; + entries.sort_by_key(|entry| entry.file_name()); + + for entry in entries { + let path = entry.path(); + if path.file_name().is_some_and(|name| name == "deps") { + continue; + } + if path.is_dir() { + continue; + } + if path.extension().and_then(|ext| ext.to_str()) != Some("wit") { + continue; + } + + if let Some((package, world)) = parse_package_and_world(&path)? { + return Ok(Some(format!("{package}/{world}"))); + } + } + + Ok(None) + } + + /// Parses a WIT source file for its package declaration and first world definition. + fn parse_package_and_world(path: &Path) -> Result, Error> { + let contents = fs::read_to_string(path).map_err(|err| { + Error::new( + Span::call_site(), + format!("failed to read WIT file '{}': {err}", path.display()), + ) + })?; + + let package = extract_package_name(&contents); + let world = extract_world_name(&contents); + + match (package, world) { + (Some(package), Some(world)) => Ok(Some((package, world))), + _ => Ok(None), + } + } + + /// Returns the package identifier from a WIT source string, if present. + fn extract_package_name(contents: &str) -> Option { + for line in contents.lines() { + let trimmed = strip_comment(line).trim_start(); + if let Some(rest) = trimmed.strip_prefix("package ") { + let mut token = rest.trim(); + if let Some(idx) = token.find(';') { + token = &token[..idx]; + } + let mut name = token.trim(); + if let Some(idx) = name.find('@') { + name = &name[..idx]; + } + return Some(name.trim().to_string()); + } + } + None + } + + /// Returns the first world identifier from a WIT source string, if present. + pub(super) fn extract_world_name(contents: &str) -> Option { + for line in contents.lines() { + let trimmed = strip_comment(line).trim_start(); + if let Some(rest) = trimmed.strip_prefix("world ") { + let mut name = String::new(); + for ch in rest.trim().chars() { + if ch.is_alphanumeric() || ch == '-' || ch == '_' { + name.push(ch); + } else { + break; + } + } + if !name.is_empty() { + return Some(name); + } + } + } + None + } + + /// Strips line comments starting with `//` from the provided source line. + fn strip_comment(line: &str) -> &str { + match line.split_once("//") { + Some((before, _)) => before, + None => line, + } + } +} diff --git a/sdk/base-macros/src/lib.rs b/sdk/base-macros/src/lib.rs new file mode 100644 index 000000000..a93e60893 --- /dev/null +++ b/sdk/base-macros/src/lib.rs @@ -0,0 +1,266 @@ +//! Module for Miden SDK macros +//! +//! ### How to use WIT generation. +//! +//! 1. Add `#[component]` on you `impl MyAccountType {`. +//! 2. Add `#[export_type]` on every defined type that is used in the public(exported) method +//! signature. +//! +//! Example: +//! ```rust +//! +//! #[export_type] +//! pub struct StructA { +//! pub foo: Word, +//! pub asset: Asset, +//! } +//! +//! #[export_type] +//! pub struct StructB { +//! pub bar: Felt, +//! pub baz: Felt, +//! } +//! +//! #[component] +//! struct MyAccount; +//! +//! #[component] +//! impl MyAccount { +//! pub fn (&self, a: StructA) -> StructB { +//! ... +//! } +//! } +//! ``` +//! + +//! ### Escape hatch (disable WIT generation) +//! +//! in a small fraction of the cases where the WIT generation is not possible (think a type defined +//! only in an external WIT file) or not desirable the WIT generation can be disabled: +//! +//! To disable WIT interface generation: +//! - Don't use `#[component]` attribute macro in the `impl MyAccountType` section; +//! +//! To use manually crafted WIT interface: +//! - Put the WIT file in the `wit` folder; +//! - call `miden::generate!();` and `bindings::export!(MyAccountType);` +//! - implement `impl Guest for MyAccountType`; + +use crate::script::ScriptConfig; + +extern crate proc_macro; + +mod account_component_metadata; +mod boilerplate; +mod component_macro; +mod export_type; +mod generate; +mod script; +mod types; +mod util; + +/// Generates the WIT interface and storage metadata. +/// +/// **NOTE:** Mark each type used in the public method with `#[export_type]` attribute macro. +/// +/// To disable WIT interface generation: +/// - don't use `#[component]` attribute macro in the `impl MyAccountType` section; +/// +/// To use manually crafted WIT interface: +/// - put WIT interface file in the `wit` folder; +/// - call `miden::generate!();` and `bindings::export!(MyAccountType);` +/// - implement `impl Guest for MyAccountType`; +#[proc_macro_attribute] +pub fn component( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + component_macro::component(attr, item) +} + +/// Generates an equvalent type in the WIT interface. +/// Required for every type mentioned in the public methods of an account component. +/// +/// Intended to be used together with `#[component]` attribute macro. +#[proc_macro_attribute] +pub fn export_type( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + export_type::expand(attr, item) +} + +/// Marks the function as a note script +#[proc_macro_attribute] +pub fn note_script( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + script::expand( + attr, + item, + ScriptConfig { + export_interface: "miden:base/note-script@1.0.0", + guest_trait_path: "self::bindings::exports::miden::base::note_script::Guest", + }, + ) +} + +/// Marks the function as a transaction script +#[proc_macro_attribute] +pub fn tx_script( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + script::expand( + attr, + item, + ScriptConfig { + export_interface: "miden:base/transaction-script@1.0.0", + guest_trait_path: "self::bindings::exports::miden::base::transaction_script::Guest", + }, + ) +} + +/// Generate bindings for an input WIT document. +/// +/// The macro here will parse [WIT] as input and generate Rust bindings to work with the `world` +/// that's specified in the [WIT]. For a primer on WIT see [this documentation][WIT] and for a +/// primer on worlds see [here][worlds]. +/// +/// [WIT]: https://component-model.bytecodealliance.org/design/wit.html +/// [worlds]: https://component-model.bytecodealliance.org/design/worlds.html +/// +/// For documentation on each option, see below. +/// +/// ## Exploring generated bindings +/// +/// Once bindings have been generated they can be explored via a number of means +/// to see what was generated: +/// +/// * Using `cargo doc` should render all of the generated bindings in addition +/// to the original comments in the WIT format itself. +/// * If your IDE supports `rust-analyzer` code completion should be available +/// to explore and see types. +/// +/// ## Namespacing +/// +/// The generated bindings are put in `bindings` module. +/// In WIT, worlds can import and export `interface`s, functions, and types. Each +/// `interface` can either be "anonymous" and only named within the context of a +/// `world` or it can have a "package ID" associated with it. Names in Rust take +/// into account all the names associated with a WIT `interface`. For example +/// the package ID `foo:bar/baz` would create a `mod foo` which contains a `mod +/// bar` which contains a `mod baz`. +/// +/// WIT imports and exports are additionally separated into their own +/// namespaces. Imports are generated at the level of the `generate!` macro +/// where exports are generated under an `exports` namespace. +/// +/// ## Exports: The `export!` macro +/// +/// Components are created by having exported WebAssembly functions with +/// specific names, and these functions are not created when `generate!` is +/// invoked. Instead these functions are created afterwards once you've defined +/// your own type an implemented the various `trait`s for it. The +/// `#[unsafe(no_mangle)]` functions that will become the component are created +/// with the generated `export!` macro. +/// +/// Each call to `generate!` will itself generate a macro called `export!`. +/// The macro's first argument is the name of a type that implements the traits +/// generated: +/// +/// ``` +/// use miden::generate; +/// +/// generate!({ +/// inline: r#" +/// package my:test; +/// +/// world my-world { +/// # export hello: func(); +/// // ... +/// } +/// "#, +/// }); +/// +/// struct MyComponent; +/// +/// impl Guest for MyComponent { +/// # fn hello() {} +/// // ... +/// } +/// +/// export!(MyComponent); +/// # +/// # fn main() {} +/// ``` +/// +/// This argument is a Rust type which implements the `Guest` traits generated +/// by `generate!`. Note that all `Guest` traits must be implemented for the +/// type provided or an error will be generated. +/// +/// ## Options to `generate!` +/// +/// The full list of options that can be passed to the `generate!` macro are as +/// follows. Note that there are no required options, they all have default +/// values. +/// +/// +/// ``` +/// use miden::generate; +/// # macro_rules! generate { ($($t:tt)*) => () } +/// +/// generate!({ +/// // Enables passing "inline WIT". If specified this is the default +/// // package that a world is selected from. Any dependencies that this +/// // inline WIT refers to must be defined in the `path` option above. +/// // +/// // By default this is not specified. +/// inline: " +/// world my-world { +/// import wasi:cli/imports; +/// +/// export my-run: func() +/// } +/// ", +/// +/// // When generating bindings for interfaces that are not defined in the +/// // same package as `world`, this option can be used to either generate +/// // those bindings or point to already generated bindings. +/// // For example, if your world refers to WASI types then the `wasi` crate +/// // already has generated bindings for all WASI types and structures. In this +/// // situation the key `with` here can be used to use those types +/// // elsewhere rather than regenerating types. +/// // If for example your world refers to some type and you want to use +/// // your own custom implementation of that type then you can specify +/// // that here as well. There is a requirement on the remapped (custom) +/// // type to have the same internal structure and identical to what would +/// // wit-bindgen generate (including alignment, etc.), since +/// // lifting/lowering uses its fields directly. +/// // +/// // If, however, your world refers to interfaces for which you don't have +/// // already generated bindings then you can use the special `generate` value +/// // to have those bindings generated. +/// // +/// // The `with` key here works for interfaces and individual types. +/// // +/// // When an interface or type is specified here no bindings will be +/// // generated at all. It's assumed bindings are fully generated +/// // somewhere else. This is an indicator that any further references to types +/// // defined in these interfaces should use the upstream paths specified +/// // here instead. +/// // +/// // Any unused keys in this map are considered an error. +/// with: { +/// "wasi:io/poll": wasi::io::poll, +/// "some:package/my-interface": generate, +/// "some:package/my-interface/my-type": my_crate::types::MyType, +/// }, +/// }); +/// ``` +/// +#[proc_macro] +pub fn generate(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + generate::expand(input) +} diff --git a/sdk/base-macros/src/script.rs b/sdk/base-macros/src/script.rs new file mode 100644 index 000000000..8d82a080c --- /dev/null +++ b/sdk/base-macros/src/script.rs @@ -0,0 +1,381 @@ +use std::{env, fs, path::Path}; + +use proc_macro2::{Literal, Span}; +use quote::quote; +use syn::{parse_macro_input, spanned::Spanned, FnArg, ItemFn, Pat, PatIdent}; +use toml::Value; + +use crate::{boilerplate::runtime_boilerplate, util::generated_wit_folder_at}; + +const SCRIPT_PACKAGE_VERSION: &str = "1.0.0"; + +/// Configuration describing the script macro expansion details. +pub(crate) struct ScriptConfig { + /// Fully-qualified export interface emitted by the generated WIT world. + pub export_interface: &'static str, + /// Fully-qualified path to the guest trait implemented by the generated struct. + pub guest_trait_path: &'static str, +} + +/// Shared expansion logic used by both `#[note_script]` and `#[tx_script]`. +pub(crate) fn expand( + attr: proc_macro::TokenStream, + item: proc_macro::TokenStream, + config: ScriptConfig, +) -> proc_macro::TokenStream { + if !attr.is_empty() { + return syn::Error::new(Span::call_site(), "this attribute does not accept arguments") + .into_compile_error() + .into(); + } + + let input_fn = parse_macro_input!(item as ItemFn); + + let fn_ident = &input_fn.sig.ident; + if fn_ident != "run" { + return syn::Error::new(fn_ident.span(), "this attribute must be applied to `fn run`") + .into_compile_error() + .into(); + } + + if input_fn.sig.receiver().is_some() { + return syn::Error::new(input_fn.sig.span(), "this attribute cannot target methods") + .into_compile_error() + .into(); + } + + let mut call_args: Vec = Vec::new(); + for arg in &input_fn.sig.inputs { + match arg { + FnArg::Typed(pat_type) => match pat_type.pat.as_ref() { + Pat::Ident(PatIdent { ident, .. }) => call_args.push(ident.clone()), + other => { + return syn::Error::new( + other.span(), + "function arguments must be simple identifiers", + ) + .into_compile_error() + .into(); + } + }, + FnArg::Receiver(receiver) => { + return syn::Error::new(receiver.span(), "unexpected receiver argument") + .into_compile_error() + .into(); + } + } + } + + let inline_wit = match build_script_wit(Span::call_site(), config.export_interface) { + Ok(wit) => wit, + Err(err) => return err.into_compile_error().into(), + }; + let inline_literal = Literal::string(&inline_wit); + let struct_ident = quote::format_ident!("Struct"); + + let fn_inputs = &input_fn.sig.inputs; + let fn_output = &input_fn.sig.output; + + let call = if call_args.is_empty() { + quote! { #fn_ident() } + } else { + quote! { #fn_ident(#(#call_args),*) } + }; + + let export_path: syn::Path = match syn::parse_str(config.guest_trait_path) { + Ok(path) => path, + Err(err) => { + return syn::Error::new( + Span::call_site(), + format!("failed to parse guest trait path '{}': {err}", config.guest_trait_path), + ) + .into_compile_error() + .into(); + } + }; + + let runtime_boilerplate = runtime_boilerplate(); + + let expanded = quote! { + #runtime_boilerplate + + #input_fn + + ::miden::generate!(inline = #inline_literal); + self::bindings::export!(#struct_ident); + + /// Guest entry point generated by the Miden script attribute. + pub struct #struct_ident; + + impl #export_path for #struct_ident { + fn run(#fn_inputs) #fn_output { + #call; + } + } + }; + + expanded.into() +} + +fn build_script_wit( + error_span: Span, + export_interface: &'static str, +) -> Result { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|err| { + syn::Error::new(error_span, format!("failed to read CARGO_MANIFEST_DIR: {err}")) + })?; + let manifest_path = Path::new(&manifest_dir).join("Cargo.toml"); + let manifest_content = fs::read_to_string(&manifest_path).map_err(|err| { + syn::Error::new( + error_span, + format!("failed to read manifest '{}': {err}", manifest_path.display()), + ) + })?; + + let manifest: Value = manifest_content.parse().map_err(|err| { + syn::Error::new( + error_span, + format!("failed to parse manifest '{}': {err}", manifest_path.display()), + ) + })?; + + let package_table = manifest + .get("package") + .and_then(Value::as_table) + .ok_or_else(|| syn::Error::new(error_span, "manifest missing [package] table"))?; + + let crate_name = package_table + .get("name") + .and_then(Value::as_str) + .ok_or_else(|| syn::Error::new(error_span, "manifest package missing `name`"))?; + + let component_package = package_table + .get("metadata") + .and_then(Value::as_table) + .and_then(|meta| meta.get("component")) + .and_then(Value::as_table) + .and_then(|component| component.get("package")) + .and_then(Value::as_str) + .ok_or_else(|| { + syn::Error::new(error_span, "manifest missing package.metadata.component.package") + })?; + + let mut imports = collect_script_imports(&manifest_dir, package_table, error_span)?; + imports.sort(); + + let package_with_version = if component_package.contains('@') { + component_package.to_string() + } else { + format!("{component_package}@{SCRIPT_PACKAGE_VERSION}") + }; + + let world_name = format!("{}-world", crate_name.replace('_', "-")); + + let mut wit_source = String::new(); + wit_source.push_str(&format!("package {package_with_version};\n\n")); + wit_source.push_str(&format!("world {world_name} {{\n")); + for import in imports { + wit_source.push_str(" import "); + wit_source.push_str(&import); + wit_source.push_str(";\n"); + } + wit_source.push_str(&format!(" export {export_interface};\n")); + wit_source.push('}'); + wit_source.push('\n'); + + Ok(wit_source) +} + +fn collect_script_imports( + manifest_dir: &str, + package_table: &toml::value::Table, + error_span: Span, +) -> Result, syn::Error> { + let dependencies = package_table + .get("metadata") + .and_then(Value::as_table) + .and_then(|meta| meta.get("miden")) + .and_then(Value::as_table) + .and_then(|miden| miden.get("dependencies")) + .and_then(Value::as_table); + + let mut imports = Vec::new(); + + if let Some(dep_table) = dependencies { + for (dep_name, dep_value) in dep_table.iter() { + let dep_config = dep_value.as_table().ok_or_else(|| { + syn::Error::new( + error_span, + format!( + "dependency '{dep_name}' under package.metadata.miden.dependencies must \ + be a table" + ), + ) + })?; + + let dependency_path = + dep_config.get("path").and_then(Value::as_str).ok_or_else(|| { + syn::Error::new( + error_span, + format!( + "dependency '{dep_name}' under package.metadata.miden.dependencies is \ + missing a 'path' entry" + ), + ) + })?; + + let absolute_path = Path::new(manifest_dir).join(dependency_path); + let canonical = fs::canonicalize(&absolute_path).map_err(|err| { + syn::Error::new( + error_span, + format!( + "failed to canonicalize dependency '{dep_name}' path '{}': {err}", + absolute_path.display() + ), + ) + })?; + + let dependency_info = parse_dependency_wit(&canonical).map_err(|msg| { + syn::Error::new( + error_span, + format!("failed to process WIT for dependency '{dep_name}': {msg}"), + ) + })?; + + let version_suffix = dependency_info + .version + .as_ref() + .map(|version| format!("@{version}")) + .unwrap_or_default(); + + let interface = dependency_info + .exports + .first() + .ok_or_else(|| { + syn::Error::new( + error_span, + format!( + "dependency '{dep_name}' did not export any interfaces in its world \ + definition" + ), + ) + })? + .clone(); + + imports.push(format!("{}/{interface}{version_suffix}", dependency_info.package)); + } + } + + Ok(imports) +} + +struct DependencyWit { + package: String, + version: Option, + exports: Vec, +} + +fn parse_dependency_wit(root: &Path) -> Result { + if root.is_file() { + return parse_wit_file(root)?.ok_or_else(|| { + format!("WIT file '{}' does not contain a world export", root.display()) + }); + } + + let default_wit_dir = root.join("wit"); + let generated_wit_dir = generated_wit_folder_at(root)?; + let wit_dirs = [default_wit_dir, generated_wit_dir]; + for wit_dir in &wit_dirs { + if !wit_dir.exists() { + continue; + } + let mut entries = fs::read_dir(wit_dir) + .map_err(|err| format!("failed to read '{}': {err}", wit_dir.display()))? + .collect::, _>>() + .map_err(|err| format!("failed to iterate '{}': {err}", wit_dir.display()))?; + + entries.sort_by_key(|entry| entry.file_name()); + + for entry in entries { + let path = entry.path(); + if path.is_dir() { + if path.file_name().is_some_and(|name| name == "deps") { + continue; + } + continue; + } + + if path.extension().and_then(|ext| ext.to_str()) != Some("wit") { + continue; + } + + if let Some(info) = parse_wit_file(&path)? { + return Ok(info); + } + } + } + + Err(format!("no WIT world definition found in directories '{wit_dirs:?}'")) +} + +fn parse_wit_file(path: &Path) -> Result, String> { + let contents = fs::read_to_string(path) + .map_err(|err| format!("failed to read WIT file '{}': {err}", path.display()))?; + + let (package, version) = match extract_package_identifier(&contents) { + Some(parts) => parts, + None => return Ok(None), + }; + + let exports = extract_world_exports(&contents); + if exports.is_empty() { + return Ok(None); + } + + Ok(Some(DependencyWit { + package, + version, + exports, + })) +} + +fn extract_package_identifier(contents: &str) -> Option<(String, Option)> { + for line in contents.lines() { + let trimmed = strip_comment(line).trim_start(); + if let Some(rest) = trimmed.strip_prefix("package ") { + let token = rest.trim_end_matches(';').trim(); + if let Some((name, version)) = token.split_once('@') { + return Some((name.trim().to_string(), Some(version.trim().to_string()))); + } + return Some((token.to_string(), None)); + } + } + None +} + +fn extract_world_exports(contents: &str) -> Vec { + let mut exports = Vec::new(); + + for line in contents.lines() { + let trimmed = strip_comment(line).trim(); + if let Some(rest) = trimmed.strip_prefix("export ") { + let rest = rest.trim_end_matches(';').trim(); + let interface = match rest.split_once(':') { + Some((_, interface)) => interface.trim(), + None => rest, + }; + if !interface.is_empty() { + exports.push(interface.to_string()); + } + } + } + + exports +} + +fn strip_comment(line: &str) -> &str { + match line.split_once("//") { + Some((before, _)) => before, + None => line, + } +} diff --git a/sdk/base-macros/src/types.rs b/sdk/base-macros/src/types.rs new file mode 100644 index 000000000..ea01d012f --- /dev/null +++ b/sdk/base-macros/src/types.rs @@ -0,0 +1,292 @@ +use std::{ + collections::{HashMap, HashSet}, + sync::{Mutex, OnceLock}, +}; + +static EXPORTED_TYPES: OnceLock>> = OnceLock::new(); + +use heck::ToKebabCase; +use proc_macro2::Span; +use syn::{spanned::Spanned, ItemStruct, Type}; + +use crate::generate::SDK_WIT_SOURCE; + +#[derive(Clone, Debug)] +pub(crate) struct TypeRef { + pub(crate) wit_name: String, + pub(crate) is_custom: bool, + pub(crate) path: Vec, +} + +#[derive(Clone, Debug)] +pub(crate) struct ExportedField { + pub(crate) name: String, + pub(crate) ty: TypeRef, +} + +#[derive(Clone, Debug)] +pub(crate) struct ExportedVariant { + pub(crate) wit_name: String, + pub(crate) payload: Option, +} + +#[derive(Clone, Debug)] +pub(crate) enum ExportedTypeKind { + Record { fields: Vec }, + Variant { variants: Vec }, +} + +#[derive(Clone, Debug)] +pub(crate) struct ExportedTypeDef { + pub(crate) rust_name: String, + pub(crate) wit_name: String, + pub(crate) kind: ExportedTypeKind, +} + +pub(crate) fn register_export_type(def: ExportedTypeDef, _span: Span) -> Result<(), syn::Error> { + let registry = EXPORTED_TYPES.get_or_init(|| Mutex::new(Vec::new())); + let mut registry = registry.lock().expect("mutex poisoned"); + if let Some(existing) = registry.iter_mut().find(|existing| existing.wit_name == def.wit_name) { + *existing = def; + return Ok(()); + } + registry.push(def); + Ok(()) +} + +pub(crate) fn registered_export_types() -> Vec { + let registry = EXPORTED_TYPES.get_or_init(|| Mutex::new(Vec::new())); + registry.lock().expect("mutex poisoned").clone() +} + +pub(crate) fn registered_export_type_map() -> HashMap { + registered_export_types() + .into_iter() + .map(|def| (def.rust_name.clone(), def)) + .collect() +} + +pub(crate) fn map_type_to_type_ref( + ty: &Type, + exported_types: &HashMap, +) -> Result { + match ty { + Type::Reference(reference) => Err(syn::Error::new( + reference.span(), + "references are not supported in component interfaces or exported types", + )), + Type::Group(group) => map_type_to_type_ref(&group.elem, exported_types), + Type::Paren(paren) => map_type_to_type_ref(&paren.elem, exported_types), + Type::Path(path) => { + let last = path.path.segments.last().ok_or_else(|| { + syn::Error::new(ty.span(), "unsupported type in component interface") + })?; + + if !last.arguments.is_empty() { + return Err(syn::Error::new( + last.span(), + "generic type arguments are not supported in exported types", + )); + } + + let ident = last.ident.to_string(); + if ident.is_empty() { + return Err(syn::Error::new( + ty.span(), + "unsupported type in component interface; identifier cannot be empty", + )); + } + + let path_segments: Vec = + path.path.segments.iter().map(|segment| segment.ident.to_string()).collect(); + let wit_name = ident.to_kebab_case(); + + if exported_types.contains_key(&ident) { + return Ok(TypeRef { + wit_name, + is_custom: true, + path: path_segments, + }); + } + + if sdk_core_type_names().contains(&wit_name) { + return Ok(TypeRef { + wit_name, + is_custom: false, + path: path_segments, + }); + } + + Ok(TypeRef { + wit_name, + is_custom: true, + path: path_segments, + }) + } + _ => Err(syn::Error::new( + ty.span(), + "unsupported type in component interface; only paths are supported", + )), + } +} + +fn sdk_core_type_names() -> &'static HashSet { + static NAMES: OnceLock> = OnceLock::new(); + NAMES.get_or_init(|| parse_wit_type_names(SDK_WIT_SOURCE)) +} + +fn parse_wit_type_names(source: &str) -> HashSet { + let mut names = HashSet::new(); + for line in source.lines() { + let trimmed = line.trim_start(); + if let Some(name) = extract_wit_type_name(trimmed, "record") { + names.insert(name); + continue; + } + if let Some(name) = extract_wit_type_name(trimmed, "variant") { + names.insert(name); + continue; + } + if let Some(name) = extract_wit_type_name(trimmed, "enum") { + names.insert(name); + continue; + } + if let Some(name) = extract_wit_type_name(trimmed, "flags") { + names.insert(name); + continue; + } + if let Some(name) = extract_wit_type_name(trimmed, "resource") { + names.insert(name); + continue; + } + if let Some(name) = extract_wit_type_name(trimmed, "type") { + names.insert(name); + continue; + } + } + names +} + +fn extract_wit_type_name(line: &str, keyword: &str) -> Option { + let prefix = format!("{keyword} "); + let rest = line.strip_prefix(&prefix)?; + let mut name = String::new(); + for ch in rest.chars() { + if ch.is_alphanumeric() || ch == '-' || ch == '_' { + name.push(ch); + } else { + break; + } + } + if name.is_empty() { + None + } else { + Some(name) + } +} + +pub(crate) fn exported_type_from_struct( + item_struct: &ItemStruct, +) -> Result { + let known_exported = registered_export_type_map(); + match &item_struct.fields { + syn::Fields::Named(named) => { + let mut fields = Vec::new(); + for field in &named.named { + let field_ident = field.ident.as_ref().ok_or_else(|| { + syn::Error::new(field.span(), "exported type fields must be named") + })?; + let field_ty = map_type_to_type_ref(&field.ty, &known_exported)?; + fields.push(ExportedField { + name: field_ident.to_string(), + ty: field_ty, + }); + } + + Ok(ExportedTypeDef { + rust_name: item_struct.ident.to_string(), + wit_name: item_struct.ident.to_string().to_kebab_case(), + kind: ExportedTypeKind::Record { fields }, + }) + } + syn::Fields::Unit => Ok(ExportedTypeDef { + rust_name: item_struct.ident.to_string(), + wit_name: item_struct.ident.to_string().to_kebab_case(), + kind: ExportedTypeKind::Record { fields: Vec::new() }, + }), + syn::Fields::Unnamed(_) => Err(syn::Error::new( + item_struct.ident.span(), + "tuple structs are not supported by #[export_type]", + )), + } +} + +#[cfg(test)] +mod tests; + +pub(crate) fn exported_type_from_enum( + item_enum: &syn::ItemEnum, +) -> Result { + let known_exported = registered_export_type_map(); + let mut variants = Vec::new(); + for variant in &item_enum.variants { + let wit_name = variant.ident.to_string().to_kebab_case(); + let payload = match &variant.fields { + syn::Fields::Unit => None, + syn::Fields::Unnamed(fields) => { + if fields.unnamed.len() != 1 { + return Err(syn::Error::new( + fields.span(), + "tuple variants in #[export_type] enums must have exactly one field", + )); + } + let field_ty = &fields.unnamed[0].ty; + let type_ref = map_type_to_type_ref(field_ty, &known_exported)?; + Some(type_ref) + } + syn::Fields::Named(named) => { + return Err(syn::Error::new( + named.span(), + "struct variants are not supported by #[export_type]", + )); + } + }; + + variants.push(ExportedVariant { wit_name, payload }); + } + + Ok(ExportedTypeDef { + rust_name: item_enum.ident.to_string(), + wit_name: item_enum.ident.to_string().to_kebab_case(), + kind: ExportedTypeKind::Variant { variants }, + }) +} + +pub(crate) fn ensure_custom_type_defined( + type_ref: &TypeRef, + exported_type_names: &HashSet, + span: Span, +) -> Result<(), syn::Error> { + if type_ref.is_custom && !exported_type_names.contains(&type_ref.wit_name) { + let rust_name = type_ref + .path + .last() + .cloned() + .unwrap_or_else(|| type_ref.wit_name.replace('-', "::")); + return Err(syn::Error::new( + span, + format!( + "type `{rust_name}` is used in the exported interface but is not exported; add \ + #[export_type] to its definition" + ), + )); + } + Ok(()) +} + +#[cfg(test)] +pub(crate) fn reset_export_type_registry_for_tests() { + if let Some(registry) = EXPORTED_TYPES.get() { + registry.lock().expect("mutex poisoned").clear(); + } +} diff --git a/sdk/base-macros/src/types/tests.rs b/sdk/base-macros/src/types/tests.rs new file mode 100644 index 000000000..2d2063948 --- /dev/null +++ b/sdk/base-macros/src/types/tests.rs @@ -0,0 +1,109 @@ +use std::collections::HashSet; + +use syn::parse_quote; + +use super::*; + +#[test] +fn emits_hint_for_missing_export_type() { + reset_export_type_registry_for_tests(); + let ty: Type = syn::parse_str("LocalType").unwrap(); + let exported = HashMap::new(); + let type_ref = map_type_to_type_ref(&ty, &exported).expect("type resolution should succeed"); + let exported_names = HashSet::new(); + let err = ensure_custom_type_defined(&type_ref, &exported_names, Span::call_site()) + .expect_err("expected failure"); + assert!( + err.to_string().contains("add #[export_type]"), + "error message missing hint: {err}" + ); +} + +#[test] +fn allows_sdk_type_without_export_attribute() { + reset_export_type_registry_for_tests(); + let ty: Type = syn::parse_str("Asset").unwrap(); + let exported = HashMap::new(); + let type_ref = map_type_to_type_ref(&ty, &exported).expect("asset should resolve"); + assert_eq!(type_ref.wit_name, "asset"); + assert!(!type_ref.is_custom); + let exported_names = HashSet::new(); + ensure_custom_type_defined(&type_ref, &exported_names, Span::call_site()) + .expect("core types require no export"); +} + +#[test] +fn struct_field_missing_export_type_hint() { + reset_export_type_registry_for_tests(); + let item: syn::ItemStruct = parse_quote! { + struct Foo { + value: LocalType, + } + }; + let def = exported_type_from_struct(&item).expect("struct definition should parse"); + let exported_names = HashSet::from([def.wit_name.clone()]); + if let ExportedTypeKind::Record { fields } = &def.kind { + let err = ensure_custom_type_defined(&fields[0].ty, &exported_names, Span::call_site()) + .expect_err("expected unresolved type error"); + assert!( + err.to_string().contains("add #[export_type]"), + "error message missing hint: {err}" + ); + } else { + panic!("expected record kind"); + } +} + +#[test] +fn enum_payload_missing_export_type_hint() { + reset_export_type_registry_for_tests(); + let item: syn::ItemEnum = parse_quote! { + enum Foo { + Variant(LocalType), + } + }; + let def = exported_type_from_enum(&item).expect("enum definition should parse"); + let exported_names = HashSet::from([def.wit_name.clone()]); + if let ExportedTypeKind::Variant { variants } = &def.kind { + if let Some(type_ref) = &variants[0].payload { + let err = ensure_custom_type_defined(type_ref, &exported_names, Span::call_site()) + .expect_err("expected unresolved type error"); + assert!( + err.to_string().contains("add #[export_type]"), + "error message missing hint: {err}" + ); + } else { + panic!("expected payload"); + } + } else { + panic!("expected variant kind"); + } +} + +#[test] +fn forward_reference_between_export_types_is_allowed() { + reset_export_type_registry_for_tests(); + + let first: syn::ItemStruct = parse_quote! { + struct First { + next: Second, + } + }; + let first_def = exported_type_from_struct(&first).expect("first struct should parse"); + + let second: syn::ItemStruct = parse_quote! { + struct Second { + value: Felt, + } + }; + let second_def = exported_type_from_struct(&second).expect("second struct should parse"); + + let exported_names = HashSet::from([first_def.wit_name.clone(), second_def.wit_name.clone()]); + + if let ExportedTypeKind::Record { fields } = &first_def.kind { + ensure_custom_type_defined(&fields[0].ty, &exported_names, Span::call_site()) + .expect("forward reference should resolve once type is exported"); + } else { + panic!("expected record kind"); + } +} diff --git a/sdk/base-macros/src/util.rs b/sdk/base-macros/src/util.rs new file mode 100644 index 000000000..0d90f2fbf --- /dev/null +++ b/sdk/base-macros/src/util.rs @@ -0,0 +1,61 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +use proc_macro2::Span; +use syn::Error; + +/// Folder within a project that holds bundled WIT files +const BUNDLED_WIT_DEPS_DIR: &str = "bundled-miden-wit"; + +/// The prefix for the folder within a project that holds autogenerated WIT files +const GENERATED_WIT_DIR: &str = "generated-wit"; + +fn target_folder() -> PathBuf { + let mut manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not set"); + manifest_dir.push_str("/target/"); + PathBuf::from(manifest_dir) +} + +pub fn bundled_wit_folder() -> Result { + let out_dir = target_folder(); + let wit_deps_dir = out_dir.join(BUNDLED_WIT_DEPS_DIR); + fs::create_dir_all(&wit_deps_dir).map_err(|err| { + Error::new( + Span::call_site(), + format!( + "failed to create WIT dependencies directory '{}': {err}", + wit_deps_dir.display() + ), + ) + })?; + Ok(wit_deps_dir) +} + +pub fn generated_wit_folder() -> Result { + let out_dir = target_folder(); + let wit_deps_dir = out_dir.join(GENERATED_WIT_DIR); + fs::create_dir_all(&wit_deps_dir).map_err(|err| { + Error::new( + Span::call_site(), + format!( + "failed to create WIT dependencies directory '{}': {err}", + wit_deps_dir.display() + ), + ) + })?; + Ok(wit_deps_dir) +} + +pub fn generated_wit_folder_at(manifest_dir: &Path) -> Result { + let out_dir = { manifest_dir.join("target/") }; + let wit_deps_dir = out_dir.join(GENERATED_WIT_DIR); + fs::create_dir_all(&wit_deps_dir).map_err(|err| { + format!( + "failed to create WIT dependencies directory '{}': {err}", + wit_deps_dir.display() + ) + })?; + Ok(wit_deps_dir) +} diff --git a/sdk/base-macros/wit/miden.wit b/sdk/base-macros/wit/miden.wit new file mode 100644 index 000000000..11786d5e5 --- /dev/null +++ b/sdk/base-macros/wit/miden.wit @@ -0,0 +1,175 @@ +package miden:base@1.0.0; + +/// Types to be used in tx-kernel interface +interface core-types { + /// Represents base field element in the field using Montgomery representation. + /// Internal values represent x * R mod M where R = 2^64 mod M and x in [0, M). + /// The backing type is `f64` but the internal values are always integer in the range [0, M). + /// Field modulus M = 2^64 - 2^32 + 1 + record felt { + /// We plan to use f32 as the backing type for the field element. It has the size that we need and + /// we don't plan to support floating point arithmetic in programs for Miden VM. + inner: f32, + } + + + /// A group of four field elements in the Miden base field. + // type word = tuple; + record word { + inner: tuple + } + + /// A cryptographic digest representing a 256-bit hash value. + /// This is a wrapper around `word` which contains 4 field elements. + record digest { + inner: word + } + + /// Unique identifier of an account. + /// + /// # Layout + /// + /// An `AccountId` consists of two field elements, where the first is called the prefix and the + /// second is called the suffix. It is laid out as follows: + /// + /// prefix: [hash (56 bits) | storage mode (2 bits) | type (2 bits) | version (4 bits)] + /// suffix: [zero bit | hash (55 bits) | 8 zero bits] + record account-id { + prefix: felt, + suffix: felt + } + + /// Creates a new account ID from a field element. + //account-id-from-felt: func(felt: felt) -> account-id; + + /// Recipient of the note, i.e., hash(hash(hash(serial_num, [0; 4]), note_script_hash), input_hash) + record recipient { + inner: word + } + + record tag { + inner: felt + } + + /// A fungible or a non-fungible asset. + /// + /// All assets are encoded using a single word (4 elements) such that it is easy to determine the + /// type of an asset both inside and outside Miden VM. Specifically: + /// Element 1 will be: + /// - ZERO for a fungible asset + /// - non-ZERO for a non-fungible asset + /// The most significant bit will be: + /// - ONE for a fungible asset + /// - ZERO for a non-fungible asset + /// + /// The above properties guarantee that there can never be a collision between a fungible and a + /// non-fungible asset. + /// + /// The methodology for constructing fungible and non-fungible assets is described below. + /// + /// # Fungible assets + /// The most significant element of a fungible asset is set to the ID of the faucet which issued + /// the asset. This guarantees the properties described above (the first bit is ONE). + /// + /// The least significant element is set to the amount of the asset. This amount cannot be greater + /// than 2^63 - 1 and thus requires 63-bits to store. + /// + /// Elements 1 and 2 are set to ZERO. + /// + /// It is impossible to find a collision between two fungible assets issued by different faucets as + /// the faucet_id is included in the description of the asset and this is guaranteed to be different + /// for each faucet as per the faucet creation logic. + /// + /// # Non-fungible assets + /// The 4 elements of non-fungible assets are computed as follows: + /// - First the asset data is hashed. This compresses an asset of an arbitrary length to 4 field + /// elements: [d0, d1, d2, d3]. + /// - d1 is then replaced with the faucet_id which issues the asset: [d0, faucet_id, d2, d3]. + /// - Lastly, the most significant bit of d3 is set to ZERO. + /// + /// It is impossible to find a collision between two non-fungible assets issued by different faucets + /// as the faucet_id is included in the description of the non-fungible asset and this is guaranteed + /// to be different as per the faucet creation logic. Collision resistance for non-fungible assets + /// issued by the same faucet is ~2^95. + record asset { + inner: word + } + + /// Account nonce + record nonce { + inner: felt + } + + /// Account hash + record account-hash { + inner: word + } + + /// Block hash + record block-hash { + inner: word + } + + /// Storage value + record storage-value { + inner: word + } + + /// Account storage root + record storage-root { + inner: word + } + + /// Account code root + record account-code-root { + inner: word + } + + /// Commitment to the account vault + record vault-commitment { + inner: word + } + + /// An index of the created note + record note-idx { + inner: felt + } + + record note-type { + inner: felt + } + + record note-execution-hint { + inner: felt + } + +} + + +/// The note script interface that is expected to be implemented by note scripts. +interface note-script { + use core-types.{word}; + + run: func(arg: word); +} + +/// The transaction script interface that is expected to be implemented by transaction scripts. +interface transaction-script { + use core-types.{word}; + + run: func(arg: word); +} + +/// The authentication component interface that is expected to export exactly one `auth__*` +/// function +interface authentication-component { + use core-types.{word}; + + auth-procedure: func(arg: word); +} + +world base-world { + export core-types; + export note-script; + export transaction-script; +} diff --git a/sdk/base-sys/CHANGELOG.md b/sdk/base-sys/CHANGELOG.md index f9432167e..f135df989 100644 --- a/sdk/base-sys/CHANGELOG.md +++ b/sdk/base-sys/CHANGELOG.md @@ -6,6 +6,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0](https://github.com/0xMiden/compiler/compare/miden-base-sys-v0.1.5...miden-base-sys-v0.4.0) - 2025-08-15 + +### Added + +- add missing and fix existing tx kernel function bindings + +### Fixed + +- `account::remove_asset` Miden SDK bindings +- update Miden SDK `AccountId` type and `account::get_id()` for two + +### Other + +- add doc comments +- use advice map API in the basic wallet tx script +- Add the test that executes counter contract, basic wallet and p2id note script on the local node ([#555](https://github.com/0xMiden/compiler/pull/555)) + +## [0.1.5](https://github.com/0xMiden/compiler/compare/miden-base-sys-v0.1.0...miden-base-sys-v0.1.5) - 2025-07-01 + +### Added + +- add Miden SDK `note::get_assets` Rust bindings + +### Fixed + +- `note` Miden SDK bindings for element-addressable memory in Miden VM #550 +- wasm import module names to be in sync with WIT files (Miden SDK) + +## [0.0.8](https://github.com/0xMiden/compiler/compare/miden-base-sys-v0.0.7...miden-base-sys-v0.0.8) - 2025-04-24 + +### Added +- *(frontend)* Low-level account storage API in Miden SDK +- *(cargo-miden)* support building Wasm component from a Cargo project + +### Fixed +- include `account` module in `MidenTxKernelLibrary`; +- fix clippy warnings + +### Other +- treat warnings as compiler errors, +- make index `u8` in account storage API in Miden SDK, +- add missing functions in miden::account, miden:note tx kernel stubs +- optimize codegen for `AccountId::as_felt`; +- add note script compilation test; +- [**breaking**] revamp Miden SDK API and expose some modules; +- remove digest-in-function-name encoding and `MidenAbiImport::digest`, + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/miden-base-sys-v0.0.6...miden-base-sys-v0.0.7) - 2024-09-17 ### Other diff --git a/sdk/base-sys/Cargo.toml b/sdk/base-sys/Cargo.toml index acfbb11a6..b4be9c6e7 100644 --- a/sdk/base-sys/Cargo.toml +++ b/sdk/base-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "miden-base-sys" description = "Miden rollup Rust bingings and MASM library" -version.workspace = true +version = "0.7.0" rust-version.workspace = true authors.workspace = true repository.workspace = true @@ -12,18 +12,11 @@ keywords.workspace = true license.workspace = true readme.workspace = true edition.workspace = true +build = "build.rs" +links = "miden_base_sys_stubs" [dependencies] -miden-assembly = { workspace = true, optional = true } -miden-stdlib-sys = { version = "0.0.7", path = "../stdlib-sys", optional = true } - -[build-dependencies] -miden-assembly = { workspace = true, optional = true } +miden-stdlib-sys = { version = "0.7.0", path = "../stdlib-sys" } [features] default = [] - -# User facing Rust bindings -"bindings" = ["dep:miden-stdlib-sys"] -# MASL library for Miden rollup (tx kernel, etc.) used by the compiler in the link phase -"masl-lib" = ["dep:miden-assembly"] diff --git a/sdk/base-sys/build.rs b/sdk/base-sys/build.rs index d4a90c4a0..cbfeced69 100644 --- a/sdk/base-sys/build.rs +++ b/sdk/base-sys/build.rs @@ -1,30 +1,110 @@ -/// Read and parse the contents from `./masm/*` and compile it to MASL. -#[cfg(feature = "masl-lib")] +// Build the Miden base stubs and link them for dependents. +// +// We produce native static libraries (.a) that contain only the stub object +// files (no panic handler) to avoid duplicate panic symbols in downstream +// component builds. We do this by compiling rlibs with rustc and naming the +// outputs `.a` so dependents pick them up via the native link search path. +// +// Why not an rlib? +// - `cargo:rustc-link-lib`/`cargo:rustc-link-search` are for native archives; +// .rlib doesn’t fit that model and attempts to use `rustc-link-arg` don’t +// propagate to dependents. +// Why not a staticlib via rustc directly? +// - A no_std staticlib usually requires a `#[panic_handler]`, which then +// collides at link time with other crates that also define panic symbols. +// - Packaging a single object keeps the archive minimal and free of panic +// symbols. + +use std::{env, path::PathBuf, process::Command}; + fn main() { - use std::{env, path::Path, sync::Arc}; - - use miden_assembly::{ - diagnostics::IntoDiagnostic, Assembler, Library as CompiledLibrary, LibraryNamespace, - }; - // re-build the `[OUT_DIR]/assets/` file iff something in the `./masm` directory - // or its builder changed: - println!("cargo:rerun-if-changed=masm"); - - let build_dir = env::var("OUT_DIR").unwrap(); - let build_dir = Path::new(&build_dir); - let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let source_manager = Arc::new(miden_assembly::DefaultSourceManager::default()); - let namespace = "miden".parse::().expect("invalid base namespace"); - - let tx_asm_dir = Path::new(manifest_dir).join("masm").join("tx"); - let asm = Assembler::new(source_manager); - let txlib = CompiledLibrary::from_dir(tx_asm_dir, namespace, asm).unwrap(); - let tx_masl_path = build_dir - .join("assets") - .join("tx") - .with_extension(CompiledLibrary::LIBRARY_EXTENSION); - txlib.write_to_file(tx_masl_path).into_diagnostic().unwrap(); -} + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let target = env::var("TARGET").unwrap_or_else(|_| "wasm32-wasip1".to_string()); + + if !target.starts_with("wasm32") { + // Still track files for re-run if changed in case of cross compilation. + let stubs_root = manifest_dir.join("stubs"); + let src_root = stubs_root.join("lib.rs"); + if src_root.exists() { + println!("cargo:rerun-if-changed={}", src_root.display()); + } + return; + } + + println!("cargo:rerun-if-env-changed=TARGET"); + println!("cargo:rerun-if-env-changed=RUSTUP_TOOLCHAIN"); + println!("cargo:rerun-if-env-changed=RUSTFLAGS"); -#[cfg(not(feature = "masl-lib"))] -fn main() {} + let stubs_root = manifest_dir.join("stubs"); + let src_root = stubs_root.join("lib.rs"); + // Ensure build script reruns when any stub file changes + println!("cargo:rerun-if-changed={}", src_root.display()); + if let Ok(read_dir) = std::fs::read_dir(&stubs_root) { + for entry in read_dir.flatten() { + let p = entry.path(); + if p.is_dir() { + if let Ok(inner) = std::fs::read_dir(&p) { + for e in inner.flatten() { + let pp = e.path(); + if pp.extension().and_then(|s| s.to_str()) == Some("rs") { + println!("cargo:rerun-if-changed={}", pp.display()); + } + } + } + } else if p.extension().and_then(|s| s.to_str()) == Some("rs") { + println!("cargo:rerun-if-changed={}", p.display()); + } + } + } + + // Build a rlib, but named it .a otherwise it will not be propagated to dependends linking + let out_rlib = out_dir.join("libmiden_base_sys_stubs.a"); + + // Ensure tools are present before invoking them. + + // 1) Compile object + // These stubs intentionally compile to `unreachable` so the frontend recognizes + // and lowers their exported symbol names to MASM calls. + // LLVM MergeFunctions pass https://llvm.org/docs/MergeFunctions.html considers some + // functions in the stub library identical (e.g. `intrinsics::felt::add` and + // `intrinsics::felt::mul`) because besides the same sig they have the same body + // (`unreachable`). The pass merges them which manifests in the compiled Wasm as if both + // `add` and `mul` are linked to the same (`add` in this case) function. + // Setting `opt-level=1` seems to be skipping this pass and is enough on its own, but I + // also put `-Z merge-functions=disabled` in case `opt-level=1` behaviour changes + // in the future and runs the MergeFunctions pass. + // `opt-level=0` - introduces import for panic infra leading to WIT encoder error (unsatisfied import). + let status = Command::new("rustc") + .arg("--crate-name") + .arg("miden_base_sys_stubs") + .arg("--edition=2021") + .arg("--crate-type=rlib") + .arg("--target") + .arg(&target) + .arg("-C") + .arg("opt-level=1") + .arg("-C") + .arg("panic=abort") + .arg("-C") + .arg("codegen-units=1") + .arg("-C") + .arg("debuginfo=0") + .arg("-Z") + .arg("merge-functions=disabled") + .arg("-C") + .arg("target-feature=+bulk-memory,+wide-arithmetic") + .arg("-o") + .arg(&out_rlib) + .arg(&src_root) + .status() + .expect("failed to spawn rustc for base stubs object"); + if !status.success() { + panic!("failed to compile miden-base-sys stubs object: {status}"); + } + + // Emit link directives for dependents + println!("cargo:rustc-link-search=native={}", out_dir.display()); + // `lib` prefix is adde by the linker automatically when it searches for the file + println!("cargo:rustc-link-lib=miden_base_sys_stubs"); +} diff --git a/sdk/base-sys/masm/tx/account.masm b/sdk/base-sys/masm/tx/account.masm deleted file mode 100644 index 47229ce03..000000000 --- a/sdk/base-sys/masm/tx/account.masm +++ /dev/null @@ -1,11 +0,0 @@ -# Stubs for miden::account tx kernel module - -export.remove_asset - push.1 - assertz -end - -export.add_asset - push.1 - assertz -end \ No newline at end of file diff --git a/sdk/base-sys/masm/tx/tx.masm b/sdk/base-sys/masm/tx/tx.masm deleted file mode 100644 index 310d6912a..000000000 --- a/sdk/base-sys/masm/tx/tx.masm +++ /dev/null @@ -1,6 +0,0 @@ -# Stubs for miden::tx tx kernel module - -export.create_note - push.1 - assertz -end \ No newline at end of file diff --git a/sdk/base-sys/src/bindings/account.rs b/sdk/base-sys/src/bindings/account.rs new file mode 100644 index 000000000..2b9752b88 --- /dev/null +++ b/sdk/base-sys/src/bindings/account.rs @@ -0,0 +1,135 @@ +use miden_stdlib_sys::{Felt, Word}; + +use super::types::{AccountId, Asset}; + +#[allow(improper_ctypes)] +extern "C" { + #[link_name = "miden::account::get_id"] + pub fn extern_account_get_id(ptr: *mut AccountId); + #[link_name = "miden::account::remove_asset"] + pub fn extern_account_remove_asset(_: Felt, _: Felt, _: Felt, _: Felt, ptr: *mut Asset); + #[link_name = "miden::account::get_nonce"] + pub fn extern_account_get_nonce() -> Felt; + #[link_name = "miden::account::incr_nonce"] + pub fn extern_account_incr_nonce() -> Felt; + #[link_name = "miden::account::get_initial_commitment"] + pub fn extern_account_get_initial_commitment(ptr: *mut Word); + #[link_name = "miden::account::compute_current_commitment"] + pub fn extern_account_compute_current_commitment(ptr: *mut Word); + #[link_name = "miden::account::compute_delta_commitment"] + pub fn extern_account_compute_delta_commitment(ptr: *mut Word); + // Resolved via stub rlib at core Wasm link time + #[link_name = "miden::account::add_asset"] + pub fn extern_account_add_asset(_: Felt, _: Felt, _: Felt, _: Felt, ptr: *mut Asset); + #[link_name = "miden::account::get_balance"] + pub fn extern_account_get_balance(faucet_id_prefix: Felt, faucet_id_suffix: Felt) -> Felt; +} + +/// Get the account ID of the currently executing note account. +pub fn get_id() -> AccountId { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + + // The MASM procedure returns the account ID on the stack. + // Inputs: [] + // Outputs: [acct_id_prefix, acct_id_suffix] + extern_account_get_id(ret_area.as_mut_ptr()); + ret_area.assume_init() + } +} + +/// Add the specified asset to the vault. +/// Returns the final asset in the account vault defined as follows: If asset is +/// a non-fungible asset, then returns the same as asset. If asset is a +/// fungible asset, then returns the total fungible asset in the account +/// vault after asset was added to it. +/// +/// Panics: +/// - If the asset is not valid. +/// - If the total value of two fungible assets is greater than or equal to 2^63. +/// - If the vault already contains the same non-fungible asset. +pub fn add_asset(asset: Asset) -> Asset { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_account_add_asset( + asset.inner[3], + asset.inner[2], + asset.inner[1], + asset.inner[0], + ret_area.as_mut_ptr(), + ); + ret_area.assume_init() + } +} + +/// Remove the specified asset from the vault. +/// +/// Panics: +/// - The fungible asset is not found in the vault. +/// - The amount of the fungible asset in the vault is less than the amount to be removed. +/// - The non-fungible asset is not found in the vault. +pub fn remove_asset(asset: Asset) -> Asset { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_account_remove_asset( + asset.inner[3], + asset.inner[2], + asset.inner[1], + asset.inner[0], + ret_area.as_mut_ptr(), + ); + ret_area.assume_init().reverse() + } +} + +/// Returns the current account nonce. +#[inline] +pub fn get_nonce() -> Felt { + unsafe { extern_account_get_nonce() } +} + +/// Increments the account nonce by one and return the new nonce +#[inline] +pub fn incr_nonce() -> Felt { + unsafe { extern_account_incr_nonce() } +} + +/// Returns the native account commitment at the beginning of the transaction. +#[inline] +pub fn get_initial_commitment() -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_account_get_initial_commitment(ret_area.as_mut_ptr()); + ret_area.assume_init().reverse() + } +} + +/// Computes and returns the commitment of the current account data. +#[inline] +pub fn compute_current_commitment() -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_account_compute_current_commitment(ret_area.as_mut_ptr()); + ret_area.assume_init().reverse() + } +} + +/// Computes and returns the commitment to the native account's delta for this transaction. +#[inline] +pub fn compute_delta_commitment() -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_account_compute_delta_commitment(ret_area.as_mut_ptr()); + ret_area.assume_init().reverse() + } +} + +/// Returns the balance of the fungible asset identified by `faucet_id`. +/// +/// # Panics +/// +/// Propagates kernel errors if the referenced asset is non-fungible or the +/// account vault invariants are violated. +pub fn get_balance(faucet_id: AccountId) -> Felt { + unsafe { extern_account_get_balance(faucet_id.prefix, faucet_id.suffix) } +} diff --git a/sdk/base-sys/src/bindings/mod.rs b/sdk/base-sys/src/bindings/mod.rs index d7c32947c..118126aab 100644 --- a/sdk/base-sys/src/bindings/mod.rs +++ b/sdk/base-sys/src/bindings/mod.rs @@ -1 +1,17 @@ +//! Bindings for Miden protocol +//! +//! # Word Field Ordering +//! +//! The Miden protocol MASM procedures expect and/or return Word on the stack with the least +//! significant felt on top of the stack. +//! +//! - In Rust: Word fields are stored as [e0, e1, e2, e3] +//! - In MASM procedures: These are pushed/popped from the stack in reverse order [e3, e2, e1, e0] + +pub mod account; +pub mod note; +pub mod storage; pub mod tx; +mod types; + +pub use types::*; diff --git a/sdk/base-sys/src/bindings/note.rs b/sdk/base-sys/src/bindings/note.rs new file mode 100644 index 000000000..72e64785d --- /dev/null +++ b/sdk/base-sys/src/bindings/note.rs @@ -0,0 +1,91 @@ +extern crate alloc; +use alloc::vec::Vec; + +use miden_stdlib_sys::{Felt, Word}; + +use super::{AccountId, Asset}; + +#[allow(improper_ctypes)] +extern "C" { + #[link_name = "miden::note::get_inputs"] + pub fn extern_note_get_inputs(ptr: *mut Felt) -> usize; + #[link_name = "miden::note::get_assets"] + pub fn extern_note_get_assets(ptr: *mut Felt) -> usize; + #[link_name = "miden::note::get_sender"] + pub fn extern_note_get_sender(ptr: *mut AccountId); + #[link_name = "miden::note::get_script_root"] + pub fn extern_note_get_script_root(ptr: *mut Word); + #[link_name = "miden::note::get_serial_number"] + pub fn extern_note_get_serial_number(ptr: *mut Word); +} + +/// Get the inputs of the currently executing note. +pub fn get_inputs() -> Vec { + const MAX_INPUTS: usize = 256; + let mut inputs: Vec = Vec::with_capacity(MAX_INPUTS); + let num_inputs = unsafe { + // Ensure the pointer is a valid Miden pointer + // + // NOTE: This relies on the fact that BumpAlloc makes all allocations + // minimally word-aligned. Each word consists of 4 elements of 4 bytes. + // Since Miden VM is field element-addressable, to get a Miden address from a Rust address, + // we divide it by 4 to get the address in field elements. + let ptr = (inputs.as_mut_ptr() as usize) / 4; + // The MASM for this function is here: + // https://github.com/0xMiden/miden-base/blob/3cbe8d59dcf4ccc9c380b7c8417ac6178fc6b86a/miden-lib/asm/miden/note.masm#L69-L102 + // #! Writes the inputs of the currently execute note into memory starting at the specified + // address. #! + // #! Inputs: [dest_ptr] + // #! Outputs: [num_inputs, dest_ptr] + // #! + // #! - dest_ptr is the memory address to write the inputs. + // Compiler generated adapter code at call site will drop the returned dest_ptr + // and return the number of inputs + extern_note_get_inputs(ptr as *mut Felt) + }; + unsafe { + inputs.set_len(num_inputs); + } + inputs +} + +/// Get the assets of the currently executing note. +pub fn get_assets() -> Vec { + const MAX_INPUTS: usize = 256; + let mut inputs: Vec = Vec::with_capacity(MAX_INPUTS); + let num_inputs = unsafe { + let ptr = (inputs.as_mut_ptr() as usize) / 4; + extern_note_get_assets(ptr as *mut Felt) + }; + unsafe { + inputs.set_len(num_inputs); + } + inputs +} + +/// Returns the sender [`AccountId`] of the note that is currently executing. +pub fn get_sender() -> AccountId { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_note_get_sender(ret_area.as_mut_ptr()); + ret_area.assume_init() + } +} + +/// Returns the script root of the currently executing note. +pub fn get_script_root() -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_note_get_script_root(ret_area.as_mut_ptr()); + ret_area.assume_init().reverse() + } +} + +/// Returns the serial number of the currently executing note. +pub fn get_serial_number() -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_note_get_serial_number(ret_area.as_mut_ptr()); + ret_area.assume_init().reverse() + } +} diff --git a/sdk/base-sys/src/bindings/storage.rs b/sdk/base-sys/src/bindings/storage.rs new file mode 100644 index 000000000..ea8ed370f --- /dev/null +++ b/sdk/base-sys/src/bindings/storage.rs @@ -0,0 +1,159 @@ +use miden_stdlib_sys::{Felt, Word}; + +use super::StorageCommitmentRoot; + +#[allow(improper_ctypes)] +extern "C" { + #[link_name = "miden::account::get_item"] + pub fn extern_get_storage_item(index: Felt, ptr: *mut Word); + + #[link_name = "miden::account::set_item"] + pub fn extern_set_storage_item( + index: Felt, + v0: Felt, + v1: Felt, + v2: Felt, + v3: Felt, + ptr: *mut (StorageCommitmentRoot, Word), + ); + + #[link_name = "miden::account::get_map_item"] + pub fn extern_get_storage_map_item( + index: Felt, + k0: Felt, + k1: Felt, + k2: Felt, + k3: Felt, + ptr: *mut Word, + ); + + #[link_name = "miden::account::set_map_item"] + pub fn extern_set_storage_map_item( + index: Felt, + k0: Felt, + k1: Felt, + k2: Felt, + k3: Felt, + v0: Felt, + v1: Felt, + v2: Felt, + v3: Felt, + ptr: *mut (StorageCommitmentRoot, Word), + ); +} + +/// Gets an item from the account storage. +/// +/// Inputs: index +/// Outputs: value +/// +/// Where: +/// - index is the index of the item to get. +/// - value is the value of the item. +/// +/// Panics if: +/// - the index of the requested item is out of bounds. +#[inline] +pub fn get_item(index: u8) -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_get_storage_item(index.into(), ret_area.as_mut_ptr()); + let word = ret_area.assume_init(); + word.reverse() + } +} + +/// Sets an item in the account storage. +/// +/// Inputs: index, value +/// Outputs: (new_root, old_value) +/// +/// Where: +/// - index is the index of the item to set. +/// - value is the value to set. +/// - new_root is the new storage commitment. +/// - old_value is the previous value of the item. +/// +/// Panics if: +/// - the index of the item is out of bounds. +#[inline] +pub fn set_item(index: u8, value: Word) -> (StorageCommitmentRoot, Word) { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::<(StorageCommitmentRoot, Word)>::uninit(); + extern_set_storage_item( + index.into(), + value[3], + value[2], + value[1], + value[0], + ret_area.as_mut_ptr(), + ); + let (comm, value) = ret_area.assume_init(); + (comm.reverse(), value.reverse()) + } +} + +/// Gets a map item from the account storage. +/// +/// Inputs: index, key +/// Outputs: value +/// +/// Where: +/// - index is the index of the map where the key value should be read. +/// - key is the key of the item to get. +/// - value is the value of the item. +/// +/// Panics if: +/// - the index for the map is out of bounds, meaning > 255. +/// - the slot item at index is not a map. +#[inline] +pub fn get_map_item(index: u8, key: &Word) -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_get_storage_map_item( + index.into(), + key[3], + key[2], + key[1], + key[0], + ret_area.as_mut_ptr(), + ); + ret_area.assume_init().reverse() + } +} + +/// Sets a map item in the account storage. +/// +/// Inputs: index, key, value +/// Outputs: (map_old_root, map_old_value) +/// +/// Where: +/// - index is the index of the map where the key value should be set. +/// - key is the key to set. +/// - value is the value to set. +/// - map_old_root is the old map root. +/// - map_old_value is the old value at key. +/// +/// Panics if: +/// - the index for the map is out of bounds, meaning > 255. +/// - the slot item at index is not a map. +#[inline] +pub fn set_map_item(index: u8, key: Word, value: Word) -> (StorageCommitmentRoot, Word) { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::<(StorageCommitmentRoot, Word)>::uninit(); + extern_set_storage_map_item( + index.into(), + key[3], + key[2], + key[1], + key[0], + value[3], + value[2], + value[1], + value[0], + ret_area.as_mut_ptr(), + ); + let (comm, value) = ret_area.assume_init(); + (comm.reverse(), value.reverse()) + } +} diff --git a/sdk/base-sys/src/bindings/tx.rs b/sdk/base-sys/src/bindings/tx.rs new file mode 100644 index 000000000..98ec9779f --- /dev/null +++ b/sdk/base-sys/src/bindings/tx.rs @@ -0,0 +1,109 @@ +use miden_stdlib_sys::{Felt, Word}; + +use super::types::{Asset, NoteIdx, NoteType, Recipient, Tag}; + +#[allow(improper_ctypes)] +extern "C" { + #[link_name = "miden::tx::create_note"] + pub fn extern_tx_create_note( + tag: Tag, + aux: Felt, + note_type: NoteType, + execution_hint: Felt, + recipient_f0: Felt, + recipient_f1: Felt, + recipient_f2: Felt, + recipient_f3: Felt, + ) -> NoteIdx; + + #[link_name = "miden::tx::add_asset_to_note"] + pub fn extern_tx_add_asset_to_note( + asset_f0: Felt, + asset_f1: Felt, + asset_f2: Felt, + asset_f3: Felt, + note_idx: NoteIdx, + result: *mut (Asset, NoteIdx), + ); + + #[link_name = "miden::tx::get_block_number"] + pub fn extern_tx_get_block_number() -> Felt; + + #[link_name = "miden::tx::get_input_notes_commitment"] + pub fn extern_tx_get_input_notes_commitment(ptr: *mut Word); + + #[link_name = "miden::tx::get_output_notes_commitment"] + pub fn extern_tx_get_output_notes_commitment(ptr: *mut Word); +} + +/// Creates a new note. asset is the asset to be included in the note. tag is +/// the tag to be included in the note. recipient is the recipient of the note. +/// Returns the id of the created note. +pub fn create_note( + tag: Tag, + aux: Felt, + note_type: NoteType, + execution_hint: Felt, + recipient: Recipient, +) -> NoteIdx { + unsafe { + extern_tx_create_note( + tag, + aux, + note_type, + execution_hint, + recipient.inner[3], + recipient.inner[2], + recipient.inner[1], + recipient.inner[0], + ) + } +} + +/// Adds the asset to the note specified by the index. +/// +/// # Arguments +/// * `asset` - The asset to be added to the note +/// * `note_idx` - The index of the note to which the asset will be added +/// +/// # Returns +/// A tuple containing the same asset and note_idx +pub fn add_asset_to_note(asset: Asset, note_idx: NoteIdx) -> (Asset, NoteIdx) { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::<(Asset, NoteIdx)>::uninit(); + extern_tx_add_asset_to_note( + asset.inner[3], + asset.inner[2], + asset.inner[1], + asset.inner[0], + note_idx, + ret_area.as_mut_ptr(), + ); + + let (asset, note_idx) = ret_area.assume_init(); + (asset.reverse(), note_idx) + } +} + +/// Returns the current block number. +pub fn get_block_number() -> Felt { + unsafe { extern_tx_get_block_number() } +} + +/// Returns the input notes commitment digest. +pub fn get_input_notes_commitment() -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_tx_get_input_notes_commitment(ret_area.as_mut_ptr()); + ret_area.assume_init().reverse() + } +} + +/// Returns the output notes commitment digest. +pub fn get_output_notes_commitment() -> Word { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + extern_tx_get_output_notes_commitment(ret_area.as_mut_ptr()); + ret_area.assume_init().reverse() + } +} diff --git a/sdk/base-sys/src/bindings/tx/externs.rs b/sdk/base-sys/src/bindings/tx/externs.rs deleted file mode 100644 index 85ee9c9ce..000000000 --- a/sdk/base-sys/src/bindings/tx/externs.rs +++ /dev/null @@ -1,36 +0,0 @@ -use miden_stdlib_sys::Felt; - -use crate::bindings::tx::{AccountId, CoreAsset, NoteId, NoteType, Tag}; - -#[link(wasm_import_module = "miden::account")] -extern "C" { - #[link_name = "get_id<0x0000000000000000000000000000000000000000000000000000000000000000>"] - pub fn extern_account_get_id() -> AccountId; - #[link_name = "add_asset<0x0000000000000000000000000000000000000000000000000000000000000000>"] - pub fn extern_account_add_asset(_: Felt, _: Felt, _: Felt, _: Felt, ptr: *mut CoreAsset); - #[link_name = "remove_asset<0x0000000000000000000000000000000000000000000000000000000000000000>"] - pub fn extern_account_remove_asset(_: Felt, _: Felt, _: Felt, _: Felt, ptr: *mut CoreAsset); -} - -#[link(wasm_import_module = "miden::note")] -extern "C" { - #[link_name = "get_inputs<0x0000000000000000000000000000000000000000000000000000000000000000>"] - pub fn extern_note_get_inputs(ptr: *mut Felt) -> usize; -} - -#[link(wasm_import_module = "miden::tx")] -extern "C" { - #[link_name = "create_note<0x0000000000000000000000000000000000000000000000000000000000000000>"] - pub fn extern_tx_create_note( - asset_f0: Felt, - asset_f1: Felt, - asset_f2: Felt, - asset_f3: Felt, - tag: Tag, - note_type: NoteType, - recipient_f0: Felt, - recipient_f1: Felt, - recipient_f2: Felt, - recipient_f3: Felt, - ) -> NoteId; -} diff --git a/sdk/base-sys/src/bindings/tx/mod.rs b/sdk/base-sys/src/bindings/tx/mod.rs deleted file mode 100644 index 853ca0e07..000000000 --- a/sdk/base-sys/src/bindings/tx/mod.rs +++ /dev/null @@ -1,116 +0,0 @@ -mod externs; -use externs::*; - -mod types; -pub use types::*; - -extern crate alloc; - -use alloc::vec::Vec; - -use miden_stdlib_sys::Felt; - -/// Get the account ID of the currently executing note account. -pub fn get_id() -> AccountId { - unsafe { extern_account_get_id() } -} - -/// Get the inputs of the currently executing note. -pub fn get_inputs() -> Vec { - const MAX_INPUTS: usize = 256; - let mut inputs: Vec = Vec::with_capacity(MAX_INPUTS); - let num_inputs = unsafe { - // Ensure the pointer is a valid Miden pointer - // - // NOTE: This relies on the fact that BumpAlloc makes all allocations - // minimally word-aligned. Each word consists of 4 elements of 4 bytes, - // so to get a Miden address from a Rust address, we divide by 16 to get - // the address in words (dividing by 4 gets us an address in elements, - // and by 4 again we get the word address). - let ptr = (inputs.as_mut_ptr() as usize) / 16; - // The MASM for this function is here: - // https://github.com/0xPolygonMiden/miden-base/blob/3cbe8d59dcf4ccc9c380b7c8417ac6178fc6b86a/miden-lib/asm/miden/note.masm#L69-L102 - // #! Writes the inputs of the currently execute note into memory starting at the specified - // address. #! - // #! Inputs: [dest_ptr] - // #! Outputs: [num_inputs, dest_ptr] - // #! - // #! - dest_ptr is the memory address to write the inputs. - // Compiler generated adapter code at call site will drop the returned dest_ptr - // and return the number of inputs - extern_note_get_inputs(ptr as *mut Felt) - }; - unsafe { - inputs.set_len(num_inputs); - } - inputs -} - -/// Add the specified asset to the vault. -/// Returns the final asset in the account vault defined as follows: If asset is -/// a non-fungible asset, then returns the same as asset. If asset is a -/// fungible asset, then returns the total fungible asset in the account -/// vault after asset was added to it. -/// -/// Panics: -/// - If the asset is not valid. -/// - If the total value of two fungible assets is greater than or equal to 2^63. -/// - If the vault already contains the same non-fungible asset. -pub fn add_asset(asset: CoreAsset) -> CoreAsset { - unsafe { - let mut ret_area = ::core::mem::MaybeUninit::::uninit(); - extern_account_add_asset( - asset.inner[0], - asset.inner[1], - asset.inner[2], - asset.inner[3], - ret_area.as_mut_ptr(), - ); - ret_area.assume_init() - } -} - -/// Remove the specified asset from the vault. -/// -/// Panics: -/// - The fungible asset is not found in the vault. -/// - The amount of the fungible asset in the vault is less than the amount to be removed. -/// - The non-fungible asset is not found in the vault. -pub fn remove_asset(asset: CoreAsset) -> CoreAsset { - unsafe { - let mut ret_area = ::core::mem::MaybeUninit::::uninit(); - extern_account_remove_asset( - asset.inner[0], - asset.inner[1], - asset.inner[2], - asset.inner[3], - ret_area.as_mut_ptr(), - ); - ret_area.assume_init() - } -} - -/// Creates a new note. asset is the asset to be included in the note. tag is -/// the tag to be included in the note. recipient is the recipient of the note. -/// Returns the id of the created note. -pub fn create_note( - asset: CoreAsset, - tag: Tag, - note_type: NoteType, - recipient: Recipient, -) -> NoteId { - unsafe { - extern_tx_create_note( - asset.inner[0], - asset.inner[1], - asset.inner[2], - asset.inner[3], - tag, - note_type, - recipient.0[0], - recipient.0[1], - recipient.0[2], - recipient.0[3], - ) - } -} diff --git a/sdk/base-sys/src/bindings/tx/types.rs b/sdk/base-sys/src/bindings/tx/types.rs deleted file mode 100644 index c9225e045..000000000 --- a/sdk/base-sys/src/bindings/tx/types.rs +++ /dev/null @@ -1,38 +0,0 @@ -use miden_stdlib_sys::{Felt, Word}; - -#[repr(transparent)] -#[derive(Copy, Clone)] -pub struct AccountId(Felt); - -impl From for Felt { - fn from(account_id: AccountId) -> Felt { - account_id.0 - } -} - -#[repr(transparent)] -pub struct CoreAsset { - pub(crate) inner: Word, -} - -impl CoreAsset { - pub fn new(word: impl Into) -> Self { - CoreAsset { inner: word.into() } - } - - pub fn as_word(&self) -> Word { - self.inner - } -} - -#[repr(transparent)] -pub struct Recipient(pub(crate) Word); - -#[repr(transparent)] -pub struct Tag(pub(crate) Felt); - -#[repr(transparent)] -pub struct NoteId(pub(crate) Felt); - -#[repr(transparent)] -pub struct NoteType(pub(crate) Felt); diff --git a/sdk/base-sys/src/bindings/types.rs b/sdk/base-sys/src/bindings/types.rs new file mode 100644 index 000000000..aa256d13a --- /dev/null +++ b/sdk/base-sys/src/bindings/types.rs @@ -0,0 +1,123 @@ +use miden_stdlib_sys::{Felt, Word}; + +#[allow(unused)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct AccountId { + pub prefix: Felt, + pub suffix: Felt, +} + +impl AccountId { + /// Creates a new AccountId from prefix and suffix Felt values + pub fn from(prefix: Felt, suffix: Felt) -> Self { + Self { prefix, suffix } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Asset { + pub inner: Word, +} + +impl Asset { + pub fn new(word: impl Into) -> Self { + Asset { inner: word.into() } + } + + pub fn as_word(&self) -> &Word { + &self.inner + } + + #[inline] + pub(crate) fn reverse(&self) -> Self { + Self { + inner: self.inner.reverse(), + } + } +} + +impl From for Asset { + fn from(value: Word) -> Self { + Self::new(value) + } +} + +impl From<[Felt; 4]> for Asset { + fn from(value: [Felt; 4]) -> Self { + Asset::new(Word::from(value)) + } +} + +impl From for Word { + fn from(val: Asset) -> Self { + val.inner + } +} + +impl AsRef for Asset { + fn as_ref(&self) -> &Word { + &self.inner + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Recipient { + pub inner: Word, +} + +impl From<[Felt; 4]> for Recipient { + fn from(value: [Felt; 4]) -> Self { + Recipient { + inner: Word::from(value), + } + } +} + +impl From for Recipient { + fn from(value: Word) -> Self { + Recipient { inner: value } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct Tag { + pub inner: Felt, +} + +impl From for Tag { + fn from(value: Felt) -> Self { + Tag { inner: value } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct NoteIdx { + pub inner: Felt, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct NoteType { + pub inner: Felt, +} + +impl From for NoteType { + fn from(value: Felt) -> Self { + NoteType { inner: value } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(transparent)] +pub struct StorageCommitmentRoot(Word); + +impl StorageCommitmentRoot { + #[inline] + pub(crate) fn reverse(&self) -> StorageCommitmentRoot { + Self(self.0.reverse()) + } +} diff --git a/sdk/base-sys/src/lib.rs b/sdk/base-sys/src/lib.rs index c716a3018..3adbebc89 100644 --- a/sdk/base-sys/src/lib.rs +++ b/sdk/base-sys/src/lib.rs @@ -1,7 +1,5 @@ // Enable no_std for the bindings module #![no_std] +#![deny(warnings)] -#[cfg(feature = "bindings")] pub mod bindings; -#[cfg(feature = "masl-lib")] -pub mod masl; diff --git a/sdk/base-sys/src/masl/mod.rs b/sdk/base-sys/src/masl/mod.rs deleted file mode 100644 index d7c32947c..000000000 --- a/sdk/base-sys/src/masl/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod tx; diff --git a/sdk/base-sys/src/masl/tx.rs b/sdk/base-sys/src/masl/tx.rs deleted file mode 100644 index 080087da7..000000000 --- a/sdk/base-sys/src/masl/tx.rs +++ /dev/null @@ -1,24 +0,0 @@ -use miden_assembly::{utils::Deserializable, Library as CompiledLibrary}; - -/// Stubs for the Miden rollup tx kernel -pub struct MidenTxKernelLibrary(CompiledLibrary); - -impl AsRef for MidenTxKernelLibrary { - fn as_ref(&self) -> &CompiledLibrary { - &self.0 - } -} - -impl From for CompiledLibrary { - fn from(lib: MidenTxKernelLibrary) -> Self { - lib.0 - } -} - -impl Default for MidenTxKernelLibrary { - fn default() -> Self { - let bytes = include_bytes!(concat!(env!("OUT_DIR"), "/assets/tx.masl")); - let contents = CompiledLibrary::read_from_bytes(bytes).expect("failed to read std masl!"); - Self(contents) - } -} diff --git a/sdk/base-sys/stubs/account.rs b/sdk/base-sys/stubs/account.rs new file mode 100644 index 000000000..118c16872 --- /dev/null +++ b/sdk/base-sys/stubs/account.rs @@ -0,0 +1,95 @@ +use core::ffi::c_void; + +/// Account interface stubs +/// +/// Unreachable stub for `add-asset` import (extern_account_add_asset). +/// Signature matches the Wasm lowering used by the SDK: (f32, f32, f32, f32, i32) +#[export_name = "miden::account::add_asset"] +pub extern "C" fn add_asset_plain(_a0: f32, _a1: f32, _a2: f32, _a3: f32, _out: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::remove_asset"] +pub extern "C" fn remove_asset_plain(_a0: f32, _a1: f32, _a2: f32, _a3: f32, _out: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::get_id"] +pub extern "C" fn account_get_id_plain(_out: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::get_nonce"] +pub extern "C" fn account_get_nonce_plain() -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::get_initial_commitment"] +pub extern "C" fn account_get_initial_commitment_plain(_out: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::compute_current_commitment"] +pub extern "C" fn account_compute_current_commitment_plain(_out: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::compute_delta_commitment"] +pub extern "C" fn account_compute_delta_commitment_plain(_out: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::get_item"] +pub extern "C" fn account_get_item_plain(_index: f32, _out: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::set_item"] +pub extern "C" fn account_set_item_plain( + _index: f32, + _v0: f32, + _v1: f32, + _v2: f32, + _v3: f32, + _out: *mut c_void, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::get_map_item"] +pub extern "C" fn account_get_map_item_plain( + _index: f32, + _k0: f32, + _k1: f32, + _k2: f32, + _k3: f32, + _out: *mut c_void, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::set_map_item"] +pub extern "C" fn account_set_map_item_plain( + _index: f32, + _k0: f32, + _k1: f32, + _k2: f32, + _k3: f32, + _v0: f32, + _v1: f32, + _v2: f32, + _v3: f32, + _out: *mut c_void, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::incr_nonce"] +pub extern "C" fn account_incr_nonce_plain() -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::account::get_balance"] +pub extern "C" fn account_get_balance_plain(_prefix: f32, _suffix: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/sdk/base-sys/stubs/lib.rs b/sdk/base-sys/stubs/lib.rs new file mode 100644 index 000000000..8badd074e --- /dev/null +++ b/sdk/base-sys/stubs/lib.rs @@ -0,0 +1,14 @@ +#![no_std] + +//! Unreachable stubs for Miden base SDK functions. +//! +//! These stubs are compiled by build.rs into a separate rlib and +//! linked to `miden-base-sys` so that the Wasm translator can lower +//! the calls appropriately. They are not part of the crate sources. + +mod account; +mod note; +mod tx; + +// No panic handler here; the stubs are packaged as a single object into a +// static archive by build.rs to avoid introducing panic symbols. diff --git a/sdk/base-sys/stubs/note.rs b/sdk/base-sys/stubs/note.rs new file mode 100644 index 000000000..549ef9363 --- /dev/null +++ b/sdk/base-sys/stubs/note.rs @@ -0,0 +1,27 @@ +use core::ffi::c_void; + +/// Note interface stubs +#[export_name = "miden::note::get_inputs"] +pub extern "C" fn note_get_inputs_plain(_ptr: *mut c_void) -> usize { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::note::get_assets"] +pub extern "C" fn note_get_assets_plain(_ptr: *mut c_void) -> usize { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::note::get_sender"] +pub extern "C" fn note_get_sender_plain(_ptr: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::note::get_script_root"] +pub extern "C" fn note_get_script_root_plain(_ptr: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::note::get_serial_number"] +pub extern "C" fn note_get_serial_number_plain(_ptr: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/sdk/base-sys/stubs/tx.rs b/sdk/base-sys/stubs/tx.rs new file mode 100644 index 000000000..37b06f9a2 --- /dev/null +++ b/sdk/base-sys/stubs/tx.rs @@ -0,0 +1,43 @@ +use core::ffi::c_void; + +/// Tx interface stubs +#[export_name = "miden::tx::create_note"] +pub extern "C" fn tx_create_note_plain( + _tag: f32, + _aux: f32, + _note_type: f32, + _execution_hint: f32, + _r0: f32, + _r1: f32, + _r2: f32, + _r3: f32, +) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::tx::add_asset_to_note"] +pub extern "C" fn tx_add_asset_to_note_plain( + _a0: f32, + _a1: f32, + _a2: f32, + _a3: f32, + _note_idx: f32, + _out: *mut c_void, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::tx::get_block_number"] +pub extern "C" fn tx_get_block_number_plain() -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::tx::get_input_notes_commitment"] +pub extern "C" fn tx_get_input_notes_commitment_plain(_out: *mut core::ffi::c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "miden::tx::get_output_notes_commitment"] +pub extern "C" fn tx_get_output_notes_commitment_plain(_out: *mut core::ffi::c_void) { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/sdk/base/CHANGELOG.md b/sdk/base/CHANGELOG.md new file mode 100644 index 000000000..8169439ed --- /dev/null +++ b/sdk/base/CHANGELOG.md @@ -0,0 +1,51 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.0](https://github.com/0xMiden/compiler/compare/miden-base-v0.1.5...miden-base-v0.4.0) - 2025-08-15 + +### Added + +- add `project-kind` with `account`, `note-script` and +- add missing and fix existing tx kernel function bindings +- rename note script rollup target into script + +### Fixed + +- add `arg: Word` parameter to `script` +- update Miden SDK `AccountId` type and `account::get_id()` for two + +### Other + +- rename `note-script` and `tx-script` entrypoints to `run` +- formatting +- make `miden_stdlib_sys::Digest` a newtype instead of type alias + +## [0.1.0](https://github.com/0xMiden/compiler/releases/tag/miden-base-v0.1.0) - 2025-05-23 + +### Added + +- switch to stable vm, link against real miden-lib +- bundle Miden SDK WIT files with relevant SDK crates +- *(sdk)* introduce `miden-base` with high-level account storage API + +### Other + +- 0.1.0 +- rename `CoreAsset` to `Asset` in Miden SDK #501 +- update url +- fixup miden-base facade in sdk +- rename `StorageMapAccess::read` and `write` to `get` and `set` +- make account storage API polymorphic for key and value types +- fix typos ([#243](https://github.com/0xMiden/compiler/pull/243)) +- a few minor improvements +- set up mdbook deploy +- add guides for compiling rust->masm +- add mdbook skeleton +- provide some initial usage instructions +- Initial commit diff --git a/sdk/base/Cargo.toml b/sdk/base/Cargo.toml new file mode 100644 index 000000000..4c278dda6 --- /dev/null +++ b/sdk/base/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "miden-base" +description = "Miden rollup Rust SDK" +version = "0.7.0" +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[dependencies] +miden-base-sys = { version = "0.7.0", path = "../base-sys" } +miden-stdlib-sys = { version = "0.7.0", path = "../stdlib-sys" } +miden-base-macros = { version = "0.7.0", path = "../base-macros" } + +[features] +default = [] diff --git a/sdk/base/src/lib.rs b/sdk/base/src/lib.rs new file mode 100644 index 000000000..7f1f3b934 --- /dev/null +++ b/sdk/base/src/lib.rs @@ -0,0 +1,6 @@ +#![no_std] + +mod types; + +pub use miden_base_macros::{component, export_type, generate, note_script, tx_script}; +pub use types::*; diff --git a/sdk/base/src/types/mod.rs b/sdk/base/src/types/mod.rs new file mode 100644 index 000000000..21ee2dffa --- /dev/null +++ b/sdk/base/src/types/mod.rs @@ -0,0 +1,3 @@ +mod storage; + +pub use storage::*; diff --git a/sdk/base/src/types/storage.rs b/sdk/base/src/types/storage.rs new file mode 100644 index 000000000..4037c78f5 --- /dev/null +++ b/sdk/base/src/types/storage.rs @@ -0,0 +1,60 @@ +use miden_base_sys::bindings::{storage, StorageCommitmentRoot}; +use miden_stdlib_sys::Word; + +pub trait ValueAccess { + fn read(&self) -> V; + fn write(&self, value: V) -> (StorageCommitmentRoot, V); +} + +pub struct Value { + pub slot: u8, +} + +impl + From> ValueAccess for Value { + /// Returns an item value from the account storage. + #[inline(always)] + fn read(&self) -> V { + storage::get_item(self.slot).into() + } + + /// Sets an item `value` in the account storage and returns (new_root, old_value) + /// Where: + /// - new_root is the new storage commitment. + /// - old_value is the previous value of the item. + #[inline(always)] + fn write(&self, value: V) -> (StorageCommitmentRoot, V) { + let (root, old_word) = storage::set_item(self.slot, value.into()); + (root, old_word.into()) + } +} + +pub trait StorageMapAccess { + /// Returns a map item value for `key` from the account storage. + fn get(&self, key: &K) -> V; + /// Sets a map item `value` for `key` in the account storage and returns (old_root, old_value) + fn set(&self, key: K, value: V) -> (StorageCommitmentRoot, V); +} + +pub struct StorageMap { + pub slot: u8, +} + +impl + AsRef, V: From + Into> StorageMapAccess + for StorageMap +{ + /// Returns a map item value from the account storage. + #[inline(always)] + fn get(&self, key: &K) -> V { + storage::get_map_item(self.slot, key.as_ref()).into() + } + + /// Sets a map item `value` in the account storage and returns (old_root, old_value) + /// Where: + /// - old_root is the old map root. + /// - old_value is the previous value of the item. + #[inline(always)] + fn set(&self, key: K, value: V) -> (StorageCommitmentRoot, V) { + let (root, old_word) = storage::set_map_item(self.slot, key.into(), value.into()); + (root, old_word.into()) + } +} diff --git a/sdk/sdk/CHANGELOG.md b/sdk/sdk/CHANGELOG.md index 602fdc177..e45a1c5b5 100644 --- a/sdk/sdk/CHANGELOG.md +++ b/sdk/sdk/CHANGELOG.md @@ -6,6 +6,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.7.0] +### BREAKING +- WIT interface generation in `#[component]` macro on `impl `. The `#[export_type]` macro is required for any type in exported function signature. +- Generate global allocator and panic handler in `#[component]`, `#[note_script]` and `#[tx_script]` macros; + +## [0.6.0] + +### BREAKING +- Add `#[note_script]` and `#[tx_script]` attribute macros; +- Generate Rust bindings in the attributes macros instead of in the `src/bindings.rs` file; +- Remove explicit `miden::base`(`miden.wit` file) dependency in Cargo.toml and generate it in the macros; + +## [0.5.0] + +### BREAKING +- Remove low-level WIT interfaces for Miden standard library and transaction protocol library and link directly using stub library and transforming the stub functions into calls to MASM procedures. + +## [0.0.8](https://github.com/0xMiden/compiler/compare/miden-v0.0.7...miden-v0.0.8) - 2025-04-24 + +### Added +- *(sdk)* introduce miden-sdk-alloc +- introduce TransformStrategy and add the "return-via-pointer" +- lay out the Rust Miden SDK structure, the first integration test + +### Fixed +- fix value type in store op in `return_via_pointer` transformation, + +### Other +- treat warnings as compiler errors, +- [**breaking**] revamp Miden SDK API and expose some modules; +- [**breaking**] rename `miden-sdk` crate to `miden` [#338](https://github.com/0xMiden/compiler/pull/338) +- release-plz update (bumped to v0.0.7) +- 0.0.6 +- switch all crates to a single workspace version (0.0.5) +- bump all crate versions to 0.0.5 +- bump all crate versions to 0.0.4 [#296](https://github.com/0xMiden/compiler/pull/296) +- `release-plz update` (bump versions, changelogs) +- `release-plz update` to update crate versions and changelogs +- set `miden-sdk-alloc` version to `0.0.0` to be in sync with +- delete `miden-tx-kernel-sys` crate and move the code to `miden-base-sys` +- `release-plz update` in `sdk` folder (SDK crates) +- fix typos ([#243](https://github.com/0xMiden/compiler/pull/243)) +- set crates versions to 0.0.0, and `publish = false` for tests +- rename `miden-sdk-tx-kernel` to `miden-tx-kernel-sys` +- rename `miden-prelude` to `miden-stdlib-sys` in SDK +- start guides for developing in rust in the book, +- introduce `miden-prelude` crate for intrinsics and stdlib +- remove `dylib` from `crate-type` in Miden SDK crates +- optimize rust Miden SDK for size +- a few minor improvements +- set up mdbook deploy +- add guides for compiling rust->masm +- add mdbook skeleton +- provide some initial usage instructions +- Initial commit + ## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/miden-sdk-v0.0.5...miden-sdk-v0.0.6) - 2024-09-06 ### Other diff --git a/sdk/sdk/Cargo.toml b/sdk/sdk/Cargo.toml index 4e8550ea6..0384fd58e 100644 --- a/sdk/sdk/Cargo.toml +++ b/sdk/sdk/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "miden-sdk" +name = "miden" description = "Miden SDK" -version.workspace = true +version = "0.7.0" rust-version.workspace = true authors.workspace = true repository.workspace = true @@ -15,6 +15,11 @@ edition.workspace = true crate-type = ["rlib"] [dependencies] -miden-sdk-alloc = { version = "0.0.7", path = "../alloc" } -miden-stdlib-sys = { version = "0.0.7", path = "../stdlib-sys" } -miden-base-sys = { version = "0.0.7", path = "../base-sys", features = ["bindings"] } +miden-sdk-alloc = { version = "0.7.0", path = "../alloc" } +miden-stdlib-sys = { version = "0.7.0", path = "../stdlib-sys" } +miden-base = { version = "0.7.0", path = "../base" } +miden-base-sys = { version = "0.7.0", path = "../base-sys" } +wit-bindgen = { version = "0.46", default-features = false, features = ["macros"] } + +[features] +default = [] diff --git a/sdk/sdk/src/lib.rs b/sdk/sdk/src/lib.rs index 01a872d33..e78082812 100644 --- a/sdk/sdk/src/lib.rs +++ b/sdk/sdk/src/lib.rs @@ -1,5 +1,9 @@ #![no_std] +#![deny(warnings)] -pub use miden_base_sys::bindings::tx::*; +pub use miden_base::*; +pub use miden_base_sys::bindings::*; pub use miden_sdk_alloc::BumpAlloc; pub use miden_stdlib_sys::*; +// Re-export since `wit_bindgen::generate!` is used in `generate!` +pub use wit_bindgen; diff --git a/sdk/stdlib-sys/CHANGELOG.md b/sdk/stdlib-sys/CHANGELOG.md index 51ff2e147..dbc6d8526 100644 --- a/sdk/stdlib-sys/CHANGELOG.md +++ b/sdk/stdlib-sys/CHANGELOG.md @@ -6,6 +6,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.0](https://github.com/0xMiden/compiler/compare/miden-stdlib-sys-v0.1.5...miden-stdlib-sys-v0.4.0) - 2025-08-15 + +### Added + +- implement advice map API in Miden SDK +- add `crypto::hmerge()` in Miden SDK (`hmerge` VM intruction); + +### Fixed + +- WIT interface for core Wasm module imports for Miden SDK +- change `align(32)` for `Word` to be `align(16)` #596 +- `hmerge` function declaration in WIT (invalid argument names), + +### Other + +- use advice map API in the basic wallet tx script +- rename `io` to `advice`, export modules in stdlib SDK +- Add the test that executes counter contract, basic wallet and p2id note script on the local node ([#555](https://github.com/0xMiden/compiler/pull/555)) +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) +- Merge pull request #603 from 0xMiden/greenhat/i598-hmerge-pass-digest-ptr +- pass the pointer to `[Digest; 2]` to `hmerge` intrinsic #598 +- add issue url in the comments +- `hmerge` intrinsic to accept digests as a pointer and load +- rename for readability, add comments +- make `miden_stdlib_sys::Digest` a newtype instead of type alias +- simplify the Rust part of the `hmerge` bindings + +## [0.1.5](https://github.com/0xMiden/compiler/compare/miden-stdlib-sys-v0.1.0...miden-stdlib-sys-v0.1.5) - 2025-07-01 + +### Fixed + +- add missing felt intrinsics in Miden SDK WIT file + +### Other + +- add globals to cross-ctx-account and note test projects +- fix doc comments + +## [0.0.8](https://github.com/0xMiden/compiler/compare/miden-stdlib-sys-v0.0.7...miden-stdlib-sys-v0.0.8) - 2025-04-24 + +### Added +- add custom dependencies to `Executor` resolver, +- restore module and function names of the intrinsics and Miden +- *(cargo-miden)* support building Wasm component from a Cargo project + +### Fixed +- refine `Component` imports and exports to reference module imports + +### Other +- treat warnings as compiler errors, +- rename `Felt::from_u32_unchecked` to `Felt::from_u32` +- [**breaking**] revamp Miden SDK API and expose some modules; +- remove digest-in-function-name encoding and `MidenAbiImport::digest`, + ## [0.0.6](https://github.com/0xpolygonmiden/compiler/compare/miden-stdlib-sys-v0.0.5...miden-stdlib-sys-v0.0.6) - 2024-09-06 ### Other diff --git a/sdk/stdlib-sys/Cargo.toml b/sdk/stdlib-sys/Cargo.toml index dd4a71f8a..47a4cc906 100644 --- a/sdk/stdlib-sys/Cargo.toml +++ b/sdk/stdlib-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "miden-stdlib-sys" description = "Low-level Rust bindings for the Miden standard library" -version.workspace = true +version = "0.7.0" rust-version.workspace = true authors.workspace = true repository.workspace = true @@ -10,8 +10,13 @@ keywords.workspace = true license.workspace = true readme.workspace = true edition.workspace = true +build = "build.rs" +links = "miden_stdlib_sys_stubs" [lib] crate-type = ["rlib"] [dependencies] + +[features] +default = [] diff --git a/sdk/stdlib-sys/README.md b/sdk/stdlib-sys/README.md index 1b0a0733d..46f11bf37 100644 --- a/sdk/stdlib-sys/README.md +++ b/sdk/stdlib-sys/README.md @@ -4,7 +4,7 @@ The `miden-stdlib-sys` crate provides a `Felt` type that represents field elemen ## Miden VM instructions -See the full instruction list in the [Miden VM book](https://0xpolygonmiden.github.io/miden-vm/user_docs/assembly/field_operations.html) +See the full instruction list in the [Miden VM book](https://0xMiden.github.io/miden-vm/user_docs/assembly/field_operations.html) ### Not yet implemented Miden VM instructions: @@ -26,7 +26,6 @@ Missing in IR: Missing in IR: - `hash`; - `hperm`; -- `hmerge`; - `mtree*`; ### Events, Tracing diff --git a/sdk/stdlib-sys/build.rs b/sdk/stdlib-sys/build.rs new file mode 100644 index 000000000..6aaecad41 --- /dev/null +++ b/sdk/stdlib-sys/build.rs @@ -0,0 +1,145 @@ +// Build the Miden stdlib stubs and link them for dependents. +// +// We produce native static libraries (.a) that contain only the stub object +// files (no panic handler) to avoid duplicate panic symbols in downstream +// component builds. We do this by compiling rlibs with rustc and naming the +// outputs `.a` so dependents pick them up via the native link search path. +// +// Why not an rlib? +// - `cargo:rustc-link-lib`/`cargo:rustc-link-search` are for native archives; +// .rlib doesn’t fit that model and attempts to use `rustc-link-arg` don’t +// propagate to dependents. +// Why not a staticlib via rustc directly? +// - A no_std staticlib usually requires a `#[panic_handler]`, which then +// collides at link time with other crates that also define panic symbols. +// - Packaging a single object keeps the archive minimal and free of panic +// symbols. + +use std::{env, path::PathBuf, process::Command}; + +fn main() { + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let target = env::var("TARGET").unwrap_or_else(|_| "wasm32-wasip1".to_string()); + + if !target.starts_with("wasm32") { + // track changes, but don’t build + let stubs_root = manifest_dir.join("stubs"); + let src_root = stubs_root.join("lib.rs"); + if src_root.exists() { + println!("cargo:rerun-if-changed={}", src_root.display()); + } + return; + } + + println!("cargo:rerun-if-env-changed=TARGET"); + println!("cargo:rerun-if-env-changed=RUSTUP_TOOLCHAIN"); + println!("cargo:rerun-if-env-changed=RUSTFLAGS"); + + let stubs_root = manifest_dir.join("stubs"); + let src_root = stubs_root.join("lib.rs"); + println!("cargo:rerun-if-changed={}", src_root.display()); + // Ensure build script reruns when any stub file changes + if let Ok(read_dir) = std::fs::read_dir(&stubs_root) { + for entry in read_dir.flatten() { + let p = entry.path(); + if p.is_dir() { + if let Ok(inner) = std::fs::read_dir(&p) { + for e in inner.flatten() { + let pp = e.path(); + if pp.extension().and_then(|s| s.to_str()) == Some("rs") { + println!("cargo:rerun-if-changed={}", pp.display()); + } + } + } + } else if p.extension().and_then(|s| s.to_str()) == Some("rs") { + println!("cargo:rerun-if-changed={}", p.display()); + } + } + } + + // Build separate libraries for intrinsics and stdlib stubs to keep + // individual object sections small. Each rlib is emitted with a `.a` + // extension so cargo treats it as a native archive. + let out_intrinsics_rlib = out_dir.join("libmiden_stdlib_sys_intrinsics_stubs.a"); + let out_stdlib_rlib = out_dir.join("libmiden_stdlib_sys_stdlib_stubs.a"); + + // LLVM MergeFunctions pass https://llvm.org/docs/MergeFunctions.html considers some + // functions in the stub library identical (e.g. `intrinsics::felt::add` and + // `intrinsics::felt::mul`) because besides the same sig they have the same body + // (`unreachable`). The pass merges them which manifests in the compiled Wasm as if both + // `add` and `mul` are linked to the same (`add` in this case) function. + // Setting `opt-level=1` seems to be skipping this pass and is enough on its own, but I + // also put `-Z merge-functions=disabled` in case `opt-level=1` behaviour changes + // in the future and runs the MergeFunctions pass. + // `opt-level=0` - introduces import for panic infra leading to WIT encoder error (unsatisfied import). + + // Although the stdlib vs intrinsics split seems redundant in the future we will have to move + // the intrinsics to the separate crate with its own build.rs script. The reason for the + // separation is the automatic bindings generation. For the Miden stdlib the bindings will be + // generated from the Miden package, but for intrinsics they will be maintained manually since + // the intrinsics are part of the compiler. + + // 1a) Compile intrinsics stubs archive + let status = Command::new("rustc") + .arg("--crate-name") + .arg("miden_stdlib_sys_intrinsics_stubs") + .arg("--edition=2021") + .arg("--crate-type=rlib") + .arg("--target") + .arg(&target) + .arg("-C") + .arg("opt-level=1") + .arg("-C") + .arg("panic=abort") + .arg("-C") + .arg("codegen-units=1") + .arg("-C") + .arg("debuginfo=0") + .arg("-Z") + .arg("merge-functions=disabled") + .arg("-C") + .arg("target-feature=+bulk-memory,+wide-arithmetic") + .arg("-o") + .arg(&out_intrinsics_rlib) + .arg(stubs_root.join("intrinsics_root.rs")) + .status() + .expect("failed to spawn rustc for stdlib intrinsics stub object"); + if !status.success() { + panic!("failed to compile stdlib intrinsics stub object: {status}"); + } + + // 1b) Compile stdlib (mem/crypto) stubs archive + let status = Command::new("rustc") + .arg("--crate-name") + .arg("miden_stdlib_sys_stdlib_stubs") + .arg("--edition=2021") + .arg("--crate-type=rlib") + .arg("--target") + .arg(&target) + .arg("-C") + .arg("opt-level=1") + .arg("-C") + .arg("panic=abort") + .arg("-C") + .arg("codegen-units=1") + .arg("-C") + .arg("debuginfo=0") + .arg("-Z") + .arg("merge-functions=disabled") + .arg("-C") + .arg("target-feature=+bulk-memory,+wide-arithmetic") + .arg("-o") + .arg(&out_stdlib_rlib) + .arg(stubs_root.join("stdlib_root.rs")) + .status() + .expect("failed to spawn rustc for stdlib (mem/crypto) stub object"); + if !status.success() { + panic!("failed to compile stdlib (mem/crypto) stub object: {status}"); + } + + // Emit link directives for dependents + println!("cargo:rustc-link-search=native={}", out_dir.display()); + println!("cargo:rustc-link-lib=static=miden_stdlib_sys_intrinsics_stubs"); + println!("cargo:rustc-link-lib=static=miden_stdlib_sys_stdlib_stubs"); +} diff --git a/sdk/stdlib-sys/src/intrinsics/advice.rs b/sdk/stdlib-sys/src/intrinsics/advice.rs new file mode 100644 index 000000000..bd1bad527 --- /dev/null +++ b/sdk/stdlib-sys/src/intrinsics/advice.rs @@ -0,0 +1,76 @@ +//! Contains intrinsics for advice operations with the advice provider. + +use crate::{Felt, Word}; + +extern "C" { + /// Pushes a list of field elements onto the advice stack. + /// The list is looked up in the advice map using `key` as the key. + /// Returns the number of elements pushed on the advice stack. + #[link_name = "intrinsics::advice::adv_push_mapvaln"] + fn extern_adv_push_mapvaln(key0: Felt, key1: Felt, key2: Felt, key3: Felt) -> Felt; +} + +/// Pushes a list of field elements onto the advice stack. +/// The list is looked up in the advice map using `key` as the key. +/// Returns the number of elements pushed on the advice stack. +#[inline] +pub fn adv_push_mapvaln(key: Word) -> Felt { + unsafe { extern_adv_push_mapvaln(key[3], key[2], key[1], key[0]) } +} + +extern "C" { + /// Emits an event to request a Falcon signature for the provided message/public key. + /// This maps to a single MASM instruction: `emit.131087`. + #[link_name = "intrinsics::advice::emit_falcon_sig_to_stack"] + fn extern_emit_falcon_sig_to_stack( + msg0: Felt, + msg1: Felt, + msg2: Felt, + msg3: Felt, + pk0: Felt, + pk1: Felt, + pk2: Felt, + pk3: Felt, + ); +} + +/// Emits an event to request a Falcon signature for the current message/public key. +/// Host is expected to push the signature onto the advice stack in response. +/// This is a workaround until migrating to VM v0.18 where the `emit` op reads the value from the stack. +#[inline] +pub fn emit_falcon_sig_to_stack(msg: Word, pub_key: Word) { + unsafe { + extern_emit_falcon_sig_to_stack( + msg[3], msg[2], msg[1], msg[0], pub_key[3], pub_key[2], pub_key[1], pub_key[0], + ); + } +} + +extern "C" { + /// Inserts values from memory into the advice map using the provided key and memory range. + /// Maps to the VM op: adv.insert_mem + /// Signature: (key0..key3, start_addr, end_addr) + #[link_name = "intrinsics::advice::adv_insert_mem"] + fn extern_adv_insert_mem( + k0: Felt, + k1: Felt, + k2: Felt, + k3: Felt, + start_addr: u32, + end_addr: u32, + ); +} + +/// Insert memory region [start, end) into advice map under the given key. +#[inline] +pub fn adv_insert_mem(key: Word, start_addr: u32, end_addr: u32) { + unsafe { extern_adv_insert_mem(key[3], key[2], key[1], key[0], start_addr, end_addr) } +} + +/// Insert values into advice map under the given key. +pub fn adv_insert(key: Word, values: &[Word]) { + let rust_ptr = values.as_ptr() as u32; + let miden_ptr = rust_ptr / 4; + let end_addr = miden_ptr + values.len() as u32 * 4; + adv_insert_mem(key, miden_ptr, end_addr); +} diff --git a/sdk/stdlib-sys/src/intrinsics/crypto.rs b/sdk/stdlib-sys/src/intrinsics/crypto.rs new file mode 100644 index 000000000..f56b30eb6 --- /dev/null +++ b/sdk/stdlib-sys/src/intrinsics/crypto.rs @@ -0,0 +1,100 @@ +//! Cryptographic intrinsics for the Miden VM. +//! +//! This module provides Rust bindings for cryptographic operations available in the Miden VM. +#![allow(warnings)] + +use crate::intrinsics::{Felt, Word}; + +/// A cryptographic digest representing a 256-bit hash value. +/// +/// This is a wrapper around `Word` which contains 4 field elements. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[repr(transparent)] +pub struct Digest { + pub inner: Word, +} + +impl Digest { + /// Creates a new `Digest` from a `[Felt; 4]` array. + #[inline] + pub fn new(felts: [Felt; 4]) -> Self { + Self { + inner: Word::from(felts), + } + } + + /// Creates a new `Digest` from a `Word`. + #[inline] + pub const fn from_word(word: Word) -> Self { + Self { inner: word } + } +} + +impl From for Digest { + #[inline] + fn from(word: Word) -> Self { + Self::from_word(word) + } +} + +impl From for Word { + #[inline] + fn from(digest: Digest) -> Self { + digest.inner + } +} + +impl From<[Felt; 4]> for Digest { + #[inline] + fn from(felts: [Felt; 4]) -> Self { + Self::new(felts) + } +} + +impl From for [Felt; 4] { + #[inline] + fn from(digest: Digest) -> Self { + digest.inner.into() + } +} + +// Remove WIT import module and resolve via a linker stub instead. The stub will export +// the MASM symbol `intrinsics::crypto::hmerge`, and the frontend will lower its +// unreachable body to a MASM exec. +extern "C" { + /// Computes the hash of two digests using the Rescue Prime Optimized (RPO) + /// permutation in 2-to-1 mode. + /// + /// This is the `hmerge` instruction in the Miden VM. + /// + /// Input: Pointer to an array of two digests (8 field elements total) + /// Output: One digest (4 field elements) written to the result pointer + #[link_name = "intrinsics::crypto::hmerge"] + fn extern_hmerge( + // Pointer to array of two digests + digests_ptr: *const Felt, + // Result pointer + result_ptr: *mut Felt, + ); +} + +/// Computes the hash of two digests using the Rescue Prime Optimized (RPO) +/// permutation in 2-to-1 mode. +/// +/// This directly maps to the `hmerge` VM instruction. +/// +/// # Arguments +/// * `digests` - An array of two digests to be merged. The function internally +/// reorders them as required by the VM instruction (from [A, B] to [B, A] on the stack). +#[inline] +pub fn merge(digests: [Digest; 2]) -> Digest { + unsafe { + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let result_ptr = ret_area.as_mut_ptr().addr() as u32; + + let digests_ptr = digests.as_ptr().addr() as u32; + extern_hmerge(digests_ptr as *const Felt, result_ptr as *mut Felt); + + Digest::from_word(ret_area.assume_init()) + } +} diff --git a/sdk/stdlib-sys/src/intrinsics/debug.rs b/sdk/stdlib-sys/src/intrinsics/debug.rs new file mode 100644 index 000000000..f96c2d590 --- /dev/null +++ b/sdk/stdlib-sys/src/intrinsics/debug.rs @@ -0,0 +1,13 @@ +extern "C" { + #[link_name = "intrinsics::debug::break"] + fn extern_break(); +} + +/// Sets a breakpoint in the emitted Miden Assembly at the point this function is called. +#[inline(always)] +#[track_caller] +pub fn breakpoint() { + unsafe { + extern_break(); + } +} diff --git a/sdk/stdlib-sys/src/intrinsics/felt.rs b/sdk/stdlib-sys/src/intrinsics/felt.rs index 632eec144..c1c2477b4 100644 --- a/sdk/stdlib-sys/src/intrinsics/felt.rs +++ b/sdk/stdlib-sys/src/intrinsics/felt.rs @@ -1,65 +1,65 @@ -#![allow(clippy::transmute_int_to_float)] - use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; -#[link(wasm_import_module = "miden:stdlib/intrinsics_felt")] extern "C" { - #[link_name = "from_u64_unchecked"] + #[link_name = "intrinsics::felt::from_u64_unchecked"] fn extern_from_u64_unchecked(value: u64) -> Felt; - #[link_name = "as_u64"] - fn extern_as_u64(felt: Felt) -> u64; + #[link_name = "intrinsics::felt::from_u32"] + fn extern_from_u32(value: u32) -> Felt; - #[link_name = "add"] - fn extern_add(a: Felt, b: Felt) -> Felt; + #[link_name = "intrinsics::felt::as_u64"] + fn extern_as_u64(felt: Felt) -> u64; - #[link_name = "sub"] + #[link_name = "intrinsics::felt::sub"] fn extern_sub(a: Felt, b: Felt) -> Felt; - #[link_name = "mul"] + #[link_name = "intrinsics::felt::mul"] fn extern_mul(a: Felt, b: Felt) -> Felt; - #[link_name = "div"] + #[link_name = "intrinsics::felt::div"] fn extern_div(a: Felt, b: Felt) -> Felt; - #[link_name = "neg"] + #[link_name = "intrinsics::felt::neg"] fn extern_neg(a: Felt) -> Felt; - #[link_name = "inv"] + #[link_name = "intrinsics::felt::inv"] fn extern_inv(a: Felt) -> Felt; - #[link_name = "pow2"] + #[link_name = "intrinsics::felt::pow2"] fn extern_pow2(a: Felt) -> Felt; - #[link_name = "exp"] + #[link_name = "intrinsics::felt::exp"] fn extern_exp(a: Felt, b: Felt) -> Felt; - #[link_name = "eq"] + #[link_name = "intrinsics::felt::eq"] fn extern_eq(a: Felt, b: Felt) -> i32; - #[link_name = "gt"] + #[link_name = "intrinsics::felt::gt"] fn extern_gt(a: Felt, b: Felt) -> i32; - #[link_name = "lt"] + #[link_name = "intrinsics::felt::lt"] fn extern_lt(a: Felt, b: Felt) -> i32; - #[link_name = "ge"] + #[link_name = "intrinsics::felt::ge"] fn extern_ge(a: Felt, b: Felt) -> i32; - #[link_name = "le"] + #[link_name = "intrinsics::felt::le"] fn extern_le(a: Felt, b: Felt) -> i32; - #[link_name = "is_odd"] + #[link_name = "intrinsics::felt::is_odd"] fn extern_is_odd(a: Felt) -> i32; - #[link_name = "assert"] + #[link_name = "intrinsics::felt::assert"] fn extern_assert(a: Felt); - #[link_name = "assertz"] + #[link_name = "intrinsics::felt::assertz"] fn extern_assertz(a: Felt); - #[link_name = "assert_eq"] + #[link_name = "intrinsics::felt::assert_eq"] fn extern_assert_eq(a: Felt, b: Felt); + + #[link_name = "intrinsics::felt::add"] + fn extern_add(a: Felt, b: Felt) -> Felt; } /// Creates a `Felt` from an integer constant checking that it is within the @@ -69,8 +69,12 @@ macro_rules! felt { // Trigger a compile-time error if the value is not a constant ($value:literal) => {{ const VALUE: u64 = $value as u64; - assert!(VALUE <= Felt::M, "Invalid Felt value, must be >= 0 and <= 2^64 - 2^32 + 1"); - Felt::from_u64_unchecked(VALUE) + // assert!(VALUE <= Felt::M, "Invalid Felt value, must be >= 0 and <= 2^64 - 2^32 + 1"); + // Temporarily switch to `from_u32` to use `bitcast` and avoid checks. + // see https://github.com/0xMiden/compiler/issues/361 + assert!(VALUE <= u32::MAX as u64, "Invalid value, must be >= 0 and <= 2^32"); + const VALUE_U32: u32 = $value as u32; + Felt::from_u32(VALUE_U32) }}; } @@ -80,8 +84,10 @@ pub enum FeltError { } #[repr(transparent)] -#[derive(Copy, Clone)] -pub struct Felt(f32); +#[derive(Copy, Clone, Debug)] +pub struct Felt { + pub inner: f32, +} impl Felt { /// Field modulus = 2^64 - 2^32 + 1 @@ -92,6 +98,11 @@ impl Felt { unsafe { extern_from_u64_unchecked(value) } } + #[inline(always)] + pub fn from_u32(value: u32) -> Self { + unsafe { extern_from_u32(value) } + } + #[inline(always)] pub fn new(value: u64) -> Result { if value > Self::M { @@ -141,26 +152,34 @@ impl From for u64 { impl From for Felt { fn from(value: u32) -> Self { - Self(unsafe { core::mem::transmute::(value) }) + Self { + inner: f32::from_bits(value), + } } } impl From for Felt { fn from(value: u16) -> Self { - Self(unsafe { core::mem::transmute::(value as u32) }) + Self { + inner: f32::from_bits(value as u32), + } } } impl From for Felt { fn from(value: u8) -> Self { - Self(unsafe { core::mem::transmute::(value as u32) }) + Self { + inner: f32::from_bits(value as u32), + } } } #[cfg(target_pointer_width = "32")] impl From for Felt { fn from(value: usize) -> Self { - Self(unsafe { core::mem::transmute(value as u32) }) + Self { + inner: f32::from_bits(value as u32), + } } } @@ -286,7 +305,7 @@ impl Ord for Felt { } } -/// If `a` == 1, removes it from the stack. Fails if `a` != 1 +/// Fails if `a` != 1 #[inline(always)] pub fn assert(a: Felt) { unsafe { @@ -294,7 +313,7 @@ pub fn assert(a: Felt) { } } -/// If `a` == 0, removes it from the stack. Fails if `a` != 0 +/// Fails if `a` != 0 #[inline(always)] pub fn assertz(a: Felt) { unsafe { @@ -302,7 +321,7 @@ pub fn assertz(a: Felt) { } } -/// If `a` == `b`, removes them from the stack. Fails if `a` != `b` +/// Fails if `a` != `b` #[inline(always)] pub fn assert_eq(a: Felt, b: Felt) { unsafe { diff --git a/sdk/stdlib-sys/src/intrinsics/mod.rs b/sdk/stdlib-sys/src/intrinsics/mod.rs index f27a9d615..e7a49b64e 100644 --- a/sdk/stdlib-sys/src/intrinsics/mod.rs +++ b/sdk/stdlib-sys/src/intrinsics/mod.rs @@ -1,7 +1,16 @@ use core::ops::{Deref, DerefMut}; -pub(crate) mod felt; -pub(crate) mod word; +pub use self::{ + crypto::Digest, + felt::{assert_eq, Felt}, + word::Word, +}; + +pub mod advice; +pub mod crypto; +pub mod debug; +pub mod felt; +pub mod word; #[repr(C, align(32))] pub struct WordAligned(T); diff --git a/sdk/stdlib-sys/src/intrinsics/word.rs b/sdk/stdlib-sys/src/intrinsics/word.rs index a37278aa3..af7e96211 100644 --- a/sdk/stdlib-sys/src/intrinsics/word.rs +++ b/sdk/stdlib-sys/src/intrinsics/word.rs @@ -1,24 +1,57 @@ use core::ops::{Index, IndexMut}; -use crate::Felt; +use super::felt::Felt; +use crate::felt; -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[repr(C, align(32))] -pub struct Word([Felt; 4]); +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] +#[repr(C, align(16))] +pub struct Word { + pub inner: (Felt, Felt, Felt, Felt), +} impl Word { pub const fn new(word: [Felt; 4]) -> Self { - Self(word) + Self { + inner: (word[0], word[1], word[2], word[3]), + } + } + + pub fn reverse(&self) -> Word { + // This is workaround for the https://github.com/0xMiden/compiler/issues/596 to avoid + // i64.rotl op in the compiled Wasm + let mut arr: [Felt; 4] = self.into(); + arr.reverse(); + arr.into() } } impl From<[Felt; 4]> for Word { fn from(word: [Felt; 4]) -> Self { - Self(word) + Self { + inner: (word[0], word[1], word[2], word[3]), + } } } impl From for [Felt; 4] { #[inline(always)] fn from(word: Word) -> Self { - word.0 + [word.inner.0, word.inner.1, word.inner.2, word.inner.3] + } +} +impl From<&Word> for [Felt; 4] { + #[inline(always)] + fn from(word: &Word) -> Self { + [word.inner.0, word.inner.1, word.inner.2, word.inner.3] + } +} +impl From for Word { + fn from(value: Felt) -> Self { + Word { + inner: (felt!(0), felt!(0), felt!(0), value), + } + } +} +impl From for Felt { + fn from(value: Word) -> Self { + value.inner.3 } } impl Index for Word { @@ -26,12 +59,30 @@ impl Index for Word { #[inline(always)] fn index(&self, index: usize) -> &Self::Output { - self.0.index(index) + match index { + 0 => &self.inner.0, + 1 => &self.inner.1, + 2 => &self.inner.2, + 3 => &self.inner.3, + _ => unreachable!(), + } } } impl IndexMut for Word { #[inline(always)] fn index_mut(&mut self, index: usize) -> &mut Self::Output { - self.0.index_mut(index) + match index { + 0 => &mut self.inner.0, + 1 => &mut self.inner.1, + 2 => &mut self.inner.2, + 3 => &mut self.inner.3, + _ => unreachable!(), + } + } +} + +impl AsRef for Word { + fn as_ref(&self) -> &Word { + self } } diff --git a/sdk/stdlib-sys/src/lib.rs b/sdk/stdlib-sys/src/lib.rs index 55185e101..7e2a4681f 100644 --- a/sdk/stdlib-sys/src/lib.rs +++ b/sdk/stdlib-sys/src/lib.rs @@ -1,7 +1,12 @@ #![no_std] +#![deny(warnings)] -mod intrinsics; +extern crate alloc; + +pub mod intrinsics; mod stdlib; -pub use intrinsics::{felt::*, word::*, WordAligned}; +pub use intrinsics::{ + advice::emit_falcon_sig_to_stack, assert_eq, Digest, Felt, Word, WordAligned, +}; pub use stdlib::*; diff --git a/sdk/stdlib-sys/src/stdlib/crypto/dsa.rs b/sdk/stdlib-sys/src/stdlib/crypto/dsa.rs index dab29c09e..dd9321fc3 100644 --- a/sdk/stdlib-sys/src/stdlib/crypto/dsa.rs +++ b/sdk/stdlib-sys/src/stdlib/crypto/dsa.rs @@ -1,8 +1,7 @@ -use crate::{Felt, Word}; +use crate::intrinsics::{Felt, Word}; -#[link(wasm_import_module = "std::crypto::dsa::rpo_falcon512")] extern "C" { - #[link_name = "rpo_falcon512_verify<0x0000000000000000000000000000000000000000000000000000000000000000>"] + #[link_name = "std::crypto::dsa::rpo_falcon512::verify"] fn extern_rpo_falcon512_verify( pk1: Felt, pk2: Felt, @@ -23,14 +22,14 @@ extern "C" { /// Where `pk` is the hash of the public key and `msg` is the hash of the message. Both hashes are /// expected to be computed using RPO hash function. /// -/// The procedure relies on the `adv.push_sig` decorator to retrieve the signature from the host. -/// The default host implementation assumes that the private-public key pair is loaded into the -/// advice provider, and uses it to generate the signature. However, for production grade -/// implementations, this functionality should be overridden to ensure more secure handling of -/// private keys. +/// The verification expects the signature to be provided by the host via the advice stack. +/// In the current flow, callers should first trigger a signature request event using +/// `crate::emit_falcon_sig_to_stack(msg, pk)` and then call this function. The host must respond by +/// pushing the signature to the advice stack. For production deployments, ensure secret key +/// handling occurs outside the VM. #[inline(always)] pub fn rpo_falcon512_verify(pk: Word, msg: Word) { unsafe { - extern_rpo_falcon512_verify(pk[0], pk[1], pk[2], pk[3], msg[0], msg[1], msg[2], msg[3]); + extern_rpo_falcon512_verify(pk[3], pk[2], pk[1], pk[0], msg[3], msg[2], msg[1], msg[0]); } } diff --git a/sdk/stdlib-sys/src/stdlib/crypto/hashes.rs b/sdk/stdlib-sys/src/stdlib/crypto/hashes.rs index 4994cc9b9..40c94578b 100644 --- a/sdk/stdlib-sys/src/stdlib/crypto/hashes.rs +++ b/sdk/stdlib-sys/src/stdlib/crypto/hashes.rs @@ -2,14 +2,20 @@ //! functions. The input and output elements are assumed to contain one 32-bit //! value per element. -#[link(wasm_import_module = "std::crypto::hashes::blake3")] +use alloc::vec::Vec; + +use crate::{ + felt, + intrinsics::{assert_eq, Digest, Felt, Word}, +}; + extern "C" { /// Computes BLAKE3 1-to-1 hash. /// /// Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element). /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element). /// The output is passed back to the caller via a pointer. - #[link_name = "hash_1to1<0x0000000000000000000000000000000000000000000000000000000000000000>"] + #[link_name = "std::crypto::hashes::blake3::hash_1to1"] fn extern_blake3_hash_1to1( e1: u32, e2: u32, @@ -27,7 +33,7 @@ extern "C" { /// Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element). /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element) /// The output is passed back to the caller via a pointer. - #[link_name = "hash_2to1<0x0000000000000000000000000000000000000000000000000000000000000000>"] + #[link_name = "std::crypto::hashes::blake3::hash_2to1"] fn extern_blake3_hash_2to1( e1: u32, e2: u32, @@ -49,14 +55,13 @@ extern "C" { ); } -#[link(wasm_import_module = "std::crypto::hashes::sha256")] extern "C" { /// Computes SHA256 1-to-1 hash. /// /// Input: 32-bytes stored in the first 8 elements of the stack (32 bits per element). /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element). /// The output is passed back to the caller via a pointer. - #[link_name = "sha256_hash_1to1<0x0000000000000000000000000000000000000000000000000000000000000000>"] + #[link_name = "std::crypto::hashes::sha256::hash_1to1"] fn extern_sha256_hash_1to1( e1: u32, e2: u32, @@ -74,7 +79,7 @@ extern "C" { /// Input: 64-bytes stored in the first 16 elements of the stack (32 bits per element). /// Output: A 32-byte digest stored in the first 8 elements of stack (32 bits per element). /// The output is passed back to the caller via a pointer. - #[link_name = "sha256_hash_2to1<0x0000000000000000000000000000000000000000000000000000000000000000>"] + #[link_name = "std::crypto::hashes::sha256::hash_2to1"] fn extern_sha256_hash_2to1( e1: u32, e2: u32, @@ -96,14 +101,38 @@ extern "C" { ); } +extern "C" { + /// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO) + /// hash function. + /// + /// This maps to the `std::crypto::rpo::hash_memory` procedure in the Miden stdlib. + /// + /// Input: A pointer to the memory location and the number of elements to hash + /// Output: One digest (4 field elements) + /// The output is passed back to the caller via a pointer. + #[link_name = "std::crypto::hashes::rpo::hash_memory"] + pub fn extern_hash_memory(ptr: u32, num_elements: u32, result_ptr: *mut Felt); + + /// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO) hash + /// function. + /// + /// This maps to the `std::crypto::hashes::rpo::hash_memory_words` procedure in the Miden + /// stdlib. + /// + /// Input: The start and end addresses (in field elements) of the words to hash. + /// Output: One digest (4 field elements) + /// The output is passed back to the caller via a pointer. + #[link_name = "std::crypto::hashes::rpo::hash_memory_words"] + pub fn extern_hash_memory_words(start_addr: u32, end_addr: u32, result_ptr: *mut Felt); +} + /// Hashes a 32-byte input to a 32-byte output using the given hash function. #[inline(always)] fn hash_1to1( input: [u8; 32], extern_hash_1to1: unsafe extern "C" fn(u32, u32, u32, u32, u32, u32, u32, u32, *mut u8), ) -> [u8; 32] { - use crate::WordAligned; - + use crate::intrinsics::WordAligned; let input = unsafe { core::mem::transmute::<[u8; 32], [u32; 8]>(input) }; unsafe { let mut ret_area = ::core::mem::MaybeUninit::>::uninit(); @@ -175,3 +204,63 @@ pub fn sha256_hash_1to1(input: [u8; 32]) -> [u8; 32] { pub fn sha256_hash_2to1(input: [u8; 64]) -> [u8; 32] { hash_2to1(input, extern_sha256_hash_2to1) } + +/// Computes the hash of a sequence of field elements using the Rescue Prime Optimized (RPO) +/// hash function. +/// +/// This maps to the `std::crypto::rpo::hash_memory` procedure in the Miden stdlib and to the +/// `std::crypto::hashes::rpo::hash_memory_words` word-optimized variant when the input length is a +/// multiple of 4. +/// +/// # Arguments +/// * `elements` - A Vec of field elements to be hashed +#[inline] +pub fn hash_elements(elements: Vec) -> Digest { + let rust_ptr = elements.as_ptr().addr() as u32; + let element_count = elements.len(); + let num_elements = element_count as u32; + + unsafe { + let mut ret_area = core::mem::MaybeUninit::::uninit(); + let result_ptr = ret_area.as_mut_ptr() as *mut Felt; + let miden_ptr = rust_ptr / 4; + // Since our BumpAlloc produces word-aligned allocations the pointer should be word-aligned + assert_eq(Felt::from_u32(miden_ptr % 4), felt!(0)); + + if element_count.is_multiple_of(4) { + let start_addr = miden_ptr; + let end_addr = start_addr + num_elements; + extern_hash_memory_words(start_addr, end_addr, result_ptr); + } else { + extern_hash_memory(miden_ptr, num_elements, result_ptr); + } + + Digest::from_word(ret_area.assume_init().reverse()) + } +} + +/// Computes the hash of a sequence of words using the Rescue Prime Optimized (RPO) +/// hash function. +/// +/// This maps to the `std::crypto::hashes::rpo::hash_memory_words` procedure in the Miden stdlib. +/// +/// # Arguments +/// * `words` - A slice of words to be hashed +#[inline] +pub fn hash_words(words: &[Word]) -> Digest { + let rust_ptr = words.as_ptr().addr() as u32; + + unsafe { + let mut ret_area = core::mem::MaybeUninit::::uninit(); + let result_ptr = ret_area.as_mut_ptr() as *mut Felt; + let miden_ptr = rust_ptr / 4; + // It's safe to assume the `words` ptr is word-aligned. + assert_eq(Felt::from_u32(miden_ptr % 4), felt!(0)); + + let start_addr = miden_ptr; + let end_addr = start_addr + (words.len() as u32 * 4); + extern_hash_memory_words(start_addr, end_addr, result_ptr); + + Digest::from_word(ret_area.assume_init().reverse()) + } +} diff --git a/sdk/stdlib-sys/src/stdlib/mem.rs b/sdk/stdlib-sys/src/stdlib/mem.rs index aedb437b7..73062026a 100644 --- a/sdk/stdlib-sys/src/stdlib/mem.rs +++ b/sdk/stdlib-sys/src/stdlib/mem.rs @@ -3,9 +3,11 @@ extern crate alloc; use alloc::vec::Vec; -use crate::{Felt, Word}; +use crate::{ + felt, + intrinsics::{Felt, Word}, +}; -#[link(wasm_import_module = "std::mem")] extern "C" { /// Moves an arbitrary number of words from the advice stack to memory. @@ -18,7 +20,7 @@ extern "C" { /// Cycles: /// - Even num_words: 48 + 9 * num_words / 2 /// - Odd num_words: 65 + 9 * round_down(num_words / 2) - #[link_name = "pipe_words_to_memory<0x0000000000000000000000000000000000000000000000000000000000000000>"] + #[link_name = "std::mem::pipe_words_to_memory"] fn extern_pipe_words_to_memory(num_words: Felt, ptr: *mut Felt, out_ptr: *mut Felt); /// Moves an even number of words from the advice stack to memory. @@ -33,7 +35,7 @@ extern "C" { /// - The value num_words = end_ptr - write_ptr must be positive and even /// /// Cycles: 10 + 9 * num_words / 2 - #[link_name = "pipe_double_words_to_memory<0x0000000000000000000000000000000000000000000000000000000000000000>"] + #[link_name = "std::mem::pipe_double_words_to_memory"] fn extern_pipe_double_words_to_memory( c0: Felt, c1: Felt, @@ -51,6 +53,24 @@ extern "C" { end_ptr: *mut Felt, out_ptr: *mut Felt, ); + + /// Moves an arbitrary number of words from the advice stack to memory and asserts it matches the commitment. + /// + /// Input: [num_words, write_ptr, COM, ...] + /// Output: [write_ptr', ...] + /// + /// Cycles: + /// - Even num_words: 58 + 9 * (num_words / 2) + /// - Odd num_words: 75 + 9 * round_down(num_words / 2) + #[link_name = "std::mem::pipe_preimage_to_memory"] + pub(crate) fn extern_pipe_preimage_to_memory( + num_words: Felt, + write_ptr: *mut Felt, + com0: Felt, + com1: Felt, + com2: Felt, + com3: Felt, + ) -> i32; } /// Reads an arbitrary number of words `num_words` from the advice stack and returns them along with @@ -96,7 +116,7 @@ pub fn pipe_double_words_to_memory(num_words: Felt) -> (Word, Vec) { let end_ptr = unsafe { write_ptr.add(num_words_in_felts) }; // Place for returned C, B, A, write_ptr let mut ret_area = ::core::mem::MaybeUninit::::uninit(); - let zero = Felt::from_u64_unchecked(0); + let zero = felt!(0); unsafe { extern_pipe_double_words_to_memory( zero, @@ -116,7 +136,35 @@ pub fn pipe_double_words_to_memory(num_words: Felt) -> (Word, Vec) { ret_area.as_mut_ptr() as *mut Felt, ); let Result { b, .. } = ret_area.assume_init(); - // B (second) is the hash (see https://github.com/0xPolygonMiden/miden-vm/blob/3a957f7c90176914bda2139f74bff9e5700d59ac/stdlib/asm/crypto/hashes/native.masm#L1-L16 ) + // B (second) is the hash (see https://github.com/0xMiden/miden-vm/blob/3a957f7c90176914bda2139f74bff9e5700d59ac/stdlib/asm/crypto/hashes/native.masm#L1-L16 ) (b, buf) } } + +/// Pops an arbitrary number of words from the advice stack and asserts it matches the commitment. +/// Returns a Vec containing the loaded words. +#[inline] +pub fn adv_load_preimage(num_words: Felt, commitment: Word) -> Vec { + // Allocate a Vec with the specified capacity + let num_words_usize = num_words.as_u64() as usize; + let num_felts = num_words_usize * 4; + let mut result: Vec = Vec::with_capacity(num_felts); + + let result_miden_ptr = (result.as_mut_ptr() as usize) / 4; + unsafe { + // Call pipe_preimage_to_memory to load words from advice stack + extern_pipe_preimage_to_memory( + num_words, + result_miden_ptr as *mut Felt, + commitment[3], + commitment[2], + commitment[1], + commitment[0], + ); + + // Set the length of the Vec to match what was loaded + result.set_len(num_felts); + } + + result +} diff --git a/sdk/stdlib-sys/src/stdlib/mod.rs b/sdk/stdlib-sys/src/stdlib/mod.rs index 2111d23da..000b317d7 100644 --- a/sdk/stdlib-sys/src/stdlib/mod.rs +++ b/sdk/stdlib-sys/src/stdlib/mod.rs @@ -3,5 +3,3 @@ mod mem; pub use crypto::*; pub use mem::*; - -// TODO: simplify bindings like in tx_kernel diff --git a/sdk/stdlib-sys/stubs/crypto/hashes_blake3.rs b/sdk/stdlib-sys/stubs/crypto/hashes_blake3.rs new file mode 100644 index 000000000..09114c23a --- /dev/null +++ b/sdk/stdlib-sys/stubs/crypto/hashes_blake3.rs @@ -0,0 +1,46 @@ +use core::ffi::c_void; + +/// Unreachable stubs for std::crypto::hashes::blake3 + +#[export_name = "std::crypto::hashes::blake3::hash_1to1"] +pub extern "C" fn blake3_hash_1to1_stub( + e1: u32, + e2: u32, + e3: u32, + e4: u32, + e5: u32, + e6: u32, + e7: u32, + e8: u32, + result_ptr: *mut c_void, +) { + let _ = (e1, e2, e3, e4, e5, e6, e7, e8, result_ptr); + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "std::crypto::hashes::blake3::hash_2to1"] +pub extern "C" fn blake3_hash_2to1_stub( + e1: u32, + e2: u32, + e3: u32, + e4: u32, + e5: u32, + e6: u32, + e7: u32, + e8: u32, + e9: u32, + e10: u32, + e11: u32, + e12: u32, + e13: u32, + e14: u32, + e15: u32, + e16: u32, + result_ptr: *mut c_void, +) { + let _ = ( + e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, result_ptr, + ); + unsafe { core::hint::unreachable_unchecked() } +} + diff --git a/sdk/stdlib-sys/stubs/crypto/hashes_rpo.rs b/sdk/stdlib-sys/stubs/crypto/hashes_rpo.rs new file mode 100644 index 000000000..3eaddd1ba --- /dev/null +++ b/sdk/stdlib-sys/stubs/crypto/hashes_rpo.rs @@ -0,0 +1,20 @@ +use core::ffi::c_void; + +/// Unreachable stub for std::crypto::hashes::rpo::hash_memory + +#[export_name = "std::crypto::hashes::rpo::hash_memory"] +pub extern "C" fn rpo_hash_memory_stub(ptr: u32, num_elements: u32, result_ptr: *mut c_void) { + let _ = (ptr, num_elements, result_ptr); + unsafe { core::hint::unreachable_unchecked() } +} + +/// Unreachable stub for std::crypto::hashes::rpo::hash_memory_words +#[export_name = "std::crypto::hashes::rpo::hash_memory_words"] +pub extern "C" fn rpo_hash_memory_words_stub( + start_addr: u32, + end_addr: u32, + result_ptr: *mut c_void, +) { + let _ = (start_addr, end_addr, result_ptr); + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/sdk/stdlib-sys/stubs/crypto/hashes_sha256.rs b/sdk/stdlib-sys/stubs/crypto/hashes_sha256.rs new file mode 100644 index 000000000..ede9ab358 --- /dev/null +++ b/sdk/stdlib-sys/stubs/crypto/hashes_sha256.rs @@ -0,0 +1,46 @@ +use core::ffi::c_void; + +/// Unreachable stubs for std::crypto::hashes::sha256 + +#[export_name = "std::crypto::hashes::sha256::hash_1to1"] +pub extern "C" fn sha256_hash_1to1_stub( + e1: u32, + e2: u32, + e3: u32, + e4: u32, + e5: u32, + e6: u32, + e7: u32, + e8: u32, + result_ptr: *mut c_void, +) { + let _ = (e1, e2, e3, e4, e5, e6, e7, e8, result_ptr); + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "std::crypto::hashes::sha256::hash_2to1"] +pub extern "C" fn sha256_hash_2to1_stub( + e1: u32, + e2: u32, + e3: u32, + e4: u32, + e5: u32, + e6: u32, + e7: u32, + e8: u32, + e9: u32, + e10: u32, + e11: u32, + e12: u32, + e13: u32, + e14: u32, + e15: u32, + e16: u32, + result_ptr: *mut c_void, +) { + let _ = ( + e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11, e12, e13, e14, e15, e16, result_ptr, + ); + unsafe { core::hint::unreachable_unchecked() } +} + diff --git a/sdk/stdlib-sys/stubs/crypto/mod.rs b/sdk/stdlib-sys/stubs/crypto/mod.rs new file mode 100644 index 000000000..04239a96b --- /dev/null +++ b/sdk/stdlib-sys/stubs/crypto/mod.rs @@ -0,0 +1,4 @@ +mod hashes_blake3; +mod hashes_sha256; +mod hashes_rpo; +mod rpo_falcon_dsa; diff --git a/sdk/stdlib-sys/stubs/crypto/rpo_falcon_dsa.rs b/sdk/stdlib-sys/stubs/crypto/rpo_falcon_dsa.rs new file mode 100644 index 000000000..91afea78c --- /dev/null +++ b/sdk/stdlib-sys/stubs/crypto/rpo_falcon_dsa.rs @@ -0,0 +1,18 @@ +#![no_std] + +/// Unreachable stub for `std::crypto::dsa::rpo_falcon512::verify`. +/// +/// This satisfies link-time references and allows the compiler to lower calls to MASM. +#[export_name = "std::crypto::dsa::rpo_falcon512::verify"] +pub extern "C" fn rpo_falcon512_verify_stub( + _pk1: f32, + _pk2: f32, + _pk3: f32, + _pk4: f32, + _msg1: f32, + _msg2: f32, + _msg3: f32, + _msg4: f32, +) { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/sdk/stdlib-sys/stubs/intrinsics/advice.rs b/sdk/stdlib-sys/stubs/intrinsics/advice.rs new file mode 100644 index 000000000..d4ab6f057 --- /dev/null +++ b/sdk/stdlib-sys/stubs/intrinsics/advice.rs @@ -0,0 +1,51 @@ +/// Unreachable stubs for intrinsics::advice interface + +#[export_name = "intrinsics::advice::adv_push_mapvaln"] +pub extern "C" fn advice_adv_push_mapvaln_stub( + _key0: f32, + _key1: f32, + _key2: f32, + _key3: f32, +) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::advice::emit_falcon_sig_to_stack"] +pub extern "C" fn advice_emit_falcon_sig_to_stack_stub( + _m0: f32, + _m1: f32, + _m2: f32, + _m3: f32, + _k0: f32, + _k1: f32, + _k2: f32, + _k3: f32, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::advice::adv_insert_mem"] +pub extern "C" fn advice_adv_insert_mem_stub( + _k0: f32, + _k1: f32, + _k2: f32, + _k3: f32, + _start: i32, + _end: i32, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::advice::emit_and_verify_falcon"] +pub extern "C" fn advice_emit_and_verify_falcon_stub( + _m0: f32, + _m1: f32, + _m2: f32, + _m3: f32, + _k0: f32, + _k1: f32, + _k2: f32, + _k3: f32, +) { + unsafe { core::hint::unreachable_unchecked() } +} diff --git a/sdk/stdlib-sys/stubs/intrinsics/crypto.rs b/sdk/stdlib-sys/stubs/intrinsics/crypto.rs new file mode 100644 index 000000000..6e2d2e7b7 --- /dev/null +++ b/sdk/stdlib-sys/stubs/intrinsics/crypto.rs @@ -0,0 +1,9 @@ +use core::ffi::c_void; + +/// Unreachable stub for intrinsics::crypto::hmerge. +/// Signature in Wasm is (i32 digests_ptr, i32 result_ptr) +#[export_name = "intrinsics::crypto::hmerge"] +pub extern "C" fn hmerge_stub(_digests_ptr: *const c_void, _result_ptr: *mut c_void) { + unsafe { core::hint::unreachable_unchecked() } +} + diff --git a/sdk/stdlib-sys/stubs/intrinsics/debug.rs b/sdk/stdlib-sys/stubs/intrinsics/debug.rs new file mode 100644 index 000000000..9767cb15e --- /dev/null +++ b/sdk/stdlib-sys/stubs/intrinsics/debug.rs @@ -0,0 +1,7 @@ +/// Unreachable stubs for intrinsics::debug interface + +#[export_name = "intrinsics::debug::break"] +pub extern "C" fn debug_break_stub() { + unsafe { core::hint::unreachable_unchecked() } +} + diff --git a/sdk/stdlib-sys/stubs/intrinsics/felt.rs b/sdk/stdlib-sys/stubs/intrinsics/felt.rs new file mode 100644 index 000000000..b6c18c060 --- /dev/null +++ b/sdk/stdlib-sys/stubs/intrinsics/felt.rs @@ -0,0 +1,104 @@ +/// Unreachable stubs for intrinsics::felt::* functions. +/// These are linked by name, and the frontend lowers calls +/// to MASM operations or functions accordingly. + +#[export_name = "intrinsics::felt::add"] +pub extern "C" fn felt_add_stub(_a: f32, _b: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::from_u64_unchecked"] +pub extern "C" fn felt_from_u64_unchecked_stub(_v: u64) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::from_u32"] +pub extern "C" fn felt_from_u32_stub(_v: u32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::as_u64"] +pub extern "C" fn felt_as_u64_stub(_a: f32) -> u64 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::sub"] +pub extern "C" fn felt_sub_stub(_a: f32, _b: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::mul"] +pub fn felt_mul_stub(_a: f32, _b: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::div"] +pub extern "C" fn felt_div_stub(_a: f32, _b: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::neg"] +pub extern "C" fn felt_neg_stub(_a: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::inv"] +pub extern "C" fn felt_inv_stub(_a: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::pow2"] +pub extern "C" fn felt_pow2_stub(_a: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::exp"] +pub extern "C" fn felt_exp_stub(_a: f32, _b: f32) -> f32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::eq"] +pub extern "C" fn felt_eq_stub(_a: f32, _b: f32) -> i32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::gt"] +pub extern "C" fn felt_gt_stub(_a: f32, _b: f32) -> i32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::lt"] +pub extern "C" fn felt_lt_stub(_a: f32, _b: f32) -> i32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::ge"] +pub extern "C" fn felt_ge_stub(_a: f32, _b: f32) -> i32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::le"] +pub extern "C" fn felt_le_stub(_a: f32, _b: f32) -> i32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::is_odd"] +pub extern "C" fn felt_is_odd_stub(_a: f32) -> i32 { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::assert"] +pub extern "C" fn felt_assert_stub(_a: f32) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::assertz"] +pub extern "C" fn felt_assertz_stub(_a: f32) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "intrinsics::felt::assert_eq"] +pub extern "C" fn felt_assert_eq_stub(_a: f32, _b: f32) { + unsafe { core::hint::unreachable_unchecked() } +} + diff --git a/sdk/stdlib-sys/stubs/intrinsics/mod.rs b/sdk/stdlib-sys/stubs/intrinsics/mod.rs new file mode 100644 index 000000000..d5cb69d23 --- /dev/null +++ b/sdk/stdlib-sys/stubs/intrinsics/mod.rs @@ -0,0 +1,4 @@ +mod felt; +mod crypto; +mod debug; +mod advice; diff --git a/sdk/stdlib-sys/stubs/intrinsics_root.rs b/sdk/stdlib-sys/stubs/intrinsics_root.rs new file mode 100644 index 000000000..b3b6de171 --- /dev/null +++ b/sdk/stdlib-sys/stubs/intrinsics_root.rs @@ -0,0 +1,4 @@ +#![no_std] + +mod intrinsics; + diff --git a/sdk/stdlib-sys/stubs/mem.rs b/sdk/stdlib-sys/stubs/mem.rs new file mode 100644 index 000000000..aec9fc0c1 --- /dev/null +++ b/sdk/stdlib-sys/stubs/mem.rs @@ -0,0 +1,46 @@ +use core::ffi::c_void; + +/// Unreachable stubs for std::mem procedures used via SDK + +#[export_name = "std::mem::pipe_words_to_memory"] +pub extern "C" fn std_mem_pipe_words_to_memory_stub( + _num_words: f32, + _write_ptr: *mut c_void, + _out_ptr: *mut c_void, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "std::mem::pipe_double_words_to_memory"] +pub extern "C" fn std_mem_pipe_double_words_to_memory_stub( + _c0: f32, + _c1: f32, + _c2: f32, + _c3: f32, + _b0: f32, + _b1: f32, + _b2: f32, + _b3: f32, + _a0: f32, + _a1: f32, + _a2: f32, + _a3: f32, + _write_ptr: *mut c_void, + _end_ptr: *mut c_void, + _out_ptr: *mut c_void, +) { + unsafe { core::hint::unreachable_unchecked() } +} + +#[export_name = "std::mem::pipe_preimage_to_memory"] +pub extern "C" fn std_mem_pipe_preimage_to_memory_stub( + _num_words: f32, + _write_ptr: *mut c_void, + _c0: f32, + _c1: f32, + _c2: f32, + _c3: f32, +) -> i32 { + unsafe { core::hint::unreachable_unchecked() } +} + diff --git a/sdk/stdlib-sys/stubs/stdlib_root.rs b/sdk/stdlib-sys/stubs/stdlib_root.rs new file mode 100644 index 000000000..8e3b7ee97 --- /dev/null +++ b/sdk/stdlib-sys/stubs/stdlib_root.rs @@ -0,0 +1,5 @@ +#![no_std] + +mod mem; +mod crypto; + diff --git a/tests/integration-node/Cargo.toml b/tests/integration-node/Cargo.toml new file mode 100644 index 000000000..377df7f81 --- /dev/null +++ b/tests/integration-node/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "miden-integration-node-tests" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true +publish = false + +[dependencies] +anyhow.workspace = true +fs2 = "0.4" +miden-client = { version = "0.11", features = [ + "tonic", + "sqlite", +] } +miden-core.workspace = true +miden-mast-package.workspace = true +miden-objects = { workspace = true, features = ["std"] } +midenc-frontend-wasm.workspace = true +rand = "0.9" +temp-dir = "0.1" +tokio.workspace = true +uuid = { version = "1.10", features = ["v4"] } + +# For accessing the test builder from the main integration tests +miden-integration-tests = { path = "../integration" } diff --git a/tests/integration-node/README.md b/tests/integration-node/README.md new file mode 100644 index 000000000..0bdd85ddb --- /dev/null +++ b/tests/integration-node/README.md @@ -0,0 +1,24 @@ +# Miden Integration Node Tests + +This crate contains integration tests that require a local Miden node instance or testnet connection. + +## Overview + +The tests in this crate are separated from the main integration tests because they: +- Require a local Miden node to be running or testnet connectivity +- Are slower due to network operations and multi-step nature of the test scenarios + +## Running Tests + +To see debug output from the node: + +```bash +MIDEN_NODE_OUTPUT=1 cargo test -p miden-integration-node-tests +``` + +## Process Cleanup + +The local node management system ensures that: +- Only one node instance runs at a time, shared across all tests +- The node is automatically stopped when the last test using the node is finished +- No orphaned miden-node processes remain after test execution diff --git a/tests/integration-node/src/lib.rs b/tests/integration-node/src/lib.rs new file mode 100644 index 000000000..a11a749db --- /dev/null +++ b/tests/integration-node/src/lib.rs @@ -0,0 +1,6 @@ +//! Integration tests that are deploying code and runnning test scenarior on a local Miden node instance or testnet + +pub mod local_node; + +#[cfg(test)] +mod node_tests; diff --git a/tests/integration-node/src/local_node/handle.rs b/tests/integration-node/src/local_node/handle.rs new file mode 100644 index 000000000..751c066e2 --- /dev/null +++ b/tests/integration-node/src/local_node/handle.rs @@ -0,0 +1,92 @@ +//! Handle for managing shared node instances + +use std::fs; + +use anyhow::{Context, Result}; +use uuid::Uuid; + +use super::{ + process::{is_port_in_use, is_process_running, start_shared_node}, + ref_count_dir, rpc_url, + setup::LocalMidenNode, + sync::{ + acquire_lock, add_reference, get_ref_count, read_pid, stop_node_if_no_references, write_pid, + }, + RPC_PORT, +}; + +/// Handle to the shared node instance. When dropped, decrements the reference count. +pub struct SharedNodeHandle { + /// The RPC URL of the shared node + rpc_url: String, + /// Unique ID for this handle + handle_id: String, +} + +impl SharedNodeHandle { + /// Get the RPC URL for connecting to the node + pub fn rpc_url(&self) -> &str { + &self.rpc_url + } +} + +impl Drop for SharedNodeHandle { + fn drop(&mut self) { + eprintln!("[SharedNode] Dropping handle {}", self.handle_id); + + // Remove our reference file + let ref_file = ref_count_dir().join(&self.handle_id); + if let Err(e) = fs::remove_file(&ref_file) { + eprintln!("[SharedNode] Warning: Failed to remove ref file: {e}"); + } + + stop_node_if_no_references(); + } +} + +/// Ensure the shared node is running and return a handle to it +pub async fn ensure_shared_node() -> Result { + LocalMidenNode::ensure_installed().context("Failed to ensure miden-node is installed")?; + + let handle_id = format!("handle-{}-{}", std::process::id(), Uuid::new_v4()); + let _lock = acquire_lock().context("Failed to acquire lock for node coordination")?; + + let existing_pid = read_pid().context("Failed to read PID file")?; + + let pid = match existing_pid { + Some(pid) if is_process_running(pid) => { + // Check if the node is actually responding + if is_port_in_use(RPC_PORT) { + eprintln!("[SharedNode] Using existing node process {pid}"); + pid + } else { + eprintln!("[SharedNode] Found dead node process {pid}, restarting..."); + // Node process exists but isn't responding, start a new one + let new_pid = start_shared_node() + .await + .context("Failed to start new node after finding dead process")?; + write_pid(new_pid).context("Failed to write PID file")?; + new_pid + } + } + _ => { + // No running node, start a new one + eprintln!("[SharedNode] No existing node found, starting new instance"); + let new_pid = start_shared_node().await.context("Failed to start new node instance")?; + write_pid(new_pid).context("Failed to write PID file")?; + new_pid + } + }; + + // Add our reference + add_reference(&handle_id).context("Failed to add reference for handle")?; + + // Log current state + let ref_count = get_ref_count().context("Failed to get reference count")?; + eprintln!("[SharedNode] Node PID: {pid}, Reference count: {ref_count}"); + + Ok(SharedNodeHandle { + rpc_url: rpc_url(), + handle_id, + }) +} diff --git a/tests/integration-node/src/local_node/mod.rs b/tests/integration-node/src/local_node/mod.rs new file mode 100644 index 000000000..c2b628e30 --- /dev/null +++ b/tests/integration-node/src/local_node/mod.rs @@ -0,0 +1,40 @@ +//! Infrastructure for running a local Miden node for integration tests + +use std::path::PathBuf; + +mod handle; +mod process; +mod setup; +mod sync; + +pub use handle::{ensure_shared_node, SharedNodeHandle}; + +// Base directory for all miden test node files +const BASE_DIR: &str = "/tmp/miden-test-node"; + +// Re-export constants that are used in multiple modules +pub(crate) const COORD_DIR: &str = BASE_DIR; + +// Construct paths at runtime since concat! doesn't work with const values +pub(crate) fn pid_file() -> PathBuf { + PathBuf::from(BASE_DIR).join("node.pid") +} + +pub(crate) fn ref_count_dir() -> PathBuf { + PathBuf::from(BASE_DIR).join("refs") +} + +pub(crate) fn lock_file() -> PathBuf { + PathBuf::from(BASE_DIR).join("node.lock") +} + +pub(crate) fn data_dir() -> PathBuf { + PathBuf::from(BASE_DIR).join("data") +} + +pub(crate) const RPC_PORT: u16 = 57291; + +// Construct RPC URL using the port constant +pub(crate) fn rpc_url() -> String { + format!("http://127.0.0.1:{RPC_PORT}") +} diff --git a/tests/integration-node/src/local_node/process.rs b/tests/integration-node/src/local_node/process.rs new file mode 100644 index 000000000..06c01c175 --- /dev/null +++ b/tests/integration-node/src/local_node/process.rs @@ -0,0 +1,159 @@ +//! Process management functionality for the shared node + +use std::{ + fs::{self, File}, + io::BufRead, + net::TcpStream, + process::{Command, Stdio}, + thread, + time::{Duration, Instant}, +}; + +use anyhow::{anyhow, Context, Result}; + +use super::{data_dir, rpc_url, setup::LocalMidenNode, RPC_PORT}; + +/// Check if a port is in use +pub fn is_port_in_use(port: u16) -> bool { + TcpStream::connect(("127.0.0.1", port)).is_ok() +} + +/// Check if a process is running +pub fn is_process_running(pid: u32) -> bool { + // Try to read from /proc/{pid}/stat on Linux/macOS + #[cfg(target_os = "linux")] + { + std::path::Path::new(&format!("/proc/{pid}")).exists() + } + + #[cfg(not(target_os = "linux"))] + { + // On macOS, use ps command + Command::new("ps") + .args(["-p", &pid.to_string()]) + .output() + .map(|output| output.status.success()) + .unwrap_or(false) + } +} + +/// Kill a process by PID +pub fn kill_process(pid: u32) -> Result<()> { + eprintln!("[SharedNode] Killing process {pid}"); + + // Use kill command for cross-platform compatibility + // First try SIGTERM + let term_result = Command::new("kill") + .args(["-TERM", &pid.to_string()]) + .output() + .context("Failed to execute kill command")?; + + if !term_result.status.success() { + let stderr = String::from_utf8_lossy(&term_result.stderr); + // If process doesn't exist, that's fine + if stderr.contains("No such process") { + return Ok(()); + } + return Err(anyhow!("Failed to send SIGTERM to process {pid}: {stderr}")); + } + + // Wait a bit for graceful shutdown + thread::sleep(Duration::from_millis(500)); + + // If still running, use SIGKILL + if is_process_running(pid) { + let kill_result = Command::new("kill") + .args(["-KILL", &pid.to_string()]) + .output() + .context("Failed to execute kill command")?; + + if !kill_result.status.success() { + let stderr = String::from_utf8_lossy(&kill_result.stderr); + if !stderr.contains("No such process") { + return Err(anyhow!("Failed to send SIGKILL to process {pid}: {stderr}")); + } + } + } + + Ok(()) +} + +/// Start the shared node process +pub async fn start_shared_node() -> Result { + eprintln!("[SharedNode] Starting shared node process..."); + + // Ensure data directory exists + let data_dir_path = data_dir(); + fs::create_dir_all(&data_dir_path).context("Failed to create data directory")?; + + // Bootstrap if needed + let marker_file = data_dir_path.join(".bootstrapped"); + if !marker_file.exists() { + LocalMidenNode::bootstrap(&data_dir_path).context("Failed to bootstrap miden-node")?; + // Create marker file + File::create(&marker_file).context("Failed to create bootstrap marker file")?; + } + + // Start the node process + let mut child = Command::new("miden-node") + .args([ + "bundled", + "start", + "--data-directory", + data_dir_path.to_str().unwrap(), + "--rpc.url", + &rpc_url(), + "--block.interval", + "1sec", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .context("Failed to start miden-node process")?; + + let pid = child.id(); + + // Capture output for debugging + let stdout = child.stdout.take().expect("Failed to capture stdout"); + let stderr = child.stderr.take().expect("Failed to capture stderr"); + + // Check if node output logging is enabled + let enable_node_output = std::env::var("MIDEN_NODE_OUTPUT").unwrap_or_default() == "1"; + + // Spawn threads to read output + thread::spawn(move || { + let reader = std::io::BufReader::new(stdout); + for line in reader.lines().map_while(Result::ok) { + if enable_node_output { + eprintln!("[shared node stdout] {line}"); + } + } + }); + + thread::spawn(move || { + let reader = std::io::BufReader::new(stderr); + for line in reader.lines().map_while(Result::ok) { + eprintln!("[shared node stderr] {line}"); + } + }); + + // Detach the child process so it continues running after we exit + drop(child); + + // Wait for node to be ready + eprintln!("[SharedNode] Waiting for node to be ready..."); + let start = Instant::now(); + let timeout = Duration::from_secs(10); + + while start.elapsed() < timeout { + if is_port_in_use(RPC_PORT) { + eprintln!("[SharedNode] Node is ready"); + return Ok(pid); + } + tokio::time::sleep(Duration::from_millis(100)).await; + } + + // If we get here, node failed to start + kill_process(pid).context("Failed to kill unresponsive node process")?; + Err(anyhow!("Timeout waiting for node to be ready")) +} diff --git a/tests/integration-node/src/local_node/setup.rs b/tests/integration-node/src/local_node/setup.rs new file mode 100644 index 000000000..8da8f87ef --- /dev/null +++ b/tests/integration-node/src/local_node/setup.rs @@ -0,0 +1,122 @@ +//! Node installation and bootstrap functionality + +use std::{fs, path::Path, process::Command}; + +use anyhow::{anyhow, Context, Result}; + +use super::{process::kill_process, sync::read_pid, COORD_DIR}; + +// Version configuration for miden-node +// NOTE: When updating miden-client version in Cargo.toml, update this constant to match +// the compatible miden-node version. Both should typically use the same major.minor version. + +/// The exact miden-node version that is compatible with the miden-client version used in tests +const MIDEN_NODE_VERSION: &str = "0.11.1"; + +/// Manages the lifecycle of a local Miden node instance +pub struct LocalMidenNode; + +impl LocalMidenNode { + /// Install miden-node binary if not already installed + pub fn ensure_installed() -> Result<()> { + // Check if miden-node is already installed and get version + let check = Command::new("miden-node").arg("--version").output(); + + let need_install = match check { + Ok(output) if output.status.success() => { + let version = String::from_utf8_lossy(&output.stdout); + let version_line = version.lines().next().unwrap_or(""); + + // Check if it's the exact version we need + if version_line.contains(MIDEN_NODE_VERSION) { + eprintln!("miden-node already installed: {version_line}"); + false + } else { + eprintln!( + "Found incompatible miden-node version: {version_line} (need \ + {MIDEN_NODE_VERSION})" + ); + eprintln!("Uninstalling current version..."); + + // Uninstall the current version + let uninstall_output = Command::new("cargo") + .args(["uninstall", "miden-node"]) + .output() + .context("Failed to run cargo uninstall")?; + + if !uninstall_output.status.success() { + let stderr = String::from_utf8_lossy(&uninstall_output.stderr); + eprintln!("Warning: Failed to uninstall miden-node: {stderr}"); + } else { + eprintln!("Successfully uninstalled old version"); + } + + // Clean all node-related data when version changes + eprintln!("Cleaning node data due to version change..."); + + // Kill any running node process + if let Ok(Some(pid)) = read_pid() { + eprintln!("Stopping existing node process {pid}"); + let _ = kill_process(pid); + } + + // Clean the entire coordination directory + if let Err(e) = fs::remove_dir_all(COORD_DIR) { + if e.kind() != std::io::ErrorKind::NotFound { + eprintln!("Warning: Failed to clean coordination directory: {e}"); + } + } + + true + } + } + _ => { + eprintln!("miden-node not found"); + true + } + }; + + if need_install { + // Install specific version compatible with miden-client + eprintln!("Installing miden-node version {MIDEN_NODE_VERSION} from crates.io..."); + let output = Command::new("cargo") + .args(["install", "miden-node", "--version", MIDEN_NODE_VERSION, "--locked"]) + .output() + .context("Failed to run cargo install")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(anyhow!("Failed to install miden-node: {stderr}")); + } + + eprintln!("miden-node {MIDEN_NODE_VERSION} installed successfully"); + } + + Ok(()) + } + + /// Bootstrap the node with genesis data + pub fn bootstrap(data_dir: &Path) -> Result<()> { + eprintln!("Bootstrapping miden-node..."); + + let output = Command::new("miden-node") + .args([ + "bundled", + "bootstrap", + "--data-directory", + data_dir.to_str().unwrap(), + "--accounts-directory", + data_dir.to_str().unwrap(), + ]) + .output() + .context("Failed to run miden-node bootstrap command")?; + + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + return Err(anyhow!("Failed to bootstrap node: {stderr}")); + } + + eprintln!("Node bootstrapped successfully"); + Ok(()) + } +} diff --git a/tests/integration-node/src/local_node/sync.rs b/tests/integration-node/src/local_node/sync.rs new file mode 100644 index 000000000..ddc2076bb --- /dev/null +++ b/tests/integration-node/src/local_node/sync.rs @@ -0,0 +1,164 @@ +//! Synchronization and reference counting logic for the shared node + +use std::{ + fs::{self, File, OpenOptions}, + thread, + time::Duration, +}; + +use anyhow::{anyhow, Context, Result}; +use fs2::FileExt; + +use super::{ + lock_file, pid_file, + process::{is_process_running, kill_process}, + ref_count_dir, COORD_DIR, +}; + +/// Lock guard using fs2 file locking +pub struct LockGuard(File); + +impl Drop for LockGuard { + fn drop(&mut self) { + let _ = self.0.unlock(); + } +} + +/// Acquire a file lock for atomic operations +pub fn acquire_lock() -> Result { + // Ensure coordination directory exists + fs::create_dir_all(COORD_DIR).context("Failed to create coordination directory")?; + + // Open or create lock file + let file = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(lock_file()) + .context("Failed to open lock file")?; + + // Try to acquire exclusive lock with retries + let mut attempts = 0; + const MAX_ATTEMPTS: u32 = 100; // 10 seconds max wait + + loop { + match file.try_lock_exclusive() { + Ok(_) => return Ok(LockGuard(file)), + Err(e) => { + if attempts >= MAX_ATTEMPTS { + return Err(anyhow!("Timeout acquiring lock: {e}")); + } + attempts += 1; + thread::sleep(Duration::from_millis(100)); + } + } + } +} + +/// Read PID from file +pub fn read_pid() -> Result> { + match fs::read_to_string(pid_file()) { + Ok(contents) => { + let pid = contents.trim().parse::().context("Failed to parse PID")?; + Ok(Some(pid)) + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(anyhow!("Failed to read PID file: {e}")), + } +} + +/// Write PID to file +pub fn write_pid(pid: u32) -> Result<()> { + fs::write(pid_file(), pid.to_string()).context("Failed to write PID file") +} + +/// Remove PID file +pub fn remove_pid() -> Result<()> { + match fs::remove_file(pid_file()) { + Ok(_) => Ok(()), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), + Err(e) => Err(anyhow!("Failed to remove PID file: {e}")), + } +} + +/// Get count of active references, cleaning up stale ones +pub fn get_ref_count() -> Result { + fs::create_dir_all(ref_count_dir()).context("Failed to create reference count directory")?; + + let entries = + fs::read_dir(ref_count_dir()).context("Failed to read reference count directory")?; + + let mut active_count = 0; + for entry in entries.flatten() { + let file_name = entry.file_name(); + let file_name_str = file_name.to_string_lossy(); + + // Extract PID from handle name (format: handle-{pid}-{uuid}) + if let Some(pid_str) = file_name_str.split('-').nth(1) { + if let Ok(pid) = pid_str.parse::() { + if is_process_running(pid) { + active_count += 1; + } else { + // Clean up stale reference from dead process + eprintln!("[SharedNode] Cleaning up stale reference from dead process {pid}"); + let _ = fs::remove_file(entry.path()); + } + } + } + } + + Ok(active_count) +} + +/// Add a reference +pub fn add_reference(handle_id: &str) -> Result<()> { + fs::create_dir_all(ref_count_dir()).context("Failed to create reference count directory")?; + + let ref_file = ref_count_dir().join(handle_id); + File::create(&ref_file).context("Failed to create reference file")?; + + Ok(()) +} + +/// Check and stop the node if no more references exist +pub fn stop_node_if_no_references() { + // Acquire lock for atomic operation + let _lock = match acquire_lock() { + Ok(lock) => lock, + Err(e) => { + eprintln!("[SharedNode] Failed to acquire lock: {e}"); + return; + } + }; + + // Check reference count + let ref_count = match get_ref_count() { + Ok(count) => count, + Err(e) => { + eprintln!("[SharedNode] Failed to get reference count: {e}"); + return; + } + }; + + eprintln!("[SharedNode] Reference count: {ref_count}"); + + if ref_count == 0 { + // No more references, stop the node + if let Ok(Some(pid)) = read_pid() { + eprintln!("[SharedNode] No more references, stopping node process {pid}"); + + if let Err(e) = kill_process(pid) { + eprintln!("[SharedNode] Failed to kill node process: {e}"); + } + + if let Err(e) = remove_pid() { + eprintln!("[SharedNode] Failed to remove PID file: {e}"); + } + + // Clean up coordination directory + if let Err(e) = fs::remove_dir_all(COORD_DIR) { + eprintln!("[SharedNode] Failed to clean up coordination directory: {e}"); + } + } + } +} diff --git a/tests/integration-node/src/node_tests/basic_wallet.rs b/tests/integration-node/src/node_tests/basic_wallet.rs new file mode 100644 index 000000000..5cc267108 --- /dev/null +++ b/tests/integration-node/src/node_tests/basic_wallet.rs @@ -0,0 +1,201 @@ +//! Basic wallet test module + +use miden_client::{ + asset::{FungibleAsset, TokenSymbol}, + note::NoteAssets, + transaction::{OutputNote, TransactionRequestBuilder}, +}; +use miden_core::{utils::Serializable, Felt}; + +use super::helpers::*; +use crate::local_node::ensure_shared_node; + +/// Tests the basic-wallet contract deployment and p2id note consumption workflow on a local node. +#[test] +pub fn test_basic_wallet_p2id_local() { + // Compile the contracts first (before creating any runtime) + let wallet_package = compile_rust_package("../../examples/basic-wallet", true); + let note_package = compile_rust_package("../../examples/p2id-note", true); + let tx_script_package = compile_rust_package("../../examples/basic-wallet-tx-script", true); + + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + // Create temp directory and get node handle + let temp_dir = temp_dir::TempDir::with_prefix("test_basic_wallet_p2id_local_") + .expect("Failed to create temp directory"); + let node_handle = ensure_shared_node().await.expect("Failed to get shared node"); + + // Initialize test infrastructure + let TestSetup { + mut client, + keystore, + } = setup_test_infrastructure(&temp_dir, &node_handle) + .await + .expect("Failed to setup test infrastructure"); + + // Write wallet package to disk for potential future use + let wallet_package_path = temp_dir.path().join("basic_wallet.masp"); + std::fs::write(&wallet_package_path, wallet_package.to_bytes()) + .expect("Failed to write wallet"); + + // Create a fungible faucet account + let token_symbol = TokenSymbol::new("TEST").unwrap(); + let decimals = 8u8; + let max_supply = Felt::new(1_000_000_000); // 1 billion tokens + + let faucet_account = create_fungible_faucet_account( + &mut client, + keystore.clone(), + token_symbol, + decimals, + max_supply, + ) + .await + .unwrap(); + + eprintln!("Faucet account ID: {:?}", faucet_account.id().to_hex()); + + // Create Alice's account with basic-wallet component + let alice_config = AccountCreationConfig { + with_basic_wallet: false, + ..Default::default() + }; + let alice_account = create_account_with_component( + &mut client, + keystore.clone(), + wallet_package.clone(), + alice_config, + ) + .await + .unwrap(); + eprintln!("Alice account ID: {:?}", alice_account.id().to_hex()); + + eprintln!("\n=== Step 1: Minting tokens from faucet to Alice ==="); + + let mint_amount = 100_000u64; // 100,000 tokens + let fungible_asset = FungibleAsset::new(faucet_account.id(), mint_amount).unwrap(); + + // Create the p2id note from faucet to Alice + let p2id_note_mint = create_note_from_package( + &mut client, + note_package.clone(), + faucet_account.id(), + NoteCreationConfig { + assets: NoteAssets::new(vec![fungible_asset.into()]).unwrap(), + inputs: vec![alice_account.id().prefix().as_felt(), alice_account.id().suffix()], + ..Default::default() + }, + ); + eprintln!("P2ID mint note hash: {:?}", p2id_note_mint.id().to_hex()); + + let mint_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(p2id_note_mint.clone())]) + .build() + .unwrap(); + + let mint_tx_result = + client.new_transaction(faucet_account.id(), mint_request).await.unwrap(); + let mint_tx_id = mint_tx_result.executed_transaction().id(); + eprintln!("Created mint transaction. Tx ID: {mint_tx_id:?}"); + + client.submit_transaction(mint_tx_result).await.unwrap(); + eprintln!("Submitted mint transaction. Tx ID: {mint_tx_id:?}"); + + eprintln!("\n=== Step 2: Alice attempts to consume mint note ==="); + + let consume_request = TransactionRequestBuilder::new() + .unauthenticated_input_notes([(p2id_note_mint, None)]) + .build() + .unwrap(); + + let consume_tx = client + .new_transaction(alice_account.id(), consume_request) + .await + .map_err(|e| format!("{e:?}")) + .unwrap(); + + client.submit_transaction(consume_tx).await.unwrap(); + + eprintln!("\n=== Checking Alice's account has the minted asset ==="); + + assert_account_has_fungible_asset( + &mut client, + alice_account.id(), + faucet_account.id(), + mint_amount, + ) + .await; + + eprintln!("\n=== Step 3: Creating Bob's account ==="); + + let bob_config = AccountCreationConfig { + with_basic_wallet: false, + ..Default::default() + }; + let bob_account = create_account_with_component( + &mut client, + keystore.clone(), + wallet_package, + bob_config, + ) + .await + .unwrap(); + eprintln!("Bob account ID: {:?}", bob_account.id().to_hex()); + + eprintln!("\n=== Step 4: Alice creates p2id note for Bob ==="); + + let transfer_amount = 10_000u64; // 10,000 tokens + let transfer_asset = FungibleAsset::new(faucet_account.id(), transfer_amount).unwrap(); + + let (alice_tx_id, bob_note) = send_asset_to_account( + &mut client, + alice_account.id(), + bob_account.id(), + transfer_asset, + note_package.clone(), + tx_script_package, + None, // Use default configuration + ) + .await + .unwrap(); + + eprintln!("Alice created p2id transaction. Tx ID: {alice_tx_id:?}"); + + // Step 5: Bob attempts to consume the p2id note + eprintln!("\n=== Step 5: Bob attempts to consume p2id note ==="); + + let consume_request = TransactionRequestBuilder::new() + .unauthenticated_input_notes([(bob_note, None)]) + .build() + .unwrap(); + + let consume_tx = client.new_transaction(bob_account.id(), consume_request).await.unwrap(); + let consume_tx_id = consume_tx.executed_transaction().id(); + eprintln!("Bob created consume transaction. Tx ID: {consume_tx_id:?}"); + + client.submit_transaction(consume_tx).await.unwrap(); + + eprintln!("\n=== Step 6: Checking Bob's account has the transferred asset ==="); + + assert_account_has_fungible_asset( + &mut client, + bob_account.id(), + faucet_account.id(), + transfer_amount, + ) + .await; + + eprintln!( + "\n=== Step 7: Checking Alice's account reflects the new token amount after sending \ + to Bob ===" + ); + + assert_account_has_fungible_asset( + &mut client, + alice_account.id(), + faucet_account.id(), + mint_amount - transfer_amount, + ) + .await; + }); +} diff --git a/tests/integration-node/src/node_tests/counter_contract.rs b/tests/integration-node/src/node_tests/counter_contract.rs new file mode 100644 index 000000000..5670b933f --- /dev/null +++ b/tests/integration-node/src/node_tests/counter_contract.rs @@ -0,0 +1,158 @@ +//! Counter contract test module + +use miden_client::{ + account::StorageMap, + transaction::{OutputNote, TransactionRequestBuilder}, + Word, +}; +use miden_core::{Felt, FieldElement}; + +use super::helpers::*; +use crate::local_node::ensure_shared_node; + +fn assert_counter_storage( + counter_account_storage: &miden_client::account::AccountStorage, + expected: u64, +) { + // according to `examples/counter-contract` for inner (slot, key) values + let counter_contract_storage_key = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + + // The counter contract is in slot 1 when deployed, auth_component takes slot 0 + let word = counter_account_storage + .get_map_item(1, counter_contract_storage_key) + .expect("Failed to get counter value from storage slot 1"); + + let val = word.last().unwrap(); + assert_eq!( + val.as_int(), + expected, + "Counter value mismatch. Expected: {}, Got: {}", + expected, + val.as_int() + ); +} + +/// Tests the counter contract deployment and note consumption workflow on a local node. +#[test] +pub fn test_counter_contract_local() { + // Compile the contracts first (before creating any runtime) + let contract_package = compile_rust_package("../../examples/counter-contract", true); + let note_package = compile_rust_package("../../examples/counter-note", true); + + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + // Create temp directory and get node handle + let temp_dir = temp_dir::TempDir::with_prefix("test_counter_contract_local_") + .expect("Failed to create temp directory"); + let node_handle = ensure_shared_node().await.expect("Failed to get shared node"); + + // Initialize test infrastructure + let TestSetup { + mut client, + keystore, + } = setup_test_infrastructure(&temp_dir, &node_handle) + .await + .expect("Failed to setup test infrastructure"); + + let sync_summary = client.sync_state().await.unwrap(); + eprintln!("Latest block: {}", sync_summary.block_num); + + // Create the counter account with initial storage + let key = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + let value = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + let config = AccountCreationConfig { + storage_slots: vec![miden_client::account::StorageSlot::Map( + StorageMap::with_entries([(key, value)]).unwrap(), + )], + ..Default::default() + }; + + let counter_account = + create_account_with_component(&mut client, keystore.clone(), contract_package, config) + .await + .unwrap(); + eprintln!("Counter account ID: {:?}", counter_account.id().to_hex()); + + // The counter contract storage value should be zero after the account creation + assert_counter_storage( + client + .get_account(counter_account.id()) + .await + .unwrap() + .unwrap() + .account() + .storage(), + 1, + ); + + // Create the counter note from sender to counter + let counter_note = create_note_from_package( + &mut client, + note_package, + counter_account.id(), + NoteCreationConfig::default(), + ); + eprintln!("Counter note hash: {:?}", counter_note.id().to_hex()); + + // Submit transaction to create the note + let note_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(counter_note.clone())]) + .build() + .unwrap(); + + let tx_result = client + .new_transaction(counter_account.id(), note_request) + .await + .map_err(|e| { + eprintln!("Transaction creation error: {e}"); + e + }) + .unwrap(); + let executed_transaction = tx_result.executed_transaction(); + // dbg!(executed_transaction.output_notes()); + + assert_eq!(executed_transaction.output_notes().num_notes(), 1); + + let executed_tx_output_note = executed_transaction.output_notes().get_note(0); + assert_eq!(executed_tx_output_note.id(), counter_note.id()); + let create_note_tx_id = executed_transaction.id(); + client.submit_transaction(tx_result).await.unwrap(); + eprintln!("Created counter note tx: {create_note_tx_id:?}"); + + // Consume the note to increment the counter + let consume_request = TransactionRequestBuilder::new() + .unauthenticated_input_notes([(counter_note, None)]) + .build() + .unwrap(); + + let tx_result = client + .new_transaction(counter_account.id(), consume_request) + .await + .map_err(|e| { + eprintln!("Note consumption transaction error: {e}"); + e + }) + .unwrap(); + eprintln!( + "Consumed counter note tx: https://testnet.midenscan.com/tx/{:?}", + &tx_result.executed_transaction().id() + ); + + client.submit_transaction(tx_result).await.unwrap(); + + let sync_result = client.sync_state().await.unwrap(); + eprintln!("Synced to block: {}", sync_result.block_num); + + // The counter contract storage value should be 1 (incremented) after the note is consumed + assert_counter_storage( + client + .get_account(counter_account.id()) + .await + .unwrap() + .unwrap() + .account() + .storage(), + 2, + ); + }); +} diff --git a/tests/integration-node/src/node_tests/counter_contract_no_auth.rs b/tests/integration-node/src/node_tests/counter_contract_no_auth.rs new file mode 100644 index 000000000..110a6a74c --- /dev/null +++ b/tests/integration-node/src/node_tests/counter_contract_no_auth.rs @@ -0,0 +1,171 @@ +//! Counter contract test with no-auth authentication component + +use miden_client::{ + account::StorageMap, + transaction::{OutputNote, TransactionRequestBuilder}, + Word, +}; +use miden_core::{Felt, FieldElement}; + +use super::helpers::*; +use crate::local_node::ensure_shared_node; + +fn assert_counter_storage( + counter_account_storage: &miden_client::account::AccountStorage, + expected: u64, +) { + // according to `examples/counter-contract` for inner (slot, key) values + let counter_contract_storage_key = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + + // With no-auth auth component (no storage), the counter component occupies slot 0 + let word = counter_account_storage + .get_map_item(0, counter_contract_storage_key) + .expect("Failed to get counter value from storage slot 1"); + + let val = word.last().unwrap(); + assert_eq!( + val.as_int(), + expected, + "Counter value mismatch. Expected: {}, Got: {}", + expected, + val.as_int() + ); +} + +/// Tests the counter contract with a "no-auth" authentication component. +/// +/// Flow: +/// - Build counter account using `examples/auth-component-no-auth` as the auth component +/// - Build a separate sender account (basic wallet) +/// - Sender issues a counter note to the network +/// - Counter account consumes the note without requiring authentication/signature +#[test] +pub fn test_counter_contract_no_auth_local() { + // Compile the contracts first (before creating any runtime) + let contract_package = compile_rust_package("../../examples/counter-contract", true); + let note_package = compile_rust_package("../../examples/counter-note", true); + let no_auth_auth_component = + compile_rust_package("../../examples/auth-component-no-auth", true); + + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let temp_dir = temp_dir::TempDir::with_prefix("test_counter_contract_no_auth_") + .expect("Failed to create temp directory"); + let node_handle = ensure_shared_node().await.expect("Failed to get shared node"); + + let TestSetup { + mut client, + keystore, + } = setup_test_infrastructure(&temp_dir, &node_handle) + .await + .expect("Failed to setup test infrastructure"); + + let sync_summary = client.sync_state().await.unwrap(); + eprintln!("Latest block: {}", sync_summary.block_num); + + // Create the counter account with initial storage and no-auth auth component + let key = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + let value = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + let counter_cfg = AccountCreationConfig { + storage_slots: vec![miden_client::account::StorageSlot::Map( + StorageMap::with_entries([(key, value)]).unwrap(), + )], + ..Default::default() + }; + + let counter_account = create_account_with_component_and_auth_package( + &mut client, + contract_package.clone(), + no_auth_auth_component.clone(), + counter_cfg, + ) + .await + .unwrap(); + eprintln!("Counter account (no-auth) ID: {:?}", counter_account.id().to_hex()); + + assert_counter_storage( + client + .get_account(counter_account.id()) + .await + .unwrap() + .unwrap() + .account() + .storage(), + 1, + ); + + // Create a separate sender account using only the BasicWallet component + let sender_cfg = AccountCreationConfig::default(); + let sender_account = create_basic_wallet_account(&mut client, keystore.clone(), sender_cfg) + .await + .unwrap(); + eprintln!("Sender account ID: {:?}", sender_account.id().to_hex()); + + // Sender creates the counter note (note script increments counter's storage on consumption) + let counter_note = create_note_from_package( + &mut client, + note_package.clone(), + sender_account.id(), + NoteCreationConfig::default(), + ); + eprintln!("Counter note hash: {:?}", counter_note.id().to_hex()); + + // Submit transaction to create the note from the sender account + let note_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(counter_note.clone())]) + .build() + .unwrap(); + + let tx_result = client + .new_transaction(sender_account.id(), note_request) + .await + .map_err(|e| { + eprintln!("Transaction creation error: {e}"); + e + }) + .unwrap(); + let executed_transaction = tx_result.executed_transaction(); + assert_eq!(executed_transaction.output_notes().num_notes(), 1); + let executed_tx_output_note = executed_transaction.output_notes().get_note(0); + assert_eq!(executed_tx_output_note.id(), counter_note.id()); + let create_note_tx_id = executed_transaction.id(); + client.submit_transaction(tx_result).await.unwrap(); + eprintln!("Created counter note tx: {create_note_tx_id:?}"); + + // Consume the note with the counter account + let consume_request = TransactionRequestBuilder::new() + .unauthenticated_input_notes([(counter_note, None)]) + .build() + .unwrap(); + + let tx_result = client + .new_transaction(counter_account.id(), consume_request) + .await + .map_err(|e| { + eprintln!("Note consumption transaction error: {e}"); + e + }) + .unwrap(); + eprintln!( + "Consumed counter note tx: https://testnet.midenscan.com/tx/{:?}", + &tx_result.executed_transaction().id() + ); + + client.submit_transaction(tx_result).await.unwrap(); + + let sync_result = client.sync_state().await.unwrap(); + eprintln!("Synced to block: {}", sync_result.block_num); + + // The counter contract storage value should be 2 after the note is consumed + assert_counter_storage( + client + .get_account(counter_account.id()) + .await + .unwrap() + .unwrap() + .account() + .storage(), + 2, + ); + }); +} diff --git a/tests/integration-node/src/node_tests/counter_contract_rust_auth.rs b/tests/integration-node/src/node_tests/counter_contract_rust_auth.rs new file mode 100644 index 000000000..89e1eea0a --- /dev/null +++ b/tests/integration-node/src/node_tests/counter_contract_rust_auth.rs @@ -0,0 +1,225 @@ +//! Counter contract test using an auth component compiled from Rust (RPO-Falcon512) +//! +//! This test ensures that an account which does not possess the correct +//! RPO-Falcon512 secret key cannot create notes on behalf of the counter +//! contract account that uses the Rust-compiled auth component. + +use miden_client::{ + account::StorageMap, + auth::AuthSecretKey, + keystore::FilesystemKeyStore, + transaction::{OutputNote, TransactionRequestBuilder}, + utils::Deserializable, + Client, DebugMode, Word, +}; +use miden_core::{Felt, FieldElement}; +use rand::{rngs::StdRng, RngCore}; + +use super::helpers::*; +use crate::local_node::ensure_shared_node; + +fn assert_counter_storage( + counter_account_storage: &miden_client::account::AccountStorage, + expected: u64, +) { + // According to `examples/counter-contract` for inner (slot, key) values + let counter_contract_storage_key = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + + // With RPO-Falcon512 auth component occupying slot 0, the counter component is at slot 1. + let word = counter_account_storage + .get_map_item(1, counter_contract_storage_key) + .expect("Failed to get counter value from storage slot 1"); + + let val = word.last().unwrap(); + assert_eq!( + val.as_int(), + expected, + "Counter value mismatch. Expected: {}, Got: {}", + expected, + val.as_int() + ); +} + +/// Build a counter account from the counter component package and the +/// Rust-compiled RPO-Falcon512 auth component package. +async fn create_counter_account_with_rust_rpo_auth( + client: &mut Client>, + component_package: std::sync::Arc, + auth_component_package: std::sync::Arc, + keystore: std::sync::Arc>, +) -> Result<(miden_client::account::Account, Word), miden_client::ClientError> { + use std::collections::BTreeSet; + + use miden_objects::account::{ + AccountBuilder, AccountComponent, AccountComponentMetadata, AccountComponentTemplate, + AccountStorageMode, AccountType, StorageSlot, + }; + + // Build counter component from template/metadata with initial storage + let account_component = match component_package.account_component_metadata_bytes.as_deref() { + None => panic!("no account component metadata present"), + Some(bytes) => { + let metadata = AccountComponentMetadata::read_from_bytes(bytes).unwrap(); + let template = AccountComponentTemplate::new( + metadata, + component_package.unwrap_library().as_ref().clone(), + ); + + // Initialize the counter storage to 1 at key [0,0,0,1] + let key = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + let value = Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ONE]); + let storage = vec![StorageSlot::Map(StorageMap::with_entries([(key, value)]).unwrap())]; + + let component = AccountComponent::new(template.library().clone(), storage).unwrap(); + component.with_supported_types(BTreeSet::from_iter([ + AccountType::RegularAccountUpdatableCode, + ])) + } + }; + + // Build the Rust-compiled auth component with public key commitment in slot 0 + let key_pair = miden_client::crypto::SecretKey::with_rng(client.rng()); + let pk_commitment = miden_objects::Word::from(key_pair.public_key()); + let mut auth_component = AccountComponent::new( + auth_component_package.unwrap_library().as_ref().clone(), + vec![StorageSlot::Value(pk_commitment)], + ) + .unwrap(); + auth_component = auth_component + .with_supported_types(BTreeSet::from_iter([AccountType::RegularAccountUpdatableCode])); + + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let _ = client.sync_state().await?; + + let (account, seed) = AccountBuilder::new(init_seed) + .account_type(AccountType::RegularAccountUpdatableCode) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(auth_component) + .with_component(miden_client::account::component::BasicWallet) + .with_component(account_component) + .build() + .unwrap(); + + client.add_account(&account, Some(seed), false).await?; + + keystore.add_key(&AuthSecretKey::RpoFalcon512(key_pair)).unwrap(); + + Ok((account, seed)) +} + +/// Verify that another client (without the RPO-Falcon512 key) cannot create notes for +/// the counter account which uses the Rust-compiled RPO-Falcon512 authentication component. +#[test] +pub fn test_counter_contract_rust_auth_blocks_unauthorized_note_creation() { + let contract_package = compile_rust_package("../../examples/counter-contract", true); + let note_package = compile_rust_package("../../examples/counter-note", true); + let rpo_auth_package = + compile_rust_package("../../examples/auth-component-rpo-falcon512", true); + + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + let temp_dir = temp_dir::TempDir::with_prefix("test_counter_contract_rust_auth_") + .expect("Failed to create temp directory"); + let node_handle = ensure_shared_node().await.expect("Failed to get shared node"); + + let TestSetup { + mut client, + keystore, + } = setup_test_infrastructure(&temp_dir, &node_handle) + .await + .expect("Failed to setup test infrastructure"); + + let (counter_account, counter_seed) = create_counter_account_with_rust_rpo_auth( + &mut client, + contract_package.clone(), + rpo_auth_package.clone(), + keystore.clone(), + ) + .await + .unwrap(); + eprintln!( + "Counter account (Rust RPO-Falcon512 auth) ID: {:?}", + counter_account.id().to_hex() + ); + + assert_counter_storage( + client + .get_account(counter_account.id()) + .await + .unwrap() + .unwrap() + .account() + .storage(), + 1, + ); + + // Positive check: original client (with the key) can create a note + let own_note = create_note_from_package( + &mut client, + note_package.clone(), + counter_account.id(), + NoteCreationConfig::default(), + ); + let own_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(own_note.clone())]) + .build() + .unwrap(); + let ok_tx = client + .new_transaction(counter_account.id(), own_request) + .await + .expect("authorized client should be able to create a note"); + assert_eq!(ok_tx.executed_transaction().output_notes().num_notes(), 1); + assert_eq!(ok_tx.executed_transaction().output_notes().get_note(0).id(), own_note.id()); + client.submit_transaction(ok_tx).await.unwrap(); + + // Create a separate client with its own empty keystore (no key for counter account) + let attacker_dir = temp_dir::TempDir::with_prefix("attacker_client_") + .expect("Failed to create temp directory"); + let rpc_url = node_handle.rpc_url().to_string(); + let endpoint = miden_client::rpc::Endpoint::try_from(rpc_url.as_str()).unwrap(); + let rpc_api = + std::sync::Arc::new(miden_client::rpc::TonicRpcClient::new(&endpoint, 10_000)); + let attacker_store_path = + attacker_dir.path().join("store.sqlite3").to_str().unwrap().to_string(); + let attacker_keystore_path = attacker_dir.path().join("keystore"); + + let mut attacker_client = miden_client::builder::ClientBuilder::new() + .rpc(rpc_api) + .sqlite_store(&attacker_store_path) + .filesystem_keystore(attacker_keystore_path.to_str().unwrap()) + .in_debug_mode(DebugMode::Enabled) + .build() + .await + .unwrap(); + + // The attacker needs the account record locally to attempt building a tx + // Reuse the same account object; seed is not needed for reading/state queries + attacker_client + .add_account(&counter_account, Some(counter_seed), false) + .await + .expect("failed to add account to attacker client"); + + // Attacker tries to create an output note on behalf of the counter account + // (origin = counter_account.id()), but does not have the required secret key. + let forged_note = create_note_from_package( + &mut attacker_client, + note_package.clone(), + counter_account.id(), + NoteCreationConfig::default(), + ); + + let forged_request = TransactionRequestBuilder::new() + .own_output_notes(vec![OutputNote::Full(forged_note.clone())]) + .build() + .unwrap(); + + let result = attacker_client.new_transaction(counter_account.id(), forged_request).await; + + assert!( + result.is_err(), + "Unauthorized client unexpectedly created a transaction for the counter account" + ); + }); +} diff --git a/tests/integration-node/src/node_tests/helpers.rs b/tests/integration-node/src/node_tests/helpers.rs new file mode 100644 index 000000000..fa57f33f3 --- /dev/null +++ b/tests/integration-node/src/node_tests/helpers.rs @@ -0,0 +1,511 @@ +//! Common helper functions for node tests + +use std::{collections::BTreeSet, sync::Arc}; + +use miden_client::{ + account::{ + component::{AuthRpoFalcon512, BasicFungibleFaucet, BasicWallet}, + Account, AccountId, AccountStorageMode, AccountType, StorageSlot, + }, + asset::{FungibleAsset, TokenSymbol}, + auth::AuthSecretKey, + builder::ClientBuilder, + crypto::{FeltRng, RpoRandomCoin, SecretKey}, + keystore::FilesystemKeyStore, + note::{ + Note, NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, + NoteType, + }, + rpc::{Endpoint, TonicRpcClient}, + transaction::{TransactionRequestBuilder, TransactionScript}, + utils::Deserializable, + Client, ClientError, +}; +use miden_core::{Felt, FieldElement, Word}; +use miden_integration_tests::CompilerTestBuilder; +use miden_mast_package::Package; +use miden_objects::{ + account::{ + AccountBuilder, AccountComponent, AccountComponentMetadata, AccountComponentTemplate, + }, + asset::Asset, + transaction::TransactionId, +}; +use midenc_frontend_wasm::WasmTranslationConfig; +use rand::{rngs::StdRng, RngCore}; + +/// Test setup configuration +pub struct TestSetup { + pub client: Client>, + pub keystore: Arc>, +} + +/// Initialize test infrastructure with client, keystore, and temporary directory +pub async fn setup_test_infrastructure( + temp_dir: &temp_dir::TempDir, + node_handle: &crate::local_node::SharedNodeHandle, +) -> Result> { + let rpc_url = node_handle.rpc_url().to_string(); + + // Initialize RPC connection + let endpoint = Endpoint::try_from(rpc_url.as_str()).expect("Failed to create endpoint"); + let timeout_ms = 10_000; + let rpc_api = Arc::new(TonicRpcClient::new(&endpoint, timeout_ms)); + + // Initialize keystore + let keystore_path = temp_dir.path().join("keystore"); + let keystore = Arc::new(FilesystemKeyStore::::new(keystore_path.clone()).unwrap()); + + // Initialize client + let store_path = temp_dir.path().join("store.sqlite3").to_str().unwrap().to_string(); + let builder = ClientBuilder::new() + .rpc(rpc_api) + .sqlite_store(&store_path) + .filesystem_keystore(keystore_path.to_str().unwrap()) + .in_debug_mode(miden_client::DebugMode::Enabled); + let client = builder.build().await?; + + Ok(TestSetup { client, keystore }) +} + +/// Configuration for creating an account with a custom component +pub struct AccountCreationConfig { + pub account_type: AccountType, + pub storage_mode: AccountStorageMode, + pub storage_slots: Vec, + pub supported_types: Option>, + pub with_basic_wallet: bool, +} + +impl Default for AccountCreationConfig { + fn default() -> Self { + Self { + account_type: AccountType::RegularAccountUpdatableCode, + storage_mode: AccountStorageMode::Public, + storage_slots: vec![], + supported_types: None, + with_basic_wallet: true, + } + } +} + +/// Helper to create an account with a custom component from a package +pub async fn create_account_with_component( + client: &mut Client>, + keystore: Arc>, + package: Arc, + config: AccountCreationConfig, +) -> Result { + let account_component = match package.account_component_metadata_bytes.as_deref() { + None => panic!("no account component metadata present"), + Some(bytes) => { + let metadata = AccountComponentMetadata::read_from_bytes(bytes).unwrap(); + let template = + AccountComponentTemplate::new(metadata, package.unwrap_library().as_ref().clone()); + + let component = + AccountComponent::new(template.library().clone(), config.storage_slots).unwrap(); + + // Use supported types from config if provided, otherwise default to RegularAccountUpdatableCode + let supported_types = if let Some(types) = config.supported_types { + BTreeSet::from_iter(types) + } else { + BTreeSet::from_iter([AccountType::RegularAccountUpdatableCode]) + }; + + component.with_supported_types(supported_types) + } + }; + + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = SecretKey::with_rng(client.rng()); + + // Sync client state to get latest block info + let _sync_summary = client.sync_state().await.unwrap(); + + let mut builder = AccountBuilder::new(init_seed) + .account_type(config.account_type) + .storage_mode(config.storage_mode) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key())); + + if config.with_basic_wallet { + builder = builder.with_component(BasicWallet); + } + + builder = builder.with_component(account_component); + + let (account, seed) = builder.build().unwrap_or_else(|e| { + eprintln!("failed to build account with custom auth component: {e}"); + panic!("failed to build account with custom auth component") + }); + client.add_account(&account, Some(seed), false).await?; + keystore.add_key(&AuthSecretKey::RpoFalcon512(key_pair)).unwrap(); + + Ok(account) +} + +/// Create a basic wallet account with standard RpoFalcon512 auth. +/// +/// This helper does not require a component package and always adds the `BasicWallet` component. +pub async fn create_basic_wallet_account( + client: &mut Client>, + keystore: Arc>, + config: AccountCreationConfig, +) -> Result { + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = SecretKey::with_rng(client.rng()); + + // Sync client state to get latest block info + let _sync_summary = client.sync_state().await.unwrap(); + + let builder = AccountBuilder::new(init_seed) + .account_type(config.account_type) + .storage_mode(config.storage_mode) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key())) + .with_component(BasicWallet); + + let (account, seed) = builder.build().unwrap(); + client.add_account(&account, Some(seed), false).await?; + keystore.add_key(&AuthSecretKey::RpoFalcon512(key_pair)).unwrap(); + + Ok(account) +} + +/// Helper to create an account with a custom component and a custom authentication component +pub async fn create_account_with_component_and_auth_package( + client: &mut Client>, + component_package: Arc, + auth_component_package: Arc, + config: AccountCreationConfig, +) -> Result { + // Build the main account component from its template metadata + let account_component = match component_package.account_component_metadata_bytes.as_deref() { + None => panic!("no account component metadata present"), + Some(bytes) => { + let metadata = AccountComponentMetadata::read_from_bytes(bytes).unwrap(); + let template = AccountComponentTemplate::new( + metadata, + component_package.unwrap_library().as_ref().clone(), + ); + + let component = + AccountComponent::new(template.library().clone(), config.storage_slots.clone()) + .unwrap(); + + // Use supported types from config if provided, otherwise default to RegularAccountUpdatableCode + let supported_types = if let Some(types) = &config.supported_types { + BTreeSet::from_iter(types.iter().cloned()) + } else { + BTreeSet::from_iter([AccountType::RegularAccountUpdatableCode]) + }; + + component.with_supported_types(supported_types) + } + }; + + // Build the authentication component from the compiled library (no storage) + let mut auth_component = + AccountComponent::new(auth_component_package.unwrap_library().as_ref().clone(), vec![]) + .unwrap(); + + // Ensure auth component supports the intended account type + if let Some(types) = &config.supported_types { + auth_component = + auth_component.with_supported_types(BTreeSet::from_iter(types.iter().cloned())); + } else { + auth_component = auth_component + .with_supported_types(BTreeSet::from_iter([AccountType::RegularAccountUpdatableCode])); + } + + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + // Sync client state to get latest block info + let _sync_summary = client.sync_state().await.unwrap(); + + let mut builder = AccountBuilder::new(init_seed) + .account_type(config.account_type) + .storage_mode(config.storage_mode) + .with_auth_component(auth_component); + + if config.with_basic_wallet { + builder = builder.with_component(BasicWallet); + } + + builder = builder.with_component(account_component); + + let (account, seed) = builder.build().unwrap(); + client.add_account(&account, Some(seed), false).await?; + // No keystore key needed for no-auth auth component + + Ok(account) +} + +pub async fn create_fungible_faucet_account( + client: &mut Client>, + keystore: Arc>, + token_symbol: TokenSymbol, + decimals: u8, + max_supply: Felt, +) -> Result { + let mut init_seed = [0_u8; 32]; + client.rng().fill_bytes(&mut init_seed); + + let key_pair = SecretKey::with_rng(client.rng()); + // Sync client state to get latest block info + let _sync_summary = client.sync_state().await.unwrap(); + let builder = AccountBuilder::new(init_seed) + .account_type(AccountType::FungibleFaucet) + .storage_mode(AccountStorageMode::Public) + .with_auth_component(AuthRpoFalcon512::new(key_pair.public_key())) + .with_component(BasicFungibleFaucet::new(token_symbol, decimals, max_supply).unwrap()); + + let (account, seed) = builder.build().unwrap(); + client.add_account(&account, Some(seed), false).await?; + keystore.add_key(&AuthSecretKey::RpoFalcon512(key_pair)).unwrap(); + + Ok(account) +} + +/// Helper to compile a Rust package to Miden +pub fn compile_rust_package(package_path: &str, release: bool) -> Arc { + let config = WasmTranslationConfig::default(); + let mut builder = CompilerTestBuilder::rust_source_cargo_miden(package_path, config, []); + + if release { + builder.with_release(true); + } + + let mut test = builder.build(); + test.compiled_package() +} + +/// Configuration for creating a note +pub struct NoteCreationConfig { + pub note_type: NoteType, + pub tag: NoteTag, + pub assets: miden_client::note::NoteAssets, + pub inputs: Vec, + pub execution_hint: NoteExecutionHint, + pub aux: Felt, +} + +impl Default for NoteCreationConfig { + fn default() -> Self { + Self { + note_type: NoteType::Public, + tag: NoteTag::for_local_use_case(0, 0).unwrap(), + assets: Default::default(), + inputs: Default::default(), + execution_hint: NoteExecutionHint::always(), + aux: Felt::ZERO, + } + } +} + +/// Helper to create a note from a compiled package +pub fn create_note_from_package( + client: &mut Client>, + package: Arc, + sender_id: AccountId, + config: NoteCreationConfig, +) -> Note { + let note_program = package.unwrap_program(); + let note_script = + NoteScript::from_parts(note_program.mast_forest().clone(), note_program.entrypoint()); + + let serial_num = client.rng().draw_word(); + let note_inputs = NoteInputs::new(config.inputs).unwrap(); + let recipient = NoteRecipient::new(serial_num, note_script, note_inputs); + + let metadata = NoteMetadata::new( + sender_id, + config.note_type, + config.tag, + config.execution_hint, + config.aux, + ) + .unwrap(); + + Note::new(config.assets, metadata, recipient) +} + +/// Helper function to assert that an account contains a specific fungible asset +/// The account may have other assets as well +pub async fn assert_account_has_fungible_asset( + client: &mut Client>, + account_id: AccountId, + expected_faucet_id: AccountId, + expected_amount: u64, +) { + let account_record = client + .get_account(account_id) + .await + .expect("Failed to get account") + .expect("Account not found"); + + let account_state: miden_objects::account::Account = account_record.into(); + + // Look for the specific fungible asset in the vault + let found_asset = account_state.vault().assets().find_map(|asset| { + if let Asset::Fungible(fungible_asset) = asset { + if fungible_asset.faucet_id() == expected_faucet_id { + Some(fungible_asset) + } else { + None + } + } else { + None + } + }); + + match found_asset { + Some(fungible_asset) => { + assert_eq!( + fungible_asset.amount(), + expected_amount, + "Found asset from faucet {expected_faucet_id} but amount {} doesn't match \ + expected {expected_amount}", + fungible_asset.amount() + ); + } + None => { + panic!("Account does not contain a fungible asset from faucet {expected_faucet_id}"); + } + } +} + +/// Configuration for sending assets between accounts +pub struct AssetTransferConfig { + pub note_type: NoteType, + pub tag: NoteTag, + pub execution_hint: NoteExecutionHint, + pub aux: Felt, +} + +impl Default for AssetTransferConfig { + fn default() -> Self { + Self { + note_type: NoteType::Public, + tag: NoteTag::for_local_use_case(0, 0).unwrap(), + execution_hint: NoteExecutionHint::always(), + aux: Felt::ZERO, + } + } +} + +/// Helper function to send assets from one account to another using a transaction script +/// +/// This function creates a p2id note for the recipient and executes a transaction script +/// to send the specified asset amount. +/// +/// # Arguments +/// * `client` - The client instance +/// * `sender_account_id` - The account ID of the sender +/// * `recipient_account_id` - The account ID of the recipient +/// * `asset` - The fungible asset to transfer +/// * `note_package` - The compiled note package (e.g., p2id-note) +/// * `tx_script_package` - The compiled transaction script package +/// * `config` - Optional configuration for the transfer +/// +/// # Returns +/// A tuple containing the transaction ID and the created Note for the recipient +pub async fn send_asset_to_account( + client: &mut Client>, + sender_account_id: AccountId, + recipient_account_id: AccountId, + asset: FungibleAsset, + note_package: Arc, + tx_script_package: Arc, + config: Option, +) -> Result<(TransactionId, Note), ClientError> { + let config = config.unwrap_or_default(); + + // Create the p2id note for the recipient + let p2id_note = create_note_from_package( + client, + note_package, + sender_account_id, + NoteCreationConfig { + assets: miden_client::note::NoteAssets::new(vec![asset.into()]).unwrap(), + inputs: vec![recipient_account_id.prefix().as_felt(), recipient_account_id.suffix()], + note_type: config.note_type, + tag: config.tag, + execution_hint: config.execution_hint, + aux: config.aux, + }, + ); + + let tx_script_program = tx_script_package.unwrap_program(); + let tx_script = TransactionScript::from_parts( + tx_script_program.mast_forest().clone(), + tx_script_program.entrypoint(), + ); + + // Prepare note recipient + let program_hash = tx_script_program.hash(); + let serial_num = RpoRandomCoin::new(program_hash).draw_word(); + let inputs = NoteInputs::new(vec![ + recipient_account_id.prefix().as_felt(), + recipient_account_id.suffix(), + ]) + .unwrap(); + let note_recipient = NoteRecipient::new(serial_num, p2id_note.script().clone(), inputs); + + // Prepare commitment data + let mut input: Vec = vec![ + config.tag.into(), + config.aux, + config.note_type.into(), + config.execution_hint.into(), + ]; + let recipient_digest: [Felt; 4] = note_recipient.digest().into(); + input.extend(recipient_digest); + + let asset_arr: Word = asset.into(); + input.extend(asset_arr); + + let mut commitment: [Felt; 4] = miden_core::crypto::hash::Rpo256::hash_elements(&input).into(); + + assert_eq!(input.len() % 4, 0, "input needs to be word-aligned"); + + // Prepare advice map + let mut advice_map = std::collections::BTreeMap::new(); + advice_map.insert(commitment.into(), input.clone()); + + let recipients = vec![note_recipient.clone()]; + + // NOTE: passed on the stack reversed + commitment.reverse(); + + let tx_request = TransactionRequestBuilder::new() + .custom_script(tx_script) + .script_arg(miden_core::Word::new(commitment)) + .expected_output_recipients(recipients) + .extend_advice_map(advice_map) + .build() + .unwrap(); + + let tx = client.new_transaction(sender_account_id, tx_request).await?; + let tx_id = tx.executed_transaction().id(); + + client.submit_transaction(tx).await?; + + // Create the Note that the recipient will consume + let assets = miden_client::note::NoteAssets::new(vec![asset.into()]).unwrap(); + let metadata = NoteMetadata::new( + sender_account_id, + config.note_type, + config.tag, + config.execution_hint, + config.aux, + ) + .unwrap(); + let recipient_note = Note::new(assets, metadata, note_recipient); + + Ok((tx_id, recipient_note)) +} diff --git a/tests/integration-node/src/node_tests/mod.rs b/tests/integration-node/src/node_tests/mod.rs new file mode 100644 index 000000000..09f58f362 --- /dev/null +++ b/tests/integration-node/src/node_tests/mod.rs @@ -0,0 +1,7 @@ +//! Integration tests that require a local Miden node instance + +pub mod basic_wallet; +pub mod counter_contract; +pub mod counter_contract_no_auth; +pub mod counter_contract_rust_auth; +pub mod helpers; diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 706e855c7..b20cd711d 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -13,33 +13,34 @@ publish = false [dependencies] anyhow.workspace = true -cargo_metadata = "0.18" cargo-util = "0.2" -derive_more.workspace = true -expect-test.workspace = true +env_logger.workspace = true +midenc-expect-test.workspace = true filetime = "0.2.23" glob = "0.3.1" log.workspace = true miden-assembly.workspace = true miden-core.workspace = true +miden-mast-package.workspace = true +miden-objects = { workspace = true, features = ["std"] } miden-processor.workspace = true -miden-stdlib.workspace = true +midenc-dialect-arith.workspace = true +midenc-dialect-cf.workspace = true +midenc-dialect-hir.workspace = true midenc-frontend-wasm.workspace = true midenc-hir.workspace = true -midenc-hir-transform.workspace = true +midenc-hir-eval.workspace = true midenc-codegen-masm.workspace = true midenc-session.workspace = true midenc-compile.workspace = true midenc-debug.workspace = true cargo-miden.workspace = true -wasmprinter = "0.2.80" +wasmprinter = "0.227" proptest.workspace = true sha2 = "0.10" walkdir = "2.5.0" [dev-dependencies] -blake3.workspace = true +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging +blake3 = "1.5" concat-idents = "1.1" -env_logger.workspace = true -miden-core.workspace = true -miden-integration-tests-rust-fib = { path = "../rust-apps/fib" } diff --git a/tests/integration/expected/abi_transform_stdlib_blake3_hash.hir b/tests/integration/expected/abi_transform_stdlib_blake3_hash.hir index e299c818b..1ee9fae7c 100644 --- a/tests/integration/expected/abi_transform_stdlib_blake3_hash.hir +++ b/tests/integration/expected/abi_transform_stdlib_blake3_hash.hir @@ -1,131 +1,149 @@ -(component - ;; Modules - (module #abi_transform_stdlib_blake3_hash - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @abi_transform_stdlib_blake3_hash { + public builtin.function @entrypoint(v0: i32, v1: i32) { + ^block4(v0: i32, v1: i32): + v3 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_stdlib_blake3_hash/__stack_pointer : ptr + v4 = hir.bitcast v3 : ptr; + v5 = hir.load v4 : i32; + v8 = arith.constant -32 : i32; + v6 = arith.constant 32 : i32; + v7 = arith.sub v5, v6 : i32 #[overflow = wrapping]; + v9 = arith.band v7, v8 : i32; + v10 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_stdlib_blake3_hash/__stack_pointer : ptr + v11 = hir.bitcast v10 : ptr; + hir.store v11, v9; + v12 = hir.bitcast v1 : u32; + v13 = hir.int_to_ptr v12 : ptr; + v14 = hir.load v13 : i32; + v16 = arith.constant 4 : u32; + v15 = hir.bitcast v1 : u32; + v17 = arith.add v15, v16 : u32 #[overflow = checked]; + v18 = hir.int_to_ptr v17 : ptr; + v19 = hir.load v18 : i32; + v21 = arith.constant 8 : u32; + v20 = hir.bitcast v1 : u32; + v22 = arith.add v20, v21 : u32 #[overflow = checked]; + v23 = hir.int_to_ptr v22 : ptr; + v24 = hir.load v23 : i32; + v26 = arith.constant 12 : u32; + v25 = hir.bitcast v1 : u32; + v27 = arith.add v25, v26 : u32 #[overflow = checked]; + v28 = hir.int_to_ptr v27 : ptr; + v29 = hir.load v28 : i32; + v31 = arith.constant 16 : u32; + v30 = hir.bitcast v1 : u32; + v32 = arith.add v30, v31 : u32 #[overflow = checked]; + v33 = hir.int_to_ptr v32 : ptr; + v34 = hir.load v33 : i32; + v36 = arith.constant 20 : u32; + v35 = hir.bitcast v1 : u32; + v37 = arith.add v35, v36 : u32 #[overflow = checked]; + v38 = hir.int_to_ptr v37 : ptr; + v39 = hir.load v38 : i32; + v41 = arith.constant 24 : u32; + v40 = hir.bitcast v1 : u32; + v42 = arith.add v40, v41 : u32 #[overflow = checked]; + v43 = hir.int_to_ptr v42 : ptr; + v44 = hir.load v43 : i32; + v46 = arith.constant 28 : u32; + v45 = hir.bitcast v1 : u32; + v47 = arith.add v45, v46 : u32 #[overflow = checked]; + v48 = hir.int_to_ptr v47 : ptr; + v49 = hir.load v48 : i32; + hir.exec @root_ns:root@1.0.0/abi_transform_stdlib_blake3_hash/std::crypto::hashes::blake3::hash_1to1(v14, v19, v24, v29, v34, v39, v44, v49, v9) + v138 = arith.constant 24 : u32; + v52 = hir.bitcast v9 : u32; + v54 = arith.add v52, v138 : u32 #[overflow = checked]; + v137 = arith.constant 8 : u32; + v56 = arith.mod v54, v137 : u32; + hir.assertz v56 #[code = 250]; + v57 = hir.int_to_ptr v54 : ptr; + v58 = hir.load v57 : i64; + v50 = arith.constant 24 : i32; + v51 = arith.add v0, v50 : i32 #[overflow = wrapping]; + v59 = hir.bitcast v51 : u32; + v60 = hir.int_to_ptr v59 : ptr; + hir.store v60, v58; + v136 = arith.constant 16 : u32; + v63 = hir.bitcast v9 : u32; + v65 = arith.add v63, v136 : u32 #[overflow = checked]; + v135 = arith.constant 8 : u32; + v67 = arith.mod v65, v135 : u32; + hir.assertz v67 #[code = 250]; + v68 = hir.int_to_ptr v65 : ptr; + v69 = hir.load v68 : i64; + v61 = arith.constant 16 : i32; + v62 = arith.add v0, v61 : i32 #[overflow = wrapping]; + v70 = hir.bitcast v62 : u32; + v71 = hir.int_to_ptr v70 : ptr; + hir.store v71, v69; + v134 = arith.constant 8 : u32; + v74 = hir.bitcast v9 : u32; + v76 = arith.add v74, v134 : u32 #[overflow = checked]; + v133 = arith.constant 8 : u32; + v78 = arith.mod v76, v133 : u32; + hir.assertz v78 #[code = 250]; + v79 = hir.int_to_ptr v76 : ptr; + v80 = hir.load v79 : i64; + v72 = arith.constant 8 : i32; + v73 = arith.add v0, v72 : i32 #[overflow = wrapping]; + v81 = hir.bitcast v73 : u32; + v82 = hir.int_to_ptr v81 : ptr; + hir.store v82, v80; + v83 = hir.bitcast v9 : u32; + v132 = arith.constant 8 : u32; + v85 = arith.mod v83, v132 : u32; + hir.assertz v85 #[code = 250]; + v86 = hir.int_to_ptr v83 : ptr; + v87 = hir.load v86 : i64; + v88 = hir.bitcast v0 : u32; + v89 = hir.int_to_ptr v88 : ptr; + hir.store v89, v87; + v90 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_stdlib_blake3_hash/__stack_pointer : ptr + v91 = hir.bitcast v90 : ptr; + hir.store v91, v5; + builtin.ret ; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @std::crypto::hashes::blake3::hash_1to1(v92: i32, v93: i32, v94: i32, v95: i32, v96: i32, v97: i32, v98: i32, v99: i32, v100: i32) { + ^block6(v92: i32, v93: i32, v94: i32, v95: i32, v96: i32, v97: i32, v98: i32, v99: i32, v100: i32): + v101, v102, v103, v104, v105, v106, v107, v108 = hir.exec @std/crypto/hashes/blake3/hash_1to1(v92, v93, v94, v95, v96, v97, v98, v99) : i32, i32, i32, i32, i32, i32, i32, i32 + v109 = hir.bitcast v100 : u32; + v110 = hir.int_to_ptr v109 : ptr; + hir.store v110, v101; + v111 = arith.constant 4 : u32; + v112 = arith.add v109, v111 : u32 #[overflow = checked]; + v113 = hir.int_to_ptr v112 : ptr; + hir.store v113, v102; + v114 = arith.constant 8 : u32; + v115 = arith.add v109, v114 : u32 #[overflow = checked]; + v116 = hir.int_to_ptr v115 : ptr; + hir.store v116, v103; + v117 = arith.constant 12 : u32; + v118 = arith.add v109, v117 : u32 #[overflow = checked]; + v119 = hir.int_to_ptr v118 : ptr; + hir.store v119, v104; + v120 = arith.constant 16 : u32; + v121 = arith.add v109, v120 : u32 #[overflow = checked]; + v122 = hir.int_to_ptr v121 : ptr; + hir.store v122, v105; + v123 = arith.constant 20 : u32; + v124 = arith.add v109, v123 : u32 #[overflow = checked]; + v125 = hir.int_to_ptr v124 : ptr; + hir.store v125, v106; + v126 = arith.constant 24 : u32; + v127 = arith.add v109, v126 : u32 #[overflow = checked]; + v128 = hir.int_to_ptr v127 : ptr; + hir.store v128, v107; + v129 = arith.constant 28 : u32; + v130 = arith.add v109, v129 : u32 #[overflow = checked]; + v131 = hir.int_to_ptr v130 : ptr; + hir.store v131, v108; + builtin.ret ; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 32)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 i32) (const.i32 -32)) - (let (v7 i32) (band v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 u32) (bitcast v1)) - (let (v10 (ptr i32)) (inttoptr v9)) - (let (v11 i32) (load v10)) - (let (v12 u32) (bitcast v1)) - (let (v13 u32) (add.checked v12 4)) - (let (v14 (ptr i32)) (inttoptr v13)) - (let (v15 i32) (load v14)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (add.checked v16 8)) - (let (v18 (ptr i32)) (inttoptr v17)) - (let (v19 i32) (load v18)) - (let (v20 u32) (bitcast v1)) - (let (v21 u32) (add.checked v20 12)) - (let (v22 (ptr i32)) (inttoptr v21)) - (let (v23 i32) (load v22)) - (let (v24 u32) (bitcast v1)) - (let (v25 u32) (add.checked v24 16)) - (let (v26 (ptr i32)) (inttoptr v25)) - (let (v27 i32) (load v26)) - (let (v28 u32) (bitcast v1)) - (let (v29 u32) (add.checked v28 20)) - (let (v30 (ptr i32)) (inttoptr v29)) - (let (v31 i32) (load v30)) - (let (v32 u32) (bitcast v1)) - (let (v33 u32) (add.checked v32 24)) - (let (v34 (ptr i32)) (inttoptr v33)) - (let (v35 i32) (load v34)) - (let (v36 u32) (bitcast v1)) - (let (v37 u32) (add.checked v36 28)) - (let (v38 (ptr i32)) (inttoptr v37)) - (let (v39 i32) (load v38)) - (let [(v40 i32) (v41 i32) (v42 i32) (v43 i32) (v44 i32) (v45 i32) (v46 i32) (v47 i32)] (call (#std::crypto::hashes::blake3 #hash_1to1) v11 v15 v19 v23 v27 v31 v35 v39)) - (let (v48 u32) (bitcast v7)) - (let (v49 (ptr i32)) (inttoptr v48)) - (store v49 v40) - (let (v50 u32) (add.checked v48 4)) - (let (v51 (ptr i32)) (inttoptr v50)) - (store v51 v41) - (let (v52 u32) (add.checked v48 8)) - (let (v53 (ptr i32)) (inttoptr v52)) - (store v53 v42) - (let (v54 u32) (add.checked v48 12)) - (let (v55 (ptr i32)) (inttoptr v54)) - (store v55 v43) - (let (v56 u32) (add.checked v48 16)) - (let (v57 (ptr i32)) (inttoptr v56)) - (store v57 v44) - (let (v58 u32) (add.checked v48 20)) - (let (v59 (ptr i32)) (inttoptr v58)) - (store v59 v45) - (let (v60 u32) (add.checked v48 24)) - (let (v61 (ptr i32)) (inttoptr v60)) - (store v61 v46) - (let (v62 u32) (add.checked v48 28)) - (let (v63 (ptr i32)) (inttoptr v62)) - (store v63 v47) - (let (v64 i32) (const.i32 24)) - (let (v65 i32) (add.wrapping v0 v64)) - (let (v66 u32) (bitcast v7)) - (let (v67 u32) (add.checked v66 24)) - (let (v68 u32) (mod.unchecked v67 8)) - (assertz 250 v68) - (let (v69 (ptr i64)) (inttoptr v67)) - (let (v70 i64) (load v69)) - (let (v71 u32) (bitcast v65)) - (let (v72 (ptr i64)) (inttoptr v71)) - (store v72 v70) - (let (v73 i32) (const.i32 16)) - (let (v74 i32) (add.wrapping v0 v73)) - (let (v75 u32) (bitcast v7)) - (let (v76 u32) (add.checked v75 16)) - (let (v77 u32) (mod.unchecked v76 8)) - (assertz 250 v77) - (let (v78 (ptr i64)) (inttoptr v76)) - (let (v79 i64) (load v78)) - (let (v80 u32) (bitcast v74)) - (let (v81 (ptr i64)) (inttoptr v80)) - (store v81 v79) - (let (v82 i32) (const.i32 8)) - (let (v83 i32) (add.wrapping v0 v82)) - (let (v84 u32) (bitcast v7)) - (let (v85 u32) (add.checked v84 8)) - (let (v86 u32) (mod.unchecked v85 8)) - (assertz 250 v86) - (let (v87 (ptr i64)) (inttoptr v85)) - (let (v88 i64) (load v87)) - (let (v89 u32) (bitcast v83)) - (let (v90 (ptr i64)) (inttoptr v89)) - (store v90 v88) - (let (v91 u32) (bitcast v7)) - (let (v92 u32) (mod.unchecked v91 8)) - (assertz 250 v92) - (let (v93 (ptr i64)) (inttoptr v91)) - (let (v94 i64) (load v93)) - (let (v95 u32) (bitcast v0)) - (let (v96 (ptr i64)) (inttoptr v95)) - (store v96 v94) - (let (v97 (ptr i32)) (global.symbol #__stack_pointer)) - (store v97 v3) - (br (block 1))) - - (block 1 - (ret)) - ) - - ;; Imports - (func (import #std::crypto::hashes::blake3 #hash_1to1) - (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32 i32 i32 i32 i32 i32 i32 i32)) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/abi_transform_stdlib_blake3_hash.masm b/tests/integration/expected/abi_transform_stdlib_blake3_hash.masm index b8ad0bce5..eef78a4b4 100644 --- a/tests/integration/expected/abi_transform_stdlib_blake3_hash.masm +++ b/tests/integration/expected/abi_transform_stdlib_blake3_hash.masm @@ -1,338 +1,359 @@ -# mod abi_transform_stdlib_blake3_hash +# mod root_ns:root@1.0.0 -use.std::crypto::hashes::blake3 +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::abi_transform_stdlib_blake3_hash export.entrypoint - mem_load.0x00010000 + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4294967264 push.32 - dup.1 + dup.2 swap.1 u32wrapping_sub - push.4294967264 u32and - push.1048576 + push.1114112 dup.1 swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::store_sw - dup.0 - dup.4 - add.28 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + trace.252 + nop + dup.3 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 dup.5 - add.24 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw + trace.252 + nop + push.8 dup.6 - add.20 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 dup.7 - add.16 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 dup.8 - add.12 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw + trace.252 + nop + push.20 dup.9 - add.8 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw + trace.252 + nop + push.24 dup.10 - add.4 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw + trace.252 + nop + push.28 movup.11 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + add + u32assert + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::load_sw - exec.::std::crypto::hashes::blake3::hash_1to1 + trace.252 + nop dup.8 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 + movup.3 + swap.5 + movdn.3 movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.7 - add.4 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + swap.6 + movdn.2 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.6 - add.8 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + swap.7 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.5 - add.12 + swap.8 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_stdlib_blake3_hash::std::crypto::hashes::blake3::hash_1to1 + trace.252 + nop + push.24 + dup.1 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + push.8 + dup.1 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.4 - add.16 + u32mod u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + assertz + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.3 - add.20 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.24 + dup.5 + u32wrapping_add + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.2 - add.24 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + push.8 + dup.1 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.2 - add.24 - u32assert - movup.2 - add.28 + u32mod u32assert - movup.2 + assertz + u32divmod.4 swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.16 + dup.5 + u32wrapping_add + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.8 dup.1 - add.16 + add u32assert + push.8 dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_dw - push.24 - dup.7 + u32mod + u32assert + assertz + u32divmod.4 swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.5 u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::store_dw - dup.2 - add.8 - u32assert + trace.252 + nop + push.8 dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_dw - push.16 - dup.8 + u32mod + u32assert + assertz + u32divmod.4 swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.3 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::store_dw - movup.3 - dup.1 - dup.0 - u32mod.16 + trace.252 + nop + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.std::crypto::hashes::blake3::hash_1to1 + trace.240 + nop + exec.::std::crypto::hashes::blake3::hash_1to1 + trace.252 + nop + movup.8 dup.0 - u32mod.4 + movup.2 + swap.1 + u32divmod.4 swap.1 - u32div.4 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert movup.2 - u32div.16 - exec.::intrinsics::mem::load_dw + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop push.8 - dup.8 + dup.1 + add + u32assert + movup.2 swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.12 + dup.1 + add + u32assert movup.2 - u32div.16 - exec.::intrinsics::mem::store_dw - dup.0 - dup.0 - u32mod.16 - dup.0 - u32mod.4 swap.1 - u32div.4 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + dup.1 + add + u32assert movup.2 - u32div.16 - exec.::intrinsics::mem::load_dw - movup.7 - dup.0 - u32mod.16 - dup.0 - u32mod.4 swap.1 - u32div.4 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.20 + dup.1 + add + u32assert movup.2 - u32div.16 - exec.::intrinsics::mem::store_dw - push.1048576 - movup.5 swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.24 + dup.1 + add + u32assert movup.2 - u32div.16 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.28 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop exec.::intrinsics::mem::store_sw - u32mod.8 - assertz.err=250 - u32mod.8 - assertz.err=250 - u32mod.8 - assertz.err=250 - u32mod.8 - assertz.err=250 + trace.252 + nop end - diff --git a/tests/integration/expected/abi_transform_stdlib_blake3_hash.wat b/tests/integration/expected/abi_transform_stdlib_blake3_hash.wat index 03b47714c..3f2d358f3 100644 --- a/tests/integration/expected/abi_transform_stdlib_blake3_hash.wat +++ b/tests/integration/expected/abi_transform_stdlib_blake3_hash.wat @@ -1,8 +1,12 @@ (module $abi_transform_stdlib_blake3_hash.wasm - (type (;0;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;1;) (func (param i32 i32))) - (import "std::crypto::hashes::blake3" "hash_1to1<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1 (;0;) (type 0))) - (func $entrypoint (;1;) (type 1) (param i32 i32) + (type (;0;) (func (param i32 i32))) + (type (;1;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32 i32) (local i32 i32) global.get $__stack_pointer local.tee 2 @@ -29,7 +33,7 @@ local.get 1 i32.load offset=28 align=1 local.get 3 - call $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1 + call $std::crypto::hashes::blake3::hash_1to1 local.get 0 i32.const 24 i32.add @@ -55,9 +59,7 @@ local.get 2 global.set $__stack_pointer ) - (table (;0;) 1 1 funcref) - (memory (;0;) 16) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $std::crypto::hashes::blake3::hash_1to1 (;1;) (type 1) (param i32 i32 i32 i32 i32 i32 i32 i32 i32) + unreachable + ) +) diff --git a/tests/integration/expected/abi_transform_tx_kernel_get_id.hir b/tests/integration/expected/abi_transform_tx_kernel_get_id.hir new file mode 100644 index 000000000..2b17dfbea --- /dev/null +++ b/tests/integration/expected/abi_transform_tx_kernel_get_id.hir @@ -0,0 +1,368 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @abi_transform_tx_kernel_get_id { + public builtin.function @entrypoint(v0: i32) { + ^block4(v0: i32): + v2 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__stack_pointer : ptr + v3 = hir.bitcast v2 : ptr; + v4 = hir.load v3 : i32; + v5 = arith.constant 16 : i32; + v6 = arith.sub v4, v5 : i32 #[overflow = wrapping]; + v7 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__stack_pointer : ptr + v8 = hir.bitcast v7 : ptr; + hir.store v8, v6; + v9 = arith.constant 8 : i32; + v10 = arith.add v6, v9 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/miden_base_sys::bindings::account::get_id(v10) + v12 = arith.constant 8 : u32; + v11 = hir.bitcast v6 : u32; + v13 = arith.add v11, v12 : u32 #[overflow = checked]; + v227 = arith.constant 8 : u32; + v15 = arith.mod v13, v227 : u32; + hir.assertz v15 #[code = 250]; + v16 = hir.int_to_ptr v13 : ptr; + v17 = hir.load v16 : i64; + v18 = hir.bitcast v0 : u32; + v19 = arith.constant 4 : u32; + v20 = arith.mod v18, v19 : u32; + hir.assertz v20 #[code = 250]; + v21 = hir.int_to_ptr v18 : ptr; + hir.store v21, v17; + v226 = arith.constant 16 : i32; + v23 = arith.add v6, v226 : i32 #[overflow = wrapping]; + v24 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__stack_pointer : ptr + v25 = hir.bitcast v24 : ptr; + hir.store v25, v23; + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_alloc(v26: i32, v27: i32) -> i32 { + ^block6(v26: i32, v27: i32): + v29 = arith.constant 1048580 : i32; + v30 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/::alloc(v29, v27, v26) : i32 + builtin.ret v30; + }; + + private builtin.function @__rustc::__rust_realloc(v31: i32, v32: i32, v33: i32, v34: i32) -> i32 { + ^block8(v31: i32, v32: i32, v33: i32, v34: i32): + v36 = arith.constant 1048580 : i32; + v37 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/::alloc(v36, v33, v34) : i32 + v236 = arith.constant 0 : i32; + v38 = arith.constant 0 : i32; + v39 = arith.eq v37, v38 : i1; + v40 = arith.zext v39 : u32; + v41 = hir.bitcast v40 : i32; + v43 = arith.neq v41, v236 : i1; + scf.if v43{ + ^block10: + scf.yield ; + } else { + ^block11: + v235 = arith.constant 0 : i32; + v45 = hir.bitcast v32 : u32; + v44 = hir.bitcast v34 : u32; + v46 = arith.lt v44, v45 : i1; + v47 = arith.zext v46 : u32; + v48 = hir.bitcast v47 : i32; + v50 = arith.neq v48, v235 : i1; + v51 = cf.select v50, v34, v32 : i32; + v233 = arith.constant 0 : i32; + v234 = arith.constant 0 : i32; + v53 = arith.eq v51, v234 : i1; + v54 = arith.zext v53 : u32; + v55 = hir.bitcast v54 : i32; + v57 = arith.neq v55, v233 : i1; + scf.if v57{ + ^block50: + scf.yield ; + } else { + ^block12: + v58 = hir.bitcast v51 : u32; + v59 = hir.bitcast v37 : u32; + v60 = hir.int_to_ptr v59 : ptr; + v61 = hir.bitcast v31 : u32; + v62 = hir.int_to_ptr v61 : ptr; + hir.mem_cpy v62, v60, v58; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v37; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block13: + builtin.ret ; + }; + + private builtin.function @::alloc(v64: i32, v65: i32, v66: i32) -> i32 { + ^block15(v64: i32, v65: i32, v66: i32): + v69 = arith.constant 16 : i32; + v68 = arith.constant 0 : i32; + v238 = arith.constant 16 : u32; + v71 = hir.bitcast v65 : u32; + v73 = arith.gt v71, v238 : i1; + v74 = arith.zext v73 : u32; + v75 = hir.bitcast v74 : i32; + v77 = arith.neq v75, v68 : i1; + v78 = cf.select v77, v65, v69 : i32; + v278 = arith.constant 0 : i32; + v79 = arith.constant -1 : i32; + v80 = arith.add v78, v79 : i32 #[overflow = wrapping]; + v81 = arith.band v78, v80 : i32; + v83 = arith.neq v81, v278 : i1; + v247, v248 = scf.if v83 : i32, u32 { + ^block55: + v239 = arith.constant 0 : u32; + v243 = ub.poison i32 : i32; + scf.yield v243, v239; + } else { + ^block18: + v85 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/core::ptr::alignment::Alignment::max(v65, v78) : i32 + v277 = arith.constant 0 : i32; + v84 = arith.constant -2147483648 : i32; + v86 = arith.sub v84, v85 : i32 #[overflow = wrapping]; + v88 = hir.bitcast v86 : u32; + v87 = hir.bitcast v66 : u32; + v89 = arith.gt v87, v88 : i1; + v90 = arith.zext v89 : u32; + v91 = hir.bitcast v90 : i32; + v93 = arith.neq v91, v277 : i1; + v262 = scf.if v93 : i32 { + ^block54: + v276 = ub.poison i32 : i32; + scf.yield v276; + } else { + ^block19: + v274 = arith.constant 0 : i32; + v99 = arith.sub v274, v85 : i32 #[overflow = wrapping]; + v275 = arith.constant -1 : i32; + v95 = arith.add v66, v85 : i32 #[overflow = wrapping]; + v97 = arith.add v95, v275 : i32 #[overflow = wrapping]; + v100 = arith.band v97, v99 : i32; + v101 = hir.bitcast v64 : u32; + v102 = arith.constant 4 : u32; + v103 = arith.mod v101, v102 : u32; + hir.assertz v103 #[code = 250]; + v104 = hir.int_to_ptr v101 : ptr; + v105 = hir.load v104 : i32; + v273 = arith.constant 0 : i32; + v107 = arith.neq v105, v273 : i1; + scf.if v107{ + ^block53: + scf.yield ; + } else { + ^block21: + v108 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/intrinsics::mem::heap_base() : i32 + v109 = hir.mem_size : u32; + v115 = hir.bitcast v64 : u32; + v272 = arith.constant 4 : u32; + v117 = arith.mod v115, v272 : u32; + hir.assertz v117 #[code = 250]; + v271 = arith.constant 16 : u32; + v110 = hir.bitcast v109 : i32; + v113 = arith.shl v110, v271 : i32; + v114 = arith.add v108, v113 : i32 #[overflow = wrapping]; + v118 = hir.int_to_ptr v115 : ptr; + hir.store v118, v114; + scf.yield ; + }; + v121 = hir.bitcast v64 : u32; + v270 = arith.constant 4 : u32; + v123 = arith.mod v121, v270 : u32; + hir.assertz v123 #[code = 250]; + v124 = hir.int_to_ptr v121 : ptr; + v125 = hir.load v124 : i32; + v268 = arith.constant 0 : i32; + v269 = arith.constant -1 : i32; + v127 = arith.bxor v125, v269 : i32; + v129 = hir.bitcast v127 : u32; + v128 = hir.bitcast v100 : u32; + v130 = arith.gt v128, v129 : i1; + v131 = arith.zext v130 : u32; + v132 = hir.bitcast v131 : i32; + v134 = arith.neq v132, v268 : i1; + v261 = scf.if v134 : i32 { + ^block22: + v267 = arith.constant 0 : i32; + scf.yield v267; + } else { + ^block23: + v136 = hir.bitcast v64 : u32; + v266 = arith.constant 4 : u32; + v138 = arith.mod v136, v266 : u32; + hir.assertz v138 #[code = 250]; + v135 = arith.add v125, v100 : i32 #[overflow = wrapping]; + v139 = hir.int_to_ptr v136 : ptr; + hir.store v139, v135; + v141 = arith.add v125, v85 : i32 #[overflow = wrapping]; + scf.yield v141; + }; + scf.yield v261; + }; + v244 = arith.constant 1 : u32; + v265 = arith.constant 0 : u32; + v263 = cf.select v93, v265, v244 : u32; + scf.yield v262, v263; + }; + v264 = arith.constant 0 : u32; + v260 = arith.eq v248, v264 : i1; + cf.cond_br v260 ^block17, ^block57(v247); + ^block17: + ub.unreachable ; + ^block57(v240: i32): + builtin.ret v240; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block24: + v144 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v144; + }; + + private builtin.function @miden_base_sys::bindings::account::get_id(v146: i32) { + ^block28(v146: i32): + v148 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__stack_pointer : ptr + v149 = hir.bitcast v148 : ptr; + v150 = hir.load v149 : i32; + v151 = arith.constant 16 : i32; + v152 = arith.sub v150, v151 : i32 #[overflow = wrapping]; + v153 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__stack_pointer : ptr + v154 = hir.bitcast v153 : ptr; + hir.store v154, v152; + v155 = arith.constant 8 : i32; + v156 = arith.add v152, v155 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/miden::account::get_id(v156) + v158 = arith.constant 8 : u32; + v157 = hir.bitcast v152 : u32; + v159 = arith.add v157, v158 : u32 #[overflow = checked]; + v160 = arith.constant 4 : u32; + v161 = arith.mod v159, v160 : u32; + hir.assertz v161 #[code = 250]; + v162 = hir.int_to_ptr v159 : ptr; + v163 = hir.load v162 : i64; + v164 = hir.bitcast v146 : u32; + v280 = arith.constant 8 : u32; + v166 = arith.mod v164, v280 : u32; + hir.assertz v166 #[code = 250]; + v167 = hir.int_to_ptr v164 : ptr; + hir.store v167, v163; + v279 = arith.constant 16 : i32; + v169 = arith.add v152, v279 : i32 #[overflow = wrapping]; + v170 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__stack_pointer : ptr + v171 = hir.bitcast v170 : ptr; + hir.store v171, v169; + builtin.ret ; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v172: i32, v173: i32) -> i32 { + ^block30(v172: i32, v173: i32): + v180 = arith.constant 0 : i32; + v176 = hir.bitcast v173 : u32; + v175 = hir.bitcast v172 : u32; + v177 = arith.gt v175, v176 : i1; + v178 = arith.zext v177 : u32; + v179 = hir.bitcast v178 : i32; + v181 = arith.neq v179, v180 : i1; + v182 = cf.select v181, v172, v173 : i32; + builtin.ret v182; + }; + + private builtin.function @miden::account::get_id(v183: i32) { + ^block32(v183: i32): + v184, v185 = hir.exec @miden/account/get_id() : felt, felt + v186 = hir.bitcast v183 : u32; + v187 = hir.int_to_ptr v186 : ptr; + hir.store v187, v184; + v188 = arith.constant 4 : u32; + v189 = arith.add v186, v188 : u32 #[overflow = checked]; + v190 = hir.int_to_ptr v189 : ptr; + hir.store v190, v185; + builtin.ret ; + }; + + public builtin.function @cabi_realloc(v191: i32, v192: i32, v193: i32, v194: i32) -> i32 { + ^block36(v191: i32, v192: i32, v193: i32, v194: i32): + v196 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/cabi_realloc_wit_bindgen_0_46_0(v191, v192, v193, v194) : i32 + builtin.ret v196; + }; + + private builtin.function @alloc::alloc::alloc(v197: i32, v198: i32) -> i32 { + ^block38(v197: i32, v198: i32): + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v200 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__rustc::__rust_alloc(v198, v197) : i32 + builtin.ret v200; + }; + + public builtin.function @cabi_realloc_wit_bindgen_0_46_0(v201: i32, v202: i32, v203: i32, v204: i32) -> i32 { + ^block40(v201: i32, v202: i32, v203: i32, v204: i32): + v206 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/wit_bindgen::rt::cabi_realloc(v201, v202, v203, v204) : i32 + builtin.ret v206; + }; + + private builtin.function @wit_bindgen::rt::cabi_realloc(v207: i32, v208: i32, v209: i32, v210: i32) -> i32 { + ^block42(v207: i32, v208: i32, v209: i32, v210: i32): + v212 = arith.constant 0 : i32; + v213 = arith.neq v208, v212 : i1; + v291, v292, v293 = scf.if v213 : i32, i32, u32 { + ^block46: + v221 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/__rustc::__rust_realloc(v207, v208, v209, v210) : i32 + v282 = arith.constant 0 : u32; + v286 = ub.poison i32 : i32; + scf.yield v221, v286, v282; + } else { + ^block47: + v321 = arith.constant 0 : i32; + v322 = arith.constant 0 : i32; + v215 = arith.eq v210, v322 : i1; + v216 = arith.zext v215 : u32; + v217 = hir.bitcast v216 : i32; + v219 = arith.neq v217, v321 : i1; + v309 = scf.if v219 : i32 { + ^block61: + v320 = ub.poison i32 : i32; + scf.yield v320; + } else { + ^block48: + v220 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_id/alloc::alloc::alloc(v209, v210) : i32 + scf.yield v220; + }; + v318 = arith.constant 0 : u32; + v287 = arith.constant 1 : u32; + v311 = cf.select v219, v287, v318 : u32; + v319 = ub.poison i32 : i32; + v310 = cf.select v219, v209, v319 : i32; + scf.yield v309, v310, v311; + }; + v298, v299 = scf.index_switch v293 : i32, u32 + case 0 { + ^block45: + v316 = arith.constant 0 : i32; + v224 = arith.neq v291, v316 : i1; + v313 = arith.constant 1 : u32; + v314 = arith.constant 0 : u32; + v308 = cf.select v224, v314, v313 : u32; + v315 = ub.poison i32 : i32; + v307 = cf.select v224, v291, v315 : i32; + scf.yield v307, v308; + } + default { + ^block68: + v317 = arith.constant 0 : u32; + scf.yield v292, v317; + }; + v312 = arith.constant 0 : u32; + v306 = arith.eq v299, v312 : i1; + cf.cond_br v306 ^block63, ^block64; + ^block63: + builtin.ret v298; + ^block64: + ub.unreachable ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.segment readonly @1048576 = 0x00000001; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/abi_transform_tx_kernel_get_id.wat b/tests/integration/expected/abi_transform_tx_kernel_get_id.wat new file mode 100644 index 000000000..01367e560 --- /dev/null +++ b/tests/integration/expected/abi_transform_tx_kernel_get_id.wat @@ -0,0 +1,225 @@ +(module $abi_transform_tx_kernel_get_id.wasm + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (type (;3;) (func)) + (type (;4;) (func (param i32 i32 i32) (result i32))) + (type (;5;) (func (result i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "cabi_realloc_wit_bindgen_0_46_0" (func $cabi_realloc_wit_bindgen_0_46_0)) + (export "cabi_realloc" (func $cabi_realloc)) + (elem (;0;) (i32.const 1) func $cabi_realloc) + (func $entrypoint (;0;) (type 0) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 8 + i32.add + call $miden_base_sys::bindings::account::get_id + local.get 0 + local.get 1 + i64.load offset=8 + i64.store align=4 + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $__rustc::__rust_alloc (;1;) (type 1) (param i32 i32) (result i32) + i32.const 1048580 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_realloc (;2;) (type 2) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + i32.const 1048580 + local.get 2 + local.get 3 + call $::alloc + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + local.get 1 + local.get 3 + local.get 1 + i32.lt_u + select + local.tee 3 + i32.eqz + br_if 0 (;@1;) + local.get 2 + local.get 0 + local.get 3 + memory.copy + end + local.get 2 + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;3;) (type 3) + return + ) + (func $::alloc (;4;) (type 4) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;5;) (type 5) (result i32) + unreachable + ) + (func $miden_base_sys::bindings::account::get_id (;6;) (type 0) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 8 + i32.add + call $miden::account::get_id + local.get 0 + local.get 1 + i64.load offset=8 align=4 + i64.store + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $core::ptr::alignment::Alignment::max (;7;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $miden::account::get_id (;8;) (type 0) (param i32) + unreachable + ) + (func $cabi_realloc (;9;) (type 2) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $cabi_realloc_wit_bindgen_0_46_0 + ) + (func $alloc::alloc::alloc (;10;) (type 1) (param i32 i32) (result i32) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + local.get 1 + local.get 0 + call $__rustc::__rust_alloc + ) + (func $cabi_realloc_wit_bindgen_0_46_0 (;11;) (type 2) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $wit_bindgen::rt::cabi_realloc + ) + (func $wit_bindgen::rt::cabi_realloc (;12;) (type 2) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 1 + br_if 0 (;@3;) + local.get 3 + i32.eqz + br_if 2 (;@1;) + local.get 2 + local.get 3 + call $alloc::alloc::alloc + local.set 2 + br 1 (;@2;) + end + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $__rustc::__rust_realloc + local.set 2 + end + local.get 2 + br_if 0 (;@1;) + unreachable + end + local.get 2 + ) + (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00") +) diff --git a/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.hir b/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.hir index 16cb9a16e..d1f2e2261 100644 --- a/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.hir +++ b/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.hir @@ -1,367 +1,1107 @@ -(component - ;; Modules - (module #abi_transform_tx_kernel_get_inputs_4 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @abi_transform_tx_kernel_get_inputs_4 { + public builtin.function @entrypoint() { + ^block4: + v1 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v2 = hir.bitcast v1 : ptr; + v3 = hir.load v2 : i32; + v4 = arith.constant 16 : i32; + v5 = arith.sub v3, v4 : i32 #[overflow = wrapping]; + v6 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v7 = hir.bitcast v6 : ptr; + hir.store v7, v5; + v8 = arith.constant 4 : i32; + v9 = arith.add v5, v8 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/miden_base_sys::bindings::note::get_inputs(v9) + v11 = arith.constant 12 : u32; + v10 = hir.bitcast v5 : u32; + v12 = arith.add v10, v11 : u32 #[overflow = checked]; + v13 = arith.constant 4 : u32; + v14 = arith.mod v12, v13 : u32; + hir.assertz v14 #[code = 250]; + v15 = hir.int_to_ptr v12 : ptr; + v16 = hir.load v15 : i32; + v17 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/>::from(v16) : felt + v766 = arith.constant 4 : i32; + v19 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::from_u32(v766) : felt + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::assert_eq(v17, v19) + v765 = arith.constant 0 : i32; + v0 = arith.constant 0 : i32; + v21 = arith.eq v16, v0 : i1; + v22 = arith.zext v21 : u32; + v23 = hir.bitcast v22 : i32; + v25 = arith.neq v23, v765 : i1; + v736 = scf.if v25 : u32 { + ^block112: + v732 = arith.constant 0 : u32; + scf.yield v732; + } else { + ^block7: + v27 = arith.constant 8 : u32; + v26 = hir.bitcast v5 : u32; + v28 = arith.add v26, v27 : u32 #[overflow = checked]; + v764 = arith.constant 4 : u32; + v30 = arith.mod v28, v764 : u32; + hir.assertz v30 #[code = 250]; + v31 = hir.int_to_ptr v28 : ptr; + v32 = hir.load v31 : i32; + v33 = hir.bitcast v32 : u32; + v763 = arith.constant 4 : u32; + v35 = arith.mod v33, v763 : u32; + hir.assertz v35 #[code = 250]; + v36 = hir.int_to_ptr v33 : ptr; + v37 = hir.load v36 : felt; + v38 = arith.constant -1 : i32; + v39 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::from_u32(v38) : felt + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::assert_eq(v37, v39) + v762 = arith.constant 0 : i32; + v40 = arith.constant 1 : i32; + v41 = arith.eq v16, v40 : i1; + v42 = arith.zext v41 : u32; + v43 = hir.bitcast v42 : i32; + v45 = arith.neq v43, v762 : i1; + v738 = scf.if v45 : u32 { + ^block111: + v761 = arith.constant 0 : u32; + scf.yield v761; + } else { + ^block8: + v760 = arith.constant 4 : u32; + v46 = hir.bitcast v32 : u32; + v48 = arith.add v46, v760 : u32 #[overflow = checked]; + v759 = arith.constant 4 : u32; + v50 = arith.mod v48, v759 : u32; + hir.assertz v50 #[code = 250]; + v51 = hir.int_to_ptr v48 : ptr; + v52 = hir.load v51 : felt; + v758 = arith.constant 1 : i32; + v54 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::from_u32(v758) : felt + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::assert_eq(v52, v54) + v757 = arith.constant 0 : i32; + v731 = arith.constant 2 : u32; + v56 = hir.bitcast v16 : u32; + v58 = arith.lte v56, v731 : i1; + v59 = arith.zext v58 : u32; + v60 = hir.bitcast v59 : i32; + v62 = arith.neq v60, v757 : i1; + v740 = scf.if v62 : u32 { + ^block110: + v756 = arith.constant 0 : u32; + scf.yield v756; + } else { + ^block9: + v755 = arith.constant 8 : u32; + v63 = hir.bitcast v32 : u32; + v65 = arith.add v63, v755 : u32 #[overflow = checked]; + v754 = arith.constant 4 : u32; + v67 = arith.mod v65, v754 : u32; + hir.assertz v67 #[code = 250]; + v68 = hir.int_to_ptr v65 : ptr; + v69 = hir.load v68 : felt; + v55 = arith.constant 2 : i32; + v71 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::from_u32(v55) : felt + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::assert_eq(v69, v71) + v753 = arith.constant 0 : i32; + v72 = arith.constant 3 : i32; + v73 = arith.eq v16, v72 : i1; + v74 = arith.zext v73 : u32; + v75 = hir.bitcast v74 : i32; + v77 = arith.neq v75, v753 : i1; + scf.if v77{ + ^block109: + scf.yield ; + } else { + ^block10: + v752 = arith.constant 12 : u32; + v78 = hir.bitcast v32 : u32; + v80 = arith.add v78, v752 : u32 #[overflow = checked]; + v751 = arith.constant 4 : u32; + v82 = arith.mod v80, v751 : u32; + hir.assertz v82 #[code = 250]; + v83 = hir.int_to_ptr v80 : ptr; + v84 = hir.load v83 : felt; + v750 = arith.constant 3 : i32; + v86 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::from_u32(v750) : felt + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::felt::assert_eq(v84, v86) + v748 = arith.constant 4 : i32; + v749 = arith.constant 4 : i32; + v88 = arith.add v5, v749 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::raw_vec::RawVecInner::deallocate(v88, v748, v748) + v747 = arith.constant 16 : i32; + v92 = arith.add v5, v747 : i32 #[overflow = wrapping]; + v93 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v94 = hir.bitcast v93 : ptr; + hir.store v94, v92; + scf.yield ; + }; + v734 = arith.constant 1 : u32; + v746 = arith.constant 0 : u32; + v744 = cf.select v77, v746, v734 : u32; + scf.yield v744; + }; + scf.yield v740; + }; + scf.yield v738; + }; + v745 = arith.constant 0 : u32; + v743 = arith.eq v736, v745 : i1; + cf.cond_br v743 ^block6, ^block114; + ^block6: + ub.unreachable ; + ^block114: + builtin.ret ; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @__rustc::__rust_alloc(v95: i32, v96: i32) -> i32 { + ^block11(v95: i32, v96: i32): + v98 = arith.constant 1048640 : i32; + v99 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/::alloc(v98, v96, v95) : i32 + builtin.ret v99; + }; - ;; Functions - (func (export #entrypoint) (param i32) - (block 0 (param v0 i32) - (call #miden_base_sys::bindings::tx::get_inputs v0) - (br (block 1))) + private builtin.function @__rustc::__rust_dealloc(v100: i32, v101: i32, v102: i32) { + ^block13(v100: i32, v101: i32, v102: i32): + builtin.ret ; + }; - (block 1 - (ret)) - ) + private builtin.function @__rustc::__rust_realloc(v103: i32, v104: i32, v105: i32, v106: i32) -> i32 { + ^block15(v103: i32, v104: i32, v105: i32, v106: i32): + v108 = arith.constant 1048640 : i32; + v109 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/::alloc(v108, v105, v106) : i32 + v775 = arith.constant 0 : i32; + v110 = arith.constant 0 : i32; + v111 = arith.eq v109, v110 : i1; + v112 = arith.zext v111 : u32; + v113 = hir.bitcast v112 : i32; + v115 = arith.neq v113, v775 : i1; + scf.if v115{ + ^block17: + scf.yield ; + } else { + ^block18: + v774 = arith.constant 0 : i32; + v117 = hir.bitcast v104 : u32; + v116 = hir.bitcast v106 : u32; + v118 = arith.lt v116, v117 : i1; + v119 = arith.zext v118 : u32; + v120 = hir.bitcast v119 : i32; + v122 = arith.neq v120, v774 : i1; + v123 = cf.select v122, v106, v104 : i32; + v772 = arith.constant 0 : i32; + v773 = arith.constant 0 : i32; + v125 = arith.eq v123, v773 : i1; + v126 = arith.zext v125 : u32; + v127 = hir.bitcast v126 : i32; + v129 = arith.neq v127, v772 : i1; + scf.if v129{ + ^block119: + scf.yield ; + } else { + ^block19: + v130 = hir.bitcast v123 : u32; + v131 = hir.bitcast v109 : u32; + v132 = hir.int_to_ptr v131 : ptr; + v133 = hir.bitcast v103 : u32; + v134 = hir.int_to_ptr v133 : ptr; + hir.mem_cpy v134, v132, v130; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v109; + }; - (func (export #__rust_alloc) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048576)) - (let (v4 i32) (call #::alloc v3 v1 v0)) - (br (block 1 v4))) + private builtin.function @__rustc::__rust_alloc_zeroed(v136: i32, v137: i32) -> i32 { + ^block20(v136: i32, v137: i32): + v139 = arith.constant 1048640 : i32; + v140 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/::alloc(v139, v137, v136) : i32 + v784 = arith.constant 0 : i32; + v141 = arith.constant 0 : i32; + v142 = arith.eq v140, v141 : i1; + v143 = arith.zext v142 : u32; + v144 = hir.bitcast v143 : i32; + v146 = arith.neq v144, v784 : i1; + scf.if v146{ + ^block22: + scf.yield ; + } else { + ^block23: + v782 = arith.constant 0 : i32; + v783 = arith.constant 0 : i32; + v148 = arith.eq v136, v783 : i1; + v149 = arith.zext v148 : u32; + v150 = hir.bitcast v149 : i32; + v152 = arith.neq v150, v782 : i1; + scf.if v152{ + ^block122: + scf.yield ; + } else { + ^block24: + v776 = arith.constant 0 : u8; + v155 = hir.bitcast v136 : u32; + v156 = hir.bitcast v140 : u32; + v157 = hir.int_to_ptr v156 : ptr; + hir.mem_set v157, v155, v776; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v140; + }; - (block 1 (param v2 i32) - (ret v2)) - ) + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block25: + builtin.ret ; + }; - (func (export #__rust_alloc_zeroed) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048576)) - (let (v4 i32) (call #::alloc v3 v1 v0)) - (let (v5 i1) (eq v4 0)) - (let (v6 i32) (zext v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 2 v4) (block 3))) + private builtin.function @::alloc(v159: i32, v160: i32, v161: i32) -> i32 { + ^block27(v159: i32, v160: i32, v161: i32): + v164 = arith.constant 16 : i32; + v163 = arith.constant 0 : i32; + v786 = arith.constant 16 : u32; + v166 = hir.bitcast v160 : u32; + v168 = arith.gt v166, v786 : i1; + v169 = arith.zext v168 : u32; + v170 = hir.bitcast v169 : i32; + v172 = arith.neq v170, v163 : i1; + v173 = cf.select v172, v160, v164 : i32; + v826 = arith.constant 0 : i32; + v174 = arith.constant -1 : i32; + v175 = arith.add v173, v174 : i32 #[overflow = wrapping]; + v176 = arith.band v173, v175 : i32; + v178 = arith.neq v176, v826 : i1; + v795, v796 = scf.if v178 : i32, u32 { + ^block127: + v787 = arith.constant 0 : u32; + v791 = ub.poison i32 : i32; + scf.yield v791, v787; + } else { + ^block30: + v180 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/core::ptr::alignment::Alignment::max(v160, v173) : i32 + v825 = arith.constant 0 : i32; + v179 = arith.constant -2147483648 : i32; + v181 = arith.sub v179, v180 : i32 #[overflow = wrapping]; + v183 = hir.bitcast v181 : u32; + v182 = hir.bitcast v161 : u32; + v184 = arith.gt v182, v183 : i1; + v185 = arith.zext v184 : u32; + v186 = hir.bitcast v185 : i32; + v188 = arith.neq v186, v825 : i1; + v810 = scf.if v188 : i32 { + ^block126: + v824 = ub.poison i32 : i32; + scf.yield v824; + } else { + ^block31: + v822 = arith.constant 0 : i32; + v194 = arith.sub v822, v180 : i32 #[overflow = wrapping]; + v823 = arith.constant -1 : i32; + v190 = arith.add v161, v180 : i32 #[overflow = wrapping]; + v192 = arith.add v190, v823 : i32 #[overflow = wrapping]; + v195 = arith.band v192, v194 : i32; + v196 = hir.bitcast v159 : u32; + v197 = arith.constant 4 : u32; + v198 = arith.mod v196, v197 : u32; + hir.assertz v198 #[code = 250]; + v199 = hir.int_to_ptr v196 : ptr; + v200 = hir.load v199 : i32; + v821 = arith.constant 0 : i32; + v202 = arith.neq v200, v821 : i1; + scf.if v202{ + ^block125: + scf.yield ; + } else { + ^block33: + v203 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/intrinsics::mem::heap_base() : i32 + v204 = hir.mem_size : u32; + v210 = hir.bitcast v159 : u32; + v820 = arith.constant 4 : u32; + v212 = arith.mod v210, v820 : u32; + hir.assertz v212 #[code = 250]; + v819 = arith.constant 16 : u32; + v205 = hir.bitcast v204 : i32; + v208 = arith.shl v205, v819 : i32; + v209 = arith.add v203, v208 : i32 #[overflow = wrapping]; + v213 = hir.int_to_ptr v210 : ptr; + hir.store v213, v209; + scf.yield ; + }; + v216 = hir.bitcast v159 : u32; + v818 = arith.constant 4 : u32; + v218 = arith.mod v216, v818 : u32; + hir.assertz v218 #[code = 250]; + v219 = hir.int_to_ptr v216 : ptr; + v220 = hir.load v219 : i32; + v816 = arith.constant 0 : i32; + v817 = arith.constant -1 : i32; + v222 = arith.bxor v220, v817 : i32; + v224 = hir.bitcast v222 : u32; + v223 = hir.bitcast v195 : u32; + v225 = arith.gt v223, v224 : i1; + v226 = arith.zext v225 : u32; + v227 = hir.bitcast v226 : i32; + v229 = arith.neq v227, v816 : i1; + v809 = scf.if v229 : i32 { + ^block34: + v815 = arith.constant 0 : i32; + scf.yield v815; + } else { + ^block35: + v231 = hir.bitcast v159 : u32; + v814 = arith.constant 4 : u32; + v233 = arith.mod v231, v814 : u32; + hir.assertz v233 #[code = 250]; + v230 = arith.add v220, v195 : i32 #[overflow = wrapping]; + v234 = hir.int_to_ptr v231 : ptr; + hir.store v234, v230; + v236 = arith.add v220, v180 : i32 #[overflow = wrapping]; + scf.yield v236; + }; + scf.yield v809; + }; + v792 = arith.constant 1 : u32; + v813 = arith.constant 0 : u32; + v811 = cf.select v188, v813, v792 : u32; + scf.yield v810, v811; + }; + v812 = arith.constant 0 : u32; + v808 = arith.eq v796, v812 : i1; + cf.cond_br v808 ^block29, ^block129(v795); + ^block29: + ub.unreachable ; + ^block129(v788: i32): + builtin.ret v788; + }; - (block 1 (param v2 i32) - (ret v2)) + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block36: + v239 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v239; + }; - (block 2 (param v13 i32) - (br (block 1 v13))) + private builtin.function @alloc::raw_vec::RawVecInner::with_capacity_in(v241: i32, v242: i32, v243: i32, v244: i32) { + ^block40(v241: i32, v242: i32, v243: i32, v244: i32): + v246 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v247 = hir.bitcast v246 : ptr; + v248 = hir.load v247 : i32; + v249 = arith.constant 16 : i32; + v250 = arith.sub v248, v249 : i32 #[overflow = wrapping]; + v251 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v252 = hir.bitcast v251 : ptr; + hir.store v252, v250; + v245 = arith.constant 0 : i32; + v255 = arith.constant 256 : i32; + v253 = arith.constant 4 : i32; + v254 = arith.add v250, v253 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::raw_vec::RawVecInner::try_allocate_in(v254, v255, v245, v242, v243) + v258 = arith.constant 8 : u32; + v257 = hir.bitcast v250 : u32; + v259 = arith.add v257, v258 : u32 #[overflow = checked]; + v260 = arith.constant 4 : u32; + v261 = arith.mod v259, v260 : u32; + hir.assertz v261 #[code = 250]; + v262 = hir.int_to_ptr v259 : ptr; + v263 = hir.load v262 : i32; + v837 = arith.constant 4 : u32; + v264 = hir.bitcast v250 : u32; + v266 = arith.add v264, v837 : u32 #[overflow = checked]; + v836 = arith.constant 4 : u32; + v268 = arith.mod v266, v836 : u32; + hir.assertz v268 #[code = 250]; + v269 = hir.int_to_ptr v266 : ptr; + v270 = hir.load v269 : i32; + v835 = arith.constant 0 : i32; + v271 = arith.constant 1 : i32; + v272 = arith.neq v270, v271 : i1; + v273 = arith.zext v272 : u32; + v274 = hir.bitcast v273 : i32; + v276 = arith.neq v274, v835 : i1; + cf.cond_br v276 ^block42, ^block43; + ^block42: + v285 = arith.constant 12 : u32; + v284 = hir.bitcast v250 : u32; + v286 = arith.add v284, v285 : u32 #[overflow = checked]; + v834 = arith.constant 4 : u32; + v288 = arith.mod v286, v834 : u32; + hir.assertz v288 #[code = 250]; + v289 = hir.int_to_ptr v286 : ptr; + v290 = hir.load v289 : i32; + v833 = arith.constant 4 : u32; + v291 = hir.bitcast v241 : u32; + v293 = arith.add v291, v833 : u32 #[overflow = checked]; + v832 = arith.constant 4 : u32; + v295 = arith.mod v293, v832 : u32; + hir.assertz v295 #[code = 250]; + v296 = hir.int_to_ptr v293 : ptr; + hir.store v296, v290; + v297 = hir.bitcast v241 : u32; + v831 = arith.constant 4 : u32; + v299 = arith.mod v297, v831 : u32; + hir.assertz v299 #[code = 250]; + v300 = hir.int_to_ptr v297 : ptr; + hir.store v300, v263; + v830 = arith.constant 16 : i32; + v302 = arith.add v250, v830 : i32 #[overflow = wrapping]; + v303 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v304 = hir.bitcast v303 : ptr; + hir.store v304, v302; + builtin.ret ; + ^block43: + v829 = arith.constant 12 : u32; + v277 = hir.bitcast v250 : u32; + v279 = arith.add v277, v829 : u32 #[overflow = checked]; + v828 = arith.constant 4 : u32; + v281 = arith.mod v279, v828 : u32; + hir.assertz v281 #[code = 250]; + v282 = hir.int_to_ptr v279 : ptr; + v283 = hir.load v282 : i32; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::raw_vec::handle_error(v263, v283, v244) + ub.unreachable ; + }; - (block 3 - (let (v8 i32) (const.i32 0)) - (let (v9 u8) (trunc v8)) - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (bitcast v4)) - (let (v12 (ptr u8)) (inttoptr v11)) - (memset v12 v10 v9) - (br (block 2 v4))) - ) + private builtin.function @miden_base_sys::bindings::note::get_inputs(v305: i32) { + ^block44(v305: i32): + v307 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v308 = hir.bitcast v307 : ptr; + v309 = hir.load v308 : i32; + v310 = arith.constant 16 : i32; + v311 = arith.sub v309, v310 : i32 #[overflow = wrapping]; + v312 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v313 = hir.bitcast v312 : ptr; + hir.store v313, v311; + v318 = arith.constant 1048620 : i32; + v316 = arith.constant 4 : i32; + v314 = arith.constant 8 : i32; + v315 = arith.add v311, v314 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::raw_vec::RawVecInner::with_capacity_in(v315, v316, v316, v318) + v320 = arith.constant 8 : u32; + v319 = hir.bitcast v311 : u32; + v321 = arith.add v319, v320 : u32 #[overflow = checked]; + v322 = arith.constant 4 : u32; + v323 = arith.mod v321, v322 : u32; + hir.assertz v323 #[code = 250]; + v324 = hir.int_to_ptr v321 : ptr; + v325 = hir.load v324 : i32; + v327 = arith.constant 12 : u32; + v326 = hir.bitcast v311 : u32; + v328 = arith.add v326, v327 : u32 #[overflow = checked]; + v845 = arith.constant 4 : u32; + v330 = arith.mod v328, v845 : u32; + hir.assertz v330 #[code = 250]; + v331 = hir.int_to_ptr v328 : ptr; + v332 = hir.load v331 : i32; + v838 = arith.constant 2 : u32; + v334 = hir.bitcast v332 : u32; + v336 = arith.shr v334, v838 : u32; + v337 = hir.bitcast v336 : i32; + v338 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/miden::note::get_inputs(v337) : i32 + v844 = arith.constant 8 : u32; + v339 = hir.bitcast v305 : u32; + v341 = arith.add v339, v844 : u32 #[overflow = checked]; + v843 = arith.constant 4 : u32; + v343 = arith.mod v341, v843 : u32; + hir.assertz v343 #[code = 250]; + v344 = hir.int_to_ptr v341 : ptr; + hir.store v344, v338; + v842 = arith.constant 4 : u32; + v345 = hir.bitcast v305 : u32; + v347 = arith.add v345, v842 : u32 #[overflow = checked]; + v841 = arith.constant 4 : u32; + v349 = arith.mod v347, v841 : u32; + hir.assertz v349 #[code = 250]; + v350 = hir.int_to_ptr v347 : ptr; + hir.store v350, v332; + v351 = hir.bitcast v305 : u32; + v840 = arith.constant 4 : u32; + v353 = arith.mod v351, v840 : u32; + hir.assertz v353 #[code = 250]; + v354 = hir.int_to_ptr v351 : ptr; + hir.store v354, v325; + v839 = arith.constant 16 : i32; + v356 = arith.add v311, v839 : i32 #[overflow = wrapping]; + v357 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v358 = hir.bitcast v357 : ptr; + hir.store v358, v356; + builtin.ret ; + }; - (func (export #::alloc) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (const.i32 32)) - (let (v6 i32) (const.i32 32)) - (let (v7 u32) (bitcast v1)) - (let (v8 u32) (bitcast v6)) - (let (v9 i1) (gt v7 v8)) - (let (v10 i32) (sext v9)) - (let (v11 i1) (neq v10 0)) - (let (v12 i32) (select v11 v1 v5)) - (let (v13 u32) (popcnt v12)) - (let (v14 i32) (bitcast v13)) - (let (v15 i32) (const.i32 1)) - (let (v16 i1) (neq v14 v15)) - (let (v17 i32) (zext v16)) - (let (v18 i1) (neq v17 0)) - (condbr v18 (block 2) (block 3))) + private builtin.function @>::from(v359: i32) -> felt { + ^block46(v359: i32): + v361 = hir.bitcast v359 : felt; + builtin.ret v361; + }; - (block 1 (param v3 i32)) + private builtin.function @intrinsics::felt::from_u32(v362: i32) -> felt { + ^block48(v362: i32): + v363 = hir.bitcast v362 : felt; + builtin.ret v363; + }; - (block 2 - (unreachable)) + private builtin.function @intrinsics::felt::assert_eq(v365: felt, v366: felt) { + ^block50(v365: felt, v366: felt): + hir.assert_eq v365, v366; + builtin.ret ; + }; - (block 3 - (let (v19 i32) (const.i32 -2147483648)) - (let (v20 i32) (sub.wrapping v19 v12)) - (let (v21 u32) (bitcast v20)) - (let (v22 u32) (bitcast v2)) - (let (v23 i1) (lt v21 v22)) - (let (v24 i32) (sext v23)) - (let (v25 i1) (neq v24 0)) - (condbr v25 (block 2) (block 4))) + private builtin.function @alloc::raw_vec::RawVecInner::deallocate(v367: i32, v368: i32, v369: i32) { + ^block52(v367: i32, v368: i32, v369: i32): + v371 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v372 = hir.bitcast v371 : ptr; + v373 = hir.load v372 : i32; + v374 = arith.constant 16 : i32; + v375 = arith.sub v373, v374 : i32 #[overflow = wrapping]; + v376 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v377 = hir.bitcast v376 : ptr; + hir.store v377, v375; + v378 = arith.constant 4 : i32; + v379 = arith.add v375, v378 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::raw_vec::RawVecInner::current_memory(v379, v367, v368, v369) + v381 = arith.constant 8 : u32; + v380 = hir.bitcast v375 : u32; + v382 = arith.add v380, v381 : u32 #[overflow = checked]; + v383 = arith.constant 4 : u32; + v384 = arith.mod v382, v383 : u32; + hir.assertz v384 #[code = 250]; + v385 = hir.int_to_ptr v382 : ptr; + v386 = hir.load v385 : i32; + v852 = arith.constant 0 : i32; + v370 = arith.constant 0 : i32; + v388 = arith.eq v386, v370 : i1; + v389 = arith.zext v388 : u32; + v390 = hir.bitcast v389 : i32; + v392 = arith.neq v390, v852 : i1; + scf.if v392{ + ^block135: + scf.yield ; + } else { + ^block55: + v851 = arith.constant 4 : u32; + v393 = hir.bitcast v375 : u32; + v395 = arith.add v393, v851 : u32 #[overflow = checked]; + v850 = arith.constant 4 : u32; + v397 = arith.mod v395, v850 : u32; + hir.assertz v397 #[code = 250]; + v398 = hir.int_to_ptr v395 : ptr; + v399 = hir.load v398 : i32; + v401 = arith.constant 12 : u32; + v400 = hir.bitcast v375 : u32; + v402 = arith.add v400, v401 : u32 #[overflow = checked]; + v849 = arith.constant 4 : u32; + v404 = arith.mod v402, v849 : u32; + hir.assertz v404 #[code = 250]; + v405 = hir.int_to_ptr v402 : ptr; + v406 = hir.load v405 : i32; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/::deallocate(v399, v386, v406) + scf.yield ; + }; + v848 = arith.constant 16 : i32; + v409 = arith.add v375, v848 : i32 #[overflow = wrapping]; + v410 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v411 = hir.bitcast v410 : ptr; + hir.store v411, v409; + builtin.ret ; + }; - (block 4 - (let (v26 i32) (const.i32 0)) - (let (v27 i32) (add.wrapping v12 v2)) - (let (v28 i32) (const.i32 -1)) - (let (v29 i32) (add.wrapping v27 v28)) - (let (v30 i32) (const.i32 0)) - (let (v31 i32) (sub.wrapping v30 v12)) - (let (v32 i32) (band v29 v31)) - (let (v33 u32) (bitcast v0)) - (let (v34 u32) (mod.unchecked v33 4)) - (assertz 250 v34) - (let (v35 (ptr i32)) (inttoptr v33)) - (let (v36 i32) (load v35)) - (let (v37 i1) (neq v36 0)) - (condbr v37 (block 5 v0 v32 v12 v26) (block 6))) + private builtin.function @alloc::raw_vec::RawVecInner::try_allocate_in(v412: i32, v413: i32, v414: i32, v415: i32, v416: i32) { + ^block56(v412: i32, v413: i32, v414: i32, v415: i32, v416: i32): + v419 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v420 = hir.bitcast v419 : ptr; + v421 = hir.load v420 : i32; + v422 = arith.constant 16 : i32; + v423 = arith.sub v421, v422 : i32 #[overflow = wrapping]; + v424 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v425 = hir.bitcast v424 : ptr; + hir.store v425, v423; + v435 = hir.bitcast v413 : u32; + v436 = arith.zext v435 : u64; + v437 = hir.bitcast v436 : i64; + v417 = arith.constant 0 : i32; + v430 = arith.sub v417, v415 : i32 #[overflow = wrapping]; + v427 = arith.constant -1 : i32; + v426 = arith.add v415, v416 : i32 #[overflow = wrapping]; + v428 = arith.add v426, v427 : i32 #[overflow = wrapping]; + v431 = arith.band v428, v430 : i32; + v432 = hir.bitcast v431 : u32; + v433 = arith.zext v432 : u64; + v434 = hir.bitcast v433 : i64; + v438 = arith.mul v434, v437 : i64 #[overflow = wrapping]; + v956 = arith.constant 0 : i32; + v439 = arith.constant 32 : i64; + v441 = hir.cast v439 : u32; + v440 = hir.bitcast v438 : u64; + v442 = arith.shr v440, v441 : u64; + v443 = hir.bitcast v442 : i64; + v444 = arith.trunc v443 : i32; + v446 = arith.neq v444, v956 : i1; + v868, v869, v870, v871, v872, v873 = scf.if v446 : i32, i32, i32, i32, i32, u32 { + ^block137: + v853 = arith.constant 0 : u32; + v860 = ub.poison i32 : i32; + scf.yield v412, v423, v860, v860, v860, v853; + } else { + ^block61: + v447 = arith.trunc v438 : i32; + v955 = arith.constant 0 : i32; + v448 = arith.constant -2147483648 : i32; + v449 = arith.sub v448, v415 : i32 #[overflow = wrapping]; + v451 = hir.bitcast v449 : u32; + v450 = hir.bitcast v447 : u32; + v452 = arith.lte v450, v451 : i1; + v453 = arith.zext v452 : u32; + v454 = hir.bitcast v453 : i32; + v456 = arith.neq v454, v955 : i1; + v916 = scf.if v456 : i32 { + ^block59: + v954 = arith.constant 0 : i32; + v467 = arith.neq v447, v954 : i1; + v915 = scf.if v467 : i32 { + ^block63: + v953 = arith.constant 0 : i32; + v483 = arith.neq v414, v953 : i1; + v914 = scf.if v483 : i32 { + ^block66: + v465 = arith.constant 1 : i32; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::alloc::Global::alloc_impl(v423, v415, v447, v465) + v494 = hir.bitcast v423 : u32; + v539 = arith.constant 4 : u32; + v496 = arith.mod v494, v539 : u32; + hir.assertz v496 #[code = 250]; + v497 = hir.int_to_ptr v494 : ptr; + v498 = hir.load v497 : i32; + scf.yield v498; + } else { + ^block67: + v484 = arith.constant 8 : i32; + v485 = arith.add v423, v484 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/::allocate(v485, v415, v447) + v469 = arith.constant 8 : u32; + v486 = hir.bitcast v423 : u32; + v488 = arith.add v486, v469 : u32 #[overflow = checked]; + v952 = arith.constant 4 : u32; + v490 = arith.mod v488, v952 : u32; + hir.assertz v490 #[code = 250]; + v491 = hir.int_to_ptr v488 : ptr; + v492 = hir.load v491 : i32; + scf.yield v492; + }; + v950 = arith.constant 0 : i32; + v951 = arith.constant 0 : i32; + v501 = arith.eq v914, v951 : i1; + v502 = arith.zext v501 : u32; + v503 = hir.bitcast v502 : i32; + v505 = arith.neq v503, v950 : i1; + scf.if v505{ + ^block68: + v949 = arith.constant 8 : u32; + v522 = hir.bitcast v412 : u32; + v524 = arith.add v522, v949 : u32 #[overflow = checked]; + v948 = arith.constant 4 : u32; + v526 = arith.mod v524, v948 : u32; + hir.assertz v526 #[code = 250]; + v527 = hir.int_to_ptr v524 : ptr; + hir.store v527, v447; + v947 = arith.constant 4 : u32; + v529 = hir.bitcast v412 : u32; + v531 = arith.add v529, v947 : u32 #[overflow = checked]; + v946 = arith.constant 4 : u32; + v533 = arith.mod v531, v946 : u32; + hir.assertz v533 #[code = 250]; + v534 = hir.int_to_ptr v531 : ptr; + hir.store v534, v415; + scf.yield ; + } else { + ^block69: + v945 = arith.constant 8 : u32; + v507 = hir.bitcast v412 : u32; + v509 = arith.add v507, v945 : u32 #[overflow = checked]; + v944 = arith.constant 4 : u32; + v511 = arith.mod v509, v944 : u32; + hir.assertz v511 #[code = 250]; + v512 = hir.int_to_ptr v509 : ptr; + hir.store v512, v914; + v943 = arith.constant 4 : u32; + v514 = hir.bitcast v412 : u32; + v516 = arith.add v514, v943 : u32 #[overflow = checked]; + v942 = arith.constant 4 : u32; + v518 = arith.mod v516, v942 : u32; + hir.assertz v518 #[code = 250]; + v519 = hir.int_to_ptr v516 : ptr; + hir.store v519, v413; + scf.yield ; + }; + v940 = arith.constant 0 : i32; + v941 = arith.constant 1 : i32; + v913 = cf.select v505, v941, v940 : i32; + scf.yield v913; + } else { + ^block64: + v939 = arith.constant 8 : u32; + v468 = hir.bitcast v412 : u32; + v470 = arith.add v468, v939 : u32 #[overflow = checked]; + v938 = arith.constant 4 : u32; + v472 = arith.mod v470, v938 : u32; + hir.assertz v472 #[code = 250]; + v473 = hir.int_to_ptr v470 : ptr; + hir.store v473, v415; + v937 = arith.constant 4 : u32; + v476 = hir.bitcast v412 : u32; + v478 = arith.add v476, v937 : u32 #[overflow = checked]; + v936 = arith.constant 4 : u32; + v480 = arith.mod v478, v936 : u32; + hir.assertz v480 #[code = 250]; + v935 = arith.constant 0 : i32; + v481 = hir.int_to_ptr v478 : ptr; + hir.store v481, v935; + v934 = arith.constant 0 : i32; + scf.yield v934; + }; + scf.yield v915; + } else { + ^block62: + v933 = ub.poison i32 : i32; + scf.yield v933; + }; + v928 = arith.constant 0 : u32; + v861 = arith.constant 1 : u32; + v921 = cf.select v456, v861, v928 : u32; + v929 = ub.poison i32 : i32; + v920 = cf.select v456, v423, v929 : i32; + v930 = ub.poison i32 : i32; + v919 = cf.select v456, v412, v930 : i32; + v931 = ub.poison i32 : i32; + v918 = cf.select v456, v931, v423 : i32; + v932 = ub.poison i32 : i32; + v917 = cf.select v456, v932, v412 : i32; + scf.yield v917, v918, v919, v916, v920, v921; + }; + v874, v875, v876 = scf.index_switch v873 : i32, i32, i32 + case 0 { + ^block60: + v927 = arith.constant 4 : u32; + v459 = hir.bitcast v868 : u32; + v461 = arith.add v459, v927 : u32 #[overflow = checked]; + v926 = arith.constant 4 : u32; + v463 = arith.mod v461, v926 : u32; + hir.assertz v463 #[code = 250]; + v925 = arith.constant 0 : i32; + v464 = hir.int_to_ptr v461 : ptr; + hir.store v464, v925; + v924 = arith.constant 1 : i32; + scf.yield v868, v924, v869; + } + default { + ^block141: + scf.yield v870, v871, v872; + }; + v538 = hir.bitcast v874 : u32; + v923 = arith.constant 4 : u32; + v540 = arith.mod v538, v923 : u32; + hir.assertz v540 #[code = 250]; + v541 = hir.int_to_ptr v538 : ptr; + hir.store v541, v875; + v922 = arith.constant 16 : i32; + v546 = arith.add v876, v922 : i32 #[overflow = wrapping]; + v547 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v548 = hir.bitcast v547 : ptr; + hir.store v548, v546; + builtin.ret ; + }; - (block 5 - (param v49 i32) - (param v55 i32) - (param v65 i32) - (param v68 i32) - (let (v48 i32) (const.i32 268435456)) - (let (v50 u32) (bitcast v49)) - (let (v51 u32) (mod.unchecked v50 4)) - (assertz 250 v51) - (let (v52 (ptr i32)) (inttoptr v50)) - (let (v53 i32) (load v52)) - (let (v54 i32) (sub.wrapping v48 v53)) - (let (v56 u32) (bitcast v54)) - (let (v57 u32) (bitcast v55)) - (let (v58 i1) (lt v56 v57)) - (let (v59 i32) (sext v58)) - (let (v60 i1) (neq v59 0)) - (condbr v60 (block 7 v68) (block 8))) + private builtin.function @::allocate(v549: i32, v550: i32, v551: i32) { + ^block70(v549: i32, v550: i32, v551: i32): + v553 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v554 = hir.bitcast v553 : ptr; + v555 = hir.load v554 : i32; + v556 = arith.constant 16 : i32; + v557 = arith.sub v555, v556 : i32 #[overflow = wrapping]; + v558 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v559 = hir.bitcast v558 : ptr; + hir.store v559, v557; + v552 = arith.constant 0 : i32; + v560 = arith.constant 8 : i32; + v561 = arith.add v557, v560 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::alloc::Global::alloc_impl(v561, v550, v551, v552) + v564 = arith.constant 12 : u32; + v563 = hir.bitcast v557 : u32; + v565 = arith.add v563, v564 : u32 #[overflow = checked]; + v566 = arith.constant 4 : u32; + v567 = arith.mod v565, v566 : u32; + hir.assertz v567 #[code = 250]; + v568 = hir.int_to_ptr v565 : ptr; + v569 = hir.load v568 : i32; + v571 = arith.constant 8 : u32; + v570 = hir.bitcast v557 : u32; + v572 = arith.add v570, v571 : u32 #[overflow = checked]; + v961 = arith.constant 4 : u32; + v574 = arith.mod v572, v961 : u32; + hir.assertz v574 #[code = 250]; + v575 = hir.int_to_ptr v572 : ptr; + v576 = hir.load v575 : i32; + v577 = hir.bitcast v549 : u32; + v960 = arith.constant 4 : u32; + v579 = arith.mod v577, v960 : u32; + hir.assertz v579 #[code = 250]; + v580 = hir.int_to_ptr v577 : ptr; + hir.store v580, v576; + v959 = arith.constant 4 : u32; + v581 = hir.bitcast v549 : u32; + v583 = arith.add v581, v959 : u32 #[overflow = checked]; + v958 = arith.constant 4 : u32; + v585 = arith.mod v583, v958 : u32; + hir.assertz v585 #[code = 250]; + v586 = hir.int_to_ptr v583 : ptr; + hir.store v586, v569; + v957 = arith.constant 16 : i32; + v588 = arith.add v557, v957 : i32 #[overflow = wrapping]; + v589 = builtin.global_symbol @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__stack_pointer : ptr + v590 = hir.bitcast v589 : ptr; + hir.store v590, v588; + builtin.ret ; + }; - (block 6 - (let (v38 u32) (call (#intrinsics::mem #heap_base))) - (let (v39 u32) (memory.size)) - (let (v40 i32) (const.i32 16)) - (let (v41 u32) (bitcast v40)) - (let (v42 u32) (shl.wrapping v39 v41)) - (let (v43 u32) (add.wrapping v38 v42)) - (let (v44 i32) (bitcast v43)) - (let (v45 u32) (bitcast v0)) - (let (v46 u32) (mod.unchecked v45 4)) - (assertz 250 v46) - (let (v47 (ptr i32)) (inttoptr v45)) - (store v47 v44) - (br (block 5 v0 v32 v12 v26))) + private builtin.function @alloc::alloc::Global::alloc_impl(v591: i32, v592: i32, v593: i32, v594: i32) { + ^block72(v591: i32, v592: i32, v593: i32, v594: i32): + v977 = arith.constant 0 : i32; + v595 = arith.constant 0 : i32; + v596 = arith.eq v593, v595 : i1; + v597 = arith.zext v596 : u32; + v598 = hir.bitcast v597 : i32; + v600 = arith.neq v598, v977 : i1; + v973 = scf.if v600 : i32 { + ^block144: + scf.yield v592; + } else { + ^block75: + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v976 = arith.constant 0 : i32; + v602 = arith.neq v594, v976 : i1; + v972 = scf.if v602 : i32 { + ^block76: + v604 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__rustc::__rust_alloc_zeroed(v593, v592) : i32 + scf.yield v604; + } else { + ^block77: + v603 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__rustc::__rust_alloc(v593, v592) : i32 + scf.yield v603; + }; + scf.yield v972; + }; + v608 = arith.constant 4 : u32; + v607 = hir.bitcast v591 : u32; + v609 = arith.add v607, v608 : u32 #[overflow = checked]; + v975 = arith.constant 4 : u32; + v611 = arith.mod v609, v975 : u32; + hir.assertz v611 #[code = 250]; + v612 = hir.int_to_ptr v609 : ptr; + hir.store v612, v593; + v614 = hir.bitcast v591 : u32; + v974 = arith.constant 4 : u32; + v616 = arith.mod v614, v974 : u32; + hir.assertz v616 #[code = 250]; + v617 = hir.int_to_ptr v614 : ptr; + hir.store v617, v973; + builtin.ret ; + }; - (block 7 (param v67 i32) - (ret v67)) + private builtin.function @alloc::raw_vec::RawVecInner::current_memory(v618: i32, v619: i32, v620: i32, v621: i32) { + ^block78(v618: i32, v619: i32, v620: i32, v621: i32): + v1003 = arith.constant 0 : i32; + v622 = arith.constant 0 : i32; + v626 = arith.eq v621, v622 : i1; + v627 = arith.zext v626 : u32; + v628 = hir.bitcast v627 : i32; + v630 = arith.neq v628, v1003 : i1; + v990, v991 = scf.if v630 : i32, i32 { + ^block148: + v1002 = arith.constant 0 : i32; + v624 = arith.constant 4 : i32; + scf.yield v624, v1002; + } else { + ^block81: + v631 = hir.bitcast v619 : u32; + v666 = arith.constant 4 : u32; + v633 = arith.mod v631, v666 : u32; + hir.assertz v633 #[code = 250]; + v634 = hir.int_to_ptr v631 : ptr; + v635 = hir.load v634 : i32; + v1000 = arith.constant 0 : i32; + v1001 = arith.constant 0 : i32; + v637 = arith.eq v635, v1001 : i1; + v638 = arith.zext v637 : u32; + v639 = hir.bitcast v638 : i32; + v641 = arith.neq v639, v1000 : i1; + v988 = scf.if v641 : i32 { + ^block147: + v999 = arith.constant 0 : i32; + scf.yield v999; + } else { + ^block82: + v998 = arith.constant 4 : u32; + v642 = hir.bitcast v618 : u32; + v644 = arith.add v642, v998 : u32 #[overflow = checked]; + v997 = arith.constant 4 : u32; + v646 = arith.mod v644, v997 : u32; + hir.assertz v646 #[code = 250]; + v647 = hir.int_to_ptr v644 : ptr; + hir.store v647, v620; + v996 = arith.constant 4 : u32; + v648 = hir.bitcast v619 : u32; + v650 = arith.add v648, v996 : u32 #[overflow = checked]; + v995 = arith.constant 4 : u32; + v652 = arith.mod v650, v995 : u32; + hir.assertz v652 #[code = 250]; + v653 = hir.int_to_ptr v650 : ptr; + v654 = hir.load v653 : i32; + v655 = hir.bitcast v618 : u32; + v994 = arith.constant 4 : u32; + v657 = arith.mod v655, v994 : u32; + hir.assertz v657 #[code = 250]; + v658 = hir.int_to_ptr v655 : ptr; + hir.store v658, v654; + v659 = arith.mul v635, v621 : i32 #[overflow = wrapping]; + scf.yield v659; + }; + v660 = arith.constant 8 : i32; + v993 = arith.constant 4 : i32; + v989 = cf.select v641, v993, v660 : i32; + scf.yield v989, v988; + }; + v663 = arith.add v618, v990 : i32 #[overflow = wrapping]; + v665 = hir.bitcast v663 : u32; + v992 = arith.constant 4 : u32; + v667 = arith.mod v665, v992 : u32; + hir.assertz v667 #[code = 250]; + v668 = hir.int_to_ptr v665 : ptr; + hir.store v668, v991; + builtin.ret ; + }; - (block 8 - (let (v61 i32) (add.wrapping v53 v55)) - (let (v62 u32) (bitcast v49)) - (let (v63 u32) (mod.unchecked v62 4)) - (assertz 250 v63) - (let (v64 (ptr i32)) (inttoptr v62)) - (store v64 v61) - (let (v66 i32) (add.wrapping v53 v65)) - (br (block 7 v66))) - ) + private builtin.function @::deallocate(v669: i32, v670: i32, v671: i32) { + ^block83(v669: i32, v670: i32, v671: i32): + v1005 = arith.constant 0 : i32; + v672 = arith.constant 0 : i32; + v673 = arith.eq v671, v672 : i1; + v674 = arith.zext v673 : u32; + v675 = hir.bitcast v674 : i32; + v677 = arith.neq v675, v1005 : i1; + scf.if v677{ + ^block85: + scf.yield ; + } else { + ^block86: + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__rustc::__rust_dealloc(v669, v671, v670) + scf.yield ; + }; + builtin.ret ; + }; - (func (export #miden_base_sys::bindings::tx::get_inputs) - (param i32) - (block 0 (param v0 i32) - (let (v1 i32) (const.i32 0)) - (let (v2 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v3 i32) (const.i32 16)) - (let (v4 i32) (sub.wrapping v2 v3)) - (let (v5 (ptr i32)) (global.symbol #__stack_pointer)) - (store v5 v4) - (let (v6 i32) (const.i32 4)) - (let (v7 i32) (add.wrapping v4 v6)) - (let (v8 i32) (const.i32 256)) - (let (v9 i32) (const.i32 0)) - (call #alloc::raw_vec::RawVec::try_allocate_in v7 v8 v9) - (let (v10 u32) (bitcast v4)) - (let (v11 u32) (add.checked v10 8)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 u32) (bitcast v4)) - (let (v16 u32) (add.checked v15 4)) - (let (v17 u32) (mod.unchecked v16 4)) - (assertz 250 v17) - (let (v18 (ptr i32)) (inttoptr v16)) - (let (v19 i32) (load v18)) - (let (v20 i32) (const.i32 1)) - (let (v21 i1) (neq v19 v20)) - (let (v22 i32) (zext v21)) - (let (v23 i1) (neq v22 0)) - (condbr v23 (block 2) (block 3))) + private builtin.function @alloc::raw_vec::handle_error(v678: i32, v679: i32, v680: i32) { + ^block87(v678: i32, v679: i32, v680: i32): + ub.unreachable ; + }; - (block 1 - (ret)) + private builtin.function @core::ptr::alignment::Alignment::max(v681: i32, v682: i32) -> i32 { + ^block89(v681: i32, v682: i32): + v689 = arith.constant 0 : i32; + v685 = hir.bitcast v682 : u32; + v684 = hir.bitcast v681 : u32; + v686 = arith.gt v684, v685 : i1; + v687 = arith.zext v686 : u32; + v688 = hir.bitcast v687 : i32; + v690 = arith.neq v688, v689 : i1; + v691 = cf.select v690, v681, v682 : i32; + builtin.ret v691; + }; - (block 2 - (let (v29 u32) (bitcast v4)) - (let (v30 u32) (add.checked v29 12)) - (let (v31 u32) (mod.unchecked v30 4)) - (assertz 250 v31) - (let (v32 (ptr i32)) (inttoptr v30)) - (let (v33 i32) (load v32)) - (let (v34 i32) (const.i32 4)) - (let (v35 u32) (bitcast v33)) - (let (v36 u32) (bitcast v34)) - (let (v37 u32) (shr.wrapping v35 v36)) - (let (v38 i32) (bitcast v37)) - (let [(v39 i32) (v40 i32)] (call (#miden::note #get_inputs) v38)) - (let (v41 u32) (bitcast v0)) - (let (v42 u32) (add.checked v41 8)) - (let (v43 u32) (mod.unchecked v42 4)) - (assertz 250 v43) - (let (v44 (ptr i32)) (inttoptr v42)) - (store v44 v39) - (let (v45 u32) (bitcast v0)) - (let (v46 u32) (add.checked v45 4)) - (let (v47 u32) (mod.unchecked v46 4)) - (assertz 250 v47) - (let (v48 (ptr i32)) (inttoptr v46)) - (store v48 v33) - (let (v49 u32) (bitcast v0)) - (let (v50 u32) (mod.unchecked v49 4)) - (assertz 250 v50) - (let (v51 (ptr i32)) (inttoptr v49)) - (store v51 v14) - (let (v52 i32) (const.i32 16)) - (let (v53 i32) (add.wrapping v4 v52)) - (let (v54 (ptr i32)) (global.symbol #__stack_pointer)) - (store v54 v53) - (br (block 1))) + private builtin.function @miden::note::get_inputs(v692: i32) -> i32 { + ^block91(v692: i32): + v693, v694 = hir.exec @miden/note/get_inputs(v692) : i32, i32 + builtin.ret v693; + }; - (block 3 - (let (v24 u32) (bitcast v4)) - (let (v25 u32) (add.checked v24 12)) - (let (v26 u32) (mod.unchecked v25 4)) - (assertz 250 v26) - (let (v27 (ptr i32)) (inttoptr v25)) - (let (v28 i32) (load v27)) - (call #alloc::raw_vec::handle_error v14 v28) - (unreachable)) - ) + public builtin.function @cabi_realloc(v696: i32, v697: i32, v698: i32, v699: i32) -> i32 { + ^block95(v696: i32, v697: i32, v698: i32, v699: i32): + v701 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/cabi_realloc_wit_bindgen_0_46_0(v696, v697, v698, v699) : i32 + builtin.ret v701; + }; - (func (export #alloc::raw_vec::RawVec::try_allocate_in) - (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i1) (neq v1 0)) - (condbr v4 (block 3) (block 4))) + private builtin.function @alloc::alloc::alloc(v702: i32, v703: i32) -> i32 { + ^block97(v702: i32, v703: i32): + hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v705 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__rustc::__rust_alloc(v703, v702) : i32 + builtin.ret v705; + }; - (block 1 - (ret)) + public builtin.function @cabi_realloc_wit_bindgen_0_46_0(v706: i32, v707: i32, v708: i32, v709: i32) -> i32 { + ^block99(v706: i32, v707: i32, v708: i32, v709: i32): + v711 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/wit_bindgen::rt::cabi_realloc(v706, v707, v708, v709) : i32 + builtin.ret v711; + }; - (block 2 (param v62 i32) (param v64 i32) - (let (v65 u32) (bitcast v62)) - (let (v66 u32) (mod.unchecked v65 4)) - (assertz 250 v66) - (let (v67 (ptr i32)) (inttoptr v65)) - (store v67 v64) - (br (block 1))) + private builtin.function @wit_bindgen::rt::cabi_realloc(v712: i32, v713: i32, v714: i32, v715: i32) -> i32 { + ^block101(v712: i32, v713: i32, v714: i32, v715: i32): + v717 = arith.constant 0 : i32; + v718 = arith.neq v713, v717 : i1; + v1016, v1017, v1018 = scf.if v718 : i32, i32, u32 { + ^block105: + v726 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/__rustc::__rust_realloc(v712, v713, v714, v715) : i32 + v1007 = arith.constant 0 : u32; + v1011 = ub.poison i32 : i32; + scf.yield v726, v1011, v1007; + } else { + ^block106: + v1046 = arith.constant 0 : i32; + v1047 = arith.constant 0 : i32; + v720 = arith.eq v715, v1047 : i1; + v721 = arith.zext v720 : u32; + v722 = hir.bitcast v721 : i32; + v724 = arith.neq v722, v1046 : i1; + v1034 = scf.if v724 : i32 { + ^block152: + v1045 = ub.poison i32 : i32; + scf.yield v1045; + } else { + ^block107: + v725 = hir.exec @root_ns:root@1.0.0/abi_transform_tx_kernel_get_inputs_4/alloc::alloc::alloc(v714, v715) : i32 + scf.yield v725; + }; + v1043 = arith.constant 0 : u32; + v1012 = arith.constant 1 : u32; + v1036 = cf.select v724, v1012, v1043 : u32; + v1044 = ub.poison i32 : i32; + v1035 = cf.select v724, v714, v1044 : i32; + scf.yield v1034, v1035, v1036; + }; + v1023, v1024 = scf.index_switch v1018 : i32, u32 + case 0 { + ^block104: + v1041 = arith.constant 0 : i32; + v729 = arith.neq v1016, v1041 : i1; + v1038 = arith.constant 1 : u32; + v1039 = arith.constant 0 : u32; + v1033 = cf.select v729, v1039, v1038 : u32; + v1040 = ub.poison i32 : i32; + v1032 = cf.select v729, v1016, v1040 : i32; + scf.yield v1032, v1033; + } + default { + ^block159: + v1042 = arith.constant 0 : u32; + scf.yield v1017, v1042; + }; + v1037 = arith.constant 0 : u32; + v1031 = arith.eq v1024, v1037 : i1; + cf.cond_br v1031 ^block154, ^block155; + ^block154: + builtin.ret v1023; + ^block155: + ub.unreachable ; + }; - (block 3 - (let (v11 i32) (const.i32 536870912)) - (let (v12 u32) (bitcast v1)) - (let (v13 u32) (bitcast v11)) - (let (v14 i1) (lt v12 v13)) - (let (v15 i32) (sext v14)) - (let (v16 i1) (neq v15 0)) - (condbr v16 (block 6) (block 7))) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - (block 4 - (let (v5 i64) (const.i64 17179869184)) - (let (v6 u32) (bitcast v0)) - (let (v7 u32) (add.checked v6 4)) - (let (v8 u32) (mod.unchecked v7 4)) - (assertz 250 v8) - (let (v9 (ptr i64)) (inttoptr v7)) - (store v9 v5) - (let (v10 i32) (const.i32 0)) - (br (block 2 v0 v10))) - - (block 5 (param v63 i32) - (let (v61 i32) (const.i32 1)) - (br (block 2 v63 v61))) - - (block 6 - (let (v22 i32) (const.i32 2)) - (let (v23 u32) (bitcast v22)) - (let (v24 i32) (shl.wrapping v1 v23)) - (let (v25 i1) (neq v2 0)) - (condbr v25 (block 9) (block 10))) - - (block 7 - (let (v17 i32) (const.i32 0)) - (let (v18 u32) (bitcast v0)) - (let (v19 u32) (add.checked v18 4)) - (let (v20 u32) (mod.unchecked v19 4)) - (assertz 250 v20) - (let (v21 (ptr i32)) (inttoptr v19)) - (store v21 v17) - (br (block 5 v0))) - - (block 8 - (param v36 i32) - (param v40 i32) - (param v45 i32) - (param v51 i32) - (let (v37 i1) (eq v36 0)) - (let (v38 i32) (zext v37)) - (let (v39 i1) (neq v38 0)) - (condbr v39 (block 11) (block 12))) - - (block 9 - (let (v34 i32) (const.i32 4)) - (let (v35 i32) (call #__rust_alloc_zeroed v24 v34)) - (br (block 8 v35 v0 v1 v24))) - - (block 10 - (let (v26 i32) (const.i32 0)) - (let (v27 u32) (bitcast v26)) - (let (v28 u32) (add.checked v27 1048580)) - (let (v29 (ptr u8)) (inttoptr v28)) - (let (v30 u8) (load v29)) - (let (v31 i32) (zext v30)) - (let (v32 i32) (const.i32 4)) - (let (v33 i32) (call #__rust_alloc v24 v32)) - (br (block 8 v33 v0 v1 v24))) - - (block 11 - (let (v52 u32) (bitcast v40)) - (let (v53 u32) (add.checked v52 8)) - (let (v54 u32) (mod.unchecked v53 4)) - (assertz 250 v54) - (let (v55 (ptr i32)) (inttoptr v53)) - (store v55 v51) - (let (v56 i32) (const.i32 4)) - (let (v57 u32) (bitcast v40)) - (let (v58 u32) (add.checked v57 4)) - (let (v59 u32) (mod.unchecked v58 4)) - (assertz 250 v59) - (let (v60 (ptr i32)) (inttoptr v58)) - (store v60 v56) - (br (block 5 v40))) - - (block 12 - (let (v41 u32) (bitcast v40)) - (let (v42 u32) (add.checked v41 8)) - (let (v43 u32) (mod.unchecked v42 4)) - (assertz 250 v43) - (let (v44 (ptr i32)) (inttoptr v42)) - (store v44 v36) - (let (v46 u32) (bitcast v40)) - (let (v47 u32) (add.checked v46 4)) - (let (v48 u32) (mod.unchecked v47 4)) - (assertz 250 v48) - (let (v49 (ptr i32)) (inttoptr v47)) - (store v49 v45) - (let (v50 i32) (const.i32 0)) - (br (block 2 v40 v50))) - ) - - (func (export #alloc::raw_vec::handle_error) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (unreachable)) - - (block 1) - ) - - ;; Imports - (func (import #intrinsics::mem #heap_base) (result u32)) - (func (import #miden::note #get_inputs) (param i32) (result i32 i32)) - ) - -) + builtin.segment readonly @1048576 = 0x000000010000002100000019000000290010000000000073722e65746f6e2f73676e69646e69622f6372732f302e372e302d7379732d657361622d6e6564696d; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.masm b/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.masm index f96d217e3..c5b1d7505 100644 --- a/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.masm +++ b/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.masm @@ -1,791 +1,2003 @@ -# mod abi_transform_tx_kernel_get_inputs_4 +# mod root_ns:root@1.0.0 -use.miden::note -use.intrinsics::mem - -export.entrypoint - exec."miden_base_sys::bindings::tx::get_inputs" +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[1401399710535556876,5110350579171046467,11284637462410625470,4413703774666677263] + adv.push_mapval + push.262144 + push.4 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278544 end +# mod root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4 -export."miden_base_sys::bindings::tx::get_inputs" - mem_load.0x00011000 +export.entrypoint + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop push.16 u32wrapping_sub - push.1114112 + push.1114176 dup.1 swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::store_sw - dup.0 - add.4 - u32assert + trace.252 + nop + push.4 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::miden_base_sys::bindings::note::get_inputs + trace.252 + nop + push.12 dup.1 - add.8 + add u32assert - push.0 - push.256 push.4 - dup.5 - swap.1 - u32wrapping_add - exec."alloc::raw_vec::RawVec::try_allocate_in" dup.1 - u32mod.4 - assertz.err=250 - dup.0 - dup.0 - u32mod.16 - dup.0 - u32mod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - swap.1 - u32mod.4 - assertz.err=250 + u32mod + u32assert + assertz + u32divmod.4 swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop dup.0 - u32mod.16 - dup.0 - u32mod.4 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::>::from + trace.252 + nop + push.4 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::from_u32 + trace.252 + nop swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - push.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::assert_eq + trace.252 + nop + push.0 + push.0 + dup.2 + eq neq - neq.0 if.true - dup.1 - add.12 - u32assert - dup.0 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - dup.4 - add.8 + drop + drop + push.0 + else + push.8 + dup.2 + add u32assert - dup.1 push.4 - u32shr - exec.::miden::note::get_inputs + dup.1 swap.1 - drop - dup.6 - add.4 + u32mod u32assert - dup.2 - movup.2 + assertz + u32divmod.4 swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - movup.6 + push.4 dup.1 - movup.4 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.0 - movup.5 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - push.16 - movup.5 - swap.1 - u32wrapping_add - push.1114112 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - else - movup.2 - drop swap.1 - add.12 + u32mod u32assert - dup.0 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + assertz + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - movup.2 - exec."alloc::raw_vec::handle_error" - u32mod.4 - assertz.err=250 - push.0 - assert - end -end - - -export."alloc::raw_vec::RawVec::try_allocate_in" - dup.1 - neq.0 - if.true - dup.1 - push.536870912 - u32lt - push.0 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4294967295 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::assert_eq + trace.252 + nop push.0 - push.4294967294 - movup.2 - cdrop - u32or - neq.0 + push.1 + dup.3 + eq + neq if.true + drop + drop + drop + push.0 + else + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::assert_eq + trace.252 + nop + push.0 push.2 - dup.2 + dup.3 swap.1 - u32shl - movup.3 - neq.0 + u32lte + neq if.true + drop + drop + drop + push.0 + else + push.8 + dup.1 + add + u32assert push.4 dup.1 - exec."__rust_alloc_zeroed" + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::assert_eq + trace.252 + nop + push.0 + push.3 + movup.3 + eq + neq dup.0 - eq.0 - neq.0 if.true - movup.3 - swap.1 + movdn.2 drop drop - dup.1 - add.8 - u32assert - dup.2 - add.4 - u32assert - dup.1 - movup.3 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - movup.2 - push.4 - dup.2 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - push.1 - dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 else - swap.1 - drop - dup.1 - add.8 - u32assert - dup.2 - add.4 - u32assert - dup.1 - movup.3 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - movup.2 - dup.1 - movup.4 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - push.0 - dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 + push.12 movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - end - else - push.4 - dup.1 - exec."__rust_alloc" - dup.0 - eq.0 - neq.0 - if.true - movup.3 - swap.1 - drop - drop - dup.1 - add.8 - u32assert - dup.2 - add.4 + add u32assert - dup.1 - movup.3 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - movup.2 push.4 - dup.2 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - push.1 dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - else swap.1 - drop - dup.1 - add.8 - u32assert - dup.2 - add.4 + u32mod u32assert - dup.1 - movup.3 + assertz + u32divmod.4 swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.3 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::from_u32 + trace.252 + nop swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - movup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::felt::assert_eq + trace.252 + nop + push.4 + push.4 + dup.3 + u32wrapping_add dup.1 - movup.4 + swap.2 swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + push.16 movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - push.0 - dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32wrapping_add + push.1114176 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 + trace.240 + nop exec.::intrinsics::mem::store_sw - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 + trace.252 + nop end + push.1 + push.0 + movup.2 + cdrop end - else - movdn.2 - drop - drop - dup.0 - add.4 - u32assert - swap.1 - push.0 - dup.2 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - push.1 - dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 end + end + push.0 + eq + if.true + push.0 + assert else - movdn.2 - drop - drop - dup.0 - add.4 - u32assert - swap.1 - push.4.0 - dup.3 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_dw - push.0 - dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - u32mod.4 - assertz.err=250 - u32mod.4 - assertz.err=250 + nop end end - -export."__rust_alloc" - push.1048576 +proc.__rustc::__rust_alloc + push.1048640 movup.2 swap.1 - exec."::alloc" + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::::alloc + trace.252 + nop end +proc.__rustc::__rust_dealloc + drop + drop + drop +end -export."__rust_alloc_zeroed" - push.1048576 - dup.1 +proc.__rustc::__rust_realloc + push.1048640 + dup.4 swap.2 - swap.3 + swap.4 swap.1 - exec."::alloc" - dup.0 - eq.0 - neq.0 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::::alloc + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq if.true - swap.1 drop + movdn.3 + drop + drop + drop else push.0 - push.128 - u32and - movup.2 dup.2 + dup.5 + swap.1 + u32lt + neq + swap.1 + swap.4 + swap.1 + cdrop + push.0 push.0 dup.2 - gte.0 - while.true - dup.1 - dup.1 - push.1 - u32overflowing_madd - assertz - dup.4 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + eq + neq + if.true + drop + drop + else + dup.2 + movup.2 + push.0 + dup.3 + push.0 + gte + while.true + dup.2 + dup.1 + push.1 + u32overflowing_madd + assertz + dup.2 + dup.2 + push.1 + u32overflowing_madd + assertz + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + swap.1 + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + u32wrapping_add.1 + dup.0 + dup.4 + u32gte + end + dropw + end + end +end + +proc.__rustc::__rust_alloc_zeroed + push.1048640 + dup.1 + swap.2 + swap.3 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::::alloc + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + swap.1 + drop + else + push.0 + push.0 + dup.3 + eq + neq + if.true swap.1 - u32div.4 + drop + else + push.0 movup.2 - u32div.16 - dup.2 dup.2 + push.0 dup.2 - exec.::intrinsics::mem::load_sw - push.4294967040 - u32and - movup.5 - u32or - movdn.4 - exec.::intrinsics::mem::store_sw - u32wrapping_add.1 - dup.0 - dup.3 - u32gte + push.0 + gte + while.true + dup.1 + dup.1 + push.1 + u32overflowing_madd + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + u32wrapping_add.1 + dup.0 + dup.3 + u32gte + end + dropw end - dropw end end +proc.__rustc::__rust_no_alloc_shim_is_unstable_v2 + nop +end -export."::alloc" - push.32 - dup.2 - push.32 - u32gt - push.0 +proc.::alloc + push.16 push.0 - push.4294967294 - movup.2 - cdrop - u32or - neq.0 - movup.3 + push.16 + dup.4 + swap.1 + u32gt + neq + dup.3 swap.1 cdrop - dup.0 - u32popcnt - push.1 + push.0 + push.4294967295 + dup.2 + u32wrapping_add + dup.2 + u32and neq - neq.0 if.true - push.0 assert + dropw + push.0 + push.3735929054 else + movup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::core::ptr::alignment::Alignment::max + trace.252 + nop + push.0 push.2147483648 - dup.1 + dup.2 u32wrapping_sub - dup.3 - u32lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or - neq.0 + dup.4 + swap.1 + u32gt + neq + dup.0 if.true - push.0 assert + movdn.3 + drop + drop + drop + push.3735929054 else - dup.1 - dup.0 - u32mod.4 - assertz.err=250 - dup.1 - swap.1 - swap.4 - u32wrapping_add - push.4294967295 - u32wrapping_add push.0 dup.2 u32wrapping_sub + push.4294967295 + movup.5 + dup.4 + u32wrapping_add + u32wrapping_add u32and - push.0 - movup.4 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + dup.3 + push.4 + dup.1 swap.1 - u32div.4 - movup.2 - u32div.16 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop exec.::intrinsics::mem::load_sw - neq.0 + trace.252 + nop + push.0 + neq if.true - dup.3 - dup.0 - u32mod.4 - assertz.err=250 - push.268435456 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - dup.0 - swap.1 - swap.2 - swap.1 - u32wrapping_sub - dup.3 - u32lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or - neq.0 - if.true - drop - movdn.3 - drop - drop - drop - else - swap.1 - drop - movup.3 - dup.1 - swap.1 - swap.3 - u32wrapping_add - dup.2 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - swap.1 - u32mod.4 - assertz.err=250 - swap.1 - u32wrapping_add - end + nop else - dup.3 - exec.::intrinsics::mem::heap_base - dup.5 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::intrinsics::mem::heap_base + trace.252 + nop + trace.240 + nop exec.::intrinsics::mem::memory_size + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz push.16 - u32shl movup.2 swap.1 - u32wrapping_add - dup.2 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 + u32shl movup.2 - u32div.16 - exec.::intrinsics::mem::store_sw - dup.0 - u32mod.4 - assertz.err=250 + u32wrapping_add swap.1 - u32mod.4 - assertz.err=250 - push.268435456 + u32divmod.4 swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.4294967295 + dup.2 + u32xor + dup.3 + swap.1 + u32gt + neq + if.true + drop + drop + movdn.2 + drop + drop + push.0 + else + movup.4 + push.4 + dup.1 swap.1 - u32div.4 + u32mod + u32assert + assertz movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - dup.0 + dup.2 + u32wrapping_add swap.1 - swap.2 + u32divmod.4 swap.1 - u32wrapping_sub - dup.3 - u32lt - push.0 - push.0 - push.4294967294 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop movup.2 - cdrop - u32or - neq.0 + u32wrapping_add + end + end + push.1 + push.0 + movup.3 + cdrop + swap.1 + end + push.0 + movup.2 + eq + if.true + drop + push.0 + assert + else + nop + end +end + +proc.intrinsics::mem::heap_base + trace.240 + nop + exec.::intrinsics::mem::heap_base + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::with_capacity_in + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.256 + push.4 + dup.3 + u32wrapping_add + movup.4 + swap.6 + movdn.4 + movup.3 + swap.5 + movdn.3 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::raw_vec::RawVecInner::try_allocate_in + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + movup.3 + drop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + movup.2 + drop + push.12 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::raw_vec::handle_error + trace.252 + nop + push.0 + assert + end +end + +proc.miden_base_sys::bindings::note::get_inputs + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1048620 + push.4 + push.8 + dup.3 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::raw_vec::RawVecInner::with_capacity_in + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.2 + dup.1 + swap.1 + u32shr + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::miden::note::get_inputs + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.>::from + nop +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + +proc.alloc::raw_vec::RawVecInner::deallocate + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + u32wrapping_add + swap.1 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::raw_vec::RawVecInner::current_memory + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + else + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movdn.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::::deallocate + trace.252 + nop + end + push.16 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::try_allocate_in + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.2 + push.0 + push.0 + dup.7 + u32wrapping_sub + push.4294967295 + movup.9 + dup.9 + u32wrapping_add + u32wrapping_add + u32and + push.0 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + push.0 + push.32 + push.0 + dup.0 + push.2147483648 + u32and + eq.2147483648 + assertz + assertz + dup.0 + push.4294967295 + u32lte + assert + dup.3 + dup.3 + movup.2 + trace.240 + nop + exec.::std::math::u64::shr + trace.252 + nop + drop + neq + if.true + drop + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + push.3735929054 + dup.0 + dup.1 + swap.4 + swap.1 + swap.3 + swap.5 + else + drop + push.0 + push.2147483648 + dup.7 + u32wrapping_sub + dup.2 + swap.1 + u32lte + neq + dup.0 + if.true + push.0 + dup.2 + neq + if.true + push.0 + movup.6 + neq if.true - drop - movdn.3 - drop - drop - drop + push.1 + dup.3 + dup.7 + dup.4 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::alloc::Global::alloc_impl + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop else + push.8 + dup.3 + u32wrapping_add + dup.6 + dup.3 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::::allocate + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + end + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + movup.6 + movup.2 + drop drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 dup.1 swap.1 - swap.3 - u32wrapping_add - dup.2 - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32mod + u32assert + assertz + movup.5 swap.1 - u32div.4 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + movup.7 + movup.4 + drop + drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz movup.2 - u32div.16 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 swap.1 - u32mod.4 - assertz.err=250 + u32mod + u32assert + assertz + movup.5 swap.1 - u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop end + push.0 + push.1 + movup.2 + cdrop + else + movup.2 + swap.5 + movdn.2 + swap.4 + swap.1 + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + swap.1 + swap.3 + swap.2 + swap.1 end + else + swap.1 + drop + movup.3 + drop + movup.3 + drop + movup.3 + drop + push.3735929054 + end + push.0 + push.1 + dup.3 + cdrop + push.3735929054 + dup.3 + dup.5 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.7 + swap.1 + cdrop + push.3735929054 + dup.5 + movup.2 + swap.7 + movdn.2 + cdrop + push.3735929054 + movup.2 + swap.7 + movdn.2 + swap.1 + swap.5 + cdrop + swap.1 + swap.5 + swap.4 + swap.2 + swap.3 + swap.1 + end + movup.5 + eq.0 + if.true + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1 + swap.1 + else + drop + drop + end + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::allocate + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.8 + dup.2 + u32wrapping_add + movup.2 + swap.5 + movdn.2 + swap.1 + swap.3 + swap.4 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::alloc::Global::alloc_impl + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::alloc::Global::alloc_impl + push.0 + push.0 + dup.4 + eq + neq + if.true + movup.3 + drop + swap.1 + else + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::__rustc::__rust_no_alloc_shim_is_unstable_v2 + trace.252 + nop + push.0 + movup.4 + neq + if.true + swap.1 + dup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::__rustc::__rust_alloc_zeroed + trace.252 + nop + else + swap.1 + dup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::__rustc::__rust_alloc + trace.252 + nop + end + end + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::current_memory + push.0 + push.0 + dup.5 + eq + neq + if.true + movdn.3 + drop + drop + drop + push.0 + push.4 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + else + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.3 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + movup.2 + swap.1 end + push.8 + push.4 + movup.3 + cdrop + end + movup.2 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::deallocate + push.0 + push.0 + dup.4 + eq + neq + if.true + drop + drop + drop + else + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::__rustc::__rust_dealloc + trace.252 + nop end end +proc.alloc::raw_vec::handle_error + drop + drop + drop + push.0 + assert +end + +proc.core::ptr::alignment::Alignment::max + push.0 + dup.2 + dup.2 + swap.1 + u32gt + neq + cdrop +end + +proc.miden::note::get_inputs + trace.240 + nop + exec.::miden::note::get_inputs + trace.252 + nop + swap.1 + drop +end + +export.cabi_realloc + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::cabi_realloc_wit_bindgen_0_46_0 + trace.252 + nop +end + +proc.alloc::alloc::alloc + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::__rustc::__rust_no_alloc_shim_is_unstable_v2 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::__rustc::__rust_alloc + trace.252 + nop +end -export."alloc::raw_vec::handle_error" - push.0 assert +export.cabi_realloc_wit_bindgen_0_46_0 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::wit_bindgen::rt::cabi_realloc + trace.252 + nop end +proc.wit_bindgen::rt::cabi_realloc + push.0 + dup.2 + neq + if.true + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::__rustc::__rust_realloc + trace.252 + nop + push.0 + push.3735929054 + movup.2 + else + drop + drop + push.0 + push.0 + dup.3 + eq + neq + dup.0 + if.true + movup.2 + drop + push.3735929054 + else + movup.2 + dup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::abi_transform_tx_kernel_get_inputs_4::alloc::alloc::alloc + trace.252 + nop + end + push.0 + push.1 + dup.3 + cdrop + push.3735929054 + swap.1 + swap.4 + swap.1 + swap.2 + swap.3 + cdrop + swap.1 + end + movup.2 + eq.0 + if.true + swap.1 + drop + push.0 + dup.1 + neq + push.1 + push.0 + dup.2 + cdrop + push.3735929054 + movdn.3 + movdn.3 + cdrop + else + drop + push.0 + swap.1 + end + push.0 + movup.2 + eq + if.true + nop + else + drop + push.0 + assert + end +end # mod miden::note export.get_inputs push.4294967295 push.1 - push.0 - push.4294967295 + push.2 + push.3 dup.4 mem_storew dropw push.4 end - diff --git a/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.wat b/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.wat index 435d4d2c0..ed97b21a5 100644 --- a/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.wat +++ b/tests/integration/expected/abi_transform_tx_kernel_get_inputs_4.wat @@ -1,32 +1,138 @@ (module $abi_transform_tx_kernel_get_inputs_4.wasm - (type (;0;) (func (result i32))) - (type (;1;) (func (param i32) (result i32))) - (type (;2;) (func (param i32))) - (type (;3;) (func (param i32 i32) (result i32))) + (type (;0;) (func)) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32 i32))) + (type (;3;) (func (param i32 i32 i32 i32) (result i32))) (type (;4;) (func (param i32 i32 i32) (result i32))) - (type (;5;) (func (param i32 i32 i32))) - (type (;6;) (func (param i32 i32))) - (import "intrinsics::mem" "heap_base" (func $miden_sdk_alloc::heap_base (;0;) (type 0))) - (import "miden::note" "get_inputs<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_note_get_inputs (;1;) (type 1))) - (func $entrypoint (;2;) (type 2) (param i32) + (type (;5;) (func (result i32))) + (type (;6;) (func (param i32 i32 i32 i32))) + (type (;7;) (func (param i32))) + (type (;8;) (func (param i32) (result f32))) + (type (;9;) (func (param f32 f32))) + (type (;10;) (func (param i32 i32 i32 i32 i32))) + (type (;11;) (func (param i32) (result i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "cabi_realloc_wit_bindgen_0_46_0" (func $cabi_realloc_wit_bindgen_0_46_0)) + (export "cabi_realloc" (func $cabi_realloc)) + (elem (;0;) (i32.const 1) func $cabi_realloc) + (func $entrypoint (;0;) (type 0) + (local i32 i32 i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 0 + global.set $__stack_pointer + local.get 0 + i32.const 4 + i32.add + call $miden_base_sys::bindings::note::get_inputs local.get 0 - call $miden_base_sys::bindings::tx::get_inputs + i32.load offset=12 + local.tee 1 + call $>::from + i32.const 4 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + block ;; label = @1 + local.get 1 + i32.eqz + br_if 0 (;@1;) + local.get 0 + i32.load offset=8 + local.tee 2 + f32.load + i32.const -1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 1 + i32.const 1 + i32.eq + br_if 0 (;@1;) + local.get 2 + f32.load offset=4 + i32.const 1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 1 + i32.const 2 + i32.le_u + br_if 0 (;@1;) + local.get 2 + f32.load offset=8 + i32.const 2 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 1 + i32.const 3 + i32.eq + br_if 0 (;@1;) + local.get 2 + f32.load offset=12 + i32.const 3 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 0 + i32.const 4 + i32.add + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 0 + i32.const 16 + i32.add + global.set $__stack_pointer + return + end + unreachable ) - (func $__rust_alloc (;3;) (type 3) (param i32 i32) (result i32) - i32.const 1048576 + (func $__rustc::__rust_alloc (;1;) (type 1) (param i32 i32) (result i32) + i32.const 1048640 local.get 1 local.get 0 call $::alloc ) - (func $__rust_alloc_zeroed (;4;) (type 3) (param i32 i32) (result i32) + (func $__rustc::__rust_dealloc (;2;) (type 2) (param i32 i32 i32)) + (func $__rustc::__rust_realloc (;3;) (type 3) (param i32 i32 i32 i32) (result i32) block ;; label = @1 - i32.const 1048576 + i32.const 1048640 + local.get 2 + local.get 3 + call $::alloc + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + local.get 1 + local.get 3 + local.get 1 + i32.lt_u + select + local.tee 3 + i32.eqz + br_if 0 (;@1;) + local.get 2 + local.get 0 + local.get 3 + memory.copy + end + local.get 2 + ) + (func $__rustc::__rust_alloc_zeroed (;4;) (type 1) (param i32 i32) (result i32) + block ;; label = @1 + i32.const 1048640 local.get 1 local.get 0 call $::alloc local.tee 1 i32.eqz br_if 0 (;@1;) + local.get 0 + i32.eqz + br_if 0 (;@1;) local.get 1 i32.const 0 local.get 0 @@ -34,30 +140,37 @@ end local.get 1 ) - (func $::alloc (;5;) (type 4) (param i32 i32 i32) (result i32) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;5;) (type 0) + return + ) + (func $::alloc (;6;) (type 4) (param i32 i32 i32) (result i32) (local i32 i32) block ;; label = @1 local.get 1 - i32.const 32 + i32.const 16 local.get 1 - i32.const 32 + i32.const 16 i32.gt_u select - local.tee 1 - i32.popcnt - i32.const 1 - i32.ne + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and br_if 0 (;@1;) + local.get 2 i32.const -2147483648 local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 i32.sub - local.get 2 - i32.lt_u + i32.gt_u br_if 0 (;@1;) i32.const 0 local.set 3 - local.get 1 local.get 2 + local.get 1 i32.add i32.const -1 i32.add @@ -71,7 +184,7 @@ i32.load br_if 0 (;@2;) local.get 0 - call $miden_sdk_alloc::heap_base + call $intrinsics::mem::heap_base memory.size i32.const 16 i32.shl @@ -79,13 +192,13 @@ i32.store end block ;; label = @2 - i32.const 268435456 + local.get 2 local.get 0 i32.load local.tee 4 - i32.sub - local.get 2 - i32.lt_u + i32.const -1 + i32.xor + i32.gt_u br_if 0 (;@2;) local.get 0 local.get 4 @@ -102,41 +215,76 @@ end unreachable ) - (func $miden_base_sys::bindings::tx::get_inputs (;6;) (type 2) (param i32) - (local i32 i32 i32) + (func $intrinsics::mem::heap_base (;7;) (type 5) (result i32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::with_capacity_in (;8;) (type 6) (param i32 i32 i32 i32) + (local i32) global.get $__stack_pointer i32.const 16 i32.sub - local.tee 1 + local.tee 4 global.set $__stack_pointer - local.get 1 + local.get 4 i32.const 4 i32.add i32.const 256 i32.const 0 - call $alloc::raw_vec::RawVec::try_allocate_in local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::try_allocate_in + local.get 4 i32.load offset=8 local.set 2 block ;; label = @1 - local.get 1 + local.get 4 i32.load offset=4 i32.const 1 i32.ne br_if 0 (;@1;) local.get 2 - local.get 1 + local.get 4 i32.load offset=12 + local.get 3 call $alloc::raw_vec::handle_error unreachable end local.get 0 + local.get 4 + i32.load offset=12 + i32.store offset=4 + local.get 0 + local.get 2 + i32.store + local.get 4 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::note::get_inputs (;9;) (type 7) (param i32) + (local i32 i32 i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 8 + i32.add + i32.const 4 + i32.const 4 + i32.const 1048620 + call $alloc::raw_vec::RawVecInner::with_capacity_in + local.get 1 + i32.load offset=8 + local.set 2 + local.get 0 local.get 1 i32.load offset=12 local.tee 3 - i32.const 4 + i32.const 2 i32.shr_u - call $miden_base_sys::bindings::tx::externs::extern_note_get_inputs + call $miden::note::get_inputs i32.store offset=8 local.get 0 local.get 3 @@ -149,86 +297,323 @@ i32.add global.set $__stack_pointer ) - (func $alloc::raw_vec::RawVec::try_allocate_in (;7;) (type 5) (param i32 i32 i32) + (func $>::from (;10;) (type 8) (param i32) (result f32) + local.get 0 + f32.reinterpret_i32 + ) + (func $intrinsics::felt::from_u32 (;11;) (type 8) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::assert_eq (;12;) (type 9) (param f32 f32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;13;) (type 2) (param i32 i32 i32) (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::try_allocate_in (;14;) (type 10) (param i32 i32 i32 i32 i32) + (local i32 i64) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer block ;; label = @1 block ;; label = @2 - local.get 1 + block ;; label = @3 + local.get 3 + local.get 4 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 3 + i32.sub + i32.and + i64.extend_i32_u + local.get 1 + i64.extend_i32_u + i64.mul + local.tee 6 + i64.const 32 + i64.shr_u + i32.wrap_i64 + br_if 0 (;@3;) + local.get 6 + i32.wrap_i64 + local.tee 4 + i32.const -2147483648 + local.get 3 + i32.sub + i32.le_u + br_if 1 (;@2;) + end + local.get 0 + i32.const 0 + i32.store offset=4 + i32.const 1 + local.set 3 + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 br_if 0 (;@2;) local.get 0 - i64.const 17179869184 - i64.store offset=4 align=4 + local.get 3 + i32.store offset=8 i32.const 0 - local.set 1 + local.set 3 + local.get 0 + i32.const 0 + i32.store offset=4 br 1 (;@1;) end block ;; label = @2 block ;; label = @3 - local.get 1 - i32.const 536870912 - i32.lt_u + local.get 2 br_if 0 (;@3;) - local.get 0 - i32.const 0 - i32.store offset=4 - br 1 (;@2;) - end - local.get 1 - i32.const 2 - i32.shl - local.set 3 - block ;; label = @3 - block ;; label = @4 - local.get 2 - br_if 0 (;@4;) - i32.const 0 - i32.load8_u offset=1048580 - drop - local.get 3 - i32.const 4 - call $__rust_alloc - local.set 2 - br 1 (;@3;) - end + local.get 5 + i32.const 8 + i32.add local.get 3 - i32.const 4 - call $__rust_alloc_zeroed + local.get 4 + call $::allocate + local.get 5 + i32.load offset=8 local.set 2 + br 1 (;@2;) end - block ;; label = @3 - local.get 2 - i32.eqz - br_if 0 (;@3;) - local.get 0 - local.get 2 - i32.store offset=8 - local.get 0 - local.get 1 - i32.store offset=4 - i32.const 0 - local.set 1 - br 2 (;@1;) - end - local.get 0 + local.get 5 local.get 3 + local.get 4 + i32.const 1 + call $alloc::alloc::Global::alloc_impl + local.get 5 + i32.load + local.set 2 + end + block ;; label = @2 + local.get 2 + i32.eqz + br_if 0 (;@2;) + local.get 0 + local.get 2 i32.store offset=8 local.get 0 - i32.const 4 + local.get 1 i32.store offset=4 + i32.const 0 + local.set 3 + br 1 (;@1;) end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 i32.const 1 + local.set 3 + end + local.get 0 + local.get 3 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::allocate (;15;) (type 2) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 2 + i32.const 0 + call $alloc::alloc::Global::alloc_impl + local.get 3 + i32.load offset=12 + local.set 2 + local.get 0 + local.get 3 + i32.load offset=8 + i32.store + local.get 0 + local.get 2 + i32.store offset=4 + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::alloc::Global::alloc_impl (;16;) (type 6) (param i32 i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + local.get 2 + local.get 1 + call $__rustc::__rust_alloc + local.set 1 + br 1 (;@1;) + end + local.get 2 + local.get 1 + call $__rustc::__rust_alloc_zeroed local.set 1 end local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 local.get 1 i32.store ) - (func $alloc::raw_vec::handle_error (;8;) (type 6) (param i32 i32) + (func $alloc::raw_vec::RawVecInner::current_memory (;17;) (type 6) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;18;) (type 2) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) + (func $alloc::raw_vec::handle_error (;19;) (type 2) (param i32 i32 i32) unreachable ) - (table (;0;) 1 1 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $core::ptr::alignment::Alignment::max (;20;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $miden::note::get_inputs (;21;) (type 11) (param i32) (result i32) + unreachable + ) + (func $cabi_realloc (;22;) (type 3) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $cabi_realloc_wit_bindgen_0_46_0 + ) + (func $alloc::alloc::alloc (;23;) (type 1) (param i32 i32) (result i32) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + local.get 1 + local.get 0 + call $__rustc::__rust_alloc + ) + (func $cabi_realloc_wit_bindgen_0_46_0 (;24;) (type 3) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $wit_bindgen::rt::cabi_realloc + ) + (func $wit_bindgen::rt::cabi_realloc (;25;) (type 3) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 1 + br_if 0 (;@3;) + local.get 3 + i32.eqz + br_if 2 (;@1;) + local.get 2 + local.get 3 + call $alloc::alloc::alloc + local.set 2 + br 1 (;@2;) + end + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $__rustc::__rust_realloc + local.set 2 + end + local.get 2 + br_if 0 (;@1;) + unreachable + end + local.get 2 + ) + (data $.rodata (;0;) (i32.const 1048576) "miden-base-sys-0.7.0/src/bindings/note.rs\00\00\00\00\00\10\00)\00\00\00\19\00\00\00!\00\00\00\01\00\00\00") +) diff --git a/tests/integration/expected/add_felt.hir b/tests/integration/expected/add_felt.hir index 7bdcb4680..ffadf0fba 100644 --- a/tests/integration/expected/add_felt.hir +++ b/tests/integration/expected/add_felt.hir @@ -1,21 +1,19 @@ -(component - ;; Modules - (module #add_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @add_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> felt { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/add_felt/intrinsics::felt::add(v0, v1) : felt + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::add(v4: felt, v5: felt) -> felt { + ^block6(v4: felt, v5: felt): + v6 = arith.add v4, v5 : felt #[overflow = unchecked]; + builtin.ret v6; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result felt) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 felt) (add.unchecked v0 v1)) - (br (block 1 v3))) - - (block 1 (param v2 felt) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_felt.masm b/tests/integration/expected/add_felt.masm index 4b8b40228..10c6974b8 100644 --- a/tests/integration/expected/add_felt.masm +++ b/tests/integration/expected/add_felt.masm @@ -1,7 +1,26 @@ -# mod add_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::add_felt export.entrypoint - swap.1 add + trace.240 + nop + exec.::root_ns:root@1.0.0::add_felt::intrinsics::felt::add + trace.252 + nop end +proc.intrinsics::felt::add + add +end diff --git a/tests/integration/expected/add_felt.wat b/tests/integration/expected/add_felt.wat index 57a6d012c..b3692c867 100644 --- a/tests/integration/expected/add_felt.wat +++ b/tests/integration/expected/add_felt.wat @@ -1,14 +1,16 @@ (module $add_felt.wasm (type (;0;) (func (param f32 f32) (result f32))) - (import "miden:stdlib/intrinsics_felt" "add" (func $miden_stdlib_sys::intrinsics::felt::extern_add (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result f32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_add - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result f32) + local.get 0 + local.get 1 + call $intrinsics::felt::add + ) + (func $intrinsics::felt::add (;1;) (type 0) (param f32 f32) (result f32) + unreachable + ) +) diff --git a/tests/integration/expected/add_i128.hir b/tests/integration/expected/add_i128.hir new file mode 100644 index 000000000..04c58d6eb --- /dev/null +++ b/tests/integration/expected/add_i128.hir @@ -0,0 +1,38 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_c232a9b0045c768c9a1fca4d8a419280b591a50dcbebe88fe280529cdd423ec0 { + public builtin.function @entrypoint(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64) { + ^block6(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64): + v6 = arith.join v1, v2 : i128; + v5 = arith.join v3, v4 : i128; + v7 = arith.add v5, v6 : i128 #[overflow = wrapping]; + v8, v9 = arith.split v7 : i64, i64; + v11 = arith.constant 8 : u32; + v10 = hir.bitcast v0 : u32; + v12 = arith.add v10, v11 : u32 #[overflow = checked]; + v21 = arith.constant 8 : u32; + v14 = arith.mod v12, v21 : u32; + hir.assertz v14 #[code = 250]; + v15 = hir.int_to_ptr v12 : ptr; + hir.store v15, v8; + v16 = hir.bitcast v0 : u32; + v20 = arith.constant 8 : u32; + v18 = arith.mod v16, v20 : u32; + hir.assertz v18 #[code = 250]; + v19 = hir.int_to_ptr v16 : ptr; + hir.store v19, v9; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_i128.masm b/tests/integration/expected/add_i128.masm new file mode 100644 index 000000000..18a7e088e --- /dev/null +++ b/tests/integration/expected/add_i128.masm @@ -0,0 +1,66 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_c232a9b0045c768c9a1fca4d8a419280b591a50dcbebe88fe280529cdd423ec0 + +export.entrypoint + movdn.4 + movup.3 + movup.3 + movup.6 + movup.6 + movup.8 + movup.8 + trace.240 + nop + exec.::intrinsics::i128::add + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + diff --git a/tests/integration/expected/add_i128.wat b/tests/integration/expected/add_i128.wat new file mode 100644 index 000000000..d8a43f7b1 --- /dev/null +++ b/tests/integration/expected/add_i128.wat @@ -0,0 +1,26 @@ +(module $test_rust_c232a9b0045c768c9a1fca4d8a419280b591a50dcbebe88fe280529cdd423ec0.wasm + (type (;0;) (func (param i32 i64 i64 i64 i64))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (func $entrypoint (;0;) (type 0) (param i32 i64 i64 i64 i64) + local.get 3 + local.get 4 + local.get 1 + local.get 2 + i64.add128 + local.set 1 + local.set 2 + local.get 0 + local.get 1 + i64.store offset=8 + local.get 0 + local.get 2 + i64.store + ) +) diff --git a/tests/integration/expected/add_i16.hir b/tests/integration/expected/add_i16.hir index ea5296370..2e660c575 100644 --- a/tests/integration/expected/add_i16.hir +++ b/tests/integration/expected/add_i16.hir @@ -1,23 +1,22 @@ -(component - ;; Modules - (module #test_rust_4a80dace0dddc4f08e0a7761b4e1d269aa474b6beb14702baa097a4626d593c1 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_06c525756fe79253a5d667de4160df5c89e45269648c98bb21fd9430caec0b19 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + v4 = arith.sext v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (add.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_i16.masm b/tests/integration/expected/add_i16.masm index a085c685c..e79bfdcba 100644 --- a/tests/integration/expected/add_i16.masm +++ b/tests/integration/expected/add_i16.masm @@ -1,7 +1,24 @@ -# mod test_rust_4a80dace0dddc4f08e0a7761b4e1d269aa474b6beb14702baa097a4626d593c1 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_06c525756fe79253a5d667de4160df5c89e45269648c98bb21fd9430caec0b19 export.entrypoint u32wrapping_add end - diff --git a/tests/integration/expected/add_i16.wat b/tests/integration/expected/add_i16.wat index 7d5efb9e3..25c4003af 100644 --- a/tests/integration/expected/add_i16.wat +++ b/tests/integration/expected/add_i16.wat @@ -1,11 +1,5 @@ -(module $test_rust_4a80dace0dddc4f08e0a7761b4e1d269aa474b6beb14702baa097a4626d593c1.wasm +(module $test_rust_06c525756fe79253a5d667de4160df5c89e45269648c98bb21fd9430caec0b19.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.add - i32.extend16_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -14,4 +8,10 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + i32.extend16_s + ) +) diff --git a/tests/integration/expected/add_i32.hir b/tests/integration/expected/add_i32.hir index 6d7b67cf7..e22813949 100644 --- a/tests/integration/expected/add_i32.hir +++ b/tests/integration/expected/add_i32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_cc3b19fe60136e21eb08ffee1b6d6f2a6534ca0afa46f10f5296cdb8f0adfc30 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e52816950d9b2deff10441c5aff28d88268e921c4c01a207c8743d871bd3a69f { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (add.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_i32.masm b/tests/integration/expected/add_i32.masm index 4e0d9cebc..77c61bb48 100644 --- a/tests/integration/expected/add_i32.masm +++ b/tests/integration/expected/add_i32.masm @@ -1,7 +1,24 @@ -# mod test_rust_cc3b19fe60136e21eb08ffee1b6d6f2a6534ca0afa46f10f5296cdb8f0adfc30 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_e52816950d9b2deff10441c5aff28d88268e921c4c01a207c8743d871bd3a69f export.entrypoint u32wrapping_add end - diff --git a/tests/integration/expected/add_i32.wat b/tests/integration/expected/add_i32.wat index 96d8d29ed..c25adda26 100644 --- a/tests/integration/expected/add_i32.wat +++ b/tests/integration/expected/add_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_cc3b19fe60136e21eb08ffee1b6d6f2a6534ca0afa46f10f5296cdb8f0adfc30.wasm +(module $test_rust_e52816950d9b2deff10441c5aff28d88268e921c4c01a207c8743d871bd3a69f.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.add - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + ) +) diff --git a/tests/integration/expected/add_i64.hir b/tests/integration/expected/add_i64.hir index ba44354e0..ff9baea92 100644 --- a/tests/integration/expected/add_i64.hir +++ b/tests/integration/expected/add_i64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_069cf45252371f826e737bc3d7f808e1df77c97acac642efbaf976f4ab507131 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_cc1ebe8ed410a033febcc7c6a7e1b2473b2337e0dff32b08d3c91be5bbc2cc0a { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.add v1, v0 : i64 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (add.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_i64.masm b/tests/integration/expected/add_i64.masm index 66e0fe2d9..f7b1eecd1 100644 --- a/tests/integration/expected/add_i64.masm +++ b/tests/integration/expected/add_i64.masm @@ -1,7 +1,28 @@ -# mod test_rust_069cf45252371f826e737bc3d7f808e1df77c97acac642efbaf976f4ab507131 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_cc1ebe8ed410a033febcc7c6a7e1b2473b2337e0dff32b08d3c91be5bbc2cc0a export.entrypoint + trace.240 + nop exec.::std::math::u64::wrapping_add + trace.252 + nop end - diff --git a/tests/integration/expected/add_i64.wat b/tests/integration/expected/add_i64.wat index 20d0ba78d..f413c0cdb 100644 --- a/tests/integration/expected/add_i64.wat +++ b/tests/integration/expected/add_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_069cf45252371f826e737bc3d7f808e1df77c97acac642efbaf976f4ab507131.wasm +(module $test_rust_cc1ebe8ed410a033febcc7c6a7e1b2473b2337e0dff32b08d3c91be5bbc2cc0a.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.add - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.add + ) +) diff --git a/tests/integration/expected/add_i8.hir b/tests/integration/expected/add_i8.hir index 838a7f89f..a07babb0c 100644 --- a/tests/integration/expected/add_i8.hir +++ b/tests/integration/expected/add_i8.hir @@ -1,23 +1,22 @@ -(component - ;; Modules - (module #test_rust_3dc5f4de1f29681a88ff9608b56c29738ebfd35b5bf875f151de489b1c7e50f7 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_62ba1afb3113b5a2557098b909eff557cd716bf21b07843db3bfb765ff41e9f7 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + v4 = arith.sext v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (add.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_i8.masm b/tests/integration/expected/add_i8.masm index 73ca10240..ae2ceaa39 100644 --- a/tests/integration/expected/add_i8.masm +++ b/tests/integration/expected/add_i8.masm @@ -1,7 +1,24 @@ -# mod test_rust_3dc5f4de1f29681a88ff9608b56c29738ebfd35b5bf875f151de489b1c7e50f7 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_62ba1afb3113b5a2557098b909eff557cd716bf21b07843db3bfb765ff41e9f7 export.entrypoint u32wrapping_add end - diff --git a/tests/integration/expected/add_i8.wat b/tests/integration/expected/add_i8.wat index 7f101004a..105113bba 100644 --- a/tests/integration/expected/add_i8.wat +++ b/tests/integration/expected/add_i8.wat @@ -1,11 +1,5 @@ -(module $test_rust_3dc5f4de1f29681a88ff9608b56c29738ebfd35b5bf875f151de489b1c7e50f7.wasm +(module $test_rust_62ba1afb3113b5a2557098b909eff557cd716bf21b07843db3bfb765ff41e9f7.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.add - i32.extend8_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -14,4 +8,10 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + i32.extend8_s + ) +) diff --git a/tests/integration/expected/add_u128.hir b/tests/integration/expected/add_u128.hir new file mode 100644 index 000000000..9d3556c70 --- /dev/null +++ b/tests/integration/expected/add_u128.hir @@ -0,0 +1,38 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_08840461ad34cd3a46fc6a8d8f1a1282c51e37f56df328c1bc8d26d8c7a38f10 { + public builtin.function @entrypoint(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64) { + ^block6(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64): + v6 = arith.join v1, v2 : i128; + v5 = arith.join v3, v4 : i128; + v7 = arith.add v5, v6 : i128 #[overflow = wrapping]; + v8, v9 = arith.split v7 : i64, i64; + v11 = arith.constant 8 : u32; + v10 = hir.bitcast v0 : u32; + v12 = arith.add v10, v11 : u32 #[overflow = checked]; + v21 = arith.constant 8 : u32; + v14 = arith.mod v12, v21 : u32; + hir.assertz v14 #[code = 250]; + v15 = hir.int_to_ptr v12 : ptr; + hir.store v15, v8; + v16 = hir.bitcast v0 : u32; + v20 = arith.constant 8 : u32; + v18 = arith.mod v16, v20 : u32; + hir.assertz v18 #[code = 250]; + v19 = hir.int_to_ptr v16 : ptr; + hir.store v19, v9; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_u128.masm b/tests/integration/expected/add_u128.masm new file mode 100644 index 000000000..947269b5e --- /dev/null +++ b/tests/integration/expected/add_u128.masm @@ -0,0 +1,66 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_08840461ad34cd3a46fc6a8d8f1a1282c51e37f56df328c1bc8d26d8c7a38f10 + +export.entrypoint + movdn.4 + movup.3 + movup.3 + movup.6 + movup.6 + movup.8 + movup.8 + trace.240 + nop + exec.::intrinsics::i128::add + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + diff --git a/tests/integration/expected/add_u128.wat b/tests/integration/expected/add_u128.wat new file mode 100644 index 000000000..f537a7892 --- /dev/null +++ b/tests/integration/expected/add_u128.wat @@ -0,0 +1,26 @@ +(module $test_rust_08840461ad34cd3a46fc6a8d8f1a1282c51e37f56df328c1bc8d26d8c7a38f10.wasm + (type (;0;) (func (param i32 i64 i64 i64 i64))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (func $entrypoint (;0;) (type 0) (param i32 i64 i64 i64 i64) + local.get 3 + local.get 4 + local.get 1 + local.get 2 + i64.add128 + local.set 1 + local.set 2 + local.get 0 + local.get 1 + i64.store offset=8 + local.get 0 + local.get 2 + i64.store + ) +) diff --git a/tests/integration/expected/add_u16.hir b/tests/integration/expected/add_u16.hir index dd4cfe0ca..c01b147b4 100644 --- a/tests/integration/expected/add_u16.hir +++ b/tests/integration/expected/add_u16.hir @@ -1,25 +1,23 @@ -(component - ;; Modules - (module #test_rust_711c7705576a28225a7e87d297c54811b91eb1b69f3f407376a0af96dcad37b2 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_41c5fa21fe36fdf84a953669187b56426048e7b2f08e8de91e3e989c579ed46f { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = arith.constant 65535 : i32; + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + v5 = arith.band v3, v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (add.wrapping v1 v0)) - (let (v4 i32) (const.i32 65535)) - (let (v5 i32) (band v3 v4)) - (br (block 1 v5))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_u16.masm b/tests/integration/expected/add_u16.masm index 5637b6e04..226aff79d 100644 --- a/tests/integration/expected/add_u16.masm +++ b/tests/integration/expected/add_u16.masm @@ -1,7 +1,27 @@ -# mod test_rust_711c7705576a28225a7e87d297c54811b91eb1b69f3f407376a0af96dcad37b2 +# mod root_ns:root@1.0.0 -export.entrypoint - u32wrapping_add push.65535 u32and +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_41c5fa21fe36fdf84a953669187b56426048e7b2f08e8de91e3e989c579ed46f + +export.entrypoint + push.65535 + movdn.2 + u32wrapping_add + u32and +end diff --git a/tests/integration/expected/add_u16.wat b/tests/integration/expected/add_u16.wat index 28623e9e6..40754659d 100644 --- a/tests/integration/expected/add_u16.wat +++ b/tests/integration/expected/add_u16.wat @@ -1,12 +1,5 @@ -(module $test_rust_711c7705576a28225a7e87d297c54811b91eb1b69f3f407376a0af96dcad37b2.wasm +(module $test_rust_41c5fa21fe36fdf84a953669187b56426048e7b2f08e8de91e3e989c579ed46f.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.add - i32.const 65535 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + i32.const 65535 + i32.and + ) +) diff --git a/tests/integration/expected/add_u32.hir b/tests/integration/expected/add_u32.hir index b70ef7da8..6ae425139 100644 --- a/tests/integration/expected/add_u32.hir +++ b/tests/integration/expected/add_u32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_ca3478b0c28e59b401b0d632fb0a1c51d0c45a319d503d2a2a705107e8aab84f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_2aac0275c4e31b615b4d6bf7d39869ccab374d5df702b3ef708ea7f981b4073c { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (add.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_u32.masm b/tests/integration/expected/add_u32.masm index 612f5bd95..ff020eee0 100644 --- a/tests/integration/expected/add_u32.masm +++ b/tests/integration/expected/add_u32.masm @@ -1,7 +1,24 @@ -# mod test_rust_ca3478b0c28e59b401b0d632fb0a1c51d0c45a319d503d2a2a705107e8aab84f +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_2aac0275c4e31b615b4d6bf7d39869ccab374d5df702b3ef708ea7f981b4073c export.entrypoint u32wrapping_add end - diff --git a/tests/integration/expected/add_u32.wat b/tests/integration/expected/add_u32.wat index 93f93487b..d79fb519f 100644 --- a/tests/integration/expected/add_u32.wat +++ b/tests/integration/expected/add_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_ca3478b0c28e59b401b0d632fb0a1c51d0c45a319d503d2a2a705107e8aab84f.wasm +(module $test_rust_2aac0275c4e31b615b4d6bf7d39869ccab374d5df702b3ef708ea7f981b4073c.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.add - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + ) +) diff --git a/tests/integration/expected/add_u64.hir b/tests/integration/expected/add_u64.hir index 7f2c3f6b2..9418ef14e 100644 --- a/tests/integration/expected/add_u64.hir +++ b/tests/integration/expected/add_u64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_11c5a2e5412edeeffd507e0b820658a62ad960143f3b5167b2fe215d1ecfecfa - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_2c24c814fbf0c5ee22a60f52279e8f7639d56708da347e071f1130dc19dc9c07 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.add v1, v0 : i64 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (add.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_u64.masm b/tests/integration/expected/add_u64.masm index 21d20476e..143ad5e58 100644 --- a/tests/integration/expected/add_u64.masm +++ b/tests/integration/expected/add_u64.masm @@ -1,7 +1,28 @@ -# mod test_rust_11c5a2e5412edeeffd507e0b820658a62ad960143f3b5167b2fe215d1ecfecfa +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_2c24c814fbf0c5ee22a60f52279e8f7639d56708da347e071f1130dc19dc9c07 export.entrypoint + trace.240 + nop exec.::std::math::u64::wrapping_add + trace.252 + nop end - diff --git a/tests/integration/expected/add_u64.wat b/tests/integration/expected/add_u64.wat index 9ea5cbba4..a74cd3301 100644 --- a/tests/integration/expected/add_u64.wat +++ b/tests/integration/expected/add_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_11c5a2e5412edeeffd507e0b820658a62ad960143f3b5167b2fe215d1ecfecfa.wasm +(module $test_rust_2c24c814fbf0c5ee22a60f52279e8f7639d56708da347e071f1130dc19dc9c07.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.add - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.add + ) +) diff --git a/tests/integration/expected/add_u8.hir b/tests/integration/expected/add_u8.hir index 6ec45e6ce..4e9cea572 100644 --- a/tests/integration/expected/add_u8.hir +++ b/tests/integration/expected/add_u8.hir @@ -1,25 +1,23 @@ -(component - ;; Modules - (module #test_rust_195424540a8740c46e8ef057ffe65c7d2c73576a03c615db26b6ef1ef00d0357 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_9c74aa9695b645cec4339ff01ac3e35e31b39b1feb0dfeef6ee7c94789fbcfce { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = arith.constant 255 : i32; + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + v5 = arith.band v3, v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (add.wrapping v1 v0)) - (let (v4 i32) (const.i32 255)) - (let (v5 i32) (band v3 v4)) - (br (block 1 v5))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/add_u8.masm b/tests/integration/expected/add_u8.masm index 4e7adf93a..73d568513 100644 --- a/tests/integration/expected/add_u8.masm +++ b/tests/integration/expected/add_u8.masm @@ -1,7 +1,27 @@ -# mod test_rust_195424540a8740c46e8ef057ffe65c7d2c73576a03c615db26b6ef1ef00d0357 +# mod root_ns:root@1.0.0 -export.entrypoint - u32wrapping_add push.255 u32and +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_9c74aa9695b645cec4339ff01ac3e35e31b39b1feb0dfeef6ee7c94789fbcfce + +export.entrypoint + push.255 + movdn.2 + u32wrapping_add + u32and +end diff --git a/tests/integration/expected/add_u8.wat b/tests/integration/expected/add_u8.wat index fb425d3ed..aea245673 100644 --- a/tests/integration/expected/add_u8.wat +++ b/tests/integration/expected/add_u8.wat @@ -1,12 +1,5 @@ -(module $test_rust_195424540a8740c46e8ef057ffe65c7d2c73576a03c615db26b6ef1ef00d0357.wasm +(module $test_rust_9c74aa9695b645cec4339ff01ac3e35e31b39b1feb0dfeef6ee7c94789fbcfce.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.add - i32.const 255 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + i32.const 255 + i32.and + ) +) diff --git a/tests/integration/expected/adv_load_preimage.hir b/tests/integration/expected/adv_load_preimage.hir new file mode 100644 index 000000000..bc1efc8c3 --- /dev/null +++ b/tests/integration/expected/adv_load_preimage.hir @@ -0,0 +1,750 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @adv_load_preimage { + public builtin.function @entrypoint(v0: i32, v1: felt, v2: felt, v3: felt, v4: felt) { + ^block4(v0: i32, v1: felt, v2: felt, v3: felt, v4: felt): + v9 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v10 = hir.bitcast v9 : ptr; + v11 = hir.load v10 : i32; + v12 = arith.constant 16 : i32; + v13 = arith.sub v11, v12 : i32 #[overflow = wrapping]; + v14 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v15 = hir.bitcast v14 : ptr; + hir.store v15, v13; + v16 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::advice::adv_push_mapvaln(v4, v3, v2, v1) : felt + v17 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::as_u64(v16) : i64 + v19 = arith.constant 3 : i32; + v18 = arith.trunc v17 : i32; + v20 = arith.band v18, v19 : i32; + v21 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::from_u32(v20) : felt + v5 = arith.constant 0 : i32; + v23 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::from_u32(v5) : felt + hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::assert_eq(v21, v23) + v26 = arith.constant 2 : i64; + v28 = hir.cast v26 : u32; + v27 = hir.bitcast v17 : u64; + v29 = arith.shr v27, v28 : u64; + v30 = hir.bitcast v29 : i64; + v31 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::from_u64_unchecked(v30) : felt + v32 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::as_u64(v31) : i64 + v515 = arith.constant 2 : u32; + v33 = arith.trunc v32 : i32; + v36 = arith.shl v33, v515 : i32; + v556 = arith.constant 4 : i32; + v557 = arith.constant 0 : i32; + v24 = arith.constant 4 : i32; + v25 = arith.add v13, v24 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/adv_load_preimage/alloc::raw_vec::RawVecInner::try_allocate_in(v25, v36, v557, v556, v556) + v41 = arith.constant 8 : u32; + v40 = hir.bitcast v13 : u32; + v42 = arith.add v40, v41 : u32 #[overflow = checked]; + v43 = arith.constant 4 : u32; + v44 = arith.mod v42, v43 : u32; + hir.assertz v44 #[code = 250]; + v45 = hir.int_to_ptr v42 : ptr; + v46 = hir.load v45 : i32; + v555 = arith.constant 4 : u32; + v47 = hir.bitcast v13 : u32; + v49 = arith.add v47, v555 : u32 #[overflow = checked]; + v554 = arith.constant 4 : u32; + v51 = arith.mod v49, v554 : u32; + hir.assertz v51 #[code = 250]; + v52 = hir.int_to_ptr v49 : ptr; + v53 = hir.load v52 : i32; + v553 = arith.constant 0 : i32; + v54 = arith.constant 1 : i32; + v55 = arith.eq v53, v54 : i1; + v56 = arith.zext v55 : u32; + v57 = hir.bitcast v56 : i32; + v59 = arith.neq v57, v553 : i1; + v520 = scf.if v59 : u32 { + ^block7: + v150 = arith.constant 12 : u32; + v149 = hir.bitcast v13 : u32; + v151 = arith.add v149, v150 : u32 #[overflow = checked]; + v552 = arith.constant 4 : u32; + v153 = arith.mod v151, v552 : u32; + hir.assertz v153 #[code = 250]; + v154 = hir.int_to_ptr v151 : ptr; + v155 = hir.load v154 : i32; + v156 = arith.constant 1048620 : i32; + hir.exec @root_ns:root@1.0.0/adv_load_preimage/alloc::raw_vec::handle_error(v46, v155, v156) + v516 = arith.constant 0 : u32; + scf.yield v516; + } else { + ^block8: + v551 = arith.constant 12 : u32; + v60 = hir.bitcast v13 : u32; + v62 = arith.add v60, v551 : u32 #[overflow = checked]; + v550 = arith.constant 4 : u32; + v64 = arith.mod v62, v550 : u32; + hir.assertz v64 #[code = 250]; + v65 = hir.int_to_ptr v62 : ptr; + v66 = hir.load v65 : i32; + v549 = arith.constant 2 : u32; + v68 = hir.bitcast v66 : u32; + v70 = arith.shr v68, v549 : u32; + v71 = hir.bitcast v70 : i32; + v72 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/std::mem::pipe_preimage_to_memory(v31, v71, v4, v3, v2, v1) : i32 + v548 = arith.constant 8 : u32; + v73 = hir.bitcast v0 : u32; + v75 = arith.add v73, v548 : u32 #[overflow = checked]; + v547 = arith.constant 4 : u32; + v77 = arith.mod v75, v547 : u32; + hir.assertz v77 #[code = 250]; + v78 = hir.int_to_ptr v75 : ptr; + hir.store v78, v36; + v546 = arith.constant 4 : u32; + v79 = hir.bitcast v0 : u32; + v81 = arith.add v79, v546 : u32 #[overflow = checked]; + v545 = arith.constant 4 : u32; + v83 = arith.mod v81, v545 : u32; + hir.assertz v83 #[code = 250]; + v84 = hir.int_to_ptr v81 : ptr; + hir.store v84, v66; + v85 = hir.bitcast v0 : u32; + v544 = arith.constant 4 : u32; + v87 = arith.mod v85, v544 : u32; + hir.assertz v87 #[code = 250]; + v88 = hir.int_to_ptr v85 : ptr; + hir.store v88, v46; + v542 = arith.constant 0 : i32; + v543 = arith.constant 0 : i32; + v90 = arith.eq v36, v543 : i1; + v91 = arith.zext v90 : u32; + v92 = hir.bitcast v91 : i32; + v94 = arith.neq v92, v542 : i1; + v522 = scf.if v94 : u32 { + ^block77: + v541 = arith.constant 0 : u32; + scf.yield v541; + } else { + ^block9: + v95 = hir.bitcast v66 : u32; + v540 = arith.constant 4 : u32; + v97 = arith.mod v95, v540 : u32; + hir.assertz v97 #[code = 250]; + v98 = hir.int_to_ptr v95 : ptr; + v99 = hir.load v98 : felt; + v539 = arith.constant 1 : i32; + v101 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::from_u32(v539) : felt + hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::assert_eq(v99, v101) + v538 = arith.constant 4 : u32; + v102 = hir.bitcast v66 : u32; + v104 = arith.add v102, v538 : u32 #[overflow = checked]; + v537 = arith.constant 4 : u32; + v106 = arith.mod v104, v537 : u32; + hir.assertz v106 #[code = 250]; + v107 = hir.int_to_ptr v104 : ptr; + v108 = hir.load v107 : felt; + v34 = arith.constant 2 : i32; + v110 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::from_u32(v34) : felt + hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::assert_eq(v108, v110) + v536 = arith.constant 0 : i32; + v513 = arith.constant 5 : u32; + v112 = hir.bitcast v36 : u32; + v114 = arith.lte v112, v513 : i1; + v115 = arith.zext v114 : u32; + v116 = hir.bitcast v115 : i32; + v118 = arith.neq v116, v536 : i1; + v524 = scf.if v118 : u32 { + ^block76: + v535 = arith.constant 0 : u32; + scf.yield v535; + } else { + ^block10: + v120 = arith.constant 20 : u32; + v119 = hir.bitcast v66 : u32; + v121 = arith.add v119, v120 : u32 #[overflow = checked]; + v534 = arith.constant 4 : u32; + v123 = arith.mod v121, v534 : u32; + hir.assertz v123 #[code = 250]; + v124 = hir.int_to_ptr v121 : ptr; + v125 = hir.load v124 : felt; + v126 = arith.constant 6 : i32; + v127 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::from_u32(v126) : felt + hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::assert_eq(v125, v127) + v533 = arith.constant 0 : i32; + v512 = arith.constant 14 : u32; + v129 = hir.bitcast v36 : u32; + v131 = arith.lte v129, v512 : i1; + v132 = arith.zext v131 : u32; + v133 = hir.bitcast v132 : i32; + v135 = arith.neq v133, v533 : i1; + scf.if v135{ + ^block75: + scf.yield ; + } else { + ^block11: + v137 = arith.constant 56 : u32; + v136 = hir.bitcast v66 : u32; + v138 = arith.add v136, v137 : u32 #[overflow = checked]; + v532 = arith.constant 4 : u32; + v140 = arith.mod v138, v532 : u32; + hir.assertz v140 #[code = 250]; + v141 = hir.int_to_ptr v138 : ptr; + v142 = hir.load v141 : felt; + v143 = arith.constant 15 : i32; + v144 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::from_u32(v143) : felt + hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::felt::assert_eq(v142, v144) + v531 = arith.constant 16 : i32; + v146 = arith.add v13, v531 : i32 #[overflow = wrapping]; + v147 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v148 = hir.bitcast v147 : ptr; + hir.store v148, v146; + scf.yield ; + }; + v518 = arith.constant 1 : u32; + v530 = arith.constant 0 : u32; + v528 = cf.select v135, v530, v518 : u32; + scf.yield v528; + }; + scf.yield v524; + }; + scf.yield v522; + }; + v529 = arith.constant 0 : u32; + v527 = arith.eq v520, v529 : i1; + cf.cond_br v527 ^block6, ^block79; + ^block6: + ub.unreachable ; + ^block79: + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_alloc(v157: i32, v158: i32) -> i32 { + ^block12(v157: i32, v158: i32): + v160 = arith.constant 1048636 : i32; + v161 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/::alloc(v160, v158, v157) : i32 + builtin.ret v161; + }; + + private builtin.function @__rustc::__rust_alloc_zeroed(v162: i32, v163: i32) -> i32 { + ^block14(v162: i32, v163: i32): + v165 = arith.constant 1048636 : i32; + v166 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/::alloc(v165, v163, v162) : i32 + v566 = arith.constant 0 : i32; + v167 = arith.constant 0 : i32; + v168 = arith.eq v166, v167 : i1; + v169 = arith.zext v168 : u32; + v170 = hir.bitcast v169 : i32; + v172 = arith.neq v170, v566 : i1; + scf.if v172{ + ^block16: + scf.yield ; + } else { + ^block17: + v564 = arith.constant 0 : i32; + v565 = arith.constant 0 : i32; + v174 = arith.eq v162, v565 : i1; + v175 = arith.zext v174 : u32; + v176 = hir.bitcast v175 : i32; + v178 = arith.neq v176, v564 : i1; + scf.if v178{ + ^block84: + scf.yield ; + } else { + ^block18: + v558 = arith.constant 0 : u8; + v181 = hir.bitcast v162 : u32; + v182 = hir.bitcast v166 : u32; + v183 = hir.int_to_ptr v182 : ptr; + hir.mem_set v183, v181, v558; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v166; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block19: + builtin.ret ; + }; + + private builtin.function @::alloc(v185: i32, v186: i32, v187: i32) -> i32 { + ^block21(v185: i32, v186: i32, v187: i32): + v190 = arith.constant 16 : i32; + v189 = arith.constant 0 : i32; + v568 = arith.constant 16 : u32; + v192 = hir.bitcast v186 : u32; + v194 = arith.gt v192, v568 : i1; + v195 = arith.zext v194 : u32; + v196 = hir.bitcast v195 : i32; + v198 = arith.neq v196, v189 : i1; + v199 = cf.select v198, v186, v190 : i32; + v608 = arith.constant 0 : i32; + v200 = arith.constant -1 : i32; + v201 = arith.add v199, v200 : i32 #[overflow = wrapping]; + v202 = arith.band v199, v201 : i32; + v204 = arith.neq v202, v608 : i1; + v577, v578 = scf.if v204 : i32, u32 { + ^block89: + v569 = arith.constant 0 : u32; + v573 = ub.poison i32 : i32; + scf.yield v573, v569; + } else { + ^block24: + v206 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/core::ptr::alignment::Alignment::max(v186, v199) : i32 + v607 = arith.constant 0 : i32; + v205 = arith.constant -2147483648 : i32; + v207 = arith.sub v205, v206 : i32 #[overflow = wrapping]; + v209 = hir.bitcast v207 : u32; + v208 = hir.bitcast v187 : u32; + v210 = arith.gt v208, v209 : i1; + v211 = arith.zext v210 : u32; + v212 = hir.bitcast v211 : i32; + v214 = arith.neq v212, v607 : i1; + v592 = scf.if v214 : i32 { + ^block88: + v606 = ub.poison i32 : i32; + scf.yield v606; + } else { + ^block25: + v604 = arith.constant 0 : i32; + v220 = arith.sub v604, v206 : i32 #[overflow = wrapping]; + v605 = arith.constant -1 : i32; + v216 = arith.add v187, v206 : i32 #[overflow = wrapping]; + v218 = arith.add v216, v605 : i32 #[overflow = wrapping]; + v221 = arith.band v218, v220 : i32; + v222 = hir.bitcast v185 : u32; + v223 = arith.constant 4 : u32; + v224 = arith.mod v222, v223 : u32; + hir.assertz v224 #[code = 250]; + v225 = hir.int_to_ptr v222 : ptr; + v226 = hir.load v225 : i32; + v603 = arith.constant 0 : i32; + v228 = arith.neq v226, v603 : i1; + scf.if v228{ + ^block87: + scf.yield ; + } else { + ^block27: + v229 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/intrinsics::mem::heap_base() : i32 + v230 = hir.mem_size : u32; + v236 = hir.bitcast v185 : u32; + v602 = arith.constant 4 : u32; + v238 = arith.mod v236, v602 : u32; + hir.assertz v238 #[code = 250]; + v601 = arith.constant 16 : u32; + v231 = hir.bitcast v230 : i32; + v234 = arith.shl v231, v601 : i32; + v235 = arith.add v229, v234 : i32 #[overflow = wrapping]; + v239 = hir.int_to_ptr v236 : ptr; + hir.store v239, v235; + scf.yield ; + }; + v242 = hir.bitcast v185 : u32; + v600 = arith.constant 4 : u32; + v244 = arith.mod v242, v600 : u32; + hir.assertz v244 #[code = 250]; + v245 = hir.int_to_ptr v242 : ptr; + v246 = hir.load v245 : i32; + v598 = arith.constant 0 : i32; + v599 = arith.constant -1 : i32; + v248 = arith.bxor v246, v599 : i32; + v250 = hir.bitcast v248 : u32; + v249 = hir.bitcast v221 : u32; + v251 = arith.gt v249, v250 : i1; + v252 = arith.zext v251 : u32; + v253 = hir.bitcast v252 : i32; + v255 = arith.neq v253, v598 : i1; + v591 = scf.if v255 : i32 { + ^block28: + v597 = arith.constant 0 : i32; + scf.yield v597; + } else { + ^block29: + v257 = hir.bitcast v185 : u32; + v596 = arith.constant 4 : u32; + v259 = arith.mod v257, v596 : u32; + hir.assertz v259 #[code = 250]; + v256 = arith.add v246, v221 : i32 #[overflow = wrapping]; + v260 = hir.int_to_ptr v257 : ptr; + hir.store v260, v256; + v262 = arith.add v246, v206 : i32 #[overflow = wrapping]; + scf.yield v262; + }; + scf.yield v591; + }; + v574 = arith.constant 1 : u32; + v595 = arith.constant 0 : u32; + v593 = cf.select v214, v595, v574 : u32; + scf.yield v592, v593; + }; + v594 = arith.constant 0 : u32; + v590 = arith.eq v578, v594 : i1; + cf.cond_br v590 ^block23, ^block91(v577); + ^block23: + ub.unreachable ; + ^block91(v570: i32): + builtin.ret v570; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block30: + v265 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v265; + }; + + private builtin.function @intrinsics::felt::from_u64_unchecked(v267: i64) -> felt { + ^block34(v267: i64): + v268 = hir.cast v267 : felt; + builtin.ret v268; + }; + + private builtin.function @intrinsics::felt::from_u32(v270: i32) -> felt { + ^block36(v270: i32): + v271 = hir.bitcast v270 : felt; + builtin.ret v271; + }; + + private builtin.function @intrinsics::felt::as_u64(v273: felt) -> i64 { + ^block38(v273: felt): + v274 = hir.cast v273 : i64; + builtin.ret v274; + }; + + private builtin.function @intrinsics::felt::assert_eq(v276: felt, v277: felt) { + ^block40(v276: felt, v277: felt): + hir.assert_eq v276, v277; + builtin.ret ; + }; + + private builtin.function @intrinsics::advice::adv_push_mapvaln(v278: felt, v279: felt, v280: felt, v281: felt) -> felt { + ^block42(v278: felt, v279: felt, v280: felt, v281: felt): + v282 = hir.exec @intrinsics/advice/adv_push_mapvaln(v278, v279, v280, v281) : felt + builtin.ret v282; + }; + + private builtin.function @std::mem::pipe_preimage_to_memory(v284: felt, v285: i32, v286: felt, v287: felt, v288: felt, v289: felt) -> i32 { + ^block45(v284: felt, v285: i32, v286: felt, v287: felt, v288: felt, v289: felt): + v290 = hir.exec @std/mem/pipe_preimage_to_memory(v284, v285, v286, v287, v288, v289) : i32 + builtin.ret v290; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::try_allocate_in(v292: i32, v293: i32, v294: i32, v295: i32, v296: i32) { + ^block49(v292: i32, v293: i32, v294: i32, v295: i32, v296: i32): + v299 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v300 = hir.bitcast v299 : ptr; + v301 = hir.load v300 : i32; + v302 = arith.constant 16 : i32; + v303 = arith.sub v301, v302 : i32 #[overflow = wrapping]; + v304 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v305 = hir.bitcast v304 : ptr; + hir.store v305, v303; + v315 = hir.bitcast v293 : u32; + v316 = arith.zext v315 : u64; + v317 = hir.bitcast v316 : i64; + v297 = arith.constant 0 : i32; + v310 = arith.sub v297, v295 : i32 #[overflow = wrapping]; + v307 = arith.constant -1 : i32; + v306 = arith.add v295, v296 : i32 #[overflow = wrapping]; + v308 = arith.add v306, v307 : i32 #[overflow = wrapping]; + v311 = arith.band v308, v310 : i32; + v312 = hir.bitcast v311 : u32; + v313 = arith.zext v312 : u64; + v314 = hir.bitcast v313 : i64; + v318 = arith.mul v314, v317 : i64 #[overflow = wrapping]; + v712 = arith.constant 0 : i32; + v319 = arith.constant 32 : i64; + v321 = hir.cast v319 : u32; + v320 = hir.bitcast v318 : u64; + v322 = arith.shr v320, v321 : u64; + v323 = hir.bitcast v322 : i64; + v324 = arith.trunc v323 : i32; + v326 = arith.neq v324, v712 : i1; + v624, v625, v626, v627, v628, v629 = scf.if v326 : i32, i32, i32, i32, i32, u32 { + ^block95: + v609 = arith.constant 0 : u32; + v616 = ub.poison i32 : i32; + scf.yield v292, v303, v616, v616, v616, v609; + } else { + ^block54: + v327 = arith.trunc v318 : i32; + v711 = arith.constant 0 : i32; + v328 = arith.constant -2147483648 : i32; + v329 = arith.sub v328, v295 : i32 #[overflow = wrapping]; + v331 = hir.bitcast v329 : u32; + v330 = hir.bitcast v327 : u32; + v332 = arith.lte v330, v331 : i1; + v333 = arith.zext v332 : u32; + v334 = hir.bitcast v333 : i32; + v336 = arith.neq v334, v711 : i1; + v672 = scf.if v336 : i32 { + ^block52: + v710 = arith.constant 0 : i32; + v347 = arith.neq v327, v710 : i1; + v671 = scf.if v347 : i32 { + ^block56: + v709 = arith.constant 0 : i32; + v363 = arith.neq v294, v709 : i1; + v670 = scf.if v363 : i32 { + ^block59: + v345 = arith.constant 1 : i32; + hir.exec @root_ns:root@1.0.0/adv_load_preimage/alloc::alloc::Global::alloc_impl(v303, v295, v327, v345) + v374 = hir.bitcast v303 : u32; + v419 = arith.constant 4 : u32; + v376 = arith.mod v374, v419 : u32; + hir.assertz v376 #[code = 250]; + v377 = hir.int_to_ptr v374 : ptr; + v378 = hir.load v377 : i32; + scf.yield v378; + } else { + ^block60: + v364 = arith.constant 8 : i32; + v365 = arith.add v303, v364 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/adv_load_preimage/::allocate(v365, v295, v327) + v349 = arith.constant 8 : u32; + v366 = hir.bitcast v303 : u32; + v368 = arith.add v366, v349 : u32 #[overflow = checked]; + v708 = arith.constant 4 : u32; + v370 = arith.mod v368, v708 : u32; + hir.assertz v370 #[code = 250]; + v371 = hir.int_to_ptr v368 : ptr; + v372 = hir.load v371 : i32; + scf.yield v372; + }; + v706 = arith.constant 0 : i32; + v707 = arith.constant 0 : i32; + v381 = arith.eq v670, v707 : i1; + v382 = arith.zext v381 : u32; + v383 = hir.bitcast v382 : i32; + v385 = arith.neq v383, v706 : i1; + scf.if v385{ + ^block61: + v705 = arith.constant 8 : u32; + v402 = hir.bitcast v292 : u32; + v404 = arith.add v402, v705 : u32 #[overflow = checked]; + v704 = arith.constant 4 : u32; + v406 = arith.mod v404, v704 : u32; + hir.assertz v406 #[code = 250]; + v407 = hir.int_to_ptr v404 : ptr; + hir.store v407, v327; + v703 = arith.constant 4 : u32; + v409 = hir.bitcast v292 : u32; + v411 = arith.add v409, v703 : u32 #[overflow = checked]; + v702 = arith.constant 4 : u32; + v413 = arith.mod v411, v702 : u32; + hir.assertz v413 #[code = 250]; + v414 = hir.int_to_ptr v411 : ptr; + hir.store v414, v295; + scf.yield ; + } else { + ^block62: + v701 = arith.constant 8 : u32; + v387 = hir.bitcast v292 : u32; + v389 = arith.add v387, v701 : u32 #[overflow = checked]; + v700 = arith.constant 4 : u32; + v391 = arith.mod v389, v700 : u32; + hir.assertz v391 #[code = 250]; + v392 = hir.int_to_ptr v389 : ptr; + hir.store v392, v670; + v699 = arith.constant 4 : u32; + v394 = hir.bitcast v292 : u32; + v396 = arith.add v394, v699 : u32 #[overflow = checked]; + v698 = arith.constant 4 : u32; + v398 = arith.mod v396, v698 : u32; + hir.assertz v398 #[code = 250]; + v399 = hir.int_to_ptr v396 : ptr; + hir.store v399, v293; + scf.yield ; + }; + v696 = arith.constant 0 : i32; + v697 = arith.constant 1 : i32; + v669 = cf.select v385, v697, v696 : i32; + scf.yield v669; + } else { + ^block57: + v695 = arith.constant 8 : u32; + v348 = hir.bitcast v292 : u32; + v350 = arith.add v348, v695 : u32 #[overflow = checked]; + v694 = arith.constant 4 : u32; + v352 = arith.mod v350, v694 : u32; + hir.assertz v352 #[code = 250]; + v353 = hir.int_to_ptr v350 : ptr; + hir.store v353, v295; + v693 = arith.constant 4 : u32; + v356 = hir.bitcast v292 : u32; + v358 = arith.add v356, v693 : u32 #[overflow = checked]; + v692 = arith.constant 4 : u32; + v360 = arith.mod v358, v692 : u32; + hir.assertz v360 #[code = 250]; + v691 = arith.constant 0 : i32; + v361 = hir.int_to_ptr v358 : ptr; + hir.store v361, v691; + v690 = arith.constant 0 : i32; + scf.yield v690; + }; + scf.yield v671; + } else { + ^block55: + v689 = ub.poison i32 : i32; + scf.yield v689; + }; + v684 = arith.constant 0 : u32; + v617 = arith.constant 1 : u32; + v677 = cf.select v336, v617, v684 : u32; + v685 = ub.poison i32 : i32; + v676 = cf.select v336, v303, v685 : i32; + v686 = ub.poison i32 : i32; + v675 = cf.select v336, v292, v686 : i32; + v687 = ub.poison i32 : i32; + v674 = cf.select v336, v687, v303 : i32; + v688 = ub.poison i32 : i32; + v673 = cf.select v336, v688, v292 : i32; + scf.yield v673, v674, v675, v672, v676, v677; + }; + v630, v631, v632 = scf.index_switch v629 : i32, i32, i32 + case 0 { + ^block53: + v683 = arith.constant 4 : u32; + v339 = hir.bitcast v624 : u32; + v341 = arith.add v339, v683 : u32 #[overflow = checked]; + v682 = arith.constant 4 : u32; + v343 = arith.mod v341, v682 : u32; + hir.assertz v343 #[code = 250]; + v681 = arith.constant 0 : i32; + v344 = hir.int_to_ptr v341 : ptr; + hir.store v344, v681; + v680 = arith.constant 1 : i32; + scf.yield v624, v680, v625; + } + default { + ^block99: + scf.yield v626, v627, v628; + }; + v418 = hir.bitcast v630 : u32; + v679 = arith.constant 4 : u32; + v420 = arith.mod v418, v679 : u32; + hir.assertz v420 #[code = 250]; + v421 = hir.int_to_ptr v418 : ptr; + hir.store v421, v631; + v678 = arith.constant 16 : i32; + v426 = arith.add v632, v678 : i32 #[overflow = wrapping]; + v427 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v428 = hir.bitcast v427 : ptr; + hir.store v428, v426; + builtin.ret ; + }; + + private builtin.function @::allocate(v429: i32, v430: i32, v431: i32) { + ^block63(v429: i32, v430: i32, v431: i32): + v433 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v434 = hir.bitcast v433 : ptr; + v435 = hir.load v434 : i32; + v436 = arith.constant 16 : i32; + v437 = arith.sub v435, v436 : i32 #[overflow = wrapping]; + v438 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v439 = hir.bitcast v438 : ptr; + hir.store v439, v437; + v432 = arith.constant 0 : i32; + v440 = arith.constant 8 : i32; + v441 = arith.add v437, v440 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/adv_load_preimage/alloc::alloc::Global::alloc_impl(v441, v430, v431, v432) + v444 = arith.constant 12 : u32; + v443 = hir.bitcast v437 : u32; + v445 = arith.add v443, v444 : u32 #[overflow = checked]; + v446 = arith.constant 4 : u32; + v447 = arith.mod v445, v446 : u32; + hir.assertz v447 #[code = 250]; + v448 = hir.int_to_ptr v445 : ptr; + v449 = hir.load v448 : i32; + v451 = arith.constant 8 : u32; + v450 = hir.bitcast v437 : u32; + v452 = arith.add v450, v451 : u32 #[overflow = checked]; + v717 = arith.constant 4 : u32; + v454 = arith.mod v452, v717 : u32; + hir.assertz v454 #[code = 250]; + v455 = hir.int_to_ptr v452 : ptr; + v456 = hir.load v455 : i32; + v457 = hir.bitcast v429 : u32; + v716 = arith.constant 4 : u32; + v459 = arith.mod v457, v716 : u32; + hir.assertz v459 #[code = 250]; + v460 = hir.int_to_ptr v457 : ptr; + hir.store v460, v456; + v715 = arith.constant 4 : u32; + v461 = hir.bitcast v429 : u32; + v463 = arith.add v461, v715 : u32 #[overflow = checked]; + v714 = arith.constant 4 : u32; + v465 = arith.mod v463, v714 : u32; + hir.assertz v465 #[code = 250]; + v466 = hir.int_to_ptr v463 : ptr; + hir.store v466, v449; + v713 = arith.constant 16 : i32; + v468 = arith.add v437, v713 : i32 #[overflow = wrapping]; + v469 = builtin.global_symbol @root_ns:root@1.0.0/adv_load_preimage/__stack_pointer : ptr + v470 = hir.bitcast v469 : ptr; + hir.store v470, v468; + builtin.ret ; + }; + + private builtin.function @alloc::alloc::Global::alloc_impl(v471: i32, v472: i32, v473: i32, v474: i32) { + ^block65(v471: i32, v472: i32, v473: i32, v474: i32): + v733 = arith.constant 0 : i32; + v475 = arith.constant 0 : i32; + v476 = arith.eq v473, v475 : i1; + v477 = arith.zext v476 : u32; + v478 = hir.bitcast v477 : i32; + v480 = arith.neq v478, v733 : i1; + v729 = scf.if v480 : i32 { + ^block102: + scf.yield v472; + } else { + ^block68: + hir.exec @root_ns:root@1.0.0/adv_load_preimage/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v732 = arith.constant 0 : i32; + v482 = arith.neq v474, v732 : i1; + v728 = scf.if v482 : i32 { + ^block69: + v484 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/__rustc::__rust_alloc_zeroed(v473, v472) : i32 + scf.yield v484; + } else { + ^block70: + v483 = hir.exec @root_ns:root@1.0.0/adv_load_preimage/__rustc::__rust_alloc(v473, v472) : i32 + scf.yield v483; + }; + scf.yield v728; + }; + v488 = arith.constant 4 : u32; + v487 = hir.bitcast v471 : u32; + v489 = arith.add v487, v488 : u32 #[overflow = checked]; + v731 = arith.constant 4 : u32; + v491 = arith.mod v489, v731 : u32; + hir.assertz v491 #[code = 250]; + v492 = hir.int_to_ptr v489 : ptr; + hir.store v492, v473; + v494 = hir.bitcast v471 : u32; + v730 = arith.constant 4 : u32; + v496 = arith.mod v494, v730 : u32; + hir.assertz v496 #[code = 250]; + v497 = hir.int_to_ptr v494 : ptr; + hir.store v497, v729; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::handle_error(v498: i32, v499: i32, v500: i32) { + ^block71(v498: i32, v499: i32, v500: i32): + ub.unreachable ; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v501: i32, v502: i32) -> i32 { + ^block73(v501: i32, v502: i32): + v509 = arith.constant 0 : i32; + v505 = hir.bitcast v502 : u32; + v504 = hir.bitcast v501 : u32; + v506 = arith.gt v504, v505 : i1; + v507 = arith.zext v506 : u32; + v508 = hir.bitcast v507 : i32; + v510 = arith.neq v508, v509 : i1; + v511 = cf.select v510, v501, v502 : i32; + builtin.ret v511; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.segment readonly @1048576 = 0x000000210000009700000028001000000000000073722e6d656d2f62696c6474732f6372732f302e372e302d7379732d62696c6474732d6e6564696d; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/adv_load_preimage.masm b/tests/integration/expected/adv_load_preimage.masm new file mode 100644 index 000000000..d951d7085 --- /dev/null +++ b/tests/integration/expected/adv_load_preimage.masm @@ -0,0 +1,1403 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[16950219637129648594,11360386184375194274,15319753906541904406,1352422243874904212] + adv.push_mapval + push.262144 + push.4 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278544 +end + +# mod root_ns:root@1.0.0::adv_load_preimage + +export.entrypoint + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.2 + dup.4 + dup.6 + dup.8 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::advice::adv_push_mapvaln + trace.252 + nop + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::as_u64 + trace.252 + nop + push.3 + dup.2 + dup.2 + drop + u32and + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::assert_eq + trace.252 + nop + push.2 + push.0 + dup.0 + push.2147483648 + u32and + eq.2147483648 + assertz + assertz + dup.0 + push.4294967295 + u32lte + assert + movdn.2 + movup.2 + trace.240 + nop + exec.::std::math::u64::shr + trace.252 + nop + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::from_u64_unchecked + trace.252 + nop + dup.0 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::as_u64 + trace.252 + nop + push.2 + movdn.2 + drop + swap.1 + u32shl + push.4 + push.0 + push.4 + dup.5 + u32wrapping_add + dup.3 + dup.3 + swap.4 + swap.3 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::alloc::raw_vec::RawVecInner::try_allocate_in + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.1 + movup.2 + eq + neq + if.true + movdn.2 + drop + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.12 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048620 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::alloc::raw_vec::handle_error + trace.252 + nop + push.0 + else + push.12 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.2 + dup.1 + swap.1 + u32shr + movup.5 + swap.7 + movdn.5 + movup.3 + swap.9 + movdn.3 + movup.2 + swap.10 + movdn.2 + swap.1 + swap.8 + swap.4 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::std::mem::pipe_preimage_to_memory + trace.252 + nop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.0 + dup.4 + eq + neq + if.true + drop + drop + drop + push.0 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::assert_eq + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::assert_eq + trace.252 + nop + push.0 + push.5 + dup.4 + swap.1 + u32lte + neq + if.true + drop + drop + drop + push.0 + else + push.20 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.6 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::assert_eq + trace.252 + nop + push.0 + push.14 + movup.4 + swap.1 + u32lte + neq + dup.0 + if.true + movdn.2 + drop + drop + else + push.56 + movup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.15 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::felt::assert_eq + trace.252 + nop + push.16 + movup.2 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.1 + push.0 + movup.2 + cdrop + end + end + end + push.0 + eq + if.true + push.0 + assert + else + nop + end +end + +proc.__rustc::__rust_alloc + push.1048636 + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::::alloc + trace.252 + nop +end + +proc.__rustc::__rust_alloc_zeroed + push.1048636 + dup.1 + swap.2 + swap.3 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::::alloc + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + swap.1 + drop + else + push.0 + push.0 + dup.3 + eq + neq + if.true + swap.1 + drop + else + push.0 + movup.2 + dup.2 + push.0 + dup.2 + push.0 + gte + while.true + dup.1 + dup.1 + push.1 + u32overflowing_madd + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + u32wrapping_add.1 + dup.0 + dup.3 + u32gte + end + dropw + end + end +end + +proc.__rustc::__rust_no_alloc_shim_is_unstable_v2 + nop +end + +proc.::alloc + push.16 + push.0 + push.16 + dup.4 + swap.1 + u32gt + neq + dup.3 + swap.1 + cdrop + push.0 + push.4294967295 + dup.2 + u32wrapping_add + dup.2 + u32and + neq + if.true + dropw + push.0 + push.3735929054 + else + movup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::core::ptr::alignment::Alignment::max + trace.252 + nop + push.0 + push.2147483648 + dup.2 + u32wrapping_sub + dup.4 + swap.1 + u32gt + neq + dup.0 + if.true + movdn.3 + drop + drop + drop + push.3735929054 + else + push.0 + dup.2 + u32wrapping_sub + push.4294967295 + movup.5 + dup.4 + u32wrapping_add + u32wrapping_add + u32and + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + neq + if.true + nop + else + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::intrinsics::mem::heap_base + trace.252 + nop + trace.240 + nop + exec.::intrinsics::mem::memory_size + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.16 + movup.2 + swap.1 + u32shl + movup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.4294967295 + dup.2 + u32xor + dup.3 + swap.1 + u32gt + neq + if.true + drop + drop + movdn.2 + drop + drop + push.0 + else + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + dup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + u32wrapping_add + end + end + push.1 + push.0 + movup.3 + cdrop + swap.1 + end + push.0 + movup.2 + eq + if.true + drop + push.0 + assert + else + nop + end +end + +proc.intrinsics::mem::heap_base + trace.240 + nop + exec.::intrinsics::mem::heap_base + trace.252 + nop +end + +proc.intrinsics::felt::from_u64_unchecked + dup.1 + dup.1 + push.1 + push.4294967295 + trace.240 + nop + exec.::std::math::u64::lt + trace.252 + nop + assert + mul.4294967296 + add +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::as_u64 + u32split +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + +proc.intrinsics::advice::adv_push_mapvaln + trace.240 + nop + exec.::intrinsics::advice::adv_push_mapvaln + trace.252 + nop +end + +proc.std::mem::pipe_preimage_to_memory + trace.240 + nop + exec.::std::mem::pipe_preimage_to_memory + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::try_allocate_in + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.2 + push.0 + push.0 + dup.7 + u32wrapping_sub + push.4294967295 + movup.9 + dup.9 + u32wrapping_add + u32wrapping_add + u32and + push.0 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + push.0 + push.32 + push.0 + dup.0 + push.2147483648 + u32and + eq.2147483648 + assertz + assertz + dup.0 + push.4294967295 + u32lte + assert + dup.3 + dup.3 + movup.2 + trace.240 + nop + exec.::std::math::u64::shr + trace.252 + nop + drop + neq + if.true + drop + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + push.3735929054 + dup.0 + dup.1 + swap.4 + swap.1 + swap.3 + swap.5 + else + drop + push.0 + push.2147483648 + dup.7 + u32wrapping_sub + dup.2 + swap.1 + u32lte + neq + dup.0 + if.true + push.0 + dup.2 + neq + if.true + push.0 + movup.6 + neq + if.true + push.1 + dup.3 + dup.7 + dup.4 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::alloc::alloc::Global::alloc_impl + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + else + push.8 + dup.3 + u32wrapping_add + dup.6 + dup.3 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::::allocate + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + end + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + movup.6 + movup.2 + drop + drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + movup.7 + movup.4 + drop + drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.0 + push.1 + movup.2 + cdrop + else + movup.2 + swap.5 + movdn.2 + swap.4 + swap.1 + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + swap.1 + swap.3 + swap.2 + swap.1 + end + else + swap.1 + drop + movup.3 + drop + movup.3 + drop + movup.3 + drop + push.3735929054 + end + push.0 + push.1 + dup.3 + cdrop + push.3735929054 + dup.3 + dup.5 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.7 + swap.1 + cdrop + push.3735929054 + dup.5 + movup.2 + swap.7 + movdn.2 + cdrop + push.3735929054 + movup.2 + swap.7 + movdn.2 + swap.1 + swap.5 + cdrop + swap.1 + swap.5 + swap.4 + swap.2 + swap.3 + swap.1 + end + movup.5 + eq.0 + if.true + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1 + swap.1 + else + drop + drop + end + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::allocate + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114176 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.8 + dup.2 + u32wrapping_add + movup.2 + swap.5 + movdn.2 + swap.1 + swap.3 + swap.4 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::alloc::alloc::Global::alloc_impl + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114176 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::alloc::Global::alloc_impl + push.0 + push.0 + dup.4 + eq + neq + if.true + movup.3 + drop + swap.1 + else + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::__rustc::__rust_no_alloc_shim_is_unstable_v2 + trace.252 + nop + push.0 + movup.4 + neq + if.true + swap.1 + dup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::__rustc::__rust_alloc_zeroed + trace.252 + nop + else + swap.1 + dup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::adv_load_preimage::__rustc::__rust_alloc + trace.252 + nop + end + end + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::handle_error + drop + drop + drop + push.0 + assert +end + +proc.core::ptr::alignment::Alignment::max + push.0 + dup.2 + dup.2 + swap.1 + u32gt + neq + cdrop +end + diff --git a/tests/integration/expected/adv_load_preimage.wat b/tests/integration/expected/adv_load_preimage.wat new file mode 100644 index 000000000..458452cf0 --- /dev/null +++ b/tests/integration/expected/adv_load_preimage.wat @@ -0,0 +1,436 @@ +(module $adv_load_preimage.wasm + (type (;0;) (func (param i32 f32 f32 f32 f32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func)) + (type (;3;) (func (param i32 i32 i32) (result i32))) + (type (;4;) (func (result i32))) + (type (;5;) (func (param i64) (result f32))) + (type (;6;) (func (param i32) (result f32))) + (type (;7;) (func (param f32) (result i64))) + (type (;8;) (func (param f32 f32))) + (type (;9;) (func (param f32 f32 f32 f32) (result f32))) + (type (;10;) (func (param f32 i32 f32 f32 f32 f32) (result i32))) + (type (;11;) (func (param i32 i32 i32 i32 i32))) + (type (;12;) (func (param i32 i32 i32))) + (type (;13;) (func (param i32 i32 i32 i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32 f32 f32 f32 f32) + (local i32 i64 f32 i32 i32 i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + local.get 4 + local.get 3 + local.get 2 + local.get 1 + call $intrinsics::advice::adv_push_mapvaln + call $intrinsics::felt::as_u64 + local.tee 6 + i32.wrap_i64 + i32.const 3 + i32.and + call $intrinsics::felt::from_u32 + i32.const 0 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + i32.const 4 + i32.add + local.get 6 + i64.const 2 + i64.shr_u + call $intrinsics::felt::from_u64_unchecked + local.tee 7 + call $intrinsics::felt::as_u64 + i32.wrap_i64 + i32.const 2 + i32.shl + local.tee 8 + i32.const 0 + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::try_allocate_in + local.get 5 + i32.load offset=8 + local.set 9 + block ;; label = @1 + block ;; label = @2 + local.get 5 + i32.load offset=4 + i32.const 1 + i32.eq + br_if 0 (;@2;) + local.get 7 + local.get 5 + i32.load offset=12 + local.tee 10 + i32.const 2 + i32.shr_u + local.get 4 + local.get 3 + local.get 2 + local.get 1 + call $std::mem::pipe_preimage_to_memory + drop + local.get 0 + local.get 8 + i32.store offset=8 + local.get 0 + local.get 10 + i32.store offset=4 + local.get 0 + local.get 9 + i32.store + local.get 8 + i32.eqz + br_if 1 (;@1;) + local.get 10 + f32.load + i32.const 1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 10 + f32.load offset=4 + i32.const 2 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 8 + i32.const 5 + i32.le_u + br_if 1 (;@1;) + local.get 10 + f32.load offset=20 + i32.const 6 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 8 + i32.const 14 + i32.le_u + br_if 1 (;@1;) + local.get 10 + f32.load offset=56 + i32.const 15 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + return + end + local.get 9 + local.get 5 + i32.load offset=12 + i32.const 1048620 + call $alloc::raw_vec::handle_error + end + unreachable + ) + (func $__rustc::__rust_alloc (;1;) (type 1) (param i32 i32) (result i32) + i32.const 1048636 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_alloc_zeroed (;2;) (type 1) (param i32 i32) (result i32) + block ;; label = @1 + i32.const 1048636 + local.get 1 + local.get 0 + call $::alloc + local.tee 1 + i32.eqz + br_if 0 (;@1;) + local.get 0 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.const 0 + local.get 0 + memory.fill + end + local.get 1 + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;3;) (type 2) + return + ) + (func $::alloc (;4;) (type 3) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;5;) (type 4) (result i32) + unreachable + ) + (func $intrinsics::felt::from_u64_unchecked (;6;) (type 5) (param i64) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;7;) (type 6) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::as_u64 (;8;) (type 7) (param f32) (result i64) + unreachable + ) + (func $intrinsics::felt::assert_eq (;9;) (type 8) (param f32 f32) + unreachable + ) + (func $intrinsics::advice::adv_push_mapvaln (;10;) (type 9) (param f32 f32 f32 f32) (result f32) + unreachable + ) + (func $std::mem::pipe_preimage_to_memory (;11;) (type 10) (param f32 i32 f32 f32 f32 f32) (result i32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::try_allocate_in (;12;) (type 11) (param i32 i32 i32 i32 i32) + (local i32 i64) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 3 + local.get 4 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 3 + i32.sub + i32.and + i64.extend_i32_u + local.get 1 + i64.extend_i32_u + i64.mul + local.tee 6 + i64.const 32 + i64.shr_u + i32.wrap_i64 + br_if 0 (;@3;) + local.get 6 + i32.wrap_i64 + local.tee 4 + i32.const -2147483648 + local.get 3 + i32.sub + i32.le_u + br_if 1 (;@2;) + end + local.get 0 + i32.const 0 + i32.store offset=4 + i32.const 1 + local.set 3 + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 + br_if 0 (;@2;) + local.get 0 + local.get 3 + i32.store offset=8 + i32.const 0 + local.set 3 + local.get 0 + i32.const 0 + i32.store offset=4 + br 1 (;@1;) + end + block ;; label = @2 + block ;; label = @3 + local.get 2 + br_if 0 (;@3;) + local.get 5 + i32.const 8 + i32.add + local.get 3 + local.get 4 + call $::allocate + local.get 5 + i32.load offset=8 + local.set 2 + br 1 (;@2;) + end + local.get 5 + local.get 3 + local.get 4 + i32.const 1 + call $alloc::alloc::Global::alloc_impl + local.get 5 + i32.load + local.set 2 + end + block ;; label = @2 + local.get 2 + i32.eqz + br_if 0 (;@2;) + local.get 0 + local.get 2 + i32.store offset=8 + local.get 0 + local.get 1 + i32.store offset=4 + i32.const 0 + local.set 3 + br 1 (;@1;) + end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + i32.const 1 + local.set 3 + end + local.get 0 + local.get 3 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::allocate (;13;) (type 12) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 2 + i32.const 0 + call $alloc::alloc::Global::alloc_impl + local.get 3 + i32.load offset=12 + local.set 2 + local.get 0 + local.get 3 + i32.load offset=8 + i32.store + local.get 0 + local.get 2 + i32.store offset=4 + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::alloc::Global::alloc_impl (;14;) (type 13) (param i32 i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + local.get 2 + local.get 1 + call $__rustc::__rust_alloc + local.set 1 + br 1 (;@1;) + end + local.get 2 + local.get 1 + call $__rustc::__rust_alloc_zeroed + local.set 1 + end + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.store + ) + (func $alloc::raw_vec::handle_error (;15;) (type 12) (param i32 i32 i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;16;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (data $.rodata (;0;) (i32.const 1048576) "miden-stdlib-sys-0.7.0/src/stdlib/mem.rs\00\00\00\00\00\00\10\00(\00\00\00\97\00\00\00!\00\00\00") +) diff --git a/tests/integration/expected/and_bool.hir b/tests/integration/expected/and_bool.hir index 3b8d3a599..be90eebfc 100644 --- a/tests/integration/expected/and_bool.hir +++ b/tests/integration/expected/and_bool.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_7ce7e9f0710d8cd1e27fed4812e6abf7f7325fdb538e7781120494f2bd2a9cab - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_f19303d702f807a0f2b8aebdfd8b2b6ae52ee927040711d5dfd94aac9e67f2c7 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.band v0, v1 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (band v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/and_bool.masm b/tests/integration/expected/and_bool.masm index 37c06e60b..1c574e596 100644 --- a/tests/integration/expected/and_bool.masm +++ b/tests/integration/expected/and_bool.masm @@ -1,7 +1,24 @@ -# mod test_rust_7ce7e9f0710d8cd1e27fed4812e6abf7f7325fdb538e7781120494f2bd2a9cab +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32and +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_f19303d702f807a0f2b8aebdfd8b2b6ae52ee927040711d5dfd94aac9e67f2c7 + +export.entrypoint + u32and +end diff --git a/tests/integration/expected/and_bool.wat b/tests/integration/expected/and_bool.wat index 7340f6e24..13362ba0c 100644 --- a/tests/integration/expected/and_bool.wat +++ b/tests/integration/expected/and_bool.wat @@ -1,10 +1,5 @@ -(module $test_rust_7ce7e9f0710d8cd1e27fed4812e6abf7f7325fdb538e7781120494f2bd2a9cab.wasm +(module $test_rust_f19303d702f807a0f2b8aebdfd8b2b6ae52ee927040711d5dfd94aac9e67f2c7.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.and + ) +) diff --git a/tests/integration/expected/arg_order.hir b/tests/integration/expected/arg_order.hir new file mode 100644 index 000000000..7a0205afa --- /dev/null +++ b/tests/integration/expected/arg_order.hir @@ -0,0 +1,117 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @arg_order { + public builtin.function @entrypoint(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt) -> felt { + ^block4(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt): + v10 = builtin.global_symbol @root_ns:root@1.0.0/arg_order/__stack_pointer : ptr + v11 = hir.bitcast v10 : ptr; + v12 = hir.load v11 : i32; + v15 = arith.constant -32 : i32; + v13 = arith.constant 96 : i32; + v14 = arith.sub v12, v13 : i32 #[overflow = wrapping]; + v16 = arith.band v14, v15 : i32; + v17 = builtin.global_symbol @root_ns:root@1.0.0/arg_order/__stack_pointer : ptr + v18 = hir.bitcast v17 : ptr; + hir.store v18, v16; + v20 = arith.constant 44 : u32; + v19 = hir.bitcast v16 : u32; + v21 = arith.add v19, v20 : u32 #[overflow = checked]; + v22 = arith.constant 4 : u32; + v23 = arith.mod v21, v22 : u32; + hir.assertz v23 #[code = 250]; + v24 = hir.int_to_ptr v21 : ptr; + hir.store v24, v7; + v26 = arith.constant 40 : u32; + v25 = hir.bitcast v16 : u32; + v27 = arith.add v25, v26 : u32 #[overflow = checked]; + v103 = arith.constant 4 : u32; + v29 = arith.mod v27, v103 : u32; + hir.assertz v29 #[code = 250]; + v30 = hir.int_to_ptr v27 : ptr; + hir.store v30, v6; + v32 = arith.constant 36 : u32; + v31 = hir.bitcast v16 : u32; + v33 = arith.add v31, v32 : u32 #[overflow = checked]; + v102 = arith.constant 4 : u32; + v35 = arith.mod v33, v102 : u32; + hir.assertz v35 #[code = 250]; + v36 = hir.int_to_ptr v33 : ptr; + hir.store v36, v5; + v38 = arith.constant 32 : u32; + v37 = hir.bitcast v16 : u32; + v39 = arith.add v37, v38 : u32 #[overflow = checked]; + v101 = arith.constant 4 : u32; + v41 = arith.mod v39, v101 : u32; + hir.assertz v41 #[code = 250]; + v42 = hir.int_to_ptr v39 : ptr; + hir.store v42, v4; + v44 = arith.constant 12 : u32; + v43 = hir.bitcast v16 : u32; + v45 = arith.add v43, v44 : u32 #[overflow = checked]; + v100 = arith.constant 4 : u32; + v47 = arith.mod v45, v100 : u32; + hir.assertz v47 #[code = 250]; + v48 = hir.int_to_ptr v45 : ptr; + hir.store v48, v3; + v50 = arith.constant 8 : u32; + v49 = hir.bitcast v16 : u32; + v51 = arith.add v49, v50 : u32 #[overflow = checked]; + v99 = arith.constant 4 : u32; + v53 = arith.mod v51, v99 : u32; + hir.assertz v53 #[code = 250]; + v54 = hir.int_to_ptr v51 : ptr; + hir.store v54, v2; + v98 = arith.constant 4 : u32; + v55 = hir.bitcast v16 : u32; + v57 = arith.add v55, v98 : u32 #[overflow = checked]; + v97 = arith.constant 4 : u32; + v59 = arith.mod v57, v97 : u32; + hir.assertz v59 #[code = 250]; + v60 = hir.int_to_ptr v57 : ptr; + hir.store v60, v1; + v61 = hir.bitcast v16 : u32; + v96 = arith.constant 4 : u32; + v63 = arith.mod v61, v96 : u32; + hir.assertz v63 #[code = 250]; + v64 = hir.int_to_ptr v61 : ptr; + hir.store v64, v0; + v93 = arith.constant 1048480 : felt; + v65 = hir.bitcast v16 : felt; + hir.assert_eq v65, v93; + v92 = arith.constant 1048544 : felt; + v68 = arith.constant 64 : i32; + v69 = arith.add v16, v68 : i32 #[overflow = wrapping]; + v70 = hir.bitcast v69 : felt; + hir.assert_eq v70, v92; + v95 = arith.constant 64 : i32; + v74 = arith.add v16, v95 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/arg_order/intrinsic(v16, v74) + v76 = arith.constant 64 : u32; + v75 = hir.bitcast v16 : u32; + v77 = arith.add v75, v76 : u32 #[overflow = checked]; + v94 = arith.constant 4 : u32; + v79 = arith.mod v77, v94 : u32; + hir.assertz v79 #[code = 250]; + v80 = hir.int_to_ptr v77 : ptr; + v81 = hir.load v80 : felt; + v82 = builtin.global_symbol @root_ns:root@1.0.0/arg_order/__stack_pointer : ptr + v83 = hir.bitcast v82 : ptr; + hir.store v83, v12; + builtin.ret v81; + }; + + public builtin.function @intrinsic(v84: i32, v85: i32) { + ^block6(v84: i32, v85: i32): + v105 = arith.constant 1048480 : felt; + v86 = hir.bitcast v84 : felt; + hir.assert_eq v86, v105; + v104 = arith.constant 1048544 : felt; + v89 = hir.bitcast v85 : felt; + hir.assert_eq v89, v104; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/arg_order.masm b/tests/integration/expected/arg_order.masm new file mode 100644 index 000000000..7350c06bc --- /dev/null +++ b/tests/integration/expected/arg_order.masm @@ -0,0 +1,245 @@ +# mod root_ns:root@1.0.0 + +export.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::arg_order + +export.entrypoint + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4294967264 + push.96 + dup.2 + swap.1 + u32wrapping_sub + u32and + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.44 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.10 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.40 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.9 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.36 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.8 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.32 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.7 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.6 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.1048480 + dup.1 + assert_eq + push.1048544 + push.64 + dup.2 + swap.1 + u32wrapping_add + assert_eq + push.64 + dup.1 + swap.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::arg_order::intrinsic + trace.252 + nop + push.64 + swap.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.1114112 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +export.intrinsic + push.1048480 + swap.1 + assert_eq + push.1048544 + swap.1 + assert_eq +end + diff --git a/tests/integration/expected/band_i16.hir b/tests/integration/expected/band_i16.hir index ca0e0fc24..b87b3981d 100644 --- a/tests/integration/expected/band_i16.hir +++ b/tests/integration/expected/band_i16.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_2d07899decfc53ff76743115618445ad5a97172f61c9e54ea539ff55362f4dc5 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_34638703ab78fea8050898ab92e28a6a09cade611d4a9a3601c8667b275e57ee { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.band v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_i16.masm b/tests/integration/expected/band_i16.masm index 4e8beed5a..a9dcd7af5 100644 --- a/tests/integration/expected/band_i16.masm +++ b/tests/integration/expected/band_i16.masm @@ -1,7 +1,24 @@ -# mod test_rust_2d07899decfc53ff76743115618445ad5a97172f61c9e54ea539ff55362f4dc5 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_34638703ab78fea8050898ab92e28a6a09cade611d4a9a3601c8667b275e57ee export.entrypoint u32and end - diff --git a/tests/integration/expected/band_i16.wat b/tests/integration/expected/band_i16.wat index 78a915f8d..12caa4c62 100644 --- a/tests/integration/expected/band_i16.wat +++ b/tests/integration/expected/band_i16.wat @@ -1,10 +1,5 @@ -(module $test_rust_2d07899decfc53ff76743115618445ad5a97172f61c9e54ea539ff55362f4dc5.wasm +(module $test_rust_34638703ab78fea8050898ab92e28a6a09cade611d4a9a3601c8667b275e57ee.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.and + ) +) diff --git a/tests/integration/expected/band_i32.hir b/tests/integration/expected/band_i32.hir index bc1aa2e0d..6849cb6d7 100644 --- a/tests/integration/expected/band_i32.hir +++ b/tests/integration/expected/band_i32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_3c1243f6bf89d22ea186e66208b1ad921f7332ce215ea72d1d9f2c00cea6323b - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_9ee88137407daefa6fa252f24e800d9fc4690951edfe7f8303f2f5225ed6eea3 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.band v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_i32.masm b/tests/integration/expected/band_i32.masm index 283fd3c89..98af1e079 100644 --- a/tests/integration/expected/band_i32.masm +++ b/tests/integration/expected/band_i32.masm @@ -1,7 +1,24 @@ -# mod test_rust_3c1243f6bf89d22ea186e66208b1ad921f7332ce215ea72d1d9f2c00cea6323b +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_9ee88137407daefa6fa252f24e800d9fc4690951edfe7f8303f2f5225ed6eea3 export.entrypoint u32and end - diff --git a/tests/integration/expected/band_i32.wat b/tests/integration/expected/band_i32.wat index 4feb9c538..f5c66eb91 100644 --- a/tests/integration/expected/band_i32.wat +++ b/tests/integration/expected/band_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_3c1243f6bf89d22ea186e66208b1ad921f7332ce215ea72d1d9f2c00cea6323b.wasm +(module $test_rust_9ee88137407daefa6fa252f24e800d9fc4690951edfe7f8303f2f5225ed6eea3.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.and + ) +) diff --git a/tests/integration/expected/band_i64.hir b/tests/integration/expected/band_i64.hir index 204b566be..00f09d965 100644 --- a/tests/integration/expected/band_i64.hir +++ b/tests/integration/expected/band_i64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_fda5d5f9a96ae19c191d7fceb423c1b36b7cf8dd7d7c4ca8e768b8305e8195fc - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_fdee33d480318f3f6165aabcaf90e290011f2d459d009ce9a6ea019e73b0e6b4 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.band v1, v0 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_i64.masm b/tests/integration/expected/band_i64.masm index f80aeca4a..c9ea01c35 100644 --- a/tests/integration/expected/band_i64.masm +++ b/tests/integration/expected/band_i64.masm @@ -1,7 +1,28 @@ -# mod test_rust_fda5d5f9a96ae19c191d7fceb423c1b36b7cf8dd7d7c4ca8e768b8305e8195fc +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_fdee33d480318f3f6165aabcaf90e290011f2d459d009ce9a6ea019e73b0e6b4 export.entrypoint + trace.240 + nop exec.::std::math::u64::and + trace.252 + nop end - diff --git a/tests/integration/expected/band_i64.wat b/tests/integration/expected/band_i64.wat index 37bf0542f..85224dd2e 100644 --- a/tests/integration/expected/band_i64.wat +++ b/tests/integration/expected/band_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_fda5d5f9a96ae19c191d7fceb423c1b36b7cf8dd7d7c4ca8e768b8305e8195fc.wasm +(module $test_rust_fdee33d480318f3f6165aabcaf90e290011f2d459d009ce9a6ea019e73b0e6b4.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.and + ) +) diff --git a/tests/integration/expected/band_i8.hir b/tests/integration/expected/band_i8.hir index b25eaac9d..260e18d96 100644 --- a/tests/integration/expected/band_i8.hir +++ b/tests/integration/expected/band_i8.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_82b97b60f6c2bdfdaa5412831eebdefbd9fcecdcda5ab71592efdf9c8f0a3fb8 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_a20ca31834cfb559286ab8bbb00ce2e657f38ec95d8459342621bc218581241d { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.band v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_i8.masm b/tests/integration/expected/band_i8.masm index dc859243c..93e2ae29c 100644 --- a/tests/integration/expected/band_i8.masm +++ b/tests/integration/expected/band_i8.masm @@ -1,7 +1,24 @@ -# mod test_rust_82b97b60f6c2bdfdaa5412831eebdefbd9fcecdcda5ab71592efdf9c8f0a3fb8 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_a20ca31834cfb559286ab8bbb00ce2e657f38ec95d8459342621bc218581241d export.entrypoint u32and end - diff --git a/tests/integration/expected/band_i8.wat b/tests/integration/expected/band_i8.wat index 4fbb7f965..c6d59f95e 100644 --- a/tests/integration/expected/band_i8.wat +++ b/tests/integration/expected/band_i8.wat @@ -1,10 +1,5 @@ -(module $test_rust_82b97b60f6c2bdfdaa5412831eebdefbd9fcecdcda5ab71592efdf9c8f0a3fb8.wasm +(module $test_rust_a20ca31834cfb559286ab8bbb00ce2e657f38ec95d8459342621bc218581241d.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.and + ) +) diff --git a/tests/integration/expected/band_u16.hir b/tests/integration/expected/band_u16.hir index e46a41c7b..06a9c7037 100644 --- a/tests/integration/expected/band_u16.hir +++ b/tests/integration/expected/band_u16.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_62f08a4fd4cafb7db864c24731da8dab986b2b5c2d54b53e960972953f553406 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_41457a077e1338454c33613079552cd2abe9d4fd50975cf8d2d8d9ee664dfbb6 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.band v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_u16.masm b/tests/integration/expected/band_u16.masm index 6a56e1eb3..8fdd30a02 100644 --- a/tests/integration/expected/band_u16.masm +++ b/tests/integration/expected/band_u16.masm @@ -1,7 +1,24 @@ -# mod test_rust_62f08a4fd4cafb7db864c24731da8dab986b2b5c2d54b53e960972953f553406 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_41457a077e1338454c33613079552cd2abe9d4fd50975cf8d2d8d9ee664dfbb6 export.entrypoint u32and end - diff --git a/tests/integration/expected/band_u16.wat b/tests/integration/expected/band_u16.wat index 8a1b955bd..06349966e 100644 --- a/tests/integration/expected/band_u16.wat +++ b/tests/integration/expected/band_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_62f08a4fd4cafb7db864c24731da8dab986b2b5c2d54b53e960972953f553406.wasm +(module $test_rust_41457a077e1338454c33613079552cd2abe9d4fd50975cf8d2d8d9ee664dfbb6.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.and + ) +) diff --git a/tests/integration/expected/band_u32.hir b/tests/integration/expected/band_u32.hir index 83b487232..e7013bcf5 100644 --- a/tests/integration/expected/band_u32.hir +++ b/tests/integration/expected/band_u32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_c4f32e60d0da9da73a9d4beef2081fc955bd8750a29571f11f6916287436cbe3 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_82d1878f26a40c75c635298e80f65f7a5aac492ad99eb90909a97ab273f4c1ad { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.band v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_u32.masm b/tests/integration/expected/band_u32.masm index 847d90217..b678ce1cb 100644 --- a/tests/integration/expected/band_u32.masm +++ b/tests/integration/expected/band_u32.masm @@ -1,7 +1,24 @@ -# mod test_rust_c4f32e60d0da9da73a9d4beef2081fc955bd8750a29571f11f6916287436cbe3 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_82d1878f26a40c75c635298e80f65f7a5aac492ad99eb90909a97ab273f4c1ad export.entrypoint u32and end - diff --git a/tests/integration/expected/band_u32.wat b/tests/integration/expected/band_u32.wat index 3a74a85bc..afd6a3ad9 100644 --- a/tests/integration/expected/band_u32.wat +++ b/tests/integration/expected/band_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_c4f32e60d0da9da73a9d4beef2081fc955bd8750a29571f11f6916287436cbe3.wasm +(module $test_rust_82d1878f26a40c75c635298e80f65f7a5aac492ad99eb90909a97ab273f4c1ad.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.and + ) +) diff --git a/tests/integration/expected/band_u64.hir b/tests/integration/expected/band_u64.hir index c29808970..e93a29705 100644 --- a/tests/integration/expected/band_u64.hir +++ b/tests/integration/expected/band_u64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_4c7e8f3e1741ccb4a5bbbee6f13443e5deb9c528380592d13a046312348bd927 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_8509e2e84a91835b60f651a4adb33e43024ab7f31520e5ee5b5a97f181859a40 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.band v1, v0 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_u64.masm b/tests/integration/expected/band_u64.masm index 7c6d13a4c..4e0777c7d 100644 --- a/tests/integration/expected/band_u64.masm +++ b/tests/integration/expected/band_u64.masm @@ -1,7 +1,28 @@ -# mod test_rust_4c7e8f3e1741ccb4a5bbbee6f13443e5deb9c528380592d13a046312348bd927 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_8509e2e84a91835b60f651a4adb33e43024ab7f31520e5ee5b5a97f181859a40 export.entrypoint + trace.240 + nop exec.::std::math::u64::and + trace.252 + nop end - diff --git a/tests/integration/expected/band_u64.wat b/tests/integration/expected/band_u64.wat index 1cb7da5e8..7d45c8d98 100644 --- a/tests/integration/expected/band_u64.wat +++ b/tests/integration/expected/band_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_4c7e8f3e1741ccb4a5bbbee6f13443e5deb9c528380592d13a046312348bd927.wasm +(module $test_rust_8509e2e84a91835b60f651a4adb33e43024ab7f31520e5ee5b5a97f181859a40.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.and + ) +) diff --git a/tests/integration/expected/band_u8.hir b/tests/integration/expected/band_u8.hir index 1e6ae42fc..a574b1c24 100644 --- a/tests/integration/expected/band_u8.hir +++ b/tests/integration/expected/band_u8.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_bcd6febe71cae37427fa9ff25c42a5ecaea8f1ac799aa86f8a46e3d07ab1bc01 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_3be102ea36db2b23b9f6f0e34bfb210f4f562d75c14b2d53d64bdcc630a44aa6 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.band v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (band v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/band_u8.masm b/tests/integration/expected/band_u8.masm index 78cb1ed58..859b3560b 100644 --- a/tests/integration/expected/band_u8.masm +++ b/tests/integration/expected/band_u8.masm @@ -1,7 +1,24 @@ -# mod test_rust_bcd6febe71cae37427fa9ff25c42a5ecaea8f1ac799aa86f8a46e3d07ab1bc01 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_3be102ea36db2b23b9f6f0e34bfb210f4f562d75c14b2d53d64bdcc630a44aa6 export.entrypoint u32and end - diff --git a/tests/integration/expected/band_u8.wat b/tests/integration/expected/band_u8.wat index 6ebc5c413..ba5f64dc0 100644 --- a/tests/integration/expected/band_u8.wat +++ b/tests/integration/expected/band_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_bcd6febe71cae37427fa9ff25c42a5ecaea8f1ac799aa86f8a46e3d07ab1bc01.wasm +(module $test_rust_3be102ea36db2b23b9f6f0e34bfb210f4f562d75c14b2d53d64bdcc630a44aa6.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.and + ) +) diff --git a/tests/integration/expected/bnot_bool.hir b/tests/integration/expected/bnot_bool.hir index 903dbe8e6..bb2ea387a 100644 --- a/tests/integration/expected/bnot_bool.hir +++ b/tests/integration/expected/bnot_bool.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_145320f8e700bf6bdf185aeb1dafe222a5808453dd3ade85b6f787389bf30c0f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_77ff166ebf5be0d308b881f765d5f8ef913f838ea7e86a97499b030fa1c0f3f4 { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant 1 : i32; + v3 = arith.bxor v0, v2 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 1)) - (let (v3 i32) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_bool.masm b/tests/integration/expected/bnot_bool.masm index ea095087e..b0358e3e4 100644 --- a/tests/integration/expected/bnot_bool.masm +++ b/tests/integration/expected/bnot_bool.masm @@ -1,7 +1,25 @@ -# mod test_rust_145320f8e700bf6bdf185aeb1dafe222a5808453dd3ade85b6f787389bf30c0f +# mod root_ns:root@1.0.0 -export.entrypoint - push.1 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_77ff166ebf5be0d308b881f765d5f8ef913f838ea7e86a97499b030fa1c0f3f4 + +export.entrypoint + push.1 + u32xor +end diff --git a/tests/integration/expected/bnot_bool.wat b/tests/integration/expected/bnot_bool.wat index 3a0c5f5c5..4d2a6c1b7 100644 --- a/tests/integration/expected/bnot_bool.wat +++ b/tests/integration/expected/bnot_bool.wat @@ -1,10 +1,5 @@ -(module $test_rust_145320f8e700bf6bdf185aeb1dafe222a5808453dd3ade85b6f787389bf30c0f.wasm +(module $test_rust_77ff166ebf5be0d308b881f765d5f8ef913f838ea7e86a97499b030fa1c0f3f4.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - local.get 0 - i32.const 1 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const 1 + i32.xor + ) +) diff --git a/tests/integration/expected/bnot_i16.hir b/tests/integration/expected/bnot_i16.hir index 6b9e33d6e..cc0fa22b0 100644 --- a/tests/integration/expected/bnot_i16.hir +++ b/tests/integration/expected/bnot_i16.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_00cddfae3f036b7f10355e9d273eb1131e732db63a7ac33f728895e93a394b99 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e24d4ca783335c84dc5ae676aec479bda06baf4852fc00f30dac4021d07cf59f { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant -1 : i32; + v3 = arith.bxor v0, v2 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 -1)) - (let (v3 i32) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_i16.masm b/tests/integration/expected/bnot_i16.masm index 45549a1a2..7e7d36549 100644 --- a/tests/integration/expected/bnot_i16.masm +++ b/tests/integration/expected/bnot_i16.masm @@ -1,7 +1,25 @@ -# mod test_rust_00cddfae3f036b7f10355e9d273eb1131e732db63a7ac33f728895e93a394b99 +# mod root_ns:root@1.0.0 -export.entrypoint - push.4294967295 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_e24d4ca783335c84dc5ae676aec479bda06baf4852fc00f30dac4021d07cf59f + +export.entrypoint + push.4294967295 + u32xor +end diff --git a/tests/integration/expected/bnot_i16.wat b/tests/integration/expected/bnot_i16.wat index 943ca98ee..7f82b9818 100644 --- a/tests/integration/expected/bnot_i16.wat +++ b/tests/integration/expected/bnot_i16.wat @@ -1,10 +1,5 @@ -(module $test_rust_00cddfae3f036b7f10355e9d273eb1131e732db63a7ac33f728895e93a394b99.wasm +(module $test_rust_e24d4ca783335c84dc5ae676aec479bda06baf4852fc00f30dac4021d07cf59f.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - local.get 0 - i32.const -1 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const -1 + i32.xor + ) +) diff --git a/tests/integration/expected/bnot_i32.hir b/tests/integration/expected/bnot_i32.hir index c8a2364b1..f9f44f0c2 100644 --- a/tests/integration/expected/bnot_i32.hir +++ b/tests/integration/expected/bnot_i32.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_597108c1534cd7c973774710a354c519e8a9ce32a5a499ffe5839f4fea0cd83b - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_0d63a98a6809a92584f1dbaab4dfd841caaf9cab88744275bd4ed034006fc6ed { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant -1 : i32; + v3 = arith.bxor v0, v2 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 -1)) - (let (v3 i32) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_i32.masm b/tests/integration/expected/bnot_i32.masm index 6b016c354..dfdfc233a 100644 --- a/tests/integration/expected/bnot_i32.masm +++ b/tests/integration/expected/bnot_i32.masm @@ -1,7 +1,25 @@ -# mod test_rust_597108c1534cd7c973774710a354c519e8a9ce32a5a499ffe5839f4fea0cd83b +# mod root_ns:root@1.0.0 -export.entrypoint - push.4294967295 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_0d63a98a6809a92584f1dbaab4dfd841caaf9cab88744275bd4ed034006fc6ed + +export.entrypoint + push.4294967295 + u32xor +end diff --git a/tests/integration/expected/bnot_i32.wat b/tests/integration/expected/bnot_i32.wat index eec2d9815..3428ff548 100644 --- a/tests/integration/expected/bnot_i32.wat +++ b/tests/integration/expected/bnot_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_597108c1534cd7c973774710a354c519e8a9ce32a5a499ffe5839f4fea0cd83b.wasm +(module $test_rust_0d63a98a6809a92584f1dbaab4dfd841caaf9cab88744275bd4ed034006fc6ed.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - local.get 0 - i32.const -1 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const -1 + i32.xor + ) +) diff --git a/tests/integration/expected/bnot_i64.hir b/tests/integration/expected/bnot_i64.hir index aac320322..8b01cfe85 100644 --- a/tests/integration/expected/bnot_i64.hir +++ b/tests/integration/expected/bnot_i64.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_318a80316e6bca93f0a48d41942655ffb321d8d6c62d387f9037272e5ec565e3 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_b907170bd28f219043267cdd24dc67337632f05fcaab8f9a4efb14def28e8e3e { + public builtin.function @entrypoint(v0: i64) -> i64 { + ^block6(v0: i64): + v2 = arith.constant -1 : i64; + v3 = arith.bxor v0, v2 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (result i64) - (block 0 (param v0 i64) - (let (v2 i64) (const.i64 -1)) - (let (v3 i64) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i64) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_i64.masm b/tests/integration/expected/bnot_i64.masm index 471e52cb3..1d848322d 100644 --- a/tests/integration/expected/bnot_i64.masm +++ b/tests/integration/expected/bnot_i64.masm @@ -1,7 +1,30 @@ -# mod test_rust_318a80316e6bca93f0a48d41942655ffb321d8d6c62d387f9037272e5ec565e3 +# mod root_ns:root@1.0.0 -export.entrypoint - push.4294967295.4294967295 exec.::std::math::u64::xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_b907170bd28f219043267cdd24dc67337632f05fcaab8f9a4efb14def28e8e3e + +export.entrypoint + push.4294967295 + push.4294967295 + trace.240 + nop + exec.::std::math::u64::xor + trace.252 + nop +end diff --git a/tests/integration/expected/bnot_i64.wat b/tests/integration/expected/bnot_i64.wat index 8c8895f8f..8715ccf08 100644 --- a/tests/integration/expected/bnot_i64.wat +++ b/tests/integration/expected/bnot_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_318a80316e6bca93f0a48d41942655ffb321d8d6c62d387f9037272e5ec565e3.wasm +(module $test_rust_b907170bd28f219043267cdd24dc67337632f05fcaab8f9a4efb14def28e8e3e.wasm (type (;0;) (func (param i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64) (result i64) - local.get 0 - i64.const -1 - i64.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64) (result i64) + local.get 0 + i64.const -1 + i64.xor + ) +) diff --git a/tests/integration/expected/bnot_i8.hir b/tests/integration/expected/bnot_i8.hir index b4c9ba762..c08ec655d 100644 --- a/tests/integration/expected/bnot_i8.hir +++ b/tests/integration/expected/bnot_i8.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_694a3a0d438c5be6815982d68ba790c15be6f3f7ce15158c74a6f5adbb86596f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_1f608e193c7b4c41422a7e789e491655410126a59a757a5572c1ed81892c7c0b { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant -1 : i32; + v3 = arith.bxor v0, v2 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 -1)) - (let (v3 i32) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_i8.masm b/tests/integration/expected/bnot_i8.masm index 6217c38d7..1d9ba18f3 100644 --- a/tests/integration/expected/bnot_i8.masm +++ b/tests/integration/expected/bnot_i8.masm @@ -1,7 +1,25 @@ -# mod test_rust_694a3a0d438c5be6815982d68ba790c15be6f3f7ce15158c74a6f5adbb86596f +# mod root_ns:root@1.0.0 -export.entrypoint - push.4294967295 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_1f608e193c7b4c41422a7e789e491655410126a59a757a5572c1ed81892c7c0b + +export.entrypoint + push.4294967295 + u32xor +end diff --git a/tests/integration/expected/bnot_i8.wat b/tests/integration/expected/bnot_i8.wat index 0d150dfee..d7ce7ef42 100644 --- a/tests/integration/expected/bnot_i8.wat +++ b/tests/integration/expected/bnot_i8.wat @@ -1,10 +1,5 @@ -(module $test_rust_694a3a0d438c5be6815982d68ba790c15be6f3f7ce15158c74a6f5adbb86596f.wasm +(module $test_rust_1f608e193c7b4c41422a7e789e491655410126a59a757a5572c1ed81892c7c0b.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - local.get 0 - i32.const -1 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const -1 + i32.xor + ) +) diff --git a/tests/integration/expected/bnot_u16.hir b/tests/integration/expected/bnot_u16.hir index ec45e4459..e42114ad2 100644 --- a/tests/integration/expected/bnot_u16.hir +++ b/tests/integration/expected/bnot_u16.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_df76c987a66b14b20c7ce818414abfb720a4e3e74eb3dc8015560af9bdb3fee4 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_801b0e92eabaaba12053c65b60b81c5cb57e2d5bf386ab548321cf375005baea { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant 65535 : i32; + v3 = arith.bxor v0, v2 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 65535)) - (let (v3 i32) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_u16.masm b/tests/integration/expected/bnot_u16.masm index 00c1ceccd..ab891ead8 100644 --- a/tests/integration/expected/bnot_u16.masm +++ b/tests/integration/expected/bnot_u16.masm @@ -1,7 +1,25 @@ -# mod test_rust_df76c987a66b14b20c7ce818414abfb720a4e3e74eb3dc8015560af9bdb3fee4 +# mod root_ns:root@1.0.0 -export.entrypoint - push.65535 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_801b0e92eabaaba12053c65b60b81c5cb57e2d5bf386ab548321cf375005baea + +export.entrypoint + push.65535 + u32xor +end diff --git a/tests/integration/expected/bnot_u16.wat b/tests/integration/expected/bnot_u16.wat index 4e4b3036c..f7f544616 100644 --- a/tests/integration/expected/bnot_u16.wat +++ b/tests/integration/expected/bnot_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_df76c987a66b14b20c7ce818414abfb720a4e3e74eb3dc8015560af9bdb3fee4.wasm +(module $test_rust_801b0e92eabaaba12053c65b60b81c5cb57e2d5bf386ab548321cf375005baea.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - local.get 0 - i32.const 65535 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const 65535 + i32.xor + ) +) diff --git a/tests/integration/expected/bnot_u32.hir b/tests/integration/expected/bnot_u32.hir index 5044aa4eb..2e7e8b4ce 100644 --- a/tests/integration/expected/bnot_u32.hir +++ b/tests/integration/expected/bnot_u32.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_a762cb529f6595cbdce20fb1385e8ed3f8f4c23824a512b32f3818d297badb1c - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_dc5405fd1533a0bda8a8715a70ef89a33300844687446825e2ae7bfc5a34655a { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant -1 : i32; + v3 = arith.bxor v0, v2 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 -1)) - (let (v3 i32) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_u32.masm b/tests/integration/expected/bnot_u32.masm index 0e9b06860..feae5fa98 100644 --- a/tests/integration/expected/bnot_u32.masm +++ b/tests/integration/expected/bnot_u32.masm @@ -1,7 +1,25 @@ -# mod test_rust_a762cb529f6595cbdce20fb1385e8ed3f8f4c23824a512b32f3818d297badb1c +# mod root_ns:root@1.0.0 -export.entrypoint - push.4294967295 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_dc5405fd1533a0bda8a8715a70ef89a33300844687446825e2ae7bfc5a34655a + +export.entrypoint + push.4294967295 + u32xor +end diff --git a/tests/integration/expected/bnot_u32.wat b/tests/integration/expected/bnot_u32.wat index d8ebe72d6..525e91405 100644 --- a/tests/integration/expected/bnot_u32.wat +++ b/tests/integration/expected/bnot_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_a762cb529f6595cbdce20fb1385e8ed3f8f4c23824a512b32f3818d297badb1c.wasm +(module $test_rust_dc5405fd1533a0bda8a8715a70ef89a33300844687446825e2ae7bfc5a34655a.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - local.get 0 - i32.const -1 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const -1 + i32.xor + ) +) diff --git a/tests/integration/expected/bnot_u64.hir b/tests/integration/expected/bnot_u64.hir index 7f88707e3..fda914a88 100644 --- a/tests/integration/expected/bnot_u64.hir +++ b/tests/integration/expected/bnot_u64.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_18ecee456448c6244ca46633baac3f3f6281f50c0c17dc427e781fb02b614132 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_345f3c1e58fe5c9ef31421da6a362d9cadbf519ad68045840229e8b76f58a4f4 { + public builtin.function @entrypoint(v0: i64) -> i64 { + ^block6(v0: i64): + v2 = arith.constant -1 : i64; + v3 = arith.bxor v0, v2 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (result i64) - (block 0 (param v0 i64) - (let (v2 i64) (const.i64 -1)) - (let (v3 i64) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i64) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_u64.masm b/tests/integration/expected/bnot_u64.masm index 2843f40b5..82079d84f 100644 --- a/tests/integration/expected/bnot_u64.masm +++ b/tests/integration/expected/bnot_u64.masm @@ -1,7 +1,30 @@ -# mod test_rust_18ecee456448c6244ca46633baac3f3f6281f50c0c17dc427e781fb02b614132 +# mod root_ns:root@1.0.0 -export.entrypoint - push.4294967295.4294967295 exec.::std::math::u64::xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_345f3c1e58fe5c9ef31421da6a362d9cadbf519ad68045840229e8b76f58a4f4 + +export.entrypoint + push.4294967295 + push.4294967295 + trace.240 + nop + exec.::std::math::u64::xor + trace.252 + nop +end diff --git a/tests/integration/expected/bnot_u64.wat b/tests/integration/expected/bnot_u64.wat index 5b6b674df..69aba5a1e 100644 --- a/tests/integration/expected/bnot_u64.wat +++ b/tests/integration/expected/bnot_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_18ecee456448c6244ca46633baac3f3f6281f50c0c17dc427e781fb02b614132.wasm +(module $test_rust_345f3c1e58fe5c9ef31421da6a362d9cadbf519ad68045840229e8b76f58a4f4.wasm (type (;0;) (func (param i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64) (result i64) - local.get 0 - i64.const -1 - i64.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64) (result i64) + local.get 0 + i64.const -1 + i64.xor + ) +) diff --git a/tests/integration/expected/bnot_u8.hir b/tests/integration/expected/bnot_u8.hir index e22120803..70c972a4e 100644 --- a/tests/integration/expected/bnot_u8.hir +++ b/tests/integration/expected/bnot_u8.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_eaff6a2806ce4f4f18d6c1d65cab18383e6ac9921c310c1866b5b554b743d7e8 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_5e3a237ba6b351b72bc4ac66517613ffe85ccd4b728c40b405f3a9ff93506063 { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant 255 : i32; + v3 = arith.bxor v0, v2 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 255)) - (let (v3 i32) (bxor v0 v2)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bnot_u8.masm b/tests/integration/expected/bnot_u8.masm index 391e013e0..6dcd87b8f 100644 --- a/tests/integration/expected/bnot_u8.masm +++ b/tests/integration/expected/bnot_u8.masm @@ -1,7 +1,25 @@ -# mod test_rust_eaff6a2806ce4f4f18d6c1d65cab18383e6ac9921c310c1866b5b554b743d7e8 +# mod root_ns:root@1.0.0 -export.entrypoint - push.255 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_5e3a237ba6b351b72bc4ac66517613ffe85ccd4b728c40b405f3a9ff93506063 + +export.entrypoint + push.255 + u32xor +end diff --git a/tests/integration/expected/bnot_u8.wat b/tests/integration/expected/bnot_u8.wat index f7d2ff4d0..3d6d40f39 100644 --- a/tests/integration/expected/bnot_u8.wat +++ b/tests/integration/expected/bnot_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_eaff6a2806ce4f4f18d6c1d65cab18383e6ac9921c310c1866b5b554b743d7e8.wasm +(module $test_rust_5e3a237ba6b351b72bc4ac66517613ffe85ccd4b728c40b405f3a9ff93506063.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - local.get 0 - i32.const 255 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.const 255 + i32.xor + ) +) diff --git a/tests/integration/expected/bor_i16.hir b/tests/integration/expected/bor_i16.hir index 542aaa102..b88a40c89 100644 --- a/tests/integration/expected/bor_i16.hir +++ b/tests/integration/expected/bor_i16.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_8dcae0a39212d4ad89597641a65bcf61179a7ecf222225943d30d5d6d1cceed9 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_44e33b9e14fa2be1f40cdd20712edb995a941e3a1a153b46fb87e39dcbcdebc1 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_i16.masm b/tests/integration/expected/bor_i16.masm index 1ec5c00b9..a2ea47f9e 100644 --- a/tests/integration/expected/bor_i16.masm +++ b/tests/integration/expected/bor_i16.masm @@ -1,7 +1,24 @@ -# mod test_rust_8dcae0a39212d4ad89597641a65bcf61179a7ecf222225943d30d5d6d1cceed9 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_44e33b9e14fa2be1f40cdd20712edb995a941e3a1a153b46fb87e39dcbcdebc1 export.entrypoint u32or end - diff --git a/tests/integration/expected/bor_i16.wat b/tests/integration/expected/bor_i16.wat index 0ec60d963..466550eac 100644 --- a/tests/integration/expected/bor_i16.wat +++ b/tests/integration/expected/bor_i16.wat @@ -1,10 +1,5 @@ -(module $test_rust_8dcae0a39212d4ad89597641a65bcf61179a7ecf222225943d30d5d6d1cceed9.wasm +(module $test_rust_44e33b9e14fa2be1f40cdd20712edb995a941e3a1a153b46fb87e39dcbcdebc1.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.or + ) +) diff --git a/tests/integration/expected/bor_i32.hir b/tests/integration/expected/bor_i32.hir index b950e33d7..3e1ad592f 100644 --- a/tests/integration/expected/bor_i32.hir +++ b/tests/integration/expected/bor_i32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_8de4f7a3511ae02636986e75ade7e321e0708f1826bcbc6aad48f37af323b92d - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e24b176dbd0fa5b54e01370fc307f6f1a06748cd8946e58e7d1469dceadc6d92 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_i32.masm b/tests/integration/expected/bor_i32.masm index 9f4ef0668..533074be7 100644 --- a/tests/integration/expected/bor_i32.masm +++ b/tests/integration/expected/bor_i32.masm @@ -1,7 +1,24 @@ -# mod test_rust_8de4f7a3511ae02636986e75ade7e321e0708f1826bcbc6aad48f37af323b92d +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_e24b176dbd0fa5b54e01370fc307f6f1a06748cd8946e58e7d1469dceadc6d92 export.entrypoint u32or end - diff --git a/tests/integration/expected/bor_i32.wat b/tests/integration/expected/bor_i32.wat index 347400bde..ce0c9fedb 100644 --- a/tests/integration/expected/bor_i32.wat +++ b/tests/integration/expected/bor_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_8de4f7a3511ae02636986e75ade7e321e0708f1826bcbc6aad48f37af323b92d.wasm +(module $test_rust_e24b176dbd0fa5b54e01370fc307f6f1a06748cd8946e58e7d1469dceadc6d92.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.or + ) +) diff --git a/tests/integration/expected/bor_i64.hir b/tests/integration/expected/bor_i64.hir index 360f06d42..b5846fe74 100644 --- a/tests/integration/expected/bor_i64.hir +++ b/tests/integration/expected/bor_i64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_f251de15b0f1cb5576431f7d54bb74bf9a7d3ecd44df0dab5c9d48f9fbc8fc10 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_19d92981d2d7f148eaa97011bd0bd979fbc793cadadb334c67791824f38e7bf8 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.bor v1, v0 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_i64.masm b/tests/integration/expected/bor_i64.masm index 4ee529fea..2d1eca6c1 100644 --- a/tests/integration/expected/bor_i64.masm +++ b/tests/integration/expected/bor_i64.masm @@ -1,7 +1,28 @@ -# mod test_rust_f251de15b0f1cb5576431f7d54bb74bf9a7d3ecd44df0dab5c9d48f9fbc8fc10 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_19d92981d2d7f148eaa97011bd0bd979fbc793cadadb334c67791824f38e7bf8 export.entrypoint + trace.240 + nop exec.::std::math::u64::or + trace.252 + nop end - diff --git a/tests/integration/expected/bor_i64.wat b/tests/integration/expected/bor_i64.wat index 0e465cf60..c4e0d8ca9 100644 --- a/tests/integration/expected/bor_i64.wat +++ b/tests/integration/expected/bor_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_f251de15b0f1cb5576431f7d54bb74bf9a7d3ecd44df0dab5c9d48f9fbc8fc10.wasm +(module $test_rust_19d92981d2d7f148eaa97011bd0bd979fbc793cadadb334c67791824f38e7bf8.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.or + ) +) diff --git a/tests/integration/expected/bor_i8.hir b/tests/integration/expected/bor_i8.hir index bc260913d..626375441 100644 --- a/tests/integration/expected/bor_i8.hir +++ b/tests/integration/expected/bor_i8.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_e78a5011ab26b989caa809c67e9a8c1aa75b6ed66a6d08724f2ade857dd972e7 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_6b279a51e04f2b6fc0be10e9ad685e34fcfc2cbc2c726847699f4721f48e3713 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_i8.masm b/tests/integration/expected/bor_i8.masm index 3a7ef3aeb..eacd6f757 100644 --- a/tests/integration/expected/bor_i8.masm +++ b/tests/integration/expected/bor_i8.masm @@ -1,7 +1,24 @@ -# mod test_rust_e78a5011ab26b989caa809c67e9a8c1aa75b6ed66a6d08724f2ade857dd972e7 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_6b279a51e04f2b6fc0be10e9ad685e34fcfc2cbc2c726847699f4721f48e3713 export.entrypoint u32or end - diff --git a/tests/integration/expected/bor_i8.wat b/tests/integration/expected/bor_i8.wat index f4ee5fe73..6e626ff38 100644 --- a/tests/integration/expected/bor_i8.wat +++ b/tests/integration/expected/bor_i8.wat @@ -1,10 +1,5 @@ -(module $test_rust_e78a5011ab26b989caa809c67e9a8c1aa75b6ed66a6d08724f2ade857dd972e7.wasm +(module $test_rust_6b279a51e04f2b6fc0be10e9ad685e34fcfc2cbc2c726847699f4721f48e3713.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.or + ) +) diff --git a/tests/integration/expected/bor_u16.hir b/tests/integration/expected/bor_u16.hir index 0ba3532a6..81616803a 100644 --- a/tests/integration/expected/bor_u16.hir +++ b/tests/integration/expected/bor_u16.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_dc995daa9cb678e867fa3a5c4e0179784bd1e7d08c037d55bd2297656517604d - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_fe8fe146eac1710c2c8529e4df4810118e471d0c279c48e765ff28ad800d1aab { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_u16.masm b/tests/integration/expected/bor_u16.masm index cf6d49691..cb0dfae2f 100644 --- a/tests/integration/expected/bor_u16.masm +++ b/tests/integration/expected/bor_u16.masm @@ -1,7 +1,24 @@ -# mod test_rust_dc995daa9cb678e867fa3a5c4e0179784bd1e7d08c037d55bd2297656517604d +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_fe8fe146eac1710c2c8529e4df4810118e471d0c279c48e765ff28ad800d1aab export.entrypoint u32or end - diff --git a/tests/integration/expected/bor_u16.wat b/tests/integration/expected/bor_u16.wat index 9dfcff939..6041102b8 100644 --- a/tests/integration/expected/bor_u16.wat +++ b/tests/integration/expected/bor_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_dc995daa9cb678e867fa3a5c4e0179784bd1e7d08c037d55bd2297656517604d.wasm +(module $test_rust_fe8fe146eac1710c2c8529e4df4810118e471d0c279c48e765ff28ad800d1aab.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.or + ) +) diff --git a/tests/integration/expected/bor_u32.hir b/tests/integration/expected/bor_u32.hir index 6bc5897bf..a6d8e5453 100644 --- a/tests/integration/expected/bor_u32.hir +++ b/tests/integration/expected/bor_u32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_77d8f367326a058c1e380ac8136670434cfbc1e8dc697dc19201190d8465e015 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e3bb04a56c487ecfc0dd1c57e0d354745ffb321466dc47bd02d51513cc6980d1 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_u32.masm b/tests/integration/expected/bor_u32.masm index 70ba7b1bf..d5ee7afa7 100644 --- a/tests/integration/expected/bor_u32.masm +++ b/tests/integration/expected/bor_u32.masm @@ -1,7 +1,24 @@ -# mod test_rust_77d8f367326a058c1e380ac8136670434cfbc1e8dc697dc19201190d8465e015 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_e3bb04a56c487ecfc0dd1c57e0d354745ffb321466dc47bd02d51513cc6980d1 export.entrypoint u32or end - diff --git a/tests/integration/expected/bor_u32.wat b/tests/integration/expected/bor_u32.wat index 6e4ba91d1..ad49fb932 100644 --- a/tests/integration/expected/bor_u32.wat +++ b/tests/integration/expected/bor_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_77d8f367326a058c1e380ac8136670434cfbc1e8dc697dc19201190d8465e015.wasm +(module $test_rust_e3bb04a56c487ecfc0dd1c57e0d354745ffb321466dc47bd02d51513cc6980d1.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.or + ) +) diff --git a/tests/integration/expected/bor_u64.hir b/tests/integration/expected/bor_u64.hir index a11f37846..7ece8d747 100644 --- a/tests/integration/expected/bor_u64.hir +++ b/tests/integration/expected/bor_u64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_ba6ec59331d835edc07f1b6d30f780b5afe487a33c70884f73400189c17cefa6 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e8616ef7e15d87b491a1dbcccfe42b1dcb7a90b965c27549b2b5d9604d28acee { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.bor v1, v0 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_u64.masm b/tests/integration/expected/bor_u64.masm index 9e3ccc6fb..6351632b2 100644 --- a/tests/integration/expected/bor_u64.masm +++ b/tests/integration/expected/bor_u64.masm @@ -1,7 +1,28 @@ -# mod test_rust_ba6ec59331d835edc07f1b6d30f780b5afe487a33c70884f73400189c17cefa6 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_e8616ef7e15d87b491a1dbcccfe42b1dcb7a90b965c27549b2b5d9604d28acee export.entrypoint + trace.240 + nop exec.::std::math::u64::or + trace.252 + nop end - diff --git a/tests/integration/expected/bor_u64.wat b/tests/integration/expected/bor_u64.wat index fcedf5b3e..e22fa7bca 100644 --- a/tests/integration/expected/bor_u64.wat +++ b/tests/integration/expected/bor_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_ba6ec59331d835edc07f1b6d30f780b5afe487a33c70884f73400189c17cefa6.wasm +(module $test_rust_e8616ef7e15d87b491a1dbcccfe42b1dcb7a90b965c27549b2b5d9604d28acee.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.or + ) +) diff --git a/tests/integration/expected/bor_u8.hir b/tests/integration/expected/bor_u8.hir index 9a977274c..f718c0dda 100644 --- a/tests/integration/expected/bor_u8.hir +++ b/tests/integration/expected/bor_u8.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_627a21fd177d4525d8fc796d148d944ac1c73323757235a4c20ab90db532b285 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_a0d8359c86d6548c3cdc5448b2a38351e3498e1db98a8d3d4bbbc27afa05af09 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bor_u8.masm b/tests/integration/expected/bor_u8.masm index 271f826cc..6ceaa3dd0 100644 --- a/tests/integration/expected/bor_u8.masm +++ b/tests/integration/expected/bor_u8.masm @@ -1,7 +1,24 @@ -# mod test_rust_627a21fd177d4525d8fc796d148d944ac1c73323757235a4c20ab90db532b285 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_a0d8359c86d6548c3cdc5448b2a38351e3498e1db98a8d3d4bbbc27afa05af09 export.entrypoint u32or end - diff --git a/tests/integration/expected/bor_u8.wat b/tests/integration/expected/bor_u8.wat index ee5e7d345..d7c20f05b 100644 --- a/tests/integration/expected/bor_u8.wat +++ b/tests/integration/expected/bor_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_627a21fd177d4525d8fc796d148d944ac1c73323757235a4c20ab90db532b285.wasm +(module $test_rust_a0d8359c86d6548c3cdc5448b2a38351e3498e1db98a8d3d4bbbc27afa05af09.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.or + ) +) diff --git a/tests/integration/expected/bxor_i16.hir b/tests/integration/expected/bxor_i16.hir index e745afcb8..e3abd4d5b 100644 --- a/tests/integration/expected/bxor_i16.hir +++ b/tests/integration/expected/bxor_i16.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_06b51ddbcc44da2e5b8841fa052397384b0f5d4eefe1a6761fd89258dd630db1 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_cb1e0c1a18c279735e94c50dd925290caa430ac65188eb20eb437a4391d25640 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bxor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_i16.masm b/tests/integration/expected/bxor_i16.masm index 3ea7d86e8..77a83dd45 100644 --- a/tests/integration/expected/bxor_i16.masm +++ b/tests/integration/expected/bxor_i16.masm @@ -1,7 +1,24 @@ -# mod test_rust_06b51ddbcc44da2e5b8841fa052397384b0f5d4eefe1a6761fd89258dd630db1 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_cb1e0c1a18c279735e94c50dd925290caa430ac65188eb20eb437a4391d25640 export.entrypoint u32xor end - diff --git a/tests/integration/expected/bxor_i16.wat b/tests/integration/expected/bxor_i16.wat index 849144765..3e7b8f881 100644 --- a/tests/integration/expected/bxor_i16.wat +++ b/tests/integration/expected/bxor_i16.wat @@ -1,10 +1,5 @@ -(module $test_rust_06b51ddbcc44da2e5b8841fa052397384b0f5d4eefe1a6761fd89258dd630db1.wasm +(module $test_rust_cb1e0c1a18c279735e94c50dd925290caa430ac65188eb20eb437a4391d25640.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.xor + ) +) diff --git a/tests/integration/expected/bxor_i32.hir b/tests/integration/expected/bxor_i32.hir index a11993025..3ea8459ad 100644 --- a/tests/integration/expected/bxor_i32.hir +++ b/tests/integration/expected/bxor_i32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_be5e210e7912f50da791746e584735851a9fd43e799bd41c0fbdf5eeef96d97f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_1c4d375403281e14e710bf442e5556c06e5933dce447f206252657b1345611f0 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bxor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_i32.masm b/tests/integration/expected/bxor_i32.masm index c6381824d..9e98ce9a7 100644 --- a/tests/integration/expected/bxor_i32.masm +++ b/tests/integration/expected/bxor_i32.masm @@ -1,7 +1,24 @@ -# mod test_rust_be5e210e7912f50da791746e584735851a9fd43e799bd41c0fbdf5eeef96d97f +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_1c4d375403281e14e710bf442e5556c06e5933dce447f206252657b1345611f0 export.entrypoint u32xor end - diff --git a/tests/integration/expected/bxor_i32.wat b/tests/integration/expected/bxor_i32.wat index 84522da2c..fcb0fdd26 100644 --- a/tests/integration/expected/bxor_i32.wat +++ b/tests/integration/expected/bxor_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_be5e210e7912f50da791746e584735851a9fd43e799bd41c0fbdf5eeef96d97f.wasm +(module $test_rust_1c4d375403281e14e710bf442e5556c06e5933dce447f206252657b1345611f0.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.xor + ) +) diff --git a/tests/integration/expected/bxor_i64.hir b/tests/integration/expected/bxor_i64.hir index cde4b91df..757dac61d 100644 --- a/tests/integration/expected/bxor_i64.hir +++ b/tests/integration/expected/bxor_i64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_885212eb58b38aa5817a1cd1ea309cf5ebf56542a26e25486674166aaa47f2cb - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_bbe2169ccd3bb4080a705f38ed5ef633d3af9e172e8f9b41ae4ee10f8059e176 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.bxor v1, v0 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_i64.masm b/tests/integration/expected/bxor_i64.masm index 62542aad5..f5033bf81 100644 --- a/tests/integration/expected/bxor_i64.masm +++ b/tests/integration/expected/bxor_i64.masm @@ -1,7 +1,28 @@ -# mod test_rust_885212eb58b38aa5817a1cd1ea309cf5ebf56542a26e25486674166aaa47f2cb +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_bbe2169ccd3bb4080a705f38ed5ef633d3af9e172e8f9b41ae4ee10f8059e176 export.entrypoint + trace.240 + nop exec.::std::math::u64::xor + trace.252 + nop end - diff --git a/tests/integration/expected/bxor_i64.wat b/tests/integration/expected/bxor_i64.wat index 64cd448cf..b1550b2f5 100644 --- a/tests/integration/expected/bxor_i64.wat +++ b/tests/integration/expected/bxor_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_885212eb58b38aa5817a1cd1ea309cf5ebf56542a26e25486674166aaa47f2cb.wasm +(module $test_rust_bbe2169ccd3bb4080a705f38ed5ef633d3af9e172e8f9b41ae4ee10f8059e176.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.xor + ) +) diff --git a/tests/integration/expected/bxor_i8.hir b/tests/integration/expected/bxor_i8.hir index 21f8d3448..17b9382d5 100644 --- a/tests/integration/expected/bxor_i8.hir +++ b/tests/integration/expected/bxor_i8.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_356739565a7fab5b3bf9f65a371ce7f192583c0220bd25c5fee3d50220aadf20 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_da1dfc177b6a72c28237264497437ada8ba555111d180be7b99b1d60791da41c { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bxor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_i8.masm b/tests/integration/expected/bxor_i8.masm index b7a6aa251..74b1bb60f 100644 --- a/tests/integration/expected/bxor_i8.masm +++ b/tests/integration/expected/bxor_i8.masm @@ -1,7 +1,24 @@ -# mod test_rust_356739565a7fab5b3bf9f65a371ce7f192583c0220bd25c5fee3d50220aadf20 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_da1dfc177b6a72c28237264497437ada8ba555111d180be7b99b1d60791da41c export.entrypoint u32xor end - diff --git a/tests/integration/expected/bxor_i8.wat b/tests/integration/expected/bxor_i8.wat index d25949d7f..841da0230 100644 --- a/tests/integration/expected/bxor_i8.wat +++ b/tests/integration/expected/bxor_i8.wat @@ -1,10 +1,5 @@ -(module $test_rust_356739565a7fab5b3bf9f65a371ce7f192583c0220bd25c5fee3d50220aadf20.wasm +(module $test_rust_da1dfc177b6a72c28237264497437ada8ba555111d180be7b99b1d60791da41c.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.xor + ) +) diff --git a/tests/integration/expected/bxor_u16.hir b/tests/integration/expected/bxor_u16.hir index 06f0ecdea..ea63c1324 100644 --- a/tests/integration/expected/bxor_u16.hir +++ b/tests/integration/expected/bxor_u16.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_1b43125ada2df0a7389224838e3cc4580de7ac09a86583219a3c08992ca30695 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_255d3558f03d3b3be3d933d40e9ae32b53db7dd904e40aafd5677d694056c91b { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bxor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_u16.masm b/tests/integration/expected/bxor_u16.masm index fc0403e3b..108cc0247 100644 --- a/tests/integration/expected/bxor_u16.masm +++ b/tests/integration/expected/bxor_u16.masm @@ -1,7 +1,24 @@ -# mod test_rust_1b43125ada2df0a7389224838e3cc4580de7ac09a86583219a3c08992ca30695 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_255d3558f03d3b3be3d933d40e9ae32b53db7dd904e40aafd5677d694056c91b export.entrypoint u32xor end - diff --git a/tests/integration/expected/bxor_u16.wat b/tests/integration/expected/bxor_u16.wat index 921b39079..2585fbb7b 100644 --- a/tests/integration/expected/bxor_u16.wat +++ b/tests/integration/expected/bxor_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_1b43125ada2df0a7389224838e3cc4580de7ac09a86583219a3c08992ca30695.wasm +(module $test_rust_255d3558f03d3b3be3d933d40e9ae32b53db7dd904e40aafd5677d694056c91b.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.xor + ) +) diff --git a/tests/integration/expected/bxor_u32.hir b/tests/integration/expected/bxor_u32.hir index 80344d81b..61ae370f7 100644 --- a/tests/integration/expected/bxor_u32.hir +++ b/tests/integration/expected/bxor_u32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_8adee8c8a1d96eeb275124845b60cbff14dd0510589dd0280f2e41e39371d112 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_b25131cea5088f57eb35dec78eb814ce753ab95c1f4faf8447870d9b6d1525dd { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bxor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_u32.masm b/tests/integration/expected/bxor_u32.masm index 675bd1edd..fd09b382b 100644 --- a/tests/integration/expected/bxor_u32.masm +++ b/tests/integration/expected/bxor_u32.masm @@ -1,7 +1,24 @@ -# mod test_rust_8adee8c8a1d96eeb275124845b60cbff14dd0510589dd0280f2e41e39371d112 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_b25131cea5088f57eb35dec78eb814ce753ab95c1f4faf8447870d9b6d1525dd export.entrypoint u32xor end - diff --git a/tests/integration/expected/bxor_u32.wat b/tests/integration/expected/bxor_u32.wat index b446e271a..de9162167 100644 --- a/tests/integration/expected/bxor_u32.wat +++ b/tests/integration/expected/bxor_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_8adee8c8a1d96eeb275124845b60cbff14dd0510589dd0280f2e41e39371d112.wasm +(module $test_rust_b25131cea5088f57eb35dec78eb814ce753ab95c1f4faf8447870d9b6d1525dd.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.xor + ) +) diff --git a/tests/integration/expected/bxor_u64.hir b/tests/integration/expected/bxor_u64.hir index 615cdc39e..012ab5f74 100644 --- a/tests/integration/expected/bxor_u64.hir +++ b/tests/integration/expected/bxor_u64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_0ac0889968c0147e0b9ffdf298215e3c144b2f0574e12a5a7110797b6cd692a5 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_ab1d59bc3952451e2ac6a22333bf5458760622594546976d2d1488d607497ae7 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.bxor v1, v0 : i64; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_u64.masm b/tests/integration/expected/bxor_u64.masm index c82a35e0b..3142d4a30 100644 --- a/tests/integration/expected/bxor_u64.masm +++ b/tests/integration/expected/bxor_u64.masm @@ -1,7 +1,28 @@ -# mod test_rust_0ac0889968c0147e0b9ffdf298215e3c144b2f0574e12a5a7110797b6cd692a5 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_ab1d59bc3952451e2ac6a22333bf5458760622594546976d2d1488d607497ae7 export.entrypoint + trace.240 + nop exec.::std::math::u64::xor + trace.252 + nop end - diff --git a/tests/integration/expected/bxor_u64.wat b/tests/integration/expected/bxor_u64.wat index a2cb976b1..6f182996e 100644 --- a/tests/integration/expected/bxor_u64.wat +++ b/tests/integration/expected/bxor_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_0ac0889968c0147e0b9ffdf298215e3c144b2f0574e12a5a7110797b6cd692a5.wasm +(module $test_rust_ab1d59bc3952451e2ac6a22333bf5458760622594546976d2d1488d607497ae7.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.xor + ) +) diff --git a/tests/integration/expected/bxor_u8.hir b/tests/integration/expected/bxor_u8.hir index ee2c04abd..b3dad1e8d 100644 --- a/tests/integration/expected/bxor_u8.hir +++ b/tests/integration/expected/bxor_u8.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_4d8ea78f0f6f413d2e269f1030408eff2561031d16e66341c04b75f1a2e08afe - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e442bdf6f0faa5b55270b06bb284534cce773aa9180fde57cdeeeddf42bb21e6 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bxor v1, v0 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bxor v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/bxor_u8.masm b/tests/integration/expected/bxor_u8.masm index b036e7b95..ba2825b6c 100644 --- a/tests/integration/expected/bxor_u8.masm +++ b/tests/integration/expected/bxor_u8.masm @@ -1,7 +1,24 @@ -# mod test_rust_4d8ea78f0f6f413d2e269f1030408eff2561031d16e66341c04b75f1a2e08afe +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_e442bdf6f0faa5b55270b06bb284534cce773aa9180fde57cdeeeddf42bb21e6 export.entrypoint u32xor end - diff --git a/tests/integration/expected/bxor_u8.wat b/tests/integration/expected/bxor_u8.wat index e2a0d431f..b3130abc6 100644 --- a/tests/integration/expected/bxor_u8.wat +++ b/tests/integration/expected/bxor_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_4d8ea78f0f6f413d2e269f1030408eff2561031d16e66341c04b75f1a2e08afe.wasm +(module $test_rust_e442bdf6f0faa5b55270b06bb284534cce773aa9180fde57cdeeeddf42bb21e6.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.xor + ) +) diff --git a/tests/integration/expected/collatz.hir b/tests/integration/expected/collatz.hir new file mode 100644 index 000000000..2a3ebe965 --- /dev/null +++ b/tests/integration/expected/collatz.hir @@ -0,0 +1,53 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @collatz { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block4(v0: i32): + v2 = arith.constant 0 : i32; + v59, v60, v61 = scf.while v0, v2 : i32, i32, i32 { + ^block17(v62: i32, v63: i32): + v76 = arith.constant 0 : i32; + v6 = arith.constant 1 : i32; + v7 = arith.neq v62, v6 : i1; + v8 = arith.zext v7 : u32; + v9 = hir.bitcast v8 : i32; + v11 = arith.neq v9, v76 : i1; + v67, v68 = scf.if v11 : i32, i32 { + ^block8: + v72 = arith.constant 1 : i32; + v28 = arith.add v63, v72 : i32 #[overflow = wrapping]; + v36 = arith.constant 1 : u32; + v18 = hir.bitcast v62 : u32; + v20 = arith.shr v18, v36 : u32; + v21 = hir.bitcast v20 : i32; + v73 = arith.constant 1 : i32; + v13 = arith.constant 3 : i32; + v14 = arith.mul v62, v13 : i32 #[overflow = wrapping]; + v16 = arith.add v14, v73 : i32 #[overflow = wrapping]; + v74 = arith.constant 0 : i32; + v75 = arith.constant 1 : i32; + v23 = arith.band v62, v75 : i32; + v25 = arith.neq v23, v74 : i1; + v26 = cf.select v25, v16, v21 : i32; + scf.yield v26, v28; + } else { + ^block16: + v37 = ub.poison i32 : i32; + scf.yield v37, v37; + }; + v31 = arith.constant 0 : u32; + v71 = arith.constant 1 : u32; + v70 = cf.select v11, v71, v31 : u32; + v54 = arith.trunc v70 : i1; + scf.condition v54, v67, v68, v63; + } do { + ^block18(v64: i32, v65: i32, v66: i32): + scf.yield v64, v65; + }; + builtin.ret v61; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/collatz.masm b/tests/integration/expected/collatz.masm new file mode 100644 index 000000000..5659061ee --- /dev/null +++ b/tests/integration/expected/collatz.masm @@ -0,0 +1,72 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::collatz + +export.entrypoint + push.0 + swap.1 + push.1 + while.true + push.0 + push.1 + dup.2 + neq + neq + dup.0 + if.true + push.1 + dup.3 + u32wrapping_add + push.1 + dup.3 + swap.1 + u32shr + push.1 + push.3 + dup.5 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + u32wrapping_add + push.0 + push.1 + movup.6 + u32and + neq + cdrop + else + swap.1 + drop + push.3735929054 + dup.0 + end + push.0 + push.1 + movup.4 + cdrop + push.1 + u32and + if.true + movup.2 + drop + push.1 + else + push.0 + end + end + drop + drop +end + diff --git a/tests/integration/expected/collatz.wat b/tests/integration/expected/collatz.wat new file mode 100644 index 000000000..78c1dc959 --- /dev/null +++ b/tests/integration/expected/collatz.wat @@ -0,0 +1,40 @@ +(module $collatz.wasm + (type (;0;) (func (param i32) (result i32))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + (local i32) + i32.const 0 + local.set 1 + loop (result i32) ;; label = @1 + block ;; label = @2 + local.get 0 + i32.const 1 + i32.ne + br_if 0 (;@2;) + local.get 1 + return + end + local.get 0 + i32.const 3 + i32.mul + i32.const 1 + i32.add + local.get 0 + i32.const 1 + i32.shr_u + local.get 0 + i32.const 1 + i32.and + select + local.set 0 + local.get 1 + i32.const 1 + i32.add + local.set 1 + br 0 (;@1;) + end + ) +) diff --git a/tests/integration/expected/components/add_wasm_component.hir b/tests/integration/expected/components/add_wasm_component.hir deleted file mode 100644 index 9935425c3..000000000 --- a/tests/integration/expected/components/add_wasm_component.hir +++ /dev/null @@ -1,1091 +0,0 @@ -(component - ;; Modules - (module #add_wasm_component - ;; Data Segments - (data (mut) (offset 1048576) 0x0100000002000000) - - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - - ;; Functions - (func (export #__wasm_call_ctors) - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #add_wasm_component::bindings::__link_custom_section_describing_imports) - - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #__rust_alloc) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048584)) - (let (v4 i32) (call #::alloc v3 v1 v0)) - (br (block 1 v4))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #__rust_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (const.i32 0)) - (let (v6 i32) (const.i32 1048584)) - (let (v7 i32) (call #::alloc v6 v2 v3)) - (let (v8 i1) (eq v7 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2 v7) (block 3))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v22 i32) - (br (block 1 v22))) - - (block 3 - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (bitcast v3)) - (let (v13 i1) (lt v11 v12)) - (let (v14 i32) (sext v13)) - (let (v15 i1) (neq v14 0)) - (let (v16 i32) (select v15 v1 v3)) - (let (v17 u32) (bitcast v7)) - (let (v18 (ptr u8)) (inttoptr v17)) - (let (v19 u32) (bitcast v0)) - (let (v20 (ptr u8)) (inttoptr v19)) - (memcpy v20 v18 v16) - (let (v21 i32) (const.i32 1048584)) - (call #::dealloc v21 v0 v2 v1) - (br (block 2 v7))) - ) - - (func (export #miden:add-package/add-interface@1.0.0#add) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (call #wit_bindgen_rt::run_ctors_once) - (let (v3 i32) (add.wrapping v1 v0)) - (br (block 1 v3))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #wit_bindgen_rt::cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i1) (neq v1 0)) - (condbr v5 (block 4) (block 5))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v19 i32) - (br (block 1 v19))) - - (block 3 (param v17 i32) - (let (v18 i1) (neq v17 0)) - (condbr v18 (block 2 v17) (block 7))) - - (block 4 - (let (v16 i32) (call #__rust_realloc v0 v1 v2 v3)) - (br (block 3 v16))) - - (block 5 - (let (v6 i1) (eq v3 0)) - (let (v7 i32) (zext v6)) - (let (v8 i1) (neq v7 0)) - (condbr v8 (block 2 v2) (block 6))) - - (block 6 - (let (v9 i32) (const.i32 0)) - (let (v10 u32) (bitcast v9)) - (let (v11 u32) (add.checked v10 1048588)) - (let (v12 (ptr u8)) (inttoptr v11)) - (let (v13 u8) (load v12)) - (let (v14 i32) (zext v13)) - (let (v15 i32) (call #__rust_alloc v3 v2)) - (br (block 3 v15))) - - (block 7 - (unreachable)) - ) - - (func (export #wit_bindgen_rt::run_ctors_once) - (block 0 - (let (v0 i32) (const.i32 0)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (add.checked v1 1048589)) - (let (v3 (ptr u8)) (inttoptr v2)) - (let (v4 u8) (load v3)) - (let (v5 i32) (zext v4)) - (let (v6 i1) (neq v5 0)) - (condbr v6 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (call #__wasm_call_ctors) - (let (v7 i32) (const.i32 0)) - (let (v8 i32) (const.i32 1)) - (let (v9 u32) (bitcast v8)) - (let (v10 u8) (trunc v9)) - (let (v11 u32) (bitcast v7)) - (let (v12 u32) (add.checked v11 1048589)) - (let (v13 (ptr u8)) (inttoptr v12)) - (store v13 v10) - (br (block 2))) - ) - - (func (export #wee_alloc::alloc_first_fit) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 u32) (bitcast v2)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr i32)) (inttoptr v5)) - (let (v8 i32) (load v7)) - (let (v9 i1) (neq v8 0)) - (condbr v9 (block 2) (block 3))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 - (let (v11 i32) (const.i32 -1)) - (let (v12 i32) (add.wrapping v1 v11)) - (let (v13 i32) (const.i32 0)) - (let (v14 i32) (sub.wrapping v13 v1)) - (let (v15 i32) (const.i32 2)) - (let (v16 u32) (bitcast v15)) - (let (v17 i32) (shl.wrapping v0 v16)) - (br (block 4 v8 v2 v17 v14 v12))) - - (block 3 - (let (v10 i32) (const.i32 0)) - (ret v10)) - - (block 4 - (param v18 i32) - (param v169 i32) - (param v182 i32) - (param v197 i32) - (param v210 i32) - (let (v19 u32) (bitcast v18)) - (let (v20 u32) (add.checked v19 8)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr i32)) (inttoptr v20)) - (let (v23 i32) (load v22)) - (let (v24 i32) (const.i32 1)) - (let (v25 i32) (band v23 v24)) - (let (v26 i1) (neq v25 0)) - (condbr v26 (block 7) (block 8))) - - (block 5 - (let (v344 i32) (const.i32 0)) - (br (block 1 v344))) - - (block 6 - (param v172 i32) - (param v179 i32) - (param v181 i32) - (param v196 i32) - (param v209 i32) - (param v218 i32) - (param v219 i32) - (let (v173 u32) (bitcast v172)) - (let (v174 u32) (mod.unchecked v173 4)) - (assertz 250 v174) - (let (v175 (ptr i32)) (inttoptr v173)) - (let (v176 i32) (load v175)) - (let (v177 i32) (const.i32 -4)) - (let (v178 i32) (band v176 v177)) - (let (v180 i32) (sub.wrapping v178 v179)) - (let (v188 u32) (bitcast v180)) - (let (v189 u32) (bitcast v181)) - (let (v190 i1) (lt v188 v189)) - (let (v191 i32) (sext v190)) - (let (v192 i1) (neq v191 0)) - (condbr v192 (block 22 v218 v219 v181 v196 v209) (block 23))) - - (block 7 - (br (block 9 v18 v23 v169 v182 v197 v210))) - - (block 8 - (let (v27 i32) (const.i32 8)) - (let (v28 i32) (add.wrapping v18 v27)) - (br (block 6 v18 v28 v182 v197 v210 v169 v23))) - - (block 9 - (param v29 i32) - (param v30 i32) - (param v156 i32) - (param v187 i32) - (param v202 i32) - (param v215 i32) - (let (v31 i32) (const.i32 -2)) - (let (v32 i32) (band v30 v31)) - (let (v33 u32) (bitcast v29)) - (let (v34 u32) (add.checked v33 8)) - (let (v35 u32) (mod.unchecked v34 4)) - (assertz 250 v35) - (let (v36 (ptr i32)) (inttoptr v34)) - (store v36 v32) - (let (v37 u32) (bitcast v29)) - (let (v38 u32) (add.checked v37 4)) - (let (v39 u32) (mod.unchecked v38 4)) - (assertz 250 v39) - (let (v40 (ptr i32)) (inttoptr v38)) - (let (v41 i32) (load v40)) - (let (v42 i32) (const.i32 -4)) - (let (v43 i32) (band v41 v42)) - (let (v44 i1) (neq v43 0)) - (condbr v44 (block 12) (block 13))) - - (block 10 - (let (v170 i32) (const.i32 8)) - (let (v171 i32) (add.wrapping v157 v170)) - (br (block 6 v157 v171 v183 v198 v211 v152 v165))) - - (block 11 - (param v55 i32) - (param v75 i32) - (param v122 i32) - (param v142 i32) - (param v155 i32) - (param v186 i32) - (param v201 i32) - (param v214 i32) - (let (v56 u32) (bitcast v55)) - (let (v57 u32) (mod.unchecked v56 4)) - (assertz 250 v57) - (let (v58 (ptr i32)) (inttoptr v56)) - (let (v59 i32) (load v58)) - (let (v60 i32) (const.i32 -4)) - (let (v61 i32) (band v59 v60)) - (let (v62 i1) (eq v61 0)) - (let (v63 i32) (zext v62)) - (let (v64 i1) (neq v63 0)) - (condbr v64 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 15))) - - (block 12 - (let (v46 i32) (const.i32 0)) - (let (v47 u32) (bitcast v43)) - (let (v48 (ptr u8)) (inttoptr v47)) - (let (v49 u8) (load v48)) - (let (v50 i32) (zext v49)) - (let (v51 i32) (const.i32 1)) - (let (v52 i32) (band v50 v51)) - (let (v53 i1) (neq v52 0)) - (let (v54 i32) (select v53 v46 v43)) - (br (block 11 v29 v43 v41 v54 v156 v187 v202 v215))) - - (block 13 - (let (v45 i32) (const.i32 0)) - (br (block 11 v29 v43 v41 v45 v156 v187 v202 v215))) - - (block 14 - (param v92 i32) - (param v102 i32) - (param v109 i32) - (param v121 i32) - (param v141 i32) - (param v154 i32) - (param v185 i32) - (param v200 i32) - (param v213 i32) - (let (v93 i1) (eq v92 0)) - (let (v94 i32) (zext v93)) - (let (v95 i1) (neq v94 0)) - (condbr v95 (block 17 v109 v121 v102 v141 v154 v185 v200 v213) (block 18))) - - (block 15 - (let (v65 i32) (const.i32 2)) - (let (v66 i32) (band v59 v65)) - (let (v67 i1) (neq v66 0)) - (condbr v67 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 16))) - - (block 16 - (let (v68 u32) (bitcast v61)) - (let (v69 u32) (add.checked v68 4)) - (let (v70 u32) (mod.unchecked v69 4)) - (assertz 250 v70) - (let (v71 (ptr i32)) (inttoptr v69)) - (let (v72 i32) (load v71)) - (let (v73 i32) (const.i32 3)) - (let (v74 i32) (band v72 v73)) - (let (v76 i32) (bor v74 v75)) - (let (v77 u32) (bitcast v61)) - (let (v78 u32) (add.checked v77 4)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v76) - (let (v81 u32) (bitcast v55)) - (let (v82 u32) (add.checked v81 4)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (let (v85 i32) (load v84)) - (let (v86 i32) (const.i32 -4)) - (let (v87 i32) (band v85 v86)) - (let (v88 u32) (bitcast v55)) - (let (v89 u32) (mod.unchecked v88 4)) - (assertz 250 v89) - (let (v90 (ptr i32)) (inttoptr v88)) - (let (v91 i32) (load v90)) - (br (block 14 v87 v91 v55 v85 v142 v155 v186 v201 v214))) - - (block 17 - (param v119 i32) - (param v120 i32) - (param v129 i32) - (param v140 i32) - (param v153 i32) - (param v184 i32) - (param v199 i32) - (param v212 i32) - (let (v123 i32) (const.i32 3)) - (let (v124 i32) (band v120 v123)) - (let (v125 u32) (bitcast v119)) - (let (v126 u32) (add.checked v125 4)) - (let (v127 u32) (mod.unchecked v126 4)) - (assertz 250 v127) - (let (v128 (ptr i32)) (inttoptr v126)) - (store v128 v124) - (let (v130 i32) (const.i32 3)) - (let (v131 i32) (band v129 v130)) - (let (v132 u32) (bitcast v119)) - (let (v133 u32) (mod.unchecked v132 4)) - (assertz 250 v133) - (let (v134 (ptr i32)) (inttoptr v132)) - (store v134 v131) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v129 v135)) - (let (v137 i1) (eq v136 0)) - (let (v138 i32) (zext v137)) - (let (v139 i1) (neq v138 0)) - (condbr v139 (block 19 v153 v140 v184 v199 v212) (block 20))) - - (block 18 - (let (v96 u32) (bitcast v92)) - (let (v97 u32) (mod.unchecked v96 4)) - (assertz 250 v97) - (let (v98 (ptr i32)) (inttoptr v96)) - (let (v99 i32) (load v98)) - (let (v100 i32) (const.i32 3)) - (let (v101 i32) (band v99 v100)) - (let (v103 i32) (const.i32 -4)) - (let (v104 i32) (band v102 v103)) - (let (v105 i32) (bor v101 v104)) - (let (v106 u32) (bitcast v92)) - (let (v107 u32) (mod.unchecked v106 4)) - (assertz 250 v107) - (let (v108 (ptr i32)) (inttoptr v106)) - (store v108 v105) - (let (v110 u32) (bitcast v109)) - (let (v111 u32) (add.checked v110 4)) - (let (v112 u32) (mod.unchecked v111 4)) - (assertz 250 v112) - (let (v113 (ptr i32)) (inttoptr v111)) - (let (v114 i32) (load v113)) - (let (v115 u32) (bitcast v109)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (let (v118 i32) (load v117)) - (br (block 17 v109 v114 v118 v141 v154 v185 v200 v213))) - - (block 19 - (param v152 i32) - (param v157 i32) - (param v183 i32) - (param v198 i32) - (param v211 i32) - (let (v158 u32) (bitcast v152)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v161 u32) (bitcast v157)) - (let (v162 u32) (add.checked v161 8)) - (let (v163 u32) (mod.unchecked v162 4)) - (assertz 250 v163) - (let (v164 (ptr i32)) (inttoptr v162)) - (let (v165 i32) (load v164)) - (let (v166 i32) (const.i32 1)) - (let (v167 i32) (band v165 v166)) - (let (v168 i1) (neq v167 0)) - (condbr v168 (block 9 v157 v165 v152 v183 v198 v211) (block 21))) - - (block 20 - (let (v143 u32) (bitcast v140)) - (let (v144 u32) (mod.unchecked v143 4)) - (assertz 250 v144) - (let (v145 (ptr i32)) (inttoptr v143)) - (let (v146 i32) (load v145)) - (let (v147 i32) (const.i32 2)) - (let (v148 i32) (bor v146 v147)) - (let (v149 u32) (bitcast v140)) - (let (v150 u32) (mod.unchecked v149 4)) - (assertz 250 v150) - (let (v151 (ptr i32)) (inttoptr v149)) - (store v151 v148) - (br (block 19 v153 v140 v184 v199 v212))) - - (block 21 - (br (block 10))) - - (block 22 - (param v335 i32) - (param v336 i32) - (param v341 i32) - (param v342 i32) - (param v343 i32) - (let (v337 u32) (bitcast v335)) - (let (v338 u32) (mod.unchecked v337 4)) - (assertz 250 v338) - (let (v339 (ptr i32)) (inttoptr v337)) - (store v339 v336) - (let (v340 i1) (neq v336 0)) - (condbr v340 (block 4 v336 v335 v341 v342 v343) (block 33))) - - (block 23 - (let (v193 i32) (const.i32 72)) - (let (v194 i32) (add.wrapping v179 v193)) - (let (v195 i32) (sub.wrapping v178 v181)) - (let (v203 i32) (band v195 v196)) - (let (v204 u32) (bitcast v194)) - (let (v205 u32) (bitcast v203)) - (let (v206 i1) (lte v204 v205)) - (let (v207 i32) (sext v206)) - (let (v208 i1) (neq v207 0)) - (condbr v208 (block 25) (block 26))) - - (block 24 (param v326 i32) (param v327 i32) - (let (v328 i32) (const.i32 1)) - (let (v329 i32) (bor v327 v328)) - (let (v330 u32) (bitcast v326)) - (let (v331 u32) (mod.unchecked v330 4)) - (assertz 250 v331) - (let (v332 (ptr i32)) (inttoptr v330)) - (store v332 v329) - (let (v333 i32) (const.i32 8)) - (let (v334 i32) (add.wrapping v326 v333)) - (ret v334)) - - (block 25 - (let (v229 i32) (const.i32 0)) - (let (v230 i32) (const.i32 0)) - (let (v231 u32) (bitcast v203)) - (let (v232 u32) (mod.unchecked v231 4)) - (assertz 250 v232) - (let (v233 (ptr i32)) (inttoptr v231)) - (store v233 v230) - (let (v234 i32) (const.i32 -8)) - (let (v235 i32) (add.wrapping v203 v234)) - (let (v236 i64) (const.i64 0)) - (let (v237 u32) (bitcast v235)) - (let (v238 u32) (mod.unchecked v237 4)) - (assertz 250 v238) - (let (v239 (ptr i64)) (inttoptr v237)) - (store v239 v236) - (let (v240 u32) (bitcast v172)) - (let (v241 u32) (mod.unchecked v240 4)) - (assertz 250 v241) - (let (v242 (ptr i32)) (inttoptr v240)) - (let (v243 i32) (load v242)) - (let (v244 i32) (const.i32 -4)) - (let (v245 i32) (band v243 v244)) - (let (v246 u32) (bitcast v235)) - (let (v247 u32) (mod.unchecked v246 4)) - (assertz 250 v247) - (let (v248 (ptr i32)) (inttoptr v246)) - (store v248 v245) - (let (v249 u32) (bitcast v172)) - (let (v250 u32) (mod.unchecked v249 4)) - (assertz 250 v250) - (let (v251 (ptr i32)) (inttoptr v249)) - (let (v252 i32) (load v251)) - (let (v253 i32) (const.i32 -4)) - (let (v254 i32) (band v252 v253)) - (let (v255 i1) (eq v254 0)) - (let (v256 i32) (zext v255)) - (let (v257 i1) (neq v256 0)) - (condbr v257 (block 28 v235 v229 v172 v179) (block 29))) - - (block 26 - (let (v216 i32) (band v209 v179)) - (let (v217 i1) (neq v216 0)) - (condbr v217 (block 22 v218 v219 v181 v196 v209) (block 27))) - - (block 27 - (let (v220 i32) (const.i32 -4)) - (let (v221 i32) (band v219 v220)) - (let (v222 u32) (bitcast v218)) - (let (v223 u32) (mod.unchecked v222 4)) - (assertz 250 v223) - (let (v224 (ptr i32)) (inttoptr v222)) - (store v224 v221) - (let (v225 u32) (bitcast v172)) - (let (v226 u32) (mod.unchecked v225 4)) - (assertz 250 v226) - (let (v227 (ptr i32)) (inttoptr v225)) - (let (v228 i32) (load v227)) - (br (block 24 v172 v228))) - - (block 28 - (param v280 i32) - (param v281 i32) - (param v282 i32) - (param v288 i32) - (let (v283 i32) (bor v281 v282)) - (let (v284 u32) (bitcast v280)) - (let (v285 u32) (add.checked v284 4)) - (let (v286 u32) (mod.unchecked v285 4)) - (assertz 250 v286) - (let (v287 (ptr i32)) (inttoptr v285)) - (store v287 v283) - (let (v289 u32) (bitcast v288)) - (let (v290 u32) (mod.unchecked v289 4)) - (assertz 250 v290) - (let (v291 (ptr i32)) (inttoptr v289)) - (let (v292 i32) (load v291)) - (let (v293 i32) (const.i32 -2)) - (let (v294 i32) (band v292 v293)) - (let (v295 u32) (bitcast v288)) - (let (v296 u32) (mod.unchecked v295 4)) - (assertz 250 v296) - (let (v297 (ptr i32)) (inttoptr v295)) - (store v297 v294) - (let (v298 u32) (bitcast v282)) - (let (v299 u32) (mod.unchecked v298 4)) - (assertz 250 v299) - (let (v300 (ptr i32)) (inttoptr v298)) - (let (v301 i32) (load v300)) - (let (v302 i32) (const.i32 3)) - (let (v303 i32) (band v301 v302)) - (let (v304 i32) (bor v303 v280)) - (let (v305 u32) (bitcast v282)) - (let (v306 u32) (mod.unchecked v305 4)) - (assertz 250 v306) - (let (v307 (ptr i32)) (inttoptr v305)) - (store v307 v304) - (let (v308 i32) (const.i32 2)) - (let (v309 i32) (band v301 v308)) - (let (v310 i1) (neq v309 0)) - (condbr v310 (block 31) (block 32))) - - (block 29 - (let (v258 i32) (const.i32 2)) - (let (v259 i32) (band v252 v258)) - (let (v260 i1) (neq v259 0)) - (condbr v260 (block 28 v235 v229 v172 v179) (block 30))) - - (block 30 - (let (v261 u32) (bitcast v254)) - (let (v262 u32) (add.checked v261 4)) - (let (v263 u32) (mod.unchecked v262 4)) - (assertz 250 v263) - (let (v264 (ptr i32)) (inttoptr v262)) - (let (v265 i32) (load v264)) - (let (v266 i32) (const.i32 3)) - (let (v267 i32) (band v265 v266)) - (let (v268 i32) (bor v267 v235)) - (let (v269 u32) (bitcast v254)) - (let (v270 u32) (add.checked v269 4)) - (let (v271 u32) (mod.unchecked v270 4)) - (assertz 250 v271) - (let (v272 (ptr i32)) (inttoptr v270)) - (store v272 v268) - (let (v273 u32) (bitcast v235)) - (let (v274 u32) (add.checked v273 4)) - (let (v275 u32) (mod.unchecked v274 4)) - (assertz 250 v275) - (let (v276 (ptr i32)) (inttoptr v274)) - (let (v277 i32) (load v276)) - (let (v278 i32) (const.i32 3)) - (let (v279 i32) (band v277 v278)) - (br (block 28 v235 v279 v172 v179))) - - (block 31 - (let (v315 i32) (const.i32 -3)) - (let (v316 i32) (band v304 v315)) - (let (v317 u32) (bitcast v282)) - (let (v318 u32) (mod.unchecked v317 4)) - (assertz 250 v318) - (let (v319 (ptr i32)) (inttoptr v317)) - (store v319 v316) - (let (v320 u32) (bitcast v280)) - (let (v321 u32) (mod.unchecked v320 4)) - (assertz 250 v321) - (let (v322 (ptr i32)) (inttoptr v320)) - (let (v323 i32) (load v322)) - (let (v324 i32) (const.i32 2)) - (let (v325 i32) (bor v323 v324)) - (br (block 24 v280 v325))) - - (block 32 - (let (v311 u32) (bitcast v280)) - (let (v312 u32) (mod.unchecked v311 4)) - (assertz 250 v312) - (let (v313 (ptr i32)) (inttoptr v311)) - (let (v314 i32) (load v313)) - (br (block 24 v280 v314))) - - (block 33 - (br (block 5))) - ) - - (func (export #::alloc) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 16)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i1) (neq v2 0)) - (condbr v9 (block 3) (block 4))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 (param v98 i32) (param v102 i32) - (let (v99 i32) (const.i32 16)) - (let (v100 i32) (add.wrapping v98 v99)) - (let (v101 (ptr i32)) (global.symbol #__stack_pointer)) - (store v101 v100) - (br (block 1 v102))) - - (block 3 - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr i32)) (inttoptr v10)) - (let (v13 i32) (load v12)) - (let (v14 u32) (bitcast v7)) - (let (v15 u32) (add.checked v14 12)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr i32)) (inttoptr v15)) - (store v17 v13) - (let (v18 i32) (const.i32 3)) - (let (v19 i32) (add.wrapping v2 v18)) - (let (v20 i32) (const.i32 2)) - (let (v21 u32) (bitcast v19)) - (let (v22 u32) (bitcast v20)) - (let (v23 u32) (shr.wrapping v21 v22)) - (let (v24 i32) (bitcast v23)) - (let (v25 i32) (const.i32 12)) - (let (v26 i32) (add.wrapping v7 v25)) - (let (v27 i32) (call #wee_alloc::alloc_first_fit v24 v1 v26)) - (let (v28 i1) (neq v27 0)) - (condbr v28 (block 5 v0 v7 v27) (block 6))) - - (block 4 - (br (block 2 v7 v1))) - - (block 5 (param v88 i32) (param v89 i32) (param v103 i32) - (let (v90 u32) (bitcast v89)) - (let (v91 u32) (add.checked v90 12)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (let (v94 i32) (load v93)) - (let (v95 u32) (bitcast v88)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (store v97 v94) - (br (block 2 v89 v103))) - - (block 6 - (let (v29 i32) (const.i32 -4)) - (let (v30 i32) (band v19 v29)) - (let (v31 i32) (const.i32 3)) - (let (v32 u32) (bitcast v31)) - (let (v33 i32) (shl.wrapping v1 v32)) - (let (v34 i32) (const.i32 512)) - (let (v35 i32) (add.wrapping v33 v34)) - (let (v36 u32) (bitcast v30)) - (let (v37 u32) (bitcast v35)) - (let (v38 i1) (gt v36 v37)) - (let (v39 i32) (sext v38)) - (let (v40 i1) (neq v39 0)) - (let (v41 i32) (select v40 v30 v35)) - (let (v42 i32) (const.i32 65543)) - (let (v43 i32) (add.wrapping v41 v42)) - (let (v44 i32) (const.i32 16)) - (let (v45 u32) (bitcast v43)) - (let (v46 u32) (bitcast v44)) - (let (v47 u32) (shr.wrapping v45 v46)) - (let (v48 i32) (bitcast v47)) - (let (v49 u32) (bitcast v48)) - (let (v50 i32) (memory.grow v49)) - (let (v51 i32) (const.i32 -1)) - (let (v52 i1) (neq v50 v51)) - (let (v53 i32) (zext v52)) - (let (v54 i1) (neq v53 0)) - (condbr v54 (block 7) (block 8))) - - (block 7 - (let (v56 i32) (const.i32 16)) - (let (v57 u32) (bitcast v56)) - (let (v58 i32) (shl.wrapping v50 v57)) - (let (v59 i32) (const.i32 0)) - (let (v60 u32) (bitcast v58)) - (let (v61 u32) (add.checked v60 4)) - (let (v62 u32) (mod.unchecked v61 4)) - (assertz 250 v62) - (let (v63 (ptr i32)) (inttoptr v61)) - (store v63 v59) - (let (v64 u32) (bitcast v7)) - (let (v65 u32) (add.checked v64 12)) - (let (v66 u32) (mod.unchecked v65 4)) - (assertz 250 v66) - (let (v67 (ptr i32)) (inttoptr v65)) - (let (v68 i32) (load v67)) - (let (v69 u32) (bitcast v58)) - (let (v70 u32) (add.checked v69 8)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (store v72 v68) - (let (v73 i32) (const.i32 -65536)) - (let (v74 i32) (band v43 v73)) - (let (v75 i32) (add.wrapping v58 v74)) - (let (v76 i32) (const.i32 2)) - (let (v77 i32) (bor v75 v76)) - (let (v78 u32) (bitcast v58)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v77) - (let (v81 u32) (bitcast v7)) - (let (v82 u32) (add.checked v81 12)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (store v84 v58) - (let (v85 i32) (const.i32 12)) - (let (v86 i32) (add.wrapping v7 v85)) - (let (v87 i32) (call #wee_alloc::alloc_first_fit v24 v1 v86)) - (br (block 5 v0 v7 v87))) - - (block 8 - (let (v55 i32) (const.i32 0)) - (br (block 5 v0 v7 v55))) - ) - - (func (export #::dealloc) - (param i32) (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i1) (eq v1 0)) - (let (v6 i32) (zext v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (let (v8 i1) (eq v3 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2) (block 4))) - - (block 4 - (let (v11 u32) (bitcast v0)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 i32) (const.i32 0)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (mod.unchecked v16 4)) - (assertz 250 v17) - (let (v18 (ptr i32)) (inttoptr v16)) - (store v18 v15) - (let (v19 i32) (const.i32 -8)) - (let (v20 i32) (add.wrapping v1 v19)) - (let (v21 u32) (bitcast v20)) - (let (v22 u32) (mod.unchecked v21 4)) - (assertz 250 v22) - (let (v23 (ptr i32)) (inttoptr v21)) - (let (v24 i32) (load v23)) - (let (v25 i32) (const.i32 -2)) - (let (v26 i32) (band v24 v25)) - (let (v27 u32) (bitcast v20)) - (let (v28 u32) (mod.unchecked v27 4)) - (assertz 250 v28) - (let (v29 (ptr i32)) (inttoptr v27)) - (store v29 v26) - (let (v30 i32) (const.i32 -4)) - (let (v31 i32) (add.wrapping v1 v30)) - (let (v32 u32) (bitcast v31)) - (let (v33 u32) (mod.unchecked v32 4)) - (assertz 250 v33) - (let (v34 (ptr i32)) (inttoptr v32)) - (let (v35 i32) (load v34)) - (let (v36 i32) (const.i32 -4)) - (let (v37 i32) (band v35 v36)) - (let (v38 i1) (eq v37 0)) - (let (v39 i32) (zext v38)) - (let (v40 i1) (neq v39 0)) - (condbr v40 (block 8 v24 v1 v20 v14 v0) (block 9))) - - (block 5 (param v177 i32) (param v183 i32) - (let (v185 u32) (bitcast v177)) - (let (v186 u32) (mod.unchecked v185 4)) - (assertz 250 v186) - (let (v187 (ptr i32)) (inttoptr v185)) - (store v187 v183) - (br (block 2))) - - (block 6 - (param v172 i32) - (param v173 i32) - (param v182 i32) - (param v184 i32) - (let (v174 u32) (bitcast v172)) - (let (v175 u32) (mod.unchecked v174 4)) - (assertz 250 v175) - (let (v176 (ptr i32)) (inttoptr v174)) - (store v176 v173) - (br (block 5 v182 v184))) - - (block 7 (param v168 i32) (param v178 i32) - (br (block 5 v178 v168))) - - (block 8 - (param v134 i32) - (param v150 i32) - (param v161 i32) - (param v171 i32) - (param v181 i32) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v134 v135)) - (let (v137 i1) (neq v136 0)) - (condbr v137 (block 6 v150 v171 v181 v161) (block 18))) - - (block 9 - (let (v41 u32) (bitcast v37)) - (let (v42 u32) (mod.unchecked v41 4)) - (assertz 250 v42) - (let (v43 (ptr i32)) (inttoptr v41)) - (let (v44 i32) (load v43)) - (let (v45 i32) (const.i32 1)) - (let (v46 i32) (band v44 v45)) - (let (v47 i1) (neq v46 0)) - (condbr v47 (block 8 v24 v1 v20 v14 v0) (block 10))) - - (block 10 - (let (v48 i32) (const.i32 -4)) - (let (v49 i32) (band v24 v48)) - (let (v50 i1) (neq v49 0)) - (condbr v50 (block 13) (block 14))) - - (block 11 - (param v104 i32) - (param v105 i32) - (param v111 i32) - (param v112 i32) - (param v123 i32) - (param v169 i32) - (param v179 i32) - (let (v106 i32) (const.i32 3)) - (let (v107 i32) (band v105 v106)) - (let (v108 u32) (bitcast v104)) - (let (v109 u32) (mod.unchecked v108 4)) - (assertz 250 v109) - (let (v110 (ptr i32)) (inttoptr v108)) - (store v110 v107) - (let (v113 i32) (const.i32 3)) - (let (v114 i32) (band v112 v113)) - (let (v115 u32) (bitcast v111)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (store v117 v114) - (let (v118 i32) (const.i32 2)) - (let (v119 i32) (band v112 v118)) - (let (v120 i1) (eq v119 0)) - (let (v121 i32) (zext v120)) - (let (v122 i1) (neq v121 0)) - (condbr v122 (block 7 v169 v179) (block 17))) - - (block 12 - (param v83 i32) - (param v84 i32) - (param v87 i32) - (param v94 i32) - (param v99 i32) - (param v124 i32) - (param v170 i32) - (param v180 i32) - (let (v85 i32) (const.i32 -4)) - (let (v86 i32) (band v84 v85)) - (let (v88 i32) (const.i32 3)) - (let (v89 i32) (band v87 v88)) - (let (v90 i32) (bor v86 v89)) - (let (v91 u32) (bitcast v83)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (store v93 v90) - (let (v95 u32) (bitcast v94)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (let (v98 i32) (load v97)) - (let (v100 u32) (bitcast v99)) - (let (v101 u32) (mod.unchecked v100 4)) - (assertz 250 v101) - (let (v102 (ptr i32)) (inttoptr v100)) - (let (v103 i32) (load v102)) - (br (block 11 v94 v98 v99 v103 v124 v170 v180))) - - (block 13 - (let (v51 i32) (const.i32 2)) - (let (v52 i32) (band v24 v51)) - (let (v53 i1) (neq v52 0)) - (condbr v53 (block 12 v37 v26 v44 v31 v20 v37 v14 v0) (block 15))) - - (block 14 - (br (block 12 v37 v26 v44 v31 v20 v37 v14 v0))) - - (block 15 - (let (v54 u32) (bitcast v49)) - (let (v55 u32) (add.checked v54 4)) - (let (v56 u32) (mod.unchecked v55 4)) - (assertz 250 v56) - (let (v57 (ptr i32)) (inttoptr v55)) - (let (v58 i32) (load v57)) - (let (v59 i32) (const.i32 3)) - (let (v60 i32) (band v58 v59)) - (let (v61 i32) (bor v60 v37)) - (let (v62 u32) (bitcast v49)) - (let (v63 u32) (add.checked v62 4)) - (let (v64 u32) (mod.unchecked v63 4)) - (assertz 250 v64) - (let (v65 (ptr i32)) (inttoptr v63)) - (store v65 v61) - (let (v66 u32) (bitcast v20)) - (let (v67 u32) (mod.unchecked v66 4)) - (assertz 250 v67) - (let (v68 (ptr i32)) (inttoptr v66)) - (let (v69 i32) (load v68)) - (let (v70 u32) (bitcast v31)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (let (v73 i32) (load v72)) - (let (v74 i32) (const.i32 -4)) - (let (v75 i32) (band v73 v74)) - (let (v76 i1) (eq v75 0)) - (let (v77 i32) (zext v76)) - (let (v78 i1) (neq v77 0)) - (condbr v78 (block 11 v31 v73 v20 v69 v37 v14 v0) (block 16))) - - (block 16 - (let (v79 u32) (bitcast v75)) - (let (v80 u32) (mod.unchecked v79 4)) - (assertz 250 v80) - (let (v81 (ptr i32)) (inttoptr v79)) - (let (v82 i32) (load v81)) - (br (block 12 v75 v69 v82 v31 v20 v37 v14 v0))) - - (block 17 - (let (v125 u32) (bitcast v123)) - (let (v126 u32) (mod.unchecked v125 4)) - (assertz 250 v126) - (let (v127 (ptr i32)) (inttoptr v125)) - (let (v128 i32) (load v127)) - (let (v129 i32) (const.i32 2)) - (let (v130 i32) (bor v128 v129)) - (let (v131 u32) (bitcast v123)) - (let (v132 u32) (mod.unchecked v131 4)) - (assertz 250 v132) - (let (v133 (ptr i32)) (inttoptr v131)) - (store v133 v130) - (br (block 7 v169 v179))) - - (block 18 - (let (v138 i32) (const.i32 -4)) - (let (v139 i32) (band v134 v138)) - (let (v140 i1) (eq v139 0)) - (let (v141 i32) (zext v140)) - (let (v142 i1) (neq v141 0)) - (condbr v142 (block 6 v150 v171 v181 v161) (block 19))) - - (block 19 - (let (v143 u32) (bitcast v139)) - (let (v144 (ptr u8)) (inttoptr v143)) - (let (v145 u8) (load v144)) - (let (v146 i32) (zext v145)) - (let (v147 i32) (const.i32 1)) - (let (v148 i32) (band v146 v147)) - (let (v149 i1) (neq v148 0)) - (condbr v149 (block 6 v150 v171 v181 v161) (block 20))) - - (block 20 - (let (v151 u32) (bitcast v139)) - (let (v152 u32) (add.checked v151 8)) - (let (v153 u32) (mod.unchecked v152 4)) - (assertz 250 v153) - (let (v154 (ptr i32)) (inttoptr v152)) - (let (v155 i32) (load v154)) - (let (v156 i32) (const.i32 -4)) - (let (v157 i32) (band v155 v156)) - (let (v158 u32) (bitcast v150)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v162 i32) (const.i32 1)) - (let (v163 i32) (bor v161 v162)) - (let (v164 u32) (bitcast v139)) - (let (v165 u32) (add.checked v164 8)) - (let (v166 u32) (mod.unchecked v165 4)) - (assertz 250 v166) - (let (v167 (ptr i32)) (inttoptr v165)) - (store v167 v163) - (br (block 7 v171 v181))) - ) - - (func (export #cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (call #wit_bindgen_rt::cabi_realloc v0 v1 v2 v3)) - (br (block 1 v5))) - - (block 1 (param v4 i32) - (ret v4)) - ) - ) - -) diff --git a/tests/integration/expected/components/add_wasm_component.wat b/tests/integration/expected/components/add_wasm_component.wat deleted file mode 100644 index 218186cbd..000000000 --- a/tests/integration/expected/components/add_wasm_component.wat +++ /dev/null @@ -1,700 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func)) - (type (;1;) (func (param i32 i32) (result i32))) - (type (;2;) (func (param i32 i32 i32 i32) (result i32))) - (type (;3;) (func (param i32 i32 i32) (result i32))) - (type (;4;) (func (param i32 i32 i32 i32))) - (func $__wasm_call_ctors (;0;) (type 0)) - (func $add_wasm_component::bindings::__link_custom_section_describing_imports (;1;) (type 0)) - (func $__rust_alloc (;2;) (type 1) (param i32 i32) (result i32) - i32.const 1048584 - local.get 1 - local.get 0 - call $::alloc - ) - (func $__rust_realloc (;3;) (type 2) (param i32 i32 i32 i32) (result i32) - (local i32) - block ;; label = @1 - i32.const 1048584 - local.get 2 - local.get 3 - call $::alloc - local.tee 4 - i32.eqz - br_if 0 (;@1;) - local.get 4 - local.get 0 - local.get 1 - local.get 3 - local.get 1 - local.get 3 - i32.lt_u - select - memory.copy - i32.const 1048584 - local.get 0 - local.get 2 - local.get 1 - call $::dealloc - end - local.get 4 - ) - (func $miden:add-package/add-interface@1.0.0#add (;4;) (type 1) (param i32 i32) (result i32) - call $wit_bindgen_rt::run_ctors_once - local.get 1 - local.get 0 - i32.add - ) - (func $wit_bindgen_rt::cabi_realloc (;5;) (type 2) (param i32 i32 i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 1 - br_if 0 (;@3;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - i32.const 0 - i32.load8_u offset=1048588 - drop - local.get 3 - local.get 2 - call $__rust_alloc - local.set 2 - br 1 (;@2;) - end - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $__rust_realloc - local.set 2 - end - local.get 2 - br_if 0 (;@1;) - unreachable - end - local.get 2 - ) - (func $wit_bindgen_rt::run_ctors_once (;6;) (type 0) - block ;; label = @1 - i32.const 0 - i32.load8_u offset=1048589 - br_if 0 (;@1;) - call $__wasm_call_ctors - i32.const 0 - i32.const 1 - i32.store8 offset=1048589 - end - ) - (func $wee_alloc::alloc_first_fit (;7;) (type 3) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 2 - i32.load - local.tee 3 - br_if 0 (;@1;) - i32.const 0 - return - end - local.get 1 - i32.const -1 - i32.add - local.set 4 - i32.const 0 - local.get 1 - i32.sub - local.set 5 - local.get 0 - i32.const 2 - i32.shl - local.set 6 - loop ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - local.get 3 - i32.const 8 - i32.add - local.set 0 - br 1 (;@2;) - end - loop ;; label = @3 - local.get 3 - local.get 1 - i32.const -2 - i32.and - i32.store offset=8 - block ;; label = @4 - block ;; label = @5 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.tee 0 - br_if 0 (;@5;) - i32.const 0 - local.set 8 - br 1 (;@4;) - end - i32.const 0 - local.get 0 - local.get 0 - i32.load8_u - i32.const 1 - i32.and - select - local.set 8 - end - block ;; label = @4 - local.get 3 - i32.load - local.tee 1 - i32.const -4 - i32.and - local.tee 9 - i32.eqz - br_if 0 (;@4;) - local.get 1 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 9 - local.get 9 - i32.load offset=4 - i32.const 3 - i32.and - local.get 0 - i32.or - i32.store offset=4 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.set 0 - local.get 3 - i32.load - local.set 1 - end - block ;; label = @4 - local.get 0 - i32.eqz - br_if 0 (;@4;) - local.get 0 - local.get 0 - i32.load - i32.const 3 - i32.and - local.get 1 - i32.const -4 - i32.and - i32.or - i32.store - local.get 3 - i32.load offset=4 - local.set 7 - local.get 3 - i32.load - local.set 1 - end - local.get 3 - local.get 7 - i32.const 3 - i32.and - i32.store offset=4 - local.get 3 - local.get 1 - i32.const 3 - i32.and - i32.store - block ;; label = @4 - local.get 1 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - end - local.get 2 - local.get 8 - i32.store - local.get 8 - local.set 3 - local.get 8 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - end - local.get 8 - i32.const 8 - i32.add - local.set 0 - local.get 8 - local.set 3 - end - block ;; label = @2 - local.get 3 - i32.load - i32.const -4 - i32.and - local.tee 8 - local.get 0 - i32.sub - local.get 6 - i32.lt_u - br_if 0 (;@2;) - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.const 72 - i32.add - local.get 8 - local.get 6 - i32.sub - local.get 5 - i32.and - local.tee 8 - i32.le_u - br_if 0 (;@4;) - local.get 4 - local.get 0 - i32.and - br_if 2 (;@2;) - local.get 2 - local.get 1 - i32.const -4 - i32.and - i32.store - local.get 3 - i32.load - local.set 0 - local.get 3 - local.set 1 - br 1 (;@3;) - end - i32.const 0 - local.set 7 - local.get 8 - i32.const 0 - i32.store - local.get 8 - i32.const -8 - i32.add - local.tee 1 - i64.const 0 - i64.store align=4 - local.get 1 - local.get 3 - i32.load - i32.const -4 - i32.and - i32.store - block ;; label = @4 - local.get 3 - i32.load - local.tee 9 - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@4;) - local.get 9 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load offset=4 - i32.const 3 - i32.and - local.get 1 - i32.or - i32.store offset=4 - local.get 1 - i32.load offset=4 - i32.const 3 - i32.and - local.set 7 - end - local.get 1 - local.get 7 - local.get 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 0 - i32.load - i32.const -2 - i32.and - i32.store - local.get 3 - local.get 3 - i32.load - local.tee 0 - i32.const 3 - i32.and - local.get 1 - i32.or - local.tee 8 - i32.store - block ;; label = @4 - local.get 0 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 1 - i32.load - local.set 0 - br 1 (;@3;) - end - local.get 3 - local.get 8 - i32.const -3 - i32.and - i32.store - local.get 1 - i32.load - i32.const 2 - i32.or - local.set 0 - end - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store - local.get 1 - i32.const 8 - i32.add - return - end - local.get 2 - local.get 1 - i32.store - local.get 1 - local.set 3 - local.get 1 - br_if 0 (;@1;) - end - i32.const 0 - ) - (func $::alloc (;8;) (type 3) (param i32 i32 i32) (result i32) - (local i32 i32 i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 3 - global.set $__stack_pointer - block ;; label = @1 - block ;; label = @2 - local.get 2 - br_if 0 (;@2;) - local.get 1 - local.set 2 - br 1 (;@1;) - end - local.get 3 - local.get 0 - i32.load - i32.store offset=12 - block ;; label = @2 - local.get 2 - i32.const 3 - i32.add - local.tee 4 - i32.const 2 - i32.shr_u - local.tee 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.tee 2 - br_if 0 (;@2;) - block ;; label = @3 - local.get 4 - i32.const -4 - i32.and - local.tee 2 - local.get 1 - i32.const 3 - i32.shl - i32.const 512 - i32.add - local.tee 4 - local.get 2 - local.get 4 - i32.gt_u - select - i32.const 65543 - i32.add - local.tee 4 - i32.const 16 - i32.shr_u - memory.grow - local.tee 2 - i32.const -1 - i32.ne - br_if 0 (;@3;) - i32.const 0 - local.set 2 - br 1 (;@2;) - end - local.get 2 - i32.const 16 - i32.shl - local.tee 2 - i32.const 0 - i32.store offset=4 - local.get 2 - local.get 3 - i32.load offset=12 - i32.store offset=8 - local.get 2 - local.get 2 - local.get 4 - i32.const -65536 - i32.and - i32.add - i32.const 2 - i32.or - i32.store - local.get 3 - local.get 2 - i32.store offset=12 - local.get 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.set 2 - end - local.get 0 - local.get 3 - i32.load offset=12 - i32.store - end - local.get 3 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 2 - ) - (func $::dealloc (;9;) (type 4) (param i32 i32 i32 i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 1 - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.eqz - br_if 0 (;@1;) - local.get 0 - i32.load - local.set 4 - local.get 1 - i32.const 0 - i32.store - local.get 1 - i32.const -8 - i32.add - local.tee 3 - local.get 3 - i32.load - local.tee 5 - i32.const -2 - i32.and - local.tee 6 - i32.store - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - local.get 1 - i32.const -4 - i32.add - local.tee 7 - i32.load - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@5;) - local.get 8 - i32.load - local.tee 9 - i32.const 1 - i32.and - br_if 0 (;@5;) - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - local.get 5 - i32.const -4 - i32.and - local.tee 10 - br_if 0 (;@8;) - local.get 8 - local.set 1 - br 1 (;@7;) - end - local.get 8 - local.set 1 - local.get 5 - i32.const 2 - i32.and - br_if 0 (;@7;) - local.get 10 - local.get 10 - i32.load offset=4 - i32.const 3 - i32.and - local.get 8 - i32.or - i32.store offset=4 - local.get 3 - i32.load - local.set 6 - local.get 7 - i32.load - local.tee 5 - i32.const -4 - i32.and - local.tee 1 - i32.eqz - br_if 1 (;@6;) - local.get 1 - i32.load - local.set 9 - end - local.get 1 - local.get 6 - i32.const -4 - i32.and - local.get 9 - i32.const 3 - i32.and - i32.or - i32.store - local.get 7 - i32.load - local.set 5 - local.get 3 - i32.load - local.set 6 - end - local.get 7 - local.get 5 - i32.const 3 - i32.and - i32.store - local.get 3 - local.get 6 - i32.const 3 - i32.and - i32.store - local.get 6 - i32.const 2 - i32.and - i32.eqz - br_if 1 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - br 1 (;@4;) - end - local.get 5 - i32.const 2 - i32.and - br_if 1 (;@3;) - local.get 5 - i32.const -4 - i32.and - local.tee 5 - i32.eqz - br_if 1 (;@3;) - local.get 5 - i32.load8_u - i32.const 1 - i32.and - br_if 1 (;@3;) - local.get 1 - local.get 5 - i32.load offset=8 - i32.const -4 - i32.and - i32.store - local.get 5 - local.get 3 - i32.const 1 - i32.or - i32.store offset=8 - end - local.get 4 - local.set 3 - br 1 (;@2;) - end - local.get 1 - local.get 4 - i32.store - end - local.get 0 - local.get 3 - i32.store - end - ) - (func $cabi_realloc (;10;) (type 2) (param i32 i32 i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $wit_bindgen_rt::cabi_realloc - ) - (table (;0;) 3 3 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "miden:add-package/add-interface@1.0.0#add" (func $miden:add-package/add-interface@1.0.0#add)) - (export "cabi_realloc" (func $cabi_realloc)) - (export "cabi_realloc_wit_bindgen_0_28_0" (func $wit_bindgen_rt::cabi_realloc)) - (elem (;0;) (i32.const 1) func $add_wasm_component::bindings::__link_custom_section_describing_imports $cabi_realloc) - (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00\02\00\00\00") - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (type (;0;) (func (param "a" u32) (param "b" u32) (result u32))) - (alias core export 0 "miden:add-package/add-interface@1.0.0#add" (core func (;1;))) - (func (;0;) (type 0) (canon lift (core func 1))) - (component (;0;) - (type (;0;) (func (param "a" u32) (param "b" u32) (result u32))) - (import "import-func-add" (func (;0;) (type 0))) - (type (;1;) (func (param "a" u32) (param "b" u32) (result u32))) - (export (;1;) "add" (func 0) (func (type 1))) - ) - (instance (;0;) (instantiate 0 - (with "import-func-add" (func 0)) - ) - ) - (export (;1;) "miden:add-package/add-interface@1.0.0" (instance 0)) -) \ No newline at end of file diff --git a/tests/integration/expected/components/adder_wasm_component.wat b/tests/integration/expected/components/adder_wasm_component.wat deleted file mode 100644 index f62be4cde..000000000 --- a/tests/integration/expected/components/adder_wasm_component.wat +++ /dev/null @@ -1,6103 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func)) - (type (;1;) (func (param i32 i32) (result i32))) - (type (;2;) (func (param i32 i32 i32 i32) (result i32))) - (type (;3;) (func (param i32) (result i32))) - (type (;4;) (func (param i32))) - (type (;5;) (func (param i32 i32))) - (type (;6;) (func (param i32 i32 i32) (result i32))) - (func $__wasm_call_ctors (;0;) (type 0)) - (func $add (;1;) (type 1) (param i32 i32) (result i32) - call $wit_bindgen::rt::run_ctors_once - local.get 1 - local.get 0 - i32.add - ) - (func $__rust_alloc (;2;) (type 1) (param i32 i32) (result i32) - (local i32) - local.get 0 - local.get 1 - call $__rdl_alloc - local.set 2 - local.get 2 - return - ) - (func $__rust_realloc (;3;) (type 2) (param i32 i32 i32 i32) (result i32) - (local i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $__rdl_realloc - local.set 4 - local.get 4 - return - ) - (func $wit_bindgen::rt::run_ctors_once (;4;) (type 0) - block ;; label = @1 - i32.const 0 - i32.load8_u offset=1048577 - br_if 0 (;@1;) - call $__wasm_call_ctors - i32.const 0 - i32.const 1 - i32.store8 offset=1048577 - end - ) - (func $cabi_realloc (;5;) (type 2) (param i32 i32 i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 1 - br_if 0 (;@3;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - i32.const 0 - i32.load8_u offset=1048576 - drop - local.get 3 - local.get 2 - call $__rust_alloc - local.set 2 - br 1 (;@2;) - end - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $__rust_realloc - local.set 2 - end - local.get 2 - br_if 0 (;@1;) - unreachable - unreachable - end - local.get 2 - ) - (func $__rdl_alloc (;6;) (type 1) (param i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - local.get 1 - i32.const 8 - i32.gt_u - br_if 0 (;@2;) - local.get 1 - local.get 0 - i32.le_u - br_if 1 (;@1;) - end - local.get 1 - local.get 0 - call $aligned_alloc - return - end - local.get 0 - call $malloc - ) - (func $__rdl_realloc (;7;) (type 2) (param i32 i32 i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - local.get 2 - i32.const 8 - i32.gt_u - br_if 0 (;@2;) - local.get 2 - local.get 3 - i32.le_u - br_if 1 (;@1;) - end - block ;; label = @2 - local.get 2 - local.get 3 - call $aligned_alloc - local.tee 2 - br_if 0 (;@2;) - i32.const 0 - return - end - local.get 2 - local.get 0 - local.get 1 - local.get 3 - local.get 1 - local.get 3 - i32.lt_u - select - call $memcpy - local.set 3 - local.get 0 - call $free - local.get 3 - return - end - local.get 0 - local.get 3 - call $realloc - ) - (func $malloc (;8;) (type 3) (param i32) (result i32) - local.get 0 - call $dlmalloc - ) - (func $dlmalloc (;9;) (type 3) (param i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 1 - global.set $__stack_pointer - block ;; label = @1 - i32.const 0 - i32.load offset=1048604 - local.tee 2 - br_if 0 (;@1;) - block ;; label = @2 - block ;; label = @3 - i32.const 0 - i32.load offset=1049052 - local.tee 3 - i32.eqz - br_if 0 (;@3;) - i32.const 0 - i32.load offset=1049056 - local.set 4 - br 1 (;@2;) - end - i32.const 0 - i64.const -1 - i64.store offset=1049064 align=4 - i32.const 0 - i64.const 281474976776192 - i64.store offset=1049056 align=4 - i32.const 0 - local.get 1 - i32.const 8 - i32.add - i32.const -16 - i32.and - i32.const 1431655768 - i32.xor - local.tee 3 - i32.store offset=1049052 - i32.const 0 - i32.const 0 - i32.store offset=1049072 - i32.const 0 - i32.const 0 - i32.store offset=1049024 - i32.const 65536 - local.set 4 - end - i32.const 0 - local.set 2 - i32.const 1114112 - i32.const 1049088 - local.get 4 - i32.add - i32.const -1 - i32.add - i32.const 0 - local.get 4 - i32.sub - i32.and - i32.const 1114112 - select - i32.const 1049088 - i32.sub - local.tee 5 - i32.const 89 - i32.lt_u - br_if 0 (;@1;) - i32.const 0 - local.set 4 - i32.const 0 - local.get 5 - i32.store offset=1049032 - i32.const 0 - i32.const 1049088 - i32.store offset=1049028 - i32.const 0 - i32.const 1049088 - i32.store offset=1048596 - i32.const 0 - local.get 3 - i32.store offset=1048616 - i32.const 0 - i32.const -1 - i32.store offset=1048612 - loop ;; label = @2 - local.get 4 - i32.const 1048640 - i32.add - local.get 4 - i32.const 1048628 - i32.add - local.tee 3 - i32.store - local.get 3 - local.get 4 - i32.const 1048620 - i32.add - local.tee 6 - i32.store - local.get 4 - i32.const 1048632 - i32.add - local.get 6 - i32.store - local.get 4 - i32.const 1048648 - i32.add - local.get 4 - i32.const 1048636 - i32.add - local.tee 6 - i32.store - local.get 6 - local.get 3 - i32.store - local.get 4 - i32.const 1048656 - i32.add - local.get 4 - i32.const 1048644 - i32.add - local.tee 3 - i32.store - local.get 3 - local.get 6 - i32.store - local.get 4 - i32.const 1048652 - i32.add - local.get 3 - i32.store - local.get 4 - i32.const 32 - i32.add - local.tee 4 - i32.const 256 - i32.ne - br_if 0 (;@2;) - end - i32.const 1049088 - i32.const -8 - i32.const 1049088 - i32.sub - i32.const 15 - i32.and - i32.const 0 - i32.const 1049088 - i32.const 8 - i32.add - i32.const 15 - i32.and - select - local.tee 4 - i32.add - local.tee 2 - i32.const 4 - i32.add - local.get 5 - i32.const -56 - i32.add - local.tee 3 - local.get 4 - i32.sub - local.tee 4 - i32.const 1 - i32.or - i32.store - i32.const 0 - i32.const 0 - i32.load offset=1049068 - i32.store offset=1048608 - i32.const 0 - local.get 4 - i32.store offset=1048592 - i32.const 0 - local.get 2 - i32.store offset=1048604 - i32.const 1049088 - local.get 3 - i32.add - i32.const 56 - i32.store offset=4 - end - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - block ;; label = @9 - block ;; label = @10 - block ;; label = @11 - block ;; label = @12 - local.get 0 - i32.const 236 - i32.gt_u - br_if 0 (;@12;) - block ;; label = @13 - i32.const 0 - i32.load offset=1048580 - local.tee 7 - i32.const 16 - local.get 0 - i32.const 19 - i32.add - i32.const -16 - i32.and - local.get 0 - i32.const 11 - i32.lt_u - select - local.tee 5 - i32.const 3 - i32.shr_u - local.tee 3 - i32.shr_u - local.tee 4 - i32.const 3 - i32.and - i32.eqz - br_if 0 (;@13;) - block ;; label = @14 - block ;; label = @15 - local.get 4 - i32.const 1 - i32.and - local.get 3 - i32.or - i32.const 1 - i32.xor - local.tee 6 - i32.const 3 - i32.shl - local.tee 3 - i32.const 1048620 - i32.add - local.tee 4 - local.get 3 - i32.const 1048628 - i32.add - i32.load - local.tee 3 - i32.load offset=8 - local.tee 5 - i32.ne - br_if 0 (;@15;) - i32.const 0 - local.get 7 - i32.const -2 - local.get 6 - i32.rotl - i32.and - i32.store offset=1048580 - br 1 (;@14;) - end - local.get 4 - local.get 5 - i32.store offset=8 - local.get 5 - local.get 4 - i32.store offset=12 - end - local.get 3 - i32.const 8 - i32.add - local.set 4 - local.get 3 - local.get 6 - i32.const 3 - i32.shl - local.tee 6 - i32.const 3 - i32.or - i32.store offset=4 - local.get 3 - local.get 6 - i32.add - local.tee 3 - local.get 3 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - br 12 (;@1;) - end - local.get 5 - i32.const 0 - i32.load offset=1048588 - local.tee 8 - i32.le_u - br_if 1 (;@11;) - block ;; label = @13 - local.get 4 - i32.eqz - br_if 0 (;@13;) - block ;; label = @14 - block ;; label = @15 - local.get 4 - local.get 3 - i32.shl - i32.const 2 - local.get 3 - i32.shl - local.tee 4 - i32.const 0 - local.get 4 - i32.sub - i32.or - i32.and - local.tee 4 - i32.const 0 - local.get 4 - i32.sub - i32.and - i32.ctz - local.tee 3 - i32.const 3 - i32.shl - local.tee 4 - i32.const 1048620 - i32.add - local.tee 6 - local.get 4 - i32.const 1048628 - i32.add - i32.load - local.tee 4 - i32.load offset=8 - local.tee 0 - i32.ne - br_if 0 (;@15;) - i32.const 0 - local.get 7 - i32.const -2 - local.get 3 - i32.rotl - i32.and - local.tee 7 - i32.store offset=1048580 - br 1 (;@14;) - end - local.get 6 - local.get 0 - i32.store offset=8 - local.get 0 - local.get 6 - i32.store offset=12 - end - local.get 4 - local.get 5 - i32.const 3 - i32.or - i32.store offset=4 - local.get 4 - local.get 3 - i32.const 3 - i32.shl - local.tee 3 - i32.add - local.get 3 - local.get 5 - i32.sub - local.tee 6 - i32.store - local.get 4 - local.get 5 - i32.add - local.tee 0 - local.get 6 - i32.const 1 - i32.or - i32.store offset=4 - block ;; label = @14 - local.get 8 - i32.eqz - br_if 0 (;@14;) - local.get 8 - i32.const -8 - i32.and - i32.const 1048620 - i32.add - local.set 5 - i32.const 0 - i32.load offset=1048600 - local.set 3 - block ;; label = @15 - block ;; label = @16 - local.get 7 - i32.const 1 - local.get 8 - i32.const 3 - i32.shr_u - i32.shl - local.tee 9 - i32.and - br_if 0 (;@16;) - i32.const 0 - local.get 7 - local.get 9 - i32.or - i32.store offset=1048580 - local.get 5 - local.set 9 - br 1 (;@15;) - end - local.get 5 - i32.load offset=8 - local.set 9 - end - local.get 9 - local.get 3 - i32.store offset=12 - local.get 5 - local.get 3 - i32.store offset=8 - local.get 3 - local.get 5 - i32.store offset=12 - local.get 3 - local.get 9 - i32.store offset=8 - end - local.get 4 - i32.const 8 - i32.add - local.set 4 - i32.const 0 - local.get 0 - i32.store offset=1048600 - i32.const 0 - local.get 6 - i32.store offset=1048588 - br 12 (;@1;) - end - i32.const 0 - i32.load offset=1048584 - local.tee 10 - i32.eqz - br_if 1 (;@11;) - local.get 10 - i32.const 0 - local.get 10 - i32.sub - i32.and - i32.ctz - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - i32.load - local.tee 0 - i32.load offset=4 - i32.const -8 - i32.and - local.get 5 - i32.sub - local.set 3 - local.get 0 - local.set 6 - block ;; label = @13 - loop ;; label = @14 - block ;; label = @15 - local.get 6 - i32.load offset=16 - local.tee 4 - br_if 0 (;@15;) - local.get 6 - i32.const 20 - i32.add - i32.load - local.tee 4 - i32.eqz - br_if 2 (;@13;) - end - local.get 4 - i32.load offset=4 - i32.const -8 - i32.and - local.get 5 - i32.sub - local.tee 6 - local.get 3 - local.get 6 - local.get 3 - i32.lt_u - local.tee 6 - select - local.set 3 - local.get 4 - local.get 0 - local.get 6 - select - local.set 0 - local.get 4 - local.set 6 - br 0 (;@14;) - end - end - local.get 0 - i32.load offset=24 - local.set 11 - block ;; label = @13 - local.get 0 - i32.load offset=12 - local.tee 9 - local.get 0 - i32.eq - br_if 0 (;@13;) - local.get 0 - i32.load offset=8 - local.tee 4 - i32.const 0 - i32.load offset=1048596 - i32.lt_u - drop - local.get 9 - local.get 4 - i32.store offset=8 - local.get 4 - local.get 9 - i32.store offset=12 - br 11 (;@2;) - end - block ;; label = @13 - local.get 0 - i32.const 20 - i32.add - local.tee 6 - i32.load - local.tee 4 - br_if 0 (;@13;) - local.get 0 - i32.load offset=16 - local.tee 4 - i32.eqz - br_if 3 (;@10;) - local.get 0 - i32.const 16 - i32.add - local.set 6 - end - loop ;; label = @13 - local.get 6 - local.set 2 - local.get 4 - local.tee 9 - i32.const 20 - i32.add - local.tee 6 - i32.load - local.tee 4 - br_if 0 (;@13;) - local.get 9 - i32.const 16 - i32.add - local.set 6 - local.get 9 - i32.load offset=16 - local.tee 4 - br_if 0 (;@13;) - end - local.get 2 - i32.const 0 - i32.store - br 10 (;@2;) - end - i32.const -1 - local.set 5 - local.get 0 - i32.const -65 - i32.gt_u - br_if 0 (;@11;) - local.get 0 - i32.const 19 - i32.add - local.tee 4 - i32.const -16 - i32.and - local.set 5 - i32.const 0 - i32.load offset=1048584 - local.tee 10 - i32.eqz - br_if 0 (;@11;) - i32.const 0 - local.set 8 - block ;; label = @12 - local.get 5 - i32.const 256 - i32.lt_u - br_if 0 (;@12;) - i32.const 31 - local.set 8 - local.get 5 - i32.const 16777215 - i32.gt_u - br_if 0 (;@12;) - local.get 5 - i32.const 38 - local.get 4 - i32.const 8 - i32.shr_u - i32.clz - local.tee 4 - i32.sub - i32.shr_u - i32.const 1 - i32.and - local.get 4 - i32.const 1 - i32.shl - i32.sub - i32.const 62 - i32.add - local.set 8 - end - i32.const 0 - local.get 5 - i32.sub - local.set 3 - block ;; label = @12 - block ;; label = @13 - block ;; label = @14 - block ;; label = @15 - local.get 8 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - i32.load - local.tee 6 - br_if 0 (;@15;) - i32.const 0 - local.set 4 - i32.const 0 - local.set 9 - br 1 (;@14;) - end - i32.const 0 - local.set 4 - local.get 5 - i32.const 0 - i32.const 25 - local.get 8 - i32.const 1 - i32.shr_u - i32.sub - local.get 8 - i32.const 31 - i32.eq - select - i32.shl - local.set 0 - i32.const 0 - local.set 9 - loop ;; label = @15 - block ;; label = @16 - local.get 6 - i32.load offset=4 - i32.const -8 - i32.and - local.get 5 - i32.sub - local.tee 7 - local.get 3 - i32.ge_u - br_if 0 (;@16;) - local.get 7 - local.set 3 - local.get 6 - local.set 9 - local.get 7 - br_if 0 (;@16;) - i32.const 0 - local.set 3 - local.get 6 - local.set 9 - local.get 6 - local.set 4 - br 3 (;@13;) - end - local.get 4 - local.get 6 - i32.const 20 - i32.add - i32.load - local.tee 7 - local.get 7 - local.get 6 - local.get 0 - i32.const 29 - i32.shr_u - i32.const 4 - i32.and - i32.add - i32.const 16 - i32.add - i32.load - local.tee 6 - i32.eq - select - local.get 4 - local.get 7 - select - local.set 4 - local.get 0 - i32.const 1 - i32.shl - local.set 0 - local.get 6 - br_if 0 (;@15;) - end - end - block ;; label = @14 - local.get 4 - local.get 9 - i32.or - br_if 0 (;@14;) - i32.const 0 - local.set 9 - i32.const 2 - local.get 8 - i32.shl - local.tee 4 - i32.const 0 - local.get 4 - i32.sub - i32.or - local.get 10 - i32.and - local.tee 4 - i32.eqz - br_if 3 (;@11;) - local.get 4 - i32.const 0 - local.get 4 - i32.sub - i32.and - i32.ctz - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - i32.load - local.set 4 - end - local.get 4 - i32.eqz - br_if 1 (;@12;) - end - loop ;; label = @13 - local.get 4 - i32.load offset=4 - i32.const -8 - i32.and - local.get 5 - i32.sub - local.tee 7 - local.get 3 - i32.lt_u - local.set 0 - block ;; label = @14 - local.get 4 - i32.load offset=16 - local.tee 6 - br_if 0 (;@14;) - local.get 4 - i32.const 20 - i32.add - i32.load - local.set 6 - end - local.get 7 - local.get 3 - local.get 0 - select - local.set 3 - local.get 4 - local.get 9 - local.get 0 - select - local.set 9 - local.get 6 - local.set 4 - local.get 6 - br_if 0 (;@13;) - end - end - local.get 9 - i32.eqz - br_if 0 (;@11;) - local.get 3 - i32.const 0 - i32.load offset=1048588 - local.get 5 - i32.sub - i32.ge_u - br_if 0 (;@11;) - local.get 9 - i32.load offset=24 - local.set 2 - block ;; label = @12 - local.get 9 - i32.load offset=12 - local.tee 0 - local.get 9 - i32.eq - br_if 0 (;@12;) - local.get 9 - i32.load offset=8 - local.tee 4 - i32.const 0 - i32.load offset=1048596 - i32.lt_u - drop - local.get 0 - local.get 4 - i32.store offset=8 - local.get 4 - local.get 0 - i32.store offset=12 - br 9 (;@3;) - end - block ;; label = @12 - local.get 9 - i32.const 20 - i32.add - local.tee 6 - i32.load - local.tee 4 - br_if 0 (;@12;) - local.get 9 - i32.load offset=16 - local.tee 4 - i32.eqz - br_if 3 (;@9;) - local.get 9 - i32.const 16 - i32.add - local.set 6 - end - loop ;; label = @12 - local.get 6 - local.set 7 - local.get 4 - local.tee 0 - i32.const 20 - i32.add - local.tee 6 - i32.load - local.tee 4 - br_if 0 (;@12;) - local.get 0 - i32.const 16 - i32.add - local.set 6 - local.get 0 - i32.load offset=16 - local.tee 4 - br_if 0 (;@12;) - end - local.get 7 - i32.const 0 - i32.store - br 8 (;@3;) - end - block ;; label = @11 - i32.const 0 - i32.load offset=1048588 - local.tee 4 - local.get 5 - i32.lt_u - br_if 0 (;@11;) - i32.const 0 - i32.load offset=1048600 - local.set 3 - block ;; label = @12 - block ;; label = @13 - local.get 4 - local.get 5 - i32.sub - local.tee 6 - i32.const 16 - i32.lt_u - br_if 0 (;@13;) - local.get 3 - local.get 5 - i32.add - local.tee 0 - local.get 6 - i32.const 1 - i32.or - i32.store offset=4 - local.get 3 - local.get 4 - i32.add - local.get 6 - i32.store - local.get 3 - local.get 5 - i32.const 3 - i32.or - i32.store offset=4 - br 1 (;@12;) - end - local.get 3 - local.get 4 - i32.const 3 - i32.or - i32.store offset=4 - local.get 3 - local.get 4 - i32.add - local.tee 4 - local.get 4 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - i32.const 0 - local.set 0 - i32.const 0 - local.set 6 - end - i32.const 0 - local.get 6 - i32.store offset=1048588 - i32.const 0 - local.get 0 - i32.store offset=1048600 - local.get 3 - i32.const 8 - i32.add - local.set 4 - br 10 (;@1;) - end - block ;; label = @11 - i32.const 0 - i32.load offset=1048592 - local.tee 6 - local.get 5 - i32.le_u - br_if 0 (;@11;) - local.get 2 - local.get 5 - i32.add - local.tee 4 - local.get 6 - local.get 5 - i32.sub - local.tee 3 - i32.const 1 - i32.or - i32.store offset=4 - i32.const 0 - local.get 4 - i32.store offset=1048604 - i32.const 0 - local.get 3 - i32.store offset=1048592 - local.get 2 - local.get 5 - i32.const 3 - i32.or - i32.store offset=4 - local.get 2 - i32.const 8 - i32.add - local.set 4 - br 10 (;@1;) - end - block ;; label = @11 - block ;; label = @12 - i32.const 0 - i32.load offset=1049052 - i32.eqz - br_if 0 (;@12;) - i32.const 0 - i32.load offset=1049060 - local.set 3 - br 1 (;@11;) - end - i32.const 0 - i64.const -1 - i64.store offset=1049064 align=4 - i32.const 0 - i64.const 281474976776192 - i64.store offset=1049056 align=4 - i32.const 0 - local.get 1 - i32.const 12 - i32.add - i32.const -16 - i32.and - i32.const 1431655768 - i32.xor - i32.store offset=1049052 - i32.const 0 - i32.const 0 - i32.store offset=1049072 - i32.const 0 - i32.const 0 - i32.store offset=1049024 - i32.const 65536 - local.set 3 - end - i32.const 0 - local.set 4 - block ;; label = @11 - local.get 3 - local.get 5 - i32.const 71 - i32.add - local.tee 8 - i32.add - local.tee 0 - i32.const 0 - local.get 3 - i32.sub - local.tee 7 - i32.and - local.tee 9 - local.get 5 - i32.gt_u - br_if 0 (;@11;) - i32.const 0 - i32.const 48 - i32.store offset=1049076 - br 10 (;@1;) - end - block ;; label = @11 - i32.const 0 - i32.load offset=1049020 - local.tee 4 - i32.eqz - br_if 0 (;@11;) - block ;; label = @12 - i32.const 0 - i32.load offset=1049012 - local.tee 3 - local.get 9 - i32.add - local.tee 10 - local.get 3 - i32.le_u - br_if 0 (;@12;) - local.get 10 - local.get 4 - i32.le_u - br_if 1 (;@11;) - end - i32.const 0 - local.set 4 - i32.const 0 - i32.const 48 - i32.store offset=1049076 - br 10 (;@1;) - end - i32.const 0 - i32.load8_u offset=1049024 - i32.const 4 - i32.and - br_if 4 (;@6;) - block ;; label = @11 - block ;; label = @12 - block ;; label = @13 - local.get 2 - i32.eqz - br_if 0 (;@13;) - i32.const 1049028 - local.set 4 - loop ;; label = @14 - block ;; label = @15 - local.get 4 - i32.load - local.tee 3 - local.get 2 - i32.gt_u - br_if 0 (;@15;) - local.get 3 - local.get 4 - i32.load offset=4 - i32.add - local.get 2 - i32.gt_u - br_if 3 (;@12;) - end - local.get 4 - i32.load offset=8 - local.tee 4 - br_if 0 (;@14;) - end - end - i32.const 0 - call $sbrk - local.tee 0 - i32.const -1 - i32.eq - br_if 5 (;@7;) - local.get 9 - local.set 7 - block ;; label = @13 - i32.const 0 - i32.load offset=1049056 - local.tee 4 - i32.const -1 - i32.add - local.tee 3 - local.get 0 - i32.and - i32.eqz - br_if 0 (;@13;) - local.get 9 - local.get 0 - i32.sub - local.get 3 - local.get 0 - i32.add - i32.const 0 - local.get 4 - i32.sub - i32.and - i32.add - local.set 7 - end - local.get 7 - local.get 5 - i32.le_u - br_if 5 (;@7;) - local.get 7 - i32.const 2147483646 - i32.gt_u - br_if 5 (;@7;) - block ;; label = @13 - i32.const 0 - i32.load offset=1049020 - local.tee 4 - i32.eqz - br_if 0 (;@13;) - i32.const 0 - i32.load offset=1049012 - local.tee 3 - local.get 7 - i32.add - local.tee 6 - local.get 3 - i32.le_u - br_if 6 (;@7;) - local.get 6 - local.get 4 - i32.gt_u - br_if 6 (;@7;) - end - local.get 7 - call $sbrk - local.tee 4 - local.get 0 - i32.ne - br_if 1 (;@11;) - br 7 (;@5;) - end - local.get 0 - local.get 6 - i32.sub - local.get 7 - i32.and - local.tee 7 - i32.const 2147483646 - i32.gt_u - br_if 4 (;@7;) - local.get 7 - call $sbrk - local.tee 0 - local.get 4 - i32.load - local.get 4 - i32.load offset=4 - i32.add - i32.eq - br_if 3 (;@8;) - local.get 0 - local.set 4 - end - block ;; label = @11 - local.get 4 - i32.const -1 - i32.eq - br_if 0 (;@11;) - local.get 5 - i32.const 72 - i32.add - local.get 7 - i32.le_u - br_if 0 (;@11;) - block ;; label = @12 - local.get 8 - local.get 7 - i32.sub - i32.const 0 - i32.load offset=1049060 - local.tee 3 - i32.add - i32.const 0 - local.get 3 - i32.sub - i32.and - local.tee 3 - i32.const 2147483646 - i32.le_u - br_if 0 (;@12;) - local.get 4 - local.set 0 - br 7 (;@5;) - end - block ;; label = @12 - local.get 3 - call $sbrk - i32.const -1 - i32.eq - br_if 0 (;@12;) - local.get 3 - local.get 7 - i32.add - local.set 7 - local.get 4 - local.set 0 - br 7 (;@5;) - end - i32.const 0 - local.get 7 - i32.sub - call $sbrk - drop - br 4 (;@7;) - end - local.get 4 - local.set 0 - local.get 4 - i32.const -1 - i32.ne - br_if 5 (;@5;) - br 3 (;@7;) - end - i32.const 0 - local.set 9 - br 7 (;@2;) - end - i32.const 0 - local.set 0 - br 5 (;@3;) - end - local.get 0 - i32.const -1 - i32.ne - br_if 2 (;@5;) - end - i32.const 0 - i32.const 0 - i32.load offset=1049024 - i32.const 4 - i32.or - i32.store offset=1049024 - end - local.get 9 - i32.const 2147483646 - i32.gt_u - br_if 1 (;@4;) - local.get 9 - call $sbrk - local.set 0 - i32.const 0 - call $sbrk - local.set 4 - local.get 0 - i32.const -1 - i32.eq - br_if 1 (;@4;) - local.get 4 - i32.const -1 - i32.eq - br_if 1 (;@4;) - local.get 0 - local.get 4 - i32.ge_u - br_if 1 (;@4;) - local.get 4 - local.get 0 - i32.sub - local.tee 7 - local.get 5 - i32.const 56 - i32.add - i32.le_u - br_if 1 (;@4;) - end - i32.const 0 - i32.const 0 - i32.load offset=1049012 - local.get 7 - i32.add - local.tee 4 - i32.store offset=1049012 - block ;; label = @5 - local.get 4 - i32.const 0 - i32.load offset=1049016 - i32.le_u - br_if 0 (;@5;) - i32.const 0 - local.get 4 - i32.store offset=1049016 - end - block ;; label = @5 - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - i32.const 0 - i32.load offset=1048604 - local.tee 3 - i32.eqz - br_if 0 (;@8;) - i32.const 1049028 - local.set 4 - loop ;; label = @9 - local.get 0 - local.get 4 - i32.load - local.tee 6 - local.get 4 - i32.load offset=4 - local.tee 9 - i32.add - i32.eq - br_if 2 (;@7;) - local.get 4 - i32.load offset=8 - local.tee 4 - br_if 0 (;@9;) - br 3 (;@6;) - end - end - block ;; label = @8 - block ;; label = @9 - i32.const 0 - i32.load offset=1048596 - local.tee 4 - i32.eqz - br_if 0 (;@9;) - local.get 0 - local.get 4 - i32.ge_u - br_if 1 (;@8;) - end - i32.const 0 - local.get 0 - i32.store offset=1048596 - end - i32.const 0 - local.set 4 - i32.const 0 - local.get 7 - i32.store offset=1049032 - i32.const 0 - local.get 0 - i32.store offset=1049028 - i32.const 0 - i32.const -1 - i32.store offset=1048612 - i32.const 0 - i32.const 0 - i32.load offset=1049052 - i32.store offset=1048616 - i32.const 0 - i32.const 0 - i32.store offset=1049040 - loop ;; label = @8 - local.get 4 - i32.const 1048640 - i32.add - local.get 4 - i32.const 1048628 - i32.add - local.tee 3 - i32.store - local.get 3 - local.get 4 - i32.const 1048620 - i32.add - local.tee 6 - i32.store - local.get 4 - i32.const 1048632 - i32.add - local.get 6 - i32.store - local.get 4 - i32.const 1048648 - i32.add - local.get 4 - i32.const 1048636 - i32.add - local.tee 6 - i32.store - local.get 6 - local.get 3 - i32.store - local.get 4 - i32.const 1048656 - i32.add - local.get 4 - i32.const 1048644 - i32.add - local.tee 3 - i32.store - local.get 3 - local.get 6 - i32.store - local.get 4 - i32.const 1048652 - i32.add - local.get 3 - i32.store - local.get 4 - i32.const 32 - i32.add - local.tee 4 - i32.const 256 - i32.ne - br_if 0 (;@8;) - end - local.get 0 - i32.const -8 - local.get 0 - i32.sub - i32.const 15 - i32.and - i32.const 0 - local.get 0 - i32.const 8 - i32.add - i32.const 15 - i32.and - select - local.tee 4 - i32.add - local.tee 3 - local.get 7 - i32.const -56 - i32.add - local.tee 6 - local.get 4 - i32.sub - local.tee 4 - i32.const 1 - i32.or - i32.store offset=4 - i32.const 0 - i32.const 0 - i32.load offset=1049068 - i32.store offset=1048608 - i32.const 0 - local.get 4 - i32.store offset=1048592 - i32.const 0 - local.get 3 - i32.store offset=1048604 - local.get 0 - local.get 6 - i32.add - i32.const 56 - i32.store offset=4 - br 2 (;@5;) - end - local.get 4 - i32.load8_u offset=12 - i32.const 8 - i32.and - br_if 0 (;@6;) - local.get 3 - local.get 6 - i32.lt_u - br_if 0 (;@6;) - local.get 3 - local.get 0 - i32.ge_u - br_if 0 (;@6;) - local.get 3 - i32.const -8 - local.get 3 - i32.sub - i32.const 15 - i32.and - i32.const 0 - local.get 3 - i32.const 8 - i32.add - i32.const 15 - i32.and - select - local.tee 6 - i32.add - local.tee 0 - i32.const 0 - i32.load offset=1048592 - local.get 7 - i32.add - local.tee 2 - local.get 6 - i32.sub - local.tee 6 - i32.const 1 - i32.or - i32.store offset=4 - local.get 4 - local.get 9 - local.get 7 - i32.add - i32.store offset=4 - i32.const 0 - i32.const 0 - i32.load offset=1049068 - i32.store offset=1048608 - i32.const 0 - local.get 6 - i32.store offset=1048592 - i32.const 0 - local.get 0 - i32.store offset=1048604 - local.get 3 - local.get 2 - i32.add - i32.const 56 - i32.store offset=4 - br 1 (;@5;) - end - block ;; label = @6 - local.get 0 - i32.const 0 - i32.load offset=1048596 - local.tee 9 - i32.ge_u - br_if 0 (;@6;) - i32.const 0 - local.get 0 - i32.store offset=1048596 - local.get 0 - local.set 9 - end - local.get 0 - local.get 7 - i32.add - local.set 6 - i32.const 1049028 - local.set 4 - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - block ;; label = @9 - block ;; label = @10 - block ;; label = @11 - block ;; label = @12 - loop ;; label = @13 - local.get 4 - i32.load - local.get 6 - i32.eq - br_if 1 (;@12;) - local.get 4 - i32.load offset=8 - local.tee 4 - br_if 0 (;@13;) - br 2 (;@11;) - end - end - local.get 4 - i32.load8_u offset=12 - i32.const 8 - i32.and - i32.eqz - br_if 1 (;@10;) - end - i32.const 1049028 - local.set 4 - loop ;; label = @11 - block ;; label = @12 - local.get 4 - i32.load - local.tee 6 - local.get 3 - i32.gt_u - br_if 0 (;@12;) - local.get 6 - local.get 4 - i32.load offset=4 - i32.add - local.tee 6 - local.get 3 - i32.gt_u - br_if 3 (;@9;) - end - local.get 4 - i32.load offset=8 - local.set 4 - br 0 (;@11;) - end - end - local.get 4 - local.get 0 - i32.store - local.get 4 - local.get 4 - i32.load offset=4 - local.get 7 - i32.add - i32.store offset=4 - local.get 0 - i32.const -8 - local.get 0 - i32.sub - i32.const 15 - i32.and - i32.const 0 - local.get 0 - i32.const 8 - i32.add - i32.const 15 - i32.and - select - i32.add - local.tee 2 - local.get 5 - i32.const 3 - i32.or - i32.store offset=4 - local.get 6 - i32.const -8 - local.get 6 - i32.sub - i32.const 15 - i32.and - i32.const 0 - local.get 6 - i32.const 8 - i32.add - i32.const 15 - i32.and - select - i32.add - local.tee 7 - local.get 2 - local.get 5 - i32.add - local.tee 5 - i32.sub - local.set 4 - block ;; label = @10 - local.get 7 - local.get 3 - i32.ne - br_if 0 (;@10;) - i32.const 0 - local.get 5 - i32.store offset=1048604 - i32.const 0 - i32.const 0 - i32.load offset=1048592 - local.get 4 - i32.add - local.tee 4 - i32.store offset=1048592 - local.get 5 - local.get 4 - i32.const 1 - i32.or - i32.store offset=4 - br 3 (;@7;) - end - block ;; label = @10 - local.get 7 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 0 (;@10;) - i32.const 0 - local.get 5 - i32.store offset=1048600 - i32.const 0 - i32.const 0 - i32.load offset=1048588 - local.get 4 - i32.add - local.tee 4 - i32.store offset=1048588 - local.get 5 - local.get 4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 5 - local.get 4 - i32.add - local.get 4 - i32.store - br 3 (;@7;) - end - block ;; label = @10 - local.get 7 - i32.load offset=4 - local.tee 3 - i32.const 3 - i32.and - i32.const 1 - i32.ne - br_if 0 (;@10;) - local.get 3 - i32.const -8 - i32.and - local.set 8 - block ;; label = @11 - block ;; label = @12 - local.get 3 - i32.const 255 - i32.gt_u - br_if 0 (;@12;) - local.get 7 - i32.load offset=8 - local.tee 6 - local.get 3 - i32.const 3 - i32.shr_u - local.tee 9 - i32.const 3 - i32.shl - i32.const 1048620 - i32.add - local.tee 0 - i32.eq - drop - block ;; label = @13 - local.get 7 - i32.load offset=12 - local.tee 3 - local.get 6 - i32.ne - br_if 0 (;@13;) - i32.const 0 - i32.const 0 - i32.load offset=1048580 - i32.const -2 - local.get 9 - i32.rotl - i32.and - i32.store offset=1048580 - br 2 (;@11;) - end - local.get 3 - local.get 0 - i32.eq - drop - local.get 3 - local.get 6 - i32.store offset=8 - local.get 6 - local.get 3 - i32.store offset=12 - br 1 (;@11;) - end - local.get 7 - i32.load offset=24 - local.set 10 - block ;; label = @12 - block ;; label = @13 - local.get 7 - i32.load offset=12 - local.tee 0 - local.get 7 - i32.eq - br_if 0 (;@13;) - local.get 7 - i32.load offset=8 - local.tee 3 - local.get 9 - i32.lt_u - drop - local.get 0 - local.get 3 - i32.store offset=8 - local.get 3 - local.get 0 - i32.store offset=12 - br 1 (;@12;) - end - block ;; label = @13 - local.get 7 - i32.const 20 - i32.add - local.tee 3 - i32.load - local.tee 6 - br_if 0 (;@13;) - local.get 7 - i32.const 16 - i32.add - local.tee 3 - i32.load - local.tee 6 - br_if 0 (;@13;) - i32.const 0 - local.set 0 - br 1 (;@12;) - end - loop ;; label = @13 - local.get 3 - local.set 9 - local.get 6 - local.tee 0 - i32.const 20 - i32.add - local.tee 3 - i32.load - local.tee 6 - br_if 0 (;@13;) - local.get 0 - i32.const 16 - i32.add - local.set 3 - local.get 0 - i32.load offset=16 - local.tee 6 - br_if 0 (;@13;) - end - local.get 9 - i32.const 0 - i32.store - end - local.get 10 - i32.eqz - br_if 0 (;@11;) - block ;; label = @12 - block ;; label = @13 - local.get 7 - local.get 7 - i32.load offset=28 - local.tee 6 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 3 - i32.load - i32.ne - br_if 0 (;@13;) - local.get 3 - local.get 0 - i32.store - local.get 0 - br_if 1 (;@12;) - i32.const 0 - i32.const 0 - i32.load offset=1048584 - i32.const -2 - local.get 6 - i32.rotl - i32.and - i32.store offset=1048584 - br 2 (;@11;) - end - local.get 10 - i32.const 16 - i32.const 20 - local.get 10 - i32.load offset=16 - local.get 7 - i32.eq - select - i32.add - local.get 0 - i32.store - local.get 0 - i32.eqz - br_if 1 (;@11;) - end - local.get 0 - local.get 10 - i32.store offset=24 - block ;; label = @12 - local.get 7 - i32.load offset=16 - local.tee 3 - i32.eqz - br_if 0 (;@12;) - local.get 0 - local.get 3 - i32.store offset=16 - local.get 3 - local.get 0 - i32.store offset=24 - end - local.get 7 - i32.load offset=20 - local.tee 3 - i32.eqz - br_if 0 (;@11;) - local.get 0 - i32.const 20 - i32.add - local.get 3 - i32.store - local.get 3 - local.get 0 - i32.store offset=24 - end - local.get 8 - local.get 4 - i32.add - local.set 4 - local.get 7 - local.get 8 - i32.add - local.tee 7 - i32.load offset=4 - local.set 3 - end - local.get 7 - local.get 3 - i32.const -2 - i32.and - i32.store offset=4 - local.get 5 - local.get 4 - i32.add - local.get 4 - i32.store - local.get 5 - local.get 4 - i32.const 1 - i32.or - i32.store offset=4 - block ;; label = @10 - local.get 4 - i32.const 255 - i32.gt_u - br_if 0 (;@10;) - local.get 4 - i32.const -8 - i32.and - i32.const 1048620 - i32.add - local.set 3 - block ;; label = @11 - block ;; label = @12 - i32.const 0 - i32.load offset=1048580 - local.tee 6 - i32.const 1 - local.get 4 - i32.const 3 - i32.shr_u - i32.shl - local.tee 4 - i32.and - br_if 0 (;@12;) - i32.const 0 - local.get 6 - local.get 4 - i32.or - i32.store offset=1048580 - local.get 3 - local.set 4 - br 1 (;@11;) - end - local.get 3 - i32.load offset=8 - local.set 4 - end - local.get 4 - local.get 5 - i32.store offset=12 - local.get 3 - local.get 5 - i32.store offset=8 - local.get 5 - local.get 3 - i32.store offset=12 - local.get 5 - local.get 4 - i32.store offset=8 - br 3 (;@7;) - end - i32.const 31 - local.set 3 - block ;; label = @10 - local.get 4 - i32.const 16777215 - i32.gt_u - br_if 0 (;@10;) - local.get 4 - i32.const 38 - local.get 4 - i32.const 8 - i32.shr_u - i32.clz - local.tee 3 - i32.sub - i32.shr_u - i32.const 1 - i32.and - local.get 3 - i32.const 1 - i32.shl - i32.sub - i32.const 62 - i32.add - local.set 3 - end - local.get 5 - local.get 3 - i32.store offset=28 - local.get 5 - i64.const 0 - i64.store offset=16 align=4 - local.get 3 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.set 6 - block ;; label = @10 - i32.const 0 - i32.load offset=1048584 - local.tee 0 - i32.const 1 - local.get 3 - i32.shl - local.tee 9 - i32.and - br_if 0 (;@10;) - local.get 6 - local.get 5 - i32.store - i32.const 0 - local.get 0 - local.get 9 - i32.or - i32.store offset=1048584 - local.get 5 - local.get 6 - i32.store offset=24 - local.get 5 - local.get 5 - i32.store offset=8 - local.get 5 - local.get 5 - i32.store offset=12 - br 3 (;@7;) - end - local.get 4 - i32.const 0 - i32.const 25 - local.get 3 - i32.const 1 - i32.shr_u - i32.sub - local.get 3 - i32.const 31 - i32.eq - select - i32.shl - local.set 3 - local.get 6 - i32.load - local.set 0 - loop ;; label = @10 - local.get 0 - local.tee 6 - i32.load offset=4 - i32.const -8 - i32.and - local.get 4 - i32.eq - br_if 2 (;@8;) - local.get 3 - i32.const 29 - i32.shr_u - local.set 0 - local.get 3 - i32.const 1 - i32.shl - local.set 3 - local.get 6 - local.get 0 - i32.const 4 - i32.and - i32.add - i32.const 16 - i32.add - local.tee 9 - i32.load - local.tee 0 - br_if 0 (;@10;) - end - local.get 9 - local.get 5 - i32.store - local.get 5 - local.get 6 - i32.store offset=24 - local.get 5 - local.get 5 - i32.store offset=12 - local.get 5 - local.get 5 - i32.store offset=8 - br 2 (;@7;) - end - local.get 0 - i32.const -8 - local.get 0 - i32.sub - i32.const 15 - i32.and - i32.const 0 - local.get 0 - i32.const 8 - i32.add - i32.const 15 - i32.and - select - local.tee 4 - i32.add - local.tee 2 - local.get 7 - i32.const -56 - i32.add - local.tee 9 - local.get 4 - i32.sub - local.tee 4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - local.get 9 - i32.add - i32.const 56 - i32.store offset=4 - local.get 3 - local.get 6 - i32.const 55 - local.get 6 - i32.sub - i32.const 15 - i32.and - i32.const 0 - local.get 6 - i32.const -55 - i32.add - i32.const 15 - i32.and - select - i32.add - i32.const -63 - i32.add - local.tee 9 - local.get 9 - local.get 3 - i32.const 16 - i32.add - i32.lt_u - select - local.tee 9 - i32.const 35 - i32.store offset=4 - i32.const 0 - i32.const 0 - i32.load offset=1049068 - i32.store offset=1048608 - i32.const 0 - local.get 4 - i32.store offset=1048592 - i32.const 0 - local.get 2 - i32.store offset=1048604 - local.get 9 - i32.const 16 - i32.add - i32.const 0 - i64.load offset=1049036 align=4 - i64.store align=4 - local.get 9 - i32.const 0 - i64.load offset=1049028 align=4 - i64.store offset=8 align=4 - i32.const 0 - local.get 9 - i32.const 8 - i32.add - i32.store offset=1049036 - i32.const 0 - local.get 7 - i32.store offset=1049032 - i32.const 0 - local.get 0 - i32.store offset=1049028 - i32.const 0 - i32.const 0 - i32.store offset=1049040 - local.get 9 - i32.const 36 - i32.add - local.set 4 - loop ;; label = @9 - local.get 4 - i32.const 7 - i32.store - local.get 4 - i32.const 4 - i32.add - local.tee 4 - local.get 6 - i32.lt_u - br_if 0 (;@9;) - end - local.get 9 - local.get 3 - i32.eq - br_if 3 (;@5;) - local.get 9 - local.get 9 - i32.load offset=4 - i32.const -2 - i32.and - i32.store offset=4 - local.get 9 - local.get 9 - local.get 3 - i32.sub - local.tee 0 - i32.store - local.get 3 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - block ;; label = @9 - local.get 0 - i32.const 255 - i32.gt_u - br_if 0 (;@9;) - local.get 0 - i32.const -8 - i32.and - i32.const 1048620 - i32.add - local.set 4 - block ;; label = @10 - block ;; label = @11 - i32.const 0 - i32.load offset=1048580 - local.tee 6 - i32.const 1 - local.get 0 - i32.const 3 - i32.shr_u - i32.shl - local.tee 0 - i32.and - br_if 0 (;@11;) - i32.const 0 - local.get 6 - local.get 0 - i32.or - i32.store offset=1048580 - local.get 4 - local.set 6 - br 1 (;@10;) - end - local.get 4 - i32.load offset=8 - local.set 6 - end - local.get 6 - local.get 3 - i32.store offset=12 - local.get 4 - local.get 3 - i32.store offset=8 - local.get 3 - local.get 4 - i32.store offset=12 - local.get 3 - local.get 6 - i32.store offset=8 - br 4 (;@5;) - end - i32.const 31 - local.set 4 - block ;; label = @9 - local.get 0 - i32.const 16777215 - i32.gt_u - br_if 0 (;@9;) - local.get 0 - i32.const 38 - local.get 0 - i32.const 8 - i32.shr_u - i32.clz - local.tee 4 - i32.sub - i32.shr_u - i32.const 1 - i32.and - local.get 4 - i32.const 1 - i32.shl - i32.sub - i32.const 62 - i32.add - local.set 4 - end - local.get 3 - local.get 4 - i32.store offset=28 - local.get 3 - i64.const 0 - i64.store offset=16 align=4 - local.get 4 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.set 6 - block ;; label = @9 - i32.const 0 - i32.load offset=1048584 - local.tee 9 - i32.const 1 - local.get 4 - i32.shl - local.tee 7 - i32.and - br_if 0 (;@9;) - local.get 6 - local.get 3 - i32.store - i32.const 0 - local.get 9 - local.get 7 - i32.or - i32.store offset=1048584 - local.get 3 - local.get 6 - i32.store offset=24 - local.get 3 - local.get 3 - i32.store offset=8 - local.get 3 - local.get 3 - i32.store offset=12 - br 4 (;@5;) - end - local.get 0 - i32.const 0 - i32.const 25 - local.get 4 - i32.const 1 - i32.shr_u - i32.sub - local.get 4 - i32.const 31 - i32.eq - select - i32.shl - local.set 4 - local.get 6 - i32.load - local.set 9 - loop ;; label = @9 - local.get 9 - local.tee 6 - i32.load offset=4 - i32.const -8 - i32.and - local.get 0 - i32.eq - br_if 3 (;@6;) - local.get 4 - i32.const 29 - i32.shr_u - local.set 9 - local.get 4 - i32.const 1 - i32.shl - local.set 4 - local.get 6 - local.get 9 - i32.const 4 - i32.and - i32.add - i32.const 16 - i32.add - local.tee 7 - i32.load - local.tee 9 - br_if 0 (;@9;) - end - local.get 7 - local.get 3 - i32.store - local.get 3 - local.get 6 - i32.store offset=24 - local.get 3 - local.get 3 - i32.store offset=12 - local.get 3 - local.get 3 - i32.store offset=8 - br 3 (;@5;) - end - local.get 6 - i32.load offset=8 - local.tee 4 - local.get 5 - i32.store offset=12 - local.get 6 - local.get 5 - i32.store offset=8 - local.get 5 - i32.const 0 - i32.store offset=24 - local.get 5 - local.get 6 - i32.store offset=12 - local.get 5 - local.get 4 - i32.store offset=8 - end - local.get 2 - i32.const 8 - i32.add - local.set 4 - br 5 (;@1;) - end - local.get 6 - i32.load offset=8 - local.tee 4 - local.get 3 - i32.store offset=12 - local.get 6 - local.get 3 - i32.store offset=8 - local.get 3 - i32.const 0 - i32.store offset=24 - local.get 3 - local.get 6 - i32.store offset=12 - local.get 3 - local.get 4 - i32.store offset=8 - end - i32.const 0 - i32.load offset=1048592 - local.tee 4 - local.get 5 - i32.le_u - br_if 0 (;@4;) - i32.const 0 - i32.load offset=1048604 - local.tee 3 - local.get 5 - i32.add - local.tee 6 - local.get 4 - local.get 5 - i32.sub - local.tee 4 - i32.const 1 - i32.or - i32.store offset=4 - i32.const 0 - local.get 4 - i32.store offset=1048592 - i32.const 0 - local.get 6 - i32.store offset=1048604 - local.get 3 - local.get 5 - i32.const 3 - i32.or - i32.store offset=4 - local.get 3 - i32.const 8 - i32.add - local.set 4 - br 3 (;@1;) - end - i32.const 0 - local.set 4 - i32.const 0 - i32.const 48 - i32.store offset=1049076 - br 2 (;@1;) - end - block ;; label = @3 - local.get 2 - i32.eqz - br_if 0 (;@3;) - block ;; label = @4 - block ;; label = @5 - local.get 9 - local.get 9 - i32.load offset=28 - local.tee 6 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 4 - i32.load - i32.ne - br_if 0 (;@5;) - local.get 4 - local.get 0 - i32.store - local.get 0 - br_if 1 (;@4;) - i32.const 0 - local.get 10 - i32.const -2 - local.get 6 - i32.rotl - i32.and - local.tee 10 - i32.store offset=1048584 - br 2 (;@3;) - end - local.get 2 - i32.const 16 - i32.const 20 - local.get 2 - i32.load offset=16 - local.get 9 - i32.eq - select - i32.add - local.get 0 - i32.store - local.get 0 - i32.eqz - br_if 1 (;@3;) - end - local.get 0 - local.get 2 - i32.store offset=24 - block ;; label = @4 - local.get 9 - i32.load offset=16 - local.tee 4 - i32.eqz - br_if 0 (;@4;) - local.get 0 - local.get 4 - i32.store offset=16 - local.get 4 - local.get 0 - i32.store offset=24 - end - local.get 9 - i32.const 20 - i32.add - i32.load - local.tee 4 - i32.eqz - br_if 0 (;@3;) - local.get 0 - i32.const 20 - i32.add - local.get 4 - i32.store - local.get 4 - local.get 0 - i32.store offset=24 - end - block ;; label = @3 - block ;; label = @4 - local.get 3 - i32.const 15 - i32.gt_u - br_if 0 (;@4;) - local.get 9 - local.get 3 - local.get 5 - i32.add - local.tee 4 - i32.const 3 - i32.or - i32.store offset=4 - local.get 9 - local.get 4 - i32.add - local.tee 4 - local.get 4 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - br 1 (;@3;) - end - local.get 9 - local.get 5 - i32.add - local.tee 0 - local.get 3 - i32.const 1 - i32.or - i32.store offset=4 - local.get 9 - local.get 5 - i32.const 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 3 - i32.add - local.get 3 - i32.store - block ;; label = @4 - local.get 3 - i32.const 255 - i32.gt_u - br_if 0 (;@4;) - local.get 3 - i32.const -8 - i32.and - i32.const 1048620 - i32.add - local.set 4 - block ;; label = @5 - block ;; label = @6 - i32.const 0 - i32.load offset=1048580 - local.tee 6 - i32.const 1 - local.get 3 - i32.const 3 - i32.shr_u - i32.shl - local.tee 3 - i32.and - br_if 0 (;@6;) - i32.const 0 - local.get 6 - local.get 3 - i32.or - i32.store offset=1048580 - local.get 4 - local.set 3 - br 1 (;@5;) - end - local.get 4 - i32.load offset=8 - local.set 3 - end - local.get 3 - local.get 0 - i32.store offset=12 - local.get 4 - local.get 0 - i32.store offset=8 - local.get 0 - local.get 4 - i32.store offset=12 - local.get 0 - local.get 3 - i32.store offset=8 - br 1 (;@3;) - end - i32.const 31 - local.set 4 - block ;; label = @4 - local.get 3 - i32.const 16777215 - i32.gt_u - br_if 0 (;@4;) - local.get 3 - i32.const 38 - local.get 3 - i32.const 8 - i32.shr_u - i32.clz - local.tee 4 - i32.sub - i32.shr_u - i32.const 1 - i32.and - local.get 4 - i32.const 1 - i32.shl - i32.sub - i32.const 62 - i32.add - local.set 4 - end - local.get 0 - local.get 4 - i32.store offset=28 - local.get 0 - i64.const 0 - i64.store offset=16 align=4 - local.get 4 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.set 6 - block ;; label = @4 - local.get 10 - i32.const 1 - local.get 4 - i32.shl - local.tee 5 - i32.and - br_if 0 (;@4;) - local.get 6 - local.get 0 - i32.store - i32.const 0 - local.get 10 - local.get 5 - i32.or - i32.store offset=1048584 - local.get 0 - local.get 6 - i32.store offset=24 - local.get 0 - local.get 0 - i32.store offset=8 - local.get 0 - local.get 0 - i32.store offset=12 - br 1 (;@3;) - end - local.get 3 - i32.const 0 - i32.const 25 - local.get 4 - i32.const 1 - i32.shr_u - i32.sub - local.get 4 - i32.const 31 - i32.eq - select - i32.shl - local.set 4 - local.get 6 - i32.load - local.set 5 - block ;; label = @4 - loop ;; label = @5 - local.get 5 - local.tee 6 - i32.load offset=4 - i32.const -8 - i32.and - local.get 3 - i32.eq - br_if 1 (;@4;) - local.get 4 - i32.const 29 - i32.shr_u - local.set 5 - local.get 4 - i32.const 1 - i32.shl - local.set 4 - local.get 6 - local.get 5 - i32.const 4 - i32.and - i32.add - i32.const 16 - i32.add - local.tee 7 - i32.load - local.tee 5 - br_if 0 (;@5;) - end - local.get 7 - local.get 0 - i32.store - local.get 0 - local.get 6 - i32.store offset=24 - local.get 0 - local.get 0 - i32.store offset=12 - local.get 0 - local.get 0 - i32.store offset=8 - br 1 (;@3;) - end - local.get 6 - i32.load offset=8 - local.tee 4 - local.get 0 - i32.store offset=12 - local.get 6 - local.get 0 - i32.store offset=8 - local.get 0 - i32.const 0 - i32.store offset=24 - local.get 0 - local.get 6 - i32.store offset=12 - local.get 0 - local.get 4 - i32.store offset=8 - end - local.get 9 - i32.const 8 - i32.add - local.set 4 - br 1 (;@1;) - end - block ;; label = @2 - local.get 11 - i32.eqz - br_if 0 (;@2;) - block ;; label = @3 - block ;; label = @4 - local.get 0 - local.get 0 - i32.load offset=28 - local.tee 6 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 4 - i32.load - i32.ne - br_if 0 (;@4;) - local.get 4 - local.get 9 - i32.store - local.get 9 - br_if 1 (;@3;) - i32.const 0 - local.get 10 - i32.const -2 - local.get 6 - i32.rotl - i32.and - i32.store offset=1048584 - br 2 (;@2;) - end - local.get 11 - i32.const 16 - i32.const 20 - local.get 11 - i32.load offset=16 - local.get 0 - i32.eq - select - i32.add - local.get 9 - i32.store - local.get 9 - i32.eqz - br_if 1 (;@2;) - end - local.get 9 - local.get 11 - i32.store offset=24 - block ;; label = @3 - local.get 0 - i32.load offset=16 - local.tee 4 - i32.eqz - br_if 0 (;@3;) - local.get 9 - local.get 4 - i32.store offset=16 - local.get 4 - local.get 9 - i32.store offset=24 - end - local.get 0 - i32.const 20 - i32.add - i32.load - local.tee 4 - i32.eqz - br_if 0 (;@2;) - local.get 9 - i32.const 20 - i32.add - local.get 4 - i32.store - local.get 4 - local.get 9 - i32.store offset=24 - end - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.const 15 - i32.gt_u - br_if 0 (;@3;) - local.get 0 - local.get 3 - local.get 5 - i32.add - local.tee 4 - i32.const 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 4 - i32.add - local.tee 4 - local.get 4 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - br 1 (;@2;) - end - local.get 0 - local.get 5 - i32.add - local.tee 6 - local.get 3 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - local.get 5 - i32.const 3 - i32.or - i32.store offset=4 - local.get 6 - local.get 3 - i32.add - local.get 3 - i32.store - block ;; label = @3 - local.get 8 - i32.eqz - br_if 0 (;@3;) - local.get 8 - i32.const -8 - i32.and - i32.const 1048620 - i32.add - local.set 5 - i32.const 0 - i32.load offset=1048600 - local.set 4 - block ;; label = @4 - block ;; label = @5 - i32.const 1 - local.get 8 - i32.const 3 - i32.shr_u - i32.shl - local.tee 9 - local.get 7 - i32.and - br_if 0 (;@5;) - i32.const 0 - local.get 9 - local.get 7 - i32.or - i32.store offset=1048580 - local.get 5 - local.set 9 - br 1 (;@4;) - end - local.get 5 - i32.load offset=8 - local.set 9 - end - local.get 9 - local.get 4 - i32.store offset=12 - local.get 5 - local.get 4 - i32.store offset=8 - local.get 4 - local.get 5 - i32.store offset=12 - local.get 4 - local.get 9 - i32.store offset=8 - end - i32.const 0 - local.get 6 - i32.store offset=1048600 - i32.const 0 - local.get 3 - i32.store offset=1048588 - end - local.get 0 - i32.const 8 - i32.add - local.set 4 - end - local.get 1 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 4 - ) - (func $free (;10;) (type 4) (param i32) - local.get 0 - call $dlfree - ) - (func $dlfree (;11;) (type 4) (param i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 0 - i32.eqz - br_if 0 (;@1;) - local.get 0 - i32.const -8 - i32.add - local.tee 1 - local.get 0 - i32.const -4 - i32.add - i32.load - local.tee 2 - i32.const -8 - i32.and - local.tee 0 - i32.add - local.set 3 - block ;; label = @2 - local.get 2 - i32.const 1 - i32.and - br_if 0 (;@2;) - local.get 2 - i32.const 3 - i32.and - i32.eqz - br_if 1 (;@1;) - local.get 1 - local.get 1 - i32.load - local.tee 2 - i32.sub - local.tee 1 - i32.const 0 - i32.load offset=1048596 - local.tee 4 - i32.lt_u - br_if 1 (;@1;) - local.get 2 - local.get 0 - i32.add - local.set 0 - block ;; label = @3 - local.get 1 - i32.const 0 - i32.load offset=1048600 - i32.eq - br_if 0 (;@3;) - block ;; label = @4 - local.get 2 - i32.const 255 - i32.gt_u - br_if 0 (;@4;) - local.get 1 - i32.load offset=8 - local.tee 4 - local.get 2 - i32.const 3 - i32.shr_u - local.tee 5 - i32.const 3 - i32.shl - i32.const 1048620 - i32.add - local.tee 6 - i32.eq - drop - block ;; label = @5 - local.get 1 - i32.load offset=12 - local.tee 2 - local.get 4 - i32.ne - br_if 0 (;@5;) - i32.const 0 - i32.const 0 - i32.load offset=1048580 - i32.const -2 - local.get 5 - i32.rotl - i32.and - i32.store offset=1048580 - br 3 (;@2;) - end - local.get 2 - local.get 6 - i32.eq - drop - local.get 2 - local.get 4 - i32.store offset=8 - local.get 4 - local.get 2 - i32.store offset=12 - br 2 (;@2;) - end - local.get 1 - i32.load offset=24 - local.set 7 - block ;; label = @4 - block ;; label = @5 - local.get 1 - i32.load offset=12 - local.tee 6 - local.get 1 - i32.eq - br_if 0 (;@5;) - local.get 1 - i32.load offset=8 - local.tee 2 - local.get 4 - i32.lt_u - drop - local.get 6 - local.get 2 - i32.store offset=8 - local.get 2 - local.get 6 - i32.store offset=12 - br 1 (;@4;) - end - block ;; label = @5 - local.get 1 - i32.const 20 - i32.add - local.tee 2 - i32.load - local.tee 4 - br_if 0 (;@5;) - local.get 1 - i32.const 16 - i32.add - local.tee 2 - i32.load - local.tee 4 - br_if 0 (;@5;) - i32.const 0 - local.set 6 - br 1 (;@4;) - end - loop ;; label = @5 - local.get 2 - local.set 5 - local.get 4 - local.tee 6 - i32.const 20 - i32.add - local.tee 2 - i32.load - local.tee 4 - br_if 0 (;@5;) - local.get 6 - i32.const 16 - i32.add - local.set 2 - local.get 6 - i32.load offset=16 - local.tee 4 - br_if 0 (;@5;) - end - local.get 5 - i32.const 0 - i32.store - end - local.get 7 - i32.eqz - br_if 1 (;@2;) - block ;; label = @4 - block ;; label = @5 - local.get 1 - local.get 1 - i32.load offset=28 - local.tee 4 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 2 - i32.load - i32.ne - br_if 0 (;@5;) - local.get 2 - local.get 6 - i32.store - local.get 6 - br_if 1 (;@4;) - i32.const 0 - i32.const 0 - i32.load offset=1048584 - i32.const -2 - local.get 4 - i32.rotl - i32.and - i32.store offset=1048584 - br 3 (;@2;) - end - local.get 7 - i32.const 16 - i32.const 20 - local.get 7 - i32.load offset=16 - local.get 1 - i32.eq - select - i32.add - local.get 6 - i32.store - local.get 6 - i32.eqz - br_if 2 (;@2;) - end - local.get 6 - local.get 7 - i32.store offset=24 - block ;; label = @4 - local.get 1 - i32.load offset=16 - local.tee 2 - i32.eqz - br_if 0 (;@4;) - local.get 6 - local.get 2 - i32.store offset=16 - local.get 2 - local.get 6 - i32.store offset=24 - end - local.get 1 - i32.load offset=20 - local.tee 2 - i32.eqz - br_if 1 (;@2;) - local.get 6 - i32.const 20 - i32.add - local.get 2 - i32.store - local.get 2 - local.get 6 - i32.store offset=24 - br 1 (;@2;) - end - local.get 3 - i32.load offset=4 - local.tee 2 - i32.const 3 - i32.and - i32.const 3 - i32.ne - br_if 0 (;@2;) - local.get 3 - local.get 2 - i32.const -2 - i32.and - i32.store offset=4 - i32.const 0 - local.get 0 - i32.store offset=1048588 - local.get 1 - local.get 0 - i32.add - local.get 0 - i32.store - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - return - end - local.get 1 - local.get 3 - i32.ge_u - br_if 0 (;@1;) - local.get 3 - i32.load offset=4 - local.tee 2 - i32.const 1 - i32.and - i32.eqz - br_if 0 (;@1;) - block ;; label = @2 - block ;; label = @3 - local.get 2 - i32.const 2 - i32.and - br_if 0 (;@3;) - block ;; label = @4 - local.get 3 - i32.const 0 - i32.load offset=1048604 - i32.ne - br_if 0 (;@4;) - i32.const 0 - local.get 1 - i32.store offset=1048604 - i32.const 0 - i32.const 0 - i32.load offset=1048592 - local.get 0 - i32.add - local.tee 0 - i32.store offset=1048592 - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 1 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 3 (;@1;) - i32.const 0 - i32.const 0 - i32.store offset=1048588 - i32.const 0 - i32.const 0 - i32.store offset=1048600 - return - end - block ;; label = @4 - local.get 3 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 0 (;@4;) - i32.const 0 - local.get 1 - i32.store offset=1048600 - i32.const 0 - i32.const 0 - i32.load offset=1048588 - local.get 0 - i32.add - local.tee 0 - i32.store offset=1048588 - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 1 - local.get 0 - i32.add - local.get 0 - i32.store - return - end - local.get 2 - i32.const -8 - i32.and - local.get 0 - i32.add - local.set 0 - block ;; label = @4 - block ;; label = @5 - local.get 2 - i32.const 255 - i32.gt_u - br_if 0 (;@5;) - local.get 3 - i32.load offset=8 - local.tee 4 - local.get 2 - i32.const 3 - i32.shr_u - local.tee 5 - i32.const 3 - i32.shl - i32.const 1048620 - i32.add - local.tee 6 - i32.eq - drop - block ;; label = @6 - local.get 3 - i32.load offset=12 - local.tee 2 - local.get 4 - i32.ne - br_if 0 (;@6;) - i32.const 0 - i32.const 0 - i32.load offset=1048580 - i32.const -2 - local.get 5 - i32.rotl - i32.and - i32.store offset=1048580 - br 2 (;@4;) - end - local.get 2 - local.get 6 - i32.eq - drop - local.get 2 - local.get 4 - i32.store offset=8 - local.get 4 - local.get 2 - i32.store offset=12 - br 1 (;@4;) - end - local.get 3 - i32.load offset=24 - local.set 7 - block ;; label = @5 - block ;; label = @6 - local.get 3 - i32.load offset=12 - local.tee 6 - local.get 3 - i32.eq - br_if 0 (;@6;) - local.get 3 - i32.load offset=8 - local.tee 2 - i32.const 0 - i32.load offset=1048596 - i32.lt_u - drop - local.get 6 - local.get 2 - i32.store offset=8 - local.get 2 - local.get 6 - i32.store offset=12 - br 1 (;@5;) - end - block ;; label = @6 - local.get 3 - i32.const 20 - i32.add - local.tee 2 - i32.load - local.tee 4 - br_if 0 (;@6;) - local.get 3 - i32.const 16 - i32.add - local.tee 2 - i32.load - local.tee 4 - br_if 0 (;@6;) - i32.const 0 - local.set 6 - br 1 (;@5;) - end - loop ;; label = @6 - local.get 2 - local.set 5 - local.get 4 - local.tee 6 - i32.const 20 - i32.add - local.tee 2 - i32.load - local.tee 4 - br_if 0 (;@6;) - local.get 6 - i32.const 16 - i32.add - local.set 2 - local.get 6 - i32.load offset=16 - local.tee 4 - br_if 0 (;@6;) - end - local.get 5 - i32.const 0 - i32.store - end - local.get 7 - i32.eqz - br_if 0 (;@4;) - block ;; label = @5 - block ;; label = @6 - local.get 3 - local.get 3 - i32.load offset=28 - local.tee 4 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 2 - i32.load - i32.ne - br_if 0 (;@6;) - local.get 2 - local.get 6 - i32.store - local.get 6 - br_if 1 (;@5;) - i32.const 0 - i32.const 0 - i32.load offset=1048584 - i32.const -2 - local.get 4 - i32.rotl - i32.and - i32.store offset=1048584 - br 2 (;@4;) - end - local.get 7 - i32.const 16 - i32.const 20 - local.get 7 - i32.load offset=16 - local.get 3 - i32.eq - select - i32.add - local.get 6 - i32.store - local.get 6 - i32.eqz - br_if 1 (;@4;) - end - local.get 6 - local.get 7 - i32.store offset=24 - block ;; label = @5 - local.get 3 - i32.load offset=16 - local.tee 2 - i32.eqz - br_if 0 (;@5;) - local.get 6 - local.get 2 - i32.store offset=16 - local.get 2 - local.get 6 - i32.store offset=24 - end - local.get 3 - i32.load offset=20 - local.tee 2 - i32.eqz - br_if 0 (;@4;) - local.get 6 - i32.const 20 - i32.add - local.get 2 - i32.store - local.get 2 - local.get 6 - i32.store offset=24 - end - local.get 1 - local.get 0 - i32.add - local.get 0 - i32.store - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - local.get 1 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 1 (;@2;) - i32.const 0 - local.get 0 - i32.store offset=1048588 - return - end - local.get 3 - local.get 2 - i32.const -2 - i32.and - i32.store offset=4 - local.get 1 - local.get 0 - i32.add - local.get 0 - i32.store - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store offset=4 - end - block ;; label = @2 - local.get 0 - i32.const 255 - i32.gt_u - br_if 0 (;@2;) - local.get 0 - i32.const -8 - i32.and - i32.const 1048620 - i32.add - local.set 2 - block ;; label = @3 - block ;; label = @4 - i32.const 0 - i32.load offset=1048580 - local.tee 4 - i32.const 1 - local.get 0 - i32.const 3 - i32.shr_u - i32.shl - local.tee 0 - i32.and - br_if 0 (;@4;) - i32.const 0 - local.get 4 - local.get 0 - i32.or - i32.store offset=1048580 - local.get 2 - local.set 0 - br 1 (;@3;) - end - local.get 2 - i32.load offset=8 - local.set 0 - end - local.get 0 - local.get 1 - i32.store offset=12 - local.get 2 - local.get 1 - i32.store offset=8 - local.get 1 - local.get 2 - i32.store offset=12 - local.get 1 - local.get 0 - i32.store offset=8 - return - end - i32.const 31 - local.set 2 - block ;; label = @2 - local.get 0 - i32.const 16777215 - i32.gt_u - br_if 0 (;@2;) - local.get 0 - i32.const 38 - local.get 0 - i32.const 8 - i32.shr_u - i32.clz - local.tee 2 - i32.sub - i32.shr_u - i32.const 1 - i32.and - local.get 2 - i32.const 1 - i32.shl - i32.sub - i32.const 62 - i32.add - local.set 2 - end - local.get 1 - local.get 2 - i32.store offset=28 - local.get 1 - i64.const 0 - i64.store offset=16 align=4 - local.get 2 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.set 4 - block ;; label = @2 - block ;; label = @3 - i32.const 0 - i32.load offset=1048584 - local.tee 6 - i32.const 1 - local.get 2 - i32.shl - local.tee 3 - i32.and - br_if 0 (;@3;) - local.get 4 - local.get 1 - i32.store - i32.const 0 - local.get 6 - local.get 3 - i32.or - i32.store offset=1048584 - local.get 1 - local.get 4 - i32.store offset=24 - local.get 1 - local.get 1 - i32.store offset=8 - local.get 1 - local.get 1 - i32.store offset=12 - br 1 (;@2;) - end - local.get 0 - i32.const 0 - i32.const 25 - local.get 2 - i32.const 1 - i32.shr_u - i32.sub - local.get 2 - i32.const 31 - i32.eq - select - i32.shl - local.set 2 - local.get 4 - i32.load - local.set 6 - block ;; label = @3 - loop ;; label = @4 - local.get 6 - local.tee 4 - i32.load offset=4 - i32.const -8 - i32.and - local.get 0 - i32.eq - br_if 1 (;@3;) - local.get 2 - i32.const 29 - i32.shr_u - local.set 6 - local.get 2 - i32.const 1 - i32.shl - local.set 2 - local.get 4 - local.get 6 - i32.const 4 - i32.and - i32.add - i32.const 16 - i32.add - local.tee 3 - i32.load - local.tee 6 - br_if 0 (;@4;) - end - local.get 3 - local.get 1 - i32.store - local.get 1 - local.get 4 - i32.store offset=24 - local.get 1 - local.get 1 - i32.store offset=12 - local.get 1 - local.get 1 - i32.store offset=8 - br 1 (;@2;) - end - local.get 4 - i32.load offset=8 - local.tee 0 - local.get 1 - i32.store offset=12 - local.get 4 - local.get 1 - i32.store offset=8 - local.get 1 - i32.const 0 - i32.store offset=24 - local.get 1 - local.get 4 - i32.store offset=12 - local.get 1 - local.get 0 - i32.store offset=8 - end - i32.const 0 - i32.const 0 - i32.load offset=1048612 - i32.const -1 - i32.add - local.tee 1 - i32.const -1 - local.get 1 - select - i32.store offset=1048612 - end - ) - (func $realloc (;12;) (type 1) (param i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 0 - br_if 0 (;@1;) - local.get 1 - call $dlmalloc - return - end - block ;; label = @1 - local.get 1 - i32.const -64 - i32.lt_u - br_if 0 (;@1;) - i32.const 0 - i32.const 48 - i32.store offset=1049076 - i32.const 0 - return - end - i32.const 16 - local.get 1 - i32.const 19 - i32.add - i32.const -16 - i32.and - local.get 1 - i32.const 11 - i32.lt_u - select - local.set 2 - local.get 0 - i32.const -4 - i32.add - local.tee 3 - i32.load - local.tee 4 - i32.const -8 - i32.and - local.set 5 - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 4 - i32.const 3 - i32.and - br_if 0 (;@3;) - local.get 2 - i32.const 256 - i32.lt_u - br_if 1 (;@2;) - local.get 5 - local.get 2 - i32.const 4 - i32.or - i32.lt_u - br_if 1 (;@2;) - local.get 5 - local.get 2 - i32.sub - i32.const 0 - i32.load offset=1049060 - i32.const 1 - i32.shl - i32.le_u - br_if 2 (;@1;) - br 1 (;@2;) - end - local.get 0 - i32.const -8 - i32.add - local.tee 6 - local.get 5 - i32.add - local.set 7 - block ;; label = @3 - local.get 5 - local.get 2 - i32.lt_u - br_if 0 (;@3;) - local.get 5 - local.get 2 - i32.sub - local.tee 1 - i32.const 16 - i32.lt_u - br_if 2 (;@1;) - local.get 3 - local.get 2 - local.get 4 - i32.const 1 - i32.and - i32.or - i32.const 2 - i32.or - i32.store - local.get 6 - local.get 2 - i32.add - local.tee 2 - local.get 1 - i32.const 3 - i32.or - i32.store offset=4 - local.get 7 - local.get 7 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 2 - local.get 1 - call $dispose_chunk - local.get 0 - return - end - block ;; label = @3 - local.get 7 - i32.const 0 - i32.load offset=1048604 - i32.ne - br_if 0 (;@3;) - i32.const 0 - i32.load offset=1048592 - local.get 5 - i32.add - local.tee 5 - local.get 2 - i32.le_u - br_if 1 (;@2;) - local.get 3 - local.get 2 - local.get 4 - i32.const 1 - i32.and - i32.or - i32.const 2 - i32.or - i32.store - i32.const 0 - local.get 6 - local.get 2 - i32.add - local.tee 1 - i32.store offset=1048604 - i32.const 0 - local.get 5 - local.get 2 - i32.sub - local.tee 2 - i32.store offset=1048592 - local.get 1 - local.get 2 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - return - end - block ;; label = @3 - local.get 7 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 0 (;@3;) - i32.const 0 - i32.load offset=1048588 - local.get 5 - i32.add - local.tee 5 - local.get 2 - i32.lt_u - br_if 1 (;@2;) - block ;; label = @4 - block ;; label = @5 - local.get 5 - local.get 2 - i32.sub - local.tee 1 - i32.const 16 - i32.lt_u - br_if 0 (;@5;) - local.get 3 - local.get 2 - local.get 4 - i32.const 1 - i32.and - i32.or - i32.const 2 - i32.or - i32.store - local.get 6 - local.get 2 - i32.add - local.tee 2 - local.get 1 - i32.const 1 - i32.or - i32.store offset=4 - local.get 6 - local.get 5 - i32.add - local.tee 5 - local.get 1 - i32.store - local.get 5 - local.get 5 - i32.load offset=4 - i32.const -2 - i32.and - i32.store offset=4 - br 1 (;@4;) - end - local.get 3 - local.get 4 - i32.const 1 - i32.and - local.get 5 - i32.or - i32.const 2 - i32.or - i32.store - local.get 6 - local.get 5 - i32.add - local.tee 1 - local.get 1 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - i32.const 0 - local.set 1 - i32.const 0 - local.set 2 - end - i32.const 0 - local.get 2 - i32.store offset=1048600 - i32.const 0 - local.get 1 - i32.store offset=1048588 - local.get 0 - return - end - local.get 7 - i32.load offset=4 - local.tee 8 - i32.const 2 - i32.and - br_if 0 (;@2;) - local.get 8 - i32.const -8 - i32.and - local.get 5 - i32.add - local.tee 9 - local.get 2 - i32.lt_u - br_if 0 (;@2;) - local.get 9 - local.get 2 - i32.sub - local.set 10 - block ;; label = @3 - block ;; label = @4 - local.get 8 - i32.const 255 - i32.gt_u - br_if 0 (;@4;) - local.get 7 - i32.load offset=8 - local.tee 1 - local.get 8 - i32.const 3 - i32.shr_u - local.tee 11 - i32.const 3 - i32.shl - i32.const 1048620 - i32.add - local.tee 8 - i32.eq - drop - block ;; label = @5 - local.get 7 - i32.load offset=12 - local.tee 5 - local.get 1 - i32.ne - br_if 0 (;@5;) - i32.const 0 - i32.const 0 - i32.load offset=1048580 - i32.const -2 - local.get 11 - i32.rotl - i32.and - i32.store offset=1048580 - br 2 (;@3;) - end - local.get 5 - local.get 8 - i32.eq - drop - local.get 5 - local.get 1 - i32.store offset=8 - local.get 1 - local.get 5 - i32.store offset=12 - br 1 (;@3;) - end - local.get 7 - i32.load offset=24 - local.set 12 - block ;; label = @4 - block ;; label = @5 - local.get 7 - i32.load offset=12 - local.tee 8 - local.get 7 - i32.eq - br_if 0 (;@5;) - local.get 7 - i32.load offset=8 - local.tee 1 - i32.const 0 - i32.load offset=1048596 - i32.lt_u - drop - local.get 8 - local.get 1 - i32.store offset=8 - local.get 1 - local.get 8 - i32.store offset=12 - br 1 (;@4;) - end - block ;; label = @5 - local.get 7 - i32.const 20 - i32.add - local.tee 1 - i32.load - local.tee 5 - br_if 0 (;@5;) - local.get 7 - i32.const 16 - i32.add - local.tee 1 - i32.load - local.tee 5 - br_if 0 (;@5;) - i32.const 0 - local.set 8 - br 1 (;@4;) - end - loop ;; label = @5 - local.get 1 - local.set 11 - local.get 5 - local.tee 8 - i32.const 20 - i32.add - local.tee 1 - i32.load - local.tee 5 - br_if 0 (;@5;) - local.get 8 - i32.const 16 - i32.add - local.set 1 - local.get 8 - i32.load offset=16 - local.tee 5 - br_if 0 (;@5;) - end - local.get 11 - i32.const 0 - i32.store - end - local.get 12 - i32.eqz - br_if 0 (;@3;) - block ;; label = @4 - block ;; label = @5 - local.get 7 - local.get 7 - i32.load offset=28 - local.tee 5 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 1 - i32.load - i32.ne - br_if 0 (;@5;) - local.get 1 - local.get 8 - i32.store - local.get 8 - br_if 1 (;@4;) - i32.const 0 - i32.const 0 - i32.load offset=1048584 - i32.const -2 - local.get 5 - i32.rotl - i32.and - i32.store offset=1048584 - br 2 (;@3;) - end - local.get 12 - i32.const 16 - i32.const 20 - local.get 12 - i32.load offset=16 - local.get 7 - i32.eq - select - i32.add - local.get 8 - i32.store - local.get 8 - i32.eqz - br_if 1 (;@3;) - end - local.get 8 - local.get 12 - i32.store offset=24 - block ;; label = @4 - local.get 7 - i32.load offset=16 - local.tee 1 - i32.eqz - br_if 0 (;@4;) - local.get 8 - local.get 1 - i32.store offset=16 - local.get 1 - local.get 8 - i32.store offset=24 - end - local.get 7 - i32.load offset=20 - local.tee 1 - i32.eqz - br_if 0 (;@3;) - local.get 8 - i32.const 20 - i32.add - local.get 1 - i32.store - local.get 1 - local.get 8 - i32.store offset=24 - end - block ;; label = @3 - local.get 10 - i32.const 15 - i32.gt_u - br_if 0 (;@3;) - local.get 3 - local.get 4 - i32.const 1 - i32.and - local.get 9 - i32.or - i32.const 2 - i32.or - i32.store - local.get 6 - local.get 9 - i32.add - local.tee 1 - local.get 1 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - return - end - local.get 3 - local.get 2 - local.get 4 - i32.const 1 - i32.and - i32.or - i32.const 2 - i32.or - i32.store - local.get 6 - local.get 2 - i32.add - local.tee 1 - local.get 10 - i32.const 3 - i32.or - i32.store offset=4 - local.get 6 - local.get 9 - i32.add - local.tee 2 - local.get 2 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 1 - local.get 10 - call $dispose_chunk - local.get 0 - return - end - block ;; label = @2 - local.get 1 - call $dlmalloc - local.tee 2 - br_if 0 (;@2;) - i32.const 0 - return - end - local.get 2 - local.get 0 - i32.const -4 - i32.const -8 - local.get 3 - i32.load - local.tee 5 - i32.const 3 - i32.and - select - local.get 5 - i32.const -8 - i32.and - i32.add - local.tee 5 - local.get 1 - local.get 5 - local.get 1 - i32.lt_u - select - call $memcpy - local.set 1 - local.get 0 - call $dlfree - local.get 1 - local.set 0 - end - local.get 0 - ) - (func $dispose_chunk (;13;) (type 5) (param i32 i32) - (local i32 i32 i32 i32 i32 i32) - local.get 0 - local.get 1 - i32.add - local.set 2 - block ;; label = @1 - block ;; label = @2 - local.get 0 - i32.load offset=4 - local.tee 3 - i32.const 1 - i32.and - br_if 0 (;@2;) - local.get 3 - i32.const 3 - i32.and - i32.eqz - br_if 1 (;@1;) - local.get 0 - i32.load - local.tee 3 - local.get 1 - i32.add - local.set 1 - block ;; label = @3 - block ;; label = @4 - local.get 0 - local.get 3 - i32.sub - local.tee 0 - i32.const 0 - i32.load offset=1048600 - i32.eq - br_if 0 (;@4;) - block ;; label = @5 - local.get 3 - i32.const 255 - i32.gt_u - br_if 0 (;@5;) - local.get 0 - i32.load offset=8 - local.tee 4 - local.get 3 - i32.const 3 - i32.shr_u - local.tee 5 - i32.const 3 - i32.shl - i32.const 1048620 - i32.add - local.tee 6 - i32.eq - drop - local.get 0 - i32.load offset=12 - local.tee 3 - local.get 4 - i32.ne - br_if 2 (;@3;) - i32.const 0 - i32.const 0 - i32.load offset=1048580 - i32.const -2 - local.get 5 - i32.rotl - i32.and - i32.store offset=1048580 - br 3 (;@2;) - end - local.get 0 - i32.load offset=24 - local.set 7 - block ;; label = @5 - block ;; label = @6 - local.get 0 - i32.load offset=12 - local.tee 6 - local.get 0 - i32.eq - br_if 0 (;@6;) - local.get 0 - i32.load offset=8 - local.tee 3 - i32.const 0 - i32.load offset=1048596 - i32.lt_u - drop - local.get 6 - local.get 3 - i32.store offset=8 - local.get 3 - local.get 6 - i32.store offset=12 - br 1 (;@5;) - end - block ;; label = @6 - local.get 0 - i32.const 20 - i32.add - local.tee 3 - i32.load - local.tee 4 - br_if 0 (;@6;) - local.get 0 - i32.const 16 - i32.add - local.tee 3 - i32.load - local.tee 4 - br_if 0 (;@6;) - i32.const 0 - local.set 6 - br 1 (;@5;) - end - loop ;; label = @6 - local.get 3 - local.set 5 - local.get 4 - local.tee 6 - i32.const 20 - i32.add - local.tee 3 - i32.load - local.tee 4 - br_if 0 (;@6;) - local.get 6 - i32.const 16 - i32.add - local.set 3 - local.get 6 - i32.load offset=16 - local.tee 4 - br_if 0 (;@6;) - end - local.get 5 - i32.const 0 - i32.store - end - local.get 7 - i32.eqz - br_if 2 (;@2;) - block ;; label = @5 - block ;; label = @6 - local.get 0 - local.get 0 - i32.load offset=28 - local.tee 4 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 3 - i32.load - i32.ne - br_if 0 (;@6;) - local.get 3 - local.get 6 - i32.store - local.get 6 - br_if 1 (;@5;) - i32.const 0 - i32.const 0 - i32.load offset=1048584 - i32.const -2 - local.get 4 - i32.rotl - i32.and - i32.store offset=1048584 - br 4 (;@2;) - end - local.get 7 - i32.const 16 - i32.const 20 - local.get 7 - i32.load offset=16 - local.get 0 - i32.eq - select - i32.add - local.get 6 - i32.store - local.get 6 - i32.eqz - br_if 3 (;@2;) - end - local.get 6 - local.get 7 - i32.store offset=24 - block ;; label = @5 - local.get 0 - i32.load offset=16 - local.tee 3 - i32.eqz - br_if 0 (;@5;) - local.get 6 - local.get 3 - i32.store offset=16 - local.get 3 - local.get 6 - i32.store offset=24 - end - local.get 0 - i32.load offset=20 - local.tee 3 - i32.eqz - br_if 2 (;@2;) - local.get 6 - i32.const 20 - i32.add - local.get 3 - i32.store - local.get 3 - local.get 6 - i32.store offset=24 - br 2 (;@2;) - end - local.get 2 - i32.load offset=4 - local.tee 3 - i32.const 3 - i32.and - i32.const 3 - i32.ne - br_if 1 (;@2;) - local.get 2 - local.get 3 - i32.const -2 - i32.and - i32.store offset=4 - i32.const 0 - local.get 1 - i32.store offset=1048588 - local.get 2 - local.get 1 - i32.store - local.get 0 - local.get 1 - i32.const 1 - i32.or - i32.store offset=4 - return - end - local.get 3 - local.get 6 - i32.eq - drop - local.get 3 - local.get 4 - i32.store offset=8 - local.get 4 - local.get 3 - i32.store offset=12 - end - block ;; label = @2 - block ;; label = @3 - local.get 2 - i32.load offset=4 - local.tee 3 - i32.const 2 - i32.and - br_if 0 (;@3;) - block ;; label = @4 - local.get 2 - i32.const 0 - i32.load offset=1048604 - i32.ne - br_if 0 (;@4;) - i32.const 0 - local.get 0 - i32.store offset=1048604 - i32.const 0 - i32.const 0 - i32.load offset=1048592 - local.get 1 - i32.add - local.tee 1 - i32.store offset=1048592 - local.get 0 - local.get 1 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 3 (;@1;) - i32.const 0 - i32.const 0 - i32.store offset=1048588 - i32.const 0 - i32.const 0 - i32.store offset=1048600 - return - end - block ;; label = @4 - local.get 2 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 0 (;@4;) - i32.const 0 - local.get 0 - i32.store offset=1048600 - i32.const 0 - i32.const 0 - i32.load offset=1048588 - local.get 1 - i32.add - local.tee 1 - i32.store offset=1048588 - local.get 0 - local.get 1 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - local.get 1 - i32.add - local.get 1 - i32.store - return - end - local.get 3 - i32.const -8 - i32.and - local.get 1 - i32.add - local.set 1 - block ;; label = @4 - block ;; label = @5 - local.get 3 - i32.const 255 - i32.gt_u - br_if 0 (;@5;) - local.get 2 - i32.load offset=8 - local.tee 4 - local.get 3 - i32.const 3 - i32.shr_u - local.tee 5 - i32.const 3 - i32.shl - i32.const 1048620 - i32.add - local.tee 6 - i32.eq - drop - block ;; label = @6 - local.get 2 - i32.load offset=12 - local.tee 3 - local.get 4 - i32.ne - br_if 0 (;@6;) - i32.const 0 - i32.const 0 - i32.load offset=1048580 - i32.const -2 - local.get 5 - i32.rotl - i32.and - i32.store offset=1048580 - br 2 (;@4;) - end - local.get 3 - local.get 6 - i32.eq - drop - local.get 3 - local.get 4 - i32.store offset=8 - local.get 4 - local.get 3 - i32.store offset=12 - br 1 (;@4;) - end - local.get 2 - i32.load offset=24 - local.set 7 - block ;; label = @5 - block ;; label = @6 - local.get 2 - i32.load offset=12 - local.tee 6 - local.get 2 - i32.eq - br_if 0 (;@6;) - local.get 2 - i32.load offset=8 - local.tee 3 - i32.const 0 - i32.load offset=1048596 - i32.lt_u - drop - local.get 6 - local.get 3 - i32.store offset=8 - local.get 3 - local.get 6 - i32.store offset=12 - br 1 (;@5;) - end - block ;; label = @6 - local.get 2 - i32.const 20 - i32.add - local.tee 4 - i32.load - local.tee 3 - br_if 0 (;@6;) - local.get 2 - i32.const 16 - i32.add - local.tee 4 - i32.load - local.tee 3 - br_if 0 (;@6;) - i32.const 0 - local.set 6 - br 1 (;@5;) - end - loop ;; label = @6 - local.get 4 - local.set 5 - local.get 3 - local.tee 6 - i32.const 20 - i32.add - local.tee 4 - i32.load - local.tee 3 - br_if 0 (;@6;) - local.get 6 - i32.const 16 - i32.add - local.set 4 - local.get 6 - i32.load offset=16 - local.tee 3 - br_if 0 (;@6;) - end - local.get 5 - i32.const 0 - i32.store - end - local.get 7 - i32.eqz - br_if 0 (;@4;) - block ;; label = @5 - block ;; label = @6 - local.get 2 - local.get 2 - i32.load offset=28 - local.tee 4 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.tee 3 - i32.load - i32.ne - br_if 0 (;@6;) - local.get 3 - local.get 6 - i32.store - local.get 6 - br_if 1 (;@5;) - i32.const 0 - i32.const 0 - i32.load offset=1048584 - i32.const -2 - local.get 4 - i32.rotl - i32.and - i32.store offset=1048584 - br 2 (;@4;) - end - local.get 7 - i32.const 16 - i32.const 20 - local.get 7 - i32.load offset=16 - local.get 2 - i32.eq - select - i32.add - local.get 6 - i32.store - local.get 6 - i32.eqz - br_if 1 (;@4;) - end - local.get 6 - local.get 7 - i32.store offset=24 - block ;; label = @5 - local.get 2 - i32.load offset=16 - local.tee 3 - i32.eqz - br_if 0 (;@5;) - local.get 6 - local.get 3 - i32.store offset=16 - local.get 3 - local.get 6 - i32.store offset=24 - end - local.get 2 - i32.load offset=20 - local.tee 3 - i32.eqz - br_if 0 (;@4;) - local.get 6 - i32.const 20 - i32.add - local.get 3 - i32.store - local.get 3 - local.get 6 - i32.store offset=24 - end - local.get 0 - local.get 1 - i32.add - local.get 1 - i32.store - local.get 0 - local.get 1 - i32.const 1 - i32.or - i32.store offset=4 - local.get 0 - i32.const 0 - i32.load offset=1048600 - i32.ne - br_if 1 (;@2;) - i32.const 0 - local.get 1 - i32.store offset=1048588 - return - end - local.get 2 - local.get 3 - i32.const -2 - i32.and - i32.store offset=4 - local.get 0 - local.get 1 - i32.add - local.get 1 - i32.store - local.get 0 - local.get 1 - i32.const 1 - i32.or - i32.store offset=4 - end - block ;; label = @2 - local.get 1 - i32.const 255 - i32.gt_u - br_if 0 (;@2;) - local.get 1 - i32.const -8 - i32.and - i32.const 1048620 - i32.add - local.set 3 - block ;; label = @3 - block ;; label = @4 - i32.const 0 - i32.load offset=1048580 - local.tee 4 - i32.const 1 - local.get 1 - i32.const 3 - i32.shr_u - i32.shl - local.tee 1 - i32.and - br_if 0 (;@4;) - i32.const 0 - local.get 4 - local.get 1 - i32.or - i32.store offset=1048580 - local.get 3 - local.set 1 - br 1 (;@3;) - end - local.get 3 - i32.load offset=8 - local.set 1 - end - local.get 1 - local.get 0 - i32.store offset=12 - local.get 3 - local.get 0 - i32.store offset=8 - local.get 0 - local.get 3 - i32.store offset=12 - local.get 0 - local.get 1 - i32.store offset=8 - return - end - i32.const 31 - local.set 3 - block ;; label = @2 - local.get 1 - i32.const 16777215 - i32.gt_u - br_if 0 (;@2;) - local.get 1 - i32.const 38 - local.get 1 - i32.const 8 - i32.shr_u - i32.clz - local.tee 3 - i32.sub - i32.shr_u - i32.const 1 - i32.and - local.get 3 - i32.const 1 - i32.shl - i32.sub - i32.const 62 - i32.add - local.set 3 - end - local.get 0 - local.get 3 - i32.store offset=28 - local.get 0 - i64.const 0 - i64.store offset=16 align=4 - local.get 3 - i32.const 2 - i32.shl - i32.const 1048884 - i32.add - local.set 4 - block ;; label = @2 - i32.const 0 - i32.load offset=1048584 - local.tee 6 - i32.const 1 - local.get 3 - i32.shl - local.tee 2 - i32.and - br_if 0 (;@2;) - local.get 4 - local.get 0 - i32.store - i32.const 0 - local.get 6 - local.get 2 - i32.or - i32.store offset=1048584 - local.get 0 - local.get 4 - i32.store offset=24 - local.get 0 - local.get 0 - i32.store offset=8 - local.get 0 - local.get 0 - i32.store offset=12 - return - end - local.get 1 - i32.const 0 - i32.const 25 - local.get 3 - i32.const 1 - i32.shr_u - i32.sub - local.get 3 - i32.const 31 - i32.eq - select - i32.shl - local.set 3 - local.get 4 - i32.load - local.set 6 - block ;; label = @2 - loop ;; label = @3 - local.get 6 - local.tee 4 - i32.load offset=4 - i32.const -8 - i32.and - local.get 1 - i32.eq - br_if 1 (;@2;) - local.get 3 - i32.const 29 - i32.shr_u - local.set 6 - local.get 3 - i32.const 1 - i32.shl - local.set 3 - local.get 4 - local.get 6 - i32.const 4 - i32.and - i32.add - i32.const 16 - i32.add - local.tee 2 - i32.load - local.tee 6 - br_if 0 (;@3;) - end - local.get 2 - local.get 0 - i32.store - local.get 0 - local.get 4 - i32.store offset=24 - local.get 0 - local.get 0 - i32.store offset=12 - local.get 0 - local.get 0 - i32.store offset=8 - return - end - local.get 4 - i32.load offset=8 - local.tee 1 - local.get 0 - i32.store offset=12 - local.get 4 - local.get 0 - i32.store offset=8 - local.get 0 - i32.const 0 - i32.store offset=24 - local.get 0 - local.get 4 - i32.store offset=12 - local.get 0 - local.get 1 - i32.store offset=8 - end - ) - (func $internal_memalign (;14;) (type 1) (param i32 i32) (result i32) - (local i32 i32 i32 i32 i32) - block ;; label = @1 - block ;; label = @2 - local.get 0 - i32.const 16 - local.get 0 - i32.const 16 - i32.gt_u - select - local.tee 2 - local.get 2 - i32.const -1 - i32.add - i32.and - br_if 0 (;@2;) - local.get 2 - local.set 0 - br 1 (;@1;) - end - i32.const 32 - local.set 3 - loop ;; label = @2 - local.get 3 - local.tee 0 - i32.const 1 - i32.shl - local.set 3 - local.get 0 - local.get 2 - i32.lt_u - br_if 0 (;@2;) - end - end - block ;; label = @1 - i32.const -64 - local.get 0 - i32.sub - local.get 1 - i32.gt_u - br_if 0 (;@1;) - i32.const 0 - i32.const 48 - i32.store offset=1049076 - i32.const 0 - return - end - block ;; label = @1 - local.get 0 - i32.const 16 - local.get 1 - i32.const 19 - i32.add - i32.const -16 - i32.and - local.get 1 - i32.const 11 - i32.lt_u - select - local.tee 1 - i32.add - i32.const 12 - i32.add - call $dlmalloc - local.tee 3 - br_if 0 (;@1;) - i32.const 0 - return - end - local.get 3 - i32.const -8 - i32.add - local.set 2 - block ;; label = @1 - block ;; label = @2 - local.get 0 - i32.const -1 - i32.add - local.get 3 - i32.and - br_if 0 (;@2;) - local.get 2 - local.set 0 - br 1 (;@1;) - end - local.get 3 - i32.const -4 - i32.add - local.tee 4 - i32.load - local.tee 5 - i32.const -8 - i32.and - local.get 3 - local.get 0 - i32.add - i32.const -1 - i32.add - i32.const 0 - local.get 0 - i32.sub - i32.and - i32.const -8 - i32.add - local.tee 3 - i32.const 0 - local.get 0 - local.get 3 - local.get 2 - i32.sub - i32.const 15 - i32.gt_u - select - i32.add - local.tee 0 - local.get 2 - i32.sub - local.tee 3 - i32.sub - local.set 6 - block ;; label = @2 - local.get 5 - i32.const 3 - i32.and - br_if 0 (;@2;) - local.get 0 - local.get 6 - i32.store offset=4 - local.get 0 - local.get 2 - i32.load - local.get 3 - i32.add - i32.store - br 1 (;@1;) - end - local.get 0 - local.get 6 - local.get 0 - i32.load offset=4 - i32.const 1 - i32.and - i32.or - i32.const 2 - i32.or - i32.store offset=4 - local.get 0 - local.get 6 - i32.add - local.tee 6 - local.get 6 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 4 - local.get 3 - local.get 4 - i32.load - i32.const 1 - i32.and - i32.or - i32.const 2 - i32.or - i32.store - local.get 2 - local.get 3 - i32.add - local.tee 6 - local.get 6 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 2 - local.get 3 - call $dispose_chunk - end - block ;; label = @1 - local.get 0 - i32.load offset=4 - local.tee 3 - i32.const 3 - i32.and - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.const -8 - i32.and - local.tee 2 - local.get 1 - i32.const 16 - i32.add - i32.le_u - br_if 0 (;@1;) - local.get 0 - local.get 1 - local.get 3 - i32.const 1 - i32.and - i32.or - i32.const 2 - i32.or - i32.store offset=4 - local.get 0 - local.get 1 - i32.add - local.tee 3 - local.get 2 - local.get 1 - i32.sub - local.tee 1 - i32.const 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 2 - i32.add - local.tee 2 - local.get 2 - i32.load offset=4 - i32.const 1 - i32.or - i32.store offset=4 - local.get 3 - local.get 1 - call $dispose_chunk - end - local.get 0 - i32.const 8 - i32.add - ) - (func $aligned_alloc (;15;) (type 1) (param i32 i32) (result i32) - block ;; label = @1 - local.get 0 - i32.const 16 - i32.gt_u - br_if 0 (;@1;) - local.get 1 - call $dlmalloc - return - end - local.get 0 - local.get 1 - call $internal_memalign - ) - (func $abort (;16;) (type 0) - unreachable - unreachable - ) - (func $sbrk (;17;) (type 3) (param i32) (result i32) - block ;; label = @1 - local.get 0 - br_if 0 (;@1;) - memory.size - i32.const 16 - i32.shl - return - end - block ;; label = @1 - local.get 0 - i32.const 65535 - i32.and - br_if 0 (;@1;) - local.get 0 - i32.const -1 - i32.le_s - br_if 0 (;@1;) - block ;; label = @2 - local.get 0 - i32.const 16 - i32.shr_u - memory.grow - local.tee 0 - i32.const -1 - i32.ne - br_if 0 (;@2;) - i32.const 0 - i32.const 48 - i32.store offset=1049076 - i32.const -1 - return - end - local.get 0 - i32.const 16 - i32.shl - return - end - call $abort - unreachable - ) - (func $memcpy (;18;) (type 6) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32) - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 2 - i32.const 32 - i32.gt_u - br_if 0 (;@3;) - local.get 1 - i32.const 3 - i32.and - i32.eqz - br_if 1 (;@2;) - local.get 2 - i32.eqz - br_if 1 (;@2;) - local.get 0 - local.get 1 - i32.load8_u - i32.store8 - local.get 2 - i32.const -1 - i32.add - local.set 3 - local.get 0 - i32.const 1 - i32.add - local.set 4 - local.get 1 - i32.const 1 - i32.add - local.tee 5 - i32.const 3 - i32.and - i32.eqz - br_if 2 (;@1;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - local.get 0 - local.get 1 - i32.load8_u offset=1 - i32.store8 offset=1 - local.get 2 - i32.const -2 - i32.add - local.set 3 - local.get 0 - i32.const 2 - i32.add - local.set 4 - local.get 1 - i32.const 2 - i32.add - local.tee 5 - i32.const 3 - i32.and - i32.eqz - br_if 2 (;@1;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - local.get 0 - local.get 1 - i32.load8_u offset=2 - i32.store8 offset=2 - local.get 2 - i32.const -3 - i32.add - local.set 3 - local.get 0 - i32.const 3 - i32.add - local.set 4 - local.get 1 - i32.const 3 - i32.add - local.tee 5 - i32.const 3 - i32.and - i32.eqz - br_if 2 (;@1;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - local.get 0 - local.get 1 - i32.load8_u offset=3 - i32.store8 offset=3 - local.get 2 - i32.const -4 - i32.add - local.set 3 - local.get 0 - i32.const 4 - i32.add - local.set 4 - local.get 1 - i32.const 4 - i32.add - local.set 5 - br 2 (;@1;) - end - local.get 0 - local.get 1 - local.get 2 - memory.copy - local.get 0 - return - end - local.get 2 - local.set 3 - local.get 0 - local.set 4 - local.get 1 - local.set 5 - end - block ;; label = @1 - block ;; label = @2 - local.get 4 - i32.const 3 - i32.and - local.tee 2 - br_if 0 (;@2;) - block ;; label = @3 - block ;; label = @4 - local.get 3 - i32.const 16 - i32.ge_u - br_if 0 (;@4;) - local.get 3 - local.set 2 - br 1 (;@3;) - end - block ;; label = @4 - local.get 3 - i32.const -16 - i32.add - local.tee 2 - i32.const 16 - i32.and - br_if 0 (;@4;) - local.get 4 - local.get 5 - i64.load align=4 - i64.store align=4 - local.get 4 - local.get 5 - i64.load offset=8 align=4 - i64.store offset=8 align=4 - local.get 4 - i32.const 16 - i32.add - local.set 4 - local.get 5 - i32.const 16 - i32.add - local.set 5 - local.get 2 - local.set 3 - end - local.get 2 - i32.const 16 - i32.lt_u - br_if 0 (;@3;) - local.get 3 - local.set 2 - loop ;; label = @4 - local.get 4 - local.get 5 - i64.load align=4 - i64.store align=4 - local.get 4 - local.get 5 - i64.load offset=8 align=4 - i64.store offset=8 align=4 - local.get 4 - local.get 5 - i64.load offset=16 align=4 - i64.store offset=16 align=4 - local.get 4 - local.get 5 - i64.load offset=24 align=4 - i64.store offset=24 align=4 - local.get 4 - i32.const 32 - i32.add - local.set 4 - local.get 5 - i32.const 32 - i32.add - local.set 5 - local.get 2 - i32.const -32 - i32.add - local.tee 2 - i32.const 15 - i32.gt_u - br_if 0 (;@4;) - end - end - block ;; label = @3 - local.get 2 - i32.const 8 - i32.lt_u - br_if 0 (;@3;) - local.get 4 - local.get 5 - i64.load align=4 - i64.store align=4 - local.get 5 - i32.const 8 - i32.add - local.set 5 - local.get 4 - i32.const 8 - i32.add - local.set 4 - end - block ;; label = @3 - local.get 2 - i32.const 4 - i32.and - i32.eqz - br_if 0 (;@3;) - local.get 4 - local.get 5 - i32.load - i32.store - local.get 5 - i32.const 4 - i32.add - local.set 5 - local.get 4 - i32.const 4 - i32.add - local.set 4 - end - block ;; label = @3 - local.get 2 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@3;) - local.get 4 - local.get 5 - i32.load16_u align=1 - i32.store16 align=1 - local.get 4 - i32.const 2 - i32.add - local.set 4 - local.get 5 - i32.const 2 - i32.add - local.set 5 - end - local.get 2 - i32.const 1 - i32.and - i32.eqz - br_if 1 (;@1;) - local.get 4 - local.get 5 - i32.load8_u - i32.store8 - local.get 0 - return - end - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - block ;; label = @6 - local.get 3 - i32.const 32 - i32.lt_u - br_if 0 (;@6;) - block ;; label = @7 - block ;; label = @8 - local.get 2 - i32.const -1 - i32.add - br_table 3 (;@5;) 0 (;@8;) 1 (;@7;) 7 (;@1;) - end - local.get 4 - local.get 5 - i32.load - i32.store16 align=1 - local.get 4 - local.get 5 - i32.const 2 - i32.add - i32.load align=2 - i32.store offset=2 - local.get 4 - local.get 5 - i32.const 6 - i32.add - i64.load align=2 - i64.store offset=6 align=4 - local.get 4 - i32.const 18 - i32.add - local.set 2 - local.get 5 - i32.const 18 - i32.add - local.set 1 - i32.const 14 - local.set 6 - local.get 5 - i32.const 14 - i32.add - i32.load align=2 - local.set 5 - i32.const 14 - local.set 3 - br 3 (;@4;) - end - local.get 4 - local.get 5 - i32.load - i32.store8 - local.get 4 - local.get 5 - i32.const 1 - i32.add - i32.load align=1 - i32.store offset=1 - local.get 4 - local.get 5 - i32.const 5 - i32.add - i64.load align=1 - i64.store offset=5 align=4 - local.get 4 - i32.const 17 - i32.add - local.set 2 - local.get 5 - i32.const 17 - i32.add - local.set 1 - i32.const 13 - local.set 6 - local.get 5 - i32.const 13 - i32.add - i32.load align=1 - local.set 5 - i32.const 15 - local.set 3 - br 2 (;@4;) - end - block ;; label = @6 - block ;; label = @7 - local.get 3 - i32.const 16 - i32.ge_u - br_if 0 (;@7;) - local.get 4 - local.set 2 - local.get 5 - local.set 1 - br 1 (;@6;) - end - local.get 4 - local.get 5 - i32.load8_u - i32.store8 - local.get 4 - local.get 5 - i32.load offset=1 align=1 - i32.store offset=1 align=1 - local.get 4 - local.get 5 - i64.load offset=5 align=1 - i64.store offset=5 align=1 - local.get 4 - local.get 5 - i32.load16_u offset=13 align=1 - i32.store16 offset=13 align=1 - local.get 4 - local.get 5 - i32.load8_u offset=15 - i32.store8 offset=15 - local.get 4 - i32.const 16 - i32.add - local.set 2 - local.get 5 - i32.const 16 - i32.add - local.set 1 - end - local.get 3 - i32.const 8 - i32.and - br_if 2 (;@3;) - br 3 (;@2;) - end - local.get 4 - local.get 5 - i32.load - local.tee 2 - i32.store8 - local.get 4 - local.get 2 - i32.const 16 - i32.shr_u - i32.store8 offset=2 - local.get 4 - local.get 2 - i32.const 8 - i32.shr_u - i32.store8 offset=1 - local.get 4 - local.get 5 - i32.const 3 - i32.add - i32.load align=1 - i32.store offset=3 - local.get 4 - local.get 5 - i32.const 7 - i32.add - i64.load align=1 - i64.store offset=7 align=4 - local.get 4 - i32.const 19 - i32.add - local.set 2 - local.get 5 - i32.const 19 - i32.add - local.set 1 - i32.const 15 - local.set 6 - local.get 5 - i32.const 15 - i32.add - i32.load align=1 - local.set 5 - i32.const 13 - local.set 3 - end - local.get 4 - local.get 6 - i32.add - local.get 5 - i32.store - end - local.get 2 - local.get 1 - i64.load align=1 - i64.store align=1 - local.get 2 - i32.const 8 - i32.add - local.set 2 - local.get 1 - i32.const 8 - i32.add - local.set 1 - end - block ;; label = @2 - local.get 3 - i32.const 4 - i32.and - i32.eqz - br_if 0 (;@2;) - local.get 2 - local.get 1 - i32.load align=1 - i32.store align=1 - local.get 2 - i32.const 4 - i32.add - local.set 2 - local.get 1 - i32.const 4 - i32.add - local.set 1 - end - block ;; label = @2 - local.get 3 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@2;) - local.get 2 - local.get 1 - i32.load16_u align=1 - i32.store16 align=1 - local.get 2 - i32.const 2 - i32.add - local.set 2 - local.get 1 - i32.const 2 - i32.add - local.set 1 - end - local.get 3 - i32.const 1 - i32.and - i32.eqz - br_if 0 (;@1;) - local.get 2 - local.get 1 - i32.load8_u - i32.store8 - end - local.get 0 - ) - (table (;0;) 1 1 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "add" (func $add)) - (export "cabi_realloc" (func $cabi_realloc)) - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (type (;0;) (func (param "a" s32) (param "b" s32) (result s32))) - (alias core export 0 "add" (core func (;1;))) - (func (;0;) (type 0) (canon lift (core func 1))) - (export (;1;) "add" (func 0)) -) \ No newline at end of file diff --git a/tests/integration/expected/components/inc_wasm_component.hir b/tests/integration/expected/components/inc_wasm_component.hir deleted file mode 100644 index fab68e3f4..000000000 --- a/tests/integration/expected/components/inc_wasm_component.hir +++ /dev/null @@ -1,1098 +0,0 @@ -(component - ;; Component Imports - (lower (("miden:add-package/add-interface@1.0.0" #add) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (param u32) (param u32) (result u32)))) (#miden:add-package/add-interface@1.0.0 #add) - - ;; Modules - (module #inc_wasm_component - ;; Data Segments - (data (mut) (offset 1048576) 0x0100000002000000) - - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - - ;; Functions - (func (export #__wasm_call_ctors) - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #inc_wasm_component::bindings::__link_custom_section_describing_imports) - - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #__rust_alloc) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048584)) - (let (v4 i32) (call #::alloc v3 v1 v0)) - (br (block 1 v4))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #__rust_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (const.i32 0)) - (let (v6 i32) (const.i32 1048584)) - (let (v7 i32) (call #::alloc v6 v2 v3)) - (let (v8 i1) (eq v7 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2 v7) (block 3))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v22 i32) - (br (block 1 v22))) - - (block 3 - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (bitcast v3)) - (let (v13 i1) (lt v11 v12)) - (let (v14 i32) (sext v13)) - (let (v15 i1) (neq v14 0)) - (let (v16 i32) (select v15 v1 v3)) - (let (v17 u32) (bitcast v7)) - (let (v18 (ptr u8)) (inttoptr v17)) - (let (v19 u32) (bitcast v0)) - (let (v20 (ptr u8)) (inttoptr v19)) - (memcpy v20 v18 v16) - (let (v21 i32) (const.i32 1048584)) - (call #::dealloc v21 v0 v2 v1) - (br (block 2 v7))) - ) - - (func (export #inc) (param i32) (result i32) - (block 0 (param v0 i32) - (call #wit_bindgen_rt::run_ctors_once) - (let (v2 i32) (const.i32 1)) - (let (v3 i32) (call (#miden:add-package/add-interface@1.0.0 #add) v0 v2)) - (br (block 1 v3))) - - (block 1 (param v1 i32) - (ret v1)) - ) - - (func (export #wit_bindgen_rt::cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i1) (neq v1 0)) - (condbr v5 (block 4) (block 5))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v19 i32) - (br (block 1 v19))) - - (block 3 (param v17 i32) - (let (v18 i1) (neq v17 0)) - (condbr v18 (block 2 v17) (block 7))) - - (block 4 - (let (v16 i32) (call #__rust_realloc v0 v1 v2 v3)) - (br (block 3 v16))) - - (block 5 - (let (v6 i1) (eq v3 0)) - (let (v7 i32) (zext v6)) - (let (v8 i1) (neq v7 0)) - (condbr v8 (block 2 v2) (block 6))) - - (block 6 - (let (v9 i32) (const.i32 0)) - (let (v10 u32) (bitcast v9)) - (let (v11 u32) (add.checked v10 1048588)) - (let (v12 (ptr u8)) (inttoptr v11)) - (let (v13 u8) (load v12)) - (let (v14 i32) (zext v13)) - (let (v15 i32) (call #__rust_alloc v3 v2)) - (br (block 3 v15))) - - (block 7 - (unreachable)) - ) - - (func (export #wit_bindgen_rt::run_ctors_once) - (block 0 - (let (v0 i32) (const.i32 0)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (add.checked v1 1048589)) - (let (v3 (ptr u8)) (inttoptr v2)) - (let (v4 u8) (load v3)) - (let (v5 i32) (zext v4)) - (let (v6 i1) (neq v5 0)) - (condbr v6 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (call #__wasm_call_ctors) - (let (v7 i32) (const.i32 0)) - (let (v8 i32) (const.i32 1)) - (let (v9 u32) (bitcast v8)) - (let (v10 u8) (trunc v9)) - (let (v11 u32) (bitcast v7)) - (let (v12 u32) (add.checked v11 1048589)) - (let (v13 (ptr u8)) (inttoptr v12)) - (store v13 v10) - (br (block 2))) - ) - - (func (export #wee_alloc::alloc_first_fit) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 u32) (bitcast v2)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr i32)) (inttoptr v5)) - (let (v8 i32) (load v7)) - (let (v9 i1) (neq v8 0)) - (condbr v9 (block 2) (block 3))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 - (let (v11 i32) (const.i32 -1)) - (let (v12 i32) (add.wrapping v1 v11)) - (let (v13 i32) (const.i32 0)) - (let (v14 i32) (sub.wrapping v13 v1)) - (let (v15 i32) (const.i32 2)) - (let (v16 u32) (bitcast v15)) - (let (v17 i32) (shl.wrapping v0 v16)) - (br (block 4 v8 v2 v17 v14 v12))) - - (block 3 - (let (v10 i32) (const.i32 0)) - (ret v10)) - - (block 4 - (param v18 i32) - (param v169 i32) - (param v182 i32) - (param v197 i32) - (param v210 i32) - (let (v19 u32) (bitcast v18)) - (let (v20 u32) (add.checked v19 8)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr i32)) (inttoptr v20)) - (let (v23 i32) (load v22)) - (let (v24 i32) (const.i32 1)) - (let (v25 i32) (band v23 v24)) - (let (v26 i1) (neq v25 0)) - (condbr v26 (block 7) (block 8))) - - (block 5 - (let (v344 i32) (const.i32 0)) - (br (block 1 v344))) - - (block 6 - (param v172 i32) - (param v179 i32) - (param v181 i32) - (param v196 i32) - (param v209 i32) - (param v218 i32) - (param v219 i32) - (let (v173 u32) (bitcast v172)) - (let (v174 u32) (mod.unchecked v173 4)) - (assertz 250 v174) - (let (v175 (ptr i32)) (inttoptr v173)) - (let (v176 i32) (load v175)) - (let (v177 i32) (const.i32 -4)) - (let (v178 i32) (band v176 v177)) - (let (v180 i32) (sub.wrapping v178 v179)) - (let (v188 u32) (bitcast v180)) - (let (v189 u32) (bitcast v181)) - (let (v190 i1) (lt v188 v189)) - (let (v191 i32) (sext v190)) - (let (v192 i1) (neq v191 0)) - (condbr v192 (block 22 v218 v219 v181 v196 v209) (block 23))) - - (block 7 - (br (block 9 v18 v23 v169 v182 v197 v210))) - - (block 8 - (let (v27 i32) (const.i32 8)) - (let (v28 i32) (add.wrapping v18 v27)) - (br (block 6 v18 v28 v182 v197 v210 v169 v23))) - - (block 9 - (param v29 i32) - (param v30 i32) - (param v156 i32) - (param v187 i32) - (param v202 i32) - (param v215 i32) - (let (v31 i32) (const.i32 -2)) - (let (v32 i32) (band v30 v31)) - (let (v33 u32) (bitcast v29)) - (let (v34 u32) (add.checked v33 8)) - (let (v35 u32) (mod.unchecked v34 4)) - (assertz 250 v35) - (let (v36 (ptr i32)) (inttoptr v34)) - (store v36 v32) - (let (v37 u32) (bitcast v29)) - (let (v38 u32) (add.checked v37 4)) - (let (v39 u32) (mod.unchecked v38 4)) - (assertz 250 v39) - (let (v40 (ptr i32)) (inttoptr v38)) - (let (v41 i32) (load v40)) - (let (v42 i32) (const.i32 -4)) - (let (v43 i32) (band v41 v42)) - (let (v44 i1) (neq v43 0)) - (condbr v44 (block 12) (block 13))) - - (block 10 - (let (v170 i32) (const.i32 8)) - (let (v171 i32) (add.wrapping v157 v170)) - (br (block 6 v157 v171 v183 v198 v211 v152 v165))) - - (block 11 - (param v55 i32) - (param v75 i32) - (param v122 i32) - (param v142 i32) - (param v155 i32) - (param v186 i32) - (param v201 i32) - (param v214 i32) - (let (v56 u32) (bitcast v55)) - (let (v57 u32) (mod.unchecked v56 4)) - (assertz 250 v57) - (let (v58 (ptr i32)) (inttoptr v56)) - (let (v59 i32) (load v58)) - (let (v60 i32) (const.i32 -4)) - (let (v61 i32) (band v59 v60)) - (let (v62 i1) (eq v61 0)) - (let (v63 i32) (zext v62)) - (let (v64 i1) (neq v63 0)) - (condbr v64 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 15))) - - (block 12 - (let (v46 i32) (const.i32 0)) - (let (v47 u32) (bitcast v43)) - (let (v48 (ptr u8)) (inttoptr v47)) - (let (v49 u8) (load v48)) - (let (v50 i32) (zext v49)) - (let (v51 i32) (const.i32 1)) - (let (v52 i32) (band v50 v51)) - (let (v53 i1) (neq v52 0)) - (let (v54 i32) (select v53 v46 v43)) - (br (block 11 v29 v43 v41 v54 v156 v187 v202 v215))) - - (block 13 - (let (v45 i32) (const.i32 0)) - (br (block 11 v29 v43 v41 v45 v156 v187 v202 v215))) - - (block 14 - (param v92 i32) - (param v102 i32) - (param v109 i32) - (param v121 i32) - (param v141 i32) - (param v154 i32) - (param v185 i32) - (param v200 i32) - (param v213 i32) - (let (v93 i1) (eq v92 0)) - (let (v94 i32) (zext v93)) - (let (v95 i1) (neq v94 0)) - (condbr v95 (block 17 v109 v121 v102 v141 v154 v185 v200 v213) (block 18))) - - (block 15 - (let (v65 i32) (const.i32 2)) - (let (v66 i32) (band v59 v65)) - (let (v67 i1) (neq v66 0)) - (condbr v67 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 16))) - - (block 16 - (let (v68 u32) (bitcast v61)) - (let (v69 u32) (add.checked v68 4)) - (let (v70 u32) (mod.unchecked v69 4)) - (assertz 250 v70) - (let (v71 (ptr i32)) (inttoptr v69)) - (let (v72 i32) (load v71)) - (let (v73 i32) (const.i32 3)) - (let (v74 i32) (band v72 v73)) - (let (v76 i32) (bor v74 v75)) - (let (v77 u32) (bitcast v61)) - (let (v78 u32) (add.checked v77 4)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v76) - (let (v81 u32) (bitcast v55)) - (let (v82 u32) (add.checked v81 4)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (let (v85 i32) (load v84)) - (let (v86 i32) (const.i32 -4)) - (let (v87 i32) (band v85 v86)) - (let (v88 u32) (bitcast v55)) - (let (v89 u32) (mod.unchecked v88 4)) - (assertz 250 v89) - (let (v90 (ptr i32)) (inttoptr v88)) - (let (v91 i32) (load v90)) - (br (block 14 v87 v91 v55 v85 v142 v155 v186 v201 v214))) - - (block 17 - (param v119 i32) - (param v120 i32) - (param v129 i32) - (param v140 i32) - (param v153 i32) - (param v184 i32) - (param v199 i32) - (param v212 i32) - (let (v123 i32) (const.i32 3)) - (let (v124 i32) (band v120 v123)) - (let (v125 u32) (bitcast v119)) - (let (v126 u32) (add.checked v125 4)) - (let (v127 u32) (mod.unchecked v126 4)) - (assertz 250 v127) - (let (v128 (ptr i32)) (inttoptr v126)) - (store v128 v124) - (let (v130 i32) (const.i32 3)) - (let (v131 i32) (band v129 v130)) - (let (v132 u32) (bitcast v119)) - (let (v133 u32) (mod.unchecked v132 4)) - (assertz 250 v133) - (let (v134 (ptr i32)) (inttoptr v132)) - (store v134 v131) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v129 v135)) - (let (v137 i1) (eq v136 0)) - (let (v138 i32) (zext v137)) - (let (v139 i1) (neq v138 0)) - (condbr v139 (block 19 v153 v140 v184 v199 v212) (block 20))) - - (block 18 - (let (v96 u32) (bitcast v92)) - (let (v97 u32) (mod.unchecked v96 4)) - (assertz 250 v97) - (let (v98 (ptr i32)) (inttoptr v96)) - (let (v99 i32) (load v98)) - (let (v100 i32) (const.i32 3)) - (let (v101 i32) (band v99 v100)) - (let (v103 i32) (const.i32 -4)) - (let (v104 i32) (band v102 v103)) - (let (v105 i32) (bor v101 v104)) - (let (v106 u32) (bitcast v92)) - (let (v107 u32) (mod.unchecked v106 4)) - (assertz 250 v107) - (let (v108 (ptr i32)) (inttoptr v106)) - (store v108 v105) - (let (v110 u32) (bitcast v109)) - (let (v111 u32) (add.checked v110 4)) - (let (v112 u32) (mod.unchecked v111 4)) - (assertz 250 v112) - (let (v113 (ptr i32)) (inttoptr v111)) - (let (v114 i32) (load v113)) - (let (v115 u32) (bitcast v109)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (let (v118 i32) (load v117)) - (br (block 17 v109 v114 v118 v141 v154 v185 v200 v213))) - - (block 19 - (param v152 i32) - (param v157 i32) - (param v183 i32) - (param v198 i32) - (param v211 i32) - (let (v158 u32) (bitcast v152)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v161 u32) (bitcast v157)) - (let (v162 u32) (add.checked v161 8)) - (let (v163 u32) (mod.unchecked v162 4)) - (assertz 250 v163) - (let (v164 (ptr i32)) (inttoptr v162)) - (let (v165 i32) (load v164)) - (let (v166 i32) (const.i32 1)) - (let (v167 i32) (band v165 v166)) - (let (v168 i1) (neq v167 0)) - (condbr v168 (block 9 v157 v165 v152 v183 v198 v211) (block 21))) - - (block 20 - (let (v143 u32) (bitcast v140)) - (let (v144 u32) (mod.unchecked v143 4)) - (assertz 250 v144) - (let (v145 (ptr i32)) (inttoptr v143)) - (let (v146 i32) (load v145)) - (let (v147 i32) (const.i32 2)) - (let (v148 i32) (bor v146 v147)) - (let (v149 u32) (bitcast v140)) - (let (v150 u32) (mod.unchecked v149 4)) - (assertz 250 v150) - (let (v151 (ptr i32)) (inttoptr v149)) - (store v151 v148) - (br (block 19 v153 v140 v184 v199 v212))) - - (block 21 - (br (block 10))) - - (block 22 - (param v335 i32) - (param v336 i32) - (param v341 i32) - (param v342 i32) - (param v343 i32) - (let (v337 u32) (bitcast v335)) - (let (v338 u32) (mod.unchecked v337 4)) - (assertz 250 v338) - (let (v339 (ptr i32)) (inttoptr v337)) - (store v339 v336) - (let (v340 i1) (neq v336 0)) - (condbr v340 (block 4 v336 v335 v341 v342 v343) (block 33))) - - (block 23 - (let (v193 i32) (const.i32 72)) - (let (v194 i32) (add.wrapping v179 v193)) - (let (v195 i32) (sub.wrapping v178 v181)) - (let (v203 i32) (band v195 v196)) - (let (v204 u32) (bitcast v194)) - (let (v205 u32) (bitcast v203)) - (let (v206 i1) (lte v204 v205)) - (let (v207 i32) (sext v206)) - (let (v208 i1) (neq v207 0)) - (condbr v208 (block 25) (block 26))) - - (block 24 (param v326 i32) (param v327 i32) - (let (v328 i32) (const.i32 1)) - (let (v329 i32) (bor v327 v328)) - (let (v330 u32) (bitcast v326)) - (let (v331 u32) (mod.unchecked v330 4)) - (assertz 250 v331) - (let (v332 (ptr i32)) (inttoptr v330)) - (store v332 v329) - (let (v333 i32) (const.i32 8)) - (let (v334 i32) (add.wrapping v326 v333)) - (ret v334)) - - (block 25 - (let (v229 i32) (const.i32 0)) - (let (v230 i32) (const.i32 0)) - (let (v231 u32) (bitcast v203)) - (let (v232 u32) (mod.unchecked v231 4)) - (assertz 250 v232) - (let (v233 (ptr i32)) (inttoptr v231)) - (store v233 v230) - (let (v234 i32) (const.i32 -8)) - (let (v235 i32) (add.wrapping v203 v234)) - (let (v236 i64) (const.i64 0)) - (let (v237 u32) (bitcast v235)) - (let (v238 u32) (mod.unchecked v237 4)) - (assertz 250 v238) - (let (v239 (ptr i64)) (inttoptr v237)) - (store v239 v236) - (let (v240 u32) (bitcast v172)) - (let (v241 u32) (mod.unchecked v240 4)) - (assertz 250 v241) - (let (v242 (ptr i32)) (inttoptr v240)) - (let (v243 i32) (load v242)) - (let (v244 i32) (const.i32 -4)) - (let (v245 i32) (band v243 v244)) - (let (v246 u32) (bitcast v235)) - (let (v247 u32) (mod.unchecked v246 4)) - (assertz 250 v247) - (let (v248 (ptr i32)) (inttoptr v246)) - (store v248 v245) - (let (v249 u32) (bitcast v172)) - (let (v250 u32) (mod.unchecked v249 4)) - (assertz 250 v250) - (let (v251 (ptr i32)) (inttoptr v249)) - (let (v252 i32) (load v251)) - (let (v253 i32) (const.i32 -4)) - (let (v254 i32) (band v252 v253)) - (let (v255 i1) (eq v254 0)) - (let (v256 i32) (zext v255)) - (let (v257 i1) (neq v256 0)) - (condbr v257 (block 28 v235 v229 v172 v179) (block 29))) - - (block 26 - (let (v216 i32) (band v209 v179)) - (let (v217 i1) (neq v216 0)) - (condbr v217 (block 22 v218 v219 v181 v196 v209) (block 27))) - - (block 27 - (let (v220 i32) (const.i32 -4)) - (let (v221 i32) (band v219 v220)) - (let (v222 u32) (bitcast v218)) - (let (v223 u32) (mod.unchecked v222 4)) - (assertz 250 v223) - (let (v224 (ptr i32)) (inttoptr v222)) - (store v224 v221) - (let (v225 u32) (bitcast v172)) - (let (v226 u32) (mod.unchecked v225 4)) - (assertz 250 v226) - (let (v227 (ptr i32)) (inttoptr v225)) - (let (v228 i32) (load v227)) - (br (block 24 v172 v228))) - - (block 28 - (param v280 i32) - (param v281 i32) - (param v282 i32) - (param v288 i32) - (let (v283 i32) (bor v281 v282)) - (let (v284 u32) (bitcast v280)) - (let (v285 u32) (add.checked v284 4)) - (let (v286 u32) (mod.unchecked v285 4)) - (assertz 250 v286) - (let (v287 (ptr i32)) (inttoptr v285)) - (store v287 v283) - (let (v289 u32) (bitcast v288)) - (let (v290 u32) (mod.unchecked v289 4)) - (assertz 250 v290) - (let (v291 (ptr i32)) (inttoptr v289)) - (let (v292 i32) (load v291)) - (let (v293 i32) (const.i32 -2)) - (let (v294 i32) (band v292 v293)) - (let (v295 u32) (bitcast v288)) - (let (v296 u32) (mod.unchecked v295 4)) - (assertz 250 v296) - (let (v297 (ptr i32)) (inttoptr v295)) - (store v297 v294) - (let (v298 u32) (bitcast v282)) - (let (v299 u32) (mod.unchecked v298 4)) - (assertz 250 v299) - (let (v300 (ptr i32)) (inttoptr v298)) - (let (v301 i32) (load v300)) - (let (v302 i32) (const.i32 3)) - (let (v303 i32) (band v301 v302)) - (let (v304 i32) (bor v303 v280)) - (let (v305 u32) (bitcast v282)) - (let (v306 u32) (mod.unchecked v305 4)) - (assertz 250 v306) - (let (v307 (ptr i32)) (inttoptr v305)) - (store v307 v304) - (let (v308 i32) (const.i32 2)) - (let (v309 i32) (band v301 v308)) - (let (v310 i1) (neq v309 0)) - (condbr v310 (block 31) (block 32))) - - (block 29 - (let (v258 i32) (const.i32 2)) - (let (v259 i32) (band v252 v258)) - (let (v260 i1) (neq v259 0)) - (condbr v260 (block 28 v235 v229 v172 v179) (block 30))) - - (block 30 - (let (v261 u32) (bitcast v254)) - (let (v262 u32) (add.checked v261 4)) - (let (v263 u32) (mod.unchecked v262 4)) - (assertz 250 v263) - (let (v264 (ptr i32)) (inttoptr v262)) - (let (v265 i32) (load v264)) - (let (v266 i32) (const.i32 3)) - (let (v267 i32) (band v265 v266)) - (let (v268 i32) (bor v267 v235)) - (let (v269 u32) (bitcast v254)) - (let (v270 u32) (add.checked v269 4)) - (let (v271 u32) (mod.unchecked v270 4)) - (assertz 250 v271) - (let (v272 (ptr i32)) (inttoptr v270)) - (store v272 v268) - (let (v273 u32) (bitcast v235)) - (let (v274 u32) (add.checked v273 4)) - (let (v275 u32) (mod.unchecked v274 4)) - (assertz 250 v275) - (let (v276 (ptr i32)) (inttoptr v274)) - (let (v277 i32) (load v276)) - (let (v278 i32) (const.i32 3)) - (let (v279 i32) (band v277 v278)) - (br (block 28 v235 v279 v172 v179))) - - (block 31 - (let (v315 i32) (const.i32 -3)) - (let (v316 i32) (band v304 v315)) - (let (v317 u32) (bitcast v282)) - (let (v318 u32) (mod.unchecked v317 4)) - (assertz 250 v318) - (let (v319 (ptr i32)) (inttoptr v317)) - (store v319 v316) - (let (v320 u32) (bitcast v280)) - (let (v321 u32) (mod.unchecked v320 4)) - (assertz 250 v321) - (let (v322 (ptr i32)) (inttoptr v320)) - (let (v323 i32) (load v322)) - (let (v324 i32) (const.i32 2)) - (let (v325 i32) (bor v323 v324)) - (br (block 24 v280 v325))) - - (block 32 - (let (v311 u32) (bitcast v280)) - (let (v312 u32) (mod.unchecked v311 4)) - (assertz 250 v312) - (let (v313 (ptr i32)) (inttoptr v311)) - (let (v314 i32) (load v313)) - (br (block 24 v280 v314))) - - (block 33 - (br (block 5))) - ) - - (func (export #::alloc) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 16)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i1) (neq v2 0)) - (condbr v9 (block 3) (block 4))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 (param v98 i32) (param v102 i32) - (let (v99 i32) (const.i32 16)) - (let (v100 i32) (add.wrapping v98 v99)) - (let (v101 (ptr i32)) (global.symbol #__stack_pointer)) - (store v101 v100) - (br (block 1 v102))) - - (block 3 - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr i32)) (inttoptr v10)) - (let (v13 i32) (load v12)) - (let (v14 u32) (bitcast v7)) - (let (v15 u32) (add.checked v14 12)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr i32)) (inttoptr v15)) - (store v17 v13) - (let (v18 i32) (const.i32 3)) - (let (v19 i32) (add.wrapping v2 v18)) - (let (v20 i32) (const.i32 2)) - (let (v21 u32) (bitcast v19)) - (let (v22 u32) (bitcast v20)) - (let (v23 u32) (shr.wrapping v21 v22)) - (let (v24 i32) (bitcast v23)) - (let (v25 i32) (const.i32 12)) - (let (v26 i32) (add.wrapping v7 v25)) - (let (v27 i32) (call #wee_alloc::alloc_first_fit v24 v1 v26)) - (let (v28 i1) (neq v27 0)) - (condbr v28 (block 5 v0 v7 v27) (block 6))) - - (block 4 - (br (block 2 v7 v1))) - - (block 5 (param v88 i32) (param v89 i32) (param v103 i32) - (let (v90 u32) (bitcast v89)) - (let (v91 u32) (add.checked v90 12)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (let (v94 i32) (load v93)) - (let (v95 u32) (bitcast v88)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (store v97 v94) - (br (block 2 v89 v103))) - - (block 6 - (let (v29 i32) (const.i32 -4)) - (let (v30 i32) (band v19 v29)) - (let (v31 i32) (const.i32 3)) - (let (v32 u32) (bitcast v31)) - (let (v33 i32) (shl.wrapping v1 v32)) - (let (v34 i32) (const.i32 512)) - (let (v35 i32) (add.wrapping v33 v34)) - (let (v36 u32) (bitcast v30)) - (let (v37 u32) (bitcast v35)) - (let (v38 i1) (gt v36 v37)) - (let (v39 i32) (sext v38)) - (let (v40 i1) (neq v39 0)) - (let (v41 i32) (select v40 v30 v35)) - (let (v42 i32) (const.i32 65543)) - (let (v43 i32) (add.wrapping v41 v42)) - (let (v44 i32) (const.i32 16)) - (let (v45 u32) (bitcast v43)) - (let (v46 u32) (bitcast v44)) - (let (v47 u32) (shr.wrapping v45 v46)) - (let (v48 i32) (bitcast v47)) - (let (v49 u32) (bitcast v48)) - (let (v50 i32) (memory.grow v49)) - (let (v51 i32) (const.i32 -1)) - (let (v52 i1) (neq v50 v51)) - (let (v53 i32) (zext v52)) - (let (v54 i1) (neq v53 0)) - (condbr v54 (block 7) (block 8))) - - (block 7 - (let (v56 i32) (const.i32 16)) - (let (v57 u32) (bitcast v56)) - (let (v58 i32) (shl.wrapping v50 v57)) - (let (v59 i32) (const.i32 0)) - (let (v60 u32) (bitcast v58)) - (let (v61 u32) (add.checked v60 4)) - (let (v62 u32) (mod.unchecked v61 4)) - (assertz 250 v62) - (let (v63 (ptr i32)) (inttoptr v61)) - (store v63 v59) - (let (v64 u32) (bitcast v7)) - (let (v65 u32) (add.checked v64 12)) - (let (v66 u32) (mod.unchecked v65 4)) - (assertz 250 v66) - (let (v67 (ptr i32)) (inttoptr v65)) - (let (v68 i32) (load v67)) - (let (v69 u32) (bitcast v58)) - (let (v70 u32) (add.checked v69 8)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (store v72 v68) - (let (v73 i32) (const.i32 -65536)) - (let (v74 i32) (band v43 v73)) - (let (v75 i32) (add.wrapping v58 v74)) - (let (v76 i32) (const.i32 2)) - (let (v77 i32) (bor v75 v76)) - (let (v78 u32) (bitcast v58)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v77) - (let (v81 u32) (bitcast v7)) - (let (v82 u32) (add.checked v81 12)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (store v84 v58) - (let (v85 i32) (const.i32 12)) - (let (v86 i32) (add.wrapping v7 v85)) - (let (v87 i32) (call #wee_alloc::alloc_first_fit v24 v1 v86)) - (br (block 5 v0 v7 v87))) - - (block 8 - (let (v55 i32) (const.i32 0)) - (br (block 5 v0 v7 v55))) - ) - - (func (export #::dealloc) - (param i32) (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i1) (eq v1 0)) - (let (v6 i32) (zext v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (let (v8 i1) (eq v3 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2) (block 4))) - - (block 4 - (let (v11 u32) (bitcast v0)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 i32) (const.i32 0)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (mod.unchecked v16 4)) - (assertz 250 v17) - (let (v18 (ptr i32)) (inttoptr v16)) - (store v18 v15) - (let (v19 i32) (const.i32 -8)) - (let (v20 i32) (add.wrapping v1 v19)) - (let (v21 u32) (bitcast v20)) - (let (v22 u32) (mod.unchecked v21 4)) - (assertz 250 v22) - (let (v23 (ptr i32)) (inttoptr v21)) - (let (v24 i32) (load v23)) - (let (v25 i32) (const.i32 -2)) - (let (v26 i32) (band v24 v25)) - (let (v27 u32) (bitcast v20)) - (let (v28 u32) (mod.unchecked v27 4)) - (assertz 250 v28) - (let (v29 (ptr i32)) (inttoptr v27)) - (store v29 v26) - (let (v30 i32) (const.i32 -4)) - (let (v31 i32) (add.wrapping v1 v30)) - (let (v32 u32) (bitcast v31)) - (let (v33 u32) (mod.unchecked v32 4)) - (assertz 250 v33) - (let (v34 (ptr i32)) (inttoptr v32)) - (let (v35 i32) (load v34)) - (let (v36 i32) (const.i32 -4)) - (let (v37 i32) (band v35 v36)) - (let (v38 i1) (eq v37 0)) - (let (v39 i32) (zext v38)) - (let (v40 i1) (neq v39 0)) - (condbr v40 (block 8 v24 v1 v20 v14 v0) (block 9))) - - (block 5 (param v177 i32) (param v183 i32) - (let (v185 u32) (bitcast v177)) - (let (v186 u32) (mod.unchecked v185 4)) - (assertz 250 v186) - (let (v187 (ptr i32)) (inttoptr v185)) - (store v187 v183) - (br (block 2))) - - (block 6 - (param v172 i32) - (param v173 i32) - (param v182 i32) - (param v184 i32) - (let (v174 u32) (bitcast v172)) - (let (v175 u32) (mod.unchecked v174 4)) - (assertz 250 v175) - (let (v176 (ptr i32)) (inttoptr v174)) - (store v176 v173) - (br (block 5 v182 v184))) - - (block 7 (param v168 i32) (param v178 i32) - (br (block 5 v178 v168))) - - (block 8 - (param v134 i32) - (param v150 i32) - (param v161 i32) - (param v171 i32) - (param v181 i32) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v134 v135)) - (let (v137 i1) (neq v136 0)) - (condbr v137 (block 6 v150 v171 v181 v161) (block 18))) - - (block 9 - (let (v41 u32) (bitcast v37)) - (let (v42 u32) (mod.unchecked v41 4)) - (assertz 250 v42) - (let (v43 (ptr i32)) (inttoptr v41)) - (let (v44 i32) (load v43)) - (let (v45 i32) (const.i32 1)) - (let (v46 i32) (band v44 v45)) - (let (v47 i1) (neq v46 0)) - (condbr v47 (block 8 v24 v1 v20 v14 v0) (block 10))) - - (block 10 - (let (v48 i32) (const.i32 -4)) - (let (v49 i32) (band v24 v48)) - (let (v50 i1) (neq v49 0)) - (condbr v50 (block 13) (block 14))) - - (block 11 - (param v104 i32) - (param v105 i32) - (param v111 i32) - (param v112 i32) - (param v123 i32) - (param v169 i32) - (param v179 i32) - (let (v106 i32) (const.i32 3)) - (let (v107 i32) (band v105 v106)) - (let (v108 u32) (bitcast v104)) - (let (v109 u32) (mod.unchecked v108 4)) - (assertz 250 v109) - (let (v110 (ptr i32)) (inttoptr v108)) - (store v110 v107) - (let (v113 i32) (const.i32 3)) - (let (v114 i32) (band v112 v113)) - (let (v115 u32) (bitcast v111)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (store v117 v114) - (let (v118 i32) (const.i32 2)) - (let (v119 i32) (band v112 v118)) - (let (v120 i1) (eq v119 0)) - (let (v121 i32) (zext v120)) - (let (v122 i1) (neq v121 0)) - (condbr v122 (block 7 v169 v179) (block 17))) - - (block 12 - (param v83 i32) - (param v84 i32) - (param v87 i32) - (param v94 i32) - (param v99 i32) - (param v124 i32) - (param v170 i32) - (param v180 i32) - (let (v85 i32) (const.i32 -4)) - (let (v86 i32) (band v84 v85)) - (let (v88 i32) (const.i32 3)) - (let (v89 i32) (band v87 v88)) - (let (v90 i32) (bor v86 v89)) - (let (v91 u32) (bitcast v83)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (store v93 v90) - (let (v95 u32) (bitcast v94)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (let (v98 i32) (load v97)) - (let (v100 u32) (bitcast v99)) - (let (v101 u32) (mod.unchecked v100 4)) - (assertz 250 v101) - (let (v102 (ptr i32)) (inttoptr v100)) - (let (v103 i32) (load v102)) - (br (block 11 v94 v98 v99 v103 v124 v170 v180))) - - (block 13 - (let (v51 i32) (const.i32 2)) - (let (v52 i32) (band v24 v51)) - (let (v53 i1) (neq v52 0)) - (condbr v53 (block 12 v37 v26 v44 v31 v20 v37 v14 v0) (block 15))) - - (block 14 - (br (block 12 v37 v26 v44 v31 v20 v37 v14 v0))) - - (block 15 - (let (v54 u32) (bitcast v49)) - (let (v55 u32) (add.checked v54 4)) - (let (v56 u32) (mod.unchecked v55 4)) - (assertz 250 v56) - (let (v57 (ptr i32)) (inttoptr v55)) - (let (v58 i32) (load v57)) - (let (v59 i32) (const.i32 3)) - (let (v60 i32) (band v58 v59)) - (let (v61 i32) (bor v60 v37)) - (let (v62 u32) (bitcast v49)) - (let (v63 u32) (add.checked v62 4)) - (let (v64 u32) (mod.unchecked v63 4)) - (assertz 250 v64) - (let (v65 (ptr i32)) (inttoptr v63)) - (store v65 v61) - (let (v66 u32) (bitcast v20)) - (let (v67 u32) (mod.unchecked v66 4)) - (assertz 250 v67) - (let (v68 (ptr i32)) (inttoptr v66)) - (let (v69 i32) (load v68)) - (let (v70 u32) (bitcast v31)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (let (v73 i32) (load v72)) - (let (v74 i32) (const.i32 -4)) - (let (v75 i32) (band v73 v74)) - (let (v76 i1) (eq v75 0)) - (let (v77 i32) (zext v76)) - (let (v78 i1) (neq v77 0)) - (condbr v78 (block 11 v31 v73 v20 v69 v37 v14 v0) (block 16))) - - (block 16 - (let (v79 u32) (bitcast v75)) - (let (v80 u32) (mod.unchecked v79 4)) - (assertz 250 v80) - (let (v81 (ptr i32)) (inttoptr v79)) - (let (v82 i32) (load v81)) - (br (block 12 v75 v69 v82 v31 v20 v37 v14 v0))) - - (block 17 - (let (v125 u32) (bitcast v123)) - (let (v126 u32) (mod.unchecked v125 4)) - (assertz 250 v126) - (let (v127 (ptr i32)) (inttoptr v125)) - (let (v128 i32) (load v127)) - (let (v129 i32) (const.i32 2)) - (let (v130 i32) (bor v128 v129)) - (let (v131 u32) (bitcast v123)) - (let (v132 u32) (mod.unchecked v131 4)) - (assertz 250 v132) - (let (v133 (ptr i32)) (inttoptr v131)) - (store v133 v130) - (br (block 7 v169 v179))) - - (block 18 - (let (v138 i32) (const.i32 -4)) - (let (v139 i32) (band v134 v138)) - (let (v140 i1) (eq v139 0)) - (let (v141 i32) (zext v140)) - (let (v142 i1) (neq v141 0)) - (condbr v142 (block 6 v150 v171 v181 v161) (block 19))) - - (block 19 - (let (v143 u32) (bitcast v139)) - (let (v144 (ptr u8)) (inttoptr v143)) - (let (v145 u8) (load v144)) - (let (v146 i32) (zext v145)) - (let (v147 i32) (const.i32 1)) - (let (v148 i32) (band v146 v147)) - (let (v149 i1) (neq v148 0)) - (condbr v149 (block 6 v150 v171 v181 v161) (block 20))) - - (block 20 - (let (v151 u32) (bitcast v139)) - (let (v152 u32) (add.checked v151 8)) - (let (v153 u32) (mod.unchecked v152 4)) - (assertz 250 v153) - (let (v154 (ptr i32)) (inttoptr v152)) - (let (v155 i32) (load v154)) - (let (v156 i32) (const.i32 -4)) - (let (v157 i32) (band v155 v156)) - (let (v158 u32) (bitcast v150)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v162 i32) (const.i32 1)) - (let (v163 i32) (bor v161 v162)) - (let (v164 u32) (bitcast v139)) - (let (v165 u32) (add.checked v164 8)) - (let (v166 u32) (mod.unchecked v165 4)) - (assertz 250 v166) - (let (v167 (ptr i32)) (inttoptr v165)) - (store v167 v163) - (br (block 7 v171 v181))) - ) - - (func (export #cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (call #wit_bindgen_rt::cabi_realloc v0 v1 v2 v3)) - (br (block 1 v5))) - - (block 1 (param v4 i32) - (ret v4)) - ) - - ;; Imports - (func (import #miden:add-package/add-interface@1.0.0 #add) - (param i32) (param i32) (result i32)) - ) - -) diff --git a/tests/integration/expected/components/inc_wasm_component.wat b/tests/integration/expected/components/inc_wasm_component.wat deleted file mode 100644 index 1e52129d8..000000000 --- a/tests/integration/expected/components/inc_wasm_component.wat +++ /dev/null @@ -1,707 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (func (param "a" u32) (param "b" u32) (result u32))) - (export (;0;) "add" (func (type 0))) - ) - ) - (import "miden:add-package/add-interface@1.0.0" (instance (;0;) (type 0))) - (core module (;0;) - (type (;0;) (func (param i32 i32) (result i32))) - (type (;1;) (func)) - (type (;2;) (func (param i32 i32 i32 i32) (result i32))) - (type (;3;) (func (param i32) (result i32))) - (type (;4;) (func (param i32 i32 i32) (result i32))) - (type (;5;) (func (param i32 i32 i32 i32))) - (import "miden:add-package/add-interface@1.0.0" "add" (func $inc_wasm_component::bindings::miden::add_package::add_interface::add::wit_import (;0;) (type 0))) - (func $__wasm_call_ctors (;1;) (type 1)) - (func $inc_wasm_component::bindings::__link_custom_section_describing_imports (;2;) (type 1)) - (func $__rust_alloc (;3;) (type 0) (param i32 i32) (result i32) - i32.const 1048584 - local.get 1 - local.get 0 - call $::alloc - ) - (func $__rust_realloc (;4;) (type 2) (param i32 i32 i32 i32) (result i32) - (local i32) - block ;; label = @1 - i32.const 1048584 - local.get 2 - local.get 3 - call $::alloc - local.tee 4 - i32.eqz - br_if 0 (;@1;) - local.get 4 - local.get 0 - local.get 1 - local.get 3 - local.get 1 - local.get 3 - i32.lt_u - select - memory.copy - i32.const 1048584 - local.get 0 - local.get 2 - local.get 1 - call $::dealloc - end - local.get 4 - ) - (func $inc (;5;) (type 3) (param i32) (result i32) - call $wit_bindgen_rt::run_ctors_once - local.get 0 - i32.const 1 - call $inc_wasm_component::bindings::miden::add_package::add_interface::add::wit_import - ) - (func $wit_bindgen_rt::cabi_realloc (;6;) (type 2) (param i32 i32 i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 1 - br_if 0 (;@3;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - i32.const 0 - i32.load8_u offset=1048588 - drop - local.get 3 - local.get 2 - call $__rust_alloc - local.set 2 - br 1 (;@2;) - end - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $__rust_realloc - local.set 2 - end - local.get 2 - br_if 0 (;@1;) - unreachable - end - local.get 2 - ) - (func $wit_bindgen_rt::run_ctors_once (;7;) (type 1) - block ;; label = @1 - i32.const 0 - i32.load8_u offset=1048589 - br_if 0 (;@1;) - call $__wasm_call_ctors - i32.const 0 - i32.const 1 - i32.store8 offset=1048589 - end - ) - (func $wee_alloc::alloc_first_fit (;8;) (type 4) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 2 - i32.load - local.tee 3 - br_if 0 (;@1;) - i32.const 0 - return - end - local.get 1 - i32.const -1 - i32.add - local.set 4 - i32.const 0 - local.get 1 - i32.sub - local.set 5 - local.get 0 - i32.const 2 - i32.shl - local.set 6 - loop ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - local.get 3 - i32.const 8 - i32.add - local.set 0 - br 1 (;@2;) - end - loop ;; label = @3 - local.get 3 - local.get 1 - i32.const -2 - i32.and - i32.store offset=8 - block ;; label = @4 - block ;; label = @5 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.tee 0 - br_if 0 (;@5;) - i32.const 0 - local.set 8 - br 1 (;@4;) - end - i32.const 0 - local.get 0 - local.get 0 - i32.load8_u - i32.const 1 - i32.and - select - local.set 8 - end - block ;; label = @4 - local.get 3 - i32.load - local.tee 1 - i32.const -4 - i32.and - local.tee 9 - i32.eqz - br_if 0 (;@4;) - local.get 1 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 9 - local.get 9 - i32.load offset=4 - i32.const 3 - i32.and - local.get 0 - i32.or - i32.store offset=4 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.set 0 - local.get 3 - i32.load - local.set 1 - end - block ;; label = @4 - local.get 0 - i32.eqz - br_if 0 (;@4;) - local.get 0 - local.get 0 - i32.load - i32.const 3 - i32.and - local.get 1 - i32.const -4 - i32.and - i32.or - i32.store - local.get 3 - i32.load offset=4 - local.set 7 - local.get 3 - i32.load - local.set 1 - end - local.get 3 - local.get 7 - i32.const 3 - i32.and - i32.store offset=4 - local.get 3 - local.get 1 - i32.const 3 - i32.and - i32.store - block ;; label = @4 - local.get 1 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - end - local.get 2 - local.get 8 - i32.store - local.get 8 - local.set 3 - local.get 8 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - end - local.get 8 - i32.const 8 - i32.add - local.set 0 - local.get 8 - local.set 3 - end - block ;; label = @2 - local.get 3 - i32.load - i32.const -4 - i32.and - local.tee 8 - local.get 0 - i32.sub - local.get 6 - i32.lt_u - br_if 0 (;@2;) - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.const 72 - i32.add - local.get 8 - local.get 6 - i32.sub - local.get 5 - i32.and - local.tee 8 - i32.le_u - br_if 0 (;@4;) - local.get 4 - local.get 0 - i32.and - br_if 2 (;@2;) - local.get 2 - local.get 1 - i32.const -4 - i32.and - i32.store - local.get 3 - i32.load - local.set 0 - local.get 3 - local.set 1 - br 1 (;@3;) - end - i32.const 0 - local.set 7 - local.get 8 - i32.const 0 - i32.store - local.get 8 - i32.const -8 - i32.add - local.tee 1 - i64.const 0 - i64.store align=4 - local.get 1 - local.get 3 - i32.load - i32.const -4 - i32.and - i32.store - block ;; label = @4 - local.get 3 - i32.load - local.tee 9 - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@4;) - local.get 9 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load offset=4 - i32.const 3 - i32.and - local.get 1 - i32.or - i32.store offset=4 - local.get 1 - i32.load offset=4 - i32.const 3 - i32.and - local.set 7 - end - local.get 1 - local.get 7 - local.get 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 0 - i32.load - i32.const -2 - i32.and - i32.store - local.get 3 - local.get 3 - i32.load - local.tee 0 - i32.const 3 - i32.and - local.get 1 - i32.or - local.tee 8 - i32.store - block ;; label = @4 - local.get 0 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 1 - i32.load - local.set 0 - br 1 (;@3;) - end - local.get 3 - local.get 8 - i32.const -3 - i32.and - i32.store - local.get 1 - i32.load - i32.const 2 - i32.or - local.set 0 - end - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store - local.get 1 - i32.const 8 - i32.add - return - end - local.get 2 - local.get 1 - i32.store - local.get 1 - local.set 3 - local.get 1 - br_if 0 (;@1;) - end - i32.const 0 - ) - (func $::alloc (;9;) (type 4) (param i32 i32 i32) (result i32) - (local i32 i32 i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 3 - global.set $__stack_pointer - block ;; label = @1 - block ;; label = @2 - local.get 2 - br_if 0 (;@2;) - local.get 1 - local.set 2 - br 1 (;@1;) - end - local.get 3 - local.get 0 - i32.load - i32.store offset=12 - block ;; label = @2 - local.get 2 - i32.const 3 - i32.add - local.tee 4 - i32.const 2 - i32.shr_u - local.tee 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.tee 2 - br_if 0 (;@2;) - block ;; label = @3 - local.get 4 - i32.const -4 - i32.and - local.tee 2 - local.get 1 - i32.const 3 - i32.shl - i32.const 512 - i32.add - local.tee 4 - local.get 2 - local.get 4 - i32.gt_u - select - i32.const 65543 - i32.add - local.tee 4 - i32.const 16 - i32.shr_u - memory.grow - local.tee 2 - i32.const -1 - i32.ne - br_if 0 (;@3;) - i32.const 0 - local.set 2 - br 1 (;@2;) - end - local.get 2 - i32.const 16 - i32.shl - local.tee 2 - i32.const 0 - i32.store offset=4 - local.get 2 - local.get 3 - i32.load offset=12 - i32.store offset=8 - local.get 2 - local.get 2 - local.get 4 - i32.const -65536 - i32.and - i32.add - i32.const 2 - i32.or - i32.store - local.get 3 - local.get 2 - i32.store offset=12 - local.get 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.set 2 - end - local.get 0 - local.get 3 - i32.load offset=12 - i32.store - end - local.get 3 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 2 - ) - (func $::dealloc (;10;) (type 5) (param i32 i32 i32 i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 1 - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.eqz - br_if 0 (;@1;) - local.get 0 - i32.load - local.set 4 - local.get 1 - i32.const 0 - i32.store - local.get 1 - i32.const -8 - i32.add - local.tee 3 - local.get 3 - i32.load - local.tee 5 - i32.const -2 - i32.and - local.tee 6 - i32.store - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - local.get 1 - i32.const -4 - i32.add - local.tee 7 - i32.load - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@5;) - local.get 8 - i32.load - local.tee 9 - i32.const 1 - i32.and - br_if 0 (;@5;) - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - local.get 5 - i32.const -4 - i32.and - local.tee 10 - br_if 0 (;@8;) - local.get 8 - local.set 1 - br 1 (;@7;) - end - local.get 8 - local.set 1 - local.get 5 - i32.const 2 - i32.and - br_if 0 (;@7;) - local.get 10 - local.get 10 - i32.load offset=4 - i32.const 3 - i32.and - local.get 8 - i32.or - i32.store offset=4 - local.get 3 - i32.load - local.set 6 - local.get 7 - i32.load - local.tee 5 - i32.const -4 - i32.and - local.tee 1 - i32.eqz - br_if 1 (;@6;) - local.get 1 - i32.load - local.set 9 - end - local.get 1 - local.get 6 - i32.const -4 - i32.and - local.get 9 - i32.const 3 - i32.and - i32.or - i32.store - local.get 7 - i32.load - local.set 5 - local.get 3 - i32.load - local.set 6 - end - local.get 7 - local.get 5 - i32.const 3 - i32.and - i32.store - local.get 3 - local.get 6 - i32.const 3 - i32.and - i32.store - local.get 6 - i32.const 2 - i32.and - i32.eqz - br_if 1 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - br 1 (;@4;) - end - local.get 5 - i32.const 2 - i32.and - br_if 1 (;@3;) - local.get 5 - i32.const -4 - i32.and - local.tee 5 - i32.eqz - br_if 1 (;@3;) - local.get 5 - i32.load8_u - i32.const 1 - i32.and - br_if 1 (;@3;) - local.get 1 - local.get 5 - i32.load offset=8 - i32.const -4 - i32.and - i32.store - local.get 5 - local.get 3 - i32.const 1 - i32.or - i32.store offset=8 - end - local.get 4 - local.set 3 - br 1 (;@2;) - end - local.get 1 - local.get 4 - i32.store - end - local.get 0 - local.get 3 - i32.store - end - ) - (func $cabi_realloc (;11;) (type 2) (param i32 i32 i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $wit_bindgen_rt::cabi_realloc - ) - (table (;0;) 3 3 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "inc" (func $inc)) - (export "cabi_realloc" (func $cabi_realloc)) - (export "cabi_realloc_wit_bindgen_0_28_0" (func $wit_bindgen_rt::cabi_realloc)) - (elem (;0;) (i32.const 1) func $inc_wasm_component::bindings::__link_custom_section_describing_imports $cabi_realloc) - (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00\02\00\00\00") - ) - (alias export 0 "add" (func (;0;))) - (core func (;0;) (canon lower (func 0))) - (core instance (;0;) - (export "add" (func 0)) - ) - (core instance (;1;) (instantiate 0 - (with "miden:add-package/add-interface@1.0.0" (instance 0)) - ) - ) - (alias core export 1 "memory" (core memory (;0;))) - (alias core export 1 "cabi_realloc" (core func (;1;))) - (type (;1;) (func (param "a" u32) (result u32))) - (alias core export 1 "inc" (core func (;2;))) - (func (;1;) (type 1) (canon lift (core func 2))) - (export (;2;) "inc" (func 1)) -) \ No newline at end of file diff --git a/tests/integration/expected/core::cmp::max_u8_u8.hir b/tests/integration/expected/core::cmp::max_u8_u8.hir index 64b11cf7e..2d17c0d5e 100644 --- a/tests/integration/expected/core::cmp::max_u8_u8.hir +++ b/tests/integration/expected/core::cmp::max_u8_u8.hir @@ -1,28 +1,28 @@ -(component - ;; Modules - (module #test_rust_935eefccf7923abc0816d374d2bb37d5149355aa52de63cf39d63cd92d8ee41d - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_adcf2f13ea1f22958c5a92d1674643acc093416b0b2de0fee390636adfcf090a { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v8 = arith.constant 0 : i32; + v4 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v5 = arith.gt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + v9 = arith.neq v7, v8 : i1; + v10 = cf.select v9, v1, v0 : i32; + builtin.ret v10; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (gt v3 v4)) - (let (v6 i32) (sext v5)) - (let (v7 i1) (neq v6 0)) - (let (v8 i32) (select v7 v0 v1)) - (br (block 1 v8))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/core::cmp::max_u8_u8.masm b/tests/integration/expected/core::cmp::max_u8_u8.masm index ed7250259..de4849e10 100644 --- a/tests/integration/expected/core::cmp::max_u8_u8.masm +++ b/tests/integration/expected/core::cmp::max_u8_u8.masm @@ -1,17 +1,32 @@ -# mod test_rust_935eefccf7923abc0816d374d2bb37d5149355aa52de63cf39d63cd92d8ee41d +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_adcf2f13ea1f22958c5a92d1674643acc093416b0b2de0fee390636adfcf090a export.entrypoint - dup.0 - dup.2 - u32gt push.0 - push.0 - push.4294967294 + dup.1 + dup.3 + swap.1 + u32gt + neq movup.2 - cdrop - u32or - neq.0 + swap.1 cdrop end - diff --git a/tests/integration/expected/core::cmp::max_u8_u8.wat b/tests/integration/expected/core::cmp::max_u8_u8.wat index 02d34a408..8d8ea5f36 100644 --- a/tests/integration/expected/core::cmp::max_u8_u8.wat +++ b/tests/integration/expected/core::cmp::max_u8_u8.wat @@ -1,13 +1,5 @@ -(module $test_rust_935eefccf7923abc0816d374d2bb37d5149355aa52de63cf39d63cd92d8ee41d.wasm +(module $test_rust_adcf2f13ea1f22958c5a92d1674643acc093416b0b2de0fee390636adfcf090a.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 0 - local.get 1 - i32.gt_u - select - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -16,4 +8,12 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + local.get 1 + local.get 0 + i32.gt_u + select + ) +) diff --git a/tests/integration/expected/core::cmp::min_i32_i32.hir b/tests/integration/expected/core::cmp::min_i32_i32.hir index 4a5433d22..981d81a45 100644 --- a/tests/integration/expected/core::cmp::min_i32_i32.hir +++ b/tests/integration/expected/core::cmp::min_i32_i32.hir @@ -1,26 +1,26 @@ -(component - ;; Modules - (module #test_rust_7ebd625ebc756910f700d1547e5bf4cc2c32a85181b0c8e5a3b9113c39335db7 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_473cae57ec6aea1b2fa1e6ffdabf82016fbf6e38e3acdf12f2f612d1044a9d0c { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v6 = arith.constant 0 : i32; + v3 = arith.lt v1, v0 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + v7 = arith.neq v5, v6 : i1; + v8 = cf.select v7, v1, v0 : i32; + builtin.ret v8; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (lt v0 v1)) - (let (v4 i32) (sext v3)) - (let (v5 i1) (neq v4 0)) - (let (v6 i32) (select v5 v0 v1)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/core::cmp::min_i32_i32.masm b/tests/integration/expected/core::cmp::min_i32_i32.masm index 34375c73d..9f53d02c4 100644 --- a/tests/integration/expected/core::cmp::min_i32_i32.masm +++ b/tests/integration/expected/core::cmp::min_i32_i32.masm @@ -1,17 +1,35 @@ -# mod test_rust_7ebd625ebc756910f700d1547e5bf4cc2c32a85181b0c8e5a3b9113c39335db7 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_473cae57ec6aea1b2fa1e6ffdabf82016fbf6e38e3acdf12f2f612d1044a9d0c export.entrypoint - dup.0 + push.0 + dup.2 dup.2 + trace.240 + nop exec.::intrinsics::i32::is_lt - push.0 - push.0 - push.4294967294 + trace.252 + nop + neq movup.2 - cdrop - u32or - neq.0 + swap.1 cdrop end - diff --git a/tests/integration/expected/core::cmp::min_i32_i32.wat b/tests/integration/expected/core::cmp::min_i32_i32.wat index 1b862cf7e..dbf393ef2 100644 --- a/tests/integration/expected/core::cmp::min_i32_i32.wat +++ b/tests/integration/expected/core::cmp::min_i32_i32.wat @@ -1,13 +1,5 @@ -(module $test_rust_7ebd625ebc756910f700d1547e5bf4cc2c32a85181b0c8e5a3b9113c39335db7.wasm +(module $test_rust_473cae57ec6aea1b2fa1e6ffdabf82016fbf6e38e3acdf12f2f612d1044a9d0c.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 0 - local.get 1 - i32.lt_s - select - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -16,4 +8,12 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + local.get 1 + local.get 0 + i32.lt_s + select + ) +) diff --git a/tests/integration/expected/core::cmp::min_u32_u32.hir b/tests/integration/expected/core::cmp::min_u32_u32.hir index 4744072eb..311f02e38 100644 --- a/tests/integration/expected/core::cmp::min_u32_u32.hir +++ b/tests/integration/expected/core::cmp::min_u32_u32.hir @@ -1,28 +1,28 @@ -(component - ;; Modules - (module #test_rust_2130eab9306152f86439d4a8076319464ef487ebdd8d670f656fd73a23036fa5 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_d58f2727da94ef76690ba318a633249545f9e38779d69d96f04c561d52921cd4 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v8 = arith.constant 0 : i32; + v4 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v5 = arith.lt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + v9 = arith.neq v7, v8 : i1; + v10 = cf.select v9, v1, v0 : i32; + builtin.ret v10; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lt v3 v4)) - (let (v6 i32) (sext v5)) - (let (v7 i1) (neq v6 0)) - (let (v8 i32) (select v7 v0 v1)) - (br (block 1 v8))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/core::cmp::min_u32_u32.masm b/tests/integration/expected/core::cmp::min_u32_u32.masm index 9102295e0..47d889167 100644 --- a/tests/integration/expected/core::cmp::min_u32_u32.masm +++ b/tests/integration/expected/core::cmp::min_u32_u32.masm @@ -1,17 +1,32 @@ -# mod test_rust_2130eab9306152f86439d4a8076319464ef487ebdd8d670f656fd73a23036fa5 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_d58f2727da94ef76690ba318a633249545f9e38779d69d96f04c561d52921cd4 export.entrypoint - dup.0 - dup.2 - u32lt push.0 - push.0 - push.4294967294 + dup.1 + dup.3 + swap.1 + u32lt + neq movup.2 - cdrop - u32or - neq.0 + swap.1 cdrop end - diff --git a/tests/integration/expected/core::cmp::min_u32_u32.wat b/tests/integration/expected/core::cmp::min_u32_u32.wat index 98f499672..9d5cb2dc4 100644 --- a/tests/integration/expected/core::cmp::min_u32_u32.wat +++ b/tests/integration/expected/core::cmp::min_u32_u32.wat @@ -1,13 +1,5 @@ -(module $test_rust_2130eab9306152f86439d4a8076319464ef487ebdd8d670f656fd73a23036fa5.wasm +(module $test_rust_d58f2727da94ef76690ba318a633249545f9e38779d69d96f04c561d52921cd4.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 0 - local.get 1 - i32.lt_u - select - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -16,4 +8,12 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + local.get 1 + local.get 0 + i32.lt_u + select + ) +) diff --git a/tests/integration/expected/core::cmp::min_u8_u8.hir b/tests/integration/expected/core::cmp::min_u8_u8.hir index 52a221f08..8819d5c98 100644 --- a/tests/integration/expected/core::cmp::min_u8_u8.hir +++ b/tests/integration/expected/core::cmp::min_u8_u8.hir @@ -1,28 +1,28 @@ -(component - ;; Modules - (module #test_rust_3114079be600d0d34a20fede92f64baec1c63ae3845306ba2de322513ab73343 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_86004120286ad9607be152901a86f66a999ee9dd70f4520b4ba4d6af29300327 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v8 = arith.constant 0 : i32; + v4 = hir.bitcast v0 : u32; + v3 = hir.bitcast v1 : u32; + v5 = arith.lt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + v9 = arith.neq v7, v8 : i1; + v10 = cf.select v9, v1, v0 : i32; + builtin.ret v10; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lt v3 v4)) - (let (v6 i32) (sext v5)) - (let (v7 i1) (neq v6 0)) - (let (v8 i32) (select v7 v0 v1)) - (br (block 1 v8))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/core::cmp::min_u8_u8.masm b/tests/integration/expected/core::cmp::min_u8_u8.masm index 9535c941f..70074d8b3 100644 --- a/tests/integration/expected/core::cmp::min_u8_u8.masm +++ b/tests/integration/expected/core::cmp::min_u8_u8.masm @@ -1,17 +1,32 @@ -# mod test_rust_3114079be600d0d34a20fede92f64baec1c63ae3845306ba2de322513ab73343 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_86004120286ad9607be152901a86f66a999ee9dd70f4520b4ba4d6af29300327 export.entrypoint - dup.0 - dup.2 - u32lt push.0 - push.0 - push.4294967294 + dup.1 + dup.3 + swap.1 + u32lt + neq movup.2 - cdrop - u32or - neq.0 + swap.1 cdrop end - diff --git a/tests/integration/expected/core::cmp::min_u8_u8.wat b/tests/integration/expected/core::cmp::min_u8_u8.wat index dfdef52a2..14946b4ba 100644 --- a/tests/integration/expected/core::cmp::min_u8_u8.wat +++ b/tests/integration/expected/core::cmp::min_u8_u8.wat @@ -1,13 +1,5 @@ -(module $test_rust_3114079be600d0d34a20fede92f64baec1c63ae3845306ba2de322513ab73343.wasm +(module $test_rust_86004120286ad9607be152901a86f66a999ee9dd70f4520b4ba4d6af29300327.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 0 - local.get 1 - i32.lt_u - select - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -16,4 +8,12 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + local.get 1 + local.get 0 + i32.lt_u + select + ) +) diff --git a/tests/integration/expected/div_felt.hir b/tests/integration/expected/div_felt.hir index 4f0d41b79..6f5ed64d0 100644 --- a/tests/integration/expected/div_felt.hir +++ b/tests/integration/expected/div_felt.hir @@ -1,21 +1,19 @@ -(component - ;; Modules - (module #div_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @div_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> felt { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/div_felt/intrinsics::felt::div(v0, v1) : felt + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::div(v4: felt, v5: felt) -> felt { + ^block6(v4: felt, v5: felt): + v6 = arith.div v4, v5 : felt; + builtin.ret v6; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result felt) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 felt) (div.unchecked v0 v1)) - (br (block 1 v3))) - - (block 1 (param v2 felt) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/div_felt.masm b/tests/integration/expected/div_felt.masm index feef22ba9..a3712e043 100644 --- a/tests/integration/expected/div_felt.masm +++ b/tests/integration/expected/div_felt.masm @@ -1,7 +1,27 @@ -# mod div_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::div_felt export.entrypoint - swap.1 div + trace.240 + nop + exec.::root_ns:root@1.0.0::div_felt::intrinsics::felt::div + trace.252 + nop end +proc.intrinsics::felt::div + swap.1 + div +end diff --git a/tests/integration/expected/div_felt.wat b/tests/integration/expected/div_felt.wat index 2fb798d57..c2029075e 100644 --- a/tests/integration/expected/div_felt.wat +++ b/tests/integration/expected/div_felt.wat @@ -1,14 +1,16 @@ (module $div_felt.wasm (type (;0;) (func (param f32 f32) (result f32))) - (import "miden:stdlib/intrinsics_felt" "div" (func $miden_stdlib_sys::intrinsics::felt::extern_div (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result f32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_div - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result f32) + local.get 0 + local.get 1 + call $intrinsics::felt::div + ) + (func $intrinsics::felt::div (;1;) (type 0) (param f32 f32) (result f32) + unreachable + ) +) diff --git a/tests/integration/expected/eq_felt.hir b/tests/integration/expected/eq_felt.hir index b20f6a22f..614267455 100644 --- a/tests/integration/expected/eq_felt.hir +++ b/tests/integration/expected/eq_felt.hir @@ -1,25 +1,24 @@ -(component - ;; Modules - (module #eq_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @eq_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> i32 { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/eq_felt/intrinsics::felt::eq(v0, v1) : i32 + v4 = arith.constant 1 : i32; + v5 = arith.eq v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::eq(v8: felt, v9: felt) -> i32 { + ^block6(v8: felt, v9: felt): + v10 = arith.eq v8, v9 : i1; + v11 = hir.cast v10 : i32; + builtin.ret v11; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result i32) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (cast v3)) - (let (v5 i32) (const.i32 1)) - (let (v6 i1) (eq v4 v5)) - (let (v7 i32) (zext v6)) - (br (block 1 v7))) - - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_felt.masm b/tests/integration/expected/eq_felt.masm index bab4b735b..74b2aa969 100644 --- a/tests/integration/expected/eq_felt.masm +++ b/tests/integration/expected/eq_felt.masm @@ -1,7 +1,28 @@ -# mod eq_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::eq_felt export.entrypoint - swap.1 eq push.1 eq + trace.240 + nop + exec.::root_ns:root@1.0.0::eq_felt::intrinsics::felt::eq + trace.252 + nop + push.1 + eq end +proc.intrinsics::felt::eq + eq +end diff --git a/tests/integration/expected/eq_felt.wat b/tests/integration/expected/eq_felt.wat index d4750e0de..40c2f6062 100644 --- a/tests/integration/expected/eq_felt.wat +++ b/tests/integration/expected/eq_felt.wat @@ -1,16 +1,18 @@ (module $eq_felt.wasm (type (;0;) (func (param f32 f32) (result i32))) - (import "miden:stdlib/intrinsics_felt" "eq" (func $miden_stdlib_sys::intrinsics::felt::extern_eq (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result i32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_eq - i32.const 1 - i32.eq - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result i32) + local.get 0 + local.get 1 + call $intrinsics::felt::eq + i32.const 1 + i32.eq + ) + (func $intrinsics::felt::eq (;1;) (type 0) (param f32 f32) (result i32) + unreachable + ) +) diff --git a/tests/integration/expected/eq_i16.hir b/tests/integration/expected/eq_i16.hir index bc617caae..f32f14e5b 100644 --- a/tests/integration/expected/eq_i16.hir +++ b/tests/integration/expected/eq_i16.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_2f29f68fe27c60920a7ea2ddd41d7b6f088aaca71b06fe98e8543c00277965f4 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_517fb449f17b407495bba1888d666bc76885dc2541249b2f6cfe05173973099e { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_i16.masm b/tests/integration/expected/eq_i16.masm index b1d06722d..70d3b8a1a 100644 --- a/tests/integration/expected/eq_i16.masm +++ b/tests/integration/expected/eq_i16.masm @@ -1,7 +1,24 @@ -# mod test_rust_2f29f68fe27c60920a7ea2ddd41d7b6f088aaca71b06fe98e8543c00277965f4 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_517fb449f17b407495bba1888d666bc76885dc2541249b2f6cfe05173973099e + +export.entrypoint + eq +end diff --git a/tests/integration/expected/eq_i16.wat b/tests/integration/expected/eq_i16.wat index 35a6a6a94..ff074f00f 100644 --- a/tests/integration/expected/eq_i16.wat +++ b/tests/integration/expected/eq_i16.wat @@ -1,10 +1,5 @@ -(module $test_rust_2f29f68fe27c60920a7ea2ddd41d7b6f088aaca71b06fe98e8543c00277965f4.wasm +(module $test_rust_517fb449f17b407495bba1888d666bc76885dc2541249b2f6cfe05173973099e.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.eq + ) +) diff --git a/tests/integration/expected/eq_i32.hir b/tests/integration/expected/eq_i32.hir index 7afb95813..732258bd3 100644 --- a/tests/integration/expected/eq_i32.hir +++ b/tests/integration/expected/eq_i32.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_c28e7308ea5f55f6d78b6647cb9c8148700b1b42240fe9786556265f83fcda8f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_3a92b0607377b819decb1029f8199a70990302e258cc8ee55f612c4d879c74f4 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_i32.masm b/tests/integration/expected/eq_i32.masm index b1f094ef3..fa5b90ea7 100644 --- a/tests/integration/expected/eq_i32.masm +++ b/tests/integration/expected/eq_i32.masm @@ -1,7 +1,24 @@ -# mod test_rust_c28e7308ea5f55f6d78b6647cb9c8148700b1b42240fe9786556265f83fcda8f +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_3a92b0607377b819decb1029f8199a70990302e258cc8ee55f612c4d879c74f4 + +export.entrypoint + eq +end diff --git a/tests/integration/expected/eq_i32.wat b/tests/integration/expected/eq_i32.wat index 7084d5fd5..4d5ad8038 100644 --- a/tests/integration/expected/eq_i32.wat +++ b/tests/integration/expected/eq_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_c28e7308ea5f55f6d78b6647cb9c8148700b1b42240fe9786556265f83fcda8f.wasm +(module $test_rust_3a92b0607377b819decb1029f8199a70990302e258cc8ee55f612c4d879c74f4.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.eq + ) +) diff --git a/tests/integration/expected/eq_i64.hir b/tests/integration/expected/eq_i64.hir index c30ce7c09..6e102cfea 100644 --- a/tests/integration/expected/eq_i64.hir +++ b/tests/integration/expected/eq_i64.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_335e8b9940dcc6e8b83a7c4b5c50143e223e0f4e2cb269440866b6f3f4a0d5a6 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_bcf098a70fb61198130acb429b0b2900b9c9d11c5cab0241410bb862be7ce2a8 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_i64.masm b/tests/integration/expected/eq_i64.masm index c2ec42865..a76ad3882 100644 --- a/tests/integration/expected/eq_i64.masm +++ b/tests/integration/expected/eq_i64.masm @@ -1,7 +1,28 @@ -# mod test_rust_335e8b9940dcc6e8b83a7c4b5c50143e223e0f4e2cb269440866b6f3f4a0d5a6 +# mod root_ns:root@1.0.0 -export.entrypoint - movdn.3 movdn.3 exec.::std::math::u64::eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_bcf098a70fb61198130acb429b0b2900b9c9d11c5cab0241410bb862be7ce2a8 + +export.entrypoint + trace.240 + nop + exec.::std::math::u64::eq + trace.252 + nop +end diff --git a/tests/integration/expected/eq_i64.wat b/tests/integration/expected/eq_i64.wat index 7b9db9cdc..ab2f05a89 100644 --- a/tests/integration/expected/eq_i64.wat +++ b/tests/integration/expected/eq_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_335e8b9940dcc6e8b83a7c4b5c50143e223e0f4e2cb269440866b6f3f4a0d5a6.wasm +(module $test_rust_bcf098a70fb61198130acb429b0b2900b9c9d11c5cab0241410bb862be7ce2a8.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.eq + ) +) diff --git a/tests/integration/expected/eq_i8.hir b/tests/integration/expected/eq_i8.hir index a9a6df2db..018131232 100644 --- a/tests/integration/expected/eq_i8.hir +++ b/tests/integration/expected/eq_i8.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_d32112fcece9e923c53ef0e7e530a320a042569dc27aa4e4b1682a14933a630b - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_c2c2ad0caabf21644314027afcbfb75555fa063ab09504517a70196ddd47c892 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_i8.masm b/tests/integration/expected/eq_i8.masm index 606f7ed5b..b3547e844 100644 --- a/tests/integration/expected/eq_i8.masm +++ b/tests/integration/expected/eq_i8.masm @@ -1,7 +1,24 @@ -# mod test_rust_d32112fcece9e923c53ef0e7e530a320a042569dc27aa4e4b1682a14933a630b +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_c2c2ad0caabf21644314027afcbfb75555fa063ab09504517a70196ddd47c892 + +export.entrypoint + eq +end diff --git a/tests/integration/expected/eq_i8.wat b/tests/integration/expected/eq_i8.wat index f472fc8a4..0710b1cab 100644 --- a/tests/integration/expected/eq_i8.wat +++ b/tests/integration/expected/eq_i8.wat @@ -1,10 +1,5 @@ -(module $test_rust_d32112fcece9e923c53ef0e7e530a320a042569dc27aa4e4b1682a14933a630b.wasm +(module $test_rust_c2c2ad0caabf21644314027afcbfb75555fa063ab09504517a70196ddd47c892.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.eq + ) +) diff --git a/tests/integration/expected/eq_u16.hir b/tests/integration/expected/eq_u16.hir index 7c2a5c3eb..f89646369 100644 --- a/tests/integration/expected/eq_u16.hir +++ b/tests/integration/expected/eq_u16.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_8fbd508d452e6be7333d34171256184bccb35bce63b4c4b324a68dfa222b449a - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_5e53494a4e3a0adfb638a6e562baf4c446d2f54c63ea6d33dec923896f12cea8 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_u16.masm b/tests/integration/expected/eq_u16.masm index 3418a8a4d..9deed334a 100644 --- a/tests/integration/expected/eq_u16.masm +++ b/tests/integration/expected/eq_u16.masm @@ -1,7 +1,24 @@ -# mod test_rust_8fbd508d452e6be7333d34171256184bccb35bce63b4c4b324a68dfa222b449a +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_5e53494a4e3a0adfb638a6e562baf4c446d2f54c63ea6d33dec923896f12cea8 + +export.entrypoint + eq +end diff --git a/tests/integration/expected/eq_u16.wat b/tests/integration/expected/eq_u16.wat index 9da87152b..fc5f1082e 100644 --- a/tests/integration/expected/eq_u16.wat +++ b/tests/integration/expected/eq_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_8fbd508d452e6be7333d34171256184bccb35bce63b4c4b324a68dfa222b449a.wasm +(module $test_rust_5e53494a4e3a0adfb638a6e562baf4c446d2f54c63ea6d33dec923896f12cea8.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.eq + ) +) diff --git a/tests/integration/expected/eq_u32.hir b/tests/integration/expected/eq_u32.hir index 1946c5092..cc2791697 100644 --- a/tests/integration/expected/eq_u32.hir +++ b/tests/integration/expected/eq_u32.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_1ec18cd34684e347de59c6bcc66cf36588b84dc96b83ce847862c54131b73875 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_528be949250a0d0dcd9cd0e4c74a012863f8f305b2ac8043a575d81640315ac0 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_u32.masm b/tests/integration/expected/eq_u32.masm index 114e02390..396d8ea31 100644 --- a/tests/integration/expected/eq_u32.masm +++ b/tests/integration/expected/eq_u32.masm @@ -1,7 +1,24 @@ -# mod test_rust_1ec18cd34684e347de59c6bcc66cf36588b84dc96b83ce847862c54131b73875 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_528be949250a0d0dcd9cd0e4c74a012863f8f305b2ac8043a575d81640315ac0 + +export.entrypoint + eq +end diff --git a/tests/integration/expected/eq_u32.wat b/tests/integration/expected/eq_u32.wat index d462760db..3f6706456 100644 --- a/tests/integration/expected/eq_u32.wat +++ b/tests/integration/expected/eq_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_1ec18cd34684e347de59c6bcc66cf36588b84dc96b83ce847862c54131b73875.wasm +(module $test_rust_528be949250a0d0dcd9cd0e4c74a012863f8f305b2ac8043a575d81640315ac0.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.eq + ) +) diff --git a/tests/integration/expected/eq_u64.hir b/tests/integration/expected/eq_u64.hir index d1c253ae8..f52b6774a 100644 --- a/tests/integration/expected/eq_u64.hir +++ b/tests/integration/expected/eq_u64.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_d3f2b162735bb643456cb68c71d6be70cdb8e4267c7ef3c9cdef6fcfdb77c2cc - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_90e3e1f038c3b9b7ba2a8c0e9313f59dc3bc3f54fe235a4c5c325df06e46d3b0 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_u64.masm b/tests/integration/expected/eq_u64.masm index 80c8f8d6a..f8aa123b9 100644 --- a/tests/integration/expected/eq_u64.masm +++ b/tests/integration/expected/eq_u64.masm @@ -1,7 +1,28 @@ -# mod test_rust_d3f2b162735bb643456cb68c71d6be70cdb8e4267c7ef3c9cdef6fcfdb77c2cc +# mod root_ns:root@1.0.0 -export.entrypoint - movdn.3 movdn.3 exec.::std::math::u64::eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_90e3e1f038c3b9b7ba2a8c0e9313f59dc3bc3f54fe235a4c5c325df06e46d3b0 + +export.entrypoint + trace.240 + nop + exec.::std::math::u64::eq + trace.252 + nop +end diff --git a/tests/integration/expected/eq_u64.wat b/tests/integration/expected/eq_u64.wat index 54b20c02d..301861ea9 100644 --- a/tests/integration/expected/eq_u64.wat +++ b/tests/integration/expected/eq_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_d3f2b162735bb643456cb68c71d6be70cdb8e4267c7ef3c9cdef6fcfdb77c2cc.wasm +(module $test_rust_90e3e1f038c3b9b7ba2a8c0e9313f59dc3bc3f54fe235a4c5c325df06e46d3b0.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.eq + ) +) diff --git a/tests/integration/expected/eq_u8.hir b/tests/integration/expected/eq_u8.hir index 4ee310dbc..fdc063bb6 100644 --- a/tests/integration/expected/eq_u8.hir +++ b/tests/integration/expected/eq_u8.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_6b299c822e52963f8b3c87b898b867e2147f25b53bb5c38ab829254425824be5 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_909444ffebd8fe6b867a4d7833f385ece2e4ae4363fd04241342e9bef1affc93 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.eq v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (eq v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/eq_u8.masm b/tests/integration/expected/eq_u8.masm index b95364737..63b7b38a9 100644 --- a/tests/integration/expected/eq_u8.masm +++ b/tests/integration/expected/eq_u8.masm @@ -1,7 +1,24 @@ -# mod test_rust_6b299c822e52963f8b3c87b898b867e2147f25b53bb5c38ab829254425824be5 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 eq +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_909444ffebd8fe6b867a4d7833f385ece2e4ae4363fd04241342e9bef1affc93 + +export.entrypoint + eq +end diff --git a/tests/integration/expected/eq_u8.wat b/tests/integration/expected/eq_u8.wat index 8189c739a..ec2d6b902 100644 --- a/tests/integration/expected/eq_u8.wat +++ b/tests/integration/expected/eq_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_6b299c822e52963f8b3c87b898b867e2147f25b53bb5c38ab829254425824be5.wasm +(module $test_rust_909444ffebd8fe6b867a4d7833f385ece2e4ae4363fd04241342e9bef1affc93.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.eq - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.eq + ) +) diff --git a/tests/integration/expected/examples/auth_component_no_auth.hir b/tests/integration/expected/examples/auth_component_no_auth.hir new file mode 100644 index 000000000..a4d1ad560 --- /dev/null +++ b/tests/integration/expected/examples/auth_component_no_auth.hir @@ -0,0 +1,476 @@ +builtin.component miden:base/authentication-component@1.0.0 { + builtin.module public @auth_component_no_auth { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @auth_component_no_auth::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:base/authentication-component@1.0.0#auth-procedure(v0: felt, v1: felt, v2: felt, v3: felt) { + ^block9(v0: felt, v1: felt, v2: felt, v3: felt): + v5 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_no_auth/__stack_pointer : ptr + v6 = hir.bitcast v5 : ptr; + v7 = hir.load v6 : i32; + v8 = arith.constant 64 : i32; + v9 = arith.sub v7, v8 : i32 #[overflow = wrapping]; + v10 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_no_auth/__stack_pointer : ptr + v11 = hir.bitcast v10 : ptr; + hir.store v11, v9; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/wit_bindgen::rt::run_ctors_once() + v12 = arith.constant 32 : i32; + v13 = arith.add v9, v12 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/miden::account::get_initial_commitment(v13) + v15 = arith.constant 40 : u32; + v14 = hir.bitcast v9 : u32; + v16 = arith.add v14, v15 : u32 #[overflow = checked]; + v17 = arith.constant 8 : u32; + v18 = arith.mod v16, v17 : u32; + hir.assertz v18 #[code = 250]; + v19 = hir.int_to_ptr v16 : ptr; + v20 = hir.load v19 : i64; + v22 = arith.constant 56 : u32; + v21 = hir.bitcast v9 : u32; + v23 = arith.add v21, v22 : u32 #[overflow = checked]; + v384 = arith.constant 8 : u32; + v25 = arith.mod v23, v384 : u32; + hir.assertz v25 #[code = 250]; + v26 = hir.int_to_ptr v23 : ptr; + hir.store v26, v20; + v28 = arith.constant 32 : u32; + v27 = hir.bitcast v9 : u32; + v29 = arith.add v27, v28 : u32 #[overflow = checked]; + v383 = arith.constant 8 : u32; + v31 = arith.mod v29, v383 : u32; + hir.assertz v31 #[code = 250]; + v32 = hir.int_to_ptr v29 : ptr; + v33 = hir.load v32 : i64; + v35 = arith.constant 48 : u32; + v34 = hir.bitcast v9 : u32; + v36 = arith.add v34, v35 : u32 #[overflow = checked]; + v382 = arith.constant 8 : u32; + v38 = arith.mod v36, v382 : u32; + hir.assertz v38 #[code = 250]; + v39 = hir.int_to_ptr v36 : ptr; + hir.store v39, v33; + v40 = arith.constant 48 : i32; + v41 = arith.add v9, v40 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/miden_stdlib_sys::intrinsics::word::Word::reverse(v9, v41) + v381 = arith.constant 32 : i32; + v43 = arith.add v9, v381 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/miden::account::compute_current_commitment(v43) + v380 = arith.constant 40 : u32; + v44 = hir.bitcast v9 : u32; + v46 = arith.add v44, v380 : u32 #[overflow = checked]; + v379 = arith.constant 8 : u32; + v48 = arith.mod v46, v379 : u32; + hir.assertz v48 #[code = 250]; + v49 = hir.int_to_ptr v46 : ptr; + v50 = hir.load v49 : i64; + v378 = arith.constant 56 : u32; + v51 = hir.bitcast v9 : u32; + v53 = arith.add v51, v378 : u32 #[overflow = checked]; + v377 = arith.constant 8 : u32; + v55 = arith.mod v53, v377 : u32; + hir.assertz v55 #[code = 250]; + v56 = hir.int_to_ptr v53 : ptr; + hir.store v56, v50; + v376 = arith.constant 32 : u32; + v57 = hir.bitcast v9 : u32; + v59 = arith.add v57, v376 : u32 #[overflow = checked]; + v375 = arith.constant 8 : u32; + v61 = arith.mod v59, v375 : u32; + hir.assertz v61 #[code = 250]; + v62 = hir.int_to_ptr v59 : ptr; + v63 = hir.load v62 : i64; + v374 = arith.constant 48 : u32; + v64 = hir.bitcast v9 : u32; + v66 = arith.add v64, v374 : u32 #[overflow = checked]; + v373 = arith.constant 8 : u32; + v68 = arith.mod v66, v373 : u32; + hir.assertz v68 #[code = 250]; + v69 = hir.int_to_ptr v66 : ptr; + hir.store v69, v63; + v372 = arith.constant 48 : i32; + v73 = arith.add v9, v372 : i32 #[overflow = wrapping]; + v70 = arith.constant 16 : i32; + v71 = arith.add v9, v70 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/miden_stdlib_sys::intrinsics::word::Word::reverse(v71, v73) + v75 = arith.constant 16 : u32; + v74 = hir.bitcast v9 : u32; + v76 = arith.add v74, v75 : u32 #[overflow = checked]; + v77 = arith.constant 4 : u32; + v78 = arith.mod v76, v77 : u32; + hir.assertz v78 #[code = 250]; + v79 = hir.int_to_ptr v76 : ptr; + v80 = hir.load v79 : felt; + v81 = hir.bitcast v9 : u32; + v371 = arith.constant 4 : u32; + v83 = arith.mod v81, v371 : u32; + hir.assertz v83 #[code = 250]; + v84 = hir.int_to_ptr v81 : ptr; + v85 = hir.load v84 : felt; + v86 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/intrinsics::felt::eq(v80, v85) : i32 + v4 = arith.constant 0 : i32; + v87 = arith.constant 1 : i32; + v88 = arith.neq v86, v87 : i1; + v89 = arith.zext v88 : u32; + v90 = hir.bitcast v89 : i32; + v92 = arith.neq v90, v4 : i1; + v327, v328, v329 = scf.if v92 : i32, i32, u32 { + ^block42: + v318 = arith.constant 0 : u32; + v322 = ub.poison i32 : i32; + scf.yield v9, v322, v318; + } else { + ^block13: + v94 = arith.constant 20 : u32; + v93 = hir.bitcast v9 : u32; + v95 = arith.add v93, v94 : u32 #[overflow = checked]; + v370 = arith.constant 4 : u32; + v97 = arith.mod v95, v370 : u32; + hir.assertz v97 #[code = 250]; + v98 = hir.int_to_ptr v95 : ptr; + v99 = hir.load v98 : felt; + v369 = arith.constant 4 : u32; + v100 = hir.bitcast v9 : u32; + v102 = arith.add v100, v369 : u32 #[overflow = checked]; + v368 = arith.constant 4 : u32; + v104 = arith.mod v102, v368 : u32; + hir.assertz v104 #[code = 250]; + v105 = hir.int_to_ptr v102 : ptr; + v106 = hir.load v105 : felt; + v107 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/intrinsics::felt::eq(v99, v106) : i32 + v366 = arith.constant 0 : i32; + v367 = arith.constant 1 : i32; + v109 = arith.neq v107, v367 : i1; + v110 = arith.zext v109 : u32; + v111 = hir.bitcast v110 : i32; + v113 = arith.neq v111, v366 : i1; + v334, v335, v336 = scf.if v113 : i32, i32, u32 { + ^block41: + v364 = arith.constant 0 : u32; + v365 = ub.poison i32 : i32; + scf.yield v9, v365, v364; + } else { + ^block14: + v115 = arith.constant 24 : u32; + v114 = hir.bitcast v9 : u32; + v116 = arith.add v114, v115 : u32 #[overflow = checked]; + v363 = arith.constant 4 : u32; + v118 = arith.mod v116, v363 : u32; + hir.assertz v118 #[code = 250]; + v119 = hir.int_to_ptr v116 : ptr; + v120 = hir.load v119 : felt; + v362 = arith.constant 8 : u32; + v121 = hir.bitcast v9 : u32; + v123 = arith.add v121, v362 : u32 #[overflow = checked]; + v361 = arith.constant 4 : u32; + v125 = arith.mod v123, v361 : u32; + hir.assertz v125 #[code = 250]; + v126 = hir.int_to_ptr v123 : ptr; + v127 = hir.load v126 : felt; + v128 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/intrinsics::felt::eq(v120, v127) : i32 + v359 = arith.constant 0 : i32; + v360 = arith.constant 1 : i32; + v130 = arith.neq v128, v360 : i1; + v131 = arith.zext v130 : u32; + v132 = hir.bitcast v131 : i32; + v134 = arith.neq v132, v359 : i1; + v340, v341, v342 = scf.if v134 : i32, i32, u32 { + ^block40: + v357 = arith.constant 0 : u32; + v358 = ub.poison i32 : i32; + scf.yield v9, v358, v357; + } else { + ^block15: + v136 = arith.constant 28 : u32; + v135 = hir.bitcast v9 : u32; + v137 = arith.add v135, v136 : u32 #[overflow = checked]; + v356 = arith.constant 4 : u32; + v139 = arith.mod v137, v356 : u32; + hir.assertz v139 #[code = 250]; + v140 = hir.int_to_ptr v137 : ptr; + v141 = hir.load v140 : felt; + v143 = arith.constant 12 : u32; + v142 = hir.bitcast v9 : u32; + v144 = arith.add v142, v143 : u32 #[overflow = checked]; + v355 = arith.constant 4 : u32; + v146 = arith.mod v144, v355 : u32; + hir.assertz v146 #[code = 250]; + v147 = hir.int_to_ptr v144 : ptr; + v148 = hir.load v147 : felt; + v149 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/intrinsics::felt::eq(v141, v148) : i32 + v353 = arith.constant 0 : i32; + v354 = arith.constant 1 : i32; + v151 = arith.eq v149, v354 : i1; + v152 = arith.zext v151 : u32; + v153 = hir.bitcast v152 : i32; + v155 = arith.neq v153, v353 : i1; + v350 = arith.constant 0 : u32; + v323 = arith.constant 1 : u32; + v348 = cf.select v155, v323, v350 : u32; + v351 = ub.poison i32 : i32; + v347 = cf.select v155, v9, v351 : i32; + v352 = ub.poison i32 : i32; + v346 = cf.select v155, v352, v9 : i32; + scf.yield v346, v347, v348; + }; + scf.yield v340, v341, v342; + }; + scf.yield v334, v335, v336; + }; + v330 = scf.index_switch v329 : i32 + case 0 { + ^block12: + v156 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/miden::account::incr_nonce() : felt + scf.yield v327; + } + default { + ^block46: + scf.yield v328; + }; + v349 = arith.constant 64 : i32; + v160 = arith.add v330, v349 : i32 #[overflow = wrapping]; + v161 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_no_auth/__stack_pointer : ptr + v162 = hir.bitcast v161 : ptr; + hir.store v162, v160; + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block17: + v164 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_no_auth/GOT.data.internal.__memory_base : ptr + v165 = hir.bitcast v164 : ptr; + v166 = hir.load v165 : i32; + v167 = arith.constant 1048584 : i32; + v168 = arith.add v166, v167 : i32 #[overflow = wrapping]; + v169 = hir.bitcast v168 : u32; + v170 = hir.int_to_ptr v169 : ptr; + v171 = hir.load v170 : u8; + v163 = arith.constant 0 : i32; + v172 = arith.zext v171 : u32; + v173 = hir.bitcast v172 : i32; + v175 = arith.neq v173, v163 : i1; + scf.if v175{ + ^block19: + scf.yield ; + } else { + ^block20: + v176 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_no_auth/GOT.data.internal.__memory_base : ptr + v177 = hir.bitcast v176 : ptr; + v178 = hir.load v177 : i32; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/__wasm_call_ctors() + v386 = arith.constant 1 : u8; + v388 = arith.constant 1048584 : i32; + v180 = arith.add v178, v388 : i32 #[overflow = wrapping]; + v184 = hir.bitcast v180 : u32; + v185 = hir.int_to_ptr v184 : ptr; + hir.store v185, v386; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v186: i32, v187: i32) { + ^block21(v186: i32, v187: i32): + v190 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_no_auth/__stack_pointer : ptr + v191 = hir.bitcast v190 : ptr; + v192 = hir.load v191 : i32; + v193 = arith.constant 16 : i32; + v194 = arith.sub v192, v193 : i32 #[overflow = wrapping]; + v196 = arith.constant 8 : u32; + v195 = hir.bitcast v187 : u32; + v197 = arith.add v195, v196 : u32 #[overflow = checked]; + v475 = arith.constant 8 : u32; + v199 = arith.mod v197, v475 : u32; + hir.assertz v199 #[code = 250]; + v200 = hir.int_to_ptr v197 : ptr; + v201 = hir.load v200 : i64; + v474 = arith.constant 8 : u32; + v202 = hir.bitcast v194 : u32; + v204 = arith.add v202, v474 : u32 #[overflow = checked]; + v205 = arith.constant 4 : u32; + v206 = arith.mod v204, v205 : u32; + hir.assertz v206 #[code = 250]; + v207 = hir.int_to_ptr v204 : ptr; + hir.store v207, v201; + v208 = hir.bitcast v187 : u32; + v473 = arith.constant 8 : u32; + v210 = arith.mod v208, v473 : u32; + hir.assertz v210 #[code = 250]; + v211 = hir.int_to_ptr v208 : ptr; + v212 = hir.load v211 : i64; + v213 = hir.bitcast v194 : u32; + v472 = arith.constant 4 : u32; + v215 = arith.mod v213, v472 : u32; + hir.assertz v215 #[code = 250]; + v216 = hir.int_to_ptr v213 : ptr; + hir.store v216, v212; + v217 = arith.constant 12 : i32; + v218 = arith.add v194, v217 : i32 #[overflow = wrapping]; + v188 = arith.constant 0 : i32; + v443, v444, v445, v446, v447, v448 = scf.while v188, v194, v218, v186 : i32, i32, i32, i32, i32, i32 { + ^block57(v449: i32, v450: i32, v451: i32, v452: i32): + v471 = arith.constant 0 : i32; + v221 = arith.constant 8 : i32; + v222 = arith.eq v449, v221 : i1; + v223 = arith.zext v222 : u32; + v224 = hir.bitcast v223 : i32; + v226 = arith.neq v224, v471 : i1; + v437, v438 = scf.if v226 : i32, i32 { + ^block56: + v397 = ub.poison i32 : i32; + scf.yield v397, v397; + } else { + ^block26: + v228 = arith.add v450, v449 : i32 #[overflow = wrapping]; + v229 = hir.bitcast v228 : u32; + v470 = arith.constant 4 : u32; + v231 = arith.mod v229, v470 : u32; + hir.assertz v231 #[code = 250]; + v232 = hir.int_to_ptr v229 : ptr; + v233 = hir.load v232 : felt; + v235 = hir.bitcast v451 : u32; + v469 = arith.constant 4 : u32; + v237 = arith.mod v235, v469 : u32; + hir.assertz v237 #[code = 250]; + v238 = hir.int_to_ptr v235 : ptr; + v239 = hir.load v238 : i32; + v240 = hir.bitcast v228 : u32; + v468 = arith.constant 4 : u32; + v242 = arith.mod v240, v468 : u32; + hir.assertz v242 #[code = 250]; + v243 = hir.int_to_ptr v240 : ptr; + hir.store v243, v239; + v244 = hir.bitcast v451 : u32; + v467 = arith.constant 4 : u32; + v246 = arith.mod v244, v467 : u32; + hir.assertz v246 #[code = 250]; + v247 = hir.int_to_ptr v244 : ptr; + hir.store v247, v233; + v250 = arith.constant -4 : i32; + v251 = arith.add v451, v250 : i32 #[overflow = wrapping]; + v248 = arith.constant 4 : i32; + v249 = arith.add v449, v248 : i32 #[overflow = wrapping]; + scf.yield v249, v251; + }; + v465 = ub.poison i32 : i32; + v440 = cf.select v226, v465, v452 : i32; + v466 = ub.poison i32 : i32; + v439 = cf.select v226, v466, v450 : i32; + v396 = arith.constant 1 : u32; + v389 = arith.constant 0 : u32; + v442 = cf.select v226, v389, v396 : u32; + v430 = arith.trunc v442 : i1; + scf.condition v430, v437, v439, v438, v440, v450, v452; + } do { + ^block58(v453: i32, v454: i32, v455: i32, v456: i32, v457: i32, v458: i32): + scf.yield v453, v454, v455, v456; + }; + v464 = arith.constant 8 : u32; + v253 = hir.bitcast v447 : u32; + v255 = arith.add v253, v464 : u32 #[overflow = checked]; + v463 = arith.constant 4 : u32; + v257 = arith.mod v255, v463 : u32; + hir.assertz v257 #[code = 250]; + v258 = hir.int_to_ptr v255 : ptr; + v259 = hir.load v258 : i64; + v462 = arith.constant 8 : u32; + v260 = hir.bitcast v448 : u32; + v262 = arith.add v260, v462 : u32 #[overflow = checked]; + v461 = arith.constant 8 : u32; + v264 = arith.mod v262, v461 : u32; + hir.assertz v264 #[code = 250]; + v265 = hir.int_to_ptr v262 : ptr; + hir.store v265, v259; + v266 = hir.bitcast v447 : u32; + v460 = arith.constant 4 : u32; + v268 = arith.mod v266, v460 : u32; + hir.assertz v268 #[code = 250]; + v269 = hir.int_to_ptr v266 : ptr; + v270 = hir.load v269 : i64; + v271 = hir.bitcast v448 : u32; + v459 = arith.constant 8 : u32; + v273 = arith.mod v271, v459 : u32; + hir.assertz v273 #[code = 250]; + v274 = hir.int_to_ptr v271 : ptr; + hir.store v274, v270; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::eq(v275: felt, v276: felt) -> i32 { + ^block27(v275: felt, v276: felt): + v277 = arith.eq v275, v276 : i1; + v278 = hir.cast v277 : i32; + builtin.ret v278; + }; + + private builtin.function @miden::account::get_initial_commitment(v280: i32) { + ^block29(v280: i32): + v281, v282, v283, v284 = hir.exec @miden/account/get_initial_commitment() : felt, felt, felt, felt + v285 = hir.bitcast v280 : u32; + v286 = hir.int_to_ptr v285 : ptr; + hir.store v286, v281; + v287 = arith.constant 4 : u32; + v288 = arith.add v285, v287 : u32 #[overflow = checked]; + v289 = hir.int_to_ptr v288 : ptr; + hir.store v289, v282; + v290 = arith.constant 8 : u32; + v291 = arith.add v285, v290 : u32 #[overflow = checked]; + v292 = hir.int_to_ptr v291 : ptr; + hir.store v292, v283; + v293 = arith.constant 12 : u32; + v294 = arith.add v285, v293 : u32 #[overflow = checked]; + v295 = hir.int_to_ptr v294 : ptr; + hir.store v295, v284; + builtin.ret ; + }; + + private builtin.function @miden::account::compute_current_commitment(v296: i32) { + ^block33(v296: i32): + v297, v298, v299, v300 = hir.exec @miden/account/compute_current_commitment() : felt, felt, felt, felt + v301 = hir.bitcast v296 : u32; + v302 = hir.int_to_ptr v301 : ptr; + hir.store v302, v297; + v303 = arith.constant 4 : u32; + v304 = arith.add v301, v303 : u32 #[overflow = checked]; + v305 = hir.int_to_ptr v304 : ptr; + hir.store v305, v298; + v306 = arith.constant 8 : u32; + v307 = arith.add v301, v306 : u32 #[overflow = checked]; + v308 = hir.int_to_ptr v307 : ptr; + hir.store v308, v299; + v309 = arith.constant 12 : u32; + v310 = arith.add v301, v309 : u32 #[overflow = checked]; + v311 = hir.int_to_ptr v310 : ptr; + hir.store v311, v300; + builtin.ret ; + }; + + private builtin.function @miden::account::incr_nonce() -> felt { + ^block35: + v312 = hir.exec @miden/account/incr_nonce() : felt + builtin.ret v312; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @auth__procedure(v314: felt, v315: felt, v316: felt, v317: felt) { + ^block37(v314: felt, v315: felt, v316: felt, v317: felt): + hir.exec @miden:base/authentication-component@1.0.0/auth_component_no_auth/miden:base/authentication-component@1.0.0#auth-procedure(v314, v315, v316, v317) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/auth_component_no_auth.masm b/tests/integration/expected/examples/auth_component_no_auth.masm new file mode 100644 index 000000000..63a38a148 --- /dev/null +++ b/tests/integration/expected/examples/auth_component_no_auth.masm @@ -0,0 +1,936 @@ +# mod miden:base/authentication-component@1.0.0 + +export.auth__procedure + exec.::miden:base/authentication-component@1.0.0::init + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::miden:base/authentication-component@1.0.0#auth-procedure + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/authentication-component@1.0.0::auth_component_no_auth + +proc.__wasm_call_ctors + nop +end + +proc.auth_component_no_auth::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/authentication-component@1.0.0#auth-procedure + drop + drop + drop + drop + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.64 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.32 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::miden::account::get_initial_commitment + trace.252 + nop + push.40 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.56 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.48 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.48 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.32 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::miden::account::compute_current_commitment + trace.252 + nop + push.40 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.56 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.48 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.48 + dup.1 + u32wrapping_add + push.16 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + push.0 + push.3735929054 + movup.2 + else + push.20 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + push.0 + push.3735929054 + movup.2 + else + push.24 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + push.0 + push.3735929054 + movup.2 + else + push.28 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + eq + neq + push.0 + push.1 + dup.2 + cdrop + push.3735929054 + dup.2 + dup.4 + swap.1 + cdrop + push.3735929054 + movup.2 + swap.4 + movdn.2 + swap.1 + swap.3 + cdrop + end + end + end + movup.2 + eq.0 + if.true + swap.1 + drop + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::miden::account::incr_nonce + trace.252 + nop + drop + else + drop + end + push.64 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_no_auth::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.intrinsics::felt::eq + eq +end + +proc.miden::account::get_initial_commitment + trace.240 + nop + exec.::miden::account::get_initial_commitment + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::compute_current_commitment + trace.240 + nop + exec.::miden::account::compute_current_commitment + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::incr_nonce + trace.240 + nop + exec.::miden::account::incr_nonce + trace.252 + nop +end + diff --git a/tests/integration/expected/examples/auth_component_no_auth.wat b/tests/integration/expected/examples/auth_component_no_auth.wat new file mode 100644 index 000000000..1178f86c7 --- /dev/null +++ b/tests/integration/expected/examples/auth_component_no_auth.wat @@ -0,0 +1,234 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32 f32 f32 f32))) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param f32 f32) (result i32))) + (type (;4;) (func (param i32))) + (type (;5;) (func (result f32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/authentication-component@1.0.0#auth-procedure" (func $miden:base/authentication-component@1.0.0#auth-procedure)) + (elem (;0;) (i32.const 1) func $auth_component_no_auth::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $auth_component_no_auth::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:base/authentication-component@1.0.0#auth-procedure (;2;) (type 1) (param f32 f32 f32 f32) + (local i32) + global.get $__stack_pointer + i32.const 64 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + local.get 4 + i32.const 32 + i32.add + call $miden::account::get_initial_commitment + local.get 4 + local.get 4 + i64.load offset=40 + i64.store offset=56 + local.get 4 + local.get 4 + i64.load offset=32 + i64.store offset=48 + local.get 4 + local.get 4 + i32.const 48 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 4 + i32.const 32 + i32.add + call $miden::account::compute_current_commitment + local.get 4 + local.get 4 + i64.load offset=40 + i64.store offset=56 + local.get 4 + local.get 4 + i64.load offset=32 + i64.store offset=48 + local.get 4 + i32.const 16 + i32.add + local.get 4 + i32.const 48 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + block ;; label = @1 + block ;; label = @2 + local.get 4 + f32.load offset=16 + local.get 4 + f32.load + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@2;) + local.get 4 + f32.load offset=20 + local.get 4 + f32.load offset=4 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@2;) + local.get 4 + f32.load offset=24 + local.get 4 + f32.load offset=8 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@2;) + local.get 4 + f32.load offset=28 + local.get 4 + f32.load offset=12 + call $intrinsics::felt::eq + i32.const 1 + i32.eq + br_if 1 (;@1;) + end + call $miden::account::incr_nonce + drop + end + local.get 4 + i32.const 64 + i32.add + global.set $__stack_pointer + ) + (func $wit_bindgen::rt::run_ctors_once (;3;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048584 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;4;) (type 2) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $intrinsics::felt::eq (;5;) (type 3) (param f32 f32) (result i32) + unreachable + ) + (func $miden::account::get_initial_commitment (;6;) (type 4) (param i32) + unreachable + ) + (func $miden::account::compute_current_commitment (;7;) (type 4) (param i32) + unreachable + ) + (func $miden::account::incr_nonce (;8;) (type 5) (result f32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + ) + (alias export 0 "word" (type (;1;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;2;) (func (param "arg" 1))) + (alias core export 0 "miden:base/authentication-component@1.0.0#auth-procedure" (core func (;0;))) + (func (;0;) (type 2) (canon lift (core func 0))) + (alias export 0 "felt" (type (;3;))) + (alias export 0 "word" (type (;4;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-auth-procedure" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "auth-procedure" (func 0) (func (type 8))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-auth-procedure" (func 0)) + (with "import-type-felt" (type 3)) + (with "import-type-word" (type 4)) + (with "import-type-word0" (type 1)) + ) + ) + (export (;2;) "miden:base/authentication-component@1.0.0" (instance 1)) +) diff --git a/tests/integration/expected/examples/auth_component_rpo_falcon512.hir b/tests/integration/expected/examples/auth_component_rpo_falcon512.hir new file mode 100644 index 000000000..c6af8a36d --- /dev/null +++ b/tests/integration/expected/examples/auth_component_rpo_falcon512.hir @@ -0,0 +1,887 @@ +builtin.component miden:base/authentication-component@1.0.0 { + builtin.module public @auth_component_rpo_falcon512 { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @auth_component_rpo_falcon512::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:base/authentication-component@1.0.0#auth-procedure(v0: felt, v1: felt, v2: felt, v3: felt) { + ^block9(v0: felt, v1: felt, v2: felt, v3: felt): + v10 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v11 = hir.bitcast v10 : ptr; + v12 = hir.load v11 : i32; + v13 = arith.constant 112 : i32; + v14 = arith.sub v12, v13 : i32 #[overflow = wrapping]; + v15 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v16 = hir.bitcast v15 : ptr; + hir.store v16, v14; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/wit_bindgen::rt::run_ctors_once() + v17 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_base_sys::bindings::tx::get_block_number() : felt + v18 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden::account::incr_nonce() : felt + v19 = arith.constant 80 : i32; + v20 = arith.add v14, v19 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden::account::compute_delta_commitment(v20) + v22 = arith.constant 88 : u32; + v21 = hir.bitcast v14 : u32; + v23 = arith.add v21, v22 : u32 #[overflow = checked]; + v24 = arith.constant 8 : u32; + v25 = arith.mod v23, v24 : u32; + hir.assertz v25 #[code = 250]; + v26 = hir.int_to_ptr v23 : ptr; + v27 = hir.load v26 : i64; + v29 = arith.constant 104 : u32; + v28 = hir.bitcast v14 : u32; + v30 = arith.add v28, v29 : u32 #[overflow = checked]; + v838 = arith.constant 8 : u32; + v32 = arith.mod v30, v838 : u32; + hir.assertz v32 #[code = 250]; + v33 = hir.int_to_ptr v30 : ptr; + hir.store v33, v27; + v35 = arith.constant 80 : u32; + v34 = hir.bitcast v14 : u32; + v36 = arith.add v34, v35 : u32 #[overflow = checked]; + v837 = arith.constant 8 : u32; + v38 = arith.mod v36, v837 : u32; + hir.assertz v38 #[code = 250]; + v39 = hir.int_to_ptr v36 : ptr; + v40 = hir.load v39 : i64; + v42 = arith.constant 96 : u32; + v41 = hir.bitcast v14 : u32; + v43 = arith.add v41, v42 : u32 #[overflow = checked]; + v836 = arith.constant 8 : u32; + v45 = arith.mod v43, v836 : u32; + hir.assertz v45 #[code = 250]; + v46 = hir.int_to_ptr v43 : ptr; + hir.store v46, v40; + v47 = arith.constant 96 : i32; + v48 = arith.add v14, v47 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_stdlib_sys::intrinsics::word::Word::reverse(v14, v48) + v49 = arith.constant 16 : i32; + v50 = arith.add v14, v49 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_base_sys::bindings::tx::get_input_notes_commitment(v50) + v51 = arith.constant 32 : i32; + v52 = arith.add v14, v51 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_base_sys::bindings::tx::get_output_notes_commitment(v52) + v4 = arith.constant 0 : i32; + v55 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/intrinsics::felt::from_u32(v4) : felt + v835 = arith.constant 0 : i32; + v57 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/intrinsics::felt::from_u32(v835) : felt + v59 = arith.constant 60 : u32; + v58 = hir.bitcast v14 : u32; + v60 = arith.add v58, v59 : u32 #[overflow = checked]; + v61 = arith.constant 4 : u32; + v62 = arith.mod v60, v61 : u32; + hir.assertz v62 #[code = 250]; + v63 = hir.int_to_ptr v60 : ptr; + hir.store v63, v18; + v65 = arith.constant 56 : u32; + v64 = hir.bitcast v14 : u32; + v66 = arith.add v64, v65 : u32 #[overflow = checked]; + v834 = arith.constant 4 : u32; + v68 = arith.mod v66, v834 : u32; + hir.assertz v68 #[code = 250]; + v69 = hir.int_to_ptr v66 : ptr; + hir.store v69, v17; + v71 = arith.constant 52 : u32; + v70 = hir.bitcast v14 : u32; + v72 = arith.add v70, v71 : u32 #[overflow = checked]; + v833 = arith.constant 4 : u32; + v74 = arith.mod v72, v833 : u32; + hir.assertz v74 #[code = 250]; + v75 = hir.int_to_ptr v72 : ptr; + hir.store v75, v57; + v77 = arith.constant 48 : u32; + v76 = hir.bitcast v14 : u32; + v78 = arith.add v76, v77 : u32 #[overflow = checked]; + v832 = arith.constant 4 : u32; + v80 = arith.mod v78, v832 : u32; + hir.assertz v80 #[code = 250]; + v81 = hir.int_to_ptr v78 : ptr; + hir.store v81, v55; + v831 = arith.constant 0 : i32; + v83 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/intrinsics::felt::from_u32(v831) : felt + v830 = arith.constant 0 : i32; + v85 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/intrinsics::felt::from_u32(v830) : felt + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/intrinsics::felt::assert_eq(v83, v85) + v649 = arith.constant 2 : u32; + v87 = hir.bitcast v14 : u32; + v89 = arith.shr v87, v649 : u32; + v90 = hir.bitcast v89 : i32; + v828 = arith.constant 80 : i32; + v94 = arith.add v14, v828 : i32 #[overflow = wrapping]; + v829 = arith.constant 16 : i32; + v92 = arith.add v90, v829 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/std::crypto::hashes::rpo::hash_memory_words(v90, v92, v94) + v827 = arith.constant 88 : u32; + v95 = hir.bitcast v14 : u32; + v97 = arith.add v95, v827 : u32 #[overflow = checked]; + v826 = arith.constant 8 : u32; + v99 = arith.mod v97, v826 : u32; + hir.assertz v99 #[code = 250]; + v100 = hir.int_to_ptr v97 : ptr; + v101 = hir.load v100 : i64; + v825 = arith.constant 104 : u32; + v102 = hir.bitcast v14 : u32; + v104 = arith.add v102, v825 : u32 #[overflow = checked]; + v824 = arith.constant 8 : u32; + v106 = arith.mod v104, v824 : u32; + hir.assertz v106 #[code = 250]; + v107 = hir.int_to_ptr v104 : ptr; + hir.store v107, v101; + v823 = arith.constant 80 : u32; + v108 = hir.bitcast v14 : u32; + v110 = arith.add v108, v823 : u32 #[overflow = checked]; + v822 = arith.constant 8 : u32; + v112 = arith.mod v110, v822 : u32; + hir.assertz v112 #[code = 250]; + v113 = hir.int_to_ptr v110 : ptr; + v114 = hir.load v113 : i64; + v821 = arith.constant 96 : u32; + v115 = hir.bitcast v14 : u32; + v117 = arith.add v115, v821 : u32 #[overflow = checked]; + v820 = arith.constant 8 : u32; + v119 = arith.mod v117, v820 : u32; + hir.assertz v119 #[code = 250]; + v120 = hir.int_to_ptr v117 : ptr; + hir.store v120, v114; + v819 = arith.constant 96 : i32; + v124 = arith.add v14, v819 : i32 #[overflow = wrapping]; + v121 = arith.constant 64 : i32; + v122 = arith.add v14, v121 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_stdlib_sys::intrinsics::word::Word::reverse(v122, v124) + v126 = arith.constant 64 : u32; + v125 = hir.bitcast v14 : u32; + v127 = arith.add v125, v126 : u32 #[overflow = checked]; + v818 = arith.constant 4 : u32; + v129 = arith.mod v127, v818 : u32; + hir.assertz v129 #[code = 250]; + v130 = hir.int_to_ptr v127 : ptr; + v131 = hir.load v130 : felt; + v133 = arith.constant 68 : u32; + v132 = hir.bitcast v14 : u32; + v134 = arith.add v132, v133 : u32 #[overflow = checked]; + v817 = arith.constant 4 : u32; + v136 = arith.mod v134, v817 : u32; + hir.assertz v136 #[code = 250]; + v137 = hir.int_to_ptr v134 : ptr; + v138 = hir.load v137 : felt; + v140 = arith.constant 72 : u32; + v139 = hir.bitcast v14 : u32; + v141 = arith.add v139, v140 : u32 #[overflow = checked]; + v816 = arith.constant 4 : u32; + v143 = arith.mod v141, v816 : u32; + hir.assertz v143 #[code = 250]; + v144 = hir.int_to_ptr v141 : ptr; + v145 = hir.load v144 : felt; + v147 = arith.constant 76 : u32; + v146 = hir.bitcast v14 : u32; + v148 = arith.add v146, v147 : u32 #[overflow = checked]; + v815 = arith.constant 4 : u32; + v150 = arith.mod v148, v815 : u32; + hir.assertz v150 #[code = 250]; + v151 = hir.int_to_ptr v148 : ptr; + v152 = hir.load v151 : felt; + v153 = arith.constant 48 : i32; + v154 = arith.add v14, v153 : i32 #[overflow = wrapping]; + v814 = arith.constant 0 : i32; + v747, v748, v749, v750, v751, v752, v753, v754, v755, v756, v757, v758 = scf.while v814, v14, v154, v152, v145, v138, v131 : i32, i32, i32, felt, felt, felt, felt, i32, felt, felt, felt, felt { + ^block85(v759: i32, v760: i32, v761: i32, v762: felt, v763: felt, v764: felt, v765: felt): + v812 = arith.constant 0 : i32; + v813 = arith.constant 32 : i32; + v157 = arith.eq v759, v813 : i1; + v158 = arith.zext v157 : u32; + v159 = hir.bitcast v158 : i32; + v161 = arith.neq v159, v812 : i1; + v738, v739 = scf.if v161 : i32, i32 { + ^block84: + v661 = ub.poison i32 : i32; + scf.yield v661, v661; + } else { + ^block14: + v200 = arith.constant 4 : i32; + v163 = arith.add v760, v759 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/core::ptr::swap_nonoverlapping_bytes::swap_nonoverlapping_chunks(v163, v761, v200) + v168 = arith.constant -16 : i32; + v169 = arith.add v761, v168 : i32 #[overflow = wrapping]; + v811 = arith.constant 16 : i32; + v167 = arith.add v759, v811 : i32 #[overflow = wrapping]; + scf.yield v167, v169; + }; + v807 = ub.poison felt : felt; + v744 = cf.select v161, v807, v765 : felt; + v808 = ub.poison felt : felt; + v743 = cf.select v161, v808, v764 : felt; + v809 = ub.poison felt : felt; + v742 = cf.select v161, v809, v763 : felt; + v662 = ub.poison felt : felt; + v741 = cf.select v161, v662, v762 : felt; + v810 = ub.poison i32 : i32; + v740 = cf.select v161, v810, v760 : i32; + v660 = arith.constant 1 : u32; + v650 = arith.constant 0 : u32; + v746 = cf.select v161, v650, v660 : u32; + v728 = arith.trunc v746 : i1; + scf.condition v728, v738, v740, v739, v741, v742, v743, v744, v760, v762, v763, v764, v765; + } do { + ^block86(v766: i32, v767: i32, v768: i32, v769: felt, v770: felt, v771: felt, v772: felt, v773: i32, v774: felt, v775: felt, v776: felt, v777: felt): + scf.yield v766, v767, v768, v769, v770, v771, v772; + }; + v172 = arith.constant 108 : u32; + v171 = hir.bitcast v754 : u32; + v173 = arith.add v171, v172 : u32 #[overflow = checked]; + v806 = arith.constant 4 : u32; + v175 = arith.mod v173, v806 : u32; + hir.assertz v175 #[code = 250]; + v176 = hir.int_to_ptr v173 : ptr; + hir.store v176, v755; + v805 = arith.constant 104 : u32; + v178 = hir.bitcast v754 : u32; + v180 = arith.add v178, v805 : u32 #[overflow = checked]; + v804 = arith.constant 4 : u32; + v182 = arith.mod v180, v804 : u32; + hir.assertz v182 #[code = 250]; + v183 = hir.int_to_ptr v180 : ptr; + hir.store v183, v756; + v186 = arith.constant 100 : u32; + v185 = hir.bitcast v754 : u32; + v187 = arith.add v185, v186 : u32 #[overflow = checked]; + v803 = arith.constant 4 : u32; + v189 = arith.mod v187, v803 : u32; + hir.assertz v189 #[code = 250]; + v190 = hir.int_to_ptr v187 : ptr; + hir.store v190, v757; + v802 = arith.constant 96 : u32; + v192 = hir.bitcast v754 : u32; + v194 = arith.add v192, v802 : u32 #[overflow = checked]; + v801 = arith.constant 4 : u32; + v196 = arith.mod v194, v801 : u32; + hir.assertz v196 #[code = 250]; + v197 = hir.int_to_ptr v194 : ptr; + hir.store v197, v758; + v799 = arith.constant 4 : i32; + v800 = arith.constant 96 : i32; + v199 = arith.add v754, v800 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_stdlib_sys::intrinsics::advice::adv_insert(v199, v754, v799) + v798 = arith.constant 0 : i32; + v202 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/>::from(v798) : felt + v797 = arith.constant 80 : i32; + v204 = arith.add v754, v797 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden::account::get_item(v202, v204) + v796 = arith.constant 88 : u32; + v205 = hir.bitcast v754 : u32; + v207 = arith.add v205, v796 : u32 #[overflow = checked]; + v795 = arith.constant 8 : u32; + v209 = arith.mod v207, v795 : u32; + hir.assertz v209 #[code = 250]; + v210 = hir.int_to_ptr v207 : ptr; + v211 = hir.load v210 : i64; + v794 = arith.constant 104 : u32; + v212 = hir.bitcast v754 : u32; + v214 = arith.add v212, v794 : u32 #[overflow = checked]; + v793 = arith.constant 8 : u32; + v216 = arith.mod v214, v793 : u32; + hir.assertz v216 #[code = 250]; + v217 = hir.int_to_ptr v214 : ptr; + hir.store v217, v211; + v792 = arith.constant 80 : u32; + v218 = hir.bitcast v754 : u32; + v220 = arith.add v218, v792 : u32 #[overflow = checked]; + v791 = arith.constant 8 : u32; + v222 = arith.mod v220, v791 : u32; + hir.assertz v222 #[code = 250]; + v223 = hir.int_to_ptr v220 : ptr; + v224 = hir.load v223 : i64; + v790 = arith.constant 96 : u32; + v225 = hir.bitcast v754 : u32; + v227 = arith.add v225, v790 : u32 #[overflow = checked]; + v789 = arith.constant 8 : u32; + v229 = arith.mod v227, v789 : u32; + hir.assertz v229 #[code = 250]; + v230 = hir.int_to_ptr v227 : ptr; + hir.store v230, v224; + v787 = arith.constant 96 : i32; + v234 = arith.add v754, v787 : i32 #[overflow = wrapping]; + v788 = arith.constant 64 : i32; + v232 = arith.add v754, v788 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_stdlib_sys::intrinsics::word::Word::reverse(v232, v234) + v786 = arith.constant 76 : u32; + v235 = hir.bitcast v754 : u32; + v237 = arith.add v235, v786 : u32 #[overflow = checked]; + v785 = arith.constant 4 : u32; + v239 = arith.mod v237, v785 : u32; + hir.assertz v239 #[code = 250]; + v240 = hir.int_to_ptr v237 : ptr; + v241 = hir.load v240 : felt; + v784 = arith.constant 72 : u32; + v242 = hir.bitcast v754 : u32; + v244 = arith.add v242, v784 : u32 #[overflow = checked]; + v783 = arith.constant 4 : u32; + v246 = arith.mod v244, v783 : u32; + hir.assertz v246 #[code = 250]; + v247 = hir.int_to_ptr v244 : ptr; + v248 = hir.load v247 : felt; + v782 = arith.constant 68 : u32; + v249 = hir.bitcast v754 : u32; + v251 = arith.add v249, v782 : u32 #[overflow = checked]; + v781 = arith.constant 4 : u32; + v253 = arith.mod v251, v781 : u32; + hir.assertz v253 #[code = 250]; + v254 = hir.int_to_ptr v251 : ptr; + v255 = hir.load v254 : felt; + v780 = arith.constant 64 : u32; + v256 = hir.bitcast v754 : u32; + v258 = arith.add v256, v780 : u32 #[overflow = checked]; + v779 = arith.constant 4 : u32; + v260 = arith.mod v258, v779 : u32; + hir.assertz v260 #[code = 250]; + v261 = hir.int_to_ptr v258 : ptr; + v262 = hir.load v261 : felt; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/intrinsics::advice::emit_falcon_sig_to_stack(v755, v756, v757, v758, v241, v248, v255, v262) + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/std::crypto::dsa::rpo_falcon512::verify(v241, v248, v255, v262, v755, v756, v757, v758) + v778 = arith.constant 112 : i32; + v264 = arith.add v754, v778 : i32 #[overflow = wrapping]; + v265 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v266 = hir.bitcast v265 : ptr; + hir.store v266, v264; + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block15: + v268 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/GOT.data.internal.__memory_base : ptr + v269 = hir.bitcast v268 : ptr; + v270 = hir.load v269 : i32; + v271 = arith.constant 1048584 : i32; + v272 = arith.add v270, v271 : i32 #[overflow = wrapping]; + v273 = hir.bitcast v272 : u32; + v274 = hir.int_to_ptr v273 : ptr; + v275 = hir.load v274 : u8; + v267 = arith.constant 0 : i32; + v276 = arith.zext v275 : u32; + v277 = hir.bitcast v276 : i32; + v279 = arith.neq v277, v267 : i1; + scf.if v279{ + ^block17: + scf.yield ; + } else { + ^block18: + v280 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/GOT.data.internal.__memory_base : ptr + v281 = hir.bitcast v280 : ptr; + v282 = hir.load v281 : i32; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__wasm_call_ctors() + v840 = arith.constant 1 : u8; + v842 = arith.constant 1048584 : i32; + v284 = arith.add v282, v842 : i32 #[overflow = wrapping]; + v288 = hir.bitcast v284 : u32; + v289 = hir.int_to_ptr v288 : ptr; + hir.store v289, v840; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::tx::get_block_number() -> felt { + ^block19: + v291 = hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden::tx::get_block_number() : felt + builtin.ret v291; + }; + + private builtin.function @miden_base_sys::bindings::tx::get_input_notes_commitment(v292: i32) { + ^block21(v292: i32): + v294 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v295 = hir.bitcast v294 : ptr; + v296 = hir.load v295 : i32; + v297 = arith.constant 32 : i32; + v298 = arith.sub v296, v297 : i32 #[overflow = wrapping]; + v299 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v300 = hir.bitcast v299 : ptr; + hir.store v300, v298; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden::tx::get_input_notes_commitment(v298) + v302 = arith.constant 8 : u32; + v301 = hir.bitcast v298 : u32; + v303 = arith.add v301, v302 : u32 #[overflow = checked]; + v847 = arith.constant 8 : u32; + v305 = arith.mod v303, v847 : u32; + hir.assertz v305 #[code = 250]; + v306 = hir.int_to_ptr v303 : ptr; + v307 = hir.load v306 : i64; + v309 = arith.constant 24 : u32; + v308 = hir.bitcast v298 : u32; + v310 = arith.add v308, v309 : u32 #[overflow = checked]; + v846 = arith.constant 8 : u32; + v312 = arith.mod v310, v846 : u32; + hir.assertz v312 #[code = 250]; + v313 = hir.int_to_ptr v310 : ptr; + hir.store v313, v307; + v314 = hir.bitcast v298 : u32; + v845 = arith.constant 8 : u32; + v316 = arith.mod v314, v845 : u32; + hir.assertz v316 #[code = 250]; + v317 = hir.int_to_ptr v314 : ptr; + v318 = hir.load v317 : i64; + v320 = arith.constant 16 : u32; + v319 = hir.bitcast v298 : u32; + v321 = arith.add v319, v320 : u32 #[overflow = checked]; + v844 = arith.constant 8 : u32; + v323 = arith.mod v321, v844 : u32; + hir.assertz v323 #[code = 250]; + v324 = hir.int_to_ptr v321 : ptr; + hir.store v324, v318; + v325 = arith.constant 16 : i32; + v326 = arith.add v298, v325 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_stdlib_sys::intrinsics::word::Word::reverse(v292, v326) + v843 = arith.constant 32 : i32; + v328 = arith.add v298, v843 : i32 #[overflow = wrapping]; + v329 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v330 = hir.bitcast v329 : ptr; + hir.store v330, v328; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::tx::get_output_notes_commitment(v331: i32) { + ^block23(v331: i32): + v333 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v334 = hir.bitcast v333 : ptr; + v335 = hir.load v334 : i32; + v336 = arith.constant 32 : i32; + v337 = arith.sub v335, v336 : i32 #[overflow = wrapping]; + v338 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v339 = hir.bitcast v338 : ptr; + hir.store v339, v337; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden::tx::get_output_notes_commitment(v337) + v341 = arith.constant 8 : u32; + v340 = hir.bitcast v337 : u32; + v342 = arith.add v340, v341 : u32 #[overflow = checked]; + v852 = arith.constant 8 : u32; + v344 = arith.mod v342, v852 : u32; + hir.assertz v344 #[code = 250]; + v345 = hir.int_to_ptr v342 : ptr; + v346 = hir.load v345 : i64; + v348 = arith.constant 24 : u32; + v347 = hir.bitcast v337 : u32; + v349 = arith.add v347, v348 : u32 #[overflow = checked]; + v851 = arith.constant 8 : u32; + v351 = arith.mod v349, v851 : u32; + hir.assertz v351 #[code = 250]; + v352 = hir.int_to_ptr v349 : ptr; + hir.store v352, v346; + v353 = hir.bitcast v337 : u32; + v850 = arith.constant 8 : u32; + v355 = arith.mod v353, v850 : u32; + hir.assertz v355 #[code = 250]; + v356 = hir.int_to_ptr v353 : ptr; + v357 = hir.load v356 : i64; + v359 = arith.constant 16 : u32; + v358 = hir.bitcast v337 : u32; + v360 = arith.add v358, v359 : u32 #[overflow = checked]; + v849 = arith.constant 8 : u32; + v362 = arith.mod v360, v849 : u32; + hir.assertz v362 #[code = 250]; + v363 = hir.int_to_ptr v360 : ptr; + hir.store v363, v357; + v364 = arith.constant 16 : i32; + v365 = arith.add v337, v364 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden_stdlib_sys::intrinsics::word::Word::reverse(v331, v365) + v848 = arith.constant 32 : i32; + v367 = arith.add v337, v848 : i32 #[overflow = wrapping]; + v368 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v369 = hir.bitcast v368 : ptr; + hir.store v369, v367; + builtin.ret ; + }; + + private builtin.function @core::ptr::swap_nonoverlapping_bytes::swap_nonoverlapping_chunks(v370: i32, v371: i32, v372: i32) { + ^block25(v370: i32, v371: i32, v372: i32): + v867, v868, v869 = scf.while v372, v370, v371 : i32, i32, i32 { + ^block28(v374: i32, v381: i32, v385: i32): + v888 = arith.constant 0 : i32; + v373 = arith.constant 0 : i32; + v376 = arith.eq v374, v373 : i1; + v377 = arith.zext v376 : u32; + v378 = hir.bitcast v377 : i32; + v380 = arith.neq v378, v888 : i1; + v882, v883, v884 = scf.if v380 : i32, i32, i32 { + ^block95: + v860 = ub.poison i32 : i32; + scf.yield v860, v860, v860; + } else { + ^block30: + v382 = hir.bitcast v381 : u32; + v383 = hir.int_to_ptr v382 : ptr; + v384 = hir.load v383 : i32; + v386 = hir.bitcast v385 : u32; + v387 = hir.int_to_ptr v386 : ptr; + v388 = hir.load v387 : i32; + v389 = hir.bitcast v381 : u32; + v390 = hir.int_to_ptr v389 : ptr; + hir.store v390, v388; + v391 = hir.bitcast v385 : u32; + v392 = hir.int_to_ptr v391 : ptr; + hir.store v392, v384; + v887 = arith.constant 4 : i32; + v396 = arith.add v385, v887 : i32 #[overflow = wrapping]; + v395 = arith.constant 4 : i32; + v398 = arith.add v381, v395 : i32 #[overflow = wrapping]; + v393 = arith.constant -1 : i32; + v394 = arith.add v374, v393 : i32 #[overflow = wrapping]; + scf.yield v394, v398, v396; + }; + v859 = arith.constant 1 : u32; + v853 = arith.constant 0 : u32; + v886 = cf.select v380, v853, v859 : u32; + v876 = arith.trunc v886 : i1; + scf.condition v876, v882, v883, v884; + } do { + ^block94(v873: i32, v874: i32, v875: i32): + scf.yield v873, v874, v875; + }; + builtin.ret ; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::advice::adv_insert(v399: i32, v400: i32, v401: i32) { + ^block31(v399: i32, v400: i32, v401: i32): + v403 = arith.constant 12 : u32; + v402 = hir.bitcast v399 : u32; + v404 = arith.add v402, v403 : u32 #[overflow = checked]; + v405 = arith.constant 4 : u32; + v406 = arith.mod v404, v405 : u32; + hir.assertz v406 #[code = 250]; + v407 = hir.int_to_ptr v404 : ptr; + v408 = hir.load v407 : felt; + v410 = arith.constant 8 : u32; + v409 = hir.bitcast v399 : u32; + v411 = arith.add v409, v410 : u32 #[overflow = checked]; + v895 = arith.constant 4 : u32; + v413 = arith.mod v411, v895 : u32; + hir.assertz v413 #[code = 250]; + v414 = hir.int_to_ptr v411 : ptr; + v415 = hir.load v414 : felt; + v894 = arith.constant 4 : u32; + v416 = hir.bitcast v399 : u32; + v418 = arith.add v416, v894 : u32 #[overflow = checked]; + v893 = arith.constant 4 : u32; + v420 = arith.mod v418, v893 : u32; + hir.assertz v420 #[code = 250]; + v421 = hir.int_to_ptr v418 : ptr; + v422 = hir.load v421 : felt; + v423 = hir.bitcast v399 : u32; + v892 = arith.constant 4 : u32; + v425 = arith.mod v423, v892 : u32; + hir.assertz v425 #[code = 250]; + v426 = hir.int_to_ptr v423 : ptr; + v427 = hir.load v426 : felt; + v890 = arith.constant 2 : u32; + v429 = hir.bitcast v400 : u32; + v431 = arith.shr v429, v890 : u32; + v432 = hir.bitcast v431 : i32; + v891 = arith.constant 2 : u32; + v435 = arith.shl v401, v891 : i32; + v436 = arith.add v432, v435 : i32 #[overflow = wrapping]; + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/intrinsics::advice::adv_insert_mem(v408, v415, v422, v427, v432, v436) + builtin.ret ; + }; + + private builtin.function @>::from(v437: i32) -> felt { + ^block33(v437: i32): + v439 = arith.constant 255 : i32; + v440 = arith.band v437, v439 : i32; + v441 = hir.bitcast v440 : felt; + builtin.ret v441; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v442: i32, v443: i32) { + ^block35(v442: i32, v443: i32): + v446 = builtin.global_symbol @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/__stack_pointer : ptr + v447 = hir.bitcast v446 : ptr; + v448 = hir.load v447 : i32; + v449 = arith.constant 16 : i32; + v450 = arith.sub v448, v449 : i32 #[overflow = wrapping]; + v452 = arith.constant 8 : u32; + v451 = hir.bitcast v443 : u32; + v453 = arith.add v451, v452 : u32 #[overflow = checked]; + v982 = arith.constant 8 : u32; + v455 = arith.mod v453, v982 : u32; + hir.assertz v455 #[code = 250]; + v456 = hir.int_to_ptr v453 : ptr; + v457 = hir.load v456 : i64; + v981 = arith.constant 8 : u32; + v458 = hir.bitcast v450 : u32; + v460 = arith.add v458, v981 : u32 #[overflow = checked]; + v461 = arith.constant 4 : u32; + v462 = arith.mod v460, v461 : u32; + hir.assertz v462 #[code = 250]; + v463 = hir.int_to_ptr v460 : ptr; + hir.store v463, v457; + v464 = hir.bitcast v443 : u32; + v980 = arith.constant 8 : u32; + v466 = arith.mod v464, v980 : u32; + hir.assertz v466 #[code = 250]; + v467 = hir.int_to_ptr v464 : ptr; + v468 = hir.load v467 : i64; + v469 = hir.bitcast v450 : u32; + v979 = arith.constant 4 : u32; + v471 = arith.mod v469, v979 : u32; + hir.assertz v471 #[code = 250]; + v472 = hir.int_to_ptr v469 : ptr; + hir.store v472, v468; + v473 = arith.constant 12 : i32; + v474 = arith.add v450, v473 : i32 #[overflow = wrapping]; + v444 = arith.constant 0 : i32; + v950, v951, v952, v953, v954, v955 = scf.while v444, v450, v474, v442 : i32, i32, i32, i32, i32, i32 { + ^block103(v956: i32, v957: i32, v958: i32, v959: i32): + v978 = arith.constant 0 : i32; + v477 = arith.constant 8 : i32; + v478 = arith.eq v956, v477 : i1; + v479 = arith.zext v478 : u32; + v480 = hir.bitcast v479 : i32; + v482 = arith.neq v480, v978 : i1; + v944, v945 = scf.if v482 : i32, i32 { + ^block102: + v904 = ub.poison i32 : i32; + scf.yield v904, v904; + } else { + ^block40: + v484 = arith.add v957, v956 : i32 #[overflow = wrapping]; + v485 = hir.bitcast v484 : u32; + v977 = arith.constant 4 : u32; + v487 = arith.mod v485, v977 : u32; + hir.assertz v487 #[code = 250]; + v488 = hir.int_to_ptr v485 : ptr; + v489 = hir.load v488 : felt; + v491 = hir.bitcast v958 : u32; + v976 = arith.constant 4 : u32; + v493 = arith.mod v491, v976 : u32; + hir.assertz v493 #[code = 250]; + v494 = hir.int_to_ptr v491 : ptr; + v495 = hir.load v494 : i32; + v496 = hir.bitcast v484 : u32; + v975 = arith.constant 4 : u32; + v498 = arith.mod v496, v975 : u32; + hir.assertz v498 #[code = 250]; + v499 = hir.int_to_ptr v496 : ptr; + hir.store v499, v495; + v500 = hir.bitcast v958 : u32; + v974 = arith.constant 4 : u32; + v502 = arith.mod v500, v974 : u32; + hir.assertz v502 #[code = 250]; + v503 = hir.int_to_ptr v500 : ptr; + hir.store v503, v489; + v506 = arith.constant -4 : i32; + v507 = arith.add v958, v506 : i32 #[overflow = wrapping]; + v504 = arith.constant 4 : i32; + v505 = arith.add v956, v504 : i32 #[overflow = wrapping]; + scf.yield v505, v507; + }; + v972 = ub.poison i32 : i32; + v947 = cf.select v482, v972, v959 : i32; + v973 = ub.poison i32 : i32; + v946 = cf.select v482, v973, v957 : i32; + v903 = arith.constant 1 : u32; + v896 = arith.constant 0 : u32; + v949 = cf.select v482, v896, v903 : u32; + v937 = arith.trunc v949 : i1; + scf.condition v937, v944, v946, v945, v947, v957, v959; + } do { + ^block104(v960: i32, v961: i32, v962: i32, v963: i32, v964: i32, v965: i32): + scf.yield v960, v961, v962, v963; + }; + v971 = arith.constant 8 : u32; + v509 = hir.bitcast v954 : u32; + v511 = arith.add v509, v971 : u32 #[overflow = checked]; + v970 = arith.constant 4 : u32; + v513 = arith.mod v511, v970 : u32; + hir.assertz v513 #[code = 250]; + v514 = hir.int_to_ptr v511 : ptr; + v515 = hir.load v514 : i64; + v969 = arith.constant 8 : u32; + v516 = hir.bitcast v955 : u32; + v518 = arith.add v516, v969 : u32 #[overflow = checked]; + v968 = arith.constant 8 : u32; + v520 = arith.mod v518, v968 : u32; + hir.assertz v520 #[code = 250]; + v521 = hir.int_to_ptr v518 : ptr; + hir.store v521, v515; + v522 = hir.bitcast v954 : u32; + v967 = arith.constant 4 : u32; + v524 = arith.mod v522, v967 : u32; + hir.assertz v524 #[code = 250]; + v525 = hir.int_to_ptr v522 : ptr; + v526 = hir.load v525 : i64; + v527 = hir.bitcast v955 : u32; + v966 = arith.constant 8 : u32; + v529 = arith.mod v527, v966 : u32; + hir.assertz v529 #[code = 250]; + v530 = hir.int_to_ptr v527 : ptr; + hir.store v530, v526; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u32(v531: i32) -> felt { + ^block41(v531: i32): + v532 = hir.bitcast v531 : felt; + builtin.ret v532; + }; + + private builtin.function @intrinsics::felt::assert_eq(v534: felt, v535: felt) { + ^block43(v534: felt, v535: felt): + hir.assert_eq v534, v535; + builtin.ret ; + }; + + private builtin.function @intrinsics::advice::emit_falcon_sig_to_stack(v536: felt, v537: felt, v538: felt, v539: felt, v540: felt, v541: felt, v542: felt, v543: felt) { + ^block45(v536: felt, v537: felt, v538: felt, v539: felt, v540: felt, v541: felt, v542: felt, v543: felt): + hir.exec @intrinsics/advice/emit_falcon_sig_to_stack(v536, v537, v538, v539, v540, v541, v542, v543) + builtin.ret ; + }; + + private builtin.function @intrinsics::advice::adv_insert_mem(v544: felt, v545: felt, v546: felt, v547: felt, v548: i32, v549: i32) { + ^block49(v544: felt, v545: felt, v546: felt, v547: felt, v548: i32, v549: i32): + hir.exec @intrinsics/advice/adv_insert_mem(v544, v545, v546, v547, v548, v549) + builtin.ret ; + }; + + private builtin.function @std::crypto::hashes::rpo::hash_memory_words(v550: i32, v551: i32, v552: i32) { + ^block51(v550: i32, v551: i32, v552: i32): + v553, v554, v555, v556 = hir.exec @std/crypto/hashes/rpo/hash_memory_words(v550, v551) : felt, felt, felt, felt + v557 = hir.bitcast v552 : u32; + v558 = hir.int_to_ptr v557 : ptr; + hir.store v558, v553; + v559 = arith.constant 4 : u32; + v560 = arith.add v557, v559 : u32 #[overflow = checked]; + v561 = hir.int_to_ptr v560 : ptr; + hir.store v561, v554; + v562 = arith.constant 8 : u32; + v563 = arith.add v557, v562 : u32 #[overflow = checked]; + v564 = hir.int_to_ptr v563 : ptr; + hir.store v564, v555; + v565 = arith.constant 12 : u32; + v566 = arith.add v557, v565 : u32 #[overflow = checked]; + v567 = hir.int_to_ptr v566 : ptr; + hir.store v567, v556; + builtin.ret ; + }; + + private builtin.function @std::crypto::dsa::rpo_falcon512::verify(v568: felt, v569: felt, v570: felt, v571: felt, v572: felt, v573: felt, v574: felt, v575: felt) { + ^block57(v568: felt, v569: felt, v570: felt, v571: felt, v572: felt, v573: felt, v574: felt, v575: felt): + hir.exec @std/crypto/dsa/rpo_falcon512/verify(v568, v569, v570, v571, v572, v573, v574, v575) + builtin.ret ; + }; + + private builtin.function @miden::account::compute_delta_commitment(v576: i32) { + ^block61(v576: i32): + v577, v578, v579, v580 = hir.exec @miden/account/compute_delta_commitment() : felt, felt, felt, felt + v581 = hir.bitcast v576 : u32; + v582 = hir.int_to_ptr v581 : ptr; + hir.store v582, v577; + v583 = arith.constant 4 : u32; + v584 = arith.add v581, v583 : u32 #[overflow = checked]; + v585 = hir.int_to_ptr v584 : ptr; + hir.store v585, v578; + v586 = arith.constant 8 : u32; + v587 = arith.add v581, v586 : u32 #[overflow = checked]; + v588 = hir.int_to_ptr v587 : ptr; + hir.store v588, v579; + v589 = arith.constant 12 : u32; + v590 = arith.add v581, v589 : u32 #[overflow = checked]; + v591 = hir.int_to_ptr v590 : ptr; + hir.store v591, v580; + builtin.ret ; + }; + + private builtin.function @miden::account::get_item(v592: felt, v593: i32) { + ^block65(v592: felt, v593: i32): + v594, v595, v596, v597 = hir.exec @miden/account/get_item(v592) : felt, felt, felt, felt + v598 = hir.bitcast v593 : u32; + v599 = hir.int_to_ptr v598 : ptr; + hir.store v599, v594; + v600 = arith.constant 4 : u32; + v601 = arith.add v598, v600 : u32 #[overflow = checked]; + v602 = hir.int_to_ptr v601 : ptr; + hir.store v602, v595; + v603 = arith.constant 8 : u32; + v604 = arith.add v598, v603 : u32 #[overflow = checked]; + v605 = hir.int_to_ptr v604 : ptr; + hir.store v605, v596; + v606 = arith.constant 12 : u32; + v607 = arith.add v598, v606 : u32 #[overflow = checked]; + v608 = hir.int_to_ptr v607 : ptr; + hir.store v608, v597; + builtin.ret ; + }; + + private builtin.function @miden::account::incr_nonce() -> felt { + ^block67: + v609 = hir.exec @miden/account/incr_nonce() : felt + builtin.ret v609; + }; + + private builtin.function @miden::tx::get_block_number() -> felt { + ^block69: + v611 = hir.exec @miden/tx/get_block_number() : felt + builtin.ret v611; + }; + + private builtin.function @miden::tx::get_input_notes_commitment(v613: i32) { + ^block72(v613: i32): + v614, v615, v616, v617 = hir.exec @miden/tx/get_input_notes_commitment() : felt, felt, felt, felt + v618 = hir.bitcast v613 : u32; + v619 = hir.int_to_ptr v618 : ptr; + hir.store v619, v614; + v620 = arith.constant 4 : u32; + v621 = arith.add v618, v620 : u32 #[overflow = checked]; + v622 = hir.int_to_ptr v621 : ptr; + hir.store v622, v615; + v623 = arith.constant 8 : u32; + v624 = arith.add v618, v623 : u32 #[overflow = checked]; + v625 = hir.int_to_ptr v624 : ptr; + hir.store v625, v616; + v626 = arith.constant 12 : u32; + v627 = arith.add v618, v626 : u32 #[overflow = checked]; + v628 = hir.int_to_ptr v627 : ptr; + hir.store v628, v617; + builtin.ret ; + }; + + private builtin.function @miden::tx::get_output_notes_commitment(v629: i32) { + ^block74(v629: i32): + v630, v631, v632, v633 = hir.exec @miden/tx/get_output_notes_commitment() : felt, felt, felt, felt + v634 = hir.bitcast v629 : u32; + v635 = hir.int_to_ptr v634 : ptr; + hir.store v635, v630; + v636 = arith.constant 4 : u32; + v637 = arith.add v634, v636 : u32 #[overflow = checked]; + v638 = hir.int_to_ptr v637 : ptr; + hir.store v638, v631; + v639 = arith.constant 8 : u32; + v640 = arith.add v634, v639 : u32 #[overflow = checked]; + v641 = hir.int_to_ptr v640 : ptr; + hir.store v641, v632; + v642 = arith.constant 12 : u32; + v643 = arith.add v634, v642 : u32 #[overflow = checked]; + v644 = hir.int_to_ptr v643 : ptr; + hir.store v644, v633; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @auth__procedure(v645: felt, v646: felt, v647: felt, v648: felt) { + ^block76(v645: felt, v646: felt, v647: felt, v648: felt): + hir.exec @miden:base/authentication-component@1.0.0/auth_component_rpo_falcon512/miden:base/authentication-component@1.0.0#auth-procedure(v645, v646, v647, v648) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/auth_component_rpo_falcon512.masm b/tests/integration/expected/examples/auth_component_rpo_falcon512.masm new file mode 100644 index 000000000..d505249fa --- /dev/null +++ b/tests/integration/expected/examples/auth_component_rpo_falcon512.masm @@ -0,0 +1,1909 @@ +# mod miden:base/authentication-component@1.0.0 + +export.auth__procedure + exec.::miden:base/authentication-component@1.0.0::init + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden:base/authentication-component@1.0.0#auth-procedure + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512 + +proc.__wasm_call_ctors + nop +end + +proc.auth_component_rpo_falcon512::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/authentication-component@1.0.0#auth-procedure + drop + drop + drop + drop + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.112 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::wit_bindgen::rt::run_ctors_once + trace.252 + nop + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_base_sys::bindings::tx::get_block_number + trace.252 + nop + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden::account::incr_nonce + trace.252 + nop + push.80 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden::account::compute_delta_commitment + trace.252 + nop + push.88 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.104 + dup.5 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.80 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.96 + dup.5 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.96 + dup.3 + u32wrapping_add + dup.3 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.16 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_base_sys::bindings::tx::get_input_notes_commitment + trace.252 + nop + push.32 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_base_sys::bindings::tx::get_output_notes_commitment + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::intrinsics::felt::from_u32 + trace.252 + nop + push.60 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.56 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.52 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.48 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::intrinsics::felt::assert_eq + trace.252 + nop + push.2 + dup.1 + swap.1 + u32shr + push.80 + dup.2 + u32wrapping_add + push.16 + dup.2 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::std::crypto::hashes::rpo::hash_memory_words + trace.252 + nop + push.88 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.104 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.80 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.96 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.96 + dup.1 + u32wrapping_add + push.64 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.64 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.68 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.72 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.76 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.48 + dup.5 + u32wrapping_add + push.0 + movup.6 + swap.1 + push.1 + while.true + push.0 + push.32 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + push.4 + dup.3 + dup.3 + u32wrapping_add + dup.5 + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::core::ptr::swap_nonoverlapping_bytes::swap_nonoverlapping_chunks + trace.252 + nop + push.4294967280 + movup.4 + u32wrapping_add + push.16 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.9 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.9 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.5 + dup.9 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.6 + dup.9 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.7 + dup.9 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.9 + cdrop + push.1 + u32and + movup.3 + swap.5 + swap.7 + movdn.3 + swap.1 + swap.2 + swap.4 + swap.6 + swap.1 + if.true + movup.7 + drop + movup.7 + drop + movup.7 + drop + movup.7 + drop + movup.7 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + drop + drop + drop + push.108 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.104 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.100 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.96 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + push.96 + dup.2 + u32wrapping_add + dup.2 + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_stdlib_sys::intrinsics::advice::adv_insert + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::>::from + trace.252 + nop + push.80 + dup.2 + u32wrapping_add + swap.1 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden::account::get_item + trace.252 + nop + push.88 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.104 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.80 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.96 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.96 + dup.1 + u32wrapping_add + push.64 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.76 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.72 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.68 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.64 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.0 + dup.2 + dup.4 + dup.6 + dup.12 + dup.12 + dup.12 + dup.12 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::intrinsics::advice::emit_falcon_sig_to_stack + trace.252 + nop + movup.4 + swap.8 + swap.7 + swap.6 + swap.5 + movdn.4 + swap.1 + swap.2 + swap.1 + swap.3 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::std::crypto::dsa::rpo_falcon512::verify + trace.252 + nop + push.112 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.miden_base_sys::bindings::tx::get_block_number + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden::tx::get_block_number + trace.252 + nop +end + +proc.miden_base_sys::bindings::tx::get_input_notes_commitment + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden::tx::get_input_notes_commitment + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.24 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.16 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_base_sys::bindings::tx::get_output_notes_commitment + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden::tx::get_output_notes_commitment + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.24 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.16 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.core::ptr::swap_nonoverlapping_bytes::swap_nonoverlapping_chunks + movup.2 + push.1 + while.true + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + movdn.3 + drop + drop + drop + push.3735929054 + dup.0 + dup.1 + swap.2 + else + dup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.4 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.4 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + u32wrapping_add + push.4 + movup.4 + u32wrapping_add + push.4294967295 + movup.4 + u32wrapping_add + end + push.1 + push.0 + movup.5 + cdrop + push.1 + u32and + if.true + push.1 + else + push.0 + end + end + drop + drop + drop +end + +proc.miden_stdlib_sys::intrinsics::advice::adv_insert + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.2 + movup.5 + swap.1 + u32shr + push.2 + movup.6 + swap.1 + u32shl + dup.1 + u32wrapping_add + movup.2 + swap.3 + movdn.2 + swap.1 + swap.4 + swap.1 + swap.5 + trace.240 + nop + exec.::miden:base/authentication-component@1.0.0::auth_component_rpo_falcon512::intrinsics::advice::adv_insert_mem + trace.252 + nop +end + +proc.>::from + push.255 + u32and +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + +proc.intrinsics::advice::emit_falcon_sig_to_stack + trace.240 + nop + exec.::intrinsics::advice::emit_falcon_sig_to_stack + trace.252 + nop +end + +proc.intrinsics::advice::adv_insert_mem + trace.240 + nop + exec.::intrinsics::advice::adv_insert_mem + trace.252 + nop +end + +proc.std::crypto::hashes::rpo::hash_memory_words + trace.240 + nop + exec.::std::crypto::hashes::rpo::hash_memory_words + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.std::crypto::dsa::rpo_falcon512::verify + trace.240 + nop + exec.::std::crypto::dsa::rpo_falcon512::verify + trace.252 + nop +end + +proc.miden::account::compute_delta_commitment + trace.240 + nop + exec.::miden::account::compute_delta_commitment + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::get_item + trace.240 + nop + exec.::miden::account::get_item + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::incr_nonce + trace.240 + nop + exec.::miden::account::incr_nonce + trace.252 + nop +end + +proc.miden::tx::get_block_number + trace.240 + nop + exec.::miden::tx::get_block_number + trace.252 + nop +end + +proc.miden::tx::get_input_notes_commitment + trace.240 + nop + exec.::miden::tx::get_input_notes_commitment + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::tx::get_output_notes_commitment + trace.240 + nop + exec.::miden::tx::get_output_notes_commitment + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + diff --git a/tests/integration/expected/examples/auth_component_rpo_falcon512.wat b/tests/integration/expected/examples/auth_component_rpo_falcon512.wat new file mode 100644 index 000000000..d7a9e4b38 --- /dev/null +++ b/tests/integration/expected/examples/auth_component_rpo_falcon512.wat @@ -0,0 +1,490 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32 f32 f32 f32))) + (type (;2;) (func (result f32))) + (type (;3;) (func (param i32))) + (type (;4;) (func (param i32 i32 i32))) + (type (;5;) (func (param i32) (result f32))) + (type (;6;) (func (param i32 i32))) + (type (;7;) (func (param f32 f32))) + (type (;8;) (func (param f32 f32 f32 f32 f32 f32 f32 f32))) + (type (;9;) (func (param f32 f32 f32 f32 i32 i32))) + (type (;10;) (func (param f32 i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/authentication-component@1.0.0#auth-procedure" (func $miden:base/authentication-component@1.0.0#auth-procedure)) + (elem (;0;) (i32.const 1) func $auth_component_rpo_falcon512::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $auth_component_rpo_falcon512::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:base/authentication-component@1.0.0#auth-procedure (;2;) (type 1) (param f32 f32 f32 f32) + (local i32 f32 f32 i32 f32 f32 i32 f32 f32 f32 f32) + global.get $__stack_pointer + i32.const 112 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + call $miden_base_sys::bindings::tx::get_block_number + local.set 5 + call $miden::account::incr_nonce + local.set 6 + local.get 4 + i32.const 80 + i32.add + call $miden::account::compute_delta_commitment + local.get 4 + local.get 4 + i64.load offset=88 + i64.store offset=104 + local.get 4 + local.get 4 + i64.load offset=80 + i64.store offset=96 + local.get 4 + local.get 4 + i32.const 96 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 4 + i32.const 16 + i32.add + call $miden_base_sys::bindings::tx::get_input_notes_commitment + local.get 4 + i32.const 32 + i32.add + call $miden_base_sys::bindings::tx::get_output_notes_commitment + i32.const 0 + local.set 7 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 8 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 9 + local.get 4 + local.get 6 + f32.store offset=60 + local.get 4 + local.get 5 + f32.store offset=56 + local.get 4 + local.get 9 + f32.store offset=52 + local.get 4 + local.get 8 + f32.store offset=48 + i32.const 0 + call $intrinsics::felt::from_u32 + i32.const 0 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 4 + i32.const 2 + i32.shr_u + local.tee 10 + local.get 10 + i32.const 16 + i32.add + local.get 4 + i32.const 80 + i32.add + call $std::crypto::hashes::rpo::hash_memory_words + local.get 4 + local.get 4 + i64.load offset=88 + i64.store offset=104 + local.get 4 + local.get 4 + i64.load offset=80 + i64.store offset=96 + local.get 4 + i32.const 64 + i32.add + local.get 4 + i32.const 96 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 4 + f32.load offset=64 + local.set 5 + local.get 4 + f32.load offset=68 + local.set 6 + local.get 4 + f32.load offset=72 + local.set 8 + local.get 4 + f32.load offset=76 + local.set 9 + local.get 4 + i32.const 48 + i32.add + local.set 10 + block ;; label = @1 + loop ;; label = @2 + local.get 7 + i32.const 32 + i32.eq + br_if 1 (;@1;) + local.get 4 + local.get 7 + i32.add + local.get 10 + i32.const 4 + call $core::ptr::swap_nonoverlapping_bytes::swap_nonoverlapping_chunks + local.get 7 + i32.const 16 + i32.add + local.set 7 + local.get 10 + i32.const -16 + i32.add + local.set 10 + br 0 (;@2;) + end + end + local.get 4 + local.get 9 + f32.store offset=108 + local.get 4 + local.get 8 + f32.store offset=104 + local.get 4 + local.get 6 + f32.store offset=100 + local.get 4 + local.get 5 + f32.store offset=96 + local.get 4 + i32.const 96 + i32.add + local.get 4 + i32.const 4 + call $miden_stdlib_sys::intrinsics::advice::adv_insert + i32.const 0 + call $>::from + local.get 4 + i32.const 80 + i32.add + call $miden::account::get_item + local.get 4 + local.get 4 + i64.load offset=88 + i64.store offset=104 + local.get 4 + local.get 4 + i64.load offset=80 + i64.store offset=96 + local.get 4 + i32.const 64 + i32.add + local.get 4 + i32.const 96 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 9 + local.get 8 + local.get 6 + local.get 5 + local.get 4 + f32.load offset=76 + local.tee 11 + local.get 4 + f32.load offset=72 + local.tee 12 + local.get 4 + f32.load offset=68 + local.tee 13 + local.get 4 + f32.load offset=64 + local.tee 14 + call $intrinsics::advice::emit_falcon_sig_to_stack + local.get 11 + local.get 12 + local.get 13 + local.get 14 + local.get 9 + local.get 8 + local.get 6 + local.get 5 + call $std::crypto::dsa::rpo_falcon512::verify + local.get 4 + i32.const 112 + i32.add + global.set $__stack_pointer + ) + (func $wit_bindgen::rt::run_ctors_once (;3;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048584 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $miden_base_sys::bindings::tx::get_block_number (;4;) (type 2) (result f32) + call $miden::tx::get_block_number + ) + (func $miden_base_sys::bindings::tx::get_input_notes_commitment (;5;) (type 3) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + call $miden::tx::get_input_notes_commitment + local.get 1 + local.get 1 + i64.load offset=8 + i64.store offset=24 + local.get 1 + local.get 1 + i64.load + i64.store offset=16 + local.get 0 + local.get 1 + i32.const 16 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 1 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::tx::get_output_notes_commitment (;6;) (type 3) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + call $miden::tx::get_output_notes_commitment + local.get 1 + local.get 1 + i64.load offset=8 + i64.store offset=24 + local.get 1 + local.get 1 + i64.load + i64.store offset=16 + local.get 0 + local.get 1 + i32.const 16 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 1 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $core::ptr::swap_nonoverlapping_bytes::swap_nonoverlapping_chunks (;7;) (type 4) (param i32 i32 i32) + (local i32) + block ;; label = @1 + loop ;; label = @2 + local.get 2 + i32.eqz + br_if 1 (;@1;) + local.get 0 + i32.load align=1 + local.set 3 + local.get 0 + local.get 1 + i32.load align=1 + i32.store align=1 + local.get 1 + local.get 3 + i32.store align=1 + local.get 2 + i32.const -1 + i32.add + local.set 2 + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 0 + i32.const 4 + i32.add + local.set 0 + br 0 (;@2;) + end + end + ) + (func $miden_stdlib_sys::intrinsics::advice::adv_insert (;8;) (type 4) (param i32 i32 i32) + local.get 0 + f32.load offset=12 + local.get 0 + f32.load offset=8 + local.get 0 + f32.load offset=4 + local.get 0 + f32.load + local.get 1 + i32.const 2 + i32.shr_u + local.tee 0 + local.get 0 + local.get 2 + i32.const 2 + i32.shl + i32.add + call $intrinsics::advice::adv_insert_mem + ) + (func $>::from (;9;) (type 5) (param i32) (result f32) + local.get 0 + i32.const 255 + i32.and + f32.reinterpret_i32 + ) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;10;) (type 6) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $intrinsics::felt::from_u32 (;11;) (type 5) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::assert_eq (;12;) (type 7) (param f32 f32) + unreachable + ) + (func $intrinsics::advice::emit_falcon_sig_to_stack (;13;) (type 8) (param f32 f32 f32 f32 f32 f32 f32 f32) + unreachable + ) + (func $intrinsics::advice::adv_insert_mem (;14;) (type 9) (param f32 f32 f32 f32 i32 i32) + unreachable + ) + (func $std::crypto::hashes::rpo::hash_memory_words (;15;) (type 4) (param i32 i32 i32) + unreachable + ) + (func $std::crypto::dsa::rpo_falcon512::verify (;16;) (type 8) (param f32 f32 f32 f32 f32 f32 f32 f32) + unreachable + ) + (func $miden::account::compute_delta_commitment (;17;) (type 3) (param i32) + unreachable + ) + (func $miden::account::get_item (;18;) (type 10) (param f32 i32) + unreachable + ) + (func $miden::account::incr_nonce (;19;) (type 2) (result f32) + unreachable + ) + (func $miden::tx::get_block_number (;20;) (type 2) (result f32) + unreachable + ) + (func $miden::tx::get_input_notes_commitment (;21;) (type 3) (param i32) + unreachable + ) + (func $miden::tx::get_output_notes_commitment (;22;) (type 3) (param i32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + (@custom "rodata,miden_account" (after data) "9auth-component-rpo-falcon512\01\0b0.1.0\01\03\00\00\00!owner_public_key\01!owner public key9auth::rpo_falcon512::pub_key\00\00\00\00\00\00\00") + ) + (alias export 0 "word" (type (;1;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;2;) (func (param "arg" 1))) + (alias core export 0 "miden:base/authentication-component@1.0.0#auth-procedure" (core func (;0;))) + (func (;0;) (type 2) (canon lift (core func 0))) + (alias export 0 "felt" (type (;3;))) + (alias export 0 "word" (type (;4;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-auth-procedure" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "auth-procedure" (func 0) (func (type 8))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-auth-procedure" (func 0)) + (with "import-type-felt" (type 3)) + (with "import-type-word" (type 4)) + (with "import-type-word0" (type 1)) + ) + ) + (export (;2;) "miden:base/authentication-component@1.0.0" (instance 1)) +) diff --git a/tests/integration/expected/examples/basic_wallet.hir b/tests/integration/expected/examples/basic_wallet.hir new file mode 100644 index 000000000..2787844b3 --- /dev/null +++ b/tests/integration/expected/examples/basic_wallet.hir @@ -0,0 +1,588 @@ +builtin.component miden:basic-wallet/basic-wallet@0.1.0 { + builtin.module public @basic_wallet { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @basic_wallet::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:basic-wallet/basic-wallet@0.1.0#receive-asset(v0: felt, v1: felt, v2: felt, v3: felt) { + ^block9(v0: felt, v1: felt, v2: felt, v3: felt): + v5 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v6 = hir.bitcast v5 : ptr; + v7 = hir.load v6 : i32; + v8 = arith.constant 32 : i32; + v9 = arith.sub v7, v8 : i32 #[overflow = wrapping]; + v10 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v11 = hir.bitcast v10 : ptr; + hir.store v11, v9; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/wit_bindgen::rt::run_ctors_once() + v13 = arith.constant 12 : u32; + v12 = hir.bitcast v9 : u32; + v14 = arith.add v12, v13 : u32 #[overflow = checked]; + v15 = arith.constant 4 : u32; + v16 = arith.mod v14, v15 : u32; + hir.assertz v16 #[code = 250]; + v17 = hir.int_to_ptr v14 : ptr; + hir.store v17, v3; + v19 = arith.constant 8 : u32; + v18 = hir.bitcast v9 : u32; + v20 = arith.add v18, v19 : u32 #[overflow = checked]; + v449 = arith.constant 4 : u32; + v22 = arith.mod v20, v449 : u32; + hir.assertz v22 #[code = 250]; + v23 = hir.int_to_ptr v20 : ptr; + hir.store v23, v2; + v448 = arith.constant 4 : u32; + v24 = hir.bitcast v9 : u32; + v26 = arith.add v24, v448 : u32 #[overflow = checked]; + v447 = arith.constant 4 : u32; + v28 = arith.mod v26, v447 : u32; + hir.assertz v28 #[code = 250]; + v29 = hir.int_to_ptr v26 : ptr; + hir.store v29, v1; + v30 = hir.bitcast v9 : u32; + v446 = arith.constant 4 : u32; + v32 = arith.mod v30, v446 : u32; + hir.assertz v32 #[code = 250]; + v33 = hir.int_to_ptr v30 : ptr; + hir.store v33, v0; + v34 = arith.constant 16 : i32; + v35 = arith.add v9, v34 : i32 #[overflow = wrapping]; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden_base_sys::bindings::account::add_asset(v35, v9) + v445 = arith.constant 32 : i32; + v37 = arith.add v9, v445 : i32 #[overflow = wrapping]; + v38 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v39 = hir.bitcast v38 : ptr; + hir.store v39, v37; + builtin.ret ; + }; + + private builtin.function @miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note(v40: felt, v41: felt, v42: felt, v43: felt, v44: felt) { + ^block11(v40: felt, v41: felt, v42: felt, v43: felt, v44: felt): + v46 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v47 = hir.bitcast v46 : ptr; + v48 = hir.load v47 : i32; + v49 = arith.constant 64 : i32; + v50 = arith.sub v48, v49 : i32 #[overflow = wrapping]; + v51 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v52 = hir.bitcast v51 : ptr; + hir.store v52, v50; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/wit_bindgen::rt::run_ctors_once() + v54 = arith.constant 12 : u32; + v53 = hir.bitcast v50 : u32; + v55 = arith.add v53, v54 : u32 #[overflow = checked]; + v56 = arith.constant 4 : u32; + v57 = arith.mod v55, v56 : u32; + hir.assertz v57 #[code = 250]; + v58 = hir.int_to_ptr v55 : ptr; + hir.store v58, v43; + v60 = arith.constant 8 : u32; + v59 = hir.bitcast v50 : u32; + v61 = arith.add v59, v60 : u32 #[overflow = checked]; + v455 = arith.constant 4 : u32; + v63 = arith.mod v61, v455 : u32; + hir.assertz v63 #[code = 250]; + v64 = hir.int_to_ptr v61 : ptr; + hir.store v64, v42; + v454 = arith.constant 4 : u32; + v65 = hir.bitcast v50 : u32; + v67 = arith.add v65, v454 : u32 #[overflow = checked]; + v453 = arith.constant 4 : u32; + v69 = arith.mod v67, v453 : u32; + hir.assertz v69 #[code = 250]; + v70 = hir.int_to_ptr v67 : ptr; + hir.store v70, v41; + v71 = hir.bitcast v50 : u32; + v452 = arith.constant 4 : u32; + v73 = arith.mod v71, v452 : u32; + hir.assertz v73 #[code = 250]; + v74 = hir.int_to_ptr v71 : ptr; + hir.store v74, v40; + v75 = arith.constant 16 : i32; + v76 = arith.add v50, v75 : i32 #[overflow = wrapping]; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden_base_sys::bindings::account::remove_asset(v76, v50) + v451 = arith.constant 16 : i32; + v80 = arith.add v50, v451 : i32 #[overflow = wrapping]; + v77 = arith.constant 32 : i32; + v78 = arith.add v50, v77 : i32 #[overflow = wrapping]; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden_base_sys::bindings::tx::add_asset_to_note(v78, v80, v44) + v450 = arith.constant 64 : i32; + v82 = arith.add v50, v450 : i32 #[overflow = wrapping]; + v83 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v84 = hir.bitcast v83 : ptr; + hir.store v84, v82; + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block13: + v86 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/GOT.data.internal.__memory_base : ptr + v87 = hir.bitcast v86 : ptr; + v88 = hir.load v87 : i32; + v89 = arith.constant 1048584 : i32; + v90 = arith.add v88, v89 : i32 #[overflow = wrapping]; + v91 = hir.bitcast v90 : u32; + v92 = hir.int_to_ptr v91 : ptr; + v93 = hir.load v92 : u8; + v85 = arith.constant 0 : i32; + v94 = arith.zext v93 : u32; + v95 = hir.bitcast v94 : i32; + v97 = arith.neq v95, v85 : i1; + scf.if v97{ + ^block15: + scf.yield ; + } else { + ^block16: + v98 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/GOT.data.internal.__memory_base : ptr + v99 = hir.bitcast v98 : ptr; + v100 = hir.load v99 : i32; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__wasm_call_ctors() + v457 = arith.constant 1 : u8; + v459 = arith.constant 1048584 : i32; + v102 = arith.add v100, v459 : i32 #[overflow = wrapping]; + v106 = hir.bitcast v102 : u32; + v107 = hir.int_to_ptr v106 : ptr; + hir.store v107, v457; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::account::add_asset(v108: i32, v109: i32) { + ^block17(v108: i32, v109: i32): + v111 = arith.constant 12 : u32; + v110 = hir.bitcast v109 : u32; + v112 = arith.add v110, v111 : u32 #[overflow = checked]; + v113 = arith.constant 4 : u32; + v114 = arith.mod v112, v113 : u32; + hir.assertz v114 #[code = 250]; + v115 = hir.int_to_ptr v112 : ptr; + v116 = hir.load v115 : felt; + v118 = arith.constant 8 : u32; + v117 = hir.bitcast v109 : u32; + v119 = arith.add v117, v118 : u32 #[overflow = checked]; + v463 = arith.constant 4 : u32; + v121 = arith.mod v119, v463 : u32; + hir.assertz v121 #[code = 250]; + v122 = hir.int_to_ptr v119 : ptr; + v123 = hir.load v122 : felt; + v462 = arith.constant 4 : u32; + v124 = hir.bitcast v109 : u32; + v126 = arith.add v124, v462 : u32 #[overflow = checked]; + v461 = arith.constant 4 : u32; + v128 = arith.mod v126, v461 : u32; + hir.assertz v128 #[code = 250]; + v129 = hir.int_to_ptr v126 : ptr; + v130 = hir.load v129 : felt; + v131 = hir.bitcast v109 : u32; + v460 = arith.constant 4 : u32; + v133 = arith.mod v131, v460 : u32; + hir.assertz v133 #[code = 250]; + v134 = hir.int_to_ptr v131 : ptr; + v135 = hir.load v134 : felt; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden::account::add_asset(v116, v123, v130, v135, v108) + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::account::remove_asset(v136: i32, v137: i32) { + ^block19(v136: i32, v137: i32): + v139 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v140 = hir.bitcast v139 : ptr; + v141 = hir.load v140 : i32; + v142 = arith.constant 32 : i32; + v143 = arith.sub v141, v142 : i32 #[overflow = wrapping]; + v144 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v145 = hir.bitcast v144 : ptr; + hir.store v145, v143; + v147 = arith.constant 12 : u32; + v146 = hir.bitcast v137 : u32; + v148 = arith.add v146, v147 : u32 #[overflow = checked]; + v149 = arith.constant 4 : u32; + v150 = arith.mod v148, v149 : u32; + hir.assertz v150 #[code = 250]; + v151 = hir.int_to_ptr v148 : ptr; + v152 = hir.load v151 : felt; + v154 = arith.constant 8 : u32; + v153 = hir.bitcast v137 : u32; + v155 = arith.add v153, v154 : u32 #[overflow = checked]; + v473 = arith.constant 4 : u32; + v157 = arith.mod v155, v473 : u32; + hir.assertz v157 #[code = 250]; + v158 = hir.int_to_ptr v155 : ptr; + v159 = hir.load v158 : felt; + v472 = arith.constant 4 : u32; + v160 = hir.bitcast v137 : u32; + v162 = arith.add v160, v472 : u32 #[overflow = checked]; + v471 = arith.constant 4 : u32; + v164 = arith.mod v162, v471 : u32; + hir.assertz v164 #[code = 250]; + v165 = hir.int_to_ptr v162 : ptr; + v166 = hir.load v165 : felt; + v167 = hir.bitcast v137 : u32; + v470 = arith.constant 4 : u32; + v169 = arith.mod v167, v470 : u32; + hir.assertz v169 #[code = 250]; + v170 = hir.int_to_ptr v167 : ptr; + v171 = hir.load v170 : felt; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden::account::remove_asset(v152, v159, v166, v171, v143) + v469 = arith.constant 8 : u32; + v172 = hir.bitcast v143 : u32; + v174 = arith.add v172, v469 : u32 #[overflow = checked]; + v468 = arith.constant 8 : u32; + v176 = arith.mod v174, v468 : u32; + hir.assertz v176 #[code = 250]; + v177 = hir.int_to_ptr v174 : ptr; + v178 = hir.load v177 : i64; + v180 = arith.constant 24 : u32; + v179 = hir.bitcast v143 : u32; + v181 = arith.add v179, v180 : u32 #[overflow = checked]; + v467 = arith.constant 8 : u32; + v183 = arith.mod v181, v467 : u32; + hir.assertz v183 #[code = 250]; + v184 = hir.int_to_ptr v181 : ptr; + hir.store v184, v178; + v185 = hir.bitcast v143 : u32; + v466 = arith.constant 8 : u32; + v187 = arith.mod v185, v466 : u32; + hir.assertz v187 #[code = 250]; + v188 = hir.int_to_ptr v185 : ptr; + v189 = hir.load v188 : i64; + v191 = arith.constant 16 : u32; + v190 = hir.bitcast v143 : u32; + v192 = arith.add v190, v191 : u32 #[overflow = checked]; + v465 = arith.constant 8 : u32; + v194 = arith.mod v192, v465 : u32; + hir.assertz v194 #[code = 250]; + v195 = hir.int_to_ptr v192 : ptr; + hir.store v195, v189; + v196 = arith.constant 16 : i32; + v197 = arith.add v143, v196 : i32 #[overflow = wrapping]; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden_stdlib_sys::intrinsics::word::Word::reverse(v136, v197) + v464 = arith.constant 32 : i32; + v199 = arith.add v143, v464 : i32 #[overflow = wrapping]; + v200 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v201 = hir.bitcast v200 : ptr; + hir.store v201, v199; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::tx::add_asset_to_note(v202: i32, v203: i32, v204: felt) { + ^block21(v202: i32, v203: i32, v204: felt): + v206 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v207 = hir.bitcast v206 : ptr; + v208 = hir.load v207 : i32; + v209 = arith.constant 48 : i32; + v210 = arith.sub v208, v209 : i32 #[overflow = wrapping]; + v211 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v212 = hir.bitcast v211 : ptr; + hir.store v212, v210; + v214 = arith.constant 12 : u32; + v213 = hir.bitcast v203 : u32; + v215 = arith.add v213, v214 : u32 #[overflow = checked]; + v216 = arith.constant 4 : u32; + v217 = arith.mod v215, v216 : u32; + hir.assertz v217 #[code = 250]; + v218 = hir.int_to_ptr v215 : ptr; + v219 = hir.load v218 : felt; + v221 = arith.constant 8 : u32; + v220 = hir.bitcast v203 : u32; + v222 = arith.add v220, v221 : u32 #[overflow = checked]; + v486 = arith.constant 4 : u32; + v224 = arith.mod v222, v486 : u32; + hir.assertz v224 #[code = 250]; + v225 = hir.int_to_ptr v222 : ptr; + v226 = hir.load v225 : felt; + v485 = arith.constant 4 : u32; + v227 = hir.bitcast v203 : u32; + v229 = arith.add v227, v485 : u32 #[overflow = checked]; + v484 = arith.constant 4 : u32; + v231 = arith.mod v229, v484 : u32; + hir.assertz v231 #[code = 250]; + v232 = hir.int_to_ptr v229 : ptr; + v233 = hir.load v232 : felt; + v234 = hir.bitcast v203 : u32; + v483 = arith.constant 4 : u32; + v236 = arith.mod v234, v483 : u32; + hir.assertz v236 #[code = 250]; + v237 = hir.int_to_ptr v234 : ptr; + v238 = hir.load v237 : felt; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden::tx::add_asset_to_note(v219, v226, v233, v238, v204, v210) + v482 = arith.constant 8 : u32; + v239 = hir.bitcast v210 : u32; + v241 = arith.add v239, v482 : u32 #[overflow = checked]; + v481 = arith.constant 8 : u32; + v243 = arith.mod v241, v481 : u32; + hir.assertz v243 #[code = 250]; + v244 = hir.int_to_ptr v241 : ptr; + v245 = hir.load v244 : i64; + v247 = arith.constant 40 : u32; + v246 = hir.bitcast v210 : u32; + v248 = arith.add v246, v247 : u32 #[overflow = checked]; + v480 = arith.constant 8 : u32; + v250 = arith.mod v248, v480 : u32; + hir.assertz v250 #[code = 250]; + v251 = hir.int_to_ptr v248 : ptr; + hir.store v251, v245; + v252 = hir.bitcast v210 : u32; + v479 = arith.constant 8 : u32; + v254 = arith.mod v252, v479 : u32; + hir.assertz v254 #[code = 250]; + v255 = hir.int_to_ptr v252 : ptr; + v256 = hir.load v255 : i64; + v258 = arith.constant 32 : u32; + v257 = hir.bitcast v210 : u32; + v259 = arith.add v257, v258 : u32 #[overflow = checked]; + v478 = arith.constant 8 : u32; + v261 = arith.mod v259, v478 : u32; + hir.assertz v261 #[code = 250]; + v262 = hir.int_to_ptr v259 : ptr; + hir.store v262, v256; + v264 = arith.constant 16 : u32; + v263 = hir.bitcast v210 : u32; + v265 = arith.add v263, v264 : u32 #[overflow = checked]; + v477 = arith.constant 4 : u32; + v267 = arith.mod v265, v477 : u32; + hir.assertz v267 #[code = 250]; + v268 = hir.int_to_ptr v265 : ptr; + v269 = hir.load v268 : felt; + v270 = arith.constant 32 : i32; + v271 = arith.add v210, v270 : i32 #[overflow = wrapping]; + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden_stdlib_sys::intrinsics::word::Word::reverse(v202, v271) + v476 = arith.constant 16 : u32; + v272 = hir.bitcast v202 : u32; + v274 = arith.add v272, v476 : u32 #[overflow = checked]; + v475 = arith.constant 4 : u32; + v276 = arith.mod v274, v475 : u32; + hir.assertz v276 #[code = 250]; + v277 = hir.int_to_ptr v274 : ptr; + hir.store v277, v269; + v474 = arith.constant 48 : i32; + v279 = arith.add v210, v474 : i32 #[overflow = wrapping]; + v280 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v281 = hir.bitcast v280 : ptr; + hir.store v281, v279; + builtin.ret ; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v282: i32, v283: i32) { + ^block23(v282: i32, v283: i32): + v286 = builtin.global_symbol @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/__stack_pointer : ptr + v287 = hir.bitcast v286 : ptr; + v288 = hir.load v287 : i32; + v289 = arith.constant 16 : i32; + v290 = arith.sub v288, v289 : i32 #[overflow = wrapping]; + v292 = arith.constant 8 : u32; + v291 = hir.bitcast v283 : u32; + v293 = arith.add v291, v292 : u32 #[overflow = checked]; + v573 = arith.constant 8 : u32; + v295 = arith.mod v293, v573 : u32; + hir.assertz v295 #[code = 250]; + v296 = hir.int_to_ptr v293 : ptr; + v297 = hir.load v296 : i64; + v572 = arith.constant 8 : u32; + v298 = hir.bitcast v290 : u32; + v300 = arith.add v298, v572 : u32 #[overflow = checked]; + v301 = arith.constant 4 : u32; + v302 = arith.mod v300, v301 : u32; + hir.assertz v302 #[code = 250]; + v303 = hir.int_to_ptr v300 : ptr; + hir.store v303, v297; + v304 = hir.bitcast v283 : u32; + v571 = arith.constant 8 : u32; + v306 = arith.mod v304, v571 : u32; + hir.assertz v306 #[code = 250]; + v307 = hir.int_to_ptr v304 : ptr; + v308 = hir.load v307 : i64; + v309 = hir.bitcast v290 : u32; + v570 = arith.constant 4 : u32; + v311 = arith.mod v309, v570 : u32; + hir.assertz v311 #[code = 250]; + v312 = hir.int_to_ptr v309 : ptr; + hir.store v312, v308; + v313 = arith.constant 12 : i32; + v314 = arith.add v290, v313 : i32 #[overflow = wrapping]; + v284 = arith.constant 0 : i32; + v541, v542, v543, v544, v545, v546 = scf.while v284, v290, v314, v282 : i32, i32, i32, i32, i32, i32 { + ^block50(v547: i32, v548: i32, v549: i32, v550: i32): + v569 = arith.constant 0 : i32; + v317 = arith.constant 8 : i32; + v318 = arith.eq v547, v317 : i1; + v319 = arith.zext v318 : u32; + v320 = hir.bitcast v319 : i32; + v322 = arith.neq v320, v569 : i1; + v535, v536 = scf.if v322 : i32, i32 { + ^block49: + v495 = ub.poison i32 : i32; + scf.yield v495, v495; + } else { + ^block28: + v324 = arith.add v548, v547 : i32 #[overflow = wrapping]; + v325 = hir.bitcast v324 : u32; + v568 = arith.constant 4 : u32; + v327 = arith.mod v325, v568 : u32; + hir.assertz v327 #[code = 250]; + v328 = hir.int_to_ptr v325 : ptr; + v329 = hir.load v328 : felt; + v331 = hir.bitcast v549 : u32; + v567 = arith.constant 4 : u32; + v333 = arith.mod v331, v567 : u32; + hir.assertz v333 #[code = 250]; + v334 = hir.int_to_ptr v331 : ptr; + v335 = hir.load v334 : i32; + v336 = hir.bitcast v324 : u32; + v566 = arith.constant 4 : u32; + v338 = arith.mod v336, v566 : u32; + hir.assertz v338 #[code = 250]; + v339 = hir.int_to_ptr v336 : ptr; + hir.store v339, v335; + v340 = hir.bitcast v549 : u32; + v565 = arith.constant 4 : u32; + v342 = arith.mod v340, v565 : u32; + hir.assertz v342 #[code = 250]; + v343 = hir.int_to_ptr v340 : ptr; + hir.store v343, v329; + v346 = arith.constant -4 : i32; + v347 = arith.add v549, v346 : i32 #[overflow = wrapping]; + v344 = arith.constant 4 : i32; + v345 = arith.add v547, v344 : i32 #[overflow = wrapping]; + scf.yield v345, v347; + }; + v563 = ub.poison i32 : i32; + v538 = cf.select v322, v563, v550 : i32; + v564 = ub.poison i32 : i32; + v537 = cf.select v322, v564, v548 : i32; + v494 = arith.constant 1 : u32; + v487 = arith.constant 0 : u32; + v540 = cf.select v322, v487, v494 : u32; + v528 = arith.trunc v540 : i1; + scf.condition v528, v535, v537, v536, v538, v548, v550; + } do { + ^block51(v551: i32, v552: i32, v553: i32, v554: i32, v555: i32, v556: i32): + scf.yield v551, v552, v553, v554; + }; + v562 = arith.constant 8 : u32; + v349 = hir.bitcast v545 : u32; + v351 = arith.add v349, v562 : u32 #[overflow = checked]; + v561 = arith.constant 4 : u32; + v353 = arith.mod v351, v561 : u32; + hir.assertz v353 #[code = 250]; + v354 = hir.int_to_ptr v351 : ptr; + v355 = hir.load v354 : i64; + v560 = arith.constant 8 : u32; + v356 = hir.bitcast v546 : u32; + v358 = arith.add v356, v560 : u32 #[overflow = checked]; + v559 = arith.constant 8 : u32; + v360 = arith.mod v358, v559 : u32; + hir.assertz v360 #[code = 250]; + v361 = hir.int_to_ptr v358 : ptr; + hir.store v361, v355; + v362 = hir.bitcast v545 : u32; + v558 = arith.constant 4 : u32; + v364 = arith.mod v362, v558 : u32; + hir.assertz v364 #[code = 250]; + v365 = hir.int_to_ptr v362 : ptr; + v366 = hir.load v365 : i64; + v367 = hir.bitcast v546 : u32; + v557 = arith.constant 8 : u32; + v369 = arith.mod v367, v557 : u32; + hir.assertz v369 #[code = 250]; + v370 = hir.int_to_ptr v367 : ptr; + hir.store v370, v366; + builtin.ret ; + }; + + private builtin.function @miden::account::add_asset(v371: felt, v372: felt, v373: felt, v374: felt, v375: i32) { + ^block29(v371: felt, v372: felt, v373: felt, v374: felt, v375: i32): + v376, v377, v378, v379 = hir.exec @miden/account/add_asset(v371, v372, v373, v374) : felt, felt, felt, felt + v380 = hir.bitcast v375 : u32; + v381 = hir.int_to_ptr v380 : ptr; + hir.store v381, v376; + v382 = arith.constant 4 : u32; + v383 = arith.add v380, v382 : u32 #[overflow = checked]; + v384 = hir.int_to_ptr v383 : ptr; + hir.store v384, v377; + v385 = arith.constant 8 : u32; + v386 = arith.add v380, v385 : u32 #[overflow = checked]; + v387 = hir.int_to_ptr v386 : ptr; + hir.store v387, v378; + v388 = arith.constant 12 : u32; + v389 = arith.add v380, v388 : u32 #[overflow = checked]; + v390 = hir.int_to_ptr v389 : ptr; + hir.store v390, v379; + builtin.ret ; + }; + + private builtin.function @miden::account::remove_asset(v391: felt, v392: felt, v393: felt, v394: felt, v395: i32) { + ^block33(v391: felt, v392: felt, v393: felt, v394: felt, v395: i32): + v396, v397, v398, v399 = hir.exec @miden/account/remove_asset(v391, v392, v393, v394) : felt, felt, felt, felt + v400 = hir.bitcast v395 : u32; + v401 = hir.int_to_ptr v400 : ptr; + hir.store v401, v396; + v402 = arith.constant 4 : u32; + v403 = arith.add v400, v402 : u32 #[overflow = checked]; + v404 = hir.int_to_ptr v403 : ptr; + hir.store v404, v397; + v405 = arith.constant 8 : u32; + v406 = arith.add v400, v405 : u32 #[overflow = checked]; + v407 = hir.int_to_ptr v406 : ptr; + hir.store v407, v398; + v408 = arith.constant 12 : u32; + v409 = arith.add v400, v408 : u32 #[overflow = checked]; + v410 = hir.int_to_ptr v409 : ptr; + hir.store v410, v399; + builtin.ret ; + }; + + private builtin.function @miden::tx::add_asset_to_note(v411: felt, v412: felt, v413: felt, v414: felt, v415: felt, v416: i32) { + ^block35(v411: felt, v412: felt, v413: felt, v414: felt, v415: felt, v416: i32): + v417, v418, v419, v420, v421 = hir.exec @miden/tx/add_asset_to_note(v411, v412, v413, v414, v415) : felt, felt, felt, felt, felt + v422 = hir.bitcast v416 : u32; + v423 = hir.int_to_ptr v422 : ptr; + hir.store v423, v417; + v424 = arith.constant 4 : u32; + v425 = arith.add v422, v424 : u32 #[overflow = checked]; + v426 = hir.int_to_ptr v425 : ptr; + hir.store v426, v418; + v427 = arith.constant 8 : u32; + v428 = arith.add v422, v427 : u32 #[overflow = checked]; + v429 = hir.int_to_ptr v428 : ptr; + hir.store v429, v419; + v430 = arith.constant 12 : u32; + v431 = arith.add v422, v430 : u32 #[overflow = checked]; + v432 = hir.int_to_ptr v431 : ptr; + hir.store v432, v420; + v433 = arith.constant 16 : u32; + v434 = arith.add v422, v433 : u32 #[overflow = checked]; + v435 = hir.int_to_ptr v434 : ptr; + hir.store v435, v421; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @receive-asset(v436: felt, v437: felt, v438: felt, v439: felt) { + ^block38(v436: felt, v437: felt, v438: felt, v439: felt): + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden:basic-wallet/basic-wallet@0.1.0#receive-asset(v436, v437, v438, v439) + builtin.ret ; + }; + + public builtin.function @move-asset-to-note(v440: felt, v441: felt, v442: felt, v443: felt, v444: felt) { + ^block40(v440: felt, v441: felt, v442: felt, v443: felt, v444: felt): + hir.exec @miden:basic-wallet/basic-wallet@0.1.0/basic_wallet/miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note(v440, v441, v442, v443, v444) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/basic_wallet.masm b/tests/integration/expected/examples/basic_wallet.masm new file mode 100644 index 000000000..fa173799a --- /dev/null +++ b/tests/integration/expected/examples/basic_wallet.masm @@ -0,0 +1,1294 @@ +# mod miden:basic-wallet/basic-wallet@0.1.0 + +export.receive-asset + exec.::miden:basic-wallet/basic-wallet@0.1.0::init + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden:basic-wallet/basic-wallet@0.1.0#receive-asset + trace.252 + nop + exec.::std::sys::truncate_stack +end + +export.move-asset-to-note + exec.::miden:basic-wallet/basic-wallet@0.1.0::init + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:basic-wallet/basic-wallet@0.1.0::basic_wallet + +proc.__wasm_call_ctors + nop +end + +proc.basic_wallet::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:basic-wallet/basic-wallet@0.1.0#receive-asset + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + dup.1 + swap.1 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden_base_sys::bindings::account::add_asset + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.64 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + dup.1 + swap.1 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden_base_sys::bindings::account::remove_asset + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + push.32 + dup.2 + u32wrapping_add + movup.2 + swap.3 + movdn.2 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden_base_sys::bindings::tx::add_asset_to_note + trace.252 + nop + push.64 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.miden_base_sys::bindings::account::add_asset + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movdn.3 + swap.2 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden::account::add_asset + trace.252 + nop +end + +proc.miden_base_sys::bindings::account::remove_asset + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.4 + swap.1 + swap.3 + swap.1 + swap.4 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden::account::remove_asset + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.24 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.16 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_base_sys::bindings::tx::add_asset_to_note + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.4 + swap.1 + swap.3 + swap.1 + swap.5 + swap.7 + swap.4 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden::tx::add_asset_to_note + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.40 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.1 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.32 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.32 + dup.3 + u32wrapping_add + dup.2 + trace.240 + nop + exec.::miden:basic-wallet/basic-wallet@0.1.0::basic_wallet::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.16 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.48 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.miden::account::add_asset + trace.240 + nop + exec.::miden::account::add_asset + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::remove_asset + trace.240 + nop + exec.::miden::account::remove_asset + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::tx::add_asset_to_note + trace.240 + nop + exec.::miden::tx::add_asset_to_note + trace.252 + nop + movup.5 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + diff --git a/tests/integration/expected/examples/basic_wallet.wat b/tests/integration/expected/examples/basic_wallet.wat new file mode 100644 index 000000000..342508337 --- /dev/null +++ b/tests/integration/expected/examples/basic_wallet.wat @@ -0,0 +1,328 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + (type (;5;) (record (field "inner" 4))) + (export (;6;) "asset" (type (eq 5))) + (type (;7;) (record (field "inner" 1))) + (export (;8;) "note-idx" (type (eq 7))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32 f32 f32 f32))) + (type (;2;) (func (param f32 f32 f32 f32 f32))) + (type (;3;) (func (param i32 i32))) + (type (;4;) (func (param i32 i32 f32))) + (type (;5;) (func (param f32 f32 f32 f32 i32))) + (type (;6;) (func (param f32 f32 f32 f32 f32 i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:basic-wallet/basic-wallet@0.1.0#receive-asset" (func $miden:basic-wallet/basic-wallet@0.1.0#receive-asset)) + (export "miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note" (func $miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note)) + (elem (;0;) (i32.const 1) func $basic_wallet::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $basic_wallet::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:basic-wallet/basic-wallet@0.1.0#receive-asset (;2;) (type 1) (param f32 f32 f32 f32) + (local i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + local.get 4 + local.get 3 + f32.store offset=12 + local.get 4 + local.get 2 + f32.store offset=8 + local.get 4 + local.get 1 + f32.store offset=4 + local.get 4 + local.get 0 + f32.store + local.get 4 + i32.const 16 + i32.add + local.get 4 + call $miden_base_sys::bindings::account::add_asset + local.get 4 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note (;3;) (type 2) (param f32 f32 f32 f32 f32) + (local i32) + global.get $__stack_pointer + i32.const 64 + i32.sub + local.tee 5 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + local.get 5 + local.get 3 + f32.store offset=12 + local.get 5 + local.get 2 + f32.store offset=8 + local.get 5 + local.get 1 + f32.store offset=4 + local.get 5 + local.get 0 + f32.store + local.get 5 + i32.const 16 + i32.add + local.get 5 + call $miden_base_sys::bindings::account::remove_asset + local.get 5 + i32.const 32 + i32.add + local.get 5 + i32.const 16 + i32.add + local.get 4 + call $miden_base_sys::bindings::tx::add_asset_to_note + local.get 5 + i32.const 64 + i32.add + global.set $__stack_pointer + ) + (func $wit_bindgen::rt::run_ctors_once (;4;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048584 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $miden_base_sys::bindings::account::add_asset (;5;) (type 3) (param i32 i32) + local.get 1 + f32.load offset=12 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=4 + local.get 1 + f32.load + local.get 0 + call $miden::account::add_asset + ) + (func $miden_base_sys::bindings::account::remove_asset (;6;) (type 3) (param i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 2 + global.set $__stack_pointer + local.get 1 + f32.load offset=12 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=4 + local.get 1 + f32.load + local.get 2 + call $miden::account::remove_asset + local.get 2 + local.get 2 + i64.load offset=8 + i64.store offset=24 + local.get 2 + local.get 2 + i64.load + i64.store offset=16 + local.get 0 + local.get 2 + i32.const 16 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 2 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::tx::add_asset_to_note (;7;) (type 4) (param i32 i32 f32) + (local i32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 1 + f32.load offset=12 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=4 + local.get 1 + f32.load + local.get 2 + local.get 3 + call $miden::tx::add_asset_to_note + local.get 3 + local.get 3 + i64.load offset=8 + i64.store offset=40 + local.get 3 + local.get 3 + i64.load + i64.store offset=32 + local.get 3 + f32.load offset=16 + local.set 2 + local.get 0 + local.get 3 + i32.const 32 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 0 + local.get 2 + f32.store offset=16 + local.get 3 + i32.const 48 + i32.add + global.set $__stack_pointer + ) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;8;) (type 3) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $miden::account::add_asset (;9;) (type 5) (param f32 f32 f32 f32 i32) + unreachable + ) + (func $miden::account::remove_asset (;10;) (type 5) (param f32 f32 f32 f32 i32) + unreachable + ) + (func $miden::tx::add_asset_to_note (;11;) (type 6) (param f32 f32 f32 f32 f32 i32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + (@custom "rodata,miden_account" (after data) "\19basic_wallet\01\0b0.1.0\03\01\01\00\00\00\00\00\00\00\00\00") + ) + (alias export 0 "asset" (type (;1;))) + (alias export 0 "note-idx" (type (;2;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;3;) (func (param "asset" 1))) + (alias core export 0 "miden:basic-wallet/basic-wallet@0.1.0#receive-asset" (core func (;0;))) + (func (;0;) (type 3) (canon lift (core func 0))) + (type (;4;) (func (param "asset" 1) (param "note-idx" 2))) + (alias core export 0 "miden:basic-wallet/basic-wallet@0.1.0#move-asset-to-note" (core func (;1;))) + (func (;1;) (type 4) (canon lift (core func 1))) + (alias export 0 "felt" (type (;5;))) + (alias export 0 "word" (type (;6;))) + (alias export 0 "asset" (type (;7;))) + (alias export 0 "note-idx" (type (;8;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (type (;5;) (record (field "inner" 4))) + (import "import-type-asset" (type (;6;) (eq 5))) + (type (;7;) (record (field "inner" 1))) + (import "import-type-note-idx" (type (;8;) (eq 7))) + (import "import-type-asset0" (type (;9;) (eq 6))) + (type (;10;) (func (param "asset" 9))) + (import "import-func-receive-asset" (func (;0;) (type 10))) + (import "import-type-note-idx0" (type (;11;) (eq 8))) + (type (;12;) (func (param "asset" 9) (param "note-idx" 11))) + (import "import-func-move-asset-to-note" (func (;1;) (type 12))) + (export (;13;) "asset" (type 6)) + (export (;14;) "note-idx" (type 8)) + (type (;15;) (func (param "asset" 13))) + (export (;2;) "receive-asset" (func 0) (func (type 15))) + (type (;16;) (func (param "asset" 13) (param "note-idx" 14))) + (export (;3;) "move-asset-to-note" (func 1) (func (type 16))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-receive-asset" (func 0)) + (with "import-func-move-asset-to-note" (func 1)) + (with "import-type-felt" (type 5)) + (with "import-type-word" (type 6)) + (with "import-type-asset" (type 7)) + (with "import-type-note-idx" (type 8)) + (with "import-type-asset0" (type 1)) + (with "import-type-note-idx0" (type 2)) + ) + ) + (export (;2;) "miden:basic-wallet/basic-wallet@0.1.0" (instance 1)) +) diff --git a/tests/integration/expected/examples/basic_wallet_tx_script.hir b/tests/integration/expected/examples/basic_wallet_tx_script.hir new file mode 100644 index 000000000..e2ec9ff9e --- /dev/null +++ b/tests/integration/expected/examples/basic_wallet_tx_script.hir @@ -0,0 +1,1272 @@ +builtin.component miden:base/transaction-script@1.0.0 { + builtin.module public @basic_wallet_tx_script { + private builtin.function @basic_wallet_tx_script::bindings::miden::basic_wallet::basic_wallet::move_asset_to_note::wit_import9(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt) { + ^block3(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt): + hir.call v0, v1, v2, v3, v4 #[callee = miden:basic-wallet/basic-wallet@0.1.0/move-asset-to-note] #[signature = (param felt) (param felt) (param felt) (param felt) (param felt)]; + builtin.ret ; + }; + + private builtin.function @__wasm_call_ctors() { + ^block8: + builtin.ret ; + }; + + private builtin.function @core::slice::index::slice_end_index_len_fail(v5: i32, v6: i32, v7: i32) { + ^block10(v5: i32, v6: i32, v7: i32): + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/core::slice::::copy_from_slice::len_mismatch_fail::do_panic::runtime(v5, v6, v7) + ub.unreachable ; + }; + + private builtin.function @ as core::ops::index::Index>::index(v8: i32, v9: i32, v10: i32, v11: i32, v12: i32) { + ^block12(v8: i32, v9: i32, v10: i32, v11: i32, v12: i32): + v15 = arith.constant 8 : u32; + v14 = hir.bitcast v9 : u32; + v16 = arith.add v14, v15 : u32 #[overflow = checked]; + v17 = arith.constant 4 : u32; + v18 = arith.mod v16, v17 : u32; + hir.assertz v18 #[code = 250]; + v19 = hir.int_to_ptr v16 : ptr; + v20 = hir.load v19 : i32; + v13 = arith.constant 0 : i32; + v22 = hir.bitcast v20 : u32; + v21 = hir.bitcast v11 : u32; + v23 = arith.lte v21, v22 : i1; + v24 = arith.zext v23 : u32; + v25 = hir.bitcast v24 : i32; + v27 = arith.neq v25, v13 : i1; + cf.cond_br v27 ^block14, ^block15; + ^block14: + v915 = arith.constant 4 : u32; + v29 = hir.bitcast v8 : u32; + v31 = arith.add v29, v915 : u32 #[overflow = checked]; + v914 = arith.constant 4 : u32; + v33 = arith.mod v31, v914 : u32; + hir.assertz v33 #[code = 250]; + v28 = arith.sub v11, v10 : i32 #[overflow = wrapping]; + v34 = hir.int_to_ptr v31 : ptr; + hir.store v34, v28; + v913 = arith.constant 4 : u32; + v35 = hir.bitcast v9 : u32; + v37 = arith.add v35, v913 : u32 #[overflow = checked]; + v912 = arith.constant 4 : u32; + v39 = arith.mod v37, v912 : u32; + hir.assertz v39 #[code = 250]; + v40 = hir.int_to_ptr v37 : ptr; + v41 = hir.load v40 : i32; + v46 = hir.bitcast v8 : u32; + v911 = arith.constant 4 : u32; + v48 = arith.mod v46, v911 : u32; + hir.assertz v48 #[code = 250]; + v909 = arith.constant 2 : u32; + v44 = arith.shl v10, v909 : i32; + v45 = arith.add v41, v44 : i32 #[overflow = wrapping]; + v49 = hir.int_to_ptr v46 : ptr; + hir.store v49, v45; + builtin.ret ; + ^block15: + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/core::slice::index::slice_end_index_len_fail(v11, v20, v12) + ub.unreachable ; + }; + + private builtin.function @__rustc::__rust_alloc(v50: i32, v51: i32) -> i32 { + ^block16(v50: i32, v51: i32): + v53 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/GOT.data.internal.__memory_base : ptr + v54 = hir.bitcast v53 : ptr; + v55 = hir.load v54 : i32; + v56 = arith.constant 1048688 : i32; + v57 = arith.add v55, v56 : i32 #[overflow = wrapping]; + v58 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/::alloc(v57, v51, v50) : i32 + builtin.ret v58; + }; + + private builtin.function @__rustc::__rust_dealloc(v59: i32, v60: i32, v61: i32) { + ^block18(v59: i32, v60: i32, v61: i32): + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_alloc_zeroed(v62: i32, v63: i32) -> i32 { + ^block20(v62: i32, v63: i32): + v65 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/GOT.data.internal.__memory_base : ptr + v66 = hir.bitcast v65 : ptr; + v67 = hir.load v66 : i32; + v68 = arith.constant 1048688 : i32; + v69 = arith.add v67, v68 : i32 #[overflow = wrapping]; + v70 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/::alloc(v69, v63, v62) : i32 + v924 = arith.constant 0 : i32; + v71 = arith.constant 0 : i32; + v72 = arith.eq v70, v71 : i1; + v73 = arith.zext v72 : u32; + v74 = hir.bitcast v73 : i32; + v76 = arith.neq v74, v924 : i1; + scf.if v76{ + ^block22: + scf.yield ; + } else { + ^block23: + v922 = arith.constant 0 : i32; + v923 = arith.constant 0 : i32; + v78 = arith.eq v62, v923 : i1; + v79 = arith.zext v78 : u32; + v80 = hir.bitcast v79 : i32; + v82 = arith.neq v80, v922 : i1; + scf.if v82{ + ^block122: + scf.yield ; + } else { + ^block24: + v916 = arith.constant 0 : u8; + v85 = hir.bitcast v62 : u32; + v86 = hir.bitcast v70 : u32; + v87 = hir.int_to_ptr v86 : ptr; + hir.mem_set v87, v85, v916; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v70; + }; + + private builtin.function @basic_wallet_tx_script::bindings::__link_custom_section_describing_imports() { + ^block25: + builtin.ret ; + }; + + private builtin.function @miden:base/transaction-script@1.0.0#run(v89: felt, v90: felt, v91: felt, v92: felt) { + ^block27(v89: felt, v90: felt, v91: felt, v92: felt): + v97 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v98 = hir.bitcast v97 : ptr; + v99 = hir.load v98 : i32; + v100 = arith.constant 80 : i32; + v101 = arith.sub v99, v100 : i32 #[overflow = wrapping]; + v102 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v103 = hir.bitcast v102 : ptr; + hir.store v103, v101; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/wit_bindgen::rt::run_ctors_once() + v104 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::advice::adv_push_mapvaln(v92, v91, v90, v89) : felt + v105 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::felt::as_u64(v104) : i64 + v107 = arith.constant 3 : i32; + v106 = arith.trunc v105 : i32; + v108 = arith.band v106, v107 : i32; + v109 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::felt::from_u32(v108) : felt + v93 = arith.constant 0 : i32; + v111 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::felt::from_u32(v93) : felt + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::felt::assert_eq(v109, v111) + v114 = arith.constant 2 : i64; + v116 = hir.cast v114 : u32; + v115 = hir.bitcast v105 : u64; + v117 = arith.shr v115, v116 : u64; + v118 = hir.bitcast v117 : i64; + v119 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::felt::from_u64_unchecked(v118) : felt + v120 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::felt::as_u64(v119) : i64 + v926 = arith.constant 2 : u32; + v121 = arith.trunc v120 : i32; + v124 = arith.shl v121, v926 : i32; + v126 = arith.constant 4 : i32; + v1002 = arith.constant 0 : i32; + v112 = arith.constant 64 : i32; + v113 = arith.add v101, v112 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/alloc::raw_vec::RawVecInner::try_allocate_in(v113, v124, v1002, v126, v126) + v129 = arith.constant 68 : u32; + v128 = hir.bitcast v101 : u32; + v130 = arith.add v128, v129 : u32 #[overflow = checked]; + v131 = arith.constant 4 : u32; + v132 = arith.mod v130, v131 : u32; + hir.assertz v132 #[code = 250]; + v133 = hir.int_to_ptr v130 : ptr; + v134 = hir.load v133 : i32; + v136 = arith.constant 64 : u32; + v135 = hir.bitcast v101 : u32; + v137 = arith.add v135, v136 : u32 #[overflow = checked]; + v1001 = arith.constant 4 : u32; + v139 = arith.mod v137, v1001 : u32; + hir.assertz v139 #[code = 250]; + v140 = hir.int_to_ptr v137 : ptr; + v141 = hir.load v140 : i32; + v1000 = arith.constant 0 : i32; + v142 = arith.constant 1 : i32; + v143 = arith.eq v141, v142 : i1; + v144 = arith.zext v143 : u32; + v145 = hir.bitcast v144 : i32; + v147 = arith.neq v145, v1000 : i1; + v931 = scf.if v147 : u32 { + ^block30: + v368 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/GOT.data.internal.__memory_base : ptr + v369 = hir.bitcast v368 : ptr; + v370 = hir.load v369 : i32; + v372 = arith.constant 72 : u32; + v371 = hir.bitcast v101 : u32; + v373 = arith.add v371, v372 : u32 #[overflow = checked]; + v999 = arith.constant 4 : u32; + v375 = arith.mod v373, v999 : u32; + hir.assertz v375 #[code = 250]; + v376 = hir.int_to_ptr v373 : ptr; + v377 = hir.load v376 : i32; + v378 = arith.constant 1048640 : i32; + v379 = arith.add v370, v378 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/alloc::raw_vec::handle_error(v134, v377, v379) + v927 = arith.constant 0 : u32; + scf.yield v927; + } else { + ^block31: + v998 = arith.constant 72 : u32; + v148 = hir.bitcast v101 : u32; + v150 = arith.add v148, v998 : u32 #[overflow = checked]; + v997 = arith.constant 4 : u32; + v152 = arith.mod v150, v997 : u32; + hir.assertz v152 #[code = 250]; + v153 = hir.int_to_ptr v150 : ptr; + v154 = hir.load v153 : i32; + v996 = arith.constant 2 : u32; + v156 = hir.bitcast v154 : u32; + v158 = arith.shr v156, v996 : u32; + v159 = hir.bitcast v158 : i32; + v160 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/std::mem::pipe_preimage_to_memory(v119, v159, v92, v91, v90, v89) : i32 + v162 = arith.constant 28 : u32; + v161 = hir.bitcast v101 : u32; + v163 = arith.add v161, v162 : u32 #[overflow = checked]; + v995 = arith.constant 4 : u32; + v165 = arith.mod v163, v995 : u32; + hir.assertz v165 #[code = 250]; + v166 = hir.int_to_ptr v163 : ptr; + hir.store v166, v124; + v168 = arith.constant 24 : u32; + v167 = hir.bitcast v101 : u32; + v169 = arith.add v167, v168 : u32 #[overflow = checked]; + v994 = arith.constant 4 : u32; + v171 = arith.mod v169, v994 : u32; + hir.assertz v171 #[code = 250]; + v172 = hir.int_to_ptr v169 : ptr; + hir.store v172, v154; + v174 = arith.constant 20 : u32; + v173 = hir.bitcast v101 : u32; + v175 = arith.add v173, v174 : u32 #[overflow = checked]; + v993 = arith.constant 4 : u32; + v177 = arith.mod v175, v993 : u32; + hir.assertz v177 #[code = 250]; + v178 = hir.int_to_ptr v175 : ptr; + hir.store v178, v134; + v991 = arith.constant 0 : i32; + v992 = arith.constant 0 : i32; + v180 = arith.eq v124, v992 : i1; + v181 = arith.zext v180 : u32; + v182 = hir.bitcast v181 : i32; + v184 = arith.neq v182, v991 : i1; + v933 = scf.if v184 : u32 { + ^block127: + v990 = arith.constant 0 : u32; + scf.yield v990; + } else { + ^block32: + v185 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/GOT.data.internal.__memory_base : ptr + v186 = hir.bitcast v185 : ptr; + v187 = hir.load v186 : i32; + v189 = arith.constant 12 : u32; + v188 = hir.bitcast v154 : u32; + v190 = arith.add v188, v189 : u32 #[overflow = checked]; + v989 = arith.constant 4 : u32; + v192 = arith.mod v190, v989 : u32; + hir.assertz v192 #[code = 250]; + v193 = hir.int_to_ptr v190 : ptr; + v194 = hir.load v193 : felt; + v196 = arith.constant 8 : u32; + v195 = hir.bitcast v154 : u32; + v197 = arith.add v195, v196 : u32 #[overflow = checked]; + v988 = arith.constant 4 : u32; + v199 = arith.mod v197, v988 : u32; + hir.assertz v199 #[code = 250]; + v200 = hir.int_to_ptr v197 : ptr; + v201 = hir.load v200 : felt; + v987 = arith.constant 4 : u32; + v202 = hir.bitcast v154 : u32; + v204 = arith.add v202, v987 : u32 #[overflow = checked]; + v986 = arith.constant 4 : u32; + v206 = arith.mod v204, v986 : u32; + hir.assertz v206 #[code = 250]; + v207 = hir.int_to_ptr v204 : ptr; + v208 = hir.load v207 : felt; + v209 = hir.bitcast v154 : u32; + v985 = arith.constant 4 : u32; + v211 = arith.mod v209, v985 : u32; + hir.assertz v211 #[code = 250]; + v212 = hir.int_to_ptr v209 : ptr; + v213 = hir.load v212 : felt; + v220 = arith.constant 1048656 : i32; + v221 = arith.add v187, v220 : i32 #[overflow = wrapping]; + v983 = arith.constant 8 : i32; + v984 = arith.constant 4 : i32; + v216 = arith.constant 20 : i32; + v217 = arith.add v101, v216 : i32 #[overflow = wrapping]; + v214 = arith.constant 8 : i32; + v215 = arith.add v101, v214 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/ as core::ops::index::Index>::index(v215, v217, v984, v983, v221) + v982 = arith.constant 12 : u32; + v222 = hir.bitcast v101 : u32; + v224 = arith.add v222, v982 : u32 #[overflow = checked]; + v981 = arith.constant 4 : u32; + v226 = arith.mod v224, v981 : u32; + hir.assertz v226 #[code = 250]; + v227 = hir.int_to_ptr v224 : ptr; + v228 = hir.load v227 : i32; + v979 = arith.constant 0 : i32; + v980 = arith.constant 4 : i32; + v230 = arith.neq v228, v980 : i1; + v231 = arith.zext v230 : u32; + v232 = hir.bitcast v231 : i32; + v234 = arith.neq v232, v979 : i1; + v935 = scf.if v234 : u32 { + ^block126: + v978 = arith.constant 0 : u32; + scf.yield v978; + } else { + ^block33: + v977 = arith.constant 8 : u32; + v235 = hir.bitcast v101 : u32; + v237 = arith.add v235, v977 : u32 #[overflow = checked]; + v976 = arith.constant 4 : u32; + v239 = arith.mod v237, v976 : u32; + hir.assertz v239 #[code = 250]; + v240 = hir.int_to_ptr v237 : ptr; + v241 = hir.load v240 : i32; + v242 = hir.bitcast v241 : u32; + v975 = arith.constant 4 : u32; + v244 = arith.mod v242, v975 : u32; + hir.assertz v244 #[code = 250]; + v245 = hir.int_to_ptr v242 : ptr; + v246 = hir.load v245 : i64; + v974 = arith.constant 8 : i32; + v252 = arith.add v241, v974 : i32 #[overflow = wrapping]; + v253 = hir.bitcast v252 : u32; + v973 = arith.constant 4 : u32; + v255 = arith.mod v253, v973 : u32; + hir.assertz v255 #[code = 250]; + v256 = hir.int_to_ptr v253 : ptr; + v257 = hir.load v256 : i64; + v972 = arith.constant 8 : i32; + v247 = arith.constant 32 : i32; + v248 = arith.add v101, v247 : i32 #[overflow = wrapping]; + v250 = arith.add v248, v972 : i32 #[overflow = wrapping]; + v258 = hir.bitcast v250 : u32; + v971 = arith.constant 8 : u32; + v260 = arith.mod v258, v971 : u32; + hir.assertz v260 #[code = 250]; + v261 = hir.int_to_ptr v258 : ptr; + hir.store v261, v257; + v263 = arith.constant 32 : u32; + v262 = hir.bitcast v101 : u32; + v264 = arith.add v262, v263 : u32 #[overflow = checked]; + v970 = arith.constant 8 : u32; + v266 = arith.mod v264, v970 : u32; + hir.assertz v266 #[code = 250]; + v267 = hir.int_to_ptr v264 : ptr; + hir.store v267, v246; + v268 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/GOT.data.internal.__memory_base : ptr + v269 = hir.bitcast v268 : ptr; + v270 = hir.load v269 : i32; + v968 = arith.constant 32 : i32; + v274 = arith.add v101, v968 : i32 #[overflow = wrapping]; + v969 = arith.constant 64 : i32; + v272 = arith.add v101, v969 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/>::from(v272, v274) + v967 = arith.constant 64 : i32; + v276 = arith.add v101, v967 : i32 #[overflow = wrapping]; + v277 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/miden_base_sys::bindings::tx::create_note(v213, v208, v201, v194, v276) : felt + v282 = arith.constant 1048672 : i32; + v283 = arith.add v270, v282 : i32 #[overflow = wrapping]; + v281 = arith.constant 12 : i32; + v965 = arith.constant 8 : i32; + v966 = arith.constant 20 : i32; + v279 = arith.add v101, v966 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/ as core::ops::index::Index>::index(v101, v279, v965, v281, v283) + v964 = arith.constant 4 : u32; + v284 = hir.bitcast v101 : u32; + v286 = arith.add v284, v964 : u32 #[overflow = checked]; + v963 = arith.constant 4 : u32; + v288 = arith.mod v286, v963 : u32; + hir.assertz v288 #[code = 250]; + v289 = hir.int_to_ptr v286 : ptr; + v290 = hir.load v289 : i32; + v961 = arith.constant 0 : i32; + v962 = arith.constant 4 : i32; + v292 = arith.neq v290, v962 : i1; + v293 = arith.zext v292 : u32; + v294 = hir.bitcast v293 : i32; + v296 = arith.neq v294, v961 : i1; + scf.if v296{ + ^block125: + scf.yield ; + } else { + ^block34: + v297 = hir.bitcast v101 : u32; + v960 = arith.constant 4 : u32; + v299 = arith.mod v297, v960 : u32; + hir.assertz v299 #[code = 250]; + v300 = hir.int_to_ptr v297 : ptr; + v301 = hir.load v300 : i32; + v302 = hir.bitcast v301 : u32; + v959 = arith.constant 4 : u32; + v304 = arith.mod v302, v959 : u32; + hir.assertz v304 #[code = 250]; + v305 = hir.int_to_ptr v302 : ptr; + v306 = hir.load v305 : i64; + v958 = arith.constant 8 : i32; + v312 = arith.add v301, v958 : i32 #[overflow = wrapping]; + v313 = hir.bitcast v312 : u32; + v957 = arith.constant 4 : u32; + v315 = arith.mod v313, v957 : u32; + hir.assertz v315 #[code = 250]; + v316 = hir.int_to_ptr v313 : ptr; + v317 = hir.load v316 : i64; + v956 = arith.constant 8 : i32; + v307 = arith.constant 48 : i32; + v308 = arith.add v101, v307 : i32 #[overflow = wrapping]; + v310 = arith.add v308, v956 : i32 #[overflow = wrapping]; + v318 = hir.bitcast v310 : u32; + v955 = arith.constant 8 : u32; + v320 = arith.mod v318, v955 : u32; + hir.assertz v320 #[code = 250]; + v321 = hir.int_to_ptr v318 : ptr; + hir.store v321, v317; + v323 = arith.constant 48 : u32; + v322 = hir.bitcast v101 : u32; + v324 = arith.add v322, v323 : u32 #[overflow = checked]; + v954 = arith.constant 8 : u32; + v326 = arith.mod v324, v954 : u32; + hir.assertz v326 #[code = 250]; + v327 = hir.int_to_ptr v324 : ptr; + hir.store v327, v306; + v952 = arith.constant 48 : i32; + v331 = arith.add v101, v952 : i32 #[overflow = wrapping]; + v953 = arith.constant 64 : i32; + v329 = arith.add v101, v953 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/>::from(v329, v331) + v951 = arith.constant 64 : u32; + v332 = hir.bitcast v101 : u32; + v334 = arith.add v332, v951 : u32 #[overflow = checked]; + v950 = arith.constant 4 : u32; + v336 = arith.mod v334, v950 : u32; + hir.assertz v336 #[code = 250]; + v337 = hir.int_to_ptr v334 : ptr; + v338 = hir.load v337 : felt; + v949 = arith.constant 68 : u32; + v339 = hir.bitcast v101 : u32; + v341 = arith.add v339, v949 : u32 #[overflow = checked]; + v948 = arith.constant 4 : u32; + v343 = arith.mod v341, v948 : u32; + hir.assertz v343 #[code = 250]; + v344 = hir.int_to_ptr v341 : ptr; + v345 = hir.load v344 : felt; + v947 = arith.constant 72 : u32; + v346 = hir.bitcast v101 : u32; + v348 = arith.add v346, v947 : u32 #[overflow = checked]; + v946 = arith.constant 4 : u32; + v350 = arith.mod v348, v946 : u32; + hir.assertz v350 #[code = 250]; + v351 = hir.int_to_ptr v348 : ptr; + v352 = hir.load v351 : felt; + v354 = arith.constant 76 : u32; + v353 = hir.bitcast v101 : u32; + v355 = arith.add v353, v354 : u32 #[overflow = checked]; + v945 = arith.constant 4 : u32; + v357 = arith.mod v355, v945 : u32; + hir.assertz v357 #[code = 250]; + v358 = hir.int_to_ptr v355 : ptr; + v359 = hir.load v358 : felt; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/basic_wallet_tx_script::bindings::miden::basic_wallet::basic_wallet::move_asset_to_note::wit_import9(v338, v345, v352, v359, v277) + v943 = arith.constant 4 : i32; + v944 = arith.constant 20 : i32; + v361 = arith.add v101, v944 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/alloc::raw_vec::RawVecInner::deallocate(v361, v943, v943) + v942 = arith.constant 80 : i32; + v365 = arith.add v101, v942 : i32 #[overflow = wrapping]; + v366 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v367 = hir.bitcast v366 : ptr; + hir.store v367, v365; + scf.yield ; + }; + v929 = arith.constant 1 : u32; + v941 = arith.constant 0 : u32; + v939 = cf.select v296, v941, v929 : u32; + scf.yield v939; + }; + scf.yield v935; + }; + scf.yield v933; + }; + v940 = arith.constant 0 : u32; + v938 = arith.eq v931, v940 : i1; + cf.cond_br v938 ^block29, ^block129; + ^block29: + ub.unreachable ; + ^block129: + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block35: + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block37: + v381 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/GOT.data.internal.__memory_base : ptr + v382 = hir.bitcast v381 : ptr; + v383 = hir.load v382 : i32; + v384 = arith.constant 1048692 : i32; + v385 = arith.add v383, v384 : i32 #[overflow = wrapping]; + v386 = hir.bitcast v385 : u32; + v387 = hir.int_to_ptr v386 : ptr; + v388 = hir.load v387 : u8; + v380 = arith.constant 0 : i32; + v389 = arith.zext v388 : u32; + v390 = hir.bitcast v389 : i32; + v392 = arith.neq v390, v380 : i1; + scf.if v392{ + ^block39: + scf.yield ; + } else { + ^block40: + v393 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/GOT.data.internal.__memory_base : ptr + v394 = hir.bitcast v393 : ptr; + v395 = hir.load v394 : i32; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__wasm_call_ctors() + v1004 = arith.constant 1 : u8; + v1006 = arith.constant 1048692 : i32; + v397 = arith.add v395, v1006 : i32 #[overflow = wrapping]; + v401 = hir.bitcast v397 : u32; + v402 = hir.int_to_ptr v401 : ptr; + hir.store v402, v1004; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @::alloc(v403: i32, v404: i32, v405: i32) -> i32 { + ^block41(v403: i32, v404: i32, v405: i32): + v408 = arith.constant 16 : i32; + v407 = arith.constant 0 : i32; + v1008 = arith.constant 16 : u32; + v410 = hir.bitcast v404 : u32; + v412 = arith.gt v410, v1008 : i1; + v413 = arith.zext v412 : u32; + v414 = hir.bitcast v413 : i32; + v416 = arith.neq v414, v407 : i1; + v417 = cf.select v416, v404, v408 : i32; + v1048 = arith.constant 0 : i32; + v418 = arith.constant -1 : i32; + v419 = arith.add v417, v418 : i32 #[overflow = wrapping]; + v420 = arith.band v417, v419 : i32; + v422 = arith.neq v420, v1048 : i1; + v1017, v1018 = scf.if v422 : i32, u32 { + ^block137: + v1009 = arith.constant 0 : u32; + v1013 = ub.poison i32 : i32; + scf.yield v1013, v1009; + } else { + ^block44: + v424 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/core::ptr::alignment::Alignment::max(v404, v417) : i32 + v1047 = arith.constant 0 : i32; + v423 = arith.constant -2147483648 : i32; + v425 = arith.sub v423, v424 : i32 #[overflow = wrapping]; + v427 = hir.bitcast v425 : u32; + v426 = hir.bitcast v405 : u32; + v428 = arith.gt v426, v427 : i1; + v429 = arith.zext v428 : u32; + v430 = hir.bitcast v429 : i32; + v432 = arith.neq v430, v1047 : i1; + v1032 = scf.if v432 : i32 { + ^block136: + v1046 = ub.poison i32 : i32; + scf.yield v1046; + } else { + ^block45: + v1044 = arith.constant 0 : i32; + v438 = arith.sub v1044, v424 : i32 #[overflow = wrapping]; + v1045 = arith.constant -1 : i32; + v434 = arith.add v405, v424 : i32 #[overflow = wrapping]; + v436 = arith.add v434, v1045 : i32 #[overflow = wrapping]; + v439 = arith.band v436, v438 : i32; + v440 = hir.bitcast v403 : u32; + v441 = arith.constant 4 : u32; + v442 = arith.mod v440, v441 : u32; + hir.assertz v442 #[code = 250]; + v443 = hir.int_to_ptr v440 : ptr; + v444 = hir.load v443 : i32; + v1043 = arith.constant 0 : i32; + v446 = arith.neq v444, v1043 : i1; + scf.if v446{ + ^block135: + scf.yield ; + } else { + ^block47: + v447 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/intrinsics::mem::heap_base() : i32 + v448 = hir.mem_size : u32; + v454 = hir.bitcast v403 : u32; + v1042 = arith.constant 4 : u32; + v456 = arith.mod v454, v1042 : u32; + hir.assertz v456 #[code = 250]; + v1041 = arith.constant 16 : u32; + v449 = hir.bitcast v448 : i32; + v452 = arith.shl v449, v1041 : i32; + v453 = arith.add v447, v452 : i32 #[overflow = wrapping]; + v457 = hir.int_to_ptr v454 : ptr; + hir.store v457, v453; + scf.yield ; + }; + v460 = hir.bitcast v403 : u32; + v1040 = arith.constant 4 : u32; + v462 = arith.mod v460, v1040 : u32; + hir.assertz v462 #[code = 250]; + v463 = hir.int_to_ptr v460 : ptr; + v464 = hir.load v463 : i32; + v1038 = arith.constant 0 : i32; + v1039 = arith.constant -1 : i32; + v466 = arith.bxor v464, v1039 : i32; + v468 = hir.bitcast v466 : u32; + v467 = hir.bitcast v439 : u32; + v469 = arith.gt v467, v468 : i1; + v470 = arith.zext v469 : u32; + v471 = hir.bitcast v470 : i32; + v473 = arith.neq v471, v1038 : i1; + v1031 = scf.if v473 : i32 { + ^block48: + v1037 = arith.constant 0 : i32; + scf.yield v1037; + } else { + ^block49: + v475 = hir.bitcast v403 : u32; + v1036 = arith.constant 4 : u32; + v477 = arith.mod v475, v1036 : u32; + hir.assertz v477 #[code = 250]; + v474 = arith.add v464, v439 : i32 #[overflow = wrapping]; + v478 = hir.int_to_ptr v475 : ptr; + hir.store v478, v474; + v480 = arith.add v464, v424 : i32 #[overflow = wrapping]; + scf.yield v480; + }; + scf.yield v1031; + }; + v1014 = arith.constant 1 : u32; + v1035 = arith.constant 0 : u32; + v1033 = cf.select v432, v1035, v1014 : u32; + scf.yield v1032, v1033; + }; + v1034 = arith.constant 0 : u32; + v1030 = arith.eq v1018, v1034 : i1; + cf.cond_br v1030 ^block43, ^block139(v1017); + ^block43: + ub.unreachable ; + ^block139(v1010: i32): + builtin.ret v1010; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block50: + v483 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v483; + }; + + private builtin.function @miden_base_sys::bindings::tx::create_note(v485: felt, v486: felt, v487: felt, v488: felt, v489: i32) -> felt { + ^block54(v485: felt, v486: felt, v487: felt, v488: felt, v489: i32): + v492 = arith.constant 12 : u32; + v491 = hir.bitcast v489 : u32; + v493 = arith.add v491, v492 : u32 #[overflow = checked]; + v494 = arith.constant 4 : u32; + v495 = arith.mod v493, v494 : u32; + hir.assertz v495 #[code = 250]; + v496 = hir.int_to_ptr v493 : ptr; + v497 = hir.load v496 : felt; + v499 = arith.constant 8 : u32; + v498 = hir.bitcast v489 : u32; + v500 = arith.add v498, v499 : u32 #[overflow = checked]; + v1052 = arith.constant 4 : u32; + v502 = arith.mod v500, v1052 : u32; + hir.assertz v502 #[code = 250]; + v503 = hir.int_to_ptr v500 : ptr; + v504 = hir.load v503 : felt; + v1051 = arith.constant 4 : u32; + v505 = hir.bitcast v489 : u32; + v507 = arith.add v505, v1051 : u32 #[overflow = checked]; + v1050 = arith.constant 4 : u32; + v509 = arith.mod v507, v1050 : u32; + hir.assertz v509 #[code = 250]; + v510 = hir.int_to_ptr v507 : ptr; + v511 = hir.load v510 : felt; + v512 = hir.bitcast v489 : u32; + v1049 = arith.constant 4 : u32; + v514 = arith.mod v512, v1049 : u32; + hir.assertz v514 #[code = 250]; + v515 = hir.int_to_ptr v512 : ptr; + v516 = hir.load v515 : felt; + v517 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/miden::tx::create_note(v485, v486, v487, v488, v497, v504, v511, v516) : felt + builtin.ret v517; + }; + + private builtin.function @>::from(v518: i32, v519: i32) { + ^block56(v518: i32, v519: i32): + v521 = arith.constant 8 : u32; + v520 = hir.bitcast v519 : u32; + v522 = arith.add v520, v521 : u32 #[overflow = checked]; + v523 = arith.constant 4 : u32; + v524 = arith.mod v522, v523 : u32; + hir.assertz v524 #[code = 250]; + v525 = hir.int_to_ptr v522 : ptr; + v526 = hir.load v525 : i64; + v1056 = arith.constant 8 : u32; + v527 = hir.bitcast v518 : u32; + v529 = arith.add v527, v1056 : u32 #[overflow = checked]; + v1055 = arith.constant 8 : u32; + v531 = arith.mod v529, v1055 : u32; + hir.assertz v531 #[code = 250]; + v532 = hir.int_to_ptr v529 : ptr; + hir.store v532, v526; + v533 = hir.bitcast v519 : u32; + v1054 = arith.constant 4 : u32; + v535 = arith.mod v533, v1054 : u32; + hir.assertz v535 #[code = 250]; + v536 = hir.int_to_ptr v533 : ptr; + v537 = hir.load v536 : i64; + v538 = hir.bitcast v518 : u32; + v1053 = arith.constant 8 : u32; + v540 = arith.mod v538, v1053 : u32; + hir.assertz v540 #[code = 250]; + v541 = hir.int_to_ptr v538 : ptr; + hir.store v541, v537; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u64_unchecked(v542: i64) -> felt { + ^block58(v542: i64): + v543 = hir.cast v542 : felt; + builtin.ret v543; + }; + + private builtin.function @intrinsics::felt::from_u32(v545: i32) -> felt { + ^block60(v545: i32): + v546 = hir.bitcast v545 : felt; + builtin.ret v546; + }; + + private builtin.function @intrinsics::felt::as_u64(v548: felt) -> i64 { + ^block62(v548: felt): + v549 = hir.cast v548 : i64; + builtin.ret v549; + }; + + private builtin.function @intrinsics::felt::assert_eq(v551: felt, v552: felt) { + ^block64(v551: felt, v552: felt): + hir.assert_eq v551, v552; + builtin.ret ; + }; + + private builtin.function @intrinsics::advice::adv_push_mapvaln(v553: felt, v554: felt, v555: felt, v556: felt) -> felt { + ^block66(v553: felt, v554: felt, v555: felt, v556: felt): + v557 = hir.exec @intrinsics/advice/adv_push_mapvaln(v553, v554, v555, v556) : felt + builtin.ret v557; + }; + + private builtin.function @std::mem::pipe_preimage_to_memory(v559: felt, v560: i32, v561: felt, v562: felt, v563: felt, v564: felt) -> i32 { + ^block69(v559: felt, v560: i32, v561: felt, v562: felt, v563: felt, v564: felt): + v565 = hir.exec @std/mem/pipe_preimage_to_memory(v559, v560, v561, v562, v563, v564) : i32 + builtin.ret v565; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::deallocate(v567: i32, v568: i32, v569: i32) { + ^block73(v567: i32, v568: i32, v569: i32): + v571 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v572 = hir.bitcast v571 : ptr; + v573 = hir.load v572 : i32; + v574 = arith.constant 16 : i32; + v575 = arith.sub v573, v574 : i32 #[overflow = wrapping]; + v576 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v577 = hir.bitcast v576 : ptr; + hir.store v577, v575; + v578 = arith.constant 4 : i32; + v579 = arith.add v575, v578 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/alloc::raw_vec::RawVecInner::current_memory(v579, v567, v568, v569) + v581 = arith.constant 8 : u32; + v580 = hir.bitcast v575 : u32; + v582 = arith.add v580, v581 : u32 #[overflow = checked]; + v583 = arith.constant 4 : u32; + v584 = arith.mod v582, v583 : u32; + hir.assertz v584 #[code = 250]; + v585 = hir.int_to_ptr v582 : ptr; + v586 = hir.load v585 : i32; + v1063 = arith.constant 0 : i32; + v570 = arith.constant 0 : i32; + v588 = arith.eq v586, v570 : i1; + v589 = arith.zext v588 : u32; + v590 = hir.bitcast v589 : i32; + v592 = arith.neq v590, v1063 : i1; + scf.if v592{ + ^block143: + scf.yield ; + } else { + ^block76: + v1062 = arith.constant 4 : u32; + v593 = hir.bitcast v575 : u32; + v595 = arith.add v593, v1062 : u32 #[overflow = checked]; + v1061 = arith.constant 4 : u32; + v597 = arith.mod v595, v1061 : u32; + hir.assertz v597 #[code = 250]; + v598 = hir.int_to_ptr v595 : ptr; + v599 = hir.load v598 : i32; + v601 = arith.constant 12 : u32; + v600 = hir.bitcast v575 : u32; + v602 = arith.add v600, v601 : u32 #[overflow = checked]; + v1060 = arith.constant 4 : u32; + v604 = arith.mod v602, v1060 : u32; + hir.assertz v604 #[code = 250]; + v605 = hir.int_to_ptr v602 : ptr; + v606 = hir.load v605 : i32; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/::deallocate(v599, v586, v606) + scf.yield ; + }; + v1059 = arith.constant 16 : i32; + v609 = arith.add v575, v1059 : i32 #[overflow = wrapping]; + v610 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v611 = hir.bitcast v610 : ptr; + hir.store v611, v609; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::try_allocate_in(v612: i32, v613: i32, v614: i32, v615: i32, v616: i32) { + ^block77(v612: i32, v613: i32, v614: i32, v615: i32, v616: i32): + v619 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v620 = hir.bitcast v619 : ptr; + v621 = hir.load v620 : i32; + v622 = arith.constant 16 : i32; + v623 = arith.sub v621, v622 : i32 #[overflow = wrapping]; + v624 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v625 = hir.bitcast v624 : ptr; + hir.store v625, v623; + v635 = hir.bitcast v613 : u32; + v636 = arith.zext v635 : u64; + v637 = hir.bitcast v636 : i64; + v617 = arith.constant 0 : i32; + v630 = arith.sub v617, v615 : i32 #[overflow = wrapping]; + v627 = arith.constant -1 : i32; + v626 = arith.add v615, v616 : i32 #[overflow = wrapping]; + v628 = arith.add v626, v627 : i32 #[overflow = wrapping]; + v631 = arith.band v628, v630 : i32; + v632 = hir.bitcast v631 : u32; + v633 = arith.zext v632 : u64; + v634 = hir.bitcast v633 : i64; + v638 = arith.mul v634, v637 : i64 #[overflow = wrapping]; + v1167 = arith.constant 0 : i32; + v639 = arith.constant 32 : i64; + v641 = hir.cast v639 : u32; + v640 = hir.bitcast v638 : u64; + v642 = arith.shr v640, v641 : u64; + v643 = hir.bitcast v642 : i64; + v644 = arith.trunc v643 : i32; + v646 = arith.neq v644, v1167 : i1; + v1079, v1080, v1081, v1082, v1083, v1084 = scf.if v646 : i32, i32, i32, i32, i32, u32 { + ^block145: + v1064 = arith.constant 0 : u32; + v1071 = ub.poison i32 : i32; + scf.yield v612, v623, v1071, v1071, v1071, v1064; + } else { + ^block82: + v647 = arith.trunc v638 : i32; + v1166 = arith.constant 0 : i32; + v648 = arith.constant -2147483648 : i32; + v649 = arith.sub v648, v615 : i32 #[overflow = wrapping]; + v651 = hir.bitcast v649 : u32; + v650 = hir.bitcast v647 : u32; + v652 = arith.lte v650, v651 : i1; + v653 = arith.zext v652 : u32; + v654 = hir.bitcast v653 : i32; + v656 = arith.neq v654, v1166 : i1; + v1127 = scf.if v656 : i32 { + ^block80: + v1165 = arith.constant 0 : i32; + v667 = arith.neq v647, v1165 : i1; + v1126 = scf.if v667 : i32 { + ^block84: + v1164 = arith.constant 0 : i32; + v683 = arith.neq v614, v1164 : i1; + v1125 = scf.if v683 : i32 { + ^block87: + v665 = arith.constant 1 : i32; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/alloc::alloc::Global::alloc_impl(v623, v615, v647, v665) + v694 = hir.bitcast v623 : u32; + v739 = arith.constant 4 : u32; + v696 = arith.mod v694, v739 : u32; + hir.assertz v696 #[code = 250]; + v697 = hir.int_to_ptr v694 : ptr; + v698 = hir.load v697 : i32; + scf.yield v698; + } else { + ^block88: + v684 = arith.constant 8 : i32; + v685 = arith.add v623, v684 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/::allocate(v685, v615, v647) + v669 = arith.constant 8 : u32; + v686 = hir.bitcast v623 : u32; + v688 = arith.add v686, v669 : u32 #[overflow = checked]; + v1163 = arith.constant 4 : u32; + v690 = arith.mod v688, v1163 : u32; + hir.assertz v690 #[code = 250]; + v691 = hir.int_to_ptr v688 : ptr; + v692 = hir.load v691 : i32; + scf.yield v692; + }; + v1161 = arith.constant 0 : i32; + v1162 = arith.constant 0 : i32; + v701 = arith.eq v1125, v1162 : i1; + v702 = arith.zext v701 : u32; + v703 = hir.bitcast v702 : i32; + v705 = arith.neq v703, v1161 : i1; + scf.if v705{ + ^block89: + v1160 = arith.constant 8 : u32; + v722 = hir.bitcast v612 : u32; + v724 = arith.add v722, v1160 : u32 #[overflow = checked]; + v1159 = arith.constant 4 : u32; + v726 = arith.mod v724, v1159 : u32; + hir.assertz v726 #[code = 250]; + v727 = hir.int_to_ptr v724 : ptr; + hir.store v727, v647; + v1158 = arith.constant 4 : u32; + v729 = hir.bitcast v612 : u32; + v731 = arith.add v729, v1158 : u32 #[overflow = checked]; + v1157 = arith.constant 4 : u32; + v733 = arith.mod v731, v1157 : u32; + hir.assertz v733 #[code = 250]; + v734 = hir.int_to_ptr v731 : ptr; + hir.store v734, v615; + scf.yield ; + } else { + ^block90: + v1156 = arith.constant 8 : u32; + v707 = hir.bitcast v612 : u32; + v709 = arith.add v707, v1156 : u32 #[overflow = checked]; + v1155 = arith.constant 4 : u32; + v711 = arith.mod v709, v1155 : u32; + hir.assertz v711 #[code = 250]; + v712 = hir.int_to_ptr v709 : ptr; + hir.store v712, v1125; + v1154 = arith.constant 4 : u32; + v714 = hir.bitcast v612 : u32; + v716 = arith.add v714, v1154 : u32 #[overflow = checked]; + v1153 = arith.constant 4 : u32; + v718 = arith.mod v716, v1153 : u32; + hir.assertz v718 #[code = 250]; + v719 = hir.int_to_ptr v716 : ptr; + hir.store v719, v613; + scf.yield ; + }; + v1151 = arith.constant 0 : i32; + v1152 = arith.constant 1 : i32; + v1124 = cf.select v705, v1152, v1151 : i32; + scf.yield v1124; + } else { + ^block85: + v1150 = arith.constant 8 : u32; + v668 = hir.bitcast v612 : u32; + v670 = arith.add v668, v1150 : u32 #[overflow = checked]; + v1149 = arith.constant 4 : u32; + v672 = arith.mod v670, v1149 : u32; + hir.assertz v672 #[code = 250]; + v673 = hir.int_to_ptr v670 : ptr; + hir.store v673, v615; + v1148 = arith.constant 4 : u32; + v676 = hir.bitcast v612 : u32; + v678 = arith.add v676, v1148 : u32 #[overflow = checked]; + v1147 = arith.constant 4 : u32; + v680 = arith.mod v678, v1147 : u32; + hir.assertz v680 #[code = 250]; + v1146 = arith.constant 0 : i32; + v681 = hir.int_to_ptr v678 : ptr; + hir.store v681, v1146; + v1145 = arith.constant 0 : i32; + scf.yield v1145; + }; + scf.yield v1126; + } else { + ^block83: + v1144 = ub.poison i32 : i32; + scf.yield v1144; + }; + v1139 = arith.constant 0 : u32; + v1072 = arith.constant 1 : u32; + v1132 = cf.select v656, v1072, v1139 : u32; + v1140 = ub.poison i32 : i32; + v1131 = cf.select v656, v623, v1140 : i32; + v1141 = ub.poison i32 : i32; + v1130 = cf.select v656, v612, v1141 : i32; + v1142 = ub.poison i32 : i32; + v1129 = cf.select v656, v1142, v623 : i32; + v1143 = ub.poison i32 : i32; + v1128 = cf.select v656, v1143, v612 : i32; + scf.yield v1128, v1129, v1130, v1127, v1131, v1132; + }; + v1085, v1086, v1087 = scf.index_switch v1084 : i32, i32, i32 + case 0 { + ^block81: + v1138 = arith.constant 4 : u32; + v659 = hir.bitcast v1079 : u32; + v661 = arith.add v659, v1138 : u32 #[overflow = checked]; + v1137 = arith.constant 4 : u32; + v663 = arith.mod v661, v1137 : u32; + hir.assertz v663 #[code = 250]; + v1136 = arith.constant 0 : i32; + v664 = hir.int_to_ptr v661 : ptr; + hir.store v664, v1136; + v1135 = arith.constant 1 : i32; + scf.yield v1079, v1135, v1080; + } + default { + ^block149: + scf.yield v1081, v1082, v1083; + }; + v738 = hir.bitcast v1085 : u32; + v1134 = arith.constant 4 : u32; + v740 = arith.mod v738, v1134 : u32; + hir.assertz v740 #[code = 250]; + v741 = hir.int_to_ptr v738 : ptr; + hir.store v741, v1086; + v1133 = arith.constant 16 : i32; + v746 = arith.add v1087, v1133 : i32 #[overflow = wrapping]; + v747 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v748 = hir.bitcast v747 : ptr; + hir.store v748, v746; + builtin.ret ; + }; + + private builtin.function @::allocate(v749: i32, v750: i32, v751: i32) { + ^block91(v749: i32, v750: i32, v751: i32): + v753 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v754 = hir.bitcast v753 : ptr; + v755 = hir.load v754 : i32; + v756 = arith.constant 16 : i32; + v757 = arith.sub v755, v756 : i32 #[overflow = wrapping]; + v758 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v759 = hir.bitcast v758 : ptr; + hir.store v759, v757; + v752 = arith.constant 0 : i32; + v760 = arith.constant 8 : i32; + v761 = arith.add v757, v760 : i32 #[overflow = wrapping]; + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/alloc::alloc::Global::alloc_impl(v761, v750, v751, v752) + v764 = arith.constant 12 : u32; + v763 = hir.bitcast v757 : u32; + v765 = arith.add v763, v764 : u32 #[overflow = checked]; + v766 = arith.constant 4 : u32; + v767 = arith.mod v765, v766 : u32; + hir.assertz v767 #[code = 250]; + v768 = hir.int_to_ptr v765 : ptr; + v769 = hir.load v768 : i32; + v771 = arith.constant 8 : u32; + v770 = hir.bitcast v757 : u32; + v772 = arith.add v770, v771 : u32 #[overflow = checked]; + v1172 = arith.constant 4 : u32; + v774 = arith.mod v772, v1172 : u32; + hir.assertz v774 #[code = 250]; + v775 = hir.int_to_ptr v772 : ptr; + v776 = hir.load v775 : i32; + v777 = hir.bitcast v749 : u32; + v1171 = arith.constant 4 : u32; + v779 = arith.mod v777, v1171 : u32; + hir.assertz v779 #[code = 250]; + v780 = hir.int_to_ptr v777 : ptr; + hir.store v780, v776; + v1170 = arith.constant 4 : u32; + v781 = hir.bitcast v749 : u32; + v783 = arith.add v781, v1170 : u32 #[overflow = checked]; + v1169 = arith.constant 4 : u32; + v785 = arith.mod v783, v1169 : u32; + hir.assertz v785 #[code = 250]; + v786 = hir.int_to_ptr v783 : ptr; + hir.store v786, v769; + v1168 = arith.constant 16 : i32; + v788 = arith.add v757, v1168 : i32 #[overflow = wrapping]; + v789 = builtin.global_symbol @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__stack_pointer : ptr + v790 = hir.bitcast v789 : ptr; + hir.store v790, v788; + builtin.ret ; + }; + + private builtin.function @alloc::alloc::Global::alloc_impl(v791: i32, v792: i32, v793: i32, v794: i32) { + ^block93(v791: i32, v792: i32, v793: i32, v794: i32): + v1188 = arith.constant 0 : i32; + v795 = arith.constant 0 : i32; + v796 = arith.eq v793, v795 : i1; + v797 = arith.zext v796 : u32; + v798 = hir.bitcast v797 : i32; + v800 = arith.neq v798, v1188 : i1; + v1184 = scf.if v800 : i32 { + ^block152: + scf.yield v792; + } else { + ^block96: + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v1187 = arith.constant 0 : i32; + v802 = arith.neq v794, v1187 : i1; + v1183 = scf.if v802 : i32 { + ^block97: + v804 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__rustc::__rust_alloc_zeroed(v793, v792) : i32 + scf.yield v804; + } else { + ^block98: + v803 = hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__rustc::__rust_alloc(v793, v792) : i32 + scf.yield v803; + }; + scf.yield v1183; + }; + v808 = arith.constant 4 : u32; + v807 = hir.bitcast v791 : u32; + v809 = arith.add v807, v808 : u32 #[overflow = checked]; + v1186 = arith.constant 4 : u32; + v811 = arith.mod v809, v1186 : u32; + hir.assertz v811 #[code = 250]; + v812 = hir.int_to_ptr v809 : ptr; + hir.store v812, v793; + v814 = hir.bitcast v791 : u32; + v1185 = arith.constant 4 : u32; + v816 = arith.mod v814, v1185 : u32; + hir.assertz v816 #[code = 250]; + v817 = hir.int_to_ptr v814 : ptr; + hir.store v817, v1184; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::current_memory(v818: i32, v819: i32, v820: i32, v821: i32) { + ^block99(v818: i32, v819: i32, v820: i32, v821: i32): + v1214 = arith.constant 0 : i32; + v822 = arith.constant 0 : i32; + v826 = arith.eq v821, v822 : i1; + v827 = arith.zext v826 : u32; + v828 = hir.bitcast v827 : i32; + v830 = arith.neq v828, v1214 : i1; + v1201, v1202 = scf.if v830 : i32, i32 { + ^block156: + v1213 = arith.constant 0 : i32; + v824 = arith.constant 4 : i32; + scf.yield v824, v1213; + } else { + ^block102: + v831 = hir.bitcast v819 : u32; + v866 = arith.constant 4 : u32; + v833 = arith.mod v831, v866 : u32; + hir.assertz v833 #[code = 250]; + v834 = hir.int_to_ptr v831 : ptr; + v835 = hir.load v834 : i32; + v1211 = arith.constant 0 : i32; + v1212 = arith.constant 0 : i32; + v837 = arith.eq v835, v1212 : i1; + v838 = arith.zext v837 : u32; + v839 = hir.bitcast v838 : i32; + v841 = arith.neq v839, v1211 : i1; + v1199 = scf.if v841 : i32 { + ^block155: + v1210 = arith.constant 0 : i32; + scf.yield v1210; + } else { + ^block103: + v1209 = arith.constant 4 : u32; + v842 = hir.bitcast v818 : u32; + v844 = arith.add v842, v1209 : u32 #[overflow = checked]; + v1208 = arith.constant 4 : u32; + v846 = arith.mod v844, v1208 : u32; + hir.assertz v846 #[code = 250]; + v847 = hir.int_to_ptr v844 : ptr; + hir.store v847, v820; + v1207 = arith.constant 4 : u32; + v848 = hir.bitcast v819 : u32; + v850 = arith.add v848, v1207 : u32 #[overflow = checked]; + v1206 = arith.constant 4 : u32; + v852 = arith.mod v850, v1206 : u32; + hir.assertz v852 #[code = 250]; + v853 = hir.int_to_ptr v850 : ptr; + v854 = hir.load v853 : i32; + v855 = hir.bitcast v818 : u32; + v1205 = arith.constant 4 : u32; + v857 = arith.mod v855, v1205 : u32; + hir.assertz v857 #[code = 250]; + v858 = hir.int_to_ptr v855 : ptr; + hir.store v858, v854; + v859 = arith.mul v835, v821 : i32 #[overflow = wrapping]; + scf.yield v859; + }; + v860 = arith.constant 8 : i32; + v1204 = arith.constant 4 : i32; + v1200 = cf.select v841, v1204, v860 : i32; + scf.yield v1200, v1199; + }; + v863 = arith.add v818, v1201 : i32 #[overflow = wrapping]; + v865 = hir.bitcast v863 : u32; + v1203 = arith.constant 4 : u32; + v867 = arith.mod v865, v1203 : u32; + hir.assertz v867 #[code = 250]; + v868 = hir.int_to_ptr v865 : ptr; + hir.store v868, v1202; + builtin.ret ; + }; + + private builtin.function @::deallocate(v869: i32, v870: i32, v871: i32) { + ^block104(v869: i32, v870: i32, v871: i32): + v1216 = arith.constant 0 : i32; + v872 = arith.constant 0 : i32; + v873 = arith.eq v871, v872 : i1; + v874 = arith.zext v873 : u32; + v875 = hir.bitcast v874 : i32; + v877 = arith.neq v875, v1216 : i1; + scf.if v877{ + ^block106: + scf.yield ; + } else { + ^block107: + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/__rustc::__rust_dealloc(v869, v871, v870) + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::handle_error(v878: i32, v879: i32, v880: i32) { + ^block108(v878: i32, v879: i32, v880: i32): + ub.unreachable ; + }; + + private builtin.function @core::slice::::copy_from_slice::len_mismatch_fail::do_panic::runtime(v881: i32, v882: i32, v883: i32) { + ^block110(v881: i32, v882: i32, v883: i32): + ub.unreachable ; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v884: i32, v885: i32) -> i32 { + ^block112(v884: i32, v885: i32): + v892 = arith.constant 0 : i32; + v888 = hir.bitcast v885 : u32; + v887 = hir.bitcast v884 : u32; + v889 = arith.gt v887, v888 : i1; + v890 = arith.zext v889 : u32; + v891 = hir.bitcast v890 : i32; + v893 = arith.neq v891, v892 : i1; + v894 = cf.select v893, v884, v885 : i32; + builtin.ret v894; + }; + + private builtin.function @miden::tx::create_note(v895: felt, v896: felt, v897: felt, v898: felt, v899: felt, v900: felt, v901: felt, v902: felt) -> felt { + ^block114(v895: felt, v896: felt, v897: felt, v898: felt, v899: felt, v900: felt, v901: felt, v902: felt): + v903 = hir.exec @miden/tx/create_note(v895, v896, v897, v898, v899, v900, v901, v902) : felt + builtin.ret v903; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment readonly @1048576 = 0x0073722e62696c2f6372730073722e6d656d2f62696c6474732f6372732f302e372e302d7379732d62696c6474732d6e6564696d; + + builtin.segment @1048628 = 0x00000021000000280000000a0010002900000025000000250000000a0010002900000021000000970000002800100000000000010000000100000001; + }; + + public builtin.function @run(v905: felt, v906: felt, v907: felt, v908: felt) { + ^block118(v905: felt, v906: felt, v907: felt, v908: felt): + hir.exec @miden:base/transaction-script@1.0.0/basic_wallet_tx_script/miden:base/transaction-script@1.0.0#run(v905, v906, v907, v908) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/basic_wallet_tx_script.masm b/tests/integration/expected/examples/basic_wallet_tx_script.masm new file mode 100644 index 000000000..08c50f65b --- /dev/null +++ b/tests/integration/expected/examples/basic_wallet_tx_script.masm @@ -0,0 +1,2392 @@ +# mod miden:base/transaction-script@1.0.0 + +export.run + exec.::miden:base/transaction-script@1.0.0::init + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::miden:base/transaction-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[6451311627802649628,13408805836566352167,15533908529537497744,16330195584285026872] + adv.push_mapval + push.262144 + push.7 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278560 + push.0 + u32assert + mem_store.278561 +end + +# mod miden:base/transaction-script@1.0.0::basic_wallet_tx_script + +proc.basic_wallet_tx_script::bindings::miden::basic_wallet::basic_wallet::move_asset_to_note::wit_import9 + trace.240 + nop + call.::miden:basic-wallet/basic-wallet@0.1.0::move-asset-to-note + trace.252 + nop +end + +proc.__wasm_call_ctors + nop +end + +proc.core::slice::index::slice_end_index_len_fail + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::core::slice::::copy_from_slice::len_mismatch_fail::do_panic::runtime + trace.252 + nop + push.0 + assert +end + +proc. as core::ops::index::Index>::index + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + dup.1 + dup.6 + swap.1 + u32lte + neq + if.true + movup.5 + swap.1 + drop + drop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + dup.4 + u32wrapping_sub + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.2 + movup.3 + swap.1 + u32shl + movup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + movdn.3 + drop + drop + drop + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::core::slice::index::slice_end_index_len_fail + trace.252 + nop + push.0 + assert + end +end + +proc.__rustc::__rust_alloc + push.1114244 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048688 + u32wrapping_add + movup.2 + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::::alloc + trace.252 + nop +end + +proc.__rustc::__rust_dealloc + drop + drop + drop +end + +proc.__rustc::__rust_alloc_zeroed + push.1114244 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048688 + u32wrapping_add + dup.1 + swap.2 + swap.3 + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::::alloc + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + swap.1 + drop + else + push.0 + push.0 + dup.3 + eq + neq + if.true + swap.1 + drop + else + push.0 + movup.2 + dup.2 + push.0 + dup.2 + push.0 + gte + while.true + dup.1 + dup.1 + push.1 + u32overflowing_madd + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + u32wrapping_add.1 + dup.0 + dup.3 + u32gte + end + dropw + end + end +end + +proc.basic_wallet_tx_script::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/transaction-script@1.0.0#run + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.80 + u32wrapping_sub + push.1114240 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::wit_bindgen::rt::run_ctors_once + trace.252 + nop + dup.1 + dup.3 + dup.5 + dup.7 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::advice::adv_push_mapvaln + trace.252 + nop + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::felt::as_u64 + trace.252 + nop + push.3 + dup.2 + dup.2 + drop + u32and + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::felt::assert_eq + trace.252 + nop + push.2 + push.0 + dup.0 + push.2147483648 + u32and + eq.2147483648 + assertz + assertz + dup.0 + push.4294967295 + u32lte + assert + movdn.2 + movup.2 + trace.240 + nop + exec.::std::math::u64::shr + trace.252 + nop + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::felt::from_u64_unchecked + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::felt::as_u64 + trace.252 + nop + push.2 + movdn.2 + drop + swap.1 + u32shl + push.4 + push.0 + push.64 + dup.5 + u32wrapping_add + dup.3 + dup.3 + swap.4 + swap.3 + swap.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::alloc::raw_vec::RawVecInner::try_allocate_in + trace.252 + nop + push.68 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.64 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.1 + movup.2 + eq + neq + if.true + movdn.2 + drop + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.1114244 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.72 + movup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048640 + movup.2 + u32wrapping_add + swap.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::alloc::raw_vec::handle_error + trace.252 + nop + push.0 + else + push.72 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.2 + dup.1 + swap.1 + u32shr + movup.5 + swap.6 + movdn.5 + movup.3 + swap.8 + movdn.3 + movup.2 + swap.9 + movdn.2 + swap.1 + swap.7 + swap.4 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::std::mem::pipe_preimage_to_memory + trace.252 + nop + drop + push.28 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.24 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.20 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.0 + movup.4 + eq + neq + if.true + drop + drop + push.0 + else + push.1114244 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.1048656 + movup.5 + u32wrapping_add + push.8 + push.4 + push.20 + dup.8 + u32wrapping_add + push.8 + dup.9 + u32wrapping_add + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script:: as core::ops::index::Index>::index + trace.252 + nop + push.12 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.4 + movup.2 + neq + neq + if.true + dropw + drop + push.0 + else + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + movup.3 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + push.32 + dup.10 + u32wrapping_add + u32wrapping_add + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.7 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.1114244 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + dup.6 + u32wrapping_add + push.64 + dup.7 + u32wrapping_add + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::>::from + trace.252 + nop + push.64 + dup.6 + u32wrapping_add + movdn.5 + movdn.5 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::miden_base_sys::bindings::tx::create_note + trace.252 + nop + push.1048672 + movup.2 + u32wrapping_add + push.12 + push.8 + push.20 + dup.5 + u32wrapping_add + dup.5 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script:: as core::ops::index::Index>::index + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.4 + movup.2 + neq + neq + dup.0 + if.true + movdn.2 + drop + drop + else + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + movup.3 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + push.48 + dup.8 + u32wrapping_add + u32wrapping_add + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.48 + dup.5 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.48 + dup.3 + u32wrapping_add + push.64 + dup.4 + u32wrapping_add + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::>::from + trace.252 + nop + push.64 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.68 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.72 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.76 + dup.6 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.4 + swap.5 + movdn.4 + swap.1 + swap.2 + swap.1 + swap.3 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::basic_wallet_tx_script::bindings::miden::basic_wallet::basic_wallet::move_asset_to_note::wit_import9 + trace.252 + nop + push.4 + push.20 + dup.3 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + push.80 + movup.2 + u32wrapping_add + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.1 + push.0 + movup.2 + cdrop + end + end + end + push.0 + eq + if.true + push.0 + assert + else + nop + end +end + +proc.__rustc::__rust_no_alloc_shim_is_unstable_v2 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114244 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048692 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114244 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048692 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.::alloc + push.16 + push.0 + push.16 + dup.4 + swap.1 + u32gt + neq + dup.3 + swap.1 + cdrop + push.0 + push.4294967295 + dup.2 + u32wrapping_add + dup.2 + u32and + neq + if.true + dropw + push.0 + push.3735929054 + else + movup.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::core::ptr::alignment::Alignment::max + trace.252 + nop + push.0 + push.2147483648 + dup.2 + u32wrapping_sub + dup.4 + swap.1 + u32gt + neq + dup.0 + if.true + movdn.3 + drop + drop + drop + push.3735929054 + else + push.0 + dup.2 + u32wrapping_sub + push.4294967295 + movup.5 + dup.4 + u32wrapping_add + u32wrapping_add + u32and + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + neq + if.true + nop + else + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::intrinsics::mem::heap_base + trace.252 + nop + trace.240 + nop + exec.::intrinsics::mem::memory_size + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.16 + movup.2 + swap.1 + u32shl + movup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.4294967295 + dup.2 + u32xor + dup.3 + swap.1 + u32gt + neq + if.true + drop + drop + movdn.2 + drop + drop + push.0 + else + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + dup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + u32wrapping_add + end + end + push.1 + push.0 + movup.3 + cdrop + swap.1 + end + push.0 + movup.2 + eq + if.true + drop + push.0 + assert + else + nop + end +end + +proc.intrinsics::mem::heap_base + trace.240 + nop + exec.::intrinsics::mem::heap_base + trace.252 + nop +end + +proc.miden_base_sys::bindings::tx::create_note + push.12 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.6 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.7 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.7 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + swap.6 + swap.2 + swap.5 + swap.1 + swap.7 + swap.3 + swap.4 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::miden::tx::create_note + trace.252 + nop +end + +proc.>::from + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.intrinsics::felt::from_u64_unchecked + dup.1 + dup.1 + push.1 + push.4294967295 + trace.240 + nop + exec.::std::math::u64::lt + trace.252 + nop + assert + mul.4294967296 + add +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::as_u64 + u32split +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + +proc.intrinsics::advice::adv_push_mapvaln + trace.240 + nop + exec.::intrinsics::advice::adv_push_mapvaln + trace.252 + nop +end + +proc.std::mem::pipe_preimage_to_memory + trace.240 + nop + exec.::std::mem::pipe_preimage_to_memory + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::deallocate + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114240 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + u32wrapping_add + swap.1 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::alloc::raw_vec::RawVecInner::current_memory + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + else + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movdn.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::::deallocate + trace.252 + nop + end + push.16 + u32wrapping_add + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::try_allocate_in + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114240 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.2 + push.0 + push.0 + dup.7 + u32wrapping_sub + push.4294967295 + movup.9 + dup.9 + u32wrapping_add + u32wrapping_add + u32and + push.0 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + push.0 + push.32 + push.0 + dup.0 + push.2147483648 + u32and + eq.2147483648 + assertz + assertz + dup.0 + push.4294967295 + u32lte + assert + dup.3 + dup.3 + movup.2 + trace.240 + nop + exec.::std::math::u64::shr + trace.252 + nop + drop + neq + if.true + drop + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + push.3735929054 + dup.0 + dup.1 + swap.4 + swap.1 + swap.3 + swap.5 + else + drop + push.0 + push.2147483648 + dup.7 + u32wrapping_sub + dup.2 + swap.1 + u32lte + neq + dup.0 + if.true + push.0 + dup.2 + neq + if.true + push.0 + movup.6 + neq + if.true + push.1 + dup.3 + dup.7 + dup.4 + swap.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::alloc::alloc::Global::alloc_impl + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + else + push.8 + dup.3 + u32wrapping_add + dup.6 + dup.3 + swap.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::::allocate + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + end + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + movup.6 + movup.2 + drop + drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + movup.7 + movup.4 + drop + drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.0 + push.1 + movup.2 + cdrop + else + movup.2 + swap.5 + movdn.2 + swap.4 + swap.1 + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + swap.1 + swap.3 + swap.2 + swap.1 + end + else + swap.1 + drop + movup.3 + drop + movup.3 + drop + movup.3 + drop + push.3735929054 + end + push.0 + push.1 + dup.3 + cdrop + push.3735929054 + dup.3 + dup.5 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.7 + swap.1 + cdrop + push.3735929054 + dup.5 + movup.2 + swap.7 + movdn.2 + cdrop + push.3735929054 + movup.2 + swap.7 + movdn.2 + swap.1 + swap.5 + cdrop + swap.1 + swap.5 + swap.4 + swap.2 + swap.3 + swap.1 + end + movup.5 + eq.0 + if.true + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1 + swap.1 + else + drop + drop + end + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::allocate + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114240 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.8 + dup.2 + u32wrapping_add + movup.2 + swap.5 + movdn.2 + swap.1 + swap.3 + swap.4 + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::alloc::alloc::Global::alloc_impl + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114240 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::alloc::Global::alloc_impl + push.0 + push.0 + dup.4 + eq + neq + if.true + movup.3 + drop + swap.1 + else + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::__rustc::__rust_no_alloc_shim_is_unstable_v2 + trace.252 + nop + push.0 + movup.4 + neq + if.true + swap.1 + dup.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::__rustc::__rust_alloc_zeroed + trace.252 + nop + else + swap.1 + dup.2 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::__rustc::__rust_alloc + trace.252 + nop + end + end + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::current_memory + push.0 + push.0 + dup.5 + eq + neq + if.true + movdn.3 + drop + drop + drop + push.0 + push.4 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + else + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.3 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + movup.2 + swap.1 + end + push.8 + push.4 + movup.3 + cdrop + end + movup.2 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::deallocate + push.0 + push.0 + dup.4 + eq + neq + if.true + drop + drop + drop + else + movup.2 + swap.1 + trace.240 + nop + exec.::miden:base/transaction-script@1.0.0::basic_wallet_tx_script::__rustc::__rust_dealloc + trace.252 + nop + end +end + +proc.alloc::raw_vec::handle_error + drop + drop + drop + push.0 + assert +end + +proc.core::slice::::copy_from_slice::len_mismatch_fail::do_panic::runtime + drop + drop + drop + push.0 + assert +end + +proc.core::ptr::alignment::Alignment::max + push.0 + dup.2 + dup.2 + swap.1 + u32gt + neq + cdrop +end + +proc.miden::tx::create_note + trace.240 + nop + exec.::miden::tx::create_note + trace.252 + nop +end + diff --git a/tests/integration/expected/examples/basic_wallet_tx_script.wat b/tests/integration/expected/examples/basic_wallet_tx_script.wat new file mode 100644 index 000000000..5719043e2 --- /dev/null +++ b/tests/integration/expected/examples/basic_wallet_tx_script.wat @@ -0,0 +1,780 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + (type (;5;) (record (field "inner" 4))) + (export (;6;) "asset" (type (eq 5))) + (type (;7;) (record (field "inner" 1))) + (export (;8;) "note-idx" (type (eq 7))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (alias export 0 "asset" (type (;1;))) + (alias export 0 "note-idx" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "asset" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "note-idx" (type (eq 2))) + (type (;4;) (func (param "asset" 1) (param "note-idx" 3))) + (export (;0;) "move-asset-to-note" (func (type 4))) + ) + ) + (import "miden:basic-wallet/basic-wallet@0.1.0" (instance (;1;) (type 3))) + (core module (;0;) + (type (;0;) (func (param f32 f32 f32 f32 f32))) + (type (;1;) (func)) + (type (;2;) (func (param i32 i32 i32))) + (type (;3;) (func (param i32 i32 i32 i32 i32))) + (type (;4;) (func (param i32 i32) (result i32))) + (type (;5;) (func (param f32 f32 f32 f32))) + (type (;6;) (func (param i32 i32 i32) (result i32))) + (type (;7;) (func (result i32))) + (type (;8;) (func (param f32 f32 f32 f32 i32) (result f32))) + (type (;9;) (func (param i32 i32))) + (type (;10;) (func (param i64) (result f32))) + (type (;11;) (func (param i32) (result f32))) + (type (;12;) (func (param f32) (result i64))) + (type (;13;) (func (param f32 f32))) + (type (;14;) (func (param f32 f32 f32 f32) (result f32))) + (type (;15;) (func (param f32 i32 f32 f32 f32 f32) (result i32))) + (type (;16;) (func (param i32 i32 i32 i32))) + (type (;17;) (func (param f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) + (import "miden:basic-wallet/basic-wallet@0.1.0" "move-asset-to-note" (func $basic_wallet_tx_script::bindings::miden::basic_wallet::basic_wallet::move_asset_to_note::wit_import9 (;0;) (type 0))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/transaction-script@1.0.0#run" (func $miden:base/transaction-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $basic_wallet_tx_script::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;1;) (type 1)) + (func $core::slice::index::slice_end_index_len_fail (;2;) (type 2) (param i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + call $core::slice::::copy_from_slice::len_mismatch_fail::do_panic::runtime + unreachable + ) + (func $ as core::ops::index::Index>::index (;3;) (type 3) (param i32 i32 i32 i32 i32) + (local i32) + block ;; label = @1 + local.get 3 + local.get 1 + i32.load offset=8 + local.tee 5 + i32.le_u + br_if 0 (;@1;) + local.get 3 + local.get 5 + local.get 4 + call $core::slice::index::slice_end_index_len_fail + unreachable + end + local.get 0 + local.get 3 + local.get 2 + i32.sub + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + local.get 2 + i32.const 2 + i32.shl + i32.add + i32.store + ) + (func $__rustc::__rust_alloc (;4;) (type 4) (param i32 i32) (result i32) + global.get $GOT.data.internal.__memory_base + i32.const 1048688 + i32.add + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_dealloc (;5;) (type 2) (param i32 i32 i32)) + (func $__rustc::__rust_alloc_zeroed (;6;) (type 4) (param i32 i32) (result i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048688 + i32.add + local.get 1 + local.get 0 + call $::alloc + local.tee 1 + i32.eqz + br_if 0 (;@1;) + local.get 0 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.const 0 + local.get 0 + memory.fill + end + local.get 1 + ) + (func $basic_wallet_tx_script::bindings::__link_custom_section_describing_imports (;7;) (type 1)) + (func $miden:base/transaction-script@1.0.0#run (;8;) (type 5) (param f32 f32 f32 f32) + (local i32 i64 f32 i32 i32 i32) + global.get $__stack_pointer + i32.const 80 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + local.get 3 + local.get 2 + local.get 1 + local.get 0 + call $intrinsics::advice::adv_push_mapvaln + call $intrinsics::felt::as_u64 + local.tee 5 + i32.wrap_i64 + i32.const 3 + i32.and + call $intrinsics::felt::from_u32 + i32.const 0 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 4 + i32.const 64 + i32.add + local.get 5 + i64.const 2 + i64.shr_u + call $intrinsics::felt::from_u64_unchecked + local.tee 6 + call $intrinsics::felt::as_u64 + i32.wrap_i64 + i32.const 2 + i32.shl + local.tee 7 + i32.const 0 + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::try_allocate_in + local.get 4 + i32.load offset=68 + local.set 8 + block ;; label = @1 + block ;; label = @2 + local.get 4 + i32.load offset=64 + i32.const 1 + i32.eq + br_if 0 (;@2;) + local.get 6 + local.get 4 + i32.load offset=72 + local.tee 9 + i32.const 2 + i32.shr_u + local.get 3 + local.get 2 + local.get 1 + local.get 0 + call $std::mem::pipe_preimage_to_memory + drop + local.get 4 + local.get 7 + i32.store offset=28 + local.get 4 + local.get 9 + i32.store offset=24 + local.get 4 + local.get 8 + i32.store offset=20 + local.get 7 + i32.eqz + br_if 1 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 7 + local.get 9 + f32.load offset=12 + local.set 0 + local.get 9 + f32.load offset=8 + local.set 1 + local.get 9 + f32.load offset=4 + local.set 2 + local.get 9 + f32.load + local.set 3 + local.get 4 + i32.const 8 + i32.add + local.get 4 + i32.const 20 + i32.add + i32.const 4 + i32.const 8 + local.get 7 + i32.const 1048656 + i32.add + call $ as core::ops::index::Index>::index + local.get 4 + i32.load offset=12 + i32.const 4 + i32.ne + br_if 1 (;@1;) + local.get 4 + i32.load offset=8 + local.tee 9 + i64.load align=4 + local.set 5 + local.get 4 + i32.const 32 + i32.add + i32.const 8 + i32.add + local.get 9 + i32.const 8 + i32.add + i64.load align=4 + i64.store + local.get 4 + local.get 5 + i64.store offset=32 + global.get $GOT.data.internal.__memory_base + local.set 9 + local.get 4 + i32.const 64 + i32.add + local.get 4 + i32.const 32 + i32.add + call $>::from + local.get 3 + local.get 2 + local.get 1 + local.get 0 + local.get 4 + i32.const 64 + i32.add + call $miden_base_sys::bindings::tx::create_note + local.set 0 + local.get 4 + local.get 4 + i32.const 20 + i32.add + i32.const 8 + i32.const 12 + local.get 9 + i32.const 1048672 + i32.add + call $ as core::ops::index::Index>::index + local.get 4 + i32.load offset=4 + i32.const 4 + i32.ne + br_if 1 (;@1;) + local.get 4 + i32.load + local.tee 9 + i64.load align=4 + local.set 5 + local.get 4 + i32.const 48 + i32.add + i32.const 8 + i32.add + local.get 9 + i32.const 8 + i32.add + i64.load align=4 + i64.store + local.get 4 + local.get 5 + i64.store offset=48 + local.get 4 + i32.const 64 + i32.add + local.get 4 + i32.const 48 + i32.add + call $>::from + local.get 4 + f32.load offset=64 + local.get 4 + f32.load offset=68 + local.get 4 + f32.load offset=72 + local.get 4 + f32.load offset=76 + local.get 0 + call $basic_wallet_tx_script::bindings::miden::basic_wallet::basic_wallet::move_asset_to_note::wit_import9 + local.get 4 + i32.const 20 + i32.add + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 4 + i32.const 80 + i32.add + global.set $__stack_pointer + return + end + global.get $GOT.data.internal.__memory_base + local.set 9 + local.get 8 + local.get 4 + i32.load offset=72 + local.get 9 + i32.const 1048640 + i32.add + call $alloc::raw_vec::handle_error + end + unreachable + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;9;) (type 1) + return + ) + (func $wit_bindgen::rt::run_ctors_once (;10;) (type 1) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048692 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048692 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $::alloc (;11;) (type 6) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;12;) (type 7) (result i32) + unreachable + ) + (func $miden_base_sys::bindings::tx::create_note (;13;) (type 8) (param f32 f32 f32 f32 i32) (result f32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + local.get 4 + f32.load offset=12 + local.get 4 + f32.load offset=8 + local.get 4 + f32.load offset=4 + local.get 4 + f32.load + call $miden::tx::create_note + ) + (func $>::from (;14;) (type 9) (param i32 i32) + local.get 0 + local.get 1 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 1 + i64.load align=4 + i64.store + ) + (func $intrinsics::felt::from_u64_unchecked (;15;) (type 10) (param i64) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;16;) (type 11) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::as_u64 (;17;) (type 12) (param f32) (result i64) + unreachable + ) + (func $intrinsics::felt::assert_eq (;18;) (type 13) (param f32 f32) + unreachable + ) + (func $intrinsics::advice::adv_push_mapvaln (;19;) (type 14) (param f32 f32 f32 f32) (result f32) + unreachable + ) + (func $std::mem::pipe_preimage_to_memory (;20;) (type 15) (param f32 i32 f32 f32 f32 f32) (result i32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;21;) (type 2) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::try_allocate_in (;22;) (type 3) (param i32 i32 i32 i32 i32) + (local i32 i64) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 3 + local.get 4 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 3 + i32.sub + i32.and + i64.extend_i32_u + local.get 1 + i64.extend_i32_u + i64.mul + local.tee 6 + i64.const 32 + i64.shr_u + i32.wrap_i64 + br_if 0 (;@3;) + local.get 6 + i32.wrap_i64 + local.tee 4 + i32.const -2147483648 + local.get 3 + i32.sub + i32.le_u + br_if 1 (;@2;) + end + local.get 0 + i32.const 0 + i32.store offset=4 + i32.const 1 + local.set 3 + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 + br_if 0 (;@2;) + local.get 0 + local.get 3 + i32.store offset=8 + i32.const 0 + local.set 3 + local.get 0 + i32.const 0 + i32.store offset=4 + br 1 (;@1;) + end + block ;; label = @2 + block ;; label = @3 + local.get 2 + br_if 0 (;@3;) + local.get 5 + i32.const 8 + i32.add + local.get 3 + local.get 4 + call $::allocate + local.get 5 + i32.load offset=8 + local.set 2 + br 1 (;@2;) + end + local.get 5 + local.get 3 + local.get 4 + i32.const 1 + call $alloc::alloc::Global::alloc_impl + local.get 5 + i32.load + local.set 2 + end + block ;; label = @2 + local.get 2 + i32.eqz + br_if 0 (;@2;) + local.get 0 + local.get 2 + i32.store offset=8 + local.get 0 + local.get 1 + i32.store offset=4 + i32.const 0 + local.set 3 + br 1 (;@1;) + end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + i32.const 1 + local.set 3 + end + local.get 0 + local.get 3 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::allocate (;23;) (type 2) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 2 + i32.const 0 + call $alloc::alloc::Global::alloc_impl + local.get 3 + i32.load offset=12 + local.set 2 + local.get 0 + local.get 3 + i32.load offset=8 + i32.store + local.get 0 + local.get 2 + i32.store offset=4 + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::alloc::Global::alloc_impl (;24;) (type 16) (param i32 i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + local.get 2 + local.get 1 + call $__rustc::__rust_alloc + local.set 1 + br 1 (;@1;) + end + local.get 2 + local.get 1 + call $__rustc::__rust_alloc_zeroed + local.set 1 + end + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.store + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;25;) (type 16) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;26;) (type 2) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) + (func $alloc::raw_vec::handle_error (;27;) (type 2) (param i32 i32 i32) + unreachable + ) + (func $core::slice::::copy_from_slice::len_mismatch_fail::do_panic::runtime (;28;) (type 2) (param i32 i32 i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;29;) (type 4) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $miden::tx::create_note (;30;) (type 17) (param f32 f32 f32 f32 f32 f32 f32 f32) (result f32) + unreachable + ) + (data $.rodata (;0;) (i32.const 1048576) "miden-stdlib-sys-0.7.0/src/stdlib/mem.rs\00src/lib.rs\00") + (data $.data (;1;) (i32.const 1048628) "\01\00\00\00\01\00\00\00\01\00\00\00\00\00\10\00(\00\00\00\97\00\00\00!\00\00\00)\00\10\00\0a\00\00\00%\00\00\00%\00\00\00)\00\10\00\0a\00\00\00(\00\00\00!\00\00\00") + ) + (alias export 0 "word" (type (;4;))) + (alias export 1 "move-asset-to-note" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (core instance (;0;) + (export "move-asset-to-note" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "miden:basic-wallet/basic-wallet@0.1.0" (instance 0)) + ) + ) + (alias core export 1 "memory" (core memory (;0;))) + (type (;5;) (func (param "arg" 4))) + (alias core export 1 "miden:base/transaction-script@1.0.0#run" (core func (;1;))) + (func (;1;) (type 5) (canon lift (core func 1))) + (alias export 0 "felt" (type (;6;))) + (alias export 0 "word" (type (;7;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;2;) (instantiate 0 + (with "import-func-run" (func 1)) + (with "import-type-felt" (type 6)) + (with "import-type-word" (type 7)) + (with "import-type-word0" (type 4)) + ) + ) + (export (;3;) "miden:base/transaction-script@1.0.0" (instance 2)) +) diff --git a/tests/integration/expected/examples/counter.hir b/tests/integration/expected/examples/counter.hir new file mode 100644 index 000000000..07ac995d0 --- /dev/null +++ b/tests/integration/expected/examples/counter.hir @@ -0,0 +1,652 @@ +builtin.component miden:counter-contract/counter@0.1.0 { + builtin.module public @counter_contract { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::storage::get_map_item(v0: i32, v1: i32) { + ^block7(v0: i32, v1: i32): + v3 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v4 = hir.bitcast v3 : ptr; + v5 = hir.load v4 : i32; + v6 = arith.constant 32 : i32; + v7 = arith.sub v5, v6 : i32 #[overflow = wrapping]; + v8 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v9 = hir.bitcast v8 : ptr; + hir.store v9, v7; + v2 = arith.constant 0 : i32; + v11 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/>::from(v2) : felt + v13 = arith.constant 12 : u32; + v12 = hir.bitcast v1 : u32; + v14 = arith.add v12, v13 : u32 #[overflow = checked]; + v15 = arith.constant 4 : u32; + v16 = arith.mod v14, v15 : u32; + hir.assertz v16 #[code = 250]; + v17 = hir.int_to_ptr v14 : ptr; + v18 = hir.load v17 : felt; + v20 = arith.constant 8 : u32; + v19 = hir.bitcast v1 : u32; + v21 = arith.add v19, v20 : u32 #[overflow = checked]; + v499 = arith.constant 4 : u32; + v23 = arith.mod v21, v499 : u32; + hir.assertz v23 #[code = 250]; + v24 = hir.int_to_ptr v21 : ptr; + v25 = hir.load v24 : felt; + v498 = arith.constant 4 : u32; + v26 = hir.bitcast v1 : u32; + v28 = arith.add v26, v498 : u32 #[overflow = checked]; + v497 = arith.constant 4 : u32; + v30 = arith.mod v28, v497 : u32; + hir.assertz v30 #[code = 250]; + v31 = hir.int_to_ptr v28 : ptr; + v32 = hir.load v31 : felt; + v33 = hir.bitcast v1 : u32; + v496 = arith.constant 4 : u32; + v35 = arith.mod v33, v496 : u32; + hir.assertz v35 #[code = 250]; + v36 = hir.int_to_ptr v33 : ptr; + v37 = hir.load v36 : felt; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden::account::get_map_item(v11, v18, v25, v32, v37, v7) + v495 = arith.constant 8 : u32; + v38 = hir.bitcast v7 : u32; + v40 = arith.add v38, v495 : u32 #[overflow = checked]; + v494 = arith.constant 8 : u32; + v42 = arith.mod v40, v494 : u32; + hir.assertz v42 #[code = 250]; + v43 = hir.int_to_ptr v40 : ptr; + v44 = hir.load v43 : i64; + v46 = arith.constant 24 : u32; + v45 = hir.bitcast v7 : u32; + v47 = arith.add v45, v46 : u32 #[overflow = checked]; + v493 = arith.constant 8 : u32; + v49 = arith.mod v47, v493 : u32; + hir.assertz v49 #[code = 250]; + v50 = hir.int_to_ptr v47 : ptr; + hir.store v50, v44; + v51 = hir.bitcast v7 : u32; + v492 = arith.constant 8 : u32; + v53 = arith.mod v51, v492 : u32; + hir.assertz v53 #[code = 250]; + v54 = hir.int_to_ptr v51 : ptr; + v55 = hir.load v54 : i64; + v57 = arith.constant 16 : u32; + v56 = hir.bitcast v7 : u32; + v58 = arith.add v56, v57 : u32 #[overflow = checked]; + v491 = arith.constant 8 : u32; + v60 = arith.mod v58, v491 : u32; + hir.assertz v60 #[code = 250]; + v61 = hir.int_to_ptr v58 : ptr; + hir.store v61, v55; + v62 = arith.constant 16 : i32; + v63 = arith.add v7, v62 : i32 #[overflow = wrapping]; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden_stdlib_sys::intrinsics::word::Word::reverse(v0, v63) + v490 = arith.constant 32 : i32; + v65 = arith.add v7, v490 : i32 #[overflow = wrapping]; + v66 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v67 = hir.bitcast v66 : ptr; + hir.store v67, v65; + builtin.ret ; + }; + + private builtin.function @counter_contract::bindings::__link_custom_section_describing_imports() { + ^block9: + builtin.ret ; + }; + + private builtin.function @miden:counter-contract/counter@0.1.0#get-count() -> felt { + ^block11: + v71 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v72 = hir.bitcast v71 : ptr; + v73 = hir.load v72 : i32; + v74 = arith.constant 32 : i32; + v75 = arith.sub v73, v74 : i32 #[overflow = wrapping]; + v76 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v77 = hir.bitcast v76 : ptr; + hir.store v77, v75; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/wit_bindgen::rt::run_ctors_once() + v69 = arith.constant 0 : i32; + v79 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v69) : felt + v507 = arith.constant 0 : i32; + v81 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v507) : felt + v506 = arith.constant 0 : i32; + v83 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v506) : felt + v84 = arith.constant 1 : i32; + v85 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v84) : felt + v87 = arith.constant 12 : u32; + v86 = hir.bitcast v75 : u32; + v88 = arith.add v86, v87 : u32 #[overflow = checked]; + v89 = arith.constant 4 : u32; + v90 = arith.mod v88, v89 : u32; + hir.assertz v90 #[code = 250]; + v91 = hir.int_to_ptr v88 : ptr; + hir.store v91, v85; + v93 = arith.constant 8 : u32; + v92 = hir.bitcast v75 : u32; + v94 = arith.add v92, v93 : u32 #[overflow = checked]; + v505 = arith.constant 4 : u32; + v96 = arith.mod v94, v505 : u32; + hir.assertz v96 #[code = 250]; + v97 = hir.int_to_ptr v94 : ptr; + hir.store v97, v83; + v504 = arith.constant 4 : u32; + v98 = hir.bitcast v75 : u32; + v100 = arith.add v98, v504 : u32 #[overflow = checked]; + v503 = arith.constant 4 : u32; + v102 = arith.mod v100, v503 : u32; + hir.assertz v102 #[code = 250]; + v103 = hir.int_to_ptr v100 : ptr; + hir.store v103, v81; + v104 = hir.bitcast v75 : u32; + v502 = arith.constant 4 : u32; + v106 = arith.mod v104, v502 : u32; + hir.assertz v106 #[code = 250]; + v107 = hir.int_to_ptr v104 : ptr; + hir.store v107, v79; + v108 = arith.constant 16 : i32; + v109 = arith.add v75, v108 : i32 #[overflow = wrapping]; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden_base_sys::bindings::storage::get_map_item(v109, v75) + v111 = arith.constant 28 : u32; + v110 = hir.bitcast v75 : u32; + v112 = arith.add v110, v111 : u32 #[overflow = checked]; + v501 = arith.constant 4 : u32; + v114 = arith.mod v112, v501 : u32; + hir.assertz v114 #[code = 250]; + v115 = hir.int_to_ptr v112 : ptr; + v116 = hir.load v115 : felt; + v500 = arith.constant 32 : i32; + v118 = arith.add v75, v500 : i32 #[overflow = wrapping]; + v119 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v120 = hir.bitcast v119 : ptr; + hir.store v120, v118; + builtin.ret v116; + }; + + private builtin.function @miden:counter-contract/counter@0.1.0#increment-count() -> felt { + ^block13: + v124 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v125 = hir.bitcast v124 : ptr; + v126 = hir.load v125 : i32; + v127 = arith.constant 128 : i32; + v128 = arith.sub v126, v127 : i32 #[overflow = wrapping]; + v129 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v130 = hir.bitcast v129 : ptr; + hir.store v130, v128; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/wit_bindgen::rt::run_ctors_once() + v122 = arith.constant 0 : i32; + v132 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v122) : felt + v530 = arith.constant 0 : i32; + v134 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v530) : felt + v529 = arith.constant 0 : i32; + v136 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v529) : felt + v137 = arith.constant 1 : i32; + v138 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v137) : felt + v140 = arith.constant 12 : u32; + v139 = hir.bitcast v128 : u32; + v141 = arith.add v139, v140 : u32 #[overflow = checked]; + v142 = arith.constant 4 : u32; + v143 = arith.mod v141, v142 : u32; + hir.assertz v143 #[code = 250]; + v144 = hir.int_to_ptr v141 : ptr; + hir.store v144, v138; + v146 = arith.constant 8 : u32; + v145 = hir.bitcast v128 : u32; + v147 = arith.add v145, v146 : u32 #[overflow = checked]; + v528 = arith.constant 4 : u32; + v149 = arith.mod v147, v528 : u32; + hir.assertz v149 #[code = 250]; + v150 = hir.int_to_ptr v147 : ptr; + hir.store v150, v136; + v527 = arith.constant 4 : u32; + v151 = hir.bitcast v128 : u32; + v153 = arith.add v151, v527 : u32 #[overflow = checked]; + v526 = arith.constant 4 : u32; + v155 = arith.mod v153, v526 : u32; + hir.assertz v155 #[code = 250]; + v156 = hir.int_to_ptr v153 : ptr; + hir.store v156, v134; + v157 = hir.bitcast v128 : u32; + v525 = arith.constant 4 : u32; + v159 = arith.mod v157, v525 : u32; + hir.assertz v159 #[code = 250]; + v160 = hir.int_to_ptr v157 : ptr; + hir.store v160, v132; + v161 = arith.constant 64 : i32; + v162 = arith.add v128, v161 : i32 #[overflow = wrapping]; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden_base_sys::bindings::storage::get_map_item(v162, v128) + v166 = arith.constant 76 : u32; + v165 = hir.bitcast v128 : u32; + v167 = arith.add v165, v166 : u32 #[overflow = checked]; + v524 = arith.constant 4 : u32; + v169 = arith.mod v167, v524 : u32; + hir.assertz v169 #[code = 250]; + v170 = hir.int_to_ptr v167 : ptr; + v171 = hir.load v170 : felt; + v523 = arith.constant 1 : i32; + v173 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v523) : felt + v174 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::add(v171, v173) : felt + v163 = arith.constant 48 : i32; + v164 = arith.add v128, v163 : i32 #[overflow = wrapping]; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/>::from(v164, v174) + v522 = arith.constant 0 : i32; + v176 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/>::from(v522) : felt + v178 = arith.constant 60 : u32; + v177 = hir.bitcast v128 : u32; + v179 = arith.add v177, v178 : u32 #[overflow = checked]; + v521 = arith.constant 4 : u32; + v181 = arith.mod v179, v521 : u32; + hir.assertz v181 #[code = 250]; + v182 = hir.int_to_ptr v179 : ptr; + v183 = hir.load v182 : felt; + v185 = arith.constant 56 : u32; + v184 = hir.bitcast v128 : u32; + v186 = arith.add v184, v185 : u32 #[overflow = checked]; + v520 = arith.constant 4 : u32; + v188 = arith.mod v186, v520 : u32; + hir.assertz v188 #[code = 250]; + v189 = hir.int_to_ptr v186 : ptr; + v190 = hir.load v189 : felt; + v192 = arith.constant 52 : u32; + v191 = hir.bitcast v128 : u32; + v193 = arith.add v191, v192 : u32 #[overflow = checked]; + v519 = arith.constant 4 : u32; + v195 = arith.mod v193, v519 : u32; + hir.assertz v195 #[code = 250]; + v196 = hir.int_to_ptr v193 : ptr; + v197 = hir.load v196 : felt; + v199 = arith.constant 48 : u32; + v198 = hir.bitcast v128 : u32; + v200 = arith.add v198, v199 : u32 #[overflow = checked]; + v518 = arith.constant 4 : u32; + v202 = arith.mod v200, v518 : u32; + hir.assertz v202 #[code = 250]; + v203 = hir.int_to_ptr v200 : ptr; + v204 = hir.load v203 : felt; + v517 = arith.constant 64 : i32; + v206 = arith.add v128, v517 : i32 #[overflow = wrapping]; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden::account::set_map_item(v176, v138, v136, v134, v132, v183, v190, v197, v204, v206) + v208 = arith.constant 72 : u32; + v207 = hir.bitcast v128 : u32; + v209 = arith.add v207, v208 : u32 #[overflow = checked]; + v516 = arith.constant 8 : u32; + v211 = arith.mod v209, v516 : u32; + hir.assertz v211 #[code = 250]; + v212 = hir.int_to_ptr v209 : ptr; + v213 = hir.load v212 : i64; + v215 = arith.constant 104 : u32; + v214 = hir.bitcast v128 : u32; + v216 = arith.add v214, v215 : u32 #[overflow = checked]; + v515 = arith.constant 8 : u32; + v218 = arith.mod v216, v515 : u32; + hir.assertz v218 #[code = 250]; + v219 = hir.int_to_ptr v216 : ptr; + hir.store v219, v213; + v221 = arith.constant 64 : u32; + v220 = hir.bitcast v128 : u32; + v222 = arith.add v220, v221 : u32 #[overflow = checked]; + v514 = arith.constant 8 : u32; + v224 = arith.mod v222, v514 : u32; + hir.assertz v224 #[code = 250]; + v225 = hir.int_to_ptr v222 : ptr; + v226 = hir.load v225 : i64; + v228 = arith.constant 96 : u32; + v227 = hir.bitcast v128 : u32; + v229 = arith.add v227, v228 : u32 #[overflow = checked]; + v513 = arith.constant 8 : u32; + v231 = arith.mod v229, v513 : u32; + hir.assertz v231 #[code = 250]; + v232 = hir.int_to_ptr v229 : ptr; + hir.store v232, v226; + v233 = arith.constant 88 : i32; + v234 = arith.add v128, v233 : i32 #[overflow = wrapping]; + v235 = hir.bitcast v234 : u32; + v512 = arith.constant 8 : u32; + v237 = arith.mod v235, v512 : u32; + hir.assertz v237 #[code = 250]; + v238 = hir.int_to_ptr v235 : ptr; + v239 = hir.load v238 : i64; + v241 = arith.constant 120 : u32; + v240 = hir.bitcast v128 : u32; + v242 = arith.add v240, v241 : u32 #[overflow = checked]; + v511 = arith.constant 8 : u32; + v244 = arith.mod v242, v511 : u32; + hir.assertz v244 #[code = 250]; + v245 = hir.int_to_ptr v242 : ptr; + hir.store v245, v239; + v247 = arith.constant 80 : u32; + v246 = hir.bitcast v128 : u32; + v248 = arith.add v246, v247 : u32 #[overflow = checked]; + v510 = arith.constant 8 : u32; + v250 = arith.mod v248, v510 : u32; + hir.assertz v250 #[code = 250]; + v251 = hir.int_to_ptr v248 : ptr; + v252 = hir.load v251 : i64; + v254 = arith.constant 112 : u32; + v253 = hir.bitcast v128 : u32; + v255 = arith.add v253, v254 : u32 #[overflow = checked]; + v509 = arith.constant 8 : u32; + v257 = arith.mod v255, v509 : u32; + hir.assertz v257 #[code = 250]; + v258 = hir.int_to_ptr v255 : ptr; + hir.store v258, v252; + v261 = arith.constant 96 : i32; + v262 = arith.add v128, v261 : i32 #[overflow = wrapping]; + v259 = arith.constant 16 : i32; + v260 = arith.add v128, v259 : i32 #[overflow = wrapping]; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden_stdlib_sys::intrinsics::word::Word::reverse(v260, v262) + v265 = arith.constant 112 : i32; + v266 = arith.add v128, v265 : i32 #[overflow = wrapping]; + v263 = arith.constant 32 : i32; + v264 = arith.add v128, v263 : i32 #[overflow = wrapping]; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden_stdlib_sys::intrinsics::word::Word::reverse(v264, v266) + v508 = arith.constant 128 : i32; + v268 = arith.add v128, v508 : i32 #[overflow = wrapping]; + v269 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v270 = hir.bitcast v269 : ptr; + hir.store v270, v268; + builtin.ret v174; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block15: + v272 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/GOT.data.internal.__memory_base : ptr + v273 = hir.bitcast v272 : ptr; + v274 = hir.load v273 : i32; + v275 = arith.constant 1048584 : i32; + v276 = arith.add v274, v275 : i32 #[overflow = wrapping]; + v277 = hir.bitcast v276 : u32; + v278 = hir.int_to_ptr v277 : ptr; + v279 = hir.load v278 : u8; + v271 = arith.constant 0 : i32; + v280 = arith.zext v279 : u32; + v281 = hir.bitcast v280 : i32; + v283 = arith.neq v281, v271 : i1; + scf.if v283{ + ^block17: + scf.yield ; + } else { + ^block18: + v284 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/GOT.data.internal.__memory_base : ptr + v285 = hir.bitcast v284 : ptr; + v286 = hir.load v285 : i32; + hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/__wasm_call_ctors() + v532 = arith.constant 1 : u8; + v534 = arith.constant 1048584 : i32; + v288 = arith.add v286, v534 : i32 #[overflow = wrapping]; + v292 = hir.bitcast v288 : u32; + v293 = hir.int_to_ptr v292 : ptr; + hir.store v293, v532; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @>::from(v294: i32) -> felt { + ^block19(v294: i32): + v296 = arith.constant 255 : i32; + v297 = arith.band v294, v296 : i32; + v298 = hir.bitcast v297 : felt; + builtin.ret v298; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v299: i32, v300: i32) { + ^block21(v299: i32, v300: i32): + v303 = builtin.global_symbol @miden:counter-contract/counter@0.1.0/counter_contract/__stack_pointer : ptr + v304 = hir.bitcast v303 : ptr; + v305 = hir.load v304 : i32; + v306 = arith.constant 16 : i32; + v307 = arith.sub v305, v306 : i32 #[overflow = wrapping]; + v309 = arith.constant 8 : u32; + v308 = hir.bitcast v300 : u32; + v310 = arith.add v308, v309 : u32 #[overflow = checked]; + v621 = arith.constant 8 : u32; + v312 = arith.mod v310, v621 : u32; + hir.assertz v312 #[code = 250]; + v313 = hir.int_to_ptr v310 : ptr; + v314 = hir.load v313 : i64; + v620 = arith.constant 8 : u32; + v315 = hir.bitcast v307 : u32; + v317 = arith.add v315, v620 : u32 #[overflow = checked]; + v318 = arith.constant 4 : u32; + v319 = arith.mod v317, v318 : u32; + hir.assertz v319 #[code = 250]; + v320 = hir.int_to_ptr v317 : ptr; + hir.store v320, v314; + v321 = hir.bitcast v300 : u32; + v619 = arith.constant 8 : u32; + v323 = arith.mod v321, v619 : u32; + hir.assertz v323 #[code = 250]; + v324 = hir.int_to_ptr v321 : ptr; + v325 = hir.load v324 : i64; + v326 = hir.bitcast v307 : u32; + v618 = arith.constant 4 : u32; + v328 = arith.mod v326, v618 : u32; + hir.assertz v328 #[code = 250]; + v329 = hir.int_to_ptr v326 : ptr; + hir.store v329, v325; + v330 = arith.constant 12 : i32; + v331 = arith.add v307, v330 : i32 #[overflow = wrapping]; + v301 = arith.constant 0 : i32; + v589, v590, v591, v592, v593, v594 = scf.while v301, v307, v331, v299 : i32, i32, i32, i32, i32, i32 { + ^block51(v595: i32, v596: i32, v597: i32, v598: i32): + v617 = arith.constant 0 : i32; + v334 = arith.constant 8 : i32; + v335 = arith.eq v595, v334 : i1; + v336 = arith.zext v335 : u32; + v337 = hir.bitcast v336 : i32; + v339 = arith.neq v337, v617 : i1; + v583, v584 = scf.if v339 : i32, i32 { + ^block50: + v543 = ub.poison i32 : i32; + scf.yield v543, v543; + } else { + ^block26: + v341 = arith.add v596, v595 : i32 #[overflow = wrapping]; + v342 = hir.bitcast v341 : u32; + v616 = arith.constant 4 : u32; + v344 = arith.mod v342, v616 : u32; + hir.assertz v344 #[code = 250]; + v345 = hir.int_to_ptr v342 : ptr; + v346 = hir.load v345 : felt; + v348 = hir.bitcast v597 : u32; + v615 = arith.constant 4 : u32; + v350 = arith.mod v348, v615 : u32; + hir.assertz v350 #[code = 250]; + v351 = hir.int_to_ptr v348 : ptr; + v352 = hir.load v351 : i32; + v353 = hir.bitcast v341 : u32; + v614 = arith.constant 4 : u32; + v355 = arith.mod v353, v614 : u32; + hir.assertz v355 #[code = 250]; + v356 = hir.int_to_ptr v353 : ptr; + hir.store v356, v352; + v357 = hir.bitcast v597 : u32; + v613 = arith.constant 4 : u32; + v359 = arith.mod v357, v613 : u32; + hir.assertz v359 #[code = 250]; + v360 = hir.int_to_ptr v357 : ptr; + hir.store v360, v346; + v363 = arith.constant -4 : i32; + v364 = arith.add v597, v363 : i32 #[overflow = wrapping]; + v361 = arith.constant 4 : i32; + v362 = arith.add v595, v361 : i32 #[overflow = wrapping]; + scf.yield v362, v364; + }; + v611 = ub.poison i32 : i32; + v586 = cf.select v339, v611, v598 : i32; + v612 = ub.poison i32 : i32; + v585 = cf.select v339, v612, v596 : i32; + v542 = arith.constant 1 : u32; + v535 = arith.constant 0 : u32; + v588 = cf.select v339, v535, v542 : u32; + v576 = arith.trunc v588 : i1; + scf.condition v576, v583, v585, v584, v586, v596, v598; + } do { + ^block52(v599: i32, v600: i32, v601: i32, v602: i32, v603: i32, v604: i32): + scf.yield v599, v600, v601, v602; + }; + v610 = arith.constant 8 : u32; + v366 = hir.bitcast v593 : u32; + v368 = arith.add v366, v610 : u32 #[overflow = checked]; + v609 = arith.constant 4 : u32; + v370 = arith.mod v368, v609 : u32; + hir.assertz v370 #[code = 250]; + v371 = hir.int_to_ptr v368 : ptr; + v372 = hir.load v371 : i64; + v608 = arith.constant 8 : u32; + v373 = hir.bitcast v594 : u32; + v375 = arith.add v373, v608 : u32 #[overflow = checked]; + v607 = arith.constant 8 : u32; + v377 = arith.mod v375, v607 : u32; + hir.assertz v377 #[code = 250]; + v378 = hir.int_to_ptr v375 : ptr; + hir.store v378, v372; + v379 = hir.bitcast v593 : u32; + v606 = arith.constant 4 : u32; + v381 = arith.mod v379, v606 : u32; + hir.assertz v381 #[code = 250]; + v382 = hir.int_to_ptr v379 : ptr; + v383 = hir.load v382 : i64; + v384 = hir.bitcast v594 : u32; + v605 = arith.constant 8 : u32; + v386 = arith.mod v384, v605 : u32; + hir.assertz v386 #[code = 250]; + v387 = hir.int_to_ptr v384 : ptr; + hir.store v387, v383; + builtin.ret ; + }; + + private builtin.function @>::from(v388: i32, v389: felt) { + ^block27(v388: i32, v389: felt): + v391 = arith.constant 0 : i32; + v392 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v391) : felt + v627 = arith.constant 0 : i32; + v394 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v627) : felt + v626 = arith.constant 0 : i32; + v396 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/intrinsics::felt::from_u32(v626) : felt + v398 = arith.constant 12 : u32; + v397 = hir.bitcast v388 : u32; + v399 = arith.add v397, v398 : u32 #[overflow = checked]; + v400 = arith.constant 4 : u32; + v401 = arith.mod v399, v400 : u32; + hir.assertz v401 #[code = 250]; + v402 = hir.int_to_ptr v399 : ptr; + hir.store v402, v389; + v404 = arith.constant 8 : u32; + v403 = hir.bitcast v388 : u32; + v405 = arith.add v403, v404 : u32 #[overflow = checked]; + v625 = arith.constant 4 : u32; + v407 = arith.mod v405, v625 : u32; + hir.assertz v407 #[code = 250]; + v408 = hir.int_to_ptr v405 : ptr; + hir.store v408, v396; + v624 = arith.constant 4 : u32; + v409 = hir.bitcast v388 : u32; + v411 = arith.add v409, v624 : u32 #[overflow = checked]; + v623 = arith.constant 4 : u32; + v413 = arith.mod v411, v623 : u32; + hir.assertz v413 #[code = 250]; + v414 = hir.int_to_ptr v411 : ptr; + hir.store v414, v394; + v415 = hir.bitcast v388 : u32; + v622 = arith.constant 4 : u32; + v417 = arith.mod v415, v622 : u32; + hir.assertz v417 #[code = 250]; + v418 = hir.int_to_ptr v415 : ptr; + hir.store v418, v392; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::add(v419: felt, v420: felt) -> felt { + ^block29(v419: felt, v420: felt): + v421 = arith.add v419, v420 : felt #[overflow = unchecked]; + builtin.ret v421; + }; + + private builtin.function @intrinsics::felt::from_u32(v423: i32) -> felt { + ^block31(v423: i32): + v424 = hir.bitcast v423 : felt; + builtin.ret v424; + }; + + private builtin.function @miden::account::get_map_item(v426: felt, v427: felt, v428: felt, v429: felt, v430: felt, v431: i32) { + ^block33(v426: felt, v427: felt, v428: felt, v429: felt, v430: felt, v431: i32): + v432, v433, v434, v435 = hir.exec @miden/account/get_map_item(v426, v427, v428, v429, v430) : felt, felt, felt, felt + v436 = hir.bitcast v431 : u32; + v437 = hir.int_to_ptr v436 : ptr; + hir.store v437, v432; + v438 = arith.constant 4 : u32; + v439 = arith.add v436, v438 : u32 #[overflow = checked]; + v440 = hir.int_to_ptr v439 : ptr; + hir.store v440, v433; + v441 = arith.constant 8 : u32; + v442 = arith.add v436, v441 : u32 #[overflow = checked]; + v443 = hir.int_to_ptr v442 : ptr; + hir.store v443, v434; + v444 = arith.constant 12 : u32; + v445 = arith.add v436, v444 : u32 #[overflow = checked]; + v446 = hir.int_to_ptr v445 : ptr; + hir.store v446, v435; + builtin.ret ; + }; + + private builtin.function @miden::account::set_map_item(v447: felt, v448: felt, v449: felt, v450: felt, v451: felt, v452: felt, v453: felt, v454: felt, v455: felt, v456: i32) { + ^block37(v447: felt, v448: felt, v449: felt, v450: felt, v451: felt, v452: felt, v453: felt, v454: felt, v455: felt, v456: i32): + v457, v458, v459, v460, v461, v462, v463, v464 = hir.exec @miden/account/set_map_item(v447, v448, v449, v450, v451, v452, v453, v454, v455) : felt, felt, felt, felt, felt, felt, felt, felt + v465 = hir.bitcast v456 : u32; + v466 = hir.int_to_ptr v465 : ptr; + hir.store v466, v457; + v467 = arith.constant 4 : u32; + v468 = arith.add v465, v467 : u32 #[overflow = checked]; + v469 = hir.int_to_ptr v468 : ptr; + hir.store v469, v458; + v470 = arith.constant 8 : u32; + v471 = arith.add v465, v470 : u32 #[overflow = checked]; + v472 = hir.int_to_ptr v471 : ptr; + hir.store v472, v459; + v473 = arith.constant 12 : u32; + v474 = arith.add v465, v473 : u32 #[overflow = checked]; + v475 = hir.int_to_ptr v474 : ptr; + hir.store v475, v460; + v476 = arith.constant 16 : u32; + v477 = arith.add v465, v476 : u32 #[overflow = checked]; + v478 = hir.int_to_ptr v477 : ptr; + hir.store v478, v461; + v479 = arith.constant 20 : u32; + v480 = arith.add v465, v479 : u32 #[overflow = checked]; + v481 = hir.int_to_ptr v480 : ptr; + hir.store v481, v462; + v482 = arith.constant 24 : u32; + v483 = arith.add v465, v482 : u32 #[overflow = checked]; + v484 = hir.int_to_ptr v483 : ptr; + hir.store v484, v463; + v485 = arith.constant 28 : u32; + v486 = arith.add v465, v485 : u32 #[overflow = checked]; + v487 = hir.int_to_ptr v486 : ptr; + hir.store v487, v464; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @get-count() -> felt { + ^block39: + v488 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden:counter-contract/counter@0.1.0#get-count() : felt + builtin.ret v488; + }; + + public builtin.function @increment-count() -> felt { + ^block41: + v489 = hir.exec @miden:counter-contract/counter@0.1.0/counter_contract/miden:counter-contract/counter@0.1.0#increment-count() : felt + builtin.ret v489; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/counter.masm b/tests/integration/expected/examples/counter.masm new file mode 100644 index 000000000..3c7459013 --- /dev/null +++ b/tests/integration/expected/examples/counter.masm @@ -0,0 +1,1429 @@ +# mod miden:counter-contract/counter@0.1.0 + +export.get-count + exec.::miden:counter-contract/counter@0.1.0::init + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden:counter-contract/counter@0.1.0#get-count + trace.252 + nop + exec.::std::sys::truncate_stack +end + +export.increment-count + exec.::miden:counter-contract/counter@0.1.0::init + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden:counter-contract/counter@0.1.0#increment-count + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:counter-contract/counter@0.1.0::counter_contract + +proc.__wasm_call_ctors + nop +end + +proc.miden_base_sys::bindings::storage::get_map_item + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::>::from + trace.252 + nop + push.12 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.6 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.6 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + movup.2 + swap.3 + movdn.2 + swap.1 + swap.4 + swap.1 + swap.5 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden::account::get_map_item + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.24 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.16 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.counter_contract::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:counter-contract/counter@0.1.0#get-count + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.12 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + dup.1 + swap.1 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden_base_sys::bindings::storage::get_map_item + trace.252 + nop + push.28 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.32 + movup.2 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden:counter-contract/counter@0.1.0#increment-count + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.128 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.12 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.64 + dup.5 + u32wrapping_add + dup.5 + swap.1 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden_base_sys::bindings::storage::get_map_item + trace.252 + nop + push.76 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::add + trace.252 + nop + push.48 + dup.6 + u32wrapping_add + dup.1 + swap.1 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::>::from + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::>::from + trace.252 + nop + push.60 + dup.7 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.56 + dup.8 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.52 + dup.9 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.48 + dup.10 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.64 + dup.11 + u32wrapping_add + swap.1 + swap.8 + swap.2 + swap.7 + swap.1 + swap.9 + swap.3 + swap.6 + swap.10 + swap.4 + swap.5 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden::account::set_map_item + trace.252 + nop + push.72 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.104 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.64 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.96 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.88 + dup.2 + u32wrapping_add + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.120 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.80 + dup.2 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.112 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.96 + dup.2 + u32wrapping_add + push.16 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.112 + dup.2 + u32wrapping_add + push.32 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.128 + movup.2 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.>::from + push.255 + u32and +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.>::from + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:counter-contract/counter@0.1.0::counter_contract::intrinsics::felt::from_u32 + trace.252 + nop + push.12 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.intrinsics::felt::add + add +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.miden::account::get_map_item + trace.240 + nop + exec.::miden::account::get_map_item + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::set_map_item + trace.240 + nop + exec.::miden::account::set_map_item + trace.252 + nop + movup.8 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.20 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.24 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.28 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + diff --git a/tests/integration/expected/examples/counter.wat b/tests/integration/expected/examples/counter.wat new file mode 100644 index 000000000..dcb506101 --- /dev/null +++ b/tests/integration/expected/examples/counter.wat @@ -0,0 +1,361 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func (result f32))) + (type (;3;) (func (param i32) (result f32))) + (type (;4;) (func (param i32 f32))) + (type (;5;) (func (param f32 f32) (result f32))) + (type (;6;) (func (param f32 f32 f32 f32 f32 i32))) + (type (;7;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:counter-contract/counter@0.1.0#get-count" (func $miden:counter-contract/counter@0.1.0#get-count)) + (export "miden:counter-contract/counter@0.1.0#increment-count" (func $miden:counter-contract/counter@0.1.0#increment-count)) + (elem (;0;) (i32.const 1) func $counter_contract::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $miden_base_sys::bindings::storage::get_map_item (;1;) (type 1) (param i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 2 + global.set $__stack_pointer + i32.const 0 + call $>::from + local.get 1 + f32.load offset=12 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=4 + local.get 1 + f32.load + local.get 2 + call $miden::account::get_map_item + local.get 2 + local.get 2 + i64.load offset=8 + i64.store offset=24 + local.get 2 + local.get 2 + i64.load + i64.store offset=16 + local.get 0 + local.get 2 + i32.const 16 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 2 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $counter_contract::bindings::__link_custom_section_describing_imports (;2;) (type 0)) + (func $miden:counter-contract/counter@0.1.0#get-count (;3;) (type 2) (result f32) + (local i32 f32 f32 f32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 0 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 1 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 2 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 3 + local.get 0 + i32.const 1 + call $intrinsics::felt::from_u32 + f32.store offset=12 + local.get 0 + local.get 3 + f32.store offset=8 + local.get 0 + local.get 2 + f32.store offset=4 + local.get 0 + local.get 1 + f32.store + local.get 0 + i32.const 16 + i32.add + local.get 0 + call $miden_base_sys::bindings::storage::get_map_item + local.get 0 + f32.load offset=28 + local.set 1 + local.get 0 + i32.const 32 + i32.add + global.set $__stack_pointer + local.get 1 + ) + (func $miden:counter-contract/counter@0.1.0#increment-count (;4;) (type 2) (result f32) + (local i32 f32 f32 f32 f32 f32) + global.get $__stack_pointer + i32.const 128 + i32.sub + local.tee 0 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 1 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 2 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 3 + local.get 0 + i32.const 1 + call $intrinsics::felt::from_u32 + local.tee 4 + f32.store offset=12 + local.get 0 + local.get 3 + f32.store offset=8 + local.get 0 + local.get 2 + f32.store offset=4 + local.get 0 + local.get 1 + f32.store + local.get 0 + i32.const 64 + i32.add + local.get 0 + call $miden_base_sys::bindings::storage::get_map_item + local.get 0 + i32.const 48 + i32.add + local.get 0 + f32.load offset=76 + i32.const 1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.tee 5 + call $>::from + i32.const 0 + call $>::from + local.get 4 + local.get 3 + local.get 2 + local.get 1 + local.get 0 + f32.load offset=60 + local.get 0 + f32.load offset=56 + local.get 0 + f32.load offset=52 + local.get 0 + f32.load offset=48 + local.get 0 + i32.const 64 + i32.add + call $miden::account::set_map_item + local.get 0 + local.get 0 + i64.load offset=72 + i64.store offset=104 + local.get 0 + local.get 0 + i64.load offset=64 + i64.store offset=96 + local.get 0 + local.get 0 + i32.const 88 + i32.add + i64.load + i64.store offset=120 + local.get 0 + local.get 0 + i64.load offset=80 + i64.store offset=112 + local.get 0 + i32.const 16 + i32.add + local.get 0 + i32.const 96 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 0 + i32.const 32 + i32.add + local.get 0 + i32.const 112 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 0 + i32.const 128 + i32.add + global.set $__stack_pointer + local.get 5 + ) + (func $wit_bindgen::rt::run_ctors_once (;5;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048584 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $>::from (;6;) (type 3) (param i32) (result f32) + local.get 0 + i32.const 255 + i32.and + f32.reinterpret_i32 + ) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;7;) (type 1) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $>::from (;8;) (type 4) (param i32 f32) + (local f32 f32 f32) + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 2 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 3 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 4 + local.get 0 + local.get 1 + f32.store offset=12 + local.get 0 + local.get 4 + f32.store offset=8 + local.get 0 + local.get 3 + f32.store offset=4 + local.get 0 + local.get 2 + f32.store + ) + (func $intrinsics::felt::add (;9;) (type 5) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;10;) (type 3) (param i32) (result f32) + unreachable + ) + (func $miden::account::get_map_item (;11;) (type 6) (param f32 f32 f32 f32 f32 i32) + unreachable + ) + (func $miden::account::set_map_item (;12;) (type 7) (param f32 f32 f32 f32 f32 f32 f32 f32 f32 i32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + (@custom "rodata,miden_account" (after data) "!counter-contract\95A simple example of a Miden counter contract using the Account Storage API\0b0.1.0\03\01\03\01\00\01\13count_map\019counter contract storage map") + ) + (alias export 0 "felt" (type (;1;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;2;) (func (result 1))) + (alias core export 0 "miden:counter-contract/counter@0.1.0#get-count" (core func (;0;))) + (func (;0;) (type 2) (canon lift (core func 0))) + (alias core export 0 "miden:counter-contract/counter@0.1.0#increment-count" (core func (;1;))) + (func (;1;) (type 2) (canon lift (core func 1))) + (alias export 0 "felt" (type (;3;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (import "import-type-felt0" (type (;2;) (eq 1))) + (type (;3;) (func (result 2))) + (import "import-func-get-count" (func (;0;) (type 3))) + (import "import-func-increment-count" (func (;1;) (type 3))) + (export (;4;) "felt" (type 1)) + (type (;5;) (func (result 4))) + (export (;2;) "get-count" (func 0) (func (type 5))) + (export (;3;) "increment-count" (func 1) (func (type 5))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-get-count" (func 0)) + (with "import-func-increment-count" (func 1)) + (with "import-type-felt" (type 3)) + (with "import-type-felt0" (type 1)) + ) + ) + (export (;2;) "miden:counter-contract/counter@0.1.0" (instance 1)) +) diff --git a/tests/integration/expected/examples/counter_note.hir b/tests/integration/expected/examples/counter_note.hir new file mode 100644 index 000000000..1f3fb0d10 --- /dev/null +++ b/tests/integration/expected/examples/counter_note.hir @@ -0,0 +1,106 @@ +builtin.component miden:base/note-script@1.0.0 { + builtin.module public @counter_note { + private builtin.function @counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0() -> felt { + ^block3: + v0 = hir.call : felt #[callee = miden:counter-contract/counter@0.1.0/get-count] #[signature = (result felt)]; + builtin.ret v0; + }; + + private builtin.function @counter_note::bindings::miden::counter_contract::counter::increment_count::wit_import0() -> felt { + ^block6: + v1 = hir.call : felt #[callee = miden:counter-contract/counter@0.1.0/increment-count] #[signature = (result felt)]; + builtin.ret v1; + }; + + private builtin.function @__wasm_call_ctors() { + ^block10: + builtin.ret ; + }; + + private builtin.function @counter_note::bindings::__link_custom_section_describing_imports() { + ^block12: + builtin.ret ; + }; + + private builtin.function @miden:base/note-script@1.0.0#run(v2: felt, v3: felt, v4: felt, v5: felt) { + ^block14(v2: felt, v3: felt, v4: felt, v5: felt): + hir.exec @miden:base/note-script@1.0.0/counter_note/wit_bindgen::rt::run_ctors_once() + v7 = hir.exec @miden:base/note-script@1.0.0/counter_note/counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0() : felt + v8 = hir.exec @miden:base/note-script@1.0.0/counter_note/counter_note::bindings::miden::counter_contract::counter::increment_count::wit_import0() : felt + v9 = arith.constant 1 : i32; + v10 = hir.exec @miden:base/note-script@1.0.0/counter_note/intrinsics::felt::from_u32(v9) : felt + v11 = hir.exec @miden:base/note-script@1.0.0/counter_note/intrinsics::felt::add(v7, v10) : felt + v12 = hir.exec @miden:base/note-script@1.0.0/counter_note/counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0() : felt + hir.exec @miden:base/note-script@1.0.0/counter_note/intrinsics::felt::assert_eq(v12, v11) + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block16: + v14 = builtin.global_symbol @miden:base/note-script@1.0.0/counter_note/GOT.data.internal.__memory_base : ptr + v15 = hir.bitcast v14 : ptr; + v16 = hir.load v15 : i32; + v17 = arith.constant 1048588 : i32; + v18 = arith.add v16, v17 : i32 #[overflow = wrapping]; + v19 = hir.bitcast v18 : u32; + v20 = hir.int_to_ptr v19 : ptr; + v21 = hir.load v20 : u8; + v13 = arith.constant 0 : i32; + v22 = arith.zext v21 : u32; + v23 = hir.bitcast v22 : i32; + v25 = arith.neq v23, v13 : i1; + scf.if v25{ + ^block18: + scf.yield ; + } else { + ^block19: + v26 = builtin.global_symbol @miden:base/note-script@1.0.0/counter_note/GOT.data.internal.__memory_base : ptr + v27 = hir.bitcast v26 : ptr; + v28 = hir.load v27 : i32; + hir.exec @miden:base/note-script@1.0.0/counter_note/__wasm_call_ctors() + v50 = arith.constant 1 : u8; + v52 = arith.constant 1048588 : i32; + v30 = arith.add v28, v52 : i32 #[overflow = wrapping]; + v34 = hir.bitcast v30 : u32; + v35 = hir.int_to_ptr v34 : ptr; + hir.store v35, v50; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::add(v36: felt, v37: felt) -> felt { + ^block20(v36: felt, v37: felt): + v38 = arith.add v36, v37 : felt #[overflow = unchecked]; + builtin.ret v38; + }; + + private builtin.function @intrinsics::felt::from_u32(v40: i32) -> felt { + ^block22(v40: i32): + v41 = hir.bitcast v40 : felt; + builtin.ret v41; + }; + + private builtin.function @intrinsics::felt::assert_eq(v43: felt, v44: felt) { + ^block24(v43: felt, v44: felt): + hir.assert_eq v43, v44; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x000000010000000100000001; + }; + + public builtin.function @run(v45: felt, v46: felt, v47: felt, v48: felt) { + ^block26(v45: felt, v46: felt, v47: felt, v48: felt): + hir.exec @miden:base/note-script@1.0.0/counter_note/miden:base/note-script@1.0.0#run(v45, v46, v47, v48) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/counter_note.masm b/tests/integration/expected/examples/counter_note.masm new file mode 100644 index 000000000..9e8acfd47 --- /dev/null +++ b/tests/integration/expected/examples/counter_note.masm @@ -0,0 +1,187 @@ +# mod miden:base/note-script@1.0.0 + +export.run + exec.::miden:base/note-script@1.0.0::init + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::miden:base/note-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[511068398781876708,4034132635148770913,11946245983825022717,413851799653899214] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/note-script@1.0.0::counter_note + +proc.counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0 + trace.240 + nop + call.::miden:counter-contract/counter@0.1.0::get-count + trace.252 + nop +end + +proc.counter_note::bindings::miden::counter_contract::counter::increment_count::wit_import0 + trace.240 + nop + call.::miden:counter-contract/counter@0.1.0::increment-count + trace.252 + nop +end + +proc.__wasm_call_ctors + nop +end + +proc.counter_note::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/note-script@1.0.0#run + drop + drop + drop + drop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::wit_bindgen::rt::run_ctors_once + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0 + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::counter_note::bindings::miden::counter_contract::counter::increment_count::wit_import0 + trace.252 + nop + drop + push.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::intrinsics::felt::add + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0 + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::intrinsics::felt::assert_eq + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048588 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::counter_note::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048588 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.intrinsics::felt::add + add +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + diff --git a/tests/integration/expected/examples/counter_note.wat b/tests/integration/expected/examples/counter_note.wat new file mode 100644 index 000000000..80fa7215e --- /dev/null +++ b/tests/integration/expected/examples/counter_note.wat @@ -0,0 +1,126 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (alias export 0 "felt" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (func (result 1))) + (export (;0;) "get-count" (func (type 2))) + (export (;1;) "increment-count" (func (type 2))) + ) + ) + (import "miden:counter-contract/counter@0.1.0" (instance (;1;) (type 2))) + (core module (;0;) + (type (;0;) (func (result f32))) + (type (;1;) (func)) + (type (;2;) (func (param f32 f32 f32 f32))) + (type (;3;) (func (param f32 f32) (result f32))) + (type (;4;) (func (param i32) (result f32))) + (type (;5;) (func (param f32 f32))) + (import "miden:counter-contract/counter@0.1.0" "get-count" (func $counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0 (;0;) (type 0))) + (import "miden:counter-contract/counter@0.1.0" "increment-count" (func $counter_note::bindings::miden::counter_contract::counter::increment_count::wit_import0 (;1;) (type 0))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/note-script@1.0.0#run" (func $miden:base/note-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $counter_note::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;2;) (type 1)) + (func $counter_note::bindings::__link_custom_section_describing_imports (;3;) (type 1)) + (func $miden:base/note-script@1.0.0#run (;4;) (type 2) (param f32 f32 f32 f32) + (local f32) + call $wit_bindgen::rt::run_ctors_once + call $counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0 + local.set 4 + call $counter_note::bindings::miden::counter_contract::counter::increment_count::wit_import0 + drop + local.get 4 + i32.const 1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 4 + call $counter_note::bindings::miden::counter_contract::counter::get_count::wit_import0 + local.get 4 + call $intrinsics::felt::assert_eq + ) + (func $wit_bindgen::rt::run_ctors_once (;5;) (type 1) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048588 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048588 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $intrinsics::felt::add (;6;) (type 3) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;7;) (type 4) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::assert_eq (;8;) (type 5) (param f32 f32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00\01\00\00\00") + ) + (alias export 0 "word" (type (;3;))) + (alias export 1 "get-count" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (alias export 1 "increment-count" (func (;1;))) + (core func (;1;) (canon lower (func 1))) + (core instance (;0;) + (export "get-count" (func 0)) + (export "increment-count" (func 1)) + ) + (core instance (;1;) (instantiate 0 + (with "miden:counter-contract/counter@0.1.0" (instance 0)) + ) + ) + (alias core export 1 "memory" (core memory (;0;))) + (type (;4;) (func (param "arg" 3))) + (alias core export 1 "miden:base/note-script@1.0.0#run" (core func (;2;))) + (func (;2;) (type 4) (canon lift (core func 2))) + (alias export 0 "felt" (type (;5;))) + (alias export 0 "word" (type (;6;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;2;) (instantiate 0 + (with "import-func-run" (func 2)) + (with "import-type-felt" (type 5)) + (with "import-type-word" (type 6)) + (with "import-type-word0" (type 3)) + ) + ) + (export (;3;) "miden:base/note-script@1.0.0" (instance 2)) +) diff --git a/tests/integration/expected/examples/fib.hir b/tests/integration/expected/examples/fib.hir new file mode 100644 index 000000000..849062295 --- /dev/null +++ b/tests/integration/expected/examples/fib.hir @@ -0,0 +1,44 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @fibonacci { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block4(v0: i32): + v2 = arith.constant 0 : i32; + v4 = arith.constant 1 : i32; + v58, v59, v60, v61 = scf.while v4, v0, v2 : i32, i32, i32, i32 { + ^block18(v62: i32, v63: i32, v64: i32): + v70 = arith.constant 0 : i32; + v71 = arith.constant 0 : i32; + v8 = arith.eq v63, v71 : i1; + v9 = arith.zext v8 : u32; + v10 = hir.bitcast v9 : i32; + v12 = arith.neq v10, v70 : i1; + v53, v54 = scf.if v12 : i32, i32 { + ^block17: + v25 = ub.poison i32 : i32; + scf.yield v25, v25; + } else { + ^block9: + v13 = arith.constant -1 : i32; + v14 = arith.add v63, v13 : i32 #[overflow = wrapping]; + v16 = arith.add v64, v62 : i32 #[overflow = wrapping]; + scf.yield v16, v14; + }; + v69 = ub.poison i32 : i32; + v55 = cf.select v12, v69, v62 : i32; + v24 = arith.constant 1 : u32; + v18 = arith.constant 0 : u32; + v57 = cf.select v12, v18, v24 : u32; + v47 = arith.trunc v57 : i1; + scf.condition v47, v53, v54, v55, v64; + } do { + ^block19(v65: i32, v66: i32, v67: i32, v68: i32): + scf.yield v65, v66, v67; + }; + builtin.ret v61; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/fib.masm b/tests/integration/expected/examples/fib.masm new file mode 100644 index 000000000..2f7c03ec3 --- /dev/null +++ b/tests/integration/expected/examples/fib.masm @@ -0,0 +1,68 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::fibonacci + +export.entrypoint + push.0 + push.1 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.0 + dup.3 + eq + neq + dup.0 + if.true + movup.2 + drop + push.3735929054 + dup.0 + else + push.4294967295 + movup.3 + u32wrapping_add + dup.3 + dup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + movup.2 + swap.5 + movdn.2 + cdrop + push.1 + push.0 + movup.4 + cdrop + push.1 + u32and + swap.1 + swap.3 + swap.1 + if.true + movup.3 + drop + push.1 + else + push.0 + end + end + drop + drop + drop +end + diff --git a/tests/integration/expected/examples/fib.wat b/tests/integration/expected/examples/fib.wat new file mode 100644 index 000000000..fdd90fef1 --- /dev/null +++ b/tests/integration/expected/examples/fib.wat @@ -0,0 +1,35 @@ +(module $fibonacci.wasm + (type (;0;) (func (param i32) (result i32))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + (local i32 i32 i32) + i32.const 0 + local.set 1 + i32.const 1 + local.set 2 + block ;; label = @1 + loop ;; label = @2 + local.get 2 + local.set 3 + local.get 0 + i32.eqz + br_if 1 (;@1;) + local.get 0 + i32.const -1 + i32.add + local.set 0 + local.get 1 + local.get 3 + i32.add + local.set 2 + local.get 3 + local.set 1 + br 0 (;@2;) + end + end + local.get 1 + ) +) diff --git a/tests/integration/expected/examples/p2id.hir b/tests/integration/expected/examples/p2id.hir new file mode 100644 index 000000000..443ba22ba --- /dev/null +++ b/tests/integration/expected/examples/p2id.hir @@ -0,0 +1,1260 @@ +builtin.component miden:base/note-script@1.0.0 { + builtin.module public @p2id { + private builtin.function @p2id::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import7(v0: felt, v1: felt, v2: felt, v3: felt) { + ^block3(v0: felt, v1: felt, v2: felt, v3: felt): + hir.call v0, v1, v2, v3 #[callee = miden:basic-wallet/basic-wallet@0.1.0/receive-asset] #[signature = (param felt) (param felt) (param felt) (param felt)]; + builtin.ret ; + }; + + private builtin.function @__wasm_call_ctors() { + ^block8: + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_alloc(v4: i32, v5: i32) -> i32 { + ^block10(v4: i32, v5: i32): + v7 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/GOT.data.internal.__memory_base : ptr + v8 = hir.bitcast v7 : ptr; + v9 = hir.load v8 : i32; + v10 = arith.constant 1048664 : i32; + v11 = arith.add v9, v10 : i32 #[overflow = wrapping]; + v12 = hir.exec @miden:base/note-script@1.0.0/p2id/::alloc(v11, v5, v4) : i32 + builtin.ret v12; + }; + + private builtin.function @__rustc::__rust_dealloc(v13: i32, v14: i32, v15: i32) { + ^block12(v13: i32, v14: i32, v15: i32): + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_alloc_zeroed(v16: i32, v17: i32) -> i32 { + ^block14(v16: i32, v17: i32): + v19 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/GOT.data.internal.__memory_base : ptr + v20 = hir.bitcast v19 : ptr; + v21 = hir.load v20 : i32; + v22 = arith.constant 1048664 : i32; + v23 = arith.add v21, v22 : i32 #[overflow = wrapping]; + v24 = hir.exec @miden:base/note-script@1.0.0/p2id/::alloc(v23, v17, v16) : i32 + v876 = arith.constant 0 : i32; + v25 = arith.constant 0 : i32; + v26 = arith.eq v24, v25 : i1; + v27 = arith.zext v26 : u32; + v28 = hir.bitcast v27 : i32; + v30 = arith.neq v28, v876 : i1; + scf.if v30{ + ^block16: + scf.yield ; + } else { + ^block17: + v874 = arith.constant 0 : i32; + v875 = arith.constant 0 : i32; + v32 = arith.eq v16, v875 : i1; + v33 = arith.zext v32 : u32; + v34 = hir.bitcast v33 : i32; + v36 = arith.neq v34, v874 : i1; + scf.if v36{ + ^block112: + scf.yield ; + } else { + ^block18: + v868 = arith.constant 0 : u8; + v39 = hir.bitcast v16 : u32; + v40 = hir.bitcast v24 : u32; + v41 = hir.int_to_ptr v40 : ptr; + hir.mem_set v41, v39, v868; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v24; + }; + + private builtin.function @p2id::bindings::__link_custom_section_describing_imports() { + ^block19: + builtin.ret ; + }; + + private builtin.function @miden:base/note-script@1.0.0#run(v43: felt, v44: felt, v45: felt, v46: felt) { + ^block21(v43: felt, v44: felt, v45: felt, v46: felt): + v50 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v51 = hir.bitcast v50 : ptr; + v52 = hir.load v51 : i32; + v53 = arith.constant 48 : i32; + v54 = arith.sub v52, v53 : i32 #[overflow = wrapping]; + v55 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v56 = hir.bitcast v55 : ptr; + hir.store v56, v54; + hir.exec @miden:base/note-script@1.0.0/p2id/wit_bindgen::rt::run_ctors_once() + v57 = arith.constant 16 : i32; + v58 = arith.add v54, v57 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/miden_base_sys::bindings::note::get_inputs(v58) + v60 = arith.constant 24 : u32; + v59 = hir.bitcast v54 : u32; + v61 = arith.add v59, v60 : u32 #[overflow = checked]; + v62 = arith.constant 4 : u32; + v63 = arith.mod v61, v62 : u32; + hir.assertz v63 #[code = 250]; + v64 = hir.int_to_ptr v61 : ptr; + v65 = hir.load v64 : i32; + v66 = hir.cast v65 : u32; + v934 = scf.index_switch v66 : u32 + case 0 { + ^block117: + v978 = arith.constant 1 : u32; + scf.yield v978; + } + case 1 { + ^block118: + v977 = arith.constant 1 : u32; + scf.yield v977; + } + default { + ^block24: + v68 = arith.constant 20 : u32; + v67 = hir.bitcast v54 : u32; + v69 = arith.add v67, v68 : u32 #[overflow = checked]; + v1011 = arith.constant 4 : u32; + v71 = arith.mod v69, v1011 : u32; + hir.assertz v71 #[code = 250]; + v72 = hir.int_to_ptr v69 : ptr; + v73 = hir.load v72 : i32; + v1010 = arith.constant 4 : u32; + v74 = hir.bitcast v73 : u32; + v76 = arith.add v74, v1010 : u32 #[overflow = checked]; + v1009 = arith.constant 4 : u32; + v78 = arith.mod v76, v1009 : u32; + hir.assertz v78 #[code = 250]; + v79 = hir.int_to_ptr v76 : ptr; + v80 = hir.load v79 : felt; + v81 = hir.bitcast v73 : u32; + v1008 = arith.constant 4 : u32; + v83 = arith.mod v81, v1008 : u32; + hir.assertz v83 #[code = 250]; + v84 = hir.int_to_ptr v81 : ptr; + v85 = hir.load v84 : felt; + v86 = arith.constant 8 : i32; + v87 = arith.add v54, v86 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/miden_base_sys::bindings::account::get_id(v87) + v89 = arith.constant 12 : u32; + v88 = hir.bitcast v54 : u32; + v90 = arith.add v88, v89 : u32 #[overflow = checked]; + v1007 = arith.constant 4 : u32; + v92 = arith.mod v90, v1007 : u32; + hir.assertz v92 #[code = 250]; + v93 = hir.int_to_ptr v90 : ptr; + v94 = hir.load v93 : felt; + v96 = arith.constant 8 : u32; + v95 = hir.bitcast v54 : u32; + v97 = arith.add v95, v96 : u32 #[overflow = checked]; + v1006 = arith.constant 4 : u32; + v99 = arith.mod v97, v1006 : u32; + hir.assertz v99 #[code = 250]; + v100 = hir.int_to_ptr v97 : ptr; + v101 = hir.load v100 : felt; + v102 = hir.exec @miden:base/note-script@1.0.0/p2id/intrinsics::felt::eq(v101, v85) : i32 + v47 = arith.constant 0 : i32; + v103 = arith.constant 1 : i32; + v104 = arith.neq v102, v103 : i1; + v105 = arith.zext v104 : u32; + v106 = hir.bitcast v105 : i32; + v108 = arith.neq v106, v47 : i1; + v936 = scf.if v108 : u32 { + ^block116: + v886 = arith.constant 1 : u32; + scf.yield v886; + } else { + ^block25: + v109 = hir.exec @miden:base/note-script@1.0.0/p2id/intrinsics::felt::eq(v94, v80) : i32 + v1004 = arith.constant 0 : i32; + v1005 = arith.constant 1 : i32; + v111 = arith.neq v109, v1005 : i1; + v112 = arith.zext v111 : u32; + v113 = hir.bitcast v112 : i32; + v115 = arith.neq v113, v1004 : i1; + scf.if v115{ + ^block115: + scf.yield ; + } else { + ^block26: + v116 = arith.constant 28 : i32; + v117 = arith.add v54, v116 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/miden_base_sys::bindings::note::get_assets(v117) + v119 = arith.constant 36 : u32; + v118 = hir.bitcast v54 : u32; + v120 = arith.add v118, v119 : u32 #[overflow = checked]; + v1003 = arith.constant 4 : u32; + v122 = arith.mod v120, v1003 : u32; + hir.assertz v122 #[code = 250]; + v123 = hir.int_to_ptr v120 : ptr; + v124 = hir.load v123 : i32; + v129 = arith.constant 28 : u32; + v128 = hir.bitcast v54 : u32; + v130 = arith.add v128, v129 : u32 #[overflow = checked]; + v1002 = arith.constant 4 : u32; + v132 = arith.mod v130, v1002 : u32; + hir.assertz v132 #[code = 250]; + v133 = hir.int_to_ptr v130 : ptr; + v134 = hir.load v133 : i32; + v136 = arith.constant 32 : u32; + v135 = hir.bitcast v54 : u32; + v137 = arith.add v135, v136 : u32 #[overflow = checked]; + v1001 = arith.constant 4 : u32; + v139 = arith.mod v137, v1001 : u32; + hir.assertz v139 #[code = 250]; + v140 = hir.int_to_ptr v137 : ptr; + v141 = hir.load v140 : i32; + v1000 = arith.constant 4 : u32; + v127 = arith.shl v124, v1000 : i32; + v954, v955, v956, v957, v958, v959, v960, v961 = scf.while v127, v141, v54, v141, v134 : i32, i32, i32, i32, i32, i32, i32, i32 { + ^block130(v962: i32, v963: i32, v964: i32, v965: i32, v966: i32): + v998 = arith.constant 0 : i32; + v999 = arith.constant 0 : i32; + v144 = arith.eq v962, v999 : i1; + v145 = arith.zext v144 : u32; + v146 = hir.bitcast v145 : i32; + v148 = arith.neq v146, v998 : i1; + v947, v948 = scf.if v148 : i32, i32 { + ^block129: + v887 = ub.poison i32 : i32; + scf.yield v887, v887; + } else { + ^block30: + v150 = hir.bitcast v963 : u32; + v997 = arith.constant 4 : u32; + v152 = arith.mod v150, v997 : u32; + hir.assertz v152 #[code = 250]; + v153 = hir.int_to_ptr v150 : ptr; + v154 = hir.load v153 : felt; + v996 = arith.constant 4 : u32; + v155 = hir.bitcast v963 : u32; + v157 = arith.add v155, v996 : u32 #[overflow = checked]; + v995 = arith.constant 4 : u32; + v159 = arith.mod v157, v995 : u32; + hir.assertz v159 #[code = 250]; + v160 = hir.int_to_ptr v157 : ptr; + v161 = hir.load v160 : felt; + v994 = arith.constant 8 : u32; + v162 = hir.bitcast v963 : u32; + v164 = arith.add v162, v994 : u32 #[overflow = checked]; + v993 = arith.constant 4 : u32; + v166 = arith.mod v164, v993 : u32; + hir.assertz v166 #[code = 250]; + v167 = hir.int_to_ptr v164 : ptr; + v168 = hir.load v167 : felt; + v992 = arith.constant 12 : u32; + v169 = hir.bitcast v963 : u32; + v171 = arith.add v169, v992 : u32 #[overflow = checked]; + v991 = arith.constant 4 : u32; + v173 = arith.mod v171, v991 : u32; + hir.assertz v173 #[code = 250]; + v174 = hir.int_to_ptr v171 : ptr; + v175 = hir.load v174 : felt; + hir.exec @miden:base/note-script@1.0.0/p2id/p2id::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import7(v154, v161, v168, v175) + v990 = arith.constant 16 : i32; + v179 = arith.add v963, v990 : i32 #[overflow = wrapping]; + v176 = arith.constant -16 : i32; + v177 = arith.add v962, v176 : i32 #[overflow = wrapping]; + scf.yield v177, v179; + }; + v986 = ub.poison i32 : i32; + v951 = cf.select v148, v986, v966 : i32; + v987 = ub.poison i32 : i32; + v950 = cf.select v148, v987, v965 : i32; + v988 = ub.poison i32 : i32; + v949 = cf.select v148, v988, v964 : i32; + v989 = arith.constant 1 : u32; + v878 = arith.constant 0 : u32; + v953 = cf.select v148, v878, v989 : u32; + v931 = arith.trunc v953 : i1; + scf.condition v931, v947, v948, v949, v950, v951, v964, v965, v966; + } do { + ^block131(v967: i32, v968: i32, v969: i32, v970: i32, v971: i32, v972: i32, v973: i32, v974: i32): + scf.yield v967, v968, v969, v970, v971; + }; + v183 = arith.constant 44 : u32; + v182 = hir.bitcast v959 : u32; + v184 = arith.add v182, v183 : u32 #[overflow = checked]; + v985 = arith.constant 4 : u32; + v186 = arith.mod v184, v985 : u32; + hir.assertz v186 #[code = 250]; + v187 = hir.int_to_ptr v184 : ptr; + hir.store v187, v960; + v190 = arith.constant 40 : u32; + v189 = hir.bitcast v959 : u32; + v191 = arith.add v189, v190 : u32 #[overflow = checked]; + v984 = arith.constant 4 : u32; + v193 = arith.mod v191, v984 : u32; + hir.assertz v193 #[code = 250]; + v194 = hir.int_to_ptr v191 : ptr; + hir.store v194, v961; + v983 = arith.constant 16 : i32; + v195 = arith.constant 40 : i32; + v196 = arith.add v959, v195 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::raw_vec::RawVecInner::deallocate(v196, v983, v983) + v125 = arith.constant 4 : i32; + v982 = arith.constant 16 : i32; + v200 = arith.add v959, v982 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::raw_vec::RawVecInner::deallocate(v200, v125, v125) + v981 = arith.constant 48 : i32; + v204 = arith.add v959, v981 : i32 #[overflow = wrapping]; + v205 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v206 = hir.bitcast v205 : ptr; + hir.store v206, v204; + scf.yield ; + }; + v979 = arith.constant 0 : u32; + v980 = arith.constant 1 : u32; + v975 = cf.select v115, v980, v979 : u32; + scf.yield v975; + }; + scf.yield v936; + }; + v976 = arith.constant 0 : u32; + v946 = arith.eq v934, v976 : i1; + cf.cond_br v946 ^block120, ^block23; + ^block23: + ub.unreachable ; + ^block120: + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block31: + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block33: + v208 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/GOT.data.internal.__memory_base : ptr + v209 = hir.bitcast v208 : ptr; + v210 = hir.load v209 : i32; + v211 = arith.constant 1048668 : i32; + v212 = arith.add v210, v211 : i32 #[overflow = wrapping]; + v213 = hir.bitcast v212 : u32; + v214 = hir.int_to_ptr v213 : ptr; + v215 = hir.load v214 : u8; + v207 = arith.constant 0 : i32; + v216 = arith.zext v215 : u32; + v217 = hir.bitcast v216 : i32; + v219 = arith.neq v217, v207 : i1; + scf.if v219{ + ^block35: + scf.yield ; + } else { + ^block36: + v220 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/GOT.data.internal.__memory_base : ptr + v221 = hir.bitcast v220 : ptr; + v222 = hir.load v221 : i32; + hir.exec @miden:base/note-script@1.0.0/p2id/__wasm_call_ctors() + v1013 = arith.constant 1 : u8; + v1015 = arith.constant 1048668 : i32; + v224 = arith.add v222, v1015 : i32 #[overflow = wrapping]; + v228 = hir.bitcast v224 : u32; + v229 = hir.int_to_ptr v228 : ptr; + hir.store v229, v1013; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @::alloc(v230: i32, v231: i32, v232: i32) -> i32 { + ^block37(v230: i32, v231: i32, v232: i32): + v235 = arith.constant 16 : i32; + v234 = arith.constant 0 : i32; + v1017 = arith.constant 16 : u32; + v237 = hir.bitcast v231 : u32; + v239 = arith.gt v237, v1017 : i1; + v240 = arith.zext v239 : u32; + v241 = hir.bitcast v240 : i32; + v243 = arith.neq v241, v234 : i1; + v244 = cf.select v243, v231, v235 : i32; + v1057 = arith.constant 0 : i32; + v245 = arith.constant -1 : i32; + v246 = arith.add v244, v245 : i32 #[overflow = wrapping]; + v247 = arith.band v244, v246 : i32; + v249 = arith.neq v247, v1057 : i1; + v1026, v1027 = scf.if v249 : i32, u32 { + ^block135: + v1018 = arith.constant 0 : u32; + v1022 = ub.poison i32 : i32; + scf.yield v1022, v1018; + } else { + ^block40: + v251 = hir.exec @miden:base/note-script@1.0.0/p2id/core::ptr::alignment::Alignment::max(v231, v244) : i32 + v1056 = arith.constant 0 : i32; + v250 = arith.constant -2147483648 : i32; + v252 = arith.sub v250, v251 : i32 #[overflow = wrapping]; + v254 = hir.bitcast v252 : u32; + v253 = hir.bitcast v232 : u32; + v255 = arith.gt v253, v254 : i1; + v256 = arith.zext v255 : u32; + v257 = hir.bitcast v256 : i32; + v259 = arith.neq v257, v1056 : i1; + v1041 = scf.if v259 : i32 { + ^block134: + v1055 = ub.poison i32 : i32; + scf.yield v1055; + } else { + ^block41: + v1053 = arith.constant 0 : i32; + v265 = arith.sub v1053, v251 : i32 #[overflow = wrapping]; + v1054 = arith.constant -1 : i32; + v261 = arith.add v232, v251 : i32 #[overflow = wrapping]; + v263 = arith.add v261, v1054 : i32 #[overflow = wrapping]; + v266 = arith.band v263, v265 : i32; + v267 = hir.bitcast v230 : u32; + v268 = arith.constant 4 : u32; + v269 = arith.mod v267, v268 : u32; + hir.assertz v269 #[code = 250]; + v270 = hir.int_to_ptr v267 : ptr; + v271 = hir.load v270 : i32; + v1052 = arith.constant 0 : i32; + v273 = arith.neq v271, v1052 : i1; + scf.if v273{ + ^block133: + scf.yield ; + } else { + ^block43: + v274 = hir.exec @miden:base/note-script@1.0.0/p2id/intrinsics::mem::heap_base() : i32 + v275 = hir.mem_size : u32; + v281 = hir.bitcast v230 : u32; + v1051 = arith.constant 4 : u32; + v283 = arith.mod v281, v1051 : u32; + hir.assertz v283 #[code = 250]; + v1050 = arith.constant 16 : u32; + v276 = hir.bitcast v275 : i32; + v279 = arith.shl v276, v1050 : i32; + v280 = arith.add v274, v279 : i32 #[overflow = wrapping]; + v284 = hir.int_to_ptr v281 : ptr; + hir.store v284, v280; + scf.yield ; + }; + v287 = hir.bitcast v230 : u32; + v1049 = arith.constant 4 : u32; + v289 = arith.mod v287, v1049 : u32; + hir.assertz v289 #[code = 250]; + v290 = hir.int_to_ptr v287 : ptr; + v291 = hir.load v290 : i32; + v1047 = arith.constant 0 : i32; + v1048 = arith.constant -1 : i32; + v293 = arith.bxor v291, v1048 : i32; + v295 = hir.bitcast v293 : u32; + v294 = hir.bitcast v266 : u32; + v296 = arith.gt v294, v295 : i1; + v297 = arith.zext v296 : u32; + v298 = hir.bitcast v297 : i32; + v300 = arith.neq v298, v1047 : i1; + v1040 = scf.if v300 : i32 { + ^block44: + v1046 = arith.constant 0 : i32; + scf.yield v1046; + } else { + ^block45: + v302 = hir.bitcast v230 : u32; + v1045 = arith.constant 4 : u32; + v304 = arith.mod v302, v1045 : u32; + hir.assertz v304 #[code = 250]; + v301 = arith.add v291, v266 : i32 #[overflow = wrapping]; + v305 = hir.int_to_ptr v302 : ptr; + hir.store v305, v301; + v307 = arith.add v291, v251 : i32 #[overflow = wrapping]; + scf.yield v307; + }; + scf.yield v1040; + }; + v1023 = arith.constant 1 : u32; + v1044 = arith.constant 0 : u32; + v1042 = cf.select v259, v1044, v1023 : u32; + scf.yield v1041, v1042; + }; + v1043 = arith.constant 0 : u32; + v1039 = arith.eq v1027, v1043 : i1; + cf.cond_br v1039 ^block39, ^block137(v1026); + ^block39: + ub.unreachable ; + ^block137(v1019: i32): + builtin.ret v1019; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block46: + v310 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v310; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::with_capacity_in(v312: i32, v313: i32, v314: i32, v315: i32) { + ^block50(v312: i32, v313: i32, v314: i32, v315: i32): + v317 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v318 = hir.bitcast v317 : ptr; + v319 = hir.load v318 : i32; + v320 = arith.constant 16 : i32; + v321 = arith.sub v319, v320 : i32 #[overflow = wrapping]; + v322 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v323 = hir.bitcast v322 : ptr; + hir.store v323, v321; + v316 = arith.constant 0 : i32; + v326 = arith.constant 256 : i32; + v324 = arith.constant 4 : i32; + v325 = arith.add v321, v324 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::raw_vec::RawVecInner::try_allocate_in(v325, v326, v316, v313, v314) + v329 = arith.constant 8 : u32; + v328 = hir.bitcast v321 : u32; + v330 = arith.add v328, v329 : u32 #[overflow = checked]; + v331 = arith.constant 4 : u32; + v332 = arith.mod v330, v331 : u32; + hir.assertz v332 #[code = 250]; + v333 = hir.int_to_ptr v330 : ptr; + v334 = hir.load v333 : i32; + v1068 = arith.constant 4 : u32; + v335 = hir.bitcast v321 : u32; + v337 = arith.add v335, v1068 : u32 #[overflow = checked]; + v1067 = arith.constant 4 : u32; + v339 = arith.mod v337, v1067 : u32; + hir.assertz v339 #[code = 250]; + v340 = hir.int_to_ptr v337 : ptr; + v341 = hir.load v340 : i32; + v1066 = arith.constant 0 : i32; + v342 = arith.constant 1 : i32; + v343 = arith.neq v341, v342 : i1; + v344 = arith.zext v343 : u32; + v345 = hir.bitcast v344 : i32; + v347 = arith.neq v345, v1066 : i1; + cf.cond_br v347 ^block52, ^block53; + ^block52: + v356 = arith.constant 12 : u32; + v355 = hir.bitcast v321 : u32; + v357 = arith.add v355, v356 : u32 #[overflow = checked]; + v1065 = arith.constant 4 : u32; + v359 = arith.mod v357, v1065 : u32; + hir.assertz v359 #[code = 250]; + v360 = hir.int_to_ptr v357 : ptr; + v361 = hir.load v360 : i32; + v1064 = arith.constant 4 : u32; + v362 = hir.bitcast v312 : u32; + v364 = arith.add v362, v1064 : u32 #[overflow = checked]; + v1063 = arith.constant 4 : u32; + v366 = arith.mod v364, v1063 : u32; + hir.assertz v366 #[code = 250]; + v367 = hir.int_to_ptr v364 : ptr; + hir.store v367, v361; + v368 = hir.bitcast v312 : u32; + v1062 = arith.constant 4 : u32; + v370 = arith.mod v368, v1062 : u32; + hir.assertz v370 #[code = 250]; + v371 = hir.int_to_ptr v368 : ptr; + hir.store v371, v334; + v1061 = arith.constant 16 : i32; + v373 = arith.add v321, v1061 : i32 #[overflow = wrapping]; + v374 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v375 = hir.bitcast v374 : ptr; + hir.store v375, v373; + builtin.ret ; + ^block53: + v1060 = arith.constant 12 : u32; + v348 = hir.bitcast v321 : u32; + v350 = arith.add v348, v1060 : u32 #[overflow = checked]; + v1059 = arith.constant 4 : u32; + v352 = arith.mod v350, v1059 : u32; + hir.assertz v352 #[code = 250]; + v353 = hir.int_to_ptr v350 : ptr; + v354 = hir.load v353 : i32; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::raw_vec::handle_error(v334, v354, v315) + ub.unreachable ; + }; + + private builtin.function @miden_base_sys::bindings::account::get_id(v376: i32) { + ^block54(v376: i32): + v378 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v379 = hir.bitcast v378 : ptr; + v380 = hir.load v379 : i32; + v381 = arith.constant 16 : i32; + v382 = arith.sub v380, v381 : i32 #[overflow = wrapping]; + v383 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v384 = hir.bitcast v383 : ptr; + hir.store v384, v382; + v385 = arith.constant 8 : i32; + v386 = arith.add v382, v385 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/miden::account::get_id(v386) + v388 = arith.constant 8 : u32; + v387 = hir.bitcast v382 : u32; + v389 = arith.add v387, v388 : u32 #[overflow = checked]; + v390 = arith.constant 4 : u32; + v391 = arith.mod v389, v390 : u32; + hir.assertz v391 #[code = 250]; + v392 = hir.int_to_ptr v389 : ptr; + v393 = hir.load v392 : i64; + v394 = hir.bitcast v376 : u32; + v1070 = arith.constant 8 : u32; + v396 = arith.mod v394, v1070 : u32; + hir.assertz v396 #[code = 250]; + v397 = hir.int_to_ptr v394 : ptr; + hir.store v397, v393; + v1069 = arith.constant 16 : i32; + v399 = arith.add v382, v1069 : i32 #[overflow = wrapping]; + v400 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v401 = hir.bitcast v400 : ptr; + hir.store v401, v399; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::note::get_inputs(v402: i32) { + ^block56(v402: i32): + v404 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v405 = hir.bitcast v404 : ptr; + v406 = hir.load v405 : i32; + v407 = arith.constant 16 : i32; + v408 = arith.sub v406, v407 : i32 #[overflow = wrapping]; + v409 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v410 = hir.bitcast v409 : ptr; + hir.store v410, v408; + v415 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/GOT.data.internal.__memory_base : ptr + v416 = hir.bitcast v415 : ptr; + v417 = hir.load v416 : i32; + v418 = arith.constant 1048632 : i32; + v419 = arith.add v417, v418 : i32 #[overflow = wrapping]; + v413 = arith.constant 4 : i32; + v411 = arith.constant 8 : i32; + v412 = arith.add v408, v411 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::raw_vec::RawVecInner::with_capacity_in(v412, v413, v413, v419) + v421 = arith.constant 8 : u32; + v420 = hir.bitcast v408 : u32; + v422 = arith.add v420, v421 : u32 #[overflow = checked]; + v423 = arith.constant 4 : u32; + v424 = arith.mod v422, v423 : u32; + hir.assertz v424 #[code = 250]; + v425 = hir.int_to_ptr v422 : ptr; + v426 = hir.load v425 : i32; + v428 = arith.constant 12 : u32; + v427 = hir.bitcast v408 : u32; + v429 = arith.add v427, v428 : u32 #[overflow = checked]; + v1078 = arith.constant 4 : u32; + v431 = arith.mod v429, v1078 : u32; + hir.assertz v431 #[code = 250]; + v432 = hir.int_to_ptr v429 : ptr; + v433 = hir.load v432 : i32; + v1071 = arith.constant 2 : u32; + v435 = hir.bitcast v433 : u32; + v437 = arith.shr v435, v1071 : u32; + v438 = hir.bitcast v437 : i32; + v439 = hir.exec @miden:base/note-script@1.0.0/p2id/miden::note::get_inputs(v438) : i32 + v1077 = arith.constant 8 : u32; + v440 = hir.bitcast v402 : u32; + v442 = arith.add v440, v1077 : u32 #[overflow = checked]; + v1076 = arith.constant 4 : u32; + v444 = arith.mod v442, v1076 : u32; + hir.assertz v444 #[code = 250]; + v445 = hir.int_to_ptr v442 : ptr; + hir.store v445, v439; + v1075 = arith.constant 4 : u32; + v446 = hir.bitcast v402 : u32; + v448 = arith.add v446, v1075 : u32 #[overflow = checked]; + v1074 = arith.constant 4 : u32; + v450 = arith.mod v448, v1074 : u32; + hir.assertz v450 #[code = 250]; + v451 = hir.int_to_ptr v448 : ptr; + hir.store v451, v433; + v452 = hir.bitcast v402 : u32; + v1073 = arith.constant 4 : u32; + v454 = arith.mod v452, v1073 : u32; + hir.assertz v454 #[code = 250]; + v455 = hir.int_to_ptr v452 : ptr; + hir.store v455, v426; + v1072 = arith.constant 16 : i32; + v457 = arith.add v408, v1072 : i32 #[overflow = wrapping]; + v458 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v459 = hir.bitcast v458 : ptr; + hir.store v459, v457; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::note::get_assets(v460: i32) { + ^block58(v460: i32): + v462 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v463 = hir.bitcast v462 : ptr; + v464 = hir.load v463 : i32; + v465 = arith.constant 16 : i32; + v466 = arith.sub v464, v465 : i32 #[overflow = wrapping]; + v467 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v468 = hir.bitcast v467 : ptr; + hir.store v468, v466; + v473 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/GOT.data.internal.__memory_base : ptr + v474 = hir.bitcast v473 : ptr; + v475 = hir.load v474 : i32; + v476 = arith.constant 1048648 : i32; + v477 = arith.add v475, v476 : i32 #[overflow = wrapping]; + v1087 = arith.constant 16 : i32; + v469 = arith.constant 8 : i32; + v470 = arith.add v466, v469 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::raw_vec::RawVecInner::with_capacity_in(v470, v1087, v1087, v477) + v479 = arith.constant 8 : u32; + v478 = hir.bitcast v466 : u32; + v480 = arith.add v478, v479 : u32 #[overflow = checked]; + v481 = arith.constant 4 : u32; + v482 = arith.mod v480, v481 : u32; + hir.assertz v482 #[code = 250]; + v483 = hir.int_to_ptr v480 : ptr; + v484 = hir.load v483 : i32; + v486 = arith.constant 12 : u32; + v485 = hir.bitcast v466 : u32; + v487 = arith.add v485, v486 : u32 #[overflow = checked]; + v1086 = arith.constant 4 : u32; + v489 = arith.mod v487, v1086 : u32; + hir.assertz v489 #[code = 250]; + v490 = hir.int_to_ptr v487 : ptr; + v491 = hir.load v490 : i32; + v1079 = arith.constant 2 : u32; + v493 = hir.bitcast v491 : u32; + v495 = arith.shr v493, v1079 : u32; + v496 = hir.bitcast v495 : i32; + v497 = hir.exec @miden:base/note-script@1.0.0/p2id/miden::note::get_assets(v496) : i32 + v1085 = arith.constant 8 : u32; + v498 = hir.bitcast v460 : u32; + v500 = arith.add v498, v1085 : u32 #[overflow = checked]; + v1084 = arith.constant 4 : u32; + v502 = arith.mod v500, v1084 : u32; + hir.assertz v502 #[code = 250]; + v503 = hir.int_to_ptr v500 : ptr; + hir.store v503, v497; + v1083 = arith.constant 4 : u32; + v504 = hir.bitcast v460 : u32; + v506 = arith.add v504, v1083 : u32 #[overflow = checked]; + v1082 = arith.constant 4 : u32; + v508 = arith.mod v506, v1082 : u32; + hir.assertz v508 #[code = 250]; + v509 = hir.int_to_ptr v506 : ptr; + hir.store v509, v491; + v510 = hir.bitcast v460 : u32; + v1081 = arith.constant 4 : u32; + v512 = arith.mod v510, v1081 : u32; + hir.assertz v512 #[code = 250]; + v513 = hir.int_to_ptr v510 : ptr; + hir.store v513, v484; + v1080 = arith.constant 16 : i32; + v515 = arith.add v466, v1080 : i32 #[overflow = wrapping]; + v516 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v517 = hir.bitcast v516 : ptr; + hir.store v517, v515; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::eq(v518: felt, v519: felt) -> i32 { + ^block60(v518: felt, v519: felt): + v520 = arith.eq v518, v519 : i1; + v521 = hir.cast v520 : i32; + builtin.ret v521; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::deallocate(v523: i32, v524: i32, v525: i32) { + ^block62(v523: i32, v524: i32, v525: i32): + v527 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v528 = hir.bitcast v527 : ptr; + v529 = hir.load v528 : i32; + v530 = arith.constant 16 : i32; + v531 = arith.sub v529, v530 : i32 #[overflow = wrapping]; + v532 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v533 = hir.bitcast v532 : ptr; + hir.store v533, v531; + v534 = arith.constant 4 : i32; + v535 = arith.add v531, v534 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::raw_vec::RawVecInner::current_memory(v535, v523, v524, v525) + v537 = arith.constant 8 : u32; + v536 = hir.bitcast v531 : u32; + v538 = arith.add v536, v537 : u32 #[overflow = checked]; + v539 = arith.constant 4 : u32; + v540 = arith.mod v538, v539 : u32; + hir.assertz v540 #[code = 250]; + v541 = hir.int_to_ptr v538 : ptr; + v542 = hir.load v541 : i32; + v1094 = arith.constant 0 : i32; + v526 = arith.constant 0 : i32; + v544 = arith.eq v542, v526 : i1; + v545 = arith.zext v544 : u32; + v546 = hir.bitcast v545 : i32; + v548 = arith.neq v546, v1094 : i1; + scf.if v548{ + ^block143: + scf.yield ; + } else { + ^block65: + v1093 = arith.constant 4 : u32; + v549 = hir.bitcast v531 : u32; + v551 = arith.add v549, v1093 : u32 #[overflow = checked]; + v1092 = arith.constant 4 : u32; + v553 = arith.mod v551, v1092 : u32; + hir.assertz v553 #[code = 250]; + v554 = hir.int_to_ptr v551 : ptr; + v555 = hir.load v554 : i32; + v557 = arith.constant 12 : u32; + v556 = hir.bitcast v531 : u32; + v558 = arith.add v556, v557 : u32 #[overflow = checked]; + v1091 = arith.constant 4 : u32; + v560 = arith.mod v558, v1091 : u32; + hir.assertz v560 #[code = 250]; + v561 = hir.int_to_ptr v558 : ptr; + v562 = hir.load v561 : i32; + hir.exec @miden:base/note-script@1.0.0/p2id/::deallocate(v555, v542, v562) + scf.yield ; + }; + v1090 = arith.constant 16 : i32; + v565 = arith.add v531, v1090 : i32 #[overflow = wrapping]; + v566 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v567 = hir.bitcast v566 : ptr; + hir.store v567, v565; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::try_allocate_in(v568: i32, v569: i32, v570: i32, v571: i32, v572: i32) { + ^block66(v568: i32, v569: i32, v570: i32, v571: i32, v572: i32): + v575 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v576 = hir.bitcast v575 : ptr; + v577 = hir.load v576 : i32; + v578 = arith.constant 16 : i32; + v579 = arith.sub v577, v578 : i32 #[overflow = wrapping]; + v580 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v581 = hir.bitcast v580 : ptr; + hir.store v581, v579; + v591 = hir.bitcast v569 : u32; + v592 = arith.zext v591 : u64; + v593 = hir.bitcast v592 : i64; + v573 = arith.constant 0 : i32; + v586 = arith.sub v573, v571 : i32 #[overflow = wrapping]; + v583 = arith.constant -1 : i32; + v582 = arith.add v571, v572 : i32 #[overflow = wrapping]; + v584 = arith.add v582, v583 : i32 #[overflow = wrapping]; + v587 = arith.band v584, v586 : i32; + v588 = hir.bitcast v587 : u32; + v589 = arith.zext v588 : u64; + v590 = hir.bitcast v589 : i64; + v594 = arith.mul v590, v593 : i64 #[overflow = wrapping]; + v1198 = arith.constant 0 : i32; + v595 = arith.constant 32 : i64; + v597 = hir.cast v595 : u32; + v596 = hir.bitcast v594 : u64; + v598 = arith.shr v596, v597 : u64; + v599 = hir.bitcast v598 : i64; + v600 = arith.trunc v599 : i32; + v602 = arith.neq v600, v1198 : i1; + v1110, v1111, v1112, v1113, v1114, v1115 = scf.if v602 : i32, i32, i32, i32, i32, u32 { + ^block145: + v1095 = arith.constant 0 : u32; + v1102 = ub.poison i32 : i32; + scf.yield v568, v579, v1102, v1102, v1102, v1095; + } else { + ^block71: + v603 = arith.trunc v594 : i32; + v1197 = arith.constant 0 : i32; + v604 = arith.constant -2147483648 : i32; + v605 = arith.sub v604, v571 : i32 #[overflow = wrapping]; + v607 = hir.bitcast v605 : u32; + v606 = hir.bitcast v603 : u32; + v608 = arith.lte v606, v607 : i1; + v609 = arith.zext v608 : u32; + v610 = hir.bitcast v609 : i32; + v612 = arith.neq v610, v1197 : i1; + v1158 = scf.if v612 : i32 { + ^block69: + v1196 = arith.constant 0 : i32; + v623 = arith.neq v603, v1196 : i1; + v1157 = scf.if v623 : i32 { + ^block73: + v1195 = arith.constant 0 : i32; + v639 = arith.neq v570, v1195 : i1; + v1156 = scf.if v639 : i32 { + ^block76: + v621 = arith.constant 1 : i32; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::alloc::Global::alloc_impl(v579, v571, v603, v621) + v650 = hir.bitcast v579 : u32; + v695 = arith.constant 4 : u32; + v652 = arith.mod v650, v695 : u32; + hir.assertz v652 #[code = 250]; + v653 = hir.int_to_ptr v650 : ptr; + v654 = hir.load v653 : i32; + scf.yield v654; + } else { + ^block77: + v640 = arith.constant 8 : i32; + v641 = arith.add v579, v640 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/::allocate(v641, v571, v603) + v625 = arith.constant 8 : u32; + v642 = hir.bitcast v579 : u32; + v644 = arith.add v642, v625 : u32 #[overflow = checked]; + v1194 = arith.constant 4 : u32; + v646 = arith.mod v644, v1194 : u32; + hir.assertz v646 #[code = 250]; + v647 = hir.int_to_ptr v644 : ptr; + v648 = hir.load v647 : i32; + scf.yield v648; + }; + v1192 = arith.constant 0 : i32; + v1193 = arith.constant 0 : i32; + v657 = arith.eq v1156, v1193 : i1; + v658 = arith.zext v657 : u32; + v659 = hir.bitcast v658 : i32; + v661 = arith.neq v659, v1192 : i1; + scf.if v661{ + ^block78: + v1191 = arith.constant 8 : u32; + v678 = hir.bitcast v568 : u32; + v680 = arith.add v678, v1191 : u32 #[overflow = checked]; + v1190 = arith.constant 4 : u32; + v682 = arith.mod v680, v1190 : u32; + hir.assertz v682 #[code = 250]; + v683 = hir.int_to_ptr v680 : ptr; + hir.store v683, v603; + v1189 = arith.constant 4 : u32; + v685 = hir.bitcast v568 : u32; + v687 = arith.add v685, v1189 : u32 #[overflow = checked]; + v1188 = arith.constant 4 : u32; + v689 = arith.mod v687, v1188 : u32; + hir.assertz v689 #[code = 250]; + v690 = hir.int_to_ptr v687 : ptr; + hir.store v690, v571; + scf.yield ; + } else { + ^block79: + v1187 = arith.constant 8 : u32; + v663 = hir.bitcast v568 : u32; + v665 = arith.add v663, v1187 : u32 #[overflow = checked]; + v1186 = arith.constant 4 : u32; + v667 = arith.mod v665, v1186 : u32; + hir.assertz v667 #[code = 250]; + v668 = hir.int_to_ptr v665 : ptr; + hir.store v668, v1156; + v1185 = arith.constant 4 : u32; + v670 = hir.bitcast v568 : u32; + v672 = arith.add v670, v1185 : u32 #[overflow = checked]; + v1184 = arith.constant 4 : u32; + v674 = arith.mod v672, v1184 : u32; + hir.assertz v674 #[code = 250]; + v675 = hir.int_to_ptr v672 : ptr; + hir.store v675, v569; + scf.yield ; + }; + v1182 = arith.constant 0 : i32; + v1183 = arith.constant 1 : i32; + v1155 = cf.select v661, v1183, v1182 : i32; + scf.yield v1155; + } else { + ^block74: + v1181 = arith.constant 8 : u32; + v624 = hir.bitcast v568 : u32; + v626 = arith.add v624, v1181 : u32 #[overflow = checked]; + v1180 = arith.constant 4 : u32; + v628 = arith.mod v626, v1180 : u32; + hir.assertz v628 #[code = 250]; + v629 = hir.int_to_ptr v626 : ptr; + hir.store v629, v571; + v1179 = arith.constant 4 : u32; + v632 = hir.bitcast v568 : u32; + v634 = arith.add v632, v1179 : u32 #[overflow = checked]; + v1178 = arith.constant 4 : u32; + v636 = arith.mod v634, v1178 : u32; + hir.assertz v636 #[code = 250]; + v1177 = arith.constant 0 : i32; + v637 = hir.int_to_ptr v634 : ptr; + hir.store v637, v1177; + v1176 = arith.constant 0 : i32; + scf.yield v1176; + }; + scf.yield v1157; + } else { + ^block72: + v1175 = ub.poison i32 : i32; + scf.yield v1175; + }; + v1170 = arith.constant 0 : u32; + v1103 = arith.constant 1 : u32; + v1163 = cf.select v612, v1103, v1170 : u32; + v1171 = ub.poison i32 : i32; + v1162 = cf.select v612, v579, v1171 : i32; + v1172 = ub.poison i32 : i32; + v1161 = cf.select v612, v568, v1172 : i32; + v1173 = ub.poison i32 : i32; + v1160 = cf.select v612, v1173, v579 : i32; + v1174 = ub.poison i32 : i32; + v1159 = cf.select v612, v1174, v568 : i32; + scf.yield v1159, v1160, v1161, v1158, v1162, v1163; + }; + v1116, v1117, v1118 = scf.index_switch v1115 : i32, i32, i32 + case 0 { + ^block70: + v1169 = arith.constant 4 : u32; + v615 = hir.bitcast v1110 : u32; + v617 = arith.add v615, v1169 : u32 #[overflow = checked]; + v1168 = arith.constant 4 : u32; + v619 = arith.mod v617, v1168 : u32; + hir.assertz v619 #[code = 250]; + v1167 = arith.constant 0 : i32; + v620 = hir.int_to_ptr v617 : ptr; + hir.store v620, v1167; + v1166 = arith.constant 1 : i32; + scf.yield v1110, v1166, v1111; + } + default { + ^block149: + scf.yield v1112, v1113, v1114; + }; + v694 = hir.bitcast v1116 : u32; + v1165 = arith.constant 4 : u32; + v696 = arith.mod v694, v1165 : u32; + hir.assertz v696 #[code = 250]; + v697 = hir.int_to_ptr v694 : ptr; + hir.store v697, v1117; + v1164 = arith.constant 16 : i32; + v702 = arith.add v1118, v1164 : i32 #[overflow = wrapping]; + v703 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v704 = hir.bitcast v703 : ptr; + hir.store v704, v702; + builtin.ret ; + }; + + private builtin.function @::allocate(v705: i32, v706: i32, v707: i32) { + ^block80(v705: i32, v706: i32, v707: i32): + v709 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v710 = hir.bitcast v709 : ptr; + v711 = hir.load v710 : i32; + v712 = arith.constant 16 : i32; + v713 = arith.sub v711, v712 : i32 #[overflow = wrapping]; + v714 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v715 = hir.bitcast v714 : ptr; + hir.store v715, v713; + v708 = arith.constant 0 : i32; + v716 = arith.constant 8 : i32; + v717 = arith.add v713, v716 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/p2id/alloc::alloc::Global::alloc_impl(v717, v706, v707, v708) + v720 = arith.constant 12 : u32; + v719 = hir.bitcast v713 : u32; + v721 = arith.add v719, v720 : u32 #[overflow = checked]; + v722 = arith.constant 4 : u32; + v723 = arith.mod v721, v722 : u32; + hir.assertz v723 #[code = 250]; + v724 = hir.int_to_ptr v721 : ptr; + v725 = hir.load v724 : i32; + v727 = arith.constant 8 : u32; + v726 = hir.bitcast v713 : u32; + v728 = arith.add v726, v727 : u32 #[overflow = checked]; + v1203 = arith.constant 4 : u32; + v730 = arith.mod v728, v1203 : u32; + hir.assertz v730 #[code = 250]; + v731 = hir.int_to_ptr v728 : ptr; + v732 = hir.load v731 : i32; + v733 = hir.bitcast v705 : u32; + v1202 = arith.constant 4 : u32; + v735 = arith.mod v733, v1202 : u32; + hir.assertz v735 #[code = 250]; + v736 = hir.int_to_ptr v733 : ptr; + hir.store v736, v732; + v1201 = arith.constant 4 : u32; + v737 = hir.bitcast v705 : u32; + v739 = arith.add v737, v1201 : u32 #[overflow = checked]; + v1200 = arith.constant 4 : u32; + v741 = arith.mod v739, v1200 : u32; + hir.assertz v741 #[code = 250]; + v742 = hir.int_to_ptr v739 : ptr; + hir.store v742, v725; + v1199 = arith.constant 16 : i32; + v744 = arith.add v713, v1199 : i32 #[overflow = wrapping]; + v745 = builtin.global_symbol @miden:base/note-script@1.0.0/p2id/__stack_pointer : ptr + v746 = hir.bitcast v745 : ptr; + hir.store v746, v744; + builtin.ret ; + }; + + private builtin.function @alloc::alloc::Global::alloc_impl(v747: i32, v748: i32, v749: i32, v750: i32) { + ^block82(v747: i32, v748: i32, v749: i32, v750: i32): + v1219 = arith.constant 0 : i32; + v751 = arith.constant 0 : i32; + v752 = arith.eq v749, v751 : i1; + v753 = arith.zext v752 : u32; + v754 = hir.bitcast v753 : i32; + v756 = arith.neq v754, v1219 : i1; + v1215 = scf.if v756 : i32 { + ^block152: + scf.yield v748; + } else { + ^block85: + hir.exec @miden:base/note-script@1.0.0/p2id/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v1218 = arith.constant 0 : i32; + v758 = arith.neq v750, v1218 : i1; + v1214 = scf.if v758 : i32 { + ^block86: + v760 = hir.exec @miden:base/note-script@1.0.0/p2id/__rustc::__rust_alloc_zeroed(v749, v748) : i32 + scf.yield v760; + } else { + ^block87: + v759 = hir.exec @miden:base/note-script@1.0.0/p2id/__rustc::__rust_alloc(v749, v748) : i32 + scf.yield v759; + }; + scf.yield v1214; + }; + v764 = arith.constant 4 : u32; + v763 = hir.bitcast v747 : u32; + v765 = arith.add v763, v764 : u32 #[overflow = checked]; + v1217 = arith.constant 4 : u32; + v767 = arith.mod v765, v1217 : u32; + hir.assertz v767 #[code = 250]; + v768 = hir.int_to_ptr v765 : ptr; + hir.store v768, v749; + v770 = hir.bitcast v747 : u32; + v1216 = arith.constant 4 : u32; + v772 = arith.mod v770, v1216 : u32; + hir.assertz v772 #[code = 250]; + v773 = hir.int_to_ptr v770 : ptr; + hir.store v773, v1215; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::current_memory(v774: i32, v775: i32, v776: i32, v777: i32) { + ^block88(v774: i32, v775: i32, v776: i32, v777: i32): + v1245 = arith.constant 0 : i32; + v778 = arith.constant 0 : i32; + v782 = arith.eq v777, v778 : i1; + v783 = arith.zext v782 : u32; + v784 = hir.bitcast v783 : i32; + v786 = arith.neq v784, v1245 : i1; + v1232, v1233 = scf.if v786 : i32, i32 { + ^block156: + v1244 = arith.constant 0 : i32; + v780 = arith.constant 4 : i32; + scf.yield v780, v1244; + } else { + ^block91: + v787 = hir.bitcast v775 : u32; + v822 = arith.constant 4 : u32; + v789 = arith.mod v787, v822 : u32; + hir.assertz v789 #[code = 250]; + v790 = hir.int_to_ptr v787 : ptr; + v791 = hir.load v790 : i32; + v1242 = arith.constant 0 : i32; + v1243 = arith.constant 0 : i32; + v793 = arith.eq v791, v1243 : i1; + v794 = arith.zext v793 : u32; + v795 = hir.bitcast v794 : i32; + v797 = arith.neq v795, v1242 : i1; + v1230 = scf.if v797 : i32 { + ^block155: + v1241 = arith.constant 0 : i32; + scf.yield v1241; + } else { + ^block92: + v1240 = arith.constant 4 : u32; + v798 = hir.bitcast v774 : u32; + v800 = arith.add v798, v1240 : u32 #[overflow = checked]; + v1239 = arith.constant 4 : u32; + v802 = arith.mod v800, v1239 : u32; + hir.assertz v802 #[code = 250]; + v803 = hir.int_to_ptr v800 : ptr; + hir.store v803, v776; + v1238 = arith.constant 4 : u32; + v804 = hir.bitcast v775 : u32; + v806 = arith.add v804, v1238 : u32 #[overflow = checked]; + v1237 = arith.constant 4 : u32; + v808 = arith.mod v806, v1237 : u32; + hir.assertz v808 #[code = 250]; + v809 = hir.int_to_ptr v806 : ptr; + v810 = hir.load v809 : i32; + v811 = hir.bitcast v774 : u32; + v1236 = arith.constant 4 : u32; + v813 = arith.mod v811, v1236 : u32; + hir.assertz v813 #[code = 250]; + v814 = hir.int_to_ptr v811 : ptr; + hir.store v814, v810; + v815 = arith.mul v791, v777 : i32 #[overflow = wrapping]; + scf.yield v815; + }; + v816 = arith.constant 8 : i32; + v1235 = arith.constant 4 : i32; + v1231 = cf.select v797, v1235, v816 : i32; + scf.yield v1231, v1230; + }; + v819 = arith.add v774, v1232 : i32 #[overflow = wrapping]; + v821 = hir.bitcast v819 : u32; + v1234 = arith.constant 4 : u32; + v823 = arith.mod v821, v1234 : u32; + hir.assertz v823 #[code = 250]; + v824 = hir.int_to_ptr v821 : ptr; + hir.store v824, v1233; + builtin.ret ; + }; + + private builtin.function @::deallocate(v825: i32, v826: i32, v827: i32) { + ^block93(v825: i32, v826: i32, v827: i32): + v1247 = arith.constant 0 : i32; + v828 = arith.constant 0 : i32; + v829 = arith.eq v827, v828 : i1; + v830 = arith.zext v829 : u32; + v831 = hir.bitcast v830 : i32; + v833 = arith.neq v831, v1247 : i1; + scf.if v833{ + ^block95: + scf.yield ; + } else { + ^block96: + hir.exec @miden:base/note-script@1.0.0/p2id/__rustc::__rust_dealloc(v825, v827, v826) + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::handle_error(v834: i32, v835: i32, v836: i32) { + ^block97(v834: i32, v835: i32, v836: i32): + ub.unreachable ; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v837: i32, v838: i32) -> i32 { + ^block99(v837: i32, v838: i32): + v845 = arith.constant 0 : i32; + v841 = hir.bitcast v838 : u32; + v840 = hir.bitcast v837 : u32; + v842 = arith.gt v840, v841 : i1; + v843 = arith.zext v842 : u32; + v844 = hir.bitcast v843 : i32; + v846 = arith.neq v844, v845 : i1; + v847 = cf.select v846, v837, v838 : i32; + builtin.ret v847; + }; + + private builtin.function @miden::account::get_id(v848: i32) { + ^block101(v848: i32): + v849, v850 = hir.exec @miden/account/get_id() : felt, felt + v851 = hir.bitcast v848 : u32; + v852 = hir.int_to_ptr v851 : ptr; + hir.store v852, v849; + v853 = arith.constant 4 : u32; + v854 = arith.add v851, v853 : u32 #[overflow = checked]; + v855 = hir.int_to_ptr v854 : ptr; + hir.store v855, v850; + builtin.ret ; + }; + + private builtin.function @miden::note::get_inputs(v856: i32) -> i32 { + ^block105(v856: i32): + v857, v858 = hir.exec @miden/note/get_inputs(v856) : i32, i32 + builtin.ret v857; + }; + + private builtin.function @miden::note::get_assets(v860: i32) -> i32 { + ^block108(v860: i32): + v861, v862 = hir.exec @miden/note/get_assets(v860) : i32, i32 + builtin.ret v861; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment readonly @1048576 = 0x0073722e65746f6e2f73676e69646e69622f6372732f302e372e302d7379732d657361622d6e6564696d; + + builtin.segment @1048620 = 0x0000002200000037000000290010000000000021000000190000002900100000000000010000000100000001; + }; + + public builtin.function @run(v864: felt, v865: felt, v866: felt, v867: felt) { + ^block110(v864: felt, v865: felt, v866: felt, v867: felt): + hir.exec @miden:base/note-script@1.0.0/p2id/miden:base/note-script@1.0.0#run(v864, v865, v866, v867) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/p2id.masm b/tests/integration/expected/examples/p2id.masm new file mode 100644 index 000000000..d04444f65 --- /dev/null +++ b/tests/integration/expected/examples/p2id.masm @@ -0,0 +1,2368 @@ +# mod miden:base/note-script@1.0.0 + +export.run + exec.::miden:base/note-script@1.0.0::init + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::miden:base/note-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[5382081793031148845,6684015720740591260,3762537607758069840,9612505383776912743] + adv.push_mapval + push.262144 + push.6 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278552 + push.0 + u32assert + mem_store.278553 +end + +# mod miden:base/note-script@1.0.0::p2id + +proc.p2id::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import7 + trace.240 + nop + call.::miden:basic-wallet/basic-wallet@0.1.0::receive-asset + trace.252 + nop +end + +proc.__wasm_call_ctors + nop +end + +proc.__rustc::__rust_alloc + push.1114212 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048664 + u32wrapping_add + movup.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::::alloc + trace.252 + nop +end + +proc.__rustc::__rust_dealloc + drop + drop + drop +end + +proc.__rustc::__rust_alloc_zeroed + push.1114212 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048664 + u32wrapping_add + dup.1 + swap.2 + swap.3 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::::alloc + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + swap.1 + drop + else + push.0 + push.0 + dup.3 + eq + neq + if.true + swap.1 + drop + else + push.0 + movup.2 + dup.2 + push.0 + dup.2 + push.0 + gte + while.true + dup.1 + dup.1 + push.1 + u32overflowing_madd + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + u32wrapping_add.1 + dup.0 + dup.3 + u32gte + end + dropw + end + end +end + +proc.p2id::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/note-script@1.0.0#run + drop + drop + drop + drop + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::miden_base_sys::bindings::note::get_inputs + trace.252 + nop + push.24 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.0 + push.2147483648 + u32lte + assert + dup.0 + push.1 + u32lte + if.true + eq.0 + if.true + drop + push.1 + else + drop + push.1 + end + else + drop + push.20 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::miden_base_sys::bindings::account::get_id + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + drop + push.1 + else + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + dup.0 + if.true + swap.1 + drop + else + push.28 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::miden_base_sys::bindings::note::get_assets + trace.252 + nop + push.36 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.28 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + movup.3 + swap.1 + u32shl + dup.1 + swap.3 + swap.4 + swap.5 + swap.2 + swap.1 + push.1 + while.true + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + movdn.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + dup.6 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movdn.3 + swap.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::p2id::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import7 + trace.252 + nop + push.16 + movup.3 + u32wrapping_add + push.4294967280 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.7 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.7 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.5 + dup.7 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.7 + cdrop + push.1 + u32and + swap.1 + swap.3 + swap.5 + swap.2 + swap.4 + swap.1 + if.true + movup.5 + drop + movup.5 + drop + movup.5 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + drop + push.44 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.40 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + push.40 + dup.2 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + push.4 + push.16 + dup.2 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + push.48 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.0 + push.1 + movup.2 + cdrop + end + end + push.0 + eq + if.true + nop + else + push.0 + assert + end +end + +proc.__rustc::__rust_no_alloc_shim_is_unstable_v2 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114212 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048668 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114212 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048668 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.::alloc + push.16 + push.0 + push.16 + dup.4 + swap.1 + u32gt + neq + dup.3 + swap.1 + cdrop + push.0 + push.4294967295 + dup.2 + u32wrapping_add + dup.2 + u32and + neq + if.true + dropw + push.0 + push.3735929054 + else + movup.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::core::ptr::alignment::Alignment::max + trace.252 + nop + push.0 + push.2147483648 + dup.2 + u32wrapping_sub + dup.4 + swap.1 + u32gt + neq + dup.0 + if.true + movdn.3 + drop + drop + drop + push.3735929054 + else + push.0 + dup.2 + u32wrapping_sub + push.4294967295 + movup.5 + dup.4 + u32wrapping_add + u32wrapping_add + u32and + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + neq + if.true + nop + else + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::intrinsics::mem::heap_base + trace.252 + nop + trace.240 + nop + exec.::intrinsics::mem::memory_size + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.16 + movup.2 + swap.1 + u32shl + movup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.4294967295 + dup.2 + u32xor + dup.3 + swap.1 + u32gt + neq + if.true + drop + drop + movdn.2 + drop + drop + push.0 + else + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + dup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + u32wrapping_add + end + end + push.1 + push.0 + movup.3 + cdrop + swap.1 + end + push.0 + movup.2 + eq + if.true + drop + push.0 + assert + else + nop + end +end + +proc.intrinsics::mem::heap_base + trace.240 + nop + exec.::intrinsics::mem::heap_base + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::with_capacity_in + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.256 + push.4 + dup.3 + u32wrapping_add + movup.4 + swap.6 + movdn.4 + movup.3 + swap.5 + movdn.3 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::raw_vec::RawVecInner::try_allocate_in + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + movup.3 + drop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + movup.2 + drop + push.12 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::raw_vec::handle_error + trace.252 + nop + push.0 + assert + end +end + +proc.miden_base_sys::bindings::account::get_id + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.8 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::miden::account::get_id + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.3 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_base_sys::bindings::note::get_inputs + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1114212 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048632 + u32wrapping_add + push.4 + push.8 + dup.3 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::raw_vec::RawVecInner::with_capacity_in + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.2 + dup.1 + swap.1 + u32shr + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::miden::note::get_inputs + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_base_sys::bindings::note::get_assets + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1114212 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048648 + u32wrapping_add + push.16 + push.8 + dup.3 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::raw_vec::RawVecInner::with_capacity_in + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.2 + dup.1 + swap.1 + u32shr + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::miden::note::get_assets + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.intrinsics::felt::eq + eq +end + +proc.alloc::raw_vec::RawVecInner::deallocate + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + u32wrapping_add + swap.1 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::raw_vec::RawVecInner::current_memory + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + else + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movdn.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::::deallocate + trace.252 + nop + end + push.16 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::try_allocate_in + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.2 + push.0 + push.0 + dup.7 + u32wrapping_sub + push.4294967295 + movup.9 + dup.9 + u32wrapping_add + u32wrapping_add + u32and + push.0 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + push.0 + push.32 + push.0 + dup.0 + push.2147483648 + u32and + eq.2147483648 + assertz + assertz + dup.0 + push.4294967295 + u32lte + assert + dup.3 + dup.3 + movup.2 + trace.240 + nop + exec.::std::math::u64::shr + trace.252 + nop + drop + neq + if.true + drop + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + push.3735929054 + dup.0 + dup.1 + swap.4 + swap.1 + swap.3 + swap.5 + else + drop + push.0 + push.2147483648 + dup.7 + u32wrapping_sub + dup.2 + swap.1 + u32lte + neq + dup.0 + if.true + push.0 + dup.2 + neq + if.true + push.0 + movup.6 + neq + if.true + push.1 + dup.3 + dup.7 + dup.4 + swap.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::alloc::Global::alloc_impl + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + else + push.8 + dup.3 + u32wrapping_add + dup.6 + dup.3 + swap.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::::allocate + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + end + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + movup.6 + movup.2 + drop + drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + movup.7 + movup.4 + drop + drop + push.8 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.0 + push.1 + movup.2 + cdrop + else + movup.2 + swap.5 + movdn.2 + swap.4 + swap.1 + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + swap.1 + swap.3 + swap.2 + swap.1 + end + else + swap.1 + drop + movup.3 + drop + movup.3 + drop + movup.3 + drop + push.3735929054 + end + push.0 + push.1 + dup.3 + cdrop + push.3735929054 + dup.3 + dup.5 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.7 + swap.1 + cdrop + push.3735929054 + dup.5 + movup.2 + swap.7 + movdn.2 + cdrop + push.3735929054 + movup.2 + swap.7 + movdn.2 + swap.1 + swap.5 + cdrop + swap.1 + swap.5 + swap.4 + swap.2 + swap.3 + swap.1 + end + movup.5 + eq.0 + if.true + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1 + swap.1 + else + drop + drop + end + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::allocate + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114208 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.8 + dup.2 + u32wrapping_add + movup.2 + swap.5 + movdn.2 + swap.1 + swap.3 + swap.4 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::alloc::alloc::Global::alloc_impl + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114208 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::alloc::Global::alloc_impl + push.0 + push.0 + dup.4 + eq + neq + if.true + movup.3 + drop + swap.1 + else + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::__rustc::__rust_no_alloc_shim_is_unstable_v2 + trace.252 + nop + push.0 + movup.4 + neq + if.true + swap.1 + dup.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::__rustc::__rust_alloc_zeroed + trace.252 + nop + else + swap.1 + dup.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::__rustc::__rust_alloc + trace.252 + nop + end + end + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::current_memory + push.0 + push.0 + dup.5 + eq + neq + if.true + movdn.3 + drop + drop + drop + push.0 + push.4 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + else + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.3 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + movup.2 + swap.1 + end + push.8 + push.4 + movup.3 + cdrop + end + movup.2 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::deallocate + push.0 + push.0 + dup.4 + eq + neq + if.true + drop + drop + drop + else + movup.2 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::p2id::__rustc::__rust_dealloc + trace.252 + nop + end +end + +proc.alloc::raw_vec::handle_error + drop + drop + drop + push.0 + assert +end + +proc.core::ptr::alignment::Alignment::max + push.0 + dup.2 + dup.2 + swap.1 + u32gt + neq + cdrop +end + +proc.miden::account::get_id + trace.240 + nop + exec.::miden::account::get_id + trace.252 + nop + movup.2 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::note::get_inputs + trace.240 + nop + exec.::miden::note::get_inputs + trace.252 + nop + swap.1 + drop +end + +proc.miden::note::get_assets + trace.240 + nop + exec.::miden::note::get_assets + trace.252 + nop + swap.1 + drop +end + diff --git a/tests/integration/expected/examples/p2id.wat b/tests/integration/expected/examples/p2id.wat new file mode 100644 index 000000000..df333dd13 --- /dev/null +++ b/tests/integration/expected/examples/p2id.wat @@ -0,0 +1,733 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + (type (;5;) (record (field "inner" 4))) + (export (;6;) "asset" (type (eq 5))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (alias export 0 "asset" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "asset" (type (eq 0))) + (type (;2;) (func (param "asset" 1))) + (export (;0;) "receive-asset" (func (type 2))) + ) + ) + (import "miden:basic-wallet/basic-wallet@0.1.0" (instance (;1;) (type 2))) + (core module (;0;) + (type (;0;) (func (param f32 f32 f32 f32))) + (type (;1;) (func)) + (type (;2;) (func (param i32 i32) (result i32))) + (type (;3;) (func (param i32 i32 i32))) + (type (;4;) (func (param i32 i32 i32) (result i32))) + (type (;5;) (func (result i32))) + (type (;6;) (func (param i32 i32 i32 i32))) + (type (;7;) (func (param i32))) + (type (;8;) (func (param f32 f32) (result i32))) + (type (;9;) (func (param i32 i32 i32 i32 i32))) + (type (;10;) (func (param i32) (result i32))) + (import "miden:basic-wallet/basic-wallet@0.1.0" "receive-asset" (func $p2id::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import7 (;0;) (type 0))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/note-script@1.0.0#run" (func $miden:base/note-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $p2id::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;1;) (type 1)) + (func $__rustc::__rust_alloc (;2;) (type 2) (param i32 i32) (result i32) + global.get $GOT.data.internal.__memory_base + i32.const 1048664 + i32.add + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_dealloc (;3;) (type 3) (param i32 i32 i32)) + (func $__rustc::__rust_alloc_zeroed (;4;) (type 2) (param i32 i32) (result i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048664 + i32.add + local.get 1 + local.get 0 + call $::alloc + local.tee 1 + i32.eqz + br_if 0 (;@1;) + local.get 0 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.const 0 + local.get 0 + memory.fill + end + local.get 1 + ) + (func $p2id::bindings::__link_custom_section_describing_imports (;5;) (type 1)) + (func $miden:base/note-script@1.0.0#run (;6;) (type 0) (param f32 f32 f32 f32) + (local i32 i32 f32 f32 f32 i32 i32 i32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + local.get 4 + i32.const 16 + i32.add + call $miden_base_sys::bindings::note::get_inputs + block ;; label = @1 + block ;; label = @2 + local.get 4 + i32.load offset=24 + br_table 1 (;@1;) 1 (;@1;) 0 (;@2;) + end + local.get 4 + i32.load offset=20 + local.tee 5 + f32.load offset=4 + local.set 6 + local.get 5 + f32.load + local.set 7 + local.get 4 + i32.const 8 + i32.add + call $miden_base_sys::bindings::account::get_id + local.get 4 + f32.load offset=12 + local.set 8 + local.get 4 + f32.load offset=8 + local.get 7 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 8 + local.get 6 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 4 + i32.const 28 + i32.add + call $miden_base_sys::bindings::note::get_assets + local.get 4 + i32.load offset=36 + i32.const 4 + i32.shl + local.set 9 + local.get 4 + i32.load offset=28 + local.set 10 + local.get 4 + i32.load offset=32 + local.tee 11 + local.set 5 + block ;; label = @2 + loop ;; label = @3 + local.get 9 + i32.eqz + br_if 1 (;@2;) + local.get 5 + f32.load + local.get 5 + f32.load offset=4 + local.get 5 + f32.load offset=8 + local.get 5 + f32.load offset=12 + call $p2id::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import7 + local.get 9 + i32.const -16 + i32.add + local.set 9 + local.get 5 + i32.const 16 + i32.add + local.set 5 + br 0 (;@3;) + end + end + local.get 4 + local.get 11 + i32.store offset=44 + local.get 4 + local.get 10 + i32.store offset=40 + local.get 4 + i32.const 40 + i32.add + i32.const 16 + i32.const 16 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 4 + i32.const 16 + i32.add + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 4 + i32.const 48 + i32.add + global.set $__stack_pointer + return + end + unreachable + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;7;) (type 1) + return + ) + (func $wit_bindgen::rt::run_ctors_once (;8;) (type 1) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048668 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048668 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $::alloc (;9;) (type 4) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;10;) (type 5) (result i32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::with_capacity_in (;11;) (type 6) (param i32 i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 4 + global.set $__stack_pointer + local.get 4 + i32.const 4 + i32.add + i32.const 256 + i32.const 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::try_allocate_in + local.get 4 + i32.load offset=8 + local.set 2 + block ;; label = @1 + local.get 4 + i32.load offset=4 + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 2 + local.get 4 + i32.load offset=12 + local.get 3 + call $alloc::raw_vec::handle_error + unreachable + end + local.get 0 + local.get 4 + i32.load offset=12 + i32.store offset=4 + local.get 0 + local.get 2 + i32.store + local.get 4 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::account::get_id (;12;) (type 7) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 8 + i32.add + call $miden::account::get_id + local.get 0 + local.get 1 + i64.load offset=8 align=4 + i64.store + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::note::get_inputs (;13;) (type 7) (param i32) + (local i32 i32 i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 8 + i32.add + i32.const 4 + i32.const 4 + global.get $GOT.data.internal.__memory_base + i32.const 1048632 + i32.add + call $alloc::raw_vec::RawVecInner::with_capacity_in + local.get 1 + i32.load offset=8 + local.set 2 + local.get 0 + local.get 1 + i32.load offset=12 + local.tee 3 + i32.const 2 + i32.shr_u + call $miden::note::get_inputs + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + local.get 0 + local.get 2 + i32.store + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::note::get_assets (;14;) (type 7) (param i32) + (local i32 i32 i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 8 + i32.add + i32.const 16 + i32.const 16 + global.get $GOT.data.internal.__memory_base + i32.const 1048648 + i32.add + call $alloc::raw_vec::RawVecInner::with_capacity_in + local.get 1 + i32.load offset=8 + local.set 2 + local.get 0 + local.get 1 + i32.load offset=12 + local.tee 3 + i32.const 2 + i32.shr_u + call $miden::note::get_assets + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + local.get 0 + local.get 2 + i32.store + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $intrinsics::felt::eq (;15;) (type 8) (param f32 f32) (result i32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;16;) (type 3) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::try_allocate_in (;17;) (type 9) (param i32 i32 i32 i32 i32) + (local i32 i64) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 3 + local.get 4 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 3 + i32.sub + i32.and + i64.extend_i32_u + local.get 1 + i64.extend_i32_u + i64.mul + local.tee 6 + i64.const 32 + i64.shr_u + i32.wrap_i64 + br_if 0 (;@3;) + local.get 6 + i32.wrap_i64 + local.tee 4 + i32.const -2147483648 + local.get 3 + i32.sub + i32.le_u + br_if 1 (;@2;) + end + local.get 0 + i32.const 0 + i32.store offset=4 + i32.const 1 + local.set 3 + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 + br_if 0 (;@2;) + local.get 0 + local.get 3 + i32.store offset=8 + i32.const 0 + local.set 3 + local.get 0 + i32.const 0 + i32.store offset=4 + br 1 (;@1;) + end + block ;; label = @2 + block ;; label = @3 + local.get 2 + br_if 0 (;@3;) + local.get 5 + i32.const 8 + i32.add + local.get 3 + local.get 4 + call $::allocate + local.get 5 + i32.load offset=8 + local.set 2 + br 1 (;@2;) + end + local.get 5 + local.get 3 + local.get 4 + i32.const 1 + call $alloc::alloc::Global::alloc_impl + local.get 5 + i32.load + local.set 2 + end + block ;; label = @2 + local.get 2 + i32.eqz + br_if 0 (;@2;) + local.get 0 + local.get 2 + i32.store offset=8 + local.get 0 + local.get 1 + i32.store offset=4 + i32.const 0 + local.set 3 + br 1 (;@1;) + end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + i32.const 1 + local.set 3 + end + local.get 0 + local.get 3 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::allocate (;18;) (type 3) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 2 + i32.const 0 + call $alloc::alloc::Global::alloc_impl + local.get 3 + i32.load offset=12 + local.set 2 + local.get 0 + local.get 3 + i32.load offset=8 + i32.store + local.get 0 + local.get 2 + i32.store offset=4 + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::alloc::Global::alloc_impl (;19;) (type 6) (param i32 i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + local.get 2 + local.get 1 + call $__rustc::__rust_alloc + local.set 1 + br 1 (;@1;) + end + local.get 2 + local.get 1 + call $__rustc::__rust_alloc_zeroed + local.set 1 + end + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.store + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;20;) (type 6) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;21;) (type 3) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) + (func $alloc::raw_vec::handle_error (;22;) (type 3) (param i32 i32 i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;23;) (type 2) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $miden::account::get_id (;24;) (type 7) (param i32) + unreachable + ) + (func $miden::note::get_inputs (;25;) (type 10) (param i32) (result i32) + unreachable + ) + (func $miden::note::get_assets (;26;) (type 10) (param i32) (result i32) + unreachable + ) + (data $.rodata (;0;) (i32.const 1048576) "miden-base-sys-0.7.0/src/bindings/note.rs\00") + (data $.data (;1;) (i32.const 1048620) "\01\00\00\00\01\00\00\00\01\00\00\00\00\00\10\00)\00\00\00\19\00\00\00!\00\00\00\00\00\10\00)\00\00\007\00\00\00\22\00\00\00") + ) + (alias export 0 "word" (type (;3;))) + (alias export 1 "receive-asset" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (core instance (;0;) + (export "receive-asset" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "miden:basic-wallet/basic-wallet@0.1.0" (instance 0)) + ) + ) + (alias core export 1 "memory" (core memory (;0;))) + (type (;4;) (func (param "arg" 3))) + (alias core export 1 "miden:base/note-script@1.0.0#run" (core func (;1;))) + (func (;1;) (type 4) (canon lift (core func 1))) + (alias export 0 "felt" (type (;5;))) + (alias export 0 "word" (type (;6;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;2;) (instantiate 0 + (with "import-func-run" (func 1)) + (with "import-type-felt" (type 5)) + (with "import-type-word" (type 6)) + (with "import-type-word0" (type 3)) + ) + ) + (export (;3;) "miden:base/note-script@1.0.0" (instance 2)) +) diff --git a/tests/integration/expected/examples/storage_example.hir b/tests/integration/expected/examples/storage_example.hir new file mode 100644 index 000000000..eb83b823e --- /dev/null +++ b/tests/integration/expected/examples/storage_example.hir @@ -0,0 +1,661 @@ +builtin.component miden:storage-example/foo@1.0.0 { + builtin.module public @storage_example { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @storage_example::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:storage-example/foo@1.0.0#set-asset-qty(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt) { + ^block9(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt): + v11 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/__stack_pointer : ptr + v12 = hir.bitcast v11 : ptr; + v13 = hir.load v12 : i32; + v14 = arith.constant 112 : i32; + v15 = arith.sub v13, v14 : i32 #[overflow = wrapping]; + v16 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/__stack_pointer : ptr + v17 = hir.bitcast v16 : ptr; + hir.store v17, v15; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/wit_bindgen::rt::run_ctors_once() + v9 = arith.constant 0 : i32; + v19 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/>::from(v9) : felt + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden::account::get_item(v19, v15) + v21 = arith.constant 8 : u32; + v20 = hir.bitcast v15 : u32; + v22 = arith.add v20, v21 : u32 #[overflow = checked]; + v547 = arith.constant 8 : u32; + v24 = arith.mod v22, v547 : u32; + hir.assertz v24 #[code = 250]; + v25 = hir.int_to_ptr v22 : ptr; + v26 = hir.load v25 : i64; + v28 = arith.constant 56 : u32; + v27 = hir.bitcast v15 : u32; + v29 = arith.add v27, v28 : u32 #[overflow = checked]; + v546 = arith.constant 8 : u32; + v31 = arith.mod v29, v546 : u32; + hir.assertz v31 #[code = 250]; + v32 = hir.int_to_ptr v29 : ptr; + hir.store v32, v26; + v33 = hir.bitcast v15 : u32; + v545 = arith.constant 8 : u32; + v35 = arith.mod v33, v545 : u32; + hir.assertz v35 #[code = 250]; + v36 = hir.int_to_ptr v33 : ptr; + v37 = hir.load v36 : i64; + v39 = arith.constant 48 : u32; + v38 = hir.bitcast v15 : u32; + v40 = arith.add v38, v39 : u32 #[overflow = checked]; + v544 = arith.constant 8 : u32; + v42 = arith.mod v40, v544 : u32; + hir.assertz v42 #[code = 250]; + v43 = hir.int_to_ptr v40 : ptr; + hir.store v43, v37; + v46 = arith.constant 48 : i32; + v47 = arith.add v15, v46 : i32 #[overflow = wrapping]; + v44 = arith.constant 96 : i32; + v45 = arith.add v15, v44 : i32 #[overflow = wrapping]; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden_stdlib_sys::intrinsics::word::Word::reverse(v45, v47) + v49 = arith.constant 100 : u32; + v48 = hir.bitcast v15 : u32; + v50 = arith.add v48, v49 : u32 #[overflow = checked]; + v51 = arith.constant 4 : u32; + v52 = arith.mod v50, v51 : u32; + hir.assertz v52 #[code = 250]; + v53 = hir.int_to_ptr v50 : ptr; + v54 = hir.load v53 : felt; + v56 = arith.constant 104 : u32; + v55 = hir.bitcast v15 : u32; + v57 = arith.add v55, v56 : u32 #[overflow = checked]; + v543 = arith.constant 4 : u32; + v59 = arith.mod v57, v543 : u32; + hir.assertz v59 #[code = 250]; + v60 = hir.int_to_ptr v57 : ptr; + v61 = hir.load v60 : felt; + v63 = arith.constant 108 : u32; + v62 = hir.bitcast v15 : u32; + v64 = arith.add v62, v63 : u32 #[overflow = checked]; + v542 = arith.constant 4 : u32; + v66 = arith.mod v64, v542 : u32; + hir.assertz v66 #[code = 250]; + v67 = hir.int_to_ptr v64 : ptr; + v68 = hir.load v67 : felt; + v70 = arith.constant 96 : u32; + v69 = hir.bitcast v15 : u32; + v71 = arith.add v69, v70 : u32 #[overflow = checked]; + v541 = arith.constant 4 : u32; + v73 = arith.mod v71, v541 : u32; + hir.assertz v73 #[code = 250]; + v74 = hir.int_to_ptr v71 : ptr; + v75 = hir.load v74 : felt; + v76 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/intrinsics::felt::eq(v0, v75) : i32 + v540 = arith.constant 0 : i32; + v77 = arith.constant 1 : i32; + v78 = arith.neq v76, v77 : i1; + v79 = arith.zext v78 : u32; + v80 = hir.bitcast v79 : i32; + v82 = arith.neq v80, v540 : i1; + scf.if v82{ + ^block51: + scf.yield ; + } else { + ^block12: + v83 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/intrinsics::felt::eq(v1, v54) : i32 + v538 = arith.constant 0 : i32; + v539 = arith.constant 1 : i32; + v85 = arith.neq v83, v539 : i1; + v86 = arith.zext v85 : u32; + v87 = hir.bitcast v86 : i32; + v89 = arith.neq v87, v538 : i1; + scf.if v89{ + ^block50: + scf.yield ; + } else { + ^block13: + v90 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/intrinsics::felt::eq(v2, v61) : i32 + v536 = arith.constant 0 : i32; + v537 = arith.constant 1 : i32; + v92 = arith.neq v90, v537 : i1; + v93 = arith.zext v92 : u32; + v94 = hir.bitcast v93 : i32; + v96 = arith.neq v94, v536 : i1; + scf.if v96{ + ^block49: + scf.yield ; + } else { + ^block14: + v97 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/intrinsics::felt::eq(v3, v68) : i32 + v534 = arith.constant 0 : i32; + v535 = arith.constant 1 : i32; + v99 = arith.neq v97, v535 : i1; + v100 = arith.zext v99 : u32; + v101 = hir.bitcast v100 : i32; + v103 = arith.neq v101, v534 : i1; + scf.if v103{ + ^block48: + scf.yield ; + } else { + ^block15: + v104 = arith.constant 32 : i32; + v105 = arith.add v15, v104 : i32 #[overflow = wrapping]; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/>::from(v105, v8) + v533 = arith.constant 1 : i32; + v107 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/>::from(v533) : felt + v109 = arith.constant 44 : u32; + v108 = hir.bitcast v15 : u32; + v110 = arith.add v108, v109 : u32 #[overflow = checked]; + v532 = arith.constant 4 : u32; + v112 = arith.mod v110, v532 : u32; + hir.assertz v112 #[code = 250]; + v113 = hir.int_to_ptr v110 : ptr; + v114 = hir.load v113 : felt; + v116 = arith.constant 40 : u32; + v115 = hir.bitcast v15 : u32; + v117 = arith.add v115, v116 : u32 #[overflow = checked]; + v531 = arith.constant 4 : u32; + v119 = arith.mod v117, v531 : u32; + hir.assertz v119 #[code = 250]; + v120 = hir.int_to_ptr v117 : ptr; + v121 = hir.load v120 : felt; + v123 = arith.constant 36 : u32; + v122 = hir.bitcast v15 : u32; + v124 = arith.add v122, v123 : u32 #[overflow = checked]; + v530 = arith.constant 4 : u32; + v126 = arith.mod v124, v530 : u32; + hir.assertz v126 #[code = 250]; + v127 = hir.int_to_ptr v124 : ptr; + v128 = hir.load v127 : felt; + v130 = arith.constant 32 : u32; + v129 = hir.bitcast v15 : u32; + v131 = arith.add v129, v130 : u32 #[overflow = checked]; + v529 = arith.constant 4 : u32; + v133 = arith.mod v131, v529 : u32; + hir.assertz v133 #[code = 250]; + v134 = hir.int_to_ptr v131 : ptr; + v135 = hir.load v134 : felt; + v528 = arith.constant 48 : i32; + v137 = arith.add v15, v528 : i32 #[overflow = wrapping]; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden::account::set_map_item(v107, v7, v6, v5, v4, v114, v121, v128, v135, v137) + v527 = arith.constant 56 : u32; + v138 = hir.bitcast v15 : u32; + v140 = arith.add v138, v527 : u32 #[overflow = checked]; + v526 = arith.constant 8 : u32; + v142 = arith.mod v140, v526 : u32; + hir.assertz v142 #[code = 250]; + v143 = hir.int_to_ptr v140 : ptr; + v144 = hir.load v143 : i64; + v146 = arith.constant 88 : u32; + v145 = hir.bitcast v15 : u32; + v147 = arith.add v145, v146 : u32 #[overflow = checked]; + v525 = arith.constant 8 : u32; + v149 = arith.mod v147, v525 : u32; + hir.assertz v149 #[code = 250]; + v150 = hir.int_to_ptr v147 : ptr; + hir.store v150, v144; + v524 = arith.constant 48 : u32; + v151 = hir.bitcast v15 : u32; + v153 = arith.add v151, v524 : u32 #[overflow = checked]; + v523 = arith.constant 8 : u32; + v155 = arith.mod v153, v523 : u32; + hir.assertz v155 #[code = 250]; + v156 = hir.int_to_ptr v153 : ptr; + v157 = hir.load v156 : i64; + v159 = arith.constant 80 : u32; + v158 = hir.bitcast v15 : u32; + v160 = arith.add v158, v159 : u32 #[overflow = checked]; + v522 = arith.constant 8 : u32; + v162 = arith.mod v160, v522 : u32; + hir.assertz v162 #[code = 250]; + v163 = hir.int_to_ptr v160 : ptr; + hir.store v163, v157; + v164 = arith.constant 72 : i32; + v165 = arith.add v15, v164 : i32 #[overflow = wrapping]; + v166 = hir.bitcast v165 : u32; + v521 = arith.constant 8 : u32; + v168 = arith.mod v166, v521 : u32; + hir.assertz v168 #[code = 250]; + v169 = hir.int_to_ptr v166 : ptr; + v170 = hir.load v169 : i64; + v520 = arith.constant 104 : u32; + v171 = hir.bitcast v15 : u32; + v173 = arith.add v171, v520 : u32 #[overflow = checked]; + v519 = arith.constant 8 : u32; + v175 = arith.mod v173, v519 : u32; + hir.assertz v175 #[code = 250]; + v176 = hir.int_to_ptr v173 : ptr; + hir.store v176, v170; + v178 = arith.constant 64 : u32; + v177 = hir.bitcast v15 : u32; + v179 = arith.add v177, v178 : u32 #[overflow = checked]; + v518 = arith.constant 8 : u32; + v181 = arith.mod v179, v518 : u32; + hir.assertz v181 #[code = 250]; + v182 = hir.int_to_ptr v179 : ptr; + v183 = hir.load v182 : i64; + v517 = arith.constant 96 : u32; + v184 = hir.bitcast v15 : u32; + v186 = arith.add v184, v517 : u32 #[overflow = checked]; + v516 = arith.constant 8 : u32; + v188 = arith.mod v186, v516 : u32; + hir.assertz v188 #[code = 250]; + v189 = hir.int_to_ptr v186 : ptr; + hir.store v189, v183; + v190 = arith.constant 80 : i32; + v191 = arith.add v15, v190 : i32 #[overflow = wrapping]; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden_stdlib_sys::intrinsics::word::Word::reverse(v15, v191) + v515 = arith.constant 96 : i32; + v195 = arith.add v15, v515 : i32 #[overflow = wrapping]; + v192 = arith.constant 16 : i32; + v193 = arith.add v15, v192 : i32 #[overflow = wrapping]; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden_stdlib_sys::intrinsics::word::Word::reverse(v193, v195) + scf.yield ; + }; + scf.yield ; + }; + scf.yield ; + }; + scf.yield ; + }; + v514 = arith.constant 112 : i32; + v198 = arith.add v15, v514 : i32 #[overflow = wrapping]; + v199 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/__stack_pointer : ptr + v200 = hir.bitcast v199 : ptr; + hir.store v200, v198; + builtin.ret ; + }; + + private builtin.function @miden:storage-example/foo@1.0.0#get-asset-qty(v201: felt, v202: felt, v203: felt, v204: felt) -> felt { + ^block16(v201: felt, v202: felt, v203: felt, v204: felt): + v207 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/__stack_pointer : ptr + v208 = hir.bitcast v207 : ptr; + v209 = hir.load v208 : i32; + v210 = arith.constant 48 : i32; + v211 = arith.sub v209, v210 : i32 #[overflow = wrapping]; + v212 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/__stack_pointer : ptr + v213 = hir.bitcast v212 : ptr; + hir.store v213, v211; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/wit_bindgen::rt::run_ctors_once() + v214 = arith.constant 1 : i32; + v215 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/>::from(v214) : felt + v216 = arith.constant 16 : i32; + v217 = arith.add v211, v216 : i32 #[overflow = wrapping]; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden::account::get_map_item(v215, v204, v203, v202, v201, v217) + v219 = arith.constant 24 : u32; + v218 = hir.bitcast v211 : u32; + v220 = arith.add v218, v219 : u32 #[overflow = checked]; + v221 = arith.constant 8 : u32; + v222 = arith.mod v220, v221 : u32; + hir.assertz v222 #[code = 250]; + v223 = hir.int_to_ptr v220 : ptr; + v224 = hir.load v223 : i64; + v226 = arith.constant 40 : u32; + v225 = hir.bitcast v211 : u32; + v227 = arith.add v225, v226 : u32 #[overflow = checked]; + v551 = arith.constant 8 : u32; + v229 = arith.mod v227, v551 : u32; + hir.assertz v229 #[code = 250]; + v230 = hir.int_to_ptr v227 : ptr; + hir.store v230, v224; + v232 = arith.constant 16 : u32; + v231 = hir.bitcast v211 : u32; + v233 = arith.add v231, v232 : u32 #[overflow = checked]; + v550 = arith.constant 8 : u32; + v235 = arith.mod v233, v550 : u32; + hir.assertz v235 #[code = 250]; + v236 = hir.int_to_ptr v233 : ptr; + v237 = hir.load v236 : i64; + v239 = arith.constant 32 : u32; + v238 = hir.bitcast v211 : u32; + v240 = arith.add v238, v239 : u32 #[overflow = checked]; + v549 = arith.constant 8 : u32; + v242 = arith.mod v240, v549 : u32; + hir.assertz v242 #[code = 250]; + v243 = hir.int_to_ptr v240 : ptr; + hir.store v243, v237; + v244 = arith.constant 32 : i32; + v245 = arith.add v211, v244 : i32 #[overflow = wrapping]; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden_stdlib_sys::intrinsics::word::Word::reverse(v211, v245) + v247 = arith.constant 12 : u32; + v246 = hir.bitcast v211 : u32; + v248 = arith.add v246, v247 : u32 #[overflow = checked]; + v249 = arith.constant 4 : u32; + v250 = arith.mod v248, v249 : u32; + hir.assertz v250 #[code = 250]; + v251 = hir.int_to_ptr v248 : ptr; + v252 = hir.load v251 : felt; + v548 = arith.constant 48 : i32; + v254 = arith.add v211, v548 : i32 #[overflow = wrapping]; + v255 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/__stack_pointer : ptr + v256 = hir.bitcast v255 : ptr; + hir.store v256, v254; + builtin.ret v252; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block18: + v258 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/GOT.data.internal.__memory_base : ptr + v259 = hir.bitcast v258 : ptr; + v260 = hir.load v259 : i32; + v261 = arith.constant 1048584 : i32; + v262 = arith.add v260, v261 : i32 #[overflow = wrapping]; + v263 = hir.bitcast v262 : u32; + v264 = hir.int_to_ptr v263 : ptr; + v265 = hir.load v264 : u8; + v257 = arith.constant 0 : i32; + v266 = arith.zext v265 : u32; + v267 = hir.bitcast v266 : i32; + v269 = arith.neq v267, v257 : i1; + scf.if v269{ + ^block20: + scf.yield ; + } else { + ^block21: + v270 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/GOT.data.internal.__memory_base : ptr + v271 = hir.bitcast v270 : ptr; + v272 = hir.load v271 : i32; + hir.exec @miden:storage-example/foo@1.0.0/storage_example/__wasm_call_ctors() + v553 = arith.constant 1 : u8; + v555 = arith.constant 1048584 : i32; + v274 = arith.add v272, v555 : i32 #[overflow = wrapping]; + v278 = hir.bitcast v274 : u32; + v279 = hir.int_to_ptr v278 : ptr; + hir.store v279, v553; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @>::from(v280: i32) -> felt { + ^block22(v280: i32): + v282 = arith.constant 255 : i32; + v283 = arith.band v280, v282 : i32; + v284 = hir.bitcast v283 : felt; + builtin.ret v284; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v285: i32, v286: i32) { + ^block24(v285: i32, v286: i32): + v289 = builtin.global_symbol @miden:storage-example/foo@1.0.0/storage_example/__stack_pointer : ptr + v290 = hir.bitcast v289 : ptr; + v291 = hir.load v290 : i32; + v292 = arith.constant 16 : i32; + v293 = arith.sub v291, v292 : i32 #[overflow = wrapping]; + v295 = arith.constant 8 : u32; + v294 = hir.bitcast v286 : u32; + v296 = arith.add v294, v295 : u32 #[overflow = checked]; + v642 = arith.constant 8 : u32; + v298 = arith.mod v296, v642 : u32; + hir.assertz v298 #[code = 250]; + v299 = hir.int_to_ptr v296 : ptr; + v300 = hir.load v299 : i64; + v641 = arith.constant 8 : u32; + v301 = hir.bitcast v293 : u32; + v303 = arith.add v301, v641 : u32 #[overflow = checked]; + v304 = arith.constant 4 : u32; + v305 = arith.mod v303, v304 : u32; + hir.assertz v305 #[code = 250]; + v306 = hir.int_to_ptr v303 : ptr; + hir.store v306, v300; + v307 = hir.bitcast v286 : u32; + v640 = arith.constant 8 : u32; + v309 = arith.mod v307, v640 : u32; + hir.assertz v309 #[code = 250]; + v310 = hir.int_to_ptr v307 : ptr; + v311 = hir.load v310 : i64; + v312 = hir.bitcast v293 : u32; + v639 = arith.constant 4 : u32; + v314 = arith.mod v312, v639 : u32; + hir.assertz v314 #[code = 250]; + v315 = hir.int_to_ptr v312 : ptr; + hir.store v315, v311; + v316 = arith.constant 12 : i32; + v317 = arith.add v293, v316 : i32 #[overflow = wrapping]; + v287 = arith.constant 0 : i32; + v610, v611, v612, v613, v614, v615 = scf.while v287, v293, v317, v285 : i32, i32, i32, i32, i32, i32 { + ^block64(v616: i32, v617: i32, v618: i32, v619: i32): + v638 = arith.constant 0 : i32; + v320 = arith.constant 8 : i32; + v321 = arith.eq v616, v320 : i1; + v322 = arith.zext v321 : u32; + v323 = hir.bitcast v322 : i32; + v325 = arith.neq v323, v638 : i1; + v604, v605 = scf.if v325 : i32, i32 { + ^block63: + v564 = ub.poison i32 : i32; + scf.yield v564, v564; + } else { + ^block29: + v327 = arith.add v617, v616 : i32 #[overflow = wrapping]; + v328 = hir.bitcast v327 : u32; + v637 = arith.constant 4 : u32; + v330 = arith.mod v328, v637 : u32; + hir.assertz v330 #[code = 250]; + v331 = hir.int_to_ptr v328 : ptr; + v332 = hir.load v331 : felt; + v334 = hir.bitcast v618 : u32; + v636 = arith.constant 4 : u32; + v336 = arith.mod v334, v636 : u32; + hir.assertz v336 #[code = 250]; + v337 = hir.int_to_ptr v334 : ptr; + v338 = hir.load v337 : i32; + v339 = hir.bitcast v327 : u32; + v635 = arith.constant 4 : u32; + v341 = arith.mod v339, v635 : u32; + hir.assertz v341 #[code = 250]; + v342 = hir.int_to_ptr v339 : ptr; + hir.store v342, v338; + v343 = hir.bitcast v618 : u32; + v634 = arith.constant 4 : u32; + v345 = arith.mod v343, v634 : u32; + hir.assertz v345 #[code = 250]; + v346 = hir.int_to_ptr v343 : ptr; + hir.store v346, v332; + v349 = arith.constant -4 : i32; + v350 = arith.add v618, v349 : i32 #[overflow = wrapping]; + v347 = arith.constant 4 : i32; + v348 = arith.add v616, v347 : i32 #[overflow = wrapping]; + scf.yield v348, v350; + }; + v632 = ub.poison i32 : i32; + v607 = cf.select v325, v632, v619 : i32; + v633 = ub.poison i32 : i32; + v606 = cf.select v325, v633, v617 : i32; + v563 = arith.constant 1 : u32; + v556 = arith.constant 0 : u32; + v609 = cf.select v325, v556, v563 : u32; + v597 = arith.trunc v609 : i1; + scf.condition v597, v604, v606, v605, v607, v617, v619; + } do { + ^block65(v620: i32, v621: i32, v622: i32, v623: i32, v624: i32, v625: i32): + scf.yield v620, v621, v622, v623; + }; + v631 = arith.constant 8 : u32; + v352 = hir.bitcast v614 : u32; + v354 = arith.add v352, v631 : u32 #[overflow = checked]; + v630 = arith.constant 4 : u32; + v356 = arith.mod v354, v630 : u32; + hir.assertz v356 #[code = 250]; + v357 = hir.int_to_ptr v354 : ptr; + v358 = hir.load v357 : i64; + v629 = arith.constant 8 : u32; + v359 = hir.bitcast v615 : u32; + v361 = arith.add v359, v629 : u32 #[overflow = checked]; + v628 = arith.constant 8 : u32; + v363 = arith.mod v361, v628 : u32; + hir.assertz v363 #[code = 250]; + v364 = hir.int_to_ptr v361 : ptr; + hir.store v364, v358; + v365 = hir.bitcast v614 : u32; + v627 = arith.constant 4 : u32; + v367 = arith.mod v365, v627 : u32; + hir.assertz v367 #[code = 250]; + v368 = hir.int_to_ptr v365 : ptr; + v369 = hir.load v368 : i64; + v370 = hir.bitcast v615 : u32; + v626 = arith.constant 8 : u32; + v372 = arith.mod v370, v626 : u32; + hir.assertz v372 #[code = 250]; + v373 = hir.int_to_ptr v370 : ptr; + hir.store v373, v369; + builtin.ret ; + }; + + private builtin.function @>::from(v374: i32, v375: felt) { + ^block30(v374: i32, v375: felt): + v377 = arith.constant 0 : i32; + v378 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/intrinsics::felt::from_u32(v377) : felt + v648 = arith.constant 0 : i32; + v380 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/intrinsics::felt::from_u32(v648) : felt + v647 = arith.constant 0 : i32; + v382 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/intrinsics::felt::from_u32(v647) : felt + v384 = arith.constant 12 : u32; + v383 = hir.bitcast v374 : u32; + v385 = arith.add v383, v384 : u32 #[overflow = checked]; + v386 = arith.constant 4 : u32; + v387 = arith.mod v385, v386 : u32; + hir.assertz v387 #[code = 250]; + v388 = hir.int_to_ptr v385 : ptr; + hir.store v388, v375; + v390 = arith.constant 8 : u32; + v389 = hir.bitcast v374 : u32; + v391 = arith.add v389, v390 : u32 #[overflow = checked]; + v646 = arith.constant 4 : u32; + v393 = arith.mod v391, v646 : u32; + hir.assertz v393 #[code = 250]; + v394 = hir.int_to_ptr v391 : ptr; + hir.store v394, v382; + v645 = arith.constant 4 : u32; + v395 = hir.bitcast v374 : u32; + v397 = arith.add v395, v645 : u32 #[overflow = checked]; + v644 = arith.constant 4 : u32; + v399 = arith.mod v397, v644 : u32; + hir.assertz v399 #[code = 250]; + v400 = hir.int_to_ptr v397 : ptr; + hir.store v400, v380; + v401 = hir.bitcast v374 : u32; + v643 = arith.constant 4 : u32; + v403 = arith.mod v401, v643 : u32; + hir.assertz v403 #[code = 250]; + v404 = hir.int_to_ptr v401 : ptr; + hir.store v404, v378; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u32(v405: i32) -> felt { + ^block32(v405: i32): + v406 = hir.bitcast v405 : felt; + builtin.ret v406; + }; + + private builtin.function @intrinsics::felt::eq(v408: felt, v409: felt) -> i32 { + ^block34(v408: felt, v409: felt): + v410 = arith.eq v408, v409 : i1; + v411 = hir.cast v410 : i32; + builtin.ret v411; + }; + + private builtin.function @miden::account::get_item(v413: felt, v414: i32) { + ^block36(v413: felt, v414: i32): + v415, v416, v417, v418 = hir.exec @miden/account/get_item(v413) : felt, felt, felt, felt + v419 = hir.bitcast v414 : u32; + v420 = hir.int_to_ptr v419 : ptr; + hir.store v420, v415; + v421 = arith.constant 4 : u32; + v422 = arith.add v419, v421 : u32 #[overflow = checked]; + v423 = hir.int_to_ptr v422 : ptr; + hir.store v423, v416; + v424 = arith.constant 8 : u32; + v425 = arith.add v419, v424 : u32 #[overflow = checked]; + v426 = hir.int_to_ptr v425 : ptr; + hir.store v426, v417; + v427 = arith.constant 12 : u32; + v428 = arith.add v419, v427 : u32 #[overflow = checked]; + v429 = hir.int_to_ptr v428 : ptr; + hir.store v429, v418; + builtin.ret ; + }; + + private builtin.function @miden::account::get_map_item(v430: felt, v431: felt, v432: felt, v433: felt, v434: felt, v435: i32) { + ^block40(v430: felt, v431: felt, v432: felt, v433: felt, v434: felt, v435: i32): + v436, v437, v438, v439 = hir.exec @miden/account/get_map_item(v430, v431, v432, v433, v434) : felt, felt, felt, felt + v440 = hir.bitcast v435 : u32; + v441 = hir.int_to_ptr v440 : ptr; + hir.store v441, v436; + v442 = arith.constant 4 : u32; + v443 = arith.add v440, v442 : u32 #[overflow = checked]; + v444 = hir.int_to_ptr v443 : ptr; + hir.store v444, v437; + v445 = arith.constant 8 : u32; + v446 = arith.add v440, v445 : u32 #[overflow = checked]; + v447 = hir.int_to_ptr v446 : ptr; + hir.store v447, v438; + v448 = arith.constant 12 : u32; + v449 = arith.add v440, v448 : u32 #[overflow = checked]; + v450 = hir.int_to_ptr v449 : ptr; + hir.store v450, v439; + builtin.ret ; + }; + + private builtin.function @miden::account::set_map_item(v451: felt, v452: felt, v453: felt, v454: felt, v455: felt, v456: felt, v457: felt, v458: felt, v459: felt, v460: i32) { + ^block42(v451: felt, v452: felt, v453: felt, v454: felt, v455: felt, v456: felt, v457: felt, v458: felt, v459: felt, v460: i32): + v461, v462, v463, v464, v465, v466, v467, v468 = hir.exec @miden/account/set_map_item(v451, v452, v453, v454, v455, v456, v457, v458, v459) : felt, felt, felt, felt, felt, felt, felt, felt + v469 = hir.bitcast v460 : u32; + v470 = hir.int_to_ptr v469 : ptr; + hir.store v470, v461; + v471 = arith.constant 4 : u32; + v472 = arith.add v469, v471 : u32 #[overflow = checked]; + v473 = hir.int_to_ptr v472 : ptr; + hir.store v473, v462; + v474 = arith.constant 8 : u32; + v475 = arith.add v469, v474 : u32 #[overflow = checked]; + v476 = hir.int_to_ptr v475 : ptr; + hir.store v476, v463; + v477 = arith.constant 12 : u32; + v478 = arith.add v469, v477 : u32 #[overflow = checked]; + v479 = hir.int_to_ptr v478 : ptr; + hir.store v479, v464; + v480 = arith.constant 16 : u32; + v481 = arith.add v469, v480 : u32 #[overflow = checked]; + v482 = hir.int_to_ptr v481 : ptr; + hir.store v482, v465; + v483 = arith.constant 20 : u32; + v484 = arith.add v469, v483 : u32 #[overflow = checked]; + v485 = hir.int_to_ptr v484 : ptr; + hir.store v485, v466; + v486 = arith.constant 24 : u32; + v487 = arith.add v469, v486 : u32 #[overflow = checked]; + v488 = hir.int_to_ptr v487 : ptr; + hir.store v488, v467; + v489 = arith.constant 28 : u32; + v490 = arith.add v469, v489 : u32 #[overflow = checked]; + v491 = hir.int_to_ptr v490 : ptr; + hir.store v491, v468; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @set-asset-qty(v492: felt, v493: felt, v494: felt, v495: felt, v496: felt, v497: felt, v498: felt, v499: felt, v500: felt) { + ^block44(v492: felt, v493: felt, v494: felt, v495: felt, v496: felt, v497: felt, v498: felt, v499: felt, v500: felt): + hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden:storage-example/foo@1.0.0#set-asset-qty(v492, v493, v494, v495, v496, v497, v498, v499, v500) + builtin.ret ; + }; + + public builtin.function @get-asset-qty(v501: felt, v502: felt, v503: felt, v504: felt) -> felt { + ^block46(v501: felt, v502: felt, v503: felt, v504: felt): + v505 = hir.exec @miden:storage-example/foo@1.0.0/storage_example/miden:storage-example/foo@1.0.0#get-asset-qty(v501, v502, v503, v504) : felt + builtin.ret v505; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/examples/storage_example.masm b/tests/integration/expected/examples/storage_example.masm new file mode 100644 index 000000000..d24fae756 --- /dev/null +++ b/tests/integration/expected/examples/storage_example.masm @@ -0,0 +1,1388 @@ +# mod miden:storage-example/foo@1.0.0 + +export.set-asset-qty + exec.::miden:storage-example/foo@1.0.0::init + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden:storage-example/foo@1.0.0#set-asset-qty + trace.252 + nop + exec.::std::sys::truncate_stack +end + +export.get-asset-qty + exec.::miden:storage-example/foo@1.0.0::init + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden:storage-example/foo@1.0.0#get-asset-qty + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:storage-example/foo@1.0.0::storage_example + +proc.__wasm_call_ctors + nop +end + +proc.storage_example::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:storage-example/foo@1.0.0#set-asset-qty + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.112 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::>::from + trace.252 + nop + dup.1 + swap.1 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden::account::get_item + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.56 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.48 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.48 + dup.1 + u32wrapping_add + push.96 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.100 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.104 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.108 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.96 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.5 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + drop + movdn.8 + dropw + dropw + else + movup.2 + movup.4 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + movdn.7 + dropw + drop + drop + drop + else + swap.3 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + movdn.7 + dropw + drop + drop + drop + else + swap.2 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + movdn.5 + dropw + drop + else + push.32 + dup.1 + u32wrapping_add + movup.6 + swap.1 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::>::from + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::>::from + trace.252 + nop + push.44 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.40 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.36 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.32 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.48 + dup.6 + u32wrapping_add + swap.1 + swap.8 + swap.3 + swap.6 + swap.10 + swap.1 + swap.9 + swap.2 + swap.7 + swap.4 + swap.5 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden::account::set_map_item + trace.252 + nop + push.56 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.88 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.48 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.80 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.72 + dup.1 + u32wrapping_add + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.104 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.64 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.96 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.80 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.96 + dup.1 + u32wrapping_add + push.16 + dup.2 + u32wrapping_add + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + end + end + end + end + push.112 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden:storage-example/foo@1.0.0#get-asset-qty + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::>::from + trace.252 + nop + push.16 + dup.2 + u32wrapping_add + movup.3 + swap.4 + movdn.3 + swap.5 + swap.2 + swap.6 + swap.1 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden::account::get_map_item + trace.252 + nop + push.24 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.40 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.32 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.48 + movup.2 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.>::from + push.255 + u32and +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.>::from + push.0 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::miden:storage-example/foo@1.0.0::storage_example::intrinsics::felt::from_u32 + trace.252 + nop + push.12 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::eq + eq +end + +proc.miden::account::get_item + trace.240 + nop + exec.::miden::account::get_item + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::get_map_item + trace.240 + nop + exec.::miden::account::get_map_item + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::account::set_map_item + trace.240 + nop + exec.::miden::account::set_map_item + trace.252 + nop + movup.8 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.20 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.24 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.28 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + diff --git a/tests/integration/expected/examples/storage_example.wat b/tests/integration/expected/examples/storage_example.wat new file mode 100644 index 000000000..af8398eaf --- /dev/null +++ b/tests/integration/expected/examples/storage_example.wat @@ -0,0 +1,369 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + (type (;5;) (record (field "inner" 4))) + (export (;6;) "asset" (type (eq 5))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32))) + (type (;2;) (func (param f32 f32 f32 f32) (result f32))) + (type (;3;) (func (param i32) (result f32))) + (type (;4;) (func (param i32 i32))) + (type (;5;) (func (param i32 f32))) + (type (;6;) (func (param f32 f32) (result i32))) + (type (;7;) (func (param f32 i32))) + (type (;8;) (func (param f32 f32 f32 f32 f32 i32))) + (type (;9;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:storage-example/foo@1.0.0#set-asset-qty" (func $miden:storage-example/foo@1.0.0#set-asset-qty)) + (export "miden:storage-example/foo@1.0.0#get-asset-qty" (func $miden:storage-example/foo@1.0.0#get-asset-qty)) + (elem (;0;) (i32.const 1) func $storage_example::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $storage_example::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:storage-example/foo@1.0.0#set-asset-qty (;2;) (type 1) (param f32 f32 f32 f32 f32 f32 f32 f32 f32) + (local i32 f32 f32 f32) + global.get $__stack_pointer + i32.const 112 + i32.sub + local.tee 9 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + i32.const 0 + call $>::from + local.get 9 + call $miden::account::get_item + local.get 9 + local.get 9 + i64.load offset=8 + i64.store offset=56 + local.get 9 + local.get 9 + i64.load + i64.store offset=48 + local.get 9 + i32.const 96 + i32.add + local.get 9 + i32.const 48 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 9 + f32.load offset=100 + local.set 10 + local.get 9 + f32.load offset=104 + local.set 11 + local.get 9 + f32.load offset=108 + local.set 12 + block ;; label = @1 + local.get 0 + local.get 9 + f32.load offset=96 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 1 + local.get 10 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 2 + local.get 11 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 3 + local.get 12 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 9 + i32.const 32 + i32.add + local.get 8 + call $>::from + i32.const 1 + call $>::from + local.get 7 + local.get 6 + local.get 5 + local.get 4 + local.get 9 + f32.load offset=44 + local.get 9 + f32.load offset=40 + local.get 9 + f32.load offset=36 + local.get 9 + f32.load offset=32 + local.get 9 + i32.const 48 + i32.add + call $miden::account::set_map_item + local.get 9 + local.get 9 + i64.load offset=56 + i64.store offset=88 + local.get 9 + local.get 9 + i64.load offset=48 + i64.store offset=80 + local.get 9 + local.get 9 + i32.const 72 + i32.add + i64.load + i64.store offset=104 + local.get 9 + local.get 9 + i64.load offset=64 + i64.store offset=96 + local.get 9 + local.get 9 + i32.const 80 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 9 + i32.const 16 + i32.add + local.get 9 + i32.const 96 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + end + local.get 9 + i32.const 112 + i32.add + global.set $__stack_pointer + ) + (func $miden:storage-example/foo@1.0.0#get-asset-qty (;3;) (type 2) (param f32 f32 f32 f32) (result f32) + (local i32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + i32.const 1 + call $>::from + local.get 3 + local.get 2 + local.get 1 + local.get 0 + local.get 4 + i32.const 16 + i32.add + call $miden::account::get_map_item + local.get 4 + local.get 4 + i64.load offset=24 + i64.store offset=40 + local.get 4 + local.get 4 + i64.load offset=16 + i64.store offset=32 + local.get 4 + local.get 4 + i32.const 32 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 4 + f32.load offset=12 + local.set 0 + local.get 4 + i32.const 48 + i32.add + global.set $__stack_pointer + local.get 0 + ) + (func $wit_bindgen::rt::run_ctors_once (;4;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048584 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $>::from (;5;) (type 3) (param i32) (result f32) + local.get 0 + i32.const 255 + i32.and + f32.reinterpret_i32 + ) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;6;) (type 4) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $>::from (;7;) (type 5) (param i32 f32) + (local f32 f32 f32) + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 2 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 3 + i32.const 0 + call $intrinsics::felt::from_u32 + local.set 4 + local.get 0 + local.get 1 + f32.store offset=12 + local.get 0 + local.get 4 + f32.store offset=8 + local.get 0 + local.get 3 + f32.store offset=4 + local.get 0 + local.get 2 + f32.store + ) + (func $intrinsics::felt::from_u32 (;8;) (type 3) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::eq (;9;) (type 6) (param f32 f32) (result i32) + unreachable + ) + (func $miden::account::get_item (;10;) (type 7) (param f32 i32) + unreachable + ) + (func $miden::account::get_map_item (;11;) (type 8) (param f32 f32 f32 f32 f32 i32) + unreachable + ) + (func $miden::account::set_map_item (;12;) (type 9) (param f32 f32 f32 f32 f32 f32 f32 f32 f32 i32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + (@custom "rodata,miden_account" (after data) "\1fstorage-example_A simple example of a Miden account storage API\0b0.1.0\03\01\05\00\00\00!owner_public_key\01\15test value9auth::rpo_falcon512::pub_key\01\01\01\1basset_qty_map\01\11test map\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00") + ) + (alias export 0 "felt" (type (;1;))) + (alias export 0 "word" (type (;2;))) + (alias export 0 "asset" (type (;3;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;4;) (func (param "pub-key" 2) (param "asset" 3) (param "qty" 1))) + (alias core export 0 "miden:storage-example/foo@1.0.0#set-asset-qty" (core func (;0;))) + (func (;0;) (type 4) (canon lift (core func 0))) + (type (;5;) (func (param "asset" 3) (result 1))) + (alias core export 0 "miden:storage-example/foo@1.0.0#get-asset-qty" (core func (;1;))) + (func (;1;) (type 5) (canon lift (core func 1))) + (alias export 0 "felt" (type (;6;))) + (alias export 0 "word" (type (;7;))) + (alias export 0 "asset" (type (;8;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (type (;5;) (record (field "inner" 4))) + (import "import-type-asset" (type (;6;) (eq 5))) + (import "import-type-word0" (type (;7;) (eq 4))) + (import "import-type-asset0" (type (;8;) (eq 6))) + (import "import-type-felt0" (type (;9;) (eq 1))) + (type (;10;) (func (param "pub-key" 7) (param "asset" 8) (param "qty" 9))) + (import "import-func-set-asset-qty" (func (;0;) (type 10))) + (type (;11;) (func (param "asset" 8) (result 9))) + (import "import-func-get-asset-qty" (func (;1;) (type 11))) + (export (;12;) "felt" (type 1)) + (export (;13;) "word" (type 4)) + (export (;14;) "asset" (type 6)) + (type (;15;) (func (param "pub-key" 13) (param "asset" 14) (param "qty" 12))) + (export (;2;) "set-asset-qty" (func 0) (func (type 15))) + (type (;16;) (func (param "asset" 14) (result 12))) + (export (;3;) "get-asset-qty" (func 1) (func (type 16))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-set-asset-qty" (func 0)) + (with "import-func-get-asset-qty" (func 1)) + (with "import-type-felt" (type 6)) + (with "import-type-word" (type 7)) + (with "import-type-asset" (type 8)) + (with "import-type-word0" (type 2)) + (with "import-type-asset0" (type 3)) + (with "import-type-felt0" (type 1)) + ) + ) + (export (;2;) "miden:storage-example/foo@1.0.0" (instance 1)) +) diff --git a/tests/integration/expected/felt_intrinsics.hir b/tests/integration/expected/felt_intrinsics.hir new file mode 100644 index 000000000..bae87be12 --- /dev/null +++ b/tests/integration/expected/felt_intrinsics.hir @@ -0,0 +1,318 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @felt_intrinsics { + public builtin.function @entrypoint(v0: felt, v1: felt) -> felt { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/intrinsics::felt::mul(v0, v1) : felt + v4 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/intrinsics::felt::sub(v3, v0) : felt + v5 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/intrinsics::felt::add(v4, v1) : felt + v6 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/intrinsics::felt::div(v0, v5) : felt + builtin.ret v6; + }; + + private builtin.function @__rustc::__rust_alloc(v7: i32, v8: i32) -> i32 { + ^block6(v7: i32, v8: i32): + v10 = arith.constant 1048580 : i32; + v11 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/::alloc(v10, v8, v7) : i32 + builtin.ret v11; + }; + + private builtin.function @__rustc::__rust_realloc(v12: i32, v13: i32, v14: i32, v15: i32) -> i32 { + ^block8(v12: i32, v13: i32, v14: i32, v15: i32): + v17 = arith.constant 1048580 : i32; + v18 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/::alloc(v17, v14, v15) : i32 + v197 = arith.constant 0 : i32; + v19 = arith.constant 0 : i32; + v20 = arith.eq v18, v19 : i1; + v21 = arith.zext v20 : u32; + v22 = hir.bitcast v21 : i32; + v24 = arith.neq v22, v197 : i1; + scf.if v24{ + ^block10: + scf.yield ; + } else { + ^block11: + v196 = arith.constant 0 : i32; + v26 = hir.bitcast v13 : u32; + v25 = hir.bitcast v15 : u32; + v27 = arith.lt v25, v26 : i1; + v28 = arith.zext v27 : u32; + v29 = hir.bitcast v28 : i32; + v31 = arith.neq v29, v196 : i1; + v32 = cf.select v31, v15, v13 : i32; + v194 = arith.constant 0 : i32; + v195 = arith.constant 0 : i32; + v34 = arith.eq v32, v195 : i1; + v35 = arith.zext v34 : u32; + v36 = hir.bitcast v35 : i32; + v38 = arith.neq v36, v194 : i1; + scf.if v38{ + ^block52: + scf.yield ; + } else { + ^block12: + v39 = hir.bitcast v32 : u32; + v40 = hir.bitcast v18 : u32; + v41 = hir.int_to_ptr v40 : ptr; + v42 = hir.bitcast v12 : u32; + v43 = hir.int_to_ptr v42 : ptr; + hir.mem_cpy v43, v41, v39; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v18; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block13: + builtin.ret ; + }; + + private builtin.function @::alloc(v45: i32, v46: i32, v47: i32) -> i32 { + ^block15(v45: i32, v46: i32, v47: i32): + v50 = arith.constant 16 : i32; + v49 = arith.constant 0 : i32; + v199 = arith.constant 16 : u32; + v52 = hir.bitcast v46 : u32; + v54 = arith.gt v52, v199 : i1; + v55 = arith.zext v54 : u32; + v56 = hir.bitcast v55 : i32; + v58 = arith.neq v56, v49 : i1; + v59 = cf.select v58, v46, v50 : i32; + v239 = arith.constant 0 : i32; + v60 = arith.constant -1 : i32; + v61 = arith.add v59, v60 : i32 #[overflow = wrapping]; + v62 = arith.band v59, v61 : i32; + v64 = arith.neq v62, v239 : i1; + v208, v209 = scf.if v64 : i32, u32 { + ^block57: + v200 = arith.constant 0 : u32; + v204 = ub.poison i32 : i32; + scf.yield v204, v200; + } else { + ^block18: + v66 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/core::ptr::alignment::Alignment::max(v46, v59) : i32 + v238 = arith.constant 0 : i32; + v65 = arith.constant -2147483648 : i32; + v67 = arith.sub v65, v66 : i32 #[overflow = wrapping]; + v69 = hir.bitcast v67 : u32; + v68 = hir.bitcast v47 : u32; + v70 = arith.gt v68, v69 : i1; + v71 = arith.zext v70 : u32; + v72 = hir.bitcast v71 : i32; + v74 = arith.neq v72, v238 : i1; + v223 = scf.if v74 : i32 { + ^block56: + v237 = ub.poison i32 : i32; + scf.yield v237; + } else { + ^block19: + v235 = arith.constant 0 : i32; + v80 = arith.sub v235, v66 : i32 #[overflow = wrapping]; + v236 = arith.constant -1 : i32; + v76 = arith.add v47, v66 : i32 #[overflow = wrapping]; + v78 = arith.add v76, v236 : i32 #[overflow = wrapping]; + v81 = arith.band v78, v80 : i32; + v82 = hir.bitcast v45 : u32; + v83 = arith.constant 4 : u32; + v84 = arith.mod v82, v83 : u32; + hir.assertz v84 #[code = 250]; + v85 = hir.int_to_ptr v82 : ptr; + v86 = hir.load v85 : i32; + v234 = arith.constant 0 : i32; + v88 = arith.neq v86, v234 : i1; + scf.if v88{ + ^block55: + scf.yield ; + } else { + ^block21: + v89 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/intrinsics::mem::heap_base() : i32 + v90 = hir.mem_size : u32; + v96 = hir.bitcast v45 : u32; + v233 = arith.constant 4 : u32; + v98 = arith.mod v96, v233 : u32; + hir.assertz v98 #[code = 250]; + v232 = arith.constant 16 : u32; + v91 = hir.bitcast v90 : i32; + v94 = arith.shl v91, v232 : i32; + v95 = arith.add v89, v94 : i32 #[overflow = wrapping]; + v99 = hir.int_to_ptr v96 : ptr; + hir.store v99, v95; + scf.yield ; + }; + v102 = hir.bitcast v45 : u32; + v231 = arith.constant 4 : u32; + v104 = arith.mod v102, v231 : u32; + hir.assertz v104 #[code = 250]; + v105 = hir.int_to_ptr v102 : ptr; + v106 = hir.load v105 : i32; + v229 = arith.constant 0 : i32; + v230 = arith.constant -1 : i32; + v108 = arith.bxor v106, v230 : i32; + v110 = hir.bitcast v108 : u32; + v109 = hir.bitcast v81 : u32; + v111 = arith.gt v109, v110 : i1; + v112 = arith.zext v111 : u32; + v113 = hir.bitcast v112 : i32; + v115 = arith.neq v113, v229 : i1; + v222 = scf.if v115 : i32 { + ^block22: + v228 = arith.constant 0 : i32; + scf.yield v228; + } else { + ^block23: + v117 = hir.bitcast v45 : u32; + v227 = arith.constant 4 : u32; + v119 = arith.mod v117, v227 : u32; + hir.assertz v119 #[code = 250]; + v116 = arith.add v106, v81 : i32 #[overflow = wrapping]; + v120 = hir.int_to_ptr v117 : ptr; + hir.store v120, v116; + v122 = arith.add v106, v66 : i32 #[overflow = wrapping]; + scf.yield v122; + }; + scf.yield v222; + }; + v205 = arith.constant 1 : u32; + v226 = arith.constant 0 : u32; + v224 = cf.select v74, v226, v205 : u32; + scf.yield v223, v224; + }; + v225 = arith.constant 0 : u32; + v221 = arith.eq v209, v225 : i1; + cf.cond_br v221 ^block17, ^block59(v208); + ^block17: + ub.unreachable ; + ^block59(v201: i32): + builtin.ret v201; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block24: + v125 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v125; + }; + + private builtin.function @intrinsics::felt::add(v127: felt, v128: felt) -> felt { + ^block28(v127: felt, v128: felt): + v129 = arith.add v127, v128 : felt #[overflow = unchecked]; + builtin.ret v129; + }; + + private builtin.function @intrinsics::felt::sub(v131: felt, v132: felt) -> felt { + ^block30(v131: felt, v132: felt): + v133 = arith.sub v131, v132 : felt #[overflow = unchecked]; + builtin.ret v133; + }; + + private builtin.function @intrinsics::felt::mul(v135: felt, v136: felt) -> felt { + ^block32(v135: felt, v136: felt): + v137 = arith.mul v135, v136 : felt #[overflow = unchecked]; + builtin.ret v137; + }; + + private builtin.function @intrinsics::felt::div(v139: felt, v140: felt) -> felt { + ^block34(v139: felt, v140: felt): + v141 = arith.div v139, v140 : felt; + builtin.ret v141; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v143: i32, v144: i32) -> i32 { + ^block36(v143: i32, v144: i32): + v151 = arith.constant 0 : i32; + v147 = hir.bitcast v144 : u32; + v146 = hir.bitcast v143 : u32; + v148 = arith.gt v146, v147 : i1; + v149 = arith.zext v148 : u32; + v150 = hir.bitcast v149 : i32; + v152 = arith.neq v150, v151 : i1; + v153 = cf.select v152, v143, v144 : i32; + builtin.ret v153; + }; + + public builtin.function @cabi_realloc(v154: i32, v155: i32, v156: i32, v157: i32) -> i32 { + ^block38(v154: i32, v155: i32, v156: i32, v157: i32): + v159 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/cabi_realloc_wit_bindgen_0_46_0(v154, v155, v156, v157) : i32 + builtin.ret v159; + }; + + private builtin.function @alloc::alloc::alloc(v160: i32, v161: i32) -> i32 { + ^block40(v160: i32, v161: i32): + hir.exec @root_ns:root@1.0.0/felt_intrinsics/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v163 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/__rustc::__rust_alloc(v161, v160) : i32 + builtin.ret v163; + }; + + public builtin.function @cabi_realloc_wit_bindgen_0_46_0(v164: i32, v165: i32, v166: i32, v167: i32) -> i32 { + ^block42(v164: i32, v165: i32, v166: i32, v167: i32): + v169 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/wit_bindgen::rt::cabi_realloc(v164, v165, v166, v167) : i32 + builtin.ret v169; + }; + + private builtin.function @wit_bindgen::rt::cabi_realloc(v170: i32, v171: i32, v172: i32, v173: i32) -> i32 { + ^block44(v170: i32, v171: i32, v172: i32, v173: i32): + v175 = arith.constant 0 : i32; + v176 = arith.neq v171, v175 : i1; + v250, v251, v252 = scf.if v176 : i32, i32, u32 { + ^block48: + v184 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/__rustc::__rust_realloc(v170, v171, v172, v173) : i32 + v241 = arith.constant 0 : u32; + v245 = ub.poison i32 : i32; + scf.yield v184, v245, v241; + } else { + ^block49: + v280 = arith.constant 0 : i32; + v281 = arith.constant 0 : i32; + v178 = arith.eq v173, v281 : i1; + v179 = arith.zext v178 : u32; + v180 = hir.bitcast v179 : i32; + v182 = arith.neq v180, v280 : i1; + v268 = scf.if v182 : i32 { + ^block63: + v279 = ub.poison i32 : i32; + scf.yield v279; + } else { + ^block50: + v183 = hir.exec @root_ns:root@1.0.0/felt_intrinsics/alloc::alloc::alloc(v172, v173) : i32 + scf.yield v183; + }; + v277 = arith.constant 0 : u32; + v246 = arith.constant 1 : u32; + v270 = cf.select v182, v246, v277 : u32; + v278 = ub.poison i32 : i32; + v269 = cf.select v182, v172, v278 : i32; + scf.yield v268, v269, v270; + }; + v257, v258 = scf.index_switch v252 : i32, u32 + case 0 { + ^block47: + v275 = arith.constant 0 : i32; + v187 = arith.neq v250, v275 : i1; + v272 = arith.constant 1 : u32; + v273 = arith.constant 0 : u32; + v267 = cf.select v187, v273, v272 : u32; + v274 = ub.poison i32 : i32; + v266 = cf.select v187, v250, v274 : i32; + scf.yield v266, v267; + } + default { + ^block70: + v276 = arith.constant 0 : u32; + scf.yield v251, v276; + }; + v271 = arith.constant 0 : u32; + v265 = arith.eq v258, v271 : i1; + cf.cond_br v265 ^block65, ^block66; + ^block65: + builtin.ret v257; + ^block66: + ub.unreachable ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.segment readonly @1048576 = 0x00000001; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/felt_intrinsics.wat b/tests/integration/expected/felt_intrinsics.wat new file mode 100644 index 000000000..90b4b3e96 --- /dev/null +++ b/tests/integration/expected/felt_intrinsics.wat @@ -0,0 +1,205 @@ +(module $felt_intrinsics.wasm + (type (;0;) (func (param f32 f32) (result f32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (type (;3;) (func)) + (type (;4;) (func (param i32 i32 i32) (result i32))) + (type (;5;) (func (result i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "cabi_realloc_wit_bindgen_0_46_0" (func $cabi_realloc_wit_bindgen_0_46_0)) + (export "cabi_realloc" (func $cabi_realloc)) + (elem (;0;) (i32.const 1) func $cabi_realloc) + (func $entrypoint (;0;) (type 0) (param f32 f32) (result f32) + local.get 0 + local.get 0 + local.get 1 + call $intrinsics::felt::mul + local.get 0 + call $intrinsics::felt::sub + local.get 1 + call $intrinsics::felt::add + call $intrinsics::felt::div + ) + (func $__rustc::__rust_alloc (;1;) (type 1) (param i32 i32) (result i32) + i32.const 1048580 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_realloc (;2;) (type 2) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + i32.const 1048580 + local.get 2 + local.get 3 + call $::alloc + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + local.get 1 + local.get 3 + local.get 1 + i32.lt_u + select + local.tee 3 + i32.eqz + br_if 0 (;@1;) + local.get 2 + local.get 0 + local.get 3 + memory.copy + end + local.get 2 + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;3;) (type 3) + return + ) + (func $::alloc (;4;) (type 4) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;5;) (type 5) (result i32) + unreachable + ) + (func $intrinsics::felt::add (;6;) (type 0) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::sub (;7;) (type 0) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::mul (;8;) (type 0) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::div (;9;) (type 0) (param f32 f32) (result f32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;10;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $cabi_realloc (;11;) (type 2) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $cabi_realloc_wit_bindgen_0_46_0 + ) + (func $alloc::alloc::alloc (;12;) (type 1) (param i32 i32) (result i32) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + local.get 1 + local.get 0 + call $__rustc::__rust_alloc + ) + (func $cabi_realloc_wit_bindgen_0_46_0 (;13;) (type 2) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $wit_bindgen::rt::cabi_realloc + ) + (func $wit_bindgen::rt::cabi_realloc (;14;) (type 2) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 1 + br_if 0 (;@3;) + local.get 3 + i32.eqz + br_if 2 (;@1;) + local.get 2 + local.get 3 + call $alloc::alloc::alloc + local.set 2 + br 1 (;@2;) + end + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $__rustc::__rust_realloc + local.set 2 + end + local.get 2 + br_if 0 (;@1;) + unreachable + end + local.get 2 + ) + (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00") +) diff --git a/tests/integration/expected/fib.hir b/tests/integration/expected/fib.hir deleted file mode 100644 index e4d110806..000000000 --- a/tests/integration/expected/fib.hir +++ /dev/null @@ -1,39 +0,0 @@ -(component - ;; Modules - (module #miden_integration_tests_rust_fib_wasm - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) - - ;; Functions - (func (export #fib) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (const.i32 1)) - (br (block 2 v4 v0 v3))) - - (block 1 (param v1 i32)) - - (block 2 (param v6 i32) (param v7 i32) (param v9 i32) - (let (v8 i1) (neq v7 0)) - (condbr v8 (block 4) (block 5))) - - (block 3 (param v5 i32)) - - (block 4 - (let (v10 i32) (const.i32 -1)) - (let (v11 i32) (add.wrapping v7 v10)) - (let (v12 i32) (add.wrapping v9 v6)) - (br (block 2 v12 v11 v6))) - - (block 5 - (ret v9)) - ) - ) - -) diff --git a/tests/integration/expected/fib.masm b/tests/integration/expected/fib.masm deleted file mode 100644 index 6096c2017..000000000 --- a/tests/integration/expected/fib.masm +++ /dev/null @@ -1,33 +0,0 @@ -# mod miden_integration_tests_rust_fib_wasm - -export.fib - push.0 - push.1 - movup.2 - swap.1 - dup.1 - neq.0 - push.1 - while.true - if.true - push.4294967295 - movup.2 - swap.1 - u32wrapping_add - dup.1 - swap.1 - swap.3 - swap.1 - u32wrapping_add - movup.2 - swap.1 - dup.1 - neq.0 - push.1 - else - drop drop push.0 - end - end -end - - diff --git a/tests/integration/expected/fib.wat b/tests/integration/expected/fib.wat deleted file mode 100644 index 580653ea1..000000000 --- a/tests/integration/expected/fib.wat +++ /dev/null @@ -1,39 +0,0 @@ -(module $miden_integration_tests_rust_fib_wasm.wasm - (type (;0;) (func (param i32) (result i32))) - (func $fib (;0;) (type 0) (param i32) (result i32) - (local i32 i32 i32) - i32.const 0 - local.set 1 - i32.const 1 - local.set 2 - loop (result i32) ;; label = @1 - local.get 2 - local.set 3 - block ;; label = @2 - local.get 0 - br_if 0 (;@2;) - local.get 1 - return - end - local.get 0 - i32.const -1 - i32.add - local.set 0 - local.get 1 - local.get 3 - i32.add - local.set 2 - local.get 3 - local.set 1 - br 0 (;@1;) - end - ) - (memory (;0;) 16) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (global (;1;) i32 i32.const 1048576) - (global (;2;) i32 i32.const 1048576) - (export "memory" (memory 0)) - (export "fib" (func $fib)) - (export "__data_end" (global 1)) - (export "__heap_base" (global 2)) -) \ No newline at end of file diff --git a/tests/integration/expected/func_arg_order.hir b/tests/integration/expected/func_arg_order.hir new file mode 100644 index 000000000..69b6eefbd --- /dev/null +++ b/tests/integration/expected/func_arg_order.hir @@ -0,0 +1,117 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @func_arg_order { + public builtin.function @entrypoint(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt) -> felt { + ^block4(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt): + v10 = builtin.global_symbol @root_ns:root@1.0.0/func_arg_order/__stack_pointer : ptr + v11 = hir.bitcast v10 : ptr; + v12 = hir.load v11 : i32; + v13 = arith.constant 48 : i32; + v14 = arith.sub v12, v13 : i32 #[overflow = wrapping]; + v15 = builtin.global_symbol @root_ns:root@1.0.0/func_arg_order/__stack_pointer : ptr + v16 = hir.bitcast v15 : ptr; + hir.store v16, v14; + v18 = arith.constant 28 : u32; + v17 = hir.bitcast v14 : u32; + v19 = arith.add v17, v18 : u32 #[overflow = checked]; + v20 = arith.constant 4 : u32; + v21 = arith.mod v19, v20 : u32; + hir.assertz v21 #[code = 250]; + v22 = hir.int_to_ptr v19 : ptr; + hir.store v22, v7; + v24 = arith.constant 24 : u32; + v23 = hir.bitcast v14 : u32; + v25 = arith.add v23, v24 : u32 #[overflow = checked]; + v104 = arith.constant 4 : u32; + v27 = arith.mod v25, v104 : u32; + hir.assertz v27 #[code = 250]; + v28 = hir.int_to_ptr v25 : ptr; + hir.store v28, v6; + v30 = arith.constant 20 : u32; + v29 = hir.bitcast v14 : u32; + v31 = arith.add v29, v30 : u32 #[overflow = checked]; + v103 = arith.constant 4 : u32; + v33 = arith.mod v31, v103 : u32; + hir.assertz v33 #[code = 250]; + v34 = hir.int_to_ptr v31 : ptr; + hir.store v34, v5; + v36 = arith.constant 16 : u32; + v35 = hir.bitcast v14 : u32; + v37 = arith.add v35, v36 : u32 #[overflow = checked]; + v102 = arith.constant 4 : u32; + v39 = arith.mod v37, v102 : u32; + hir.assertz v39 #[code = 250]; + v40 = hir.int_to_ptr v37 : ptr; + hir.store v40, v4; + v42 = arith.constant 12 : u32; + v41 = hir.bitcast v14 : u32; + v43 = arith.add v41, v42 : u32 #[overflow = checked]; + v101 = arith.constant 4 : u32; + v45 = arith.mod v43, v101 : u32; + hir.assertz v45 #[code = 250]; + v46 = hir.int_to_ptr v43 : ptr; + hir.store v46, v3; + v48 = arith.constant 8 : u32; + v47 = hir.bitcast v14 : u32; + v49 = arith.add v47, v48 : u32 #[overflow = checked]; + v100 = arith.constant 4 : u32; + v51 = arith.mod v49, v100 : u32; + hir.assertz v51 #[code = 250]; + v52 = hir.int_to_ptr v49 : ptr; + hir.store v52, v2; + v99 = arith.constant 4 : u32; + v53 = hir.bitcast v14 : u32; + v55 = arith.add v53, v99 : u32 #[overflow = checked]; + v98 = arith.constant 4 : u32; + v57 = arith.mod v55, v98 : u32; + hir.assertz v57 #[code = 250]; + v58 = hir.int_to_ptr v55 : ptr; + hir.store v58, v1; + v59 = hir.bitcast v14 : u32; + v97 = arith.constant 4 : u32; + v61 = arith.mod v59, v97 : u32; + hir.assertz v61 #[code = 250]; + v62 = hir.int_to_ptr v59 : ptr; + hir.store v62, v0; + v93 = arith.constant 1048528 : felt; + v63 = hir.bitcast v14 : felt; + hir.assert_eq v63, v93; + v92 = arith.constant 1048560 : felt; + v66 = arith.constant 32 : i32; + v67 = arith.add v14, v66 : i32 #[overflow = wrapping]; + v68 = hir.bitcast v67 : felt; + hir.assert_eq v68, v92; + v96 = arith.constant 32 : i32; + v72 = arith.add v14, v96 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/func_arg_order/intrinsic(v14, v72) + v74 = arith.constant 32 : u32; + v73 = hir.bitcast v14 : u32; + v75 = arith.add v73, v74 : u32 #[overflow = checked]; + v95 = arith.constant 4 : u32; + v77 = arith.mod v75, v95 : u32; + hir.assertz v77 #[code = 250]; + v78 = hir.int_to_ptr v75 : ptr; + v79 = hir.load v78 : felt; + v94 = arith.constant 48 : i32; + v81 = arith.add v14, v94 : i32 #[overflow = wrapping]; + v82 = builtin.global_symbol @root_ns:root@1.0.0/func_arg_order/__stack_pointer : ptr + v83 = hir.bitcast v82 : ptr; + hir.store v83, v81; + builtin.ret v79; + }; + + public builtin.function @intrinsic(v84: i32, v85: i32) { + ^block6(v84: i32, v85: i32): + v106 = arith.constant 1048528 : felt; + v86 = hir.bitcast v84 : felt; + hir.assert_eq v86, v106; + v105 = arith.constant 1048560 : felt; + v89 = hir.bitcast v85 : felt; + hir.assert_eq v89, v105; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/func_arg_order.masm b/tests/integration/expected/func_arg_order.masm new file mode 100644 index 000000000..b3a0ee3c4 --- /dev/null +++ b/tests/integration/expected/func_arg_order.masm @@ -0,0 +1,243 @@ +# mod root_ns:root@1.0.0 + +export.init + push.1114112 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.262144 +end + +# mod root_ns:root@1.0.0::func_arg_order + +export.entrypoint + push.1048576 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1048576 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.28 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.9 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.24 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.8 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.20 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.7 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.6 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.1048528 + dup.1 + assert_eq + push.1048560 + push.32 + dup.2 + swap.1 + u32wrapping_add + assert_eq + push.32 + dup.1 + swap.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::func_arg_order::intrinsic + trace.252 + nop + push.32 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.48 + movup.2 + swap.1 + u32wrapping_add + push.1048576 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +export.intrinsic + push.1048528 + swap.1 + assert_eq + push.1048560 + swap.1 + assert_eq +end + diff --git a/tests/integration/expected/func_arg_order.wat b/tests/integration/expected/func_arg_order.wat new file mode 100644 index 000000000..f5dc7e1c9 --- /dev/null +++ b/tests/integration/expected/func_arg_order.wat @@ -0,0 +1,83 @@ +(module $func_arg_order.wasm + (type (;0;) (func (param i32) (result f32))) + (type (;1;) (func (param f32 f32))) + (type (;2;) (func (param f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) + (type (;3;) (func (param i32 i32))) + (import "miden:core-intrinsics/intrinsics-felt@1.0.0" "from-u32" (func $miden_stdlib_sys::intrinsics::felt::extern_from_u32 (;0;) (type 0))) + (import "miden:core-intrinsics/intrinsics-felt@1.0.0" "assert-eq" (func $miden_stdlib_sys::intrinsics::felt::extern_assert_eq (;1;) (type 1))) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "intrinsic" (func $intrinsic)) + (func $entrypoint (;2;) (type 2) (param f32 f32 f32 f32 f32 f32 f32 f32) (result f32) + (local i32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 8 + global.set $__stack_pointer + local.get 8 + local.get 7 + f32.store offset=28 + local.get 8 + local.get 6 + f32.store offset=24 + local.get 8 + local.get 5 + f32.store offset=20 + local.get 8 + local.get 4 + f32.store offset=16 + local.get 8 + local.get 3 + f32.store offset=12 + local.get 8 + local.get 2 + f32.store offset=8 + local.get 8 + local.get 1 + f32.store offset=4 + local.get 8 + local.get 0 + f32.store + local.get 8 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + i32.const 1048528 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + call $miden_stdlib_sys::intrinsics::felt::extern_assert_eq + local.get 8 + i32.const 32 + i32.add + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + i32.const 1048560 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + call $miden_stdlib_sys::intrinsics::felt::extern_assert_eq + local.get 8 + local.get 8 + i32.const 32 + i32.add + call $intrinsic + local.get 8 + f32.load offset=32 + local.set 7 + local.get 8 + i32.const 48 + i32.add + global.set $__stack_pointer + local.get 7 + ) + (func $intrinsic (;3;) (type 3) (param i32 i32) + local.get 0 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + i32.const 1048528 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + call $miden_stdlib_sys::intrinsics::felt::extern_assert_eq + local.get 1 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + i32.const 1048560 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + call $miden_stdlib_sys::intrinsics::felt::extern_assert_eq + ) +) diff --git a/tests/integration/expected/func_arg_same.hir b/tests/integration/expected/func_arg_same.hir new file mode 100644 index 000000000..21c7284a4 --- /dev/null +++ b/tests/integration/expected/func_arg_same.hir @@ -0,0 +1,18 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @func_arg_same { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block4(v0: i32, v1: i32): + v3 = hir.exec @root_ns:root@1.0.0/func_arg_same/intrinsic(v0, v0) : i32 + builtin.ret v3; + }; + + public builtin.function @intrinsic(v4: i32, v5: i32) -> i32 { + ^block6(v4: i32, v5: i32): + builtin.ret v4; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/func_arg_same.masm b/tests/integration/expected/func_arg_same.masm new file mode 100644 index 000000000..a52d0bf49 --- /dev/null +++ b/tests/integration/expected/func_arg_same.masm @@ -0,0 +1,30 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::func_arg_same + +export.entrypoint + swap.1 + drop + dup.0 + trace.240 + nop + exec.::root_ns:root@1.0.0::func_arg_same::intrinsic + trace.252 + nop +end + +export.intrinsic + swap.1 + drop +end + diff --git a/tests/integration/expected/func_arg_same.wat b/tests/integration/expected/func_arg_same.wat new file mode 100644 index 000000000..2dc97a133 --- /dev/null +++ b/tests/integration/expected/func_arg_same.wat @@ -0,0 +1,17 @@ +(module $func_arg_same.wasm + (type (;0;) (func (param i32 i32) (result i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "intrinsic" (func $intrinsic)) + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 0 + call $intrinsic + ) + (func $intrinsic (;1;) (type 0) (param i32 i32) (result i32) + local.get 0 + ) +) diff --git a/tests/integration/expected/function_call_hir2.hir b/tests/integration/expected/function_call_hir2.hir new file mode 100644 index 000000000..a13cf1eda --- /dev/null +++ b/tests/integration/expected/function_call_hir2.hir @@ -0,0 +1,19 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @function_call_hir2 { + public builtin.function @add(v0: i32, v1: i32) -> i32 { + ^block4(v0: i32, v1: i32): + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; + + public builtin.function @entrypoint(v4: i32, v5: i32) -> i32 { + ^block6(v4: i32, v5: i32): + v7 = hir.exec @root_ns:root@1.0.0/function_call_hir2/add(v4, v5) : i32 + builtin.ret v7; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/function_call_hir2.wat b/tests/integration/expected/function_call_hir2.wat new file mode 100644 index 000000000..ffb443153 --- /dev/null +++ b/tests/integration/expected/function_call_hir2.wat @@ -0,0 +1,18 @@ +(module $function_call_hir2.wasm + (type (;0;) (func (param i32 i32) (result i32))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "add" (func $add)) + (export "entrypoint" (func $entrypoint)) + (func $add (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + ) + (func $entrypoint (;1;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + call $add + ) +) diff --git a/tests/integration/expected/ge_felt.hir b/tests/integration/expected/ge_felt.hir index 0b42efeb8..25206f0ae 100644 --- a/tests/integration/expected/ge_felt.hir +++ b/tests/integration/expected/ge_felt.hir @@ -1,25 +1,24 @@ -(component - ;; Modules - (module #ge_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @ge_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> i32 { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/ge_felt/intrinsics::felt::ge(v0, v1) : i32 + v4 = arith.constant 0 : i32; + v5 = arith.neq v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::ge(v8: felt, v9: felt) -> i32 { + ^block6(v8: felt, v9: felt): + v10 = arith.gte v8, v9 : i1; + v11 = hir.cast v10 : i32; + builtin.ret v11; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result i32) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 i1) (gte v0 v1)) - (let (v4 i32) (cast v3)) - (let (v5 i32) (const.i32 0)) - (let (v6 i1) (neq v4 v5)) - (let (v7 i32) (zext v6)) - (br (block 1 v7))) - - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/ge_felt.masm b/tests/integration/expected/ge_felt.masm index 5b8b2d1b4..d5462380b 100644 --- a/tests/integration/expected/ge_felt.masm +++ b/tests/integration/expected/ge_felt.masm @@ -1,7 +1,29 @@ -# mod ge_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::ge_felt export.entrypoint - swap.1 gte push.0 neq + trace.240 + nop + exec.::root_ns:root@1.0.0::ge_felt::intrinsics::felt::ge + trace.252 + nop + push.0 + neq end +proc.intrinsics::felt::ge + swap.1 + gte +end diff --git a/tests/integration/expected/ge_felt.wat b/tests/integration/expected/ge_felt.wat index 38609083a..b5ba15e8d 100644 --- a/tests/integration/expected/ge_felt.wat +++ b/tests/integration/expected/ge_felt.wat @@ -1,16 +1,18 @@ (module $ge_felt.wasm (type (;0;) (func (param f32 f32) (result i32))) - (import "miden:stdlib/intrinsics_felt" "ge" (func $miden_stdlib_sys::intrinsics::felt::extern_ge (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result i32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_ge - i32.const 0 - i32.ne - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result i32) + local.get 0 + local.get 1 + call $intrinsics::felt::ge + i32.const 0 + i32.ne + ) + (func $intrinsics::felt::ge (;1;) (type 0) (param f32 f32) (result i32) + unreachable + ) +) diff --git a/tests/integration/expected/ge_i32.hir b/tests/integration/expected/ge_i32.hir index 0aceff9f7..f3ccd89ae 100644 --- a/tests/integration/expected/ge_i32.hir +++ b/tests/integration/expected/ge_i32.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_c0f27bd914ea4f9a33c6502694b5eeace55ed7a40a907b0c5fd68e0aa656a56b - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_1e76175a91b5fc3090baafa017f5a38c53c37f1e1a73be7ebaa886f57b9f86fb { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.gte v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (gte v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/ge_i32.masm b/tests/integration/expected/ge_i32.masm index 1a09454ea..6f6e80941 100644 --- a/tests/integration/expected/ge_i32.masm +++ b/tests/integration/expected/ge_i32.masm @@ -1,7 +1,29 @@ -# mod test_rust_c0f27bd914ea4f9a33c6502694b5eeace55ed7a40a907b0c5fd68e0aa656a56b +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 exec.::intrinsics::i32::is_gte +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_1e76175a91b5fc3090baafa017f5a38c53c37f1e1a73be7ebaa886f57b9f86fb + +export.entrypoint + swap.1 + trace.240 + nop + exec.::intrinsics::i32::is_gte + trace.252 + nop +end diff --git a/tests/integration/expected/ge_i32.wat b/tests/integration/expected/ge_i32.wat index 502095ece..96813a3e3 100644 --- a/tests/integration/expected/ge_i32.wat +++ b/tests/integration/expected/ge_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_c0f27bd914ea4f9a33c6502694b5eeace55ed7a40a907b0c5fd68e0aa656a56b.wasm +(module $test_rust_1e76175a91b5fc3090baafa017f5a38c53c37f1e1a73be7ebaa886f57b9f86fb.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.ge_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.ge_s + ) +) diff --git a/tests/integration/expected/ge_i64.hir b/tests/integration/expected/ge_i64.hir index 165f912d5..1f78218e9 100644 --- a/tests/integration/expected/ge_i64.hir +++ b/tests/integration/expected/ge_i64.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_1dc910df9e5ef302ee236233065a2a50427fb4ff2db8591dcc6713f05a95d77a - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_9edabb66d927b03a3d0e64c65bad79cd5488a5bf59a9059a832f3582c7184d6b { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v3 = arith.gte v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i1) (gte v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/ge_i64.masm b/tests/integration/expected/ge_i64.masm index 39752f12f..1f66cf70c 100644 --- a/tests/integration/expected/ge_i64.masm +++ b/tests/integration/expected/ge_i64.masm @@ -1,7 +1,30 @@ -# mod test_rust_1dc910df9e5ef302ee236233065a2a50427fb4ff2db8591dcc6713f05a95d77a +# mod root_ns:root@1.0.0 -export.entrypoint - movdn.3 movdn.3 exec.::intrinsics::i64::gte +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_9edabb66d927b03a3d0e64c65bad79cd5488a5bf59a9059a832f3582c7184d6b + +export.entrypoint + movdn.3 + movdn.3 + trace.240 + nop + exec.::intrinsics::i64::gte + trace.252 + nop +end diff --git a/tests/integration/expected/ge_i64.wat b/tests/integration/expected/ge_i64.wat index dd121bf6d..630373626 100644 --- a/tests/integration/expected/ge_i64.wat +++ b/tests/integration/expected/ge_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_1dc910df9e5ef302ee236233065a2a50427fb4ff2db8591dcc6713f05a95d77a.wasm +(module $test_rust_9edabb66d927b03a3d0e64c65bad79cd5488a5bf59a9059a832f3582c7184d6b.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.ge_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.ge_s + ) +) diff --git a/tests/integration/expected/ge_u16.hir b/tests/integration/expected/ge_u16.hir index fbbcf4292..013c945bf 100644 --- a/tests/integration/expected/ge_u16.hir +++ b/tests/integration/expected/ge_u16.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_0eeac93afe2a6fee749f5e48efe12f98e2e005f6a92fa7f020166662c68c1750 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_6b89d656eff2711a9ee50d11f22845c4cbbc0df54339492d30dcf628910850d2 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.gte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (gte v3 v4)) - (let (v6 i32) (zext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/ge_u16.masm b/tests/integration/expected/ge_u16.masm index 8ffcb9ae9..9256b0c94 100644 --- a/tests/integration/expected/ge_u16.masm +++ b/tests/integration/expected/ge_u16.masm @@ -1,7 +1,27 @@ -# mod test_rust_0eeac93afe2a6fee749f5e48efe12f98e2e005f6a92fa7f020166662c68c1750 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32gte +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_6b89d656eff2711a9ee50d11f22845c4cbbc0df54339492d30dcf628910850d2 + +export.entrypoint + swap.1 + swap.1 + swap.1 + u32gte +end diff --git a/tests/integration/expected/ge_u16.wat b/tests/integration/expected/ge_u16.wat index 23bfec6e6..58d04246e 100644 --- a/tests/integration/expected/ge_u16.wat +++ b/tests/integration/expected/ge_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_0eeac93afe2a6fee749f5e48efe12f98e2e005f6a92fa7f020166662c68c1750.wasm +(module $test_rust_6b89d656eff2711a9ee50d11f22845c4cbbc0df54339492d30dcf628910850d2.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.ge_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.ge_u + ) +) diff --git a/tests/integration/expected/ge_u32.hir b/tests/integration/expected/ge_u32.hir index 85ed05ff0..aa3054ee5 100644 --- a/tests/integration/expected/ge_u32.hir +++ b/tests/integration/expected/ge_u32.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_4fa5dff76055193b0eab22d64f4284da486889ba07bebda7bae15ce0af5473af - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_a6a04660dda2007e5b39d22340b438c2120c4114b075c90883d9b212365eb5fe { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.gte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (gte v3 v4)) - (let (v6 i32) (zext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/ge_u32.masm b/tests/integration/expected/ge_u32.masm index 7dd8988a5..e740e594f 100644 --- a/tests/integration/expected/ge_u32.masm +++ b/tests/integration/expected/ge_u32.masm @@ -1,7 +1,27 @@ -# mod test_rust_4fa5dff76055193b0eab22d64f4284da486889ba07bebda7bae15ce0af5473af +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32gte +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_a6a04660dda2007e5b39d22340b438c2120c4114b075c90883d9b212365eb5fe + +export.entrypoint + swap.1 + swap.1 + swap.1 + u32gte +end diff --git a/tests/integration/expected/ge_u32.wat b/tests/integration/expected/ge_u32.wat index 3ead6d43c..5d29c38cf 100644 --- a/tests/integration/expected/ge_u32.wat +++ b/tests/integration/expected/ge_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_4fa5dff76055193b0eab22d64f4284da486889ba07bebda7bae15ce0af5473af.wasm +(module $test_rust_a6a04660dda2007e5b39d22340b438c2120c4114b075c90883d9b212365eb5fe.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.ge_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.ge_u + ) +) diff --git a/tests/integration/expected/ge_u64.hir b/tests/integration/expected/ge_u64.hir index 344f246f7..94e1bb434 100644 --- a/tests/integration/expected/ge_u64.hir +++ b/tests/integration/expected/ge_u64.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_77837611a2574dae16586d25d91d315da506c432500ce2d6c4234173b9ea07b2 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_b008d40be4d6e94465c1a26de42c380b06e09a3774e7a937bf5c338fd437b333 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v4 = hir.bitcast v1 : u64; + v3 = hir.bitcast v0 : u64; + v5 = arith.gte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u64) (bitcast v0)) - (let (v4 u64) (bitcast v1)) - (let (v5 i1) (gte v3 v4)) - (let (v6 i32) (zext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/ge_u64.masm b/tests/integration/expected/ge_u64.masm index b9b9e7d8a..cd643deb3 100644 --- a/tests/integration/expected/ge_u64.masm +++ b/tests/integration/expected/ge_u64.masm @@ -1,7 +1,34 @@ -# mod test_rust_77837611a2574dae16586d25d91d315da506c432500ce2d6c4234173b9ea07b2 +# mod root_ns:root@1.0.0 -export.entrypoint - movdn.3 movdn.3 exec.::std::math::u64::gte +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_b008d40be4d6e94465c1a26de42c380b06e09a3774e7a937bf5c338fd437b333 + +export.entrypoint + movdn.3 + movdn.3 + movdn.3 + movdn.3 + movdn.3 + movdn.3 + trace.240 + nop + exec.::std::math::u64::gte + trace.252 + nop +end diff --git a/tests/integration/expected/ge_u64.wat b/tests/integration/expected/ge_u64.wat index 65453aee7..2065ee97b 100644 --- a/tests/integration/expected/ge_u64.wat +++ b/tests/integration/expected/ge_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_77837611a2574dae16586d25d91d315da506c432500ce2d6c4234173b9ea07b2.wasm +(module $test_rust_b008d40be4d6e94465c1a26de42c380b06e09a3774e7a937bf5c338fd437b333.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.ge_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.ge_u + ) +) diff --git a/tests/integration/expected/ge_u8.hir b/tests/integration/expected/ge_u8.hir index 064d0cc30..300baa6cc 100644 --- a/tests/integration/expected/ge_u8.hir +++ b/tests/integration/expected/ge_u8.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_e66ace9d45878034c39e91c35eb5f67915dc01fb5a1ed5c34627e3bb99107855 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_a7d66021fee4ca04dff85edcc2a0482cfea1494683ab39c1ae381746dfbf0d98 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.gte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (gte v3 v4)) - (let (v6 i32) (zext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/ge_u8.masm b/tests/integration/expected/ge_u8.masm index 14681b3cc..983f1ba1c 100644 --- a/tests/integration/expected/ge_u8.masm +++ b/tests/integration/expected/ge_u8.masm @@ -1,7 +1,27 @@ -# mod test_rust_e66ace9d45878034c39e91c35eb5f67915dc01fb5a1ed5c34627e3bb99107855 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32gte +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_a7d66021fee4ca04dff85edcc2a0482cfea1494683ab39c1ae381746dfbf0d98 + +export.entrypoint + swap.1 + swap.1 + swap.1 + u32gte +end diff --git a/tests/integration/expected/ge_u8.wat b/tests/integration/expected/ge_u8.wat index cc253bfca..9e2ec6fd4 100644 --- a/tests/integration/expected/ge_u8.wat +++ b/tests/integration/expected/ge_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_e66ace9d45878034c39e91c35eb5f67915dc01fb5a1ed5c34627e3bb99107855.wasm +(module $test_rust_a7d66021fee4ca04dff85edcc2a0482cfea1494683ab39c1ae381746dfbf0d98.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.ge_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.ge_u + ) +) diff --git a/tests/integration/expected/gt_felt.hir b/tests/integration/expected/gt_felt.hir index e5aeaf64a..481aa55f1 100644 --- a/tests/integration/expected/gt_felt.hir +++ b/tests/integration/expected/gt_felt.hir @@ -1,25 +1,24 @@ -(component - ;; Modules - (module #gt_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @gt_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> i32 { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/gt_felt/intrinsics::felt::gt(v0, v1) : i32 + v4 = arith.constant 0 : i32; + v5 = arith.neq v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::gt(v8: felt, v9: felt) -> i32 { + ^block6(v8: felt, v9: felt): + v10 = arith.gt v8, v9 : i1; + v11 = hir.cast v10 : i32; + builtin.ret v11; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result i32) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 i1) (gt v0 v1)) - (let (v4 i32) (cast v3)) - (let (v5 i32) (const.i32 0)) - (let (v6 i1) (neq v4 v5)) - (let (v7 i32) (zext v6)) - (br (block 1 v7))) - - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/gt_felt.masm b/tests/integration/expected/gt_felt.masm index 6da744d4c..2c30c07b2 100644 --- a/tests/integration/expected/gt_felt.masm +++ b/tests/integration/expected/gt_felt.masm @@ -1,7 +1,29 @@ -# mod gt_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::gt_felt export.entrypoint - swap.1 gt push.0 neq + trace.240 + nop + exec.::root_ns:root@1.0.0::gt_felt::intrinsics::felt::gt + trace.252 + nop + push.0 + neq end +proc.intrinsics::felt::gt + swap.1 + gt +end diff --git a/tests/integration/expected/gt_felt.wat b/tests/integration/expected/gt_felt.wat index b91e22491..672192f3c 100644 --- a/tests/integration/expected/gt_felt.wat +++ b/tests/integration/expected/gt_felt.wat @@ -1,16 +1,18 @@ (module $gt_felt.wasm (type (;0;) (func (param f32 f32) (result i32))) - (import "miden:stdlib/intrinsics_felt" "gt" (func $miden_stdlib_sys::intrinsics::felt::extern_gt (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result i32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_gt - i32.const 0 - i32.ne - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result i32) + local.get 0 + local.get 1 + call $intrinsics::felt::gt + i32.const 0 + i32.ne + ) + (func $intrinsics::felt::gt (;1;) (type 0) (param f32 f32) (result i32) + unreachable + ) +) diff --git a/tests/integration/expected/gt_i32.hir b/tests/integration/expected/gt_i32.hir index dee3370d7..b3c630c71 100644 --- a/tests/integration/expected/gt_i32.hir +++ b/tests/integration/expected/gt_i32.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_dcf68bbd3d38962baca8bcc39895a74e9f927a15ae1af780f83843028d53154e - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_9d2e8775109dcfc1c935f1f5d3649375cc140aedefb7659a7c1d3720c82086ec { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.gt v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (gt v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/gt_i32.masm b/tests/integration/expected/gt_i32.masm index 104bd0b78..520c7b25d 100644 --- a/tests/integration/expected/gt_i32.masm +++ b/tests/integration/expected/gt_i32.masm @@ -1,7 +1,29 @@ -# mod test_rust_dcf68bbd3d38962baca8bcc39895a74e9f927a15ae1af780f83843028d53154e +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 exec.::intrinsics::i32::is_gt +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_9d2e8775109dcfc1c935f1f5d3649375cc140aedefb7659a7c1d3720c82086ec + +export.entrypoint + swap.1 + trace.240 + nop + exec.::intrinsics::i32::is_gt + trace.252 + nop +end diff --git a/tests/integration/expected/gt_i32.wat b/tests/integration/expected/gt_i32.wat index 80ba05774..732115b64 100644 --- a/tests/integration/expected/gt_i32.wat +++ b/tests/integration/expected/gt_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_dcf68bbd3d38962baca8bcc39895a74e9f927a15ae1af780f83843028d53154e.wasm +(module $test_rust_9d2e8775109dcfc1c935f1f5d3649375cc140aedefb7659a7c1d3720c82086ec.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.gt_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.gt_s + ) +) diff --git a/tests/integration/expected/gt_i64.hir b/tests/integration/expected/gt_i64.hir index 2391ac9af..d62c5b788 100644 --- a/tests/integration/expected/gt_i64.hir +++ b/tests/integration/expected/gt_i64.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_ceadd0c619424ea8897ac24b5176dad699485353543e7607a12bdee342a50966 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_7d82f2e08b811f59ed036f725c1b72ca685b95ee301d765a74b8f7da3048058b { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v3 = arith.gt v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i1) (gt v0 v1)) - (let (v4 i32) (zext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/gt_i64.masm b/tests/integration/expected/gt_i64.masm index ff1907cb8..8ad8e748d 100644 --- a/tests/integration/expected/gt_i64.masm +++ b/tests/integration/expected/gt_i64.masm @@ -1,7 +1,30 @@ -# mod test_rust_ceadd0c619424ea8897ac24b5176dad699485353543e7607a12bdee342a50966 +# mod root_ns:root@1.0.0 -export.entrypoint - movdn.3 movdn.3 exec.::intrinsics::i64::gt +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_7d82f2e08b811f59ed036f725c1b72ca685b95ee301d765a74b8f7da3048058b + +export.entrypoint + movdn.3 + movdn.3 + trace.240 + nop + exec.::intrinsics::i64::gt + trace.252 + nop +end diff --git a/tests/integration/expected/gt_i64.wat b/tests/integration/expected/gt_i64.wat index d2365888d..27c39e641 100644 --- a/tests/integration/expected/gt_i64.wat +++ b/tests/integration/expected/gt_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_ceadd0c619424ea8897ac24b5176dad699485353543e7607a12bdee342a50966.wasm +(module $test_rust_7d82f2e08b811f59ed036f725c1b72ca685b95ee301d765a74b8f7da3048058b.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.gt_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.gt_s + ) +) diff --git a/tests/integration/expected/gt_u16.hir b/tests/integration/expected/gt_u16.hir index 3a36d99f6..88e5bfa25 100644 --- a/tests/integration/expected/gt_u16.hir +++ b/tests/integration/expected/gt_u16.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_f264d3777d3a68a4fab468d26338823b06e58dd59c87e8d6f2fc2d0d672bb1cd - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_cacaae112ac2013d020f0f82f9c334edde1d01c5854d6174ba2ebf34c2a8fc37 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.gt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (gt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/gt_u16.masm b/tests/integration/expected/gt_u16.masm index 5f0c5bf3b..599f813ce 100644 --- a/tests/integration/expected/gt_u16.masm +++ b/tests/integration/expected/gt_u16.masm @@ -1,14 +1,27 @@ -# mod test_rust_f264d3777d3a68a4fab468d26338823b06e58dd59c87e8d6f2fc2d0d672bb1cd +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_cacaae112ac2013d020f0f82f9c334edde1d01c5854d6174ba2ebf34c2a8fc37 export.entrypoint + swap.1 + swap.1 swap.1 u32gt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/gt_u16.wat b/tests/integration/expected/gt_u16.wat index c201cd3b3..1d291858a 100644 --- a/tests/integration/expected/gt_u16.wat +++ b/tests/integration/expected/gt_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_f264d3777d3a68a4fab468d26338823b06e58dd59c87e8d6f2fc2d0d672bb1cd.wasm +(module $test_rust_cacaae112ac2013d020f0f82f9c334edde1d01c5854d6174ba2ebf34c2a8fc37.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.gt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.gt_u + ) +) diff --git a/tests/integration/expected/gt_u32.hir b/tests/integration/expected/gt_u32.hir index 5707d2374..05cc07890 100644 --- a/tests/integration/expected/gt_u32.hir +++ b/tests/integration/expected/gt_u32.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_1e85457275952da03652b53164a185a9463cbfd5807e6d9eeae9b23a1844e96e - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_35873b6ce3c76e068032779fcd1e75617c26429a13cc7bcd687d3302885cf1f1 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.gt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (gt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/gt_u32.masm b/tests/integration/expected/gt_u32.masm index 70270946a..5bd2610b2 100644 --- a/tests/integration/expected/gt_u32.masm +++ b/tests/integration/expected/gt_u32.masm @@ -1,14 +1,27 @@ -# mod test_rust_1e85457275952da03652b53164a185a9463cbfd5807e6d9eeae9b23a1844e96e +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_35873b6ce3c76e068032779fcd1e75617c26429a13cc7bcd687d3302885cf1f1 export.entrypoint + swap.1 + swap.1 swap.1 u32gt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/gt_u32.wat b/tests/integration/expected/gt_u32.wat index d1ad2ab74..8f8605733 100644 --- a/tests/integration/expected/gt_u32.wat +++ b/tests/integration/expected/gt_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_1e85457275952da03652b53164a185a9463cbfd5807e6d9eeae9b23a1844e96e.wasm +(module $test_rust_35873b6ce3c76e068032779fcd1e75617c26429a13cc7bcd687d3302885cf1f1.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.gt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.gt_u + ) +) diff --git a/tests/integration/expected/gt_u64.hir b/tests/integration/expected/gt_u64.hir index 4536e74ae..05ee32996 100644 --- a/tests/integration/expected/gt_u64.hir +++ b/tests/integration/expected/gt_u64.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_70753af3178921ed6f0c2f66b095a4c78bd32e752ad580a8203438bf231ba0db - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_6489ee5460bd7aa58670638ded1591ff3f7c6abbcc5423cc0a2f5a4feb1ca663 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v4 = hir.bitcast v1 : u64; + v3 = hir.bitcast v0 : u64; + v5 = arith.gt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u64) (bitcast v0)) - (let (v4 u64) (bitcast v1)) - (let (v5 i1) (gt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/gt_u64.masm b/tests/integration/expected/gt_u64.masm index b420cc058..10bc982b9 100644 --- a/tests/integration/expected/gt_u64.masm +++ b/tests/integration/expected/gt_u64.masm @@ -1,15 +1,34 @@ -# mod test_rust_70753af3178921ed6f0c2f66b095a4c78bd32e752ad580a8203438bf231ba0db +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_6489ee5460bd7aa58670638ded1591ff3f7c6abbcc5423cc0a2f5a4feb1ca663 export.entrypoint movdn.3 movdn.3 + movdn.3 + movdn.3 + movdn.3 + movdn.3 + trace.240 + nop exec.::std::math::u64::gt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or + trace.252 + nop end - diff --git a/tests/integration/expected/gt_u64.wat b/tests/integration/expected/gt_u64.wat index 8a497d6d9..8945f2955 100644 --- a/tests/integration/expected/gt_u64.wat +++ b/tests/integration/expected/gt_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_70753af3178921ed6f0c2f66b095a4c78bd32e752ad580a8203438bf231ba0db.wasm +(module $test_rust_6489ee5460bd7aa58670638ded1591ff3f7c6abbcc5423cc0a2f5a4feb1ca663.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.gt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.gt_u + ) +) diff --git a/tests/integration/expected/gt_u8.hir b/tests/integration/expected/gt_u8.hir index 374a89f0b..7d0725a8b 100644 --- a/tests/integration/expected/gt_u8.hir +++ b/tests/integration/expected/gt_u8.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_c2720ddf3b4d450003b652cb7974691bccf82cfb40715e2a2f4ac9445d02156e - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_455aa9bc12d63425df846020b1734fc76252696200bb44305ecdb2dacce6f789 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.gt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (gt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/gt_u8.masm b/tests/integration/expected/gt_u8.masm index a22cbe0b8..af8700fb3 100644 --- a/tests/integration/expected/gt_u8.masm +++ b/tests/integration/expected/gt_u8.masm @@ -1,14 +1,27 @@ -# mod test_rust_c2720ddf3b4d450003b652cb7974691bccf82cfb40715e2a2f4ac9445d02156e +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_455aa9bc12d63425df846020b1734fc76252696200bb44305ecdb2dacce6f789 export.entrypoint + swap.1 + swap.1 swap.1 u32gt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/gt_u8.wat b/tests/integration/expected/gt_u8.wat index f64a0833f..c3e7db4d8 100644 --- a/tests/integration/expected/gt_u8.wat +++ b/tests/integration/expected/gt_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_c2720ddf3b4d450003b652cb7974691bccf82cfb40715e2a2f4ac9445d02156e.wasm +(module $test_rust_455aa9bc12d63425df846020b1734fc76252696200bb44305ecdb2dacce6f789.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.gt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.gt_u + ) +) diff --git a/tests/integration/expected/hash_elements.hir b/tests/integration/expected/hash_elements.hir new file mode 100644 index 000000000..d9bb0af50 --- /dev/null +++ b/tests/integration/expected/hash_elements.hir @@ -0,0 +1,453 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @hash_elements { + public builtin.function @entrypoint(v0: i32) -> felt { + ^block4(v0: i32): + v4 = builtin.global_symbol @root_ns:root@1.0.0/hash_elements/__stack_pointer : ptr + v5 = hir.bitcast v4 : ptr; + v6 = hir.load v5 : i32; + v7 = arith.constant 48 : i32; + v8 = arith.sub v6, v7 : i32 #[overflow = wrapping]; + v9 = builtin.global_symbol @root_ns:root@1.0.0/hash_elements/__stack_pointer : ptr + v10 = hir.bitcast v9 : ptr; + hir.store v10, v8; + v12 = arith.constant 8 : u32; + v11 = hir.bitcast v0 : u32; + v13 = arith.add v11, v12 : u32 #[overflow = checked]; + v14 = arith.constant 4 : u32; + v15 = arith.mod v13, v14 : u32; + hir.assertz v15 #[code = 250]; + v16 = hir.int_to_ptr v13 : ptr; + v17 = hir.load v16 : i32; + v343 = arith.constant 4 : u32; + v18 = hir.bitcast v0 : u32; + v20 = arith.add v18, v343 : u32 #[overflow = checked]; + v342 = arith.constant 4 : u32; + v22 = arith.mod v20, v342 : u32; + hir.assertz v22 #[code = 250]; + v23 = hir.int_to_ptr v20 : ptr; + v24 = hir.load v23 : i32; + v327 = arith.constant 2 : u32; + v26 = hir.bitcast v24 : u32; + v28 = arith.shr v26, v327 : u32; + v29 = hir.bitcast v28 : i32; + v30 = arith.constant 3 : i32; + v31 = arith.band v29, v30 : i32; + v32 = hir.exec @root_ns:root@1.0.0/hash_elements/intrinsics::felt::from_u32(v31) : felt + v2 = arith.constant 0 : i32; + v34 = hir.exec @root_ns:root@1.0.0/hash_elements/intrinsics::felt::from_u32(v2) : felt + hir.exec @root_ns:root@1.0.0/hash_elements/intrinsics::felt::assert_eq(v32, v34) + v339 = arith.constant 0 : i32; + v340 = arith.constant 0 : i32; + v341 = arith.constant 3 : i32; + v36 = arith.band v17, v341 : i32; + v38 = arith.eq v36, v340 : i1; + v39 = arith.zext v38 : u32; + v40 = hir.bitcast v39 : i32; + v42 = arith.neq v40, v339 : i1; + scf.if v42{ + ^block7: + v46 = arith.constant 16 : i32; + v47 = arith.add v8, v46 : i32 #[overflow = wrapping]; + v45 = arith.add v29, v17 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hash_elements/std::crypto::hashes::rpo::hash_memory_words(v29, v45, v47) + scf.yield ; + } else { + ^block8: + v338 = arith.constant 16 : i32; + v44 = arith.add v8, v338 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hash_elements/std::crypto::hashes::rpo::hash_memory(v29, v17, v44) + scf.yield ; + }; + v50 = arith.constant 24 : u32; + v49 = hir.bitcast v8 : u32; + v51 = arith.add v49, v50 : u32 #[overflow = checked]; + v337 = arith.constant 8 : u32; + v53 = arith.mod v51, v337 : u32; + hir.assertz v53 #[code = 250]; + v54 = hir.int_to_ptr v51 : ptr; + v55 = hir.load v54 : i64; + v57 = arith.constant 40 : u32; + v56 = hir.bitcast v8 : u32; + v58 = arith.add v56, v57 : u32 #[overflow = checked]; + v336 = arith.constant 8 : u32; + v60 = arith.mod v58, v336 : u32; + hir.assertz v60 #[code = 250]; + v61 = hir.int_to_ptr v58 : ptr; + hir.store v61, v55; + v63 = arith.constant 16 : u32; + v62 = hir.bitcast v8 : u32; + v64 = arith.add v62, v63 : u32 #[overflow = checked]; + v335 = arith.constant 8 : u32; + v66 = arith.mod v64, v335 : u32; + hir.assertz v66 #[code = 250]; + v67 = hir.int_to_ptr v64 : ptr; + v68 = hir.load v67 : i64; + v70 = arith.constant 32 : u32; + v69 = hir.bitcast v8 : u32; + v71 = arith.add v69, v70 : u32 #[overflow = checked]; + v334 = arith.constant 8 : u32; + v73 = arith.mod v71, v334 : u32; + hir.assertz v73 #[code = 250]; + v74 = hir.int_to_ptr v71 : ptr; + hir.store v74, v68; + v75 = arith.constant 32 : i32; + v76 = arith.add v8, v75 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hash_elements/miden_stdlib_sys::intrinsics::word::Word::reverse(v8, v76) + v78 = arith.constant 4 : i32; + hir.exec @root_ns:root@1.0.0/hash_elements/alloc::raw_vec::RawVecInner::deallocate(v0, v78, v78) + v80 = hir.bitcast v8 : u32; + v333 = arith.constant 4 : u32; + v82 = arith.mod v80, v333 : u32; + hir.assertz v82 #[code = 250]; + v83 = hir.int_to_ptr v80 : ptr; + v84 = hir.load v83 : felt; + v332 = arith.constant 48 : i32; + v86 = arith.add v8, v332 : i32 #[overflow = wrapping]; + v87 = builtin.global_symbol @root_ns:root@1.0.0/hash_elements/__stack_pointer : ptr + v88 = hir.bitcast v87 : ptr; + hir.store v88, v86; + builtin.ret v84; + }; + + private builtin.function @__rustc::__rust_dealloc(v89: i32, v90: i32, v91: i32) { + ^block9(v89: i32, v90: i32, v91: i32): + builtin.ret ; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v92: i32, v93: i32) { + ^block11(v92: i32, v93: i32): + v96 = builtin.global_symbol @root_ns:root@1.0.0/hash_elements/__stack_pointer : ptr + v97 = hir.bitcast v96 : ptr; + v98 = hir.load v97 : i32; + v99 = arith.constant 16 : i32; + v100 = arith.sub v98, v99 : i32 #[overflow = wrapping]; + v102 = arith.constant 8 : u32; + v101 = hir.bitcast v93 : u32; + v103 = arith.add v101, v102 : u32 #[overflow = checked]; + v430 = arith.constant 8 : u32; + v105 = arith.mod v103, v430 : u32; + hir.assertz v105 #[code = 250]; + v106 = hir.int_to_ptr v103 : ptr; + v107 = hir.load v106 : i64; + v429 = arith.constant 8 : u32; + v108 = hir.bitcast v100 : u32; + v110 = arith.add v108, v429 : u32 #[overflow = checked]; + v111 = arith.constant 4 : u32; + v112 = arith.mod v110, v111 : u32; + hir.assertz v112 #[code = 250]; + v113 = hir.int_to_ptr v110 : ptr; + hir.store v113, v107; + v114 = hir.bitcast v93 : u32; + v428 = arith.constant 8 : u32; + v116 = arith.mod v114, v428 : u32; + hir.assertz v116 #[code = 250]; + v117 = hir.int_to_ptr v114 : ptr; + v118 = hir.load v117 : i64; + v119 = hir.bitcast v100 : u32; + v427 = arith.constant 4 : u32; + v121 = arith.mod v119, v427 : u32; + hir.assertz v121 #[code = 250]; + v122 = hir.int_to_ptr v119 : ptr; + hir.store v122, v118; + v123 = arith.constant 12 : i32; + v124 = arith.add v100, v123 : i32 #[overflow = wrapping]; + v94 = arith.constant 0 : i32; + v398, v399, v400, v401, v402, v403 = scf.while v94, v100, v124, v92 : i32, i32, i32, i32, i32, i32 { + ^block50(v404: i32, v405: i32, v406: i32, v407: i32): + v426 = arith.constant 0 : i32; + v127 = arith.constant 8 : i32; + v128 = arith.eq v404, v127 : i1; + v129 = arith.zext v128 : u32; + v130 = hir.bitcast v129 : i32; + v132 = arith.neq v130, v426 : i1; + v392, v393 = scf.if v132 : i32, i32 { + ^block49: + v352 = ub.poison i32 : i32; + scf.yield v352, v352; + } else { + ^block16: + v134 = arith.add v405, v404 : i32 #[overflow = wrapping]; + v135 = hir.bitcast v134 : u32; + v425 = arith.constant 4 : u32; + v137 = arith.mod v135, v425 : u32; + hir.assertz v137 #[code = 250]; + v138 = hir.int_to_ptr v135 : ptr; + v139 = hir.load v138 : felt; + v141 = hir.bitcast v406 : u32; + v424 = arith.constant 4 : u32; + v143 = arith.mod v141, v424 : u32; + hir.assertz v143 #[code = 250]; + v144 = hir.int_to_ptr v141 : ptr; + v145 = hir.load v144 : i32; + v146 = hir.bitcast v134 : u32; + v423 = arith.constant 4 : u32; + v148 = arith.mod v146, v423 : u32; + hir.assertz v148 #[code = 250]; + v149 = hir.int_to_ptr v146 : ptr; + hir.store v149, v145; + v150 = hir.bitcast v406 : u32; + v422 = arith.constant 4 : u32; + v152 = arith.mod v150, v422 : u32; + hir.assertz v152 #[code = 250]; + v153 = hir.int_to_ptr v150 : ptr; + hir.store v153, v139; + v156 = arith.constant -4 : i32; + v157 = arith.add v406, v156 : i32 #[overflow = wrapping]; + v154 = arith.constant 4 : i32; + v155 = arith.add v404, v154 : i32 #[overflow = wrapping]; + scf.yield v155, v157; + }; + v420 = ub.poison i32 : i32; + v395 = cf.select v132, v420, v407 : i32; + v421 = ub.poison i32 : i32; + v394 = cf.select v132, v421, v405 : i32; + v351 = arith.constant 1 : u32; + v344 = arith.constant 0 : u32; + v397 = cf.select v132, v344, v351 : u32; + v385 = arith.trunc v397 : i1; + scf.condition v385, v392, v394, v393, v395, v405, v407; + } do { + ^block51(v408: i32, v409: i32, v410: i32, v411: i32, v412: i32, v413: i32): + scf.yield v408, v409, v410, v411; + }; + v419 = arith.constant 8 : u32; + v159 = hir.bitcast v402 : u32; + v161 = arith.add v159, v419 : u32 #[overflow = checked]; + v418 = arith.constant 4 : u32; + v163 = arith.mod v161, v418 : u32; + hir.assertz v163 #[code = 250]; + v164 = hir.int_to_ptr v161 : ptr; + v165 = hir.load v164 : i64; + v417 = arith.constant 8 : u32; + v166 = hir.bitcast v403 : u32; + v168 = arith.add v166, v417 : u32 #[overflow = checked]; + v416 = arith.constant 8 : u32; + v170 = arith.mod v168, v416 : u32; + hir.assertz v170 #[code = 250]; + v171 = hir.int_to_ptr v168 : ptr; + hir.store v171, v165; + v172 = hir.bitcast v402 : u32; + v415 = arith.constant 4 : u32; + v174 = arith.mod v172, v415 : u32; + hir.assertz v174 #[code = 250]; + v175 = hir.int_to_ptr v172 : ptr; + v176 = hir.load v175 : i64; + v177 = hir.bitcast v403 : u32; + v414 = arith.constant 8 : u32; + v179 = arith.mod v177, v414 : u32; + hir.assertz v179 #[code = 250]; + v180 = hir.int_to_ptr v177 : ptr; + hir.store v180, v176; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u32(v181: i32) -> felt { + ^block17(v181: i32): + v182 = hir.bitcast v181 : felt; + builtin.ret v182; + }; + + private builtin.function @intrinsics::felt::assert_eq(v184: felt, v185: felt) { + ^block19(v184: felt, v185: felt): + hir.assert_eq v184, v185; + builtin.ret ; + }; + + private builtin.function @std::crypto::hashes::rpo::hash_memory(v186: i32, v187: i32, v188: i32) { + ^block21(v186: i32, v187: i32, v188: i32): + v189, v190, v191, v192 = hir.exec @std/crypto/hashes/rpo/hash_memory(v186, v187) : felt, felt, felt, felt + v193 = hir.bitcast v188 : u32; + v194 = hir.int_to_ptr v193 : ptr; + hir.store v194, v189; + v195 = arith.constant 4 : u32; + v196 = arith.add v193, v195 : u32 #[overflow = checked]; + v197 = hir.int_to_ptr v196 : ptr; + hir.store v197, v190; + v198 = arith.constant 8 : u32; + v199 = arith.add v193, v198 : u32 #[overflow = checked]; + v200 = hir.int_to_ptr v199 : ptr; + hir.store v200, v191; + v201 = arith.constant 12 : u32; + v202 = arith.add v193, v201 : u32 #[overflow = checked]; + v203 = hir.int_to_ptr v202 : ptr; + hir.store v203, v192; + builtin.ret ; + }; + + private builtin.function @std::crypto::hashes::rpo::hash_memory_words(v204: i32, v205: i32, v206: i32) { + ^block27(v204: i32, v205: i32, v206: i32): + v207, v208, v209, v210 = hir.exec @std/crypto/hashes/rpo/hash_memory_words(v204, v205) : felt, felt, felt, felt + v211 = hir.bitcast v206 : u32; + v212 = hir.int_to_ptr v211 : ptr; + hir.store v212, v207; + v213 = arith.constant 4 : u32; + v214 = arith.add v211, v213 : u32 #[overflow = checked]; + v215 = hir.int_to_ptr v214 : ptr; + hir.store v215, v208; + v216 = arith.constant 8 : u32; + v217 = arith.add v211, v216 : u32 #[overflow = checked]; + v218 = hir.int_to_ptr v217 : ptr; + hir.store v218, v209; + v219 = arith.constant 12 : u32; + v220 = arith.add v211, v219 : u32 #[overflow = checked]; + v221 = hir.int_to_ptr v220 : ptr; + hir.store v221, v210; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::deallocate(v222: i32, v223: i32, v224: i32) { + ^block29(v222: i32, v223: i32, v224: i32): + v226 = builtin.global_symbol @root_ns:root@1.0.0/hash_elements/__stack_pointer : ptr + v227 = hir.bitcast v226 : ptr; + v228 = hir.load v227 : i32; + v229 = arith.constant 16 : i32; + v230 = arith.sub v228, v229 : i32 #[overflow = wrapping]; + v231 = builtin.global_symbol @root_ns:root@1.0.0/hash_elements/__stack_pointer : ptr + v232 = hir.bitcast v231 : ptr; + hir.store v232, v230; + v233 = arith.constant 4 : i32; + v234 = arith.add v230, v233 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hash_elements/alloc::raw_vec::RawVecInner::current_memory(v234, v222, v223, v224) + v236 = arith.constant 8 : u32; + v235 = hir.bitcast v230 : u32; + v237 = arith.add v235, v236 : u32 #[overflow = checked]; + v238 = arith.constant 4 : u32; + v239 = arith.mod v237, v238 : u32; + hir.assertz v239 #[code = 250]; + v240 = hir.int_to_ptr v237 : ptr; + v241 = hir.load v240 : i32; + v437 = arith.constant 0 : i32; + v225 = arith.constant 0 : i32; + v243 = arith.eq v241, v225 : i1; + v244 = arith.zext v243 : u32; + v245 = hir.bitcast v244 : i32; + v247 = arith.neq v245, v437 : i1; + scf.if v247{ + ^block52: + scf.yield ; + } else { + ^block32: + v436 = arith.constant 4 : u32; + v248 = hir.bitcast v230 : u32; + v250 = arith.add v248, v436 : u32 #[overflow = checked]; + v435 = arith.constant 4 : u32; + v252 = arith.mod v250, v435 : u32; + hir.assertz v252 #[code = 250]; + v253 = hir.int_to_ptr v250 : ptr; + v254 = hir.load v253 : i32; + v256 = arith.constant 12 : u32; + v255 = hir.bitcast v230 : u32; + v257 = arith.add v255, v256 : u32 #[overflow = checked]; + v434 = arith.constant 4 : u32; + v259 = arith.mod v257, v434 : u32; + hir.assertz v259 #[code = 250]; + v260 = hir.int_to_ptr v257 : ptr; + v261 = hir.load v260 : i32; + hir.exec @root_ns:root@1.0.0/hash_elements/::deallocate(v254, v241, v261) + scf.yield ; + }; + v433 = arith.constant 16 : i32; + v264 = arith.add v230, v433 : i32 #[overflow = wrapping]; + v265 = builtin.global_symbol @root_ns:root@1.0.0/hash_elements/__stack_pointer : ptr + v266 = hir.bitcast v265 : ptr; + hir.store v266, v264; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::current_memory(v267: i32, v268: i32, v269: i32, v270: i32) { + ^block33(v267: i32, v268: i32, v269: i32, v270: i32): + v463 = arith.constant 0 : i32; + v271 = arith.constant 0 : i32; + v275 = arith.eq v270, v271 : i1; + v276 = arith.zext v275 : u32; + v277 = hir.bitcast v276 : i32; + v279 = arith.neq v277, v463 : i1; + v450, v451 = scf.if v279 : i32, i32 { + ^block55: + v462 = arith.constant 0 : i32; + v273 = arith.constant 4 : i32; + scf.yield v273, v462; + } else { + ^block36: + v280 = hir.bitcast v268 : u32; + v315 = arith.constant 4 : u32; + v282 = arith.mod v280, v315 : u32; + hir.assertz v282 #[code = 250]; + v283 = hir.int_to_ptr v280 : ptr; + v284 = hir.load v283 : i32; + v460 = arith.constant 0 : i32; + v461 = arith.constant 0 : i32; + v286 = arith.eq v284, v461 : i1; + v287 = arith.zext v286 : u32; + v288 = hir.bitcast v287 : i32; + v290 = arith.neq v288, v460 : i1; + v448 = scf.if v290 : i32 { + ^block54: + v459 = arith.constant 0 : i32; + scf.yield v459; + } else { + ^block37: + v458 = arith.constant 4 : u32; + v291 = hir.bitcast v267 : u32; + v293 = arith.add v291, v458 : u32 #[overflow = checked]; + v457 = arith.constant 4 : u32; + v295 = arith.mod v293, v457 : u32; + hir.assertz v295 #[code = 250]; + v296 = hir.int_to_ptr v293 : ptr; + hir.store v296, v269; + v456 = arith.constant 4 : u32; + v297 = hir.bitcast v268 : u32; + v299 = arith.add v297, v456 : u32 #[overflow = checked]; + v455 = arith.constant 4 : u32; + v301 = arith.mod v299, v455 : u32; + hir.assertz v301 #[code = 250]; + v302 = hir.int_to_ptr v299 : ptr; + v303 = hir.load v302 : i32; + v304 = hir.bitcast v267 : u32; + v454 = arith.constant 4 : u32; + v306 = arith.mod v304, v454 : u32; + hir.assertz v306 #[code = 250]; + v307 = hir.int_to_ptr v304 : ptr; + hir.store v307, v303; + v308 = arith.mul v284, v270 : i32 #[overflow = wrapping]; + scf.yield v308; + }; + v309 = arith.constant 8 : i32; + v453 = arith.constant 4 : i32; + v449 = cf.select v290, v453, v309 : i32; + scf.yield v449, v448; + }; + v312 = arith.add v267, v450 : i32 #[overflow = wrapping]; + v314 = hir.bitcast v312 : u32; + v452 = arith.constant 4 : u32; + v316 = arith.mod v314, v452 : u32; + hir.assertz v316 #[code = 250]; + v317 = hir.int_to_ptr v314 : ptr; + hir.store v317, v451; + builtin.ret ; + }; + + private builtin.function @::deallocate(v318: i32, v319: i32, v320: i32) { + ^block38(v318: i32, v319: i32, v320: i32): + v465 = arith.constant 0 : i32; + v321 = arith.constant 0 : i32; + v322 = arith.eq v320, v321 : i1; + v323 = arith.zext v322 : u32; + v324 = hir.bitcast v323 : i32; + v326 = arith.neq v324, v465 : i1; + scf.if v326{ + ^block40: + scf.yield ; + } else { + ^block41: + hir.exec @root_ns:root@1.0.0/hash_elements/__rustc::__rust_dealloc(v318, v320, v319) + scf.yield ; + }; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/hash_elements.masm b/tests/integration/expected/hash_elements.masm new file mode 100644 index 000000000..cc7deffdf --- /dev/null +++ b/tests/integration/expected/hash_elements.masm @@ -0,0 +1,881 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::hash_elements + +export.entrypoint + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.2 + swap.1 + swap.1 + u32shr + push.3 + dup.1 + u32and + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::intrinsics::felt::assert_eq + trace.252 + nop + push.0 + push.0 + push.3 + dup.4 + u32and + eq + neq + if.true + push.16 + dup.3 + u32wrapping_add + movup.2 + dup.2 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::std::crypto::hashes::rpo::hash_memory_words + trace.252 + nop + else + push.16 + dup.3 + u32wrapping_add + movdn.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::std::crypto::hashes::rpo::hash_memory + trace.252 + nop + end + push.24 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.40 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.32 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.4 + dup.0 + swap.2 + swap.3 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.48 + movup.2 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.__rustc::__rust_dealloc + drop + drop + drop +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + +proc.std::crypto::hashes::rpo::hash_memory + trace.240 + nop + exec.::std::crypto::hashes::rpo::hash_memory + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.std::crypto::hashes::rpo::hash_memory_words + trace.240 + nop + exec.::std::crypto::hashes::rpo::hash_memory_words + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::deallocate + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + u32wrapping_add + swap.1 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::alloc::raw_vec::RawVecInner::current_memory + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + else + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movdn.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::::deallocate + trace.252 + nop + end + push.16 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::current_memory + push.0 + push.0 + dup.5 + eq + neq + if.true + movdn.3 + drop + drop + drop + push.0 + push.4 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + else + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.3 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + movup.2 + swap.1 + end + push.8 + push.4 + movup.3 + cdrop + end + movup.2 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::deallocate + push.0 + push.0 + dup.4 + eq + neq + if.true + drop + drop + drop + else + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_elements::__rustc::__rust_dealloc + trace.252 + nop + end +end + diff --git a/tests/integration/expected/hash_elements.wat b/tests/integration/expected/hash_elements.wat new file mode 100644 index 000000000..6b6a876ce --- /dev/null +++ b/tests/integration/expected/hash_elements.wat @@ -0,0 +1,232 @@ +(module $hash_elements.wasm + (type (;0;) (func (param i32) (result f32))) + (type (;1;) (func (param i32 i32 i32))) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param f32 f32))) + (type (;4;) (func (param i32 i32 i32 i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32) (result f32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 0 + i32.load offset=8 + local.set 2 + local.get 0 + i32.load offset=4 + i32.const 2 + i32.shr_u + local.tee 3 + i32.const 3 + i32.and + call $intrinsics::felt::from_u32 + i32.const 0 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + block ;; label = @1 + block ;; label = @2 + local.get 2 + i32.const 3 + i32.and + i32.eqz + br_if 0 (;@2;) + local.get 3 + local.get 2 + local.get 1 + i32.const 16 + i32.add + call $std::crypto::hashes::rpo::hash_memory + br 1 (;@1;) + end + local.get 3 + local.get 3 + local.get 2 + i32.add + local.get 1 + i32.const 16 + i32.add + call $std::crypto::hashes::rpo::hash_memory_words + end + local.get 1 + local.get 1 + i64.load offset=24 + i64.store offset=40 + local.get 1 + local.get 1 + i64.load offset=16 + i64.store offset=32 + local.get 1 + local.get 1 + i32.const 32 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 0 + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 1 + f32.load + local.set 4 + local.get 1 + i32.const 48 + i32.add + global.set $__stack_pointer + local.get 4 + ) + (func $__rustc::__rust_dealloc (;1;) (type 1) (param i32 i32 i32)) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;2;) (type 2) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $intrinsics::felt::from_u32 (;3;) (type 0) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::assert_eq (;4;) (type 3) (param f32 f32) + unreachable + ) + (func $std::crypto::hashes::rpo::hash_memory (;5;) (type 1) (param i32 i32 i32) + unreachable + ) + (func $std::crypto::hashes::rpo::hash_memory_words (;6;) (type 1) (param i32 i32 i32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;7;) (type 1) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;8;) (type 4) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;9;) (type 1) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) +) diff --git a/tests/integration/expected/hash_words.hir b/tests/integration/expected/hash_words.hir new file mode 100644 index 000000000..a7364e0d4 --- /dev/null +++ b/tests/integration/expected/hash_words.hir @@ -0,0 +1,415 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @hash_words { + public builtin.function @entrypoint(v0: i32) -> felt { + ^block4(v0: i32): + v4 = builtin.global_symbol @root_ns:root@1.0.0/hash_words/__stack_pointer : ptr + v5 = hir.bitcast v4 : ptr; + v6 = hir.load v5 : i32; + v7 = arith.constant 48 : i32; + v8 = arith.sub v6, v7 : i32 #[overflow = wrapping]; + v9 = builtin.global_symbol @root_ns:root@1.0.0/hash_words/__stack_pointer : ptr + v10 = hir.bitcast v9 : ptr; + hir.store v10, v8; + v12 = arith.constant 4 : u32; + v11 = hir.bitcast v0 : u32; + v13 = arith.add v11, v12 : u32 #[overflow = checked]; + v311 = arith.constant 4 : u32; + v15 = arith.mod v13, v311 : u32; + hir.assertz v15 #[code = 250]; + v16 = hir.int_to_ptr v13 : ptr; + v17 = hir.load v16 : i32; + v19 = arith.constant 8 : u32; + v18 = hir.bitcast v0 : u32; + v20 = arith.add v18, v19 : u32 #[overflow = checked]; + v310 = arith.constant 4 : u32; + v22 = arith.mod v20, v310 : u32; + hir.assertz v22 #[code = 250]; + v23 = hir.int_to_ptr v20 : ptr; + v24 = hir.load v23 : i32; + v2 = arith.constant 0 : i32; + v26 = hir.exec @root_ns:root@1.0.0/hash_words/intrinsics::felt::from_u32(v2) : felt + v309 = arith.constant 0 : i32; + v28 = hir.exec @root_ns:root@1.0.0/hash_words/intrinsics::felt::from_u32(v309) : felt + hir.exec @root_ns:root@1.0.0/hash_words/intrinsics::felt::assert_eq(v26, v28) + v300 = arith.constant 2 : u32; + v30 = hir.bitcast v17 : u32; + v32 = arith.shr v30, v300 : u32; + v33 = hir.bitcast v32 : i32; + v38 = arith.constant 16 : i32; + v39 = arith.add v8, v38 : i32 #[overflow = wrapping]; + v308 = arith.constant 2 : u32; + v36 = arith.shl v24, v308 : i32; + v37 = arith.add v33, v36 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hash_words/std::crypto::hashes::rpo::hash_memory_words(v33, v37, v39) + v41 = arith.constant 24 : u32; + v40 = hir.bitcast v8 : u32; + v42 = arith.add v40, v41 : u32 #[overflow = checked]; + v307 = arith.constant 8 : u32; + v44 = arith.mod v42, v307 : u32; + hir.assertz v44 #[code = 250]; + v45 = hir.int_to_ptr v42 : ptr; + v46 = hir.load v45 : i64; + v48 = arith.constant 40 : u32; + v47 = hir.bitcast v8 : u32; + v49 = arith.add v47, v48 : u32 #[overflow = checked]; + v306 = arith.constant 8 : u32; + v51 = arith.mod v49, v306 : u32; + hir.assertz v51 #[code = 250]; + v52 = hir.int_to_ptr v49 : ptr; + hir.store v52, v46; + v54 = arith.constant 16 : u32; + v53 = hir.bitcast v8 : u32; + v55 = arith.add v53, v54 : u32 #[overflow = checked]; + v305 = arith.constant 8 : u32; + v57 = arith.mod v55, v305 : u32; + hir.assertz v57 #[code = 250]; + v58 = hir.int_to_ptr v55 : ptr; + v59 = hir.load v58 : i64; + v61 = arith.constant 32 : u32; + v60 = hir.bitcast v8 : u32; + v62 = arith.add v60, v61 : u32 #[overflow = checked]; + v304 = arith.constant 8 : u32; + v64 = arith.mod v62, v304 : u32; + hir.assertz v64 #[code = 250]; + v65 = hir.int_to_ptr v62 : ptr; + hir.store v65, v59; + v66 = arith.constant 32 : i32; + v67 = arith.add v8, v66 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hash_words/miden_stdlib_sys::intrinsics::word::Word::reverse(v8, v67) + v68 = hir.bitcast v8 : u32; + v303 = arith.constant 4 : u32; + v70 = arith.mod v68, v303 : u32; + hir.assertz v70 #[code = 250]; + v71 = hir.int_to_ptr v68 : ptr; + v72 = hir.load v71 : felt; + v302 = arith.constant 16 : i32; + hir.exec @root_ns:root@1.0.0/hash_words/alloc::raw_vec::RawVecInner::deallocate(v0, v302, v302) + v301 = arith.constant 48 : i32; + v76 = arith.add v8, v301 : i32 #[overflow = wrapping]; + v77 = builtin.global_symbol @root_ns:root@1.0.0/hash_words/__stack_pointer : ptr + v78 = hir.bitcast v77 : ptr; + hir.store v78, v76; + builtin.ret v72; + }; + + private builtin.function @__rustc::__rust_dealloc(v79: i32, v80: i32, v81: i32) { + ^block6(v79: i32, v80: i32, v81: i32): + builtin.ret ; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v82: i32, v83: i32) { + ^block8(v82: i32, v83: i32): + v86 = builtin.global_symbol @root_ns:root@1.0.0/hash_words/__stack_pointer : ptr + v87 = hir.bitcast v86 : ptr; + v88 = hir.load v87 : i32; + v89 = arith.constant 16 : i32; + v90 = arith.sub v88, v89 : i32 #[overflow = wrapping]; + v92 = arith.constant 8 : u32; + v91 = hir.bitcast v83 : u32; + v93 = arith.add v91, v92 : u32 #[overflow = checked]; + v398 = arith.constant 8 : u32; + v95 = arith.mod v93, v398 : u32; + hir.assertz v95 #[code = 250]; + v96 = hir.int_to_ptr v93 : ptr; + v97 = hir.load v96 : i64; + v397 = arith.constant 8 : u32; + v98 = hir.bitcast v90 : u32; + v100 = arith.add v98, v397 : u32 #[overflow = checked]; + v101 = arith.constant 4 : u32; + v102 = arith.mod v100, v101 : u32; + hir.assertz v102 #[code = 250]; + v103 = hir.int_to_ptr v100 : ptr; + hir.store v103, v97; + v104 = hir.bitcast v83 : u32; + v396 = arith.constant 8 : u32; + v106 = arith.mod v104, v396 : u32; + hir.assertz v106 #[code = 250]; + v107 = hir.int_to_ptr v104 : ptr; + v108 = hir.load v107 : i64; + v109 = hir.bitcast v90 : u32; + v395 = arith.constant 4 : u32; + v111 = arith.mod v109, v395 : u32; + hir.assertz v111 #[code = 250]; + v112 = hir.int_to_ptr v109 : ptr; + hir.store v112, v108; + v113 = arith.constant 12 : i32; + v114 = arith.add v90, v113 : i32 #[overflow = wrapping]; + v84 = arith.constant 0 : i32; + v366, v367, v368, v369, v370, v371 = scf.while v84, v90, v114, v82 : i32, i32, i32, i32, i32, i32 { + ^block44(v372: i32, v373: i32, v374: i32, v375: i32): + v394 = arith.constant 0 : i32; + v117 = arith.constant 8 : i32; + v118 = arith.eq v372, v117 : i1; + v119 = arith.zext v118 : u32; + v120 = hir.bitcast v119 : i32; + v122 = arith.neq v120, v394 : i1; + v360, v361 = scf.if v122 : i32, i32 { + ^block43: + v320 = ub.poison i32 : i32; + scf.yield v320, v320; + } else { + ^block13: + v124 = arith.add v373, v372 : i32 #[overflow = wrapping]; + v125 = hir.bitcast v124 : u32; + v393 = arith.constant 4 : u32; + v127 = arith.mod v125, v393 : u32; + hir.assertz v127 #[code = 250]; + v128 = hir.int_to_ptr v125 : ptr; + v129 = hir.load v128 : felt; + v131 = hir.bitcast v374 : u32; + v392 = arith.constant 4 : u32; + v133 = arith.mod v131, v392 : u32; + hir.assertz v133 #[code = 250]; + v134 = hir.int_to_ptr v131 : ptr; + v135 = hir.load v134 : i32; + v136 = hir.bitcast v124 : u32; + v391 = arith.constant 4 : u32; + v138 = arith.mod v136, v391 : u32; + hir.assertz v138 #[code = 250]; + v139 = hir.int_to_ptr v136 : ptr; + hir.store v139, v135; + v140 = hir.bitcast v374 : u32; + v390 = arith.constant 4 : u32; + v142 = arith.mod v140, v390 : u32; + hir.assertz v142 #[code = 250]; + v143 = hir.int_to_ptr v140 : ptr; + hir.store v143, v129; + v146 = arith.constant -4 : i32; + v147 = arith.add v374, v146 : i32 #[overflow = wrapping]; + v144 = arith.constant 4 : i32; + v145 = arith.add v372, v144 : i32 #[overflow = wrapping]; + scf.yield v145, v147; + }; + v388 = ub.poison i32 : i32; + v363 = cf.select v122, v388, v375 : i32; + v389 = ub.poison i32 : i32; + v362 = cf.select v122, v389, v373 : i32; + v319 = arith.constant 1 : u32; + v312 = arith.constant 0 : u32; + v365 = cf.select v122, v312, v319 : u32; + v353 = arith.trunc v365 : i1; + scf.condition v353, v360, v362, v361, v363, v373, v375; + } do { + ^block45(v376: i32, v377: i32, v378: i32, v379: i32, v380: i32, v381: i32): + scf.yield v376, v377, v378, v379; + }; + v387 = arith.constant 8 : u32; + v149 = hir.bitcast v370 : u32; + v151 = arith.add v149, v387 : u32 #[overflow = checked]; + v386 = arith.constant 4 : u32; + v153 = arith.mod v151, v386 : u32; + hir.assertz v153 #[code = 250]; + v154 = hir.int_to_ptr v151 : ptr; + v155 = hir.load v154 : i64; + v385 = arith.constant 8 : u32; + v156 = hir.bitcast v371 : u32; + v158 = arith.add v156, v385 : u32 #[overflow = checked]; + v384 = arith.constant 8 : u32; + v160 = arith.mod v158, v384 : u32; + hir.assertz v160 #[code = 250]; + v161 = hir.int_to_ptr v158 : ptr; + hir.store v161, v155; + v162 = hir.bitcast v370 : u32; + v383 = arith.constant 4 : u32; + v164 = arith.mod v162, v383 : u32; + hir.assertz v164 #[code = 250]; + v165 = hir.int_to_ptr v162 : ptr; + v166 = hir.load v165 : i64; + v167 = hir.bitcast v371 : u32; + v382 = arith.constant 8 : u32; + v169 = arith.mod v167, v382 : u32; + hir.assertz v169 #[code = 250]; + v170 = hir.int_to_ptr v167 : ptr; + hir.store v170, v166; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u32(v171: i32) -> felt { + ^block14(v171: i32): + v172 = hir.bitcast v171 : felt; + builtin.ret v172; + }; + + private builtin.function @intrinsics::felt::assert_eq(v174: felt, v175: felt) { + ^block16(v174: felt, v175: felt): + hir.assert_eq v174, v175; + builtin.ret ; + }; + + private builtin.function @std::crypto::hashes::rpo::hash_memory_words(v176: i32, v177: i32, v178: i32) { + ^block18(v176: i32, v177: i32, v178: i32): + v179, v180, v181, v182 = hir.exec @std/crypto/hashes/rpo/hash_memory_words(v176, v177) : felt, felt, felt, felt + v183 = hir.bitcast v178 : u32; + v184 = hir.int_to_ptr v183 : ptr; + hir.store v184, v179; + v185 = arith.constant 4 : u32; + v186 = arith.add v183, v185 : u32 #[overflow = checked]; + v187 = hir.int_to_ptr v186 : ptr; + hir.store v187, v180; + v188 = arith.constant 8 : u32; + v189 = arith.add v183, v188 : u32 #[overflow = checked]; + v190 = hir.int_to_ptr v189 : ptr; + hir.store v190, v181; + v191 = arith.constant 12 : u32; + v192 = arith.add v183, v191 : u32 #[overflow = checked]; + v193 = hir.int_to_ptr v192 : ptr; + hir.store v193, v182; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::deallocate(v194: i32, v195: i32, v196: i32) { + ^block24(v194: i32, v195: i32, v196: i32): + v198 = builtin.global_symbol @root_ns:root@1.0.0/hash_words/__stack_pointer : ptr + v199 = hir.bitcast v198 : ptr; + v200 = hir.load v199 : i32; + v201 = arith.constant 16 : i32; + v202 = arith.sub v200, v201 : i32 #[overflow = wrapping]; + v203 = builtin.global_symbol @root_ns:root@1.0.0/hash_words/__stack_pointer : ptr + v204 = hir.bitcast v203 : ptr; + hir.store v204, v202; + v205 = arith.constant 4 : i32; + v206 = arith.add v202, v205 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hash_words/alloc::raw_vec::RawVecInner::current_memory(v206, v194, v195, v196) + v208 = arith.constant 8 : u32; + v207 = hir.bitcast v202 : u32; + v209 = arith.add v207, v208 : u32 #[overflow = checked]; + v210 = arith.constant 4 : u32; + v211 = arith.mod v209, v210 : u32; + hir.assertz v211 #[code = 250]; + v212 = hir.int_to_ptr v209 : ptr; + v213 = hir.load v212 : i32; + v405 = arith.constant 0 : i32; + v197 = arith.constant 0 : i32; + v215 = arith.eq v213, v197 : i1; + v216 = arith.zext v215 : u32; + v217 = hir.bitcast v216 : i32; + v219 = arith.neq v217, v405 : i1; + scf.if v219{ + ^block46: + scf.yield ; + } else { + ^block27: + v404 = arith.constant 4 : u32; + v220 = hir.bitcast v202 : u32; + v222 = arith.add v220, v404 : u32 #[overflow = checked]; + v403 = arith.constant 4 : u32; + v224 = arith.mod v222, v403 : u32; + hir.assertz v224 #[code = 250]; + v225 = hir.int_to_ptr v222 : ptr; + v226 = hir.load v225 : i32; + v228 = arith.constant 12 : u32; + v227 = hir.bitcast v202 : u32; + v229 = arith.add v227, v228 : u32 #[overflow = checked]; + v402 = arith.constant 4 : u32; + v231 = arith.mod v229, v402 : u32; + hir.assertz v231 #[code = 250]; + v232 = hir.int_to_ptr v229 : ptr; + v233 = hir.load v232 : i32; + hir.exec @root_ns:root@1.0.0/hash_words/::deallocate(v226, v213, v233) + scf.yield ; + }; + v401 = arith.constant 16 : i32; + v236 = arith.add v202, v401 : i32 #[overflow = wrapping]; + v237 = builtin.global_symbol @root_ns:root@1.0.0/hash_words/__stack_pointer : ptr + v238 = hir.bitcast v237 : ptr; + hir.store v238, v236; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::current_memory(v239: i32, v240: i32, v241: i32, v242: i32) { + ^block28(v239: i32, v240: i32, v241: i32, v242: i32): + v431 = arith.constant 0 : i32; + v243 = arith.constant 0 : i32; + v247 = arith.eq v242, v243 : i1; + v248 = arith.zext v247 : u32; + v249 = hir.bitcast v248 : i32; + v251 = arith.neq v249, v431 : i1; + v418, v419 = scf.if v251 : i32, i32 { + ^block49: + v430 = arith.constant 0 : i32; + v245 = arith.constant 4 : i32; + scf.yield v245, v430; + } else { + ^block31: + v252 = hir.bitcast v240 : u32; + v287 = arith.constant 4 : u32; + v254 = arith.mod v252, v287 : u32; + hir.assertz v254 #[code = 250]; + v255 = hir.int_to_ptr v252 : ptr; + v256 = hir.load v255 : i32; + v428 = arith.constant 0 : i32; + v429 = arith.constant 0 : i32; + v258 = arith.eq v256, v429 : i1; + v259 = arith.zext v258 : u32; + v260 = hir.bitcast v259 : i32; + v262 = arith.neq v260, v428 : i1; + v416 = scf.if v262 : i32 { + ^block48: + v427 = arith.constant 0 : i32; + scf.yield v427; + } else { + ^block32: + v426 = arith.constant 4 : u32; + v263 = hir.bitcast v239 : u32; + v265 = arith.add v263, v426 : u32 #[overflow = checked]; + v425 = arith.constant 4 : u32; + v267 = arith.mod v265, v425 : u32; + hir.assertz v267 #[code = 250]; + v268 = hir.int_to_ptr v265 : ptr; + hir.store v268, v241; + v424 = arith.constant 4 : u32; + v269 = hir.bitcast v240 : u32; + v271 = arith.add v269, v424 : u32 #[overflow = checked]; + v423 = arith.constant 4 : u32; + v273 = arith.mod v271, v423 : u32; + hir.assertz v273 #[code = 250]; + v274 = hir.int_to_ptr v271 : ptr; + v275 = hir.load v274 : i32; + v276 = hir.bitcast v239 : u32; + v422 = arith.constant 4 : u32; + v278 = arith.mod v276, v422 : u32; + hir.assertz v278 #[code = 250]; + v279 = hir.int_to_ptr v276 : ptr; + hir.store v279, v275; + v280 = arith.mul v256, v242 : i32 #[overflow = wrapping]; + scf.yield v280; + }; + v281 = arith.constant 8 : i32; + v421 = arith.constant 4 : i32; + v417 = cf.select v262, v421, v281 : i32; + scf.yield v417, v416; + }; + v284 = arith.add v239, v418 : i32 #[overflow = wrapping]; + v286 = hir.bitcast v284 : u32; + v420 = arith.constant 4 : u32; + v288 = arith.mod v286, v420 : u32; + hir.assertz v288 #[code = 250]; + v289 = hir.int_to_ptr v286 : ptr; + hir.store v289, v419; + builtin.ret ; + }; + + private builtin.function @::deallocate(v290: i32, v291: i32, v292: i32) { + ^block33(v290: i32, v291: i32, v292: i32): + v433 = arith.constant 0 : i32; + v293 = arith.constant 0 : i32; + v294 = arith.eq v292, v293 : i1; + v295 = arith.zext v294 : u32; + v296 = hir.bitcast v295 : i32; + v298 = arith.neq v296, v433 : i1; + scf.if v298{ + ^block35: + scf.yield ; + } else { + ^block36: + hir.exec @root_ns:root@1.0.0/hash_words/__rustc::__rust_dealloc(v290, v292, v291) + scf.yield ; + }; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/hash_words.masm b/tests/integration/expected/hash_words.masm new file mode 100644 index 000000000..45cd58bcc --- /dev/null +++ b/tests/integration/expected/hash_words.masm @@ -0,0 +1,807 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::hash_words + +export.entrypoint + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::intrinsics::felt::from_u32 + trace.252 + nop + push.0 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::intrinsics::felt::assert_eq + trace.252 + nop + push.2 + movup.2 + swap.1 + u32shr + push.16 + dup.3 + u32wrapping_add + push.2 + movup.3 + swap.1 + u32shl + dup.2 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::std::crypto::hashes::rpo::hash_memory_words + trace.252 + nop + push.24 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.40 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.32 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.32 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.16 + dup.0 + swap.2 + swap.4 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + push.48 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.__rustc::__rust_dealloc + drop + drop + drop +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + +proc.std::crypto::hashes::rpo::hash_memory_words + trace.240 + nop + exec.::std::crypto::hashes::rpo::hash_memory_words + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::deallocate + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + u32wrapping_add + swap.1 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::alloc::raw_vec::RawVecInner::current_memory + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + else + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movdn.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::::deallocate + trace.252 + nop + end + push.16 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::current_memory + push.0 + push.0 + dup.5 + eq + neq + if.true + movdn.3 + drop + drop + drop + push.0 + push.4 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + else + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.3 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + movup.2 + swap.1 + end + push.8 + push.4 + movup.3 + cdrop + end + movup.2 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::deallocate + push.0 + push.0 + dup.4 + eq + neq + if.true + drop + drop + drop + else + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hash_words::__rustc::__rust_dealloc + trace.252 + nop + end +end + diff --git a/tests/integration/expected/hash_words.wat b/tests/integration/expected/hash_words.wat new file mode 100644 index 000000000..f4b411bf3 --- /dev/null +++ b/tests/integration/expected/hash_words.wat @@ -0,0 +1,215 @@ +(module $hash_words.wasm + (type (;0;) (func (param i32) (result f32))) + (type (;1;) (func (param i32 i32 i32))) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param f32 f32))) + (type (;4;) (func (param i32 i32 i32 i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32) (result f32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 0 + i32.load offset=4 + local.set 2 + local.get 0 + i32.load offset=8 + local.set 3 + i32.const 0 + call $intrinsics::felt::from_u32 + i32.const 0 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 2 + i32.const 2 + i32.shr_u + local.tee 2 + local.get 2 + local.get 3 + i32.const 2 + i32.shl + i32.add + local.get 1 + i32.const 16 + i32.add + call $std::crypto::hashes::rpo::hash_memory_words + local.get 1 + local.get 1 + i64.load offset=24 + i64.store offset=40 + local.get 1 + local.get 1 + i64.load offset=16 + i64.store offset=32 + local.get 1 + local.get 1 + i32.const 32 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 1 + f32.load + local.set 4 + local.get 0 + i32.const 16 + i32.const 16 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 1 + i32.const 48 + i32.add + global.set $__stack_pointer + local.get 4 + ) + (func $__rustc::__rust_dealloc (;1;) (type 1) (param i32 i32 i32)) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;2;) (type 2) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $intrinsics::felt::from_u32 (;3;) (type 0) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::assert_eq (;4;) (type 3) (param f32 f32) + unreachable + ) + (func $std::crypto::hashes::rpo::hash_memory_words (;5;) (type 1) (param i32 i32 i32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;6;) (type 1) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;7;) (type 4) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;8;) (type 1) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) +) diff --git a/tests/integration/expected/hmerge.hir b/tests/integration/expected/hmerge.hir new file mode 100644 index 000000000..187e6634e --- /dev/null +++ b/tests/integration/expected/hmerge.hir @@ -0,0 +1,104 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @hmerge { + public builtin.function @entrypoint(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt) -> felt { + ^block4(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt): + v10 = builtin.global_symbol @root_ns:root@1.0.0/hmerge/__stack_pointer : ptr + v11 = hir.bitcast v10 : ptr; + v12 = hir.load v11 : i32; + v13 = arith.constant 48 : i32; + v14 = arith.sub v12, v13 : i32 #[overflow = wrapping]; + v15 = builtin.global_symbol @root_ns:root@1.0.0/hmerge/__stack_pointer : ptr + v16 = hir.bitcast v15 : ptr; + hir.store v16, v14; + v18 = arith.constant 28 : u32; + v17 = hir.bitcast v14 : u32; + v19 = arith.add v17, v18 : u32 #[overflow = checked]; + v20 = arith.constant 4 : u32; + v21 = arith.mod v19, v20 : u32; + hir.assertz v21 #[code = 250]; + v22 = hir.int_to_ptr v19 : ptr; + hir.store v22, v7; + v24 = arith.constant 24 : u32; + v23 = hir.bitcast v14 : u32; + v25 = arith.add v23, v24 : u32 #[overflow = checked]; + v87 = arith.constant 4 : u32; + v27 = arith.mod v25, v87 : u32; + hir.assertz v27 #[code = 250]; + v28 = hir.int_to_ptr v25 : ptr; + hir.store v28, v6; + v30 = arith.constant 20 : u32; + v29 = hir.bitcast v14 : u32; + v31 = arith.add v29, v30 : u32 #[overflow = checked]; + v86 = arith.constant 4 : u32; + v33 = arith.mod v31, v86 : u32; + hir.assertz v33 #[code = 250]; + v34 = hir.int_to_ptr v31 : ptr; + hir.store v34, v5; + v36 = arith.constant 16 : u32; + v35 = hir.bitcast v14 : u32; + v37 = arith.add v35, v36 : u32 #[overflow = checked]; + v85 = arith.constant 4 : u32; + v39 = arith.mod v37, v85 : u32; + hir.assertz v39 #[code = 250]; + v40 = hir.int_to_ptr v37 : ptr; + hir.store v40, v4; + v42 = arith.constant 12 : u32; + v41 = hir.bitcast v14 : u32; + v43 = arith.add v41, v42 : u32 #[overflow = checked]; + v84 = arith.constant 4 : u32; + v45 = arith.mod v43, v84 : u32; + hir.assertz v45 #[code = 250]; + v46 = hir.int_to_ptr v43 : ptr; + hir.store v46, v3; + v48 = arith.constant 8 : u32; + v47 = hir.bitcast v14 : u32; + v49 = arith.add v47, v48 : u32 #[overflow = checked]; + v83 = arith.constant 4 : u32; + v51 = arith.mod v49, v83 : u32; + hir.assertz v51 #[code = 250]; + v52 = hir.int_to_ptr v49 : ptr; + hir.store v52, v2; + v82 = arith.constant 4 : u32; + v53 = hir.bitcast v14 : u32; + v55 = arith.add v53, v82 : u32 #[overflow = checked]; + v81 = arith.constant 4 : u32; + v57 = arith.mod v55, v81 : u32; + hir.assertz v57 #[code = 250]; + v58 = hir.int_to_ptr v55 : ptr; + hir.store v58, v1; + v59 = hir.bitcast v14 : u32; + v80 = arith.constant 4 : u32; + v61 = arith.mod v59, v80 : u32; + hir.assertz v61 #[code = 250]; + v62 = hir.int_to_ptr v59 : ptr; + hir.store v62, v0; + v63 = arith.constant 32 : i32; + v64 = arith.add v14, v63 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/hmerge/intrinsics::crypto::hmerge(v14, v64) + v66 = arith.constant 32 : u32; + v65 = hir.bitcast v14 : u32; + v67 = arith.add v65, v66 : u32 #[overflow = checked]; + v79 = arith.constant 4 : u32; + v69 = arith.mod v67, v79 : u32; + hir.assertz v69 #[code = 250]; + v70 = hir.int_to_ptr v67 : ptr; + v71 = hir.load v70 : felt; + v78 = arith.constant 48 : i32; + v73 = arith.add v14, v78 : i32 #[overflow = wrapping]; + v74 = builtin.global_symbol @root_ns:root@1.0.0/hmerge/__stack_pointer : ptr + v75 = hir.bitcast v74 : ptr; + hir.store v75, v73; + builtin.ret v71; + }; + + private builtin.function @intrinsics::crypto::hmerge(v76: i32, v77: i32) { + ^block6(v76: i32, v77: i32): + hir.exec @intrinsics/crypto/hmerge(v76, v77) + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/hmerge.masm b/tests/integration/expected/hmerge.masm new file mode 100644 index 000000000..a1fd9247b --- /dev/null +++ b/tests/integration/expected/hmerge.masm @@ -0,0 +1,231 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::hmerge + +export.entrypoint + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.28 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.9 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.24 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.8 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.20 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.7 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.6 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.32 + dup.1 + u32wrapping_add + dup.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::hmerge::intrinsics::crypto::hmerge + trace.252 + nop + push.32 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.48 + movup.2 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.intrinsics::crypto::hmerge + trace.240 + nop + exec.::intrinsics::crypto::hmerge + trace.252 + nop +end + diff --git a/tests/integration/expected/hmerge.wat b/tests/integration/expected/hmerge.wat new file mode 100644 index 000000000..769febe96 --- /dev/null +++ b/tests/integration/expected/hmerge.wat @@ -0,0 +1,57 @@ +(module $hmerge.wasm + (type (;0;) (func (param f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) + (type (;1;) (func (param i32 i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param f32 f32 f32 f32 f32 f32 f32 f32) (result f32) + (local i32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 8 + global.set $__stack_pointer + local.get 8 + local.get 7 + f32.store offset=28 + local.get 8 + local.get 6 + f32.store offset=24 + local.get 8 + local.get 5 + f32.store offset=20 + local.get 8 + local.get 4 + f32.store offset=16 + local.get 8 + local.get 3 + f32.store offset=12 + local.get 8 + local.get 2 + f32.store offset=8 + local.get 8 + local.get 1 + f32.store offset=4 + local.get 8 + local.get 0 + f32.store + local.get 8 + local.get 8 + i32.const 32 + i32.add + call $intrinsics::crypto::hmerge + local.get 8 + f32.load offset=32 + local.set 7 + local.get 8 + i32.const 48 + i32.add + global.set $__stack_pointer + local.get 7 + ) + (func $intrinsics::crypto::hmerge (;1;) (type 1) (param i32 i32) + unreachable + ) +) diff --git a/tests/integration/expected/is_prime.hir b/tests/integration/expected/is_prime.hir new file mode 100644 index 000000000..7766bea6e --- /dev/null +++ b/tests/integration/expected/is_prime.hir @@ -0,0 +1,226 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @is_prime { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block4(v0: i32): + v2 = arith.constant 0 : i32; + v96 = arith.constant 2 : u32; + v4 = hir.bitcast v0 : u32; + v6 = arith.lt v4, v96 : i1; + v7 = arith.zext v6 : u32; + v8 = hir.bitcast v7 : i32; + v10 = arith.neq v8, v2 : i1; + v128, v129 = scf.if v10 : i32, u32 { + ^block6: + v89 = arith.constant 0 : u32; + v247 = arith.constant 0 : i32; + scf.yield v247, v89; + } else { + ^block7: + v246 = arith.constant 0 : i32; + v98 = arith.constant 4 : u32; + v13 = hir.bitcast v0 : u32; + v15 = arith.lt v13, v98 : i1; + v16 = arith.zext v15 : u32; + v17 = hir.bitcast v16 : i32; + v19 = arith.neq v17, v246 : i1; + v132, v133 = scf.if v19 : i32, u32 { + ^block9: + v245 = arith.constant 0 : u32; + v11 = arith.constant 1 : i32; + scf.yield v11, v245; + } else { + ^block10: + v242 = arith.constant 0 : i32; + v243 = arith.constant 0 : i32; + v244 = arith.constant 1 : i32; + v26 = arith.band v0, v244 : i32; + v28 = arith.eq v26, v243 : i1; + v29 = arith.zext v28 : u32; + v30 = hir.bitcast v29 : i32; + v32 = arith.neq v30, v242 : i1; + v136, v137 = scf.if v32 : i32, u32 { + ^block26: + v240 = arith.constant 0 : u32; + v241 = arith.constant 0 : i32; + scf.yield v241, v240; + } else { + ^block11: + v238 = arith.constant 0 : i32; + v239 = arith.constant 0 : i32; + v97 = arith.constant 3 : u32; + v21 = hir.bitcast v0 : u32; + v23 = arith.mod v21, v97 : u32; + v24 = hir.bitcast v23 : i32; + v35 = arith.eq v24, v239 : i1; + v36 = arith.zext v35 : u32; + v37 = hir.bitcast v36 : i32; + v39 = arith.neq v37, v238 : i1; + v140, v141 = scf.if v39 : i32, u32 { + ^block25: + v236 = arith.constant 0 : u32; + v237 = arith.constant 0 : i32; + scf.yield v237, v236; + } else { + ^block12: + v40 = arith.constant 5 : i32; + v192, v193, v194, v195 = scf.while v40, v0 : i32, i32, i32, u32 { + ^block50(v196: i32, v197: i32): + v45 = hir.bitcast v197 : u32; + v42 = arith.mul v196, v196 : i32 #[overflow = wrapping]; + v44 = hir.bitcast v42 : u32; + v46 = arith.gt v44, v45 : i1; + v47 = arith.zext v46 : u32; + v48 = hir.bitcast v47 : i32; + v235 = arith.constant 0 : i32; + v50 = arith.neq v48, v235 : i1; + v154, v155, v156, v157 = scf.if v50 : i32, i32, u32, u32 { + ^block41: + v234 = arith.constant 0 : u32; + v99 = arith.constant 5 : u32; + v95 = ub.poison i32 : i32; + scf.yield v95, v95, v99, v234; + } else { + ^block15: + v232 = arith.constant 0 : i32; + v233 = arith.constant 0 : i32; + v52 = arith.eq v196, v233 : i1; + v53 = arith.zext v52 : u32; + v54 = hir.bitcast v53 : i32; + v56 = arith.neq v54, v232 : i1; + v162, v163, v164, v165 = scf.if v56 : i32, i32, u32, u32 { + ^block43: + v229 = arith.constant 0 : u32; + v230 = arith.constant 4 : u32; + v231 = ub.poison i32 : i32; + scf.yield v231, v231, v230, v229; + } else { + ^block16: + v227 = arith.constant 0 : i32; + v228 = arith.constant 0 : i32; + v58 = hir.bitcast v196 : u32; + v57 = hir.bitcast v197 : u32; + v59 = arith.mod v57, v58 : u32; + v60 = hir.bitcast v59 : i32; + v62 = arith.eq v60, v228 : i1; + v63 = arith.zext v62 : u32; + v64 = hir.bitcast v63 : i32; + v66 = arith.neq v64, v227 : i1; + v170, v171, v172, v173 = scf.if v66 : i32, i32, u32, u32 { + ^block45: + v224 = arith.constant 0 : u32; + v225 = arith.constant 3 : u32; + v226 = ub.poison i32 : i32; + scf.yield v226, v226, v225, v224; + } else { + ^block17: + v3 = arith.constant 2 : i32; + v68 = arith.add v196, v3 : i32 #[overflow = wrapping]; + v222 = arith.constant 0 : i32; + v223 = arith.constant 0 : i32; + v70 = arith.eq v68, v223 : i1; + v71 = arith.zext v70 : u32; + v72 = hir.bitcast v71 : i32; + v74 = arith.neq v72, v222 : i1; + v178, v179, v180, v181 = scf.if v74 : i32, i32, u32, u32 { + ^block47: + v219 = arith.constant 0 : u32; + v220 = arith.constant 2 : u32; + v221 = ub.poison i32 : i32; + scf.yield v221, v221, v220, v219; + } else { + ^block18: + v218 = arith.constant 0 : i32; + v78 = hir.bitcast v68 : u32; + v77 = hir.bitcast v197 : u32; + v79 = arith.mod v77, v78 : u32; + v80 = hir.bitcast v79 : i32; + v82 = arith.neq v80, v218 : i1; + v213 = arith.constant 0 : u32; + v214 = arith.constant 1 : u32; + v191 = cf.select v82, v214, v213 : u32; + v94 = arith.constant 1 : u32; + v215 = arith.constant 0 : u32; + v190 = cf.select v82, v215, v94 : u32; + v216 = ub.poison i32 : i32; + v189 = cf.select v82, v197, v216 : i32; + v217 = ub.poison i32 : i32; + v12 = arith.constant 4 : i32; + v76 = arith.add v68, v12 : i32 #[overflow = wrapping]; + v188 = cf.select v82, v76, v217 : i32; + scf.yield v188, v189, v190, v191; + }; + scf.yield v178, v179, v180, v181; + }; + scf.yield v170, v171, v172, v173; + }; + scf.yield v162, v163, v164, v165; + }; + v123 = arith.trunc v157 : i1; + scf.condition v123, v154, v155, v48, v156; + } do { + ^block51(v198: i32, v199: i32, v200: i32, v201: u32): + scf.yield v198, v199; + }; + v145, v146, v147 = scf.index_switch v195 : i32, u32, u32 + case 1 { + ^block19: + v211 = arith.constant 0 : u32; + scf.yield v194, v211, v211; + } + case 2 { + ^block21: + v209 = arith.constant 1 : u32; + v106 = ub.poison u32 : u32; + v210 = ub.poison i32 : i32; + scf.yield v210, v106, v209; + } + case 3 { + ^block22: + v208 = arith.constant 0 : u32; + scf.yield v194, v208, v208; + } + case 4 { + ^block23: + v205 = arith.constant 1 : u32; + v206 = ub.poison u32 : u32; + v207 = ub.poison i32 : i32; + scf.yield v207, v206, v205; + } + default { + ^block24: + v212 = arith.constant 0 : u32; + scf.yield v194, v212, v212; + }; + v148, v149 = scf.index_switch v147 : i32, u32 + case 0 { + ^block40: + scf.yield v145, v146; + } + default { + ^block8: + v203 = arith.constant 1 : u32; + v204 = ub.poison i32 : i32; + scf.yield v204, v203; + }; + scf.yield v148, v149; + }; + scf.yield v140, v141; + }; + scf.yield v136, v137; + }; + scf.yield v132, v133; + }; + v202 = arith.constant 0 : u32; + v187 = arith.eq v129, v202 : i1; + cf.cond_br v187 ^block27, ^block28; + ^block27: + builtin.ret v128; + ^block28: + ub.unreachable ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/is_prime.masm b/tests/integration/expected/is_prime.masm new file mode 100644 index 000000000..65a18a366 --- /dev/null +++ b/tests/integration/expected/is_prime.masm @@ -0,0 +1,260 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::is_prime + +export.entrypoint + push.0 + push.2 + dup.2 + swap.1 + u32lt + neq + if.true + drop + push.0 + push.0 + else + push.0 + push.4 + dup.2 + swap.1 + u32lt + neq + if.true + drop + push.0 + push.1 + else + push.0 + push.0 + push.1 + dup.3 + u32and + eq + neq + if.true + drop + push.0 + push.0 + else + push.0 + push.0 + push.3 + dup.3 + swap.1 + u32mod + u32assert + eq + neq + if.true + drop + push.0 + push.0 + else + push.5 + push.1 + while.true + dup.1 + dup.1 + dup.0 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + swap.1 + u32gt + push.0 + dup.1 + neq + if.true + movdn.2 + drop + drop + push.0 + push.5 + push.3735929054 + dup.0 + swap.1 + else + push.0 + push.0 + dup.3 + eq + neq + if.true + movdn.2 + drop + drop + push.0 + push.4 + push.3735929054 + dup.0 + swap.1 + else + push.0 + push.0 + dup.3 + dup.5 + swap.1 + u32mod + u32assert + eq + neq + if.true + movdn.2 + drop + drop + push.0 + push.3 + push.3735929054 + dup.0 + swap.1 + else + push.2 + movup.2 + u32wrapping_add + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + swap.1 + drop + push.0 + push.2 + push.3735929054 + dup.0 + swap.1 + else + push.0 + dup.1 + dup.4 + swap.1 + u32mod + u32assert + neq + push.0 + push.1 + dup.2 + cdrop + push.1 + push.0 + dup.3 + cdrop + push.3735929054 + dup.3 + swap.1 + swap.2 + swap.7 + swap.1 + cdrop + push.3735929054 + push.4 + movup.5 + u32wrapping_add + movup.4 + cdrop + movup.2 + swap.3 + swap.4 + movdn.2 + end + end + end + end + movup.3 + push.1 + u32and + movup.3 + swap.4 + movdn.3 + if.true + movup.2 + drop + movup.2 + drop + push.1 + else + push.0 + end + end + drop + drop + swap.1 + dup.0 + push.2 + u32lte + if.true + eq.1 + if.true + push.0 + dup.0 + swap.2 + else + drop + push.1 + push.3735929054 + push.3735929054 + end + else + dup.0 + push.4 + u32lte + if.true + eq.3 + if.true + push.0 + dup.0 + swap.2 + else + drop + push.1 + push.3735929054 + push.3735929054 + end + else + drop + push.0 + dup.0 + swap.2 + end + end + movup.2 + eq.0 + if.true + nop + else + drop + drop + push.1 + push.3735929054 + end + end + end + end + end + push.0 + movup.2 + eq + if.true + nop + else + drop + push.0 + assert + end +end + diff --git a/tests/integration/expected/is_prime.wat b/tests/integration/expected/is_prime.wat new file mode 100644 index 000000000..05449d34b --- /dev/null +++ b/tests/integration/expected/is_prime.wat @@ -0,0 +1,77 @@ +(module $is_prime.wasm + (type (;0;) (func (param i32) (result i32))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + (local i32 i32 i32) + block ;; label = @1 + local.get 0 + i32.const 2 + i32.lt_u + br_if 0 (;@1;) + i32.const 1 + local.set 1 + block ;; label = @2 + block ;; label = @3 + local.get 0 + i32.const 4 + i32.lt_u + br_if 0 (;@3;) + local.get 0 + i32.const 3 + i32.rem_u + local.set 2 + local.get 0 + i32.const 1 + i32.and + i32.eqz + br_if 2 (;@1;) + i32.const 0 + local.set 1 + local.get 2 + i32.eqz + br_if 0 (;@3;) + i32.const 5 + local.set 2 + loop ;; label = @4 + local.get 2 + local.get 2 + i32.mul + local.get 0 + i32.gt_u + local.tee 1 + br_if 1 (;@3;) + local.get 2 + i32.eqz + br_if 2 (;@2;) + local.get 0 + local.get 2 + i32.rem_u + i32.eqz + br_if 1 (;@3;) + local.get 2 + i32.const 2 + i32.add + local.tee 3 + i32.eqz + br_if 2 (;@2;) + local.get 3 + i32.const 4 + i32.add + local.set 2 + local.get 0 + local.get 3 + i32.rem_u + br_if 0 (;@4;) + end + end + local.get 1 + return + end + unreachable + end + i32.const 0 + ) +) diff --git a/tests/integration/expected/le_felt.hir b/tests/integration/expected/le_felt.hir index 6c2688997..24ed8c61c 100644 --- a/tests/integration/expected/le_felt.hir +++ b/tests/integration/expected/le_felt.hir @@ -1,25 +1,24 @@ -(component - ;; Modules - (module #le_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @le_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> i32 { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/le_felt/intrinsics::felt::le(v1, v0) : i32 + v4 = arith.constant 0 : i32; + v5 = arith.neq v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::le(v8: felt, v9: felt) -> i32 { + ^block6(v8: felt, v9: felt): + v10 = arith.lte v8, v9 : i1; + v11 = hir.cast v10 : i32; + builtin.ret v11; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result i32) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 i1) (lte v1 v0)) - (let (v4 i32) (cast v3)) - (let (v5 i32) (const.i32 0)) - (let (v6 i1) (neq v4 v5)) - (let (v7 i32) (zext v6)) - (br (block 1 v7))) - - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/le_felt.masm b/tests/integration/expected/le_felt.masm index a772379e4..b03d5ebca 100644 --- a/tests/integration/expected/le_felt.masm +++ b/tests/integration/expected/le_felt.masm @@ -1,7 +1,30 @@ -# mod le_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::le_felt export.entrypoint - lte push.0 neq + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::le_felt::intrinsics::felt::le + trace.252 + nop + push.0 + neq end +proc.intrinsics::felt::le + swap.1 + lte +end diff --git a/tests/integration/expected/le_felt.wat b/tests/integration/expected/le_felt.wat index 17fff234f..2d1ccc487 100644 --- a/tests/integration/expected/le_felt.wat +++ b/tests/integration/expected/le_felt.wat @@ -1,16 +1,18 @@ (module $le_felt.wasm (type (;0;) (func (param f32 f32) (result i32))) - (import "miden:stdlib/intrinsics_felt" "le" (func $miden_stdlib_sys::intrinsics::felt::extern_le (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result i32) - local.get 1 - local.get 0 - call $miden_stdlib_sys::intrinsics::felt::extern_le - i32.const 0 - i32.ne - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result i32) + local.get 1 + local.get 0 + call $intrinsics::felt::le + i32.const 0 + i32.ne + ) + (func $intrinsics::felt::le (;1;) (type 0) (param f32 f32) (result i32) + unreachable + ) +) diff --git a/tests/integration/expected/le_i32.hir b/tests/integration/expected/le_i32.hir index dfcb5559e..6e781e95d 100644 --- a/tests/integration/expected/le_i32.hir +++ b/tests/integration/expected/le_i32.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_26428c714af584632b95b2863903e39a0f8601727e3054ad7ebaedf128da35c7 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_92cb575434e29cb53446988749bb020eaeee06891c154db71ddd9e1df57a151a { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.lte v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (lte v0 v1)) - (let (v4 i32) (sext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/le_i32.masm b/tests/integration/expected/le_i32.masm index 81992171d..51dfb239c 100644 --- a/tests/integration/expected/le_i32.masm +++ b/tests/integration/expected/le_i32.masm @@ -1,14 +1,29 @@ -# mod test_rust_26428c714af584632b95b2863903e39a0f8601727e3054ad7ebaedf128da35c7 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_92cb575434e29cb53446988749bb020eaeee06891c154db71ddd9e1df57a151a export.entrypoint swap.1 + trace.240 + nop exec.::intrinsics::i32::is_lte - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or + trace.252 + nop end - diff --git a/tests/integration/expected/le_i32.wat b/tests/integration/expected/le_i32.wat index f36494daf..4556da9f9 100644 --- a/tests/integration/expected/le_i32.wat +++ b/tests/integration/expected/le_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_26428c714af584632b95b2863903e39a0f8601727e3054ad7ebaedf128da35c7.wasm +(module $test_rust_92cb575434e29cb53446988749bb020eaeee06891c154db71ddd9e1df57a151a.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.le_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.le_s + ) +) diff --git a/tests/integration/expected/le_i64.hir b/tests/integration/expected/le_i64.hir index 128916b08..1e8fc9974 100644 --- a/tests/integration/expected/le_i64.hir +++ b/tests/integration/expected/le_i64.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_c4a9c74221b4f52c7cc028d994d8735ad8985dec62b6a6980159d497d7c3fa93 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_d1d5c9c08b80e76c14cb392695a30c0d0f06df4b14bd1301c713918bb7259fd5 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v3 = arith.lte v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i1) (lte v0 v1)) - (let (v4 i32) (sext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/le_i64.masm b/tests/integration/expected/le_i64.masm index ed88167d3..339b0856c 100644 --- a/tests/integration/expected/le_i64.masm +++ b/tests/integration/expected/le_i64.masm @@ -1,15 +1,30 @@ -# mod test_rust_c4a9c74221b4f52c7cc028d994d8735ad8985dec62b6a6980159d497d7c3fa93 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_d1d5c9c08b80e76c14cb392695a30c0d0f06df4b14bd1301c713918bb7259fd5 export.entrypoint movdn.3 movdn.3 + trace.240 + nop exec.::intrinsics::i64::lte - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or + trace.252 + nop end - diff --git a/tests/integration/expected/le_i64.wat b/tests/integration/expected/le_i64.wat index 6001d4edc..1835cbc82 100644 --- a/tests/integration/expected/le_i64.wat +++ b/tests/integration/expected/le_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_c4a9c74221b4f52c7cc028d994d8735ad8985dec62b6a6980159d497d7c3fa93.wasm +(module $test_rust_d1d5c9c08b80e76c14cb392695a30c0d0f06df4b14bd1301c713918bb7259fd5.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.le_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.le_s + ) +) diff --git a/tests/integration/expected/le_u16.hir b/tests/integration/expected/le_u16.hir index 92a905bf3..bf19c926c 100644 --- a/tests/integration/expected/le_u16.hir +++ b/tests/integration/expected/le_u16.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_aaaa272f39ec3f234fd119869db2d6791ce4b1e8d3f284fe6c212db33f480554 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_8e5b56cf449e682f0de4180f5e5ed7f7dd36f7d96e84a0c17bc979aa91efca6e { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.lte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lte v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/le_u16.masm b/tests/integration/expected/le_u16.masm index 74411e04e..e26c33f88 100644 --- a/tests/integration/expected/le_u16.masm +++ b/tests/integration/expected/le_u16.masm @@ -1,14 +1,27 @@ -# mod test_rust_aaaa272f39ec3f234fd119869db2d6791ce4b1e8d3f284fe6c212db33f480554 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_8e5b56cf449e682f0de4180f5e5ed7f7dd36f7d96e84a0c17bc979aa91efca6e export.entrypoint + swap.1 + swap.1 swap.1 u32lte - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/le_u16.wat b/tests/integration/expected/le_u16.wat index 7e571d9da..1c50d7c6c 100644 --- a/tests/integration/expected/le_u16.wat +++ b/tests/integration/expected/le_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_aaaa272f39ec3f234fd119869db2d6791ce4b1e8d3f284fe6c212db33f480554.wasm +(module $test_rust_8e5b56cf449e682f0de4180f5e5ed7f7dd36f7d96e84a0c17bc979aa91efca6e.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.le_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.le_u + ) +) diff --git a/tests/integration/expected/le_u32.hir b/tests/integration/expected/le_u32.hir index 3ab52806d..3c43308bf 100644 --- a/tests/integration/expected/le_u32.hir +++ b/tests/integration/expected/le_u32.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_cdcf596bb19cb80fff51986e5bc56794a88886a743030b2648f5742396b13095 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_bb7287e639f21ae334cbe77ea9953a6971d3586eb3527866fb5a973c6a9d7999 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.lte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lte v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/le_u32.masm b/tests/integration/expected/le_u32.masm index 4179417d7..08ab3e34b 100644 --- a/tests/integration/expected/le_u32.masm +++ b/tests/integration/expected/le_u32.masm @@ -1,14 +1,27 @@ -# mod test_rust_cdcf596bb19cb80fff51986e5bc56794a88886a743030b2648f5742396b13095 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_bb7287e639f21ae334cbe77ea9953a6971d3586eb3527866fb5a973c6a9d7999 export.entrypoint + swap.1 + swap.1 swap.1 u32lte - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/le_u32.wat b/tests/integration/expected/le_u32.wat index 707314bce..bde286b5b 100644 --- a/tests/integration/expected/le_u32.wat +++ b/tests/integration/expected/le_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_cdcf596bb19cb80fff51986e5bc56794a88886a743030b2648f5742396b13095.wasm +(module $test_rust_bb7287e639f21ae334cbe77ea9953a6971d3586eb3527866fb5a973c6a9d7999.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.le_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.le_u + ) +) diff --git a/tests/integration/expected/le_u64.hir b/tests/integration/expected/le_u64.hir index 1af33c0d8..e0d02b77d 100644 --- a/tests/integration/expected/le_u64.hir +++ b/tests/integration/expected/le_u64.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_907925d5e81fd1c82255ae3aa7232016c43e62c6867115675920cfe0d85013b1 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_175cf1929f30f3603a14a8443c3a31ee62ac12c89cd674c497750d11c4b1fcb6 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v4 = hir.bitcast v1 : u64; + v3 = hir.bitcast v0 : u64; + v5 = arith.lte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u64) (bitcast v0)) - (let (v4 u64) (bitcast v1)) - (let (v5 i1) (lte v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/le_u64.masm b/tests/integration/expected/le_u64.masm index c3aa2f967..355cfea5d 100644 --- a/tests/integration/expected/le_u64.masm +++ b/tests/integration/expected/le_u64.masm @@ -1,15 +1,34 @@ -# mod test_rust_907925d5e81fd1c82255ae3aa7232016c43e62c6867115675920cfe0d85013b1 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_175cf1929f30f3603a14a8443c3a31ee62ac12c89cd674c497750d11c4b1fcb6 export.entrypoint movdn.3 movdn.3 + movdn.3 + movdn.3 + movdn.3 + movdn.3 + trace.240 + nop exec.::std::math::u64::lte - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or + trace.252 + nop end - diff --git a/tests/integration/expected/le_u64.wat b/tests/integration/expected/le_u64.wat index 01c542b30..24a9dfade 100644 --- a/tests/integration/expected/le_u64.wat +++ b/tests/integration/expected/le_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_907925d5e81fd1c82255ae3aa7232016c43e62c6867115675920cfe0d85013b1.wasm +(module $test_rust_175cf1929f30f3603a14a8443c3a31ee62ac12c89cd674c497750d11c4b1fcb6.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.le_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.le_u + ) +) diff --git a/tests/integration/expected/le_u8.hir b/tests/integration/expected/le_u8.hir index a5f33154d..908dc8369 100644 --- a/tests/integration/expected/le_u8.hir +++ b/tests/integration/expected/le_u8.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_aace3f1ae4da30ba44b3c078c978343b282544139b496703f5f6a78953f42b7e - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_6b31118a71e100e1653e01e16012428a3ef4b388ea4b8aa8f00a0e4ff40c4ad9 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.lte v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lte v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/le_u8.masm b/tests/integration/expected/le_u8.masm index 1d879a9bb..86dbd9d92 100644 --- a/tests/integration/expected/le_u8.masm +++ b/tests/integration/expected/le_u8.masm @@ -1,14 +1,27 @@ -# mod test_rust_aace3f1ae4da30ba44b3c078c978343b282544139b496703f5f6a78953f42b7e +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_6b31118a71e100e1653e01e16012428a3ef4b388ea4b8aa8f00a0e4ff40c4ad9 export.entrypoint + swap.1 + swap.1 swap.1 u32lte - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/le_u8.wat b/tests/integration/expected/le_u8.wat index 1bf7fcf22..88f2dd30c 100644 --- a/tests/integration/expected/le_u8.wat +++ b/tests/integration/expected/le_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_aace3f1ae4da30ba44b3c078c978343b282544139b496703f5f6a78953f42b7e.wasm +(module $test_rust_6b31118a71e100e1653e01e16012428a3ef4b388ea4b8aa8f00a0e4ff40c4ad9.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.le_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.le_u + ) +) diff --git a/tests/integration/expected/lt_felt.hir b/tests/integration/expected/lt_felt.hir index 0b9b423e3..f7d15f7ca 100644 --- a/tests/integration/expected/lt_felt.hir +++ b/tests/integration/expected/lt_felt.hir @@ -1,25 +1,24 @@ -(component - ;; Modules - (module #lt_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @lt_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> i32 { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/lt_felt/intrinsics::felt::lt(v1, v0) : i32 + v4 = arith.constant 0 : i32; + v5 = arith.neq v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::lt(v8: felt, v9: felt) -> i32 { + ^block6(v8: felt, v9: felt): + v10 = arith.lt v8, v9 : i1; + v11 = hir.cast v10 : i32; + builtin.ret v11; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result i32) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 i1) (lt v1 v0)) - (let (v4 i32) (cast v3)) - (let (v5 i32) (const.i32 0)) - (let (v6 i1) (neq v4 v5)) - (let (v7 i32) (zext v6)) - (br (block 1 v7))) - - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/lt_felt.masm b/tests/integration/expected/lt_felt.masm index a8a8df452..8c60af6d6 100644 --- a/tests/integration/expected/lt_felt.masm +++ b/tests/integration/expected/lt_felt.masm @@ -1,7 +1,30 @@ -# mod lt_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::lt_felt export.entrypoint - lt push.0 neq + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::lt_felt::intrinsics::felt::lt + trace.252 + nop + push.0 + neq end +proc.intrinsics::felt::lt + swap.1 + lt +end diff --git a/tests/integration/expected/lt_felt.wat b/tests/integration/expected/lt_felt.wat index 9c65945e1..0ae010705 100644 --- a/tests/integration/expected/lt_felt.wat +++ b/tests/integration/expected/lt_felt.wat @@ -1,16 +1,18 @@ (module $lt_felt.wasm (type (;0;) (func (param f32 f32) (result i32))) - (import "miden:stdlib/intrinsics_felt" "lt" (func $miden_stdlib_sys::intrinsics::felt::extern_lt (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result i32) - local.get 1 - local.get 0 - call $miden_stdlib_sys::intrinsics::felt::extern_lt - i32.const 0 - i32.ne - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result i32) + local.get 1 + local.get 0 + call $intrinsics::felt::lt + i32.const 0 + i32.ne + ) + (func $intrinsics::felt::lt (;1;) (type 0) (param f32 f32) (result i32) + unreachable + ) +) diff --git a/tests/integration/expected/lt_i32.hir b/tests/integration/expected/lt_i32.hir index 3cf24438d..b38279292 100644 --- a/tests/integration/expected/lt_i32.hir +++ b/tests/integration/expected/lt_i32.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_57859c51c85389425b294750a9837d618293dd384d0b8582ca4c8bd6789e363e - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_5693119d28cd4640a4976d9000775e22d586752187efa219aae5739cf37d894a { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.lt v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i1) (lt v0 v1)) - (let (v4 i32) (sext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/lt_i32.masm b/tests/integration/expected/lt_i32.masm index 8ec02cc71..2bdc787b1 100644 --- a/tests/integration/expected/lt_i32.masm +++ b/tests/integration/expected/lt_i32.masm @@ -1,14 +1,29 @@ -# mod test_rust_57859c51c85389425b294750a9837d618293dd384d0b8582ca4c8bd6789e363e +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_5693119d28cd4640a4976d9000775e22d586752187efa219aae5739cf37d894a export.entrypoint swap.1 + trace.240 + nop exec.::intrinsics::i32::is_lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or + trace.252 + nop end - diff --git a/tests/integration/expected/lt_i32.wat b/tests/integration/expected/lt_i32.wat index 0280aea07..ae4d4c2b4 100644 --- a/tests/integration/expected/lt_i32.wat +++ b/tests/integration/expected/lt_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_57859c51c85389425b294750a9837d618293dd384d0b8582ca4c8bd6789e363e.wasm +(module $test_rust_5693119d28cd4640a4976d9000775e22d586752187efa219aae5739cf37d894a.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.lt_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.lt_s + ) +) diff --git a/tests/integration/expected/lt_i64.hir b/tests/integration/expected/lt_i64.hir index 2f224a175..0f025716c 100644 --- a/tests/integration/expected/lt_i64.hir +++ b/tests/integration/expected/lt_i64.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_228292094cebd3f423ea05660793b4754a31411dd7db56529b976b81e4d7066b - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_193ef2ef135ab65a9e3987e493e8cf00a81359975e6bffaf693a15c44afbf24b { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v3 = arith.lt v0, v1 : i1; + v4 = arith.zext v3 : u32; + v5 = hir.bitcast v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i1) (lt v0 v1)) - (let (v4 i32) (sext v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/lt_i64.masm b/tests/integration/expected/lt_i64.masm index e119b34db..3dc3a1649 100644 --- a/tests/integration/expected/lt_i64.masm +++ b/tests/integration/expected/lt_i64.masm @@ -1,15 +1,30 @@ -# mod test_rust_228292094cebd3f423ea05660793b4754a31411dd7db56529b976b81e4d7066b +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_193ef2ef135ab65a9e3987e493e8cf00a81359975e6bffaf693a15c44afbf24b export.entrypoint movdn.3 movdn.3 + trace.240 + nop exec.::intrinsics::i64::lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or + trace.252 + nop end - diff --git a/tests/integration/expected/lt_i64.wat b/tests/integration/expected/lt_i64.wat index 1d3f8d9ec..82a3a28bd 100644 --- a/tests/integration/expected/lt_i64.wat +++ b/tests/integration/expected/lt_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_228292094cebd3f423ea05660793b4754a31411dd7db56529b976b81e4d7066b.wasm +(module $test_rust_193ef2ef135ab65a9e3987e493e8cf00a81359975e6bffaf693a15c44afbf24b.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.lt_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.lt_s + ) +) diff --git a/tests/integration/expected/lt_u16.hir b/tests/integration/expected/lt_u16.hir index c045e3c46..688656c59 100644 --- a/tests/integration/expected/lt_u16.hir +++ b/tests/integration/expected/lt_u16.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_0899dac7a0388d3cd5c5447bcaf230146812c2c0ca4ede178224db721afe9aba - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_66380427a6137e56f56ac9060dff040ec50d21b1cf2dab70538313874bc4a594 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.lt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/lt_u16.masm b/tests/integration/expected/lt_u16.masm index 6e1cef3e5..d8c285783 100644 --- a/tests/integration/expected/lt_u16.masm +++ b/tests/integration/expected/lt_u16.masm @@ -1,14 +1,27 @@ -# mod test_rust_0899dac7a0388d3cd5c5447bcaf230146812c2c0ca4ede178224db721afe9aba +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_66380427a6137e56f56ac9060dff040ec50d21b1cf2dab70538313874bc4a594 export.entrypoint + swap.1 + swap.1 swap.1 u32lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/lt_u16.wat b/tests/integration/expected/lt_u16.wat index 07bc08916..869d08e4b 100644 --- a/tests/integration/expected/lt_u16.wat +++ b/tests/integration/expected/lt_u16.wat @@ -1,10 +1,5 @@ -(module $test_rust_0899dac7a0388d3cd5c5447bcaf230146812c2c0ca4ede178224db721afe9aba.wasm +(module $test_rust_66380427a6137e56f56ac9060dff040ec50d21b1cf2dab70538313874bc4a594.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.lt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.lt_u + ) +) diff --git a/tests/integration/expected/lt_u32.hir b/tests/integration/expected/lt_u32.hir index 04795a1e9..5955651c9 100644 --- a/tests/integration/expected/lt_u32.hir +++ b/tests/integration/expected/lt_u32.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_b1f1b13a84c328e970f28e0b366623db73f213249bdf37e4787af5f64f69adf1 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_1fcb2f151e54a93413cfbf27b720161db50a02b59d060ed448c3ffe2c3c9cc6c { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.lt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/lt_u32.masm b/tests/integration/expected/lt_u32.masm index 4b06252b9..0540350e6 100644 --- a/tests/integration/expected/lt_u32.masm +++ b/tests/integration/expected/lt_u32.masm @@ -1,14 +1,27 @@ -# mod test_rust_b1f1b13a84c328e970f28e0b366623db73f213249bdf37e4787af5f64f69adf1 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_1fcb2f151e54a93413cfbf27b720161db50a02b59d060ed448c3ffe2c3c9cc6c export.entrypoint + swap.1 + swap.1 swap.1 u32lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/lt_u32.wat b/tests/integration/expected/lt_u32.wat index 3b58372d9..e46b361e5 100644 --- a/tests/integration/expected/lt_u32.wat +++ b/tests/integration/expected/lt_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_b1f1b13a84c328e970f28e0b366623db73f213249bdf37e4787af5f64f69adf1.wasm +(module $test_rust_1fcb2f151e54a93413cfbf27b720161db50a02b59d060ed448c3ffe2c3c9cc6c.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.lt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.lt_u + ) +) diff --git a/tests/integration/expected/lt_u64.hir b/tests/integration/expected/lt_u64.hir index b827c507c..2ba49078e 100644 --- a/tests/integration/expected/lt_u64.hir +++ b/tests/integration/expected/lt_u64.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_32f5b5ff2aa96482da580eb924797236536a4ae0cbbb6d7daa491e9293c00bda - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_416d83d84b3ae500f053222602966b59099d85bd2706dbbaf20b4eb34e76cf0e { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i32 { + ^block6(v0: i64, v1: i64): + v4 = hir.bitcast v1 : u64; + v3 = hir.bitcast v0 : u64; + v5 = arith.lt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i32) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u64) (bitcast v0)) - (let (v4 u64) (bitcast v1)) - (let (v5 i1) (lt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/lt_u64.masm b/tests/integration/expected/lt_u64.masm index bb8075c11..9be51afcf 100644 --- a/tests/integration/expected/lt_u64.masm +++ b/tests/integration/expected/lt_u64.masm @@ -1,15 +1,34 @@ -# mod test_rust_32f5b5ff2aa96482da580eb924797236536a4ae0cbbb6d7daa491e9293c00bda +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_416d83d84b3ae500f053222602966b59099d85bd2706dbbaf20b4eb34e76cf0e export.entrypoint movdn.3 movdn.3 + movdn.3 + movdn.3 + movdn.3 + movdn.3 + trace.240 + nop exec.::std::math::u64::lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or + trace.252 + nop end - diff --git a/tests/integration/expected/lt_u64.wat b/tests/integration/expected/lt_u64.wat index 4ef4020f5..ea1f0f500 100644 --- a/tests/integration/expected/lt_u64.wat +++ b/tests/integration/expected/lt_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_32f5b5ff2aa96482da580eb924797236536a4ae0cbbb6d7daa491e9293c00bda.wasm +(module $test_rust_416d83d84b3ae500f053222602966b59099d85bd2706dbbaf20b4eb34e76cf0e.wasm (type (;0;) (func (param i64 i64) (result i32))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) - local.get 0 - local.get 1 - i64.lt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i32) + local.get 0 + local.get 1 + i64.lt_u + ) +) diff --git a/tests/integration/expected/lt_u8.hir b/tests/integration/expected/lt_u8.hir index 40f9b092f..23eb80cce 100644 --- a/tests/integration/expected/lt_u8.hir +++ b/tests/integration/expected/lt_u8.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_ddc15fe9df7100cbbcc5a51f3d4d29284c4cf43fb071224bc37b218c1758db30 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_b8700ecc87997bcf6eefb8bf0b6e645582a0ad9339bbe24fccc168e4c61e746e { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.lt v3, v4 : i1; + v6 = arith.zext v5 : u32; + v7 = hir.bitcast v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 i1) (lt v3 v4)) - (let (v6 i32) (sext v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/lt_u8.masm b/tests/integration/expected/lt_u8.masm index 5b561234a..3e225630b 100644 --- a/tests/integration/expected/lt_u8.masm +++ b/tests/integration/expected/lt_u8.masm @@ -1,14 +1,27 @@ -# mod test_rust_ddc15fe9df7100cbbcc5a51f3d4d29284c4cf43fb071224bc37b218c1758db30 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_b8700ecc87997bcf6eefb8bf0b6e645582a0ad9339bbe24fccc168e4c61e746e export.entrypoint + swap.1 + swap.1 swap.1 u32lt - push.0 - push.0 - push.4294967294 - movup.2 - cdrop - u32or end - diff --git a/tests/integration/expected/lt_u8.wat b/tests/integration/expected/lt_u8.wat index e811e6eca..cc6433795 100644 --- a/tests/integration/expected/lt_u8.wat +++ b/tests/integration/expected/lt_u8.wat @@ -1,10 +1,5 @@ -(module $test_rust_ddc15fe9df7100cbbcc5a51f3d4d29284c4cf43fb071224bc37b218c1758db30.wasm +(module $test_rust_b8700ecc87997bcf6eefb8bf0b6e645582a0ad9339bbe24fccc168e4c61e746e.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.lt_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.lt_u + ) +) diff --git a/tests/integration/expected/mem_intrinsics_heap_base.hir b/tests/integration/expected/mem_intrinsics_heap_base.hir new file mode 100644 index 000000000..c82e30528 --- /dev/null +++ b/tests/integration/expected/mem_intrinsics_heap_base.hir @@ -0,0 +1,210 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @mem_intrinsics_heap_base { + public builtin.function @entrypoint(v0: i32, v1: i32) { + ^block4(v0: i32, v1: i32): + hir.exec @root_ns:root@1.0.0/mem_intrinsics_heap_base/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v3 = arith.constant 4 : i32; + v5 = hir.exec @root_ns:root@1.0.0/mem_intrinsics_heap_base/__rustc::__rust_alloc(v3, v3) : i32 + v2 = arith.constant 0 : i32; + v7 = arith.neq v5, v2 : i1; + cf.cond_br v7 ^block6, ^block7; + ^block6: + v12 = arith.constant 8 : u32; + v11 = hir.bitcast v0 : u32; + v13 = arith.add v11, v12 : u32 #[overflow = checked]; + v14 = arith.constant 4 : u32; + v15 = arith.mod v13, v14 : u32; + hir.assertz v15 #[code = 250]; + v10 = arith.constant 1 : i32; + v16 = hir.int_to_ptr v13 : ptr; + hir.store v16, v10; + v142 = arith.constant 4 : u32; + v17 = hir.bitcast v0 : u32; + v19 = arith.add v17, v142 : u32 #[overflow = checked]; + v141 = arith.constant 4 : u32; + v21 = arith.mod v19, v141 : u32; + hir.assertz v21 #[code = 250]; + v22 = hir.int_to_ptr v19 : ptr; + hir.store v22, v5; + v24 = hir.bitcast v0 : u32; + v140 = arith.constant 4 : u32; + v26 = arith.mod v24, v140 : u32; + hir.assertz v26 #[code = 250]; + v139 = arith.constant 1 : i32; + v27 = hir.int_to_ptr v24 : ptr; + hir.store v27, v139; + v31 = hir.bitcast v5 : u32; + v138 = arith.constant 4 : u32; + v33 = arith.mod v31, v138 : u32; + hir.assertz v33 #[code = 250]; + v135 = arith.constant 1 : u32; + v30 = arith.shl v1, v135 : i32; + v34 = hir.int_to_ptr v31 : ptr; + hir.store v34, v30; + builtin.ret ; + ^block7: + v137 = arith.constant 4 : i32; + hir.exec @root_ns:root@1.0.0/mem_intrinsics_heap_base/alloc::alloc::handle_alloc_error(v137, v137) + ub.unreachable ; + }; + + private builtin.function @__rustc::__rust_alloc(v35: i32, v36: i32) -> i32 { + ^block8(v35: i32, v36: i32): + v38 = arith.constant 1048576 : i32; + v39 = hir.exec @root_ns:root@1.0.0/mem_intrinsics_heap_base/::alloc(v38, v36, v35) : i32 + builtin.ret v39; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block10: + builtin.ret ; + }; + + private builtin.function @::alloc(v40: i32, v41: i32, v42: i32) -> i32 { + ^block12(v40: i32, v41: i32, v42: i32): + v45 = arith.constant 16 : i32; + v44 = arith.constant 0 : i32; + v144 = arith.constant 16 : u32; + v47 = hir.bitcast v41 : u32; + v49 = arith.gt v47, v144 : i1; + v50 = arith.zext v49 : u32; + v51 = hir.bitcast v50 : i32; + v53 = arith.neq v51, v44 : i1; + v54 = cf.select v53, v41, v45 : i32; + v184 = arith.constant 0 : i32; + v55 = arith.constant -1 : i32; + v56 = arith.add v54, v55 : i32 #[overflow = wrapping]; + v57 = arith.band v54, v56 : i32; + v59 = arith.neq v57, v184 : i1; + v153, v154 = scf.if v59 : i32, u32 { + ^block33: + v145 = arith.constant 0 : u32; + v149 = ub.poison i32 : i32; + scf.yield v149, v145; + } else { + ^block15: + v61 = hir.exec @root_ns:root@1.0.0/mem_intrinsics_heap_base/core::ptr::alignment::Alignment::max(v41, v54) : i32 + v183 = arith.constant 0 : i32; + v60 = arith.constant -2147483648 : i32; + v62 = arith.sub v60, v61 : i32 #[overflow = wrapping]; + v64 = hir.bitcast v62 : u32; + v63 = hir.bitcast v42 : u32; + v65 = arith.gt v63, v64 : i1; + v66 = arith.zext v65 : u32; + v67 = hir.bitcast v66 : i32; + v69 = arith.neq v67, v183 : i1; + v168 = scf.if v69 : i32 { + ^block32: + v182 = ub.poison i32 : i32; + scf.yield v182; + } else { + ^block16: + v180 = arith.constant 0 : i32; + v75 = arith.sub v180, v61 : i32 #[overflow = wrapping]; + v181 = arith.constant -1 : i32; + v71 = arith.add v42, v61 : i32 #[overflow = wrapping]; + v73 = arith.add v71, v181 : i32 #[overflow = wrapping]; + v76 = arith.band v73, v75 : i32; + v77 = hir.bitcast v40 : u32; + v78 = arith.constant 4 : u32; + v79 = arith.mod v77, v78 : u32; + hir.assertz v79 #[code = 250]; + v80 = hir.int_to_ptr v77 : ptr; + v81 = hir.load v80 : i32; + v179 = arith.constant 0 : i32; + v83 = arith.neq v81, v179 : i1; + scf.if v83{ + ^block31: + scf.yield ; + } else { + ^block18: + v84 = hir.exec @root_ns:root@1.0.0/mem_intrinsics_heap_base/intrinsics::mem::heap_base() : i32 + v85 = hir.mem_size : u32; + v91 = hir.bitcast v40 : u32; + v178 = arith.constant 4 : u32; + v93 = arith.mod v91, v178 : u32; + hir.assertz v93 #[code = 250]; + v177 = arith.constant 16 : u32; + v86 = hir.bitcast v85 : i32; + v89 = arith.shl v86, v177 : i32; + v90 = arith.add v84, v89 : i32 #[overflow = wrapping]; + v94 = hir.int_to_ptr v91 : ptr; + hir.store v94, v90; + scf.yield ; + }; + v97 = hir.bitcast v40 : u32; + v176 = arith.constant 4 : u32; + v99 = arith.mod v97, v176 : u32; + hir.assertz v99 #[code = 250]; + v100 = hir.int_to_ptr v97 : ptr; + v101 = hir.load v100 : i32; + v174 = arith.constant 0 : i32; + v175 = arith.constant -1 : i32; + v103 = arith.bxor v101, v175 : i32; + v105 = hir.bitcast v103 : u32; + v104 = hir.bitcast v76 : u32; + v106 = arith.gt v104, v105 : i1; + v107 = arith.zext v106 : u32; + v108 = hir.bitcast v107 : i32; + v110 = arith.neq v108, v174 : i1; + v167 = scf.if v110 : i32 { + ^block19: + v173 = arith.constant 0 : i32; + scf.yield v173; + } else { + ^block20: + v112 = hir.bitcast v40 : u32; + v172 = arith.constant 4 : u32; + v114 = arith.mod v112, v172 : u32; + hir.assertz v114 #[code = 250]; + v111 = arith.add v101, v76 : i32 #[overflow = wrapping]; + v115 = hir.int_to_ptr v112 : ptr; + hir.store v115, v111; + v117 = arith.add v101, v61 : i32 #[overflow = wrapping]; + scf.yield v117; + }; + scf.yield v167; + }; + v150 = arith.constant 1 : u32; + v171 = arith.constant 0 : u32; + v169 = cf.select v69, v171, v150 : u32; + scf.yield v168, v169; + }; + v170 = arith.constant 0 : u32; + v166 = arith.eq v154, v170 : i1; + cf.cond_br v166 ^block14, ^block35(v153); + ^block14: + ub.unreachable ; + ^block35(v146: i32): + builtin.ret v146; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block21: + v120 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v120; + }; + + private builtin.function @alloc::alloc::handle_alloc_error(v122: i32, v123: i32) { + ^block25(v122: i32, v123: i32): + ub.unreachable ; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v124: i32, v125: i32) -> i32 { + ^block27(v124: i32, v125: i32): + v132 = arith.constant 0 : i32; + v128 = hir.bitcast v125 : u32; + v127 = hir.bitcast v124 : u32; + v129 = arith.gt v127, v128 : i1; + v130 = arith.zext v129 : u32; + v131 = hir.bitcast v130 : i32; + v133 = arith.neq v131, v132 : i1; + v134 = cf.select v133, v124, v125 : i32; + builtin.ret v134; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mem_intrinsics_heap_base.wat b/tests/integration/expected/mem_intrinsics_heap_base.wat new file mode 100644 index 000000000..42eac0aa2 --- /dev/null +++ b/tests/integration/expected/mem_intrinsics_heap_base.wat @@ -0,0 +1,136 @@ +(module $mem_intrinsics_heap_base.wasm + (type (;0;) (func (param i32 i32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func)) + (type (;3;) (func (param i32 i32 i32) (result i32))) + (type (;4;) (func (result i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32 i32) + (local i32) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + block ;; label = @1 + i32.const 4 + i32.const 4 + call $__rustc::__rust_alloc + local.tee 2 + br_if 0 (;@1;) + i32.const 4 + i32.const 4 + call $alloc::alloc::handle_alloc_error + unreachable + end + local.get 0 + i32.const 1 + i32.store offset=8 + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + i32.const 1 + i32.store + local.get 2 + local.get 1 + i32.const 1 + i32.shl + i32.store + ) + (func $__rustc::__rust_alloc (;1;) (type 1) (param i32 i32) (result i32) + i32.const 1048576 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;2;) (type 2) + return + ) + (func $::alloc (;3;) (type 3) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;4;) (type 4) (result i32) + unreachable + ) + (func $alloc::alloc::handle_alloc_error (;5;) (type 0) (param i32 i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;6;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) +) diff --git a/tests/integration/expected/mul_felt.hir b/tests/integration/expected/mul_felt.hir index 4b1b8b2c1..49de5efab 100644 --- a/tests/integration/expected/mul_felt.hir +++ b/tests/integration/expected/mul_felt.hir @@ -1,21 +1,19 @@ -(component - ;; Modules - (module #mul_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @mul_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> felt { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/mul_felt/intrinsics::felt::mul(v0, v1) : felt + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::mul(v4: felt, v5: felt) -> felt { + ^block6(v4: felt, v5: felt): + v6 = arith.mul v4, v5 : felt #[overflow = unchecked]; + builtin.ret v6; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result felt) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 felt) (mul.unchecked v0 v1)) - (br (block 1 v3))) - - (block 1 (param v2 felt) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_felt.masm b/tests/integration/expected/mul_felt.masm index 00dd11354..81c2b1ae3 100644 --- a/tests/integration/expected/mul_felt.masm +++ b/tests/integration/expected/mul_felt.masm @@ -1,7 +1,26 @@ -# mod mul_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::mul_felt export.entrypoint - swap.1 mul + trace.240 + nop + exec.::root_ns:root@1.0.0::mul_felt::intrinsics::felt::mul + trace.252 + nop end +proc.intrinsics::felt::mul + mul +end diff --git a/tests/integration/expected/mul_felt.wat b/tests/integration/expected/mul_felt.wat index b1578b463..e2d772f8a 100644 --- a/tests/integration/expected/mul_felt.wat +++ b/tests/integration/expected/mul_felt.wat @@ -1,14 +1,16 @@ (module $mul_felt.wasm (type (;0;) (func (param f32 f32) (result f32))) - (import "miden:stdlib/intrinsics_felt" "mul" (func $miden_stdlib_sys::intrinsics::felt::extern_mul (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result f32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_mul - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result f32) + local.get 0 + local.get 1 + call $intrinsics::felt::mul + ) + (func $intrinsics::felt::mul (;1;) (type 0) (param f32 f32) (result f32) + unreachable + ) +) diff --git a/tests/integration/expected/mul_i128.hir b/tests/integration/expected/mul_i128.hir new file mode 100644 index 000000000..44fd35d96 --- /dev/null +++ b/tests/integration/expected/mul_i128.hir @@ -0,0 +1,46 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_aef84d8c0f31ef839006490963a826949aaedc9315fd0ca99dc18ddcd7c7205b { + public builtin.function @entrypoint(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64) { + ^block6(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64): + v8 = hir.bitcast v1 : u64; + v9 = arith.zext v8 : u128; + v6 = hir.bitcast v3 : u64; + hir.store_local v3 #[local = lv0]; + v7 = arith.zext v6 : u128; + v10 = arith.mul v7, v9 : u128 #[overflow = wrapping]; + v11, v12 = arith.split v10 : i64, i64; + v13 = hir.bitcast v0 : u32; + v14 = arith.constant 8 : u32; + v15 = arith.mod v13, v14 : u32; + hir.assertz v15 #[code = 250]; + v16 = hir.int_to_ptr v13 : ptr; + hir.store v16, v12; + v28 = arith.constant 8 : u32; + v21 = hir.bitcast v0 : u32; + v23 = arith.add v21, v28 : u32 #[overflow = checked]; + v27 = arith.constant 8 : u32; + v25 = arith.mod v23, v27 : u32; + hir.assertz v25 #[code = 250]; + v19 = arith.mul v4, v1 : i64 #[overflow = wrapping]; + v30 = hir.load_local : i64 #[local = lv0]; + v17 = arith.mul v30, v2 : i64 #[overflow = wrapping]; + v18 = arith.add v11, v17 : i64 #[overflow = wrapping]; + v20 = arith.add v18, v19 : i64 #[overflow = wrapping]; + v26 = hir.int_to_ptr v23 : ptr; + hir.store v26, v20; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_i128.masm b/tests/integration/expected/mul_i128.masm new file mode 100644 index 000000000..e9147c808 --- /dev/null +++ b/tests/integration/expected/mul_i128.masm @@ -0,0 +1,117 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_aef84d8c0f31ef839006490963a826949aaedc9315fd0ca99dc18ddcd7c7205b + +export.entrypoint.2 + dup.2 + dup.2 + push.0 + push.0 + dup.10 + dup.10 + movup.12 + movup.12 + locaddr.0 + push.0 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.0 + push.0 + trace.240 + nop + exec.::intrinsics::i128::mul + trace.252 + nop + dup.4 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + movup.4 + movup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.8 + movup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.8 + movup.8 + movup.6 + movup.6 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + locaddr.0 + push.0 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.8 + movup.8 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + movup.6 + movup.6 + trace.240 + nop + exec.::std::math::u64::wrapping_add + trace.252 + nop + trace.240 + nop + exec.::std::math::u64::wrapping_add + trace.252 + nop + movup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + diff --git a/tests/integration/expected/mul_i128.wat b/tests/integration/expected/mul_i128.wat new file mode 100644 index 000000000..bbeeabca6 --- /dev/null +++ b/tests/integration/expected/mul_i128.wat @@ -0,0 +1,31 @@ +(module $test_rust_aef84d8c0f31ef839006490963a826949aaedc9315fd0ca99dc18ddcd7c7205b.wasm + (type (;0;) (func (param i32 i64 i64 i64 i64))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (func $entrypoint (;0;) (type 0) (param i32 i64 i64 i64 i64) + (local i64) + local.get 0 + local.get 3 + local.get 1 + i64.mul_wide_u + local.set 5 + i64.store + local.get 0 + local.get 5 + local.get 3 + local.get 2 + i64.mul + i64.add + local.get 4 + local.get 1 + i64.mul + i64.add + i64.store offset=8 + ) +) diff --git a/tests/integration/expected/mul_i32.hir b/tests/integration/expected/mul_i32.hir index 309b695c3..7ab7d0f78 100644 --- a/tests/integration/expected/mul_i32.hir +++ b/tests/integration/expected/mul_i32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_262e71c5bff4a385e1ce68a8feb20203bbf0c528647e9c602609c0808d505876 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_4a870fe7a144fba5513f0d246ed1179aba172aab8f12afb574634dd1bb298024 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.mul v1, v0 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (mul.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_i32.masm b/tests/integration/expected/mul_i32.masm index 56b4b6124..1bcbdf0fd 100644 --- a/tests/integration/expected/mul_i32.masm +++ b/tests/integration/expected/mul_i32.masm @@ -1,7 +1,28 @@ -# mod test_rust_262e71c5bff4a385e1ce68a8feb20203bbf0c528647e9c602609c0808d505876 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_4a870fe7a144fba5513f0d246ed1179aba172aab8f12afb574634dd1bb298024 export.entrypoint + trace.240 + nop exec.::intrinsics::i32::wrapping_mul + trace.252 + nop end - diff --git a/tests/integration/expected/mul_i32.wat b/tests/integration/expected/mul_i32.wat index 0fb0b0341..fcfe45746 100644 --- a/tests/integration/expected/mul_i32.wat +++ b/tests/integration/expected/mul_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_262e71c5bff4a385e1ce68a8feb20203bbf0c528647e9c602609c0808d505876.wasm +(module $test_rust_4a870fe7a144fba5513f0d246ed1179aba172aab8f12afb574634dd1bb298024.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.mul - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.mul + ) +) diff --git a/tests/integration/expected/mul_i64.hir b/tests/integration/expected/mul_i64.hir index f5bb7abf7..49c839997 100644 --- a/tests/integration/expected/mul_i64.hir +++ b/tests/integration/expected/mul_i64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_4fa4a84dc2c1fe1a2bc729c72d5e78982ba8edf5490a729e66438056dcb06101 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_ee95318918a451f864b66b576532b422841bcd75ad8a62c4d4d3489a9158c08d { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.mul v1, v0 : i64 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (mul.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_i64.masm b/tests/integration/expected/mul_i64.masm index 1a6eda6ff..eabbd9360 100644 --- a/tests/integration/expected/mul_i64.masm +++ b/tests/integration/expected/mul_i64.masm @@ -1,7 +1,28 @@ -# mod test_rust_4fa4a84dc2c1fe1a2bc729c72d5e78982ba8edf5490a729e66438056dcb06101 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_ee95318918a451f864b66b576532b422841bcd75ad8a62c4d4d3489a9158c08d export.entrypoint + trace.240 + nop exec.::intrinsics::i64::wrapping_mul + trace.252 + nop end - diff --git a/tests/integration/expected/mul_i64.wat b/tests/integration/expected/mul_i64.wat index ce0bdc6af..e7d2eef63 100644 --- a/tests/integration/expected/mul_i64.wat +++ b/tests/integration/expected/mul_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_4fa4a84dc2c1fe1a2bc729c72d5e78982ba8edf5490a729e66438056dcb06101.wasm +(module $test_rust_ee95318918a451f864b66b576532b422841bcd75ad8a62c4d4d3489a9158c08d.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.mul - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.mul + ) +) diff --git a/tests/integration/expected/mul_u128.hir b/tests/integration/expected/mul_u128.hir new file mode 100644 index 000000000..53723c3ce --- /dev/null +++ b/tests/integration/expected/mul_u128.hir @@ -0,0 +1,46 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_47451efa7b59553058cbdbb88f5ae77d281126afc1fd9ca385bcdeb64d64800e { + public builtin.function @entrypoint(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64) { + ^block6(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64): + v8 = hir.bitcast v1 : u64; + v9 = arith.zext v8 : u128; + v6 = hir.bitcast v3 : u64; + hir.store_local v3 #[local = lv0]; + v7 = arith.zext v6 : u128; + v10 = arith.mul v7, v9 : u128 #[overflow = wrapping]; + v11, v12 = arith.split v10 : i64, i64; + v13 = hir.bitcast v0 : u32; + v14 = arith.constant 8 : u32; + v15 = arith.mod v13, v14 : u32; + hir.assertz v15 #[code = 250]; + v16 = hir.int_to_ptr v13 : ptr; + hir.store v16, v12; + v28 = arith.constant 8 : u32; + v21 = hir.bitcast v0 : u32; + v23 = arith.add v21, v28 : u32 #[overflow = checked]; + v27 = arith.constant 8 : u32; + v25 = arith.mod v23, v27 : u32; + hir.assertz v25 #[code = 250]; + v19 = arith.mul v4, v1 : i64 #[overflow = wrapping]; + v30 = hir.load_local : i64 #[local = lv0]; + v17 = arith.mul v30, v2 : i64 #[overflow = wrapping]; + v18 = arith.add v11, v17 : i64 #[overflow = wrapping]; + v20 = arith.add v18, v19 : i64 #[overflow = wrapping]; + v26 = hir.int_to_ptr v23 : ptr; + hir.store v26, v20; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_u128.masm b/tests/integration/expected/mul_u128.masm new file mode 100644 index 000000000..3a5a45a90 --- /dev/null +++ b/tests/integration/expected/mul_u128.masm @@ -0,0 +1,117 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_47451efa7b59553058cbdbb88f5ae77d281126afc1fd9ca385bcdeb64d64800e + +export.entrypoint.2 + dup.2 + dup.2 + push.0 + push.0 + dup.10 + dup.10 + movup.12 + movup.12 + locaddr.0 + push.0 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.0 + push.0 + trace.240 + nop + exec.::intrinsics::i128::mul + trace.252 + nop + dup.4 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + movup.4 + movup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.8 + movup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.8 + movup.8 + movup.6 + movup.6 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + locaddr.0 + push.0 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.8 + movup.8 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + movup.6 + movup.6 + trace.240 + nop + exec.::std::math::u64::wrapping_add + trace.252 + nop + trace.240 + nop + exec.::std::math::u64::wrapping_add + trace.252 + nop + movup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + diff --git a/tests/integration/expected/mul_u128.wat b/tests/integration/expected/mul_u128.wat new file mode 100644 index 000000000..2d0be0793 --- /dev/null +++ b/tests/integration/expected/mul_u128.wat @@ -0,0 +1,31 @@ +(module $test_rust_47451efa7b59553058cbdbb88f5ae77d281126afc1fd9ca385bcdeb64d64800e.wasm + (type (;0;) (func (param i32 i64 i64 i64 i64))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (func $entrypoint (;0;) (type 0) (param i32 i64 i64 i64 i64) + (local i64) + local.get 0 + local.get 3 + local.get 1 + i64.mul_wide_u + local.set 5 + i64.store + local.get 0 + local.get 5 + local.get 3 + local.get 2 + i64.mul + i64.add + local.get 4 + local.get 1 + i64.mul + i64.add + i64.store offset=8 + ) +) diff --git a/tests/integration/expected/mul_u16.hir b/tests/integration/expected/mul_u16.hir index b85066c27..06c8866c8 100644 --- a/tests/integration/expected/mul_u16.hir +++ b/tests/integration/expected/mul_u16.hir @@ -1,25 +1,23 @@ -(component - ;; Modules - (module #test_rust_742f0d1b5e8390cf229b8c736825d5c524abf07a2bec3f4109afcc68d1547946 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_1503849cd0d258094fa54f6a7e16334db70e5daa56a5858f344d2a2c90b757d6 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = arith.constant 65535 : i32; + v3 = arith.mul v1, v0 : i32 #[overflow = wrapping]; + v5 = arith.band v3, v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (mul.wrapping v1 v0)) - (let (v4 i32) (const.i32 65535)) - (let (v5 i32) (band v3 v4)) - (br (block 1 v5))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_u16.masm b/tests/integration/expected/mul_u16.masm index 2162a097a..141789c36 100644 --- a/tests/integration/expected/mul_u16.masm +++ b/tests/integration/expected/mul_u16.masm @@ -1,7 +1,31 @@ -# mod test_rust_742f0d1b5e8390cf229b8c736825d5c524abf07a2bec3f4109afcc68d1547946 +# mod root_ns:root@1.0.0 -export.entrypoint - exec.::intrinsics::i32::wrapping_mul push.65535 u32and +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_1503849cd0d258094fa54f6a7e16334db70e5daa56a5858f344d2a2c90b757d6 + +export.entrypoint + push.65535 + movdn.2 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + u32and +end diff --git a/tests/integration/expected/mul_u16.wat b/tests/integration/expected/mul_u16.wat index ac50cee5e..2097aee11 100644 --- a/tests/integration/expected/mul_u16.wat +++ b/tests/integration/expected/mul_u16.wat @@ -1,12 +1,5 @@ -(module $test_rust_742f0d1b5e8390cf229b8c736825d5c524abf07a2bec3f4109afcc68d1547946.wasm +(module $test_rust_1503849cd0d258094fa54f6a7e16334db70e5daa56a5858f344d2a2c90b757d6.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.mul - i32.const 65535 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.mul + i32.const 65535 + i32.and + ) +) diff --git a/tests/integration/expected/mul_u32.hir b/tests/integration/expected/mul_u32.hir index 8d42ad966..186db2b96 100644 --- a/tests/integration/expected/mul_u32.hir +++ b/tests/integration/expected/mul_u32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_b88e2ce441d27c44cb7d5b1e9a0fdde312f6e4c8ba7ce98f46069c983aef0f27 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_774ac315a535180c25b481293c43de2ad5b38b8ed718ccd906a367b8df969453 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.mul v1, v0 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (mul.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_u32.masm b/tests/integration/expected/mul_u32.masm index 944ecdabb..292731b08 100644 --- a/tests/integration/expected/mul_u32.masm +++ b/tests/integration/expected/mul_u32.masm @@ -1,7 +1,28 @@ -# mod test_rust_b88e2ce441d27c44cb7d5b1e9a0fdde312f6e4c8ba7ce98f46069c983aef0f27 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_774ac315a535180c25b481293c43de2ad5b38b8ed718ccd906a367b8df969453 export.entrypoint + trace.240 + nop exec.::intrinsics::i32::wrapping_mul + trace.252 + nop end - diff --git a/tests/integration/expected/mul_u32.wat b/tests/integration/expected/mul_u32.wat index f90d24a00..6d4744ddb 100644 --- a/tests/integration/expected/mul_u32.wat +++ b/tests/integration/expected/mul_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_b88e2ce441d27c44cb7d5b1e9a0fdde312f6e4c8ba7ce98f46069c983aef0f27.wasm +(module $test_rust_774ac315a535180c25b481293c43de2ad5b38b8ed718ccd906a367b8df969453.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.mul - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.mul + ) +) diff --git a/tests/integration/expected/mul_u64.hir b/tests/integration/expected/mul_u64.hir index d0a2c9425..da7557cb9 100644 --- a/tests/integration/expected/mul_u64.hir +++ b/tests/integration/expected/mul_u64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_ac7483d8e665bc8618c926c4a827af8cc8b060db1a400373319417a1045674f6 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_ef43647914852c132ded20e7ab9569f69610af521a6074095544ea088f8908ae { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.mul v1, v0 : i64 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (mul.wrapping v1 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_u64.masm b/tests/integration/expected/mul_u64.masm index 3b87e6684..d627223a6 100644 --- a/tests/integration/expected/mul_u64.masm +++ b/tests/integration/expected/mul_u64.masm @@ -1,7 +1,28 @@ -# mod test_rust_ac7483d8e665bc8618c926c4a827af8cc8b060db1a400373319417a1045674f6 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_ef43647914852c132ded20e7ab9569f69610af521a6074095544ea088f8908ae export.entrypoint + trace.240 + nop exec.::intrinsics::i64::wrapping_mul + trace.252 + nop end - diff --git a/tests/integration/expected/mul_u64.wat b/tests/integration/expected/mul_u64.wat index 9aab11d0c..9310decdc 100644 --- a/tests/integration/expected/mul_u64.wat +++ b/tests/integration/expected/mul_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_ac7483d8e665bc8618c926c4a827af8cc8b060db1a400373319417a1045674f6.wasm +(module $test_rust_ef43647914852c132ded20e7ab9569f69610af521a6074095544ea088f8908ae.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 1 - local.get 0 - i64.mul - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 1 + local.get 0 + i64.mul + ) +) diff --git a/tests/integration/expected/mul_u8.hir b/tests/integration/expected/mul_u8.hir index 10ea42fba..4236c9b8b 100644 --- a/tests/integration/expected/mul_u8.hir +++ b/tests/integration/expected/mul_u8.hir @@ -1,25 +1,23 @@ -(component - ;; Modules - (module #test_rust_ca665f22f7b5ce9ba2e09ff8053ec1ab7a69e93524c27a02273f65f21b7b3e9a - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_3332f4e22611553fee6834bdfb5edc7af11b545fb7a55941be9fef2efaecf059 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = arith.constant 255 : i32; + v3 = arith.mul v1, v0 : i32 #[overflow = wrapping]; + v5 = arith.band v3, v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (mul.wrapping v1 v0)) - (let (v4 i32) (const.i32 255)) - (let (v5 i32) (band v3 v4)) - (br (block 1 v5))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/mul_u8.masm b/tests/integration/expected/mul_u8.masm index e6dc93666..ca010a6f7 100644 --- a/tests/integration/expected/mul_u8.masm +++ b/tests/integration/expected/mul_u8.masm @@ -1,7 +1,31 @@ -# mod test_rust_ca665f22f7b5ce9ba2e09ff8053ec1ab7a69e93524c27a02273f65f21b7b3e9a +# mod root_ns:root@1.0.0 -export.entrypoint - exec.::intrinsics::i32::wrapping_mul push.255 u32and +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_3332f4e22611553fee6834bdfb5edc7af11b545fb7a55941be9fef2efaecf059 + +export.entrypoint + push.255 + movdn.2 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + u32and +end diff --git a/tests/integration/expected/mul_u8.wat b/tests/integration/expected/mul_u8.wat index 657682786..d9cfaf799 100644 --- a/tests/integration/expected/mul_u8.wat +++ b/tests/integration/expected/mul_u8.wat @@ -1,12 +1,5 @@ -(module $test_rust_ca665f22f7b5ce9ba2e09ff8053ec1ab7a69e93524c27a02273f65f21b7b3e9a.wasm +(module $test_rust_3332f4e22611553fee6834bdfb5edc7af11b545fb7a55941be9fef2efaecf059.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.mul - i32.const 255 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.mul + i32.const 255 + i32.and + ) +) diff --git a/tests/integration/expected/neg_felt.hir b/tests/integration/expected/neg_felt.hir index 3a2d32653..f29547876 100644 --- a/tests/integration/expected/neg_felt.hir +++ b/tests/integration/expected/neg_felt.hir @@ -1,21 +1,19 @@ -(component - ;; Modules - (module #neg_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @neg_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> felt { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/neg_felt/intrinsics::felt::sub(v0, v1) : felt + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::sub(v4: felt, v5: felt) -> felt { + ^block6(v4: felt, v5: felt): + v6 = arith.sub v4, v5 : felt #[overflow = unchecked]; + builtin.ret v6; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result felt) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 felt) (sub.unchecked v0 v1)) - (br (block 1 v3))) - - (block 1 (param v2 felt) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/neg_felt.masm b/tests/integration/expected/neg_felt.masm index 3a7aa7632..ea8d3b6b3 100644 --- a/tests/integration/expected/neg_felt.masm +++ b/tests/integration/expected/neg_felt.masm @@ -1,7 +1,27 @@ -# mod neg_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::neg_felt export.entrypoint - swap.1 sub + trace.240 + nop + exec.::root_ns:root@1.0.0::neg_felt::intrinsics::felt::sub + trace.252 + nop end +proc.intrinsics::felt::sub + swap.1 + sub +end diff --git a/tests/integration/expected/neg_felt.wat b/tests/integration/expected/neg_felt.wat index 63759171b..0bc5fa4ba 100644 --- a/tests/integration/expected/neg_felt.wat +++ b/tests/integration/expected/neg_felt.wat @@ -1,14 +1,16 @@ (module $neg_felt.wasm (type (;0;) (func (param f32 f32) (result f32))) - (import "miden:stdlib/intrinsics_felt" "sub" (func $miden_stdlib_sys::intrinsics::felt::extern_sub (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result f32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_sub - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result f32) + local.get 0 + local.get 1 + call $intrinsics::felt::sub + ) + (func $intrinsics::felt::sub (;1;) (type 0) (param f32 f32) (result f32) + unreachable + ) +) diff --git a/tests/integration/expected/neg_i16.hir b/tests/integration/expected/neg_i16.hir index ac10683d2..bd46ce3b9 100644 --- a/tests/integration/expected/neg_i16.hir +++ b/tests/integration/expected/neg_i16.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_ba1c5a590d707919e2a80d5ac961624d71143891517702d79d0d07e5430ec666 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_ff799fd5021c298afc16f98dda4d174c028df9b4ad9e425d365d3c1fb3cef25a { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant 0 : i32; + v3 = arith.sub v2, v0 : i32 #[overflow = wrapping]; + v4 = arith.sext v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (sub.wrapping v2 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/neg_i16.masm b/tests/integration/expected/neg_i16.masm index be7b74b7c..6f760a5e2 100644 --- a/tests/integration/expected/neg_i16.masm +++ b/tests/integration/expected/neg_i16.masm @@ -1,7 +1,26 @@ -# mod test_rust_ba1c5a590d707919e2a80d5ac961624d71143891517702d79d0d07e5430ec666 +# mod root_ns:root@1.0.0 -export.entrypoint - push.0 swap.1 u32wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_ff799fd5021c298afc16f98dda4d174c028df9b4ad9e425d365d3c1fb3cef25a + +export.entrypoint + push.0 + swap.1 + u32wrapping_sub +end diff --git a/tests/integration/expected/neg_i16.wat b/tests/integration/expected/neg_i16.wat index 12f5f714d..7dc576add 100644 --- a/tests/integration/expected/neg_i16.wat +++ b/tests/integration/expected/neg_i16.wat @@ -1,11 +1,5 @@ -(module $test_rust_ba1c5a590d707919e2a80d5ac961624d71143891517702d79d0d07e5430ec666.wasm +(module $test_rust_ff799fd5021c298afc16f98dda4d174c028df9b4ad9e425d365d3c1fb3cef25a.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - i32.const 0 - local.get 0 - i32.sub - i32.extend16_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -14,4 +8,10 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + i32.const 0 + local.get 0 + i32.sub + i32.extend16_s + ) +) diff --git a/tests/integration/expected/neg_i32.hir b/tests/integration/expected/neg_i32.hir index 7badaf767..00805aeca 100644 --- a/tests/integration/expected/neg_i32.hir +++ b/tests/integration/expected/neg_i32.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_c8278a646eb10b96eaa494478de597b1874226f5f5037ee4923d9049413f8317 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_ad225f3904404156f79b92bcde2daa108dc69c4c2e00a61030a898c2fcb6f87c { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant 0 : i32; + v3 = arith.sub v2, v0 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (sub.wrapping v2 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/neg_i32.masm b/tests/integration/expected/neg_i32.masm index 378047ff4..ad558543c 100644 --- a/tests/integration/expected/neg_i32.masm +++ b/tests/integration/expected/neg_i32.masm @@ -1,7 +1,26 @@ -# mod test_rust_c8278a646eb10b96eaa494478de597b1874226f5f5037ee4923d9049413f8317 +# mod root_ns:root@1.0.0 -export.entrypoint - push.0 swap.1 u32wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_ad225f3904404156f79b92bcde2daa108dc69c4c2e00a61030a898c2fcb6f87c + +export.entrypoint + push.0 + swap.1 + u32wrapping_sub +end diff --git a/tests/integration/expected/neg_i32.wat b/tests/integration/expected/neg_i32.wat index e4c6ab849..770699781 100644 --- a/tests/integration/expected/neg_i32.wat +++ b/tests/integration/expected/neg_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_c8278a646eb10b96eaa494478de597b1874226f5f5037ee4923d9049413f8317.wasm +(module $test_rust_ad225f3904404156f79b92bcde2daa108dc69c4c2e00a61030a898c2fcb6f87c.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - i32.const 0 - local.get 0 - i32.sub - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + i32.const 0 + local.get 0 + i32.sub + ) +) diff --git a/tests/integration/expected/neg_i64.hir b/tests/integration/expected/neg_i64.hir index 73b59d586..b9a239ca8 100644 --- a/tests/integration/expected/neg_i64.hir +++ b/tests/integration/expected/neg_i64.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_01dcb6fbaad510deb917ef5e283ccfaa280559139455d99f8b7c76af466af9dd - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_7e6ecbf37c062a73130e37145b77eb9dd3cce8aa16128ac9fcca7dc837ca562c { + public builtin.function @entrypoint(v0: i64) -> i64 { + ^block6(v0: i64): + v2 = arith.constant 0 : i64; + v3 = arith.sub v2, v0 : i64 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (result i64) - (block 0 (param v0 i64) - (let (v2 i64) (const.i64 0)) - (let (v3 i64) (sub.wrapping v2 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i64) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/neg_i64.masm b/tests/integration/expected/neg_i64.masm index fdb3f2c15..a28f394b0 100644 --- a/tests/integration/expected/neg_i64.masm +++ b/tests/integration/expected/neg_i64.masm @@ -1,7 +1,32 @@ -# mod test_rust_01dcb6fbaad510deb917ef5e283ccfaa280559139455d99f8b7c76af466af9dd +# mod root_ns:root@1.0.0 -export.entrypoint - push.0.0 movdn.3 movdn.3 exec.::std::math::u64::wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_7e6ecbf37c062a73130e37145b77eb9dd3cce8aa16128ac9fcca7dc837ca562c + +export.entrypoint + push.0 + push.0 + movdn.3 + movdn.3 + trace.240 + nop + exec.::std::math::u64::wrapping_sub + trace.252 + nop +end diff --git a/tests/integration/expected/neg_i64.wat b/tests/integration/expected/neg_i64.wat index 03e8c9173..bde40bfe9 100644 --- a/tests/integration/expected/neg_i64.wat +++ b/tests/integration/expected/neg_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_01dcb6fbaad510deb917ef5e283ccfaa280559139455d99f8b7c76af466af9dd.wasm +(module $test_rust_7e6ecbf37c062a73130e37145b77eb9dd3cce8aa16128ac9fcca7dc837ca562c.wasm (type (;0;) (func (param i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64) (result i64) - i64.const 0 - local.get 0 - i64.sub - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64) (result i64) + i64.const 0 + local.get 0 + i64.sub + ) +) diff --git a/tests/integration/expected/neg_i8.hir b/tests/integration/expected/neg_i8.hir index 69d5ff099..1eca2fdab 100644 --- a/tests/integration/expected/neg_i8.hir +++ b/tests/integration/expected/neg_i8.hir @@ -1,24 +1,23 @@ -(component - ;; Modules - (module #test_rust_c284c5e58a865bc45e93bf2024b2c204ef39187b90a2cf9cc04c023ea2f4fe6d - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_7564589530886ab89f7963543a4d7e1529e7436aaa330e5e742ddde3149deefe { + public builtin.function @entrypoint(v0: i32) -> i32 { + ^block6(v0: i32): + v2 = arith.constant 0 : i32; + v3 = arith.sub v2, v0 : i32 #[overflow = wrapping]; + v4 = arith.sext v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (sub.wrapping v2 v0)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v1 i32) - (ret v1)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/neg_i8.masm b/tests/integration/expected/neg_i8.masm index 46209a74b..906193217 100644 --- a/tests/integration/expected/neg_i8.masm +++ b/tests/integration/expected/neg_i8.masm @@ -1,7 +1,26 @@ -# mod test_rust_c284c5e58a865bc45e93bf2024b2c204ef39187b90a2cf9cc04c023ea2f4fe6d +# mod root_ns:root@1.0.0 -export.entrypoint - push.0 swap.1 u32wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_7564589530886ab89f7963543a4d7e1529e7436aaa330e5e742ddde3149deefe + +export.entrypoint + push.0 + swap.1 + u32wrapping_sub +end diff --git a/tests/integration/expected/neg_i8.wat b/tests/integration/expected/neg_i8.wat index 38fbf8dba..ac014ef88 100644 --- a/tests/integration/expected/neg_i8.wat +++ b/tests/integration/expected/neg_i8.wat @@ -1,11 +1,5 @@ -(module $test_rust_c284c5e58a865bc45e93bf2024b2c204ef39187b90a2cf9cc04c023ea2f4fe6d.wasm +(module $test_rust_7564589530886ab89f7963543a4d7e1529e7436aaa330e5e742ddde3149deefe.wasm (type (;0;) (func (param i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32) (result i32) - i32.const 0 - local.get 0 - i32.sub - i32.extend8_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -14,4 +8,10 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32) (result i32) + i32.const 0 + local.get 0 + i32.sub + i32.extend8_s + ) +) diff --git a/tests/integration/expected/or_bool.hir b/tests/integration/expected/or_bool.hir index 680584860..7d5bbc643 100644 --- a/tests/integration/expected/or_bool.hir +++ b/tests/integration/expected/or_bool.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_fcd39ca2fcbe5490a38db6dfe57f9ead120d9b7ced8d4170a040c64bd03b0413 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_f1d36010a1862f085dfb5f246228723fc87e291a764ca76107b03554afd7db26 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bor v0, v1 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bor v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/or_bool.masm b/tests/integration/expected/or_bool.masm index 65970c3b2..e42849ab6 100644 --- a/tests/integration/expected/or_bool.masm +++ b/tests/integration/expected/or_bool.masm @@ -1,7 +1,24 @@ -# mod test_rust_fcd39ca2fcbe5490a38db6dfe57f9ead120d9b7ced8d4170a040c64bd03b0413 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32or +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_f1d36010a1862f085dfb5f246228723fc87e291a764ca76107b03554afd7db26 + +export.entrypoint + u32or +end diff --git a/tests/integration/expected/or_bool.wat b/tests/integration/expected/or_bool.wat index 3f519e2af..3851144f9 100644 --- a/tests/integration/expected/or_bool.wat +++ b/tests/integration/expected/or_bool.wat @@ -1,10 +1,5 @@ -(module $test_rust_fcd39ca2fcbe5490a38db6dfe57f9ead120d9b7ced8d4170a040c64bd03b0413.wasm +(module $test_rust_f1d36010a1862f085dfb5f246228723fc87e291a764ca76107b03554afd7db26.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.or - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.or + ) +) diff --git a/tests/integration/expected/rust_sdk/component_macros_account.hir b/tests/integration/expected/rust_sdk/component_macros_account.hir new file mode 100644 index 000000000..f6847e040 --- /dev/null +++ b/tests/integration/expected/rust_sdk/component_macros_account.hir @@ -0,0 +1,143 @@ +builtin.component miden:component-macros-account/component-macros-account@0.1.0 { + builtin.module public @component_macros_account { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @component_macros_account::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:component-macros-account/component-macros-account@0.1.0#test-custom-types(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt) -> i32 { + ^block9(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt): + v14 = builtin.global_symbol @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/GOT.data.internal.__memory_base : ptr + v15 = hir.bitcast v14 : ptr; + v16 = hir.load v15 : i32; + hir.exec @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/wit_bindgen::rt::run_ctors_once() + v17 = arith.constant 1048584 : i32; + v18 = arith.add v16, v17 : i32 #[overflow = wrapping]; + v20 = arith.constant 4 : u32; + v19 = hir.bitcast v18 : u32; + v21 = arith.add v19, v20 : u32 #[overflow = checked]; + v136 = arith.constant 4 : u32; + v23 = arith.mod v21, v136 : u32; + hir.assertz v23 #[code = 250]; + v24 = hir.int_to_ptr v21 : ptr; + hir.store v24, v8; + v25 = hir.bitcast v18 : u32; + v135 = arith.constant 4 : u32; + v27 = arith.mod v25, v135 : u32; + hir.assertz v27 #[code = 250]; + v28 = hir.int_to_ptr v25 : ptr; + hir.store v28, v0; + builtin.ret v18; + }; + + private builtin.function @miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2(v29: felt, v30: felt, v31: felt, v32: felt, v33: felt, v34: felt, v35: felt, v36: felt, v37: felt, v38: felt, v39: felt, v40: felt) -> i32 { + ^block11(v29: felt, v30: felt, v31: felt, v32: felt, v33: felt, v34: felt, v35: felt, v36: felt, v37: felt, v38: felt, v39: felt, v40: felt): + v43 = builtin.global_symbol @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/GOT.data.internal.__memory_base : ptr + v44 = hir.bitcast v43 : ptr; + v45 = hir.load v44 : i32; + hir.exec @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/wit_bindgen::rt::run_ctors_once() + v46 = arith.constant 1048584 : i32; + v47 = arith.add v45, v46 : i32 #[overflow = wrapping]; + v49 = arith.constant 4 : u32; + v48 = hir.bitcast v47 : u32; + v50 = arith.add v48, v49 : u32 #[overflow = checked]; + v138 = arith.constant 4 : u32; + v52 = arith.mod v50, v138 : u32; + hir.assertz v52 #[code = 250]; + v53 = hir.int_to_ptr v50 : ptr; + hir.store v53, v30; + v54 = hir.bitcast v47 : u32; + v137 = arith.constant 4 : u32; + v56 = arith.mod v54, v137 : u32; + hir.assertz v56 #[code = 250]; + v57 = hir.int_to_ptr v54 : ptr; + hir.store v57, v29; + builtin.ret v47; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block13: + v59 = builtin.global_symbol @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/GOT.data.internal.__memory_base : ptr + v60 = hir.bitcast v59 : ptr; + v61 = hir.load v60 : i32; + v62 = arith.constant 1048592 : i32; + v63 = arith.add v61, v62 : i32 #[overflow = wrapping]; + v64 = hir.bitcast v63 : u32; + v65 = hir.int_to_ptr v64 : ptr; + v66 = hir.load v65 : u8; + v58 = arith.constant 0 : i32; + v67 = arith.zext v66 : u32; + v68 = hir.bitcast v67 : i32; + v70 = arith.neq v68, v58 : i1; + scf.if v70{ + ^block15: + scf.yield ; + } else { + ^block16: + v71 = builtin.global_symbol @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/GOT.data.internal.__memory_base : ptr + v72 = hir.bitcast v71 : ptr; + v73 = hir.load v72 : i32; + hir.exec @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/__wasm_call_ctors() + v140 = arith.constant 1 : u8; + v142 = arith.constant 1048592 : i32; + v75 = arith.add v73, v142 : i32 #[overflow = wrapping]; + v79 = hir.bitcast v75 : u32; + v80 = hir.int_to_ptr v79 : ptr; + hir.store v80, v140; + scf.yield ; + }; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @test-custom-types(v81: felt, v82: felt, v83: felt, v84: felt, v85: felt, v86: felt, v87: felt, v88: felt, v89: felt, v90: felt, v91: felt, v92: felt) -> felt, felt { + ^block17(v81: felt, v82: felt, v83: felt, v84: felt, v85: felt, v86: felt, v87: felt, v88: felt, v89: felt, v90: felt, v91: felt, v92: felt): + v93 = hir.exec @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/miden:component-macros-account/component-macros-account@0.1.0#test-custom-types(v81, v82, v83, v84, v85, v86, v87, v88, v89, v90, v91, v92) : i32 + v144 = arith.constant 0 : i32; + v94 = arith.constant 0 : i32; + v95 = arith.add v93, v94 : i32 #[overflow = unchecked]; + v97 = arith.add v95, v144 : i32 #[overflow = unchecked]; + v98 = hir.int_to_ptr v97 : ptr; + v99 = hir.load v98 : felt; + v143 = arith.constant 0 : i32; + v100 = arith.constant 4 : i32; + v101 = arith.add v93, v100 : i32 #[overflow = unchecked]; + v103 = arith.add v101, v143 : i32 #[overflow = unchecked]; + v104 = hir.int_to_ptr v103 : ptr; + v105 = hir.load v104 : felt; + builtin.ret v99, v105; + }; + + public builtin.function @test-custom-types2(v108: felt, v109: felt, v110: felt, v111: felt, v112: felt, v113: felt, v114: felt, v115: felt, v116: felt, v117: felt, v118: felt, v119: felt) -> felt, felt { + ^block19(v108: felt, v109: felt, v110: felt, v111: felt, v112: felt, v113: felt, v114: felt, v115: felt, v116: felt, v117: felt, v118: felt, v119: felt): + v120 = hir.exec @miden:component-macros-account/component-macros-account@0.1.0/component_macros_account/miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2(v108, v109, v110, v111, v112, v113, v114, v115, v116, v117, v118, v119) : i32 + v146 = arith.constant 0 : i32; + v121 = arith.constant 0 : i32; + v122 = arith.add v120, v121 : i32 #[overflow = unchecked]; + v124 = arith.add v122, v146 : i32 #[overflow = unchecked]; + v125 = hir.int_to_ptr v124 : ptr; + v126 = hir.load v125 : felt; + v145 = arith.constant 0 : i32; + v127 = arith.constant 4 : i32; + v128 = arith.add v120, v127 : i32 #[overflow = unchecked]; + v130 = arith.add v128, v145 : i32 #[overflow = unchecked]; + v131 = hir.int_to_ptr v130 : ptr; + v132 = hir.load v131 : felt; + builtin.ret v126, v132; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/component_macros_account.masm b/tests/integration/expected/rust_sdk/component_macros_account.masm new file mode 100644 index 000000000..f530e9aae --- /dev/null +++ b/tests/integration/expected/rust_sdk/component_macros_account.masm @@ -0,0 +1,320 @@ +# mod miden:component-macros-account/component-macros-account@0.1.0 + +export.test-custom-types + exec.::miden:component-macros-account/component-macros-account@0.1.0::init + trace.240 + nop + exec.::miden:component-macros-account/component-macros-account@0.1.0::component_macros_account::miden:component-macros-account/component-macros-account@0.1.0#test-custom-types + trace.252 + nop + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + exec.::std::sys::truncate_stack +end + +export.test-custom-types2 + exec.::miden:component-macros-account/component-macros-account@0.1.0::init + trace.240 + nop + exec.::miden:component-macros-account/component-macros-account@0.1.0::component_macros_account::miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2 + trace.252 + nop + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:component-macros-account/component-macros-account@0.1.0::component_macros_account + +proc.__wasm_call_ctors + nop +end + +proc.component_macros_account::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:component-macros-account/component-macros-account@0.1.0#test-custom-types + swap.1 + drop + swap.1 + drop + swap.1 + drop + swap.1 + drop + swap.1 + drop + swap.1 + drop + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:component-macros-account/component-macros-account@0.1.0::component_macros_account::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.1048584 + u32wrapping_add + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2 + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:component-macros-account/component-macros-account@0.1.0::component_macros_account::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.1048584 + u32wrapping_add + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048592 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:component-macros-account/component-macros-account@0.1.0::component_macros_account::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048592 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + diff --git a/tests/integration/expected/rust_sdk/component_macros_account.wat b/tests/integration/expected/rust_sdk/component_macros_account.wat new file mode 100644 index 000000000..647ca1304 --- /dev/null +++ b/tests/integration/expected/rust_sdk/component_macros_account.wat @@ -0,0 +1,159 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + (type (;5;) (record (field "inner" 4))) + (export (;6;) "asset" (type (eq 5))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:component-macros-account/component-macros-account@0.1.0#test-custom-types" (func $miden:component-macros-account/component-macros-account@0.1.0#test-custom-types)) + (export "miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2" (func $miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2)) + (elem (;0;) (i32.const 1) func $component_macros_account::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $component_macros_account::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:component-macros-account/component-macros-account@0.1.0#test-custom-types (;2;) (type 1) (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 12 + call $wit_bindgen::rt::run_ctors_once + local.get 12 + i32.const 1048584 + i32.add + local.tee 12 + local.get 8 + f32.store offset=4 + local.get 12 + local.get 0 + f32.store + local.get 12 + ) + (func $miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2 (;3;) (type 1) (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 12 + call $wit_bindgen::rt::run_ctors_once + local.get 12 + i32.const 1048584 + i32.add + local.tee 12 + local.get 1 + f32.store offset=4 + local.get 12 + local.get 0 + f32.store + local.get 12 + ) + (func $wit_bindgen::rt::run_ctors_once (;4;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048592 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048592 + i32.add + i32.const 1 + i32.store8 + end + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + (@custom "rodata,miden_account" (after data) "1component_macros_account\01\0b0.1.0\03\01\01\00\00\00\00\00\00\00\00\00\00\00\00\00") + ) + (alias export 0 "asset" (type (;1;))) + (alias export 0 "felt" (type (;2;))) + (alias export 0 "word" (type (;3;))) + (type (;4;) (variant (case "variant-a") (case "variant-b"))) + (type (;5;) (record (field "value" 2))) + (type (;6;) (record (field "nested" 5))) + (type (;7;) (record (field "foo" 3) (field "asset" 1))) + (type (;8;) (record (field "bar" 2) (field "baz" 2))) + (type (;9;) (record (field "inner1" 2) (field "inner2" 2))) + (type (;10;) (record (field "bar" 2) (field "baz" 2))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;11;) (func (param "a" 7) (param "asset" 1) (result 8))) + (alias core export 0 "miden:component-macros-account/component-macros-account@0.1.0#test-custom-types" (core func (;0;))) + (func (;0;) (type 11) (canon lift (core func 0) (memory 0))) + (type (;12;) (func (param "a" 7) (param "asset" 1) (result 9))) + (alias core export 0 "miden:component-macros-account/component-macros-account@0.1.0#test-custom-types2" (core func (;1;))) + (func (;1;) (type 12) (canon lift (core func 1) (memory 0))) + (alias export 0 "felt" (type (;13;))) + (alias export 0 "word" (type (;14;))) + (alias export 0 "asset" (type (;15;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (type (;5;) (record (field "inner" 4))) + (import "import-type-asset" (type (;6;) (eq 5))) + (import "import-type-word0" (type (;7;) (eq 4))) + (import "import-type-asset0" (type (;8;) (eq 6))) + (type (;9;) (record (field "foo" 7) (field "asset" 8))) + (import "import-type-struct-a" (type (;10;) (eq 9))) + (import "import-type-felt0" (type (;11;) (eq 1))) + (type (;12;) (record (field "bar" 11) (field "baz" 11))) + (import "import-type-struct-b" (type (;13;) (eq 12))) + (type (;14;) (func (param "a" 10) (param "asset" 8) (result 13))) + (import "import-func-test-custom-types" (func (;0;) (type 14))) + (type (;15;) (record (field "inner1" 11) (field "inner2" 11))) + (import "import-type-struct-c" (type (;16;) (eq 15))) + (type (;17;) (func (param "a" 10) (param "asset" 8) (result 16))) + (import "import-func-test-custom-types2" (func (;1;) (type 17))) + (export (;18;) "asset" (type 6)) + (export (;19;) "felt" (type 1)) + (export (;20;) "word" (type 4)) + (type (;21;) (variant (case "variant-a") (case "variant-b"))) + (export (;22;) "enum-a" (type 21)) + (type (;23;) (record (field "value" 19))) + (export (;24;) "later-defined" (type 23)) + (type (;25;) (record (field "nested" 24))) + (export (;26;) "forward-holder" (type 25)) + (type (;27;) (record (field "foo" 20) (field "asset" 18))) + (export (;28;) "struct-a" (type 27)) + (type (;29;) (record (field "bar" 19) (field "baz" 19))) + (export (;30;) "struct-b" (type 29)) + (type (;31;) (record (field "inner1" 19) (field "inner2" 19))) + (export (;32;) "struct-c" (type 31)) + (type (;33;) (record (field "bar" 19) (field "baz" 19))) + (export (;34;) "struct-d" (type 33)) + (type (;35;) (func (param "a" 28) (param "asset" 18) (result 30))) + (export (;2;) "test-custom-types" (func 0) (func (type 35))) + (type (;36;) (func (param "a" 28) (param "asset" 18) (result 32))) + (export (;3;) "test-custom-types2" (func 1) (func (type 36))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-test-custom-types" (func 0)) + (with "import-func-test-custom-types2" (func 1)) + (with "import-type-felt" (type 13)) + (with "import-type-word" (type 14)) + (with "import-type-asset" (type 15)) + (with "import-type-word0" (type 3)) + (with "import-type-asset0" (type 1)) + (with "import-type-struct-a" (type 7)) + (with "import-type-felt0" (type 2)) + (with "import-type-struct-b" (type 8)) + (with "import-type-struct-c" (type 9)) + ) + ) + (export (;2;) "miden:component-macros-account/component-macros-account@0.1.0" (instance 1)) +) diff --git a/tests/integration/expected/rust_sdk/component_macros_note.hir b/tests/integration/expected/rust_sdk/component_macros_note.hir new file mode 100644 index 000000000..2963c370d --- /dev/null +++ b/tests/integration/expected/rust_sdk/component_macros_note.hir @@ -0,0 +1,195 @@ +builtin.component miden:base/note-script@1.0.0 { + builtin.module public @component_macros_note { + private builtin.function @component_macros_note::bindings::miden::component_macros_account::component_macros_account::test_custom_types::wit_import22(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt, v12: i32) { + ^block3(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt, v12: i32): + v13, v14 = hir.call v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11 : felt, felt #[callee = miden:component-macros-account/component-macros-account@0.1.0/test-custom-types] #[signature = (cc canon-lower) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (result felt felt)]; + v131 = arith.constant 0 : i32; + v15 = arith.constant 0 : i32; + v16 = arith.add v12, v15 : i32 #[overflow = unchecked]; + v18 = arith.add v16, v131 : i32 #[overflow = unchecked]; + v19 = hir.int_to_ptr v18 : ptr; + hir.store v19, v13; + v130 = arith.constant 0 : i32; + v20 = arith.constant 4 : i32; + v21 = arith.add v12, v20 : i32 #[overflow = unchecked]; + v23 = arith.add v21, v130 : i32 #[overflow = unchecked]; + v24 = hir.int_to_ptr v23 : ptr; + hir.store v24, v14; + builtin.ret ; + }; + + private builtin.function @__wasm_call_ctors() { + ^block8: + builtin.ret ; + }; + + private builtin.function @component_macros_note::bindings::__link_custom_section_describing_imports() { + ^block10: + builtin.ret ; + }; + + private builtin.function @miden:base/note-script@1.0.0#run(v25: felt, v26: felt, v27: felt, v28: felt) { + ^block12(v25: felt, v26: felt, v27: felt, v28: felt): + v31 = builtin.global_symbol @miden:base/note-script@1.0.0/component_macros_note/__stack_pointer : ptr + v32 = hir.bitcast v31 : ptr; + v33 = hir.load v32 : i32; + v34 = arith.constant 16 : i32; + v35 = arith.sub v33, v34 : i32 #[overflow = wrapping]; + v36 = builtin.global_symbol @miden:base/note-script@1.0.0/component_macros_note/__stack_pointer : ptr + v37 = hir.bitcast v36 : ptr; + hir.store v37, v35; + hir.exec @miden:base/note-script@1.0.0/component_macros_note/wit_bindgen::rt::run_ctors_once() + v38 = arith.constant 11 : i32; + v39 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v38) : felt + v40 = arith.constant 22 : i32; + v41 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v40) : felt + v42 = arith.constant 33 : i32; + v43 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v42) : felt + v44 = arith.constant 44 : i32; + v45 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v44) : felt + v46 = arith.constant 99 : i32; + v47 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v46) : felt + v48 = arith.constant 88 : i32; + v49 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v48) : felt + v50 = arith.constant 77 : i32; + v51 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v50) : felt + v52 = arith.constant 66 : i32; + v53 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::from_u32(v52) : felt + v56 = arith.constant 8 : u32; + v55 = hir.bitcast v35 : u32; + v57 = arith.add v55, v56 : u32 #[overflow = checked]; + v148 = arith.constant 8 : u32; + v59 = arith.mod v57, v148 : u32; + hir.assertz v59 #[code = 250]; + v54 = arith.constant 0 : i64; + v60 = hir.int_to_ptr v57 : ptr; + hir.store v60, v54; + v61 = arith.constant 8 : i32; + v62 = arith.add v35, v61 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/component_macros_note/component_macros_note::bindings::miden::component_macros_account::component_macros_account::test_custom_types::wit_import22(v39, v41, v43, v45, v47, v49, v51, v53, v47, v49, v51, v53, v62) + v64 = arith.constant 12 : u32; + v63 = hir.bitcast v35 : u32; + v65 = arith.add v63, v64 : u32 #[overflow = checked]; + v66 = arith.constant 4 : u32; + v67 = arith.mod v65, v66 : u32; + hir.assertz v67 #[code = 250]; + v68 = hir.int_to_ptr v65 : ptr; + v69 = hir.load v68 : felt; + v147 = arith.constant 8 : u32; + v70 = hir.bitcast v35 : u32; + v72 = arith.add v70, v147 : u32 #[overflow = checked]; + v146 = arith.constant 4 : u32; + v74 = arith.mod v72, v146 : u32; + hir.assertz v74 #[code = 250]; + v75 = hir.int_to_ptr v72 : ptr; + v76 = hir.load v75 : felt; + v77 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::eq(v76, v39) : i32 + v29 = arith.constant 0 : i32; + v78 = arith.constant 1 : i32; + v79 = arith.neq v77, v78 : i1; + v80 = arith.zext v79 : u32; + v81 = hir.bitcast v80 : i32; + v83 = arith.neq v81, v29 : i1; + v136 = scf.if v83 : u32 { + ^block28: + v132 = arith.constant 0 : u32; + scf.yield v132; + } else { + ^block15: + v84 = hir.exec @miden:base/note-script@1.0.0/component_macros_note/intrinsics::felt::eq(v69, v47) : i32 + v144 = arith.constant 0 : i32; + v145 = arith.constant 1 : i32; + v86 = arith.neq v84, v145 : i1; + v87 = arith.zext v86 : u32; + v88 = hir.bitcast v87 : i32; + v90 = arith.neq v88, v144 : i1; + scf.if v90{ + ^block27: + scf.yield ; + } else { + ^block16: + v143 = arith.constant 16 : i32; + v92 = arith.add v35, v143 : i32 #[overflow = wrapping]; + v93 = builtin.global_symbol @miden:base/note-script@1.0.0/component_macros_note/__stack_pointer : ptr + v94 = hir.bitcast v93 : ptr; + hir.store v94, v92; + scf.yield ; + }; + v134 = arith.constant 1 : u32; + v142 = arith.constant 0 : u32; + v140 = cf.select v90, v142, v134 : u32; + scf.yield v140; + }; + v141 = arith.constant 0 : u32; + v139 = arith.eq v136, v141 : i1; + cf.cond_br v139 ^block14, ^block30; + ^block14: + ub.unreachable ; + ^block30: + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block17: + v96 = builtin.global_symbol @miden:base/note-script@1.0.0/component_macros_note/GOT.data.internal.__memory_base : ptr + v97 = hir.bitcast v96 : ptr; + v98 = hir.load v97 : i32; + v99 = arith.constant 1048588 : i32; + v100 = arith.add v98, v99 : i32 #[overflow = wrapping]; + v101 = hir.bitcast v100 : u32; + v102 = hir.int_to_ptr v101 : ptr; + v103 = hir.load v102 : u8; + v95 = arith.constant 0 : i32; + v104 = arith.zext v103 : u32; + v105 = hir.bitcast v104 : i32; + v107 = arith.neq v105, v95 : i1; + scf.if v107{ + ^block19: + scf.yield ; + } else { + ^block20: + v108 = builtin.global_symbol @miden:base/note-script@1.0.0/component_macros_note/GOT.data.internal.__memory_base : ptr + v109 = hir.bitcast v108 : ptr; + v110 = hir.load v109 : i32; + hir.exec @miden:base/note-script@1.0.0/component_macros_note/__wasm_call_ctors() + v150 = arith.constant 1 : u8; + v152 = arith.constant 1048588 : i32; + v112 = arith.add v110, v152 : i32 #[overflow = wrapping]; + v116 = hir.bitcast v112 : u32; + v117 = hir.int_to_ptr v116 : ptr; + hir.store v117, v150; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u32(v118: i32) -> felt { + ^block21(v118: i32): + v119 = hir.bitcast v118 : felt; + builtin.ret v119; + }; + + private builtin.function @intrinsics::felt::eq(v121: felt, v122: felt) -> i32 { + ^block23(v121: felt, v122: felt): + v123 = arith.eq v121, v122 : i1; + v124 = hir.cast v123 : i32; + builtin.ret v124; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x000000010000000100000001; + }; + + public builtin.function @run(v126: felt, v127: felt, v128: felt, v129: felt) { + ^block25(v126: felt, v127: felt, v128: felt, v129: felt): + hir.exec @miden:base/note-script@1.0.0/component_macros_note/miden:base/note-script@1.0.0#run(v126, v127, v128, v129) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/component_macros_note.masm b/tests/integration/expected/rust_sdk/component_macros_note.masm new file mode 100644 index 000000000..6b908a765 --- /dev/null +++ b/tests/integration/expected/rust_sdk/component_macros_note.masm @@ -0,0 +1,372 @@ +# mod miden:base/note-script@1.0.0 + +export.run + exec.::miden:base/note-script@1.0.0::init + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::miden:base/note-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[511068398781876708,4034132635148770913,11946245983825022717,413851799653899214] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/note-script@1.0.0::component_macros_note + +proc.component_macros_note::bindings::miden::component_macros_account::component_macros_account::test_custom_types::wit_import22 + trace.240 + nop + call.::miden:component-macros-account/component-macros-account@0.1.0::test-custom-types + trace.252 + nop + push.0 + push.0 + dup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.4 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.__wasm_call_ctors + nop +end + +proc.component_macros_note::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/note-script@1.0.0#run + drop + drop + drop + drop + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.11 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.22 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.33 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.44 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.99 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.88 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.77 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.66 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + dup.9 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + push.0 + movup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.8 + dup.9 + u32wrapping_add + dup.8 + dup.5 + dup.6 + dup.6 + dup.6 + dup.6 + swap.1 + swap.10 + swap.13 + swap.1 + swap.11 + swap.3 + swap.8 + swap.6 + swap.12 + swap.2 + swap.9 + swap.5 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::component_macros_note::bindings::miden::component_macros_account::component_macros_account::test_custom_types::wit_import22 + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movup.3 + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + drop + push.0 + else + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + dup.0 + if.true + swap.1 + drop + else + push.16 + movup.2 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.1 + push.0 + movup.2 + cdrop + end + push.0 + eq + if.true + push.0 + assert + else + nop + end +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048588 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::component_macros_note::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048588 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::eq + eq +end + diff --git a/tests/integration/expected/rust_sdk/component_macros_note.wat b/tests/integration/expected/rust_sdk/component_macros_note.wat new file mode 100644 index 000000000..c6923add8 --- /dev/null +++ b/tests/integration/expected/rust_sdk/component_macros_note.wat @@ -0,0 +1,229 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + (type (;5;) (record (field "inner" 4))) + (export (;6;) "asset" (type (eq 5))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (alias export 0 "word" (type (;1;))) + (alias export 0 "asset" (type (;2;))) + (alias export 0 "felt" (type (;3;))) + (type (;4;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "word" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "asset" (type (eq 2))) + (type (;4;) (record (field "foo" 1) (field "asset" 3))) + (export (;5;) "struct-a" (type (eq 4))) + (alias outer 1 3 (type (;6;))) + (export (;7;) "felt" (type (eq 6))) + (type (;8;) (record (field "bar" 7) (field "baz" 7))) + (export (;9;) "struct-b" (type (eq 8))) + (type (;10;) (func (param "a" 5) (param "asset" 3) (result 9))) + (export (;0;) "test-custom-types" (func (type 10))) + ) + ) + (import "miden:component-macros-account/component-macros-account@0.1.0" (instance (;1;) (type 4))) + (core module (;0;) + (type (;0;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 i32))) + (type (;1;) (func)) + (type (;2;) (func (param f32 f32 f32 f32))) + (type (;3;) (func (param i32) (result f32))) + (type (;4;) (func (param f32 f32) (result i32))) + (import "miden:component-macros-account/component-macros-account@0.1.0" "test-custom-types" (func $component_macros_note::bindings::miden::component_macros_account::component_macros_account::test_custom_types::wit_import22 (;0;) (type 0))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/note-script@1.0.0#run" (func $miden:base/note-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $component_macros_note::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;1;) (type 1)) + (func $component_macros_note::bindings::__link_custom_section_describing_imports (;2;) (type 1)) + (func $miden:base/note-script@1.0.0#run (;3;) (type 2) (param f32 f32 f32 f32) + (local i32 f32 f32 f32 f32 f32 f32 f32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + i32.const 11 + call $intrinsics::felt::from_u32 + local.set 5 + i32.const 22 + call $intrinsics::felt::from_u32 + local.set 6 + i32.const 33 + call $intrinsics::felt::from_u32 + local.set 7 + i32.const 44 + call $intrinsics::felt::from_u32 + local.set 8 + i32.const 99 + call $intrinsics::felt::from_u32 + local.set 9 + i32.const 88 + call $intrinsics::felt::from_u32 + local.set 10 + i32.const 77 + call $intrinsics::felt::from_u32 + local.set 11 + i32.const 66 + call $intrinsics::felt::from_u32 + local.set 12 + local.get 4 + i64.const 0 + i64.store offset=8 + local.get 5 + local.get 6 + local.get 7 + local.get 8 + local.get 9 + local.get 10 + local.get 11 + local.get 12 + local.get 9 + local.get 10 + local.get 11 + local.get 12 + local.get 4 + i32.const 8 + i32.add + call $component_macros_note::bindings::miden::component_macros_account::component_macros_account::test_custom_types::wit_import22 + local.get 4 + f32.load offset=12 + local.set 10 + block ;; label = @1 + local.get 4 + f32.load offset=8 + local.get 5 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 10 + local.get 9 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 4 + i32.const 16 + i32.add + global.set $__stack_pointer + return + end + unreachable + ) + (func $wit_bindgen::rt::run_ctors_once (;4;) (type 1) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048588 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048588 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $intrinsics::felt::from_u32 (;5;) (type 3) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::eq (;6;) (type 4) (param f32 f32) (result i32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00\01\00\00\00") + ) + (core module (;1;) + (type (;0;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 i32))) + (table (;0;) 1 1 funcref) + (export "0" (func $indirect-miden:component-macros-account/component-macros-account@0.1.0-test-custom-types)) + (export "$imports" (table 0)) + (func $indirect-miden:component-macros-account/component-macros-account@0.1.0-test-custom-types (;0;) (type 0) (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + local.get 4 + local.get 5 + local.get 6 + local.get 7 + local.get 8 + local.get 9 + local.get 10 + local.get 11 + local.get 12 + i32.const 0 + call_indirect (type 0) + ) + ) + (core module (;2;) + (type (;0;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "$imports" (table (;0;) 1 1 funcref)) + (elem (;0;) (i32.const 0) func 0) + ) + (core instance (;0;) (instantiate 1)) + (alias export 0 "word" (type (;5;))) + (alias core export 0 "0" (core func (;0;))) + (core instance (;1;) + (export "test-custom-types" (func 0)) + ) + (core instance (;2;) (instantiate 0 + (with "miden:component-macros-account/component-macros-account@0.1.0" (instance 1)) + ) + ) + (alias core export 2 "memory" (core memory (;0;))) + (alias core export 0 "$imports" (core table (;0;))) + (alias export 1 "test-custom-types" (func (;0;))) + (core func (;1;) (canon lower (func 0) (memory 0))) + (core instance (;3;) + (export "$imports" (table 0)) + (export "0" (func 1)) + ) + (core instance (;4;) (instantiate 2 + (with "" (instance 3)) + ) + ) + (type (;6;) (func (param "arg" 5))) + (alias core export 2 "miden:base/note-script@1.0.0#run" (core func (;2;))) + (func (;1;) (type 6) (canon lift (core func 2))) + (alias export 0 "felt" (type (;7;))) + (alias export 0 "word" (type (;8;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;2;) (instantiate 0 + (with "import-func-run" (func 1)) + (with "import-type-felt" (type 7)) + (with "import-type-word" (type 8)) + (with "import-type-word0" (type 5)) + ) + ) + (export (;3;) "miden:base/note-script@1.0.0" (instance 2)) +) diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account.hir b/tests/integration/expected/rust_sdk/cross_ctx_account.hir new file mode 100644 index 000000000..5b2db79f1 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account.hir @@ -0,0 +1,109 @@ +builtin.component miden:cross-ctx-account/foo@1.0.0 { + builtin.module public @cross_ctx_account { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @cross_ctx_account::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:cross-ctx-account/foo@1.0.0#process-felt(v0: felt) -> felt { + ^block9(v0: felt): + v3 = builtin.global_symbol @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/GOT.data.internal.__memory_base : ptr + v4 = hir.bitcast v3 : ptr; + v5 = hir.load v4 : i32; + v6 = arith.constant 1048584 : i32; + v7 = arith.add v5, v6 : i32 #[overflow = wrapping]; + hir.exec @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/wit_bindgen::rt::run_ctors_once() + v8 = hir.bitcast v7 : u32; + v9 = arith.constant 4 : u32; + v10 = arith.mod v8, v9 : u32; + hir.assertz v10 #[code = 250]; + v11 = hir.int_to_ptr v8 : ptr; + v12 = hir.load v11 : i32; + v13 = hir.exec @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/intrinsics::felt::from_u32(v12) : felt + v14 = hir.exec @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/intrinsics::felt::add(v0, v13) : felt + v15 = hir.exec @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/intrinsics::felt::as_u64(v14) : i64 + v18 = hir.bitcast v7 : u32; + v57 = arith.constant 4 : u32; + v20 = arith.mod v18, v57 : u32; + hir.assertz v20 #[code = 250]; + v16 = hir.bitcast v15 : u64; + v17 = arith.trunc v16 : u32; + v21 = hir.int_to_ptr v18 : ptr; + hir.store v21, v17; + builtin.ret v14; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block11: + v23 = builtin.global_symbol @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/GOT.data.internal.__memory_base : ptr + v24 = hir.bitcast v23 : ptr; + v25 = hir.load v24 : i32; + v26 = arith.constant 1048588 : i32; + v27 = arith.add v25, v26 : i32 #[overflow = wrapping]; + v28 = hir.bitcast v27 : u32; + v29 = hir.int_to_ptr v28 : ptr; + v30 = hir.load v29 : u8; + v22 = arith.constant 0 : i32; + v31 = arith.zext v30 : u32; + v32 = hir.bitcast v31 : i32; + v34 = arith.neq v32, v22 : i1; + scf.if v34{ + ^block13: + scf.yield ; + } else { + ^block14: + v35 = builtin.global_symbol @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/GOT.data.internal.__memory_base : ptr + v36 = hir.bitcast v35 : ptr; + v37 = hir.load v36 : i32; + hir.exec @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/__wasm_call_ctors() + v59 = arith.constant 1 : u8; + v61 = arith.constant 1048588 : i32; + v39 = arith.add v37, v61 : i32 #[overflow = wrapping]; + v43 = hir.bitcast v39 : u32; + v44 = hir.int_to_ptr v43 : ptr; + hir.store v44, v59; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::add(v45: felt, v46: felt) -> felt { + ^block15(v45: felt, v46: felt): + v47 = arith.add v45, v46 : felt #[overflow = unchecked]; + builtin.ret v47; + }; + + private builtin.function @intrinsics::felt::from_u32(v49: i32) -> felt { + ^block17(v49: i32): + v50 = hir.bitcast v49 : felt; + builtin.ret v50; + }; + + private builtin.function @intrinsics::felt::as_u64(v52: felt) -> i64 { + ^block19(v52: felt): + v53 = hir.cast v52 : i64; + builtin.ret v53; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000002a0000000100000001; + }; + + public builtin.function @process-felt(v55: felt) -> felt { + ^block21(v55: felt): + v56 = hir.exec @miden:cross-ctx-account/foo@1.0.0/cross_ctx_account/miden:cross-ctx-account/foo@1.0.0#process-felt(v55) : felt + builtin.ret v56; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account.masm b/tests/integration/expected/rust_sdk/cross_ctx_account.masm new file mode 100644 index 000000000..e63e3a05c --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account.masm @@ -0,0 +1,192 @@ +# mod miden:cross-ctx-account/foo@1.0.0 + +export.process-felt + exec.::miden:cross-ctx-account/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account/foo@1.0.0::cross_ctx_account::miden:cross-ctx-account/foo@1.0.0#process-felt + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[17087610586833254709,13035385099392546601,11872459138297551738,11525982497240551389] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:cross-ctx-account/foo@1.0.0::cross_ctx_account + +proc.__wasm_call_ctors + nop +end + +proc.cross_ctx_account::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:cross-ctx-account/foo@1.0.0#process-felt + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + trace.240 + nop + exec.::miden:cross-ctx-account/foo@1.0.0::cross_ctx_account::wit_bindgen::rt::run_ctors_once + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account/foo@1.0.0::cross_ctx_account::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account/foo@1.0.0::cross_ctx_account::intrinsics::felt::add + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:cross-ctx-account/foo@1.0.0::cross_ctx_account::intrinsics::felt::as_u64 + trace.252 + nop + movup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movdn.2 + drop + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048588 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account/foo@1.0.0::cross_ctx_account::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048588 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.intrinsics::felt::add + add +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::as_u64 + u32split +end + diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account.wat b/tests/integration/expected/rust_sdk/cross_ctx_account.wat new file mode 100644 index 000000000..89af0be24 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account.wat @@ -0,0 +1,95 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32) (result f32))) + (type (;2;) (func (param f32 f32) (result f32))) + (type (;3;) (func (param i32) (result f32))) + (type (;4;) (func (param f32) (result i64))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:cross-ctx-account/foo@1.0.0#process-felt" (func $miden:cross-ctx-account/foo@1.0.0#process-felt)) + (elem (;0;) (i32.const 1) func $cross_ctx_account::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $cross_ctx_account::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:cross-ctx-account/foo@1.0.0#process-felt (;2;) (type 1) (param f32) (result f32) + (local i32) + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + local.set 1 + call $wit_bindgen::rt::run_ctors_once + local.get 1 + local.get 0 + local.get 1 + i32.load + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.tee 0 + call $intrinsics::felt::as_u64 + i64.store32 + local.get 0 + ) + (func $wit_bindgen::rt::run_ctors_once (;3;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048588 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048588 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $intrinsics::felt::add (;4;) (type 2) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;5;) (type 3) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::as_u64 (;6;) (type 4) (param f32) (result i64) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00*\00\00\00") + ) + (alias export 0 "felt" (type (;1;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;2;) (func (param "input" 1) (result 1))) + (alias core export 0 "miden:cross-ctx-account/foo@1.0.0#process-felt" (core func (;0;))) + (func (;0;) (type 2) (canon lift (core func 0))) + (alias export 0 "felt" (type (;3;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (import "import-type-felt0" (type (;2;) (eq 1))) + (type (;3;) (func (param "input" 2) (result 2))) + (import "import-func-process-felt" (func (;0;) (type 3))) + (export (;4;) "felt" (type 1)) + (type (;5;) (func (param "input" 4) (result 4))) + (export (;1;) "process-felt" (func 0) (func (type 5))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-process-felt" (func 0)) + (with "import-type-felt" (type 3)) + (with "import-type-felt0" (type 1)) + ) + ) + (export (;2;) "miden:cross-ctx-account/foo@1.0.0" (instance 1)) +) diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account_word.hir b/tests/integration/expected/rust_sdk/cross_ctx_account_word.hir new file mode 100644 index 000000000..d16d78076 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account_word.hir @@ -0,0 +1,560 @@ +builtin.component miden:cross-ctx-account-word/foo@1.0.0 { + builtin.module public @cross_ctx_account_word { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @cross_ctx_account_word::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:cross-ctx-account-word/foo@1.0.0#process-word(v0: felt, v1: felt, v2: felt, v3: felt) -> i32 { + ^block9(v0: felt, v1: felt, v2: felt, v3: felt): + v6 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v7 = hir.bitcast v6 : ptr; + v8 = hir.load v7 : i32; + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/wit_bindgen::rt::run_ctors_once() + v9 = arith.constant 1 : i32; + v10 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v9) : felt + v11 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v0, v10) : felt + v12 = arith.constant 2 : i32; + v13 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v12) : felt + v14 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v1, v13) : felt + v15 = arith.constant 3 : i32; + v16 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v15) : felt + v17 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v2, v16) : felt + v18 = arith.constant 1048584 : i32; + v19 = arith.add v8, v18 : i32 #[overflow = wrapping]; + v20 = arith.constant 4 : i32; + v21 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v20) : felt + v22 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v3, v21) : felt + v24 = arith.constant 12 : u32; + v23 = hir.bitcast v19 : u32; + v25 = arith.add v23, v24 : u32 #[overflow = checked]; + v26 = arith.constant 4 : u32; + v27 = arith.mod v25, v26 : u32; + hir.assertz v27 #[code = 250]; + v28 = hir.int_to_ptr v25 : ptr; + hir.store v28, v22; + v30 = arith.constant 8 : u32; + v29 = hir.bitcast v19 : u32; + v31 = arith.add v29, v30 : u32 #[overflow = checked]; + v487 = arith.constant 4 : u32; + v33 = arith.mod v31, v487 : u32; + hir.assertz v33 #[code = 250]; + v34 = hir.int_to_ptr v31 : ptr; + hir.store v34, v17; + v486 = arith.constant 4 : u32; + v35 = hir.bitcast v19 : u32; + v37 = arith.add v35, v486 : u32 #[overflow = checked]; + v485 = arith.constant 4 : u32; + v39 = arith.mod v37, v485 : u32; + hir.assertz v39 #[code = 250]; + v40 = hir.int_to_ptr v37 : ptr; + hir.store v40, v14; + v41 = hir.bitcast v19 : u32; + v484 = arith.constant 4 : u32; + v43 = arith.mod v41, v484 : u32; + hir.assertz v43 #[code = 250]; + v44 = hir.int_to_ptr v41 : ptr; + hir.store v44, v11; + builtin.ret v19; + }; + + private builtin.function @miden:cross-ctx-account-word/foo@1.0.0#process-another-word(v45: felt, v46: felt, v47: felt, v48: felt) -> i32 { + ^block11(v45: felt, v46: felt, v47: felt, v48: felt): + v51 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v52 = hir.bitcast v51 : ptr; + v53 = hir.load v52 : i32; + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/wit_bindgen::rt::run_ctors_once() + v54 = arith.constant 2 : i32; + v55 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v54) : felt + v56 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v45, v55) : felt + v57 = arith.constant 3 : i32; + v58 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v57) : felt + v59 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v46, v58) : felt + v60 = arith.constant 4 : i32; + v61 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v60) : felt + v62 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v47, v61) : felt + v63 = arith.constant 1048584 : i32; + v64 = arith.add v53, v63 : i32 #[overflow = wrapping]; + v65 = arith.constant 5 : i32; + v66 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v65) : felt + v67 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v48, v66) : felt + v69 = arith.constant 12 : u32; + v68 = hir.bitcast v64 : u32; + v70 = arith.add v68, v69 : u32 #[overflow = checked]; + v71 = arith.constant 4 : u32; + v72 = arith.mod v70, v71 : u32; + hir.assertz v72 #[code = 250]; + v73 = hir.int_to_ptr v70 : ptr; + hir.store v73, v67; + v75 = arith.constant 8 : u32; + v74 = hir.bitcast v64 : u32; + v76 = arith.add v74, v75 : u32 #[overflow = checked]; + v491 = arith.constant 4 : u32; + v78 = arith.mod v76, v491 : u32; + hir.assertz v78 #[code = 250]; + v79 = hir.int_to_ptr v76 : ptr; + hir.store v79, v62; + v490 = arith.constant 4 : u32; + v80 = hir.bitcast v64 : u32; + v82 = arith.add v80, v490 : u32 #[overflow = checked]; + v489 = arith.constant 4 : u32; + v84 = arith.mod v82, v489 : u32; + hir.assertz v84 #[code = 250]; + v85 = hir.int_to_ptr v82 : ptr; + hir.store v85, v59; + v86 = hir.bitcast v64 : u32; + v488 = arith.constant 4 : u32; + v88 = arith.mod v86, v488 : u32; + hir.assertz v88 #[code = 250]; + v89 = hir.int_to_ptr v86 : ptr; + hir.store v89, v56; + builtin.ret v64; + }; + + private builtin.function @miden:cross-ctx-account-word/foo@1.0.0#process-felt(v90: felt) -> felt { + ^block13(v90: felt): + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/wit_bindgen::rt::run_ctors_once() + v92 = arith.constant 3 : i32; + v93 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v92) : felt + v94 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v90, v93) : felt + builtin.ret v94; + }; + + private builtin.function @miden:cross-ctx-account-word/foo@1.0.0#process-pair(v95: felt, v96: felt) -> i32 { + ^block15(v95: felt, v96: felt): + v99 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v100 = hir.bitcast v99 : ptr; + v101 = hir.load v100 : i32; + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/wit_bindgen::rt::run_ctors_once() + v102 = arith.constant 4 : i32; + v103 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v102) : felt + v104 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v95, v103) : felt + v105 = arith.constant 1048584 : i32; + v106 = arith.add v101, v105 : i32 #[overflow = wrapping]; + v494 = arith.constant 4 : i32; + v108 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v494) : felt + v109 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v96, v108) : felt + v111 = arith.constant 4 : u32; + v110 = hir.bitcast v106 : u32; + v112 = arith.add v110, v111 : u32 #[overflow = checked]; + v493 = arith.constant 4 : u32; + v114 = arith.mod v112, v493 : u32; + hir.assertz v114 #[code = 250]; + v115 = hir.int_to_ptr v112 : ptr; + hir.store v115, v109; + v116 = hir.bitcast v106 : u32; + v492 = arith.constant 4 : u32; + v118 = arith.mod v116, v492 : u32; + hir.assertz v118 #[code = 250]; + v119 = hir.int_to_ptr v116 : ptr; + hir.store v119, v104; + builtin.ret v106; + }; + + private builtin.function @miden:cross-ctx-account-word/foo@1.0.0#process-triple(v120: felt, v121: felt, v122: felt) -> i32 { + ^block17(v120: felt, v121: felt, v122: felt): + v125 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v126 = hir.bitcast v125 : ptr; + v127 = hir.load v126 : i32; + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/wit_bindgen::rt::run_ctors_once() + v128 = arith.constant 5 : i32; + v129 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v128) : felt + v130 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v120, v129) : felt + v499 = arith.constant 5 : i32; + v132 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v499) : felt + v133 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v121, v132) : felt + v134 = arith.constant 1048584 : i32; + v135 = arith.add v127, v134 : i32 #[overflow = wrapping]; + v498 = arith.constant 5 : i32; + v137 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v498) : felt + v138 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v122, v137) : felt + v140 = arith.constant 8 : u32; + v139 = hir.bitcast v135 : u32; + v141 = arith.add v139, v140 : u32 #[overflow = checked]; + v142 = arith.constant 4 : u32; + v143 = arith.mod v141, v142 : u32; + hir.assertz v143 #[code = 250]; + v144 = hir.int_to_ptr v141 : ptr; + hir.store v144, v138; + v497 = arith.constant 4 : u32; + v145 = hir.bitcast v135 : u32; + v147 = arith.add v145, v497 : u32 #[overflow = checked]; + v496 = arith.constant 4 : u32; + v149 = arith.mod v147, v496 : u32; + hir.assertz v149 #[code = 250]; + v150 = hir.int_to_ptr v147 : ptr; + hir.store v150, v133; + v151 = hir.bitcast v135 : u32; + v495 = arith.constant 4 : u32; + v153 = arith.mod v151, v495 : u32; + hir.assertz v153 #[code = 250]; + v154 = hir.int_to_ptr v151 : ptr; + hir.store v154, v130; + builtin.ret v135; + }; + + private builtin.function @miden:cross-ctx-account-word/foo@1.0.0#process-mixed(v155: i64, v156: felt, v157: i32, v158: felt, v159: i32, v160: i32, v161: i32) -> i32 { + ^block19(v155: i64, v156: felt, v157: i32, v158: felt, v159: i32, v160: i32, v161: i32): + v164 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v165 = hir.bitcast v164 : ptr; + v166 = hir.load v165 : i32; + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/wit_bindgen::rt::run_ctors_once() + v167 = arith.constant 6 : i32; + v168 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v167) : felt + v169 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v156, v168) : felt + v170 = arith.constant 7 : i32; + v171 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v170) : felt + v172 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v158, v171) : felt + v173 = arith.constant 1048584 : i32; + v174 = arith.add v166, v173 : i32 #[overflow = wrapping]; + v180 = arith.constant 22 : u32; + v179 = hir.bitcast v174 : u32; + v181 = arith.add v179, v180 : u32 #[overflow = checked]; + v182 = arith.constant 2 : u32; + v183 = arith.mod v181, v182 : u32; + hir.assertz v183 #[code = 250]; + v175 = arith.constant 9 : i32; + v176 = arith.add v161, v175 : i32 #[overflow = wrapping]; + v177 = hir.bitcast v176 : u32; + v178 = arith.trunc v177 : u16; + v184 = hir.int_to_ptr v181 : ptr; + hir.store v184, v178; + v163 = arith.constant 0 : i32; + v185 = arith.constant 255 : i32; + v186 = arith.band v160, v185 : i32; + v188 = arith.eq v186, v163 : i1; + v189 = arith.zext v188 : u32; + v190 = hir.bitcast v189 : i32; + v191 = hir.bitcast v190 : u32; + v192 = arith.trunc v191 : u8; + v194 = arith.constant 21 : u32; + v193 = hir.bitcast v174 : u32; + v195 = arith.add v193, v194 : u32 #[overflow = checked]; + v196 = hir.int_to_ptr v195 : ptr; + hir.store v196, v192; + v197 = arith.constant 11 : i32; + v198 = arith.add v159, v197 : i32 #[overflow = wrapping]; + v199 = hir.bitcast v198 : u32; + v200 = arith.trunc v199 : u8; + v202 = arith.constant 20 : u32; + v201 = hir.bitcast v174 : u32; + v203 = arith.add v201, v202 : u32 #[overflow = checked]; + v204 = hir.int_to_ptr v203 : ptr; + hir.store v204, v200; + v206 = arith.constant 16 : u32; + v205 = hir.bitcast v174 : u32; + v207 = arith.add v205, v206 : u32 #[overflow = checked]; + v208 = arith.constant 4 : u32; + v209 = arith.mod v207, v208 : u32; + hir.assertz v209 #[code = 250]; + v210 = hir.int_to_ptr v207 : ptr; + hir.store v210, v172; + v214 = arith.constant 12 : u32; + v213 = hir.bitcast v174 : u32; + v215 = arith.add v213, v214 : u32 #[overflow = checked]; + v502 = arith.constant 4 : u32; + v217 = arith.mod v215, v502 : u32; + hir.assertz v217 #[code = 250]; + v211 = arith.constant 10 : i32; + v212 = arith.add v157, v211 : i32 #[overflow = wrapping]; + v218 = hir.int_to_ptr v215 : ptr; + hir.store v218, v212; + v220 = arith.constant 8 : u32; + v219 = hir.bitcast v174 : u32; + v221 = arith.add v219, v220 : u32 #[overflow = checked]; + v501 = arith.constant 4 : u32; + v223 = arith.mod v221, v501 : u32; + hir.assertz v223 #[code = 250]; + v224 = hir.int_to_ptr v221 : ptr; + hir.store v224, v169; + v227 = hir.bitcast v174 : u32; + v500 = arith.constant 8 : u32; + v229 = arith.mod v227, v500 : u32; + hir.assertz v229 #[code = 250]; + v225 = arith.constant 1000 : i64; + v226 = arith.add v155, v225 : i64 #[overflow = wrapping]; + v230 = hir.int_to_ptr v227 : ptr; + hir.store v230, v226; + builtin.ret v174; + }; + + private builtin.function @miden:cross-ctx-account-word/foo@1.0.0#process-nested(v231: felt, v232: felt, v233: felt) -> i32 { + ^block21(v231: felt, v232: felt, v233: felt): + v236 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v237 = hir.bitcast v236 : ptr; + v238 = hir.load v237 : i32; + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/wit_bindgen::rt::run_ctors_once() + v239 = arith.constant 8 : i32; + v240 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v239) : felt + v241 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v231, v240) : felt + v507 = arith.constant 8 : i32; + v243 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v507) : felt + v244 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v232, v243) : felt + v245 = arith.constant 1048584 : i32; + v246 = arith.add v238, v245 : i32 #[overflow = wrapping]; + v506 = arith.constant 8 : i32; + v248 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::from_u32(v506) : felt + v249 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/intrinsics::felt::add(v233, v248) : felt + v251 = arith.constant 8 : u32; + v250 = hir.bitcast v246 : u32; + v252 = arith.add v250, v251 : u32 #[overflow = checked]; + v253 = arith.constant 4 : u32; + v254 = arith.mod v252, v253 : u32; + hir.assertz v254 #[code = 250]; + v255 = hir.int_to_ptr v252 : ptr; + hir.store v255, v249; + v505 = arith.constant 4 : u32; + v256 = hir.bitcast v246 : u32; + v258 = arith.add v256, v505 : u32 #[overflow = checked]; + v504 = arith.constant 4 : u32; + v260 = arith.mod v258, v504 : u32; + hir.assertz v260 #[code = 250]; + v261 = hir.int_to_ptr v258 : ptr; + hir.store v261, v244; + v262 = hir.bitcast v246 : u32; + v503 = arith.constant 4 : u32; + v264 = arith.mod v262, v503 : u32; + hir.assertz v264 #[code = 250]; + v265 = hir.int_to_ptr v262 : ptr; + hir.store v265, v241; + builtin.ret v246; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block23: + v267 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v268 = hir.bitcast v267 : ptr; + v269 = hir.load v268 : i32; + v270 = arith.constant 1048608 : i32; + v271 = arith.add v269, v270 : i32 #[overflow = wrapping]; + v272 = hir.bitcast v271 : u32; + v273 = hir.int_to_ptr v272 : ptr; + v274 = hir.load v273 : u8; + v266 = arith.constant 0 : i32; + v275 = arith.zext v274 : u32; + v276 = hir.bitcast v275 : i32; + v278 = arith.neq v276, v266 : i1; + scf.if v278{ + ^block25: + scf.yield ; + } else { + ^block26: + v279 = builtin.global_symbol @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/GOT.data.internal.__memory_base : ptr + v280 = hir.bitcast v279 : ptr; + v281 = hir.load v280 : i32; + hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/__wasm_call_ctors() + v509 = arith.constant 1 : u8; + v511 = arith.constant 1048608 : i32; + v283 = arith.add v281, v511 : i32 #[overflow = wrapping]; + v287 = hir.bitcast v283 : u32; + v288 = hir.int_to_ptr v287 : ptr; + hir.store v288, v509; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::add(v289: felt, v290: felt) -> felt { + ^block27(v289: felt, v290: felt): + v291 = arith.add v289, v290 : felt #[overflow = unchecked]; + builtin.ret v291; + }; + + private builtin.function @intrinsics::felt::from_u32(v293: i32) -> felt { + ^block29(v293: i32): + v294 = hir.bitcast v293 : felt; + builtin.ret v294; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @process-word(v296: felt, v297: felt, v298: felt, v299: felt) -> felt, felt, felt, felt { + ^block31(v296: felt, v297: felt, v298: felt, v299: felt): + v300 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/miden:cross-ctx-account-word/foo@1.0.0#process-word(v296, v297, v298, v299) : i32 + v301 = arith.constant 0 : i32; + v302 = arith.add v300, v301 : i32 #[overflow = unchecked]; + v515 = arith.constant 0 : i32; + v516 = arith.constant 0 : i32; + v304 = arith.add v302, v516 : i32 #[overflow = unchecked]; + v306 = arith.add v304, v515 : i32 #[overflow = unchecked]; + v307 = hir.int_to_ptr v306 : ptr; + v308 = hir.load v307 : felt; + v514 = arith.constant 0 : i32; + v309 = arith.constant 4 : i32; + v310 = arith.add v302, v309 : i32 #[overflow = unchecked]; + v312 = arith.add v310, v514 : i32 #[overflow = unchecked]; + v313 = hir.int_to_ptr v312 : ptr; + v314 = hir.load v313 : felt; + v513 = arith.constant 0 : i32; + v315 = arith.constant 8 : i32; + v316 = arith.add v302, v315 : i32 #[overflow = unchecked]; + v318 = arith.add v316, v513 : i32 #[overflow = unchecked]; + v319 = hir.int_to_ptr v318 : ptr; + v320 = hir.load v319 : felt; + v512 = arith.constant 0 : i32; + v321 = arith.constant 12 : i32; + v322 = arith.add v302, v321 : i32 #[overflow = unchecked]; + v324 = arith.add v322, v512 : i32 #[overflow = unchecked]; + v325 = hir.int_to_ptr v324 : ptr; + v326 = hir.load v325 : felt; + builtin.ret v308, v314, v320, v326; + }; + + public builtin.function @process-another-word(v331: felt, v332: felt, v333: felt, v334: felt) -> felt, felt, felt, felt { + ^block33(v331: felt, v332: felt, v333: felt, v334: felt): + v335 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/miden:cross-ctx-account-word/foo@1.0.0#process-another-word(v331, v332, v333, v334) : i32 + v336 = arith.constant 0 : i32; + v337 = arith.add v335, v336 : i32 #[overflow = unchecked]; + v520 = arith.constant 0 : i32; + v521 = arith.constant 0 : i32; + v339 = arith.add v337, v521 : i32 #[overflow = unchecked]; + v341 = arith.add v339, v520 : i32 #[overflow = unchecked]; + v342 = hir.int_to_ptr v341 : ptr; + v343 = hir.load v342 : felt; + v519 = arith.constant 0 : i32; + v344 = arith.constant 4 : i32; + v345 = arith.add v337, v344 : i32 #[overflow = unchecked]; + v347 = arith.add v345, v519 : i32 #[overflow = unchecked]; + v348 = hir.int_to_ptr v347 : ptr; + v349 = hir.load v348 : felt; + v518 = arith.constant 0 : i32; + v350 = arith.constant 8 : i32; + v351 = arith.add v337, v350 : i32 #[overflow = unchecked]; + v353 = arith.add v351, v518 : i32 #[overflow = unchecked]; + v354 = hir.int_to_ptr v353 : ptr; + v355 = hir.load v354 : felt; + v517 = arith.constant 0 : i32; + v356 = arith.constant 12 : i32; + v357 = arith.add v337, v356 : i32 #[overflow = unchecked]; + v359 = arith.add v357, v517 : i32 #[overflow = unchecked]; + v360 = hir.int_to_ptr v359 : ptr; + v361 = hir.load v360 : felt; + builtin.ret v343, v349, v355, v361; + }; + + public builtin.function @process-felt(v366: felt) -> felt { + ^block35(v366: felt): + v367 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/miden:cross-ctx-account-word/foo@1.0.0#process-felt(v366) : felt + builtin.ret v367; + }; + + public builtin.function @process-pair(v368: felt, v369: felt) -> felt, felt { + ^block37(v368: felt, v369: felt): + v370 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/miden:cross-ctx-account-word/foo@1.0.0#process-pair(v368, v369) : i32 + v523 = arith.constant 0 : i32; + v371 = arith.constant 0 : i32; + v372 = arith.add v370, v371 : i32 #[overflow = unchecked]; + v374 = arith.add v372, v523 : i32 #[overflow = unchecked]; + v375 = hir.int_to_ptr v374 : ptr; + v376 = hir.load v375 : felt; + v522 = arith.constant 0 : i32; + v377 = arith.constant 4 : i32; + v378 = arith.add v370, v377 : i32 #[overflow = unchecked]; + v380 = arith.add v378, v522 : i32 #[overflow = unchecked]; + v381 = hir.int_to_ptr v380 : ptr; + v382 = hir.load v381 : felt; + builtin.ret v376, v382; + }; + + public builtin.function @process-triple(v385: felt, v386: felt, v387: felt) -> felt, felt, felt { + ^block39(v385: felt, v386: felt, v387: felt): + v388 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/miden:cross-ctx-account-word/foo@1.0.0#process-triple(v385, v386, v387) : i32 + v526 = arith.constant 0 : i32; + v389 = arith.constant 0 : i32; + v390 = arith.add v388, v389 : i32 #[overflow = unchecked]; + v392 = arith.add v390, v526 : i32 #[overflow = unchecked]; + v393 = hir.int_to_ptr v392 : ptr; + v394 = hir.load v393 : felt; + v525 = arith.constant 0 : i32; + v395 = arith.constant 4 : i32; + v396 = arith.add v388, v395 : i32 #[overflow = unchecked]; + v398 = arith.add v396, v525 : i32 #[overflow = unchecked]; + v399 = hir.int_to_ptr v398 : ptr; + v400 = hir.load v399 : felt; + v524 = arith.constant 0 : i32; + v401 = arith.constant 8 : i32; + v402 = arith.add v388, v401 : i32 #[overflow = unchecked]; + v404 = arith.add v402, v524 : i32 #[overflow = unchecked]; + v405 = hir.int_to_ptr v404 : ptr; + v406 = hir.load v405 : felt; + builtin.ret v394, v400, v406; + }; + + public builtin.function @process-mixed(v410: i64, v411: felt, v412: i32, v413: felt, v414: i32, v415: i32, v416: i32) -> i64, felt, i32, felt, i32, i32, i32 { + ^block41(v410: i64, v411: felt, v412: i32, v413: felt, v414: i32, v415: i32, v416: i32): + v417 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/miden:cross-ctx-account-word/foo@1.0.0#process-mixed(v410, v411, v412, v413, v414, v415, v416) : i32 + v418 = arith.constant 0 : i32; + v419 = arith.add v417, v418 : i32 #[overflow = unchecked]; + v420 = hir.int_to_ptr v419 : ptr; + v421 = hir.load v420 : u64; + v528 = arith.constant 0 : i32; + v422 = arith.constant 8 : i32; + v423 = arith.add v417, v422 : i32 #[overflow = unchecked]; + v425 = arith.add v423, v528 : i32 #[overflow = unchecked]; + v426 = hir.int_to_ptr v425 : ptr; + v427 = hir.load v426 : felt; + v428 = arith.constant 12 : i32; + v429 = arith.add v417, v428 : i32 #[overflow = unchecked]; + v430 = hir.int_to_ptr v429 : ptr; + v431 = hir.load v430 : u32; + v527 = arith.constant 0 : i32; + v432 = arith.constant 16 : i32; + v433 = arith.add v417, v432 : i32 #[overflow = unchecked]; + v435 = arith.add v433, v527 : i32 #[overflow = unchecked]; + v436 = hir.int_to_ptr v435 : ptr; + v437 = hir.load v436 : felt; + v438 = arith.constant 20 : i32; + v439 = arith.add v417, v438 : i32 #[overflow = unchecked]; + v440 = hir.int_to_ptr v439 : ptr; + v441 = hir.load v440 : u8; + v442 = arith.constant 21 : i32; + v443 = arith.add v417, v442 : i32 #[overflow = unchecked]; + v444 = hir.int_to_ptr v443 : ptr; + v445 = hir.load v444 : i1; + v446 = arith.constant 22 : i32; + v447 = arith.add v417, v446 : i32 #[overflow = unchecked]; + v448 = hir.int_to_ptr v447 : ptr; + v449 = hir.load v448 : u16; + builtin.ret v421, v427, v431, v437, v441, v445, v449; + }; + + public builtin.function @process-nested(v457: felt, v458: felt, v459: felt) -> felt, felt, felt { + ^block43(v457: felt, v458: felt, v459: felt): + v460 = hir.exec @miden:cross-ctx-account-word/foo@1.0.0/cross_ctx_account_word/miden:cross-ctx-account-word/foo@1.0.0#process-nested(v457, v458, v459) : i32 + v461 = arith.constant 0 : i32; + v462 = arith.add v460, v461 : i32 #[overflow = unchecked]; + v531 = arith.constant 0 : i32; + v532 = arith.constant 0 : i32; + v464 = arith.add v462, v532 : i32 #[overflow = unchecked]; + v466 = arith.add v464, v531 : i32 #[overflow = unchecked]; + v467 = hir.int_to_ptr v466 : ptr; + v468 = hir.load v467 : felt; + v530 = arith.constant 0 : i32; + v469 = arith.constant 4 : i32; + v470 = arith.add v462, v469 : i32 #[overflow = unchecked]; + v472 = arith.add v470, v530 : i32 #[overflow = unchecked]; + v473 = hir.int_to_ptr v472 : ptr; + v474 = hir.load v473 : felt; + v529 = arith.constant 0 : i32; + v475 = arith.constant 8 : i32; + v476 = arith.add v460, v475 : i32 #[overflow = unchecked]; + v478 = arith.add v476, v529 : i32 #[overflow = unchecked]; + v479 = hir.int_to_ptr v478 : ptr; + v480 = hir.load v479 : felt; + builtin.ret v468, v474, v480; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account_word.masm b/tests/integration/expected/rust_sdk/cross_ctx_account_word.masm new file mode 100644 index 000000000..4dda13776 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account_word.masm @@ -0,0 +1,1300 @@ +# mod miden:cross-ctx-account-word/foo@1.0.0 + +export.process-word + exec.::miden:cross-ctx-account-word/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::miden:cross-ctx-account-word/foo@1.0.0#process-word + trace.252 + nop + push.0 + u32wrapping_add + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4 + dup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.8 + dup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.12 + movup.5 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movdn.3 + swap.2 + exec.::std::sys::truncate_stack +end + +export.process-another-word + exec.::miden:cross-ctx-account-word/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::miden:cross-ctx-account-word/foo@1.0.0#process-another-word + trace.252 + nop + push.0 + u32wrapping_add + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4 + dup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.8 + dup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.12 + movup.5 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + movdn.3 + swap.2 + exec.::std::sys::truncate_stack +end + +export.process-felt + exec.::miden:cross-ctx-account-word/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::miden:cross-ctx-account-word/foo@1.0.0#process-felt + trace.252 + nop + exec.::std::sys::truncate_stack +end + +export.process-pair + exec.::miden:cross-ctx-account-word/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::miden:cross-ctx-account-word/foo@1.0.0#process-pair + trace.252 + nop + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + exec.::std::sys::truncate_stack +end + +export.process-triple + exec.::miden:cross-ctx-account-word/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::miden:cross-ctx-account-word/foo@1.0.0#process-triple + trace.252 + nop + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4 + dup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.8 + movup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.2 + exec.::std::sys::truncate_stack +end + +export.process-mixed + exec.::miden:cross-ctx-account-word/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::miden:cross-ctx-account-word/foo@1.0.0#process-mixed + trace.252 + nop + push.0 + dup.1 + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.0 + push.8 + dup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + dup.4 + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.16 + dup.6 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.20 + dup.6 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.21 + dup.7 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.1 + u32and + push.22 + movup.8 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.65535 + u32and + movup.2 + swap.4 + movdn.2 + swap.1 + swap.5 + swap.1 + movdn.7 + movup.6 + movup.6 + exec.::std::sys::truncate_stack +end + +export.process-nested + exec.::miden:cross-ctx-account-word/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::miden:cross-ctx-account-word/foo@1.0.0#process-nested + trace.252 + nop + push.0 + dup.1 + u32wrapping_add + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.8 + movup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.2 + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word + +proc.__wasm_call_ctors + nop +end + +proc.cross_ctx_account_word::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:cross-ctx-account-word/foo@1.0.0#process-word + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.1048584 + movup.4 + u32wrapping_add + push.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden:cross-ctx-account-word/foo@1.0.0#process-another-word + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.1048584 + movup.4 + u32wrapping_add + push.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden:cross-ctx-account-word/foo@1.0.0#process-felt + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop +end + +proc.miden:cross-ctx-account-word/foo@1.0.0#process-pair + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.1048584 + movup.2 + u32wrapping_add + push.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden:cross-ctx-account-word/foo@1.0.0#process-triple + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.1048584 + movup.3 + u32wrapping_add + push.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden:cross-ctx-account-word/foo@1.0.0#process-mixed + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.6 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.7 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.6 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.1048584 + movup.3 + u32wrapping_add + push.22 + dup.1 + add + u32assert + push.2 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.9 + movup.10 + u32wrapping_add + push.65535 + u32and + swap.1 + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.65535 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + push.0 + push.255 + movup.9 + u32and + eq + push.255 + u32and + push.21 + dup.2 + add + u32assert + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + push.11 + movup.7 + u32wrapping_add + push.255 + u32and + push.20 + dup.2 + add + u32assert + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + push.16 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.10 + movup.6 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.1000 + push.0 + movup.5 + movup.5 + trace.240 + nop + exec.::std::math::u64::wrapping_add + trace.252 + nop + movup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.miden:cross-ctx-account-word/foo@1.0.0#process-nested + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.8 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.8 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.1048584 + movup.3 + u32wrapping_add + push.8 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::intrinsics::felt::add + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048608 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word/foo@1.0.0::cross_ctx_account_word::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048608 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.intrinsics::felt::add + add +end + +proc.intrinsics::felt::from_u32 + nop +end + diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account_word.wat b/tests/integration/expected/rust_sdk/cross_ctx_account_word.wat new file mode 100644 index 000000000..fad410705 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account_word.wat @@ -0,0 +1,391 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32 f32 f32 f32) (result i32))) + (type (;2;) (func (param f32) (result f32))) + (type (;3;) (func (param f32 f32) (result i32))) + (type (;4;) (func (param f32 f32 f32) (result i32))) + (type (;5;) (func (param i64 f32 i32 f32 i32 i32 i32) (result i32))) + (type (;6;) (func (param f32 f32) (result f32))) + (type (;7;) (func (param i32) (result f32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:cross-ctx-account-word/foo@1.0.0#process-word" (func $miden:cross-ctx-account-word/foo@1.0.0#process-word)) + (export "miden:cross-ctx-account-word/foo@1.0.0#process-another-word" (func $miden:cross-ctx-account-word/foo@1.0.0#process-another-word)) + (export "miden:cross-ctx-account-word/foo@1.0.0#process-felt" (func $miden:cross-ctx-account-word/foo@1.0.0#process-felt)) + (export "miden:cross-ctx-account-word/foo@1.0.0#process-pair" (func $miden:cross-ctx-account-word/foo@1.0.0#process-pair)) + (export "miden:cross-ctx-account-word/foo@1.0.0#process-triple" (func $miden:cross-ctx-account-word/foo@1.0.0#process-triple)) + (export "miden:cross-ctx-account-word/foo@1.0.0#process-mixed" (func $miden:cross-ctx-account-word/foo@1.0.0#process-mixed)) + (export "miden:cross-ctx-account-word/foo@1.0.0#process-nested" (func $miden:cross-ctx-account-word/foo@1.0.0#process-nested)) + (elem (;0;) (i32.const 1) func $cross_ctx_account_word::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $cross_ctx_account_word::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:cross-ctx-account-word/foo@1.0.0#process-word (;2;) (type 1) (param f32 f32 f32 f32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 4 + call $wit_bindgen::rt::run_ctors_once + local.get 0 + i32.const 1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 0 + local.get 1 + i32.const 2 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 1 + local.get 2 + i32.const 3 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 2 + local.get 4 + i32.const 1048584 + i32.add + local.tee 4 + local.get 3 + i32.const 4 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + f32.store offset=12 + local.get 4 + local.get 2 + f32.store offset=8 + local.get 4 + local.get 1 + f32.store offset=4 + local.get 4 + local.get 0 + f32.store + local.get 4 + ) + (func $miden:cross-ctx-account-word/foo@1.0.0#process-another-word (;3;) (type 1) (param f32 f32 f32 f32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 4 + call $wit_bindgen::rt::run_ctors_once + local.get 0 + i32.const 2 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 0 + local.get 1 + i32.const 3 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 1 + local.get 2 + i32.const 4 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 2 + local.get 4 + i32.const 1048584 + i32.add + local.tee 4 + local.get 3 + i32.const 5 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + f32.store offset=12 + local.get 4 + local.get 2 + f32.store offset=8 + local.get 4 + local.get 1 + f32.store offset=4 + local.get 4 + local.get 0 + f32.store + local.get 4 + ) + (func $miden:cross-ctx-account-word/foo@1.0.0#process-felt (;4;) (type 2) (param f32) (result f32) + call $wit_bindgen::rt::run_ctors_once + local.get 0 + i32.const 3 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + ) + (func $miden:cross-ctx-account-word/foo@1.0.0#process-pair (;5;) (type 3) (param f32 f32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 2 + call $wit_bindgen::rt::run_ctors_once + local.get 0 + i32.const 4 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 0 + local.get 2 + i32.const 1048584 + i32.add + local.tee 2 + local.get 1 + i32.const 4 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + f32.store offset=4 + local.get 2 + local.get 0 + f32.store + local.get 2 + ) + (func $miden:cross-ctx-account-word/foo@1.0.0#process-triple (;6;) (type 4) (param f32 f32 f32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 3 + call $wit_bindgen::rt::run_ctors_once + local.get 0 + i32.const 5 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 0 + local.get 1 + i32.const 5 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 1 + local.get 3 + i32.const 1048584 + i32.add + local.tee 3 + local.get 2 + i32.const 5 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + f32.store offset=8 + local.get 3 + local.get 1 + f32.store offset=4 + local.get 3 + local.get 0 + f32.store + local.get 3 + ) + (func $miden:cross-ctx-account-word/foo@1.0.0#process-mixed (;7;) (type 5) (param i64 f32 i32 f32 i32 i32 i32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 7 + call $wit_bindgen::rt::run_ctors_once + local.get 1 + i32.const 6 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 1 + local.get 3 + i32.const 7 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 3 + local.get 7 + i32.const 1048584 + i32.add + local.tee 7 + local.get 6 + i32.const 9 + i32.add + i32.store16 offset=22 + local.get 7 + local.get 5 + i32.const 255 + i32.and + i32.eqz + i32.store8 offset=21 + local.get 7 + local.get 4 + i32.const 11 + i32.add + i32.store8 offset=20 + local.get 7 + local.get 3 + f32.store offset=16 + local.get 7 + local.get 2 + i32.const 10 + i32.add + i32.store offset=12 + local.get 7 + local.get 1 + f32.store offset=8 + local.get 7 + local.get 0 + i64.const 1000 + i64.add + i64.store + local.get 7 + ) + (func $miden:cross-ctx-account-word/foo@1.0.0#process-nested (;8;) (type 4) (param f32 f32 f32) (result i32) + (local i32) + global.get $GOT.data.internal.__memory_base + local.set 3 + call $wit_bindgen::rt::run_ctors_once + local.get 0 + i32.const 8 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 0 + local.get 1 + i32.const 8 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + local.set 1 + local.get 3 + i32.const 1048584 + i32.add + local.tee 3 + local.get 2 + i32.const 8 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::add + f32.store offset=8 + local.get 3 + local.get 1 + f32.store offset=4 + local.get 3 + local.get 0 + f32.store + local.get 3 + ) + (func $wit_bindgen::rt::run_ctors_once (;9;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048608 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048608 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $intrinsics::felt::add (;10;) (type 6) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;11;) (type 7) (param i32) (result f32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + ) + (alias export 0 "word" (type (;1;))) + (alias export 0 "felt" (type (;2;))) + (type (;3;) (record (field "first" 2) (field "second" 2))) + (type (;4;) (record (field "x" 2) (field "y" 2) (field "z" 2))) + (type (;5;) (record (field "f" u64) (field "a" 2) (field "b" u32) (field "c" 2) (field "d" u8) (field "e" bool) (field "g" u16))) + (type (;6;) (record (field "inner" 3) (field "value" 2))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;7;) (func (param "input" 1) (result 1))) + (alias core export 0 "miden:cross-ctx-account-word/foo@1.0.0#process-word" (core func (;0;))) + (func (;0;) (type 7) (canon lift (core func 0) (memory 0))) + (alias core export 0 "miden:cross-ctx-account-word/foo@1.0.0#process-another-word" (core func (;1;))) + (func (;1;) (type 7) (canon lift (core func 1) (memory 0))) + (type (;8;) (func (param "input" 2) (result 2))) + (alias core export 0 "miden:cross-ctx-account-word/foo@1.0.0#process-felt" (core func (;2;))) + (func (;2;) (type 8) (canon lift (core func 2))) + (type (;9;) (func (param "input" 3) (result 3))) + (alias core export 0 "miden:cross-ctx-account-word/foo@1.0.0#process-pair" (core func (;3;))) + (func (;3;) (type 9) (canon lift (core func 3) (memory 0))) + (type (;10;) (func (param "input" 4) (result 4))) + (alias core export 0 "miden:cross-ctx-account-word/foo@1.0.0#process-triple" (core func (;4;))) + (func (;4;) (type 10) (canon lift (core func 4) (memory 0))) + (type (;11;) (func (param "input" 5) (result 5))) + (alias core export 0 "miden:cross-ctx-account-word/foo@1.0.0#process-mixed" (core func (;5;))) + (func (;5;) (type 11) (canon lift (core func 5) (memory 0))) + (type (;12;) (func (param "input" 6) (result 6))) + (alias core export 0 "miden:cross-ctx-account-word/foo@1.0.0#process-nested" (core func (;6;))) + (func (;6;) (type 12) (canon lift (core func 6) (memory 0))) + (alias export 0 "felt" (type (;13;))) + (alias export 0 "word" (type (;14;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "input" 5) (result 5))) + (import "import-func-process-word" (func (;0;) (type 6))) + (import "import-func-process-another-word" (func (;1;) (type 6))) + (import "import-type-felt0" (type (;7;) (eq 1))) + (type (;8;) (func (param "input" 7) (result 7))) + (import "import-func-process-felt" (func (;2;) (type 8))) + (type (;9;) (record (field "first" 7) (field "second" 7))) + (import "import-type-pair" (type (;10;) (eq 9))) + (type (;11;) (func (param "input" 10) (result 10))) + (import "import-func-process-pair" (func (;3;) (type 11))) + (type (;12;) (record (field "x" 7) (field "y" 7) (field "z" 7))) + (import "import-type-triple" (type (;13;) (eq 12))) + (type (;14;) (func (param "input" 13) (result 13))) + (import "import-func-process-triple" (func (;4;) (type 14))) + (type (;15;) (record (field "f" u64) (field "a" 7) (field "b" u32) (field "c" 7) (field "d" u8) (field "e" bool) (field "g" u16))) + (import "import-type-mixed-struct" (type (;16;) (eq 15))) + (type (;17;) (func (param "input" 16) (result 16))) + (import "import-func-process-mixed" (func (;5;) (type 17))) + (type (;18;) (record (field "inner" 10) (field "value" 7))) + (import "import-type-nested-struct" (type (;19;) (eq 18))) + (type (;20;) (func (param "input" 19) (result 19))) + (import "import-func-process-nested" (func (;6;) (type 20))) + (export (;21;) "word" (type 4)) + (export (;22;) "felt" (type 1)) + (type (;23;) (record (field "first" 22) (field "second" 22))) + (export (;24;) "pair" (type 23)) + (type (;25;) (record (field "x" 22) (field "y" 22) (field "z" 22))) + (export (;26;) "triple" (type 25)) + (type (;27;) (record (field "f" u64) (field "a" 22) (field "b" u32) (field "c" 22) (field "d" u8) (field "e" bool) (field "g" u16))) + (export (;28;) "mixed-struct" (type 27)) + (type (;29;) (record (field "inner" 24) (field "value" 22))) + (export (;30;) "nested-struct" (type 29)) + (type (;31;) (func (param "input" 21) (result 21))) + (export (;7;) "process-word" (func 0) (func (type 31))) + (export (;8;) "process-another-word" (func 1) (func (type 31))) + (type (;32;) (func (param "input" 22) (result 22))) + (export (;9;) "process-felt" (func 2) (func (type 32))) + (type (;33;) (func (param "input" 24) (result 24))) + (export (;10;) "process-pair" (func 3) (func (type 33))) + (type (;34;) (func (param "input" 26) (result 26))) + (export (;11;) "process-triple" (func 4) (func (type 34))) + (type (;35;) (func (param "input" 28) (result 28))) + (export (;12;) "process-mixed" (func 5) (func (type 35))) + (type (;36;) (func (param "input" 30) (result 30))) + (export (;13;) "process-nested" (func 6) (func (type 36))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-process-word" (func 0)) + (with "import-func-process-another-word" (func 1)) + (with "import-func-process-felt" (func 2)) + (with "import-func-process-pair" (func 3)) + (with "import-func-process-triple" (func 4)) + (with "import-func-process-mixed" (func 5)) + (with "import-func-process-nested" (func 6)) + (with "import-type-felt" (type 13)) + (with "import-type-word" (type 14)) + (with "import-type-word0" (type 1)) + (with "import-type-felt0" (type 2)) + (with "import-type-pair" (type 3)) + (with "import-type-triple" (type 4)) + (with "import-type-mixed-struct" (type 5)) + (with "import-type-nested-struct" (type 6)) + ) + ) + (export (;2;) "miden:cross-ctx-account-word/foo@1.0.0" (instance 1)) +) diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.hir b/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.hir new file mode 100644 index 000000000..8ffffcb56 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.hir @@ -0,0 +1,149 @@ +builtin.component miden:cross-ctx-account-word-arg/foo@1.0.0 { + builtin.module public @cross_ctx_account_word_arg { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @cross_ctx_account_word_arg::bindings::__link_custom_section_describing_imports() { + ^block7: + builtin.ret ; + }; + + private builtin.function @miden:cross-ctx-account-word-arg/foo@1.0.0#process-word(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt, v12: felt, v13: felt, v14: felt, v15: felt) -> felt { + ^block9(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt, v12: felt, v13: felt, v14: felt, v15: felt): + hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/wit_bindgen::rt::run_ctors_once() + hir.store_local v15 #[local = lv0]; + v17 = arith.constant 1 : i32; + v18 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v17) : felt + v19 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v0, v18) : felt + v20 = arith.constant 2 : i32; + v21 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v20) : felt + v22 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v1, v21) : felt + v23 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v19, v22) : felt + v24 = arith.constant 4 : i32; + v25 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v24) : felt + v26 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v2, v25) : felt + v27 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v23, v26) : felt + v28 = arith.constant 8 : i32; + v29 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v28) : felt + v30 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v3, v29) : felt + v31 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v27, v30) : felt + v32 = arith.constant 16 : i32; + v33 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v32) : felt + v34 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v4, v33) : felt + v35 = arith.constant 32 : i32; + v36 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v35) : felt + v37 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v5, v36) : felt + v38 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v34, v37) : felt + v39 = arith.constant 64 : i32; + v40 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v39) : felt + v41 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v6, v40) : felt + v42 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v38, v41) : felt + v43 = arith.constant 128 : i32; + v44 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v43) : felt + v45 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v7, v44) : felt + v46 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v42, v45) : felt + v47 = arith.constant 256 : i32; + v48 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v47) : felt + v49 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v8, v48) : felt + v50 = arith.constant 512 : i32; + v51 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v50) : felt + v52 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v9, v51) : felt + v53 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v49, v52) : felt + v54 = arith.constant 1024 : i32; + v55 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v54) : felt + v56 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v10, v55) : felt + v57 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v53, v56) : felt + v58 = arith.constant 2048 : i32; + v59 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v58) : felt + v60 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v11, v59) : felt + v61 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v57, v60) : felt + v62 = arith.constant 4096 : i32; + v63 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v62) : felt + v64 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v12, v63) : felt + v65 = arith.constant 8192 : i32; + v66 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v65) : felt + v67 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v13, v66) : felt + v68 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v64, v67) : felt + v69 = arith.constant 16384 : i32; + v70 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::from_u32(v69) : felt + v71 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::mul(v14, v70) : felt + v72 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v68, v71) : felt + v73 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v31, v46) : felt + v74 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v73, v61) : felt + v75 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v74, v72) : felt + v129 = hir.load_local : felt #[local = lv0]; + v76 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/intrinsics::felt::add(v75, v129) : felt + builtin.ret v76; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block11: + v78 = builtin.global_symbol @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/GOT.data.internal.__memory_base : ptr + v79 = hir.bitcast v78 : ptr; + v80 = hir.load v79 : i32; + v81 = arith.constant 1048584 : i32; + v82 = arith.add v80, v81 : i32 #[overflow = wrapping]; + v83 = hir.bitcast v82 : u32; + v84 = hir.int_to_ptr v83 : ptr; + v85 = hir.load v84 : u8; + v77 = arith.constant 0 : i32; + v86 = arith.zext v85 : u32; + v87 = hir.bitcast v86 : i32; + v89 = arith.neq v87, v77 : i1; + scf.if v89{ + ^block13: + scf.yield ; + } else { + ^block14: + v90 = builtin.global_symbol @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/GOT.data.internal.__memory_base : ptr + v91 = hir.bitcast v90 : ptr; + v92 = hir.load v91 : i32; + hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/__wasm_call_ctors() + v131 = arith.constant 1 : u8; + v133 = arith.constant 1048584 : i32; + v94 = arith.add v92, v133 : i32 #[overflow = wrapping]; + v98 = hir.bitcast v94 : u32; + v99 = hir.int_to_ptr v98 : ptr; + hir.store v99, v131; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::add(v100: felt, v101: felt) -> felt { + ^block15(v100: felt, v101: felt): + v102 = arith.add v100, v101 : felt #[overflow = unchecked]; + builtin.ret v102; + }; + + private builtin.function @intrinsics::felt::from_u32(v104: i32) -> felt { + ^block17(v104: i32): + v105 = hir.bitcast v104 : felt; + builtin.ret v105; + }; + + private builtin.function @intrinsics::felt::mul(v107: felt, v108: felt) -> felt { + ^block19(v107: felt, v108: felt): + v109 = arith.mul v107, v108 : felt #[overflow = unchecked]; + builtin.ret v109; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @process-word(v111: felt, v112: felt, v113: felt, v114: felt, v115: felt, v116: felt, v117: felt, v118: felt, v119: felt, v120: felt, v121: felt, v122: felt, v123: felt, v124: felt, v125: felt, v126: felt) -> felt { + ^block21(v111: felt, v112: felt, v113: felt, v114: felt, v115: felt, v116: felt, v117: felt, v118: felt, v119: felt, v120: felt, v121: felt, v122: felt, v123: felt, v124: felt, v125: felt, v126: felt): + v127 = hir.exec @miden:cross-ctx-account-word-arg/foo@1.0.0/cross_ctx_account_word_arg/miden:cross-ctx-account-word-arg/foo@1.0.0#process-word(v111, v112, v113, v114, v115, v116, v117, v118, v119, v120, v121, v122, v123, v124, v125, v126) : felt + builtin.ret v127; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.masm b/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.masm new file mode 100644 index 000000000..c0602906f --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.masm @@ -0,0 +1,422 @@ +# mod miden:cross-ctx-account-word-arg/foo@1.0.0 + +export.process-word + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::init + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::miden:cross-ctx-account-word-arg/foo@1.0.0#process-word + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg + +proc.__wasm_call_ctors + nop +end + +proc.cross_ctx_account_word_arg::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:cross-ctx-account-word-arg/foo@1.0.0#process-word.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::wit_bindgen::rt::run_ctors_once + trace.252 + nop + movup.15 + locaddr.0 + push.0 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + push.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.8 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.16 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.2 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + push.32 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.64 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.128 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.256 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + push.512 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.1024 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.2048 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.4096 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.4 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + push.8192 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + push.16384 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.5 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::mul + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + movup.2 + movup.3 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + movup.2 + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop + locaddr.0 + push.0 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::intrinsics::felt::add + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:cross-ctx-account-word-arg/foo@1.0.0::cross_ctx_account_word_arg::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.intrinsics::felt::add + add +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::mul + mul +end + diff --git a/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.wat b/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.wat new file mode 100644 index 000000000..42fc578e0 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_account_word_arg.wat @@ -0,0 +1,175 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) + (type (;2;) (func (param f32 f32) (result f32))) + (type (;3;) (func (param i32) (result f32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:cross-ctx-account-word-arg/foo@1.0.0#process-word" (func $miden:cross-ctx-account-word-arg/foo@1.0.0#process-word)) + (elem (;0;) (i32.const 1) func $cross_ctx_account_word_arg::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $cross_ctx_account_word_arg::bindings::__link_custom_section_describing_imports (;1;) (type 0)) + (func $miden:cross-ctx-account-word-arg/foo@1.0.0#process-word (;2;) (type 1) (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32) + call $wit_bindgen::rt::run_ctors_once + local.get 0 + i32.const 1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + local.get 1 + i32.const 2 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.get 2 + i32.const 4 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.get 3 + i32.const 8 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.set 3 + local.get 4 + i32.const 16 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + local.get 5 + i32.const 32 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.get 6 + i32.const 64 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.get 7 + i32.const 128 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.set 7 + local.get 8 + i32.const 256 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + local.get 9 + i32.const 512 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.get 10 + i32.const 1024 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.get 11 + i32.const 2048 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.set 11 + local.get 12 + i32.const 4096 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + local.get 13 + i32.const 8192 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.get 14 + i32.const 16384 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::mul + call $intrinsics::felt::add + local.set 14 + local.get 3 + local.get 7 + call $intrinsics::felt::add + local.get 11 + call $intrinsics::felt::add + local.get 14 + call $intrinsics::felt::add + local.get 15 + call $intrinsics::felt::add + ) + (func $wit_bindgen::rt::run_ctors_once (;3;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048584 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $intrinsics::felt::add (;4;) (type 2) (param f32 f32) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;5;) (type 3) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::mul (;6;) (type 2) (param f32 f32) (result f32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + ) + (alias export 0 "word" (type (;1;))) + (alias export 0 "felt" (type (;2;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;3;) (func (param "input1" 1) (param "input2" 1) (param "input3" 1) (param "felt1" 2) (param "felt2" 2) (param "felt3" 2) (param "felt4" 2) (result 2))) + (alias core export 0 "miden:cross-ctx-account-word-arg/foo@1.0.0#process-word" (core func (;0;))) + (func (;0;) (type 3) (canon lift (core func 0))) + (alias export 0 "felt" (type (;4;))) + (alias export 0 "word" (type (;5;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (import "import-type-felt0" (type (;6;) (eq 1))) + (type (;7;) (func (param "input1" 5) (param "input2" 5) (param "input3" 5) (param "felt1" 6) (param "felt2" 6) (param "felt3" 6) (param "felt4" 6) (result 6))) + (import "import-func-process-word" (func (;0;) (type 7))) + (export (;8;) "word" (type 4)) + (export (;9;) "felt" (type 1)) + (type (;10;) (func (param "input1" 8) (param "input2" 8) (param "input3" 8) (param "felt1" 9) (param "felt2" 9) (param "felt3" 9) (param "felt4" 9) (result 9))) + (export (;1;) "process-word" (func 0) (func (type 10))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-process-word" (func 0)) + (with "import-type-felt" (type 4)) + (with "import-type-word" (type 5)) + (with "import-type-word0" (type 1)) + (with "import-type-felt0" (type 2)) + ) + ) + (export (;2;) "miden:cross-ctx-account-word-arg/foo@1.0.0" (instance 1)) +) diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note.hir b/tests/integration/expected/rust_sdk/cross_ctx_note.hir new file mode 100644 index 000000000..701fc0154 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note.hir @@ -0,0 +1,121 @@ +builtin.component miden:base/note-script@1.0.0 { + builtin.module public @cross_ctx_note { + private builtin.function @cross_ctx_note::bindings::miden::cross_ctx_account::foo::process_felt::wit_import1(v0: felt) -> felt { + ^block3(v0: felt): + v1 = hir.call v0 : felt #[callee = miden:cross-ctx-account/foo@1.0.0/process-felt] #[signature = (param felt) (result felt)]; + builtin.ret v1; + }; + + private builtin.function @__wasm_call_ctors() { + ^block8: + builtin.ret ; + }; + + private builtin.function @cross_ctx_note::bindings::__link_custom_section_describing_imports() { + ^block10: + builtin.ret ; + }; + + private builtin.function @miden:base/note-script@1.0.0#run(v2: felt, v3: felt, v4: felt, v5: felt) { + ^block12(v2: felt, v3: felt, v4: felt, v5: felt): + v8 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note/GOT.data.internal.__memory_base : ptr + v9 = hir.bitcast v8 : ptr; + v10 = hir.load v9 : i32; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/wit_bindgen::rt::run_ctors_once() + v11 = arith.constant 1048588 : i32; + v12 = arith.add v10, v11 : i32 #[overflow = wrapping]; + v13 = hir.bitcast v12 : u32; + v14 = arith.constant 4 : u32; + v15 = arith.mod v13, v14 : u32; + hir.assertz v15 #[code = 250]; + v16 = hir.int_to_ptr v13 : ptr; + v17 = hir.load v16 : i32; + v18 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/intrinsics::felt::from_u32(v17) : felt + v19 = arith.constant 11 : i32; + v20 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/intrinsics::felt::from_u32(v19) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/intrinsics::felt::assert_eq(v18, v20) + v21 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/cross_ctx_note::bindings::miden::cross_ctx_account::foo::process_felt::wit_import1(v18) : felt + v22 = arith.constant 53 : i32; + v23 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/intrinsics::felt::from_u32(v22) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/intrinsics::felt::assert_eq(v21, v23) + v24 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/intrinsics::felt::as_u64(v21) : i64 + v27 = hir.bitcast v12 : u32; + v66 = arith.constant 4 : u32; + v29 = arith.mod v27, v66 : u32; + hir.assertz v29 #[code = 250]; + v25 = hir.bitcast v24 : u64; + v26 = arith.trunc v25 : u32; + v30 = hir.int_to_ptr v27 : ptr; + hir.store v30, v26; + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block14: + v32 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note/GOT.data.internal.__memory_base : ptr + v33 = hir.bitcast v32 : ptr; + v34 = hir.load v33 : i32; + v35 = arith.constant 1048592 : i32; + v36 = arith.add v34, v35 : i32 #[overflow = wrapping]; + v37 = hir.bitcast v36 : u32; + v38 = hir.int_to_ptr v37 : ptr; + v39 = hir.load v38 : u8; + v31 = arith.constant 0 : i32; + v40 = arith.zext v39 : u32; + v41 = hir.bitcast v40 : i32; + v43 = arith.neq v41, v31 : i1; + scf.if v43{ + ^block16: + scf.yield ; + } else { + ^block17: + v44 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note/GOT.data.internal.__memory_base : ptr + v45 = hir.bitcast v44 : ptr; + v46 = hir.load v45 : i32; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/__wasm_call_ctors() + v68 = arith.constant 1 : u8; + v70 = arith.constant 1048592 : i32; + v48 = arith.add v46, v70 : i32 #[overflow = wrapping]; + v52 = hir.bitcast v48 : u32; + v53 = hir.int_to_ptr v52 : ptr; + hir.store v53, v68; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u32(v54: i32) -> felt { + ^block18(v54: i32): + v55 = hir.bitcast v54 : felt; + builtin.ret v55; + }; + + private builtin.function @intrinsics::felt::as_u64(v57: felt) -> i64 { + ^block20(v57: felt): + v58 = hir.cast v57 : i64; + builtin.ret v58; + }; + + private builtin.function @intrinsics::felt::assert_eq(v60: felt, v61: felt) { + ^block22(v60: felt, v61: felt): + hir.assert_eq v60, v61; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000b000000010000000100000001; + }; + + public builtin.function @run(v62: felt, v63: felt, v64: felt, v65: felt) { + ^block24(v62: felt, v63: felt, v64: felt, v65: felt): + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note/miden:base/note-script@1.0.0#run(v62, v63, v64, v65) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note.masm b/tests/integration/expected/rust_sdk/cross_ctx_note.masm new file mode 100644 index 000000000..7649f6abf --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note.masm @@ -0,0 +1,226 @@ +# mod miden:base/note-script@1.0.0 + +export.run + exec.::miden:base/note-script@1.0.0::init + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::miden:base/note-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[10615597203579269634,13770205199844343813,15841743244050277340,1132879909491380236] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/note-script@1.0.0::cross_ctx_note + +proc.cross_ctx_note::bindings::miden::cross_ctx_account::foo::process_felt::wit_import1 + trace.240 + nop + call.::miden:cross-ctx-account/foo@1.0.0::process-felt + trace.252 + nop +end + +proc.__wasm_call_ctors + nop +end + +proc.cross_ctx_note::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/note-script@1.0.0#run + drop + drop + drop + drop + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.1048588 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::intrinsics::felt::from_u32 + trace.252 + nop + push.11 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::intrinsics::felt::from_u32 + trace.252 + nop + dup.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::intrinsics::felt::assert_eq + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::cross_ctx_note::bindings::miden::cross_ctx_account::foo::process_felt::wit_import1 + trace.252 + nop + push.53 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::intrinsics::felt::from_u32 + trace.252 + nop + dup.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::intrinsics::felt::assert_eq + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::intrinsics::felt::as_u64 + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movdn.2 + drop + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048592 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048592 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::as_u64 + u32split +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note.wat b/tests/integration/expected/rust_sdk/cross_ctx_note.wat new file mode 100644 index 000000000..5cbf1d9db --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note.wat @@ -0,0 +1,131 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (alias export 0 "felt" (type (;1;))) + (type (;2;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (func (param "input" 1) (result 1))) + (export (;0;) "process-felt" (func (type 2))) + ) + ) + (import "miden:cross-ctx-account/foo@1.0.0" (instance (;1;) (type 2))) + (core module (;0;) + (type (;0;) (func (param f32) (result f32))) + (type (;1;) (func)) + (type (;2;) (func (param f32 f32 f32 f32))) + (type (;3;) (func (param i32) (result f32))) + (type (;4;) (func (param f32) (result i64))) + (type (;5;) (func (param f32 f32))) + (import "miden:cross-ctx-account/foo@1.0.0" "process-felt" (func $cross_ctx_note::bindings::miden::cross_ctx_account::foo::process_felt::wit_import1 (;0;) (type 0))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/note-script@1.0.0#run" (func $miden:base/note-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $cross_ctx_note::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;1;) (type 1)) + (func $cross_ctx_note::bindings::__link_custom_section_describing_imports (;2;) (type 1)) + (func $miden:base/note-script@1.0.0#run (;3;) (type 2) (param f32 f32 f32 f32) + (local i32 f32) + global.get $GOT.data.internal.__memory_base + local.set 4 + call $wit_bindgen::rt::run_ctors_once + local.get 4 + i32.const 1048588 + i32.add + local.tee 4 + i32.load + call $intrinsics::felt::from_u32 + local.tee 5 + i32.const 11 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + call $cross_ctx_note::bindings::miden::cross_ctx_account::foo::process_felt::wit_import1 + local.tee 5 + i32.const 53 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 4 + local.get 5 + call $intrinsics::felt::as_u64 + i64.store32 + ) + (func $wit_bindgen::rt::run_ctors_once (;4;) (type 1) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048592 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048592 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $intrinsics::felt::from_u32 (;5;) (type 3) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::as_u64 (;6;) (type 4) (param f32) (result i64) + unreachable + ) + (func $intrinsics::felt::assert_eq (;7;) (type 5) (param f32 f32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00\01\00\00\00\0b\00\00\00") + ) + (alias export 0 "word" (type (;3;))) + (alias export 1 "process-felt" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (core instance (;0;) + (export "process-felt" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "miden:cross-ctx-account/foo@1.0.0" (instance 0)) + ) + ) + (alias core export 1 "memory" (core memory (;0;))) + (type (;4;) (func (param "arg" 3))) + (alias core export 1 "miden:base/note-script@1.0.0#run" (core func (;1;))) + (func (;1;) (type 4) (canon lift (core func 1))) + (alias export 0 "felt" (type (;5;))) + (alias export 0 "word" (type (;6;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;2;) (instantiate 0 + (with "import-func-run" (func 1)) + (with "import-type-felt" (type 5)) + (with "import-type-word" (type 6)) + (with "import-type-word0" (type 3)) + ) + ) + (export (;3;) "miden:base/note-script@1.0.0" (instance 2)) +) diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note_word.hir b/tests/integration/expected/rust_sdk/cross_ctx_note_word.hir new file mode 100644 index 000000000..631ba28c0 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note_word.hir @@ -0,0 +1,639 @@ +builtin.component miden:base/note-script@1.0.0 { + builtin.module public @cross_ctx_note_word { + private builtin.function @cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_word::wit_import7(v0: felt, v1: felt, v2: felt, v3: felt, v4: i32) { + ^block3(v0: felt, v1: felt, v2: felt, v3: felt, v4: i32): + v5, v6, v7, v8 = hir.call v0, v1, v2, v3 : felt, felt, felt, felt #[callee = miden:cross-ctx-account-word/foo@1.0.0/process-word] #[signature = (cc canon-lower) (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)]; + v9 = arith.constant 0 : i32; + v10 = arith.add v4, v9 : i32 #[overflow = unchecked]; + v519 = arith.constant 0 : i32; + v520 = arith.constant 0 : i32; + v12 = arith.add v10, v520 : i32 #[overflow = unchecked]; + v14 = arith.add v12, v519 : i32 #[overflow = unchecked]; + v15 = hir.int_to_ptr v14 : ptr; + hir.store v15, v5; + v518 = arith.constant 0 : i32; + v16 = arith.constant 4 : i32; + v17 = arith.add v10, v16 : i32 #[overflow = unchecked]; + v19 = arith.add v17, v518 : i32 #[overflow = unchecked]; + v20 = hir.int_to_ptr v19 : ptr; + hir.store v20, v6; + v517 = arith.constant 0 : i32; + v21 = arith.constant 8 : i32; + v22 = arith.add v10, v21 : i32 #[overflow = unchecked]; + v24 = arith.add v22, v517 : i32 #[overflow = unchecked]; + v25 = hir.int_to_ptr v24 : ptr; + hir.store v25, v7; + v516 = arith.constant 0 : i32; + v26 = arith.constant 12 : i32; + v27 = arith.add v10, v26 : i32 #[overflow = unchecked]; + v29 = arith.add v27, v516 : i32 #[overflow = unchecked]; + v30 = hir.int_to_ptr v29 : ptr; + hir.store v30, v8; + builtin.ret ; + }; + + private builtin.function @cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_another_word::wit_import7(v31: felt, v32: felt, v33: felt, v34: felt, v35: i32) { + ^block6(v31: felt, v32: felt, v33: felt, v34: felt, v35: i32): + v36, v37, v38, v39 = hir.call v31, v32, v33, v34 : felt, felt, felt, felt #[callee = miden:cross-ctx-account-word/foo@1.0.0/process-another-word] #[signature = (cc canon-lower) (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)]; + v40 = arith.constant 0 : i32; + v41 = arith.add v35, v40 : i32 #[overflow = unchecked]; + v524 = arith.constant 0 : i32; + v525 = arith.constant 0 : i32; + v43 = arith.add v41, v525 : i32 #[overflow = unchecked]; + v45 = arith.add v43, v524 : i32 #[overflow = unchecked]; + v46 = hir.int_to_ptr v45 : ptr; + hir.store v46, v36; + v523 = arith.constant 0 : i32; + v47 = arith.constant 4 : i32; + v48 = arith.add v41, v47 : i32 #[overflow = unchecked]; + v50 = arith.add v48, v523 : i32 #[overflow = unchecked]; + v51 = hir.int_to_ptr v50 : ptr; + hir.store v51, v37; + v522 = arith.constant 0 : i32; + v52 = arith.constant 8 : i32; + v53 = arith.add v41, v52 : i32 #[overflow = unchecked]; + v55 = arith.add v53, v522 : i32 #[overflow = unchecked]; + v56 = hir.int_to_ptr v55 : ptr; + hir.store v56, v38; + v521 = arith.constant 0 : i32; + v57 = arith.constant 12 : i32; + v58 = arith.add v41, v57 : i32 #[overflow = unchecked]; + v60 = arith.add v58, v521 : i32 #[overflow = unchecked]; + v61 = hir.int_to_ptr v60 : ptr; + hir.store v61, v39; + builtin.ret ; + }; + + private builtin.function @cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_felt::wit_import1(v62: felt) -> felt { + ^block8(v62: felt): + v63 = hir.call v62 : felt #[callee = miden:cross-ctx-account-word/foo@1.0.0/process-felt] #[signature = (param felt) (result felt)]; + builtin.ret v63; + }; + + private builtin.function @cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_pair::wit_import4(v64: felt, v65: felt, v66: i32) { + ^block10(v64: felt, v65: felt, v66: i32): + v67, v68 = hir.call v64, v65 : felt, felt #[callee = miden:cross-ctx-account-word/foo@1.0.0/process-pair] #[signature = (cc canon-lower) (param felt) (param felt) (result felt felt)]; + v527 = arith.constant 0 : i32; + v69 = arith.constant 0 : i32; + v70 = arith.add v66, v69 : i32 #[overflow = unchecked]; + v72 = arith.add v70, v527 : i32 #[overflow = unchecked]; + v73 = hir.int_to_ptr v72 : ptr; + hir.store v73, v67; + v526 = arith.constant 0 : i32; + v74 = arith.constant 4 : i32; + v75 = arith.add v66, v74 : i32 #[overflow = unchecked]; + v77 = arith.add v75, v526 : i32 #[overflow = unchecked]; + v78 = hir.int_to_ptr v77 : ptr; + hir.store v78, v68; + builtin.ret ; + }; + + private builtin.function @cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_triple::wit_import5(v79: felt, v80: felt, v81: felt, v82: i32) { + ^block12(v79: felt, v80: felt, v81: felt, v82: i32): + v83, v84, v85 = hir.call v79, v80, v81 : felt, felt, felt #[callee = miden:cross-ctx-account-word/foo@1.0.0/process-triple] #[signature = (cc canon-lower) (param felt) (param felt) (param felt) (result felt felt felt)]; + v530 = arith.constant 0 : i32; + v86 = arith.constant 0 : i32; + v87 = arith.add v82, v86 : i32 #[overflow = unchecked]; + v89 = arith.add v87, v530 : i32 #[overflow = unchecked]; + v90 = hir.int_to_ptr v89 : ptr; + hir.store v90, v83; + v529 = arith.constant 0 : i32; + v91 = arith.constant 4 : i32; + v92 = arith.add v82, v91 : i32 #[overflow = unchecked]; + v94 = arith.add v92, v529 : i32 #[overflow = unchecked]; + v95 = hir.int_to_ptr v94 : ptr; + hir.store v95, v84; + v528 = arith.constant 0 : i32; + v96 = arith.constant 8 : i32; + v97 = arith.add v82, v96 : i32 #[overflow = unchecked]; + v99 = arith.add v97, v528 : i32 #[overflow = unchecked]; + v100 = hir.int_to_ptr v99 : ptr; + hir.store v100, v85; + builtin.ret ; + }; + + private builtin.function @cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_mixed::wit_import4(v101: i64, v102: felt, v103: i32, v104: felt, v105: i32, v106: i32, v107: i32, v108: i32) { + ^block14(v101: i64, v102: felt, v103: i32, v104: felt, v105: i32, v106: i32, v107: i32, v108: i32): + v109, v110, v111, v112, v113, v114, v115 = hir.call v101, v102, v103, v104, v105, v106, v107 : i64, felt, i32, felt, i32, i32, i32 #[callee = miden:cross-ctx-account-word/foo@1.0.0/process-mixed] #[signature = (cc canon-lower) (param i64) (param felt) (param i32) (param felt) (param (zext) i32) (param (zext) i32) (param (zext) i32) (result i64 felt i32 felt i32 i32 i32)]; + v119 = hir.bitcast v109 : u64; + v116 = arith.constant 0 : i32; + v117 = arith.add v108, v116 : i32 #[overflow = unchecked]; + v118 = hir.int_to_ptr v117 : ptr; + hir.store v118, v119; + v532 = arith.constant 0 : i32; + v120 = arith.constant 8 : i32; + v121 = arith.add v108, v120 : i32 #[overflow = unchecked]; + v123 = arith.add v121, v532 : i32 #[overflow = unchecked]; + v124 = hir.int_to_ptr v123 : ptr; + hir.store v124, v110; + v128 = hir.bitcast v111 : u32; + v125 = arith.constant 12 : i32; + v126 = arith.add v108, v125 : i32 #[overflow = unchecked]; + v127 = hir.int_to_ptr v126 : ptr; + hir.store v127, v128; + v531 = arith.constant 0 : i32; + v129 = arith.constant 16 : i32; + v130 = arith.add v108, v129 : i32 #[overflow = unchecked]; + v132 = arith.add v130, v531 : i32 #[overflow = unchecked]; + v133 = hir.int_to_ptr v132 : ptr; + hir.store v133, v112; + v137 = hir.bitcast v113 : u8; + v134 = arith.constant 20 : i32; + v135 = arith.add v108, v134 : i32 #[overflow = unchecked]; + v136 = hir.int_to_ptr v135 : ptr; + hir.store v136, v137; + v141 = hir.bitcast v114 : i1; + v138 = arith.constant 21 : i32; + v139 = arith.add v108, v138 : i32 #[overflow = unchecked]; + v140 = hir.int_to_ptr v139 : ptr; + hir.store v140, v141; + v145 = hir.bitcast v115 : u16; + v142 = arith.constant 22 : i32; + v143 = arith.add v108, v142 : i32 #[overflow = unchecked]; + v144 = hir.int_to_ptr v143 : ptr; + hir.store v144, v145; + builtin.ret ; + }; + + private builtin.function @cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_nested::wit_import6(v146: felt, v147: felt, v148: felt, v149: i32) { + ^block16(v146: felt, v147: felt, v148: felt, v149: i32): + v150, v151, v152 = hir.call v146, v147, v148 : felt, felt, felt #[callee = miden:cross-ctx-account-word/foo@1.0.0/process-nested] #[signature = (cc canon-lower) (param felt) (param felt) (param felt) (result felt felt felt)]; + v153 = arith.constant 0 : i32; + v154 = arith.add v149, v153 : i32 #[overflow = unchecked]; + v535 = arith.constant 0 : i32; + v536 = arith.constant 0 : i32; + v156 = arith.add v154, v536 : i32 #[overflow = unchecked]; + v158 = arith.add v156, v535 : i32 #[overflow = unchecked]; + v159 = hir.int_to_ptr v158 : ptr; + hir.store v159, v150; + v534 = arith.constant 0 : i32; + v160 = arith.constant 4 : i32; + v161 = arith.add v154, v160 : i32 #[overflow = unchecked]; + v163 = arith.add v161, v534 : i32 #[overflow = unchecked]; + v164 = hir.int_to_ptr v163 : ptr; + hir.store v164, v151; + v533 = arith.constant 0 : i32; + v165 = arith.constant 8 : i32; + v166 = arith.add v149, v165 : i32 #[overflow = unchecked]; + v168 = arith.add v166, v533 : i32 #[overflow = unchecked]; + v169 = hir.int_to_ptr v168 : ptr; + hir.store v169, v152; + builtin.ret ; + }; + + private builtin.function @__wasm_call_ctors() { + ^block20: + builtin.ret ; + }; + + private builtin.function @cross_ctx_note_word::bindings::__link_custom_section_describing_imports() { + ^block22: + builtin.ret ; + }; + + private builtin.function @miden:base/note-script@1.0.0#run(v170: felt, v171: felt, v172: felt, v173: felt) { + ^block24(v170: felt, v171: felt, v172: felt, v173: felt): + v177 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note_word/__stack_pointer : ptr + v178 = hir.bitcast v177 : ptr; + v179 = hir.load v178 : i32; + v180 = arith.constant 32 : i32; + v181 = arith.sub v179, v180 : i32 #[overflow = wrapping]; + v182 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note_word/__stack_pointer : ptr + v183 = hir.bitcast v182 : ptr; + hir.store v183, v181; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/wit_bindgen::rt::run_ctors_once() + v184 = arith.constant 2 : i32; + v185 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v184) : felt + v186 = arith.constant 3 : i32; + v187 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v186) : felt + v188 = arith.constant 4 : i32; + v189 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v188) : felt + v190 = arith.constant 5 : i32; + v191 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v190) : felt + v192 = arith.constant 8 : i32; + v193 = arith.add v181, v192 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_word::wit_import7(v185, v187, v189, v191, v193) + v195 = arith.constant 20 : u32; + v194 = hir.bitcast v181 : u32; + v196 = arith.add v194, v195 : u32 #[overflow = checked]; + v197 = arith.constant 4 : u32; + v198 = arith.mod v196, v197 : u32; + hir.assertz v198 #[code = 250]; + v199 = hir.int_to_ptr v196 : ptr; + v200 = hir.load v199 : felt; + v202 = arith.constant 16 : u32; + v201 = hir.bitcast v181 : u32; + v203 = arith.add v201, v202 : u32 #[overflow = checked]; + v590 = arith.constant 4 : u32; + v205 = arith.mod v203, v590 : u32; + hir.assertz v205 #[code = 250]; + v206 = hir.int_to_ptr v203 : ptr; + v207 = hir.load v206 : felt; + v209 = arith.constant 12 : u32; + v208 = hir.bitcast v181 : u32; + v210 = arith.add v208, v209 : u32 #[overflow = checked]; + v589 = arith.constant 4 : u32; + v212 = arith.mod v210, v589 : u32; + hir.assertz v212 #[code = 250]; + v213 = hir.int_to_ptr v210 : ptr; + v214 = hir.load v213 : felt; + v216 = arith.constant 8 : u32; + v215 = hir.bitcast v181 : u32; + v217 = arith.add v215, v216 : u32 #[overflow = checked]; + v588 = arith.constant 4 : u32; + v219 = arith.mod v217, v588 : u32; + hir.assertz v219 #[code = 250]; + v220 = hir.int_to_ptr v217 : ptr; + v221 = hir.load v220 : felt; + v587 = arith.constant 3 : i32; + v223 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v587) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v221, v223) + v586 = arith.constant 5 : i32; + v225 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v586) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v214, v225) + v226 = arith.constant 7 : i32; + v227 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v226) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v207, v227) + v228 = arith.constant 9 : i32; + v229 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v228) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v200, v229) + v585 = arith.constant 8 : i32; + v231 = arith.add v181, v585 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_another_word::wit_import7(v185, v187, v189, v191, v231) + v584 = arith.constant 20 : u32; + v232 = hir.bitcast v181 : u32; + v234 = arith.add v232, v584 : u32 #[overflow = checked]; + v583 = arith.constant 4 : u32; + v236 = arith.mod v234, v583 : u32; + hir.assertz v236 #[code = 250]; + v237 = hir.int_to_ptr v234 : ptr; + v238 = hir.load v237 : felt; + v582 = arith.constant 16 : u32; + v239 = hir.bitcast v181 : u32; + v241 = arith.add v239, v582 : u32 #[overflow = checked]; + v581 = arith.constant 4 : u32; + v243 = arith.mod v241, v581 : u32; + hir.assertz v243 #[code = 250]; + v244 = hir.int_to_ptr v241 : ptr; + v245 = hir.load v244 : felt; + v580 = arith.constant 12 : u32; + v246 = hir.bitcast v181 : u32; + v248 = arith.add v246, v580 : u32 #[overflow = checked]; + v579 = arith.constant 4 : u32; + v250 = arith.mod v248, v579 : u32; + hir.assertz v250 #[code = 250]; + v251 = hir.int_to_ptr v248 : ptr; + v252 = hir.load v251 : felt; + v578 = arith.constant 8 : u32; + v253 = hir.bitcast v181 : u32; + v255 = arith.add v253, v578 : u32 #[overflow = checked]; + v577 = arith.constant 4 : u32; + v257 = arith.mod v255, v577 : u32; + hir.assertz v257 #[code = 250]; + v258 = hir.int_to_ptr v255 : ptr; + v259 = hir.load v258 : felt; + v576 = arith.constant 4 : i32; + v261 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v576) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v259, v261) + v262 = arith.constant 6 : i32; + v263 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v262) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v252, v263) + v575 = arith.constant 8 : i32; + v265 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v575) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v245, v265) + v266 = arith.constant 10 : i32; + v267 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v266) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v238, v267) + v574 = arith.constant 9 : i32; + v269 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v574) : felt + v270 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_felt::wit_import1(v269) : felt + v271 = arith.constant 12 : i32; + v272 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v271) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v270, v272) + v573 = arith.constant 10 : i32; + v274 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v573) : felt + v275 = arith.constant 20 : i32; + v276 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v275) : felt + v572 = arith.constant 8 : u32; + v278 = hir.bitcast v181 : u32; + v280 = arith.add v278, v572 : u32 #[overflow = checked]; + v571 = arith.constant 8 : u32; + v282 = arith.mod v280, v571 : u32; + hir.assertz v282 #[code = 250]; + v277 = arith.constant 0 : i64; + v283 = hir.int_to_ptr v280 : ptr; + hir.store v283, v277; + v570 = arith.constant 8 : i32; + v285 = arith.add v181, v570 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_pair::wit_import4(v274, v276, v285) + v569 = arith.constant 12 : u32; + v286 = hir.bitcast v181 : u32; + v288 = arith.add v286, v569 : u32 #[overflow = checked]; + v568 = arith.constant 4 : u32; + v290 = arith.mod v288, v568 : u32; + hir.assertz v290 #[code = 250]; + v291 = hir.int_to_ptr v288 : ptr; + v292 = hir.load v291 : felt; + v567 = arith.constant 8 : u32; + v293 = hir.bitcast v181 : u32; + v295 = arith.add v293, v567 : u32 #[overflow = checked]; + v566 = arith.constant 4 : u32; + v297 = arith.mod v295, v566 : u32; + hir.assertz v297 #[code = 250]; + v298 = hir.int_to_ptr v295 : ptr; + v299 = hir.load v298 : felt; + v300 = arith.constant 14 : i32; + v301 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v300) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v299, v301) + v302 = arith.constant 24 : i32; + v303 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v302) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v292, v303) + v304 = arith.constant 100 : i32; + v305 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v304) : felt + v306 = arith.constant 200 : i32; + v307 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v306) : felt + v308 = arith.constant 300 : i32; + v309 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v308) : felt + v565 = arith.constant 8 : i32; + v311 = arith.add v181, v565 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_triple::wit_import5(v305, v307, v309, v311) + v564 = arith.constant 16 : u32; + v312 = hir.bitcast v181 : u32; + v314 = arith.add v312, v564 : u32 #[overflow = checked]; + v563 = arith.constant 4 : u32; + v316 = arith.mod v314, v563 : u32; + hir.assertz v316 #[code = 250]; + v317 = hir.int_to_ptr v314 : ptr; + v318 = hir.load v317 : felt; + v562 = arith.constant 12 : u32; + v319 = hir.bitcast v181 : u32; + v321 = arith.add v319, v562 : u32 #[overflow = checked]; + v561 = arith.constant 4 : u32; + v323 = arith.mod v321, v561 : u32; + hir.assertz v323 #[code = 250]; + v324 = hir.int_to_ptr v321 : ptr; + v325 = hir.load v324 : felt; + v560 = arith.constant 8 : u32; + v326 = hir.bitcast v181 : u32; + v328 = arith.add v326, v560 : u32 #[overflow = checked]; + v559 = arith.constant 4 : u32; + v330 = arith.mod v328, v559 : u32; + hir.assertz v330 #[code = 250]; + v331 = hir.int_to_ptr v328 : ptr; + v332 = hir.load v331 : felt; + v333 = arith.constant 105 : i32; + v334 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v333) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v332, v334) + v335 = arith.constant 205 : i32; + v336 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v335) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v325, v336) + v337 = arith.constant 305 : i32; + v338 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v337) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v318, v338) + v340 = arith.constant -4294967302 : i64; + v341 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u64_unchecked(v340) : felt + v343 = arith.constant 50 : i32; + v344 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v343) : felt + v557 = arith.constant 8 : i32; + v349 = arith.add v181, v557 : i32 #[overflow = wrapping]; + v558 = arith.constant 3 : i32; + v174 = arith.constant 0 : i32; + v345 = arith.constant 111 : i32; + v342 = arith.constant -11 : i32; + v339 = arith.constant -1001 : i64; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_mixed::wit_import4(v339, v341, v342, v344, v345, v174, v558, v349) + v556 = arith.constant 8 : u32; + v350 = hir.bitcast v181 : u32; + v352 = arith.add v350, v556 : u32 #[overflow = checked]; + v555 = arith.constant 8 : u32; + v354 = arith.mod v352, v555 : u32; + hir.assertz v354 #[code = 250]; + v355 = hir.int_to_ptr v352 : ptr; + v356 = hir.load v355 : i64; + v554 = arith.constant 0 : i32; + v357 = arith.constant -1 : i64; + v358 = arith.eq v356, v357 : i1; + v359 = arith.zext v358 : u32; + v360 = hir.bitcast v359 : i32; + v362 = arith.neq v360, v554 : i1; + cf.cond_br v362 ^block26, ^block27; + ^block26: + v364 = arith.constant 30 : u32; + v363 = hir.bitcast v181 : u32; + v365 = arith.add v363, v364 : u32 #[overflow = checked]; + v366 = arith.constant 2 : u32; + v367 = arith.mod v365, v366 : u32; + hir.assertz v367 #[code = 250]; + v368 = hir.int_to_ptr v365 : ptr; + v369 = hir.load v368 : u16; + v373 = arith.constant 29 : u32; + v372 = hir.bitcast v181 : u32; + v374 = arith.add v372, v373 : u32 #[overflow = checked]; + v375 = hir.int_to_ptr v374 : ptr; + v376 = hir.load v375 : u8; + v380 = arith.constant 28 : u32; + v379 = hir.bitcast v181 : u32; + v381 = arith.add v379, v380 : u32 #[overflow = checked]; + v382 = hir.int_to_ptr v381 : ptr; + v383 = hir.load v382 : u8; + v387 = arith.constant 24 : u32; + v386 = hir.bitcast v181 : u32; + v388 = arith.add v386, v387 : u32 #[overflow = checked]; + v553 = arith.constant 4 : u32; + v390 = arith.mod v388, v553 : u32; + hir.assertz v390 #[code = 250]; + v391 = hir.int_to_ptr v388 : ptr; + v392 = hir.load v391 : felt; + v552 = arith.constant 20 : u32; + v393 = hir.bitcast v181 : u32; + v395 = arith.add v393, v552 : u32 #[overflow = checked]; + v551 = arith.constant 4 : u32; + v397 = arith.mod v395, v551 : u32; + hir.assertz v397 #[code = 250]; + v398 = hir.int_to_ptr v395 : ptr; + v399 = hir.load v398 : i32; + v550 = arith.constant 16 : u32; + v400 = hir.bitcast v181 : u32; + v402 = arith.add v400, v550 : u32 #[overflow = checked]; + v549 = arith.constant 4 : u32; + v404 = arith.mod v402, v549 : u32; + hir.assertz v404 #[code = 250]; + v405 = hir.int_to_ptr v402 : ptr; + v406 = hir.load v405 : felt; + v407 = arith.constant -4294967296 : i64; + v408 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u64_unchecked(v407) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v406, v408) + v409 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/>::from(v399) : felt + v410 = arith.constant -1 : i32; + v411 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v410) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v409, v411) + v412 = arith.constant 57 : i32; + v413 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v412) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v392, v413) + v384 = arith.zext v383 : u32; + v385 = hir.bitcast v384 : i32; + v414 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/>::from(v385) : felt + v415 = arith.constant 122 : i32; + v416 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v415) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v414, v416) + v548 = arith.constant 0 : i32; + v417 = arith.constant 255 : i32; + v377 = arith.zext v376 : u32; + v378 = hir.bitcast v377 : i32; + v418 = arith.band v378, v417 : i32; + v420 = arith.neq v418, v548 : i1; + v421 = arith.zext v420 : u32; + v422 = hir.bitcast v421 : i32; + v423 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v422) : felt + v424 = arith.constant 1 : i32; + v425 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v424) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v423, v425) + v370 = arith.zext v369 : u32; + v371 = hir.bitcast v370 : i32; + v426 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/>::from(v371) : felt + v547 = arith.constant 12 : i32; + v428 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v547) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v426, v428) + v429 = arith.constant 30 : i32; + v430 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v429) : felt + v431 = arith.constant 40 : i32; + v432 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v431) : felt + v546 = arith.constant 50 : i32; + v434 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v546) : felt + v545 = arith.constant 8 : i32; + v436 = arith.add v181, v545 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_nested::wit_import6(v430, v432, v434, v436) + v544 = arith.constant 16 : u32; + v437 = hir.bitcast v181 : u32; + v439 = arith.add v437, v544 : u32 #[overflow = checked]; + v543 = arith.constant 4 : u32; + v441 = arith.mod v439, v543 : u32; + hir.assertz v441 #[code = 250]; + v442 = hir.int_to_ptr v439 : ptr; + v443 = hir.load v442 : felt; + v542 = arith.constant 12 : u32; + v444 = hir.bitcast v181 : u32; + v446 = arith.add v444, v542 : u32 #[overflow = checked]; + v541 = arith.constant 4 : u32; + v448 = arith.mod v446, v541 : u32; + hir.assertz v448 #[code = 250]; + v449 = hir.int_to_ptr v446 : ptr; + v450 = hir.load v449 : felt; + v540 = arith.constant 8 : u32; + v451 = hir.bitcast v181 : u32; + v453 = arith.add v451, v540 : u32 #[overflow = checked]; + v539 = arith.constant 4 : u32; + v455 = arith.mod v453, v539 : u32; + hir.assertz v455 #[code = 250]; + v456 = hir.int_to_ptr v453 : ptr; + v457 = hir.load v456 : felt; + v458 = arith.constant 38 : i32; + v459 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v458) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v457, v459) + v460 = arith.constant 48 : i32; + v461 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v460) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v450, v461) + v462 = arith.constant 58 : i32; + v463 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::from_u32(v462) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/intrinsics::felt::assert_eq(v443, v463) + v538 = arith.constant 32 : i32; + v465 = arith.add v181, v538 : i32 #[overflow = wrapping]; + v466 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note_word/__stack_pointer : ptr + v467 = hir.bitcast v466 : ptr; + hir.store v467, v465; + builtin.ret ; + ^block27: + ub.unreachable ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block28: + v469 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note_word/GOT.data.internal.__memory_base : ptr + v470 = hir.bitcast v469 : ptr; + v471 = hir.load v470 : i32; + v472 = arith.constant 1048588 : i32; + v473 = arith.add v471, v472 : i32 #[overflow = wrapping]; + v474 = hir.bitcast v473 : u32; + v475 = hir.int_to_ptr v474 : ptr; + v476 = hir.load v475 : u8; + v468 = arith.constant 0 : i32; + v477 = arith.zext v476 : u32; + v478 = hir.bitcast v477 : i32; + v480 = arith.neq v478, v468 : i1; + scf.if v480{ + ^block30: + scf.yield ; + } else { + ^block31: + v481 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note_word/GOT.data.internal.__memory_base : ptr + v482 = hir.bitcast v481 : ptr; + v483 = hir.load v482 : i32; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/__wasm_call_ctors() + v592 = arith.constant 1 : u8; + v594 = arith.constant 1048588 : i32; + v485 = arith.add v483, v594 : i32 #[overflow = wrapping]; + v489 = hir.bitcast v485 : u32; + v490 = hir.int_to_ptr v489 : ptr; + hir.store v490, v592; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @>::from(v491: i32) -> felt { + ^block32(v491: i32): + v493 = hir.bitcast v491 : felt; + builtin.ret v493; + }; + + private builtin.function @>::from(v494: i32) -> felt { + ^block34(v494: i32): + v496 = arith.constant 65535 : i32; + v497 = arith.band v494, v496 : i32; + v498 = hir.bitcast v497 : felt; + builtin.ret v498; + }; + + private builtin.function @>::from(v499: i32) -> felt { + ^block36(v499: i32): + v501 = arith.constant 255 : i32; + v502 = arith.band v499, v501 : i32; + v503 = hir.bitcast v502 : felt; + builtin.ret v503; + }; + + private builtin.function @intrinsics::felt::from_u64_unchecked(v504: i64) -> felt { + ^block38(v504: i64): + v505 = hir.cast v504 : felt; + builtin.ret v505; + }; + + private builtin.function @intrinsics::felt::from_u32(v507: i32) -> felt { + ^block40(v507: i32): + v508 = hir.bitcast v507 : felt; + builtin.ret v508; + }; + + private builtin.function @intrinsics::felt::assert_eq(v510: felt, v511: felt) { + ^block42(v510: felt, v511: felt): + hir.assert_eq v510, v511; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x000000010000000100000001; + }; + + public builtin.function @run(v512: felt, v513: felt, v514: felt, v515: felt) { + ^block44(v512: felt, v513: felt, v514: felt, v515: felt): + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word/miden:base/note-script@1.0.0#run(v512, v513, v514, v515) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note_word.masm b/tests/integration/expected/rust_sdk/cross_ctx_note_word.masm new file mode 100644 index 000000000..bed2bbc74 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note_word.masm @@ -0,0 +1,1491 @@ +# mod miden:base/note-script@1.0.0 + +export.run + exec.::miden:base/note-script@1.0.0::init + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::miden:base/note-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[511068398781876708,4034132635148770913,11946245983825022717,413851799653899214] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/note-script@1.0.0::cross_ctx_note_word + +proc.cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_word::wit_import7 + trace.240 + nop + call.::miden:cross-ctx-account-word/foo@1.0.0::process-word + trace.252 + nop + push.0 + movup.5 + u32wrapping_add + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.4 + dup.2 + u32wrapping_add + u32wrapping_add + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.8 + dup.2 + u32wrapping_add + u32wrapping_add + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.12 + movup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_another_word::wit_import7 + trace.240 + nop + call.::miden:cross-ctx-account-word/foo@1.0.0::process-another-word + trace.252 + nop + push.0 + movup.5 + u32wrapping_add + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.4 + dup.2 + u32wrapping_add + u32wrapping_add + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.8 + dup.2 + u32wrapping_add + u32wrapping_add + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.12 + movup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_felt::wit_import1 + trace.240 + nop + call.::miden:cross-ctx-account-word/foo@1.0.0::process-felt + trace.252 + nop +end + +proc.cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_pair::wit_import4 + trace.240 + nop + call.::miden:cross-ctx-account-word/foo@1.0.0::process-pair + trace.252 + nop + push.0 + push.0 + dup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.4 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_triple::wit_import5 + trace.240 + nop + call.::miden:cross-ctx-account-word/foo@1.0.0::process-triple + trace.252 + nop + push.0 + push.0 + dup.5 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.4 + dup.4 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.8 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_mixed::wit_import4 + trace.240 + nop + call.::miden:cross-ctx-account-word/foo@1.0.0::process-mixed + trace.252 + nop + push.0 + dup.9 + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.0 + push.8 + dup.8 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.6 + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.16 + dup.6 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.20 + dup.4 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + push.21 + dup.3 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.1 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + push.22 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.65535 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store +end + +proc.cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_nested::wit_import6 + trace.240 + nop + call.::miden:cross-ctx-account-word/foo@1.0.0::process-nested + trace.252 + nop + push.0 + dup.4 + u32wrapping_add + push.0 + push.0 + dup.2 + u32wrapping_add + u32wrapping_add + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.4 + movup.2 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.0 + push.8 + movup.3 + u32wrapping_add + u32wrapping_add + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.__wasm_call_ctors + nop +end + +proc.cross_ctx_note_word::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/note-script@1.0.0#run + drop + drop + drop + drop + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.3 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.4 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.5 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + dup.5 + u32wrapping_add + dup.4 + dup.4 + dup.4 + dup.4 + swap.1 + swap.2 + swap.1 + swap.3 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_word::wit_import7 + trace.252 + nop + push.20 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.16 + dup.6 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + dup.7 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.8 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.3 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.5 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.7 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.9 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.8 + dup.5 + u32wrapping_add + swap.1 + swap.3 + swap.1 + swap.4 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_another_word::wit_import7 + trace.252 + nop + push.20 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.16 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.6 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.8 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.10 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.9 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_felt::wit_import1 + trace.252 + nop + push.12 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.10 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.20 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + push.0 + movup.2 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.8 + dup.3 + u32wrapping_add + swap.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_pair::wit_import4 + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.14 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.24 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.100 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.200 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.300 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + dup.4 + u32wrapping_add + movdn.3 + swap.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_triple::wit_import5 + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.105 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.205 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.305 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.4294967290 + push.4294967294 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u64_unchecked + trace.252 + nop + push.50 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + dup.3 + u32wrapping_add + push.3 + push.0 + push.111 + push.4294967285 + push.4294966295 + push.4294967295 + movup.2 + swap.3 + swap.5 + swap.7 + swap.4 + swap.6 + swap.8 + movdn.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_mixed::wit_import4 + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.0 + push.4294967295 + push.4294967295 + movup.4 + movup.4 + trace.240 + nop + exec.::std::math::u64::eq + trace.252 + nop + neq + if.true + push.30 + dup.1 + add + u32assert + push.2 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.65535 + u32and + push.29 + dup.2 + add + u32assert + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.28 + dup.3 + add + u32assert + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.24 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.20 + dup.5 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + dup.6 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.0 + push.4294967295 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u64_unchecked + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::>::from + trace.252 + nop + push.4294967295 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.57 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::>::from + trace.252 + nop + push.122 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.0 + push.255 + movup.2 + u32and + neq + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::>::from + trace.252 + nop + push.12 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.30 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.40 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.50 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + dup.4 + u32wrapping_add + movdn.3 + swap.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_nested::wit_import6 + trace.252 + nop + push.16 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.38 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.48 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.58 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::intrinsics::felt::assert_eq + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + drop + push.0 + assert + end +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048588 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048588 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.>::from + nop +end + +proc.>::from + push.65535 + u32and +end + +proc.>::from + push.255 + u32and +end + +proc.intrinsics::felt::from_u64_unchecked + dup.1 + dup.1 + push.1 + push.4294967295 + trace.240 + nop + exec.::std::math::u64::lt + trace.252 + nop + assert + mul.4294967296 + add +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note_word.wat b/tests/integration/expected/rust_sdk/cross_ctx_note_word.wat new file mode 100644 index 000000000..fb82b6fd4 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note_word.wat @@ -0,0 +1,522 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (alias export 0 "word" (type (;1;))) + (alias export 0 "felt" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "word" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "felt" (type (eq 2))) + (type (;4;) (record (field "first" 3) (field "second" 3))) + (export (;5;) "pair" (type (eq 4))) + (type (;6;) (record (field "x" 3) (field "y" 3) (field "z" 3))) + (export (;7;) "triple" (type (eq 6))) + (type (;8;) (record (field "f" u64) (field "a" 3) (field "b" u32) (field "c" 3) (field "d" u8) (field "e" bool) (field "g" u16))) + (export (;9;) "mixed-struct" (type (eq 8))) + (type (;10;) (record (field "inner" 5) (field "value" 3))) + (export (;11;) "nested-struct" (type (eq 10))) + (type (;12;) (func (param "input" 1) (result 1))) + (export (;0;) "process-word" (func (type 12))) + (export (;1;) "process-another-word" (func (type 12))) + (type (;13;) (func (param "input" 3) (result 3))) + (export (;2;) "process-felt" (func (type 13))) + (type (;14;) (func (param "input" 5) (result 5))) + (export (;3;) "process-pair" (func (type 14))) + (type (;15;) (func (param "input" 7) (result 7))) + (export (;4;) "process-triple" (func (type 15))) + (type (;16;) (func (param "input" 9) (result 9))) + (export (;5;) "process-mixed" (func (type 16))) + (type (;17;) (func (param "input" 11) (result 11))) + (export (;6;) "process-nested" (func (type 17))) + ) + ) + (import "miden:cross-ctx-account-word/foo@1.0.0" (instance (;1;) (type 3))) + (core module (;0;) + (type (;0;) (func (param f32 f32 f32 f32 i32))) + (type (;1;) (func (param f32) (result f32))) + (type (;2;) (func (param f32 f32 i32))) + (type (;3;) (func (param f32 f32 f32 i32))) + (type (;4;) (func (param i64 f32 i32 f32 i32 i32 i32 i32))) + (type (;5;) (func)) + (type (;6;) (func (param f32 f32 f32 f32))) + (type (;7;) (func (param i32) (result f32))) + (type (;8;) (func (param i64) (result f32))) + (type (;9;) (func (param f32 f32))) + (import "miden:cross-ctx-account-word/foo@1.0.0" "process-word" (func $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_word::wit_import7 (;0;) (type 0))) + (import "miden:cross-ctx-account-word/foo@1.0.0" "process-another-word" (func $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_another_word::wit_import7 (;1;) (type 0))) + (import "miden:cross-ctx-account-word/foo@1.0.0" "process-felt" (func $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_felt::wit_import1 (;2;) (type 1))) + (import "miden:cross-ctx-account-word/foo@1.0.0" "process-pair" (func $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_pair::wit_import4 (;3;) (type 2))) + (import "miden:cross-ctx-account-word/foo@1.0.0" "process-triple" (func $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_triple::wit_import5 (;4;) (type 3))) + (import "miden:cross-ctx-account-word/foo@1.0.0" "process-mixed" (func $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_mixed::wit_import4 (;5;) (type 4))) + (import "miden:cross-ctx-account-word/foo@1.0.0" "process-nested" (func $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_nested::wit_import6 (;6;) (type 3))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/note-script@1.0.0#run" (func $miden:base/note-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $cross_ctx_note_word::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;7;) (type 5)) + (func $cross_ctx_note_word::bindings::__link_custom_section_describing_imports (;8;) (type 5)) + (func $miden:base/note-script@1.0.0#run (;9;) (type 6) (param f32 f32 f32 f32) + (local i32 f32 f32 f32 f32 f32 f32 f32 i32 i32 i32 i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + i32.const 2 + call $intrinsics::felt::from_u32 + local.tee 5 + i32.const 3 + call $intrinsics::felt::from_u32 + local.tee 6 + i32.const 4 + call $intrinsics::felt::from_u32 + local.tee 7 + i32.const 5 + call $intrinsics::felt::from_u32 + local.tee 8 + local.get 4 + i32.const 8 + i32.add + call $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_word::wit_import7 + local.get 4 + f32.load offset=20 + local.set 9 + local.get 4 + f32.load offset=16 + local.set 10 + local.get 4 + f32.load offset=12 + local.set 11 + local.get 4 + f32.load offset=8 + i32.const 3 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 11 + i32.const 5 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 10 + i32.const 7 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 9 + i32.const 9 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + local.get 6 + local.get 7 + local.get 8 + local.get 4 + i32.const 8 + i32.add + call $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_another_word::wit_import7 + local.get 4 + f32.load offset=20 + local.set 5 + local.get 4 + f32.load offset=16 + local.set 6 + local.get 4 + f32.load offset=12 + local.set 7 + local.get 4 + f32.load offset=8 + i32.const 4 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 7 + i32.const 6 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 6 + i32.const 8 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + i32.const 10 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + i32.const 9 + call $intrinsics::felt::from_u32 + call $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_felt::wit_import1 + i32.const 12 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + i32.const 10 + call $intrinsics::felt::from_u32 + local.set 5 + i32.const 20 + call $intrinsics::felt::from_u32 + local.set 6 + local.get 4 + i64.const 0 + i64.store offset=8 + local.get 5 + local.get 6 + local.get 4 + i32.const 8 + i32.add + call $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_pair::wit_import4 + local.get 4 + f32.load offset=12 + local.set 5 + local.get 4 + f32.load offset=8 + i32.const 14 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + i32.const 24 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + i32.const 100 + call $intrinsics::felt::from_u32 + i32.const 200 + call $intrinsics::felt::from_u32 + i32.const 300 + call $intrinsics::felt::from_u32 + local.get 4 + i32.const 8 + i32.add + call $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_triple::wit_import5 + local.get 4 + f32.load offset=16 + local.set 5 + local.get 4 + f32.load offset=12 + local.set 6 + local.get 4 + f32.load offset=8 + i32.const 105 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 6 + i32.const 205 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + i32.const 305 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + i64.const -1001 + i64.const -4294967302 + call $intrinsics::felt::from_u64_unchecked + i32.const -11 + i32.const 50 + call $intrinsics::felt::from_u32 + i32.const 111 + i32.const 0 + i32.const 3 + local.get 4 + i32.const 8 + i32.add + call $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_mixed::wit_import4 + block ;; label = @1 + local.get 4 + i64.load offset=8 + i64.const -1 + i64.eq + br_if 0 (;@1;) + unreachable + end + local.get 4 + i32.load16_u offset=30 + local.set 12 + local.get 4 + i32.load8_u offset=29 + local.set 13 + local.get 4 + i32.load8_u offset=28 + local.set 14 + local.get 4 + f32.load offset=24 + local.set 5 + local.get 4 + i32.load offset=20 + local.set 15 + local.get 4 + f32.load offset=16 + i64.const -4294967296 + call $intrinsics::felt::from_u64_unchecked + call $intrinsics::felt::assert_eq + local.get 15 + call $>::from + i32.const -1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + i32.const 57 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 14 + call $>::from + i32.const 122 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 13 + i32.const 255 + i32.and + i32.const 0 + i32.ne + call $intrinsics::felt::from_u32 + i32.const 1 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 12 + call $>::from + i32.const 12 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + i32.const 30 + call $intrinsics::felt::from_u32 + i32.const 40 + call $intrinsics::felt::from_u32 + i32.const 50 + call $intrinsics::felt::from_u32 + local.get 4 + i32.const 8 + i32.add + call $cross_ctx_note_word::bindings::miden::cross_ctx_account_word::foo::process_nested::wit_import6 + local.get 4 + f32.load offset=16 + local.set 5 + local.get 4 + f32.load offset=12 + local.set 6 + local.get 4 + f32.load offset=8 + i32.const 38 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 6 + i32.const 48 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 5 + i32.const 58 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + local.get 4 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $wit_bindgen::rt::run_ctors_once (;10;) (type 5) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048588 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048588 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $>::from (;11;) (type 7) (param i32) (result f32) + local.get 0 + f32.reinterpret_i32 + ) + (func $>::from (;12;) (type 7) (param i32) (result f32) + local.get 0 + i32.const 65535 + i32.and + f32.reinterpret_i32 + ) + (func $>::from (;13;) (type 7) (param i32) (result f32) + local.get 0 + i32.const 255 + i32.and + f32.reinterpret_i32 + ) + (func $intrinsics::felt::from_u64_unchecked (;14;) (type 8) (param i64) (result f32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;15;) (type 7) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::assert_eq (;16;) (type 9) (param f32 f32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00\01\00\00\00") + ) + (core module (;1;) + (type (;0;) (func (param f32 f32 f32 f32 i32))) + (type (;1;) (func (param f32 f32 i32))) + (type (;2;) (func (param f32 f32 f32 i32))) + (type (;3;) (func (param i64 f32 i32 f32 i32 i32 i32 i32))) + (table (;0;) 6 6 funcref) + (export "0" (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-word)) + (export "1" (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-another-word)) + (export "2" (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-pair)) + (export "3" (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-triple)) + (export "4" (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-mixed)) + (export "5" (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-nested)) + (export "$imports" (table 0)) + (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-word (;0;) (type 0) (param f32 f32 f32 f32 i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + local.get 4 + i32.const 0 + call_indirect (type 0) + ) + (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-another-word (;1;) (type 0) (param f32 f32 f32 f32 i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + local.get 4 + i32.const 1 + call_indirect (type 0) + ) + (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-pair (;2;) (type 1) (param f32 f32 i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 2 + call_indirect (type 1) + ) + (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-triple (;3;) (type 2) (param f32 f32 f32 i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + i32.const 3 + call_indirect (type 2) + ) + (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-mixed (;4;) (type 3) (param i64 f32 i32 f32 i32 i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + local.get 4 + local.get 5 + local.get 6 + local.get 7 + i32.const 4 + call_indirect (type 3) + ) + (func $indirect-miden:cross-ctx-account-word/foo@1.0.0-process-nested (;5;) (type 2) (param f32 f32 f32 i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + i32.const 5 + call_indirect (type 2) + ) + ) + (core module (;2;) + (type (;0;) (func (param f32 f32 f32 f32 i32))) + (type (;1;) (func (param f32 f32 i32))) + (type (;2;) (func (param f32 f32 f32 i32))) + (type (;3;) (func (param i64 f32 i32 f32 i32 i32 i32 i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 0))) + (import "" "2" (func (;2;) (type 1))) + (import "" "3" (func (;3;) (type 2))) + (import "" "4" (func (;4;) (type 3))) + (import "" "5" (func (;5;) (type 2))) + (import "" "$imports" (table (;0;) 6 6 funcref)) + (elem (;0;) (i32.const 0) func 0 1 2 3 4 5) + ) + (core instance (;0;) (instantiate 1)) + (alias export 0 "word" (type (;4;))) + (alias core export 0 "0" (core func (;0;))) + (alias core export 0 "1" (core func (;1;))) + (alias export 1 "process-felt" (func (;0;))) + (core func (;2;) (canon lower (func 0))) + (alias core export 0 "2" (core func (;3;))) + (alias core export 0 "3" (core func (;4;))) + (alias core export 0 "4" (core func (;5;))) + (alias core export 0 "5" (core func (;6;))) + (core instance (;1;) + (export "process-word" (func 0)) + (export "process-another-word" (func 1)) + (export "process-felt" (func 2)) + (export "process-pair" (func 3)) + (export "process-triple" (func 4)) + (export "process-mixed" (func 5)) + (export "process-nested" (func 6)) + ) + (core instance (;2;) (instantiate 0 + (with "miden:cross-ctx-account-word/foo@1.0.0" (instance 1)) + ) + ) + (alias core export 2 "memory" (core memory (;0;))) + (alias core export 0 "$imports" (core table (;0;))) + (alias export 1 "process-word" (func (;1;))) + (core func (;7;) (canon lower (func 1) (memory 0))) + (alias export 1 "process-another-word" (func (;2;))) + (core func (;8;) (canon lower (func 2) (memory 0))) + (alias export 1 "process-pair" (func (;3;))) + (core func (;9;) (canon lower (func 3) (memory 0))) + (alias export 1 "process-triple" (func (;4;))) + (core func (;10;) (canon lower (func 4) (memory 0))) + (alias export 1 "process-mixed" (func (;5;))) + (core func (;11;) (canon lower (func 5) (memory 0))) + (alias export 1 "process-nested" (func (;6;))) + (core func (;12;) (canon lower (func 6) (memory 0))) + (core instance (;3;) + (export "$imports" (table 0)) + (export "0" (func 7)) + (export "1" (func 8)) + (export "2" (func 9)) + (export "3" (func 10)) + (export "4" (func 11)) + (export "5" (func 12)) + ) + (core instance (;4;) (instantiate 2 + (with "" (instance 3)) + ) + ) + (type (;5;) (func (param "arg" 4))) + (alias core export 2 "miden:base/note-script@1.0.0#run" (core func (;13;))) + (func (;7;) (type 5) (canon lift (core func 13))) + (alias export 0 "felt" (type (;6;))) + (alias export 0 "word" (type (;7;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;2;) (instantiate 0 + (with "import-func-run" (func 7)) + (with "import-type-felt" (type 6)) + (with "import-type-word" (type 7)) + (with "import-type-word0" (type 4)) + ) + ) + (export (;3;) "miden:base/note-script@1.0.0" (instance 2)) +) diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.hir b/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.hir new file mode 100644 index 000000000..ae4f5f5df --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.hir @@ -0,0 +1,123 @@ +builtin.component miden:base/note-script@1.0.0 { + builtin.module public @cross_ctx_note_word_arg { + private builtin.function @cross_ctx_note_word_arg::bindings::miden::cross_ctx_account_word_arg::foo::process_word::wit_import22(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt, v12: felt, v13: felt, v14: felt, v15: felt) -> felt { + ^block3(v0: felt, v1: felt, v2: felt, v3: felt, v4: felt, v5: felt, v6: felt, v7: felt, v8: felt, v9: felt, v10: felt, v11: felt, v12: felt, v13: felt, v14: felt, v15: felt): + v16 = hir.call v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15 : felt #[callee = miden:cross-ctx-account-word-arg/foo@1.0.0/process-word] #[signature = (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (result felt)]; + builtin.ret v16; + }; + + private builtin.function @__wasm_call_ctors() { + ^block8: + builtin.ret ; + }; + + private builtin.function @cross_ctx_note_word_arg::bindings::__link_custom_section_describing_imports() { + ^block10: + builtin.ret ; + }; + + private builtin.function @miden:base/note-script@1.0.0#run(v17: felt, v18: felt, v19: felt, v20: felt) { + ^block12(v17: felt, v18: felt, v19: felt, v20: felt): + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/wit_bindgen::rt::run_ctors_once() + v21 = arith.constant 1 : i32; + v22 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v21) : felt + v23 = arith.constant 2 : i32; + v24 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v23) : felt + v25 = arith.constant 3 : i32; + v26 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v25) : felt + v27 = arith.constant 4 : i32; + v28 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v27) : felt + v29 = arith.constant 5 : i32; + v30 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v29) : felt + v31 = arith.constant 6 : i32; + v32 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v31) : felt + v33 = arith.constant 7 : i32; + v34 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v33) : felt + v35 = arith.constant 8 : i32; + v36 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v35) : felt + v37 = arith.constant 9 : i32; + v38 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v37) : felt + v39 = arith.constant 10 : i32; + v40 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v39) : felt + v41 = arith.constant 11 : i32; + v42 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v41) : felt + v43 = arith.constant 12 : i32; + v44 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v43) : felt + v45 = arith.constant 13 : i32; + v46 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v45) : felt + v47 = arith.constant 14 : i32; + v48 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v47) : felt + v49 = arith.constant 15 : i32; + v50 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v49) : felt + v88 = arith.constant 7 : i32; + v52 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v88) : felt + v53 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/cross_ctx_note_word_arg::bindings::miden::cross_ctx_account_word_arg::foo::process_word::wit_import22(v22, v24, v26, v28, v30, v32, v34, v36, v38, v40, v42, v44, v46, v48, v50, v52) : felt + v54 = arith.constant 458760 : i32; + v55 = hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::from_u32(v54) : felt + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/intrinsics::felt::assert_eq(v53, v55) + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block14: + v57 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/GOT.data.internal.__memory_base : ptr + v58 = hir.bitcast v57 : ptr; + v59 = hir.load v58 : i32; + v60 = arith.constant 1048588 : i32; + v61 = arith.add v59, v60 : i32 #[overflow = wrapping]; + v62 = hir.bitcast v61 : u32; + v63 = hir.int_to_ptr v62 : ptr; + v64 = hir.load v63 : u8; + v56 = arith.constant 0 : i32; + v65 = arith.zext v64 : u32; + v66 = hir.bitcast v65 : i32; + v68 = arith.neq v66, v56 : i1; + scf.if v68{ + ^block16: + scf.yield ; + } else { + ^block17: + v69 = builtin.global_symbol @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/GOT.data.internal.__memory_base : ptr + v70 = hir.bitcast v69 : ptr; + v71 = hir.load v70 : i32; + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/__wasm_call_ctors() + v90 = arith.constant 1 : u8; + v92 = arith.constant 1048588 : i32; + v73 = arith.add v71, v92 : i32 #[overflow = wrapping]; + v77 = hir.bitcast v73 : u32; + v78 = hir.int_to_ptr v77 : ptr; + hir.store v78, v90; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::from_u32(v79: i32) -> felt { + ^block18(v79: i32): + v80 = hir.bitcast v79 : felt; + builtin.ret v80; + }; + + private builtin.function @intrinsics::felt::assert_eq(v82: felt, v83: felt) { + ^block20(v82: felt, v83: felt): + hir.assert_eq v82, v83; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x000000010000000100000001; + }; + + public builtin.function @run(v84: felt, v85: felt, v86: felt, v87: felt) { + ^block22(v84: felt, v85: felt, v86: felt, v87: felt): + hir.exec @miden:base/note-script@1.0.0/cross_ctx_note_word_arg/miden:base/note-script@1.0.0#run(v84, v85, v86, v87) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.masm b/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.masm new file mode 100644 index 000000000..e8c7a800a --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.masm @@ -0,0 +1,277 @@ +# mod miden:base/note-script@1.0.0 + +export.run + exec.::miden:base/note-script@1.0.0::init + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::miden:base/note-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[511068398781876708,4034132635148770913,11946245983825022717,413851799653899214] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/note-script@1.0.0::cross_ctx_note_word_arg + +proc.cross_ctx_note_word_arg::bindings::miden::cross_ctx_account_word_arg::foo::process_word::wit_import22 + trace.240 + nop + call.::miden:cross-ctx-account-word-arg/foo@1.0.0::process-word + trace.252 + nop +end + +proc.__wasm_call_ctors + nop +end + +proc.cross_ctx_note_word_arg::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/note-script@1.0.0#run + drop + drop + drop + drop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.3 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.4 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.5 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.6 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.7 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.9 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.10 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.11 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.12 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.13 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.14 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.15 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + push.7 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + movup.7 + swap.8 + movdn.7 + movup.6 + swap.9 + movdn.6 + movup.5 + swap.10 + movdn.5 + movup.4 + swap.11 + movdn.4 + movup.3 + swap.12 + movdn.3 + movup.2 + swap.13 + movdn.2 + swap.1 + swap.14 + swap.1 + swap.15 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::cross_ctx_note_word_arg::bindings::miden::cross_ctx_account_word_arg::foo::process_word::wit_import22 + trace.252 + nop + push.458760 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::from_u32 + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::intrinsics::felt::assert_eq + trace.252 + nop +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048588 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::cross_ctx_note_word_arg::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048588 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.intrinsics::felt::assert_eq + assert_eq +end + diff --git a/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.wat b/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.wat new file mode 100644 index 000000000..545f5c7e7 --- /dev/null +++ b/tests/integration/expected/rust_sdk/cross_ctx_note_word_arg.wat @@ -0,0 +1,143 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (alias export 0 "word" (type (;1;))) + (alias export 0 "felt" (type (;2;))) + (type (;3;) + (instance + (alias outer 1 1 (type (;0;))) + (export (;1;) "word" (type (eq 0))) + (alias outer 1 2 (type (;2;))) + (export (;3;) "felt" (type (eq 2))) + (type (;4;) (func (param "input1" 1) (param "input2" 1) (param "input3" 1) (param "felt1" 3) (param "felt2" 3) (param "felt3" 3) (param "felt4" 3) (result 3))) + (export (;0;) "process-word" (func (type 4))) + ) + ) + (import "miden:cross-ctx-account-word-arg/foo@1.0.0" (instance (;1;) (type 3))) + (core module (;0;) + (type (;0;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) + (type (;1;) (func)) + (type (;2;) (func (param f32 f32 f32 f32))) + (type (;3;) (func (param i32) (result f32))) + (type (;4;) (func (param f32 f32))) + (import "miden:cross-ctx-account-word-arg/foo@1.0.0" "process-word" (func $cross_ctx_note_word_arg::bindings::miden::cross_ctx_account_word_arg::foo::process_word::wit_import22 (;0;) (type 0))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/note-script@1.0.0#run" (func $miden:base/note-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $cross_ctx_note_word_arg::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;1;) (type 1)) + (func $cross_ctx_note_word_arg::bindings::__link_custom_section_describing_imports (;2;) (type 1)) + (func $miden:base/note-script@1.0.0#run (;3;) (type 2) (param f32 f32 f32 f32) + call $wit_bindgen::rt::run_ctors_once + i32.const 1 + call $intrinsics::felt::from_u32 + i32.const 2 + call $intrinsics::felt::from_u32 + i32.const 3 + call $intrinsics::felt::from_u32 + i32.const 4 + call $intrinsics::felt::from_u32 + i32.const 5 + call $intrinsics::felt::from_u32 + i32.const 6 + call $intrinsics::felt::from_u32 + i32.const 7 + call $intrinsics::felt::from_u32 + i32.const 8 + call $intrinsics::felt::from_u32 + i32.const 9 + call $intrinsics::felt::from_u32 + i32.const 10 + call $intrinsics::felt::from_u32 + i32.const 11 + call $intrinsics::felt::from_u32 + i32.const 12 + call $intrinsics::felt::from_u32 + i32.const 13 + call $intrinsics::felt::from_u32 + i32.const 14 + call $intrinsics::felt::from_u32 + i32.const 15 + call $intrinsics::felt::from_u32 + i32.const 7 + call $intrinsics::felt::from_u32 + call $cross_ctx_note_word_arg::bindings::miden::cross_ctx_account_word_arg::foo::process_word::wit_import22 + i32.const 458760 + call $intrinsics::felt::from_u32 + call $intrinsics::felt::assert_eq + ) + (func $wit_bindgen::rt::run_ctors_once (;4;) (type 1) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048588 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048588 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $intrinsics::felt::from_u32 (;5;) (type 3) (param i32) (result f32) + unreachable + ) + (func $intrinsics::felt::assert_eq (;6;) (type 4) (param f32 f32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00\01\00\00\00") + ) + (alias export 0 "word" (type (;4;))) + (alias export 1 "process-word" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (core instance (;0;) + (export "process-word" (func 0)) + ) + (core instance (;1;) (instantiate 0 + (with "miden:cross-ctx-account-word-arg/foo@1.0.0" (instance 0)) + ) + ) + (alias core export 1 "memory" (core memory (;0;))) + (type (;5;) (func (param "arg" 4))) + (alias core export 1 "miden:base/note-script@1.0.0#run" (core func (;1;))) + (func (;1;) (type 5) (canon lift (core func 1))) + (alias export 0 "felt" (type (;6;))) + (alias export 0 "word" (type (;7;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;2;) (instantiate 0 + (with "import-func-run" (func 1)) + (with "import-type-felt" (type 6)) + (with "import-type-word" (type 7)) + (with "import-type-word0" (type 4)) + ) + ) + (export (;3;) "miden:base/note-script@1.0.0" (instance 2)) +) diff --git a/tests/integration/expected/rust_sdk/hir2_sketch_multi_interface.hir b/tests/integration/expected/rust_sdk/hir2_sketch_multi_interface.hir new file mode 100644 index 000000000..13635ce44 --- /dev/null +++ b/tests/integration/expected/rust_sdk/hir2_sketch_multi_interface.hir @@ -0,0 +1,4 @@ +builtin.component #[name = #root] #[namespace = #root_ns] #[version = 1.0.0] #[visibility = public] { + + +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/hir2_sketch_multi_interface.wat b/tests/integration/expected/rust_sdk/hir2_sketch_multi_interface.wat new file mode 100644 index 000000000..2034e8916 --- /dev/null +++ b/tests/integration/expected/rust_sdk/hir2_sketch_multi_interface.wat @@ -0,0 +1,1145 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" float32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + (type (;5;) (record (field "inner" 4))) + (export (;6;) "core-asset" (type (eq 5))) + (type (;7;) (record (field "inner" 1))) + (export (;8;) "tag" (type (eq 7))) + (type (;9;) (record (field "inner" 4))) + (export (;10;) "recipient" (type (eq 9))) + (type (;11;) (record (field "inner" 1))) + (export (;12;) "note-type" (type (eq 11))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (func (result s32))) + (export (;0;) "heap-base" (func (type 0))) + ) + ) + (import "miden:core-import/intrinsics-mem@1.0.0" (instance (;1;) (type 1))) + (type (;2;) + (instance + (type (;0;) (func (param "a" float32) (param "b" float32) (result float32))) + (export (;0;) "add" (func (type 0))) + ) + ) + (import "miden:core-import/intrinsics-felt@1.0.0" (instance (;2;) (type 2))) + (type (;3;) + (instance + (type (;0;) (func (param "a0" s32) (param "a1" s32) (param "a2" s32) (param "a3" s32) (param "a4" s32) (param "a5" s32) (param "a6" s32) (param "a7" s32) (param "result-ptr" s32))) + (export (;0;) "hash-one-to-one" (func (type 0))) + ) + ) + (import "miden:core-import/stdlib-crypto-hashes-blake3@1.0.0" (instance (;3;) (type 3))) + (type (;4;) + (instance + (type (;0;) (func (param "asset0" float32) (param "asset1" float32) (param "asset2" float32) (param "asset3" float32) (param "result-ptr" s32))) + (export (;0;) "add-asset" (func (type 0))) + (export (;1;) "remove-asset" (func (type 0))) + ) + ) + (import "miden:core-import/account@1.0.0" (instance (;4;) (type 4))) + (type (;5;) + (instance + (type (;0;) (func (param "asset0" float32) (param "asset1" float32) (param "asset2" float32) (param "asset3" float32) (param "tag" float32) (param "note-type" float32) (param "recipient0" float32) (param "recipient1" float32) (param "recipient2" float32) (param "recipient3" float32) (result float32))) + (export (;0;) "create-note" (func (type 0))) + ) + ) + (import "miden:core-import/tx@1.0.0" (instance (;5;) (type 5))) + (core module (;0;) + (type (;0;) (func (param f32 f32) (result f32))) + (type (;1;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32))) + (type (;2;) (func (result i32))) + (type (;3;) (func (param f32 f32 f32 f32 i32))) + (type (;4;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) + (type (;5;) (func)) + (type (;6;) (func (param i32 i32) (result i32))) + (type (;7;) (func (param i32 i32 i32))) + (type (;8;) (func (param i32 i32 i32 i32) (result i32))) + (type (;9;) (func (param f32 f32 f32 f32))) + (type (;10;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32))) + (type (;11;) (func (param i32))) + (type (;12;) (func (param f32 f32 f32 f32) (result i32))) + (type (;13;) (func (param i32 i32 i32) (result i32))) + (type (;14;) (func (param i32 i32))) + (type (;15;) (func (param i32 f32 f32 i32) (result f32))) + (type (;16;) (func (param i32) (result i32))) + (type (;17;) (func (param i32 i32 i32 i32 i32))) + (type (;18;) (func (param i32 i32 i32 i32))) + (import "miden:core-import/intrinsics-felt@1.0.0" "add" (func $miden_stdlib_sys::intrinsics::felt::extern_add (;0;) (type 0))) + (import "miden:core-import/stdlib-crypto-hashes-blake3@1.0.0" "hash-one-to-one" (func $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1 (;1;) (type 1))) + (import "miden:core-import/intrinsics-mem@1.0.0" "heap-base" (func $miden_sdk_alloc::heap_base (;2;) (type 2))) + (import "miden:core-import/account@1.0.0" "add-asset" (func $miden_base_sys::bindings::account::extern_account_add_asset (;3;) (type 3))) + (import "miden:core-import/account@1.0.0" "remove-asset" (func $miden_base_sys::bindings::account::extern_account_remove_asset (;4;) (type 3))) + (import "miden:core-import/tx@1.0.0" "create-note" (func $miden_base_sys::bindings::tx::extern_tx_create_note (;5;) (type 4))) + (func $__wasm_call_ctors (;6;) (type 5)) + (func $basic_wallet::bindings::__link_custom_section_describing_imports (;7;) (type 5)) + (func $__rust_alloc (;8;) (type 6) (param i32 i32) (result i32) + i32.const 1048756 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rust_dealloc (;9;) (type 7) (param i32 i32 i32)) + (func $__rust_realloc (;10;) (type 8) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + i32.const 1048756 + local.get 2 + local.get 3 + call $::alloc + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 2 + local.get 0 + local.get 1 + local.get 3 + local.get 1 + local.get 3 + i32.lt_u + select + memory.copy + end + local.get 2 + ) + (func $__rust_alloc_zeroed (;11;) (type 6) (param i32 i32) (result i32) + block ;; label = @1 + i32.const 1048756 + local.get 1 + local.get 0 + call $::alloc + local.tee 1 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.const 0 + local.get 0 + memory.fill + end + local.get 1 + ) + (func $miden:basic-wallet/basic-wallet@1.0.0#receive-asset (;12;) (type 9) (param f32 f32 f32 f32) + (local i32 i32) + global.get $__stack_pointer + local.tee 4 + i32.const 64 + i32.sub + i32.const -32 + i32.and + local.tee 5 + global.set $__stack_pointer + call $wit_bindgen_rt::run_ctors_once + local.get 5 + local.get 3 + f32.store offset=12 + local.get 5 + local.get 2 + f32.store offset=8 + local.get 5 + local.get 1 + f32.store offset=4 + local.get 5 + local.get 0 + f32.store + local.get 5 + i32.const 32 + i32.add + local.get 5 + call $miden_base_sys::bindings::account::add_asset + local.get 4 + global.set $__stack_pointer + ) + (func $miden:basic-wallet/basic-wallet@1.0.0#send-asset (;13;) (type 10) (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) + (local i32 i32) + global.get $__stack_pointer + local.tee 10 + i32.const 96 + i32.sub + i32.const -32 + i32.and + local.tee 11 + global.set $__stack_pointer + call $wit_bindgen_rt::run_ctors_once + local.get 11 + local.get 3 + f32.store offset=12 + local.get 11 + local.get 2 + f32.store offset=8 + local.get 11 + local.get 1 + f32.store offset=4 + local.get 11 + local.get 0 + f32.store + local.get 11 + local.get 9 + f32.store offset=44 + local.get 11 + local.get 8 + f32.store offset=40 + local.get 11 + local.get 7 + f32.store offset=36 + local.get 11 + local.get 6 + f32.store offset=32 + local.get 11 + i32.const 64 + i32.add + local.get 11 + call $miden_base_sys::bindings::account::remove_asset + local.get 11 + i32.const 64 + i32.add + local.get 4 + local.get 5 + local.get 11 + i32.const 32 + i32.add + call $miden_base_sys::bindings::tx::create_note + drop + local.get 10 + global.set $__stack_pointer + ) + (func $miden:basic-wallet/aux@1.0.0#test-felt-intrinsics (;14;) (type 0) (param f32 f32) (result f32) + call $wit_bindgen_rt::run_ctors_once + local.get 0 + local.get 1 + call $miden_stdlib_sys::intrinsics::felt::extern_add + ) + (func $miden:basic-wallet/aux@1.0.0#test-stdlib (;15;) (type 6) (param i32 i32) (result i32) + (local i32 i32 i32 i32 i32 i32 i32 i32) + global.get $__stack_pointer + local.tee 2 + local.set 3 + local.get 2 + i32.const 96 + i32.sub + i32.const -32 + i32.and + local.tee 2 + global.set $__stack_pointer + call $wit_bindgen_rt::run_ctors_once + local.get 1 + call $alloc::raw_vec::new_cap + local.set 4 + local.get 2 + local.get 0 + i32.store offset=24 + local.get 2 + local.get 4 + i32.store offset=20 + block ;; label = @1 + block ;; label = @2 + local.get 1 + i32.const 32 + i32.ne + br_if 0 (;@2;) + local.get 2 + i32.const 0 + i32.store offset=28 + local.get 0 + i32.load align=1 + local.set 1 + local.get 0 + i32.load offset=4 align=1 + local.set 4 + local.get 0 + i32.load offset=8 align=1 + local.set 5 + local.get 0 + i32.load offset=12 align=1 + local.set 6 + local.get 0 + i32.load offset=16 align=1 + local.set 7 + local.get 0 + i32.load offset=20 align=1 + local.set 8 + local.get 0 + i32.load offset=24 align=1 + local.set 9 + local.get 0 + i32.load offset=28 align=1 + local.set 0 + local.get 2 + i32.const 20 + i32.add + call $ as core::ops::drop::Drop>::drop + local.get 2 + i32.const 20 + i32.add + call $ as core::ops::drop::Drop>::drop + local.get 1 + local.get 4 + local.get 5 + local.get 6 + local.get 7 + local.get 8 + local.get 9 + local.get 0 + local.get 2 + i32.const 32 + i32.add + call $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1 + local.get 2 + i32.const 84 + i32.add + i32.const 32 + i32.const 0 + i32.const 1 + i32.const 1 + call $alloc::raw_vec::RawVecInner::try_allocate_in + local.get 2 + i32.load offset=88 + local.set 1 + local.get 2 + i32.load offset=84 + i32.const 1 + i32.eq + br_if 1 (;@1;) + local.get 2 + i32.load offset=92 + local.tee 0 + i32.const 24 + i32.add + local.get 2 + i64.load offset=56 + i64.store align=1 + local.get 0 + i32.const 16 + i32.add + local.get 2 + i64.load offset=48 + i64.store align=1 + local.get 0 + i32.const 8 + i32.add + local.get 2 + i64.load offset=40 + i64.store align=1 + local.get 0 + local.get 2 + i64.load offset=32 + i64.store align=1 + local.get 2 + i32.const 32 + i32.store offset=92 + local.get 2 + local.get 0 + i32.store offset=88 + local.get 2 + local.get 1 + i32.store offset=84 + local.get 2 + i32.const 8 + i32.add + local.get 2 + i32.const 84 + i32.add + i32.const 1048720 + call $alloc::vec::Vec::into_boxed_slice + i32.const 0 + local.get 2 + i64.load offset=8 + i64.store offset=1048740 align=4 + local.get 3 + global.set $__stack_pointer + i32.const 1048740 + return + end + unreachable + end + local.get 1 + local.get 2 + i32.load offset=92 + i32.const 1048652 + call $alloc::raw_vec::handle_error + unreachable + ) + (func $miden:basic-wallet/aux@1.0.0#process-list-felt (;16;) (type 6) (param i32 i32) (result i32) + call $wit_bindgen_rt::run_ctors_once + unreachable + ) + (func $cabi_post_miden:basic-wallet/aux@1.0.0#process-list-felt (;17;) (type 11) (param i32)) + (func $miden:basic-wallet/aux@1.0.0#process-core-asset (;18;) (type 12) (param f32 f32 f32 f32) (result i32) + call $wit_bindgen_rt::run_ctors_once + unreachable + ) + (func $cabi_realloc_wit_bindgen_0_28_0 (;19;) (type 8) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $wit_bindgen_rt::cabi_realloc + ) + (func $wit_bindgen_rt::cabi_realloc (;20;) (type 8) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 1 + br_if 0 (;@3;) + local.get 3 + i32.eqz + br_if 2 (;@1;) + i32.const 0 + i32.load8_u offset=1048760 + drop + local.get 3 + local.get 2 + call $__rust_alloc + local.set 2 + br 1 (;@2;) + end + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $__rust_realloc + local.set 2 + end + local.get 2 + br_if 0 (;@1;) + unreachable + end + local.get 2 + ) + (func $wit_bindgen_rt::run_ctors_once (;21;) (type 5) + block ;; label = @1 + i32.const 0 + i32.load8_u offset=1048761 + br_if 0 (;@1;) + call $__wasm_call_ctors + i32.const 0 + i32.const 1 + i32.store8 offset=1048761 + end + ) + (func $::alloc (;22;) (type 13) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 32 + local.get 1 + i32.const 32 + i32.gt_u + select + local.tee 3 + i32.popcnt + i32.const 1 + i32.ne + br_if 0 (;@1;) + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + local.get 2 + i32.lt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $miden_sdk_alloc::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + i32.const 268435456 + local.get 0 + i32.load + local.tee 4 + i32.sub + local.get 2 + i32.lt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $miden_base_sys::bindings::account::add_asset (;23;) (type 14) (param i32 i32) + local.get 1 + f32.load + local.get 1 + f32.load offset=4 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=12 + local.get 0 + call $miden_base_sys::bindings::account::extern_account_add_asset + ) + (func $miden_base_sys::bindings::account::remove_asset (;24;) (type 14) (param i32 i32) + local.get 1 + f32.load + local.get 1 + f32.load offset=4 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=12 + local.get 0 + call $miden_base_sys::bindings::account::extern_account_remove_asset + ) + (func $miden_base_sys::bindings::tx::create_note (;25;) (type 15) (param i32 f32 f32 i32) (result f32) + local.get 0 + f32.load + local.get 0 + f32.load offset=4 + local.get 0 + f32.load offset=8 + local.get 0 + f32.load offset=12 + local.get 1 + local.get 2 + local.get 3 + f32.load + local.get 3 + f32.load offset=4 + local.get 3 + f32.load offset=8 + local.get 3 + f32.load offset=12 + call $miden_base_sys::bindings::tx::extern_tx_create_note + ) + (func $alloc::vec::Vec::into_boxed_slice (;26;) (type 7) (param i32 i32 i32) + (local i32 i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + block ;; label = @1 + block ;; label = @2 + local.get 1 + i32.load + local.get 1 + i32.load offset=8 + local.tee 4 + i32.le_u + br_if 0 (;@2;) + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 4 + i32.const 1 + i32.const 1 + call $alloc::raw_vec::RawVecInner::shrink_unchecked + local.get 3 + i32.load offset=8 + i32.const -2147483647 + i32.ne + br_if 1 (;@1;) + local.get 1 + i32.load offset=8 + local.set 4 + end + local.get 0 + local.get 4 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + return + end + unreachable + ) + (func $ as core::ops::drop::Drop>::drop (;27;) (type 11) (param i32)) + (func $alloc::raw_vec::new_cap (;28;) (type 16) (param i32) (result i32) + local.get 0 + ) + (func $ as core::ops::drop::Drop>::drop (;29;) (type 11) (param i32) + local.get 0 + i32.const 1 + i32.const 1 + call $alloc::raw_vec::RawVecInner::deallocate + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;30;) (type 7) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::try_allocate_in (;31;) (type 17) (param i32 i32 i32 i32 i32) + (local i32 i64) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 3 + local.get 4 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 3 + i32.sub + i32.and + i64.extend_i32_u + local.get 1 + i64.extend_i32_u + i64.mul + local.tee 6 + i64.const 32 + i64.shr_u + i32.wrap_i64 + br_if 0 (;@3;) + i32.const -2147483648 + local.get 3 + i32.sub + local.get 6 + i32.wrap_i64 + local.tee 4 + i32.lt_u + br_if 0 (;@3;) + block ;; label = @4 + local.get 4 + br_if 0 (;@4;) + local.get 0 + local.get 3 + i32.store offset=8 + i32.const 0 + local.set 3 + local.get 0 + i32.const 0 + i32.store offset=4 + br 3 (;@1;) + end + block ;; label = @4 + block ;; label = @5 + local.get 2 + br_if 0 (;@5;) + local.get 5 + i32.const 8 + i32.add + local.get 3 + local.get 4 + call $::allocate + local.get 5 + i32.load offset=8 + local.set 2 + br 1 (;@4;) + end + local.get 5 + local.get 3 + local.get 4 + i32.const 1 + call $alloc::alloc::Global::alloc_impl + local.get 5 + i32.load + local.set 2 + end + local.get 2 + i32.eqz + br_if 1 (;@2;) + local.get 0 + local.get 2 + i32.store offset=8 + local.get 0 + local.get 1 + i32.store offset=4 + i32.const 0 + local.set 3 + br 2 (;@1;) + end + local.get 0 + i32.const 0 + i32.store offset=4 + i32.const 1 + local.set 3 + br 1 (;@1;) + end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + i32.const 1 + local.set 3 + end + local.get 0 + local.get 3 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::allocate (;32;) (type 7) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 2 + i32.const 0 + call $alloc::alloc::Global::alloc_impl + local.get 3 + i32.load offset=12 + local.set 2 + local.get 0 + local.get 3 + i32.load offset=8 + i32.store + local.get 0 + local.get 2 + i32.store offset=4 + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::alloc::Global::alloc_impl (;33;) (type 18) (param i32 i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + i32.const 0 + i32.load8_u offset=1048760 + drop + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + local.get 2 + local.get 1 + call $__rust_alloc + local.set 1 + br 1 (;@1;) + end + local.get 2 + local.get 1 + call $__rust_alloc_zeroed + local.set 1 + end + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.store + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;34;) (type 18) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $alloc::raw_vec::RawVecInner::shrink_unchecked (;35;) (type 17) (param i32 i32 i32 i32 i32) + (local i32 i32 i32 i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + local.get 5 + i32.const 4 + i32.add + local.get 1 + local.get 3 + local.get 4 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + block ;; label = @2 + local.get 5 + i32.load offset=8 + local.tee 6 + i32.eqz + br_if 0 (;@2;) + local.get 5 + i32.load offset=12 + local.set 7 + local.get 5 + i32.load offset=4 + local.set 8 + block ;; label = @3 + local.get 2 + br_if 0 (;@3;) + local.get 8 + local.get 6 + local.get 7 + call $::deallocate + local.get 1 + i32.const 0 + i32.store + local.get 1 + local.get 3 + i32.store offset=4 + br 1 (;@2;) + end + block ;; label = @3 + block ;; label = @4 + local.get 4 + br_if 0 (;@4;) + local.get 8 + local.get 6 + local.get 7 + call $::deallocate + local.get 6 + local.set 3 + br 1 (;@3;) + end + local.get 8 + local.get 7 + local.get 6 + local.get 4 + local.get 2 + i32.mul + local.tee 4 + call $__rust_realloc + local.tee 3 + i32.eqz + br_if 2 (;@1;) + end + local.get 1 + local.get 2 + i32.store + local.get 1 + local.get 3 + i32.store offset=4 + end + i32.const -2147483647 + local.set 6 + end + local.get 0 + local.get 4 + i32.store offset=4 + local.get 0 + local.get 6 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::deallocate (;36;) (type 7) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rust_dealloc + end + ) + (func $alloc::raw_vec::handle_error (;37;) (type 7) (param i32 i32 i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;38;) (type 6) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $cabi_realloc (;39;) (type 8) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $cabi_realloc_wit_bindgen_0_28_0 + ) + (table (;0;) 3 3 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "miden:basic-wallet/basic-wallet@1.0.0#receive-asset" (func $miden:basic-wallet/basic-wallet@1.0.0#receive-asset)) + (export "miden:basic-wallet/basic-wallet@1.0.0#send-asset" (func $miden:basic-wallet/basic-wallet@1.0.0#send-asset)) + (export "miden:basic-wallet/aux@1.0.0#test-felt-intrinsics" (func $miden:basic-wallet/aux@1.0.0#test-felt-intrinsics)) + (export "miden:basic-wallet/aux@1.0.0#test-stdlib" (func $miden:basic-wallet/aux@1.0.0#test-stdlib)) + (export "miden:basic-wallet/aux@1.0.0#process-list-felt" (func $miden:basic-wallet/aux@1.0.0#process-list-felt)) + (export "cabi_post_miden:basic-wallet/aux@1.0.0#process-list-felt" (func $cabi_post_miden:basic-wallet/aux@1.0.0#process-list-felt)) + (export "miden:basic-wallet/aux@1.0.0#process-core-asset" (func $miden:basic-wallet/aux@1.0.0#process-core-asset)) + (export "cabi_post_miden:basic-wallet/aux@1.0.0#test-stdlib" (func $cabi_post_miden:basic-wallet/aux@1.0.0#process-list-felt)) + (export "cabi_realloc_wit_bindgen_0_28_0" (func $cabi_realloc_wit_bindgen_0_28_0)) + (export "cabi_realloc" (func $cabi_realloc)) + (elem (;0;) (i32.const 1) func $basic_wallet::bindings::__link_custom_section_describing_imports $cabi_realloc) + (data $.rodata (;0;) (i32.const 1048576) "/rustc/419b3e2d3e350822550eee0e82eeded4d324d584/library/alloc/src/slice.rs\00\00\00\00\10\00J\00\00\00\a1\00\00\00\19\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00src/bindings.rs\00\80\00\10\00\0f\00\00\00@\01\00\00*\00\00\00\02\00\00\00") + ) + (alias export 2 "add" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (core instance (;0;) + (export "add" (func 0)) + ) + (alias export 3 "hash-one-to-one" (func (;1;))) + (core func (;1;) (canon lower (func 1))) + (core instance (;1;) + (export "hash-one-to-one" (func 1)) + ) + (alias export 1 "heap-base" (func (;2;))) + (core func (;2;) (canon lower (func 2))) + (core instance (;2;) + (export "heap-base" (func 2)) + ) + (alias export 4 "add-asset" (func (;3;))) + (core func (;3;) (canon lower (func 3))) + (alias export 4 "remove-asset" (func (;4;))) + (core func (;4;) (canon lower (func 4))) + (core instance (;3;) + (export "add-asset" (func 3)) + (export "remove-asset" (func 4)) + ) + (alias export 5 "create-note" (func (;5;))) + (core func (;5;) (canon lower (func 5))) + (core instance (;4;) + (export "create-note" (func 5)) + ) + (core instance (;5;) (instantiate 0 + (with "miden:core-import/intrinsics-felt@1.0.0" (instance 0)) + (with "miden:core-import/stdlib-crypto-hashes-blake3@1.0.0" (instance 1)) + (with "miden:core-import/intrinsics-mem@1.0.0" (instance 2)) + (with "miden:core-import/account@1.0.0" (instance 3)) + (with "miden:core-import/tx@1.0.0" (instance 4)) + ) + ) + (alias core export 5 "memory" (core memory (;0;))) + (alias core export 5 "cabi_realloc" (core func (;6;))) + (alias export 0 "core-asset" (type (;6;))) + (type (;7;) (func (param "core-asset" 6))) + (alias core export 5 "miden:basic-wallet/basic-wallet@1.0.0#receive-asset" (core func (;7;))) + (func (;6;) (type 7) (canon lift (core func 7))) + (alias export 0 "tag" (type (;8;))) + (alias export 0 "note-type" (type (;9;))) + (alias export 0 "recipient" (type (;10;))) + (type (;11;) (func (param "core-asset" 6) (param "tag" 8) (param "note-type" 9) (param "recipient" 10))) + (alias core export 5 "miden:basic-wallet/basic-wallet@1.0.0#send-asset" (core func (;8;))) + (func (;7;) (type 11) (canon lift (core func 8))) + (alias export 0 "felt" (type (;12;))) + (alias export 0 "word" (type (;13;))) + (alias export 0 "core-asset" (type (;14;))) + (alias export 0 "tag" (type (;15;))) + (alias export 0 "recipient" (type (;16;))) + (alias export 0 "note-type" (type (;17;))) + (component (;0;) + (type (;0;) (record (field "inner" float32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (type (;5;) (record (field "inner" 4))) + (import "import-type-core-asset" (type (;6;) (eq 5))) + (type (;7;) (record (field "inner" 1))) + (import "import-type-tag" (type (;8;) (eq 7))) + (type (;9;) (record (field "inner" 4))) + (import "import-type-recipient" (type (;10;) (eq 9))) + (type (;11;) (record (field "inner" 1))) + (import "import-type-note-type" (type (;12;) (eq 11))) + (import "import-type-core-asset0" (type (;13;) (eq 6))) + (type (;14;) (func (param "core-asset" 13))) + (import "import-func-receive-asset" (func (;0;) (type 14))) + (import "import-type-tag0" (type (;15;) (eq 8))) + (import "import-type-note-type0" (type (;16;) (eq 12))) + (import "import-type-recipient0" (type (;17;) (eq 10))) + (type (;18;) (func (param "core-asset" 13) (param "tag" 15) (param "note-type" 16) (param "recipient" 17))) + (import "import-func-send-asset" (func (;1;) (type 18))) + (export (;19;) "core-asset" (type 6)) + (export (;20;) "tag" (type 8)) + (export (;21;) "recipient" (type 10)) + (export (;22;) "note-type" (type 12)) + (export (;23;) "felt" (type 1)) + (type (;24;) (func (param "core-asset" 19))) + (export (;2;) "receive-asset" (func 0) (func (type 24))) + (type (;25;) (func (param "core-asset" 19) (param "tag" 20) (param "note-type" 22) (param "recipient" 21))) + (export (;3;) "send-asset" (func 1) (func (type 25))) + ) + (instance (;6;) (instantiate 0 + (with "import-func-receive-asset" (func 6)) + (with "import-func-send-asset" (func 7)) + (with "import-type-felt" (type 12)) + (with "import-type-word" (type 13)) + (with "import-type-core-asset" (type 14)) + (with "import-type-tag" (type 15)) + (with "import-type-recipient" (type 16)) + (with "import-type-note-type" (type 17)) + (with "import-type-core-asset0" (type 6)) + (with "import-type-tag0" (type 8)) + (with "import-type-note-type0" (type 9)) + (with "import-type-recipient0" (type 10)) + ) + ) + (export (;7;) "miden:basic-wallet/basic-wallet@1.0.0" (instance 6)) + (alias export 0 "felt" (type (;18;))) + (type (;19;) (func (param "a" 18) (param "b" 18) (result 18))) + (alias core export 5 "miden:basic-wallet/aux@1.0.0#test-felt-intrinsics" (core func (;9;))) + (func (;8;) (type 19) (canon lift (core func 9))) + (type (;20;) (list u8)) + (type (;21;) (func (param "input" 20) (result 20))) + (alias core export 5 "miden:basic-wallet/aux@1.0.0#test-stdlib" (core func (;10;))) + (alias core export 5 "cabi_post_miden:basic-wallet/aux@1.0.0#test-stdlib" (core func (;11;))) + (func (;9;) (type 21) (canon lift (core func 10) (memory 0) (realloc 6) (post-return 11))) + (type (;22;) (list 18)) + (type (;23;) (func (param "input" 22) (result 22))) + (alias core export 5 "miden:basic-wallet/aux@1.0.0#process-list-felt" (core func (;12;))) + (alias core export 5 "cabi_post_miden:basic-wallet/aux@1.0.0#process-list-felt" (core func (;13;))) + (func (;10;) (type 23) (canon lift (core func 12) (memory 0) (realloc 6) (post-return 13))) + (type (;24;) (func (param "input" 6) (result 6))) + (alias core export 5 "miden:basic-wallet/aux@1.0.0#process-core-asset" (core func (;14;))) + (func (;11;) (type 24) (canon lift (core func 14) (memory 0))) + (component (;1;) + (type (;0;) (record (field "inner" float32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (type (;5;) (record (field "inner" 4))) + (import "import-type-core-asset" (type (;6;) (eq 5))) + (type (;7;) (record (field "inner" 1))) + (import "import-type-tag" (type (;8;) (eq 7))) + (type (;9;) (record (field "inner" 4))) + (import "import-type-recipient" (type (;10;) (eq 9))) + (type (;11;) (record (field "inner" 1))) + (import "import-type-note-type" (type (;12;) (eq 11))) + (import "import-type-felt0" (type (;13;) (eq 1))) + (type (;14;) (func (param "a" 13) (param "b" 13) (result 13))) + (import "import-func-test-felt-intrinsics" (func (;0;) (type 14))) + (type (;15;) (list u8)) + (type (;16;) (func (param "input" 15) (result 15))) + (import "import-func-test-stdlib" (func (;1;) (type 16))) + (type (;17;) (list 13)) + (type (;18;) (func (param "input" 17) (result 17))) + (import "import-func-process-list-felt" (func (;2;) (type 18))) + (import "import-type-core-asset0" (type (;19;) (eq 6))) + (type (;20;) (func (param "input" 19) (result 19))) + (import "import-func-process-core-asset" (func (;3;) (type 20))) + (export (;21;) "core-asset" (type 6)) + (export (;22;) "tag" (type 8)) + (export (;23;) "recipient" (type 10)) + (export (;24;) "note-type" (type 12)) + (export (;25;) "felt" (type 1)) + (type (;26;) (func (param "a" 25) (param "b" 25) (result 25))) + (export (;4;) "test-felt-intrinsics" (func 0) (func (type 26))) + (type (;27;) (list u8)) + (type (;28;) (func (param "input" 27) (result 27))) + (export (;5;) "test-stdlib" (func 1) (func (type 28))) + (type (;29;) (list 25)) + (type (;30;) (func (param "input" 29) (result 29))) + (export (;6;) "process-list-felt" (func 2) (func (type 30))) + (type (;31;) (func (param "input" 21) (result 21))) + (export (;7;) "process-core-asset" (func 3) (func (type 31))) + ) + (instance (;8;) (instantiate 1 + (with "import-func-test-felt-intrinsics" (func 8)) + (with "import-func-test-stdlib" (func 9)) + (with "import-func-process-list-felt" (func 10)) + (with "import-func-process-core-asset" (func 11)) + (with "import-type-felt" (type 12)) + (with "import-type-word" (type 13)) + (with "import-type-core-asset" (type 14)) + (with "import-type-tag" (type 15)) + (with "import-type-recipient" (type 16)) + (with "import-type-note-type" (type 17)) + (with "import-type-felt0" (type 18)) + (with "import-type-core-asset0" (type 6)) + ) + ) + (export (;9;) "miden:basic-wallet/aux@1.0.0" (instance 8)) +) \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/hir2_sketch_single_interface.hir b/tests/integration/expected/rust_sdk/hir2_sketch_single_interface.hir new file mode 100644 index 000000000..13635ce44 --- /dev/null +++ b/tests/integration/expected/rust_sdk/hir2_sketch_single_interface.hir @@ -0,0 +1,4 @@ +builtin.component #[name = #root] #[namespace = #root_ns] #[version = 1.0.0] #[visibility = public] { + + +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/hir2_sketch_single_interface.wat b/tests/integration/expected/rust_sdk/hir2_sketch_single_interface.wat new file mode 100644 index 000000000..2dd920661 --- /dev/null +++ b/tests/integration/expected/rust_sdk/hir2_sketch_single_interface.wat @@ -0,0 +1,260 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" float32))) + (export (;1;) "felt" (type (eq 0))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (type (;1;) + (instance + (type (;0;) (func (result s32))) + (export (;0;) "heap-base" (func (type 0))) + ) + ) + (import "miden:core-import/intrinsics-mem@1.0.0" (instance (;1;) (type 1))) + (type (;2;) + (instance + (type (;0;) (func (param "a" float32) (param "b" float32) (result float32))) + (export (;0;) "add" (func (type 0))) + (type (;1;) (func (param "a" u32) (result float32))) + (export (;1;) "from-u32" (func (type 1))) + ) + ) + (import "miden:core-import/intrinsics-felt@1.0.0" (instance (;2;) (type 2))) + (core module (;0;) + (type (;0;) (func (param i32) (result f32))) + (type (;1;) (func (param f32 f32) (result f32))) + (type (;2;) (func (result i32))) + (type (;3;) (func)) + (type (;4;) (func (param i32 i32) (result i32))) + (type (;5;) (func (param i32 i32 i32 i32) (result i32))) + (type (;6;) (func (param f32) (result f32))) + (type (;7;) (func (param i32 i32 i32) (result i32))) + (import "miden:core-import/intrinsics-felt@1.0.0" "from-u32" (func $miden_stdlib_sys::intrinsics::felt::extern_from_u32 (;0;) (type 0))) + (import "miden:core-import/intrinsics-felt@1.0.0" "add" (func $miden_stdlib_sys::intrinsics::felt::extern_add (;1;) (type 1))) + (import "miden:core-import/intrinsics-mem@1.0.0" "heap-base" (func $miden_sdk_alloc::heap_base (;2;) (type 2))) + (func $__wasm_call_ctors (;3;) (type 3)) + (func $cross_ctx_account::bindings::__link_custom_section_describing_imports (;4;) (type 3)) + (func $__rust_alloc (;5;) (type 4) (param i32 i32) (result i32) + i32.const 1048612 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rust_realloc (;6;) (type 5) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + i32.const 1048612 + local.get 2 + local.get 3 + call $::alloc + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 2 + local.get 0 + local.get 1 + local.get 3 + local.get 1 + local.get 3 + i32.lt_u + select + memory.copy + end + local.get 2 + ) + (func $miden:cross-ctx-account/foo@1.0.0#process-felt (;7;) (type 6) (param f32) (result f32) + call $wit_bindgen_rt::run_ctors_once + local.get 0 + i32.const 3 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + call $miden_stdlib_sys::intrinsics::felt::extern_add + ) + (func $cabi_realloc_wit_bindgen_0_28_0 (;8;) (type 5) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $wit_bindgen_rt::cabi_realloc + ) + (func $wit_bindgen_rt::cabi_realloc (;9;) (type 5) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 1 + br_if 0 (;@3;) + local.get 3 + i32.eqz + br_if 2 (;@1;) + i32.const 0 + i32.load8_u offset=1048616 + drop + local.get 3 + local.get 2 + call $__rust_alloc + local.set 2 + br 1 (;@2;) + end + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $__rust_realloc + local.set 2 + end + local.get 2 + br_if 0 (;@1;) + unreachable + end + local.get 2 + ) + (func $wit_bindgen_rt::run_ctors_once (;10;) (type 3) + block ;; label = @1 + i32.const 0 + i32.load8_u offset=1048617 + br_if 0 (;@1;) + call $__wasm_call_ctors + i32.const 0 + i32.const 1 + i32.store8 offset=1048617 + end + ) + (func $::alloc (;11;) (type 7) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 32 + local.get 1 + i32.const 32 + i32.gt_u + select + local.tee 3 + i32.popcnt + i32.const 1 + i32.ne + br_if 0 (;@1;) + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + local.get 2 + i32.lt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $miden_sdk_alloc::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + i32.const 268435456 + local.get 0 + i32.load + local.tee 4 + i32.sub + local.get 2 + i32.lt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;12;) (type 4) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $cabi_realloc (;13;) (type 5) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $cabi_realloc_wit_bindgen_0_28_0 + ) + (table (;0;) 3 3 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "miden:cross-ctx-account/foo@1.0.0#process-felt" (func $miden:cross-ctx-account/foo@1.0.0#process-felt)) + (export "cabi_realloc_wit_bindgen_0_28_0" (func $cabi_realloc_wit_bindgen_0_28_0)) + (export "cabi_realloc" (func $cabi_realloc)) + (elem (;0;) (i32.const 1) func $cross_ctx_account::bindings::__link_custom_section_describing_imports $cabi_realloc) + (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\02\00\00\00") + ) + (alias export 2 "add" (func (;0;))) + (core func (;0;) (canon lower (func 0))) + (alias export 2 "from-u32" (func (;1;))) + (core func (;1;) (canon lower (func 1))) + (core instance (;0;) + (export "add" (func 0)) + (export "from-u32" (func 1)) + ) + (alias export 1 "heap-base" (func (;2;))) + (core func (;2;) (canon lower (func 2))) + (core instance (;1;) + (export "heap-base" (func 2)) + ) + (core instance (;2;) (instantiate 0 + (with "miden:core-import/intrinsics-felt@1.0.0" (instance 0)) + (with "miden:core-import/intrinsics-mem@1.0.0" (instance 1)) + ) + ) + (alias core export 2 "memory" (core memory (;0;))) + (alias core export 2 "cabi_realloc" (core func (;3;))) + (alias export 0 "felt" (type (;3;))) + (type (;4;) (func (param "input" 3) (result 3))) + (alias core export 2 "miden:cross-ctx-account/foo@1.0.0#process-felt" (core func (;4;))) + (func (;3;) (type 4) (canon lift (core func 4))) + (alias export 0 "felt" (type (;5;))) + (component (;0;) + (type (;0;) (record (field "inner" float32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (import "import-type-felt0" (type (;2;) (eq 1))) + (type (;3;) (func (param "input" 2) (result 2))) + (import "import-func-process-felt" (func (;0;) (type 3))) + (export (;4;) "felt" (type 1)) + (type (;5;) (func (param "input" 4) (result 4))) + (export (;1;) "process-felt" (func 0) (func (type 5))) + ) + (instance (;3;) (instantiate 0 + (with "import-func-process-felt" (func 3)) + (with "import-type-felt" (type 5)) + (with "import-type-felt0" (type 3)) + ) + ) + (export (;4;) "miden:cross-ctx-account/foo@1.0.0" (instance 3)) +) \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/pure_rust_add.hir b/tests/integration/expected/rust_sdk/pure_rust_add.hir new file mode 100644 index 000000000..15f1c5485 --- /dev/null +++ b/tests/integration/expected/rust_sdk/pure_rust_add.hir @@ -0,0 +1,291 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @pure_rust_add { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block4(v0: i32, v1: i32): + v3 = arith.add v1, v0 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; + + private builtin.function @__rustc::__rust_alloc(v4: i32, v5: i32) -> i32 { + ^block6(v4: i32, v5: i32): + v7 = arith.constant 1048580 : i32; + v8 = hir.exec @root_ns:root@1.0.0/pure_rust_add/::alloc(v7, v5, v4) : i32 + builtin.ret v8; + }; + + private builtin.function @__rustc::__rust_realloc(v9: i32, v10: i32, v11: i32, v12: i32) -> i32 { + ^block8(v9: i32, v10: i32, v11: i32, v12: i32): + v14 = arith.constant 1048580 : i32; + v15 = hir.exec @root_ns:root@1.0.0/pure_rust_add/::alloc(v14, v11, v12) : i32 + v178 = arith.constant 0 : i32; + v16 = arith.constant 0 : i32; + v17 = arith.eq v15, v16 : i1; + v18 = arith.zext v17 : u32; + v19 = hir.bitcast v18 : i32; + v21 = arith.neq v19, v178 : i1; + scf.if v21{ + ^block10: + scf.yield ; + } else { + ^block11: + v177 = arith.constant 0 : i32; + v23 = hir.bitcast v10 : u32; + v22 = hir.bitcast v12 : u32; + v24 = arith.lt v22, v23 : i1; + v25 = arith.zext v24 : u32; + v26 = hir.bitcast v25 : i32; + v28 = arith.neq v26, v177 : i1; + v29 = cf.select v28, v12, v10 : i32; + v175 = arith.constant 0 : i32; + v176 = arith.constant 0 : i32; + v31 = arith.eq v29, v176 : i1; + v32 = arith.zext v31 : u32; + v33 = hir.bitcast v32 : i32; + v35 = arith.neq v33, v175 : i1; + scf.if v35{ + ^block44: + scf.yield ; + } else { + ^block12: + v36 = hir.bitcast v29 : u32; + v37 = hir.bitcast v15 : u32; + v38 = hir.int_to_ptr v37 : ptr; + v39 = hir.bitcast v9 : u32; + v40 = hir.int_to_ptr v39 : ptr; + hir.mem_cpy v40, v38, v36; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v15; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block13: + builtin.ret ; + }; + + private builtin.function @::alloc(v42: i32, v43: i32, v44: i32) -> i32 { + ^block15(v42: i32, v43: i32, v44: i32): + v47 = arith.constant 16 : i32; + v46 = arith.constant 0 : i32; + v180 = arith.constant 16 : u32; + v49 = hir.bitcast v43 : u32; + v51 = arith.gt v49, v180 : i1; + v52 = arith.zext v51 : u32; + v53 = hir.bitcast v52 : i32; + v55 = arith.neq v53, v46 : i1; + v56 = cf.select v55, v43, v47 : i32; + v220 = arith.constant 0 : i32; + v57 = arith.constant -1 : i32; + v58 = arith.add v56, v57 : i32 #[overflow = wrapping]; + v59 = arith.band v56, v58 : i32; + v61 = arith.neq v59, v220 : i1; + v189, v190 = scf.if v61 : i32, u32 { + ^block49: + v181 = arith.constant 0 : u32; + v185 = ub.poison i32 : i32; + scf.yield v185, v181; + } else { + ^block18: + v63 = hir.exec @root_ns:root@1.0.0/pure_rust_add/core::ptr::alignment::Alignment::max(v43, v56) : i32 + v219 = arith.constant 0 : i32; + v62 = arith.constant -2147483648 : i32; + v64 = arith.sub v62, v63 : i32 #[overflow = wrapping]; + v66 = hir.bitcast v64 : u32; + v65 = hir.bitcast v44 : u32; + v67 = arith.gt v65, v66 : i1; + v68 = arith.zext v67 : u32; + v69 = hir.bitcast v68 : i32; + v71 = arith.neq v69, v219 : i1; + v204 = scf.if v71 : i32 { + ^block48: + v218 = ub.poison i32 : i32; + scf.yield v218; + } else { + ^block19: + v216 = arith.constant 0 : i32; + v77 = arith.sub v216, v63 : i32 #[overflow = wrapping]; + v217 = arith.constant -1 : i32; + v73 = arith.add v44, v63 : i32 #[overflow = wrapping]; + v75 = arith.add v73, v217 : i32 #[overflow = wrapping]; + v78 = arith.band v75, v77 : i32; + v79 = hir.bitcast v42 : u32; + v80 = arith.constant 4 : u32; + v81 = arith.mod v79, v80 : u32; + hir.assertz v81 #[code = 250]; + v82 = hir.int_to_ptr v79 : ptr; + v83 = hir.load v82 : i32; + v215 = arith.constant 0 : i32; + v85 = arith.neq v83, v215 : i1; + scf.if v85{ + ^block47: + scf.yield ; + } else { + ^block21: + v86 = hir.exec @root_ns:root@1.0.0/pure_rust_add/intrinsics::mem::heap_base() : i32 + v87 = hir.mem_size : u32; + v93 = hir.bitcast v42 : u32; + v214 = arith.constant 4 : u32; + v95 = arith.mod v93, v214 : u32; + hir.assertz v95 #[code = 250]; + v213 = arith.constant 16 : u32; + v88 = hir.bitcast v87 : i32; + v91 = arith.shl v88, v213 : i32; + v92 = arith.add v86, v91 : i32 #[overflow = wrapping]; + v96 = hir.int_to_ptr v93 : ptr; + hir.store v96, v92; + scf.yield ; + }; + v99 = hir.bitcast v42 : u32; + v212 = arith.constant 4 : u32; + v101 = arith.mod v99, v212 : u32; + hir.assertz v101 #[code = 250]; + v102 = hir.int_to_ptr v99 : ptr; + v103 = hir.load v102 : i32; + v210 = arith.constant 0 : i32; + v211 = arith.constant -1 : i32; + v105 = arith.bxor v103, v211 : i32; + v107 = hir.bitcast v105 : u32; + v106 = hir.bitcast v78 : u32; + v108 = arith.gt v106, v107 : i1; + v109 = arith.zext v108 : u32; + v110 = hir.bitcast v109 : i32; + v112 = arith.neq v110, v210 : i1; + v203 = scf.if v112 : i32 { + ^block22: + v209 = arith.constant 0 : i32; + scf.yield v209; + } else { + ^block23: + v114 = hir.bitcast v42 : u32; + v208 = arith.constant 4 : u32; + v116 = arith.mod v114, v208 : u32; + hir.assertz v116 #[code = 250]; + v113 = arith.add v103, v78 : i32 #[overflow = wrapping]; + v117 = hir.int_to_ptr v114 : ptr; + hir.store v117, v113; + v119 = arith.add v103, v63 : i32 #[overflow = wrapping]; + scf.yield v119; + }; + scf.yield v203; + }; + v186 = arith.constant 1 : u32; + v207 = arith.constant 0 : u32; + v205 = cf.select v71, v207, v186 : u32; + scf.yield v204, v205; + }; + v206 = arith.constant 0 : u32; + v202 = arith.eq v190, v206 : i1; + cf.cond_br v202 ^block17, ^block51(v189); + ^block17: + ub.unreachable ; + ^block51(v182: i32): + builtin.ret v182; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block24: + v122 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v122; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v124: i32, v125: i32) -> i32 { + ^block28(v124: i32, v125: i32): + v132 = arith.constant 0 : i32; + v128 = hir.bitcast v125 : u32; + v127 = hir.bitcast v124 : u32; + v129 = arith.gt v127, v128 : i1; + v130 = arith.zext v129 : u32; + v131 = hir.bitcast v130 : i32; + v133 = arith.neq v131, v132 : i1; + v134 = cf.select v133, v124, v125 : i32; + builtin.ret v134; + }; + + public builtin.function @cabi_realloc(v135: i32, v136: i32, v137: i32, v138: i32) -> i32 { + ^block30(v135: i32, v136: i32, v137: i32, v138: i32): + v140 = hir.exec @root_ns:root@1.0.0/pure_rust_add/cabi_realloc_wit_bindgen_0_46_0(v135, v136, v137, v138) : i32 + builtin.ret v140; + }; + + private builtin.function @alloc::alloc::alloc(v141: i32, v142: i32) -> i32 { + ^block32(v141: i32, v142: i32): + hir.exec @root_ns:root@1.0.0/pure_rust_add/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v144 = hir.exec @root_ns:root@1.0.0/pure_rust_add/__rustc::__rust_alloc(v142, v141) : i32 + builtin.ret v144; + }; + + public builtin.function @cabi_realloc_wit_bindgen_0_46_0(v145: i32, v146: i32, v147: i32, v148: i32) -> i32 { + ^block34(v145: i32, v146: i32, v147: i32, v148: i32): + v150 = hir.exec @root_ns:root@1.0.0/pure_rust_add/wit_bindgen::rt::cabi_realloc(v145, v146, v147, v148) : i32 + builtin.ret v150; + }; + + private builtin.function @wit_bindgen::rt::cabi_realloc(v151: i32, v152: i32, v153: i32, v154: i32) -> i32 { + ^block36(v151: i32, v152: i32, v153: i32, v154: i32): + v156 = arith.constant 0 : i32; + v157 = arith.neq v152, v156 : i1; + v231, v232, v233 = scf.if v157 : i32, i32, u32 { + ^block40: + v165 = hir.exec @root_ns:root@1.0.0/pure_rust_add/__rustc::__rust_realloc(v151, v152, v153, v154) : i32 + v222 = arith.constant 0 : u32; + v226 = ub.poison i32 : i32; + scf.yield v165, v226, v222; + } else { + ^block41: + v261 = arith.constant 0 : i32; + v262 = arith.constant 0 : i32; + v159 = arith.eq v154, v262 : i1; + v160 = arith.zext v159 : u32; + v161 = hir.bitcast v160 : i32; + v163 = arith.neq v161, v261 : i1; + v249 = scf.if v163 : i32 { + ^block55: + v260 = ub.poison i32 : i32; + scf.yield v260; + } else { + ^block42: + v164 = hir.exec @root_ns:root@1.0.0/pure_rust_add/alloc::alloc::alloc(v153, v154) : i32 + scf.yield v164; + }; + v258 = arith.constant 0 : u32; + v227 = arith.constant 1 : u32; + v251 = cf.select v163, v227, v258 : u32; + v259 = ub.poison i32 : i32; + v250 = cf.select v163, v153, v259 : i32; + scf.yield v249, v250, v251; + }; + v238, v239 = scf.index_switch v233 : i32, u32 + case 0 { + ^block39: + v256 = arith.constant 0 : i32; + v168 = arith.neq v231, v256 : i1; + v253 = arith.constant 1 : u32; + v254 = arith.constant 0 : u32; + v248 = cf.select v168, v254, v253 : u32; + v255 = ub.poison i32 : i32; + v247 = cf.select v168, v231, v255 : i32; + scf.yield v247, v248; + } + default { + ^block62: + v257 = arith.constant 0 : u32; + scf.yield v232, v257; + }; + v252 = arith.constant 0 : u32; + v246 = arith.eq v239, v252 : i1; + cf.cond_br v246 ^block57, ^block58; + ^block57: + builtin.ret v238; + ^block58: + ub.unreachable ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.segment readonly @1048576 = 0x00000001; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/pure_rust_add.wat b/tests/integration/expected/rust_sdk/pure_rust_add.wat new file mode 100644 index 000000000..9ef269e25 --- /dev/null +++ b/tests/integration/expected/rust_sdk/pure_rust_add.wat @@ -0,0 +1,186 @@ +(module $pure_rust_add.wasm + (type (;0;) (func (param i32 i32) (result i32))) + (type (;1;) (func (param i32 i32 i32 i32) (result i32))) + (type (;2;) (func)) + (type (;3;) (func (param i32 i32 i32) (result i32))) + (type (;4;) (func (result i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "cabi_realloc_wit_bindgen_0_46_0" (func $cabi_realloc_wit_bindgen_0_46_0)) + (export "cabi_realloc" (func $cabi_realloc)) + (elem (;0;) (i32.const 1) func $cabi_realloc) + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 1 + local.get 0 + i32.add + ) + (func $__rustc::__rust_alloc (;1;) (type 0) (param i32 i32) (result i32) + i32.const 1048580 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_realloc (;2;) (type 1) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + i32.const 1048580 + local.get 2 + local.get 3 + call $::alloc + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + local.get 1 + local.get 3 + local.get 1 + i32.lt_u + select + local.tee 3 + i32.eqz + br_if 0 (;@1;) + local.get 2 + local.get 0 + local.get 3 + memory.copy + end + local.get 2 + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;3;) (type 2) + return + ) + (func $::alloc (;4;) (type 3) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;5;) (type 4) (result i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;6;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $cabi_realloc (;7;) (type 1) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $cabi_realloc_wit_bindgen_0_46_0 + ) + (func $alloc::alloc::alloc (;8;) (type 0) (param i32 i32) (result i32) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + local.get 1 + local.get 0 + call $__rustc::__rust_alloc + ) + (func $cabi_realloc_wit_bindgen_0_46_0 (;9;) (type 1) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $wit_bindgen::rt::cabi_realloc + ) + (func $wit_bindgen::rt::cabi_realloc (;10;) (type 1) (param i32 i32 i32 i32) (result i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 1 + br_if 0 (;@3;) + local.get 3 + i32.eqz + br_if 2 (;@1;) + local.get 2 + local.get 3 + call $alloc::alloc::alloc + local.set 2 + br 1 (;@2;) + end + local.get 0 + local.get 1 + local.get 2 + local.get 3 + call $__rustc::__rust_realloc + local.set 2 + end + local.get 2 + br_if 0 (;@1;) + unreachable + end + local.get 2 + ) + (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00") +) diff --git a/tests/integration/expected/rust_sdk/pure_rust_add_old.hir b/tests/integration/expected/rust_sdk/pure_rust_add_old.hir new file mode 100644 index 000000000..b85756ff0 --- /dev/null +++ b/tests/integration/expected/rust_sdk/pure_rust_add_old.hir @@ -0,0 +1,21 @@ +(component + ;; Modules + (module #pure_rust_add + ;; Constants + (const (id 0) 0x00100000) + + ;; Global Variables + (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + + ;; Functions + (func (export #entrypoint) (param i32) (param i32) (result i32) + (block 0 (param v0 i32) (param v1 i32) + (let (v3 i32) (add.wrapping v1 v0)) + (br (block 1 v3))) + + (block 1 (param v2 i32) + (ret v2)) + ) + ) + +) diff --git a/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.hir b/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.hir new file mode 100644 index 000000000..e13f3c6f3 --- /dev/null +++ b/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.hir @@ -0,0 +1,654 @@ +builtin.component miden:base/note-script@1.0.0 { + builtin.module public @rust_sdk_swapp_note_bindings { + private builtin.function @__wasm_call_ctors() { + ^block5: + builtin.ret ; + }; + + private builtin.function @::eq(v0: i32, v1: i32) -> i32 { + ^block7(v0: i32, v1: i32): + v5 = hir.bitcast v0 : u32; + v6 = arith.constant 4 : u32; + v7 = arith.mod v5, v6 : u32; + hir.assertz v7 #[code = 250]; + v8 = hir.int_to_ptr v5 : ptr; + v9 = hir.load v8 : felt; + v10 = hir.bitcast v1 : u32; + v463 = arith.constant 4 : u32; + v12 = arith.mod v10, v463 : u32; + hir.assertz v12 #[code = 250]; + v13 = hir.int_to_ptr v10 : ptr; + v14 = hir.load v13 : felt; + v15 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/intrinsics::felt::eq(v9, v14) : i32 + v3 = arith.constant 0 : i32; + v16 = arith.constant 1 : i32; + v17 = arith.neq v15, v16 : i1; + v18 = arith.zext v17 : u32; + v19 = hir.bitcast v18 : i32; + v21 = arith.neq v19, v3 : i1; + v441 = scf.if v21 : i32 { + ^block9: + v462 = arith.constant 0 : i32; + scf.yield v462; + } else { + ^block10: + v461 = arith.constant 4 : u32; + v22 = hir.bitcast v0 : u32; + v24 = arith.add v22, v461 : u32 #[overflow = checked]; + v460 = arith.constant 4 : u32; + v26 = arith.mod v24, v460 : u32; + hir.assertz v26 #[code = 250]; + v27 = hir.int_to_ptr v24 : ptr; + v28 = hir.load v27 : felt; + v459 = arith.constant 4 : u32; + v29 = hir.bitcast v1 : u32; + v31 = arith.add v29, v459 : u32 #[overflow = checked]; + v458 = arith.constant 4 : u32; + v33 = arith.mod v31, v458 : u32; + hir.assertz v33 #[code = 250]; + v34 = hir.int_to_ptr v31 : ptr; + v35 = hir.load v34 : felt; + v36 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/intrinsics::felt::eq(v28, v35) : i32 + v456 = arith.constant 0 : i32; + v457 = arith.constant 1 : i32; + v38 = arith.neq v36, v457 : i1; + v39 = arith.zext v38 : u32; + v40 = hir.bitcast v39 : i32; + v42 = arith.neq v40, v456 : i1; + v443 = scf.if v42 : i32 { + ^block57: + v455 = arith.constant 0 : i32; + scf.yield v455; + } else { + ^block11: + v44 = arith.constant 8 : u32; + v43 = hir.bitcast v0 : u32; + v45 = arith.add v43, v44 : u32 #[overflow = checked]; + v454 = arith.constant 4 : u32; + v47 = arith.mod v45, v454 : u32; + hir.assertz v47 #[code = 250]; + v48 = hir.int_to_ptr v45 : ptr; + v49 = hir.load v48 : felt; + v453 = arith.constant 8 : u32; + v50 = hir.bitcast v1 : u32; + v52 = arith.add v50, v453 : u32 #[overflow = checked]; + v452 = arith.constant 4 : u32; + v54 = arith.mod v52, v452 : u32; + hir.assertz v54 #[code = 250]; + v55 = hir.int_to_ptr v52 : ptr; + v56 = hir.load v55 : felt; + v57 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/intrinsics::felt::eq(v49, v56) : i32 + v450 = arith.constant 0 : i32; + v451 = arith.constant 1 : i32; + v59 = arith.neq v57, v451 : i1; + v60 = arith.zext v59 : u32; + v61 = hir.bitcast v60 : i32; + v63 = arith.neq v61, v450 : i1; + v444 = scf.if v63 : i32 { + ^block56: + v449 = arith.constant 0 : i32; + scf.yield v449; + } else { + ^block12: + v65 = arith.constant 12 : u32; + v64 = hir.bitcast v0 : u32; + v66 = arith.add v64, v65 : u32 #[overflow = checked]; + v448 = arith.constant 4 : u32; + v68 = arith.mod v66, v448 : u32; + hir.assertz v68 #[code = 250]; + v69 = hir.int_to_ptr v66 : ptr; + v70 = hir.load v69 : felt; + v447 = arith.constant 12 : u32; + v71 = hir.bitcast v1 : u32; + v73 = arith.add v71, v447 : u32 #[overflow = checked]; + v446 = arith.constant 4 : u32; + v75 = arith.mod v73, v446 : u32; + hir.assertz v75 #[code = 250]; + v76 = hir.int_to_ptr v73 : ptr; + v77 = hir.load v76 : felt; + v78 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/intrinsics::felt::eq(v70, v77) : i32 + v445 = arith.constant 1 : i32; + v80 = arith.eq v78, v445 : i1; + v81 = arith.zext v80 : u32; + v82 = hir.bitcast v81 : i32; + scf.yield v82; + }; + scf.yield v444; + }; + scf.yield v443; + }; + builtin.ret v441; + }; + + private builtin.function @rust_sdk_swapp_note_bindings::bindings::__link_custom_section_describing_imports() { + ^block13: + builtin.ret ; + }; + + private builtin.function @miden:base/note-script@1.0.0#run(v84: felt, v85: felt, v86: felt, v87: felt) { + ^block15(v84: felt, v85: felt, v86: felt, v87: felt): + v90 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v91 = hir.bitcast v90 : ptr; + v92 = hir.load v91 : i32; + v93 = arith.constant 48 : i32; + v94 = arith.sub v92, v93 : i32 #[overflow = wrapping]; + v95 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v96 = hir.bitcast v95 : ptr; + hir.store v96, v94; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/wit_bindgen::rt::run_ctors_once() + v97 = arith.constant 8 : i32; + v98 = arith.add v94, v97 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden_base_sys::bindings::note::get_sender(v98) + v100 = arith.constant 12 : u32; + v99 = hir.bitcast v94 : u32; + v101 = arith.add v99, v100 : u32 #[overflow = checked]; + v102 = arith.constant 4 : u32; + v103 = arith.mod v101, v102 : u32; + hir.assertz v103 #[code = 250]; + v104 = hir.int_to_ptr v101 : ptr; + v105 = hir.load v104 : felt; + v107 = arith.constant 8 : u32; + v106 = hir.bitcast v94 : u32; + v108 = arith.add v106, v107 : u32 #[overflow = checked]; + v497 = arith.constant 4 : u32; + v110 = arith.mod v108, v497 : u32; + hir.assertz v110 #[code = 250]; + v111 = hir.int_to_ptr v108 : ptr; + v112 = hir.load v111 : felt; + v113 = arith.constant 16 : i32; + v114 = arith.add v94, v113 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden_base_sys::bindings::note::get_script_root(v114) + v115 = arith.constant 32 : i32; + v116 = arith.add v94, v115 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden_base_sys::bindings::note::get_serial_number(v116) + v117 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden_base_sys::bindings::account::get_balance(v112, v105) : felt + v118 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/intrinsics::felt::eq(v112, v112) : i32 + v88 = arith.constant 0 : i32; + v119 = arith.constant 1 : i32; + v120 = arith.neq v118, v119 : i1; + v121 = arith.zext v120 : u32; + v122 = hir.bitcast v121 : i32; + v124 = arith.neq v122, v88 : i1; + v468 = scf.if v124 : u32 { + ^block65: + v464 = arith.constant 0 : u32; + scf.yield v464; + } else { + ^block18: + v125 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/intrinsics::felt::eq(v105, v105) : i32 + v495 = arith.constant 0 : i32; + v496 = arith.constant 1 : i32; + v127 = arith.neq v125, v496 : i1; + v128 = arith.zext v127 : u32; + v129 = hir.bitcast v128 : i32; + v131 = arith.neq v129, v495 : i1; + v470 = scf.if v131 : u32 { + ^block64: + v494 = arith.constant 0 : u32; + scf.yield v494; + } else { + ^block19: + v492 = arith.constant 16 : i32; + v135 = arith.add v94, v492 : i32 #[overflow = wrapping]; + v493 = arith.constant 16 : i32; + v133 = arith.add v94, v493 : i32 #[overflow = wrapping]; + v136 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/::eq(v133, v135) : i32 + v490 = arith.constant 0 : i32; + v491 = arith.constant 0 : i32; + v138 = arith.eq v136, v491 : i1; + v139 = arith.zext v138 : u32; + v140 = hir.bitcast v139 : i32; + v142 = arith.neq v140, v490 : i1; + v472 = scf.if v142 : u32 { + ^block63: + v489 = arith.constant 0 : u32; + scf.yield v489; + } else { + ^block20: + v487 = arith.constant 32 : i32; + v146 = arith.add v94, v487 : i32 #[overflow = wrapping]; + v488 = arith.constant 32 : i32; + v144 = arith.add v94, v488 : i32 #[overflow = wrapping]; + v147 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/::eq(v144, v146) : i32 + v485 = arith.constant 0 : i32; + v486 = arith.constant 0 : i32; + v149 = arith.eq v147, v486 : i1; + v150 = arith.zext v149 : u32; + v151 = hir.bitcast v150 : i32; + v153 = arith.neq v151, v485 : i1; + v474 = scf.if v153 : u32 { + ^block62: + v484 = arith.constant 0 : u32; + scf.yield v484; + } else { + ^block21: + v154 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/intrinsics::felt::eq(v117, v117) : i32 + v482 = arith.constant 0 : i32; + v483 = arith.constant 1 : i32; + v156 = arith.neq v154, v483 : i1; + v157 = arith.zext v156 : u32; + v158 = hir.bitcast v157 : i32; + v160 = arith.neq v158, v482 : i1; + scf.if v160{ + ^block61: + scf.yield ; + } else { + ^block22: + v481 = arith.constant 48 : i32; + v162 = arith.add v94, v481 : i32 #[overflow = wrapping]; + v163 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v164 = hir.bitcast v163 : ptr; + hir.store v164, v162; + scf.yield ; + }; + v466 = arith.constant 1 : u32; + v480 = arith.constant 0 : u32; + v478 = cf.select v160, v480, v466 : u32; + scf.yield v478; + }; + scf.yield v474; + }; + scf.yield v472; + }; + scf.yield v470; + }; + v479 = arith.constant 0 : u32; + v477 = arith.eq v468, v479 : i1; + cf.cond_br v477 ^block17, ^block67; + ^block17: + ub.unreachable ; + ^block67: + builtin.ret ; + }; + + private builtin.function @wit_bindgen::rt::run_ctors_once() { + ^block23: + v166 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/GOT.data.internal.__memory_base : ptr + v167 = hir.bitcast v166 : ptr; + v168 = hir.load v167 : i32; + v169 = arith.constant 1048584 : i32; + v170 = arith.add v168, v169 : i32 #[overflow = wrapping]; + v171 = hir.bitcast v170 : u32; + v172 = hir.int_to_ptr v171 : ptr; + v173 = hir.load v172 : u8; + v165 = arith.constant 0 : i32; + v174 = arith.zext v173 : u32; + v175 = hir.bitcast v174 : i32; + v177 = arith.neq v175, v165 : i1; + scf.if v177{ + ^block25: + scf.yield ; + } else { + ^block26: + v178 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/GOT.data.internal.__memory_base : ptr + v179 = hir.bitcast v178 : ptr; + v180 = hir.load v179 : i32; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__wasm_call_ctors() + v499 = arith.constant 1 : u8; + v501 = arith.constant 1048584 : i32; + v182 = arith.add v180, v501 : i32 #[overflow = wrapping]; + v186 = hir.bitcast v182 : u32; + v187 = hir.int_to_ptr v186 : ptr; + hir.store v187, v499; + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::account::get_balance(v188: felt, v189: felt) -> felt { + ^block27(v188: felt, v189: felt): + v191 = hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden::account::get_balance(v188, v189) : felt + builtin.ret v191; + }; + + private builtin.function @miden_base_sys::bindings::note::get_sender(v192: i32) { + ^block29(v192: i32): + v194 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v195 = hir.bitcast v194 : ptr; + v196 = hir.load v195 : i32; + v197 = arith.constant 16 : i32; + v198 = arith.sub v196, v197 : i32 #[overflow = wrapping]; + v199 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v200 = hir.bitcast v199 : ptr; + hir.store v200, v198; + v201 = arith.constant 8 : i32; + v202 = arith.add v198, v201 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden::note::get_sender(v202) + v204 = arith.constant 8 : u32; + v203 = hir.bitcast v198 : u32; + v205 = arith.add v203, v204 : u32 #[overflow = checked]; + v206 = arith.constant 4 : u32; + v207 = arith.mod v205, v206 : u32; + hir.assertz v207 #[code = 250]; + v208 = hir.int_to_ptr v205 : ptr; + v209 = hir.load v208 : i64; + v210 = hir.bitcast v192 : u32; + v503 = arith.constant 8 : u32; + v212 = arith.mod v210, v503 : u32; + hir.assertz v212 #[code = 250]; + v213 = hir.int_to_ptr v210 : ptr; + hir.store v213, v209; + v502 = arith.constant 16 : i32; + v215 = arith.add v198, v502 : i32 #[overflow = wrapping]; + v216 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v217 = hir.bitcast v216 : ptr; + hir.store v217, v215; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::note::get_script_root(v218: i32) { + ^block31(v218: i32): + v220 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v221 = hir.bitcast v220 : ptr; + v222 = hir.load v221 : i32; + v223 = arith.constant 32 : i32; + v224 = arith.sub v222, v223 : i32 #[overflow = wrapping]; + v225 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v226 = hir.bitcast v225 : ptr; + hir.store v226, v224; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden::note::get_script_root(v224) + v228 = arith.constant 8 : u32; + v227 = hir.bitcast v224 : u32; + v229 = arith.add v227, v228 : u32 #[overflow = checked]; + v508 = arith.constant 8 : u32; + v231 = arith.mod v229, v508 : u32; + hir.assertz v231 #[code = 250]; + v232 = hir.int_to_ptr v229 : ptr; + v233 = hir.load v232 : i64; + v235 = arith.constant 24 : u32; + v234 = hir.bitcast v224 : u32; + v236 = arith.add v234, v235 : u32 #[overflow = checked]; + v507 = arith.constant 8 : u32; + v238 = arith.mod v236, v507 : u32; + hir.assertz v238 #[code = 250]; + v239 = hir.int_to_ptr v236 : ptr; + hir.store v239, v233; + v240 = hir.bitcast v224 : u32; + v506 = arith.constant 8 : u32; + v242 = arith.mod v240, v506 : u32; + hir.assertz v242 #[code = 250]; + v243 = hir.int_to_ptr v240 : ptr; + v244 = hir.load v243 : i64; + v246 = arith.constant 16 : u32; + v245 = hir.bitcast v224 : u32; + v247 = arith.add v245, v246 : u32 #[overflow = checked]; + v505 = arith.constant 8 : u32; + v249 = arith.mod v247, v505 : u32; + hir.assertz v249 #[code = 250]; + v250 = hir.int_to_ptr v247 : ptr; + hir.store v250, v244; + v251 = arith.constant 16 : i32; + v252 = arith.add v224, v251 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden_stdlib_sys::intrinsics::word::Word::reverse(v218, v252) + v504 = arith.constant 32 : i32; + v254 = arith.add v224, v504 : i32 #[overflow = wrapping]; + v255 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v256 = hir.bitcast v255 : ptr; + hir.store v256, v254; + builtin.ret ; + }; + + private builtin.function @miden_base_sys::bindings::note::get_serial_number(v257: i32) { + ^block33(v257: i32): + v259 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v260 = hir.bitcast v259 : ptr; + v261 = hir.load v260 : i32; + v262 = arith.constant 32 : i32; + v263 = arith.sub v261, v262 : i32 #[overflow = wrapping]; + v264 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v265 = hir.bitcast v264 : ptr; + hir.store v265, v263; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden::note::get_serial_number(v263) + v267 = arith.constant 8 : u32; + v266 = hir.bitcast v263 : u32; + v268 = arith.add v266, v267 : u32 #[overflow = checked]; + v513 = arith.constant 8 : u32; + v270 = arith.mod v268, v513 : u32; + hir.assertz v270 #[code = 250]; + v271 = hir.int_to_ptr v268 : ptr; + v272 = hir.load v271 : i64; + v274 = arith.constant 24 : u32; + v273 = hir.bitcast v263 : u32; + v275 = arith.add v273, v274 : u32 #[overflow = checked]; + v512 = arith.constant 8 : u32; + v277 = arith.mod v275, v512 : u32; + hir.assertz v277 #[code = 250]; + v278 = hir.int_to_ptr v275 : ptr; + hir.store v278, v272; + v279 = hir.bitcast v263 : u32; + v511 = arith.constant 8 : u32; + v281 = arith.mod v279, v511 : u32; + hir.assertz v281 #[code = 250]; + v282 = hir.int_to_ptr v279 : ptr; + v283 = hir.load v282 : i64; + v285 = arith.constant 16 : u32; + v284 = hir.bitcast v263 : u32; + v286 = arith.add v284, v285 : u32 #[overflow = checked]; + v510 = arith.constant 8 : u32; + v288 = arith.mod v286, v510 : u32; + hir.assertz v288 #[code = 250]; + v289 = hir.int_to_ptr v286 : ptr; + hir.store v289, v283; + v290 = arith.constant 16 : i32; + v291 = arith.add v263, v290 : i32 #[overflow = wrapping]; + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden_stdlib_sys::intrinsics::word::Word::reverse(v257, v291) + v509 = arith.constant 32 : i32; + v293 = arith.add v263, v509 : i32 #[overflow = wrapping]; + v294 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v295 = hir.bitcast v294 : ptr; + hir.store v295, v293; + builtin.ret ; + }; + + private builtin.function @miden_stdlib_sys::intrinsics::word::Word::reverse(v296: i32, v297: i32) { + ^block35(v296: i32, v297: i32): + v300 = builtin.global_symbol @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/__stack_pointer : ptr + v301 = hir.bitcast v300 : ptr; + v302 = hir.load v301 : i32; + v303 = arith.constant 16 : i32; + v304 = arith.sub v302, v303 : i32 #[overflow = wrapping]; + v306 = arith.constant 8 : u32; + v305 = hir.bitcast v297 : u32; + v307 = arith.add v305, v306 : u32 #[overflow = checked]; + v600 = arith.constant 8 : u32; + v309 = arith.mod v307, v600 : u32; + hir.assertz v309 #[code = 250]; + v310 = hir.int_to_ptr v307 : ptr; + v311 = hir.load v310 : i64; + v599 = arith.constant 8 : u32; + v312 = hir.bitcast v304 : u32; + v314 = arith.add v312, v599 : u32 #[overflow = checked]; + v315 = arith.constant 4 : u32; + v316 = arith.mod v314, v315 : u32; + hir.assertz v316 #[code = 250]; + v317 = hir.int_to_ptr v314 : ptr; + hir.store v317, v311; + v318 = hir.bitcast v297 : u32; + v598 = arith.constant 8 : u32; + v320 = arith.mod v318, v598 : u32; + hir.assertz v320 #[code = 250]; + v321 = hir.int_to_ptr v318 : ptr; + v322 = hir.load v321 : i64; + v323 = hir.bitcast v304 : u32; + v597 = arith.constant 4 : u32; + v325 = arith.mod v323, v597 : u32; + hir.assertz v325 #[code = 250]; + v326 = hir.int_to_ptr v323 : ptr; + hir.store v326, v322; + v327 = arith.constant 12 : i32; + v328 = arith.add v304, v327 : i32 #[overflow = wrapping]; + v298 = arith.constant 0 : i32; + v568, v569, v570, v571, v572, v573 = scf.while v298, v304, v328, v296 : i32, i32, i32, i32, i32, i32 { + ^block81(v574: i32, v575: i32, v576: i32, v577: i32): + v596 = arith.constant 0 : i32; + v331 = arith.constant 8 : i32; + v332 = arith.eq v574, v331 : i1; + v333 = arith.zext v332 : u32; + v334 = hir.bitcast v333 : i32; + v336 = arith.neq v334, v596 : i1; + v562, v563 = scf.if v336 : i32, i32 { + ^block80: + v522 = ub.poison i32 : i32; + scf.yield v522, v522; + } else { + ^block40: + v338 = arith.add v575, v574 : i32 #[overflow = wrapping]; + v339 = hir.bitcast v338 : u32; + v595 = arith.constant 4 : u32; + v341 = arith.mod v339, v595 : u32; + hir.assertz v341 #[code = 250]; + v342 = hir.int_to_ptr v339 : ptr; + v343 = hir.load v342 : felt; + v345 = hir.bitcast v576 : u32; + v594 = arith.constant 4 : u32; + v347 = arith.mod v345, v594 : u32; + hir.assertz v347 #[code = 250]; + v348 = hir.int_to_ptr v345 : ptr; + v349 = hir.load v348 : i32; + v350 = hir.bitcast v338 : u32; + v593 = arith.constant 4 : u32; + v352 = arith.mod v350, v593 : u32; + hir.assertz v352 #[code = 250]; + v353 = hir.int_to_ptr v350 : ptr; + hir.store v353, v349; + v354 = hir.bitcast v576 : u32; + v592 = arith.constant 4 : u32; + v356 = arith.mod v354, v592 : u32; + hir.assertz v356 #[code = 250]; + v357 = hir.int_to_ptr v354 : ptr; + hir.store v357, v343; + v360 = arith.constant -4 : i32; + v361 = arith.add v576, v360 : i32 #[overflow = wrapping]; + v358 = arith.constant 4 : i32; + v359 = arith.add v574, v358 : i32 #[overflow = wrapping]; + scf.yield v359, v361; + }; + v590 = ub.poison i32 : i32; + v565 = cf.select v336, v590, v577 : i32; + v591 = ub.poison i32 : i32; + v564 = cf.select v336, v591, v575 : i32; + v521 = arith.constant 1 : u32; + v514 = arith.constant 0 : u32; + v567 = cf.select v336, v514, v521 : u32; + v555 = arith.trunc v567 : i1; + scf.condition v555, v562, v564, v563, v565, v575, v577; + } do { + ^block82(v578: i32, v579: i32, v580: i32, v581: i32, v582: i32, v583: i32): + scf.yield v578, v579, v580, v581; + }; + v589 = arith.constant 8 : u32; + v363 = hir.bitcast v572 : u32; + v365 = arith.add v363, v589 : u32 #[overflow = checked]; + v588 = arith.constant 4 : u32; + v367 = arith.mod v365, v588 : u32; + hir.assertz v367 #[code = 250]; + v368 = hir.int_to_ptr v365 : ptr; + v369 = hir.load v368 : i64; + v587 = arith.constant 8 : u32; + v370 = hir.bitcast v573 : u32; + v372 = arith.add v370, v587 : u32 #[overflow = checked]; + v586 = arith.constant 8 : u32; + v374 = arith.mod v372, v586 : u32; + hir.assertz v374 #[code = 250]; + v375 = hir.int_to_ptr v372 : ptr; + hir.store v375, v369; + v376 = hir.bitcast v572 : u32; + v585 = arith.constant 4 : u32; + v378 = arith.mod v376, v585 : u32; + hir.assertz v378 #[code = 250]; + v379 = hir.int_to_ptr v376 : ptr; + v380 = hir.load v379 : i64; + v381 = hir.bitcast v573 : u32; + v584 = arith.constant 8 : u32; + v383 = arith.mod v381, v584 : u32; + hir.assertz v383 #[code = 250]; + v384 = hir.int_to_ptr v381 : ptr; + hir.store v384, v380; + builtin.ret ; + }; + + private builtin.function @intrinsics::felt::eq(v385: felt, v386: felt) -> i32 { + ^block41(v385: felt, v386: felt): + v387 = arith.eq v385, v386 : i1; + v388 = hir.cast v387 : i32; + builtin.ret v388; + }; + + private builtin.function @miden::account::get_balance(v390: felt, v391: felt) -> felt { + ^block43(v390: felt, v391: felt): + v392 = hir.exec @miden/account/get_balance(v390, v391) : felt + builtin.ret v392; + }; + + private builtin.function @miden::note::get_sender(v394: i32) { + ^block47(v394: i32): + v395, v396 = hir.exec @miden/note/get_sender() : felt, felt + v397 = hir.bitcast v394 : u32; + v398 = hir.int_to_ptr v397 : ptr; + hir.store v398, v395; + v399 = arith.constant 4 : u32; + v400 = arith.add v397, v399 : u32 #[overflow = checked]; + v401 = hir.int_to_ptr v400 : ptr; + hir.store v401, v396; + builtin.ret ; + }; + + private builtin.function @miden::note::get_script_root(v402: i32) { + ^block50(v402: i32): + v403, v404, v405, v406 = hir.exec @miden/note/get_script_root() : felt, felt, felt, felt + v407 = hir.bitcast v402 : u32; + v408 = hir.int_to_ptr v407 : ptr; + hir.store v408, v403; + v409 = arith.constant 4 : u32; + v410 = arith.add v407, v409 : u32 #[overflow = checked]; + v411 = hir.int_to_ptr v410 : ptr; + hir.store v411, v404; + v412 = arith.constant 8 : u32; + v413 = arith.add v407, v412 : u32 #[overflow = checked]; + v414 = hir.int_to_ptr v413 : ptr; + hir.store v414, v405; + v415 = arith.constant 12 : u32; + v416 = arith.add v407, v415 : u32 #[overflow = checked]; + v417 = hir.int_to_ptr v416 : ptr; + hir.store v417, v406; + builtin.ret ; + }; + + private builtin.function @miden::note::get_serial_number(v418: i32) { + ^block52(v418: i32): + v419, v420, v421, v422 = hir.exec @miden/note/get_serial_number() : felt, felt, felt, felt + v423 = hir.bitcast v418 : u32; + v424 = hir.int_to_ptr v423 : ptr; + hir.store v424, v419; + v425 = arith.constant 4 : u32; + v426 = arith.add v423, v425 : u32 #[overflow = checked]; + v427 = hir.int_to_ptr v426 : ptr; + hir.store v427, v420; + v428 = arith.constant 8 : u32; + v429 = arith.add v423, v428 : u32 #[overflow = checked]; + v430 = hir.int_to_ptr v429 : ptr; + hir.store v430, v421; + v431 = arith.constant 12 : u32; + v432 = arith.add v423, v431 : u32 #[overflow = checked]; + v433 = hir.int_to_ptr v432 : ptr; + hir.store v433, v422; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable private @#GOT.data.internal.__memory_base : i32 { + builtin.ret_imm 0; + }; + + builtin.segment @1048576 = 0x0000000100000001; + }; + + public builtin.function @run(v434: felt, v435: felt, v436: felt, v437: felt) { + ^block54(v434: felt, v435: felt, v436: felt, v437: felt): + hir.exec @miden:base/note-script@1.0.0/rust_sdk_swapp_note_bindings/miden:base/note-script@1.0.0#run(v434, v435, v436, v437) + builtin.ret ; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.masm b/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.masm new file mode 100644 index 000000000..9761fa4f2 --- /dev/null +++ b/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.masm @@ -0,0 +1,1242 @@ +# mod miden:base/note-script@1.0.0 + +export.run + exec.::miden:base/note-script@1.0.0::init + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden:base/note-script@1.0.0#run + trace.252 + nop + exec.::std::sys::truncate_stack +end + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[7028007876379170725,18060021366771303825,13412364500725888848,14178532912296021363] + adv.push_mapval + push.262144 + push.1 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.0 + u32assert + mem_store.278537 +end + +# mod miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings + +proc.__wasm_call_ctors + nop +end + +proc.::eq + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + push.0 + else + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + push.0 + else + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + push.0 + else + push.12 + swap.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.12 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + swap.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::intrinsics::felt::eq + trace.252 + nop + push.1 + eq + end + end + end +end + +proc.rust_sdk_swapp_note_bindings::bindings::__link_custom_section_describing_imports + nop +end + +proc.miden:base/note-script@1.0.0#run + drop + drop + drop + drop + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.48 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::wit_bindgen::rt::run_ctors_once + trace.252 + nop + push.8 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden_base_sys::bindings::note::get_sender + trace.252 + nop + push.12 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.16 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden_base_sys::bindings::note::get_script_root + trace.252 + nop + push.32 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden_base_sys::bindings::note::get_serial_number + trace.252 + nop + dup.1 + dup.1 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden_base_sys::bindings::account::get_balance + trace.252 + nop + swap.1 + dup.0 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + drop + push.0 + else + swap.1 + dup.0 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + if.true + drop + drop + push.0 + else + push.16 + dup.2 + u32wrapping_add + push.16 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::::eq + trace.252 + nop + push.0 + push.0 + movup.2 + eq + neq + if.true + drop + drop + push.0 + else + push.32 + dup.2 + u32wrapping_add + push.32 + dup.3 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::::eq + trace.252 + nop + push.0 + push.0 + movup.2 + eq + neq + if.true + drop + drop + push.0 + else + dup.0 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::intrinsics::felt::eq + trace.252 + nop + push.0 + push.1 + movup.2 + neq + neq + dup.0 + if.true + swap.1 + drop + else + push.48 + movup.2 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.1 + push.0 + movup.2 + cdrop + end + end + end + end + push.0 + eq + if.true + push.0 + assert + else + nop + end +end + +proc.wit_bindgen::rt::run_ctors_once + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048584 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + push.0 + swap.1 + neq + if.true + nop + else + push.1114148 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::__wasm_call_ctors + trace.252 + nop + push.1 + push.1048584 + movup.2 + u32wrapping_add + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + end +end + +proc.miden_base_sys::bindings::account::get_balance + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden::account::get_balance + trace.252 + nop +end + +proc.miden_base_sys::bindings::note::get_sender + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.8 + dup.1 + u32wrapping_add + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden::note::get_sender + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.3 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_base_sys::bindings::note::get_script_root + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden::note::get_script_root + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.24 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.16 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_base_sys::bindings::note::get_serial_number + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114144 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.0 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden::note::get_serial_number + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.24 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + dup.0 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.16 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.16 + dup.1 + u32wrapping_add + movup.2 + trace.240 + nop + exec.::miden:base/note-script@1.0.0::rust_sdk_swapp_note_bindings::miden_stdlib_sys::intrinsics::word::Word::reverse + trace.252 + nop + push.32 + u32wrapping_add + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.miden_stdlib_sys::intrinsics::word::Word::reverse + push.1114144 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.8 + dup.3 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.12 + dup.1 + u32wrapping_add + push.0 + movup.2 + swap.1 + push.1 + while.true + push.0 + push.8 + dup.2 + eq + neq + dup.0 + if.true + movup.3 + movup.2 + drop + drop + push.3735929054 + dup.0 + else + dup.2 + dup.2 + u32wrapping_add + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4294967292 + movup.4 + u32wrapping_add + push.4 + movup.3 + u32wrapping_add + end + push.3735929054 + dup.3 + dup.6 + swap.2 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.6 + swap.2 + swap.1 + cdrop + push.1 + push.0 + movup.6 + cdrop + push.1 + u32and + swap.1 + swap.2 + swap.4 + swap.3 + swap.1 + if.true + movup.4 + drop + movup.4 + drop + push.1 + else + push.0 + end + end + drop + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + +proc.intrinsics::felt::eq + eq +end + +proc.miden::account::get_balance + trace.240 + nop + exec.::miden::account::get_balance + trace.252 + nop +end + +proc.miden::note::get_sender + trace.240 + nop + exec.::miden::note::get_sender + trace.252 + nop + movup.2 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::note::get_script_root + trace.240 + nop + exec.::miden::note::get_script_root + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + +proc.miden::note::get_serial_number + trace.240 + nop + exec.::miden::note::get_serial_number + trace.252 + nop + movup.4 + dup.0 + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.8 + dup.1 + add + u32assert + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + add + u32assert + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop +end + diff --git a/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.wat b/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.wat new file mode 100644 index 000000000..5685f9534 --- /dev/null +++ b/tests/integration/expected/rust_sdk/rust_sdk_swapp_note_bindings.wat @@ -0,0 +1,345 @@ +(component + (type (;0;) + (instance + (type (;0;) (record (field "inner" f32))) + (export (;1;) "felt" (type (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (export (;4;) "word" (type (eq 3))) + ) + ) + (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param f32 f32 f32 f32))) + (type (;3;) (func (param f32 f32) (result f32))) + (type (;4;) (func (param i32))) + (type (;5;) (func (param i32 i32))) + (type (;6;) (func (param f32 f32) (result i32))) + (table (;0;) 2 2 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global $GOT.data.internal.__memory_base (;1;) i32 i32.const 0) + (export "memory" (memory 0)) + (export "miden:base/note-script@1.0.0#run" (func $miden:base/note-script@1.0.0#run)) + (elem (;0;) (i32.const 1) func $rust_sdk_swapp_note_bindings::bindings::__link_custom_section_describing_imports) + (func $__wasm_call_ctors (;0;) (type 0)) + (func $::eq (;1;) (type 1) (param i32 i32) (result i32) + (local i32) + i32.const 0 + local.set 2 + block ;; label = @1 + local.get 0 + f32.load + local.get 1 + f32.load + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 0 + f32.load offset=4 + local.get 1 + f32.load offset=4 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 0 + f32.load offset=8 + local.get 1 + f32.load offset=8 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 0 + f32.load offset=12 + local.get 1 + f32.load offset=12 + call $intrinsics::felt::eq + i32.const 1 + i32.eq + local.set 2 + end + local.get 2 + ) + (func $rust_sdk_swapp_note_bindings::bindings::__link_custom_section_describing_imports (;2;) (type 0)) + (func $miden:base/note-script@1.0.0#run (;3;) (type 2) (param f32 f32 f32 f32) + (local i32 f32 f32 f32) + global.get $__stack_pointer + i32.const 48 + i32.sub + local.tee 4 + global.set $__stack_pointer + call $wit_bindgen::rt::run_ctors_once + local.get 4 + i32.const 8 + i32.add + call $miden_base_sys::bindings::note::get_sender + local.get 4 + f32.load offset=12 + local.set 5 + local.get 4 + f32.load offset=8 + local.set 6 + local.get 4 + i32.const 16 + i32.add + call $miden_base_sys::bindings::note::get_script_root + local.get 4 + i32.const 32 + i32.add + call $miden_base_sys::bindings::note::get_serial_number + local.get 6 + local.get 5 + call $miden_base_sys::bindings::account::get_balance + local.set 7 + block ;; label = @1 + local.get 6 + local.get 6 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 5 + local.get 5 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 4 + i32.const 16 + i32.add + local.get 4 + i32.const 16 + i32.add + call $::eq + i32.eqz + br_if 0 (;@1;) + local.get 4 + i32.const 32 + i32.add + local.get 4 + i32.const 32 + i32.add + call $::eq + i32.eqz + br_if 0 (;@1;) + local.get 7 + local.get 7 + call $intrinsics::felt::eq + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 4 + i32.const 48 + i32.add + global.set $__stack_pointer + return + end + unreachable + ) + (func $wit_bindgen::rt::run_ctors_once (;4;) (type 0) + (local i32) + block ;; label = @1 + global.get $GOT.data.internal.__memory_base + i32.const 1048584 + i32.add + i32.load8_u + br_if 0 (;@1;) + global.get $GOT.data.internal.__memory_base + local.set 0 + call $__wasm_call_ctors + local.get 0 + i32.const 1048584 + i32.add + i32.const 1 + i32.store8 + end + ) + (func $miden_base_sys::bindings::account::get_balance (;5;) (type 3) (param f32 f32) (result f32) + local.get 0 + local.get 1 + call $miden::account::get_balance + ) + (func $miden_base_sys::bindings::note::get_sender (;6;) (type 4) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 8 + i32.add + call $miden::note::get_sender + local.get 0 + local.get 1 + i64.load offset=8 align=4 + i64.store + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::note::get_script_root (;7;) (type 4) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + call $miden::note::get_script_root + local.get 1 + local.get 1 + i64.load offset=8 + i64.store offset=24 + local.get 1 + local.get 1 + i64.load + i64.store offset=16 + local.get 0 + local.get 1 + i32.const 16 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 1 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $miden_base_sys::bindings::note::get_serial_number (;8;) (type 4) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + call $miden::note::get_serial_number + local.get 1 + local.get 1 + i64.load offset=8 + i64.store offset=24 + local.get 1 + local.get 1 + i64.load + i64.store offset=16 + local.get 0 + local.get 1 + i32.const 16 + i32.add + call $miden_stdlib_sys::intrinsics::word::Word::reverse + local.get 1 + i32.const 32 + i32.add + global.set $__stack_pointer + ) + (func $miden_stdlib_sys::intrinsics::word::Word::reverse (;9;) (type 5) (param i32 i32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 2 + local.get 1 + i64.load offset=8 + i64.store offset=8 align=4 + local.get 2 + local.get 1 + i64.load + i64.store align=4 + local.get 2 + i32.const 12 + i32.add + local.set 3 + i32.const 0 + local.set 1 + block ;; label = @1 + loop ;; label = @2 + local.get 1 + i32.const 8 + i32.eq + br_if 1 (;@1;) + local.get 2 + local.get 1 + i32.add + local.tee 4 + f32.load + local.set 5 + local.get 4 + local.get 3 + i32.load + i32.store + local.get 3 + local.get 5 + f32.store + local.get 1 + i32.const 4 + i32.add + local.set 1 + local.get 3 + i32.const -4 + i32.add + local.set 3 + br 0 (;@2;) + end + end + local.get 0 + local.get 2 + i64.load offset=8 align=4 + i64.store offset=8 + local.get 0 + local.get 2 + i64.load align=4 + i64.store + ) + (func $intrinsics::felt::eq (;10;) (type 6) (param f32 f32) (result i32) + unreachable + ) + (func $miden::account::get_balance (;11;) (type 3) (param f32 f32) (result f32) + unreachable + ) + (func $miden::note::get_sender (;12;) (type 4) (param i32) + unreachable + ) + (func $miden::note::get_script_root (;13;) (type 4) (param i32) + unreachable + ) + (func $miden::note::get_serial_number (;14;) (type 4) (param i32) + unreachable + ) + (data $.data (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00") + ) + (alias export 0 "word" (type (;1;))) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (type (;2;) (func (param "arg" 1))) + (alias core export 0 "miden:base/note-script@1.0.0#run" (core func (;0;))) + (func (;0;) (type 2) (canon lift (core func 0))) + (alias export 0 "felt" (type (;3;))) + (alias export 0 "word" (type (;4;))) + (component (;0;) + (type (;0;) (record (field "inner" f32))) + (import "import-type-felt" (type (;1;) (eq 0))) + (type (;2;) (tuple 1 1 1 1)) + (type (;3;) (record (field "inner" 2))) + (import "import-type-word" (type (;4;) (eq 3))) + (import "import-type-word0" (type (;5;) (eq 4))) + (type (;6;) (func (param "arg" 5))) + (import "import-func-run" (func (;0;) (type 6))) + (export (;7;) "word" (type 4)) + (type (;8;) (func (param "arg" 7))) + (export (;1;) "run" (func 0) (func (type 8))) + ) + (instance (;1;) (instantiate 0 + (with "import-func-run" (func 0)) + (with "import-type-felt" (type 3)) + (with "import-type-word" (type 4)) + (with "import-type-word0" (type 1)) + ) + ) + (export (;2;) "miden:base/note-script@1.0.0" (instance 1)) +) diff --git a/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.hir b/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.hir index e32e397f6..bcfa2b3be 100644 --- a/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.hir +++ b/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.hir @@ -1,1614 +1,2270 @@ -(component - ;; Component Imports - (lower ((digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi canon) (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)))) (#miden::account #add_asset) - (lower ((digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi canon) (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)))) (#miden::account #remove_asset) - (lower ((digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi canon) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32 i32 i32 i32 i32 i32 i32 i32)))) (#std::crypto::hashes::blake3 #hash_2to1) - (lower ((digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi canon) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param i32) (param i32) (result felt felt felt felt felt felt felt felt felt felt felt felt i32)))) (#std::mem #pipe_double_words_to_memory) - - ;; Modules - (module #miden_sdk_account_test - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - - ;; Functions - (func (export #core::alloc::global::GlobalAlloc::alloc_zeroed) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (call #::alloc v0 v1 v2)) - (let (v5 i1) (eq v4 0)) - (let (v6 i32) (zext v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 2 v4) (block 3))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 (param v13 i32) - (br (block 1 v13))) - - (block 3 - (let (v8 i32) (const.i32 0)) - (let (v9 u8) (trunc v8)) - (let (v10 u32) (bitcast v2)) - (let (v11 u32) (bitcast v4)) - (let (v12 (ptr u8)) (inttoptr v11)) - (memset v12 v10 v9) - (br (block 2 v4))) - ) - - (func (export #get_wallet_magic_number) (result felt) - (block 0 - (let (v1 felt) (const.felt 0)) - (let (v2 felt) (call #miden_base_sys::bindings::tx::get_id)) - (let (v3 i64) (const.i64 42)) - (let (v4 felt) (cast v3)) - (let (v5 felt) (add.unchecked v4 v2)) - (br (block 1 v5))) - - (block 1 (param v0 felt) - (ret v0)) - ) - - (func (export #test_add_asset) (result felt) - (block 0 - (let (v1 i32) (const.i32 0)) - (let (v2 felt) (const.felt 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 64)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 i32) (const.i32 -32)) - (let (v7 i32) (band v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i64) (const.i64 1)) - (let (v10 felt) (cast v9)) - (let (v11 i64) (const.i64 2)) - (let (v12 felt) (cast v11)) - (let (v13 i64) (const.i64 3)) - (let (v14 felt) (cast v13)) - (let (v15 i64) (const.i64 4)) - (let (v16 felt) (cast v15)) - (let (v17 u32) (bitcast v7)) - (let (v18 u32) (add.checked v17 12)) - (let (v19 u32) (mod.unchecked v18 4)) - (assertz 250 v19) - (let (v20 (ptr felt)) (inttoptr v18)) - (store v20 v16) - (let (v21 u32) (bitcast v7)) - (let (v22 u32) (add.checked v21 8)) - (let (v23 u32) (mod.unchecked v22 4)) - (assertz 250 v23) - (let (v24 (ptr felt)) (inttoptr v22)) - (store v24 v14) - (let (v25 u32) (bitcast v7)) - (let (v26 u32) (add.checked v25 4)) - (let (v27 u32) (mod.unchecked v26 4)) - (assertz 250 v27) - (let (v28 (ptr felt)) (inttoptr v26)) - (store v28 v12) - (let (v29 u32) (bitcast v7)) - (let (v30 u32) (mod.unchecked v29 4)) - (assertz 250 v30) - (let (v31 (ptr felt)) (inttoptr v29)) - (store v31 v10) - (let (v32 i32) (const.i32 32)) - (let (v33 i32) (add.wrapping v7 v32)) - (call #miden_base_sys::bindings::tx::add_asset v33 v7) - (let (v34 u32) (bitcast v7)) - (let (v35 u32) (add.checked v34 32)) - (let (v36 u32) (mod.unchecked v35 4)) - (assertz 250 v36) - (let (v37 (ptr felt)) (inttoptr v35)) - (let (v38 felt) (load v37)) - (let (v39 (ptr i32)) (global.symbol #__stack_pointer)) - (store v39 v3) - (br (block 1 v38))) - - (block 1 (param v0 felt) - (ret v0)) - ) - - (func (export #test_felt_ops_smoke) - (param felt) (param felt) (result felt) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 i64) (const.i64 0)) - (let (v4 i64) (cast v0)) - (let (v5 i1) (gt v0 v1)) - (let (v6 i32) (cast v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 7) (block 8))) - - (block 1 (param v2 felt) - (ret v2)) - - (block 2 - (let (v41 felt) (neg v0)) - (br (block 1 v41))) - - (block 3 - (assert.eq v1 v0) - (let (v39 felt) (cast v4)) - (let (v40 felt) (add.unchecked v0 v39)) - (ret v40)) - - (block 4 - (let (v38 felt) (div.unchecked v1 v0)) - (ret v38)) - - (block 5 - (let (v36 felt) (pow2 v0)) - (let (v37 felt) (mul.unchecked v36 v1)) - (ret v37)) - - (block 6 - (let (v34 felt) (exp v0 v1)) - (let (v35 felt) (sub.unchecked v34 v1)) - (ret v35)) - - (block 7 - (let (v32 felt) (inv v0)) - (let (v33 felt) (add.unchecked v32 v1)) - (ret v33)) - - (block 8 - (let (v8 i1) (lt v1 v0)) - (let (v9 i32) (cast v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 6) (block 9))) - - (block 9 - (let (v11 i1) (lte v1 v0)) - (let (v12 i32) (cast v11)) - (let (v13 i1) (neq v12 0)) - (condbr v13 (block 5) (block 10))) - - (block 10 - (let (v14 i1) (gte v0 v1)) - (let (v15 i32) (cast v14)) - (let (v16 i1) (neq v15 0)) - (condbr v16 (block 4) (block 11))) - - (block 11 - (let (v17 i1) (eq v0 v1)) - (let (v18 i32) (cast v17)) - (let (v19 i32) (const.i32 1)) - (let (v20 i1) (eq v18 v19)) - (let (v21 i32) (zext v20)) - (let (v22 i1) (neq v21 0)) - (condbr v22 (block 3) (block 12))) - - (block 12 - (let (v23 i1) (eq v0 v1)) - (let (v24 i32) (cast v23)) - (let (v25 i32) (const.i32 1)) - (let (v26 i1) (neq v24 v25)) - (let (v27 i32) (zext v26)) - (let (v28 i1) (neq v27 0)) - (condbr v28 (block 2) (block 13))) - - (block 13 - (let (v29 i1) (is_odd v1)) - (let (v30 i32) (cast v29)) - (let (v31 i1) (neq v30 0)) - (condbr v31 (block 14) (block 15))) - - (block 14 - (assert v0) - (ret v1)) - - (block 15 - (assertz v1) - (ret v0)) - ) - - (func (export #note_script) (result felt) - (block 0 - (let (v1 i32) (const.i32 0)) - (let (v2 felt) (const.felt 0)) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v5 i32) (const.i32 16)) - (let (v6 i32) (sub.wrapping v4 v5)) - (let (v7 (ptr i32)) (global.symbol #__stack_pointer)) - (store v7 v6) - (let (v8 i64) (const.i64 0)) - (let (v9 felt) (cast v8)) - (let (v10 i32) (const.i32 4)) - (let (v11 i32) (add.wrapping v6 v10)) - (call #miden_base_sys::bindings::tx::get_inputs v11) - (let (v12 u32) (bitcast v6)) - (let (v13 u32) (add.checked v12 12)) - (let (v14 u32) (mod.unchecked v13 4)) - (assertz 250 v14) - (let (v15 (ptr i32)) (inttoptr v13)) - (let (v16 i32) (load v15)) - (let (v17 i32) (const.i32 2)) - (let (v18 u32) (bitcast v17)) - (let (v19 i32) (shl.wrapping v16 v18)) - (let (v20 u32) (bitcast v6)) - (let (v21 u32) (add.checked v20 8)) - (let (v22 u32) (mod.unchecked v21 4)) - (assertz 250 v22) - (let (v23 (ptr i32)) (inttoptr v21)) - (let (v24 i32) (load v23)) - (br (block 2 v19 v6 v9 v24))) - - (block 1 (param v0 felt)) - - (block 2 - (param v26 i32) - (param v28 i32) - (param v32 felt) - (param v35 i32) - (let (v27 i1) (neq v26 0)) - (condbr v27 (block 4) (block 5))) - - (block 3 (param v25 felt)) - - (block 4 - (let (v33 i32) (const.i32 -4)) - (let (v34 i32) (add.wrapping v26 v33)) - (let (v36 u32) (bitcast v35)) - (let (v37 u32) (mod.unchecked v36 4)) - (assertz 250 v37) - (let (v38 (ptr felt)) (inttoptr v36)) - (let (v39 felt) (load v38)) - (let (v40 felt) (add.unchecked v32 v39)) - (let (v41 i32) (const.i32 4)) - (let (v42 i32) (add.wrapping v35 v41)) - (br (block 2 v34 v28 v40 v42))) - - (block 5 - (let (v29 i32) (const.i32 16)) - (let (v30 i32) (add.wrapping v28 v29)) - (let (v31 (ptr i32)) (global.symbol #__stack_pointer)) - (store v31 v30) - (ret v32)) - ) - - (func (export #test_blake3_hash_1to1) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 32)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 i32) (const.i32 -32)) - (let (v7 i32) (band v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 u32) (bitcast v1)) - (let (v10 (ptr i32)) (inttoptr v9)) - (let (v11 i32) (load v10)) - (let (v12 u32) (bitcast v1)) - (let (v13 u32) (add.checked v12 4)) - (let (v14 (ptr i32)) (inttoptr v13)) - (let (v15 i32) (load v14)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (add.checked v16 8)) - (let (v18 (ptr i32)) (inttoptr v17)) - (let (v19 i32) (load v18)) - (let (v20 u32) (bitcast v1)) - (let (v21 u32) (add.checked v20 12)) - (let (v22 (ptr i32)) (inttoptr v21)) - (let (v23 i32) (load v22)) - (let (v24 u32) (bitcast v1)) - (let (v25 u32) (add.checked v24 16)) - (let (v26 (ptr i32)) (inttoptr v25)) - (let (v27 i32) (load v26)) - (let (v28 u32) (bitcast v1)) - (let (v29 u32) (add.checked v28 20)) - (let (v30 (ptr i32)) (inttoptr v29)) - (let (v31 i32) (load v30)) - (let (v32 u32) (bitcast v1)) - (let (v33 u32) (add.checked v32 24)) - (let (v34 (ptr i32)) (inttoptr v33)) - (let (v35 i32) (load v34)) - (let (v36 u32) (bitcast v1)) - (let (v37 u32) (add.checked v36 28)) - (let (v38 (ptr i32)) (inttoptr v37)) - (let (v39 i32) (load v38)) - (let [(v40 i32) (v41 i32) (v42 i32) (v43 i32) (v44 i32) (v45 i32) (v46 i32) (v47 i32)] (call (#std::crypto::hashes::blake3 #hash_1to1) v11 v15 v19 v23 v27 v31 v35 v39)) - (let (v48 u32) (bitcast v7)) - (let (v49 (ptr i32)) (inttoptr v48)) - (store v49 v40) - (let (v50 u32) (add.checked v48 4)) - (let (v51 (ptr i32)) (inttoptr v50)) - (store v51 v41) - (let (v52 u32) (add.checked v48 8)) - (let (v53 (ptr i32)) (inttoptr v52)) - (store v53 v42) - (let (v54 u32) (add.checked v48 12)) - (let (v55 (ptr i32)) (inttoptr v54)) - (store v55 v43) - (let (v56 u32) (add.checked v48 16)) - (let (v57 (ptr i32)) (inttoptr v56)) - (store v57 v44) - (let (v58 u32) (add.checked v48 20)) - (let (v59 (ptr i32)) (inttoptr v58)) - (store v59 v45) - (let (v60 u32) (add.checked v48 24)) - (let (v61 (ptr i32)) (inttoptr v60)) - (store v61 v46) - (let (v62 u32) (add.checked v48 28)) - (let (v63 (ptr i32)) (inttoptr v62)) - (store v63 v47) - (let (v64 i32) (const.i32 24)) - (let (v65 i32) (add.wrapping v0 v64)) - (let (v66 u32) (bitcast v7)) - (let (v67 u32) (add.checked v66 24)) - (let (v68 u32) (mod.unchecked v67 8)) - (assertz 250 v68) - (let (v69 (ptr i64)) (inttoptr v67)) - (let (v70 i64) (load v69)) - (let (v71 u32) (bitcast v65)) - (let (v72 (ptr i64)) (inttoptr v71)) - (store v72 v70) - (let (v73 i32) (const.i32 16)) - (let (v74 i32) (add.wrapping v0 v73)) - (let (v75 u32) (bitcast v7)) - (let (v76 u32) (add.checked v75 16)) - (let (v77 u32) (mod.unchecked v76 8)) - (assertz 250 v77) - (let (v78 (ptr i64)) (inttoptr v76)) - (let (v79 i64) (load v78)) - (let (v80 u32) (bitcast v74)) - (let (v81 (ptr i64)) (inttoptr v80)) - (store v81 v79) - (let (v82 i32) (const.i32 8)) - (let (v83 i32) (add.wrapping v0 v82)) - (let (v84 u32) (bitcast v7)) - (let (v85 u32) (add.checked v84 8)) - (let (v86 u32) (mod.unchecked v85 8)) - (assertz 250 v86) - (let (v87 (ptr i64)) (inttoptr v85)) - (let (v88 i64) (load v87)) - (let (v89 u32) (bitcast v83)) - (let (v90 (ptr i64)) (inttoptr v89)) - (store v90 v88) - (let (v91 u32) (bitcast v7)) - (let (v92 u32) (mod.unchecked v91 8)) - (assertz 250 v92) - (let (v93 (ptr i64)) (inttoptr v91)) - (let (v94 i64) (load v93)) - (let (v95 u32) (bitcast v0)) - (let (v96 (ptr i64)) (inttoptr v95)) - (store v96 v94) - (let (v97 (ptr i32)) (global.symbol #__stack_pointer)) - (store v97 v3) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_blake3_hash_2to1) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 u32) (bitcast v1)) - (let (v3 (ptr i32)) (inttoptr v2)) - (let (v4 i32) (load v3)) - (let (v5 u32) (bitcast v1)) - (let (v6 u32) (add.checked v5 4)) - (let (v7 (ptr i32)) (inttoptr v6)) - (let (v8 i32) (load v7)) - (let (v9 u32) (bitcast v1)) - (let (v10 u32) (add.checked v9 8)) - (let (v11 (ptr i32)) (inttoptr v10)) - (let (v12 i32) (load v11)) - (let (v13 u32) (bitcast v1)) - (let (v14 u32) (add.checked v13 12)) - (let (v15 (ptr i32)) (inttoptr v14)) - (let (v16 i32) (load v15)) - (let (v17 u32) (bitcast v1)) - (let (v18 u32) (add.checked v17 16)) - (let (v19 (ptr i32)) (inttoptr v18)) - (let (v20 i32) (load v19)) - (let (v21 u32) (bitcast v1)) - (let (v22 u32) (add.checked v21 20)) - (let (v23 (ptr i32)) (inttoptr v22)) - (let (v24 i32) (load v23)) - (let (v25 u32) (bitcast v1)) - (let (v26 u32) (add.checked v25 24)) - (let (v27 (ptr i32)) (inttoptr v26)) - (let (v28 i32) (load v27)) - (let (v29 u32) (bitcast v1)) - (let (v30 u32) (add.checked v29 28)) - (let (v31 (ptr i32)) (inttoptr v30)) - (let (v32 i32) (load v31)) - (let (v33 u32) (bitcast v1)) - (let (v34 u32) (add.checked v33 32)) - (let (v35 (ptr i32)) (inttoptr v34)) - (let (v36 i32) (load v35)) - (let (v37 u32) (bitcast v1)) - (let (v38 u32) (add.checked v37 36)) - (let (v39 (ptr i32)) (inttoptr v38)) - (let (v40 i32) (load v39)) - (let (v41 u32) (bitcast v1)) - (let (v42 u32) (add.checked v41 40)) - (let (v43 (ptr i32)) (inttoptr v42)) - (let (v44 i32) (load v43)) - (let (v45 u32) (bitcast v1)) - (let (v46 u32) (add.checked v45 44)) - (let (v47 (ptr i32)) (inttoptr v46)) - (let (v48 i32) (load v47)) - (let (v49 u32) (bitcast v1)) - (let (v50 u32) (add.checked v49 48)) - (let (v51 (ptr i32)) (inttoptr v50)) - (let (v52 i32) (load v51)) - (let (v53 u32) (bitcast v1)) - (let (v54 u32) (add.checked v53 52)) - (let (v55 (ptr i32)) (inttoptr v54)) - (let (v56 i32) (load v55)) - (let (v57 u32) (bitcast v1)) - (let (v58 u32) (add.checked v57 56)) - (let (v59 (ptr i32)) (inttoptr v58)) - (let (v60 i32) (load v59)) - (let (v61 u32) (bitcast v1)) - (let (v62 u32) (add.checked v61 60)) - (let (v63 (ptr i32)) (inttoptr v62)) - (let (v64 i32) (load v63)) - (let [(v65 i32) (v66 i32) (v67 i32) (v68 i32) (v69 i32) (v70 i32) (v71 i32) (v72 i32)] (call (#std::crypto::hashes::blake3 #hash_2to1) v4 v8 v12 v16 v20 v24 v28 v32 v36 v40 v44 v48 v52 v56 v60 v64)) - (let (v73 u32) (bitcast v0)) - (let (v74 (ptr i32)) (inttoptr v73)) - (store v74 v65) - (let (v75 u32) (add.checked v73 4)) - (let (v76 (ptr i32)) (inttoptr v75)) - (store v76 v66) - (let (v77 u32) (add.checked v73 8)) - (let (v78 (ptr i32)) (inttoptr v77)) - (store v78 v67) - (let (v79 u32) (add.checked v73 12)) - (let (v80 (ptr i32)) (inttoptr v79)) - (store v80 v68) - (let (v81 u32) (add.checked v73 16)) - (let (v82 (ptr i32)) (inttoptr v81)) - (store v82 v69) - (let (v83 u32) (add.checked v73 20)) - (let (v84 (ptr i32)) (inttoptr v83)) - (store v84 v70) - (let (v85 u32) (add.checked v73 24)) - (let (v86 (ptr i32)) (inttoptr v85)) - (store v86 v71) - (let (v87 u32) (add.checked v73 28)) - (let (v88 (ptr i32)) (inttoptr v87)) - (store v88 v72) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_rpo_falcon512_verify) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 u32) (bitcast v0)) - (let (v3 u32) (mod.unchecked v2 4)) - (assertz 250 v3) - (let (v4 (ptr felt)) (inttoptr v2)) - (let (v5 felt) (load v4)) - (let (v6 u32) (bitcast v0)) - (let (v7 u32) (add.checked v6 4)) - (let (v8 u32) (mod.unchecked v7 4)) - (assertz 250 v8) - (let (v9 (ptr felt)) (inttoptr v7)) - (let (v10 felt) (load v9)) - (let (v11 u32) (bitcast v0)) - (let (v12 u32) (add.checked v11 8)) - (let (v13 u32) (mod.unchecked v12 4)) - (assertz 250 v13) - (let (v14 (ptr felt)) (inttoptr v12)) - (let (v15 felt) (load v14)) - (let (v16 u32) (bitcast v0)) - (let (v17 u32) (add.checked v16 12)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr felt)) (inttoptr v17)) - (let (v20 felt) (load v19)) - (let (v21 u32) (bitcast v1)) - (let (v22 u32) (mod.unchecked v21 4)) - (assertz 250 v22) - (let (v23 (ptr felt)) (inttoptr v21)) - (let (v24 felt) (load v23)) - (let (v25 u32) (bitcast v1)) - (let (v26 u32) (add.checked v25 4)) - (let (v27 u32) (mod.unchecked v26 4)) - (assertz 250 v27) - (let (v28 (ptr felt)) (inttoptr v26)) - (let (v29 felt) (load v28)) - (let (v30 u32) (bitcast v1)) - (let (v31 u32) (add.checked v30 8)) - (let (v32 u32) (mod.unchecked v31 4)) - (assertz 250 v32) - (let (v33 (ptr felt)) (inttoptr v31)) - (let (v34 felt) (load v33)) - (let (v35 u32) (bitcast v1)) - (let (v36 u32) (add.checked v35 12)) - (let (v37 u32) (mod.unchecked v36 4)) - (assertz 250 v37) - (let (v38 (ptr felt)) (inttoptr v36)) - (let (v39 felt) (load v38)) - (call (#std::crypto::dsa::rpo_falcon512 #rpo_falcon512_verify) v5 v10 v15 v20 v24 v29 v34 v39) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_pipe_words_to_memory) (param i32) (param felt) - (block 0 (param v0 i32) (param v1 felt) - (call #miden_stdlib_sys::stdlib::mem::pipe_words_to_memory v0 v1) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_pipe_double_words_to_memory) - (param i32) (param felt) - (block 0 (param v0 i32) (param v1 felt) - (call #miden_stdlib_sys::stdlib::mem::pipe_double_words_to_memory v0 v1) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_remove_asset) (param i32) (result felt) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 felt) (const.felt 0)) - (let (v4 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v5 i32) (const.i32 32)) - (let (v6 i32) (sub.wrapping v4 v5)) - (let (v7 i32) (const.i32 -32)) - (let (v8 i32) (band v6 v7)) - (let (v9 (ptr i32)) (global.symbol #__stack_pointer)) - (store v9 v8) - (call #miden_base_sys::bindings::tx::remove_asset v8 v0) - (let (v10 u32) (bitcast v8)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr felt)) (inttoptr v10)) - (let (v13 felt) (load v12)) - (let (v14 (ptr i32)) (global.symbol #__stack_pointer)) - (store v14 v4) - (br (block 1 v13))) - - (block 1 (param v1 felt) - (ret v1)) - ) - - (func (export #test_create_note) - (param i32) (param felt) (param felt) (param i32) (result felt) - (block 0 - (param v0 i32) - (param v1 felt) - (param v2 felt) - (param v3 i32) - (let (v5 felt) (call #miden_base_sys::bindings::tx::create_note v0 v1 v2 v3)) - (br (block 1 v5))) - - (block 1 (param v4 felt) - (ret v4)) - ) - - (func (export #__rust_alloc) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048576)) - (let (v4 i32) (call #::alloc v3 v1 v0)) - (br (block 1 v4))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #__rust_alloc_zeroed) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048576)) - (let (v4 i32) (call #core::alloc::global::GlobalAlloc::alloc_zeroed v3 v1 v0)) - (br (block 1 v4))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #::alloc) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (const.i32 32)) - (let (v6 i32) (const.i32 32)) - (let (v7 u32) (bitcast v1)) - (let (v8 u32) (bitcast v6)) - (let (v9 i1) (gt v7 v8)) - (let (v10 i32) (sext v9)) - (let (v11 i1) (neq v10 0)) - (let (v12 i32) (select v11 v1 v5)) - (let (v13 u32) (popcnt v12)) - (let (v14 i32) (bitcast v13)) - (let (v15 i32) (const.i32 1)) - (let (v16 i1) (neq v14 v15)) - (let (v17 i32) (zext v16)) - (let (v18 i1) (neq v17 0)) - (condbr v18 (block 2) (block 3))) - - (block 1 (param v3 i32)) - - (block 2 - (unreachable)) - - (block 3 - (let (v19 i32) (const.i32 -2147483648)) - (let (v20 i32) (sub.wrapping v19 v12)) - (let (v21 u32) (bitcast v20)) - (let (v22 u32) (bitcast v2)) - (let (v23 i1) (lt v21 v22)) - (let (v24 i32) (sext v23)) - (let (v25 i1) (neq v24 0)) - (condbr v25 (block 2) (block 4))) - - (block 4 - (let (v26 i32) (const.i32 0)) - (let (v27 i32) (add.wrapping v12 v2)) - (let (v28 i32) (const.i32 -1)) - (let (v29 i32) (add.wrapping v27 v28)) - (let (v30 i32) (const.i32 0)) - (let (v31 i32) (sub.wrapping v30 v12)) - (let (v32 i32) (band v29 v31)) - (let (v33 u32) (bitcast v0)) - (let (v34 u32) (mod.unchecked v33 4)) - (assertz 250 v34) - (let (v35 (ptr i32)) (inttoptr v33)) - (let (v36 i32) (load v35)) - (let (v37 i1) (neq v36 0)) - (condbr v37 (block 5 v0 v32 v12 v26) (block 6))) - - (block 5 - (param v49 i32) - (param v55 i32) - (param v65 i32) - (param v68 i32) - (let (v48 i32) (const.i32 268435456)) - (let (v50 u32) (bitcast v49)) - (let (v51 u32) (mod.unchecked v50 4)) - (assertz 250 v51) - (let (v52 (ptr i32)) (inttoptr v50)) - (let (v53 i32) (load v52)) - (let (v54 i32) (sub.wrapping v48 v53)) - (let (v56 u32) (bitcast v54)) - (let (v57 u32) (bitcast v55)) - (let (v58 i1) (lt v56 v57)) - (let (v59 i32) (sext v58)) - (let (v60 i1) (neq v59 0)) - (condbr v60 (block 7 v68) (block 8))) - - (block 6 - (let (v38 u32) (call (#intrinsics::mem #heap_base))) - (let (v39 u32) (memory.size)) - (let (v40 i32) (const.i32 16)) - (let (v41 u32) (bitcast v40)) - (let (v42 u32) (shl.wrapping v39 v41)) - (let (v43 u32) (add.wrapping v38 v42)) - (let (v44 i32) (bitcast v43)) - (let (v45 u32) (bitcast v0)) - (let (v46 u32) (mod.unchecked v45 4)) - (assertz 250 v46) - (let (v47 (ptr i32)) (inttoptr v45)) - (store v47 v44) - (br (block 5 v0 v32 v12 v26))) - - (block 7 (param v67 i32) - (ret v67)) - - (block 8 - (let (v61 i32) (add.wrapping v53 v55)) - (let (v62 u32) (bitcast v49)) - (let (v63 u32) (mod.unchecked v62 4)) - (assertz 250 v63) - (let (v64 (ptr i32)) (inttoptr v62)) - (store v64 v61) - (let (v66 i32) (add.wrapping v53 v65)) - (br (block 7 v66))) - ) - - (func (export #miden_base_sys::bindings::tx::get_id) - (result felt) - (block 0 - (let (v1 felt) (call (#miden::account #get_id))) - (br (block 1 v1))) - - (block 1 (param v0 felt) - (ret v0)) - ) - - (func (export #miden_base_sys::bindings::tx::get_inputs) - (param i32) - (block 0 (param v0 i32) - (let (v1 i32) (const.i32 0)) - (let (v2 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v3 i32) (const.i32 16)) - (let (v4 i32) (sub.wrapping v2 v3)) - (let (v5 (ptr i32)) (global.symbol #__stack_pointer)) - (store v5 v4) - (let (v6 i32) (const.i32 4)) - (let (v7 i32) (add.wrapping v4 v6)) - (let (v8 i32) (const.i32 256)) - (let (v9 i32) (const.i32 0)) - (call #alloc::raw_vec::RawVec::try_allocate_in v7 v8 v9) - (let (v10 u32) (bitcast v4)) - (let (v11 u32) (add.checked v10 8)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 u32) (bitcast v4)) - (let (v16 u32) (add.checked v15 4)) - (let (v17 u32) (mod.unchecked v16 4)) - (assertz 250 v17) - (let (v18 (ptr i32)) (inttoptr v16)) - (let (v19 i32) (load v18)) - (let (v20 i32) (const.i32 1)) - (let (v21 i1) (neq v19 v20)) - (let (v22 i32) (zext v21)) - (let (v23 i1) (neq v22 0)) - (condbr v23 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (let (v29 u32) (bitcast v4)) - (let (v30 u32) (add.checked v29 12)) - (let (v31 u32) (mod.unchecked v30 4)) - (assertz 250 v31) - (let (v32 (ptr i32)) (inttoptr v30)) - (let (v33 i32) (load v32)) - (let (v34 i32) (const.i32 4)) - (let (v35 u32) (bitcast v33)) - (let (v36 u32) (bitcast v34)) - (let (v37 u32) (shr.wrapping v35 v36)) - (let (v38 i32) (bitcast v37)) - (let [(v39 i32) (v40 i32)] (call (#miden::note #get_inputs) v38)) - (let (v41 u32) (bitcast v0)) - (let (v42 u32) (add.checked v41 8)) - (let (v43 u32) (mod.unchecked v42 4)) - (assertz 250 v43) - (let (v44 (ptr i32)) (inttoptr v42)) - (store v44 v39) - (let (v45 u32) (bitcast v0)) - (let (v46 u32) (add.checked v45 4)) - (let (v47 u32) (mod.unchecked v46 4)) - (assertz 250 v47) - (let (v48 (ptr i32)) (inttoptr v46)) - (store v48 v33) - (let (v49 u32) (bitcast v0)) - (let (v50 u32) (mod.unchecked v49 4)) - (assertz 250 v50) - (let (v51 (ptr i32)) (inttoptr v49)) - (store v51 v14) - (let (v52 i32) (const.i32 16)) - (let (v53 i32) (add.wrapping v4 v52)) - (let (v54 (ptr i32)) (global.symbol #__stack_pointer)) - (store v54 v53) - (br (block 1))) - - (block 3 - (let (v24 u32) (bitcast v4)) - (let (v25 u32) (add.checked v24 12)) - (let (v26 u32) (mod.unchecked v25 4)) - (assertz 250 v26) - (let (v27 (ptr i32)) (inttoptr v25)) - (let (v28 i32) (load v27)) - (call #alloc::raw_vec::handle_error v14 v28) - (unreachable)) - ) - - (func (export #miden_base_sys::bindings::tx::add_asset) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 u32) (bitcast v1)) - (let (v3 u32) (mod.unchecked v2 4)) - (assertz 250 v3) - (let (v4 (ptr felt)) (inttoptr v2)) - (let (v5 felt) (load v4)) - (let (v6 u32) (bitcast v1)) - (let (v7 u32) (add.checked v6 4)) - (let (v8 u32) (mod.unchecked v7 4)) - (assertz 250 v8) - (let (v9 (ptr felt)) (inttoptr v7)) - (let (v10 felt) (load v9)) - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (add.checked v11 8)) - (let (v13 u32) (mod.unchecked v12 4)) - (assertz 250 v13) - (let (v14 (ptr felt)) (inttoptr v12)) - (let (v15 felt) (load v14)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (add.checked v16 12)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr felt)) (inttoptr v17)) - (let (v20 felt) (load v19)) - (let [(v21 felt) (v22 felt) (v23 felt) (v24 felt)] (call (#miden::account #add_asset) v5 v10 v15 v20)) - (let (v25 u32) (bitcast v0)) - (let (v26 (ptr felt)) (inttoptr v25)) - (store v26 v21) - (let (v27 u32) (add.checked v25 4)) - (let (v28 (ptr felt)) (inttoptr v27)) - (store v28 v22) - (let (v29 u32) (add.checked v25 8)) - (let (v30 (ptr felt)) (inttoptr v29)) - (store v30 v23) - (let (v31 u32) (add.checked v25 12)) - (let (v32 (ptr felt)) (inttoptr v31)) - (store v32 v24) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #miden_base_sys::bindings::tx::remove_asset) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 u32) (bitcast v1)) - (let (v3 u32) (mod.unchecked v2 4)) - (assertz 250 v3) - (let (v4 (ptr felt)) (inttoptr v2)) - (let (v5 felt) (load v4)) - (let (v6 u32) (bitcast v1)) - (let (v7 u32) (add.checked v6 4)) - (let (v8 u32) (mod.unchecked v7 4)) - (assertz 250 v8) - (let (v9 (ptr felt)) (inttoptr v7)) - (let (v10 felt) (load v9)) - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (add.checked v11 8)) - (let (v13 u32) (mod.unchecked v12 4)) - (assertz 250 v13) - (let (v14 (ptr felt)) (inttoptr v12)) - (let (v15 felt) (load v14)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (add.checked v16 12)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr felt)) (inttoptr v17)) - (let (v20 felt) (load v19)) - (let [(v21 felt) (v22 felt) (v23 felt) (v24 felt)] (call (#miden::account #remove_asset) v5 v10 v15 v20)) - (let (v25 u32) (bitcast v0)) - (let (v26 (ptr felt)) (inttoptr v25)) - (store v26 v21) - (let (v27 u32) (add.checked v25 4)) - (let (v28 (ptr felt)) (inttoptr v27)) - (store v28 v22) - (let (v29 u32) (add.checked v25 8)) - (let (v30 (ptr felt)) (inttoptr v29)) - (store v30 v23) - (let (v31 u32) (add.checked v25 12)) - (let (v32 (ptr felt)) (inttoptr v31)) - (store v32 v24) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #miden_base_sys::bindings::tx::create_note) - (param i32) (param felt) (param felt) (param i32) (result felt) - (block 0 - (param v0 i32) - (param v1 felt) - (param v2 felt) - (param v3 i32) - (let (v5 u32) (bitcast v0)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr felt)) (inttoptr v5)) - (let (v8 felt) (load v7)) - (let (v9 u32) (bitcast v0)) - (let (v10 u32) (add.checked v9 4)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr felt)) (inttoptr v10)) - (let (v13 felt) (load v12)) - (let (v14 u32) (bitcast v0)) - (let (v15 u32) (add.checked v14 8)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr felt)) (inttoptr v15)) - (let (v18 felt) (load v17)) - (let (v19 u32) (bitcast v0)) - (let (v20 u32) (add.checked v19 12)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr felt)) (inttoptr v20)) - (let (v23 felt) (load v22)) - (let (v24 u32) (bitcast v3)) - (let (v25 u32) (mod.unchecked v24 4)) - (assertz 250 v25) - (let (v26 (ptr felt)) (inttoptr v24)) - (let (v27 felt) (load v26)) - (let (v28 u32) (bitcast v3)) - (let (v29 u32) (add.checked v28 4)) - (let (v30 u32) (mod.unchecked v29 4)) - (assertz 250 v30) - (let (v31 (ptr felt)) (inttoptr v29)) - (let (v32 felt) (load v31)) - (let (v33 u32) (bitcast v3)) - (let (v34 u32) (add.checked v33 8)) - (let (v35 u32) (mod.unchecked v34 4)) - (assertz 250 v35) - (let (v36 (ptr felt)) (inttoptr v34)) - (let (v37 felt) (load v36)) - (let (v38 u32) (bitcast v3)) - (let (v39 u32) (add.checked v38 12)) - (let (v40 u32) (mod.unchecked v39 4)) - (assertz 250 v40) - (let (v41 (ptr felt)) (inttoptr v39)) - (let (v42 felt) (load v41)) - (let (v43 felt) (call (#miden::tx #create_note) v8 v13 v18 v23 v1 v2 v27 v32 v37 v42)) - (br (block 1 v43))) - - (block 1 (param v4 felt) - (ret v4)) - ) - - (func (export #alloc::vec::Vec::with_capacity) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 16)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 (ptr i32)) (global.symbol #__stack_pointer)) - (store v6 v5) - (let (v7 i32) (const.i32 4)) - (let (v8 i32) (add.wrapping v5 v7)) - (let (v9 i32) (const.i32 0)) - (call #alloc::raw_vec::RawVec::try_allocate_in v8 v1 v9) - (let (v10 u32) (bitcast v5)) - (let (v11 u32) (add.checked v10 8)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 u32) (bitcast v5)) - (let (v16 u32) (add.checked v15 4)) - (let (v17 u32) (mod.unchecked v16 4)) - (assertz 250 v17) - (let (v18 (ptr i32)) (inttoptr v16)) - (let (v19 i32) (load v18)) - (let (v20 i32) (const.i32 1)) - (let (v21 i1) (neq v19 v20)) - (let (v22 i32) (zext v21)) - (let (v23 i1) (neq v22 0)) - (condbr v23 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (let (v29 u32) (bitcast v5)) - (let (v30 u32) (add.checked v29 12)) - (let (v31 u32) (mod.unchecked v30 4)) - (assertz 250 v31) - (let (v32 (ptr i32)) (inttoptr v30)) - (let (v33 i32) (load v32)) - (let (v34 i32) (const.i32 0)) - (let (v35 u32) (bitcast v0)) - (let (v36 u32) (add.checked v35 8)) - (let (v37 u32) (mod.unchecked v36 4)) - (assertz 250 v37) - (let (v38 (ptr i32)) (inttoptr v36)) - (store v38 v34) - (let (v39 u32) (bitcast v0)) - (let (v40 u32) (add.checked v39 4)) - (let (v41 u32) (mod.unchecked v40 4)) - (assertz 250 v41) - (let (v42 (ptr i32)) (inttoptr v40)) - (store v42 v33) - (let (v43 u32) (bitcast v0)) - (let (v44 u32) (mod.unchecked v43 4)) - (assertz 250 v44) - (let (v45 (ptr i32)) (inttoptr v43)) - (store v45 v14) - (let (v46 i32) (const.i32 16)) - (let (v47 i32) (add.wrapping v5 v46)) - (let (v48 (ptr i32)) (global.symbol #__stack_pointer)) - (store v48 v47) - (br (block 1))) - - (block 3 - (let (v24 u32) (bitcast v5)) - (let (v25 u32) (add.checked v24 12)) - (let (v26 u32) (mod.unchecked v25 4)) - (assertz 250 v26) - (let (v27 (ptr i32)) (inttoptr v25)) - (let (v28 i32) (load v27)) - (call #alloc::raw_vec::handle_error v14 v28) - (unreachable)) - ) - - (func (export #alloc::raw_vec::RawVec::try_allocate_in) - (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i1) (neq v1 0)) - (condbr v4 (block 3) (block 4))) - - (block 1 - (ret)) - - (block 2 (param v62 i32) (param v64 i32) - (let (v65 u32) (bitcast v62)) - (let (v66 u32) (mod.unchecked v65 4)) - (assertz 250 v66) - (let (v67 (ptr i32)) (inttoptr v65)) - (store v67 v64) - (br (block 1))) - - (block 3 - (let (v11 i32) (const.i32 536870912)) - (let (v12 u32) (bitcast v1)) - (let (v13 u32) (bitcast v11)) - (let (v14 i1) (lt v12 v13)) - (let (v15 i32) (sext v14)) - (let (v16 i1) (neq v15 0)) - (condbr v16 (block 6) (block 7))) - - (block 4 - (let (v5 i64) (const.i64 17179869184)) - (let (v6 u32) (bitcast v0)) - (let (v7 u32) (add.checked v6 4)) - (let (v8 u32) (mod.unchecked v7 4)) - (assertz 250 v8) - (let (v9 (ptr i64)) (inttoptr v7)) - (store v9 v5) - (let (v10 i32) (const.i32 0)) - (br (block 2 v0 v10))) - - (block 5 (param v63 i32) - (let (v61 i32) (const.i32 1)) - (br (block 2 v63 v61))) - - (block 6 - (let (v22 i32) (const.i32 2)) - (let (v23 u32) (bitcast v22)) - (let (v24 i32) (shl.wrapping v1 v23)) - (let (v25 i1) (neq v2 0)) - (condbr v25 (block 9) (block 10))) - - (block 7 - (let (v17 i32) (const.i32 0)) - (let (v18 u32) (bitcast v0)) - (let (v19 u32) (add.checked v18 4)) - (let (v20 u32) (mod.unchecked v19 4)) - (assertz 250 v20) - (let (v21 (ptr i32)) (inttoptr v19)) - (store v21 v17) - (br (block 5 v0))) - - (block 8 - (param v36 i32) - (param v40 i32) - (param v45 i32) - (param v51 i32) - (let (v37 i1) (eq v36 0)) - (let (v38 i32) (zext v37)) - (let (v39 i1) (neq v38 0)) - (condbr v39 (block 11) (block 12))) - - (block 9 - (let (v34 i32) (const.i32 4)) - (let (v35 i32) (call #__rust_alloc_zeroed v24 v34)) - (br (block 8 v35 v0 v1 v24))) - - (block 10 - (let (v26 i32) (const.i32 0)) - (let (v27 u32) (bitcast v26)) - (let (v28 u32) (add.checked v27 1048580)) - (let (v29 (ptr u8)) (inttoptr v28)) - (let (v30 u8) (load v29)) - (let (v31 i32) (zext v30)) - (let (v32 i32) (const.i32 4)) - (let (v33 i32) (call #__rust_alloc v24 v32)) - (br (block 8 v33 v0 v1 v24))) - - (block 11 - (let (v52 u32) (bitcast v40)) - (let (v53 u32) (add.checked v52 8)) - (let (v54 u32) (mod.unchecked v53 4)) - (assertz 250 v54) - (let (v55 (ptr i32)) (inttoptr v53)) - (store v55 v51) - (let (v56 i32) (const.i32 4)) - (let (v57 u32) (bitcast v40)) - (let (v58 u32) (add.checked v57 4)) - (let (v59 u32) (mod.unchecked v58 4)) - (assertz 250 v59) - (let (v60 (ptr i32)) (inttoptr v58)) - (store v60 v56) - (br (block 5 v40))) - - (block 12 - (let (v41 u32) (bitcast v40)) - (let (v42 u32) (add.checked v41 8)) - (let (v43 u32) (mod.unchecked v42 4)) - (assertz 250 v43) - (let (v44 (ptr i32)) (inttoptr v42)) - (store v44 v36) - (let (v46 u32) (bitcast v40)) - (let (v47 u32) (add.checked v46 4)) - (let (v48 u32) (mod.unchecked v47 4)) - (assertz 250 v48) - (let (v49 (ptr i32)) (inttoptr v47)) - (store v49 v45) - (let (v50 i32) (const.i32 0)) - (br (block 2 v40 v50))) - ) - - (func (export #miden_stdlib_sys::stdlib::mem::pipe_words_to_memory) - (param i32) (param felt) - (block 0 (param v0 i32) (param v1 felt) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 96)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 i32) (const.i32 -32)) - (let (v7 i32) (band v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i32) (const.i32 20)) - (let (v10 i32) (add.wrapping v7 v9)) - (let (v11 i64) (cast v1)) - (let (v12 i32) (trunc v11)) - (let (v13 i32) (const.i32 2)) - (let (v14 u32) (bitcast v13)) - (let (v15 i32) (shl.wrapping v12 v14)) - (call #alloc::vec::Vec::with_capacity v10 v15) - (let (v16 u32) (bitcast v7)) - (let (v17 u32) (add.checked v16 24)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr i32)) (inttoptr v17)) - (let (v20 i32) (load v19)) - (let (v21 i32) (const.i32 32)) - (let (v22 i32) (add.wrapping v7 v21)) - (let [(v23 felt) (v24 felt) (v25 felt) (v26 felt) (v27 i32)] (call (#std::mem #pipe_words_to_memory) v1 v20)) - (let (v28 u32) (bitcast v22)) - (let (v29 (ptr felt)) (inttoptr v28)) - (store v29 v23) - (let (v30 u32) (add.checked v28 4)) - (let (v31 (ptr felt)) (inttoptr v30)) - (store v31 v24) - (let (v32 u32) (add.checked v28 8)) - (let (v33 (ptr felt)) (inttoptr v32)) - (store v33 v25) - (let (v34 u32) (add.checked v28 12)) - (let (v35 (ptr felt)) (inttoptr v34)) - (store v35 v26) - (let (v36 u32) (add.checked v28 16)) - (let (v37 (ptr i32)) (inttoptr v36)) - (store v37 v27) - (let (v38 i32) (const.i32 24)) - (let (v39 i32) (add.wrapping v0 v38)) - (let (v40 u32) (bitcast v7)) - (let (v41 u32) (add.checked v40 56)) - (let (v42 u32) (mod.unchecked v41 8)) - (assertz 250 v42) - (let (v43 (ptr i64)) (inttoptr v41)) - (let (v44 i64) (load v43)) - (let (v45 u32) (bitcast v39)) - (let (v46 u32) (mod.unchecked v45 8)) - (assertz 250 v46) - (let (v47 (ptr i64)) (inttoptr v45)) - (store v47 v44) - (let (v48 i32) (const.i32 16)) - (let (v49 i32) (add.wrapping v0 v48)) - (let (v50 u32) (bitcast v7)) - (let (v51 u32) (add.checked v50 48)) - (let (v52 u32) (mod.unchecked v51 8)) - (assertz 250 v52) - (let (v53 (ptr i64)) (inttoptr v51)) - (let (v54 i64) (load v53)) - (let (v55 u32) (bitcast v49)) - (let (v56 u32) (mod.unchecked v55 8)) - (assertz 250 v56) - (let (v57 (ptr i64)) (inttoptr v55)) - (store v57 v54) - (let (v58 i32) (const.i32 8)) - (let (v59 i32) (add.wrapping v0 v58)) - (let (v60 u32) (bitcast v7)) - (let (v61 u32) (add.checked v60 40)) - (let (v62 u32) (mod.unchecked v61 8)) - (assertz 250 v62) - (let (v63 (ptr i64)) (inttoptr v61)) - (let (v64 i64) (load v63)) - (let (v65 u32) (bitcast v59)) - (let (v66 u32) (mod.unchecked v65 8)) - (assertz 250 v66) - (let (v67 (ptr i64)) (inttoptr v65)) - (store v67 v64) - (let (v68 u32) (bitcast v7)) - (let (v69 u32) (add.checked v68 32)) - (let (v70 u32) (mod.unchecked v69 8)) - (assertz 250 v70) - (let (v71 (ptr i64)) (inttoptr v69)) - (let (v72 i64) (load v71)) - (let (v73 u32) (bitcast v0)) - (let (v74 u32) (mod.unchecked v73 8)) - (assertz 250 v74) - (let (v75 (ptr i64)) (inttoptr v73)) - (store v75 v72) - (let (v76 i32) (const.i32 40)) - (let (v77 i32) (add.wrapping v0 v76)) - (let (v78 i32) (const.i32 20)) - (let (v79 i32) (add.wrapping v7 v78)) - (let (v80 i32) (const.i32 8)) - (let (v81 i32) (add.wrapping v79 v80)) - (let (v82 u32) (bitcast v81)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (let (v85 i32) (load v84)) - (let (v86 u32) (bitcast v77)) - (let (v87 u32) (mod.unchecked v86 4)) - (assertz 250 v87) - (let (v88 (ptr i32)) (inttoptr v86)) - (store v88 v85) - (let (v89 u32) (bitcast v7)) - (let (v90 u32) (add.checked v89 20)) - (let (v91 u32) (mod.unchecked v90 4)) - (assertz 250 v91) - (let (v92 (ptr i64)) (inttoptr v90)) - (let (v93 i64) (load v92)) - (let (v94 u32) (bitcast v0)) - (let (v95 u32) (add.checked v94 32)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i64)) (inttoptr v95)) - (store v97 v93) - (let (v98 (ptr i32)) (global.symbol #__stack_pointer)) - (store v98 v3) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #miden_stdlib_sys::stdlib::mem::pipe_double_words_to_memory) - (param i32) (param felt) - (block 0 (param v0 i32) (param v1 felt) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 160)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 i32) (const.i32 -32)) - (let (v7 i32) (band v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i32) (const.i32 20)) - (let (v10 i32) (add.wrapping v7 v9)) - (let (v11 i64) (cast v1)) - (let (v12 i32) (trunc v11)) - (let (v13 i32) (const.i32 2)) - (let (v14 u32) (bitcast v13)) - (let (v15 i32) (shl.wrapping v12 v14)) - (call #alloc::vec::Vec::with_capacity v10 v15) - (let (v16 u32) (bitcast v7)) - (let (v17 u32) (add.checked v16 24)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr i32)) (inttoptr v17)) - (let (v20 i32) (load v19)) - (let (v21 i64) (const.i64 0)) - (let (v22 felt) (cast v21)) - (let (v23 i32) (const.i32 4)) - (let (v24 u32) (bitcast v23)) - (let (v25 i32) (shl.wrapping v12 v24)) - (let (v26 i32) (add.wrapping v20 v25)) - (let (v27 i32) (const.i32 32)) - (let (v28 i32) (add.wrapping v7 v27)) - (let [(v29 felt) (v30 felt) (v31 felt) (v32 felt) (v33 felt) (v34 felt) (v35 felt) (v36 felt) (v37 felt) (v38 felt) (v39 felt) (v40 felt) (v41 i32)] (call (#std::mem #pipe_double_words_to_memory) v22 v22 v22 v22 v22 v22 v22 v22 v22 v22 v22 v22 v20 v26)) - (let (v42 u32) (bitcast v28)) - (let (v43 (ptr felt)) (inttoptr v42)) - (store v43 v29) - (let (v44 u32) (add.checked v42 4)) - (let (v45 (ptr felt)) (inttoptr v44)) - (store v45 v30) - (let (v46 u32) (add.checked v42 8)) - (let (v47 (ptr felt)) (inttoptr v46)) - (store v47 v31) - (let (v48 u32) (add.checked v42 12)) - (let (v49 (ptr felt)) (inttoptr v48)) - (store v49 v32) - (let (v50 u32) (add.checked v42 16)) - (let (v51 (ptr felt)) (inttoptr v50)) - (store v51 v33) - (let (v52 u32) (add.checked v42 20)) - (let (v53 (ptr felt)) (inttoptr v52)) - (store v53 v34) - (let (v54 u32) (add.checked v42 24)) - (let (v55 (ptr felt)) (inttoptr v54)) - (store v55 v35) - (let (v56 u32) (add.checked v42 28)) - (let (v57 (ptr felt)) (inttoptr v56)) - (store v57 v36) - (let (v58 u32) (add.checked v42 32)) - (let (v59 (ptr felt)) (inttoptr v58)) - (store v59 v37) - (let (v60 u32) (add.checked v42 36)) - (let (v61 (ptr felt)) (inttoptr v60)) - (store v61 v38) - (let (v62 u32) (add.checked v42 40)) - (let (v63 (ptr felt)) (inttoptr v62)) - (store v63 v39) - (let (v64 u32) (add.checked v42 44)) - (let (v65 (ptr felt)) (inttoptr v64)) - (store v65 v40) - (let (v66 u32) (add.checked v42 48)) - (let (v67 (ptr i32)) (inttoptr v66)) - (store v67 v41) - (let (v68 i32) (const.i32 24)) - (let (v69 i32) (add.wrapping v0 v68)) - (let (v70 i32) (const.i32 88)) - (let (v71 i32) (add.wrapping v7 v70)) - (let (v72 u32) (bitcast v71)) - (let (v73 u32) (mod.unchecked v72 8)) - (assertz 250 v73) - (let (v74 (ptr i64)) (inttoptr v72)) - (let (v75 i64) (load v74)) - (let (v76 u32) (bitcast v69)) - (let (v77 u32) (mod.unchecked v76 8)) - (assertz 250 v77) - (let (v78 (ptr i64)) (inttoptr v76)) - (store v78 v75) - (let (v79 i32) (const.i32 16)) - (let (v80 i32) (add.wrapping v0 v79)) - (let (v81 i32) (const.i32 80)) - (let (v82 i32) (add.wrapping v7 v81)) - (let (v83 u32) (bitcast v82)) - (let (v84 u32) (mod.unchecked v83 8)) - (assertz 250 v84) - (let (v85 (ptr i64)) (inttoptr v83)) - (let (v86 i64) (load v85)) - (let (v87 u32) (bitcast v80)) - (let (v88 u32) (mod.unchecked v87 8)) - (assertz 250 v88) - (let (v89 (ptr i64)) (inttoptr v87)) - (store v89 v86) - (let (v90 i32) (const.i32 8)) - (let (v91 i32) (add.wrapping v0 v90)) - (let (v92 i32) (const.i32 32)) - (let (v93 i32) (add.wrapping v7 v92)) - (let (v94 i32) (const.i32 40)) - (let (v95 i32) (add.wrapping v93 v94)) - (let (v96 u32) (bitcast v95)) - (let (v97 u32) (mod.unchecked v96 8)) - (assertz 250 v97) - (let (v98 (ptr i64)) (inttoptr v96)) - (let (v99 i64) (load v98)) - (let (v100 u32) (bitcast v91)) - (let (v101 u32) (mod.unchecked v100 8)) - (assertz 250 v101) - (let (v102 (ptr i64)) (inttoptr v100)) - (store v102 v99) - (let (v103 u32) (bitcast v7)) - (let (v104 u32) (add.checked v103 64)) - (let (v105 u32) (mod.unchecked v104 8)) - (assertz 250 v105) - (let (v106 (ptr i64)) (inttoptr v104)) - (let (v107 i64) (load v106)) - (let (v108 u32) (bitcast v0)) - (let (v109 u32) (mod.unchecked v108 8)) - (assertz 250 v109) - (let (v110 (ptr i64)) (inttoptr v108)) - (store v110 v107) - (let (v111 i32) (const.i32 40)) - (let (v112 i32) (add.wrapping v0 v111)) - (let (v113 i32) (const.i32 20)) - (let (v114 i32) (add.wrapping v7 v113)) - (let (v115 i32) (const.i32 8)) - (let (v116 i32) (add.wrapping v114 v115)) - (let (v117 u32) (bitcast v116)) - (let (v118 u32) (mod.unchecked v117 4)) - (assertz 250 v118) - (let (v119 (ptr i32)) (inttoptr v117)) - (let (v120 i32) (load v119)) - (let (v121 u32) (bitcast v112)) - (let (v122 u32) (mod.unchecked v121 4)) - (assertz 250 v122) - (let (v123 (ptr i32)) (inttoptr v121)) - (store v123 v120) - (let (v124 u32) (bitcast v7)) - (let (v125 u32) (add.checked v124 20)) - (let (v126 u32) (mod.unchecked v125 4)) - (assertz 250 v126) - (let (v127 (ptr i64)) (inttoptr v125)) - (let (v128 i64) (load v127)) - (let (v129 u32) (bitcast v0)) - (let (v130 u32) (add.checked v129 32)) - (let (v131 u32) (mod.unchecked v130 4)) - (assertz 250 v131) - (let (v132 (ptr i64)) (inttoptr v130)) - (store v132 v128) - (let (v133 (ptr i32)) (global.symbol #__stack_pointer)) - (store v133 v3) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #dummy) - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #__wasm_call_dtors) - (block 0 - (call #dummy) - (call #dummy) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #alloc::raw_vec::handle_error) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (unreachable)) - - (block 1) - ) - - (func (export #get_wallet_magic_number.command_export) (result felt) - (block 0 - (let (v1 felt) (call #get_wallet_magic_number)) - (call #__wasm_call_dtors) - (br (block 1 v1))) - - (block 1 (param v0 felt) - (ret v0)) - ) - - (func (export #test_add_asset.command_export) (result felt) - (block 0 - (let (v1 felt) (call #test_add_asset)) - (call #__wasm_call_dtors) - (br (block 1 v1))) - - (block 1 (param v0 felt) - (ret v0)) - ) - - (func (export #test_felt_ops_smoke.command_export) - (param felt) (param felt) (result felt) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 felt) (call #test_felt_ops_smoke v0 v1)) - (call #__wasm_call_dtors) - (br (block 1 v3))) - - (block 1 (param v2 felt) - (ret v2)) - ) - - (func (export #note_script.command_export) (result felt) - (block 0 - (let (v1 felt) (call #note_script)) - (call #__wasm_call_dtors) - (br (block 1 v1))) - - (block 1 (param v0 felt) - (ret v0)) - ) - - (func (export #test_blake3_hash_1to1.command_export) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (call #test_blake3_hash_1to1 v0 v1) - (call #__wasm_call_dtors) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_blake3_hash_2to1.command_export) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (call #test_blake3_hash_2to1 v0 v1) - (call #__wasm_call_dtors) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_rpo_falcon512_verify.command_export) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (call #test_rpo_falcon512_verify v0 v1) - (call #__wasm_call_dtors) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_pipe_words_to_memory.command_export) - (param i32) (param felt) - (block 0 (param v0 i32) (param v1 felt) - (call #test_pipe_words_to_memory v0 v1) - (call #__wasm_call_dtors) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_pipe_double_words_to_memory.command_export) - (param i32) (param felt) - (block 0 (param v0 i32) (param v1 felt) - (call #test_pipe_double_words_to_memory v0 v1) - (call #__wasm_call_dtors) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #test_remove_asset.command_export) - (param i32) (result felt) - (block 0 (param v0 i32) - (let (v2 felt) (call #test_remove_asset v0)) - (call #__wasm_call_dtors) - (br (block 1 v2))) - - (block 1 (param v1 felt) - (ret v1)) - ) - - (func (export #test_create_note.command_export) - (param i32) (param felt) (param felt) (param i32) (result felt) - (block 0 - (param v0 i32) - (param v1 felt) - (param v2 felt) - (param v3 i32) - (let (v5 felt) (call #test_create_note v0 v1 v2 v3)) - (call #__wasm_call_dtors) - (br (block 1 v5))) - - (block 1 (param v4 felt) - (ret v4)) - ) - - ;; Imports - (func (import #intrinsics::mem #heap_base) (result u32)) - (func (import #miden::account #add_asset) - (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)) - (func (import #miden::account #get_id) (result felt)) - (func (import #miden::account #remove_asset) - (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)) - (func (import #miden::note #get_inputs) (param i32) (result i32 i32)) - (func (import #miden::tx #create_note) - (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (result felt)) - (func (import #std::crypto::dsa::rpo_falcon512 #rpo_falcon512_verify) - (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt)) - (func (import #std::crypto::hashes::blake3 #hash_1to1) - (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32 i32 i32 i32 i32 i32 i32 i32)) - (func (import #std::crypto::hashes::blake3 #hash_2to1) - (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32 i32 i32 i32 i32 i32 i32 i32)) - (func (import #std::mem #pipe_double_words_to_memory) - (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param i32) (param i32) (result felt felt felt felt felt felt felt felt felt felt felt felt i32)) - (func (import #std::mem #pipe_words_to_memory) - (param felt) (param i32) (result felt felt felt felt i32)) - ) - -) +builtin.component root_ns:root@1.0.0 { + builtin.module public @miden_sdk_account_test { + builtin.function public @miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1(v0: i32, v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32, v8: i32) { + ^block3(v0: i32, v1: i32, v2: i32, v3: i32, v4: i32, v5: i32, v6: i32, v7: i32, v8: i32): + v9, v10, v11, v12, v13, v14, v15, v16 = hir.exec @std::crypto::hashes::blake3/hash_1to1(v0, v1, v2, v3, v4, v5, v6, v7) : i32, i32, i32, i32, i32, i32, i32, i32 + v17 = hir.bitcast v8 : u32; + v18 = hir.int_to_ptr v17 : (ptr i32); + hir.store v18, v9; + v19 = hir.constant 4 : u32; + v20 = hir.add v17, v19 : u32 #[overflow = checked]; + v21 = hir.int_to_ptr v20 : (ptr i32); + hir.store v21, v10; + v22 = hir.constant 8 : u32; + v23 = hir.add v17, v22 : u32 #[overflow = checked]; + v24 = hir.int_to_ptr v23 : (ptr i32); + hir.store v24, v11; + v25 = hir.constant 12 : u32; + v26 = hir.add v17, v25 : u32 #[overflow = checked]; + v27 = hir.int_to_ptr v26 : (ptr i32); + hir.store v27, v12; + v28 = hir.constant 16 : u32; + v29 = hir.add v17, v28 : u32 #[overflow = checked]; + v30 = hir.int_to_ptr v29 : (ptr i32); + hir.store v30, v13; + v31 = hir.constant 20 : u32; + v32 = hir.add v17, v31 : u32 #[overflow = checked]; + v33 = hir.int_to_ptr v32 : (ptr i32); + hir.store v33, v14; + v34 = hir.constant 24 : u32; + v35 = hir.add v17, v34 : u32 #[overflow = checked]; + v36 = hir.int_to_ptr v35 : (ptr i32); + hir.store v36, v15; + v37 = hir.constant 28 : u32; + v38 = hir.add v17, v37 : u32 #[overflow = checked]; + v39 = hir.int_to_ptr v38 : (ptr i32); + hir.store v39, v16; + hir.br ^block5; + ^block5: + hir.ret ; + }; + + builtin.function public @miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_2to1(v40: i32, v41: i32, v42: i32, v43: i32, v44: i32, v45: i32, v46: i32, v47: i32, v48: i32, v49: i32, v50: i32, v51: i32, v52: i32, v53: i32, v54: i32, v55: i32, v56: i32) { + ^block6(v40: i32, v41: i32, v42: i32, v43: i32, v44: i32, v45: i32, v46: i32, v47: i32, v48: i32, v49: i32, v50: i32, v51: i32, v52: i32, v53: i32, v54: i32, v55: i32, v56: i32): + v57, v58, v59, v60, v61, v62, v63, v64 = hir.exec @std::crypto::hashes::blake3/hash_2to1(v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) : i32, i32, i32, i32, i32, i32, i32, i32 + v65 = hir.bitcast v56 : u32; + v66 = hir.int_to_ptr v65 : (ptr i32); + hir.store v66, v57; + v67 = hir.constant 4 : u32; + v68 = hir.add v65, v67 : u32 #[overflow = checked]; + v69 = hir.int_to_ptr v68 : (ptr i32); + hir.store v69, v58; + v70 = hir.constant 8 : u32; + v71 = hir.add v65, v70 : u32 #[overflow = checked]; + v72 = hir.int_to_ptr v71 : (ptr i32); + hir.store v72, v59; + v73 = hir.constant 12 : u32; + v74 = hir.add v65, v73 : u32 #[overflow = checked]; + v75 = hir.int_to_ptr v74 : (ptr i32); + hir.store v75, v60; + v76 = hir.constant 16 : u32; + v77 = hir.add v65, v76 : u32 #[overflow = checked]; + v78 = hir.int_to_ptr v77 : (ptr i32); + hir.store v78, v61; + v79 = hir.constant 20 : u32; + v80 = hir.add v65, v79 : u32 #[overflow = checked]; + v81 = hir.int_to_ptr v80 : (ptr i32); + hir.store v81, v62; + v82 = hir.constant 24 : u32; + v83 = hir.add v65, v82 : u32 #[overflow = checked]; + v84 = hir.int_to_ptr v83 : (ptr i32); + hir.store v84, v63; + v85 = hir.constant 28 : u32; + v86 = hir.add v65, v85 : u32 #[overflow = checked]; + v87 = hir.int_to_ptr v86 : (ptr i32); + hir.store v87, v64; + hir.br ^block7; + ^block7: + hir.ret ; + }; + + builtin.function public @miden_stdlib_sys::stdlib::crypto::dsa::extern_rpo_falcon512_verify(v88: felt, v89: felt, v90: felt, v91: felt, v92: felt, v93: felt, v94: felt, v95: felt) { + ^block8(v88: felt, v89: felt, v90: felt, v91: felt, v92: felt, v93: felt, v94: felt, v95: felt): + hir.exec @std::crypto::dsa::rpo_falcon/rpo_falcon512_verify(v88, v89, v90, v91, v92, v93, v94, v95) + hir.br ^block10; + ^block10: + hir.ret ; + }; + + builtin.function public @miden_base_sys::bindings::account::extern_account_get_id() -> felt { + ^block12: + v96 = hir.exec @miden::account/get_id() : felt + hir.br ^block14(v96); + ^block14(v97: felt): + hir.ret ; + }; + + builtin.function public @miden_base_sys::bindings::account::extern_account_add_asset(v98: felt, v99: felt, v100: felt, v101: felt, v102: i32) { + ^block15(v98: felt, v99: felt, v100: felt, v101: felt, v102: i32): + v103, v104, v105, v106 = hir.exec @miden::account/add_asset(v98, v99, v100, v101) : felt, felt, felt, felt + v107 = hir.bitcast v102 : u32; + v108 = hir.int_to_ptr v107 : (ptr felt); + hir.store v108, v103; + v109 = hir.constant 4 : u32; + v110 = hir.add v107, v109 : u32 #[overflow = checked]; + v111 = hir.int_to_ptr v110 : (ptr felt); + hir.store v111, v104; + v112 = hir.constant 8 : u32; + v113 = hir.add v107, v112 : u32 #[overflow = checked]; + v114 = hir.int_to_ptr v113 : (ptr felt); + hir.store v114, v105; + v115 = hir.constant 12 : u32; + v116 = hir.add v107, v115 : u32 #[overflow = checked]; + v117 = hir.int_to_ptr v116 : (ptr felt); + hir.store v117, v106; + hir.br ^block16; + ^block16: + hir.ret ; + }; + + builtin.function public @miden_base_sys::bindings::account::extern_account_remove_asset(v118: felt, v119: felt, v120: felt, v121: felt, v122: i32) { + ^block17(v118: felt, v119: felt, v120: felt, v121: felt, v122: i32): + v123, v124, v125, v126 = hir.exec @miden::account/remove_asset(v118, v119, v120, v121) : felt, felt, felt, felt + v127 = hir.bitcast v122 : u32; + v128 = hir.int_to_ptr v127 : (ptr felt); + hir.store v128, v123; + v129 = hir.constant 4 : u32; + v130 = hir.add v127, v129 : u32 #[overflow = checked]; + v131 = hir.int_to_ptr v130 : (ptr felt); + hir.store v131, v124; + v132 = hir.constant 8 : u32; + v133 = hir.add v127, v132 : u32 #[overflow = checked]; + v134 = hir.int_to_ptr v133 : (ptr felt); + hir.store v134, v125; + v135 = hir.constant 12 : u32; + v136 = hir.add v127, v135 : u32 #[overflow = checked]; + v137 = hir.int_to_ptr v136 : (ptr felt); + hir.store v137, v126; + hir.br ^block18; + ^block18: + hir.ret ; + }; + + builtin.function public @miden_base_sys::bindings::note::extern_note_get_inputs(v138: i32) -> i32 { + ^block19(v138: i32): + v139, v140 = hir.exec @miden::note/get_inputs(v138) : i32, i32 + hir.br ^block21(v139); + ^block21(v141: i32): + hir.ret ; + }; + + builtin.function public @miden_base_sys::bindings::tx::extern_tx_create_note(v142: felt, v143: felt, v144: felt, v145: felt, v146: felt, v147: felt, v148: felt, v149: felt, v150: felt, v151: felt) -> felt { + ^block22(v142: felt, v143: felt, v144: felt, v145: felt, v146: felt, v147: felt, v148: felt, v149: felt, v150: felt, v151: felt): + v152 = hir.exec @miden::tx/create_note(v142, v143, v144, v145, v146, v147, v148, v149, v150, v151) : felt + hir.br ^block24(v152); + ^block24(v153: felt): + hir.ret ; + }; + + builtin.function public @miden_stdlib_sys::stdlib::mem::extern_pipe_words_to_memory(v154: felt, v155: i32, v156: i32) { + ^block25(v154: felt, v155: i32, v156: i32): + v157, v158, v159, v160, v161 = hir.exec @std::mem/pipe_words_to_memory(v154, v155) : felt, felt, felt, felt, i32 + v162 = hir.bitcast v156 : u32; + v163 = hir.int_to_ptr v162 : (ptr felt); + hir.store v163, v157; + v164 = hir.constant 4 : u32; + v165 = hir.add v162, v164 : u32 #[overflow = checked]; + v166 = hir.int_to_ptr v165 : (ptr felt); + hir.store v166, v158; + v167 = hir.constant 8 : u32; + v168 = hir.add v162, v167 : u32 #[overflow = checked]; + v169 = hir.int_to_ptr v168 : (ptr felt); + hir.store v169, v159; + v170 = hir.constant 12 : u32; + v171 = hir.add v162, v170 : u32 #[overflow = checked]; + v172 = hir.int_to_ptr v171 : (ptr felt); + hir.store v172, v160; + v173 = hir.constant 16 : u32; + v174 = hir.add v162, v173 : u32 #[overflow = checked]; + v175 = hir.int_to_ptr v174 : (ptr i32); + hir.store v175, v161; + hir.br ^block27; + ^block27: + hir.ret ; + }; + + builtin.function public @miden_stdlib_sys::stdlib::mem::extern_pipe_double_words_to_memory(v176: felt, v177: felt, v178: felt, v179: felt, v180: felt, v181: felt, v182: felt, v183: felt, v184: felt, v185: felt, v186: felt, v187: felt, v188: i32, v189: i32, v190: i32) { + ^block28(v176: felt, v177: felt, v178: felt, v179: felt, v180: felt, v181: felt, v182: felt, v183: felt, v184: felt, v185: felt, v186: felt, v187: felt, v188: i32, v189: i32, v190: i32): + v191, v192, v193, v194, v195, v196, v197, v198, v199, v200, v201, v202, v203 = hir.exec @std::mem/pipe_double_words_to_memory(v176, v177, v178, v179, v180, v181, v182, v183, v184, v185, v186, v187, v188, v189) : felt, felt, felt, felt, felt, felt, felt, felt, felt, felt, felt, felt, i32 + v204 = hir.bitcast v190 : u32; + v205 = hir.int_to_ptr v204 : (ptr felt); + hir.store v205, v191; + v206 = hir.constant 4 : u32; + v207 = hir.add v204, v206 : u32 #[overflow = checked]; + v208 = hir.int_to_ptr v207 : (ptr felt); + hir.store v208, v192; + v209 = hir.constant 8 : u32; + v210 = hir.add v204, v209 : u32 #[overflow = checked]; + v211 = hir.int_to_ptr v210 : (ptr felt); + hir.store v211, v193; + v212 = hir.constant 12 : u32; + v213 = hir.add v204, v212 : u32 #[overflow = checked]; + v214 = hir.int_to_ptr v213 : (ptr felt); + hir.store v214, v194; + v215 = hir.constant 16 : u32; + v216 = hir.add v204, v215 : u32 #[overflow = checked]; + v217 = hir.int_to_ptr v216 : (ptr felt); + hir.store v217, v195; + v218 = hir.constant 20 : u32; + v219 = hir.add v204, v218 : u32 #[overflow = checked]; + v220 = hir.int_to_ptr v219 : (ptr felt); + hir.store v220, v196; + v221 = hir.constant 24 : u32; + v222 = hir.add v204, v221 : u32 #[overflow = checked]; + v223 = hir.int_to_ptr v222 : (ptr felt); + hir.store v223, v197; + v224 = hir.constant 28 : u32; + v225 = hir.add v204, v224 : u32 #[overflow = checked]; + v226 = hir.int_to_ptr v225 : (ptr felt); + hir.store v226, v198; + v227 = hir.constant 32 : u32; + v228 = hir.add v204, v227 : u32 #[overflow = checked]; + v229 = hir.int_to_ptr v228 : (ptr felt); + hir.store v229, v199; + v230 = hir.constant 36 : u32; + v231 = hir.add v204, v230 : u32 #[overflow = checked]; + v232 = hir.int_to_ptr v231 : (ptr felt); + hir.store v232, v200; + v233 = hir.constant 40 : u32; + v234 = hir.add v204, v233 : u32 #[overflow = checked]; + v235 = hir.int_to_ptr v234 : (ptr felt); + hir.store v235, v201; + v236 = hir.constant 44 : u32; + v237 = hir.add v204, v236 : u32 #[overflow = checked]; + v238 = hir.int_to_ptr v237 : (ptr felt); + hir.store v238, v202; + v239 = hir.constant 48 : u32; + v240 = hir.add v204, v239 : u32 #[overflow = checked]; + v241 = hir.int_to_ptr v240 : (ptr i32); + hir.store v241, v203; + hir.br ^block29; + ^block29: + hir.ret ; + }; + + builtin.function public @< as core::ops::drop::Drop>::drop::DropGuard as core::ops::drop::Drop>::drop(v242: i32) { + ^block31(v242: i32): + v243 = hir.constant 0 : i32; + v244 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v245 = hir.bitcast v244 : (ptr i32); + v246 = hir.load v245 : i32; + v247 = hir.constant 16 : i32; + v248 = hir.sub v246, v247 : i32 #[overflow = wrapping]; + v249 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v250 = hir.bitcast v249 : (ptr i32); + hir.store v250, v248; + v251 = hir.bitcast v242 : u32; + v252 = hir.constant 4 : u32; + v253 = hir.mod v251, v252 : u32; + hir.assertz v253 #[code = 250]; + v254 = hir.int_to_ptr v251 : (ptr i32); + v255 = hir.load v254 : i32; + v256 = hir.bitcast v255 : u32; + v257 = hir.constant 4 : u32; + v258 = hir.mod v256, v257 : u32; + hir.assertz v258 #[code = 250]; + v259 = hir.int_to_ptr v256 : (ptr i32); + v260 = hir.load v259 : i32; + v261 = hir.bitcast v248 : u32; + v262 = hir.constant 12 : u32; + v263 = hir.add v261, v262 : u32 #[overflow = checked]; + v264 = hir.constant 4 : u32; + v265 = hir.mod v263, v264 : u32; + hir.assertz v265 #[code = 250]; + v266 = hir.int_to_ptr v263 : (ptr i32); + hir.store v266, v260; + v267 = hir.bitcast v255 : u32; + v268 = hir.constant 8 : u32; + v269 = hir.add v267, v268 : u32 #[overflow = checked]; + v270 = hir.constant 4 : u32; + v271 = hir.mod v269, v270 : u32; + hir.assertz v271 #[code = 250]; + v272 = hir.int_to_ptr v269 : (ptr i32); + v273 = hir.load v272 : i32; + v274 = hir.bitcast v248 : u32; + v275 = hir.constant 8 : u32; + v276 = hir.add v274, v275 : u32 #[overflow = checked]; + v277 = hir.constant 4 : u32; + v278 = hir.mod v276, v277 : u32; + hir.assertz v278 #[code = 250]; + v279 = hir.int_to_ptr v276 : (ptr i32); + hir.store v279, v273; + v280 = hir.constant 8 : i32; + v281 = hir.add v248, v280 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/ as core::ops::drop::Drop>::drop(v281) + v282 = hir.constant 16 : i32; + v283 = hir.add v248, v282 : i32 #[overflow = wrapping]; + v284 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v285 = hir.bitcast v284 : (ptr i32); + hir.store v285, v283; + hir.br ^block32; + ^block32: + hir.ret ; + }; + + builtin.function public @ as core::ops::drop::Drop>::drop(v286: i32) { + ^block33(v286: i32): + v287 = hir.constant 4 : i32; + v288 = hir.constant 4 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::raw_vec::RawVecInner::deallocate(v286, v287, v288) + hir.br ^block34; + ^block34: + hir.ret ; + }; + + builtin.function public @core::alloc::global::GlobalAlloc::alloc_zeroed(v289: i32, v290: i32, v291: i32) -> i32 { + ^block35(v289: i32, v290: i32, v291: i32): + v293 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/::alloc(v289, v290, v291) : i32 + v294 = hir.constant 0 : i32; + v295 = hir.eq v293, v294 : i1; + v296 = hir.zext v295 : u32; + v297 = hir.bitcast v296 : i32; + v298 = hir.constant 0 : i32; + v299 = hir.neq v297, v298 : i1; + hir.cond_br v299 ^block37(v293), ^block38; + ^block36(v292: i32): + hir.ret v292; + ^block37(v305: i32): + hir.br ^block36(v305); + ^block38: + v300 = hir.constant 0 : i32; + v301 = hir.trunc v300 : u8; + v302 = hir.bitcast v291 : u32; + v303 = hir.bitcast v293 : u32; + v304 = hir.int_to_ptr v303 : (ptr u8); + hir.mem_set v304, v302, v301; + hir.br ^block37(v293); + }; + + builtin.function public @ as core::ops::drop::Drop>::drop(v306: i32) { + ^block39(v306: i32): + v307 = hir.constant 0 : i32; + v308 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v309 = hir.bitcast v308 : (ptr i32); + v310 = hir.load v309 : i32; + v311 = hir.constant 16 : i32; + v312 = hir.sub v310, v311 : i32 #[overflow = wrapping]; + v313 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v314 = hir.bitcast v313 : (ptr i32); + hir.store v314, v312; + v315 = hir.bitcast v312 : u32; + v316 = hir.constant 12 : u32; + v317 = hir.add v315, v316 : u32 #[overflow = checked]; + v318 = hir.constant 4 : u32; + v319 = hir.mod v317, v318 : u32; + hir.assertz v319 #[code = 250]; + v320 = hir.int_to_ptr v317 : (ptr i32); + hir.store v320, v306; + v321 = hir.constant 12 : i32; + v322 = hir.add v312, v321 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/< as core::ops::drop::Drop>::drop::DropGuard as core::ops::drop::Drop>::drop(v322) + v323 = hir.constant 16 : i32; + v324 = hir.add v312, v323 : i32 #[overflow = wrapping]; + v325 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v326 = hir.bitcast v325 : (ptr i32); + hir.store v326, v324; + hir.br ^block40; + ^block40: + hir.ret ; + }; + + builtin.function public @get_wallet_magic_number() -> felt { + ^block41: + v328 = hir.constant 0 : felt; + v329 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::account::get_id() : felt + v330 = hir.constant 42 : i32; + v331 = hir.bitcast v330 : felt; + v332 = hir.add v331, v329 : felt #[overflow = unchecked]; + hir.br ^block42(v332); + ^block42(v327: felt): + hir.ret v327; + }; + + builtin.function public @test_add_asset() -> felt { + ^block43: + v334 = hir.constant 0 : i32; + v335 = hir.constant 0 : felt; + v336 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v337 = hir.bitcast v336 : (ptr i32); + v338 = hir.load v337 : i32; + v339 = hir.constant 64 : i32; + v340 = hir.sub v338, v339 : i32 #[overflow = wrapping]; + v341 = hir.constant -32 : i32; + v342 = hir.band v340, v341 : i32; + v343 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v344 = hir.bitcast v343 : (ptr i32); + hir.store v344, v342; + v345 = hir.constant 1 : i32; + v346 = hir.bitcast v345 : felt; + v347 = hir.constant 2 : i32; + v348 = hir.bitcast v347 : felt; + v349 = hir.constant 3 : i32; + v350 = hir.bitcast v349 : felt; + v351 = hir.constant 4 : i32; + v352 = hir.bitcast v351 : felt; + v353 = hir.bitcast v342 : u32; + v354 = hir.constant 12 : u32; + v355 = hir.add v353, v354 : u32 #[overflow = checked]; + v356 = hir.constant 4 : u32; + v357 = hir.mod v355, v356 : u32; + hir.assertz v357 #[code = 250]; + v358 = hir.int_to_ptr v355 : (ptr felt); + hir.store v358, v352; + v359 = hir.bitcast v342 : u32; + v360 = hir.constant 8 : u32; + v361 = hir.add v359, v360 : u32 #[overflow = checked]; + v362 = hir.constant 4 : u32; + v363 = hir.mod v361, v362 : u32; + hir.assertz v363 #[code = 250]; + v364 = hir.int_to_ptr v361 : (ptr felt); + hir.store v364, v350; + v365 = hir.bitcast v342 : u32; + v366 = hir.constant 4 : u32; + v367 = hir.add v365, v366 : u32 #[overflow = checked]; + v368 = hir.constant 4 : u32; + v369 = hir.mod v367, v368 : u32; + hir.assertz v369 #[code = 250]; + v370 = hir.int_to_ptr v367 : (ptr felt); + hir.store v370, v348; + v371 = hir.bitcast v342 : u32; + v372 = hir.constant 4 : u32; + v373 = hir.mod v371, v372 : u32; + hir.assertz v373 #[code = 250]; + v374 = hir.int_to_ptr v371 : (ptr felt); + hir.store v374, v346; + v375 = hir.constant 32 : i32; + v376 = hir.add v342, v375 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::account::add_asset(v376, v342) + v377 = hir.bitcast v342 : u32; + v378 = hir.constant 32 : u32; + v379 = hir.add v377, v378 : u32 #[overflow = checked]; + v380 = hir.constant 4 : u32; + v381 = hir.mod v379, v380 : u32; + hir.assertz v381 #[code = 250]; + v382 = hir.int_to_ptr v379 : (ptr felt); + v383 = hir.load v382 : felt; + v384 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v385 = hir.bitcast v384 : (ptr i32); + hir.store v385, v338; + hir.br ^block44(v383); + ^block44(v333: felt): + hir.ret v333; + }; + + builtin.function public @test_felt_ops_smoke(v386: felt, v387: felt) -> felt { + ^block45(v386: felt, v387: felt): + v389 = hir.constant 0 : i64; + v390 = hir.cast v386 : i64; + v391 = hir.gt v386, v387 : i1; + v392 = hir.cast v391 : i32; + v393 = hir.constant 0 : i32; + v394 = hir.neq v392, v393 : i1; + hir.cond_br v394 ^block52, ^block53; + ^block46(v388: felt): + hir.ret v388; + ^block47: + v436 = hir.neg v386 : felt; + hir.br ^block46(v436); + ^block48: + hir.assert_eq v386, v387; + v434 = hir.cast v390 : felt; + v435 = hir.add v386, v434 : felt #[overflow = unchecked]; + hir.ret v435; + ^block49: + v433 = hir.div v387, v386 : felt; + hir.ret v433; + ^block50: + v431 = hir.pow_2 v386 : felt; + v432 = hir.mul v431, v387 : felt #[overflow = unchecked]; + hir.ret v432; + ^block51: + v429 = hir.exp v386, v387 : felt; + v430 = hir.sub v429, v387 : felt #[overflow = unchecked]; + hir.ret v430; + ^block52: + v427 = hir.inv v386 : felt; + v428 = hir.add v427, v387 : felt #[overflow = unchecked]; + hir.ret v428; + ^block53: + v395 = hir.lt v387, v386 : i1; + v396 = hir.cast v395 : i32; + v397 = hir.constant 0 : i32; + v398 = hir.neq v396, v397 : i1; + hir.cond_br v398 ^block51, ^block54; + ^block54: + v399 = hir.lte v387, v386 : i1; + v400 = hir.cast v399 : i32; + v401 = hir.constant 0 : i32; + v402 = hir.neq v400, v401 : i1; + hir.cond_br v402 ^block50, ^block55; + ^block55: + v403 = hir.gte v386, v387 : i1; + v404 = hir.cast v403 : i32; + v405 = hir.constant 0 : i32; + v406 = hir.neq v404, v405 : i1; + hir.cond_br v406 ^block49, ^block56; + ^block56: + v407 = hir.eq v386, v387 : i1; + v408 = hir.cast v407 : i32; + v409 = hir.constant 1 : i32; + v410 = hir.eq v408, v409 : i1; + v411 = hir.zext v410 : u32; + v412 = hir.bitcast v411 : i32; + v413 = hir.constant 0 : i32; + v414 = hir.neq v412, v413 : i1; + hir.cond_br v414 ^block48, ^block57; + ^block57: + v415 = hir.eq v386, v387 : i1; + v416 = hir.cast v415 : i32; + v417 = hir.constant 1 : i32; + v418 = hir.neq v416, v417 : i1; + v419 = hir.zext v418 : u32; + v420 = hir.bitcast v419 : i32; + v421 = hir.constant 0 : i32; + v422 = hir.neq v420, v421 : i1; + hir.cond_br v422 ^block47, ^block58; + ^block58: + v423 = hir.is_odd v387 : i1; + v424 = hir.cast v423 : i32; + v425 = hir.constant 0 : i32; + v426 = hir.neq v424, v425 : i1; + hir.cond_br v426 ^block59, ^block60; + ^block59: + hir.assert v386 #[code = 0]; + hir.ret v387; + ^block60: + hir.assertz v387 #[code = 0]; + hir.ret v386; + }; + + builtin.function public @note_script() -> felt { + ^block61: + v438 = hir.constant 0 : i32; + v439 = hir.constant 0 : felt; + v440 = hir.constant 0 : i32; + v441 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v442 = hir.bitcast v441 : (ptr i32); + v443 = hir.load v442 : i32; + v444 = hir.constant 16 : i32; + v445 = hir.sub v443, v444 : i32 #[overflow = wrapping]; + v446 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v447 = hir.bitcast v446 : (ptr i32); + hir.store v447, v445; + v448 = hir.constant 0 : i64; + v449 = hir.cast v448 : felt; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::note::get_inputs(v445) + v450 = hir.bitcast v445 : u32; + v451 = hir.constant 4 : u32; + v452 = hir.mod v450, v451 : u32; + hir.assertz v452 #[code = 250]; + v453 = hir.int_to_ptr v450 : (ptr i32); + v454 = hir.load v453 : i32; + v455 = hir.bitcast v445 : u32; + v456 = hir.constant 4 : u32; + v457 = hir.add v455, v456 : u32 #[overflow = checked]; + v458 = hir.constant 4 : u32; + v459 = hir.mod v457, v458 : u32; + hir.assertz v459 #[code = 250]; + v460 = hir.int_to_ptr v457 : (ptr i32); + v461 = hir.load v460 : i32; + v462 = hir.bitcast v445 : u32; + v463 = hir.constant 8 : u32; + v464 = hir.add v462, v463 : u32 #[overflow = checked]; + v465 = hir.constant 4 : u32; + v466 = hir.mod v464, v465 : u32; + hir.assertz v466 #[code = 250]; + v467 = hir.int_to_ptr v464 : (ptr i32); + v468 = hir.load v467 : i32; + v469 = hir.constant 2 : i32; + v470 = hir.bitcast v469 : u32; + v471 = hir.shl v468, v470 : i32; + v472 = hir.add v461, v471 : i32 #[overflow = wrapping]; + v473 = hir.bitcast v445 : u32; + v474 = hir.constant 12 : u32; + v475 = hir.add v473, v474 : u32 #[overflow = checked]; + v476 = hir.constant 4 : u32; + v477 = hir.mod v475, v476 : u32; + hir.assertz v477 #[code = 250]; + v478 = hir.int_to_ptr v475 : (ptr i32); + hir.store v478, v472; + v479 = hir.bitcast v445 : u32; + v480 = hir.constant 8 : u32; + v481 = hir.add v479, v480 : u32 #[overflow = checked]; + v482 = hir.constant 4 : u32; + v483 = hir.mod v481, v482 : u32; + hir.assertz v483 #[code = 250]; + v484 = hir.int_to_ptr v481 : (ptr i32); + hir.store v484, v454; + v485 = hir.bitcast v445 : u32; + v486 = hir.constant 4 : u32; + v487 = hir.mod v485, v486 : u32; + hir.assertz v487 #[code = 250]; + v488 = hir.int_to_ptr v485 : (ptr i32); + hir.store v488, v461; + hir.br ^block63(v471, v445, v472, v449, v461); + ^block62(v437: felt): + + ^block63(v490: i32, v493: i32, v494: i32, v505: felt, v508: i32): + v491 = hir.constant 0 : i32; + v492 = hir.neq v490, v491 : i1; + hir.cond_br v492 ^block65, ^block66; + ^block64(v489: felt): + + ^block65: + v506 = hir.constant -4 : i32; + v507 = hir.add v490, v506 : i32 #[overflow = wrapping]; + v509 = hir.bitcast v508 : u32; + v510 = hir.constant 4 : u32; + v511 = hir.mod v509, v510 : u32; + hir.assertz v511 #[code = 250]; + v512 = hir.int_to_ptr v509 : (ptr felt); + v513 = hir.load v512 : felt; + v514 = hir.add v505, v513 : felt #[overflow = unchecked]; + v515 = hir.constant 4 : i32; + v516 = hir.add v508, v515 : i32 #[overflow = wrapping]; + hir.br ^block63(v507, v493, v494, v514, v516); + ^block66: + v495 = hir.bitcast v493 : u32; + v496 = hir.constant 4 : u32; + v497 = hir.add v495, v496 : u32 #[overflow = checked]; + v498 = hir.constant 4 : u32; + v499 = hir.mod v497, v498 : u32; + hir.assertz v499 #[code = 250]; + v500 = hir.int_to_ptr v497 : (ptr i32); + hir.store v500, v494; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/ as core::ops::drop::Drop>::drop(v493) + v501 = hir.constant 16 : i32; + v502 = hir.add v493, v501 : i32 #[overflow = wrapping]; + v503 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v504 = hir.bitcast v503 : (ptr i32); + hir.store v504, v502; + hir.ret v505; + }; + + builtin.function public @test_blake3_hash_1to1(v517: i32, v518: i32) { + ^block67(v517: i32, v518: i32): + v519 = hir.constant 0 : i32; + v520 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v521 = hir.bitcast v520 : (ptr i32); + v522 = hir.load v521 : i32; + v523 = hir.constant 32 : i32; + v524 = hir.sub v522, v523 : i32 #[overflow = wrapping]; + v525 = hir.constant -32 : i32; + v526 = hir.band v524, v525 : i32; + v527 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v528 = hir.bitcast v527 : (ptr i32); + hir.store v528, v526; + v529 = hir.bitcast v518 : u32; + v530 = hir.int_to_ptr v529 : (ptr i32); + v531 = hir.load v530 : i32; + v532 = hir.bitcast v518 : u32; + v533 = hir.constant 4 : u32; + v534 = hir.add v532, v533 : u32 #[overflow = checked]; + v535 = hir.int_to_ptr v534 : (ptr i32); + v536 = hir.load v535 : i32; + v537 = hir.bitcast v518 : u32; + v538 = hir.constant 8 : u32; + v539 = hir.add v537, v538 : u32 #[overflow = checked]; + v540 = hir.int_to_ptr v539 : (ptr i32); + v541 = hir.load v540 : i32; + v542 = hir.bitcast v518 : u32; + v543 = hir.constant 12 : u32; + v544 = hir.add v542, v543 : u32 #[overflow = checked]; + v545 = hir.int_to_ptr v544 : (ptr i32); + v546 = hir.load v545 : i32; + v547 = hir.bitcast v518 : u32; + v548 = hir.constant 16 : u32; + v549 = hir.add v547, v548 : u32 #[overflow = checked]; + v550 = hir.int_to_ptr v549 : (ptr i32); + v551 = hir.load v550 : i32; + v552 = hir.bitcast v518 : u32; + v553 = hir.constant 20 : u32; + v554 = hir.add v552, v553 : u32 #[overflow = checked]; + v555 = hir.int_to_ptr v554 : (ptr i32); + v556 = hir.load v555 : i32; + v557 = hir.bitcast v518 : u32; + v558 = hir.constant 24 : u32; + v559 = hir.add v557, v558 : u32 #[overflow = checked]; + v560 = hir.int_to_ptr v559 : (ptr i32); + v561 = hir.load v560 : i32; + v562 = hir.bitcast v518 : u32; + v563 = hir.constant 28 : u32; + v564 = hir.add v562, v563 : u32 #[overflow = checked]; + v565 = hir.int_to_ptr v564 : (ptr i32); + v566 = hir.load v565 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1(v531, v536, v541, v546, v551, v556, v561, v566, v526) + v567 = hir.constant 24 : i32; + v568 = hir.add v517, v567 : i32 #[overflow = wrapping]; + v569 = hir.bitcast v526 : u32; + v570 = hir.constant 24 : u32; + v571 = hir.add v569, v570 : u32 #[overflow = checked]; + v572 = hir.constant 8 : u32; + v573 = hir.mod v571, v572 : u32; + hir.assertz v573 #[code = 250]; + v574 = hir.int_to_ptr v571 : (ptr i64); + v575 = hir.load v574 : i64; + v576 = hir.bitcast v568 : u32; + v577 = hir.int_to_ptr v576 : (ptr i64); + hir.store v577, v575; + v578 = hir.constant 16 : i32; + v579 = hir.add v517, v578 : i32 #[overflow = wrapping]; + v580 = hir.bitcast v526 : u32; + v581 = hir.constant 16 : u32; + v582 = hir.add v580, v581 : u32 #[overflow = checked]; + v583 = hir.constant 8 : u32; + v584 = hir.mod v582, v583 : u32; + hir.assertz v584 #[code = 250]; + v585 = hir.int_to_ptr v582 : (ptr i64); + v586 = hir.load v585 : i64; + v587 = hir.bitcast v579 : u32; + v588 = hir.int_to_ptr v587 : (ptr i64); + hir.store v588, v586; + v589 = hir.constant 8 : i32; + v590 = hir.add v517, v589 : i32 #[overflow = wrapping]; + v591 = hir.bitcast v526 : u32; + v592 = hir.constant 8 : u32; + v593 = hir.add v591, v592 : u32 #[overflow = checked]; + v594 = hir.constant 8 : u32; + v595 = hir.mod v593, v594 : u32; + hir.assertz v595 #[code = 250]; + v596 = hir.int_to_ptr v593 : (ptr i64); + v597 = hir.load v596 : i64; + v598 = hir.bitcast v590 : u32; + v599 = hir.int_to_ptr v598 : (ptr i64); + hir.store v599, v597; + v600 = hir.bitcast v526 : u32; + v601 = hir.constant 8 : u32; + v602 = hir.mod v600, v601 : u32; + hir.assertz v602 #[code = 250]; + v603 = hir.int_to_ptr v600 : (ptr i64); + v604 = hir.load v603 : i64; + v605 = hir.bitcast v517 : u32; + v606 = hir.int_to_ptr v605 : (ptr i64); + hir.store v606, v604; + v607 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v608 = hir.bitcast v607 : (ptr i32); + hir.store v608, v522; + hir.br ^block68; + ^block68: + hir.ret ; + }; + + builtin.function public @test_blake3_hash_2to1(v609: i32, v610: i32) { + ^block69(v609: i32, v610: i32): + v611 = hir.bitcast v610 : u32; + v612 = hir.int_to_ptr v611 : (ptr i32); + v613 = hir.load v612 : i32; + v614 = hir.bitcast v610 : u32; + v615 = hir.constant 4 : u32; + v616 = hir.add v614, v615 : u32 #[overflow = checked]; + v617 = hir.int_to_ptr v616 : (ptr i32); + v618 = hir.load v617 : i32; + v619 = hir.bitcast v610 : u32; + v620 = hir.constant 8 : u32; + v621 = hir.add v619, v620 : u32 #[overflow = checked]; + v622 = hir.int_to_ptr v621 : (ptr i32); + v623 = hir.load v622 : i32; + v624 = hir.bitcast v610 : u32; + v625 = hir.constant 12 : u32; + v626 = hir.add v624, v625 : u32 #[overflow = checked]; + v627 = hir.int_to_ptr v626 : (ptr i32); + v628 = hir.load v627 : i32; + v629 = hir.bitcast v610 : u32; + v630 = hir.constant 16 : u32; + v631 = hir.add v629, v630 : u32 #[overflow = checked]; + v632 = hir.int_to_ptr v631 : (ptr i32); + v633 = hir.load v632 : i32; + v634 = hir.bitcast v610 : u32; + v635 = hir.constant 20 : u32; + v636 = hir.add v634, v635 : u32 #[overflow = checked]; + v637 = hir.int_to_ptr v636 : (ptr i32); + v638 = hir.load v637 : i32; + v639 = hir.bitcast v610 : u32; + v640 = hir.constant 24 : u32; + v641 = hir.add v639, v640 : u32 #[overflow = checked]; + v642 = hir.int_to_ptr v641 : (ptr i32); + v643 = hir.load v642 : i32; + v644 = hir.bitcast v610 : u32; + v645 = hir.constant 28 : u32; + v646 = hir.add v644, v645 : u32 #[overflow = checked]; + v647 = hir.int_to_ptr v646 : (ptr i32); + v648 = hir.load v647 : i32; + v649 = hir.bitcast v610 : u32; + v650 = hir.constant 32 : u32; + v651 = hir.add v649, v650 : u32 #[overflow = checked]; + v652 = hir.int_to_ptr v651 : (ptr i32); + v653 = hir.load v652 : i32; + v654 = hir.bitcast v610 : u32; + v655 = hir.constant 36 : u32; + v656 = hir.add v654, v655 : u32 #[overflow = checked]; + v657 = hir.int_to_ptr v656 : (ptr i32); + v658 = hir.load v657 : i32; + v659 = hir.bitcast v610 : u32; + v660 = hir.constant 40 : u32; + v661 = hir.add v659, v660 : u32 #[overflow = checked]; + v662 = hir.int_to_ptr v661 : (ptr i32); + v663 = hir.load v662 : i32; + v664 = hir.bitcast v610 : u32; + v665 = hir.constant 44 : u32; + v666 = hir.add v664, v665 : u32 #[overflow = checked]; + v667 = hir.int_to_ptr v666 : (ptr i32); + v668 = hir.load v667 : i32; + v669 = hir.bitcast v610 : u32; + v670 = hir.constant 48 : u32; + v671 = hir.add v669, v670 : u32 #[overflow = checked]; + v672 = hir.int_to_ptr v671 : (ptr i32); + v673 = hir.load v672 : i32; + v674 = hir.bitcast v610 : u32; + v675 = hir.constant 52 : u32; + v676 = hir.add v674, v675 : u32 #[overflow = checked]; + v677 = hir.int_to_ptr v676 : (ptr i32); + v678 = hir.load v677 : i32; + v679 = hir.bitcast v610 : u32; + v680 = hir.constant 56 : u32; + v681 = hir.add v679, v680 : u32 #[overflow = checked]; + v682 = hir.int_to_ptr v681 : (ptr i32); + v683 = hir.load v682 : i32; + v684 = hir.bitcast v610 : u32; + v685 = hir.constant 60 : u32; + v686 = hir.add v684, v685 : u32 #[overflow = checked]; + v687 = hir.int_to_ptr v686 : (ptr i32); + v688 = hir.load v687 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_2to1(v613, v618, v623, v628, v633, v638, v643, v648, v653, v658, v663, v668, v673, v678, v683, v688, v609) + hir.br ^block70; + ^block70: + hir.ret ; + }; + + builtin.function public @test_rpo_falcon512_verify(v689: i32, v690: i32) { + ^block71(v689: i32, v690: i32): + v691 = hir.bitcast v689 : u32; + v692 = hir.constant 4 : u32; + v693 = hir.mod v691, v692 : u32; + hir.assertz v693 #[code = 250]; + v694 = hir.int_to_ptr v691 : (ptr felt); + v695 = hir.load v694 : felt; + v696 = hir.bitcast v689 : u32; + v697 = hir.constant 4 : u32; + v698 = hir.add v696, v697 : u32 #[overflow = checked]; + v699 = hir.constant 4 : u32; + v700 = hir.mod v698, v699 : u32; + hir.assertz v700 #[code = 250]; + v701 = hir.int_to_ptr v698 : (ptr felt); + v702 = hir.load v701 : felt; + v703 = hir.bitcast v689 : u32; + v704 = hir.constant 8 : u32; + v705 = hir.add v703, v704 : u32 #[overflow = checked]; + v706 = hir.constant 4 : u32; + v707 = hir.mod v705, v706 : u32; + hir.assertz v707 #[code = 250]; + v708 = hir.int_to_ptr v705 : (ptr felt); + v709 = hir.load v708 : felt; + v710 = hir.bitcast v689 : u32; + v711 = hir.constant 12 : u32; + v712 = hir.add v710, v711 : u32 #[overflow = checked]; + v713 = hir.constant 4 : u32; + v714 = hir.mod v712, v713 : u32; + hir.assertz v714 #[code = 250]; + v715 = hir.int_to_ptr v712 : (ptr felt); + v716 = hir.load v715 : felt; + v717 = hir.bitcast v690 : u32; + v718 = hir.constant 4 : u32; + v719 = hir.mod v717, v718 : u32; + hir.assertz v719 #[code = 250]; + v720 = hir.int_to_ptr v717 : (ptr felt); + v721 = hir.load v720 : felt; + v722 = hir.bitcast v690 : u32; + v723 = hir.constant 4 : u32; + v724 = hir.add v722, v723 : u32 #[overflow = checked]; + v725 = hir.constant 4 : u32; + v726 = hir.mod v724, v725 : u32; + hir.assertz v726 #[code = 250]; + v727 = hir.int_to_ptr v724 : (ptr felt); + v728 = hir.load v727 : felt; + v729 = hir.bitcast v690 : u32; + v730 = hir.constant 8 : u32; + v731 = hir.add v729, v730 : u32 #[overflow = checked]; + v732 = hir.constant 4 : u32; + v733 = hir.mod v731, v732 : u32; + hir.assertz v733 #[code = 250]; + v734 = hir.int_to_ptr v731 : (ptr felt); + v735 = hir.load v734 : felt; + v736 = hir.bitcast v690 : u32; + v737 = hir.constant 12 : u32; + v738 = hir.add v736, v737 : u32 #[overflow = checked]; + v739 = hir.constant 4 : u32; + v740 = hir.mod v738, v739 : u32; + hir.assertz v740 #[code = 250]; + v741 = hir.int_to_ptr v738 : (ptr felt); + v742 = hir.load v741 : felt; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_stdlib_sys::stdlib::crypto::dsa::extern_rpo_falcon512_verify(v695, v702, v709, v716, v721, v728, v735, v742) + hir.br ^block72; + ^block72: + hir.ret ; + }; + + builtin.function public @test_pipe_words_to_memory(v743: i32, v744: felt) { + ^block73(v743: i32, v744: felt): + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_stdlib_sys::stdlib::mem::pipe_words_to_memory(v743, v744) + hir.br ^block74; + ^block74: + hir.ret ; + }; + + builtin.function public @test_pipe_double_words_to_memory(v745: i32, v746: felt) { + ^block75(v745: i32, v746: felt): + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_stdlib_sys::stdlib::mem::pipe_double_words_to_memory(v745, v746) + hir.br ^block76; + ^block76: + hir.ret ; + }; + + builtin.function public @test_remove_asset(v747: i32) -> felt { + ^block77(v747: i32): + v749 = hir.constant 0 : i32; + v750 = hir.constant 0 : felt; + v751 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v752 = hir.bitcast v751 : (ptr i32); + v753 = hir.load v752 : i32; + v754 = hir.constant 32 : i32; + v755 = hir.sub v753, v754 : i32 #[overflow = wrapping]; + v756 = hir.constant -32 : i32; + v757 = hir.band v755, v756 : i32; + v758 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v759 = hir.bitcast v758 : (ptr i32); + hir.store v759, v757; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::account::remove_asset(v757, v747) + v760 = hir.bitcast v757 : u32; + v761 = hir.constant 4 : u32; + v762 = hir.mod v760, v761 : u32; + hir.assertz v762 #[code = 250]; + v763 = hir.int_to_ptr v760 : (ptr felt); + v764 = hir.load v763 : felt; + v765 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v766 = hir.bitcast v765 : (ptr i32); + hir.store v766, v753; + hir.br ^block78(v764); + ^block78(v748: felt): + hir.ret v748; + }; + + builtin.function public @test_create_note(v767: i32, v768: felt, v769: felt, v770: i32) -> felt { + ^block79(v767: i32, v768: felt, v769: felt, v770: i32): + v772 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::tx::create_note(v767, v768, v769, v770) : felt + hir.br ^block80(v772); + ^block80(v771: felt): + hir.ret v771; + }; + + builtin.function public @__rust_alloc(v773: i32, v774: i32) -> i32 { + ^block81(v773: i32, v774: i32): + v776 = hir.constant 1048704 : i32; + v777 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/::alloc(v776, v774, v773) : i32 + hir.br ^block82(v777); + ^block82(v775: i32): + hir.ret v775; + }; + + builtin.function public @__rust_dealloc(v778: i32, v779: i32, v780: i32) { + ^block83(v778: i32, v779: i32, v780: i32): + hir.br ^block84; + ^block84: + hir.ret ; + }; + + builtin.function public @__rust_alloc_zeroed(v781: i32, v782: i32) -> i32 { + ^block85(v781: i32, v782: i32): + v784 = hir.constant 1048704 : i32; + v785 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/core::alloc::global::GlobalAlloc::alloc_zeroed(v784, v782, v781) : i32 + hir.br ^block86(v785); + ^block86(v783: i32): + hir.ret v783; + }; + + builtin.function public @::alloc(v786: i32, v787: i32, v788: i32) -> i32 { + ^block87(v786: i32, v787: i32, v788: i32): + v790 = hir.constant 0 : i32; + v791 = hir.constant 32 : i32; + v792 = hir.constant 32 : i32; + v793 = hir.bitcast v787 : u32; + v794 = hir.bitcast v792 : u32; + v795 = hir.gt v793, v794 : i1; + v796 = hir.zext v795 : u32; + v797 = hir.bitcast v796 : i32; + v798 = hir.constant 0 : i32; + v799 = hir.neq v797, v798 : i1; + v800 = hir.select v799, v787, v791 : i32; + v801 = hir.popcnt v800 : u32; + v802 = hir.bitcast v801 : i32; + v803 = hir.constant 1 : i32; + v804 = hir.neq v802, v803 : i1; + v805 = hir.zext v804 : u32; + v806 = hir.bitcast v805 : i32; + v807 = hir.constant 0 : i32; + v808 = hir.neq v806, v807 : i1; + hir.cond_br v808 ^block89, ^block90; + ^block88(v789: i32): + + ^block89: + hir.unreachable ; + ^block90: + v809 = hir.constant -2147483648 : i32; + v810 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/core::ptr::alignment::Alignment::max(v787, v800) : i32 + v811 = hir.sub v809, v810 : i32 #[overflow = wrapping]; + v812 = hir.bitcast v811 : u32; + v813 = hir.bitcast v788 : u32; + v814 = hir.lt v812, v813 : i1; + v815 = hir.zext v814 : u32; + v816 = hir.bitcast v815 : i32; + v817 = hir.constant 0 : i32; + v818 = hir.neq v816, v817 : i1; + hir.cond_br v818 ^block89, ^block91; + ^block91: + v819 = hir.constant 0 : i32; + v820 = hir.add v788, v810 : i32 #[overflow = wrapping]; + v821 = hir.constant -1 : i32; + v822 = hir.add v820, v821 : i32 #[overflow = wrapping]; + v823 = hir.constant 0 : i32; + v824 = hir.sub v823, v810 : i32 #[overflow = wrapping]; + v825 = hir.band v822, v824 : i32; + v826 = hir.bitcast v786 : u32; + v827 = hir.constant 4 : u32; + v828 = hir.mod v826, v827 : u32; + hir.assertz v828 #[code = 250]; + v829 = hir.int_to_ptr v826 : (ptr i32); + v830 = hir.load v829 : i32; + v831 = hir.constant 0 : i32; + v832 = hir.neq v830, v831 : i1; + hir.cond_br v832 ^block92(v786, v825, v810, v819), ^block93; + ^block92(v845: i32, v852: i32, v865: i32, v868: i32): + v844 = hir.constant 268435456 : i32; + v846 = hir.bitcast v845 : u32; + v847 = hir.constant 4 : u32; + v848 = hir.mod v846, v847 : u32; + hir.assertz v848 #[code = 250]; + v849 = hir.int_to_ptr v846 : (ptr i32); + v850 = hir.load v849 : i32; + v851 = hir.sub v844, v850 : i32 #[overflow = wrapping]; + v853 = hir.bitcast v851 : u32; + v854 = hir.bitcast v852 : u32; + v855 = hir.lt v853, v854 : i1; + v856 = hir.zext v855 : u32; + v857 = hir.bitcast v856 : i32; + v858 = hir.constant 0 : i32; + v859 = hir.neq v857, v858 : i1; + hir.cond_br v859 ^block94(v868), ^block95; + ^block93: + v833 = hir.exec @intrinsics::mem/heap_base() : i32 + v834 = hir.mem_size : u32; + v835 = hir.constant 16 : i32; + v836 = hir.bitcast v835 : u32; + v837 = hir.shl v834, v836 : u32; + v838 = hir.bitcast v837 : i32; + v839 = hir.add v833, v838 : i32 #[overflow = wrapping]; + v840 = hir.bitcast v786 : u32; + v841 = hir.constant 4 : u32; + v842 = hir.mod v840, v841 : u32; + hir.assertz v842 #[code = 250]; + v843 = hir.int_to_ptr v840 : (ptr i32); + hir.store v843, v839; + hir.br ^block92(v786, v825, v810, v819); + ^block94(v867: i32): + hir.ret v867; + ^block95: + v860 = hir.add v850, v852 : i32 #[overflow = wrapping]; + v861 = hir.bitcast v845 : u32; + v862 = hir.constant 4 : u32; + v863 = hir.mod v861, v862 : u32; + hir.assertz v863 #[code = 250]; + v864 = hir.int_to_ptr v861 : (ptr i32); + hir.store v864, v860; + v866 = hir.add v850, v865 : i32 #[overflow = wrapping]; + hir.br ^block94(v866); + }; + + builtin.function public @miden_base_sys::bindings::account::get_id() -> felt { + ^block96: + v870 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::account::extern_account_get_id() : felt + hir.br ^block97(v870); + ^block97(v869: felt): + hir.ret v869; + }; + + builtin.function public @miden_base_sys::bindings::account::add_asset(v871: i32, v872: i32) { + ^block98(v871: i32, v872: i32): + v873 = hir.bitcast v872 : u32; + v874 = hir.constant 4 : u32; + v875 = hir.mod v873, v874 : u32; + hir.assertz v875 #[code = 250]; + v876 = hir.int_to_ptr v873 : (ptr felt); + v877 = hir.load v876 : felt; + v878 = hir.bitcast v872 : u32; + v879 = hir.constant 4 : u32; + v880 = hir.add v878, v879 : u32 #[overflow = checked]; + v881 = hir.constant 4 : u32; + v882 = hir.mod v880, v881 : u32; + hir.assertz v882 #[code = 250]; + v883 = hir.int_to_ptr v880 : (ptr felt); + v884 = hir.load v883 : felt; + v885 = hir.bitcast v872 : u32; + v886 = hir.constant 8 : u32; + v887 = hir.add v885, v886 : u32 #[overflow = checked]; + v888 = hir.constant 4 : u32; + v889 = hir.mod v887, v888 : u32; + hir.assertz v889 #[code = 250]; + v890 = hir.int_to_ptr v887 : (ptr felt); + v891 = hir.load v890 : felt; + v892 = hir.bitcast v872 : u32; + v893 = hir.constant 12 : u32; + v894 = hir.add v892, v893 : u32 #[overflow = checked]; + v895 = hir.constant 4 : u32; + v896 = hir.mod v894, v895 : u32; + hir.assertz v896 #[code = 250]; + v897 = hir.int_to_ptr v894 : (ptr felt); + v898 = hir.load v897 : felt; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::account::extern_account_add_asset(v877, v884, v891, v898, v871) + hir.br ^block99; + ^block99: + hir.ret ; + }; + + builtin.function public @miden_base_sys::bindings::account::remove_asset(v899: i32, v900: i32) { + ^block100(v899: i32, v900: i32): + v901 = hir.bitcast v900 : u32; + v902 = hir.constant 4 : u32; + v903 = hir.mod v901, v902 : u32; + hir.assertz v903 #[code = 250]; + v904 = hir.int_to_ptr v901 : (ptr felt); + v905 = hir.load v904 : felt; + v906 = hir.bitcast v900 : u32; + v907 = hir.constant 4 : u32; + v908 = hir.add v906, v907 : u32 #[overflow = checked]; + v909 = hir.constant 4 : u32; + v910 = hir.mod v908, v909 : u32; + hir.assertz v910 #[code = 250]; + v911 = hir.int_to_ptr v908 : (ptr felt); + v912 = hir.load v911 : felt; + v913 = hir.bitcast v900 : u32; + v914 = hir.constant 8 : u32; + v915 = hir.add v913, v914 : u32 #[overflow = checked]; + v916 = hir.constant 4 : u32; + v917 = hir.mod v915, v916 : u32; + hir.assertz v917 #[code = 250]; + v918 = hir.int_to_ptr v915 : (ptr felt); + v919 = hir.load v918 : felt; + v920 = hir.bitcast v900 : u32; + v921 = hir.constant 12 : u32; + v922 = hir.add v920, v921 : u32 #[overflow = checked]; + v923 = hir.constant 4 : u32; + v924 = hir.mod v922, v923 : u32; + hir.assertz v924 #[code = 250]; + v925 = hir.int_to_ptr v922 : (ptr felt); + v926 = hir.load v925 : felt; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::account::extern_account_remove_asset(v905, v912, v919, v926, v899) + hir.br ^block101; + ^block101: + hir.ret ; + }; + + builtin.function public @miden_base_sys::bindings::note::get_inputs(v927: i32) { + ^block102(v927: i32): + v928 = hir.constant 0 : i32; + v929 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v930 = hir.bitcast v929 : (ptr i32); + v931 = hir.load v930 : i32; + v932 = hir.constant 16 : i32; + v933 = hir.sub v931, v932 : i32 #[overflow = wrapping]; + v934 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v935 = hir.bitcast v934 : (ptr i32); + hir.store v935, v933; + v936 = hir.constant 4 : i32; + v937 = hir.add v933, v936 : i32 #[overflow = wrapping]; + v938 = hir.constant 256 : i32; + v939 = hir.constant 0 : i32; + v940 = hir.constant 4 : i32; + v941 = hir.constant 4 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::raw_vec::RawVecInner::try_allocate_in(v937, v938, v939, v940, v941) + v942 = hir.bitcast v933 : u32; + v943 = hir.constant 8 : u32; + v944 = hir.add v942, v943 : u32 #[overflow = checked]; + v945 = hir.constant 4 : u32; + v946 = hir.mod v944, v945 : u32; + hir.assertz v946 #[code = 250]; + v947 = hir.int_to_ptr v944 : (ptr i32); + v948 = hir.load v947 : i32; + v949 = hir.bitcast v933 : u32; + v950 = hir.constant 4 : u32; + v951 = hir.add v949, v950 : u32 #[overflow = checked]; + v952 = hir.constant 4 : u32; + v953 = hir.mod v951, v952 : u32; + hir.assertz v953 #[code = 250]; + v954 = hir.int_to_ptr v951 : (ptr i32); + v955 = hir.load v954 : i32; + v956 = hir.constant 1 : i32; + v957 = hir.neq v955, v956 : i1; + v958 = hir.zext v957 : u32; + v959 = hir.bitcast v958 : i32; + v960 = hir.constant 0 : i32; + v961 = hir.neq v959, v960 : i1; + hir.cond_br v961 ^block104, ^block105; + ^block103: + hir.ret ; + ^block104: + v970 = hir.bitcast v933 : u32; + v971 = hir.constant 12 : u32; + v972 = hir.add v970, v971 : u32 #[overflow = checked]; + v973 = hir.constant 4 : u32; + v974 = hir.mod v972, v973 : u32; + hir.assertz v974 #[code = 250]; + v975 = hir.int_to_ptr v972 : (ptr i32); + v976 = hir.load v975 : i32; + v977 = hir.constant 4 : i32; + v978 = hir.bitcast v976 : u32; + v979 = hir.bitcast v977 : u32; + v980 = hir.shr v978, v979 : u32; + v981 = hir.bitcast v980 : i32; + v982 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::note::extern_note_get_inputs(v981) : i32 + v983 = hir.bitcast v927 : u32; + v984 = hir.constant 8 : u32; + v985 = hir.add v983, v984 : u32 #[overflow = checked]; + v986 = hir.constant 4 : u32; + v987 = hir.mod v985, v986 : u32; + hir.assertz v987 #[code = 250]; + v988 = hir.int_to_ptr v985 : (ptr i32); + hir.store v988, v982; + v989 = hir.bitcast v927 : u32; + v990 = hir.constant 4 : u32; + v991 = hir.add v989, v990 : u32 #[overflow = checked]; + v992 = hir.constant 4 : u32; + v993 = hir.mod v991, v992 : u32; + hir.assertz v993 #[code = 250]; + v994 = hir.int_to_ptr v991 : (ptr i32); + hir.store v994, v976; + v995 = hir.bitcast v927 : u32; + v996 = hir.constant 4 : u32; + v997 = hir.mod v995, v996 : u32; + hir.assertz v997 #[code = 250]; + v998 = hir.int_to_ptr v995 : (ptr i32); + hir.store v998, v948; + v999 = hir.constant 16 : i32; + v1000 = hir.add v933, v999 : i32 #[overflow = wrapping]; + v1001 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1002 = hir.bitcast v1001 : (ptr i32); + hir.store v1002, v1000; + hir.br ^block103; + ^block105: + v962 = hir.bitcast v933 : u32; + v963 = hir.constant 12 : u32; + v964 = hir.add v962, v963 : u32 #[overflow = checked]; + v965 = hir.constant 4 : u32; + v966 = hir.mod v964, v965 : u32; + hir.assertz v966 #[code = 250]; + v967 = hir.int_to_ptr v964 : (ptr i32); + v968 = hir.load v967 : i32; + v969 = hir.constant 1048616 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::raw_vec::handle_error(v948, v968, v969) + hir.unreachable ; + }; + + builtin.function public @miden_base_sys::bindings::tx::create_note(v1003: i32, v1004: felt, v1005: felt, v1006: i32) -> felt { + ^block106(v1003: i32, v1004: felt, v1005: felt, v1006: i32): + v1008 = hir.bitcast v1003 : u32; + v1009 = hir.constant 4 : u32; + v1010 = hir.mod v1008, v1009 : u32; + hir.assertz v1010 #[code = 250]; + v1011 = hir.int_to_ptr v1008 : (ptr felt); + v1012 = hir.load v1011 : felt; + v1013 = hir.bitcast v1003 : u32; + v1014 = hir.constant 4 : u32; + v1015 = hir.add v1013, v1014 : u32 #[overflow = checked]; + v1016 = hir.constant 4 : u32; + v1017 = hir.mod v1015, v1016 : u32; + hir.assertz v1017 #[code = 250]; + v1018 = hir.int_to_ptr v1015 : (ptr felt); + v1019 = hir.load v1018 : felt; + v1020 = hir.bitcast v1003 : u32; + v1021 = hir.constant 8 : u32; + v1022 = hir.add v1020, v1021 : u32 #[overflow = checked]; + v1023 = hir.constant 4 : u32; + v1024 = hir.mod v1022, v1023 : u32; + hir.assertz v1024 #[code = 250]; + v1025 = hir.int_to_ptr v1022 : (ptr felt); + v1026 = hir.load v1025 : felt; + v1027 = hir.bitcast v1003 : u32; + v1028 = hir.constant 12 : u32; + v1029 = hir.add v1027, v1028 : u32 #[overflow = checked]; + v1030 = hir.constant 4 : u32; + v1031 = hir.mod v1029, v1030 : u32; + hir.assertz v1031 #[code = 250]; + v1032 = hir.int_to_ptr v1029 : (ptr felt); + v1033 = hir.load v1032 : felt; + v1034 = hir.bitcast v1006 : u32; + v1035 = hir.constant 4 : u32; + v1036 = hir.mod v1034, v1035 : u32; + hir.assertz v1036 #[code = 250]; + v1037 = hir.int_to_ptr v1034 : (ptr felt); + v1038 = hir.load v1037 : felt; + v1039 = hir.bitcast v1006 : u32; + v1040 = hir.constant 4 : u32; + v1041 = hir.add v1039, v1040 : u32 #[overflow = checked]; + v1042 = hir.constant 4 : u32; + v1043 = hir.mod v1041, v1042 : u32; + hir.assertz v1043 #[code = 250]; + v1044 = hir.int_to_ptr v1041 : (ptr felt); + v1045 = hir.load v1044 : felt; + v1046 = hir.bitcast v1006 : u32; + v1047 = hir.constant 8 : u32; + v1048 = hir.add v1046, v1047 : u32 #[overflow = checked]; + v1049 = hir.constant 4 : u32; + v1050 = hir.mod v1048, v1049 : u32; + hir.assertz v1050 #[code = 250]; + v1051 = hir.int_to_ptr v1048 : (ptr felt); + v1052 = hir.load v1051 : felt; + v1053 = hir.bitcast v1006 : u32; + v1054 = hir.constant 12 : u32; + v1055 = hir.add v1053, v1054 : u32 #[overflow = checked]; + v1056 = hir.constant 4 : u32; + v1057 = hir.mod v1055, v1056 : u32; + hir.assertz v1057 #[code = 250]; + v1058 = hir.int_to_ptr v1055 : (ptr felt); + v1059 = hir.load v1058 : felt; + v1060 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_base_sys::bindings::tx::extern_tx_create_note(v1012, v1019, v1026, v1033, v1004, v1005, v1038, v1045, v1052, v1059) : felt + hir.br ^block107(v1060); + ^block107(v1007: felt): + hir.ret v1007; + }; + + builtin.function public @alloc::vec::Vec::with_capacity(v1061: i32, v1062: i32, v1063: i32) { + ^block108(v1061: i32, v1062: i32, v1063: i32): + v1064 = hir.constant 0 : i32; + v1065 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1066 = hir.bitcast v1065 : (ptr i32); + v1067 = hir.load v1066 : i32; + v1068 = hir.constant 16 : i32; + v1069 = hir.sub v1067, v1068 : i32 #[overflow = wrapping]; + v1070 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1071 = hir.bitcast v1070 : (ptr i32); + hir.store v1071, v1069; + v1072 = hir.constant 4 : i32; + v1073 = hir.add v1069, v1072 : i32 #[overflow = wrapping]; + v1074 = hir.constant 0 : i32; + v1075 = hir.constant 4 : i32; + v1076 = hir.constant 4 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::raw_vec::RawVecInner::try_allocate_in(v1073, v1062, v1074, v1075, v1076) + v1077 = hir.bitcast v1069 : u32; + v1078 = hir.constant 8 : u32; + v1079 = hir.add v1077, v1078 : u32 #[overflow = checked]; + v1080 = hir.constant 4 : u32; + v1081 = hir.mod v1079, v1080 : u32; + hir.assertz v1081 #[code = 250]; + v1082 = hir.int_to_ptr v1079 : (ptr i32); + v1083 = hir.load v1082 : i32; + v1084 = hir.bitcast v1069 : u32; + v1085 = hir.constant 4 : u32; + v1086 = hir.add v1084, v1085 : u32 #[overflow = checked]; + v1087 = hir.constant 4 : u32; + v1088 = hir.mod v1086, v1087 : u32; + hir.assertz v1088 #[code = 250]; + v1089 = hir.int_to_ptr v1086 : (ptr i32); + v1090 = hir.load v1089 : i32; + v1091 = hir.constant 1 : i32; + v1092 = hir.neq v1090, v1091 : i1; + v1093 = hir.zext v1092 : u32; + v1094 = hir.bitcast v1093 : i32; + v1095 = hir.constant 0 : i32; + v1096 = hir.neq v1094, v1095 : i1; + hir.cond_br v1096 ^block110, ^block111; + ^block109: + hir.ret ; + ^block110: + v1104 = hir.bitcast v1069 : u32; + v1105 = hir.constant 12 : u32; + v1106 = hir.add v1104, v1105 : u32 #[overflow = checked]; + v1107 = hir.constant 4 : u32; + v1108 = hir.mod v1106, v1107 : u32; + hir.assertz v1108 #[code = 250]; + v1109 = hir.int_to_ptr v1106 : (ptr i32); + v1110 = hir.load v1109 : i32; + v1111 = hir.constant 0 : i32; + v1112 = hir.bitcast v1061 : u32; + v1113 = hir.constant 8 : u32; + v1114 = hir.add v1112, v1113 : u32 #[overflow = checked]; + v1115 = hir.constant 4 : u32; + v1116 = hir.mod v1114, v1115 : u32; + hir.assertz v1116 #[code = 250]; + v1117 = hir.int_to_ptr v1114 : (ptr i32); + hir.store v1117, v1111; + v1118 = hir.bitcast v1061 : u32; + v1119 = hir.constant 4 : u32; + v1120 = hir.add v1118, v1119 : u32 #[overflow = checked]; + v1121 = hir.constant 4 : u32; + v1122 = hir.mod v1120, v1121 : u32; + hir.assertz v1122 #[code = 250]; + v1123 = hir.int_to_ptr v1120 : (ptr i32); + hir.store v1123, v1110; + v1124 = hir.bitcast v1061 : u32; + v1125 = hir.constant 4 : u32; + v1126 = hir.mod v1124, v1125 : u32; + hir.assertz v1126 #[code = 250]; + v1127 = hir.int_to_ptr v1124 : (ptr i32); + hir.store v1127, v1083; + v1128 = hir.constant 16 : i32; + v1129 = hir.add v1069, v1128 : i32 #[overflow = wrapping]; + v1130 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1131 = hir.bitcast v1130 : (ptr i32); + hir.store v1131, v1129; + hir.br ^block109; + ^block111: + v1097 = hir.bitcast v1069 : u32; + v1098 = hir.constant 12 : u32; + v1099 = hir.add v1097, v1098 : u32 #[overflow = checked]; + v1100 = hir.constant 4 : u32; + v1101 = hir.mod v1099, v1100 : u32; + hir.assertz v1101 #[code = 250]; + v1102 = hir.int_to_ptr v1099 : (ptr i32); + v1103 = hir.load v1102 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::raw_vec::handle_error(v1083, v1103, v1063) + hir.unreachable ; + }; + + builtin.function public @miden_stdlib_sys::stdlib::mem::pipe_words_to_memory(v1132: i32, v1133: felt) { + ^block112(v1132: i32, v1133: felt): + v1134 = hir.constant 0 : i32; + v1135 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1136 = hir.bitcast v1135 : (ptr i32); + v1137 = hir.load v1136 : i32; + v1138 = hir.constant 96 : i32; + v1139 = hir.sub v1137, v1138 : i32 #[overflow = wrapping]; + v1140 = hir.constant -32 : i32; + v1141 = hir.band v1139, v1140 : i32; + v1142 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1143 = hir.bitcast v1142 : (ptr i32); + hir.store v1143, v1141; + v1144 = hir.constant 20 : i32; + v1145 = hir.add v1141, v1144 : i32 #[overflow = wrapping]; + v1146 = hir.cast v1133 : i64; + v1147 = hir.trunc v1146 : i32; + v1148 = hir.constant 2 : i32; + v1149 = hir.bitcast v1148 : u32; + v1150 = hir.shl v1147, v1149 : i32; + v1151 = hir.constant 1048672 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::vec::Vec::with_capacity(v1145, v1150, v1151) + v1152 = hir.bitcast v1141 : u32; + v1153 = hir.constant 24 : u32; + v1154 = hir.add v1152, v1153 : u32 #[overflow = checked]; + v1155 = hir.constant 4 : u32; + v1156 = hir.mod v1154, v1155 : u32; + hir.assertz v1156 #[code = 250]; + v1157 = hir.int_to_ptr v1154 : (ptr i32); + v1158 = hir.load v1157 : i32; + v1159 = hir.constant 32 : i32; + v1160 = hir.add v1141, v1159 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_stdlib_sys::stdlib::mem::extern_pipe_words_to_memory(v1133, v1158, v1160) + v1161 = hir.constant 24 : i32; + v1162 = hir.add v1132, v1161 : i32 #[overflow = wrapping]; + v1163 = hir.bitcast v1141 : u32; + v1164 = hir.constant 56 : u32; + v1165 = hir.add v1163, v1164 : u32 #[overflow = checked]; + v1166 = hir.constant 8 : u32; + v1167 = hir.mod v1165, v1166 : u32; + hir.assertz v1167 #[code = 250]; + v1168 = hir.int_to_ptr v1165 : (ptr i64); + v1169 = hir.load v1168 : i64; + v1170 = hir.bitcast v1162 : u32; + v1171 = hir.constant 8 : u32; + v1172 = hir.mod v1170, v1171 : u32; + hir.assertz v1172 #[code = 250]; + v1173 = hir.int_to_ptr v1170 : (ptr i64); + hir.store v1173, v1169; + v1174 = hir.constant 16 : i32; + v1175 = hir.add v1132, v1174 : i32 #[overflow = wrapping]; + v1176 = hir.bitcast v1141 : u32; + v1177 = hir.constant 48 : u32; + v1178 = hir.add v1176, v1177 : u32 #[overflow = checked]; + v1179 = hir.constant 8 : u32; + v1180 = hir.mod v1178, v1179 : u32; + hir.assertz v1180 #[code = 250]; + v1181 = hir.int_to_ptr v1178 : (ptr i64); + v1182 = hir.load v1181 : i64; + v1183 = hir.bitcast v1175 : u32; + v1184 = hir.constant 8 : u32; + v1185 = hir.mod v1183, v1184 : u32; + hir.assertz v1185 #[code = 250]; + v1186 = hir.int_to_ptr v1183 : (ptr i64); + hir.store v1186, v1182; + v1187 = hir.constant 8 : i32; + v1188 = hir.add v1132, v1187 : i32 #[overflow = wrapping]; + v1189 = hir.bitcast v1141 : u32; + v1190 = hir.constant 40 : u32; + v1191 = hir.add v1189, v1190 : u32 #[overflow = checked]; + v1192 = hir.constant 8 : u32; + v1193 = hir.mod v1191, v1192 : u32; + hir.assertz v1193 #[code = 250]; + v1194 = hir.int_to_ptr v1191 : (ptr i64); + v1195 = hir.load v1194 : i64; + v1196 = hir.bitcast v1188 : u32; + v1197 = hir.constant 8 : u32; + v1198 = hir.mod v1196, v1197 : u32; + hir.assertz v1198 #[code = 250]; + v1199 = hir.int_to_ptr v1196 : (ptr i64); + hir.store v1199, v1195; + v1200 = hir.bitcast v1141 : u32; + v1201 = hir.constant 32 : u32; + v1202 = hir.add v1200, v1201 : u32 #[overflow = checked]; + v1203 = hir.constant 8 : u32; + v1204 = hir.mod v1202, v1203 : u32; + hir.assertz v1204 #[code = 250]; + v1205 = hir.int_to_ptr v1202 : (ptr i64); + v1206 = hir.load v1205 : i64; + v1207 = hir.bitcast v1132 : u32; + v1208 = hir.constant 8 : u32; + v1209 = hir.mod v1207, v1208 : u32; + hir.assertz v1209 #[code = 250]; + v1210 = hir.int_to_ptr v1207 : (ptr i64); + hir.store v1210, v1206; + v1211 = hir.constant 40 : i32; + v1212 = hir.add v1132, v1211 : i32 #[overflow = wrapping]; + v1213 = hir.constant 20 : i32; + v1214 = hir.add v1141, v1213 : i32 #[overflow = wrapping]; + v1215 = hir.constant 8 : i32; + v1216 = hir.add v1214, v1215 : i32 #[overflow = wrapping]; + v1217 = hir.bitcast v1216 : u32; + v1218 = hir.constant 4 : u32; + v1219 = hir.mod v1217, v1218 : u32; + hir.assertz v1219 #[code = 250]; + v1220 = hir.int_to_ptr v1217 : (ptr i32); + v1221 = hir.load v1220 : i32; + v1222 = hir.bitcast v1212 : u32; + v1223 = hir.constant 4 : u32; + v1224 = hir.mod v1222, v1223 : u32; + hir.assertz v1224 #[code = 250]; + v1225 = hir.int_to_ptr v1222 : (ptr i32); + hir.store v1225, v1221; + v1226 = hir.bitcast v1141 : u32; + v1227 = hir.constant 20 : u32; + v1228 = hir.add v1226, v1227 : u32 #[overflow = checked]; + v1229 = hir.constant 4 : u32; + v1230 = hir.mod v1228, v1229 : u32; + hir.assertz v1230 #[code = 250]; + v1231 = hir.int_to_ptr v1228 : (ptr i64); + v1232 = hir.load v1231 : i64; + v1233 = hir.bitcast v1132 : u32; + v1234 = hir.constant 32 : u32; + v1235 = hir.add v1233, v1234 : u32 #[overflow = checked]; + v1236 = hir.constant 4 : u32; + v1237 = hir.mod v1235, v1236 : u32; + hir.assertz v1237 #[code = 250]; + v1238 = hir.int_to_ptr v1235 : (ptr i64); + hir.store v1238, v1232; + v1239 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1240 = hir.bitcast v1239 : (ptr i32); + hir.store v1240, v1137; + hir.br ^block113; + ^block113: + hir.ret ; + }; + + builtin.function public @miden_stdlib_sys::stdlib::mem::pipe_double_words_to_memory(v1241: i32, v1242: felt) { + ^block114(v1241: i32, v1242: felt): + v1243 = hir.constant 0 : i32; + v1244 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1245 = hir.bitcast v1244 : (ptr i32); + v1246 = hir.load v1245 : i32; + v1247 = hir.constant 160 : i32; + v1248 = hir.sub v1246, v1247 : i32 #[overflow = wrapping]; + v1249 = hir.constant -32 : i32; + v1250 = hir.band v1248, v1249 : i32; + v1251 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1252 = hir.bitcast v1251 : (ptr i32); + hir.store v1252, v1250; + v1253 = hir.constant 20 : i32; + v1254 = hir.add v1250, v1253 : i32 #[overflow = wrapping]; + v1255 = hir.cast v1242 : i64; + v1256 = hir.trunc v1255 : i32; + v1257 = hir.constant 2 : i32; + v1258 = hir.bitcast v1257 : u32; + v1259 = hir.shl v1256, v1258 : i32; + v1260 = hir.constant 1048688 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::vec::Vec::with_capacity(v1254, v1259, v1260) + v1261 = hir.bitcast v1250 : u32; + v1262 = hir.constant 24 : u32; + v1263 = hir.add v1261, v1262 : u32 #[overflow = checked]; + v1264 = hir.constant 4 : u32; + v1265 = hir.mod v1263, v1264 : u32; + hir.assertz v1265 #[code = 250]; + v1266 = hir.int_to_ptr v1263 : (ptr i32); + v1267 = hir.load v1266 : i32; + v1268 = hir.constant 0 : i32; + v1269 = hir.bitcast v1268 : felt; + v1270 = hir.constant 4 : i32; + v1271 = hir.bitcast v1270 : u32; + v1272 = hir.shl v1256, v1271 : i32; + v1273 = hir.add v1267, v1272 : i32 #[overflow = wrapping]; + v1274 = hir.constant 32 : i32; + v1275 = hir.add v1250, v1274 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/miden_stdlib_sys::stdlib::mem::extern_pipe_double_words_to_memory(v1269, v1269, v1269, v1269, v1269, v1269, v1269, v1269, v1269, v1269, v1269, v1269, v1267, v1273, v1275) + v1276 = hir.constant 24 : i32; + v1277 = hir.add v1241, v1276 : i32 #[overflow = wrapping]; + v1278 = hir.constant 88 : i32; + v1279 = hir.add v1250, v1278 : i32 #[overflow = wrapping]; + v1280 = hir.bitcast v1279 : u32; + v1281 = hir.constant 8 : u32; + v1282 = hir.mod v1280, v1281 : u32; + hir.assertz v1282 #[code = 250]; + v1283 = hir.int_to_ptr v1280 : (ptr i64); + v1284 = hir.load v1283 : i64; + v1285 = hir.bitcast v1277 : u32; + v1286 = hir.constant 8 : u32; + v1287 = hir.mod v1285, v1286 : u32; + hir.assertz v1287 #[code = 250]; + v1288 = hir.int_to_ptr v1285 : (ptr i64); + hir.store v1288, v1284; + v1289 = hir.constant 16 : i32; + v1290 = hir.add v1241, v1289 : i32 #[overflow = wrapping]; + v1291 = hir.constant 80 : i32; + v1292 = hir.add v1250, v1291 : i32 #[overflow = wrapping]; + v1293 = hir.bitcast v1292 : u32; + v1294 = hir.constant 8 : u32; + v1295 = hir.mod v1293, v1294 : u32; + hir.assertz v1295 #[code = 250]; + v1296 = hir.int_to_ptr v1293 : (ptr i64); + v1297 = hir.load v1296 : i64; + v1298 = hir.bitcast v1290 : u32; + v1299 = hir.constant 8 : u32; + v1300 = hir.mod v1298, v1299 : u32; + hir.assertz v1300 #[code = 250]; + v1301 = hir.int_to_ptr v1298 : (ptr i64); + hir.store v1301, v1297; + v1302 = hir.constant 8 : i32; + v1303 = hir.add v1241, v1302 : i32 #[overflow = wrapping]; + v1304 = hir.constant 32 : i32; + v1305 = hir.add v1250, v1304 : i32 #[overflow = wrapping]; + v1306 = hir.constant 40 : i32; + v1307 = hir.add v1305, v1306 : i32 #[overflow = wrapping]; + v1308 = hir.bitcast v1307 : u32; + v1309 = hir.constant 8 : u32; + v1310 = hir.mod v1308, v1309 : u32; + hir.assertz v1310 #[code = 250]; + v1311 = hir.int_to_ptr v1308 : (ptr i64); + v1312 = hir.load v1311 : i64; + v1313 = hir.bitcast v1303 : u32; + v1314 = hir.constant 8 : u32; + v1315 = hir.mod v1313, v1314 : u32; + hir.assertz v1315 #[code = 250]; + v1316 = hir.int_to_ptr v1313 : (ptr i64); + hir.store v1316, v1312; + v1317 = hir.bitcast v1250 : u32; + v1318 = hir.constant 64 : u32; + v1319 = hir.add v1317, v1318 : u32 #[overflow = checked]; + v1320 = hir.constant 8 : u32; + v1321 = hir.mod v1319, v1320 : u32; + hir.assertz v1321 #[code = 250]; + v1322 = hir.int_to_ptr v1319 : (ptr i64); + v1323 = hir.load v1322 : i64; + v1324 = hir.bitcast v1241 : u32; + v1325 = hir.constant 8 : u32; + v1326 = hir.mod v1324, v1325 : u32; + hir.assertz v1326 #[code = 250]; + v1327 = hir.int_to_ptr v1324 : (ptr i64); + hir.store v1327, v1323; + v1328 = hir.constant 40 : i32; + v1329 = hir.add v1241, v1328 : i32 #[overflow = wrapping]; + v1330 = hir.constant 20 : i32; + v1331 = hir.add v1250, v1330 : i32 #[overflow = wrapping]; + v1332 = hir.constant 8 : i32; + v1333 = hir.add v1331, v1332 : i32 #[overflow = wrapping]; + v1334 = hir.bitcast v1333 : u32; + v1335 = hir.constant 4 : u32; + v1336 = hir.mod v1334, v1335 : u32; + hir.assertz v1336 #[code = 250]; + v1337 = hir.int_to_ptr v1334 : (ptr i32); + v1338 = hir.load v1337 : i32; + v1339 = hir.bitcast v1329 : u32; + v1340 = hir.constant 4 : u32; + v1341 = hir.mod v1339, v1340 : u32; + hir.assertz v1341 #[code = 250]; + v1342 = hir.int_to_ptr v1339 : (ptr i32); + hir.store v1342, v1338; + v1343 = hir.bitcast v1250 : u32; + v1344 = hir.constant 20 : u32; + v1345 = hir.add v1343, v1344 : u32 #[overflow = checked]; + v1346 = hir.constant 4 : u32; + v1347 = hir.mod v1345, v1346 : u32; + hir.assertz v1347 #[code = 250]; + v1348 = hir.int_to_ptr v1345 : (ptr i64); + v1349 = hir.load v1348 : i64; + v1350 = hir.bitcast v1241 : u32; + v1351 = hir.constant 32 : u32; + v1352 = hir.add v1350, v1351 : u32 #[overflow = checked]; + v1353 = hir.constant 4 : u32; + v1354 = hir.mod v1352, v1353 : u32; + hir.assertz v1354 #[code = 250]; + v1355 = hir.int_to_ptr v1352 : (ptr i64); + hir.store v1355, v1349; + v1356 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1357 = hir.bitcast v1356 : (ptr i32); + hir.store v1357, v1246; + hir.br ^block115; + ^block115: + hir.ret ; + }; + + builtin.function public @dummy() { + ^block116: + hir.br ^block117; + ^block117: + hir.ret ; + }; + + builtin.function public @__wasm_call_dtors() { + ^block118: + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/dummy() + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/dummy() + hir.br ^block119; + ^block119: + hir.ret ; + }; + + builtin.function public @alloc::raw_vec::RawVecInner::deallocate(v1358: i32, v1359: i32, v1360: i32) { + ^block120(v1358: i32, v1359: i32, v1360: i32): + v1361 = hir.constant 0 : i32; + v1362 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1363 = hir.bitcast v1362 : (ptr i32); + v1364 = hir.load v1363 : i32; + v1365 = hir.constant 16 : i32; + v1366 = hir.sub v1364, v1365 : i32 #[overflow = wrapping]; + v1367 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1368 = hir.bitcast v1367 : (ptr i32); + hir.store v1368, v1366; + v1369 = hir.constant 4 : i32; + v1370 = hir.add v1366, v1369 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::raw_vec::RawVecInner::current_memory(v1370, v1358, v1359, v1360) + v1371 = hir.bitcast v1366 : u32; + v1372 = hir.constant 8 : u32; + v1373 = hir.add v1371, v1372 : u32 #[overflow = checked]; + v1374 = hir.constant 4 : u32; + v1375 = hir.mod v1373, v1374 : u32; + hir.assertz v1375 #[code = 250]; + v1376 = hir.int_to_ptr v1373 : (ptr i32); + v1377 = hir.load v1376 : i32; + v1378 = hir.constant 0 : i32; + v1379 = hir.eq v1377, v1378 : i1; + v1380 = hir.zext v1379 : u32; + v1381 = hir.bitcast v1380 : i32; + v1382 = hir.constant 0 : i32; + v1383 = hir.neq v1381, v1382 : i1; + hir.cond_br v1383 ^block122(v1366), ^block123; + ^block121: + hir.ret ; + ^block122(v1398: i32): + v1399 = hir.constant 16 : i32; + v1400 = hir.add v1398, v1399 : i32 #[overflow = wrapping]; + v1401 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1402 = hir.bitcast v1401 : (ptr i32); + hir.store v1402, v1400; + hir.br ^block121; + ^block123: + v1384 = hir.bitcast v1366 : u32; + v1385 = hir.constant 4 : u32; + v1386 = hir.add v1384, v1385 : u32 #[overflow = checked]; + v1387 = hir.constant 4 : u32; + v1388 = hir.mod v1386, v1387 : u32; + hir.assertz v1388 #[code = 250]; + v1389 = hir.int_to_ptr v1386 : (ptr i32); + v1390 = hir.load v1389 : i32; + v1391 = hir.bitcast v1366 : u32; + v1392 = hir.constant 12 : u32; + v1393 = hir.add v1391, v1392 : u32 #[overflow = checked]; + v1394 = hir.constant 4 : u32; + v1395 = hir.mod v1393, v1394 : u32; + hir.assertz v1395 #[code = 250]; + v1396 = hir.int_to_ptr v1393 : (ptr i32); + v1397 = hir.load v1396 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/::deallocate(v1390, v1377, v1397) + hir.br ^block122(v1366); + }; + + builtin.function public @alloc::raw_vec::RawVecInner::try_allocate_in(v1403: i32, v1404: i32, v1405: i32, v1406: i32, v1407: i32) { + ^block124(v1403: i32, v1404: i32, v1405: i32, v1406: i32, v1407: i32): + v1408 = hir.constant 0 : i32; + v1409 = hir.constant 0 : i64; + v1410 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1411 = hir.bitcast v1410 : (ptr i32); + v1412 = hir.load v1411 : i32; + v1413 = hir.constant 16 : i32; + v1414 = hir.sub v1412, v1413 : i32 #[overflow = wrapping]; + v1415 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1416 = hir.bitcast v1415 : (ptr i32); + hir.store v1416, v1414; + v1417 = hir.add v1406, v1407 : i32 #[overflow = wrapping]; + v1418 = hir.constant -1 : i32; + v1419 = hir.add v1417, v1418 : i32 #[overflow = wrapping]; + v1420 = hir.constant 0 : i32; + v1421 = hir.sub v1420, v1406 : i32 #[overflow = wrapping]; + v1422 = hir.band v1419, v1421 : i32; + v1423 = hir.bitcast v1422 : u32; + v1424 = hir.zext v1423 : u64; + v1425 = hir.bitcast v1424 : i64; + v1426 = hir.bitcast v1404 : u32; + v1427 = hir.zext v1426 : u64; + v1428 = hir.bitcast v1427 : i64; + v1429 = hir.mul v1425, v1428 : i64 #[overflow = wrapping]; + v1430 = hir.constant 32 : i64; + v1431 = hir.bitcast v1429 : u64; + v1432 = hir.cast v1430 : u32; + v1433 = hir.shr v1431, v1432 : u64; + v1434 = hir.bitcast v1433 : i64; + v1435 = hir.trunc v1434 : i32; + v1436 = hir.constant 0 : i32; + v1437 = hir.neq v1435, v1436 : i1; + hir.cond_br v1437 ^block128(v1403, v1414), ^block129; + ^block125: + hir.ret ; + ^block126(v1527: i32, v1528: i32, v1533: i32): + v1529 = hir.bitcast v1527 : u32; + v1530 = hir.constant 4 : u32; + v1531 = hir.mod v1529, v1530 : u32; + hir.assertz v1531 #[code = 250]; + v1532 = hir.int_to_ptr v1529 : (ptr i32); + hir.store v1532, v1528; + v1536 = hir.constant 16 : i32; + v1537 = hir.add v1533, v1536 : i32 #[overflow = wrapping]; + v1538 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1539 = hir.bitcast v1538 : (ptr i32); + hir.store v1539, v1537; + hir.br ^block125; + ^block127: + v1513 = hir.bitcast v1488 : u32; + v1514 = hir.constant 8 : u32; + v1515 = hir.add v1513, v1514 : u32 #[overflow = checked]; + v1516 = hir.constant 4 : u32; + v1517 = hir.mod v1515, v1516 : u32; + hir.assertz v1517 #[code = 250]; + v1518 = hir.int_to_ptr v1515 : (ptr i32); + hir.store v1518, v1512; + v1520 = hir.bitcast v1488 : u32; + v1521 = hir.constant 4 : u32; + v1522 = hir.add v1520, v1521 : u32 #[overflow = checked]; + v1523 = hir.constant 4 : u32; + v1524 = hir.mod v1522, v1523 : u32; + hir.assertz v1524 #[code = 250]; + v1525 = hir.int_to_ptr v1522 : (ptr i32); + hir.store v1525, v1519; + v1526 = hir.constant 1 : i32; + hir.br ^block126(v1488, v1526, v1534); + ^block128(v1503: i32, v1535: i32): + v1504 = hir.constant 0 : i32; + v1505 = hir.bitcast v1503 : u32; + v1506 = hir.constant 4 : u32; + v1507 = hir.add v1505, v1506 : u32 #[overflow = checked]; + v1508 = hir.constant 4 : u32; + v1509 = hir.mod v1507, v1508 : u32; + hir.assertz v1509 #[code = 250]; + v1510 = hir.int_to_ptr v1507 : (ptr i32); + hir.store v1510, v1504; + v1511 = hir.constant 1 : i32; + hir.br ^block126(v1503, v1511, v1535); + ^block129: + v1438 = hir.constant -2147483648 : i32; + v1439 = hir.sub v1438, v1406 : i32 #[overflow = wrapping]; + v1440 = hir.trunc v1429 : i32; + v1441 = hir.bitcast v1439 : u32; + v1442 = hir.bitcast v1440 : u32; + v1443 = hir.lt v1441, v1442 : i1; + v1444 = hir.zext v1443 : u32; + v1445 = hir.bitcast v1444 : i32; + v1446 = hir.constant 0 : i32; + v1447 = hir.neq v1445, v1446 : i1; + hir.cond_br v1447 ^block128(v1403, v1414), ^block130; + ^block130: + v1448 = hir.constant 0 : i32; + v1449 = hir.neq v1440, v1448 : i1; + hir.cond_br v1449 ^block131, ^block132; + ^block131: + v1464 = hir.constant 0 : i32; + v1465 = hir.neq v1405, v1464 : i1; + hir.cond_br v1465 ^block134, ^block135; + ^block132: + v1450 = hir.bitcast v1403 : u32; + v1451 = hir.constant 8 : u32; + v1452 = hir.add v1450, v1451 : u32 #[overflow = checked]; + v1453 = hir.constant 4 : u32; + v1454 = hir.mod v1452, v1453 : u32; + hir.assertz v1454 #[code = 250]; + v1455 = hir.int_to_ptr v1452 : (ptr i32); + hir.store v1455, v1406; + v1456 = hir.constant 0 : i32; + v1457 = hir.constant 0 : i32; + v1458 = hir.bitcast v1403 : u32; + v1459 = hir.constant 4 : u32; + v1460 = hir.add v1458, v1459 : u32 #[overflow = checked]; + v1461 = hir.constant 4 : u32; + v1462 = hir.mod v1460, v1461 : u32; + hir.assertz v1462 #[code = 250]; + v1463 = hir.int_to_ptr v1460 : (ptr i32); + hir.store v1463, v1457; + hir.br ^block126(v1403, v1456, v1414); + ^block133(v1481: i32, v1488: i32, v1495: i32, v1512: i32, v1519: i32, v1534: i32): + v1482 = hir.constant 0 : i32; + v1483 = hir.eq v1481, v1482 : i1; + v1484 = hir.zext v1483 : u32; + v1485 = hir.bitcast v1484 : i32; + v1486 = hir.constant 0 : i32; + v1487 = hir.neq v1485, v1486 : i1; + hir.cond_br v1487 ^block127, ^block136; + ^block134: + v1475 = hir.constant 1 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::alloc::Global::alloc_impl(v1414, v1406, v1440, v1475) + v1476 = hir.bitcast v1414 : u32; + v1477 = hir.constant 4 : u32; + v1478 = hir.mod v1476, v1477 : u32; + hir.assertz v1478 #[code = 250]; + v1479 = hir.int_to_ptr v1476 : (ptr i32); + v1480 = hir.load v1479 : i32; + hir.br ^block133(v1480, v1403, v1404, v1440, v1406, v1414); + ^block135: + v1466 = hir.constant 8 : i32; + v1467 = hir.add v1414, v1466 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/::allocate(v1467, v1406, v1440) + v1468 = hir.bitcast v1414 : u32; + v1469 = hir.constant 8 : u32; + v1470 = hir.add v1468, v1469 : u32 #[overflow = checked]; + v1471 = hir.constant 4 : u32; + v1472 = hir.mod v1470, v1471 : u32; + hir.assertz v1472 #[code = 250]; + v1473 = hir.int_to_ptr v1470 : (ptr i32); + v1474 = hir.load v1473 : i32; + hir.br ^block133(v1474, v1403, v1404, v1440, v1406, v1414); + ^block136: + v1489 = hir.bitcast v1488 : u32; + v1490 = hir.constant 8 : u32; + v1491 = hir.add v1489, v1490 : u32 #[overflow = checked]; + v1492 = hir.constant 4 : u32; + v1493 = hir.mod v1491, v1492 : u32; + hir.assertz v1493 #[code = 250]; + v1494 = hir.int_to_ptr v1491 : (ptr i32); + hir.store v1494, v1481; + v1496 = hir.bitcast v1488 : u32; + v1497 = hir.constant 4 : u32; + v1498 = hir.add v1496, v1497 : u32 #[overflow = checked]; + v1499 = hir.constant 4 : u32; + v1500 = hir.mod v1498, v1499 : u32; + hir.assertz v1500 #[code = 250]; + v1501 = hir.int_to_ptr v1498 : (ptr i32); + hir.store v1501, v1495; + v1502 = hir.constant 0 : i32; + hir.br ^block126(v1488, v1502, v1534); + }; + + builtin.function public @::allocate(v1540: i32, v1541: i32, v1542: i32) { + ^block137(v1540: i32, v1541: i32, v1542: i32): + v1543 = hir.constant 0 : i32; + v1544 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1545 = hir.bitcast v1544 : (ptr i32); + v1546 = hir.load v1545 : i32; + v1547 = hir.constant 16 : i32; + v1548 = hir.sub v1546, v1547 : i32 #[overflow = wrapping]; + v1549 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1550 = hir.bitcast v1549 : (ptr i32); + hir.store v1550, v1548; + v1551 = hir.constant 8 : i32; + v1552 = hir.add v1548, v1551 : i32 #[overflow = wrapping]; + v1553 = hir.constant 0 : i32; + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/alloc::alloc::Global::alloc_impl(v1552, v1541, v1542, v1553) + v1554 = hir.bitcast v1548 : u32; + v1555 = hir.constant 12 : u32; + v1556 = hir.add v1554, v1555 : u32 #[overflow = checked]; + v1557 = hir.constant 4 : u32; + v1558 = hir.mod v1556, v1557 : u32; + hir.assertz v1558 #[code = 250]; + v1559 = hir.int_to_ptr v1556 : (ptr i32); + v1560 = hir.load v1559 : i32; + v1561 = hir.bitcast v1548 : u32; + v1562 = hir.constant 8 : u32; + v1563 = hir.add v1561, v1562 : u32 #[overflow = checked]; + v1564 = hir.constant 4 : u32; + v1565 = hir.mod v1563, v1564 : u32; + hir.assertz v1565 #[code = 250]; + v1566 = hir.int_to_ptr v1563 : (ptr i32); + v1567 = hir.load v1566 : i32; + v1568 = hir.bitcast v1540 : u32; + v1569 = hir.constant 4 : u32; + v1570 = hir.mod v1568, v1569 : u32; + hir.assertz v1570 #[code = 250]; + v1571 = hir.int_to_ptr v1568 : (ptr i32); + hir.store v1571, v1567; + v1572 = hir.bitcast v1540 : u32; + v1573 = hir.constant 4 : u32; + v1574 = hir.add v1572, v1573 : u32 #[overflow = checked]; + v1575 = hir.constant 4 : u32; + v1576 = hir.mod v1574, v1575 : u32; + hir.assertz v1576 #[code = 250]; + v1577 = hir.int_to_ptr v1574 : (ptr i32); + hir.store v1577, v1560; + v1578 = hir.constant 16 : i32; + v1579 = hir.add v1548, v1578 : i32 #[overflow = wrapping]; + v1580 = builtin.global_symbol : (ptr u8) #[offset = 0] #[symbol = root_ns:root@1.0.0/miden_sdk_account_test/__stack_pointer]; + v1581 = hir.bitcast v1580 : (ptr i32); + hir.store v1581, v1579; + hir.br ^block138; + ^block138: + hir.ret ; + }; + + builtin.function public @alloc::alloc::Global::alloc_impl(v1582: i32, v1583: i32, v1584: i32, v1585: i32) { + ^block139(v1582: i32, v1583: i32, v1584: i32, v1585: i32): + v1586 = hir.constant 0 : i32; + v1587 = hir.eq v1584, v1586 : i1; + v1588 = hir.zext v1587 : u32; + v1589 = hir.bitcast v1588 : i32; + v1590 = hir.constant 0 : i32; + v1591 = hir.neq v1589, v1590 : i1; + hir.cond_br v1591 ^block141(v1582, v1584, v1583), ^block142; + ^block140: + hir.ret ; + ^block141(v1603: i32, v1604: i32, v1611: i32): + v1605 = hir.bitcast v1603 : u32; + v1606 = hir.constant 4 : u32; + v1607 = hir.add v1605, v1606 : u32 #[overflow = checked]; + v1608 = hir.constant 4 : u32; + v1609 = hir.mod v1607, v1608 : u32; + hir.assertz v1609 #[code = 250]; + v1610 = hir.int_to_ptr v1607 : (ptr i32); + hir.store v1610, v1604; + v1612 = hir.bitcast v1603 : u32; + v1613 = hir.constant 4 : u32; + v1614 = hir.mod v1612, v1613 : u32; + hir.assertz v1614 #[code = 250]; + v1615 = hir.int_to_ptr v1612 : (ptr i32); + hir.store v1615, v1611; + hir.br ^block140; + ^block142: + v1592 = hir.constant 0 : i32; + v1593 = hir.bitcast v1592 : u32; + v1594 = hir.constant 1048708 : u32; + v1595 = hir.add v1593, v1594 : u32 #[overflow = checked]; + v1596 = hir.int_to_ptr v1595 : (ptr u8); + v1597 = hir.load v1596 : u8; + v1598 = hir.zext v1597 : u32; + v1599 = hir.constant 0 : i32; + v1600 = hir.neq v1585, v1599 : i1; + hir.cond_br v1600 ^block143, ^block144; + ^block143: + v1602 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__rust_alloc_zeroed(v1584, v1583) : i32 + hir.br ^block141(v1582, v1584, v1602); + ^block144: + v1601 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__rust_alloc(v1584, v1583) : i32 + hir.br ^block141(v1582, v1584, v1601); + }; + + builtin.function public @alloc::raw_vec::RawVecInner::current_memory(v1616: i32, v1617: i32, v1618: i32, v1619: i32) { + ^block145(v1616: i32, v1617: i32, v1618: i32, v1619: i32): + v1620 = hir.constant 0 : i32; + v1621 = hir.constant 0 : i32; + v1622 = hir.constant 4 : i32; + v1623 = hir.constant 0 : i32; + v1624 = hir.eq v1619, v1623 : i1; + v1625 = hir.zext v1624 : u32; + v1626 = hir.bitcast v1625 : i32; + v1627 = hir.constant 0 : i32; + v1628 = hir.neq v1626, v1627 : i1; + hir.cond_br v1628 ^block147(v1616, v1622, v1621), ^block148; + ^block146: + hir.ret ; + ^block147(v1659: i32, v1660: i32, v1662: i32): + v1661 = hir.add v1659, v1660 : i32 #[overflow = wrapping]; + v1663 = hir.bitcast v1661 : u32; + v1664 = hir.constant 4 : u32; + v1665 = hir.mod v1663, v1664 : u32; + hir.assertz v1665 #[code = 250]; + v1666 = hir.int_to_ptr v1663 : (ptr i32); + hir.store v1666, v1662; + hir.br ^block146; + ^block148: + v1629 = hir.bitcast v1617 : u32; + v1630 = hir.constant 4 : u32; + v1631 = hir.mod v1629, v1630 : u32; + hir.assertz v1631 #[code = 250]; + v1632 = hir.int_to_ptr v1629 : (ptr i32); + v1633 = hir.load v1632 : i32; + v1634 = hir.constant 0 : i32; + v1635 = hir.eq v1633, v1634 : i1; + v1636 = hir.zext v1635 : u32; + v1637 = hir.bitcast v1636 : i32; + v1638 = hir.constant 0 : i32; + v1639 = hir.neq v1637, v1638 : i1; + hir.cond_br v1639 ^block147(v1616, v1622, v1621), ^block149; + ^block149: + v1640 = hir.bitcast v1616 : u32; + v1641 = hir.constant 4 : u32; + v1642 = hir.add v1640, v1641 : u32 #[overflow = checked]; + v1643 = hir.constant 4 : u32; + v1644 = hir.mod v1642, v1643 : u32; + hir.assertz v1644 #[code = 250]; + v1645 = hir.int_to_ptr v1642 : (ptr i32); + hir.store v1645, v1618; + v1646 = hir.bitcast v1617 : u32; + v1647 = hir.constant 4 : u32; + v1648 = hir.add v1646, v1647 : u32 #[overflow = checked]; + v1649 = hir.constant 4 : u32; + v1650 = hir.mod v1648, v1649 : u32; + hir.assertz v1650 #[code = 250]; + v1651 = hir.int_to_ptr v1648 : (ptr i32); + v1652 = hir.load v1651 : i32; + v1653 = hir.bitcast v1616 : u32; + v1654 = hir.constant 4 : u32; + v1655 = hir.mod v1653, v1654 : u32; + hir.assertz v1655 #[code = 250]; + v1656 = hir.int_to_ptr v1653 : (ptr i32); + hir.store v1656, v1652; + v1657 = hir.mul v1633, v1619 : i32 #[overflow = wrapping]; + v1658 = hir.constant 8 : i32; + hir.br ^block147(v1616, v1658, v1657); + }; + + builtin.function public @::deallocate(v1667: i32, v1668: i32, v1669: i32) { + ^block150(v1667: i32, v1668: i32, v1669: i32): + v1670 = hir.constant 0 : i32; + v1671 = hir.eq v1669, v1670 : i1; + v1672 = hir.zext v1671 : u32; + v1673 = hir.bitcast v1672 : i32; + v1674 = hir.constant 0 : i32; + v1675 = hir.neq v1673, v1674 : i1; + hir.cond_br v1675 ^block152, ^block153; + ^block151: + hir.ret ; + ^block152: + hir.br ^block151; + ^block153: + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__rust_dealloc(v1667, v1669, v1668) + hir.br ^block152; + }; + + builtin.function public @alloc::raw_vec::handle_error(v1676: i32, v1677: i32, v1678: i32) { + ^block154(v1676: i32, v1677: i32, v1678: i32): + hir.unreachable ; + ^block155: + + }; + + builtin.function public @core::ptr::alignment::Alignment::max(v1679: i32, v1680: i32) -> i32 { + ^block156(v1679: i32, v1680: i32): + v1682 = hir.bitcast v1679 : u32; + v1683 = hir.bitcast v1680 : u32; + v1684 = hir.gt v1682, v1683 : i1; + v1685 = hir.zext v1684 : u32; + v1686 = hir.bitcast v1685 : i32; + v1687 = hir.constant 0 : i32; + v1688 = hir.neq v1686, v1687 : i1; + v1689 = hir.select v1688, v1679, v1680 : i32; + hir.br ^block157(v1689); + ^block157(v1681: i32): + hir.ret v1681; + }; + + builtin.function public @get_wallet_magic_number.command_export() -> felt { + ^block158: + v1691 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/get_wallet_magic_number() : felt + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block159(v1691); + ^block159(v1690: felt): + hir.ret v1690; + }; + + builtin.function public @test_add_asset.command_export() -> felt { + ^block160: + v1693 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_add_asset() : felt + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block161(v1693); + ^block161(v1692: felt): + hir.ret v1692; + }; + + builtin.function public @test_felt_ops_smoke.command_export(v1694: felt, v1695: felt) -> felt { + ^block162(v1694: felt, v1695: felt): + v1697 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_felt_ops_smoke(v1694, v1695) : felt + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block163(v1697); + ^block163(v1696: felt): + hir.ret v1696; + }; + + builtin.function public @note_script.command_export() -> felt { + ^block164: + v1699 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/note_script() : felt + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block165(v1699); + ^block165(v1698: felt): + hir.ret v1698; + }; + + builtin.function public @test_blake3_hash_1to1.command_export(v1700: i32, v1701: i32) { + ^block166(v1700: i32, v1701: i32): + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_blake3_hash_1to1(v1700, v1701) + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block167; + ^block167: + hir.ret ; + }; + + builtin.function public @test_blake3_hash_2to1.command_export(v1702: i32, v1703: i32) { + ^block168(v1702: i32, v1703: i32): + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_blake3_hash_2to1(v1702, v1703) + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block169; + ^block169: + hir.ret ; + }; + + builtin.function public @test_rpo_falcon512_verify.command_export(v1704: i32, v1705: i32) { + ^block170(v1704: i32, v1705: i32): + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_rpo_falcon512_verify(v1704, v1705) + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block171; + ^block171: + hir.ret ; + }; + + builtin.function public @test_pipe_words_to_memory.command_export(v1706: i32, v1707: felt) { + ^block172(v1706: i32, v1707: felt): + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_pipe_words_to_memory(v1706, v1707) + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block173; + ^block173: + hir.ret ; + }; + + builtin.function public @test_pipe_double_words_to_memory.command_export(v1708: i32, v1709: felt) { + ^block174(v1708: i32, v1709: felt): + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_pipe_double_words_to_memory(v1708, v1709) + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block175; + ^block175: + hir.ret ; + }; + + builtin.function public @test_remove_asset.command_export(v1710: i32) -> felt { + ^block176(v1710: i32): + v1712 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_remove_asset(v1710) : felt + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block177(v1712); + ^block177(v1711: felt): + hir.ret v1711; + }; + + builtin.function public @test_create_note.command_export(v1713: i32, v1714: felt, v1715: felt, v1716: i32) -> felt { + ^block178(v1713: i32, v1714: felt, v1715: felt, v1716: i32): + v1718 = hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/test_create_note(v1713, v1714, v1715, v1716) : felt + hir.exec @root_ns:root@1.0.0/miden_sdk_account_test/__wasm_call_dtors() + hir.br ^block179(v1718); + ^block179(v1717: felt): + hir.ret v1717; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + hir.ret_imm 1048576; + }; + + builtin.segment readonly @1048576 = 0x0000001e000000610000002600100038000000220000004a0000002600100038000073722e6d656d2f62696c6474732f6372732f7379732d62696c6474732f6b64732f2e2e2f2e2e000000210000001100000027001000000073722e65746f6e2f73676e69646e69622f6372732f7379732d657361622f6b64732f2e2e2f2e2e; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.wat b/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.wat index 35b85aa4d..020a266a8 100644 --- a/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.wat +++ b/tests/integration/expected/rust_sdk_account_test/miden_sdk_account_test.wat @@ -1,5 +1,5 @@ (module $miden_sdk_account_test.wasm - (type (;0;) (func (param i64) (result f32))) + (type (;0;) (func (param i32) (result f32))) (type (;1;) (func (param f32 f32) (result f32))) (type (;2;) (func (param f32) (result i64))) (type (;3;) (func (param f32 f32) (result i32))) @@ -7,56 +7,106 @@ (type (;5;) (func (param f32))) (type (;6;) (func (param f32) (result f32))) (type (;7;) (func (param f32 f32))) - (type (;8;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;9;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) - (type (;10;) (func (param f32 f32 f32 f32 f32 f32 f32 f32))) - (type (;11;) (func (result i32))) - (type (;12;) (func (result f32))) - (type (;13;) (func (param i32) (result i32))) + (type (;8;) (func (param i64) (result f32))) + (type (;9;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32))) + (type (;10;) (func (param i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32))) + (type (;11;) (func (param f32 f32 f32 f32 f32 f32 f32 f32))) + (type (;12;) (func (result i32))) + (type (;13;) (func (result f32))) (type (;14;) (func (param f32 f32 f32 f32 i32))) - (type (;15;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) - (type (;16;) (func (param f32 i32 i32))) - (type (;17;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 i32 i32 i32))) - (type (;18;) (func (param i32 i32 i32) (result i32))) - (type (;19;) (func (param i32 i32))) - (type (;20;) (func (param i32 f32))) - (type (;21;) (func (param i32) (result f32))) - (type (;22;) (func (param i32 f32 f32 i32) (result f32))) - (type (;23;) (func (param i32 i32) (result i32))) - (type (;24;) (func (param i32))) + (type (;15;) (func (param i32) (result i32))) + (type (;16;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) + (type (;17;) (func (param f32 i32 i32))) + (type (;18;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 f32 i32 i32 i32))) + (type (;19;) (func (param i32))) + (type (;20;) (func (param i32 i32 i32) (result i32))) + (type (;21;) (func (param i32 i32))) + (type (;22;) (func (param i32 f32))) + (type (;23;) (func (param i32 f32 f32 i32) (result f32))) + (type (;24;) (func (param i32 i32) (result i32))) (type (;25;) (func (param i32 i32 i32))) (type (;26;) (func)) - (import "miden:stdlib/intrinsics_felt" "from_u64_unchecked" (func $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked (;0;) (type 0))) - (import "miden:stdlib/intrinsics_felt" "add" (func $miden_stdlib_sys::intrinsics::felt::extern_add (;1;) (type 1))) - (import "miden:stdlib/intrinsics_felt" "as_u64" (func $miden_stdlib_sys::intrinsics::felt::extern_as_u64 (;2;) (type 2))) - (import "miden:stdlib/intrinsics_felt" "gt" (func $miden_stdlib_sys::intrinsics::felt::extern_gt (;3;) (type 3))) - (import "miden:stdlib/intrinsics_felt" "lt" (func $miden_stdlib_sys::intrinsics::felt::extern_lt (;4;) (type 3))) - (import "miden:stdlib/intrinsics_felt" "le" (func $miden_stdlib_sys::intrinsics::felt::extern_le (;5;) (type 3))) - (import "miden:stdlib/intrinsics_felt" "ge" (func $miden_stdlib_sys::intrinsics::felt::extern_ge (;6;) (type 3))) - (import "miden:stdlib/intrinsics_felt" "eq" (func $miden_stdlib_sys::intrinsics::felt::extern_eq (;7;) (type 3))) - (import "miden:stdlib/intrinsics_felt" "is_odd" (func $miden_stdlib_sys::intrinsics::felt::extern_is_odd (;8;) (type 4))) - (import "miden:stdlib/intrinsics_felt" "assertz" (func $miden_stdlib_sys::intrinsics::felt::extern_assertz (;9;) (type 5))) - (import "miden:stdlib/intrinsics_felt" "assert" (func $miden_stdlib_sys::intrinsics::felt::extern_assert (;10;) (type 5))) - (import "miden:stdlib/intrinsics_felt" "inv" (func $miden_stdlib_sys::intrinsics::felt::extern_inv (;11;) (type 6))) - (import "miden:stdlib/intrinsics_felt" "exp" (func $miden_stdlib_sys::intrinsics::felt::extern_exp (;12;) (type 1))) - (import "miden:stdlib/intrinsics_felt" "sub" (func $miden_stdlib_sys::intrinsics::felt::extern_sub (;13;) (type 1))) - (import "miden:stdlib/intrinsics_felt" "pow2" (func $miden_stdlib_sys::intrinsics::felt::extern_pow2 (;14;) (type 6))) - (import "miden:stdlib/intrinsics_felt" "mul" (func $miden_stdlib_sys::intrinsics::felt::extern_mul (;15;) (type 1))) - (import "miden:stdlib/intrinsics_felt" "div" (func $miden_stdlib_sys::intrinsics::felt::extern_div (;16;) (type 1))) - (import "miden:stdlib/intrinsics_felt" "assert_eq" (func $miden_stdlib_sys::intrinsics::felt::extern_assert_eq (;17;) (type 7))) - (import "miden:stdlib/intrinsics_felt" "neg" (func $miden_stdlib_sys::intrinsics::felt::extern_neg (;18;) (type 6))) - (import "std::crypto::hashes::blake3" "hash_1to1<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1 (;19;) (type 8))) - (import "std::crypto::hashes::blake3" "hash_2to1<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_2to1 (;20;) (type 9))) - (import "std::crypto::dsa::rpo_falcon512" "rpo_falcon512_verify<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_stdlib_sys::stdlib::crypto::dsa::extern_rpo_falcon512_verify (;21;) (type 10))) - (import "intrinsics::mem" "heap_base" (func $miden_sdk_alloc::heap_base (;22;) (type 11))) - (import "miden::account" "get_id<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_account_get_id (;23;) (type 12))) - (import "miden::note" "get_inputs<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_note_get_inputs (;24;) (type 13))) - (import "miden::account" "add_asset<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_account_add_asset (;25;) (type 14))) - (import "miden::account" "remove_asset<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_account_remove_asset (;26;) (type 14))) - (import "miden::tx" "create_note<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_tx_create_note (;27;) (type 15))) - (import "std::mem" "pipe_words_to_memory<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_stdlib_sys::stdlib::mem::extern_pipe_words_to_memory (;28;) (type 16))) - (import "std::mem" "pipe_double_words_to_memory<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_stdlib_sys::stdlib::mem::extern_pipe_double_words_to_memory (;29;) (type 17))) - (func $core::alloc::global::GlobalAlloc::alloc_zeroed (;30;) (type 18) (param i32 i32 i32) (result i32) + (type (;27;) (func (param i32 i32 i32 i32 i32))) + (type (;28;) (func (param i32 i32 i32 i32))) + (import "miden:core-import/intrinsics-felt@1.0.0" "from-u32" (func $miden_stdlib_sys::intrinsics::felt::extern_from_u32 (;0;) (type 0))) + (import "miden:core-import/intrinsics-felt@1.0.0" "add" (func $miden_stdlib_sys::intrinsics::felt::extern_add (;1;) (type 1))) + (import "miden:core-import/intrinsics-felt@1.0.0" "as_u64" (func $miden_stdlib_sys::intrinsics::felt::extern_as_u64 (;2;) (type 2))) + (import "miden:core-import/intrinsics-felt@1.0.0" "gt" (func $miden_stdlib_sys::intrinsics::felt::extern_gt (;3;) (type 3))) + (import "miden:core-import/intrinsics-felt@1.0.0" "lt" (func $miden_stdlib_sys::intrinsics::felt::extern_lt (;4;) (type 3))) + (import "miden:core-import/intrinsics-felt@1.0.0" "le" (func $miden_stdlib_sys::intrinsics::felt::extern_le (;5;) (type 3))) + (import "miden:core-import/intrinsics-felt@1.0.0" "ge" (func $miden_stdlib_sys::intrinsics::felt::extern_ge (;6;) (type 3))) + (import "miden:core-import/intrinsics-felt@1.0.0" "eq" (func $miden_stdlib_sys::intrinsics::felt::extern_eq (;7;) (type 3))) + (import "miden:core-import/intrinsics-felt@1.0.0" "is-odd" (func $miden_stdlib_sys::intrinsics::felt::extern_is_odd (;8;) (type 4))) + (import "miden:core-import/intrinsics-felt@1.0.0" "assertz" (func $miden_stdlib_sys::intrinsics::felt::extern_assertz (;9;) (type 5))) + (import "miden:core-import/intrinsics-felt@1.0.0" "assert" (func $miden_stdlib_sys::intrinsics::felt::extern_assert (;10;) (type 5))) + (import "miden:core-import/intrinsics-felt@1.0.0" "inv" (func $miden_stdlib_sys::intrinsics::felt::extern_inv (;11;) (type 6))) + (import "miden:core-import/intrinsics-felt@1.0.0" "exp" (func $miden_stdlib_sys::intrinsics::felt::extern_exp (;12;) (type 1))) + (import "miden:core-import/intrinsics-felt@1.0.0" "sub" (func $miden_stdlib_sys::intrinsics::felt::extern_sub (;13;) (type 1))) + (import "miden:core-import/intrinsics-felt@1.0.0" "pow2" (func $miden_stdlib_sys::intrinsics::felt::extern_pow2 (;14;) (type 6))) + (import "miden:core-import/intrinsics-felt@1.0.0" "mul" (func $miden_stdlib_sys::intrinsics::felt::extern_mul (;15;) (type 1))) + (import "miden:core-import/intrinsics-felt@1.0.0" "div" (func $miden_stdlib_sys::intrinsics::felt::extern_div (;16;) (type 1))) + (import "miden:core-import/intrinsics-felt@1.0.0" "assert-eq" (func $miden_stdlib_sys::intrinsics::felt::extern_assert_eq (;17;) (type 7))) + (import "miden:core-import/intrinsics-felt@1.0.0" "from-u64-unchecked" (func $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked (;18;) (type 8))) + (import "miden:core-import/intrinsics-felt@1.0.0" "neg" (func $miden_stdlib_sys::intrinsics::felt::extern_neg (;19;) (type 6))) + (import "miden:core-import/stdlib-crypto-hashes-blake3@1.0.0" "hash-one-to-one" (func $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_1to1 (;20;) (type 9))) + (import "miden:core-import/stdlib-crypto-hashes-blake3@1.0.0" "hash-two-to-one" (func $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_2to1 (;21;) (type 10))) + (import "miden:core-import/stdlib-crypto-dsa-rpo-falcon@1.0.0" "rpo-falcon512-verify" (func $miden_stdlib_sys::stdlib::crypto::dsa::extern_rpo_falcon512_verify (;22;) (type 11))) + (import "miden:core-import/intrinsics-mem@1.0.0" "heap-base" (func $miden_sdk_alloc::heap_base (;23;) (type 12))) + (import "miden:core-import/account@1.0.0" "get-id" (func $miden_base_sys::bindings::account::extern_account_get_id (;24;) (type 13))) + (import "miden:core-import/account@1.0.0" "add-asset" (func $miden_base_sys::bindings::account::extern_account_add_asset (;25;) (type 14))) + (import "miden:core-import/account@1.0.0" "remove-asset" (func $miden_base_sys::bindings::account::extern_account_remove_asset (;26;) (type 14))) + (import "miden:core-import/note@1.0.0" "get-inputs" (func $miden_base_sys::bindings::note::extern_note_get_inputs (;27;) (type 15))) + (import "miden:core-import/tx@1.0.0" "create-note" (func $miden_base_sys::bindings::tx::extern_tx_create_note (;28;) (type 16))) + (import "miden:core-import/stdlib-mem@1.0.0" "pipe-words-to-memory" (func $miden_stdlib_sys::stdlib::mem::extern_pipe_words_to_memory (;29;) (type 17))) + (import "miden:core-import/stdlib-mem@1.0.0" "pipe-double-words-to-memory" (func $miden_stdlib_sys::stdlib::mem::extern_pipe_double_words_to_memory (;30;) (type 18))) + (table (;0;) 1 1 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "get_wallet_magic_number" (func $get_wallet_magic_number.command_export)) + (export "test_add_asset" (func $test_add_asset.command_export)) + (export "test_felt_ops_smoke" (func $test_felt_ops_smoke.command_export)) + (export "note_script" (func $note_script.command_export)) + (export "test_blake3_hash_1to1" (func $test_blake3_hash_1to1.command_export)) + (export "test_blake3_hash_2to1" (func $test_blake3_hash_2to1.command_export)) + (export "test_rpo_falcon512_verify" (func $test_rpo_falcon512_verify.command_export)) + (export "test_pipe_words_to_memory" (func $test_pipe_words_to_memory.command_export)) + (export "test_pipe_double_words_to_memory" (func $test_pipe_double_words_to_memory.command_export)) + (export "test_remove_asset" (func $test_remove_asset.command_export)) + (export "test_create_note" (func $test_create_note.command_export)) + (func $< as core::ops::drop::Drop>::drop::DropGuard as core::ops::drop::Drop>::drop (;31;) (type 19) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + local.get 0 + i32.load + local.tee 0 + i32.load + i32.store offset=12 + local.get 1 + local.get 0 + i32.load offset=8 + i32.store offset=8 + local.get 1 + i32.const 8 + i32.add + call $ as core::ops::drop::Drop>::drop + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $ as core::ops::drop::Drop>::drop (;32;) (type 19) (param i32) + local.get 0 + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::deallocate + ) + (func $core::alloc::global::GlobalAlloc::alloc_zeroed (;33;) (type 20) (param i32 i32 i32) (result i32) block ;; label = @1 local.get 0 local.get 1 @@ -65,6 +115,9 @@ local.tee 1 i32.eqz br_if 0 (;@1;) + local.get 2 + i32.eqz + br_if 0 (;@1;) local.get 1 i32.const 0 local.get 2 @@ -72,16 +125,35 @@ end local.get 1 ) - (func $get_wallet_magic_number (;31;) (type 12) (result f32) + (func $ as core::ops::drop::Drop>::drop (;34;) (type 19) (param i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + local.get 0 + i32.store offset=12 + local.get 1 + i32.const 12 + i32.add + call $< as core::ops::drop::Drop>::drop::DropGuard as core::ops::drop::Drop>::drop + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $get_wallet_magic_number (;35;) (type 13) (result f32) (local f32) - call $miden_base_sys::bindings::tx::get_id + call $miden_base_sys::bindings::account::get_id local.set 0 - i64.const 42 - call $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked + i32.const 42 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 local.get 0 call $miden_stdlib_sys::intrinsics::felt::extern_add ) - (func $test_add_asset (;32;) (type 12) (result f32) + (func $test_add_asset (;36;) (type 13) (result f32) (local i32 i32 f32 f32 f32) global.get $__stack_pointer local.tee 0 @@ -91,18 +163,18 @@ i32.and local.tee 1 global.set $__stack_pointer - i64.const 1 - call $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked + i32.const 1 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 local.set 2 - i64.const 2 - call $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked + i32.const 2 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 local.set 3 - i64.const 3 - call $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked + i32.const 3 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 local.set 4 local.get 1 - i64.const 4 - call $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked + i32.const 4 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 f32.store offset=12 local.get 1 local.get 4 @@ -117,7 +189,7 @@ i32.const 32 i32.add local.get 1 - call $miden_base_sys::bindings::tx::add_asset + call $miden_base_sys::bindings::account::add_asset local.get 1 f32.load offset=32 local.set 2 @@ -125,7 +197,7 @@ global.set $__stack_pointer local.get 2 ) - (func $test_felt_ops_smoke (;33;) (type 1) (param f32 f32) (result f32) + (func $test_felt_ops_smoke (;37;) (type 1) (param f32 f32) (result f32) (local i64) local.get 0 call $miden_stdlib_sys::intrinsics::felt::extern_as_u64 @@ -214,8 +286,8 @@ local.get 0 call $miden_stdlib_sys::intrinsics::felt::extern_neg ) - (func $note_script (;34;) (type 12) (result f32) - (local i32 f32 i32 i32) + (func $note_script (;38;) (type 13) (result f32) + (local i32 f32 i32 i32 i32 i32) global.get $__stack_pointer i32.const 16 i32.sub @@ -225,45 +297,61 @@ call $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked local.set 1 local.get 0 - i32.const 4 - i32.add - call $miden_base_sys::bindings::tx::get_inputs + call $miden_base_sys::bindings::note::get_inputs local.get 0 - i32.load offset=12 - i32.const 2 - i32.shl + i32.load local.set 2 local.get 0 + local.get 0 + i32.load offset=4 + local.tee 3 + local.get 0 i32.load offset=8 - local.set 3 - loop (result f32) ;; label = @1 - block ;; label = @2 - local.get 2 - br_if 0 (;@2;) - local.get 0 - i32.const 16 + i32.const 2 + i32.shl + local.tee 4 + i32.add + local.tee 5 + i32.store offset=12 + local.get 0 + local.get 2 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store + block ;; label = @1 + loop ;; label = @2 + local.get 4 + i32.eqz + br_if 1 (;@1;) + local.get 4 + i32.const -4 i32.add - global.set $__stack_pointer + local.set 4 local.get 1 - return + local.get 3 + f32.load + call $miden_stdlib_sys::intrinsics::felt::extern_add + local.set 1 + local.get 3 + i32.const 4 + i32.add + local.set 3 + br 0 (;@2;) end - local.get 2 - i32.const -4 - i32.add - local.set 2 - local.get 1 - local.get 3 - f32.load - call $miden_stdlib_sys::intrinsics::felt::extern_add - local.set 1 - local.get 3 - i32.const 4 - i32.add - local.set 3 - br 0 (;@1;) end + local.get 0 + local.get 5 + i32.store offset=4 + local.get 0 + call $ as core::ops::drop::Drop>::drop + local.get 0 + i32.const 16 + i32.add + global.set $__stack_pointer + local.get 1 ) - (func $test_blake3_hash_1to1 (;35;) (type 19) (param i32 i32) + (func $test_blake3_hash_1to1 (;39;) (type 21) (param i32 i32) (local i32 i32) global.get $__stack_pointer local.tee 2 @@ -316,7 +404,7 @@ local.get 2 global.set $__stack_pointer ) - (func $test_blake3_hash_2to1 (;36;) (type 19) (param i32 i32) + (func $test_blake3_hash_2to1 (;40;) (type 21) (param i32 i32) local.get 1 i32.load align=1 local.get 1 @@ -352,7 +440,7 @@ local.get 0 call $miden_stdlib_sys::stdlib::crypto::hashes::extern_blake3_hash_2to1 ) - (func $test_rpo_falcon512_verify (;37;) (type 19) (param i32 i32) + (func $test_rpo_falcon512_verify (;41;) (type 21) (param i32 i32) local.get 0 f32.load local.get 0 @@ -371,17 +459,17 @@ f32.load offset=12 call $miden_stdlib_sys::stdlib::crypto::dsa::extern_rpo_falcon512_verify ) - (func $test_pipe_words_to_memory (;38;) (type 20) (param i32 f32) + (func $test_pipe_words_to_memory (;42;) (type 22) (param i32 f32) local.get 0 local.get 1 call $miden_stdlib_sys::stdlib::mem::pipe_words_to_memory ) - (func $test_pipe_double_words_to_memory (;39;) (type 20) (param i32 f32) + (func $test_pipe_double_words_to_memory (;43;) (type 22) (param i32 f32) local.get 0 local.get 1 call $miden_stdlib_sys::stdlib::mem::pipe_double_words_to_memory ) - (func $test_remove_asset (;40;) (type 21) (param i32) (result f32) + (func $test_remove_asset (;44;) (type 0) (param i32) (result f32) (local i32 i32 f32) global.get $__stack_pointer local.tee 1 @@ -393,7 +481,7 @@ global.set $__stack_pointer local.get 2 local.get 0 - call $miden_base_sys::bindings::tx::remove_asset + call $miden_base_sys::bindings::account::remove_asset local.get 2 f32.load local.set 3 @@ -401,26 +489,27 @@ global.set $__stack_pointer local.get 3 ) - (func $test_create_note (;41;) (type 22) (param i32 f32 f32 i32) (result f32) + (func $test_create_note (;45;) (type 23) (param i32 f32 f32 i32) (result f32) local.get 0 local.get 1 local.get 2 local.get 3 call $miden_base_sys::bindings::tx::create_note ) - (func $__rust_alloc (;42;) (type 23) (param i32 i32) (result i32) - i32.const 1048576 + (func $__rustc::__rust_alloc (;46;) (type 24) (param i32 i32) (result i32) + i32.const 1048704 local.get 1 local.get 0 call $::alloc ) - (func $__rust_alloc_zeroed (;43;) (type 23) (param i32 i32) (result i32) - i32.const 1048576 + (func $__rustc::__rust_dealloc (;47;) (type 25) (param i32 i32 i32)) + (func $__rustc::__rust_alloc_zeroed (;48;) (type 24) (param i32 i32) (result i32) + i32.const 1048704 local.get 1 local.get 0 call $core::alloc::global::GlobalAlloc::alloc_zeroed ) - (func $::alloc (;44;) (type 18) (param i32 i32 i32) (result i32) + (func $::alloc (;49;) (type 20) (param i32 i32 i32) (result i32) (local i32 i32) block ;; label = @1 local.get 1 @@ -429,21 +518,25 @@ i32.const 32 i32.gt_u select - local.tee 1 - i32.popcnt - i32.const 1 - i32.ne + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and br_if 0 (;@1;) + local.get 2 i32.const -2147483648 local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 i32.sub - local.get 2 - i32.lt_u + i32.gt_u br_if 0 (;@1;) i32.const 0 local.set 3 - local.get 1 local.get 2 + local.get 1 i32.add i32.const -1 i32.add @@ -488,10 +581,34 @@ end unreachable ) - (func $miden_base_sys::bindings::tx::get_id (;45;) (type 12) (result f32) - call $miden_base_sys::bindings::tx::externs::extern_account_get_id + (func $miden_base_sys::bindings::account::get_id (;50;) (type 13) (result f32) + call $miden_base_sys::bindings::account::extern_account_get_id ) - (func $miden_base_sys::bindings::tx::get_inputs (;46;) (type 24) (param i32) + (func $miden_base_sys::bindings::account::add_asset (;51;) (type 21) (param i32 i32) + local.get 1 + f32.load + local.get 1 + f32.load offset=4 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=12 + local.get 0 + call $miden_base_sys::bindings::account::extern_account_add_asset + ) + (func $miden_base_sys::bindings::account::remove_asset (;52;) (type 21) (param i32 i32) + local.get 1 + f32.load + local.get 1 + f32.load offset=4 + local.get 1 + f32.load offset=8 + local.get 1 + f32.load offset=12 + local.get 0 + call $miden_base_sys::bindings::account::extern_account_remove_asset + ) + (func $miden_base_sys::bindings::note::get_inputs (;53;) (type 19) (param i32) (local i32 i32 i32) global.get $__stack_pointer i32.const 16 @@ -503,7 +620,9 @@ i32.add i32.const 256 i32.const 0 - call $alloc::raw_vec::RawVec::try_allocate_in + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::try_allocate_in local.get 1 i32.load offset=8 local.set 2 @@ -516,6 +635,7 @@ local.get 2 local.get 1 i32.load offset=12 + i32.const 1048616 call $alloc::raw_vec::handle_error unreachable end @@ -525,7 +645,7 @@ local.tee 3 i32.const 4 i32.shr_u - call $miden_base_sys::bindings::tx::externs::extern_note_get_inputs + call $miden_base_sys::bindings::note::extern_note_get_inputs i32.store offset=8 local.get 0 local.get 3 @@ -538,31 +658,7 @@ i32.add global.set $__stack_pointer ) - (func $miden_base_sys::bindings::tx::add_asset (;47;) (type 19) (param i32 i32) - local.get 1 - f32.load - local.get 1 - f32.load offset=4 - local.get 1 - f32.load offset=8 - local.get 1 - f32.load offset=12 - local.get 0 - call $miden_base_sys::bindings::tx::externs::extern_account_add_asset - ) - (func $miden_base_sys::bindings::tx::remove_asset (;48;) (type 19) (param i32 i32) - local.get 1 - f32.load - local.get 1 - f32.load offset=4 - local.get 1 - f32.load offset=8 - local.get 1 - f32.load offset=12 - local.get 0 - call $miden_base_sys::bindings::tx::externs::extern_account_remove_asset - ) - (func $miden_base_sys::bindings::tx::create_note (;49;) (type 22) (param i32 f32 f32 i32) (result f32) + (func $miden_base_sys::bindings::tx::create_note (;54;) (type 23) (param i32 f32 f32 i32) (result f32) local.get 0 f32.load local.get 0 @@ -581,128 +677,57 @@ f32.load offset=8 local.get 3 f32.load offset=12 - call $miden_base_sys::bindings::tx::externs::extern_tx_create_note + call $miden_base_sys::bindings::tx::extern_tx_create_note ) - (func $alloc::vec::Vec::with_capacity (;50;) (type 19) (param i32 i32) - (local i32 i32) + (func $alloc::vec::Vec::with_capacity (;55;) (type 25) (param i32 i32 i32) + (local i32) global.get $__stack_pointer i32.const 16 i32.sub - local.tee 2 + local.tee 3 global.set $__stack_pointer - local.get 2 + local.get 3 i32.const 4 i32.add local.get 1 i32.const 0 - call $alloc::raw_vec::RawVec::try_allocate_in - local.get 2 + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::try_allocate_in + local.get 3 i32.load offset=8 local.set 1 block ;; label = @1 - local.get 2 + local.get 3 i32.load offset=4 i32.const 1 i32.ne br_if 0 (;@1;) local.get 1 - local.get 2 + local.get 3 i32.load offset=12 + local.get 2 call $alloc::raw_vec::handle_error unreachable end - local.get 2 + local.get 3 i32.load offset=12 - local.set 3 + local.set 2 local.get 0 i32.const 0 i32.store offset=8 local.get 0 - local.get 3 + local.get 2 i32.store offset=4 local.get 0 local.get 1 i32.store - local.get 2 + local.get 3 i32.const 16 i32.add global.set $__stack_pointer ) - (func $alloc::raw_vec::RawVec::try_allocate_in (;51;) (type 25) (param i32 i32 i32) - (local i32) - block ;; label = @1 - block ;; label = @2 - local.get 1 - br_if 0 (;@2;) - local.get 0 - i64.const 17179869184 - i64.store offset=4 align=4 - i32.const 0 - local.set 1 - br 1 (;@1;) - end - block ;; label = @2 - block ;; label = @3 - local.get 1 - i32.const 536870912 - i32.lt_u - br_if 0 (;@3;) - local.get 0 - i32.const 0 - i32.store offset=4 - br 1 (;@2;) - end - local.get 1 - i32.const 2 - i32.shl - local.set 3 - block ;; label = @3 - block ;; label = @4 - local.get 2 - br_if 0 (;@4;) - i32.const 0 - i32.load8_u offset=1048580 - drop - local.get 3 - i32.const 4 - call $__rust_alloc - local.set 2 - br 1 (;@3;) - end - local.get 3 - i32.const 4 - call $__rust_alloc_zeroed - local.set 2 - end - block ;; label = @3 - local.get 2 - i32.eqz - br_if 0 (;@3;) - local.get 0 - local.get 2 - i32.store offset=8 - local.get 0 - local.get 1 - i32.store offset=4 - i32.const 0 - local.set 1 - br 2 (;@1;) - end - local.get 0 - local.get 3 - i32.store offset=8 - local.get 0 - i32.const 4 - i32.store offset=4 - end - i32.const 1 - local.set 1 - end - local.get 0 - local.get 1 - i32.store - ) - (func $miden_stdlib_sys::stdlib::mem::pipe_words_to_memory (;52;) (type 20) (param i32 f32) + (func $miden_stdlib_sys::stdlib::mem::pipe_words_to_memory (;56;) (type 22) (param i32 f32) (local i32 i32) global.get $__stack_pointer local.tee 2 @@ -720,6 +745,7 @@ i32.wrap_i64 i32.const 2 i32.shl + i32.const 1048672 call $alloc::vec::Vec::with_capacity local.get 1 local.get 3 @@ -767,7 +793,7 @@ local.get 2 global.set $__stack_pointer ) - (func $miden_stdlib_sys::stdlib::mem::pipe_double_words_to_memory (;53;) (type 20) (param i32 f32) + (func $miden_stdlib_sys::stdlib::mem::pipe_double_words_to_memory (;57;) (type 22) (param i32 f32) (local i32 i32 i32 i32) global.get $__stack_pointer local.tee 2 @@ -786,12 +812,13 @@ local.tee 4 i32.const 2 i32.shl + i32.const 1048688 call $alloc::vec::Vec::with_capacity local.get 3 i32.load offset=24 local.set 5 - i64.const 0 - call $miden_stdlib_sys::intrinsics::felt::extern_from_u64_unchecked + i32.const 0 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 local.tee 1 local.get 1 local.get 1 @@ -861,68 +888,324 @@ local.get 2 global.set $__stack_pointer ) - (func $dummy (;54;) (type 26)) - (func $__wasm_call_dtors (;55;) (type 26) + (func $dummy (;58;) (type 26)) + (func $__wasm_call_dtors (;59;) (type 26) call $dummy call $dummy ) - (func $alloc::raw_vec::handle_error (;56;) (type 19) (param i32 i32) + (func $alloc::raw_vec::RawVecInner::deallocate (;60;) (type 25) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::try_allocate_in (;61;) (type 27) (param i32 i32 i32 i32 i32) + (local i32 i64) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 3 + local.get 4 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 3 + i32.sub + i32.and + i64.extend_i32_u + local.get 1 + i64.extend_i32_u + i64.mul + local.tee 6 + i64.const 32 + i64.shr_u + i32.wrap_i64 + br_if 0 (;@3;) + local.get 6 + i32.wrap_i64 + local.tee 4 + i32.const -2147483648 + local.get 3 + i32.sub + i32.le_u + br_if 1 (;@2;) + end + local.get 0 + i32.const 0 + i32.store offset=4 + i32.const 1 + local.set 3 + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 + br_if 0 (;@2;) + local.get 0 + local.get 3 + i32.store offset=8 + i32.const 0 + local.set 3 + local.get 0 + i32.const 0 + i32.store offset=4 + br 1 (;@1;) + end + block ;; label = @2 + block ;; label = @3 + local.get 2 + br_if 0 (;@3;) + local.get 5 + i32.const 8 + i32.add + local.get 3 + local.get 4 + call $::allocate + local.get 5 + i32.load offset=8 + local.set 2 + br 1 (;@2;) + end + local.get 5 + local.get 3 + local.get 4 + i32.const 1 + call $alloc::alloc::Global::alloc_impl + local.get 5 + i32.load + local.set 2 + end + block ;; label = @2 + local.get 2 + i32.eqz + br_if 0 (;@2;) + local.get 0 + local.get 2 + i32.store offset=8 + local.get 0 + local.get 1 + i32.store offset=4 + i32.const 0 + local.set 3 + br 1 (;@1;) + end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + i32.const 1 + local.set 3 + end + local.get 0 + local.get 3 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::allocate (;62;) (type 25) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 2 + i32.const 0 + call $alloc::alloc::Global::alloc_impl + local.get 3 + i32.load offset=12 + local.set 2 + local.get 0 + local.get 3 + i32.load offset=8 + i32.store + local.get 0 + local.get 2 + i32.store offset=4 + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::alloc::Global::alloc_impl (;63;) (type 28) (param i32 i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + i32.const 0 + i32.load8_u offset=1048708 + drop + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + local.get 2 + local.get 1 + call $__rustc::__rust_alloc + local.set 1 + br 1 (;@1;) + end + local.get 2 + local.get 1 + call $__rustc::__rust_alloc_zeroed + local.set 1 + end + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.store + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;64;) (type 28) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;65;) (type 25) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) + (func $alloc::raw_vec::handle_error (;66;) (type 25) (param i32 i32 i32) unreachable ) - (func $get_wallet_magic_number.command_export (;57;) (type 12) (result f32) + (func $core::ptr::alignment::Alignment::max (;67;) (type 24) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (func $get_wallet_magic_number.command_export (;68;) (type 13) (result f32) call $get_wallet_magic_number call $__wasm_call_dtors ) - (func $test_add_asset.command_export (;58;) (type 12) (result f32) + (func $test_add_asset.command_export (;69;) (type 13) (result f32) call $test_add_asset call $__wasm_call_dtors ) - (func $test_felt_ops_smoke.command_export (;59;) (type 1) (param f32 f32) (result f32) + (func $test_felt_ops_smoke.command_export (;70;) (type 1) (param f32 f32) (result f32) local.get 0 local.get 1 call $test_felt_ops_smoke call $__wasm_call_dtors ) - (func $note_script.command_export (;60;) (type 12) (result f32) + (func $note_script.command_export (;71;) (type 13) (result f32) call $note_script call $__wasm_call_dtors ) - (func $test_blake3_hash_1to1.command_export (;61;) (type 19) (param i32 i32) + (func $test_blake3_hash_1to1.command_export (;72;) (type 21) (param i32 i32) local.get 0 local.get 1 call $test_blake3_hash_1to1 call $__wasm_call_dtors ) - (func $test_blake3_hash_2to1.command_export (;62;) (type 19) (param i32 i32) + (func $test_blake3_hash_2to1.command_export (;73;) (type 21) (param i32 i32) local.get 0 local.get 1 call $test_blake3_hash_2to1 call $__wasm_call_dtors ) - (func $test_rpo_falcon512_verify.command_export (;63;) (type 19) (param i32 i32) + (func $test_rpo_falcon512_verify.command_export (;74;) (type 21) (param i32 i32) local.get 0 local.get 1 call $test_rpo_falcon512_verify call $__wasm_call_dtors ) - (func $test_pipe_words_to_memory.command_export (;64;) (type 20) (param i32 f32) + (func $test_pipe_words_to_memory.command_export (;75;) (type 22) (param i32 f32) local.get 0 local.get 1 call $test_pipe_words_to_memory call $__wasm_call_dtors ) - (func $test_pipe_double_words_to_memory.command_export (;65;) (type 20) (param i32 f32) + (func $test_pipe_double_words_to_memory.command_export (;76;) (type 22) (param i32 f32) local.get 0 local.get 1 call $test_pipe_double_words_to_memory call $__wasm_call_dtors ) - (func $test_remove_asset.command_export (;66;) (type 21) (param i32) (result f32) + (func $test_remove_asset.command_export (;77;) (type 0) (param i32) (result f32) local.get 0 call $test_remove_asset call $__wasm_call_dtors ) - (func $test_create_note.command_export (;67;) (type 22) (param i32 f32 f32 i32) (result f32) + (func $test_create_note.command_export (;78;) (type 23) (param i32 f32 f32 i32) (result f32) local.get 0 local.get 1 local.get 2 @@ -930,19 +1213,5 @@ call $test_create_note call $__wasm_call_dtors ) - (table (;0;) 1 1 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "get_wallet_magic_number" (func $get_wallet_magic_number.command_export)) - (export "test_add_asset" (func $test_add_asset.command_export)) - (export "test_felt_ops_smoke" (func $test_felt_ops_smoke.command_export)) - (export "note_script" (func $note_script.command_export)) - (export "test_blake3_hash_1to1" (func $test_blake3_hash_1to1.command_export)) - (export "test_blake3_hash_2to1" (func $test_blake3_hash_2to1.command_export)) - (export "test_rpo_falcon512_verify" (func $test_rpo_falcon512_verify.command_export)) - (export "test_pipe_words_to_memory" (func $test_pipe_words_to_memory.command_export)) - (export "test_pipe_double_words_to_memory" (func $test_pipe_double_words_to_memory.command_export)) - (export "test_remove_asset" (func $test_remove_asset.command_export)) - (export "test_create_note" (func $test_create_note.command_export)) -) \ No newline at end of file + (data $.rodata (;0;) (i32.const 1048576) "../../sdk/base-sys/src/bindings/note.rs\00\00\00\10\00'\00\00\00\11\00\00\00!\00\00\00../../sdk/stdlib-sys/src/stdlib/mem.rs\00\008\00\10\00&\00\00\00J\00\00\00\22\00\00\008\00\10\00&\00\00\00a\00\00\00\1e\00\00\00") +) diff --git a/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.hir b/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.hir deleted file mode 100644 index cd5e29cac..000000000 --- a/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.hir +++ /dev/null @@ -1,217 +0,0 @@ -(component - ;; Component Imports - (lower ((digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi canon) (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)))) (#miden::account #remove_asset) - - ;; Modules - (module #rust_sdk_basic_wallet - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - - ;; Functions - (func (export #receive_asset) (param i32) - (block 0 (param v0 i32) - (let (v1 i32) (const.i32 0)) - (let (v2 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v3 i32) (const.i32 32)) - (let (v4 i32) (sub.wrapping v2 v3)) - (let (v5 i32) (const.i32 -32)) - (let (v6 i32) (band v4 v5)) - (let (v7 (ptr i32)) (global.symbol #__stack_pointer)) - (store v7 v6) - (call #miden_base_sys::bindings::tx::add_asset v6 v0) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v2) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #send_asset) - (param i32) (param felt) (param felt) (param i32) - (block 0 - (param v0 i32) - (param v1 felt) - (param v2 felt) - (param v3 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 32)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 i32) (const.i32 -32)) - (let (v9 i32) (band v7 v8)) - (let (v10 (ptr i32)) (global.symbol #__stack_pointer)) - (store v10 v9) - (call #miden_base_sys::bindings::tx::remove_asset v9 v0) - (let (v11 felt) (call #miden_base_sys::bindings::tx::create_note v9 v1 v2 v3)) - (let (v12 (ptr i32)) (global.symbol #__stack_pointer)) - (store v12 v5) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #miden_base_sys::bindings::tx::add_asset) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 u32) (bitcast v1)) - (let (v3 u32) (mod.unchecked v2 4)) - (assertz 250 v3) - (let (v4 (ptr felt)) (inttoptr v2)) - (let (v5 felt) (load v4)) - (let (v6 u32) (bitcast v1)) - (let (v7 u32) (add.checked v6 4)) - (let (v8 u32) (mod.unchecked v7 4)) - (assertz 250 v8) - (let (v9 (ptr felt)) (inttoptr v7)) - (let (v10 felt) (load v9)) - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (add.checked v11 8)) - (let (v13 u32) (mod.unchecked v12 4)) - (assertz 250 v13) - (let (v14 (ptr felt)) (inttoptr v12)) - (let (v15 felt) (load v14)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (add.checked v16 12)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr felt)) (inttoptr v17)) - (let (v20 felt) (load v19)) - (let [(v21 felt) (v22 felt) (v23 felt) (v24 felt)] (call (#miden::account #add_asset) v5 v10 v15 v20)) - (let (v25 u32) (bitcast v0)) - (let (v26 (ptr felt)) (inttoptr v25)) - (store v26 v21) - (let (v27 u32) (add.checked v25 4)) - (let (v28 (ptr felt)) (inttoptr v27)) - (store v28 v22) - (let (v29 u32) (add.checked v25 8)) - (let (v30 (ptr felt)) (inttoptr v29)) - (store v30 v23) - (let (v31 u32) (add.checked v25 12)) - (let (v32 (ptr felt)) (inttoptr v31)) - (store v32 v24) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #miden_base_sys::bindings::tx::remove_asset) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 u32) (bitcast v1)) - (let (v3 u32) (mod.unchecked v2 4)) - (assertz 250 v3) - (let (v4 (ptr felt)) (inttoptr v2)) - (let (v5 felt) (load v4)) - (let (v6 u32) (bitcast v1)) - (let (v7 u32) (add.checked v6 4)) - (let (v8 u32) (mod.unchecked v7 4)) - (assertz 250 v8) - (let (v9 (ptr felt)) (inttoptr v7)) - (let (v10 felt) (load v9)) - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (add.checked v11 8)) - (let (v13 u32) (mod.unchecked v12 4)) - (assertz 250 v13) - (let (v14 (ptr felt)) (inttoptr v12)) - (let (v15 felt) (load v14)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (add.checked v16 12)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr felt)) (inttoptr v17)) - (let (v20 felt) (load v19)) - (let [(v21 felt) (v22 felt) (v23 felt) (v24 felt)] (call (#miden::account #remove_asset) v5 v10 v15 v20)) - (let (v25 u32) (bitcast v0)) - (let (v26 (ptr felt)) (inttoptr v25)) - (store v26 v21) - (let (v27 u32) (add.checked v25 4)) - (let (v28 (ptr felt)) (inttoptr v27)) - (store v28 v22) - (let (v29 u32) (add.checked v25 8)) - (let (v30 (ptr felt)) (inttoptr v29)) - (store v30 v23) - (let (v31 u32) (add.checked v25 12)) - (let (v32 (ptr felt)) (inttoptr v31)) - (store v32 v24) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #miden_base_sys::bindings::tx::create_note) - (param i32) (param felt) (param felt) (param i32) (result felt) - (block 0 - (param v0 i32) - (param v1 felt) - (param v2 felt) - (param v3 i32) - (let (v5 u32) (bitcast v0)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr felt)) (inttoptr v5)) - (let (v8 felt) (load v7)) - (let (v9 u32) (bitcast v0)) - (let (v10 u32) (add.checked v9 4)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr felt)) (inttoptr v10)) - (let (v13 felt) (load v12)) - (let (v14 u32) (bitcast v0)) - (let (v15 u32) (add.checked v14 8)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr felt)) (inttoptr v15)) - (let (v18 felt) (load v17)) - (let (v19 u32) (bitcast v0)) - (let (v20 u32) (add.checked v19 12)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr felt)) (inttoptr v20)) - (let (v23 felt) (load v22)) - (let (v24 u32) (bitcast v3)) - (let (v25 u32) (mod.unchecked v24 4)) - (assertz 250 v25) - (let (v26 (ptr felt)) (inttoptr v24)) - (let (v27 felt) (load v26)) - (let (v28 u32) (bitcast v3)) - (let (v29 u32) (add.checked v28 4)) - (let (v30 u32) (mod.unchecked v29 4)) - (assertz 250 v30) - (let (v31 (ptr felt)) (inttoptr v29)) - (let (v32 felt) (load v31)) - (let (v33 u32) (bitcast v3)) - (let (v34 u32) (add.checked v33 8)) - (let (v35 u32) (mod.unchecked v34 4)) - (assertz 250 v35) - (let (v36 (ptr felt)) (inttoptr v34)) - (let (v37 felt) (load v36)) - (let (v38 u32) (bitcast v3)) - (let (v39 u32) (add.checked v38 12)) - (let (v40 u32) (mod.unchecked v39 4)) - (assertz 250 v40) - (let (v41 (ptr felt)) (inttoptr v39)) - (let (v42 felt) (load v41)) - (let (v43 felt) (call (#miden::tx #create_note) v8 v13 v18 v23 v1 v2 v27 v32 v37 v42)) - (br (block 1 v43))) - - (block 1 (param v4 felt) - (ret v4)) - ) - - ;; Imports - (func (import #miden::account #add_asset) - (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)) - (func (import #miden::account #remove_asset) - (param felt) (param felt) (param felt) (param felt) (result felt felt felt felt)) - (func (import #miden::tx #create_note) - (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (param felt) (result felt)) - ) - -) diff --git a/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.masm b/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.masm deleted file mode 100644 index 69e62dea0..000000000 --- a/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.masm +++ /dev/null @@ -1,802 +0,0 @@ -mod rust_sdk_basic_wallet - -use.miden:tx_kernel/account -use.miden:tx_kernel/tx - -export.receive_asset - mem_load.0x00000000 - push.32 - u32wrapping_sub - push.32 - dup.1 - swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw - dup.0 - swap.1 - swap.2 - swap.1 - exec."_ZN19miden_sdk_tx_kernel9add_asset17h6f4cff304c095ffcE" - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw -end - - -export.send_asset - mem_load.0x00000000 - push.32 - u32wrapping_sub - dup.0 - movup.2 - swap.4 - movdn.2 - swap.1 - swap.5 - swap.3 - swap.1 - exec."_ZN19miden_sdk_tx_kernel11create_note17h99477639e0ff4f18E" - push.32 - dup.3 - swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw - dup.2 - swap.1 - swap.2 - swap.1 - exec."_ZN19miden_sdk_tx_kernel12remove_asset17hf5f373d8386f7b96E" - movup.2 - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw - dropw -end - - -export."_ZN19miden_sdk_tx_kernel9add_asset17h6f4cff304c095ffcE" - mem_load.0x00000000 - push.32 - u32wrapping_sub - dup.0 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.3 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.24 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.4 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.16 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.5 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.8 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - movup.6 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - exec.miden:tx_kernel/account::add_asset - push.32 - dup.6 - swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw - dup.5 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - dup.8 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - push.8 - dup.6 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - push.8 - dup.9 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - push.16 - dup.6 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - push.16 - dup.9 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - push.24 - dup.6 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - push.24 - movup.9 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - dup.4 - add.24 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - dup.3 - add.16 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - dup.2 - add.8 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw -end - - -export."_ZN19miden_sdk_tx_kernel12remove_asset17hf5f373d8386f7b96E" - mem_load.0x00000000 - push.32 - u32wrapping_sub - dup.0 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.3 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.24 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.4 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.16 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.5 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.8 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - movup.6 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - exec.miden:tx_kernel/account::remove_asset - push.32 - dup.6 - swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw - dup.5 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - dup.8 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - push.8 - dup.6 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - push.8 - dup.9 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - push.16 - dup.6 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - push.16 - dup.9 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - push.24 - dup.6 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_dw - push.24 - movup.9 - swap.1 - u32wrapping_add - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_dw - dup.4 - add.24 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - dup.3 - add.16 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - dup.2 - add.8 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - swap.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_felt - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::store_sw -end - - -export."_ZN19miden_sdk_tx_kernel11create_note17h99477639e0ff4f18E" - dup.3 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.24 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.4 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.16 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.5 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.8 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - movup.6 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.4 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.24 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.5 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.16 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - dup.6 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - add.8 - u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - movup.7 - dup.0 - push.2147483648 - u32and - eq.2147483648 - assertz - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.intrinsics::mem::load_felt - movup.5 - swap.7 - swap.9 - movdn.5 - movup.4 - swap.6 - swap.8 - movdn.4 - exec.miden:tx_kernel/tx::create_note -end - - diff --git a/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.wat b/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.wat deleted file mode 100644 index 77d9de8bc..000000000 --- a/tests/integration/expected/rust_sdk_basic_wallet/rust_sdk_basic_wallet.wat +++ /dev/null @@ -1,100 +0,0 @@ -(module $rust_sdk_basic_wallet.wasm - (type (;0;) (func (param f32 f32 f32 f32 i32))) - (type (;1;) (func (param f32 f32 f32 f32 f32 f32 f32 f32 f32 f32) (result f32))) - (type (;2;) (func (param i32))) - (type (;3;) (func (param i32 f32 f32 i32))) - (type (;4;) (func (param i32 i32))) - (type (;5;) (func (param i32 f32 f32 i32) (result f32))) - (import "miden::account" "add_asset<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_account_add_asset (;0;) (type 0))) - (import "miden::account" "remove_asset<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_account_remove_asset (;1;) (type 0))) - (import "miden::tx" "create_note<0x0000000000000000000000000000000000000000000000000000000000000000>" (func $miden_base_sys::bindings::tx::externs::extern_tx_create_note (;2;) (type 1))) - (func $receive_asset (;3;) (type 2) (param i32) - (local i32 i32) - global.get $__stack_pointer - local.tee 1 - i32.const 32 - i32.sub - i32.const -32 - i32.and - local.tee 2 - global.set $__stack_pointer - local.get 2 - local.get 0 - call $miden_base_sys::bindings::tx::add_asset - local.get 1 - global.set $__stack_pointer - ) - (func $send_asset (;4;) (type 3) (param i32 f32 f32 i32) - (local i32 i32) - global.get $__stack_pointer - local.tee 4 - i32.const 32 - i32.sub - i32.const -32 - i32.and - local.tee 5 - global.set $__stack_pointer - local.get 5 - local.get 0 - call $miden_base_sys::bindings::tx::remove_asset - local.get 5 - local.get 1 - local.get 2 - local.get 3 - call $miden_base_sys::bindings::tx::create_note - drop - local.get 4 - global.set $__stack_pointer - ) - (func $miden_base_sys::bindings::tx::add_asset (;5;) (type 4) (param i32 i32) - local.get 1 - f32.load - local.get 1 - f32.load offset=4 - local.get 1 - f32.load offset=8 - local.get 1 - f32.load offset=12 - local.get 0 - call $miden_base_sys::bindings::tx::externs::extern_account_add_asset - ) - (func $miden_base_sys::bindings::tx::remove_asset (;6;) (type 4) (param i32 i32) - local.get 1 - f32.load - local.get 1 - f32.load offset=4 - local.get 1 - f32.load offset=8 - local.get 1 - f32.load offset=12 - local.get 0 - call $miden_base_sys::bindings::tx::externs::extern_account_remove_asset - ) - (func $miden_base_sys::bindings::tx::create_note (;7;) (type 5) (param i32 f32 f32 i32) (result f32) - local.get 0 - f32.load - local.get 0 - f32.load offset=4 - local.get 0 - f32.load offset=8 - local.get 0 - f32.load offset=12 - local.get 1 - local.get 2 - local.get 3 - f32.load - local.get 3 - f32.load offset=4 - local.get 3 - f32.load offset=8 - local.get 3 - f32.load offset=12 - call $miden_base_sys::bindings::tx::externs::extern_tx_create_note - ) - (table (;0;) 1 1 funcref) - (memory (;0;) 16) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "receive_asset" (func $receive_asset)) - (export "send_asset" (func $send_asset)) -) \ No newline at end of file diff --git a/tests/integration/expected/shl_i16.hir b/tests/integration/expected/shl_i16.hir index fe8d73cb0..2f10c1a0c 100644 --- a/tests/integration/expected/shl_i16.hir +++ b/tests/integration/expected/shl_i16.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_947b1fd0c78559d180609e8a959684c5090a8ac458193ed6a1103a863d3c8bd0 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_c3eed17236100365f8c1660de046d9d6663a7b82697275884d4fcb6fa25a7447 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.constant 15 : i32; + v4 = arith.band v1, v3 : i32; + v5 = hir.bitcast v4 : u32; + v6 = arith.shl v0, v5 : i32; + v7 = arith.sext v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 15)) - (let (v4 i32) (band v1 v3)) - (let (v5 u32) (bitcast v4)) - (let (v6 i32) (shl.wrapping v0 v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_i16.masm b/tests/integration/expected/shl_i16.masm index 567a2ab86..c844db47a 100644 --- a/tests/integration/expected/shl_i16.masm +++ b/tests/integration/expected/shl_i16.masm @@ -1,11 +1,27 @@ -# mod test_rust_947b1fd0c78559d180609e8a959684c5090a8ac458193ed6a1103a863d3c8bd0 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_c3eed17236100365f8c1660de046d9d6663a7b82697275884d4fcb6fa25a7447 export.entrypoint push.15 movup.2 - swap.1 u32and u32shl end - diff --git a/tests/integration/expected/shl_i16.wat b/tests/integration/expected/shl_i16.wat index 65f7ea5c6..590b75501 100644 --- a/tests/integration/expected/shl_i16.wat +++ b/tests/integration/expected/shl_i16.wat @@ -1,13 +1,5 @@ -(module $test_rust_947b1fd0c78559d180609e8a959684c5090a8ac458193ed6a1103a863d3c8bd0.wasm +(module $test_rust_c3eed17236100365f8c1660de046d9d6663a7b82697275884d4fcb6fa25a7447.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.const 15 - i32.and - i32.shl - i32.extend16_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -16,4 +8,12 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 15 + i32.and + i32.shl + i32.extend16_s + ) +) diff --git a/tests/integration/expected/shl_i32.hir b/tests/integration/expected/shl_i32.hir index 362242a79..b9c3ebe15 100644 --- a/tests/integration/expected/shl_i32.hir +++ b/tests/integration/expected/shl_i32.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_b1a7a85321f9f6b080f6e9a0676c7398d86d5c2a5214aa2a59f442c8152d33f8 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_bb1e5acff11c647800da7bb67237f59cfa6dcc846fe64b76bfdce3d151cb1de9 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = hir.bitcast v1 : u32; + v4 = arith.shl v0, v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v1)) - (let (v4 i32) (shl.wrapping v0 v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_i32.masm b/tests/integration/expected/shl_i32.masm index 9219851ba..d123fe5c4 100644 --- a/tests/integration/expected/shl_i32.masm +++ b/tests/integration/expected/shl_i32.masm @@ -1,7 +1,25 @@ -# mod test_rust_b1a7a85321f9f6b080f6e9a0676c7398d86d5c2a5214aa2a59f442c8152d33f8 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32shl +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_bb1e5acff11c647800da7bb67237f59cfa6dcc846fe64b76bfdce3d151cb1de9 + +export.entrypoint + swap.1 + u32shl +end diff --git a/tests/integration/expected/shl_i32.wat b/tests/integration/expected/shl_i32.wat index ffa6e89a2..c4283da66 100644 --- a/tests/integration/expected/shl_i32.wat +++ b/tests/integration/expected/shl_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_b1a7a85321f9f6b080f6e9a0676c7398d86d5c2a5214aa2a59f442c8152d33f8.wasm +(module $test_rust_bb1e5acff11c647800da7bb67237f59cfa6dcc846fe64b76bfdce3d151cb1de9.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.shl - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.shl + ) +) diff --git a/tests/integration/expected/shl_i64.hir b/tests/integration/expected/shl_i64.hir index a52be75f1..f8f7381a7 100644 --- a/tests/integration/expected/shl_i64.hir +++ b/tests/integration/expected/shl_i64.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_25e2507052972928b154ff844858eb75529a3497622ee9ca52b471d8a099e59f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_9d7fa5c817c0e8ad40aeb12756fb108047ae365f6bc8c673617a6bc264b3917a { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = hir.cast v1 : u32; + v4 = arith.shl v0, v3 : i64; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u32) (cast v1)) - (let (v4 i64) (shl.wrapping v0 v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_i64.masm b/tests/integration/expected/shl_i64.masm index 6ce2a7c5c..e6161e350 100644 --- a/tests/integration/expected/shl_i64.masm +++ b/tests/integration/expected/shl_i64.masm @@ -1,4 +1,22 @@ -# mod test_rust_25e2507052972928b154ff844858eb75529a3497622ee9ca52b471d8a099e59f +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_9d7fa5c817c0e8ad40aeb12756fb108047ae365f6bc8c673617a6bc264b3917a export.entrypoint movdn.3 @@ -13,7 +31,10 @@ export.entrypoint push.4294967295 u32lte assert + trace.240 + nop exec.::std::math::u64::shl + trace.252 + nop end - diff --git a/tests/integration/expected/shl_i64.wat b/tests/integration/expected/shl_i64.wat index cb9076a17..9e19a8d72 100644 --- a/tests/integration/expected/shl_i64.wat +++ b/tests/integration/expected/shl_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_25e2507052972928b154ff844858eb75529a3497622ee9ca52b471d8a099e59f.wasm +(module $test_rust_9d7fa5c817c0e8ad40aeb12756fb108047ae365f6bc8c673617a6bc264b3917a.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 0 - local.get 1 - i64.shl - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 0 + local.get 1 + i64.shl + ) +) diff --git a/tests/integration/expected/shl_i8.hir b/tests/integration/expected/shl_i8.hir index 577fdc6ad..597268256 100644 --- a/tests/integration/expected/shl_i8.hir +++ b/tests/integration/expected/shl_i8.hir @@ -1,26 +1,25 @@ -(component - ;; Modules - (module #test_rust_5f1b43480ab6707fd5d599fff30d72a40baf9972173edc344c239f0c3b48da64 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_64d9b2f2c2b5e80b282b2d87195903012daf88be2507d12d0bc75d869a811922 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.constant 7 : i32; + v4 = arith.band v1, v3 : i32; + v5 = hir.bitcast v4 : u32; + v6 = arith.shl v0, v5 : i32; + v7 = arith.sext v6 : i32; + builtin.ret v7; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 7)) - (let (v4 i32) (band v1 v3)) - (let (v5 u32) (bitcast v4)) - (let (v6 i32) (shl.wrapping v0 v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_i8.masm b/tests/integration/expected/shl_i8.masm index c461e25dc..991b5bc0b 100644 --- a/tests/integration/expected/shl_i8.masm +++ b/tests/integration/expected/shl_i8.masm @@ -1,11 +1,27 @@ -# mod test_rust_5f1b43480ab6707fd5d599fff30d72a40baf9972173edc344c239f0c3b48da64 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_64d9b2f2c2b5e80b282b2d87195903012daf88be2507d12d0bc75d869a811922 export.entrypoint push.7 movup.2 - swap.1 u32and u32shl end - diff --git a/tests/integration/expected/shl_i8.wat b/tests/integration/expected/shl_i8.wat index 5632f9486..dfac4e2bd 100644 --- a/tests/integration/expected/shl_i8.wat +++ b/tests/integration/expected/shl_i8.wat @@ -1,13 +1,5 @@ -(module $test_rust_5f1b43480ab6707fd5d599fff30d72a40baf9972173edc344c239f0c3b48da64.wasm +(module $test_rust_64d9b2f2c2b5e80b282b2d87195903012daf88be2507d12d0bc75d869a811922.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.const 7 - i32.and - i32.shl - i32.extend8_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -16,4 +8,12 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 7 + i32.and + i32.shl + i32.extend8_s + ) +) diff --git a/tests/integration/expected/shl_u16.hir b/tests/integration/expected/shl_u16.hir index 7ee28126f..83f458f76 100644 --- a/tests/integration/expected/shl_u16.hir +++ b/tests/integration/expected/shl_u16.hir @@ -1,28 +1,26 @@ -(component - ;; Modules - (module #test_rust_0ab094171adf5f867d5a41b825b648320b4a7b9662828d177cacb8db3f7de5ef - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_3ab387e5fafe4f8316074728a13b9c62413b6215a11492c80aa3e1d10383e008 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v7 = arith.constant 65535 : i32; + v3 = arith.constant 15 : i32; + v4 = arith.band v1, v3 : i32; + v5 = hir.bitcast v4 : u32; + v6 = arith.shl v0, v5 : i32; + v8 = arith.band v6, v7 : i32; + builtin.ret v8; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 15)) - (let (v4 i32) (band v1 v3)) - (let (v5 u32) (bitcast v4)) - (let (v6 i32) (shl.wrapping v0 v5)) - (let (v7 i32) (const.i32 65535)) - (let (v8 i32) (band v6 v7)) - (br (block 1 v8))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_u16.masm b/tests/integration/expected/shl_u16.masm index c9d986e26..a009a0b43 100644 --- a/tests/integration/expected/shl_u16.masm +++ b/tests/integration/expected/shl_u16.masm @@ -1,13 +1,31 @@ -# mod test_rust_0ab094171adf5f867d5a41b825b648320b4a7b9662828d177cacb8db3f7de5ef +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_3ab387e5fafe4f8316074728a13b9c62413b6215a11492c80aa3e1d10383e008 export.entrypoint + push.65535 push.15 + movup.3 + u32and movup.2 swap.1 - u32and u32shl - push.65535 u32and end - diff --git a/tests/integration/expected/shl_u16.wat b/tests/integration/expected/shl_u16.wat index 2eee52438..30e8f75b2 100644 --- a/tests/integration/expected/shl_u16.wat +++ b/tests/integration/expected/shl_u16.wat @@ -1,5 +1,13 @@ -(module $test_rust_0ab094171adf5f867d5a41b825b648320b4a7b9662828d177cacb8db3f7de5ef.wasm +(module $test_rust_3ab387e5fafe4f8316074728a13b9c62413b6215a11492c80aa3e1d10383e008.wasm (type (;0;) (func (param i32 i32) (result i32))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) local.get 0 local.get 1 @@ -9,12 +17,4 @@ i32.const 65535 i32.and ) - (memory (;0;) 16) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (global (;1;) i32 i32.const 1048576) - (global (;2;) i32 i32.const 1048576) - (export "memory" (memory 0)) - (export "entrypoint" (func $entrypoint)) - (export "__data_end" (global 1)) - (export "__heap_base" (global 2)) -) \ No newline at end of file +) diff --git a/tests/integration/expected/shl_u32.hir b/tests/integration/expected/shl_u32.hir index 8dacc68de..d8ea8e040 100644 --- a/tests/integration/expected/shl_u32.hir +++ b/tests/integration/expected/shl_u32.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_7e6c842b77a3cb1324eb4149ea3db75e8175bb301dd8d842a28fa5eb165bc64c - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_3618ee9a49fe14195a2097b20a230da5249d5b99ab7da35b402a7c16f4fa5609 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = hir.bitcast v1 : u32; + v4 = arith.shl v0, v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v1)) - (let (v4 i32) (shl.wrapping v0 v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_u32.masm b/tests/integration/expected/shl_u32.masm index ca5370bf4..2a868cf42 100644 --- a/tests/integration/expected/shl_u32.masm +++ b/tests/integration/expected/shl_u32.masm @@ -1,7 +1,25 @@ -# mod test_rust_7e6c842b77a3cb1324eb4149ea3db75e8175bb301dd8d842a28fa5eb165bc64c +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32shl +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_3618ee9a49fe14195a2097b20a230da5249d5b99ab7da35b402a7c16f4fa5609 + +export.entrypoint + swap.1 + u32shl +end diff --git a/tests/integration/expected/shl_u32.wat b/tests/integration/expected/shl_u32.wat index 3e60a40c4..121e9200a 100644 --- a/tests/integration/expected/shl_u32.wat +++ b/tests/integration/expected/shl_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_7e6c842b77a3cb1324eb4149ea3db75e8175bb301dd8d842a28fa5eb165bc64c.wasm +(module $test_rust_3618ee9a49fe14195a2097b20a230da5249d5b99ab7da35b402a7c16f4fa5609.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.shl - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.shl + ) +) diff --git a/tests/integration/expected/shl_u64.hir b/tests/integration/expected/shl_u64.hir index 9fcd32ba1..b45b98052 100644 --- a/tests/integration/expected/shl_u64.hir +++ b/tests/integration/expected/shl_u64.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_2a329af439c3fda4a420516309900eb5dee52a3d62676c5dfee856e4056b3fa5 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_ef9c837ab7c2bd11aacdf5ad41eb9264c0daecd169f2b4cfe73fd928059744bb { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = hir.cast v1 : u32; + v4 = arith.shl v0, v3 : i64; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u32) (cast v1)) - (let (v4 i64) (shl.wrapping v0 v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_u64.masm b/tests/integration/expected/shl_u64.masm index 329c9bb43..20c62fc2b 100644 --- a/tests/integration/expected/shl_u64.masm +++ b/tests/integration/expected/shl_u64.masm @@ -1,4 +1,22 @@ -# mod test_rust_2a329af439c3fda4a420516309900eb5dee52a3d62676c5dfee856e4056b3fa5 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_ef9c837ab7c2bd11aacdf5ad41eb9264c0daecd169f2b4cfe73fd928059744bb export.entrypoint movdn.3 @@ -13,7 +31,10 @@ export.entrypoint push.4294967295 u32lte assert + trace.240 + nop exec.::std::math::u64::shl + trace.252 + nop end - diff --git a/tests/integration/expected/shl_u64.wat b/tests/integration/expected/shl_u64.wat index c91d40442..9e54d1c16 100644 --- a/tests/integration/expected/shl_u64.wat +++ b/tests/integration/expected/shl_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_2a329af439c3fda4a420516309900eb5dee52a3d62676c5dfee856e4056b3fa5.wasm +(module $test_rust_ef9c837ab7c2bd11aacdf5ad41eb9264c0daecd169f2b4cfe73fd928059744bb.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 0 - local.get 1 - i64.shl - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 0 + local.get 1 + i64.shl + ) +) diff --git a/tests/integration/expected/shl_u8.hir b/tests/integration/expected/shl_u8.hir index 6c204c7fa..b805ed217 100644 --- a/tests/integration/expected/shl_u8.hir +++ b/tests/integration/expected/shl_u8.hir @@ -1,28 +1,26 @@ -(component - ;; Modules - (module #test_rust_c41b1b7ac309f4bb3497653978e4c903a0185e0b268c2fca135833d9389bacc9 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_b211bf2251e0ca4732fe0f4b9390e5838c61a0ef97c8b47f099f8c5ab3657e7f { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v7 = arith.constant 255 : i32; + v3 = arith.constant 7 : i32; + v4 = arith.band v1, v3 : i32; + v5 = hir.bitcast v4 : u32; + v6 = arith.shl v0, v5 : i32; + v8 = arith.band v6, v7 : i32; + builtin.ret v8; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 7)) - (let (v4 i32) (band v1 v3)) - (let (v5 u32) (bitcast v4)) - (let (v6 i32) (shl.wrapping v0 v5)) - (let (v7 i32) (const.i32 255)) - (let (v8 i32) (band v6 v7)) - (br (block 1 v8))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shl_u8.masm b/tests/integration/expected/shl_u8.masm index d86141056..7d958853d 100644 --- a/tests/integration/expected/shl_u8.masm +++ b/tests/integration/expected/shl_u8.masm @@ -1,13 +1,31 @@ -# mod test_rust_c41b1b7ac309f4bb3497653978e4c903a0185e0b268c2fca135833d9389bacc9 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_b211bf2251e0ca4732fe0f4b9390e5838c61a0ef97c8b47f099f8c5ab3657e7f export.entrypoint + push.255 push.7 + movup.3 + u32and movup.2 swap.1 - u32and u32shl - push.255 u32and end - diff --git a/tests/integration/expected/shl_u8.wat b/tests/integration/expected/shl_u8.wat index 43c468865..ed04eecc6 100644 --- a/tests/integration/expected/shl_u8.wat +++ b/tests/integration/expected/shl_u8.wat @@ -1,5 +1,13 @@ -(module $test_rust_c41b1b7ac309f4bb3497653978e4c903a0185e0b268c2fca135833d9389bacc9.wasm +(module $test_rust_b211bf2251e0ca4732fe0f4b9390e5838c61a0ef97c8b47f099f8c5ab3657e7f.wasm (type (;0;) (func (param i32 i32) (result i32))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) local.get 0 local.get 1 @@ -9,12 +17,4 @@ i32.const 255 i32.and ) - (memory (;0;) 16) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (global (;1;) i32 i32.const 1048576) - (global (;2;) i32 i32.const 1048576) - (export "memory" (memory 0)) - (export "entrypoint" (func $entrypoint)) - (export "__data_end" (global 1)) - (export "__heap_base" (global 2)) -) \ No newline at end of file +) diff --git a/tests/integration/expected/shr_i64.hir b/tests/integration/expected/shr_i64.hir index 5c854f3d7..c9084b593 100644 --- a/tests/integration/expected/shr_i64.hir +++ b/tests/integration/expected/shr_i64.hir @@ -1,24 +1,22 @@ -(component - ;; Modules - (module #test_rust_9e782fbd0f39c9f1f4f8c1623ec32ad4d1666e9dfad0fc58c43a8ade8dfcb60c - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_186d93d83b74a241eb99ffa0ead88d2435a288695c19f22c9c35eecfda978717 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = hir.cast v1 : u32; + v4 = arith.shr v0, v3 : i64; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u32) (cast v1)) - (let (v4 i64) (shr.wrapping v0 v3)) - (br (block 1 v4))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shr_i64.masm b/tests/integration/expected/shr_i64.masm index 8ff8b76a6..afd089696 100644 --- a/tests/integration/expected/shr_i64.masm +++ b/tests/integration/expected/shr_i64.masm @@ -1,4 +1,22 @@ -# mod test_rust_9e782fbd0f39c9f1f4f8c1623ec32ad4d1666e9dfad0fc58c43a8ade8dfcb60c +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_186d93d83b74a241eb99ffa0ead88d2435a288695c19f22c9c35eecfda978717 export.entrypoint movdn.3 @@ -13,7 +31,10 @@ export.entrypoint push.4294967295 u32lte assert + trace.240 + nop exec.::intrinsics::i64::checked_shr + trace.252 + nop end - diff --git a/tests/integration/expected/shr_i64.wat b/tests/integration/expected/shr_i64.wat index 1727fc115..fc9d52926 100644 --- a/tests/integration/expected/shr_i64.wat +++ b/tests/integration/expected/shr_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_9e782fbd0f39c9f1f4f8c1623ec32ad4d1666e9dfad0fc58c43a8ade8dfcb60c.wasm +(module $test_rust_186d93d83b74a241eb99ffa0ead88d2435a288695c19f22c9c35eecfda978717.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 0 - local.get 1 - i64.shr_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 0 + local.get 1 + i64.shr_s + ) +) diff --git a/tests/integration/expected/shr_u16.hir b/tests/integration/expected/shr_u16.hir index 6b5370848..5177f0eab 100644 --- a/tests/integration/expected/shr_u16.hir +++ b/tests/integration/expected/shr_u16.hir @@ -1,28 +1,26 @@ -(component - ;; Modules - (module #test_rust_044d9f6f5bf1c98609cfe5f9496ee9e517cb2900d43714636541400c34d5fb03 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_96d61f728452f9b581e6c765749adb51e9f48ad7e7527bde3c25a5943fa1a541 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.constant 15 : i32; + v4 = arith.band v1, v3 : i32; + v6 = hir.bitcast v4 : u32; + v5 = hir.bitcast v0 : u32; + v7 = arith.shr v5, v6 : u32; + v8 = hir.bitcast v7 : i32; + builtin.ret v8; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 15)) - (let (v4 i32) (band v1 v3)) - (let (v5 u32) (bitcast v0)) - (let (v6 u32) (bitcast v4)) - (let (v7 u32) (shr.wrapping v5 v6)) - (let (v8 i32) (bitcast v7)) - (br (block 1 v8))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shr_u16.masm b/tests/integration/expected/shr_u16.masm index 25e00a36a..e9231c36a 100644 --- a/tests/integration/expected/shr_u16.masm +++ b/tests/integration/expected/shr_u16.masm @@ -1,11 +1,29 @@ -# mod test_rust_044d9f6f5bf1c98609cfe5f9496ee9e517cb2900d43714636541400c34d5fb03 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_96d61f728452f9b581e6c765749adb51e9f48ad7e7527bde3c25a5943fa1a541 export.entrypoint push.15 movup.2 - swap.1 u32and + swap.1 + swap.1 u32shr end - diff --git a/tests/integration/expected/shr_u16.wat b/tests/integration/expected/shr_u16.wat index baef8b152..f39e53f86 100644 --- a/tests/integration/expected/shr_u16.wat +++ b/tests/integration/expected/shr_u16.wat @@ -1,12 +1,5 @@ -(module $test_rust_044d9f6f5bf1c98609cfe5f9496ee9e517cb2900d43714636541400c34d5fb03.wasm +(module $test_rust_96d61f728452f9b581e6c765749adb51e9f48ad7e7527bde3c25a5943fa1a541.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.const 15 - i32.and - i32.shr_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 15 + i32.and + i32.shr_u + ) +) diff --git a/tests/integration/expected/shr_u32.hir b/tests/integration/expected/shr_u32.hir index 9a777df78..f7a1f57f0 100644 --- a/tests/integration/expected/shr_u32.hir +++ b/tests/integration/expected/shr_u32.hir @@ -1,26 +1,24 @@ -(component - ;; Modules - (module #test_rust_8ce82de7c4fb77fe54a97d76d270876e6dc8484997171b1a52821c97bc67dbba - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_98363f37c9b0fb3dc27cafaa81c0eb63c6a764c82cbedfc0da3e37a43e1d2331 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = hir.bitcast v1 : u32; + v3 = hir.bitcast v0 : u32; + v5 = arith.shr v3, v4 : u32; + v6 = hir.bitcast v5 : i32; + builtin.ret v6; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (bitcast v1)) - (let (v5 u32) (shr.wrapping v3 v4)) - (let (v6 i32) (bitcast v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shr_u32.masm b/tests/integration/expected/shr_u32.masm index 1eec3a1f1..d7803cd6b 100644 --- a/tests/integration/expected/shr_u32.masm +++ b/tests/integration/expected/shr_u32.masm @@ -1,7 +1,27 @@ -# mod test_rust_8ce82de7c4fb77fe54a97d76d270876e6dc8484997171b1a52821c97bc67dbba +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32shr +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_98363f37c9b0fb3dc27cafaa81c0eb63c6a764c82cbedfc0da3e37a43e1d2331 + +export.entrypoint + swap.1 + swap.1 + swap.1 + u32shr +end diff --git a/tests/integration/expected/shr_u32.wat b/tests/integration/expected/shr_u32.wat index 226f94e0a..5efba3f02 100644 --- a/tests/integration/expected/shr_u32.wat +++ b/tests/integration/expected/shr_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_8ce82de7c4fb77fe54a97d76d270876e6dc8484997171b1a52821c97bc67dbba.wasm +(module $test_rust_98363f37c9b0fb3dc27cafaa81c0eb63c6a764c82cbedfc0da3e37a43e1d2331.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.shr_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.shr_u + ) +) diff --git a/tests/integration/expected/shr_u64.hir b/tests/integration/expected/shr_u64.hir index 3f35e6cb7..e4940cde7 100644 --- a/tests/integration/expected/shr_u64.hir +++ b/tests/integration/expected/shr_u64.hir @@ -1,26 +1,24 @@ -(component - ;; Modules - (module #test_rust_06af793a34a89f72818a0d65a5b5ac2526e0731e32c76ae951f9d05d12b6d4e4 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_9efc7a825f7a743a5fc284e5d1f0d3fc1bb239f780841274be29a84565e75592 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v4 = hir.cast v1 : u32; + v3 = hir.bitcast v0 : u64; + v5 = arith.shr v3, v4 : u64; + v6 = hir.bitcast v5 : i64; + builtin.ret v6; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 u64) (bitcast v0)) - (let (v4 u32) (cast v1)) - (let (v5 u64) (shr.wrapping v3 v4)) - (let (v6 i64) (bitcast v5)) - (br (block 1 v6))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shr_u64.masm b/tests/integration/expected/shr_u64.masm index 9995090c3..f19ea2a42 100644 --- a/tests/integration/expected/shr_u64.masm +++ b/tests/integration/expected/shr_u64.masm @@ -1,4 +1,22 @@ -# mod test_rust_06af793a34a89f72818a0d65a5b5ac2526e0731e32c76ae951f9d05d12b6d4e4 +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_9efc7a825f7a743a5fc284e5d1f0d3fc1bb239f780841274be29a84565e75592 export.entrypoint movdn.3 @@ -13,7 +31,12 @@ export.entrypoint push.4294967295 u32lte assert + movdn.2 + movup.2 + trace.240 + nop exec.::std::math::u64::shr + trace.252 + nop end - diff --git a/tests/integration/expected/shr_u64.wat b/tests/integration/expected/shr_u64.wat index 9bbfb32e6..28999708d 100644 --- a/tests/integration/expected/shr_u64.wat +++ b/tests/integration/expected/shr_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_06af793a34a89f72818a0d65a5b5ac2526e0731e32c76ae951f9d05d12b6d4e4.wasm +(module $test_rust_9efc7a825f7a743a5fc284e5d1f0d3fc1bb239f780841274be29a84565e75592.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 0 - local.get 1 - i64.shr_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 0 + local.get 1 + i64.shr_u + ) +) diff --git a/tests/integration/expected/shr_u8.hir b/tests/integration/expected/shr_u8.hir index af08be0ac..c7b5676a6 100644 --- a/tests/integration/expected/shr_u8.hir +++ b/tests/integration/expected/shr_u8.hir @@ -1,28 +1,26 @@ -(component - ;; Modules - (module #test_rust_5e6fba59478d938d56559a1d9ca7b9fe13244e2b0714fdfc42042b83af78612f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_04fd0cb7f25bc890cc2570ad64142734f4731c48e1507938bd1b769f028cf2c4 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.constant 7 : i32; + v4 = arith.band v1, v3 : i32; + v6 = hir.bitcast v4 : u32; + v5 = hir.bitcast v0 : u32; + v7 = arith.shr v5, v6 : u32; + v8 = hir.bitcast v7 : i32; + builtin.ret v8; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 7)) - (let (v4 i32) (band v1 v3)) - (let (v5 u32) (bitcast v0)) - (let (v6 u32) (bitcast v4)) - (let (v7 u32) (shr.wrapping v5 v6)) - (let (v8 i32) (bitcast v7)) - (br (block 1 v8))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/shr_u8.masm b/tests/integration/expected/shr_u8.masm index e0c172398..95dc1777f 100644 --- a/tests/integration/expected/shr_u8.masm +++ b/tests/integration/expected/shr_u8.masm @@ -1,11 +1,29 @@ -# mod test_rust_5e6fba59478d938d56559a1d9ca7b9fe13244e2b0714fdfc42042b83af78612f +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_04fd0cb7f25bc890cc2570ad64142734f4731c48e1507938bd1b769f028cf2c4 export.entrypoint push.7 movup.2 - swap.1 u32and + swap.1 + swap.1 u32shr end - diff --git a/tests/integration/expected/shr_u8.wat b/tests/integration/expected/shr_u8.wat index 84a0704b2..ada7eff4a 100644 --- a/tests/integration/expected/shr_u8.wat +++ b/tests/integration/expected/shr_u8.wat @@ -1,12 +1,5 @@ -(module $test_rust_5e6fba59478d938d56559a1d9ca7b9fe13244e2b0714fdfc42042b83af78612f.wasm +(module $test_rust_04fd0cb7f25bc890cc2570ad64142734f4731c48e1507938bd1b769f028cf2c4.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.const 7 - i32.and - i32.shr_u - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.const 7 + i32.and + i32.shr_u + ) +) diff --git a/tests/integration/expected/sub_felt.hir b/tests/integration/expected/sub_felt.hir index 9ff0f4a57..fd06f6d21 100644 --- a/tests/integration/expected/sub_felt.hir +++ b/tests/integration/expected/sub_felt.hir @@ -1,21 +1,19 @@ -(component - ;; Modules - (module #sub_felt - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @sub_felt { + public builtin.function @entrypoint(v0: felt, v1: felt) -> felt { + ^block4(v0: felt, v1: felt): + v3 = hir.exec @root_ns:root@1.0.0/sub_felt/intrinsics::felt::sub(v0, v1) : felt + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) + private builtin.function @intrinsics::felt::sub(v4: felt, v5: felt) -> felt { + ^block6(v4: felt, v5: felt): + v6 = arith.sub v4, v5 : felt #[overflow = unchecked]; + builtin.ret v6; + }; - ;; Functions - (func (export #entrypoint) (param felt) (param felt) (result felt) - (block 0 (param v0 felt) (param v1 felt) - (let (v3 felt) (sub.unchecked v0 v1)) - (br (block 1 v3))) - - (block 1 (param v2 felt) - (ret v2)) - ) - ) - -) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_felt.masm b/tests/integration/expected/sub_felt.masm index 6ea35947f..c2ae54ef3 100644 --- a/tests/integration/expected/sub_felt.masm +++ b/tests/integration/expected/sub_felt.masm @@ -1,7 +1,27 @@ -# mod sub_felt +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::sub_felt export.entrypoint - swap.1 sub + trace.240 + nop + exec.::root_ns:root@1.0.0::sub_felt::intrinsics::felt::sub + trace.252 + nop end +proc.intrinsics::felt::sub + swap.1 + sub +end diff --git a/tests/integration/expected/sub_felt.wat b/tests/integration/expected/sub_felt.wat index 19b418397..28109d06f 100644 --- a/tests/integration/expected/sub_felt.wat +++ b/tests/integration/expected/sub_felt.wat @@ -1,14 +1,16 @@ (module $sub_felt.wasm (type (;0;) (func (param f32 f32) (result f32))) - (import "miden:stdlib/intrinsics_felt" "sub" (func $miden_stdlib_sys::intrinsics::felt::extern_sub (;0;) (type 0))) - (func $entrypoint (;1;) (type 0) (param f32 f32) (result f32) - local.get 0 - local.get 1 - call $miden_stdlib_sys::intrinsics::felt::extern_sub - ) (table (;0;) 1 1 funcref) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (export "memory" (memory 0)) (export "entrypoint" (func $entrypoint)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param f32 f32) (result f32) + local.get 0 + local.get 1 + call $intrinsics::felt::sub + ) + (func $intrinsics::felt::sub (;1;) (type 0) (param f32 f32) (result f32) + unreachable + ) +) diff --git a/tests/integration/expected/sub_i128.hir b/tests/integration/expected/sub_i128.hir new file mode 100644 index 000000000..305b185cc --- /dev/null +++ b/tests/integration/expected/sub_i128.hir @@ -0,0 +1,38 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e91108ee147c81774f718f1c0531bff5bc1cdc56c047c1e0475e4e94badee2c8 { + public builtin.function @entrypoint(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64) { + ^block6(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64): + v6 = arith.join v3, v4 : i128; + v5 = arith.join v1, v2 : i128; + v7 = arith.sub v5, v6 : i128 #[overflow = wrapping]; + v8, v9 = arith.split v7 : i64, i64; + v11 = arith.constant 8 : u32; + v10 = hir.bitcast v0 : u32; + v12 = arith.add v10, v11 : u32 #[overflow = checked]; + v21 = arith.constant 8 : u32; + v14 = arith.mod v12, v21 : u32; + hir.assertz v14 #[code = 250]; + v15 = hir.int_to_ptr v12 : ptr; + hir.store v15, v8; + v16 = hir.bitcast v0 : u32; + v20 = arith.constant 8 : u32; + v18 = arith.mod v16, v20 : u32; + hir.assertz v18 #[code = 250]; + v19 = hir.int_to_ptr v16 : ptr; + hir.store v19, v9; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_i128.masm b/tests/integration/expected/sub_i128.masm new file mode 100644 index 000000000..371cf817a --- /dev/null +++ b/tests/integration/expected/sub_i128.masm @@ -0,0 +1,71 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_e91108ee147c81774f718f1c0531bff5bc1cdc56c047c1e0475e4e94badee2c8 + +export.entrypoint + movup.6 + movup.6 + movup.8 + movup.8 + movup.6 + movup.6 + movup.8 + movup.8 + movdn.7 + movdn.7 + movdn.7 + movdn.7 + trace.240 + nop + exec.::intrinsics::i128::sub + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + diff --git a/tests/integration/expected/sub_i128.wat b/tests/integration/expected/sub_i128.wat new file mode 100644 index 000000000..656309e6d --- /dev/null +++ b/tests/integration/expected/sub_i128.wat @@ -0,0 +1,26 @@ +(module $test_rust_e91108ee147c81774f718f1c0531bff5bc1cdc56c047c1e0475e4e94badee2c8.wasm + (type (;0;) (func (param i32 i64 i64 i64 i64))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (func $entrypoint (;0;) (type 0) (param i32 i64 i64 i64 i64) + local.get 1 + local.get 2 + local.get 3 + local.get 4 + i64.sub128 + local.set 3 + local.set 4 + local.get 0 + local.get 3 + i64.store offset=8 + local.get 0 + local.get 4 + i64.store + ) +) diff --git a/tests/integration/expected/sub_i16.hir b/tests/integration/expected/sub_i16.hir index 928a70698..8dccf79a3 100644 --- a/tests/integration/expected/sub_i16.hir +++ b/tests/integration/expected/sub_i16.hir @@ -1,23 +1,22 @@ -(component - ;; Modules - (module #test_rust_6fb192cd8f45d09bfbce4dc3824f4ab50385e70882ac508535654ca25af2ff66 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_b6360a7c4709c8e140b5433c6b25904ac7efbab248a7f37933a33b31d5f40c49 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + v4 = arith.sext v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (sub.wrapping v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_i16.masm b/tests/integration/expected/sub_i16.masm index a477c6a8f..522432b71 100644 --- a/tests/integration/expected/sub_i16.masm +++ b/tests/integration/expected/sub_i16.masm @@ -1,7 +1,25 @@ -# mod test_rust_6fb192cd8f45d09bfbce4dc3824f4ab50385e70882ac508535654ca25af2ff66 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_b6360a7c4709c8e140b5433c6b25904ac7efbab248a7f37933a33b31d5f40c49 + +export.entrypoint + swap.1 + u32wrapping_sub +end diff --git a/tests/integration/expected/sub_i16.wat b/tests/integration/expected/sub_i16.wat index b544978a8..da2157dec 100644 --- a/tests/integration/expected/sub_i16.wat +++ b/tests/integration/expected/sub_i16.wat @@ -1,11 +1,5 @@ -(module $test_rust_6fb192cd8f45d09bfbce4dc3824f4ab50385e70882ac508535654ca25af2ff66.wasm +(module $test_rust_b6360a7c4709c8e140b5433c6b25904ac7efbab248a7f37933a33b31d5f40c49.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.sub - i32.extend16_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -14,4 +8,10 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub + i32.extend16_s + ) +) diff --git a/tests/integration/expected/sub_i32.hir b/tests/integration/expected/sub_i32.hir index 839cc8d4f..af1862ac8 100644 --- a/tests/integration/expected/sub_i32.hir +++ b/tests/integration/expected/sub_i32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_698d995f2c0b88d2812071d204a744bc080ce741c0da79a5c54d851946837d51 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_6fc6fd0b2e86628d32094545ba25215329fcd62cd1f981d0dbdd2c989eed3a52 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (sub.wrapping v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_i32.masm b/tests/integration/expected/sub_i32.masm index 03d8c48d7..43d10bdeb 100644 --- a/tests/integration/expected/sub_i32.masm +++ b/tests/integration/expected/sub_i32.masm @@ -1,7 +1,25 @@ -# mod test_rust_698d995f2c0b88d2812071d204a744bc080ce741c0da79a5c54d851946837d51 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_6fc6fd0b2e86628d32094545ba25215329fcd62cd1f981d0dbdd2c989eed3a52 + +export.entrypoint + swap.1 + u32wrapping_sub +end diff --git a/tests/integration/expected/sub_i32.wat b/tests/integration/expected/sub_i32.wat index b49aef1d9..b7db18e82 100644 --- a/tests/integration/expected/sub_i32.wat +++ b/tests/integration/expected/sub_i32.wat @@ -1,10 +1,5 @@ -(module $test_rust_698d995f2c0b88d2812071d204a744bc080ce741c0da79a5c54d851946837d51.wasm +(module $test_rust_6fc6fd0b2e86628d32094545ba25215329fcd62cd1f981d0dbdd2c989eed3a52.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.sub - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub + ) +) diff --git a/tests/integration/expected/sub_i64.hir b/tests/integration/expected/sub_i64.hir index 8357f11cd..bc70b8fda 100644 --- a/tests/integration/expected/sub_i64.hir +++ b/tests/integration/expected/sub_i64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_52c69166bf1f3ac3df7337dc12e7ba379d9ff44d7d8a8eb36c6c73182238ea88 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_c2e6cec606b20447e4c5accdc801c32098f0d7cda3811f8c4e38f16911210b8f { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.sub v0, v1 : i64 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (sub.wrapping v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_i64.masm b/tests/integration/expected/sub_i64.masm index fd61d56c9..632794135 100644 --- a/tests/integration/expected/sub_i64.masm +++ b/tests/integration/expected/sub_i64.masm @@ -1,7 +1,30 @@ -# mod test_rust_52c69166bf1f3ac3df7337dc12e7ba379d9ff44d7d8a8eb36c6c73182238ea88 +# mod root_ns:root@1.0.0 -export.entrypoint - movdn.3 movdn.3 exec.::std::math::u64::wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_c2e6cec606b20447e4c5accdc801c32098f0d7cda3811f8c4e38f16911210b8f + +export.entrypoint + movdn.3 + movdn.3 + trace.240 + nop + exec.::std::math::u64::wrapping_sub + trace.252 + nop +end diff --git a/tests/integration/expected/sub_i64.wat b/tests/integration/expected/sub_i64.wat index f1d27dbd6..6cb84589e 100644 --- a/tests/integration/expected/sub_i64.wat +++ b/tests/integration/expected/sub_i64.wat @@ -1,10 +1,5 @@ -(module $test_rust_52c69166bf1f3ac3df7337dc12e7ba379d9ff44d7d8a8eb36c6c73182238ea88.wasm +(module $test_rust_c2e6cec606b20447e4c5accdc801c32098f0d7cda3811f8c4e38f16911210b8f.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 0 - local.get 1 - i64.sub - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 0 + local.get 1 + i64.sub + ) +) diff --git a/tests/integration/expected/sub_i8.hir b/tests/integration/expected/sub_i8.hir index 6c48a97a8..1ff039382 100644 --- a/tests/integration/expected/sub_i8.hir +++ b/tests/integration/expected/sub_i8.hir @@ -1,23 +1,22 @@ -(component - ;; Modules - (module #test_rust_ad1749dbdf86cbb2d7af210b3181b5a21a0a457d8a76cf3261cf21a9902c3de0 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_c22e1496a88c74570b0e64374e0cf2e7a89efbbc6ead48d3e0471d8654d2afdc { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + v4 = arith.sext v3 : i32; + builtin.ret v4; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (sub.wrapping v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_i8.masm b/tests/integration/expected/sub_i8.masm index dcb12c601..31181496b 100644 --- a/tests/integration/expected/sub_i8.masm +++ b/tests/integration/expected/sub_i8.masm @@ -1,7 +1,25 @@ -# mod test_rust_ad1749dbdf86cbb2d7af210b3181b5a21a0a457d8a76cf3261cf21a9902c3de0 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_c22e1496a88c74570b0e64374e0cf2e7a89efbbc6ead48d3e0471d8654d2afdc + +export.entrypoint + swap.1 + u32wrapping_sub +end diff --git a/tests/integration/expected/sub_i8.wat b/tests/integration/expected/sub_i8.wat index ea7fbf0f4..65eccc33a 100644 --- a/tests/integration/expected/sub_i8.wat +++ b/tests/integration/expected/sub_i8.wat @@ -1,11 +1,5 @@ -(module $test_rust_ad1749dbdf86cbb2d7af210b3181b5a21a0a457d8a76cf3261cf21a9902c3de0.wasm +(module $test_rust_c22e1496a88c74570b0e64374e0cf2e7a89efbbc6ead48d3e0471d8654d2afdc.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.sub - i32.extend8_s - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -14,4 +8,10 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub + i32.extend8_s + ) +) diff --git a/tests/integration/expected/sub_u128.hir b/tests/integration/expected/sub_u128.hir new file mode 100644 index 000000000..230087f8a --- /dev/null +++ b/tests/integration/expected/sub_u128.hir @@ -0,0 +1,38 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_db478c28ad80aa271563ad7738787d5589cd21f31f7c92248f7f1de9cb727546 { + public builtin.function @entrypoint(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64) { + ^block6(v0: i32, v1: i64, v2: i64, v3: i64, v4: i64): + v6 = arith.join v3, v4 : i128; + v5 = arith.join v1, v2 : i128; + v7 = arith.sub v5, v6 : i128 #[overflow = wrapping]; + v8, v9 = arith.split v7 : i64, i64; + v11 = arith.constant 8 : u32; + v10 = hir.bitcast v0 : u32; + v12 = arith.add v10, v11 : u32 #[overflow = checked]; + v21 = arith.constant 8 : u32; + v14 = arith.mod v12, v21 : u32; + hir.assertz v14 #[code = 250]; + v15 = hir.int_to_ptr v12 : ptr; + hir.store v15, v8; + v16 = hir.bitcast v0 : u32; + v20 = arith.constant 8 : u32; + v18 = arith.mod v16, v20 : u32; + hir.assertz v18 #[code = 250]; + v19 = hir.int_to_ptr v16 : ptr; + hir.store v19, v9; + builtin.ret ; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_u128.masm b/tests/integration/expected/sub_u128.masm new file mode 100644 index 000000000..33ef2f9a6 --- /dev/null +++ b/tests/integration/expected/sub_u128.masm @@ -0,0 +1,71 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_db478c28ad80aa271563ad7738787d5589cd21f31f7c92248f7f1de9cb727546 + +export.entrypoint + movup.6 + movup.6 + movup.8 + movup.8 + movup.6 + movup.6 + movup.8 + movup.8 + movdn.7 + movdn.7 + movdn.7 + movdn.7 + trace.240 + nop + exec.::intrinsics::i128::sub + trace.252 + nop + push.8 + dup.5 + add + u32assert + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop + movup.2 + push.8 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_dw + trace.252 + nop +end + diff --git a/tests/integration/expected/sub_u128.wat b/tests/integration/expected/sub_u128.wat new file mode 100644 index 000000000..1407ffea3 --- /dev/null +++ b/tests/integration/expected/sub_u128.wat @@ -0,0 +1,26 @@ +(module $test_rust_db478c28ad80aa271563ad7738787d5589cd21f31f7c92248f7f1de9cb727546.wasm + (type (;0;) (func (param i32 i64 i64 i64 i64))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) + (func $entrypoint (;0;) (type 0) (param i32 i64 i64 i64 i64) + local.get 1 + local.get 2 + local.get 3 + local.get 4 + i64.sub128 + local.set 3 + local.set 4 + local.get 0 + local.get 3 + i64.store offset=8 + local.get 0 + local.get 4 + i64.store + ) +) diff --git a/tests/integration/expected/sub_u16.hir b/tests/integration/expected/sub_u16.hir index bfebe4a7f..e6d6dae6d 100644 --- a/tests/integration/expected/sub_u16.hir +++ b/tests/integration/expected/sub_u16.hir @@ -1,25 +1,23 @@ -(component - ;; Modules - (module #test_rust_f938d50f6700b001e6e4fa342fa235de0b28c5e79d3c4837b142860d9d8b8e6f - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_d8aa82f0674d72cf16435a3b3f6c248ab8b08b77539ac08b01e393fd53f96cb7 { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = arith.constant 65535 : i32; + v3 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + v5 = arith.band v3, v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (sub.wrapping v0 v1)) - (let (v4 i32) (const.i32 65535)) - (let (v5 i32) (band v3 v4)) - (br (block 1 v5))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_u16.masm b/tests/integration/expected/sub_u16.masm index c90196aa4..88ff236ca 100644 --- a/tests/integration/expected/sub_u16.masm +++ b/tests/integration/expected/sub_u16.masm @@ -1,7 +1,27 @@ -# mod test_rust_f938d50f6700b001e6e4fa342fa235de0b28c5e79d3c4837b142860d9d8b8e6f +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32wrapping_sub push.65535 u32and +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_d8aa82f0674d72cf16435a3b3f6c248ab8b08b77539ac08b01e393fd53f96cb7 + +export.entrypoint + push.65535 + swap.2 + u32wrapping_sub + u32and +end diff --git a/tests/integration/expected/sub_u16.wat b/tests/integration/expected/sub_u16.wat index 1090e3014..7e3ba92fd 100644 --- a/tests/integration/expected/sub_u16.wat +++ b/tests/integration/expected/sub_u16.wat @@ -1,12 +1,5 @@ -(module $test_rust_f938d50f6700b001e6e4fa342fa235de0b28c5e79d3c4837b142860d9d8b8e6f.wasm +(module $test_rust_d8aa82f0674d72cf16435a3b3f6c248ab8b08b77539ac08b01e393fd53f96cb7.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.sub - i32.const 65535 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub + i32.const 65535 + i32.and + ) +) diff --git a/tests/integration/expected/sub_u32.hir b/tests/integration/expected/sub_u32.hir index 77f7c3f07..86edc4982 100644 --- a/tests/integration/expected/sub_u32.hir +++ b/tests/integration/expected/sub_u32.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_a27a86095ba52da4bcd985229e6b3275c1a4a8354c4b119cef358f6ec3ac98c4 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_19bb93ff4625e8bdb6a3470a0b61a142a94458a4ee602822cb8360bf1f07368f { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (sub.wrapping v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_u32.masm b/tests/integration/expected/sub_u32.masm index a435830db..b130e3214 100644 --- a/tests/integration/expected/sub_u32.masm +++ b/tests/integration/expected/sub_u32.masm @@ -1,7 +1,25 @@ -# mod test_rust_a27a86095ba52da4bcd985229e6b3275c1a4a8354c4b119cef358f6ec3ac98c4 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_19bb93ff4625e8bdb6a3470a0b61a142a94458a4ee602822cb8360bf1f07368f + +export.entrypoint + swap.1 + u32wrapping_sub +end diff --git a/tests/integration/expected/sub_u32.wat b/tests/integration/expected/sub_u32.wat index ee25bf192..20a29ecc2 100644 --- a/tests/integration/expected/sub_u32.wat +++ b/tests/integration/expected/sub_u32.wat @@ -1,10 +1,5 @@ -(module $test_rust_a27a86095ba52da4bcd985229e6b3275c1a4a8354c4b119cef358f6ec3ac98c4.wasm +(module $test_rust_19bb93ff4625e8bdb6a3470a0b61a142a94458a4ee602822cb8360bf1f07368f.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.sub - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub + ) +) diff --git a/tests/integration/expected/sub_u64.hir b/tests/integration/expected/sub_u64.hir index c3a350578..73adec476 100644 --- a/tests/integration/expected/sub_u64.hir +++ b/tests/integration/expected/sub_u64.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_5574a62019b06e2b235711c006fa92058c6184438940dab99ef3bc7024353803 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_07a967cd239987abbfb9236f41c9268dd55be09bc1efb30f1e4684134ef772b0 { + public builtin.function @entrypoint(v0: i64, v1: i64) -> i64 { + ^block6(v0: i64, v1: i64): + v3 = arith.sub v0, v1 : i64 #[overflow = wrapping]; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i64) (param i64) (result i64) - (block 0 (param v0 i64) (param v1 i64) - (let (v3 i64) (sub.wrapping v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i64) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_u64.masm b/tests/integration/expected/sub_u64.masm index c7fddefe3..a027064f2 100644 --- a/tests/integration/expected/sub_u64.masm +++ b/tests/integration/expected/sub_u64.masm @@ -1,7 +1,30 @@ -# mod test_rust_5574a62019b06e2b235711c006fa92058c6184438940dab99ef3bc7024353803 +# mod root_ns:root@1.0.0 -export.entrypoint - movdn.3 movdn.3 exec.::std::math::u64::wrapping_sub +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_07a967cd239987abbfb9236f41c9268dd55be09bc1efb30f1e4684134ef772b0 + +export.entrypoint + movdn.3 + movdn.3 + trace.240 + nop + exec.::std::math::u64::wrapping_sub + trace.252 + nop +end diff --git a/tests/integration/expected/sub_u64.wat b/tests/integration/expected/sub_u64.wat index 3e65c8d35..6345e27a5 100644 --- a/tests/integration/expected/sub_u64.wat +++ b/tests/integration/expected/sub_u64.wat @@ -1,10 +1,5 @@ -(module $test_rust_5574a62019b06e2b235711c006fa92058c6184438940dab99ef3bc7024353803.wasm +(module $test_rust_07a967cd239987abbfb9236f41c9268dd55be09bc1efb30f1e4684134ef772b0.wasm (type (;0;) (func (param i64 i64) (result i64))) - (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) - local.get 0 - local.get 1 - i64.sub - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i64 i64) (result i64) + local.get 0 + local.get 1 + i64.sub + ) +) diff --git a/tests/integration/expected/sub_u8.hir b/tests/integration/expected/sub_u8.hir index 45679df95..339ce6d53 100644 --- a/tests/integration/expected/sub_u8.hir +++ b/tests/integration/expected/sub_u8.hir @@ -1,25 +1,23 @@ -(component - ;; Modules - (module #test_rust_35c4da632353887f7b18847780ef2124ea854f0ed6df12437e5a3eaa8f8aacc6 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_be0120d0a160e710cbd53bd6dc4ac525bbb3596615ae6ec2d5f88c74c069aabe { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v4 = arith.constant 255 : i32; + v3 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + v5 = arith.band v3, v4 : i32; + builtin.ret v5; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (sub.wrapping v0 v1)) - (let (v4 i32) (const.i32 255)) - (let (v5 i32) (band v3 v4)) - (br (block 1 v5))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/sub_u8.masm b/tests/integration/expected/sub_u8.masm index cf22c681e..9c3400fca 100644 --- a/tests/integration/expected/sub_u8.masm +++ b/tests/integration/expected/sub_u8.masm @@ -1,7 +1,27 @@ -# mod test_rust_35c4da632353887f7b18847780ef2124ea854f0ed6df12437e5a3eaa8f8aacc6 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32wrapping_sub push.255 u32and +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_be0120d0a160e710cbd53bd6dc4ac525bbb3596615ae6ec2d5f88c74c069aabe + +export.entrypoint + push.255 + swap.2 + u32wrapping_sub + u32and +end diff --git a/tests/integration/expected/sub_u8.wat b/tests/integration/expected/sub_u8.wat index 270880f84..01e9d49f4 100644 --- a/tests/integration/expected/sub_u8.wat +++ b/tests/integration/expected/sub_u8.wat @@ -1,12 +1,5 @@ -(module $test_rust_35c4da632353887f7b18847780ef2124ea854f0ed6df12437e5a3eaa8f8aacc6.wasm +(module $test_rust_be0120d0a160e710cbd53bd6dc4ac525bbb3596615ae6ec2d5f88c74c069aabe.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.sub - i32.const 255 - i32.and - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -15,4 +8,11 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub + i32.const 255 + i32.and + ) +) diff --git a/tests/integration/expected/types/array.hir b/tests/integration/expected/types/array.hir index 0eb582d5e..0cff3eeaa 100644 --- a/tests/integration/expected/types/array.hir +++ b/tests/integration/expected/types/array.hir @@ -1,73 +1,79 @@ -(component - ;; Modules - (module #test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6 - ;; Data Segments - (data (mut) (offset 1048576) 0x0100000002000000030000000400000005000000060000000700000008000000090000000a000000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6 { + public builtin.function @sum_arr(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v93 = arith.constant 0 : i32; + v3 = arith.constant 0 : i32; + v6 = arith.eq v1, v3 : i1; + v7 = arith.zext v6 : u32; + v8 = hir.bitcast v7 : i32; + v10 = arith.neq v8, v93 : i1; + v66 = scf.if v10 : i32 { + ^block8: + v92 = arith.constant 0 : i32; + scf.yield v92; + } else { + ^block9: + v91 = arith.constant 0 : i32; + v77, v78, v79, v80 = scf.while v0, v91, v1 : i32, i32, i32, i32 { + ^block23(v81: i32, v82: i32, v83: i32): + v12 = hir.bitcast v81 : u32; + v13 = arith.constant 4 : u32; + v14 = arith.mod v12, v13 : u32; + hir.assertz v14 #[code = 250]; + v15 = hir.int_to_ptr v12 : ptr; + v16 = hir.load v15 : i32; + v18 = arith.add v16, v82 : i32 #[overflow = wrapping]; + v22 = arith.constant -1 : i32; + v23 = arith.add v83, v22 : i32 #[overflow = wrapping]; + v90 = arith.constant 0 : i32; + v25 = arith.neq v23, v90 : i1; + v88 = ub.poison i32 : i32; + v74 = cf.select v25, v23, v88 : i32; + v89 = ub.poison i32 : i32; + v73 = cf.select v25, v18, v89 : i32; + v43 = ub.poison i32 : i32; + v19 = arith.constant 4 : i32; + v20 = arith.add v81, v19 : i32 #[overflow = wrapping]; + v72 = cf.select v25, v20, v43 : i32; + v36 = arith.constant 0 : u32; + v42 = arith.constant 1 : u32; + v76 = cf.select v25, v42, v36 : u32; + v65 = arith.trunc v76 : i1; + scf.condition v65, v72, v73, v74, v18; + } do { + ^block24(v84: i32, v85: i32, v86: i32, v87: i32): + scf.yield v84, v85, v86; + }; + scf.yield v80; + }; + builtin.ret v66; + }; - ;; Constants - (const (id 0) 0x00100000) - (const (id 1) 0x00100028) - (const (id 2) 0x00100030) + public builtin.function @__main() -> i32 { + ^block13: + v29 = arith.constant 5 : i32; + v28 = arith.constant 1048576 : i32; + v30 = hir.exec @root_ns:root@1.0.0/test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6/sum_arr(v28, v29) : i32 + v94 = arith.constant 5 : i32; + v31 = arith.constant 1048596 : i32; + v33 = hir.exec @root_ns:root@1.0.0/test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6/sum_arr(v31, v94) : i32 + v34 = arith.add v30, v33 : i32 #[overflow = wrapping]; + builtin.ret v34; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 1)) - (global (export #gv2) (id 2) (type i32) (const 2)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #sum_arr) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (const.i32 0)) - (let (v5 i1) (eq v1 0)) - (let (v6 i32) (zext v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 2 v4) (block 3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048616; + }; - (block 1 (param v2 i32) - (ret v2)) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048624; + }; - (block 2 (param v21 i32) - (br (block 1 v21))) - - (block 3 - (br (block 4 v0 v4 v1))) - - (block 4 (param v8 i32) (param v13 i32) (param v17 i32) - (let (v9 u32) (bitcast v8)) - (let (v10 u32) (mod.unchecked v9 4)) - (assertz 250 v10) - (let (v11 (ptr i32)) (inttoptr v9)) - (let (v12 i32) (load v11)) - (let (v14 i32) (add.wrapping v12 v13)) - (let (v15 i32) (const.i32 4)) - (let (v16 i32) (add.wrapping v8 v15)) - (let (v18 i32) (const.i32 -1)) - (let (v19 i32) (add.wrapping v17 v18)) - (let (v20 i1) (neq v19 0)) - (condbr v20 (block 4 v16 v14 v19) (block 6))) - - (block 5 - (br (block 2 v14))) - - (block 6 - (br (block 5))) - ) - - (func (export #__main) (result i32) - (block 0 - (let (v1 i32) (const.i32 1048576)) - (let (v2 i32) (const.i32 5)) - (let (v3 i32) (call #sum_arr v1 v2)) - (let (v4 i32) (const.i32 1048596)) - (let (v5 i32) (const.i32 5)) - (let (v6 i32) (call #sum_arr v4 v5)) - (let (v7 i32) (add.wrapping v3 v6)) - (br (block 1 v7))) - - (block 1 (param v0 i32) - (ret v0)) - ) - ) - -) + builtin.segment readonly @1048576 = 0x0000000a000000090000000800000007000000060000000500000004000000030000000200000001; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/types/array.masm b/tests/integration/expected/types/array.masm index de75d8343..dc297481f 100644 --- a/tests/integration/expected/types/array.masm +++ b/tests/integration/expected/types/array.masm @@ -1,87 +1,121 @@ -# mod test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6 +# mod root_ns:root@1.0.0 -export."__main" - push.5 +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[2552035692683956824,5323511717551755022,6168074652703322390,2298154438505521656] + adv.push_mapval + push.262144 + push.3 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop push.1048576 - exec.sum_arr - push.5 - push.1048596 - exec.sum_arr - u32wrapping_add + u32assert + mem_store.278544 + push.1048616 + u32assert + mem_store.278545 + push.1048624 + u32assert + mem_store.278546 end +# mod root_ns:root@1.0.0::test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6 export.sum_arr push.0 - dup.2 - eq.0 - neq.0 + push.0 + dup.3 + eq + neq if.true - movdn.2 drop drop + drop + drop + push.0 else + push.0 swap.1 - dup.0 - push.4 - movup.2 - swap.1 - u32wrapping_add - dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - movup.3 - u32wrapping_add - movup.2 - u32mod.4 - assertz.err=250 - push.4294967295 - movup.3 - swap.1 - u32wrapping_add - dup.0 - neq.0 push.1 while.true + dup.0 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movup.2 + u32wrapping_add + push.4294967295 + movup.3 + u32wrapping_add + push.0 + dup.1 + neq + push.3735929054 + dup.1 + swap.1 + swap.2 + swap.3 + swap.1 + cdrop + push.3735929054 + dup.2 + dup.4 + swap.1 + cdrop + push.3735929054 + push.4 + movup.6 + u32wrapping_add + dup.4 + cdrop + push.0 + push.1 + movup.5 + cdrop + push.1 + u32and if.true - swap.2 - dup.0 - push.4 - movup.2 - swap.1 - u32wrapping_add - dup.1 - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw movup.3 - u32wrapping_add - movup.2 - u32mod.4 - assertz.err=250 - push.4294967295 - movup.3 - swap.1 - u32wrapping_add - dup.0 - neq.0 + drop push.1 else - drop swap.1 drop push.0 + push.0 end end + drop + drop + drop end end +export.__main + push.5 + push.1048576 + trace.240 + nop + exec.::root_ns:root@1.0.0::test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6::sum_arr + trace.252 + nop + push.5 + push.1048596 + trace.240 + nop + exec.::root_ns:root@1.0.0::test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6::sum_arr + trace.252 + nop + u32wrapping_add +end diff --git a/tests/integration/expected/types/array.wat b/tests/integration/expected/types/array.wat index 199fc5cef..04fe03ebf 100644 --- a/tests/integration/expected/types/array.wat +++ b/tests/integration/expected/types/array.wat @@ -1,6 +1,15 @@ (module $test_rust_d63291a98b435c53f58385d5782fb46f0b0b78bee8e860843e7223106d66f7d6.wasm (type (;0;) (func (param i32 i32) (result i32))) (type (;1;) (func (result i32))) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048616) + (global (;2;) i32 i32.const 1048624) + (export "memory" (memory 0)) + (export "sum_arr" (func $sum_arr)) + (export "__main" (func $__main)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) (func $sum_arr (;0;) (type 0) (param i32 i32) (result i32) (local i32) i32.const 0 @@ -37,14 +46,5 @@ call $sum_arr i32.add ) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (global (;1;) i32 i32.const 1048616) - (global (;2;) i32 i32.const 1048624) - (export "memory" (memory 0)) - (export "sum_arr" (func $sum_arr)) - (export "__main" (func $__main)) - (export "__data_end" (global 1)) - (export "__heap_base" (global 2)) (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00\02\00\00\00\03\00\00\00\04\00\00\00\05\00\00\00\06\00\00\00\07\00\00\00\08\00\00\00\09\00\00\00\0a\00\00\00") -) \ No newline at end of file +) diff --git a/tests/integration/expected/types/enum.hir b/tests/integration/expected/types/enum.hir index da794dd98..3b4a6b6e2 100644 --- a/tests/integration/expected/types/enum.hir +++ b/tests/integration/expected/types/enum.hir @@ -1,64 +1,58 @@ -(component - ;; Modules - (module #test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824 - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) - - ;; Functions - (func (export #match_enum) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 255)) - (let (v5 i32) (band v2 v4)) - (let (v6 u32) (cast v5)) - (switchv6 - (0 . (block 4)) - (1 . (block 3)) - (2 . (block 2)) - (_ . (block 4)))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 - (let (v9 i32) (mul.wrapping v1 v0)) - (br (block 1 v9))) - - (block 3 - (let (v8 i32) (sub.wrapping v0 v1)) - (ret v8)) - - (block 4 - (let (v7 i32) (add.wrapping v1 v0)) - (ret v7)) - ) - - (func (export #__main) (result i32) - (block 0 - (let (v1 i32) (const.i32 3)) - (let (v2 i32) (const.i32 5)) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (call #match_enum v1 v2 v3)) - (let (v5 i32) (const.i32 3)) - (let (v6 i32) (const.i32 5)) - (let (v7 i32) (const.i32 1)) - (let (v8 i32) (call #match_enum v5 v6 v7)) - (let (v9 i32) (add.wrapping v4 v8)) - (let (v10 i32) (const.i32 3)) - (let (v11 i32) (const.i32 5)) - (let (v12 i32) (const.i32 2)) - (let (v13 i32) (call #match_enum v10 v11 v12)) - (let (v14 i32) (add.wrapping v9 v13)) - (br (block 1 v14))) - - (block 1 (param v0 i32) - (ret v0)) - ) - ) - -) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824 { + public builtin.function @match_enum(v0: i32, v1: i32, v2: i32) -> i32 { + ^block6(v0: i32, v1: i32, v2: i32): + v4 = arith.constant 255 : i32; + v5 = arith.band v2, v4 : i32; + v6 = hir.cast v5 : u32; + v27 = scf.index_switch v6 : i32 + case 1 { + ^block9: + v8 = arith.sub v0, v1 : i32 #[overflow = wrapping]; + scf.yield v8; + } + case 2 { + ^block8: + v9 = arith.mul v1, v0 : i32 #[overflow = wrapping]; + scf.yield v9; + } + default { + ^block10: + v7 = arith.add v1, v0 : i32 #[overflow = wrapping]; + scf.yield v7; + }; + builtin.ret v27; + }; + + public builtin.function @__main() -> i32 { + ^block11: + v13 = arith.constant 0 : i32; + v12 = arith.constant 5 : i32; + v11 = arith.constant 3 : i32; + v14 = hir.exec @root_ns:root@1.0.0/test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824/match_enum(v11, v12, v13) : i32 + v17 = arith.constant 1 : i32; + v30 = arith.constant 5 : i32; + v31 = arith.constant 3 : i32; + v18 = hir.exec @root_ns:root@1.0.0/test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824/match_enum(v31, v30, v17) : i32 + v22 = arith.constant 2 : i32; + v28 = arith.constant 5 : i32; + v29 = arith.constant 3 : i32; + v23 = hir.exec @root_ns:root@1.0.0/test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824/match_enum(v29, v28, v22) : i32 + v19 = arith.add v14, v18 : i32 #[overflow = wrapping]; + v24 = arith.add v19, v23 : i32 #[overflow = wrapping]; + builtin.ret v24; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; + + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/types/enum.masm b/tests/integration/expected/types/enum.masm new file mode 100644 index 000000000..9643cc827 --- /dev/null +++ b/tests/integration/expected/types/enum.masm @@ -0,0 +1,79 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 +end + +# mod root_ns:root@1.0.0::test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824 + +export.match_enum + push.255 + movup.3 + u32and + dup.0 + push.2147483648 + u32lte + assert + dup.0 + push.2 + u32lte + if.true + eq.1 + if.true + swap.1 + u32wrapping_sub + else + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + end + else + drop + u32wrapping_add + end +end + +export.__main + push.0 + push.5 + push.3 + trace.240 + nop + exec.::root_ns:root@1.0.0::test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824::match_enum + trace.252 + nop + push.1 + push.5 + push.3 + trace.240 + nop + exec.::root_ns:root@1.0.0::test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824::match_enum + trace.252 + nop + push.2 + push.5 + push.3 + trace.240 + nop + exec.::root_ns:root@1.0.0::test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824::match_enum + trace.252 + nop + movdn.2 + u32wrapping_add + u32wrapping_add +end + diff --git a/tests/integration/expected/types/enum.wat b/tests/integration/expected/types/enum.wat index 4584725e1..f7eeed058 100644 --- a/tests/integration/expected/types/enum.wat +++ b/tests/integration/expected/types/enum.wat @@ -1,6 +1,15 @@ (module $test_rust_f0bb65319ffababec660ada9dd2dd5f137503f60cf9c37332d6f7e171f275824.wasm (type (;0;) (func (param i32 i32 i32) (result i32))) (type (;1;) (func (result i32))) + (memory (;0;) 16) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048576) + (global (;2;) i32 i32.const 1048576) + (export "memory" (memory 0)) + (export "match_enum" (func $match_enum)) + (export "__main" (func $__main)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) (func $match_enum (;0;) (type 0) (param i32 i32 i32) (result i32) block ;; label = @1 block ;; label = @2 @@ -40,13 +49,4 @@ call $match_enum i32.add ) - (memory (;0;) 16) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (global (;1;) i32 i32.const 1048576) - (global (;2;) i32 i32.const 1048576) - (export "memory" (memory 0)) - (export "match_enum" (func $match_enum)) - (export "__main" (func $__main)) - (export "__data_end" (global 1)) - (export "__heap_base" (global 2)) -) \ No newline at end of file +) diff --git a/tests/integration/expected/types/static_mut.hir b/tests/integration/expected/types/static_mut.hir index d57f4c178..28650d2dd 100644 --- a/tests/integration/expected/types/static_mut.hir +++ b/tests/integration/expected/types/static_mut.hir @@ -1,75 +1,75 @@ -(component - ;; Modules - (module #test_rust_e6d553fb1c80aef6e5d6f2891701197bedac471cf510bd2495f99889d9543cd4 - ;; Data Segments - (data (offset 1048576) 0x010203040506070809) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_e6d553fb1c80aef6e5d6f2891701197bedac471cf510bd2495f99889d9543cd4 { + public builtin.function @global_var_update() { + ^block6: + v3 = arith.constant 1048577 : u32; + v38 = arith.constant 0 : u32; + v4 = arith.add v38, v3 : u32 #[overflow = checked]; + v5 = hir.int_to_ptr v4 : ptr; + v6 = hir.load v5 : u8; + v9 = arith.constant 1 : i32; + v7 = arith.zext v6 : u32; + v8 = hir.bitcast v7 : i32; + v10 = arith.add v8, v9 : i32 #[overflow = wrapping]; + v11 = hir.bitcast v10 : u32; + v12 = arith.trunc v11 : u8; + v14 = arith.constant 1048576 : u32; + v39 = arith.constant 0 : u32; + v15 = arith.add v39, v14 : u32 #[overflow = checked]; + v16 = hir.int_to_ptr v15 : ptr; + hir.store v16, v12; + builtin.ret ; + }; - ;; Constants - (const (id 0) 0x00100000) - (const (id 1) 0x00100009) - (const (id 2) 0x00100010) + public builtin.function @__main() -> i32 { + ^block8: + hir.exec @root_ns:root@1.0.0/test_rust_e6d553fb1c80aef6e5d6f2891701197bedac471cf510bd2495f99889d9543cd4/global_var_update() + v18 = arith.constant 0 : i32; + v20 = arith.constant -9 : i32; + v73, v74, v75 = scf.while v20, v18 : i32, i32, i32 { + ^block21(v76: i32, v77: i32): + v22 = arith.constant 1048585 : i32; + v23 = arith.add v76, v22 : i32 #[overflow = wrapping]; + v24 = hir.bitcast v23 : u32; + v25 = hir.int_to_ptr v24 : ptr; + v26 = hir.load v25 : u8; + v27 = arith.zext v26 : u32; + v28 = hir.bitcast v27 : i32; + v30 = arith.add v28, v77 : i32 #[overflow = wrapping]; + v31 = arith.constant 1 : i32; + v32 = arith.add v76, v31 : i32 #[overflow = wrapping]; + v82 = arith.constant 0 : i32; + v34 = arith.neq v32, v82 : i1; + v81 = ub.poison i32 : i32; + v70 = cf.select v34, v30, v81 : i32; + v47 = ub.poison i32 : i32; + v69 = cf.select v34, v32, v47 : i32; + v41 = arith.constant 0 : u32; + v46 = arith.constant 1 : u32; + v72 = cf.select v34, v46, v41 : u32; + v64 = arith.trunc v72 : i1; + scf.condition v64, v69, v70, v30; + } do { + ^block22(v78: i32, v79: i32, v80: i32): + scf.yield v78, v79; + }; + v35 = arith.constant 255 : i32; + v36 = arith.band v75, v35 : i32; + builtin.ret v36; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 1)) - (global (export #gv2) (id 2) (type i32) (const 2)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #global_var_update) - (block 0 - (let (v0 i32) (const.i32 0)) - (let (v1 i32) (const.i32 0)) - (let (v2 u32) (bitcast v1)) - (let (v3 u32) (add.checked v2 1048577)) - (let (v4 (ptr u8)) (inttoptr v3)) - (let (v5 u8) (load v4)) - (let (v6 i32) (zext v5)) - (let (v7 i32) (const.i32 1)) - (let (v8 i32) (add.wrapping v6 v7)) - (let (v9 u32) (bitcast v8)) - (let (v10 u8) (trunc v9)) - (let (v11 u32) (bitcast v0)) - (let (v12 u32) (add.checked v11 1048576)) - (let (v13 (ptr u8)) (inttoptr v12)) - (store v13 v10) - (br (block 1))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048585; + }; - (block 1 - (ret)) - ) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048592; + }; - (func (export #__main) (result i32) - (block 0 - (let (v1 i32) (const.i32 0)) - (call #global_var_update) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (const.i32 -9)) - (br (block 2 v3 v2))) - - (block 1 (param v0 i32) - (ret v0)) - - (block 2 (param v4 i32) (param v11 i32) - (let (v5 i32) (const.i32 1048585)) - (let (v6 i32) (add.wrapping v4 v5)) - (let (v7 u32) (bitcast v6)) - (let (v8 (ptr u8)) (inttoptr v7)) - (let (v9 u8) (load v8)) - (let (v10 i32) (zext v9)) - (let (v12 i32) (add.wrapping v10 v11)) - (let (v13 i32) (const.i32 1)) - (let (v14 i32) (add.wrapping v4 v13)) - (let (v15 i1) (neq v14 0)) - (condbr v15 (block 2 v14 v12) (block 4))) - - (block 3 - (let (v16 i32) (const.i32 255)) - (let (v17 i32) (band v12 v16)) - (br (block 1 v17))) - - (block 4 - (br (block 3))) - ) - ) - -) + builtin.segment @1048576 = 0x090807060504030201; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/types/static_mut.masm b/tests/integration/expected/types/static_mut.masm index 33962b469..b9fbba4d9 100644 --- a/tests/integration/expected/types/static_mut.masm +++ b/tests/integration/expected/types/static_mut.masm @@ -1,106 +1,144 @@ -# mod test_rust_e6d553fb1c80aef6e5d6f2891701197bedac471cf510bd2495f99889d9543cd4 +# mod root_ns:root@1.0.0 -export."__main" - exec.global_var_update - push.0 - push.4294967287 - push.1048585 - dup.1 - swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - push.128 - u32and - movup.2 - u32wrapping_add - push.1 - movup.2 - swap.1 - u32wrapping_add - dup.0 - neq.0 +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.[11434542961250426374,12905227908764935604,12374698099726530180,495727412639301595] + adv.push_mapval + push.262144 push.1 - while.true - if.true - push.1048585 - dup.1 - swap.1 - u32wrapping_add - dup.0 - u32mod.16 - dup.0 - u32mod.4 - swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - push.128 - u32and - movup.2 - u32wrapping_add - push.1 - movup.2 - swap.1 - u32wrapping_add - dup.0 - neq.0 - push.1 - else - drop push.255 u32and push.0 - end - end + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278536 + push.1048585 + u32assert + mem_store.278537 + push.1048592 + u32assert + mem_store.278538 end +# mod root_ns:root@1.0.0::test_rust_e6d553fb1c80aef6e5d6f2891701197bedac471cf510bd2495f99889d9543cd4 export.global_var_update + push.1048577 push.0 - add.1048577 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr swap.1 - u32div.4 - movup.2 - u32div.16 - exec.::intrinsics::mem::load_sw - push.128 + drop + push.255 u32and push.1 + swap.1 u32wrapping_add - push.128 + push.255 u32and + push.1048576 push.0 - add.1048576 + add u32assert - dup.0 - u32mod.16 - dup.0 - u32mod.4 + u32divmod.4 swap.1 - u32div.4 - movup.2 - u32div.16 - dup.2 - dup.2 + dup.0 + mem_load dup.2 - exec.::intrinsics::mem::load_sw - push.4294967040 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 u32and - movup.5 + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl u32or - movdn.4 - exec.::intrinsics::mem::store_sw + swap.1 + mem_store end +export.__main + trace.240 + nop + exec.::root_ns:root@1.0.0::test_rust_e6d553fb1c80aef6e5d6f2891701197bedac471cf510bd2495f99889d9543cd4::global_var_update + trace.252 + nop + push.0 + push.4294967287 + push.1 + while.true + push.1048585 + dup.1 + u32wrapping_add + u32divmod.4 + swap.1 + swap.1 + dup.1 + mem_load + swap.1 + push.8 + u32wrapping_mul + u32shr + swap.1 + drop + push.255 + u32and + movup.2 + u32wrapping_add + push.1 + movup.2 + u32wrapping_add + push.0 + dup.1 + neq + push.3735929054 + dup.1 + dup.4 + swap.1 + cdrop + push.3735929054 + dup.2 + swap.1 + swap.2 + swap.4 + swap.1 + cdrop + push.0 + push.1 + movup.3 + cdrop + push.1 + u32and + if.true + movup.2 + drop + push.1 + else + push.0 + end + end + drop + drop + push.255 + u32and +end diff --git a/tests/integration/expected/types/static_mut.wat b/tests/integration/expected/types/static_mut.wat index a57bb6154..04aa08a0d 100644 --- a/tests/integration/expected/types/static_mut.wat +++ b/tests/integration/expected/types/static_mut.wat @@ -1,6 +1,15 @@ (module $test_rust_e6d553fb1c80aef6e5d6f2891701197bedac471cf510bd2495f99889d9543cd4.wasm (type (;0;) (func)) (type (;1;) (func (result i32))) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048585) + (global (;2;) i32 i32.const 1048592) + (export "memory" (memory 0)) + (export "global_var_update" (func $global_var_update)) + (export "__main" (func $__main)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2)) (func $global_var_update (;0;) (type 0) i32.const 0 i32.const 0 @@ -36,14 +45,5 @@ i32.const 255 i32.and ) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (global (;1;) i32 i32.const 1048585) - (global (;2;) i32 i32.const 1048592) - (export "memory" (memory 0)) - (export "global_var_update" (func $global_var_update)) - (export "__main" (func $__main)) - (export "__data_end" (global 1)) - (export "__heap_base" (global 2)) (data $.data (;0;) (i32.const 1048576) "\01\02\03\04\05\06\07\08\09") -) \ No newline at end of file +) diff --git a/tests/integration/expected/vec_alloc_new.hir b/tests/integration/expected/vec_alloc_new.hir new file mode 100644 index 000000000..661d316c8 --- /dev/null +++ b/tests/integration/expected/vec_alloc_new.hir @@ -0,0 +1,754 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @vec_alloc_new { + public builtin.function @entrypoint(v0: felt) -> felt { + ^block6(v0: felt): + v4 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v5 = hir.bitcast v4 : ptr; + v6 = hir.load v5 : i32; + v7 = arith.constant 32 : i32; + v8 = arith.sub v6, v7 : i32 #[overflow = wrapping]; + v9 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v10 = hir.bitcast v9 : ptr; + hir.store v10, v8; + v15 = arith.constant 4 : i32; + v2 = arith.constant 0 : i32; + v13 = arith.constant 1 : i32; + v11 = arith.constant 20 : i32; + v12 = arith.add v8, v11 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/alloc::raw_vec::RawVecInner::try_allocate_in(v12, v13, v2, v15, v15) + v18 = arith.constant 24 : u32; + v17 = hir.bitcast v8 : u32; + v19 = arith.add v17, v18 : u32 #[overflow = checked]; + v20 = arith.constant 4 : u32; + v21 = arith.mod v19, v20 : u32; + hir.assertz v21 #[code = 250]; + v22 = hir.int_to_ptr v19 : ptr; + v23 = hir.load v22 : i32; + v25 = arith.constant 20 : u32; + v24 = hir.bitcast v8 : u32; + v26 = arith.add v24, v25 : u32 #[overflow = checked]; + v535 = arith.constant 4 : u32; + v28 = arith.mod v26, v535 : u32; + hir.assertz v28 #[code = 250]; + v29 = hir.int_to_ptr v26 : ptr; + v30 = hir.load v29 : i32; + v533 = arith.constant 0 : i32; + v534 = arith.constant 1 : i32; + v32 = arith.neq v30, v534 : i1; + v33 = arith.zext v32 : u32; + v34 = hir.bitcast v33 : i32; + v36 = arith.neq v34, v533 : i1; + cf.cond_br v36 ^block8, ^block9; + ^block8: + v47 = arith.constant 16 : u32; + v46 = hir.bitcast v8 : u32; + v48 = arith.add v46, v47 : u32 #[overflow = checked]; + v532 = arith.constant 4 : u32; + v50 = arith.mod v48, v532 : u32; + hir.assertz v50 #[code = 250]; + v531 = arith.constant 0 : i32; + v51 = hir.int_to_ptr v48 : ptr; + hir.store v51, v531; + v53 = arith.constant 28 : u32; + v52 = hir.bitcast v8 : u32; + v54 = arith.add v52, v53 : u32 #[overflow = checked]; + v530 = arith.constant 4 : u32; + v56 = arith.mod v54, v530 : u32; + hir.assertz v56 #[code = 250]; + v57 = hir.int_to_ptr v54 : ptr; + v58 = hir.load v57 : i32; + v60 = arith.constant 12 : u32; + v59 = hir.bitcast v8 : u32; + v61 = arith.add v59, v60 : u32 #[overflow = checked]; + v529 = arith.constant 4 : u32; + v63 = arith.mod v61, v529 : u32; + hir.assertz v63 #[code = 250]; + v64 = hir.int_to_ptr v61 : ptr; + hir.store v64, v58; + v66 = arith.constant 8 : u32; + v65 = hir.bitcast v8 : u32; + v67 = arith.add v65, v66 : u32 #[overflow = checked]; + v528 = arith.constant 4 : u32; + v69 = arith.mod v67, v528 : u32; + hir.assertz v69 #[code = 250]; + v70 = hir.int_to_ptr v67 : ptr; + hir.store v70, v23; + v521 = arith.constant 1114128 : felt; + v71 = hir.bitcast v58 : felt; + hir.assert_eq v71, v521; + v527 = arith.constant 4 : i32; + v76 = arith.constant 8 : i32; + v77 = arith.add v8, v76 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/alloc::raw_vec::RawVecInner::deallocate(v77, v527, v527) + v526 = arith.constant 32 : i32; + v81 = arith.add v8, v526 : i32 #[overflow = wrapping]; + v82 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v83 = hir.bitcast v82 : ptr; + hir.store v83, v81; + v520 = arith.constant 0 : felt; + builtin.ret v520; + ^block9: + v525 = arith.constant 28 : u32; + v37 = hir.bitcast v8 : u32; + v39 = arith.add v37, v525 : u32 #[overflow = checked]; + v524 = arith.constant 4 : u32; + v41 = arith.mod v39, v524 : u32; + hir.assertz v41 #[code = 250]; + v42 = hir.int_to_ptr v39 : ptr; + v43 = hir.load v42 : i32; + v44 = arith.constant 1048588 : i32; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/alloc::raw_vec::handle_error(v23, v43, v44) + ub.unreachable ; + }; + + private builtin.function @__rustc::__rust_alloc(v84: i32, v85: i32) -> i32 { + ^block10(v84: i32, v85: i32): + v87 = arith.constant 1048604 : i32; + v88 = hir.exec @root_ns:root@1.0.0/vec_alloc_new/::alloc(v87, v85, v84) : i32 + builtin.ret v88; + }; + + private builtin.function @__rustc::__rust_dealloc(v89: i32, v90: i32, v91: i32) { + ^block12(v89: i32, v90: i32, v91: i32): + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_alloc_zeroed(v92: i32, v93: i32) -> i32 { + ^block14(v92: i32, v93: i32): + v95 = arith.constant 1048604 : i32; + v96 = hir.exec @root_ns:root@1.0.0/vec_alloc_new/::alloc(v95, v93, v92) : i32 + v544 = arith.constant 0 : i32; + v97 = arith.constant 0 : i32; + v98 = arith.eq v96, v97 : i1; + v99 = arith.zext v98 : u32; + v100 = hir.bitcast v99 : i32; + v102 = arith.neq v100, v544 : i1; + scf.if v102{ + ^block16: + scf.yield ; + } else { + ^block17: + v542 = arith.constant 0 : i32; + v543 = arith.constant 0 : i32; + v104 = arith.eq v92, v543 : i1; + v105 = arith.zext v104 : u32; + v106 = hir.bitcast v105 : i32; + v108 = arith.neq v106, v542 : i1; + scf.if v108{ + ^block71: + scf.yield ; + } else { + ^block18: + v536 = arith.constant 0 : u8; + v111 = hir.bitcast v92 : u32; + v112 = hir.bitcast v96 : u32; + v113 = hir.int_to_ptr v112 : ptr; + hir.mem_set v113, v111, v536; + scf.yield ; + }; + scf.yield ; + }; + builtin.ret v96; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block19: + builtin.ret ; + }; + + private builtin.function @::alloc(v115: i32, v116: i32, v117: i32) -> i32 { + ^block21(v115: i32, v116: i32, v117: i32): + v120 = arith.constant 16 : i32; + v119 = arith.constant 0 : i32; + v546 = arith.constant 16 : u32; + v122 = hir.bitcast v116 : u32; + v124 = arith.gt v122, v546 : i1; + v125 = arith.zext v124 : u32; + v126 = hir.bitcast v125 : i32; + v128 = arith.neq v126, v119 : i1; + v129 = cf.select v128, v116, v120 : i32; + v585 = arith.constant 0 : i32; + v130 = arith.constant -1 : i32; + v131 = arith.add v129, v130 : i32 #[overflow = wrapping]; + v132 = arith.band v129, v131 : i32; + v134 = arith.neq v132, v585 : i1; + v555, v556 = scf.if v134 : i32, u32 { + ^block76: + v547 = arith.constant 0 : u32; + v551 = ub.poison i32 : i32; + scf.yield v551, v547; + } else { + ^block24: + v136 = hir.exec @root_ns:root@1.0.0/vec_alloc_new/core::ptr::alignment::Alignment::max(v116, v129) : i32 + v584 = arith.constant 0 : i32; + v135 = arith.constant -2147483648 : i32; + v137 = arith.sub v135, v136 : i32 #[overflow = wrapping]; + v139 = hir.bitcast v137 : u32; + v138 = hir.bitcast v117 : u32; + v140 = arith.gt v138, v139 : i1; + v141 = arith.zext v140 : u32; + v142 = hir.bitcast v141 : i32; + v144 = arith.neq v142, v584 : i1; + v570 = scf.if v144 : i32 { + ^block75: + v583 = ub.poison i32 : i32; + scf.yield v583; + } else { + ^block25: + v581 = arith.constant 0 : i32; + v150 = arith.sub v581, v136 : i32 #[overflow = wrapping]; + v582 = arith.constant -1 : i32; + v146 = arith.add v117, v136 : i32 #[overflow = wrapping]; + v148 = arith.add v146, v582 : i32 #[overflow = wrapping]; + v151 = arith.band v148, v150 : i32; + v152 = hir.bitcast v115 : u32; + v153 = arith.constant 4 : u32; + v154 = arith.mod v152, v153 : u32; + hir.assertz v154 #[code = 250]; + v155 = hir.int_to_ptr v152 : ptr; + v156 = hir.load v155 : i32; + v580 = arith.constant 0 : i32; + v158 = arith.neq v156, v580 : i1; + scf.if v158{ + ^block74: + scf.yield ; + } else { + ^block27: + v159 = hir.exec @intrinsics/mem/heap_base() : i32 + v160 = hir.mem_size : u32; + v166 = hir.bitcast v115 : u32; + v579 = arith.constant 4 : u32; + v168 = arith.mod v166, v579 : u32; + hir.assertz v168 #[code = 250]; + v578 = arith.constant 16 : u32; + v161 = hir.bitcast v160 : i32; + v164 = arith.shl v161, v578 : i32; + v165 = arith.add v159, v164 : i32 #[overflow = wrapping]; + v169 = hir.int_to_ptr v166 : ptr; + hir.store v169, v165; + scf.yield ; + }; + v172 = hir.bitcast v115 : u32; + v577 = arith.constant 4 : u32; + v174 = arith.mod v172, v577 : u32; + hir.assertz v174 #[code = 250]; + v175 = hir.int_to_ptr v172 : ptr; + v176 = hir.load v175 : i32; + v576 = arith.constant 0 : i32; + v180 = hir.bitcast v151 : u32; + v170 = arith.constant 268435456 : i32; + v177 = arith.sub v170, v176 : i32 #[overflow = wrapping]; + v179 = hir.bitcast v177 : u32; + v181 = arith.lt v179, v180 : i1; + v182 = arith.zext v181 : u32; + v183 = hir.bitcast v182 : i32; + v185 = arith.neq v183, v576 : i1; + v569 = scf.if v185 : i32 { + ^block28: + v575 = arith.constant 0 : i32; + scf.yield v575; + } else { + ^block29: + v187 = hir.bitcast v115 : u32; + v574 = arith.constant 4 : u32; + v189 = arith.mod v187, v574 : u32; + hir.assertz v189 #[code = 250]; + v186 = arith.add v176, v151 : i32 #[overflow = wrapping]; + v190 = hir.int_to_ptr v187 : ptr; + hir.store v190, v186; + v192 = arith.add v176, v136 : i32 #[overflow = wrapping]; + scf.yield v192; + }; + scf.yield v569; + }; + v552 = arith.constant 1 : u32; + v573 = arith.constant 0 : u32; + v571 = cf.select v144, v573, v552 : u32; + scf.yield v570, v571; + }; + v572 = arith.constant 0 : u32; + v568 = arith.eq v556, v572 : i1; + cf.cond_br v568 ^block23, ^block78(v555); + ^block23: + ub.unreachable ; + ^block78(v548: i32): + builtin.ret v548; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::deallocate(v195: i32, v196: i32, v197: i32) { + ^block30(v195: i32, v196: i32, v197: i32): + v199 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v200 = hir.bitcast v199 : ptr; + v201 = hir.load v200 : i32; + v202 = arith.constant 16 : i32; + v203 = arith.sub v201, v202 : i32 #[overflow = wrapping]; + v204 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v205 = hir.bitcast v204 : ptr; + hir.store v205, v203; + v206 = arith.constant 4 : i32; + v207 = arith.add v203, v206 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/alloc::raw_vec::RawVecInner::current_memory(v207, v195, v196, v197) + v209 = arith.constant 8 : u32; + v208 = hir.bitcast v203 : u32; + v210 = arith.add v208, v209 : u32 #[overflow = checked]; + v211 = arith.constant 4 : u32; + v212 = arith.mod v210, v211 : u32; + hir.assertz v212 #[code = 250]; + v213 = hir.int_to_ptr v210 : ptr; + v214 = hir.load v213 : i32; + v592 = arith.constant 0 : i32; + v198 = arith.constant 0 : i32; + v216 = arith.eq v214, v198 : i1; + v217 = arith.zext v216 : u32; + v218 = hir.bitcast v217 : i32; + v220 = arith.neq v218, v592 : i1; + scf.if v220{ + ^block82: + scf.yield ; + } else { + ^block33: + v591 = arith.constant 4 : u32; + v221 = hir.bitcast v203 : u32; + v223 = arith.add v221, v591 : u32 #[overflow = checked]; + v590 = arith.constant 4 : u32; + v225 = arith.mod v223, v590 : u32; + hir.assertz v225 #[code = 250]; + v226 = hir.int_to_ptr v223 : ptr; + v227 = hir.load v226 : i32; + v229 = arith.constant 12 : u32; + v228 = hir.bitcast v203 : u32; + v230 = arith.add v228, v229 : u32 #[overflow = checked]; + v589 = arith.constant 4 : u32; + v232 = arith.mod v230, v589 : u32; + hir.assertz v232 #[code = 250]; + v233 = hir.int_to_ptr v230 : ptr; + v234 = hir.load v233 : i32; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/::deallocate(v227, v214, v234) + scf.yield ; + }; + v588 = arith.constant 16 : i32; + v237 = arith.add v203, v588 : i32 #[overflow = wrapping]; + v238 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v239 = hir.bitcast v238 : ptr; + hir.store v239, v237; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::try_allocate_in(v240: i32, v241: i32, v242: i32, v243: i32, v244: i32) { + ^block34(v240: i32, v241: i32, v242: i32, v243: i32, v244: i32): + v247 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v248 = hir.bitcast v247 : ptr; + v249 = hir.load v248 : i32; + v250 = arith.constant 16 : i32; + v251 = arith.sub v249, v250 : i32 #[overflow = wrapping]; + v252 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v253 = hir.bitcast v252 : ptr; + hir.store v253, v251; + v263 = hir.bitcast v241 : u32; + v264 = arith.zext v263 : u64; + v265 = hir.bitcast v264 : i64; + v245 = arith.constant 0 : i32; + v258 = arith.sub v245, v243 : i32 #[overflow = wrapping]; + v255 = arith.constant -1 : i32; + v254 = arith.add v243, v244 : i32 #[overflow = wrapping]; + v256 = arith.add v254, v255 : i32 #[overflow = wrapping]; + v259 = arith.band v256, v258 : i32; + v260 = hir.bitcast v259 : u32; + v261 = arith.zext v260 : u64; + v262 = hir.bitcast v261 : i64; + v266 = arith.mul v262, v265 : i64 #[overflow = wrapping]; + v696 = arith.constant 0 : i32; + v267 = arith.constant 32 : i64; + v269 = hir.cast v267 : u32; + v268 = hir.bitcast v266 : u64; + v270 = arith.shr v268, v269 : u64; + v271 = hir.bitcast v270 : i64; + v272 = arith.trunc v271 : i32; + v274 = arith.neq v272, v696 : i1; + v608, v609, v610, v611, v612, v613 = scf.if v274 : i32, i32, i32, i32, i32, u32 { + ^block84: + v593 = arith.constant 0 : u32; + v600 = ub.poison i32 : i32; + scf.yield v240, v251, v600, v600, v600, v593; + } else { + ^block39: + v275 = arith.trunc v266 : i32; + v695 = arith.constant 0 : i32; + v276 = arith.constant -2147483648 : i32; + v277 = arith.sub v276, v243 : i32 #[overflow = wrapping]; + v279 = hir.bitcast v277 : u32; + v278 = hir.bitcast v275 : u32; + v280 = arith.lte v278, v279 : i1; + v281 = arith.zext v280 : u32; + v282 = hir.bitcast v281 : i32; + v284 = arith.neq v282, v695 : i1; + v656 = scf.if v284 : i32 { + ^block37: + v694 = arith.constant 0 : i32; + v295 = arith.neq v275, v694 : i1; + v655 = scf.if v295 : i32 { + ^block41: + v693 = arith.constant 0 : i32; + v311 = arith.neq v242, v693 : i1; + v654 = scf.if v311 : i32 { + ^block44: + v293 = arith.constant 1 : i32; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/alloc::alloc::Global::alloc_impl(v251, v243, v275, v293) + v322 = hir.bitcast v251 : u32; + v367 = arith.constant 4 : u32; + v324 = arith.mod v322, v367 : u32; + hir.assertz v324 #[code = 250]; + v325 = hir.int_to_ptr v322 : ptr; + v326 = hir.load v325 : i32; + scf.yield v326; + } else { + ^block45: + v312 = arith.constant 8 : i32; + v313 = arith.add v251, v312 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/::allocate(v313, v243, v275) + v297 = arith.constant 8 : u32; + v314 = hir.bitcast v251 : u32; + v316 = arith.add v314, v297 : u32 #[overflow = checked]; + v692 = arith.constant 4 : u32; + v318 = arith.mod v316, v692 : u32; + hir.assertz v318 #[code = 250]; + v319 = hir.int_to_ptr v316 : ptr; + v320 = hir.load v319 : i32; + scf.yield v320; + }; + v690 = arith.constant 0 : i32; + v691 = arith.constant 0 : i32; + v329 = arith.eq v654, v691 : i1; + v330 = arith.zext v329 : u32; + v331 = hir.bitcast v330 : i32; + v333 = arith.neq v331, v690 : i1; + scf.if v333{ + ^block46: + v689 = arith.constant 8 : u32; + v350 = hir.bitcast v240 : u32; + v352 = arith.add v350, v689 : u32 #[overflow = checked]; + v688 = arith.constant 4 : u32; + v354 = arith.mod v352, v688 : u32; + hir.assertz v354 #[code = 250]; + v355 = hir.int_to_ptr v352 : ptr; + hir.store v355, v275; + v687 = arith.constant 4 : u32; + v357 = hir.bitcast v240 : u32; + v359 = arith.add v357, v687 : u32 #[overflow = checked]; + v686 = arith.constant 4 : u32; + v361 = arith.mod v359, v686 : u32; + hir.assertz v361 #[code = 250]; + v362 = hir.int_to_ptr v359 : ptr; + hir.store v362, v243; + scf.yield ; + } else { + ^block47: + v685 = arith.constant 8 : u32; + v335 = hir.bitcast v240 : u32; + v337 = arith.add v335, v685 : u32 #[overflow = checked]; + v684 = arith.constant 4 : u32; + v339 = arith.mod v337, v684 : u32; + hir.assertz v339 #[code = 250]; + v340 = hir.int_to_ptr v337 : ptr; + hir.store v340, v654; + v683 = arith.constant 4 : u32; + v342 = hir.bitcast v240 : u32; + v344 = arith.add v342, v683 : u32 #[overflow = checked]; + v682 = arith.constant 4 : u32; + v346 = arith.mod v344, v682 : u32; + hir.assertz v346 #[code = 250]; + v347 = hir.int_to_ptr v344 : ptr; + hir.store v347, v241; + scf.yield ; + }; + v680 = arith.constant 0 : i32; + v681 = arith.constant 1 : i32; + v653 = cf.select v333, v681, v680 : i32; + scf.yield v653; + } else { + ^block42: + v679 = arith.constant 8 : u32; + v296 = hir.bitcast v240 : u32; + v298 = arith.add v296, v679 : u32 #[overflow = checked]; + v678 = arith.constant 4 : u32; + v300 = arith.mod v298, v678 : u32; + hir.assertz v300 #[code = 250]; + v301 = hir.int_to_ptr v298 : ptr; + hir.store v301, v243; + v677 = arith.constant 4 : u32; + v304 = hir.bitcast v240 : u32; + v306 = arith.add v304, v677 : u32 #[overflow = checked]; + v676 = arith.constant 4 : u32; + v308 = arith.mod v306, v676 : u32; + hir.assertz v308 #[code = 250]; + v675 = arith.constant 0 : i32; + v309 = hir.int_to_ptr v306 : ptr; + hir.store v309, v675; + v674 = arith.constant 0 : i32; + scf.yield v674; + }; + scf.yield v655; + } else { + ^block40: + v673 = ub.poison i32 : i32; + scf.yield v673; + }; + v668 = arith.constant 0 : u32; + v601 = arith.constant 1 : u32; + v661 = cf.select v284, v601, v668 : u32; + v669 = ub.poison i32 : i32; + v660 = cf.select v284, v251, v669 : i32; + v670 = ub.poison i32 : i32; + v659 = cf.select v284, v240, v670 : i32; + v671 = ub.poison i32 : i32; + v658 = cf.select v284, v671, v251 : i32; + v672 = ub.poison i32 : i32; + v657 = cf.select v284, v672, v240 : i32; + scf.yield v657, v658, v659, v656, v660, v661; + }; + v614, v615, v616 = scf.index_switch v613 : i32, i32, i32 + case 0 { + ^block38: + v667 = arith.constant 4 : u32; + v287 = hir.bitcast v608 : u32; + v289 = arith.add v287, v667 : u32 #[overflow = checked]; + v666 = arith.constant 4 : u32; + v291 = arith.mod v289, v666 : u32; + hir.assertz v291 #[code = 250]; + v665 = arith.constant 0 : i32; + v292 = hir.int_to_ptr v289 : ptr; + hir.store v292, v665; + v664 = arith.constant 1 : i32; + scf.yield v608, v664, v609; + } + default { + ^block88: + scf.yield v610, v611, v612; + }; + v366 = hir.bitcast v614 : u32; + v663 = arith.constant 4 : u32; + v368 = arith.mod v366, v663 : u32; + hir.assertz v368 #[code = 250]; + v369 = hir.int_to_ptr v366 : ptr; + hir.store v369, v615; + v662 = arith.constant 16 : i32; + v374 = arith.add v616, v662 : i32 #[overflow = wrapping]; + v375 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v376 = hir.bitcast v375 : ptr; + hir.store v376, v374; + builtin.ret ; + }; + + private builtin.function @::allocate(v377: i32, v378: i32, v379: i32) { + ^block48(v377: i32, v378: i32, v379: i32): + v381 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v382 = hir.bitcast v381 : ptr; + v383 = hir.load v382 : i32; + v384 = arith.constant 16 : i32; + v385 = arith.sub v383, v384 : i32 #[overflow = wrapping]; + v386 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v387 = hir.bitcast v386 : ptr; + hir.store v387, v385; + v380 = arith.constant 0 : i32; + v388 = arith.constant 8 : i32; + v389 = arith.add v385, v388 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/vec_alloc_new/alloc::alloc::Global::alloc_impl(v389, v378, v379, v380) + v392 = arith.constant 12 : u32; + v391 = hir.bitcast v385 : u32; + v393 = arith.add v391, v392 : u32 #[overflow = checked]; + v394 = arith.constant 4 : u32; + v395 = arith.mod v393, v394 : u32; + hir.assertz v395 #[code = 250]; + v396 = hir.int_to_ptr v393 : ptr; + v397 = hir.load v396 : i32; + v399 = arith.constant 8 : u32; + v398 = hir.bitcast v385 : u32; + v400 = arith.add v398, v399 : u32 #[overflow = checked]; + v701 = arith.constant 4 : u32; + v402 = arith.mod v400, v701 : u32; + hir.assertz v402 #[code = 250]; + v403 = hir.int_to_ptr v400 : ptr; + v404 = hir.load v403 : i32; + v405 = hir.bitcast v377 : u32; + v700 = arith.constant 4 : u32; + v407 = arith.mod v405, v700 : u32; + hir.assertz v407 #[code = 250]; + v408 = hir.int_to_ptr v405 : ptr; + hir.store v408, v404; + v699 = arith.constant 4 : u32; + v409 = hir.bitcast v377 : u32; + v411 = arith.add v409, v699 : u32 #[overflow = checked]; + v698 = arith.constant 4 : u32; + v413 = arith.mod v411, v698 : u32; + hir.assertz v413 #[code = 250]; + v414 = hir.int_to_ptr v411 : ptr; + hir.store v414, v397; + v697 = arith.constant 16 : i32; + v416 = arith.add v385, v697 : i32 #[overflow = wrapping]; + v417 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_new/__stack_pointer : ptr + v418 = hir.bitcast v417 : ptr; + hir.store v418, v416; + builtin.ret ; + }; + + private builtin.function @alloc::alloc::Global::alloc_impl(v419: i32, v420: i32, v421: i32, v422: i32) { + ^block50(v419: i32, v420: i32, v421: i32, v422: i32): + v717 = arith.constant 0 : i32; + v423 = arith.constant 0 : i32; + v424 = arith.eq v421, v423 : i1; + v425 = arith.zext v424 : u32; + v426 = hir.bitcast v425 : i32; + v428 = arith.neq v426, v717 : i1; + v713 = scf.if v428 : i32 { + ^block91: + scf.yield v420; + } else { + ^block53: + hir.exec @root_ns:root@1.0.0/vec_alloc_new/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v716 = arith.constant 0 : i32; + v430 = arith.neq v422, v716 : i1; + v712 = scf.if v430 : i32 { + ^block54: + v432 = hir.exec @root_ns:root@1.0.0/vec_alloc_new/__rustc::__rust_alloc_zeroed(v421, v420) : i32 + scf.yield v432; + } else { + ^block55: + v431 = hir.exec @root_ns:root@1.0.0/vec_alloc_new/__rustc::__rust_alloc(v421, v420) : i32 + scf.yield v431; + }; + scf.yield v712; + }; + v436 = arith.constant 4 : u32; + v435 = hir.bitcast v419 : u32; + v437 = arith.add v435, v436 : u32 #[overflow = checked]; + v715 = arith.constant 4 : u32; + v439 = arith.mod v437, v715 : u32; + hir.assertz v439 #[code = 250]; + v440 = hir.int_to_ptr v437 : ptr; + hir.store v440, v421; + v442 = hir.bitcast v419 : u32; + v714 = arith.constant 4 : u32; + v444 = arith.mod v442, v714 : u32; + hir.assertz v444 #[code = 250]; + v445 = hir.int_to_ptr v442 : ptr; + hir.store v445, v713; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::current_memory(v446: i32, v447: i32, v448: i32, v449: i32) { + ^block56(v446: i32, v447: i32, v448: i32, v449: i32): + v743 = arith.constant 0 : i32; + v450 = arith.constant 0 : i32; + v454 = arith.eq v449, v450 : i1; + v455 = arith.zext v454 : u32; + v456 = hir.bitcast v455 : i32; + v458 = arith.neq v456, v743 : i1; + v730, v731 = scf.if v458 : i32, i32 { + ^block95: + v742 = arith.constant 0 : i32; + v452 = arith.constant 4 : i32; + scf.yield v452, v742; + } else { + ^block59: + v459 = hir.bitcast v447 : u32; + v494 = arith.constant 4 : u32; + v461 = arith.mod v459, v494 : u32; + hir.assertz v461 #[code = 250]; + v462 = hir.int_to_ptr v459 : ptr; + v463 = hir.load v462 : i32; + v740 = arith.constant 0 : i32; + v741 = arith.constant 0 : i32; + v465 = arith.eq v463, v741 : i1; + v466 = arith.zext v465 : u32; + v467 = hir.bitcast v466 : i32; + v469 = arith.neq v467, v740 : i1; + v728 = scf.if v469 : i32 { + ^block94: + v739 = arith.constant 0 : i32; + scf.yield v739; + } else { + ^block60: + v738 = arith.constant 4 : u32; + v470 = hir.bitcast v446 : u32; + v472 = arith.add v470, v738 : u32 #[overflow = checked]; + v737 = arith.constant 4 : u32; + v474 = arith.mod v472, v737 : u32; + hir.assertz v474 #[code = 250]; + v475 = hir.int_to_ptr v472 : ptr; + hir.store v475, v448; + v736 = arith.constant 4 : u32; + v476 = hir.bitcast v447 : u32; + v478 = arith.add v476, v736 : u32 #[overflow = checked]; + v735 = arith.constant 4 : u32; + v480 = arith.mod v478, v735 : u32; + hir.assertz v480 #[code = 250]; + v481 = hir.int_to_ptr v478 : ptr; + v482 = hir.load v481 : i32; + v483 = hir.bitcast v446 : u32; + v734 = arith.constant 4 : u32; + v485 = arith.mod v483, v734 : u32; + hir.assertz v485 #[code = 250]; + v486 = hir.int_to_ptr v483 : ptr; + hir.store v486, v482; + v487 = arith.mul v463, v449 : i32 #[overflow = wrapping]; + scf.yield v487; + }; + v488 = arith.constant 8 : i32; + v733 = arith.constant 4 : i32; + v729 = cf.select v469, v733, v488 : i32; + scf.yield v729, v728; + }; + v491 = arith.add v446, v730 : i32 #[overflow = wrapping]; + v493 = hir.bitcast v491 : u32; + v732 = arith.constant 4 : u32; + v495 = arith.mod v493, v732 : u32; + hir.assertz v495 #[code = 250]; + v496 = hir.int_to_ptr v493 : ptr; + hir.store v496, v731; + builtin.ret ; + }; + + private builtin.function @::deallocate(v497: i32, v498: i32, v499: i32) { + ^block61(v497: i32, v498: i32, v499: i32): + v745 = arith.constant 0 : i32; + v500 = arith.constant 0 : i32; + v501 = arith.eq v499, v500 : i1; + v502 = arith.zext v501 : u32; + v503 = hir.bitcast v502 : i32; + v505 = arith.neq v503, v745 : i1; + scf.if v505{ + ^block63: + scf.yield ; + } else { + ^block64: + hir.exec @root_ns:root@1.0.0/vec_alloc_new/__rustc::__rust_dealloc(v497, v499, v498) + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::handle_error(v506: i32, v507: i32, v508: i32) { + ^block65(v506: i32, v507: i32, v508: i32): + ub.unreachable ; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v509: i32, v510: i32) -> i32 { + ^block67(v509: i32, v510: i32): + v517 = arith.constant 0 : i32; + v513 = hir.bitcast v510 : u32; + v512 = hir.bitcast v509 : u32; + v514 = arith.gt v512, v513 : i1; + v515 = arith.zext v514 : u32; + v516 = hir.bitcast v515 : i32; + v518 = arith.neq v516, v517 : i1; + v519 = cf.select v518, v509, v510 : i32; + builtin.ret v519; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + + builtin.segment readonly @1048576 = 0x00000030000000190000000a00100000000073722e62696c2f637273; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/vec_alloc_new.masm b/tests/integration/expected/vec_alloc_new.masm new file mode 100644 index 000000000..b671f539b --- /dev/null +++ b/tests/integration/expected/vec_alloc_new.masm @@ -0,0 +1,1392 @@ +# mod root_ns:root@1.0.0 + +export.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.14263334321981421119 + push.9578538975708744562 + push.10650686308652434678 + push.8285110771116136771 + adv.push_mapval + push.262144 + push.2 + trace.240 + exec.::std::mem::pipe_preimage_to_memory + trace.252 + drop + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::vec_alloc_new + +export.entrypoint + drop + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.32 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + push.0 + push.1 + push.20 + dup.4 + swap.1 + u32wrapping_add + dup.3 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::alloc::raw_vec::RawVecInner::try_allocate_in + trace.252 + nop + push.24 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.20 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.1 + movup.2 + swap.1 + neq + neq + if.true + push.16 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.28 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1114128 + swap.1 + assert_eq + push.4 + push.8 + dup.2 + swap.1 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + push.32 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + else + push.28 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.1048588 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::alloc::raw_vec::handle_error + trace.252 + nop + push.0 + assert + end +end + +proc.__rustc::__rust_alloc + push.1048604 + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::::alloc + trace.252 + nop +end + +proc.__rustc::__rust_dealloc + drop + drop + drop +end + +proc.__rustc::__rust_alloc_zeroed + push.1048604 + dup.1 + swap.2 + swap.3 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::::alloc + trace.252 + nop + push.0 + push.0 + dup.2 + swap.1 + eq + neq + if.true + swap.1 + drop + else + push.0 + push.0 + dup.3 + swap.1 + eq + neq + if.true + swap.1 + drop + else + push.0 + movup.2 + dup.2 + push.0 + dup.2 + push.0 + gte + while.true + dup.1 + dup.1 + push.1 + u32overflowing_madd + assertz + dup.4 + swap.1 + u32divmod.4 + swap.1 + dup.0 + mem_load + dup.2 + push.8 + u32wrapping_mul + push.255 + swap.1 + u32shl + u32not + swap.1 + u32and + movup.3 + movup.3 + push.8 + u32wrapping_mul + u32shl + u32or + swap.1 + mem_store + u32wrapping_add.1 + dup.0 + dup.3 + u32gte + end + dropw + end + end +end + +proc.__rustc::__rust_no_alloc_shim_is_unstable_v2 + nop +end + +proc.::alloc + push.16 + push.0 + push.16 + dup.4 + swap.1 + u32gt + neq + dup.3 + swap.1 + cdrop + push.0 + push.4294967295 + dup.2 + swap.1 + u32wrapping_add + dup.2 + swap.1 + u32and + neq + if.true + dropw + push.0 + push.3735929054 + else + movup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::core::ptr::alignment::Alignment::max + trace.252 + nop + push.0 + push.2147483648 + dup.2 + u32wrapping_sub + dup.4 + swap.1 + u32gt + neq + dup.0 + if.true + movdn.3 + drop + drop + drop + push.3735929054 + else + push.0 + dup.2 + u32wrapping_sub + push.4294967295 + movup.5 + dup.4 + u32wrapping_add + u32wrapping_add + u32and + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + neq + if.true + nop + else + trace.240 + nop + exec.::intrinsics::mem::heap_base + trace.252 + nop + trace.240 + nop + exec.::intrinsics::mem::memory_size + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.16 + movup.2 + swap.1 + u32shl + movup.2 + swap.1 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + dup.2 + push.268435456 + dup.3 + u32wrapping_sub + swap.1 + u32lt + neq + if.true + drop + drop + movdn.2 + drop + drop + push.0 + else + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.1 + movup.3 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + u32wrapping_add + end + end + push.1 + push.0 + movup.3 + cdrop + swap.1 + end + push.0 + movup.2 + swap.1 + eq + if.true + drop + push.0 + assert + else + nop + end +end + +proc.alloc::raw_vec::RawVecInner::deallocate + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + swap.1 + u32wrapping_add + swap.1 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::alloc::raw_vec::RawVecInner::current_memory + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + swap.1 + eq + neq + if.true + drop + else + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movdn.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::::deallocate + trace.252 + nop + end + push.16 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::try_allocate_in + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + dup.2 + push.0 + push.0 + dup.7 + u32wrapping_sub + push.4294967295 + dup.8 + movup.10 + u32wrapping_add + u32wrapping_add + u32and + push.0 + trace.240 + nop + exec.::intrinsics::i64::wrapping_mul + trace.252 + nop + push.0 + push.32 + push.0 + dup.0 + push.2147483648 + u32and + eq.2147483648 + assertz + assertz + dup.0 + push.4294967295 + u32lte + assert + dup.3 + dup.3 + movup.2 + trace.240 + nop + exec.::std::math::u64::shr + trace.252 + nop + drop + neq + if.true + drop + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + push.3735929054 + dup.0 + dup.1 + swap.4 + swap.1 + swap.3 + swap.5 + else + drop + push.0 + push.2147483648 + dup.7 + u32wrapping_sub + dup.2 + swap.1 + u32lte + neq + dup.0 + if.true + push.0 + dup.2 + swap.1 + neq + if.true + push.0 + movup.6 + swap.1 + neq + if.true + push.1 + dup.3 + dup.7 + dup.4 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::alloc::alloc::Global::alloc_impl + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + else + push.8 + dup.3 + swap.1 + u32wrapping_add + dup.6 + dup.3 + swap.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::::allocate + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + end + push.0 + push.0 + dup.2 + swap.1 + eq + neq + dup.0 + if.true + swap.6 + swap.1 + drop + drop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + else + swap.7 + movup.3 + drop + drop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.0 + push.1 + movup.5 + cdrop + else + movup.2 + swap.5 + movdn.2 + swap.4 + swap.1 + drop + drop + drop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.4 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + swap.1 + swap.3 + swap.2 + swap.1 + end + else + swap.1 + drop + movup.3 + drop + movup.3 + drop + movup.3 + drop + push.3735929054 + end + push.0 + push.1 + dup.3 + cdrop + push.3735929054 + dup.3 + dup.5 + swap.1 + cdrop + push.3735929054 + dup.4 + dup.7 + swap.1 + cdrop + push.3735929054 + dup.5 + movup.2 + swap.7 + movdn.2 + cdrop + push.3735929054 + movup.2 + swap.7 + movdn.2 + swap.1 + swap.5 + cdrop + swap.1 + swap.5 + swap.4 + swap.2 + swap.3 + swap.1 + end + movup.5 + eq.0 + if.true + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.4 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.0 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.1 + swap.1 + else + drop + drop + end + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::allocate + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.8 + dup.2 + swap.1 + u32wrapping_add + movup.2 + swap.5 + movdn.2 + swap.1 + swap.3 + swap.4 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::alloc::alloc::Global::alloc_impl + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.8 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.2 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.16 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::alloc::Global::alloc_impl + push.0 + push.0 + dup.4 + swap.1 + eq + neq + if.true + movup.3 + drop + swap.1 + else + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::__rustc::__rust_no_alloc_shim_is_unstable_v2 + trace.252 + nop + push.0 + movup.4 + swap.1 + neq + if.true + swap.1 + dup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::__rustc::__rust_alloc_zeroed + trace.252 + nop + else + swap.1 + dup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::__rustc::__rust_alloc + trace.252 + nop + end + end + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::current_memory + push.0 + push.0 + dup.5 + swap.1 + eq + neq + if.true + movdn.3 + drop + drop + drop + push.0 + push.4 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + swap.1 + eq + neq + dup.0 + if.true + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + else + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.1 + movup.3 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + end + push.8 + push.4 + movup.3 + cdrop + end + movup.2 + swap.1 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::deallocate + push.0 + push.0 + dup.4 + swap.1 + eq + neq + if.true + drop + drop + drop + else + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_new::__rustc::__rust_dealloc + trace.252 + nop + end +end + +proc.alloc::raw_vec::handle_error + drop + drop + drop + push.0 + assert +end + +proc.core::ptr::alignment::Alignment::max + push.0 + dup.2 + dup.2 + swap.1 + u32gt + neq + cdrop +end + diff --git a/tests/integration/expected/vec_alloc_new.wat b/tests/integration/expected/vec_alloc_new.wat new file mode 100644 index 000000000..c397e2f0e --- /dev/null +++ b/tests/integration/expected/vec_alloc_new.wat @@ -0,0 +1,441 @@ +(module $vec_alloc_new.wasm + (type (;0;) (func (param i32) (result f32))) + (type (;1;) (func (param f32 f32))) + (type (;2;) (func (result i32))) + (type (;3;) (func (param f32) (result f32))) + (type (;4;) (func (param i32 i32) (result i32))) + (type (;5;) (func (param i32 i32 i32))) + (type (;6;) (func)) + (type (;7;) (func (param i32 i32 i32) (result i32))) + (type (;8;) (func (param i32 i32 i32 i32 i32))) + (type (;9;) (func (param i32 i32 i32 i32))) + (import "miden:core-intrinsics/intrinsics-felt@1.0.0" "from-u32" (func $miden_stdlib_sys::intrinsics::felt::extern_from_u32 (;0;) (type 0))) + (import "miden:core-intrinsics/intrinsics-felt@1.0.0" "assert-eq" (func $miden_stdlib_sys::intrinsics::felt::extern_assert_eq (;1;) (type 1))) + (import "miden:core-intrinsics/intrinsics-mem@1.0.0" "heap-base" (func $miden_sdk_alloc::heap_base (;2;) (type 2))) + (table (;0;) 1 1 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;3;) (type 3) (param f32) (result f32) + (local i32 i32 i32 f32) + global.get $__stack_pointer + i32.const 32 + i32.sub + local.tee 1 + global.set $__stack_pointer + local.get 1 + i32.const 20 + i32.add + i32.const 1 + i32.const 0 + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::try_allocate_in + local.get 1 + i32.load offset=24 + local.set 2 + block ;; label = @1 + local.get 1 + i32.load offset=20 + i32.const 1 + i32.ne + br_if 0 (;@1;) + local.get 2 + local.get 1 + i32.load offset=28 + i32.const 1048588 + call $alloc::raw_vec::handle_error + unreachable + end + local.get 1 + i32.const 0 + i32.store offset=16 + local.get 1 + local.get 1 + i32.load offset=28 + local.tee 3 + i32.store offset=12 + local.get 1 + local.get 2 + i32.store offset=8 + local.get 3 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + i32.const 1114128 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + call $miden_stdlib_sys::intrinsics::felt::extern_assert_eq + i32.const 0 + call $miden_stdlib_sys::intrinsics::felt::extern_from_u32 + local.set 4 + local.get 1 + i32.const 8 + i32.add + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 1 + i32.const 32 + i32.add + global.set $__stack_pointer + local.get 4 + ) + (func $__rustc::__rust_alloc (;4;) (type 4) (param i32 i32) (result i32) + i32.const 1048604 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_dealloc (;5;) (type 5) (param i32 i32 i32)) + (func $__rustc::__rust_alloc_zeroed (;6;) (type 4) (param i32 i32) (result i32) + block ;; label = @1 + i32.const 1048604 + local.get 1 + local.get 0 + call $::alloc + local.tee 1 + i32.eqz + br_if 0 (;@1;) + local.get 0 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.const 0 + local.get 0 + memory.fill + end + local.get 1 + ) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;7;) (type 6) + return + ) + (func $::alloc (;8;) (type 7) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $miden_sdk_alloc::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + i32.const 268435456 + local.get 0 + i32.load + local.tee 4 + i32.sub + local.get 2 + i32.lt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;9;) (type 5) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::try_allocate_in (;10;) (type 8) (param i32 i32 i32 i32 i32) + (local i32 i64) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 5 + global.set $__stack_pointer + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 3 + local.get 4 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 3 + i32.sub + i32.and + i64.extend_i32_u + local.get 1 + i64.extend_i32_u + i64.mul + local.tee 6 + i64.const 32 + i64.shr_u + i32.wrap_i64 + br_if 0 (;@3;) + local.get 6 + i32.wrap_i64 + local.tee 4 + i32.const -2147483648 + local.get 3 + i32.sub + i32.le_u + br_if 1 (;@2;) + end + local.get 0 + i32.const 0 + i32.store offset=4 + i32.const 1 + local.set 3 + br 1 (;@1;) + end + block ;; label = @2 + local.get 4 + br_if 0 (;@2;) + local.get 0 + local.get 3 + i32.store offset=8 + i32.const 0 + local.set 3 + local.get 0 + i32.const 0 + i32.store offset=4 + br 1 (;@1;) + end + block ;; label = @2 + block ;; label = @3 + local.get 2 + br_if 0 (;@3;) + local.get 5 + i32.const 8 + i32.add + local.get 3 + local.get 4 + call $::allocate + local.get 5 + i32.load offset=8 + local.set 2 + br 1 (;@2;) + end + local.get 5 + local.get 3 + local.get 4 + i32.const 1 + call $alloc::alloc::Global::alloc_impl + local.get 5 + i32.load + local.set 2 + end + block ;; label = @2 + local.get 2 + i32.eqz + br_if 0 (;@2;) + local.get 0 + local.get 2 + i32.store offset=8 + local.get 0 + local.get 1 + i32.store offset=4 + i32.const 0 + local.set 3 + br 1 (;@1;) + end + local.get 0 + local.get 4 + i32.store offset=8 + local.get 0 + local.get 3 + i32.store offset=4 + i32.const 1 + local.set 3 + end + local.get 0 + local.get 3 + i32.store + local.get 5 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $::allocate (;11;) (type 5) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 8 + i32.add + local.get 1 + local.get 2 + i32.const 0 + call $alloc::alloc::Global::alloc_impl + local.get 3 + i32.load offset=12 + local.set 2 + local.get 0 + local.get 3 + i32.load offset=8 + i32.store + local.get 0 + local.get 2 + i32.store offset=4 + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::alloc::Global::alloc_impl (;12;) (type 9) (param i32 i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + local.get 2 + local.get 1 + call $__rustc::__rust_alloc + local.set 1 + br 1 (;@1;) + end + local.get 2 + local.get 1 + call $__rustc::__rust_alloc_zeroed + local.set 1 + end + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.store + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;13;) (type 9) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;14;) (type 5) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) + (func $alloc::raw_vec::handle_error (;15;) (type 5) (param i32 i32 i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;16;) (type 4) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) + (data $.rodata (;0;) (i32.const 1048576) "src/lib.rs\00\00\00\00\10\00\0a\00\00\00\19\00\00\000\00\00\00") +) diff --git a/tests/integration/expected/vec_alloc_vec.hir b/tests/integration/expected/vec_alloc_vec.hir new file mode 100644 index 000000000..10c30ffd4 --- /dev/null +++ b/tests/integration/expected/vec_alloc_vec.hir @@ -0,0 +1,455 @@ +builtin.component root_ns:root@1.0.0 { + builtin.module public @vec_alloc_vec { + public builtin.function @entrypoint(v0: i32) -> felt { + ^block4(v0: i32): + v4 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_vec/__stack_pointer : ptr + v5 = hir.bitcast v4 : ptr; + v6 = hir.load v5 : i32; + v7 = arith.constant 16 : i32; + v8 = arith.sub v6, v7 : i32 #[overflow = wrapping]; + v9 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_vec/__stack_pointer : ptr + v10 = hir.bitcast v9 : ptr; + hir.store v10, v8; + hir.exec @root_ns:root@1.0.0/vec_alloc_vec/__rustc::__rust_no_alloc_shim_is_unstable_v2() + v12 = arith.constant 4 : i32; + v11 = arith.constant 12 : i32; + v13 = hir.exec @root_ns:root@1.0.0/vec_alloc_vec/__rustc::__rust_alloc(v11, v12) : i32 + v338 = arith.constant 0 : i32; + v2 = arith.constant 0 : i32; + v15 = arith.eq v13, v2 : i1; + v16 = arith.zext v15 : u32; + v17 = hir.bitcast v16 : i32; + v19 = arith.neq v17, v338 : i1; + v310, v311 = scf.if v19 : felt, u32 { + ^block7: + v336 = arith.constant 12 : i32; + v337 = arith.constant 4 : i32; + hir.exec @root_ns:root@1.0.0/vec_alloc_vec/alloc::alloc::handle_alloc_error(v337, v336) + v302 = arith.constant 0 : u32; + v306 = ub.poison felt : felt; + scf.yield v306, v302; + } else { + ^block8: + v20 = arith.constant 1 : i32; + v21 = hir.exec @root_ns:root@1.0.0/vec_alloc_vec/intrinsics::felt::from_u32(v20) : felt + v22 = arith.constant 2 : i32; + v23 = hir.exec @root_ns:root@1.0.0/vec_alloc_vec/intrinsics::felt::from_u32(v22) : felt + v24 = arith.constant 3 : i32; + v25 = hir.exec @root_ns:root@1.0.0/vec_alloc_vec/intrinsics::felt::from_u32(v24) : felt + v27 = arith.constant 8 : u32; + v26 = hir.bitcast v13 : u32; + v28 = arith.add v26, v27 : u32 #[overflow = checked]; + v29 = arith.constant 4 : u32; + v30 = arith.mod v28, v29 : u32; + hir.assertz v30 #[code = 250]; + v31 = hir.int_to_ptr v28 : ptr; + hir.store v31, v25; + v335 = arith.constant 4 : u32; + v32 = hir.bitcast v13 : u32; + v34 = arith.add v32, v335 : u32 #[overflow = checked]; + v334 = arith.constant 4 : u32; + v36 = arith.mod v34, v334 : u32; + hir.assertz v36 #[code = 250]; + v37 = hir.int_to_ptr v34 : ptr; + hir.store v37, v23; + v38 = hir.bitcast v13 : u32; + v333 = arith.constant 4 : u32; + v40 = arith.mod v38, v333 : u32; + hir.assertz v40 #[code = 250]; + v41 = hir.int_to_ptr v38 : ptr; + hir.store v41, v21; + v44 = arith.constant 12 : u32; + v43 = hir.bitcast v8 : u32; + v45 = arith.add v43, v44 : u32 #[overflow = checked]; + v332 = arith.constant 4 : u32; + v47 = arith.mod v45, v332 : u32; + hir.assertz v47 #[code = 250]; + v331 = arith.constant 3 : i32; + v48 = hir.int_to_ptr v45 : ptr; + hir.store v48, v331; + v330 = arith.constant 8 : u32; + v49 = hir.bitcast v8 : u32; + v51 = arith.add v49, v330 : u32 #[overflow = checked]; + v329 = arith.constant 4 : u32; + v53 = arith.mod v51, v329 : u32; + hir.assertz v53 #[code = 250]; + v54 = hir.int_to_ptr v51 : ptr; + hir.store v54, v13; + v328 = arith.constant 4 : u32; + v56 = hir.bitcast v8 : u32; + v58 = arith.add v56, v328 : u32 #[overflow = checked]; + v327 = arith.constant 4 : u32; + v60 = arith.mod v58, v327 : u32; + hir.assertz v60 #[code = 250]; + v326 = arith.constant 3 : i32; + v61 = hir.int_to_ptr v58 : ptr; + hir.store v61, v326; + v325 = arith.constant 0 : i32; + v301 = arith.constant 3 : u32; + v63 = hir.bitcast v0 : u32; + v65 = arith.gte v63, v301 : i1; + v66 = arith.zext v65 : u32; + v67 = hir.bitcast v66 : i32; + v69 = arith.neq v67, v325 : i1; + v316 = scf.if v69 : felt { + ^block48: + v324 = ub.poison felt : felt; + scf.yield v324; + } else { + ^block9: + v300 = arith.constant 2 : u32; + v72 = arith.shl v0, v300 : i32; + v73 = arith.add v13, v72 : i32 #[overflow = wrapping]; + v74 = hir.bitcast v73 : u32; + v323 = arith.constant 4 : u32; + v76 = arith.mod v74, v323 : u32; + hir.assertz v76 #[code = 250]; + v77 = hir.int_to_ptr v74 : ptr; + v78 = hir.load v77 : felt; + v321 = arith.constant 4 : i32; + v322 = arith.constant 4 : i32; + v80 = arith.add v8, v322 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/vec_alloc_vec/alloc::raw_vec::RawVecInner::deallocate(v80, v321, v321) + v320 = arith.constant 16 : i32; + v84 = arith.add v8, v320 : i32 #[overflow = wrapping]; + v85 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_vec/__stack_pointer : ptr + v86 = hir.bitcast v85 : ptr; + hir.store v86, v84; + scf.yield v78; + }; + v307 = arith.constant 1 : u32; + v319 = arith.constant 0 : u32; + v317 = cf.select v69, v319, v307 : u32; + scf.yield v316, v317; + }; + v318 = arith.constant 0 : u32; + v315 = arith.eq v311, v318 : i1; + cf.cond_br v315 ^block6, ^block50(v310); + ^block6: + ub.unreachable ; + ^block50(v303: felt): + builtin.ret v303; + }; + + private builtin.function @__rustc::__rust_alloc(v89: i32, v90: i32) -> i32 { + ^block10(v89: i32, v90: i32): + v92 = arith.constant 1048576 : i32; + v93 = hir.exec @root_ns:root@1.0.0/vec_alloc_vec/::alloc(v92, v90, v89) : i32 + builtin.ret v93; + }; + + private builtin.function @__rustc::__rust_dealloc(v94: i32, v95: i32, v96: i32) { + ^block12(v94: i32, v95: i32, v96: i32): + builtin.ret ; + }; + + private builtin.function @__rustc::__rust_no_alloc_shim_is_unstable_v2() { + ^block14: + builtin.ret ; + }; + + private builtin.function @::alloc(v97: i32, v98: i32, v99: i32) -> i32 { + ^block16(v97: i32, v98: i32, v99: i32): + v102 = arith.constant 16 : i32; + v101 = arith.constant 0 : i32; + v340 = arith.constant 16 : u32; + v104 = hir.bitcast v98 : u32; + v106 = arith.gt v104, v340 : i1; + v107 = arith.zext v106 : u32; + v108 = hir.bitcast v107 : i32; + v110 = arith.neq v108, v101 : i1; + v111 = cf.select v110, v98, v102 : i32; + v380 = arith.constant 0 : i32; + v112 = arith.constant -1 : i32; + v113 = arith.add v111, v112 : i32 #[overflow = wrapping]; + v114 = arith.band v111, v113 : i32; + v116 = arith.neq v114, v380 : i1; + v349, v350 = scf.if v116 : i32, u32 { + ^block55: + v341 = arith.constant 0 : u32; + v345 = ub.poison i32 : i32; + scf.yield v345, v341; + } else { + ^block19: + v118 = hir.exec @root_ns:root@1.0.0/vec_alloc_vec/core::ptr::alignment::Alignment::max(v98, v111) : i32 + v379 = arith.constant 0 : i32; + v117 = arith.constant -2147483648 : i32; + v119 = arith.sub v117, v118 : i32 #[overflow = wrapping]; + v121 = hir.bitcast v119 : u32; + v120 = hir.bitcast v99 : u32; + v122 = arith.gt v120, v121 : i1; + v123 = arith.zext v122 : u32; + v124 = hir.bitcast v123 : i32; + v126 = arith.neq v124, v379 : i1; + v364 = scf.if v126 : i32 { + ^block54: + v378 = ub.poison i32 : i32; + scf.yield v378; + } else { + ^block20: + v376 = arith.constant 0 : i32; + v132 = arith.sub v376, v118 : i32 #[overflow = wrapping]; + v377 = arith.constant -1 : i32; + v128 = arith.add v99, v118 : i32 #[overflow = wrapping]; + v130 = arith.add v128, v377 : i32 #[overflow = wrapping]; + v133 = arith.band v130, v132 : i32; + v134 = hir.bitcast v97 : u32; + v135 = arith.constant 4 : u32; + v136 = arith.mod v134, v135 : u32; + hir.assertz v136 #[code = 250]; + v137 = hir.int_to_ptr v134 : ptr; + v138 = hir.load v137 : i32; + v375 = arith.constant 0 : i32; + v140 = arith.neq v138, v375 : i1; + scf.if v140{ + ^block53: + scf.yield ; + } else { + ^block22: + v141 = hir.exec @root_ns:root@1.0.0/vec_alloc_vec/intrinsics::mem::heap_base() : i32 + v142 = hir.mem_size : u32; + v148 = hir.bitcast v97 : u32; + v374 = arith.constant 4 : u32; + v150 = arith.mod v148, v374 : u32; + hir.assertz v150 #[code = 250]; + v373 = arith.constant 16 : u32; + v143 = hir.bitcast v142 : i32; + v146 = arith.shl v143, v373 : i32; + v147 = arith.add v141, v146 : i32 #[overflow = wrapping]; + v151 = hir.int_to_ptr v148 : ptr; + hir.store v151, v147; + scf.yield ; + }; + v154 = hir.bitcast v97 : u32; + v372 = arith.constant 4 : u32; + v156 = arith.mod v154, v372 : u32; + hir.assertz v156 #[code = 250]; + v157 = hir.int_to_ptr v154 : ptr; + v158 = hir.load v157 : i32; + v370 = arith.constant 0 : i32; + v371 = arith.constant -1 : i32; + v160 = arith.bxor v158, v371 : i32; + v162 = hir.bitcast v160 : u32; + v161 = hir.bitcast v133 : u32; + v163 = arith.gt v161, v162 : i1; + v164 = arith.zext v163 : u32; + v165 = hir.bitcast v164 : i32; + v167 = arith.neq v165, v370 : i1; + v363 = scf.if v167 : i32 { + ^block23: + v369 = arith.constant 0 : i32; + scf.yield v369; + } else { + ^block24: + v169 = hir.bitcast v97 : u32; + v368 = arith.constant 4 : u32; + v171 = arith.mod v169, v368 : u32; + hir.assertz v171 #[code = 250]; + v168 = arith.add v158, v133 : i32 #[overflow = wrapping]; + v172 = hir.int_to_ptr v169 : ptr; + hir.store v172, v168; + v174 = arith.add v158, v118 : i32 #[overflow = wrapping]; + scf.yield v174; + }; + scf.yield v363; + }; + v346 = arith.constant 1 : u32; + v367 = arith.constant 0 : u32; + v365 = cf.select v126, v367, v346 : u32; + scf.yield v364, v365; + }; + v366 = arith.constant 0 : u32; + v362 = arith.eq v350, v366 : i1; + cf.cond_br v362 ^block18, ^block57(v349); + ^block18: + ub.unreachable ; + ^block57(v342: i32): + builtin.ret v342; + }; + + private builtin.function @intrinsics::mem::heap_base() -> i32 { + ^block25: + v177 = hir.exec @intrinsics/mem/heap_base() : i32 + builtin.ret v177; + }; + + private builtin.function @intrinsics::felt::from_u32(v179: i32) -> felt { + ^block29(v179: i32): + v180 = hir.bitcast v179 : felt; + builtin.ret v180; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::deallocate(v182: i32, v183: i32, v184: i32) { + ^block31(v182: i32, v183: i32, v184: i32): + v186 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_vec/__stack_pointer : ptr + v187 = hir.bitcast v186 : ptr; + v188 = hir.load v187 : i32; + v189 = arith.constant 16 : i32; + v190 = arith.sub v188, v189 : i32 #[overflow = wrapping]; + v191 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_vec/__stack_pointer : ptr + v192 = hir.bitcast v191 : ptr; + hir.store v192, v190; + v193 = arith.constant 4 : i32; + v194 = arith.add v190, v193 : i32 #[overflow = wrapping]; + hir.exec @root_ns:root@1.0.0/vec_alloc_vec/alloc::raw_vec::RawVecInner::current_memory(v194, v182, v183, v184) + v196 = arith.constant 8 : u32; + v195 = hir.bitcast v190 : u32; + v197 = arith.add v195, v196 : u32 #[overflow = checked]; + v198 = arith.constant 4 : u32; + v199 = arith.mod v197, v198 : u32; + hir.assertz v199 #[code = 250]; + v200 = hir.int_to_ptr v197 : ptr; + v201 = hir.load v200 : i32; + v387 = arith.constant 0 : i32; + v185 = arith.constant 0 : i32; + v203 = arith.eq v201, v185 : i1; + v204 = arith.zext v203 : u32; + v205 = hir.bitcast v204 : i32; + v207 = arith.neq v205, v387 : i1; + scf.if v207{ + ^block61: + scf.yield ; + } else { + ^block34: + v386 = arith.constant 4 : u32; + v208 = hir.bitcast v190 : u32; + v210 = arith.add v208, v386 : u32 #[overflow = checked]; + v385 = arith.constant 4 : u32; + v212 = arith.mod v210, v385 : u32; + hir.assertz v212 #[code = 250]; + v213 = hir.int_to_ptr v210 : ptr; + v214 = hir.load v213 : i32; + v216 = arith.constant 12 : u32; + v215 = hir.bitcast v190 : u32; + v217 = arith.add v215, v216 : u32 #[overflow = checked]; + v384 = arith.constant 4 : u32; + v219 = arith.mod v217, v384 : u32; + hir.assertz v219 #[code = 250]; + v220 = hir.int_to_ptr v217 : ptr; + v221 = hir.load v220 : i32; + hir.exec @root_ns:root@1.0.0/vec_alloc_vec/::deallocate(v214, v201, v221) + scf.yield ; + }; + v383 = arith.constant 16 : i32; + v224 = arith.add v190, v383 : i32 #[overflow = wrapping]; + v225 = builtin.global_symbol @root_ns:root@1.0.0/vec_alloc_vec/__stack_pointer : ptr + v226 = hir.bitcast v225 : ptr; + hir.store v226, v224; + builtin.ret ; + }; + + private builtin.function @alloc::raw_vec::RawVecInner::current_memory(v227: i32, v228: i32, v229: i32, v230: i32) { + ^block35(v227: i32, v228: i32, v229: i32, v230: i32): + v413 = arith.constant 0 : i32; + v231 = arith.constant 0 : i32; + v235 = arith.eq v230, v231 : i1; + v236 = arith.zext v235 : u32; + v237 = hir.bitcast v236 : i32; + v239 = arith.neq v237, v413 : i1; + v400, v401 = scf.if v239 : i32, i32 { + ^block64: + v412 = arith.constant 0 : i32; + v233 = arith.constant 4 : i32; + scf.yield v233, v412; + } else { + ^block38: + v240 = hir.bitcast v228 : u32; + v275 = arith.constant 4 : u32; + v242 = arith.mod v240, v275 : u32; + hir.assertz v242 #[code = 250]; + v243 = hir.int_to_ptr v240 : ptr; + v244 = hir.load v243 : i32; + v410 = arith.constant 0 : i32; + v411 = arith.constant 0 : i32; + v246 = arith.eq v244, v411 : i1; + v247 = arith.zext v246 : u32; + v248 = hir.bitcast v247 : i32; + v250 = arith.neq v248, v410 : i1; + v398 = scf.if v250 : i32 { + ^block63: + v409 = arith.constant 0 : i32; + scf.yield v409; + } else { + ^block39: + v408 = arith.constant 4 : u32; + v251 = hir.bitcast v227 : u32; + v253 = arith.add v251, v408 : u32 #[overflow = checked]; + v407 = arith.constant 4 : u32; + v255 = arith.mod v253, v407 : u32; + hir.assertz v255 #[code = 250]; + v256 = hir.int_to_ptr v253 : ptr; + hir.store v256, v229; + v406 = arith.constant 4 : u32; + v257 = hir.bitcast v228 : u32; + v259 = arith.add v257, v406 : u32 #[overflow = checked]; + v405 = arith.constant 4 : u32; + v261 = arith.mod v259, v405 : u32; + hir.assertz v261 #[code = 250]; + v262 = hir.int_to_ptr v259 : ptr; + v263 = hir.load v262 : i32; + v264 = hir.bitcast v227 : u32; + v404 = arith.constant 4 : u32; + v266 = arith.mod v264, v404 : u32; + hir.assertz v266 #[code = 250]; + v267 = hir.int_to_ptr v264 : ptr; + hir.store v267, v263; + v268 = arith.mul v244, v230 : i32 #[overflow = wrapping]; + scf.yield v268; + }; + v269 = arith.constant 8 : i32; + v403 = arith.constant 4 : i32; + v399 = cf.select v250, v403, v269 : i32; + scf.yield v399, v398; + }; + v272 = arith.add v227, v400 : i32 #[overflow = wrapping]; + v274 = hir.bitcast v272 : u32; + v402 = arith.constant 4 : u32; + v276 = arith.mod v274, v402 : u32; + hir.assertz v276 #[code = 250]; + v277 = hir.int_to_ptr v274 : ptr; + hir.store v277, v401; + builtin.ret ; + }; + + private builtin.function @::deallocate(v278: i32, v279: i32, v280: i32) { + ^block40(v278: i32, v279: i32, v280: i32): + v415 = arith.constant 0 : i32; + v281 = arith.constant 0 : i32; + v282 = arith.eq v280, v281 : i1; + v283 = arith.zext v282 : u32; + v284 = hir.bitcast v283 : i32; + v286 = arith.neq v284, v415 : i1; + scf.if v286{ + ^block42: + scf.yield ; + } else { + ^block43: + hir.exec @root_ns:root@1.0.0/vec_alloc_vec/__rustc::__rust_dealloc(v278, v280, v279) + scf.yield ; + }; + builtin.ret ; + }; + + private builtin.function @alloc::alloc::handle_alloc_error(v287: i32, v288: i32) { + ^block44(v287: i32, v288: i32): + ub.unreachable ; + }; + + private builtin.function @core::ptr::alignment::Alignment::max(v289: i32, v290: i32) -> i32 { + ^block46(v289: i32, v290: i32): + v297 = arith.constant 0 : i32; + v293 = hir.bitcast v290 : u32; + v292 = hir.bitcast v289 : u32; + v294 = arith.gt v292, v293 : i1; + v295 = arith.zext v294 : u32; + v296 = hir.bitcast v295 : i32; + v298 = arith.neq v296, v297 : i1; + v299 = cf.select v298, v289, v290 : i32; + builtin.ret v299; + }; + + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/vec_alloc_vec.masm b/tests/integration/expected/vec_alloc_vec.masm new file mode 100644 index 000000000..bd3e81ccd --- /dev/null +++ b/tests/integration/expected/vec_alloc_vec.masm @@ -0,0 +1,748 @@ +# mod root_ns:root@1.0.0 + +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 +end + +# mod root_ns:root@1.0.0::vec_alloc_vec + +export.entrypoint + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::__rustc::__rust_no_alloc_shim_is_unstable_v2 + trace.252 + nop + push.4 + push.12 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::__rustc::__rust_alloc + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + drop + drop + push.12 + push.4 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::alloc::alloc::handle_alloc_error + trace.252 + nop + push.0 + push.3735929054 + else + push.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::intrinsics::felt::from_u32 + trace.252 + nop + push.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::intrinsics::felt::from_u32 + trace.252 + nop + push.3 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::intrinsics::felt::from_u32 + trace.252 + nop + push.8 + dup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_felt + trace.252 + nop + push.12 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.8 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.3 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.0 + push.3 + dup.4 + swap.1 + u32gte + neq + dup.0 + if.true + movdn.3 + drop + drop + drop + push.3735929054 + else + push.2 + movup.4 + swap.1 + u32shl + movup.2 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_felt + trace.252 + nop + push.4 + push.4 + dup.4 + u32wrapping_add + dup.1 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::alloc::raw_vec::RawVecInner::deallocate + trace.252 + nop + push.16 + movup.3 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + push.1 + push.0 + movup.3 + cdrop + swap.1 + end + push.0 + movup.2 + eq + if.true + drop + push.0 + assert + else + nop + end +end + +proc.__rustc::__rust_alloc + push.1048576 + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::::alloc + trace.252 + nop +end + +proc.__rustc::__rust_dealloc + drop + drop + drop +end + +proc.__rustc::__rust_no_alloc_shim_is_unstable_v2 + nop +end + +proc.::alloc + push.16 + push.0 + push.16 + dup.4 + swap.1 + u32gt + neq + dup.3 + swap.1 + cdrop + push.0 + push.4294967295 + dup.2 + u32wrapping_add + dup.2 + u32and + neq + if.true + dropw + push.0 + push.3735929054 + else + movup.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::core::ptr::alignment::Alignment::max + trace.252 + nop + push.0 + push.2147483648 + dup.2 + u32wrapping_sub + dup.4 + swap.1 + u32gt + neq + dup.0 + if.true + movdn.3 + drop + drop + drop + push.3735929054 + else + push.0 + dup.2 + u32wrapping_sub + push.4294967295 + movup.5 + dup.4 + u32wrapping_add + u32wrapping_add + u32and + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + neq + if.true + nop + else + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::intrinsics::mem::heap_base + trace.252 + nop + trace.240 + nop + exec.::intrinsics::mem::memory_size + trace.252 + nop + dup.5 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + push.16 + movup.2 + swap.1 + u32shl + movup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + end + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.4294967295 + dup.2 + u32xor + dup.3 + swap.1 + u32gt + neq + if.true + drop + drop + movdn.2 + drop + drop + push.0 + else + movup.4 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.2 + dup.2 + u32wrapping_add + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + movup.2 + u32wrapping_add + end + end + push.1 + push.0 + movup.3 + cdrop + swap.1 + end + push.0 + movup.2 + eq + if.true + drop + push.0 + assert + else + nop + end +end + +proc.intrinsics::mem::heap_base + trace.240 + nop + exec.::intrinsics::mem::heap_base + trace.252 + nop +end + +proc.intrinsics::felt::from_u32 + nop +end + +proc.alloc::raw_vec::RawVecInner::deallocate + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.16 + u32wrapping_sub + push.1114112 + dup.1 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + dup.1 + u32wrapping_add + swap.1 + swap.4 + swap.3 + swap.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::alloc::raw_vec::RawVecInner::current_memory + trace.252 + nop + push.8 + dup.1 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + if.true + drop + else + push.4 + dup.2 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.12 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + movdn.2 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::::deallocate + trace.252 + nop + end + push.16 + u32wrapping_add + push.1114112 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.alloc::raw_vec::RawVecInner::current_memory + push.0 + push.0 + dup.5 + eq + neq + if.true + movdn.3 + drop + drop + drop + push.0 + push.4 + else + dup.1 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + push.0 + push.0 + dup.2 + eq + neq + dup.0 + if.true + swap.1 + drop + movup.2 + drop + movup.2 + drop + movup.2 + drop + push.0 + else + push.4 + dup.3 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + movup.5 + swap.1 + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + push.4 + movup.4 + add + u32assert + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::load_sw + trace.252 + nop + dup.3 + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop + swap.3 + trace.240 + nop + exec.::intrinsics::i32::wrapping_mul + trace.252 + nop + movup.2 + swap.1 + end + push.8 + push.4 + movup.3 + cdrop + end + movup.2 + u32wrapping_add + push.4 + dup.1 + swap.1 + u32mod + u32assert + assertz + u32divmod.4 + swap.1 + trace.240 + nop + exec.::intrinsics::mem::store_sw + trace.252 + nop +end + +proc.::deallocate + push.0 + push.0 + dup.4 + eq + neq + if.true + drop + drop + drop + else + movup.2 + swap.1 + trace.240 + nop + exec.::root_ns:root@1.0.0::vec_alloc_vec::__rustc::__rust_dealloc + trace.252 + nop + end +end + +proc.alloc::alloc::handle_alloc_error + drop + drop + push.0 + assert +end + +proc.core::ptr::alignment::Alignment::max + push.0 + dup.2 + dup.2 + swap.1 + u32gt + neq + cdrop +end + diff --git a/tests/integration/expected/vec_alloc_vec.wat b/tests/integration/expected/vec_alloc_vec.wat new file mode 100644 index 000000000..2336a527b --- /dev/null +++ b/tests/integration/expected/vec_alloc_vec.wat @@ -0,0 +1,263 @@ +(module $vec_alloc_vec.wasm + (type (;0;) (func (param i32) (result f32))) + (type (;1;) (func (param i32 i32) (result i32))) + (type (;2;) (func (param i32 i32 i32))) + (type (;3;) (func)) + (type (;4;) (func (param i32 i32 i32) (result i32))) + (type (;5;) (func (result i32))) + (type (;6;) (func (param i32 i32 i32 i32))) + (type (;7;) (func (param i32 i32))) + (table (;0;) 1 1 funcref) + (memory (;0;) 17) + (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) + (export "memory" (memory 0)) + (export "entrypoint" (func $entrypoint)) + (func $entrypoint (;0;) (type 0) (param i32) (result f32) + (local i32 i32 f32 f32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 1 + global.set $__stack_pointer + call $__rustc::__rust_no_alloc_shim_is_unstable_v2 + block ;; label = @1 + block ;; label = @2 + i32.const 12 + i32.const 4 + call $__rustc::__rust_alloc + local.tee 2 + i32.eqz + br_if 0 (;@2;) + i32.const 1 + call $intrinsics::felt::from_u32 + local.set 3 + i32.const 2 + call $intrinsics::felt::from_u32 + local.set 4 + local.get 2 + i32.const 3 + call $intrinsics::felt::from_u32 + f32.store offset=8 + local.get 2 + local.get 4 + f32.store offset=4 + local.get 2 + local.get 3 + f32.store + local.get 1 + i32.const 3 + i32.store offset=12 + local.get 1 + local.get 2 + i32.store offset=8 + local.get 1 + i32.const 3 + i32.store offset=4 + local.get 0 + i32.const 3 + i32.ge_u + br_if 1 (;@1;) + local.get 2 + local.get 0 + i32.const 2 + i32.shl + i32.add + f32.load + local.set 3 + local.get 1 + i32.const 4 + i32.add + i32.const 4 + i32.const 4 + call $alloc::raw_vec::RawVecInner::deallocate + local.get 1 + i32.const 16 + i32.add + global.set $__stack_pointer + local.get 3 + return + end + i32.const 4 + i32.const 12 + call $alloc::alloc::handle_alloc_error + end + unreachable + ) + (func $__rustc::__rust_alloc (;1;) (type 1) (param i32 i32) (result i32) + i32.const 1048576 + local.get 1 + local.get 0 + call $::alloc + ) + (func $__rustc::__rust_dealloc (;2;) (type 2) (param i32 i32 i32)) + (func $__rustc::__rust_no_alloc_shim_is_unstable_v2 (;3;) (type 3) + return + ) + (func $::alloc (;4;) (type 4) (param i32 i32 i32) (result i32) + (local i32 i32) + block ;; label = @1 + local.get 1 + i32.const 16 + local.get 1 + i32.const 16 + i32.gt_u + select + local.tee 3 + local.get 3 + i32.const -1 + i32.add + i32.and + br_if 0 (;@1;) + local.get 2 + i32.const -2147483648 + local.get 1 + local.get 3 + call $core::ptr::alignment::Alignment::max + local.tee 1 + i32.sub + i32.gt_u + br_if 0 (;@1;) + i32.const 0 + local.set 3 + local.get 2 + local.get 1 + i32.add + i32.const -1 + i32.add + i32.const 0 + local.get 1 + i32.sub + i32.and + local.set 2 + block ;; label = @2 + local.get 0 + i32.load + br_if 0 (;@2;) + local.get 0 + call $intrinsics::mem::heap_base + memory.size + i32.const 16 + i32.shl + i32.add + i32.store + end + block ;; label = @2 + local.get 2 + local.get 0 + i32.load + local.tee 4 + i32.const -1 + i32.xor + i32.gt_u + br_if 0 (;@2;) + local.get 0 + local.get 4 + local.get 2 + i32.add + i32.store + local.get 4 + local.get 1 + i32.add + local.set 3 + end + local.get 3 + return + end + unreachable + ) + (func $intrinsics::mem::heap_base (;5;) (type 5) (result i32) + unreachable + ) + (func $intrinsics::felt::from_u32 (;6;) (type 0) (param i32) (result f32) + unreachable + ) + (func $alloc::raw_vec::RawVecInner::deallocate (;7;) (type 2) (param i32 i32 i32) + (local i32) + global.get $__stack_pointer + i32.const 16 + i32.sub + local.tee 3 + global.set $__stack_pointer + local.get 3 + i32.const 4 + i32.add + local.get 0 + local.get 1 + local.get 2 + call $alloc::raw_vec::RawVecInner::current_memory + block ;; label = @1 + local.get 3 + i32.load offset=8 + local.tee 2 + i32.eqz + br_if 0 (;@1;) + local.get 3 + i32.load offset=4 + local.get 2 + local.get 3 + i32.load offset=12 + call $::deallocate + end + local.get 3 + i32.const 16 + i32.add + global.set $__stack_pointer + ) + (func $alloc::raw_vec::RawVecInner::current_memory (;8;) (type 6) (param i32 i32 i32 i32) + (local i32 i32 i32) + i32.const 0 + local.set 4 + i32.const 4 + local.set 5 + block ;; label = @1 + local.get 3 + i32.eqz + br_if 0 (;@1;) + local.get 1 + i32.load + local.tee 6 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + i32.store offset=4 + local.get 0 + local.get 1 + i32.load offset=4 + i32.store + local.get 6 + local.get 3 + i32.mul + local.set 4 + i32.const 8 + local.set 5 + end + local.get 0 + local.get 5 + i32.add + local.get 4 + i32.store + ) + (func $::deallocate (;9;) (type 2) (param i32 i32 i32) + block ;; label = @1 + local.get 2 + i32.eqz + br_if 0 (;@1;) + local.get 0 + local.get 2 + local.get 1 + call $__rustc::__rust_dealloc + end + ) + (func $alloc::alloc::handle_alloc_error (;10;) (type 7) (param i32 i32) + unreachable + ) + (func $core::ptr::alignment::Alignment::max (;11;) (type 1) (param i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 0 + local.get 1 + i32.gt_u + select + ) +) diff --git a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet.hir b/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet.hir deleted file mode 100644 index b4b9e1f26..000000000 --- a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet.hir +++ /dev/null @@ -1,1205 +0,0 @@ -(component - ;; Component Imports - (lower (("miden:base/account@1.0.0" #add-asset) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (param (struct (struct (struct u64) (struct u64) (struct u64) (struct u64)))) (result (struct (struct (struct u64) (struct u64) (struct u64) (struct u64))))))) (# #0) - (lower (("miden:base/account@1.0.0" #remove-asset) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (param (struct (struct (struct u64) (struct u64) (struct u64) (struct u64)))) (result (struct (struct (struct u64) (struct u64) (struct u64) (struct u64))))))) (# #1) - (lower (("miden:base/tx@1.0.0" #create-note) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (param (struct (struct (struct u64) (struct u64) (struct u64) (struct u64)))) (param (struct (struct u64))) (param (struct (struct (struct u64) (struct u64) (struct u64) (struct u64)))) (result (struct (struct u64)))))) (#miden:base/tx@1.0.0 #create-note) - - ;; Modules - (module #wit-component:shim - ;; Functions - (func (export #indirect-miden:base/account@1.0.0-add-asset) - (param i64) (param i64) (param i64) (param i64) (param i32) - (block 0 - (param v0 i64) - (param v1 i64) - (param v2 i64) - (param v3 i64) - (param v4 i32) - (let (v5 i32) (const.i32 0)) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #indirect-miden:base/account@1.0.0-remove-asset) - (param i64) (param i64) (param i64) (param i64) (param i32) - (block 0 - (param v0 i64) - (param v1 i64) - (param v2 i64) - (param v3 i64) - (param v4 i32) - (let (v5 i32) (const.i32 1)) - (br (block 1))) - - (block 1 - (ret)) - ) - ) - - (module #basic_wallet - ;; Data Segments - (data (mut) (offset 1048576) 0x0100000001000000010000000100000002000000) - - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - - ;; Functions - (func (export #__wasm_call_ctors) - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #basic_wallet::bindings::__link_custom_section_describing_imports) - - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #__rust_alloc) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048596)) - (let (v4 i32) (call #::alloc v3 v1 v0)) - (br (block 1 v4))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #__rust_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (const.i32 0)) - (let (v6 i32) (const.i32 1048596)) - (let (v7 i32) (call #::alloc v6 v2 v3)) - (let (v8 i1) (eq v7 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2 v7) (block 3))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v22 i32) - (br (block 1 v22))) - - (block 3 - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (bitcast v3)) - (let (v13 i1) (lt v11 v12)) - (let (v14 i32) (sext v13)) - (let (v15 i1) (neq v14 0)) - (let (v16 i32) (select v15 v1 v3)) - (let (v17 u32) (bitcast v7)) - (let (v18 (ptr u8)) (inttoptr v17)) - (let (v19 u32) (bitcast v0)) - (let (v20 (ptr u8)) (inttoptr v19)) - (memcpy v20 v18 v16) - (let (v21 i32) (const.i32 1048596)) - (call #::dealloc v21 v0 v2 v1) - (br (block 2 v7))) - ) - - (func (export #miden:basic-wallet/basic-wallet@1.0.0#receive-asset) - (param i64) (param i64) (param i64) (param i64) - (block 0 (param v0 i64) (param v1 i64) (param v2 i64) (param v3 i64) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 32)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (call #wit_bindgen_rt::run_ctors_once) - (call (#wit-component:shim #indirect-miden:base/account@1.0.0-add-asset) v0 v1 v2 v3 v7) - (let (v9 i32) (const.i32 32)) - (let (v10 i32) (add.wrapping v7 v9)) - (let (v11 (ptr i32)) (global.symbol #__stack_pointer)) - (store v11 v10) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #miden:basic-wallet/basic-wallet@1.0.0#send-asset) - (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) - (block 0 - (param v0 i64) - (param v1 i64) - (param v2 i64) - (param v3 i64) - (param v4 i64) - (param v5 i64) - (param v6 i64) - (param v7 i64) - (param v8 i64) - (let (v9 i32) (const.i32 0)) - (let (v10 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v11 i32) (const.i32 32)) - (let (v12 i32) (sub.wrapping v10 v11)) - (let (v13 (ptr i32)) (global.symbol #__stack_pointer)) - (store v13 v12) - (call #wit_bindgen_rt::run_ctors_once) - (call (#wit-component:shim #indirect-miden:base/account@1.0.0-remove-asset) v0 v1 v2 v3 v12) - (let (v14 u32) (bitcast v12)) - (let (v15 u32) (mod.unchecked v14 8)) - (assertz 250 v15) - (let (v16 (ptr i64)) (inttoptr v14)) - (let (v17 i64) (load v16)) - (let (v18 u32) (bitcast v12)) - (let (v19 u32) (add.checked v18 8)) - (let (v20 u32) (mod.unchecked v19 8)) - (assertz 250 v20) - (let (v21 (ptr i64)) (inttoptr v19)) - (let (v22 i64) (load v21)) - (let (v23 u32) (bitcast v12)) - (let (v24 u32) (add.checked v23 16)) - (let (v25 u32) (mod.unchecked v24 8)) - (assertz 250 v25) - (let (v26 (ptr i64)) (inttoptr v24)) - (let (v27 i64) (load v26)) - (let (v28 u32) (bitcast v12)) - (let (v29 u32) (add.checked v28 24)) - (let (v30 u32) (mod.unchecked v29 8)) - (assertz 250 v30) - (let (v31 (ptr i64)) (inttoptr v29)) - (let (v32 i64) (load v31)) - (let (v33 i64) (call (#miden:base/tx@1.0.0 #create-note) v17 v22 v27 v32 v4 v5 v6 v7 v8)) - (let (v34 i32) (const.i32 32)) - (let (v35 i32) (add.wrapping v12 v34)) - (let (v36 (ptr i32)) (global.symbol #__stack_pointer)) - (store v36 v35) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #wit_bindgen_rt::cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i1) (neq v1 0)) - (condbr v5 (block 4) (block 5))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v19 i32) - (br (block 1 v19))) - - (block 3 (param v17 i32) - (let (v18 i1) (neq v17 0)) - (condbr v18 (block 2 v17) (block 7))) - - (block 4 - (let (v16 i32) (call #__rust_realloc v0 v1 v2 v3)) - (br (block 3 v16))) - - (block 5 - (let (v6 i1) (eq v3 0)) - (let (v7 i32) (zext v6)) - (let (v8 i1) (neq v7 0)) - (condbr v8 (block 2 v2) (block 6))) - - (block 6 - (let (v9 i32) (const.i32 0)) - (let (v10 u32) (bitcast v9)) - (let (v11 u32) (add.checked v10 1048600)) - (let (v12 (ptr u8)) (inttoptr v11)) - (let (v13 u8) (load v12)) - (let (v14 i32) (zext v13)) - (let (v15 i32) (call #__rust_alloc v3 v2)) - (br (block 3 v15))) - - (block 7 - (unreachable)) - ) - - (func (export #wit_bindgen_rt::run_ctors_once) - (block 0 - (let (v0 i32) (const.i32 0)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (add.checked v1 1048601)) - (let (v3 (ptr u8)) (inttoptr v2)) - (let (v4 u8) (load v3)) - (let (v5 i32) (zext v4)) - (let (v6 i1) (neq v5 0)) - (condbr v6 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (call #__wasm_call_ctors) - (let (v7 i32) (const.i32 0)) - (let (v8 i32) (const.i32 1)) - (let (v9 u32) (bitcast v8)) - (let (v10 u8) (trunc v9)) - (let (v11 u32) (bitcast v7)) - (let (v12 u32) (add.checked v11 1048601)) - (let (v13 (ptr u8)) (inttoptr v12)) - (store v13 v10) - (br (block 2))) - ) - - (func (export #wee_alloc::alloc_first_fit) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 u32) (bitcast v2)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr i32)) (inttoptr v5)) - (let (v8 i32) (load v7)) - (let (v9 i1) (neq v8 0)) - (condbr v9 (block 2) (block 3))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 - (let (v11 i32) (const.i32 -1)) - (let (v12 i32) (add.wrapping v1 v11)) - (let (v13 i32) (const.i32 0)) - (let (v14 i32) (sub.wrapping v13 v1)) - (let (v15 i32) (const.i32 2)) - (let (v16 u32) (bitcast v15)) - (let (v17 i32) (shl.wrapping v0 v16)) - (br (block 4 v8 v2 v17 v14 v12))) - - (block 3 - (let (v10 i32) (const.i32 0)) - (ret v10)) - - (block 4 - (param v18 i32) - (param v169 i32) - (param v182 i32) - (param v197 i32) - (param v210 i32) - (let (v19 u32) (bitcast v18)) - (let (v20 u32) (add.checked v19 8)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr i32)) (inttoptr v20)) - (let (v23 i32) (load v22)) - (let (v24 i32) (const.i32 1)) - (let (v25 i32) (band v23 v24)) - (let (v26 i1) (neq v25 0)) - (condbr v26 (block 7) (block 8))) - - (block 5 - (let (v344 i32) (const.i32 0)) - (br (block 1 v344))) - - (block 6 - (param v172 i32) - (param v179 i32) - (param v181 i32) - (param v196 i32) - (param v209 i32) - (param v218 i32) - (param v219 i32) - (let (v173 u32) (bitcast v172)) - (let (v174 u32) (mod.unchecked v173 4)) - (assertz 250 v174) - (let (v175 (ptr i32)) (inttoptr v173)) - (let (v176 i32) (load v175)) - (let (v177 i32) (const.i32 -4)) - (let (v178 i32) (band v176 v177)) - (let (v180 i32) (sub.wrapping v178 v179)) - (let (v188 u32) (bitcast v180)) - (let (v189 u32) (bitcast v181)) - (let (v190 i1) (lt v188 v189)) - (let (v191 i32) (sext v190)) - (let (v192 i1) (neq v191 0)) - (condbr v192 (block 22 v218 v219 v181 v196 v209) (block 23))) - - (block 7 - (br (block 9 v18 v23 v169 v182 v197 v210))) - - (block 8 - (let (v27 i32) (const.i32 8)) - (let (v28 i32) (add.wrapping v18 v27)) - (br (block 6 v18 v28 v182 v197 v210 v169 v23))) - - (block 9 - (param v29 i32) - (param v30 i32) - (param v156 i32) - (param v187 i32) - (param v202 i32) - (param v215 i32) - (let (v31 i32) (const.i32 -2)) - (let (v32 i32) (band v30 v31)) - (let (v33 u32) (bitcast v29)) - (let (v34 u32) (add.checked v33 8)) - (let (v35 u32) (mod.unchecked v34 4)) - (assertz 250 v35) - (let (v36 (ptr i32)) (inttoptr v34)) - (store v36 v32) - (let (v37 u32) (bitcast v29)) - (let (v38 u32) (add.checked v37 4)) - (let (v39 u32) (mod.unchecked v38 4)) - (assertz 250 v39) - (let (v40 (ptr i32)) (inttoptr v38)) - (let (v41 i32) (load v40)) - (let (v42 i32) (const.i32 -4)) - (let (v43 i32) (band v41 v42)) - (let (v44 i1) (neq v43 0)) - (condbr v44 (block 12) (block 13))) - - (block 10 - (let (v170 i32) (const.i32 8)) - (let (v171 i32) (add.wrapping v157 v170)) - (br (block 6 v157 v171 v183 v198 v211 v152 v165))) - - (block 11 - (param v55 i32) - (param v75 i32) - (param v122 i32) - (param v142 i32) - (param v155 i32) - (param v186 i32) - (param v201 i32) - (param v214 i32) - (let (v56 u32) (bitcast v55)) - (let (v57 u32) (mod.unchecked v56 4)) - (assertz 250 v57) - (let (v58 (ptr i32)) (inttoptr v56)) - (let (v59 i32) (load v58)) - (let (v60 i32) (const.i32 -4)) - (let (v61 i32) (band v59 v60)) - (let (v62 i1) (eq v61 0)) - (let (v63 i32) (zext v62)) - (let (v64 i1) (neq v63 0)) - (condbr v64 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 15))) - - (block 12 - (let (v46 i32) (const.i32 0)) - (let (v47 u32) (bitcast v43)) - (let (v48 (ptr u8)) (inttoptr v47)) - (let (v49 u8) (load v48)) - (let (v50 i32) (zext v49)) - (let (v51 i32) (const.i32 1)) - (let (v52 i32) (band v50 v51)) - (let (v53 i1) (neq v52 0)) - (let (v54 i32) (select v53 v46 v43)) - (br (block 11 v29 v43 v41 v54 v156 v187 v202 v215))) - - (block 13 - (let (v45 i32) (const.i32 0)) - (br (block 11 v29 v43 v41 v45 v156 v187 v202 v215))) - - (block 14 - (param v92 i32) - (param v102 i32) - (param v109 i32) - (param v121 i32) - (param v141 i32) - (param v154 i32) - (param v185 i32) - (param v200 i32) - (param v213 i32) - (let (v93 i1) (eq v92 0)) - (let (v94 i32) (zext v93)) - (let (v95 i1) (neq v94 0)) - (condbr v95 (block 17 v109 v121 v102 v141 v154 v185 v200 v213) (block 18))) - - (block 15 - (let (v65 i32) (const.i32 2)) - (let (v66 i32) (band v59 v65)) - (let (v67 i1) (neq v66 0)) - (condbr v67 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 16))) - - (block 16 - (let (v68 u32) (bitcast v61)) - (let (v69 u32) (add.checked v68 4)) - (let (v70 u32) (mod.unchecked v69 4)) - (assertz 250 v70) - (let (v71 (ptr i32)) (inttoptr v69)) - (let (v72 i32) (load v71)) - (let (v73 i32) (const.i32 3)) - (let (v74 i32) (band v72 v73)) - (let (v76 i32) (bor v74 v75)) - (let (v77 u32) (bitcast v61)) - (let (v78 u32) (add.checked v77 4)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v76) - (let (v81 u32) (bitcast v55)) - (let (v82 u32) (add.checked v81 4)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (let (v85 i32) (load v84)) - (let (v86 i32) (const.i32 -4)) - (let (v87 i32) (band v85 v86)) - (let (v88 u32) (bitcast v55)) - (let (v89 u32) (mod.unchecked v88 4)) - (assertz 250 v89) - (let (v90 (ptr i32)) (inttoptr v88)) - (let (v91 i32) (load v90)) - (br (block 14 v87 v91 v55 v85 v142 v155 v186 v201 v214))) - - (block 17 - (param v119 i32) - (param v120 i32) - (param v129 i32) - (param v140 i32) - (param v153 i32) - (param v184 i32) - (param v199 i32) - (param v212 i32) - (let (v123 i32) (const.i32 3)) - (let (v124 i32) (band v120 v123)) - (let (v125 u32) (bitcast v119)) - (let (v126 u32) (add.checked v125 4)) - (let (v127 u32) (mod.unchecked v126 4)) - (assertz 250 v127) - (let (v128 (ptr i32)) (inttoptr v126)) - (store v128 v124) - (let (v130 i32) (const.i32 3)) - (let (v131 i32) (band v129 v130)) - (let (v132 u32) (bitcast v119)) - (let (v133 u32) (mod.unchecked v132 4)) - (assertz 250 v133) - (let (v134 (ptr i32)) (inttoptr v132)) - (store v134 v131) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v129 v135)) - (let (v137 i1) (eq v136 0)) - (let (v138 i32) (zext v137)) - (let (v139 i1) (neq v138 0)) - (condbr v139 (block 19 v153 v140 v184 v199 v212) (block 20))) - - (block 18 - (let (v96 u32) (bitcast v92)) - (let (v97 u32) (mod.unchecked v96 4)) - (assertz 250 v97) - (let (v98 (ptr i32)) (inttoptr v96)) - (let (v99 i32) (load v98)) - (let (v100 i32) (const.i32 3)) - (let (v101 i32) (band v99 v100)) - (let (v103 i32) (const.i32 -4)) - (let (v104 i32) (band v102 v103)) - (let (v105 i32) (bor v101 v104)) - (let (v106 u32) (bitcast v92)) - (let (v107 u32) (mod.unchecked v106 4)) - (assertz 250 v107) - (let (v108 (ptr i32)) (inttoptr v106)) - (store v108 v105) - (let (v110 u32) (bitcast v109)) - (let (v111 u32) (add.checked v110 4)) - (let (v112 u32) (mod.unchecked v111 4)) - (assertz 250 v112) - (let (v113 (ptr i32)) (inttoptr v111)) - (let (v114 i32) (load v113)) - (let (v115 u32) (bitcast v109)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (let (v118 i32) (load v117)) - (br (block 17 v109 v114 v118 v141 v154 v185 v200 v213))) - - (block 19 - (param v152 i32) - (param v157 i32) - (param v183 i32) - (param v198 i32) - (param v211 i32) - (let (v158 u32) (bitcast v152)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v161 u32) (bitcast v157)) - (let (v162 u32) (add.checked v161 8)) - (let (v163 u32) (mod.unchecked v162 4)) - (assertz 250 v163) - (let (v164 (ptr i32)) (inttoptr v162)) - (let (v165 i32) (load v164)) - (let (v166 i32) (const.i32 1)) - (let (v167 i32) (band v165 v166)) - (let (v168 i1) (neq v167 0)) - (condbr v168 (block 9 v157 v165 v152 v183 v198 v211) (block 21))) - - (block 20 - (let (v143 u32) (bitcast v140)) - (let (v144 u32) (mod.unchecked v143 4)) - (assertz 250 v144) - (let (v145 (ptr i32)) (inttoptr v143)) - (let (v146 i32) (load v145)) - (let (v147 i32) (const.i32 2)) - (let (v148 i32) (bor v146 v147)) - (let (v149 u32) (bitcast v140)) - (let (v150 u32) (mod.unchecked v149 4)) - (assertz 250 v150) - (let (v151 (ptr i32)) (inttoptr v149)) - (store v151 v148) - (br (block 19 v153 v140 v184 v199 v212))) - - (block 21 - (br (block 10))) - - (block 22 - (param v335 i32) - (param v336 i32) - (param v341 i32) - (param v342 i32) - (param v343 i32) - (let (v337 u32) (bitcast v335)) - (let (v338 u32) (mod.unchecked v337 4)) - (assertz 250 v338) - (let (v339 (ptr i32)) (inttoptr v337)) - (store v339 v336) - (let (v340 i1) (neq v336 0)) - (condbr v340 (block 4 v336 v335 v341 v342 v343) (block 33))) - - (block 23 - (let (v193 i32) (const.i32 72)) - (let (v194 i32) (add.wrapping v179 v193)) - (let (v195 i32) (sub.wrapping v178 v181)) - (let (v203 i32) (band v195 v196)) - (let (v204 u32) (bitcast v194)) - (let (v205 u32) (bitcast v203)) - (let (v206 i1) (lte v204 v205)) - (let (v207 i32) (sext v206)) - (let (v208 i1) (neq v207 0)) - (condbr v208 (block 25) (block 26))) - - (block 24 (param v326 i32) (param v327 i32) - (let (v328 i32) (const.i32 1)) - (let (v329 i32) (bor v327 v328)) - (let (v330 u32) (bitcast v326)) - (let (v331 u32) (mod.unchecked v330 4)) - (assertz 250 v331) - (let (v332 (ptr i32)) (inttoptr v330)) - (store v332 v329) - (let (v333 i32) (const.i32 8)) - (let (v334 i32) (add.wrapping v326 v333)) - (ret v334)) - - (block 25 - (let (v229 i32) (const.i32 0)) - (let (v230 i32) (const.i32 0)) - (let (v231 u32) (bitcast v203)) - (let (v232 u32) (mod.unchecked v231 4)) - (assertz 250 v232) - (let (v233 (ptr i32)) (inttoptr v231)) - (store v233 v230) - (let (v234 i32) (const.i32 -8)) - (let (v235 i32) (add.wrapping v203 v234)) - (let (v236 i64) (const.i64 0)) - (let (v237 u32) (bitcast v235)) - (let (v238 u32) (mod.unchecked v237 4)) - (assertz 250 v238) - (let (v239 (ptr i64)) (inttoptr v237)) - (store v239 v236) - (let (v240 u32) (bitcast v172)) - (let (v241 u32) (mod.unchecked v240 4)) - (assertz 250 v241) - (let (v242 (ptr i32)) (inttoptr v240)) - (let (v243 i32) (load v242)) - (let (v244 i32) (const.i32 -4)) - (let (v245 i32) (band v243 v244)) - (let (v246 u32) (bitcast v235)) - (let (v247 u32) (mod.unchecked v246 4)) - (assertz 250 v247) - (let (v248 (ptr i32)) (inttoptr v246)) - (store v248 v245) - (let (v249 u32) (bitcast v172)) - (let (v250 u32) (mod.unchecked v249 4)) - (assertz 250 v250) - (let (v251 (ptr i32)) (inttoptr v249)) - (let (v252 i32) (load v251)) - (let (v253 i32) (const.i32 -4)) - (let (v254 i32) (band v252 v253)) - (let (v255 i1) (eq v254 0)) - (let (v256 i32) (zext v255)) - (let (v257 i1) (neq v256 0)) - (condbr v257 (block 28 v235 v229 v172 v179) (block 29))) - - (block 26 - (let (v216 i32) (band v209 v179)) - (let (v217 i1) (neq v216 0)) - (condbr v217 (block 22 v218 v219 v181 v196 v209) (block 27))) - - (block 27 - (let (v220 i32) (const.i32 -4)) - (let (v221 i32) (band v219 v220)) - (let (v222 u32) (bitcast v218)) - (let (v223 u32) (mod.unchecked v222 4)) - (assertz 250 v223) - (let (v224 (ptr i32)) (inttoptr v222)) - (store v224 v221) - (let (v225 u32) (bitcast v172)) - (let (v226 u32) (mod.unchecked v225 4)) - (assertz 250 v226) - (let (v227 (ptr i32)) (inttoptr v225)) - (let (v228 i32) (load v227)) - (br (block 24 v172 v228))) - - (block 28 - (param v280 i32) - (param v281 i32) - (param v282 i32) - (param v288 i32) - (let (v283 i32) (bor v281 v282)) - (let (v284 u32) (bitcast v280)) - (let (v285 u32) (add.checked v284 4)) - (let (v286 u32) (mod.unchecked v285 4)) - (assertz 250 v286) - (let (v287 (ptr i32)) (inttoptr v285)) - (store v287 v283) - (let (v289 u32) (bitcast v288)) - (let (v290 u32) (mod.unchecked v289 4)) - (assertz 250 v290) - (let (v291 (ptr i32)) (inttoptr v289)) - (let (v292 i32) (load v291)) - (let (v293 i32) (const.i32 -2)) - (let (v294 i32) (band v292 v293)) - (let (v295 u32) (bitcast v288)) - (let (v296 u32) (mod.unchecked v295 4)) - (assertz 250 v296) - (let (v297 (ptr i32)) (inttoptr v295)) - (store v297 v294) - (let (v298 u32) (bitcast v282)) - (let (v299 u32) (mod.unchecked v298 4)) - (assertz 250 v299) - (let (v300 (ptr i32)) (inttoptr v298)) - (let (v301 i32) (load v300)) - (let (v302 i32) (const.i32 3)) - (let (v303 i32) (band v301 v302)) - (let (v304 i32) (bor v303 v280)) - (let (v305 u32) (bitcast v282)) - (let (v306 u32) (mod.unchecked v305 4)) - (assertz 250 v306) - (let (v307 (ptr i32)) (inttoptr v305)) - (store v307 v304) - (let (v308 i32) (const.i32 2)) - (let (v309 i32) (band v301 v308)) - (let (v310 i1) (neq v309 0)) - (condbr v310 (block 31) (block 32))) - - (block 29 - (let (v258 i32) (const.i32 2)) - (let (v259 i32) (band v252 v258)) - (let (v260 i1) (neq v259 0)) - (condbr v260 (block 28 v235 v229 v172 v179) (block 30))) - - (block 30 - (let (v261 u32) (bitcast v254)) - (let (v262 u32) (add.checked v261 4)) - (let (v263 u32) (mod.unchecked v262 4)) - (assertz 250 v263) - (let (v264 (ptr i32)) (inttoptr v262)) - (let (v265 i32) (load v264)) - (let (v266 i32) (const.i32 3)) - (let (v267 i32) (band v265 v266)) - (let (v268 i32) (bor v267 v235)) - (let (v269 u32) (bitcast v254)) - (let (v270 u32) (add.checked v269 4)) - (let (v271 u32) (mod.unchecked v270 4)) - (assertz 250 v271) - (let (v272 (ptr i32)) (inttoptr v270)) - (store v272 v268) - (let (v273 u32) (bitcast v235)) - (let (v274 u32) (add.checked v273 4)) - (let (v275 u32) (mod.unchecked v274 4)) - (assertz 250 v275) - (let (v276 (ptr i32)) (inttoptr v274)) - (let (v277 i32) (load v276)) - (let (v278 i32) (const.i32 3)) - (let (v279 i32) (band v277 v278)) - (br (block 28 v235 v279 v172 v179))) - - (block 31 - (let (v315 i32) (const.i32 -3)) - (let (v316 i32) (band v304 v315)) - (let (v317 u32) (bitcast v282)) - (let (v318 u32) (mod.unchecked v317 4)) - (assertz 250 v318) - (let (v319 (ptr i32)) (inttoptr v317)) - (store v319 v316) - (let (v320 u32) (bitcast v280)) - (let (v321 u32) (mod.unchecked v320 4)) - (assertz 250 v321) - (let (v322 (ptr i32)) (inttoptr v320)) - (let (v323 i32) (load v322)) - (let (v324 i32) (const.i32 2)) - (let (v325 i32) (bor v323 v324)) - (br (block 24 v280 v325))) - - (block 32 - (let (v311 u32) (bitcast v280)) - (let (v312 u32) (mod.unchecked v311 4)) - (assertz 250 v312) - (let (v313 (ptr i32)) (inttoptr v311)) - (let (v314 i32) (load v313)) - (br (block 24 v280 v314))) - - (block 33 - (br (block 5))) - ) - - (func (export #::alloc) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 16)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i1) (neq v2 0)) - (condbr v9 (block 3) (block 4))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 (param v98 i32) (param v102 i32) - (let (v99 i32) (const.i32 16)) - (let (v100 i32) (add.wrapping v98 v99)) - (let (v101 (ptr i32)) (global.symbol #__stack_pointer)) - (store v101 v100) - (br (block 1 v102))) - - (block 3 - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr i32)) (inttoptr v10)) - (let (v13 i32) (load v12)) - (let (v14 u32) (bitcast v7)) - (let (v15 u32) (add.checked v14 12)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr i32)) (inttoptr v15)) - (store v17 v13) - (let (v18 i32) (const.i32 3)) - (let (v19 i32) (add.wrapping v2 v18)) - (let (v20 i32) (const.i32 2)) - (let (v21 u32) (bitcast v19)) - (let (v22 u32) (bitcast v20)) - (let (v23 u32) (shr.wrapping v21 v22)) - (let (v24 i32) (bitcast v23)) - (let (v25 i32) (const.i32 12)) - (let (v26 i32) (add.wrapping v7 v25)) - (let (v27 i32) (call #wee_alloc::alloc_first_fit v24 v1 v26)) - (let (v28 i1) (neq v27 0)) - (condbr v28 (block 5 v0 v7 v27) (block 6))) - - (block 4 - (br (block 2 v7 v1))) - - (block 5 (param v88 i32) (param v89 i32) (param v103 i32) - (let (v90 u32) (bitcast v89)) - (let (v91 u32) (add.checked v90 12)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (let (v94 i32) (load v93)) - (let (v95 u32) (bitcast v88)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (store v97 v94) - (br (block 2 v89 v103))) - - (block 6 - (let (v29 i32) (const.i32 -4)) - (let (v30 i32) (band v19 v29)) - (let (v31 i32) (const.i32 3)) - (let (v32 u32) (bitcast v31)) - (let (v33 i32) (shl.wrapping v1 v32)) - (let (v34 i32) (const.i32 512)) - (let (v35 i32) (add.wrapping v33 v34)) - (let (v36 u32) (bitcast v30)) - (let (v37 u32) (bitcast v35)) - (let (v38 i1) (gt v36 v37)) - (let (v39 i32) (sext v38)) - (let (v40 i1) (neq v39 0)) - (let (v41 i32) (select v40 v30 v35)) - (let (v42 i32) (const.i32 65543)) - (let (v43 i32) (add.wrapping v41 v42)) - (let (v44 i32) (const.i32 16)) - (let (v45 u32) (bitcast v43)) - (let (v46 u32) (bitcast v44)) - (let (v47 u32) (shr.wrapping v45 v46)) - (let (v48 i32) (bitcast v47)) - (let (v49 u32) (bitcast v48)) - (let (v50 i32) (memory.grow v49)) - (let (v51 i32) (const.i32 -1)) - (let (v52 i1) (neq v50 v51)) - (let (v53 i32) (zext v52)) - (let (v54 i1) (neq v53 0)) - (condbr v54 (block 7) (block 8))) - - (block 7 - (let (v56 i32) (const.i32 16)) - (let (v57 u32) (bitcast v56)) - (let (v58 i32) (shl.wrapping v50 v57)) - (let (v59 i32) (const.i32 0)) - (let (v60 u32) (bitcast v58)) - (let (v61 u32) (add.checked v60 4)) - (let (v62 u32) (mod.unchecked v61 4)) - (assertz 250 v62) - (let (v63 (ptr i32)) (inttoptr v61)) - (store v63 v59) - (let (v64 u32) (bitcast v7)) - (let (v65 u32) (add.checked v64 12)) - (let (v66 u32) (mod.unchecked v65 4)) - (assertz 250 v66) - (let (v67 (ptr i32)) (inttoptr v65)) - (let (v68 i32) (load v67)) - (let (v69 u32) (bitcast v58)) - (let (v70 u32) (add.checked v69 8)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (store v72 v68) - (let (v73 i32) (const.i32 -65536)) - (let (v74 i32) (band v43 v73)) - (let (v75 i32) (add.wrapping v58 v74)) - (let (v76 i32) (const.i32 2)) - (let (v77 i32) (bor v75 v76)) - (let (v78 u32) (bitcast v58)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v77) - (let (v81 u32) (bitcast v7)) - (let (v82 u32) (add.checked v81 12)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (store v84 v58) - (let (v85 i32) (const.i32 12)) - (let (v86 i32) (add.wrapping v7 v85)) - (let (v87 i32) (call #wee_alloc::alloc_first_fit v24 v1 v86)) - (br (block 5 v0 v7 v87))) - - (block 8 - (let (v55 i32) (const.i32 0)) - (br (block 5 v0 v7 v55))) - ) - - (func (export #::dealloc) - (param i32) (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i1) (eq v1 0)) - (let (v6 i32) (zext v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (let (v8 i1) (eq v3 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2) (block 4))) - - (block 4 - (let (v11 u32) (bitcast v0)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 i32) (const.i32 0)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (mod.unchecked v16 4)) - (assertz 250 v17) - (let (v18 (ptr i32)) (inttoptr v16)) - (store v18 v15) - (let (v19 i32) (const.i32 -8)) - (let (v20 i32) (add.wrapping v1 v19)) - (let (v21 u32) (bitcast v20)) - (let (v22 u32) (mod.unchecked v21 4)) - (assertz 250 v22) - (let (v23 (ptr i32)) (inttoptr v21)) - (let (v24 i32) (load v23)) - (let (v25 i32) (const.i32 -2)) - (let (v26 i32) (band v24 v25)) - (let (v27 u32) (bitcast v20)) - (let (v28 u32) (mod.unchecked v27 4)) - (assertz 250 v28) - (let (v29 (ptr i32)) (inttoptr v27)) - (store v29 v26) - (let (v30 i32) (const.i32 -4)) - (let (v31 i32) (add.wrapping v1 v30)) - (let (v32 u32) (bitcast v31)) - (let (v33 u32) (mod.unchecked v32 4)) - (assertz 250 v33) - (let (v34 (ptr i32)) (inttoptr v32)) - (let (v35 i32) (load v34)) - (let (v36 i32) (const.i32 -4)) - (let (v37 i32) (band v35 v36)) - (let (v38 i1) (eq v37 0)) - (let (v39 i32) (zext v38)) - (let (v40 i1) (neq v39 0)) - (condbr v40 (block 8 v24 v1 v20 v14 v0) (block 9))) - - (block 5 (param v177 i32) (param v183 i32) - (let (v185 u32) (bitcast v177)) - (let (v186 u32) (mod.unchecked v185 4)) - (assertz 250 v186) - (let (v187 (ptr i32)) (inttoptr v185)) - (store v187 v183) - (br (block 2))) - - (block 6 - (param v172 i32) - (param v173 i32) - (param v182 i32) - (param v184 i32) - (let (v174 u32) (bitcast v172)) - (let (v175 u32) (mod.unchecked v174 4)) - (assertz 250 v175) - (let (v176 (ptr i32)) (inttoptr v174)) - (store v176 v173) - (br (block 5 v182 v184))) - - (block 7 (param v168 i32) (param v178 i32) - (br (block 5 v178 v168))) - - (block 8 - (param v134 i32) - (param v150 i32) - (param v161 i32) - (param v171 i32) - (param v181 i32) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v134 v135)) - (let (v137 i1) (neq v136 0)) - (condbr v137 (block 6 v150 v171 v181 v161) (block 18))) - - (block 9 - (let (v41 u32) (bitcast v37)) - (let (v42 u32) (mod.unchecked v41 4)) - (assertz 250 v42) - (let (v43 (ptr i32)) (inttoptr v41)) - (let (v44 i32) (load v43)) - (let (v45 i32) (const.i32 1)) - (let (v46 i32) (band v44 v45)) - (let (v47 i1) (neq v46 0)) - (condbr v47 (block 8 v24 v1 v20 v14 v0) (block 10))) - - (block 10 - (let (v48 i32) (const.i32 -4)) - (let (v49 i32) (band v24 v48)) - (let (v50 i1) (neq v49 0)) - (condbr v50 (block 13) (block 14))) - - (block 11 - (param v104 i32) - (param v105 i32) - (param v111 i32) - (param v112 i32) - (param v123 i32) - (param v169 i32) - (param v179 i32) - (let (v106 i32) (const.i32 3)) - (let (v107 i32) (band v105 v106)) - (let (v108 u32) (bitcast v104)) - (let (v109 u32) (mod.unchecked v108 4)) - (assertz 250 v109) - (let (v110 (ptr i32)) (inttoptr v108)) - (store v110 v107) - (let (v113 i32) (const.i32 3)) - (let (v114 i32) (band v112 v113)) - (let (v115 u32) (bitcast v111)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (store v117 v114) - (let (v118 i32) (const.i32 2)) - (let (v119 i32) (band v112 v118)) - (let (v120 i1) (eq v119 0)) - (let (v121 i32) (zext v120)) - (let (v122 i1) (neq v121 0)) - (condbr v122 (block 7 v169 v179) (block 17))) - - (block 12 - (param v83 i32) - (param v84 i32) - (param v87 i32) - (param v94 i32) - (param v99 i32) - (param v124 i32) - (param v170 i32) - (param v180 i32) - (let (v85 i32) (const.i32 -4)) - (let (v86 i32) (band v84 v85)) - (let (v88 i32) (const.i32 3)) - (let (v89 i32) (band v87 v88)) - (let (v90 i32) (bor v86 v89)) - (let (v91 u32) (bitcast v83)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (store v93 v90) - (let (v95 u32) (bitcast v94)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (let (v98 i32) (load v97)) - (let (v100 u32) (bitcast v99)) - (let (v101 u32) (mod.unchecked v100 4)) - (assertz 250 v101) - (let (v102 (ptr i32)) (inttoptr v100)) - (let (v103 i32) (load v102)) - (br (block 11 v94 v98 v99 v103 v124 v170 v180))) - - (block 13 - (let (v51 i32) (const.i32 2)) - (let (v52 i32) (band v24 v51)) - (let (v53 i1) (neq v52 0)) - (condbr v53 (block 12 v37 v26 v44 v31 v20 v37 v14 v0) (block 15))) - - (block 14 - (br (block 12 v37 v26 v44 v31 v20 v37 v14 v0))) - - (block 15 - (let (v54 u32) (bitcast v49)) - (let (v55 u32) (add.checked v54 4)) - (let (v56 u32) (mod.unchecked v55 4)) - (assertz 250 v56) - (let (v57 (ptr i32)) (inttoptr v55)) - (let (v58 i32) (load v57)) - (let (v59 i32) (const.i32 3)) - (let (v60 i32) (band v58 v59)) - (let (v61 i32) (bor v60 v37)) - (let (v62 u32) (bitcast v49)) - (let (v63 u32) (add.checked v62 4)) - (let (v64 u32) (mod.unchecked v63 4)) - (assertz 250 v64) - (let (v65 (ptr i32)) (inttoptr v63)) - (store v65 v61) - (let (v66 u32) (bitcast v20)) - (let (v67 u32) (mod.unchecked v66 4)) - (assertz 250 v67) - (let (v68 (ptr i32)) (inttoptr v66)) - (let (v69 i32) (load v68)) - (let (v70 u32) (bitcast v31)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (let (v73 i32) (load v72)) - (let (v74 i32) (const.i32 -4)) - (let (v75 i32) (band v73 v74)) - (let (v76 i1) (eq v75 0)) - (let (v77 i32) (zext v76)) - (let (v78 i1) (neq v77 0)) - (condbr v78 (block 11 v31 v73 v20 v69 v37 v14 v0) (block 16))) - - (block 16 - (let (v79 u32) (bitcast v75)) - (let (v80 u32) (mod.unchecked v79 4)) - (assertz 250 v80) - (let (v81 (ptr i32)) (inttoptr v79)) - (let (v82 i32) (load v81)) - (br (block 12 v75 v69 v82 v31 v20 v37 v14 v0))) - - (block 17 - (let (v125 u32) (bitcast v123)) - (let (v126 u32) (mod.unchecked v125 4)) - (assertz 250 v126) - (let (v127 (ptr i32)) (inttoptr v125)) - (let (v128 i32) (load v127)) - (let (v129 i32) (const.i32 2)) - (let (v130 i32) (bor v128 v129)) - (let (v131 u32) (bitcast v123)) - (let (v132 u32) (mod.unchecked v131 4)) - (assertz 250 v132) - (let (v133 (ptr i32)) (inttoptr v131)) - (store v133 v130) - (br (block 7 v169 v179))) - - (block 18 - (let (v138 i32) (const.i32 -4)) - (let (v139 i32) (band v134 v138)) - (let (v140 i1) (eq v139 0)) - (let (v141 i32) (zext v140)) - (let (v142 i1) (neq v141 0)) - (condbr v142 (block 6 v150 v171 v181 v161) (block 19))) - - (block 19 - (let (v143 u32) (bitcast v139)) - (let (v144 (ptr u8)) (inttoptr v143)) - (let (v145 u8) (load v144)) - (let (v146 i32) (zext v145)) - (let (v147 i32) (const.i32 1)) - (let (v148 i32) (band v146 v147)) - (let (v149 i1) (neq v148 0)) - (condbr v149 (block 6 v150 v171 v181 v161) (block 20))) - - (block 20 - (let (v151 u32) (bitcast v139)) - (let (v152 u32) (add.checked v151 8)) - (let (v153 u32) (mod.unchecked v152 4)) - (assertz 250 v153) - (let (v154 (ptr i32)) (inttoptr v152)) - (let (v155 i32) (load v154)) - (let (v156 i32) (const.i32 -4)) - (let (v157 i32) (band v155 v156)) - (let (v158 u32) (bitcast v150)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v162 i32) (const.i32 1)) - (let (v163 i32) (bor v161 v162)) - (let (v164 u32) (bitcast v139)) - (let (v165 u32) (add.checked v164 8)) - (let (v166 u32) (mod.unchecked v165 4)) - (assertz 250 v166) - (let (v167 (ptr i32)) (inttoptr v165)) - (store v167 v163) - (br (block 7 v171 v181))) - ) - - (func (export #cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (call #wit_bindgen_rt::cabi_realloc v0 v1 v2 v3)) - (br (block 1 v5))) - - (block 1 (param v4 i32) - (ret v4)) - ) - - ;; Imports - (func (import #miden:base/tx@1.0.0 #create-note) - (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) (param i64) (result i64)) - (func (import #wit-component:shim #indirect-miden:base/account@1.0.0-add-asset) - (param i64) (param i64) (param i64) (param i64) (param i32)) - (func (import #wit-component:shim #indirect-miden:base/account@1.0.0-remove-asset) - (param i64) (param i64) (param i64) (param i64) (param i32)) - ) - - (module #wit-component:fixups - - ) - -) diff --git a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet.wat b/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet.wat deleted file mode 100644 index b15c11285..000000000 --- a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet.wat +++ /dev/null @@ -1,904 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "inner" u64))) - (export (;1;) "felt" (type (eq 0))) - (type (;2;) (tuple 1 1 1 1)) - (export (;3;) "word" (type (eq 2))) - (type (;4;) (record (field "inner" 3))) - (export (;5;) "core-asset" (type (eq 4))) - (type (;6;) (record (field "inner" 1))) - (export (;7;) "tag" (type (eq 6))) - (type (;8;) (record (field "inner" 3))) - (export (;9;) "recipient" (type (eq 8))) - (type (;10;) (record (field "inner" 1))) - (export (;11;) "note-id" (type (eq 10))) - ) - ) - (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) - (alias export 0 "core-asset" (type (;1;))) - (type (;2;) - (instance - (alias outer 1 1 (type (;0;))) - (export (;1;) "core-asset" (type (eq 0))) - (type (;2;) (func (param "asset" 1) (result 1))) - (export (;0;) "add-asset" (func (type 2))) - (export (;1;) "remove-asset" (func (type 2))) - ) - ) - (import "miden:base/account@1.0.0" (instance (;1;) (type 2))) - (alias export 0 "core-asset" (type (;3;))) - (alias export 0 "tag" (type (;4;))) - (alias export 0 "recipient" (type (;5;))) - (alias export 0 "note-id" (type (;6;))) - (type (;7;) - (instance - (alias outer 1 3 (type (;0;))) - (export (;1;) "core-asset" (type (eq 0))) - (alias outer 1 4 (type (;2;))) - (export (;3;) "tag" (type (eq 2))) - (alias outer 1 5 (type (;4;))) - (export (;5;) "recipient" (type (eq 4))) - (alias outer 1 6 (type (;6;))) - (export (;7;) "note-id" (type (eq 6))) - (type (;8;) (func (param "asset" 1) (param "tag" 3) (param "recipient" 5) (result 7))) - (export (;0;) "create-note" (func (type 8))) - ) - ) - (import "miden:base/tx@1.0.0" (instance (;2;) (type 7))) - (core module (;0;) - (type (;0;) (func (param i64 i64 i64 i64 i32))) - (type (;1;) (func (param i64 i64 i64 i64 i64 i64 i64 i64 i64) (result i64))) - (type (;2;) (func)) - (type (;3;) (func (param i32 i32) (result i32))) - (type (;4;) (func (param i32 i32 i32 i32) (result i32))) - (type (;5;) (func (param i64 i64 i64 i64))) - (type (;6;) (func (param i64 i64 i64 i64 i64 i64 i64 i64 i64))) - (type (;7;) (func (param i32 i32 i32) (result i32))) - (type (;8;) (func (param i32 i32 i32 i32))) - (import "miden:base/account@1.0.0" "add-asset" (func $basic_wallet::bindings::miden::base::account::add_asset::wit_import (;0;) (type 0))) - (import "miden:base/account@1.0.0" "remove-asset" (func $basic_wallet::bindings::miden::base::account::remove_asset::wit_import (;1;) (type 0))) - (import "miden:base/tx@1.0.0" "create-note" (func $basic_wallet::bindings::miden::base::tx::create_note::wit_import (;2;) (type 1))) - (func $__wasm_call_ctors (;3;) (type 2)) - (func $basic_wallet::bindings::__link_custom_section_describing_imports (;4;) (type 2)) - (func $__rust_alloc (;5;) (type 3) (param i32 i32) (result i32) - i32.const 1048596 - local.get 1 - local.get 0 - call $::alloc - ) - (func $__rust_realloc (;6;) (type 4) (param i32 i32 i32 i32) (result i32) - (local i32) - block ;; label = @1 - i32.const 1048596 - local.get 2 - local.get 3 - call $::alloc - local.tee 4 - i32.eqz - br_if 0 (;@1;) - local.get 4 - local.get 0 - local.get 1 - local.get 3 - local.get 1 - local.get 3 - i32.lt_u - select - memory.copy - i32.const 1048596 - local.get 0 - local.get 2 - local.get 1 - call $::dealloc - end - local.get 4 - ) - (func $miden:basic-wallet/basic-wallet@1.0.0#receive-asset (;7;) (type 5) (param i64 i64 i64 i64) - (local i32) - global.get $__stack_pointer - i32.const 32 - i32.sub - local.tee 4 - global.set $__stack_pointer - call $wit_bindgen_rt::run_ctors_once - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - call $basic_wallet::bindings::miden::base::account::add_asset::wit_import - local.get 4 - i32.const 32 - i32.add - global.set $__stack_pointer - ) - (func $miden:basic-wallet/basic-wallet@1.0.0#send-asset (;8;) (type 6) (param i64 i64 i64 i64 i64 i64 i64 i64 i64) - (local i32) - global.get $__stack_pointer - i32.const 32 - i32.sub - local.tee 9 - global.set $__stack_pointer - call $wit_bindgen_rt::run_ctors_once - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 9 - call $basic_wallet::bindings::miden::base::account::remove_asset::wit_import - local.get 9 - i64.load - local.get 9 - i64.load offset=8 - local.get 9 - i64.load offset=16 - local.get 9 - i64.load offset=24 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - local.get 8 - call $basic_wallet::bindings::miden::base::tx::create_note::wit_import - drop - local.get 9 - i32.const 32 - i32.add - global.set $__stack_pointer - ) - (func $wit_bindgen_rt::cabi_realloc (;9;) (type 4) (param i32 i32 i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 1 - br_if 0 (;@3;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - i32.const 0 - i32.load8_u offset=1048600 - drop - local.get 3 - local.get 2 - call $__rust_alloc - local.set 2 - br 1 (;@2;) - end - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $__rust_realloc - local.set 2 - end - local.get 2 - br_if 0 (;@1;) - unreachable - end - local.get 2 - ) - (func $wit_bindgen_rt::run_ctors_once (;10;) (type 2) - block ;; label = @1 - i32.const 0 - i32.load8_u offset=1048601 - br_if 0 (;@1;) - call $__wasm_call_ctors - i32.const 0 - i32.const 1 - i32.store8 offset=1048601 - end - ) - (func $wee_alloc::alloc_first_fit (;11;) (type 7) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 2 - i32.load - local.tee 3 - br_if 0 (;@1;) - i32.const 0 - return - end - local.get 1 - i32.const -1 - i32.add - local.set 4 - i32.const 0 - local.get 1 - i32.sub - local.set 5 - local.get 0 - i32.const 2 - i32.shl - local.set 6 - loop ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - local.get 3 - i32.const 8 - i32.add - local.set 0 - br 1 (;@2;) - end - loop ;; label = @3 - local.get 3 - local.get 1 - i32.const -2 - i32.and - i32.store offset=8 - block ;; label = @4 - block ;; label = @5 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.tee 0 - br_if 0 (;@5;) - i32.const 0 - local.set 8 - br 1 (;@4;) - end - i32.const 0 - local.get 0 - local.get 0 - i32.load8_u - i32.const 1 - i32.and - select - local.set 8 - end - block ;; label = @4 - local.get 3 - i32.load - local.tee 1 - i32.const -4 - i32.and - local.tee 9 - i32.eqz - br_if 0 (;@4;) - local.get 1 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 9 - local.get 9 - i32.load offset=4 - i32.const 3 - i32.and - local.get 0 - i32.or - i32.store offset=4 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.set 0 - local.get 3 - i32.load - local.set 1 - end - block ;; label = @4 - local.get 0 - i32.eqz - br_if 0 (;@4;) - local.get 0 - local.get 0 - i32.load - i32.const 3 - i32.and - local.get 1 - i32.const -4 - i32.and - i32.or - i32.store - local.get 3 - i32.load offset=4 - local.set 7 - local.get 3 - i32.load - local.set 1 - end - local.get 3 - local.get 7 - i32.const 3 - i32.and - i32.store offset=4 - local.get 3 - local.get 1 - i32.const 3 - i32.and - i32.store - block ;; label = @4 - local.get 1 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - end - local.get 2 - local.get 8 - i32.store - local.get 8 - local.set 3 - local.get 8 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - end - local.get 8 - i32.const 8 - i32.add - local.set 0 - local.get 8 - local.set 3 - end - block ;; label = @2 - local.get 3 - i32.load - i32.const -4 - i32.and - local.tee 8 - local.get 0 - i32.sub - local.get 6 - i32.lt_u - br_if 0 (;@2;) - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.const 72 - i32.add - local.get 8 - local.get 6 - i32.sub - local.get 5 - i32.and - local.tee 8 - i32.le_u - br_if 0 (;@4;) - local.get 4 - local.get 0 - i32.and - br_if 2 (;@2;) - local.get 2 - local.get 1 - i32.const -4 - i32.and - i32.store - local.get 3 - i32.load - local.set 0 - local.get 3 - local.set 1 - br 1 (;@3;) - end - i32.const 0 - local.set 7 - local.get 8 - i32.const 0 - i32.store - local.get 8 - i32.const -8 - i32.add - local.tee 1 - i64.const 0 - i64.store align=4 - local.get 1 - local.get 3 - i32.load - i32.const -4 - i32.and - i32.store - block ;; label = @4 - local.get 3 - i32.load - local.tee 9 - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@4;) - local.get 9 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load offset=4 - i32.const 3 - i32.and - local.get 1 - i32.or - i32.store offset=4 - local.get 1 - i32.load offset=4 - i32.const 3 - i32.and - local.set 7 - end - local.get 1 - local.get 7 - local.get 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 0 - i32.load - i32.const -2 - i32.and - i32.store - local.get 3 - local.get 3 - i32.load - local.tee 0 - i32.const 3 - i32.and - local.get 1 - i32.or - local.tee 8 - i32.store - block ;; label = @4 - local.get 0 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 1 - i32.load - local.set 0 - br 1 (;@3;) - end - local.get 3 - local.get 8 - i32.const -3 - i32.and - i32.store - local.get 1 - i32.load - i32.const 2 - i32.or - local.set 0 - end - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store - local.get 1 - i32.const 8 - i32.add - return - end - local.get 2 - local.get 1 - i32.store - local.get 1 - local.set 3 - local.get 1 - br_if 0 (;@1;) - end - i32.const 0 - ) - (func $::alloc (;12;) (type 7) (param i32 i32 i32) (result i32) - (local i32 i32 i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 3 - global.set $__stack_pointer - block ;; label = @1 - block ;; label = @2 - local.get 2 - br_if 0 (;@2;) - local.get 1 - local.set 2 - br 1 (;@1;) - end - local.get 3 - local.get 0 - i32.load - i32.store offset=12 - block ;; label = @2 - local.get 2 - i32.const 3 - i32.add - local.tee 4 - i32.const 2 - i32.shr_u - local.tee 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.tee 2 - br_if 0 (;@2;) - block ;; label = @3 - local.get 4 - i32.const -4 - i32.and - local.tee 2 - local.get 1 - i32.const 3 - i32.shl - i32.const 512 - i32.add - local.tee 4 - local.get 2 - local.get 4 - i32.gt_u - select - i32.const 65543 - i32.add - local.tee 4 - i32.const 16 - i32.shr_u - memory.grow - local.tee 2 - i32.const -1 - i32.ne - br_if 0 (;@3;) - i32.const 0 - local.set 2 - br 1 (;@2;) - end - local.get 2 - i32.const 16 - i32.shl - local.tee 2 - i32.const 0 - i32.store offset=4 - local.get 2 - local.get 3 - i32.load offset=12 - i32.store offset=8 - local.get 2 - local.get 2 - local.get 4 - i32.const -65536 - i32.and - i32.add - i32.const 2 - i32.or - i32.store - local.get 3 - local.get 2 - i32.store offset=12 - local.get 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.set 2 - end - local.get 0 - local.get 3 - i32.load offset=12 - i32.store - end - local.get 3 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 2 - ) - (func $::dealloc (;13;) (type 8) (param i32 i32 i32 i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 1 - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.eqz - br_if 0 (;@1;) - local.get 0 - i32.load - local.set 4 - local.get 1 - i32.const 0 - i32.store - local.get 1 - i32.const -8 - i32.add - local.tee 3 - local.get 3 - i32.load - local.tee 5 - i32.const -2 - i32.and - local.tee 6 - i32.store - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - local.get 1 - i32.const -4 - i32.add - local.tee 7 - i32.load - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@5;) - local.get 8 - i32.load - local.tee 9 - i32.const 1 - i32.and - br_if 0 (;@5;) - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - local.get 5 - i32.const -4 - i32.and - local.tee 10 - br_if 0 (;@8;) - local.get 8 - local.set 1 - br 1 (;@7;) - end - local.get 8 - local.set 1 - local.get 5 - i32.const 2 - i32.and - br_if 0 (;@7;) - local.get 10 - local.get 10 - i32.load offset=4 - i32.const 3 - i32.and - local.get 8 - i32.or - i32.store offset=4 - local.get 3 - i32.load - local.set 6 - local.get 7 - i32.load - local.tee 5 - i32.const -4 - i32.and - local.tee 1 - i32.eqz - br_if 1 (;@6;) - local.get 1 - i32.load - local.set 9 - end - local.get 1 - local.get 6 - i32.const -4 - i32.and - local.get 9 - i32.const 3 - i32.and - i32.or - i32.store - local.get 7 - i32.load - local.set 5 - local.get 3 - i32.load - local.set 6 - end - local.get 7 - local.get 5 - i32.const 3 - i32.and - i32.store - local.get 3 - local.get 6 - i32.const 3 - i32.and - i32.store - local.get 6 - i32.const 2 - i32.and - i32.eqz - br_if 1 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - br 1 (;@4;) - end - local.get 5 - i32.const 2 - i32.and - br_if 1 (;@3;) - local.get 5 - i32.const -4 - i32.and - local.tee 5 - i32.eqz - br_if 1 (;@3;) - local.get 5 - i32.load8_u - i32.const 1 - i32.and - br_if 1 (;@3;) - local.get 1 - local.get 5 - i32.load offset=8 - i32.const -4 - i32.and - i32.store - local.get 5 - local.get 3 - i32.const 1 - i32.or - i32.store offset=8 - end - local.get 4 - local.set 3 - br 1 (;@2;) - end - local.get 1 - local.get 4 - i32.store - end - local.get 0 - local.get 3 - i32.store - end - ) - (func $cabi_realloc (;14;) (type 4) (param i32 i32 i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $wit_bindgen_rt::cabi_realloc - ) - (table (;0;) 3 3 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "miden:basic-wallet/basic-wallet@1.0.0#receive-asset" (func $miden:basic-wallet/basic-wallet@1.0.0#receive-asset)) - (export "miden:basic-wallet/basic-wallet@1.0.0#send-asset" (func $miden:basic-wallet/basic-wallet@1.0.0#send-asset)) - (export "cabi_realloc" (func $cabi_realloc)) - (export "cabi_realloc_wit_bindgen_0_28_0" (func $wit_bindgen_rt::cabi_realloc)) - (elem (;0;) (i32.const 1) func $basic_wallet::bindings::__link_custom_section_describing_imports $cabi_realloc) - (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00\01\00\00\00\01\00\00\00\02\00\00\00") - ) - (core module (;1;) - (type (;0;) (func (param i64 i64 i64 i64 i32))) - (func $indirect-miden:base/account@1.0.0-add-asset (;0;) (type 0) (param i64 i64 i64 i64 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-miden:base/account@1.0.0-remove-asset (;1;) (type 0) (param i64 i64 i64 i64 i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - i32.const 1 - call_indirect (type 0) - ) - (table (;0;) 2 2 funcref) - (export "0" (func $indirect-miden:base/account@1.0.0-add-asset)) - (export "1" (func $indirect-miden:base/account@1.0.0-remove-asset)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i64 i64 i64 i64 i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "$imports" (table (;0;) 2 2 funcref)) - (elem (;0;) (i32.const 0) func 0 1) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (core instance (;1;) - (export "add-asset" (func 0)) - (export "remove-asset" (func 1)) - ) - (alias export 2 "create-note" (func (;0;))) - (core func (;2;) (canon lower (func 0))) - (core instance (;2;) - (export "create-note" (func 2)) - ) - (core instance (;3;) (instantiate 0 - (with "miden:base/account@1.0.0" (instance 1)) - (with "miden:base/tx@1.0.0" (instance 2)) - ) - ) - (alias core export 3 "memory" (core memory (;0;))) - (alias core export 3 "cabi_realloc" (core func (;3;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 1 "add-asset" (func (;1;))) - (core func (;4;) (canon lower (func 1) (memory 0))) - (alias export 1 "remove-asset" (func (;2;))) - (core func (;5;) (canon lower (func 2) (memory 0))) - (core instance (;4;) - (export "$imports" (table 0)) - (export "0" (func 4)) - (export "1" (func 5)) - ) - (core instance (;5;) (instantiate 2 - (with "" (instance 4)) - ) - ) - (alias export 0 "core-asset" (type (;8;))) - (type (;9;) (func (param "core-asset" 8))) - (alias core export 3 "miden:basic-wallet/basic-wallet@1.0.0#receive-asset" (core func (;6;))) - (func (;3;) (type 9) (canon lift (core func 6))) - (alias export 0 "tag" (type (;10;))) - (alias export 0 "recipient" (type (;11;))) - (type (;12;) (func (param "core-asset" 8) (param "tag" 10) (param "recipient" 11))) - (alias core export 3 "miden:basic-wallet/basic-wallet@1.0.0#send-asset" (core func (;7;))) - (func (;4;) (type 12) (canon lift (core func 7))) - (alias export 0 "felt" (type (;13;))) - (alias export 0 "word" (type (;14;))) - (alias export 0 "core-asset" (type (;15;))) - (alias export 0 "tag" (type (;16;))) - (alias export 0 "recipient" (type (;17;))) - (component (;0;) - (type (;0;) (record (field "inner" u64))) - (import "import-type-felt" (type (;1;) (eq 0))) - (type (;2;) (tuple 1 1 1 1)) - (import "import-type-word" (type (;3;) (eq 2))) - (type (;4;) (record (field "inner" 3))) - (import "import-type-core-asset" (type (;5;) (eq 4))) - (type (;6;) (record (field "inner" 1))) - (import "import-type-tag" (type (;7;) (eq 6))) - (type (;8;) (record (field "inner" 3))) - (import "import-type-recipient" (type (;9;) (eq 8))) - (import "import-type-core-asset0" (type (;10;) (eq 5))) - (type (;11;) (func (param "core-asset" 10))) - (import "import-func-receive-asset" (func (;0;) (type 11))) - (import "import-type-tag0" (type (;12;) (eq 7))) - (import "import-type-recipient0" (type (;13;) (eq 9))) - (type (;14;) (func (param "core-asset" 10) (param "tag" 12) (param "recipient" 13))) - (import "import-func-send-asset" (func (;1;) (type 14))) - (export (;15;) "core-asset" (type 5)) - (export (;16;) "tag" (type 7)) - (export (;17;) "recipient" (type 9)) - (type (;18;) (func (param "core-asset" 15))) - (export (;2;) "receive-asset" (func 0) (func (type 18))) - (type (;19;) (func (param "core-asset" 15) (param "tag" 16) (param "recipient" 17))) - (export (;3;) "send-asset" (func 1) (func (type 19))) - ) - (instance (;3;) (instantiate 0 - (with "import-func-receive-asset" (func 3)) - (with "import-func-send-asset" (func 4)) - (with "import-type-felt" (type 13)) - (with "import-type-word" (type 14)) - (with "import-type-core-asset" (type 15)) - (with "import-type-tag" (type 16)) - (with "import-type-recipient" (type 17)) - (with "import-type-core-asset0" (type 8)) - (with "import-type-tag0" (type 10)) - (with "import-type-recipient0" (type 11)) - ) - ) - (export (;4;) "miden:basic-wallet/basic-wallet@1.0.0" (instance 3)) -) \ No newline at end of file diff --git a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet_p2id_note.hir b/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet_p2id_note.hir deleted file mode 100644 index 1cab550a3..000000000 --- a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet_p2id_note.hir +++ /dev/null @@ -1,6315 +0,0 @@ -(component - ;; Component Imports - (lower (("miden:base/note@1.0.0" #get-inputs) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (result (list (struct u64)))))) (# #0) - (lower (("miden:base/note@1.0.0" #get-assets) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (result (list (struct (struct (struct u64) (struct u64) (struct u64) (struct u64)))))))) (# #1) - (lower (("miden:base/account@1.0.0" #get-id) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (result (struct (struct u64)))))) (#miden:base/account@1.0.0 #get-id) - (lower (("miden:base/core-types@1.0.0" #account-id-from-felt) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (param (struct u64)) (result (struct (struct u64)))))) (#miden:base/core-types@1.0.0 #account-id-from-felt) - (lower (("miden:basic-wallet/basic-wallet@1.0.0" #receive-asset) (digest 0x0000000000000000000000000000000000000000000000000000000000000000) (type (func (abi wasm) (param (struct (struct (struct u64) (struct u64) (struct u64) (struct u64))))))) (#miden:basic-wallet/basic-wallet@1.0.0 #receive-asset) - - ;; Modules - (module #wit-component:shim - ;; Functions - (func (export #indirect-miden:base/note@1.0.0-get-inputs) (param i32) - (block 0 (param v0 i32) - (let (v1 i32) (const.i32 0)) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #indirect-miden:base/note@1.0.0-get-assets) (param i32) - (block 0 (param v0 i32) - (let (v1 i32) (const.i32 1)) - (br (block 1))) - - (block 1 - (ret)) - ) - ) - - (module #basic_wallet_p2id_note - ;; Data Segments - (data (mut) (offset 1048576) 0x000000000400000004000000010000000200000046656c7400000000080000000800000003000000696e6e65724163636f756e74496400000000000008000000080000000400000002000000020000000200000002000000020000007372632f6c69622e727300005c0010000a000000210000002c0000005c0010000a000000240000000900000005000000696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e6465782069732000008c00100020000000ac001000120000003d3d213d6d617463686573617373657274696f6e20606c6566742020726967687460206661696c65640a20206c6566743a200a2072696768743a2000db00100010000000eb00100017000000020110000900000020726967687460206661696c65643a200a20206c6566743a20000000db001000100000002401100010000000340110000900000002011000090000003a200000000000000c000000040000000a0000000b0000000c00000020202020207b202c20207b0a2c0a7d207d636f72652f7372632f666d742f6e756d2e72738d0110001300000066000000170000003078303030313032303330343035303630373038303931303131313231333134313531363137313831393230323132323233323432353236323732383239333033313332333333343335333633373338333934303431343234333434343534363437343834393530353135323533353435353536353735383539363036313632363336343635363636373638363937303731373237333734373537363737373837393830383138323833383438353836383738383839393039313932393339343935393639373938393972616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e6774682000007a021000120000008c02100022000000) - - ;; Constants - (const (id 0) 0x00100000) - - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - - ;; Functions - (func (export #__wasm_call_ctors) - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #<&T as core::fmt::Debug>::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v5 i32) (const.i32 16)) - (let (v6 i32) (sub.wrapping v4 v5)) - (let (v7 (ptr i32)) (global.symbol #__stack_pointer)) - (store v7 v6) - (let (v8 u32) (bitcast v0)) - (let (v9 u32) (mod.unchecked v8 4)) - (assertz 250 v9) - (let (v10 (ptr i32)) (inttoptr v8)) - (let (v11 i32) (load v10)) - (let (v12 i32) (const.i32 8)) - (let (v13 i32) (add.wrapping v6 v12)) - (let (v14 i32) (const.i32 1048621)) - (let (v15 i32) (const.i32 9)) - (call #core::fmt::Formatter::debug_struct v13 v1 v14 v15) - (let (v16 i32) (const.i32 8)) - (let (v17 i32) (add.wrapping v6 v16)) - (let (v18 i32) (const.i32 1048616)) - (let (v19 i32) (const.i32 5)) - (let (v20 i32) (const.i32 1048632)) - (let (v21 i32) (call #core::fmt::builders::DebugStruct::field v17 v18 v19 v11 v20)) - (let (v22 i32) (call #core::fmt::builders::DebugStruct::finish v21)) - (let (v23 i32) (const.i32 16)) - (let (v24 i32) (add.wrapping v6 v23)) - (let (v25 (ptr i32)) (global.symbol #__stack_pointer)) - (store v25 v24) - (br (block 1 v22))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #core::fmt::num::::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 u32) (bitcast v1)) - (let (v5 u32) (add.checked v4 28)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr i32)) (inttoptr v5)) - (let (v8 i32) (load v7)) - (let (v9 i32) (const.i32 16)) - (let (v10 i32) (band v8 v9)) - (let (v11 i1) (neq v10 0)) - (condbr v11 (block 2) (block 3))) - - (block 1 (param v2 i32) - (ret v2)) - - (block 2 - (let (v17 i32) (call #core::fmt::num::::fmt v0 v1)) - (br (block 1 v17))) - - (block 3 - (let (v12 i32) (const.i32 32)) - (let (v13 i32) (band v8 v12)) - (let (v14 i1) (neq v13 0)) - (condbr v14 (block 4) (block 5))) - - (block 4 - (let (v16 i32) (call #core::fmt::num::::fmt v0 v1)) - (ret v16)) - - (block 5 - (let (v15 i32) (call #core::fmt::num::imp::::fmt v0 v1)) - (ret v15)) - ) - - (func (export #core::panicking::assert_failed) - (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v5 i32) (const.i32 16)) - (let (v6 i32) (sub.wrapping v4 v5)) - (let (v7 (ptr i32)) (global.symbol #__stack_pointer)) - (store v7 v6) - (let (v8 u32) (bitcast v6)) - (let (v9 u32) (add.checked v8 12)) - (let (v10 u32) (mod.unchecked v9 4)) - (assertz 250 v10) - (let (v11 (ptr i32)) (inttoptr v9)) - (store v11 v1) - (let (v12 u32) (bitcast v6)) - (let (v13 u32) (add.checked v12 8)) - (let (v14 u32) (mod.unchecked v13 4)) - (assertz 250 v14) - (let (v15 (ptr i32)) (inttoptr v13)) - (store v15 v0) - (let (v16 i32) (const.i32 0)) - (let (v17 i32) (const.i32 8)) - (let (v18 i32) (add.wrapping v6 v17)) - (let (v19 i32) (const.i32 1048576)) - (let (v20 i32) (const.i32 12)) - (let (v21 i32) (add.wrapping v6 v20)) - (let (v22 i32) (const.i32 1048576)) - (let (v23 i32) (const.i32 1048696)) - (call #core::panicking::assert_failed_inner v16 v18 v19 v21 v22 v2 v23) - (unreachable)) - - (block 1) - ) - - (func (export #rust_begin_unwind) (param i32) - (block 0 (param v0 i32) - (br (block 2))) - - (block 1) - - (block 2 - (br (block 2))) - - (block 3) - ) - - (func (export #::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v5 i32) (const.i32 16)) - (let (v6 i32) (sub.wrapping v4 v5)) - (let (v7 (ptr i32)) (global.symbol #__stack_pointer)) - (store v7 v6) - (let (v8 i32) (const.i32 8)) - (let (v9 i32) (add.wrapping v6 v8)) - (let (v10 i32) (const.i32 1048596)) - (let (v11 i32) (const.i32 4)) - (call #core::fmt::Formatter::debug_struct v9 v1 v10 v11) - (let (v12 i32) (const.i32 8)) - (let (v13 i32) (add.wrapping v6 v12)) - (let (v14 i32) (const.i32 1048616)) - (let (v15 i32) (const.i32 5)) - (let (v16 i32) (const.i32 1048600)) - (let (v17 i32) (call #core::fmt::builders::DebugStruct::field v13 v14 v15 v0 v16)) - (let (v18 i32) (call #core::fmt::builders::DebugStruct::finish v17)) - (let (v19 i32) (const.i32 16)) - (let (v20 i32) (add.wrapping v6 v19)) - (let (v21 (ptr i32)) (global.symbol #__stack_pointer)) - (store v21 v20) - (br (block 1 v18))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #basic_wallet_p2id_note::bindings::__link_custom_section_describing_imports) - - (block 0 - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #__rust_alloc) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1049280)) - (let (v4 i32) (call #::alloc v3 v1 v0)) - (br (block 1 v4))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #__rust_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (const.i32 0)) - (let (v6 i32) (const.i32 1049280)) - (let (v7 i32) (call #::alloc v6 v2 v3)) - (let (v8 i1) (eq v7 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2 v7) (block 3))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v22 i32) - (br (block 1 v22))) - - (block 3 - (let (v11 u32) (bitcast v1)) - (let (v12 u32) (bitcast v3)) - (let (v13 i1) (lt v11 v12)) - (let (v14 i32) (sext v13)) - (let (v15 i1) (neq v14 0)) - (let (v16 i32) (select v15 v1 v3)) - (let (v17 u32) (bitcast v7)) - (let (v18 (ptr u8)) (inttoptr v17)) - (let (v19 u32) (bitcast v0)) - (let (v20 (ptr u8)) (inttoptr v19)) - (memcpy v20 v18 v16) - (let (v21 i32) (const.i32 1049280)) - (call #::dealloc v21 v0 v2 v1) - (br (block 2 v7))) - ) - - (func (export #miden:base/note-script@1.0.0#note-script) - (block 0 - (let (v0 i32) (const.i32 0)) - (let (v1 i64) (const.i64 0)) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 48)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 (ptr i32)) (global.symbol #__stack_pointer)) - (store v6 v5) - (call #wit_bindgen_rt::run_ctors_once) - (let (v7 i64) (const.i64 0)) - (let (v8 u32) (bitcast v5)) - (let (v9 u32) (add.checked v8 24)) - (let (v10 u32) (mod.unchecked v9 8)) - (assertz 250 v10) - (let (v11 (ptr i64)) (inttoptr v9)) - (store v11 v7) - (let (v12 i32) (const.i32 24)) - (let (v13 i32) (add.wrapping v5 v12)) - (call (#wit-component:shim #indirect-miden:base/note@1.0.0-get-inputs) v13) - (let (v14 u32) (bitcast v5)) - (let (v15 u32) (add.checked v14 28)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr i32)) (inttoptr v15)) - (let (v18 i32) (load v17)) - (let (v19 i1) (eq v18 0)) - (let (v20 i32) (zext v19)) - (let (v21 i1) (neq v20 0)) - (condbr v21 (block 3) (block 4))) - - (block 1) - - (block 2 - (let (v118 i32) (const.i32 0)) - (let (v119 u32) (bitcast v5)) - (let (v120 u32) (add.checked v119 24)) - (let (v121 u32) (mod.unchecked v120 4)) - (assertz 250 v121) - (let (v122 (ptr i32)) (inttoptr v120)) - (store v122 v118) - (let (v123 i32) (const.i32 16)) - (let (v124 i32) (add.wrapping v5 v123)) - (let (v125 i32) (const.i32 8)) - (let (v126 i32) (add.wrapping v5 v125)) - (let (v127 i32) (const.i32 24)) - (let (v128 i32) (add.wrapping v5 v127)) - (call #core::panicking::assert_failed v124 v126 v128) - (unreachable)) - - (block 3 - (let (v115 i32) (const.i32 0)) - (let (v116 i32) (const.i32 0)) - (let (v117 i32) (const.i32 1048680)) - (call #core::panicking::panic_bounds_check v115 v116 v117) - (unreachable)) - - (block 4 - (let (v22 u32) (bitcast v5)) - (let (v23 u32) (add.checked v22 24)) - (let (v24 u32) (mod.unchecked v23 4)) - (assertz 250 v24) - (let (v25 (ptr i32)) (inttoptr v23)) - (let (v26 i32) (load v25)) - (let (v27 u32) (bitcast v26)) - (let (v28 u32) (mod.unchecked v27 8)) - (assertz 250 v28) - (let (v29 (ptr i64)) (inttoptr v27)) - (let (v30 i64) (load v29)) - (let (v31 i64) (call (#miden:base/core-types@1.0.0 #account-id-from-felt) v30)) - (let (v32 u32) (bitcast v5)) - (let (v33 u32) (add.checked v32 8)) - (let (v34 u32) (mod.unchecked v33 8)) - (assertz 250 v34) - (let (v35 (ptr i64)) (inttoptr v33)) - (store v35 v31) - (let (v36 i64) (call (#miden:base/account@1.0.0 #get-id))) - (let (v37 u32) (bitcast v5)) - (let (v38 u32) (add.checked v37 16)) - (let (v39 u32) (mod.unchecked v38 8)) - (assertz 250 v39) - (let (v40 (ptr i64)) (inttoptr v38)) - (store v40 v36) - (let (v41 i1) (neq v36 v31)) - (let (v42 i32) (zext v41)) - (let (v43 i1) (neq v42 0)) - (condbr v43 (block 2) (block 5))) - - (block 5 - (let (v44 i64) (const.i64 0)) - (let (v45 u32) (bitcast v5)) - (let (v46 u32) (add.checked v45 24)) - (let (v47 u32) (mod.unchecked v46 8)) - (assertz 250 v47) - (let (v48 (ptr i64)) (inttoptr v46)) - (store v48 v44) - (let (v49 i32) (const.i32 24)) - (let (v50 i32) (add.wrapping v5 v49)) - (call (#wit-component:shim #indirect-miden:base/note@1.0.0-get-assets) v50) - (let (v51 u32) (bitcast v5)) - (let (v52 u32) (add.checked v51 28)) - (let (v53 u32) (mod.unchecked v52 4)) - (assertz 250 v53) - (let (v54 (ptr i32)) (inttoptr v52)) - (let (v55 i32) (load v54)) - (let (v56 i1) (eq v55 0)) - (let (v57 i32) (zext v56)) - (let (v58 i1) (neq v57 0)) - (condbr v58 (block 6 v26 v18 v5) (block 7))) - - (block 6 (param v102 i32) (param v105 i32) (param v110 i32) - (let (v101 i32) (const.i32 1049280)) - (let (v104 i32) (const.i32 8)) - (let (v107 i32) (const.i32 3)) - (let (v108 u32) (bitcast v107)) - (let (v109 i32) (shl.wrapping v105 v108)) - (call #::dealloc v101 v102 v104 v109) - (let (v112 i32) (const.i32 48)) - (let (v113 i32) (add.wrapping v110 v112)) - (let (v114 (ptr i32)) (global.symbol #__stack_pointer)) - (store v114 v113) - (ret)) - - (block 7 - (let (v59 u32) (bitcast v5)) - (let (v60 u32) (add.checked v59 24)) - (let (v61 u32) (mod.unchecked v60 4)) - (assertz 250 v61) - (let (v62 (ptr i32)) (inttoptr v60)) - (let (v63 i32) (load v62)) - (let (v64 i32) (const.i32 5)) - (let (v65 u32) (bitcast v64)) - (let (v66 i32) (shl.wrapping v55 v65)) - (let (v67 i32) (add.wrapping v63 v66)) - (br (block 8 v63 v67 v63 v55 v26 v18 v5))) - - (block 8 - (param v68 i32) - (param v90 i32) - (param v95 i32) - (param v97 i32) - (param v103 i32) - (param v106 i32) - (param v111 i32) - (let (v69 u32) (bitcast v68)) - (let (v70 u32) (mod.unchecked v69 8)) - (assertz 250 v70) - (let (v71 (ptr i64)) (inttoptr v69)) - (let (v72 i64) (load v71)) - (let (v73 u32) (bitcast v68)) - (let (v74 u32) (add.checked v73 8)) - (let (v75 u32) (mod.unchecked v74 8)) - (assertz 250 v75) - (let (v76 (ptr i64)) (inttoptr v74)) - (let (v77 i64) (load v76)) - (let (v78 u32) (bitcast v68)) - (let (v79 u32) (add.checked v78 16)) - (let (v80 u32) (mod.unchecked v79 8)) - (assertz 250 v80) - (let (v81 (ptr i64)) (inttoptr v79)) - (let (v82 i64) (load v81)) - (let (v83 u32) (bitcast v68)) - (let (v84 u32) (add.checked v83 24)) - (let (v85 u32) (mod.unchecked v84 8)) - (assertz 250 v85) - (let (v86 (ptr i64)) (inttoptr v84)) - (let (v87 i64) (load v86)) - (call (#miden:basic-wallet/basic-wallet@1.0.0 #receive-asset) v72 v77 v82 v87) - (let (v88 i32) (const.i32 32)) - (let (v89 i32) (add.wrapping v68 v88)) - (let (v91 i1) (neq v89 v90)) - (let (v92 i32) (zext v91)) - (let (v93 i1) (neq v92 0)) - (condbr v93 (block 8 v89 v90 v95 v97 v103 v106 v111) (block 10))) - - (block 9 - (let (v94 i32) (const.i32 1049280)) - (let (v96 i32) (const.i32 8)) - (let (v98 i32) (const.i32 5)) - (let (v99 u32) (bitcast v98)) - (let (v100 i32) (shl.wrapping v97 v99)) - (call #::dealloc v94 v95 v96 v100) - (br (block 6 v103 v106 v111))) - - (block 10 - (br (block 9))) - ) - - (func (export #wit_bindgen_rt::cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i1) (neq v1 0)) - (condbr v5 (block 4) (block 5))) - - (block 1 (param v4 i32) - (ret v4)) - - (block 2 (param v19 i32) - (br (block 1 v19))) - - (block 3 (param v17 i32) - (let (v18 i1) (neq v17 0)) - (condbr v18 (block 2 v17) (block 7))) - - (block 4 - (let (v16 i32) (call #__rust_realloc v0 v1 v2 v3)) - (br (block 3 v16))) - - (block 5 - (let (v6 i1) (eq v3 0)) - (let (v7 i32) (zext v6)) - (let (v8 i1) (neq v7 0)) - (condbr v8 (block 2 v2) (block 6))) - - (block 6 - (let (v9 i32) (const.i32 0)) - (let (v10 u32) (bitcast v9)) - (let (v11 u32) (add.checked v10 1049284)) - (let (v12 (ptr u8)) (inttoptr v11)) - (let (v13 u8) (load v12)) - (let (v14 i32) (zext v13)) - (let (v15 i32) (call #__rust_alloc v3 v2)) - (br (block 3 v15))) - - (block 7 - (unreachable)) - ) - - (func (export #wit_bindgen_rt::run_ctors_once) - (block 0 - (let (v0 i32) (const.i32 0)) - (let (v1 u32) (bitcast v0)) - (let (v2 u32) (add.checked v1 1049285)) - (let (v3 (ptr u8)) (inttoptr v2)) - (let (v4 u8) (load v3)) - (let (v5 i32) (zext v4)) - (let (v6 i1) (neq v5 0)) - (condbr v6 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (call #__wasm_call_ctors) - (let (v7 i32) (const.i32 0)) - (let (v8 i32) (const.i32 1)) - (let (v9 u32) (bitcast v8)) - (let (v10 u8) (trunc v9)) - (let (v11 u32) (bitcast v7)) - (let (v12 u32) (add.checked v11 1049285)) - (let (v13 (ptr u8)) (inttoptr v12)) - (store v13 v10) - (br (block 2))) - ) - - (func (export #wee_alloc::alloc_first_fit) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 u32) (bitcast v2)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr i32)) (inttoptr v5)) - (let (v8 i32) (load v7)) - (let (v9 i1) (neq v8 0)) - (condbr v9 (block 2) (block 3))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 - (let (v11 i32) (const.i32 -1)) - (let (v12 i32) (add.wrapping v1 v11)) - (let (v13 i32) (const.i32 0)) - (let (v14 i32) (sub.wrapping v13 v1)) - (let (v15 i32) (const.i32 2)) - (let (v16 u32) (bitcast v15)) - (let (v17 i32) (shl.wrapping v0 v16)) - (br (block 4 v8 v2 v17 v14 v12))) - - (block 3 - (let (v10 i32) (const.i32 0)) - (ret v10)) - - (block 4 - (param v18 i32) - (param v169 i32) - (param v182 i32) - (param v197 i32) - (param v210 i32) - (let (v19 u32) (bitcast v18)) - (let (v20 u32) (add.checked v19 8)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr i32)) (inttoptr v20)) - (let (v23 i32) (load v22)) - (let (v24 i32) (const.i32 1)) - (let (v25 i32) (band v23 v24)) - (let (v26 i1) (neq v25 0)) - (condbr v26 (block 7) (block 8))) - - (block 5 - (let (v344 i32) (const.i32 0)) - (br (block 1 v344))) - - (block 6 - (param v172 i32) - (param v179 i32) - (param v181 i32) - (param v196 i32) - (param v209 i32) - (param v218 i32) - (param v219 i32) - (let (v173 u32) (bitcast v172)) - (let (v174 u32) (mod.unchecked v173 4)) - (assertz 250 v174) - (let (v175 (ptr i32)) (inttoptr v173)) - (let (v176 i32) (load v175)) - (let (v177 i32) (const.i32 -4)) - (let (v178 i32) (band v176 v177)) - (let (v180 i32) (sub.wrapping v178 v179)) - (let (v188 u32) (bitcast v180)) - (let (v189 u32) (bitcast v181)) - (let (v190 i1) (lt v188 v189)) - (let (v191 i32) (sext v190)) - (let (v192 i1) (neq v191 0)) - (condbr v192 (block 22 v218 v219 v181 v196 v209) (block 23))) - - (block 7 - (br (block 9 v18 v23 v169 v182 v197 v210))) - - (block 8 - (let (v27 i32) (const.i32 8)) - (let (v28 i32) (add.wrapping v18 v27)) - (br (block 6 v18 v28 v182 v197 v210 v169 v23))) - - (block 9 - (param v29 i32) - (param v30 i32) - (param v156 i32) - (param v187 i32) - (param v202 i32) - (param v215 i32) - (let (v31 i32) (const.i32 -2)) - (let (v32 i32) (band v30 v31)) - (let (v33 u32) (bitcast v29)) - (let (v34 u32) (add.checked v33 8)) - (let (v35 u32) (mod.unchecked v34 4)) - (assertz 250 v35) - (let (v36 (ptr i32)) (inttoptr v34)) - (store v36 v32) - (let (v37 u32) (bitcast v29)) - (let (v38 u32) (add.checked v37 4)) - (let (v39 u32) (mod.unchecked v38 4)) - (assertz 250 v39) - (let (v40 (ptr i32)) (inttoptr v38)) - (let (v41 i32) (load v40)) - (let (v42 i32) (const.i32 -4)) - (let (v43 i32) (band v41 v42)) - (let (v44 i1) (neq v43 0)) - (condbr v44 (block 12) (block 13))) - - (block 10 - (let (v170 i32) (const.i32 8)) - (let (v171 i32) (add.wrapping v157 v170)) - (br (block 6 v157 v171 v183 v198 v211 v152 v165))) - - (block 11 - (param v55 i32) - (param v75 i32) - (param v122 i32) - (param v142 i32) - (param v155 i32) - (param v186 i32) - (param v201 i32) - (param v214 i32) - (let (v56 u32) (bitcast v55)) - (let (v57 u32) (mod.unchecked v56 4)) - (assertz 250 v57) - (let (v58 (ptr i32)) (inttoptr v56)) - (let (v59 i32) (load v58)) - (let (v60 i32) (const.i32 -4)) - (let (v61 i32) (band v59 v60)) - (let (v62 i1) (eq v61 0)) - (let (v63 i32) (zext v62)) - (let (v64 i1) (neq v63 0)) - (condbr v64 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 15))) - - (block 12 - (let (v46 i32) (const.i32 0)) - (let (v47 u32) (bitcast v43)) - (let (v48 (ptr u8)) (inttoptr v47)) - (let (v49 u8) (load v48)) - (let (v50 i32) (zext v49)) - (let (v51 i32) (const.i32 1)) - (let (v52 i32) (band v50 v51)) - (let (v53 i1) (neq v52 0)) - (let (v54 i32) (select v53 v46 v43)) - (br (block 11 v29 v43 v41 v54 v156 v187 v202 v215))) - - (block 13 - (let (v45 i32) (const.i32 0)) - (br (block 11 v29 v43 v41 v45 v156 v187 v202 v215))) - - (block 14 - (param v92 i32) - (param v102 i32) - (param v109 i32) - (param v121 i32) - (param v141 i32) - (param v154 i32) - (param v185 i32) - (param v200 i32) - (param v213 i32) - (let (v93 i1) (eq v92 0)) - (let (v94 i32) (zext v93)) - (let (v95 i1) (neq v94 0)) - (condbr v95 (block 17 v109 v121 v102 v141 v154 v185 v200 v213) (block 18))) - - (block 15 - (let (v65 i32) (const.i32 2)) - (let (v66 i32) (band v59 v65)) - (let (v67 i1) (neq v66 0)) - (condbr v67 (block 14 v75 v59 v55 v122 v142 v155 v186 v201 v214) (block 16))) - - (block 16 - (let (v68 u32) (bitcast v61)) - (let (v69 u32) (add.checked v68 4)) - (let (v70 u32) (mod.unchecked v69 4)) - (assertz 250 v70) - (let (v71 (ptr i32)) (inttoptr v69)) - (let (v72 i32) (load v71)) - (let (v73 i32) (const.i32 3)) - (let (v74 i32) (band v72 v73)) - (let (v76 i32) (bor v74 v75)) - (let (v77 u32) (bitcast v61)) - (let (v78 u32) (add.checked v77 4)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v76) - (let (v81 u32) (bitcast v55)) - (let (v82 u32) (add.checked v81 4)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (let (v85 i32) (load v84)) - (let (v86 i32) (const.i32 -4)) - (let (v87 i32) (band v85 v86)) - (let (v88 u32) (bitcast v55)) - (let (v89 u32) (mod.unchecked v88 4)) - (assertz 250 v89) - (let (v90 (ptr i32)) (inttoptr v88)) - (let (v91 i32) (load v90)) - (br (block 14 v87 v91 v55 v85 v142 v155 v186 v201 v214))) - - (block 17 - (param v119 i32) - (param v120 i32) - (param v129 i32) - (param v140 i32) - (param v153 i32) - (param v184 i32) - (param v199 i32) - (param v212 i32) - (let (v123 i32) (const.i32 3)) - (let (v124 i32) (band v120 v123)) - (let (v125 u32) (bitcast v119)) - (let (v126 u32) (add.checked v125 4)) - (let (v127 u32) (mod.unchecked v126 4)) - (assertz 250 v127) - (let (v128 (ptr i32)) (inttoptr v126)) - (store v128 v124) - (let (v130 i32) (const.i32 3)) - (let (v131 i32) (band v129 v130)) - (let (v132 u32) (bitcast v119)) - (let (v133 u32) (mod.unchecked v132 4)) - (assertz 250 v133) - (let (v134 (ptr i32)) (inttoptr v132)) - (store v134 v131) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v129 v135)) - (let (v137 i1) (eq v136 0)) - (let (v138 i32) (zext v137)) - (let (v139 i1) (neq v138 0)) - (condbr v139 (block 19 v153 v140 v184 v199 v212) (block 20))) - - (block 18 - (let (v96 u32) (bitcast v92)) - (let (v97 u32) (mod.unchecked v96 4)) - (assertz 250 v97) - (let (v98 (ptr i32)) (inttoptr v96)) - (let (v99 i32) (load v98)) - (let (v100 i32) (const.i32 3)) - (let (v101 i32) (band v99 v100)) - (let (v103 i32) (const.i32 -4)) - (let (v104 i32) (band v102 v103)) - (let (v105 i32) (bor v101 v104)) - (let (v106 u32) (bitcast v92)) - (let (v107 u32) (mod.unchecked v106 4)) - (assertz 250 v107) - (let (v108 (ptr i32)) (inttoptr v106)) - (store v108 v105) - (let (v110 u32) (bitcast v109)) - (let (v111 u32) (add.checked v110 4)) - (let (v112 u32) (mod.unchecked v111 4)) - (assertz 250 v112) - (let (v113 (ptr i32)) (inttoptr v111)) - (let (v114 i32) (load v113)) - (let (v115 u32) (bitcast v109)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (let (v118 i32) (load v117)) - (br (block 17 v109 v114 v118 v141 v154 v185 v200 v213))) - - (block 19 - (param v152 i32) - (param v157 i32) - (param v183 i32) - (param v198 i32) - (param v211 i32) - (let (v158 u32) (bitcast v152)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v161 u32) (bitcast v157)) - (let (v162 u32) (add.checked v161 8)) - (let (v163 u32) (mod.unchecked v162 4)) - (assertz 250 v163) - (let (v164 (ptr i32)) (inttoptr v162)) - (let (v165 i32) (load v164)) - (let (v166 i32) (const.i32 1)) - (let (v167 i32) (band v165 v166)) - (let (v168 i1) (neq v167 0)) - (condbr v168 (block 9 v157 v165 v152 v183 v198 v211) (block 21))) - - (block 20 - (let (v143 u32) (bitcast v140)) - (let (v144 u32) (mod.unchecked v143 4)) - (assertz 250 v144) - (let (v145 (ptr i32)) (inttoptr v143)) - (let (v146 i32) (load v145)) - (let (v147 i32) (const.i32 2)) - (let (v148 i32) (bor v146 v147)) - (let (v149 u32) (bitcast v140)) - (let (v150 u32) (mod.unchecked v149 4)) - (assertz 250 v150) - (let (v151 (ptr i32)) (inttoptr v149)) - (store v151 v148) - (br (block 19 v153 v140 v184 v199 v212))) - - (block 21 - (br (block 10))) - - (block 22 - (param v335 i32) - (param v336 i32) - (param v341 i32) - (param v342 i32) - (param v343 i32) - (let (v337 u32) (bitcast v335)) - (let (v338 u32) (mod.unchecked v337 4)) - (assertz 250 v338) - (let (v339 (ptr i32)) (inttoptr v337)) - (store v339 v336) - (let (v340 i1) (neq v336 0)) - (condbr v340 (block 4 v336 v335 v341 v342 v343) (block 33))) - - (block 23 - (let (v193 i32) (const.i32 72)) - (let (v194 i32) (add.wrapping v179 v193)) - (let (v195 i32) (sub.wrapping v178 v181)) - (let (v203 i32) (band v195 v196)) - (let (v204 u32) (bitcast v194)) - (let (v205 u32) (bitcast v203)) - (let (v206 i1) (lte v204 v205)) - (let (v207 i32) (sext v206)) - (let (v208 i1) (neq v207 0)) - (condbr v208 (block 25) (block 26))) - - (block 24 (param v326 i32) (param v327 i32) - (let (v328 i32) (const.i32 1)) - (let (v329 i32) (bor v327 v328)) - (let (v330 u32) (bitcast v326)) - (let (v331 u32) (mod.unchecked v330 4)) - (assertz 250 v331) - (let (v332 (ptr i32)) (inttoptr v330)) - (store v332 v329) - (let (v333 i32) (const.i32 8)) - (let (v334 i32) (add.wrapping v326 v333)) - (ret v334)) - - (block 25 - (let (v229 i32) (const.i32 0)) - (let (v230 i32) (const.i32 0)) - (let (v231 u32) (bitcast v203)) - (let (v232 u32) (mod.unchecked v231 4)) - (assertz 250 v232) - (let (v233 (ptr i32)) (inttoptr v231)) - (store v233 v230) - (let (v234 i32) (const.i32 -8)) - (let (v235 i32) (add.wrapping v203 v234)) - (let (v236 i64) (const.i64 0)) - (let (v237 u32) (bitcast v235)) - (let (v238 u32) (mod.unchecked v237 4)) - (assertz 250 v238) - (let (v239 (ptr i64)) (inttoptr v237)) - (store v239 v236) - (let (v240 u32) (bitcast v172)) - (let (v241 u32) (mod.unchecked v240 4)) - (assertz 250 v241) - (let (v242 (ptr i32)) (inttoptr v240)) - (let (v243 i32) (load v242)) - (let (v244 i32) (const.i32 -4)) - (let (v245 i32) (band v243 v244)) - (let (v246 u32) (bitcast v235)) - (let (v247 u32) (mod.unchecked v246 4)) - (assertz 250 v247) - (let (v248 (ptr i32)) (inttoptr v246)) - (store v248 v245) - (let (v249 u32) (bitcast v172)) - (let (v250 u32) (mod.unchecked v249 4)) - (assertz 250 v250) - (let (v251 (ptr i32)) (inttoptr v249)) - (let (v252 i32) (load v251)) - (let (v253 i32) (const.i32 -4)) - (let (v254 i32) (band v252 v253)) - (let (v255 i1) (eq v254 0)) - (let (v256 i32) (zext v255)) - (let (v257 i1) (neq v256 0)) - (condbr v257 (block 28 v235 v229 v172 v179) (block 29))) - - (block 26 - (let (v216 i32) (band v209 v179)) - (let (v217 i1) (neq v216 0)) - (condbr v217 (block 22 v218 v219 v181 v196 v209) (block 27))) - - (block 27 - (let (v220 i32) (const.i32 -4)) - (let (v221 i32) (band v219 v220)) - (let (v222 u32) (bitcast v218)) - (let (v223 u32) (mod.unchecked v222 4)) - (assertz 250 v223) - (let (v224 (ptr i32)) (inttoptr v222)) - (store v224 v221) - (let (v225 u32) (bitcast v172)) - (let (v226 u32) (mod.unchecked v225 4)) - (assertz 250 v226) - (let (v227 (ptr i32)) (inttoptr v225)) - (let (v228 i32) (load v227)) - (br (block 24 v172 v228))) - - (block 28 - (param v280 i32) - (param v281 i32) - (param v282 i32) - (param v288 i32) - (let (v283 i32) (bor v281 v282)) - (let (v284 u32) (bitcast v280)) - (let (v285 u32) (add.checked v284 4)) - (let (v286 u32) (mod.unchecked v285 4)) - (assertz 250 v286) - (let (v287 (ptr i32)) (inttoptr v285)) - (store v287 v283) - (let (v289 u32) (bitcast v288)) - (let (v290 u32) (mod.unchecked v289 4)) - (assertz 250 v290) - (let (v291 (ptr i32)) (inttoptr v289)) - (let (v292 i32) (load v291)) - (let (v293 i32) (const.i32 -2)) - (let (v294 i32) (band v292 v293)) - (let (v295 u32) (bitcast v288)) - (let (v296 u32) (mod.unchecked v295 4)) - (assertz 250 v296) - (let (v297 (ptr i32)) (inttoptr v295)) - (store v297 v294) - (let (v298 u32) (bitcast v282)) - (let (v299 u32) (mod.unchecked v298 4)) - (assertz 250 v299) - (let (v300 (ptr i32)) (inttoptr v298)) - (let (v301 i32) (load v300)) - (let (v302 i32) (const.i32 3)) - (let (v303 i32) (band v301 v302)) - (let (v304 i32) (bor v303 v280)) - (let (v305 u32) (bitcast v282)) - (let (v306 u32) (mod.unchecked v305 4)) - (assertz 250 v306) - (let (v307 (ptr i32)) (inttoptr v305)) - (store v307 v304) - (let (v308 i32) (const.i32 2)) - (let (v309 i32) (band v301 v308)) - (let (v310 i1) (neq v309 0)) - (condbr v310 (block 31) (block 32))) - - (block 29 - (let (v258 i32) (const.i32 2)) - (let (v259 i32) (band v252 v258)) - (let (v260 i1) (neq v259 0)) - (condbr v260 (block 28 v235 v229 v172 v179) (block 30))) - - (block 30 - (let (v261 u32) (bitcast v254)) - (let (v262 u32) (add.checked v261 4)) - (let (v263 u32) (mod.unchecked v262 4)) - (assertz 250 v263) - (let (v264 (ptr i32)) (inttoptr v262)) - (let (v265 i32) (load v264)) - (let (v266 i32) (const.i32 3)) - (let (v267 i32) (band v265 v266)) - (let (v268 i32) (bor v267 v235)) - (let (v269 u32) (bitcast v254)) - (let (v270 u32) (add.checked v269 4)) - (let (v271 u32) (mod.unchecked v270 4)) - (assertz 250 v271) - (let (v272 (ptr i32)) (inttoptr v270)) - (store v272 v268) - (let (v273 u32) (bitcast v235)) - (let (v274 u32) (add.checked v273 4)) - (let (v275 u32) (mod.unchecked v274 4)) - (assertz 250 v275) - (let (v276 (ptr i32)) (inttoptr v274)) - (let (v277 i32) (load v276)) - (let (v278 i32) (const.i32 3)) - (let (v279 i32) (band v277 v278)) - (br (block 28 v235 v279 v172 v179))) - - (block 31 - (let (v315 i32) (const.i32 -3)) - (let (v316 i32) (band v304 v315)) - (let (v317 u32) (bitcast v282)) - (let (v318 u32) (mod.unchecked v317 4)) - (assertz 250 v318) - (let (v319 (ptr i32)) (inttoptr v317)) - (store v319 v316) - (let (v320 u32) (bitcast v280)) - (let (v321 u32) (mod.unchecked v320 4)) - (assertz 250 v321) - (let (v322 (ptr i32)) (inttoptr v320)) - (let (v323 i32) (load v322)) - (let (v324 i32) (const.i32 2)) - (let (v325 i32) (bor v323 v324)) - (br (block 24 v280 v325))) - - (block 32 - (let (v311 u32) (bitcast v280)) - (let (v312 u32) (mod.unchecked v311 4)) - (assertz 250 v312) - (let (v313 (ptr i32)) (inttoptr v311)) - (let (v314 i32) (load v313)) - (br (block 24 v280 v314))) - - (block 33 - (br (block 5))) - ) - - (func (export #::alloc) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 16)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i1) (neq v2 0)) - (condbr v9 (block 3) (block 4))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 (param v98 i32) (param v102 i32) - (let (v99 i32) (const.i32 16)) - (let (v100 i32) (add.wrapping v98 v99)) - (let (v101 (ptr i32)) (global.symbol #__stack_pointer)) - (store v101 v100) - (br (block 1 v102))) - - (block 3 - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr i32)) (inttoptr v10)) - (let (v13 i32) (load v12)) - (let (v14 u32) (bitcast v7)) - (let (v15 u32) (add.checked v14 12)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr i32)) (inttoptr v15)) - (store v17 v13) - (let (v18 i32) (const.i32 3)) - (let (v19 i32) (add.wrapping v2 v18)) - (let (v20 i32) (const.i32 2)) - (let (v21 u32) (bitcast v19)) - (let (v22 u32) (bitcast v20)) - (let (v23 u32) (shr.wrapping v21 v22)) - (let (v24 i32) (bitcast v23)) - (let (v25 i32) (const.i32 12)) - (let (v26 i32) (add.wrapping v7 v25)) - (let (v27 i32) (call #wee_alloc::alloc_first_fit v24 v1 v26)) - (let (v28 i1) (neq v27 0)) - (condbr v28 (block 5 v0 v7 v27) (block 6))) - - (block 4 - (br (block 2 v7 v1))) - - (block 5 (param v88 i32) (param v89 i32) (param v103 i32) - (let (v90 u32) (bitcast v89)) - (let (v91 u32) (add.checked v90 12)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (let (v94 i32) (load v93)) - (let (v95 u32) (bitcast v88)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (store v97 v94) - (br (block 2 v89 v103))) - - (block 6 - (let (v29 i32) (const.i32 -4)) - (let (v30 i32) (band v19 v29)) - (let (v31 i32) (const.i32 3)) - (let (v32 u32) (bitcast v31)) - (let (v33 i32) (shl.wrapping v1 v32)) - (let (v34 i32) (const.i32 512)) - (let (v35 i32) (add.wrapping v33 v34)) - (let (v36 u32) (bitcast v30)) - (let (v37 u32) (bitcast v35)) - (let (v38 i1) (gt v36 v37)) - (let (v39 i32) (sext v38)) - (let (v40 i1) (neq v39 0)) - (let (v41 i32) (select v40 v30 v35)) - (let (v42 i32) (const.i32 65543)) - (let (v43 i32) (add.wrapping v41 v42)) - (let (v44 i32) (const.i32 16)) - (let (v45 u32) (bitcast v43)) - (let (v46 u32) (bitcast v44)) - (let (v47 u32) (shr.wrapping v45 v46)) - (let (v48 i32) (bitcast v47)) - (let (v49 u32) (bitcast v48)) - (let (v50 i32) (memory.grow v49)) - (let (v51 i32) (const.i32 -1)) - (let (v52 i1) (neq v50 v51)) - (let (v53 i32) (zext v52)) - (let (v54 i1) (neq v53 0)) - (condbr v54 (block 7) (block 8))) - - (block 7 - (let (v56 i32) (const.i32 16)) - (let (v57 u32) (bitcast v56)) - (let (v58 i32) (shl.wrapping v50 v57)) - (let (v59 i32) (const.i32 0)) - (let (v60 u32) (bitcast v58)) - (let (v61 u32) (add.checked v60 4)) - (let (v62 u32) (mod.unchecked v61 4)) - (assertz 250 v62) - (let (v63 (ptr i32)) (inttoptr v61)) - (store v63 v59) - (let (v64 u32) (bitcast v7)) - (let (v65 u32) (add.checked v64 12)) - (let (v66 u32) (mod.unchecked v65 4)) - (assertz 250 v66) - (let (v67 (ptr i32)) (inttoptr v65)) - (let (v68 i32) (load v67)) - (let (v69 u32) (bitcast v58)) - (let (v70 u32) (add.checked v69 8)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (store v72 v68) - (let (v73 i32) (const.i32 -65536)) - (let (v74 i32) (band v43 v73)) - (let (v75 i32) (add.wrapping v58 v74)) - (let (v76 i32) (const.i32 2)) - (let (v77 i32) (bor v75 v76)) - (let (v78 u32) (bitcast v58)) - (let (v79 u32) (mod.unchecked v78 4)) - (assertz 250 v79) - (let (v80 (ptr i32)) (inttoptr v78)) - (store v80 v77) - (let (v81 u32) (bitcast v7)) - (let (v82 u32) (add.checked v81 12)) - (let (v83 u32) (mod.unchecked v82 4)) - (assertz 250 v83) - (let (v84 (ptr i32)) (inttoptr v82)) - (store v84 v58) - (let (v85 i32) (const.i32 12)) - (let (v86 i32) (add.wrapping v7 v85)) - (let (v87 i32) (call #wee_alloc::alloc_first_fit v24 v1 v86)) - (br (block 5 v0 v7 v87))) - - (block 8 - (let (v55 i32) (const.i32 0)) - (br (block 5 v0 v7 v55))) - ) - - (func (export #::dealloc) - (param i32) (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i1) (eq v1 0)) - (let (v6 i32) (zext v5)) - (let (v7 i1) (neq v6 0)) - (condbr v7 (block 2) (block 3))) - - (block 1 - (ret)) - - (block 2 - (br (block 1))) - - (block 3 - (let (v8 i1) (eq v3 0)) - (let (v9 i32) (zext v8)) - (let (v10 i1) (neq v9 0)) - (condbr v10 (block 2) (block 4))) - - (block 4 - (let (v11 u32) (bitcast v0)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 i32) (const.i32 0)) - (let (v16 u32) (bitcast v1)) - (let (v17 u32) (mod.unchecked v16 4)) - (assertz 250 v17) - (let (v18 (ptr i32)) (inttoptr v16)) - (store v18 v15) - (let (v19 i32) (const.i32 -8)) - (let (v20 i32) (add.wrapping v1 v19)) - (let (v21 u32) (bitcast v20)) - (let (v22 u32) (mod.unchecked v21 4)) - (assertz 250 v22) - (let (v23 (ptr i32)) (inttoptr v21)) - (let (v24 i32) (load v23)) - (let (v25 i32) (const.i32 -2)) - (let (v26 i32) (band v24 v25)) - (let (v27 u32) (bitcast v20)) - (let (v28 u32) (mod.unchecked v27 4)) - (assertz 250 v28) - (let (v29 (ptr i32)) (inttoptr v27)) - (store v29 v26) - (let (v30 i32) (const.i32 -4)) - (let (v31 i32) (add.wrapping v1 v30)) - (let (v32 u32) (bitcast v31)) - (let (v33 u32) (mod.unchecked v32 4)) - (assertz 250 v33) - (let (v34 (ptr i32)) (inttoptr v32)) - (let (v35 i32) (load v34)) - (let (v36 i32) (const.i32 -4)) - (let (v37 i32) (band v35 v36)) - (let (v38 i1) (eq v37 0)) - (let (v39 i32) (zext v38)) - (let (v40 i1) (neq v39 0)) - (condbr v40 (block 8 v24 v1 v20 v14 v0) (block 9))) - - (block 5 (param v177 i32) (param v183 i32) - (let (v185 u32) (bitcast v177)) - (let (v186 u32) (mod.unchecked v185 4)) - (assertz 250 v186) - (let (v187 (ptr i32)) (inttoptr v185)) - (store v187 v183) - (br (block 2))) - - (block 6 - (param v172 i32) - (param v173 i32) - (param v182 i32) - (param v184 i32) - (let (v174 u32) (bitcast v172)) - (let (v175 u32) (mod.unchecked v174 4)) - (assertz 250 v175) - (let (v176 (ptr i32)) (inttoptr v174)) - (store v176 v173) - (br (block 5 v182 v184))) - - (block 7 (param v168 i32) (param v178 i32) - (br (block 5 v178 v168))) - - (block 8 - (param v134 i32) - (param v150 i32) - (param v161 i32) - (param v171 i32) - (param v181 i32) - (let (v135 i32) (const.i32 2)) - (let (v136 i32) (band v134 v135)) - (let (v137 i1) (neq v136 0)) - (condbr v137 (block 6 v150 v171 v181 v161) (block 18))) - - (block 9 - (let (v41 u32) (bitcast v37)) - (let (v42 u32) (mod.unchecked v41 4)) - (assertz 250 v42) - (let (v43 (ptr i32)) (inttoptr v41)) - (let (v44 i32) (load v43)) - (let (v45 i32) (const.i32 1)) - (let (v46 i32) (band v44 v45)) - (let (v47 i1) (neq v46 0)) - (condbr v47 (block 8 v24 v1 v20 v14 v0) (block 10))) - - (block 10 - (let (v48 i32) (const.i32 -4)) - (let (v49 i32) (band v24 v48)) - (let (v50 i1) (neq v49 0)) - (condbr v50 (block 13) (block 14))) - - (block 11 - (param v104 i32) - (param v105 i32) - (param v111 i32) - (param v112 i32) - (param v123 i32) - (param v169 i32) - (param v179 i32) - (let (v106 i32) (const.i32 3)) - (let (v107 i32) (band v105 v106)) - (let (v108 u32) (bitcast v104)) - (let (v109 u32) (mod.unchecked v108 4)) - (assertz 250 v109) - (let (v110 (ptr i32)) (inttoptr v108)) - (store v110 v107) - (let (v113 i32) (const.i32 3)) - (let (v114 i32) (band v112 v113)) - (let (v115 u32) (bitcast v111)) - (let (v116 u32) (mod.unchecked v115 4)) - (assertz 250 v116) - (let (v117 (ptr i32)) (inttoptr v115)) - (store v117 v114) - (let (v118 i32) (const.i32 2)) - (let (v119 i32) (band v112 v118)) - (let (v120 i1) (eq v119 0)) - (let (v121 i32) (zext v120)) - (let (v122 i1) (neq v121 0)) - (condbr v122 (block 7 v169 v179) (block 17))) - - (block 12 - (param v83 i32) - (param v84 i32) - (param v87 i32) - (param v94 i32) - (param v99 i32) - (param v124 i32) - (param v170 i32) - (param v180 i32) - (let (v85 i32) (const.i32 -4)) - (let (v86 i32) (band v84 v85)) - (let (v88 i32) (const.i32 3)) - (let (v89 i32) (band v87 v88)) - (let (v90 i32) (bor v86 v89)) - (let (v91 u32) (bitcast v83)) - (let (v92 u32) (mod.unchecked v91 4)) - (assertz 250 v92) - (let (v93 (ptr i32)) (inttoptr v91)) - (store v93 v90) - (let (v95 u32) (bitcast v94)) - (let (v96 u32) (mod.unchecked v95 4)) - (assertz 250 v96) - (let (v97 (ptr i32)) (inttoptr v95)) - (let (v98 i32) (load v97)) - (let (v100 u32) (bitcast v99)) - (let (v101 u32) (mod.unchecked v100 4)) - (assertz 250 v101) - (let (v102 (ptr i32)) (inttoptr v100)) - (let (v103 i32) (load v102)) - (br (block 11 v94 v98 v99 v103 v124 v170 v180))) - - (block 13 - (let (v51 i32) (const.i32 2)) - (let (v52 i32) (band v24 v51)) - (let (v53 i1) (neq v52 0)) - (condbr v53 (block 12 v37 v26 v44 v31 v20 v37 v14 v0) (block 15))) - - (block 14 - (br (block 12 v37 v26 v44 v31 v20 v37 v14 v0))) - - (block 15 - (let (v54 u32) (bitcast v49)) - (let (v55 u32) (add.checked v54 4)) - (let (v56 u32) (mod.unchecked v55 4)) - (assertz 250 v56) - (let (v57 (ptr i32)) (inttoptr v55)) - (let (v58 i32) (load v57)) - (let (v59 i32) (const.i32 3)) - (let (v60 i32) (band v58 v59)) - (let (v61 i32) (bor v60 v37)) - (let (v62 u32) (bitcast v49)) - (let (v63 u32) (add.checked v62 4)) - (let (v64 u32) (mod.unchecked v63 4)) - (assertz 250 v64) - (let (v65 (ptr i32)) (inttoptr v63)) - (store v65 v61) - (let (v66 u32) (bitcast v20)) - (let (v67 u32) (mod.unchecked v66 4)) - (assertz 250 v67) - (let (v68 (ptr i32)) (inttoptr v66)) - (let (v69 i32) (load v68)) - (let (v70 u32) (bitcast v31)) - (let (v71 u32) (mod.unchecked v70 4)) - (assertz 250 v71) - (let (v72 (ptr i32)) (inttoptr v70)) - (let (v73 i32) (load v72)) - (let (v74 i32) (const.i32 -4)) - (let (v75 i32) (band v73 v74)) - (let (v76 i1) (eq v75 0)) - (let (v77 i32) (zext v76)) - (let (v78 i1) (neq v77 0)) - (condbr v78 (block 11 v31 v73 v20 v69 v37 v14 v0) (block 16))) - - (block 16 - (let (v79 u32) (bitcast v75)) - (let (v80 u32) (mod.unchecked v79 4)) - (assertz 250 v80) - (let (v81 (ptr i32)) (inttoptr v79)) - (let (v82 i32) (load v81)) - (br (block 12 v75 v69 v82 v31 v20 v37 v14 v0))) - - (block 17 - (let (v125 u32) (bitcast v123)) - (let (v126 u32) (mod.unchecked v125 4)) - (assertz 250 v126) - (let (v127 (ptr i32)) (inttoptr v125)) - (let (v128 i32) (load v127)) - (let (v129 i32) (const.i32 2)) - (let (v130 i32) (bor v128 v129)) - (let (v131 u32) (bitcast v123)) - (let (v132 u32) (mod.unchecked v131 4)) - (assertz 250 v132) - (let (v133 (ptr i32)) (inttoptr v131)) - (store v133 v130) - (br (block 7 v169 v179))) - - (block 18 - (let (v138 i32) (const.i32 -4)) - (let (v139 i32) (band v134 v138)) - (let (v140 i1) (eq v139 0)) - (let (v141 i32) (zext v140)) - (let (v142 i1) (neq v141 0)) - (condbr v142 (block 6 v150 v171 v181 v161) (block 19))) - - (block 19 - (let (v143 u32) (bitcast v139)) - (let (v144 (ptr u8)) (inttoptr v143)) - (let (v145 u8) (load v144)) - (let (v146 i32) (zext v145)) - (let (v147 i32) (const.i32 1)) - (let (v148 i32) (band v146 v147)) - (let (v149 i1) (neq v148 0)) - (condbr v149 (block 6 v150 v171 v181 v161) (block 20))) - - (block 20 - (let (v151 u32) (bitcast v139)) - (let (v152 u32) (add.checked v151 8)) - (let (v153 u32) (mod.unchecked v152 4)) - (assertz 250 v153) - (let (v154 (ptr i32)) (inttoptr v152)) - (let (v155 i32) (load v154)) - (let (v156 i32) (const.i32 -4)) - (let (v157 i32) (band v155 v156)) - (let (v158 u32) (bitcast v150)) - (let (v159 u32) (mod.unchecked v158 4)) - (assertz 250 v159) - (let (v160 (ptr i32)) (inttoptr v158)) - (store v160 v157) - (let (v162 i32) (const.i32 1)) - (let (v163 i32) (bor v161 v162)) - (let (v164 u32) (bitcast v139)) - (let (v165 u32) (add.checked v164 8)) - (let (v166 u32) (mod.unchecked v165 4)) - (assertz 250 v166) - (let (v167 (ptr i32)) (inttoptr v165)) - (store v167 v163) - (br (block 7 v171 v181))) - ) - - (func (export #core::panicking::panic_fmt) - (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v4 i32) (const.i32 32)) - (let (v5 i32) (sub.wrapping v3 v4)) - (let (v6 (ptr i32)) (global.symbol #__stack_pointer)) - (store v6 v5) - (let (v7 i32) (const.i32 16)) - (let (v8 i32) (add.wrapping v5 v7)) - (let (v9 i32) (const.i32 16)) - (let (v10 i32) (add.wrapping v0 v9)) - (let (v11 u32) (bitcast v10)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i64)) (inttoptr v11)) - (let (v14 i64) (load v13)) - (let (v15 u32) (bitcast v8)) - (let (v16 u32) (mod.unchecked v15 8)) - (assertz 250 v16) - (let (v17 (ptr i64)) (inttoptr v15)) - (store v17 v14) - (let (v18 i32) (const.i32 8)) - (let (v19 i32) (add.wrapping v5 v18)) - (let (v20 i32) (const.i32 8)) - (let (v21 i32) (add.wrapping v0 v20)) - (let (v22 u32) (bitcast v21)) - (let (v23 u32) (mod.unchecked v22 4)) - (assertz 250 v23) - (let (v24 (ptr i64)) (inttoptr v22)) - (let (v25 i64) (load v24)) - (let (v26 u32) (bitcast v19)) - (let (v27 u32) (mod.unchecked v26 8)) - (assertz 250 v27) - (let (v28 (ptr i64)) (inttoptr v26)) - (store v28 v25) - (let (v29 i32) (const.i32 1)) - (let (v30 u32) (bitcast v29)) - (let (v31 u16) (trunc v30)) - (let (v32 u32) (bitcast v5)) - (let (v33 u32) (add.checked v32 28)) - (let (v34 u32) (mod.unchecked v33 2)) - (assertz 250 v34) - (let (v35 (ptr u16)) (inttoptr v33)) - (store v35 v31) - (let (v36 u32) (bitcast v5)) - (let (v37 u32) (add.checked v36 24)) - (let (v38 u32) (mod.unchecked v37 4)) - (assertz 250 v38) - (let (v39 (ptr i32)) (inttoptr v37)) - (store v39 v1) - (let (v40 u32) (bitcast v0)) - (let (v41 u32) (mod.unchecked v40 4)) - (assertz 250 v41) - (let (v42 (ptr i64)) (inttoptr v40)) - (let (v43 i64) (load v42)) - (let (v44 u32) (bitcast v5)) - (let (v45 u32) (mod.unchecked v44 8)) - (assertz 250 v45) - (let (v46 (ptr i64)) (inttoptr v44)) - (store v46 v43) - (call #rust_begin_unwind v5) - (unreachable)) - - (block 1) - ) - - (func (export #core::slice::index::slice_start_index_len_fail) - (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i64) (const.i64 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 48)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 u32) (bitcast v7)) - (let (v10 u32) (mod.unchecked v9 4)) - (assertz 250 v10) - (let (v11 (ptr i32)) (inttoptr v9)) - (store v11 v0) - (let (v12 u32) (bitcast v7)) - (let (v13 u32) (add.checked v12 4)) - (let (v14 u32) (mod.unchecked v13 4)) - (assertz 250 v14) - (let (v15 (ptr i32)) (inttoptr v13)) - (store v15 v1) - (let (v16 i32) (const.i32 2)) - (let (v17 u32) (bitcast v7)) - (let (v18 u32) (add.checked v17 12)) - (let (v19 u32) (mod.unchecked v18 4)) - (assertz 250 v19) - (let (v20 (ptr i32)) (inttoptr v18)) - (store v20 v16) - (let (v21 i32) (const.i32 1049264)) - (let (v22 u32) (bitcast v7)) - (let (v23 u32) (add.checked v22 8)) - (let (v24 u32) (mod.unchecked v23 4)) - (assertz 250 v24) - (let (v25 (ptr i32)) (inttoptr v23)) - (store v25 v21) - (let (v26 i64) (const.i64 2)) - (let (v27 u32) (bitcast v7)) - (let (v28 u32) (add.checked v27 20)) - (let (v29 u32) (mod.unchecked v28 4)) - (assertz 250 v29) - (let (v30 (ptr i64)) (inttoptr v28)) - (store v30 v26) - (let (v31 i32) (const.i32 6)) - (let (v32 u32) (bitcast v31)) - (let (v33 u64) (zext v32)) - (let (v34 i64) (bitcast v33)) - (let (v35 i64) (const.i64 32)) - (let (v36 u32) (cast v35)) - (let (v37 i64) (shl.wrapping v34 v36)) - (let (v38 i32) (const.i32 4)) - (let (v39 i32) (add.wrapping v7 v38)) - (let (v40 u32) (bitcast v39)) - (let (v41 u64) (zext v40)) - (let (v42 i64) (bitcast v41)) - (let (v43 i64) (bor v37 v42)) - (let (v44 u32) (bitcast v7)) - (let (v45 u32) (add.checked v44 40)) - (let (v46 u32) (mod.unchecked v45 8)) - (assertz 250 v46) - (let (v47 (ptr i64)) (inttoptr v45)) - (store v47 v43) - (let (v48 u32) (bitcast v7)) - (let (v49 u64) (zext v48)) - (let (v50 i64) (bitcast v49)) - (let (v51 i64) (bor v37 v50)) - (let (v52 u32) (bitcast v7)) - (let (v53 u32) (add.checked v52 32)) - (let (v54 u32) (mod.unchecked v53 8)) - (assertz 250 v54) - (let (v55 (ptr i64)) (inttoptr v53)) - (store v55 v51) - (let (v56 i32) (const.i32 32)) - (let (v57 i32) (add.wrapping v7 v56)) - (let (v58 u32) (bitcast v7)) - (let (v59 u32) (add.checked v58 16)) - (let (v60 u32) (mod.unchecked v59 4)) - (assertz 250 v60) - (let (v61 (ptr i32)) (inttoptr v59)) - (store v61 v57) - (let (v62 i32) (const.i32 8)) - (let (v63 i32) (add.wrapping v7 v62)) - (call #core::panicking::panic_fmt v63 v2) - (unreachable)) - - (block 1) - ) - - (func (export #core::panicking::panic_bounds_check) - (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i64) (const.i64 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 48)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 u32) (bitcast v7)) - (let (v10 u32) (add.checked v9 4)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr i32)) (inttoptr v10)) - (store v12 v1) - (let (v13 u32) (bitcast v7)) - (let (v14 u32) (mod.unchecked v13 4)) - (assertz 250 v14) - (let (v15 (ptr i32)) (inttoptr v13)) - (store v15 v0) - (let (v16 i32) (const.i32 2)) - (let (v17 u32) (bitcast v7)) - (let (v18 u32) (add.checked v17 12)) - (let (v19 u32) (mod.unchecked v18 4)) - (assertz 250 v19) - (let (v20 (ptr i32)) (inttoptr v18)) - (store v20 v16) - (let (v21 i32) (const.i32 1048768)) - (let (v22 u32) (bitcast v7)) - (let (v23 u32) (add.checked v22 8)) - (let (v24 u32) (mod.unchecked v23 4)) - (assertz 250 v24) - (let (v25 (ptr i32)) (inttoptr v23)) - (store v25 v21) - (let (v26 i64) (const.i64 2)) - (let (v27 u32) (bitcast v7)) - (let (v28 u32) (add.checked v27 20)) - (let (v29 u32) (mod.unchecked v28 4)) - (assertz 250 v29) - (let (v30 (ptr i64)) (inttoptr v28)) - (store v30 v26) - (let (v31 i32) (const.i32 6)) - (let (v32 u32) (bitcast v31)) - (let (v33 u64) (zext v32)) - (let (v34 i64) (bitcast v33)) - (let (v35 i64) (const.i64 32)) - (let (v36 u32) (cast v35)) - (let (v37 i64) (shl.wrapping v34 v36)) - (let (v38 u32) (bitcast v7)) - (let (v39 u64) (zext v38)) - (let (v40 i64) (bitcast v39)) - (let (v41 i64) (bor v37 v40)) - (let (v42 u32) (bitcast v7)) - (let (v43 u32) (add.checked v42 40)) - (let (v44 u32) (mod.unchecked v43 8)) - (assertz 250 v44) - (let (v45 (ptr i64)) (inttoptr v43)) - (store v45 v41) - (let (v46 i32) (const.i32 4)) - (let (v47 i32) (add.wrapping v7 v46)) - (let (v48 u32) (bitcast v47)) - (let (v49 u64) (zext v48)) - (let (v50 i64) (bitcast v49)) - (let (v51 i64) (bor v37 v50)) - (let (v52 u32) (bitcast v7)) - (let (v53 u32) (add.checked v52 32)) - (let (v54 u32) (mod.unchecked v53 8)) - (assertz 250 v54) - (let (v55 (ptr i64)) (inttoptr v53)) - (store v55 v51) - (let (v56 i32) (const.i32 32)) - (let (v57 i32) (add.wrapping v7 v56)) - (let (v58 u32) (bitcast v7)) - (let (v59 u32) (add.checked v58 16)) - (let (v60 u32) (mod.unchecked v59 4)) - (assertz 250 v60) - (let (v61 (ptr i32)) (inttoptr v59)) - (store v61 v57) - (let (v62 i32) (const.i32 8)) - (let (v63 i32) (add.wrapping v7 v62)) - (call #core::panicking::panic_fmt v63 v2) - (unreachable)) - - (block 1) - ) - - (func (export #core::fmt::Formatter::pad) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 u32) (bitcast v0)) - (let (v6 u32) (add.checked v5 8)) - (let (v7 u32) (mod.unchecked v6 4)) - (assertz 250 v7) - (let (v8 (ptr i32)) (inttoptr v6)) - (let (v9 i32) (load v8)) - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr i32)) (inttoptr v10)) - (let (v13 i32) (load v12)) - (let (v14 i1) (neq v13 0)) - (condbr v14 (block 3 v9 v1 v2 v0 v13) (block 4))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 - (let (v415 u32) (bitcast v0)) - (let (v416 u32) (add.checked v415 20)) - (let (v417 u32) (mod.unchecked v416 4)) - (assertz 250 v417) - (let (v418 (ptr i32)) (inttoptr v416)) - (let (v419 i32) (load v418)) - (let (v420 u32) (bitcast v0)) - (let (v421 u32) (add.checked v420 24)) - (let (v422 u32) (mod.unchecked v421 4)) - (assertz 250 v422) - (let (v423 (ptr i32)) (inttoptr v421)) - (let (v424 i32) (load v423)) - (let (v425 u32) (bitcast v424)) - (let (v426 u32) (add.checked v425 12)) - (let (v427 u32) (mod.unchecked v426 4)) - (assertz 250 v427) - (let (v428 (ptr i32)) (inttoptr v426)) - (let (v429 i32) (load v428)) - (br (block 1 v429))) - - (block 3 - (param v20 i32) - (param v26 i32) - (param v27 i32) - (param v29 i32) - (param v145 i32) - (let (v21 i32) (const.i32 1)) - (let (v22 i32) (band v20 v21)) - (let (v23 i1) (eq v22 0)) - (let (v24 i32) (zext v23)) - (let (v25 i1) (neq v24 0)) - (condbr v25 (block 6 v145 v29 v26 v27) (block 7))) - - (block 4 - (let (v15 i32) (const.i32 1)) - (let (v16 i32) (band v9 v15)) - (let (v17 i1) (eq v16 0)) - (let (v18 i32) (zext v17)) - (let (v19 i1) (neq v18 0)) - (condbr v19 (block 2) (block 5))) - - (block 5 - (br (block 3 v9 v1 v2 v0 v13))) - - (block 6 - (param v144 i32) - (param v153 i32) - (param v165 i32) - (param v166 i32) - (let (v152 i1) (neq v144 0)) - (condbr v152 (block 32) (block 33))) - - (block 7 - (let (v28 i32) (add.wrapping v26 v27)) - (let (v30 u32) (bitcast v29)) - (let (v31 u32) (add.checked v30 12)) - (let (v32 u32) (mod.unchecked v31 4)) - (assertz 250 v32) - (let (v33 (ptr i32)) (inttoptr v31)) - (let (v34 i32) (load v33)) - (let (v35 i1) (neq v34 0)) - (condbr v35 (block 9) (block 10))) - - (block 8 - (param v83 i32) - (param v84 i32) - (param v102 i32) - (param v107 i32) - (param v116 i32) - (param v148 i32) - (param v156 i32) - (let (v85 i1) (eq v83 v84)) - (let (v86 i32) (zext v85)) - (let (v87 i1) (neq v86 0)) - (condbr v87 (block 6 v148 v156 v116 v107) (block 22))) - - (block 9 - (let (v37 i32) (const.i32 0)) - (br (block 11 v26 v28 v37 v34 v27 v26 v145 v29))) - - (block 10 - (let (v36 i32) (const.i32 0)) - (br (block 8 v26 v28 v36 v27 v26 v145 v29))) - - (block 11 - (param v38 i32) - (param v39 i32) - (param v75 i32) - (param v78 i32) - (param v109 i32) - (param v118 i32) - (param v146 i32) - (param v154 i32) - (let (v40 i1) (eq v38 v39)) - (let (v41 i32) (zext v40)) - (let (v42 i1) (neq v41 0)) - (condbr v42 (block 6 v146 v154 v118 v109) (block 13))) - - (block 12 - (br (block 8 v71 v82 v76 v108 v117 v147 v155))) - - (block 13 - (let (v43 u32) (bitcast v38)) - (let (v44 (ptr i8)) (inttoptr v43)) - (let (v45 i8) (load v44)) - (let (v46 i32) (sext v45)) - (let (v47 i32) (const.i32 -1)) - (let (v48 i1) (lte v46 v47)) - (let (v49 i32) (sext v48)) - (let (v50 i1) (neq v49 0)) - (condbr v50 (block 15) (block 16))) - - (block 14 - (param v71 i32) - (param v72 i32) - (param v74 i32) - (param v77 i32) - (param v82 i32) - (param v108 i32) - (param v117 i32) - (param v147 i32) - (param v155 i32) - (let (v73 i32) (sub.wrapping v71 v72)) - (let (v76 i32) (add.wrapping v73 v74)) - (let (v79 i32) (const.i32 -1)) - (let (v80 i32) (add.wrapping v77 v79)) - (let (v81 i1) (neq v80 0)) - (condbr v81 (block 11 v71 v82 v76 v80 v108 v117 v147 v155) (block 21))) - - (block 15 - (let (v53 i32) (const.i32 -32)) - (let (v54 u32) (bitcast v46)) - (let (v55 u32) (bitcast v53)) - (let (v56 i1) (gte v54 v55)) - (let (v57 i32) (zext v56)) - (let (v58 i1) (neq v57 0)) - (condbr v58 (block 17) (block 18))) - - (block 16 - (let (v51 i32) (const.i32 1)) - (let (v52 i32) (add.wrapping v38 v51)) - (br (block 14 v52 v38 v75 v78 v39 v109 v118 v146 v154))) - - (block 17 - (let (v61 i32) (const.i32 -16)) - (let (v62 u32) (bitcast v46)) - (let (v63 u32) (bitcast v61)) - (let (v64 i1) (gte v62 v63)) - (let (v65 i32) (zext v64)) - (let (v66 i1) (neq v65 0)) - (condbr v66 (block 19) (block 20))) - - (block 18 - (let (v59 i32) (const.i32 2)) - (let (v60 i32) (add.wrapping v38 v59)) - (br (block 14 v60 v38 v75 v78 v39 v109 v118 v146 v154))) - - (block 19 - (let (v69 i32) (const.i32 4)) - (let (v70 i32) (add.wrapping v38 v69)) - (br (block 14 v70 v38 v75 v78 v39 v109 v118 v146 v154))) - - (block 20 - (let (v67 i32) (const.i32 3)) - (let (v68 i32) (add.wrapping v38 v67)) - (br (block 14 v68 v38 v75 v78 v39 v109 v118 v146 v154))) - - (block 21 - (br (block 12))) - - (block 22 - (let (v88 u32) (bitcast v83)) - (let (v89 (ptr i8)) (inttoptr v88)) - (let (v90 i8) (load v89)) - (let (v91 i32) (sext v90)) - (let (v92 i32) (const.i32 -1)) - (let (v93 i1) (gt v91 v92)) - (let (v94 i32) (zext v93)) - (let (v95 i1) (neq v94 0)) - (condbr v95 (block 23 v102 v107 v116 v148 v156) (block 24))) - - (block 23 - (param v101 i32) - (param v106 i32) - (param v115 i32) - (param v150 i32) - (param v158 i32) - (let (v103 i1) (eq v101 0)) - (let (v104 i32) (zext v103)) - (let (v105 i1) (neq v104 0)) - (condbr v105 (block 26 v115 v101 v106 v150 v158) (block 27))) - - (block 24 - (let (v96 i32) (const.i32 -32)) - (let (v97 u32) (bitcast v91)) - (let (v98 u32) (bitcast v96)) - (let (v99 i1) (lt v97 v98)) - (let (v100 i32) (sext v99)) - (br (block 23 v102 v107 v116 v148 v156))) - - (block 25 - (param v134 i32) - (param v136 i32) - (param v138 i32) - (param v141 i32) - (param v149 i32) - (param v157 i32) - (let (v139 i1) (neq v138 0)) - (let (v140 i32) (select v139 v134 v136)) - (let (v142 i1) (neq v138 0)) - (let (v143 i32) (select v142 v138 v141)) - (br (block 6 v149 v157 v143 v140))) - - (block 26 - (param v133 i32) - (param v135 i32) - (param v137 i32) - (param v151 i32) - (param v159 i32) - (br (block 25 v135 v137 v133 v133 v151 v159))) - - (block 27 - (let (v110 u32) (bitcast v101)) - (let (v111 u32) (bitcast v106)) - (let (v112 i1) (gte v110 v111)) - (let (v113 i32) (zext v112)) - (let (v114 i1) (neq v113 0)) - (condbr v114 (block 28) (block 29))) - - (block 28 - (let (v129 i1) (eq v101 v106)) - (let (v130 i32) (zext v129)) - (let (v131 i1) (neq v130 0)) - (condbr v131 (block 26 v115 v101 v106 v150 v158) (block 31))) - - (block 29 - (let (v119 i32) (add.wrapping v115 v101)) - (let (v120 u32) (bitcast v119)) - (let (v121 (ptr i8)) (inttoptr v120)) - (let (v122 i8) (load v121)) - (let (v123 i32) (sext v122)) - (let (v124 i32) (const.i32 -65)) - (let (v125 i1) (gt v123 v124)) - (let (v126 i32) (zext v125)) - (let (v127 i1) (neq v126 0)) - (condbr v127 (block 26 v115 v101 v106 v150 v158) (block 30))) - - (block 30 - (let (v128 i32) (const.i32 0)) - (br (block 25 v101 v106 v128 v115 v150 v158))) - - (block 31 - (let (v132 i32) (const.i32 0)) - (br (block 25 v101 v106 v132 v115 v150 v158))) - - (block 32 - (let (v177 u32) (bitcast v153)) - (let (v178 u32) (add.checked v177 4)) - (let (v179 u32) (mod.unchecked v178 4)) - (assertz 250 v179) - (let (v180 (ptr i32)) (inttoptr v178)) - (let (v181 i32) (load v180)) - (let (v182 i32) (const.i32 16)) - (let (v183 u32) (bitcast v166)) - (let (v184 u32) (bitcast v182)) - (let (v185 i1) (lt v183 v184)) - (let (v186 i32) (sext v185)) - (let (v187 i1) (neq v186 0)) - (condbr v187 (block 35) (block 36))) - - (block 33 - (let (v160 u32) (bitcast v153)) - (let (v161 u32) (add.checked v160 20)) - (let (v162 u32) (mod.unchecked v161 4)) - (assertz 250 v162) - (let (v163 (ptr i32)) (inttoptr v161)) - (let (v164 i32) (load v163)) - (let (v167 u32) (bitcast v153)) - (let (v168 u32) (add.checked v167 24)) - (let (v169 u32) (mod.unchecked v168 4)) - (assertz 250 v169) - (let (v170 (ptr i32)) (inttoptr v168)) - (let (v171 i32) (load v170)) - (let (v172 u32) (bitcast v171)) - (let (v173 u32) (add.checked v172 12)) - (let (v174 u32) (mod.unchecked v173 4)) - (assertz 250 v174) - (let (v175 (ptr i32)) (inttoptr v173)) - (let (v176 i32) (load v175)) - (ret v176)) - - (block 34 - (param v278 i32) - (param v282 i32) - (param v290 i32) - (param v355 i32) - (param v357 i32) - (let (v283 u32) (bitcast v278)) - (let (v284 u32) (bitcast v282)) - (let (v285 i1) (lte v283 v284)) - (let (v286 i32) (sext v285)) - (let (v287 i1) (neq v286 0)) - (condbr v287 (block 50) (block 51))) - - (block 35 - (let (v189 i1) (neq v166 0)) - (condbr v189 (block 37) (block 38))) - - (block 36 - (let (v188 i32) (call #core::str::count::do_count_chars v165 v166)) - (br (block 34 v181 v188 v153 v165 v166))) - - (block 37 - (let (v191 i32) (const.i32 3)) - (let (v192 i32) (band v166 v191)) - (let (v193 i32) (const.i32 4)) - (let (v194 u32) (bitcast v166)) - (let (v195 u32) (bitcast v193)) - (let (v196 i1) (gte v194 v195)) - (let (v197 i32) (zext v196)) - (let (v198 i1) (neq v197 0)) - (condbr v198 (block 40) (block 41))) - - (block 38 - (let (v190 i32) (const.i32 0)) - (br (block 34 v181 v190 v153 v165 v166))) - - (block 39 - (param v253 i32) - (param v258 i32) - (param v259 i32) - (param v277 i32) - (param v279 i32) - (param v291 i32) - (param v358 i32) - (let (v255 i1) (eq v253 0)) - (let (v256 i32) (zext v255)) - (let (v257 i1) (neq v256 0)) - (condbr v257 (block 34 v279 v277 v291 v258 v358) (block 45))) - - (block 40 - (let (v201 i32) (const.i32 12)) - (let (v202 i32) (band v166 v201)) - (let (v203 i32) (const.i32 0)) - (let (v204 i32) (const.i32 0)) - (br (block 42 v203 v165 v204 v202 v192 v181 v153 v166))) - - (block 41 - (let (v199 i32) (const.i32 0)) - (let (v200 i32) (const.i32 0)) - (br (block 39 v192 v165 v200 v199 v181 v153 v166))) - - (block 42 - (param v205 i32) - (param v206 i32) - (param v207 i32) - (param v247 i32) - (param v254 i32) - (param v280 i32) - (param v292 i32) - (param v359 i32) - (let (v208 i32) (add.wrapping v206 v207)) - (let (v209 u32) (bitcast v208)) - (let (v210 (ptr i8)) (inttoptr v209)) - (let (v211 i8) (load v210)) - (let (v212 i32) (sext v211)) - (let (v213 i32) (const.i32 -65)) - (let (v214 i1) (gt v212 v213)) - (let (v215 i32) (zext v214)) - (let (v216 i32) (add.wrapping v205 v215)) - (let (v217 i32) (const.i32 1)) - (let (v218 i32) (add.wrapping v208 v217)) - (let (v219 u32) (bitcast v218)) - (let (v220 (ptr i8)) (inttoptr v219)) - (let (v221 i8) (load v220)) - (let (v222 i32) (sext v221)) - (let (v223 i32) (const.i32 -65)) - (let (v224 i1) (gt v222 v223)) - (let (v225 i32) (zext v224)) - (let (v226 i32) (add.wrapping v216 v225)) - (let (v227 i32) (const.i32 2)) - (let (v228 i32) (add.wrapping v208 v227)) - (let (v229 u32) (bitcast v228)) - (let (v230 (ptr i8)) (inttoptr v229)) - (let (v231 i8) (load v230)) - (let (v232 i32) (sext v231)) - (let (v233 i32) (const.i32 -65)) - (let (v234 i1) (gt v232 v233)) - (let (v235 i32) (zext v234)) - (let (v236 i32) (add.wrapping v226 v235)) - (let (v237 i32) (const.i32 3)) - (let (v238 i32) (add.wrapping v208 v237)) - (let (v239 u32) (bitcast v238)) - (let (v240 (ptr i8)) (inttoptr v239)) - (let (v241 i8) (load v240)) - (let (v242 i32) (sext v241)) - (let (v243 i32) (const.i32 -65)) - (let (v244 i1) (gt v242 v243)) - (let (v245 i32) (zext v244)) - (let (v246 i32) (add.wrapping v236 v245)) - (let (v248 i32) (const.i32 4)) - (let (v249 i32) (add.wrapping v207 v248)) - (let (v250 i1) (neq v247 v249)) - (let (v251 i32) (zext v250)) - (let (v252 i1) (neq v251 0)) - (condbr v252 (block 42 v246 v206 v249 v247 v254 v280 v292 v359) (block 44))) - - (block 43 - (br (block 39 v254 v206 v249 v246 v280 v292 v359))) - - (block 44 - (br (block 43))) - - (block 45 - (let (v260 i32) (add.wrapping v258 v259)) - (br (block 46 v277 v260 v253 v279 v291 v258 v358))) - - (block 46 - (param v261 i32) - (param v262 i32) - (param v273 i32) - (param v281 i32) - (param v293 i32) - (param v356 i32) - (param v360 i32) - (let (v263 u32) (bitcast v262)) - (let (v264 (ptr i8)) (inttoptr v263)) - (let (v265 i8) (load v264)) - (let (v266 i32) (sext v265)) - (let (v267 i32) (const.i32 -65)) - (let (v268 i1) (gt v266 v267)) - (let (v269 i32) (zext v268)) - (let (v270 i32) (add.wrapping v261 v269)) - (let (v271 i32) (const.i32 1)) - (let (v272 i32) (add.wrapping v262 v271)) - (let (v274 i32) (const.i32 -1)) - (let (v275 i32) (add.wrapping v273 v274)) - (let (v276 i1) (neq v275 0)) - (condbr v276 (block 46 v270 v272 v275 v281 v293 v356 v360) (block 48))) - - (block 47 - (br (block 34 v281 v270 v293 v356 v360))) - - (block 48 - (br (block 47))) - - (block 49 - (let (v375 u32) (bitcast v340)) - (let (v376 u32) (add.checked v375 12)) - (let (v377 u32) (mod.unchecked v376 4)) - (assertz 250 v377) - (let (v378 (ptr i32)) (inttoptr v376)) - (let (v379 i32) (load v378)) - (let (v380 i1) (eq v379 0)) - (let (v381 i32) (zext v380)) - (let (v382 i1) (neq v381 0)) - (condbr v382 (block 59) (block 60))) - - (block 50 - (let (v350 u32) (bitcast v290)) - (let (v351 u32) (add.checked v350 20)) - (let (v352 u32) (mod.unchecked v351 4)) - (assertz 250 v352) - (let (v353 (ptr i32)) (inttoptr v351)) - (let (v354 i32) (load v353)) - (let (v361 u32) (bitcast v290)) - (let (v362 u32) (add.checked v361 24)) - (let (v363 u32) (mod.unchecked v362 4)) - (assertz 250 v363) - (let (v364 (ptr i32)) (inttoptr v362)) - (let (v365 i32) (load v364)) - (let (v366 u32) (bitcast v365)) - (let (v367 u32) (add.checked v366 12)) - (let (v368 u32) (mod.unchecked v367 4)) - (assertz 250 v368) - (let (v369 (ptr i32)) (inttoptr v367)) - (let (v370 i32) (load v369)) - (ret v370)) - - (block 51 - (let (v288 i32) (sub.wrapping v278 v282)) - (let (v289 i32) (const.i32 0)) - (let (v294 u32) (bitcast v290)) - (let (v295 u32) (add.checked v294 32)) - (let (v296 (ptr u8)) (inttoptr v295)) - (let (v297 u8) (load v296)) - (let (v298 i32) (zext v297)) - (let (v299 u32) (cast v298)) - (switchv299 - (0 . (block 54)) - (1 . (block 53)) - (2 . (block 52 v289 v290 v355 v357 v288)) - (3 . (block 52 v289 v290 v355 v357 v288)) - (_ . (block 52 v289 v290 v355 v357 v288)))) - - (block 52 - (param v313 i32) - (param v316 i32) - (param v372 i32) - (param v374 i32) - (param v408 i32) - (let (v314 i32) (const.i32 1)) - (let (v315 i32) (add.wrapping v313 v314)) - (let (v317 u32) (bitcast v316)) - (let (v318 u32) (add.checked v317 16)) - (let (v319 u32) (mod.unchecked v318 4)) - (assertz 250 v319) - (let (v320 (ptr i32)) (inttoptr v318)) - (let (v321 i32) (load v320)) - (let (v322 u32) (bitcast v316)) - (let (v323 u32) (add.checked v322 24)) - (let (v324 u32) (mod.unchecked v323 4)) - (assertz 250 v324) - (let (v325 (ptr i32)) (inttoptr v323)) - (let (v326 i32) (load v325)) - (let (v327 u32) (bitcast v316)) - (let (v328 u32) (add.checked v327 20)) - (let (v329 u32) (mod.unchecked v328 4)) - (assertz 250 v329) - (let (v330 (ptr i32)) (inttoptr v328)) - (let (v331 i32) (load v330)) - (br (block 55 v315 v331 v321 v326 v372 v374 v408))) - - (block 53 - (let (v301 i32) (const.i32 1)) - (let (v302 u32) (bitcast v288)) - (let (v303 u32) (bitcast v301)) - (let (v304 u32) (shr.wrapping v302 v303)) - (let (v305 i32) (bitcast v304)) - (let (v306 i32) (const.i32 1)) - (let (v307 i32) (add.wrapping v288 v306)) - (let (v308 i32) (const.i32 1)) - (let (v309 u32) (bitcast v307)) - (let (v310 u32) (bitcast v308)) - (let (v311 u32) (shr.wrapping v309 v310)) - (let (v312 i32) (bitcast v311)) - (br (block 52 v305 v290 v355 v357 v312))) - - (block 54 - (let (v300 i32) (const.i32 0)) - (br (block 52 v288 v290 v355 v357 v300))) - - (block 55 - (param v332 i32) - (param v338 i32) - (param v339 i32) - (param v340 i32) - (param v371 i32) - (param v373 i32) - (param v407 i32) - (let (v333 i32) (const.i32 -1)) - (let (v334 i32) (add.wrapping v332 v333)) - (let (v335 i1) (eq v334 0)) - (let (v336 i32) (zext v335)) - (let (v337 i1) (neq v336 0)) - (condbr v337 (block 49) (block 57))) - - (block 56 - (let (v349 i32) (const.i32 1)) - (ret v349)) - - (block 57 - (let (v341 u32) (bitcast v340)) - (let (v342 u32) (add.checked v341 16)) - (let (v343 u32) (mod.unchecked v342 4)) - (assertz 250 v343) - (let (v344 (ptr i32)) (inttoptr v342)) - (let (v345 i32) (load v344)) - (let (v346 i1) (eq v345 0)) - (let (v347 i32) (zext v346)) - (let (v348 i1) (neq v347 0)) - (condbr v348 (block 55 v334 v338 v339 v340 v371 v373 v407) (block 58))) - - (block 58 - (br (block 56))) - - (block 59 - (let (v384 i32) (const.i32 0)) - (br (block 61 v407 v384 v338 v339 v340))) - - (block 60 - (let (v383 i32) (const.i32 1)) - (ret v383)) - - (block 61 - (param v385 i32) - (param v386 i32) - (param v396 i32) - (param v397 i32) - (param v398 i32) - (let (v387 i1) (neq v385 v386)) - (let (v388 i32) (zext v387)) - (let (v389 i1) (neq v388 0)) - (condbr v389 (block 63) (block 64))) - - (block 62 - (let (v409 i32) (const.i32 -1)) - (let (v410 i32) (add.wrapping v395 v409)) - (let (v411 u32) (bitcast v410)) - (let (v412 u32) (bitcast v385)) - (let (v413 i1) (lt v411 v412)) - (let (v414 i32) (sext v413)) - (ret v414)) - - (block 63 - (let (v394 i32) (const.i32 1)) - (let (v395 i32) (add.wrapping v386 v394)) - (let (v399 u32) (bitcast v398)) - (let (v400 u32) (add.checked v399 16)) - (let (v401 u32) (mod.unchecked v400 4)) - (assertz 250 v401) - (let (v402 (ptr i32)) (inttoptr v400)) - (let (v403 i32) (load v402)) - (let (v404 i1) (eq v403 0)) - (let (v405 i32) (zext v404)) - (let (v406 i1) (neq v405 0)) - (condbr v406 (block 61 v385 v395 v396 v397 v398) (block 65))) - - (block 64 - (let (v390 u32) (bitcast v385)) - (let (v391 u32) (bitcast v385)) - (let (v392 i1) (lt v390 v391)) - (let (v393 i32) (sext v392)) - (ret v393)) - - (block 65 - (br (block 62))) - ) - - (func (export #core::fmt::num::imp::::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (mod.unchecked v3 4)) - (assertz 250 v4) - (let (v5 (ptr u32)) (inttoptr v3)) - (let (v6 u32) (load v5)) - (let (v7 i64) (zext v6)) - (let (v8 i32) (const.i32 1)) - (let (v9 i32) (call #core::fmt::num::imp::fmt_u64 v7 v8 v1)) - (br (block 1 v9))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #core::fmt::write) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v6 i32) (const.i32 48)) - (let (v7 i32) (sub.wrapping v5 v6)) - (let (v8 (ptr i32)) (global.symbol #__stack_pointer)) - (store v8 v7) - (let (v9 i32) (const.i32 3)) - (let (v10 u32) (bitcast v9)) - (let (v11 u8) (trunc v10)) - (let (v12 u32) (bitcast v7)) - (let (v13 u32) (add.checked v12 44)) - (let (v14 (ptr u8)) (inttoptr v13)) - (store v14 v11) - (let (v15 i32) (const.i32 32)) - (let (v16 u32) (bitcast v7)) - (let (v17 u32) (add.checked v16 28)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr i32)) (inttoptr v17)) - (store v19 v15) - (let (v20 i32) (const.i32 0)) - (let (v21 i32) (const.i32 0)) - (let (v22 u32) (bitcast v7)) - (let (v23 u32) (add.checked v22 40)) - (let (v24 u32) (mod.unchecked v23 4)) - (assertz 250 v24) - (let (v25 (ptr i32)) (inttoptr v23)) - (store v25 v21) - (let (v26 u32) (bitcast v7)) - (let (v27 u32) (add.checked v26 36)) - (let (v28 u32) (mod.unchecked v27 4)) - (assertz 250 v28) - (let (v29 (ptr i32)) (inttoptr v27)) - (store v29 v1) - (let (v30 u32) (bitcast v7)) - (let (v31 u32) (add.checked v30 32)) - (let (v32 u32) (mod.unchecked v31 4)) - (assertz 250 v32) - (let (v33 (ptr i32)) (inttoptr v31)) - (store v33 v0) - (let (v34 i32) (const.i32 0)) - (let (v35 u32) (bitcast v7)) - (let (v36 u32) (add.checked v35 20)) - (let (v37 u32) (mod.unchecked v36 4)) - (assertz 250 v37) - (let (v38 (ptr i32)) (inttoptr v36)) - (store v38 v34) - (let (v39 i32) (const.i32 0)) - (let (v40 u32) (bitcast v7)) - (let (v41 u32) (add.checked v40 12)) - (let (v42 u32) (mod.unchecked v41 4)) - (assertz 250 v42) - (let (v43 (ptr i32)) (inttoptr v41)) - (store v43 v39) - (let (v44 u32) (bitcast v2)) - (let (v45 u32) (add.checked v44 16)) - (let (v46 u32) (mod.unchecked v45 4)) - (assertz 250 v46) - (let (v47 (ptr i32)) (inttoptr v45)) - (let (v48 i32) (load v47)) - (let (v49 i1) (neq v48 0)) - (condbr v49 (block 6) (block 7))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 (param v436 i32) (param v442 i32) - (let (v439 i32) (const.i32 48)) - (let (v440 i32) (add.wrapping v436 v439)) - (let (v441 (ptr i32)) (global.symbol #__stack_pointer)) - (store v441 v440) - (br (block 1 v442))) - - (block 3 (param v438 i32) - (let (v435 i32) (const.i32 0)) - (br (block 2 v438 v435))) - - (block 4 (param v437 i32) - (let (v434 i32) (const.i32 1)) - (br (block 2 v437 v434))) - - (block 5 (param v370 i32) (param v379 i32) (param v398 i32) - (let (v388 u32) (bitcast v379)) - (let (v389 u32) (add.checked v388 4)) - (let (v390 u32) (mod.unchecked v389 4)) - (assertz 250 v390) - (let (v391 (ptr i32)) (inttoptr v389)) - (let (v392 i32) (load v391)) - (let (v393 u32) (bitcast v370)) - (let (v394 u32) (bitcast v392)) - (let (v395 i1) (gte v393 v394)) - (let (v396 i32) (zext v395)) - (let (v397 i1) (neq v396 0)) - (condbr v397 (block 3 v398) (block 32))) - - (block 6 - (let (v132 u32) (bitcast v2)) - (let (v133 u32) (add.checked v132 20)) - (let (v134 u32) (mod.unchecked v133 4)) - (assertz 250 v134) - (let (v135 (ptr i32)) (inttoptr v133)) - (let (v136 i32) (load v135)) - (let (v137 i1) (eq v136 0)) - (let (v138 i32) (zext v137)) - (let (v139 i1) (neq v138 0)) - (condbr v139 (block 5 v20 v2 v7) (block 16))) - - (block 7 - (let (v50 u32) (bitcast v2)) - (let (v51 u32) (add.checked v50 12)) - (let (v52 u32) (mod.unchecked v51 4)) - (assertz 250 v52) - (let (v53 (ptr i32)) (inttoptr v51)) - (let (v54 i32) (load v53)) - (let (v55 i1) (eq v54 0)) - (let (v56 i32) (zext v55)) - (let (v57 i1) (neq v56 0)) - (condbr v57 (block 5 v20 v2 v7) (block 8))) - - (block 8 - (let (v58 u32) (bitcast v2)) - (let (v59 u32) (add.checked v58 8)) - (let (v60 u32) (mod.unchecked v59 4)) - (assertz 250 v60) - (let (v61 (ptr i32)) (inttoptr v59)) - (let (v62 i32) (load v61)) - (let (v63 i32) (const.i32 3)) - (let (v64 u32) (bitcast v63)) - (let (v65 i32) (shl.wrapping v54 v64)) - (let (v66 i32) (const.i32 -1)) - (let (v67 i32) (add.wrapping v54 v66)) - (let (v68 i32) (const.i32 536870911)) - (let (v69 i32) (band v67 v68)) - (let (v70 i32) (const.i32 1)) - (let (v71 i32) (add.wrapping v69 v70)) - (let (v72 u32) (bitcast v2)) - (let (v73 u32) (mod.unchecked v72 4)) - (assertz 250 v73) - (let (v74 (ptr i32)) (inttoptr v72)) - (let (v75 i32) (load v74)) - (br (block 9 v75 v7 v62 v65 v71 v2))) - - (block 9 - (param v76 i32) - (param v86 i32) - (param v108 i32) - (param v128 i32) - (param v372 i32) - (param v381 i32) - (let (v77 i32) (const.i32 4)) - (let (v78 i32) (add.wrapping v76 v77)) - (let (v79 u32) (bitcast v78)) - (let (v80 u32) (mod.unchecked v79 4)) - (assertz 250 v80) - (let (v81 (ptr i32)) (inttoptr v79)) - (let (v82 i32) (load v81)) - (let (v83 i1) (eq v82 0)) - (let (v84 i32) (zext v83)) - (let (v85 i1) (neq v84 0)) - (condbr v85 (block 11 v108 v86 v76 v128 v372 v381) (block 12))) - - (block 10) - - (block 11 - (param v107 i32) - (param v113 i32) - (param v124 i32) - (param v127 i32) - (param v371 i32) - (param v380 i32) - (let (v109 u32) (bitcast v107)) - (let (v110 u32) (mod.unchecked v109 4)) - (assertz 250 v110) - (let (v111 (ptr i32)) (inttoptr v109)) - (let (v112 i32) (load v111)) - (let (v114 i32) (const.i32 12)) - (let (v115 i32) (add.wrapping v113 v114)) - (let (v116 u32) (bitcast v107)) - (let (v117 u32) (add.checked v116 4)) - (let (v118 u32) (mod.unchecked v117 4)) - (assertz 250 v118) - (let (v119 (ptr i32)) (inttoptr v117)) - (let (v120 i32) (load v119)) - (let (v121 i1) (neq v120 0)) - (condbr v121 (block 4 v113) (block 14))) - - (block 12 - (let (v87 u32) (bitcast v86)) - (let (v88 u32) (add.checked v87 32)) - (let (v89 u32) (mod.unchecked v88 4)) - (assertz 250 v89) - (let (v90 (ptr i32)) (inttoptr v88)) - (let (v91 i32) (load v90)) - (let (v92 u32) (bitcast v76)) - (let (v93 u32) (mod.unchecked v92 4)) - (assertz 250 v93) - (let (v94 (ptr i32)) (inttoptr v92)) - (let (v95 i32) (load v94)) - (let (v96 u32) (bitcast v86)) - (let (v97 u32) (add.checked v96 36)) - (let (v98 u32) (mod.unchecked v97 4)) - (assertz 250 v98) - (let (v99 (ptr i32)) (inttoptr v97)) - (let (v100 i32) (load v99)) - (let (v101 u32) (bitcast v100)) - (let (v102 u32) (add.checked v101 12)) - (let (v103 u32) (mod.unchecked v102 4)) - (assertz 250 v103) - (let (v104 (ptr i32)) (inttoptr v102)) - (let (v105 i32) (load v104)) - (let (v106 i1) (neq v105 0)) - (condbr v106 (block 4 v86) (block 13))) - - (block 13 - (br (block 11 v108 v86 v76 v128 v372 v381))) - - (block 14 - (let (v122 i32) (const.i32 8)) - (let (v123 i32) (add.wrapping v107 v122)) - (let (v125 i32) (const.i32 8)) - (let (v126 i32) (add.wrapping v124 v125)) - (let (v129 i32) (const.i32 -8)) - (let (v130 i32) (add.wrapping v127 v129)) - (let (v131 i1) (neq v130 0)) - (condbr v131 (block 9 v126 v113 v123 v130 v371 v380) (block 15))) - - (block 15 - (br (block 5 v371 v380 v113))) - - (block 16 - (let (v140 i32) (const.i32 5)) - (let (v141 u32) (bitcast v140)) - (let (v142 i32) (shl.wrapping v136 v141)) - (let (v143 i32) (const.i32 -1)) - (let (v144 i32) (add.wrapping v136 v143)) - (let (v145 i32) (const.i32 134217727)) - (let (v146 i32) (band v144 v145)) - (let (v147 i32) (const.i32 1)) - (let (v148 i32) (add.wrapping v146 v147)) - (let (v149 u32) (bitcast v2)) - (let (v150 u32) (add.checked v149 8)) - (let (v151 u32) (mod.unchecked v150 4)) - (assertz 250 v151) - (let (v152 (ptr i32)) (inttoptr v150)) - (let (v153 i32) (load v152)) - (let (v154 u32) (bitcast v2)) - (let (v155 u32) (mod.unchecked v154 4)) - (assertz 250 v155) - (let (v156 (ptr i32)) (inttoptr v154)) - (let (v157 i32) (load v156)) - (let (v158 i32) (const.i32 0)) - (br (block 17 v157 v7 v48 v158 v153 v142 v148 v2))) - - (block 17 - (param v159 i32) - (param v169 i32) - (param v192 i32) - (param v194 i32) - (param v247 i32) - (param v354 i32) - (param v376 i32) - (param v385 i32) - (let (v160 i32) (const.i32 4)) - (let (v161 i32) (add.wrapping v159 v160)) - (let (v162 u32) (bitcast v161)) - (let (v163 u32) (mod.unchecked v162 4)) - (assertz 250 v163) - (let (v164 (ptr i32)) (inttoptr v162)) - (let (v165 i32) (load v164)) - (let (v166 i1) (eq v165 0)) - (let (v167 i32) (zext v166)) - (let (v168 i1) (neq v167 0)) - (condbr v168 (block 19 v169 v192 v194 v247 v159 v354 v376 v385) (block 20))) - - (block 18 - (br (block 5 v373 v382 v303))) - - (block 19 - (param v190 i32) - (param v191 i32) - (param v193 i32) - (param v246 i32) - (param v346 i32) - (param v353 i32) - (param v375 i32) - (param v384 i32) - (let (v195 i32) (add.wrapping v191 v193)) - (let (v196 i32) (const.i32 16)) - (let (v197 i32) (add.wrapping v195 v196)) - (let (v198 u32) (bitcast v197)) - (let (v199 u32) (mod.unchecked v198 4)) - (assertz 250 v199) - (let (v200 (ptr i32)) (inttoptr v198)) - (let (v201 i32) (load v200)) - (let (v202 u32) (bitcast v190)) - (let (v203 u32) (add.checked v202 28)) - (let (v204 u32) (mod.unchecked v203 4)) - (assertz 250 v204) - (let (v205 (ptr i32)) (inttoptr v203)) - (store v205 v201) - (let (v206 i32) (const.i32 28)) - (let (v207 i32) (add.wrapping v195 v206)) - (let (v208 u32) (bitcast v207)) - (let (v209 (ptr u8)) (inttoptr v208)) - (let (v210 u8) (load v209)) - (let (v211 i32) (zext v210)) - (let (v212 u32) (bitcast v211)) - (let (v213 u8) (trunc v212)) - (let (v214 u32) (bitcast v190)) - (let (v215 u32) (add.checked v214 44)) - (let (v216 (ptr u8)) (inttoptr v215)) - (store v216 v213) - (let (v217 i32) (const.i32 24)) - (let (v218 i32) (add.wrapping v195 v217)) - (let (v219 u32) (bitcast v218)) - (let (v220 u32) (mod.unchecked v219 4)) - (assertz 250 v220) - (let (v221 (ptr i32)) (inttoptr v219)) - (let (v222 i32) (load v221)) - (let (v223 u32) (bitcast v190)) - (let (v224 u32) (add.checked v223 40)) - (let (v225 u32) (mod.unchecked v224 4)) - (assertz 250 v225) - (let (v226 (ptr i32)) (inttoptr v224)) - (store v226 v222) - (let (v227 i32) (const.i32 12)) - (let (v228 i32) (add.wrapping v195 v227)) - (let (v229 u32) (bitcast v228)) - (let (v230 u32) (mod.unchecked v229 4)) - (assertz 250 v230) - (let (v231 (ptr i32)) (inttoptr v229)) - (let (v232 i32) (load v231)) - (let (v233 i32) (const.i32 0)) - (let (v234 i32) (const.i32 0)) - (let (v235 i32) (const.i32 8)) - (let (v236 i32) (add.wrapping v195 v235)) - (let (v237 u32) (bitcast v236)) - (let (v238 u32) (mod.unchecked v237 4)) - (assertz 250 v238) - (let (v239 (ptr i32)) (inttoptr v237)) - (let (v240 i32) (load v239)) - (let (v241 u32) (cast v240)) - (switchv241 - (0 . (block 24)) - (1 . (block 23 v190 v232 v195 v246 v233 v346 v353 v193 v191 v375 v384)) - (2 . (block 22 v190 v232 v234 v195 v246 v233 v346 v353 v193 v191 v375 v384)) - (_ . (block 23 v190 v232 v195 v246 v233 v346 v353 v193 v191 v375 v384)))) - - (block 20 - (let (v170 u32) (bitcast v169)) - (let (v171 u32) (add.checked v170 32)) - (let (v172 u32) (mod.unchecked v171 4)) - (assertz 250 v172) - (let (v173 (ptr i32)) (inttoptr v171)) - (let (v174 i32) (load v173)) - (let (v175 u32) (bitcast v159)) - (let (v176 u32) (mod.unchecked v175 4)) - (assertz 250 v176) - (let (v177 (ptr i32)) (inttoptr v175)) - (let (v178 i32) (load v177)) - (let (v179 u32) (bitcast v169)) - (let (v180 u32) (add.checked v179 36)) - (let (v181 u32) (mod.unchecked v180 4)) - (assertz 250 v181) - (let (v182 (ptr i32)) (inttoptr v180)) - (let (v183 i32) (load v182)) - (let (v184 u32) (bitcast v183)) - (let (v185 u32) (add.checked v184 12)) - (let (v186 u32) (mod.unchecked v185 4)) - (assertz 250 v186) - (let (v187 (ptr i32)) (inttoptr v185)) - (let (v188 i32) (load v187)) - (let (v189 i1) (neq v188 0)) - (condbr v189 (block 4 v169) (block 21))) - - (block 21 - (br (block 19 v169 v192 v194 v247 v159 v354 v376 v385))) - - (block 22 - (param v260 i32) - (param v262 i32) - (param v268 i32) - (param v273 i32) - (param v289 i32) - (param v312 i32) - (param v345 i32) - (param v352 i32) - (param v358 i32) - (param v367 i32) - (param v374 i32) - (param v383 i32) - (let (v264 u32) (bitcast v260)) - (let (v265 u32) (add.checked v264 16)) - (let (v266 u32) (mod.unchecked v265 4)) - (assertz 250 v266) - (let (v267 (ptr i32)) (inttoptr v265)) - (store v267 v262) - (let (v269 u32) (bitcast v260)) - (let (v270 u32) (add.checked v269 12)) - (let (v271 u32) (mod.unchecked v270 4)) - (assertz 250 v271) - (let (v272 (ptr i32)) (inttoptr v270)) - (store v272 v268) - (let (v275 i32) (const.i32 4)) - (let (v276 i32) (add.wrapping v273 v275)) - (let (v277 u32) (bitcast v276)) - (let (v278 u32) (mod.unchecked v277 4)) - (assertz 250 v278) - (let (v279 (ptr i32)) (inttoptr v277)) - (let (v280 i32) (load v279)) - (let (v281 u32) (bitcast v273)) - (let (v282 u32) (mod.unchecked v281 4)) - (assertz 250 v282) - (let (v283 (ptr i32)) (inttoptr v281)) - (let (v284 i32) (load v283)) - (let (v285 u32) (cast v284)) - (switchv285 - (0 . (block 28)) - (1 . (block 27 v260 v280 v289 v273 v345 v352 v358 v367 v374 v383)) - (2 . (block 26 v260 v280 v312 v289 v273 v345 v352 v358 v367 v374 v383)) - (_ . (block 27 v260 v280 v289 v273 v345 v352 v358 v367 v374 v383)))) - - (block 23 - (param v261 i32) - (param v263 i32) - (param v274 i32) - (param v290 i32) - (param v313 i32) - (param v347 i32) - (param v355 i32) - (param v359 i32) - (param v368 i32) - (param v377 i32) - (param v386 i32) - (let (v259 i32) (const.i32 1)) - (br (block 22 v261 v263 v259 v274 v290 v313 v347 v355 v359 v368 v377 v386))) - - (block 24 - (let (v242 i32) (const.i32 3)) - (let (v243 u32) (bitcast v242)) - (let (v244 i32) (shl.wrapping v232 v243)) - (let (v245 i32) (const.i32 0)) - (let (v248 i32) (add.wrapping v246 v244)) - (let (v249 u32) (bitcast v248)) - (let (v250 u32) (add.checked v249 4)) - (let (v251 u32) (mod.unchecked v250 4)) - (assertz 250 v251) - (let (v252 (ptr i32)) (inttoptr v250)) - (let (v253 i32) (load v252)) - (let (v254 i1) (neq v253 0)) - (condbr v254 (block 22 v190 v232 v245 v195 v246 v233 v346 v353 v193 v191 v375 v384) (block 25))) - - (block 25 - (let (v255 u32) (bitcast v248)) - (let (v256 u32) (mod.unchecked v255 4)) - (assertz 250 v256) - (let (v257 (ptr i32)) (inttoptr v255)) - (let (v258 i32) (load v257)) - (br (block 23 v190 v258 v195 v246 v233 v346 v353 v193 v191 v375 v384))) - - (block 26 - (param v303 i32) - (param v305 i32) - (param v311 i32) - (param v318 i32) - (param v320 i32) - (param v344 i32) - (param v351 i32) - (param v357 i32) - (param v366 i32) - (param v373 i32) - (param v382 i32) - (let (v307 u32) (bitcast v303)) - (let (v308 u32) (add.checked v307 24)) - (let (v309 u32) (mod.unchecked v308 4)) - (assertz 250 v309) - (let (v310 (ptr i32)) (inttoptr v308)) - (store v310 v305) - (let (v314 u32) (bitcast v303)) - (let (v315 u32) (add.checked v314 20)) - (let (v316 u32) (mod.unchecked v315 4)) - (assertz 250 v316) - (let (v317 (ptr i32)) (inttoptr v315)) - (store v317 v311) - (let (v322 i32) (const.i32 20)) - (let (v323 i32) (add.wrapping v320 v322)) - (let (v324 u32) (bitcast v323)) - (let (v325 u32) (mod.unchecked v324 4)) - (assertz 250 v325) - (let (v326 (ptr i32)) (inttoptr v324)) - (let (v327 i32) (load v326)) - (let (v328 i32) (const.i32 3)) - (let (v329 u32) (bitcast v328)) - (let (v330 i32) (shl.wrapping v327 v329)) - (let (v331 i32) (add.wrapping v318 v330)) - (let (v332 u32) (bitcast v331)) - (let (v333 u32) (mod.unchecked v332 4)) - (assertz 250 v333) - (let (v334 (ptr i32)) (inttoptr v332)) - (let (v335 i32) (load v334)) - (let (v336 i32) (const.i32 12)) - (let (v337 i32) (add.wrapping v303 v336)) - (let (v338 u32) (bitcast v331)) - (let (v339 u32) (add.checked v338 4)) - (let (v340 u32) (mod.unchecked v339 4)) - (assertz 250 v340) - (let (v341 (ptr i32)) (inttoptr v339)) - (let (v342 i32) (load v341)) - (let (v343 i1) (neq v342 0)) - (condbr v343 (block 4 v303) (block 30))) - - (block 27 - (param v304 i32) - (param v306 i32) - (param v319 i32) - (param v321 i32) - (param v348 i32) - (param v356 i32) - (param v360 i32) - (param v369 i32) - (param v378 i32) - (param v387 i32) - (let (v302 i32) (const.i32 1)) - (br (block 26 v304 v306 v302 v319 v321 v348 v356 v360 v369 v378 v387))) - - (block 28 - (let (v286 i32) (const.i32 3)) - (let (v287 u32) (bitcast v286)) - (let (v288 i32) (shl.wrapping v280 v287)) - (let (v291 i32) (add.wrapping v289 v288)) - (let (v292 u32) (bitcast v291)) - (let (v293 u32) (add.checked v292 4)) - (let (v294 u32) (mod.unchecked v293 4)) - (assertz 250 v294) - (let (v295 (ptr i32)) (inttoptr v293)) - (let (v296 i32) (load v295)) - (let (v297 i1) (neq v296 0)) - (condbr v297 (block 26 v260 v280 v312 v289 v273 v345 v352 v358 v367 v374 v383) (block 29))) - - (block 29 - (let (v298 u32) (bitcast v291)) - (let (v299 u32) (mod.unchecked v298 4)) - (assertz 250 v299) - (let (v300 (ptr i32)) (inttoptr v298)) - (let (v301 i32) (load v300)) - (br (block 27 v260 v301 v289 v273 v345 v352 v358 v367 v374 v383))) - - (block 30 - (let (v349 i32) (const.i32 8)) - (let (v350 i32) (add.wrapping v344 v349)) - (let (v361 i32) (const.i32 32)) - (let (v362 i32) (add.wrapping v357 v361)) - (let (v363 i1) (neq v351 v362)) - (let (v364 i32) (zext v363)) - (let (v365 i1) (neq v364 0)) - (condbr v365 (block 17 v350 v303 v366 v362 v318 v351 v373 v382) (block 31))) - - (block 31 - (br (block 18))) - - (block 32 - (let (v399 u32) (bitcast v398)) - (let (v400 u32) (add.checked v399 32)) - (let (v401 u32) (mod.unchecked v400 4)) - (assertz 250 v401) - (let (v402 (ptr i32)) (inttoptr v400)) - (let (v403 i32) (load v402)) - (let (v404 u32) (bitcast v379)) - (let (v405 u32) (mod.unchecked v404 4)) - (assertz 250 v405) - (let (v406 (ptr i32)) (inttoptr v404)) - (let (v407 i32) (load v406)) - (let (v408 i32) (const.i32 3)) - (let (v409 u32) (bitcast v408)) - (let (v410 i32) (shl.wrapping v370 v409)) - (let (v411 i32) (add.wrapping v407 v410)) - (let (v412 u32) (bitcast v411)) - (let (v413 u32) (mod.unchecked v412 4)) - (assertz 250 v413) - (let (v414 (ptr i32)) (inttoptr v412)) - (let (v415 i32) (load v414)) - (let (v416 u32) (bitcast v411)) - (let (v417 u32) (add.checked v416 4)) - (let (v418 u32) (mod.unchecked v417 4)) - (assertz 250 v418) - (let (v419 (ptr i32)) (inttoptr v417)) - (let (v420 i32) (load v419)) - (let (v421 u32) (bitcast v398)) - (let (v422 u32) (add.checked v421 36)) - (let (v423 u32) (mod.unchecked v422 4)) - (assertz 250 v423) - (let (v424 (ptr i32)) (inttoptr v422)) - (let (v425 i32) (load v424)) - (let (v426 u32) (bitcast v425)) - (let (v427 u32) (add.checked v426 12)) - (let (v428 u32) (mod.unchecked v427 4)) - (assertz 250 v428) - (let (v429 (ptr i32)) (inttoptr v427)) - (let (v430 i32) (load v429)) - (let (v431 i1) (eq v430 0)) - (let (v432 i32) (zext v431)) - (let (v433 i1) (neq v432 0)) - (condbr v433 (block 3 v398) (block 33))) - - (block 33 - (br (block 4 v398))) - ) - - (func (export #core::fmt::builders::DebugStruct::field) - (param i32) (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 - (param v0 i32) - (param v1 i32) - (param v2 i32) - (param v3 i32) - (param v4 i32) - (let (v6 i32) (const.i32 0)) - (let (v7 i64) (const.i64 0)) - (let (v8 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v9 i32) (const.i32 64)) - (let (v10 i32) (sub.wrapping v8 v9)) - (let (v11 (ptr i32)) (global.symbol #__stack_pointer)) - (store v11 v10) - (let (v12 i32) (const.i32 1)) - (let (v13 u32) (bitcast v0)) - (let (v14 u32) (add.checked v13 4)) - (let (v15 (ptr u8)) (inttoptr v14)) - (let (v16 u8) (load v15)) - (let (v17 i32) (zext v16)) - (let (v18 i1) (neq v17 0)) - (condbr v18 (block 2 v0 v12 v10) (block 3))) - - (block 1 (param v5 i32) - (ret v5)) - - (block 2 (param v244 i32) (param v252 i32) (param v258 i32) - (let (v246 i32) (const.i32 1)) - (let (v247 u32) (bitcast v246)) - (let (v248 u8) (trunc v247)) - (let (v249 u32) (bitcast v244)) - (let (v250 u32) (add.checked v249 5)) - (let (v251 (ptr u8)) (inttoptr v250)) - (store v251 v248) - (let (v253 u32) (bitcast v252)) - (let (v254 u8) (trunc v253)) - (let (v255 u32) (bitcast v244)) - (let (v256 u32) (add.checked v255 4)) - (let (v257 (ptr u8)) (inttoptr v256)) - (store v257 v254) - (let (v259 i32) (const.i32 64)) - (let (v260 i32) (add.wrapping v258 v259)) - (let (v261 (ptr i32)) (global.symbol #__stack_pointer)) - (store v261 v260) - (br (block 1 v244))) - - (block 3 - (let (v19 u32) (bitcast v0)) - (let (v20 u32) (add.checked v19 5)) - (let (v21 (ptr u8)) (inttoptr v20)) - (let (v22 u8) (load v21)) - (let (v23 i32) (zext v22)) - (let (v24 u32) (bitcast v0)) - (let (v25 u32) (mod.unchecked v24 4)) - (assertz 250 v25) - (let (v26 (ptr i32)) (inttoptr v24)) - (let (v27 i32) (load v26)) - (let (v28 u32) (bitcast v27)) - (let (v29 u32) (add.checked v28 28)) - (let (v30 u32) (mod.unchecked v29 4)) - (assertz 250 v30) - (let (v31 (ptr i32)) (inttoptr v29)) - (let (v32 i32) (load v31)) - (let (v33 i32) (const.i32 4)) - (let (v34 i32) (band v32 v33)) - (let (v35 i1) (neq v34 0)) - (condbr v35 (block 4) (block 5))) - - (block 4 - (let (v102 i32) (const.i32 1)) - (let (v103 i32) (const.i32 1)) - (let (v104 i32) (band v23 v103)) - (let (v105 i1) (neq v104 0)) - (condbr v105 (block 9 v10 v27 v32 v1 v2 v3 v4 v0) (block 10))) - - (block 5 - (let (v36 i32) (const.i32 1)) - (let (v37 u32) (bitcast v27)) - (let (v38 u32) (add.checked v37 20)) - (let (v39 u32) (mod.unchecked v38 4)) - (assertz 250 v39) - (let (v40 (ptr i32)) (inttoptr v38)) - (let (v41 i32) (load v40)) - (let (v42 i32) (const.i32 1048963)) - (let (v43 i32) (const.i32 1048960)) - (let (v44 i32) (const.i32 1)) - (let (v45 i32) (band v23 v44)) - (let (v46 i1) (neq v45 0)) - (let (v47 i32) (select v46 v42 v43)) - (let (v48 i32) (const.i32 2)) - (let (v49 i32) (const.i32 3)) - (let (v50 i1) (neq v45 0)) - (let (v51 i32) (select v50 v48 v49)) - (let (v52 u32) (bitcast v27)) - (let (v53 u32) (add.checked v52 24)) - (let (v54 u32) (mod.unchecked v53 4)) - (assertz 250 v54) - (let (v55 (ptr i32)) (inttoptr v53)) - (let (v56 i32) (load v55)) - (let (v57 u32) (bitcast v56)) - (let (v58 u32) (add.checked v57 12)) - (let (v59 u32) (mod.unchecked v58 4)) - (assertz 250 v59) - (let (v60 (ptr i32)) (inttoptr v58)) - (let (v61 i32) (load v60)) - (let (v62 i1) (neq v61 0)) - (condbr v62 (block 2 v0 v36 v10) (block 6))) - - (block 6 - (let (v63 u32) (bitcast v27)) - (let (v64 u32) (add.checked v63 20)) - (let (v65 u32) (mod.unchecked v64 4)) - (assertz 250 v65) - (let (v66 (ptr i32)) (inttoptr v64)) - (let (v67 i32) (load v66)) - (let (v68 u32) (bitcast v27)) - (let (v69 u32) (add.checked v68 24)) - (let (v70 u32) (mod.unchecked v69 4)) - (assertz 250 v70) - (let (v71 (ptr i32)) (inttoptr v69)) - (let (v72 i32) (load v71)) - (let (v73 u32) (bitcast v72)) - (let (v74 u32) (add.checked v73 12)) - (let (v75 u32) (mod.unchecked v74 4)) - (assertz 250 v75) - (let (v76 (ptr i32)) (inttoptr v74)) - (let (v77 i32) (load v76)) - (let (v78 i1) (neq v77 0)) - (condbr v78 (block 2 v0 v36 v10) (block 7))) - - (block 7 - (let (v79 u32) (bitcast v27)) - (let (v80 u32) (add.checked v79 20)) - (let (v81 u32) (mod.unchecked v80 4)) - (assertz 250 v81) - (let (v82 (ptr i32)) (inttoptr v80)) - (let (v83 i32) (load v82)) - (let (v84 i32) (const.i32 1048928)) - (let (v85 i32) (const.i32 2)) - (let (v86 u32) (bitcast v27)) - (let (v87 u32) (add.checked v86 24)) - (let (v88 u32) (mod.unchecked v87 4)) - (assertz 250 v88) - (let (v89 (ptr i32)) (inttoptr v87)) - (let (v90 i32) (load v89)) - (let (v91 u32) (bitcast v90)) - (let (v92 u32) (add.checked v91 12)) - (let (v93 u32) (mod.unchecked v92 4)) - (assertz 250 v93) - (let (v94 (ptr i32)) (inttoptr v92)) - (let (v95 i32) (load v94)) - (let (v96 i1) (neq v95 0)) - (condbr v96 (block 2 v0 v36 v10) (block 8))) - - (block 8 - (let (v97 u32) (bitcast v4)) - (let (v98 u32) (add.checked v97 12)) - (let (v99 u32) (mod.unchecked v98 4)) - (assertz 250 v99) - (let (v100 (ptr i32)) (inttoptr v98)) - (let (v101 i32) (load v100)) - (br (block 2 v0 v101 v10))) - - (block 9 - (param v130 i32) - (param v137 i32) - (param v171 i32) - (param v207 i32) - (param v208 i32) - (param v217 i32) - (param v220 i32) - (param v245 i32) - (let (v129 i32) (const.i32 1)) - (let (v131 i32) (const.i32 1)) - (let (v132 u32) (bitcast v131)) - (let (v133 u8) (trunc v132)) - (let (v134 u32) (bitcast v130)) - (let (v135 u32) (add.checked v134 27)) - (let (v136 (ptr u8)) (inttoptr v135)) - (store v136 v133) - (let (v138 u32) (bitcast v137)) - (let (v139 u32) (add.checked v138 20)) - (let (v140 u32) (mod.unchecked v139 4)) - (assertz 250 v140) - (let (v141 (ptr i64)) (inttoptr v139)) - (let (v142 i64) (load v141)) - (let (v143 u32) (bitcast v130)) - (let (v144 u32) (add.checked v143 12)) - (let (v145 u32) (mod.unchecked v144 4)) - (assertz 250 v145) - (let (v146 (ptr i64)) (inttoptr v144)) - (store v146 v142) - (let (v147 i32) (const.i32 1048932)) - (let (v148 u32) (bitcast v130)) - (let (v149 u32) (add.checked v148 52)) - (let (v150 u32) (mod.unchecked v149 4)) - (assertz 250 v150) - (let (v151 (ptr i32)) (inttoptr v149)) - (store v151 v147) - (let (v152 i32) (const.i32 27)) - (let (v153 i32) (add.wrapping v130 v152)) - (let (v154 u32) (bitcast v130)) - (let (v155 u32) (add.checked v154 20)) - (let (v156 u32) (mod.unchecked v155 4)) - (assertz 250 v156) - (let (v157 (ptr i32)) (inttoptr v155)) - (store v157 v153) - (let (v158 u32) (bitcast v137)) - (let (v159 u32) (add.checked v158 8)) - (let (v160 u32) (mod.unchecked v159 4)) - (assertz 250 v160) - (let (v161 (ptr i64)) (inttoptr v159)) - (let (v162 i64) (load v161)) - (let (v163 u32) (bitcast v130)) - (let (v164 u32) (add.checked v163 36)) - (let (v165 u32) (mod.unchecked v164 4)) - (assertz 250 v165) - (let (v166 (ptr i64)) (inttoptr v164)) - (store v166 v162) - (let (v167 u32) (bitcast v137)) - (let (v168 u32) (mod.unchecked v167 4)) - (assertz 250 v168) - (let (v169 (ptr i64)) (inttoptr v167)) - (let (v170 i64) (load v169)) - (let (v172 u32) (bitcast v130)) - (let (v173 u32) (add.checked v172 56)) - (let (v174 u32) (mod.unchecked v173 4)) - (assertz 250 v174) - (let (v175 (ptr i32)) (inttoptr v173)) - (store v175 v171) - (let (v176 u32) (bitcast v137)) - (let (v177 u32) (add.checked v176 16)) - (let (v178 u32) (mod.unchecked v177 4)) - (assertz 250 v178) - (let (v179 (ptr i32)) (inttoptr v177)) - (let (v180 i32) (load v179)) - (let (v181 u32) (bitcast v130)) - (let (v182 u32) (add.checked v181 44)) - (let (v183 u32) (mod.unchecked v182 4)) - (assertz 250 v183) - (let (v184 (ptr i32)) (inttoptr v182)) - (store v184 v180) - (let (v185 u32) (bitcast v137)) - (let (v186 u32) (add.checked v185 32)) - (let (v187 (ptr u8)) (inttoptr v186)) - (let (v188 u8) (load v187)) - (let (v189 i32) (zext v188)) - (let (v190 u32) (bitcast v189)) - (let (v191 u8) (trunc v190)) - (let (v192 u32) (bitcast v130)) - (let (v193 u32) (add.checked v192 60)) - (let (v194 (ptr u8)) (inttoptr v193)) - (store v194 v191) - (let (v195 u32) (bitcast v130)) - (let (v196 u32) (add.checked v195 28)) - (let (v197 u32) (mod.unchecked v196 4)) - (assertz 250 v197) - (let (v198 (ptr i64)) (inttoptr v196)) - (store v198 v170) - (let (v199 i32) (const.i32 12)) - (let (v200 i32) (add.wrapping v130 v199)) - (let (v201 u32) (bitcast v130)) - (let (v202 u32) (add.checked v201 48)) - (let (v203 u32) (mod.unchecked v202 4)) - (assertz 250 v203) - (let (v204 (ptr i32)) (inttoptr v202)) - (store v204 v200) - (let (v205 i32) (const.i32 12)) - (let (v206 i32) (add.wrapping v130 v205)) - (let (v209 i32) (call #::write_str v206 v207 v208)) - (let (v210 i1) (neq v209 0)) - (condbr v210 (block 2 v245 v129 v130) (block 12))) - - (block 10 - (let (v106 u32) (bitcast v27)) - (let (v107 u32) (add.checked v106 20)) - (let (v108 u32) (mod.unchecked v107 4)) - (assertz 250 v108) - (let (v109 (ptr i32)) (inttoptr v107)) - (let (v110 i32) (load v109)) - (let (v111 i32) (const.i32 1048965)) - (let (v112 i32) (const.i32 3)) - (let (v113 u32) (bitcast v27)) - (let (v114 u32) (add.checked v113 24)) - (let (v115 u32) (mod.unchecked v114 4)) - (assertz 250 v115) - (let (v116 (ptr i32)) (inttoptr v114)) - (let (v117 i32) (load v116)) - (let (v118 u32) (bitcast v117)) - (let (v119 u32) (add.checked v118 12)) - (let (v120 u32) (mod.unchecked v119 4)) - (assertz 250 v120) - (let (v121 (ptr i32)) (inttoptr v119)) - (let (v122 i32) (load v121)) - (let (v123 i1) (neq v122 0)) - (condbr v123 (block 2 v0 v102 v10) (block 11))) - - (block 11 - (let (v124 u32) (bitcast v27)) - (let (v125 u32) (add.checked v124 28)) - (let (v126 u32) (mod.unchecked v125 4)) - (assertz 250 v126) - (let (v127 (ptr i32)) (inttoptr v125)) - (let (v128 i32) (load v127)) - (br (block 9 v10 v27 v128 v1 v2 v3 v4 v0))) - - (block 12 - (let (v211 i32) (const.i32 12)) - (let (v212 i32) (add.wrapping v130 v211)) - (let (v213 i32) (const.i32 1048928)) - (let (v214 i32) (const.i32 2)) - (let (v215 i32) (call #::write_str v212 v213 v214)) - (let (v216 i1) (neq v215 0)) - (condbr v216 (block 2 v245 v129 v130) (block 13))) - - (block 13 - (let (v218 i32) (const.i32 28)) - (let (v219 i32) (add.wrapping v130 v218)) - (let (v221 u32) (bitcast v220)) - (let (v222 u32) (add.checked v221 12)) - (let (v223 u32) (mod.unchecked v222 4)) - (assertz 250 v223) - (let (v224 (ptr i32)) (inttoptr v222)) - (let (v225 i32) (load v224)) - (let (v226 i1) (neq v225 0)) - (condbr v226 (block 2 v245 v129 v130) (block 14))) - - (block 14 - (let (v227 u32) (bitcast v130)) - (let (v228 u32) (add.checked v227 48)) - (let (v229 u32) (mod.unchecked v228 4)) - (assertz 250 v229) - (let (v230 (ptr i32)) (inttoptr v228)) - (let (v231 i32) (load v230)) - (let (v232 i32) (const.i32 1048968)) - (let (v233 i32) (const.i32 2)) - (let (v234 u32) (bitcast v130)) - (let (v235 u32) (add.checked v234 52)) - (let (v236 u32) (mod.unchecked v235 4)) - (assertz 250 v236) - (let (v237 (ptr i32)) (inttoptr v235)) - (let (v238 i32) (load v237)) - (let (v239 u32) (bitcast v238)) - (let (v240 u32) (add.checked v239 12)) - (let (v241 u32) (mod.unchecked v240 4)) - (assertz 250 v241) - (let (v242 (ptr i32)) (inttoptr v240)) - (let (v243 i32) (load v242)) - (br (block 2 v245 v243 v130))) - ) - - (func (export #<&T as core::fmt::Display>::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (mod.unchecked v3 4)) - (assertz 250 v4) - (let (v5 (ptr i32)) (inttoptr v3)) - (let (v6 i32) (load v5)) - (let (v7 u32) (bitcast v0)) - (let (v8 u32) (add.checked v7 4)) - (let (v9 u32) (mod.unchecked v8 4)) - (assertz 250 v9) - (let (v10 (ptr i32)) (inttoptr v8)) - (let (v11 i32) (load v10)) - (let (v12 i32) (call #core::fmt::Formatter::pad v1 v6 v11)) - (br (block 1 v12))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #core::panicking::assert_failed_inner) - (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) - (block 0 - (param v0 i32) - (param v1 i32) - (param v2 i32) - (param v3 i32) - (param v4 i32) - (param v5 i32) - (param v6 i32) - (let (v7 i32) (const.i32 0)) - (let (v8 i64) (const.i64 0)) - (let (v9 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v10 i32) (const.i32 112)) - (let (v11 i32) (sub.wrapping v9 v10)) - (let (v12 (ptr i32)) (global.symbol #__stack_pointer)) - (store v12 v11) - (let (v13 u32) (bitcast v11)) - (let (v14 u32) (add.checked v13 12)) - (let (v15 u32) (mod.unchecked v14 4)) - (assertz 250 v15) - (let (v16 (ptr i32)) (inttoptr v14)) - (store v16 v2) - (let (v17 u32) (bitcast v11)) - (let (v18 u32) (add.checked v17 8)) - (let (v19 u32) (mod.unchecked v18 4)) - (assertz 250 v19) - (let (v20 (ptr i32)) (inttoptr v18)) - (store v20 v1) - (let (v21 u32) (bitcast v11)) - (let (v22 u32) (add.checked v21 20)) - (let (v23 u32) (mod.unchecked v22 4)) - (assertz 250 v23) - (let (v24 (ptr i32)) (inttoptr v22)) - (store v24 v4) - (let (v25 u32) (bitcast v11)) - (let (v26 u32) (add.checked v25 16)) - (let (v27 u32) (mod.unchecked v26 4)) - (assertz 250 v27) - (let (v28 (ptr i32)) (inttoptr v26)) - (store v28 v3) - (let (v29 i32) (const.i32 255)) - (let (v30 i32) (band v0 v29)) - (let (v31 u32) (cast v30)) - (switchv31 - (0 . (block 5)) - (1 . (block 4)) - (2 . (block 3)) - (_ . (block 5)))) - - (block 1) - - (block 2 - (param v50 i32) - (param v51 i32) - (param v56 i32) - (param v129 i32) - (let (v52 u32) (bitcast v50)) - (let (v53 u32) (add.checked v52 28)) - (let (v54 u32) (mod.unchecked v53 4)) - (assertz 250 v54) - (let (v55 (ptr i32)) (inttoptr v53)) - (store v55 v51) - (let (v57 u32) (bitcast v56)) - (let (v58 u32) (mod.unchecked v57 4)) - (assertz 250 v58) - (let (v59 (ptr i32)) (inttoptr v57)) - (let (v60 i32) (load v59)) - (let (v61 i1) (neq v60 0)) - (condbr v61 (block 6) (block 7))) - - (block 3 - (let (v44 i32) (const.i32 1048788)) - (let (v45 u32) (bitcast v11)) - (let (v46 u32) (add.checked v45 24)) - (let (v47 u32) (mod.unchecked v46 4)) - (assertz 250 v47) - (let (v48 (ptr i32)) (inttoptr v46)) - (store v48 v44) - (let (v49 i32) (const.i32 7)) - (br (block 2 v11 v49 v5 v6))) - - (block 4 - (let (v38 i32) (const.i32 1048786)) - (let (v39 u32) (bitcast v11)) - (let (v40 u32) (add.checked v39 24)) - (let (v41 u32) (mod.unchecked v40 4)) - (assertz 250 v41) - (let (v42 (ptr i32)) (inttoptr v40)) - (store v42 v38) - (let (v43 i32) (const.i32 2)) - (br (block 2 v11 v43 v5 v6))) - - (block 5 - (let (v32 i32) (const.i32 1048784)) - (let (v33 u32) (bitcast v11)) - (let (v34 u32) (add.checked v33 24)) - (let (v35 u32) (mod.unchecked v34 4)) - (assertz 250 v35) - (let (v36 (ptr i32)) (inttoptr v34)) - (store v36 v32) - (let (v37 i32) (const.i32 2)) - (br (block 2 v11 v37 v5 v6))) - - (block 6 - (let (v130 i32) (const.i32 32)) - (let (v131 i32) (add.wrapping v50 v130)) - (let (v132 i32) (const.i32 16)) - (let (v133 i32) (add.wrapping v131 v132)) - (let (v134 i32) (const.i32 16)) - (let (v135 i32) (add.wrapping v56 v134)) - (let (v136 u32) (bitcast v135)) - (let (v137 u32) (mod.unchecked v136 4)) - (assertz 250 v137) - (let (v138 (ptr i64)) (inttoptr v136)) - (let (v139 i64) (load v138)) - (let (v140 u32) (bitcast v133)) - (let (v141 u32) (mod.unchecked v140 8)) - (assertz 250 v141) - (let (v142 (ptr i64)) (inttoptr v140)) - (store v142 v139) - (let (v143 i32) (const.i32 32)) - (let (v144 i32) (add.wrapping v50 v143)) - (let (v145 i32) (const.i32 8)) - (let (v146 i32) (add.wrapping v144 v145)) - (let (v147 i32) (const.i32 8)) - (let (v148 i32) (add.wrapping v56 v147)) - (let (v149 u32) (bitcast v148)) - (let (v150 u32) (mod.unchecked v149 4)) - (assertz 250 v150) - (let (v151 (ptr i64)) (inttoptr v149)) - (let (v152 i64) (load v151)) - (let (v153 u32) (bitcast v146)) - (let (v154 u32) (mod.unchecked v153 8)) - (assertz 250 v154) - (let (v155 (ptr i64)) (inttoptr v153)) - (store v155 v152) - (let (v156 u32) (bitcast v56)) - (let (v157 u32) (mod.unchecked v156 4)) - (assertz 250 v157) - (let (v158 (ptr i64)) (inttoptr v156)) - (let (v159 i64) (load v158)) - (let (v160 u32) (bitcast v50)) - (let (v161 u32) (add.checked v160 32)) - (let (v162 u32) (mod.unchecked v161 8)) - (assertz 250 v162) - (let (v163 (ptr i64)) (inttoptr v161)) - (store v163 v159) - (let (v164 i32) (const.i32 4)) - (let (v165 u32) (bitcast v50)) - (let (v166 u32) (add.checked v165 92)) - (let (v167 u32) (mod.unchecked v166 4)) - (assertz 250 v167) - (let (v168 (ptr i32)) (inttoptr v166)) - (store v168 v164) - (let (v169 i32) (const.i32 1048896)) - (let (v170 u32) (bitcast v50)) - (let (v171 u32) (add.checked v170 88)) - (let (v172 u32) (mod.unchecked v171 4)) - (assertz 250 v172) - (let (v173 (ptr i32)) (inttoptr v171)) - (store v173 v169) - (let (v174 i64) (const.i64 4)) - (let (v175 u32) (bitcast v50)) - (let (v176 u32) (add.checked v175 100)) - (let (v177 u32) (mod.unchecked v176 4)) - (assertz 250 v177) - (let (v178 (ptr i64)) (inttoptr v176)) - (store v178 v174) - (let (v179 i32) (const.i32 7)) - (let (v180 u32) (bitcast v179)) - (let (v181 u64) (zext v180)) - (let (v182 i64) (bitcast v181)) - (let (v183 i64) (const.i64 32)) - (let (v184 u32) (cast v183)) - (let (v185 i64) (shl.wrapping v182 v184)) - (let (v186 i32) (const.i32 16)) - (let (v187 i32) (add.wrapping v50 v186)) - (let (v188 u32) (bitcast v187)) - (let (v189 u64) (zext v188)) - (let (v190 i64) (bitcast v189)) - (let (v191 i64) (bor v185 v190)) - (let (v192 u32) (bitcast v50)) - (let (v193 u32) (add.checked v192 80)) - (let (v194 u32) (mod.unchecked v193 8)) - (assertz 250 v194) - (let (v195 (ptr i64)) (inttoptr v193)) - (store v195 v191) - (let (v196 i32) (const.i32 8)) - (let (v197 i32) (add.wrapping v50 v196)) - (let (v198 u32) (bitcast v197)) - (let (v199 u64) (zext v198)) - (let (v200 i64) (bitcast v199)) - (let (v201 i64) (bor v185 v200)) - (let (v202 u32) (bitcast v50)) - (let (v203 u32) (add.checked v202 72)) - (let (v204 u32) (mod.unchecked v203 8)) - (assertz 250 v204) - (let (v205 (ptr i64)) (inttoptr v203)) - (store v205 v201) - (let (v206 i32) (const.i32 9)) - (let (v207 u32) (bitcast v206)) - (let (v208 u64) (zext v207)) - (let (v209 i64) (bitcast v208)) - (let (v210 i64) (const.i64 32)) - (let (v211 u32) (cast v210)) - (let (v212 i64) (shl.wrapping v209 v211)) - (let (v213 i32) (const.i32 32)) - (let (v214 i32) (add.wrapping v50 v213)) - (let (v215 u32) (bitcast v214)) - (let (v216 u64) (zext v215)) - (let (v217 i64) (bitcast v216)) - (let (v218 i64) (bor v212 v217)) - (let (v219 u32) (bitcast v50)) - (let (v220 u32) (add.checked v219 64)) - (let (v221 u32) (mod.unchecked v220 8)) - (assertz 250 v221) - (let (v222 (ptr i64)) (inttoptr v220)) - (store v222 v218) - (let (v223 i32) (const.i32 8)) - (let (v224 u32) (bitcast v223)) - (let (v225 u64) (zext v224)) - (let (v226 i64) (bitcast v225)) - (let (v227 i64) (const.i64 32)) - (let (v228 u32) (cast v227)) - (let (v229 i64) (shl.wrapping v226 v228)) - (let (v230 i32) (const.i32 24)) - (let (v231 i32) (add.wrapping v50 v230)) - (let (v232 u32) (bitcast v231)) - (let (v233 u64) (zext v232)) - (let (v234 i64) (bitcast v233)) - (let (v235 i64) (bor v229 v234)) - (let (v236 u32) (bitcast v50)) - (let (v237 u32) (add.checked v236 56)) - (let (v238 u32) (mod.unchecked v237 8)) - (assertz 250 v238) - (let (v239 (ptr i64)) (inttoptr v237)) - (store v239 v235) - (let (v240 i32) (const.i32 56)) - (let (v241 i32) (add.wrapping v50 v240)) - (let (v242 u32) (bitcast v50)) - (let (v243 u32) (add.checked v242 96)) - (let (v244 u32) (mod.unchecked v243 4)) - (assertz 250 v244) - (let (v245 (ptr i32)) (inttoptr v243)) - (store v245 v241) - (let (v246 i32) (const.i32 88)) - (let (v247 i32) (add.wrapping v50 v246)) - (call #core::panicking::panic_fmt v247 v129) - (unreachable)) - - (block 7 - (let (v62 i32) (const.i32 3)) - (let (v63 u32) (bitcast v50)) - (let (v64 u32) (add.checked v63 92)) - (let (v65 u32) (mod.unchecked v64 4)) - (assertz 250 v65) - (let (v66 (ptr i32)) (inttoptr v64)) - (store v66 v62) - (let (v67 i32) (const.i32 1048844)) - (let (v68 u32) (bitcast v50)) - (let (v69 u32) (add.checked v68 88)) - (let (v70 u32) (mod.unchecked v69 4)) - (assertz 250 v70) - (let (v71 (ptr i32)) (inttoptr v69)) - (store v71 v67) - (let (v72 i64) (const.i64 3)) - (let (v73 u32) (bitcast v50)) - (let (v74 u32) (add.checked v73 100)) - (let (v75 u32) (mod.unchecked v74 4)) - (assertz 250 v75) - (let (v76 (ptr i64)) (inttoptr v74)) - (store v76 v72) - (let (v77 i32) (const.i32 7)) - (let (v78 u32) (bitcast v77)) - (let (v79 u64) (zext v78)) - (let (v80 i64) (bitcast v79)) - (let (v81 i64) (const.i64 32)) - (let (v82 u32) (cast v81)) - (let (v83 i64) (shl.wrapping v80 v82)) - (let (v84 i32) (const.i32 16)) - (let (v85 i32) (add.wrapping v50 v84)) - (let (v86 u32) (bitcast v85)) - (let (v87 u64) (zext v86)) - (let (v88 i64) (bitcast v87)) - (let (v89 i64) (bor v83 v88)) - (let (v90 u32) (bitcast v50)) - (let (v91 u32) (add.checked v90 72)) - (let (v92 u32) (mod.unchecked v91 8)) - (assertz 250 v92) - (let (v93 (ptr i64)) (inttoptr v91)) - (store v93 v89) - (let (v94 i32) (const.i32 8)) - (let (v95 i32) (add.wrapping v50 v94)) - (let (v96 u32) (bitcast v95)) - (let (v97 u64) (zext v96)) - (let (v98 i64) (bitcast v97)) - (let (v99 i64) (bor v83 v98)) - (let (v100 u32) (bitcast v50)) - (let (v101 u32) (add.checked v100 64)) - (let (v102 u32) (mod.unchecked v101 8)) - (assertz 250 v102) - (let (v103 (ptr i64)) (inttoptr v101)) - (store v103 v99) - (let (v104 i32) (const.i32 8)) - (let (v105 u32) (bitcast v104)) - (let (v106 u64) (zext v105)) - (let (v107 i64) (bitcast v106)) - (let (v108 i64) (const.i64 32)) - (let (v109 u32) (cast v108)) - (let (v110 i64) (shl.wrapping v107 v109)) - (let (v111 i32) (const.i32 24)) - (let (v112 i32) (add.wrapping v50 v111)) - (let (v113 u32) (bitcast v112)) - (let (v114 u64) (zext v113)) - (let (v115 i64) (bitcast v114)) - (let (v116 i64) (bor v110 v115)) - (let (v117 u32) (bitcast v50)) - (let (v118 u32) (add.checked v117 56)) - (let (v119 u32) (mod.unchecked v118 8)) - (assertz 250 v119) - (let (v120 (ptr i64)) (inttoptr v118)) - (store v120 v116) - (let (v121 i32) (const.i32 56)) - (let (v122 i32) (add.wrapping v50 v121)) - (let (v123 u32) (bitcast v50)) - (let (v124 u32) (add.checked v123 96)) - (let (v125 u32) (mod.unchecked v124 4)) - (assertz 250 v125) - (let (v126 (ptr i32)) (inttoptr v124)) - (store v126 v122) - (let (v127 i32) (const.i32 88)) - (let (v128 i32) (add.wrapping v50 v127)) - (call #core::panicking::panic_fmt v128 v129) - (unreachable)) - ) - - (func (export #<&T as core::fmt::Debug>::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (mod.unchecked v3 4)) - (assertz 250 v4) - (let (v5 (ptr i32)) (inttoptr v3)) - (let (v6 i32) (load v5)) - (let (v7 u32) (bitcast v0)) - (let (v8 u32) (add.checked v7 4)) - (let (v9 u32) (mod.unchecked v8 4)) - (assertz 250 v9) - (let (v10 (ptr i32)) (inttoptr v8)) - (let (v11 i32) (load v10)) - (let (v12 u32) (bitcast v11)) - (let (v13 u32) (add.checked v12 12)) - (let (v14 u32) (mod.unchecked v13 4)) - (assertz 250 v14) - (let (v15 (ptr i32)) (inttoptr v13)) - (let (v16 i32) (load v15)) - (br (block 1 v16))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v1)) - (let (v4 u32) (add.checked v3 20)) - (let (v5 u32) (mod.unchecked v4 4)) - (assertz 250 v5) - (let (v6 (ptr i32)) (inttoptr v4)) - (let (v7 i32) (load v6)) - (let (v8 u32) (bitcast v1)) - (let (v9 u32) (add.checked v8 24)) - (let (v10 u32) (mod.unchecked v9 4)) - (assertz 250 v10) - (let (v11 (ptr i32)) (inttoptr v9)) - (let (v12 i32) (load v11)) - (let (v13 i32) (call #core::fmt::write v7 v12 v0)) - (br (block 1 v13))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #::write_str) - (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i32) (const.i32 -1)) - (let (v6 i32) (add.wrapping v1 v5)) - (let (v7 u32) (bitcast v0)) - (let (v8 u32) (add.checked v7 4)) - (let (v9 u32) (mod.unchecked v8 4)) - (assertz 250 v9) - (let (v10 (ptr i32)) (inttoptr v8)) - (let (v11 i32) (load v10)) - (let (v12 u32) (bitcast v0)) - (let (v13 u32) (mod.unchecked v12 4)) - (assertz 250 v13) - (let (v14 (ptr i32)) (inttoptr v12)) - (let (v15 i32) (load v14)) - (let (v16 u32) (bitcast v0)) - (let (v17 u32) (add.checked v16 8)) - (let (v18 u32) (mod.unchecked v17 4)) - (assertz 250 v18) - (let (v19 (ptr i32)) (inttoptr v17)) - (let (v20 i32) (load v19)) - (let (v21 i32) (const.i32 0)) - (let (v22 i32) (const.i32 0)) - (let (v23 i32) (const.i32 0)) - (let (v24 i32) (const.i32 0)) - (br (block 3 v24 v23 v2 v1 v22 v20 v15 v11 v6 v21))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 (param v370 i32) - (br (block 1 v370))) - - (block 3 - (param v25 i32) - (param v29 i32) - (param v30 i32) - (param v214 i32) - (param v225 i32) - (param v243 i32) - (param v262 i32) - (param v276 i32) - (param v304 i32) - (param v371 i32) - (let (v26 i32) (const.i32 1)) - (let (v27 i32) (band v25 v26)) - (let (v28 i1) (neq v27 0)) - (condbr v28 (block 2 v371) (block 5))) - - (block 4 - (let (v369 i32) (const.i32 1)) - (br (block 2 v369))) - - (block 5 - (let (v31 u32) (bitcast v29)) - (let (v32 u32) (bitcast v30)) - (let (v33 i1) (gt v31 v32)) - (let (v34 i32) (sext v33)) - (let (v35 i1) (neq v34 0)) - (condbr v35 (block 7 v225 v30 v243 v262 v276 v304 v214 v29 v371) (block 8))) - - (block 6 - (param v239 i32) - (param v258 i32) - (param v272 i32) - (param v291 i32) - (param v293 i32) - (param v300 i32) - (param v322 i32) - (param v335 i32) - (param v351 i32) - (param v363 i32) - (param v367 i32) - (param v374 i32) - (let (v251 u32) (bitcast v239)) - (let (v252 (ptr u8)) (inttoptr v251)) - (let (v253 u8) (load v252)) - (let (v254 i32) (zext v253)) - (let (v255 i1) (eq v254 0)) - (let (v256 i32) (zext v255)) - (let (v257 i1) (neq v256 0)) - (condbr v257 (block 46 v291 v293 v300 v322 v239 v335 v258 v272 v351 v363 v367 v374) (block 47))) - - (block 7 - (param v224 i32) - (param v235 i32) - (param v250 i32) - (param v269 i32) - (param v283 i32) - (param v311 i32) - (param v323 i32) - (param v364 i32) - (param v384 i32) - (let (v223 i32) (const.i32 1)) - (let (v236 i1) (eq v224 v235)) - (let (v237 i32) (zext v236)) - (let (v238 i1) (neq v237 0)) - (condbr v238 (block 2 v384) (block 44))) - - (block 8 - (br (block 9 v214 v29 v30 v225 v243 v262 v276 v304 v25 v371))) - - (block 9 - (param v36 i32) - (param v37 i32) - (param v39 i32) - (param v226 i32) - (param v242 i32) - (param v261 i32) - (param v275 i32) - (param v303 i32) - (param v354 i32) - (param v377 i32) - (let (v38 i32) (add.wrapping v36 v37)) - (let (v40 i32) (sub.wrapping v39 v37)) - (let (v41 i32) (const.i32 7)) - (let (v42 u32) (bitcast v40)) - (let (v43 u32) (bitcast v41)) - (let (v44 i1) (gt v42 v43)) - (let (v45 i32) (sext v44)) - (let (v46 i1) (neq v45 0)) - (condbr v46 (block 14) (block 15))) - - (block 10 - (br (block 7 v227 v208 v244 v263 v277 v305 v215 v207 v378))) - - (block 11 - (param v180 i32) - (param v181 i32) - (param v191 i32) - (param v197 i32) - (param v216 i32) - (param v228 i32) - (param v240 i32) - (param v259 i32) - (param v273 i32) - (param v301 i32) - (param v352 i32) - (param v375 i32) - (let (v188 i32) (add.wrapping v180 v181)) - (let (v189 i32) (const.i32 1)) - (let (v190 i32) (add.wrapping v188 v189)) - (let (v192 u32) (bitcast v188)) - (let (v193 u32) (bitcast v191)) - (let (v194 i1) (gte v192 v193)) - (let (v195 i32) (zext v194)) - (let (v196 i1) (neq v195 0)) - (condbr v196 (block 40 v190 v191 v216 v228 v240 v259 v273 v301 v352 v375) (block 41))) - - (block 12 - (param v148 i32) - (param v149 i32) - (param v155 i32) - (param v176 i32) - (param v185 i32) - (param v220 i32) - (param v232 i32) - (param v247 i32) - (param v266 i32) - (param v280 i32) - (param v308 i32) - (param v358 i32) - (param v381 i32) - (let (v152 i1) (neq v148 v149)) - (let (v153 i32) (zext v152)) - (let (v154 i1) (neq v153 0)) - (condbr v154 (block 33) (block 34))) - - (block 13 - (let (v129 i32) (const.i32 0)) - (br (block 29 v38 v129 v40 v39 v37 v36 v226 v242 v261 v275 v303 v354 v377))) - - (block 14 - (let (v50 i32) (const.i32 3)) - (let (v51 i32) (add.wrapping v38 v50)) - (let (v52 i32) (const.i32 -4)) - (let (v53 i32) (band v51 v52)) - (let (v54 i32) (sub.wrapping v53 v38)) - (let (v55 i1) (eq v54 0)) - (let (v56 i32) (zext v55)) - (let (v57 i1) (neq v56 0)) - (condbr v57 (block 18) (block 19))) - - (block 15 - (let (v47 i1) (neq v39 v37)) - (let (v48 i32) (zext v47)) - (let (v49 i1) (neq v48 0)) - (condbr v49 (block 13) (block 16))) - - (block 16 - (br (block 7 v226 v39 v242 v261 v275 v303 v36 v39 v377))) - - (block 17 - (param v125 i32) - (param v127 i32) - (param v128 i32) - (param v151 i32) - (param v158 i32) - (param v178 i32) - (param v187 i32) - (param v222 i32) - (param v234 i32) - (param v249 i32) - (param v268 i32) - (param v282 i32) - (param v310 i32) - (param v360 i32) - (param v383 i32) - (br (block 25 v125 v127 v128 v151 v158 v178 v187 v222 v234 v249 v268 v282 v310 v360 v383))) - - (block 18 - (let (v84 i32) (const.i32 -8)) - (let (v85 i32) (add.wrapping v40 v84)) - (br (block 17 v53 v54 v85 v40 v39 v38 v37 v36 v226 v242 v261 v275 v303 v354 v377))) - - (block 19 - (let (v58 i32) (const.i32 0)) - (br (block 20 v38 v58 v54 v40 v53 v39 v37 v36 v226 v242 v261 v275 v303 v354 v377))) - - (block 20 - (param v59 i32) - (param v60 i32) - (param v70 i32) - (param v76 i32) - (param v126 i32) - (param v156 i32) - (param v182 i32) - (param v217 i32) - (param v229 i32) - (param v241 i32) - (param v260 i32) - (param v274 i32) - (param v302 i32) - (param v353 i32) - (param v376 i32) - (let (v61 i32) (add.wrapping v59 v60)) - (let (v62 u32) (bitcast v61)) - (let (v63 (ptr u8)) (inttoptr v62)) - (let (v64 u8) (load v63)) - (let (v65 i32) (zext v64)) - (let (v66 i32) (const.i32 10)) - (let (v67 i1) (eq v65 v66)) - (let (v68 i32) (zext v67)) - (let (v69 i1) (neq v68 0)) - (condbr v69 (block 11 v60 v182 v156 v59 v217 v229 v241 v260 v274 v302 v353 v376) (block 22))) - - (block 21 - (let (v77 i32) (const.i32 -8)) - (let (v78 i32) (add.wrapping v76 v77)) - (let (v79 u32) (bitcast v70)) - (let (v80 u32) (bitcast v78)) - (let (v81 i1) (lte v79 v80)) - (let (v82 i32) (sext v81)) - (let (v83 i1) (neq v82 0)) - (condbr v83 (block 17 v126 v70 v78 v76 v156 v59 v182 v217 v229 v241 v260 v274 v302 v353 v376) (block 24))) - - (block 22 - (let (v71 i32) (const.i32 1)) - (let (v72 i32) (add.wrapping v60 v71)) - (let (v73 i1) (neq v70 v72)) - (let (v74 i32) (zext v73)) - (let (v75 i1) (neq v74 0)) - (condbr v75 (block 20 v59 v72 v70 v76 v126 v156 v182 v217 v229 v241 v260 v274 v302 v353 v376) (block 23))) - - (block 23 - (br (block 21))) - - (block 24 - (br (block 12 v70 v76 v156 v59 v182 v217 v229 v241 v260 v274 v302 v353 v376))) - - (block 25 - (param v87 i32) - (param v116 i32) - (param v119 i32) - (param v150 i32) - (param v157 i32) - (param v177 i32) - (param v186 i32) - (param v221 i32) - (param v233 i32) - (param v248 i32) - (param v267 i32) - (param v281 i32) - (param v309 i32) - (param v359 i32) - (param v382 i32) - (let (v86 i32) (const.i32 16843008)) - (let (v88 u32) (bitcast v87)) - (let (v89 u32) (mod.unchecked v88 4)) - (assertz 250 v89) - (let (v90 (ptr i32)) (inttoptr v88)) - (let (v91 i32) (load v90)) - (let (v92 i32) (const.i32 168430090)) - (let (v93 i32) (bxor v91 v92)) - (let (v94 i32) (sub.wrapping v86 v93)) - (let (v95 i32) (bor v94 v91)) - (let (v96 i32) (const.i32 16843008)) - (let (v97 i32) (const.i32 4)) - (let (v98 i32) (add.wrapping v87 v97)) - (let (v99 u32) (bitcast v98)) - (let (v100 u32) (mod.unchecked v99 4)) - (assertz 250 v100) - (let (v101 (ptr i32)) (inttoptr v99)) - (let (v102 i32) (load v101)) - (let (v103 i32) (const.i32 168430090)) - (let (v104 i32) (bxor v102 v103)) - (let (v105 i32) (sub.wrapping v96 v104)) - (let (v106 i32) (bor v105 v102)) - (let (v107 i32) (band v95 v106)) - (let (v108 i32) (const.i32 -2139062144)) - (let (v109 i32) (band v107 v108)) - (let (v110 i32) (const.i32 -2139062144)) - (let (v111 i1) (neq v109 v110)) - (let (v112 i32) (zext v111)) - (let (v113 i1) (neq v112 0)) - (condbr v113 (block 12 v116 v150 v157 v177 v186 v221 v233 v248 v267 v281 v309 v359 v382) (block 27))) - - (block 26) - - (block 27 - (let (v114 i32) (const.i32 8)) - (let (v115 i32) (add.wrapping v87 v114)) - (let (v117 i32) (const.i32 8)) - (let (v118 i32) (add.wrapping v116 v117)) - (let (v120 u32) (bitcast v118)) - (let (v121 u32) (bitcast v119)) - (let (v122 i1) (lte v120 v121)) - (let (v123 i32) (sext v122)) - (let (v124 i1) (neq v123 0)) - (condbr v124 (block 25 v115 v118 v119 v150 v157 v177 v186 v221 v233 v248 v267 v281 v309 v359 v382) (block 28))) - - (block 28 - (br (block 12 v118 v150 v157 v177 v186 v221 v233 v248 v267 v281 v309 v359 v382))) - - (block 29 - (param v130 i32) - (param v131 i32) - (param v141 i32) - (param v147 i32) - (param v183 i32) - (param v218 i32) - (param v230 i32) - (param v245 i32) - (param v264 i32) - (param v278 i32) - (param v306 i32) - (param v356 i32) - (param v379 i32) - (let (v132 i32) (add.wrapping v130 v131)) - (let (v133 u32) (bitcast v132)) - (let (v134 (ptr u8)) (inttoptr v133)) - (let (v135 u8) (load v134)) - (let (v136 i32) (zext v135)) - (let (v137 i32) (const.i32 10)) - (let (v138 i1) (eq v136 v137)) - (let (v139 i32) (zext v138)) - (let (v140 i1) (neq v139 0)) - (condbr v140 (block 11 v131 v183 v147 v130 v218 v230 v245 v264 v278 v306 v356 v379) (block 31))) - - (block 30 - (br (block 7 v230 v147 v245 v264 v278 v306 v218 v147 v379))) - - (block 31 - (let (v142 i32) (const.i32 1)) - (let (v143 i32) (add.wrapping v131 v142)) - (let (v144 i1) (neq v141 v143)) - (let (v145 i32) (zext v144)) - (let (v146 i1) (neq v145 0)) - (condbr v146 (block 29 v130 v143 v141 v147 v183 v218 v230 v245 v264 v278 v306 v356 v379) (block 32))) - - (block 32 - (br (block 30))) - - (block 33 - (br (block 35 v176 v148 v149 v155 v185 v220 v232 v247 v266 v280 v308 v358 v381))) - - (block 34 - (br (block 7 v232 v155 v247 v266 v280 v308 v220 v155 v381))) - - (block 35 - (param v159 i32) - (param v160 i32) - (param v170 i32) - (param v179 i32) - (param v184 i32) - (param v219 i32) - (param v231 i32) - (param v246 i32) - (param v265 i32) - (param v279 i32) - (param v307 i32) - (param v357 i32) - (param v380 i32) - (let (v161 i32) (add.wrapping v159 v160)) - (let (v162 u32) (bitcast v161)) - (let (v163 (ptr u8)) (inttoptr v162)) - (let (v164 u8) (load v163)) - (let (v165 i32) (zext v164)) - (let (v166 i32) (const.i32 10)) - (let (v167 i1) (neq v165 v166)) - (let (v168 i32) (zext v167)) - (let (v169 i1) (neq v168 0)) - (condbr v169 (block 37) (block 38))) - - (block 36 - (br (block 7 v231 v179 v246 v265 v279 v307 v219 v179 v380))) - - (block 37 - (let (v171 i32) (const.i32 1)) - (let (v172 i32) (add.wrapping v160 v171)) - (let (v173 i1) (neq v170 v172)) - (let (v174 i32) (zext v173)) - (let (v175 i1) (neq v174 0)) - (condbr v175 (block 35 v159 v172 v170 v179 v184 v219 v231 v246 v265 v279 v307 v357 v380) (block 39))) - - (block 38 - (br (block 11 v160 v184 v179 v159 v219 v231 v246 v265 v279 v307 v357 v380))) - - (block 39 - (br (block 36))) - - (block 40 - (param v207 i32) - (param v208 i32) - (param v215 i32) - (param v227 i32) - (param v244 i32) - (param v263 i32) - (param v277 i32) - (param v305 i32) - (param v355 i32) - (param v378 i32) - (let (v209 u32) (bitcast v207)) - (let (v210 u32) (bitcast v208)) - (let (v211 i1) (lte v209 v210)) - (let (v212 i32) (sext v211)) - (let (v213 i1) (neq v212 0)) - (condbr v213 (block 9 v215 v207 v208 v227 v244 v263 v277 v305 v355 v378) (block 43))) - - (block 41 - (let (v198 i32) (add.wrapping v197 v180)) - (let (v199 u32) (bitcast v198)) - (let (v200 (ptr u8)) (inttoptr v199)) - (let (v201 u8) (load v200)) - (let (v202 i32) (zext v201)) - (let (v203 i32) (const.i32 10)) - (let (v204 i1) (neq v202 v203)) - (let (v205 i32) (zext v204)) - (let (v206 i1) (neq v205 0)) - (condbr v206 (block 40 v190 v191 v216 v228 v240 v259 v273 v301 v352 v375) (block 42))) - - (block 42 - (br (block 6 v240 v259 v273 v190 v228 v301 v216 v190 v352 v190 v191 v375))) - - (block 43 - (br (block 10))) - - (block 44 - (br (block 6 v250 v269 v283 v235 v224 v311 v323 v224 v223 v364 v235 v384))) - - (block 45 - (br (block 4))) - - (block 46 - (param v290 i32) - (param v292 i32) - (param v299 i32) - (param v321 i32) - (param v327 i32) - (param v334 i32) - (param v337 i32) - (param v340 i32) - (param v350 i32) - (param v362 i32) - (param v366 i32) - (param v373 i32) - (let (v294 i32) (sub.wrapping v290 v292)) - (let (v295 i32) (const.i32 0)) - (let (v296 i1) (eq v290 v292)) - (let (v297 i32) (zext v296)) - (let (v298 i1) (neq v297 0)) - (condbr v298 (block 49 v321 v292 v327 v295 v334 v337 v294 v340 v350 v362 v366 v299 v373) (block 50))) - - (block 47 - (let (v270 i32) (const.i32 1048956)) - (let (v271 i32) (const.i32 4)) - (let (v284 u32) (bitcast v272)) - (let (v285 u32) (add.checked v284 12)) - (let (v286 u32) (mod.unchecked v285 4)) - (assertz 250 v286) - (let (v287 (ptr i32)) (inttoptr v285)) - (let (v288 i32) (load v287)) - (let (v289 i1) (neq v288 0)) - (condbr v289 (block 45) (block 48))) - - (block 48 - (br (block 46 v291 v293 v300 v322 v239 v335 v258 v272 v351 v363 v367 v374))) - - (block 49 - (param v320 i32) - (param v324 i32) - (param v326 i32) - (param v328 i32) - (param v333 i32) - (param v336 i32) - (param v338 i32) - (param v339 i32) - (param v349 i32) - (param v361 i32) - (param v365 i32) - (param v368 i32) - (param v372 i32) - (let (v325 i32) (add.wrapping v320 v324)) - (let (v329 u32) (bitcast v328)) - (let (v330 u8) (trunc v329)) - (let (v331 u32) (bitcast v326)) - (let (v332 (ptr u8)) (inttoptr v331)) - (store v332 v330) - (let (v341 u32) (bitcast v339)) - (let (v342 u32) (add.checked v341 12)) - (let (v343 u32) (mod.unchecked v342 4)) - (assertz 250 v343) - (let (v344 (ptr i32)) (inttoptr v342)) - (let (v345 i32) (load v344)) - (let (v346 i1) (eq v345 0)) - (let (v347 i32) (zext v346)) - (let (v348 i1) (neq v347 0)) - (condbr v348 (block 3 v349 v361 v365 v320 v333 v326 v336 v339 v368 v372) (block 51))) - - (block 50 - (let (v312 i32) (add.wrapping v299 v290)) - (let (v313 u32) (bitcast v312)) - (let (v314 (ptr u8)) (inttoptr v313)) - (let (v315 u8) (load v314)) - (let (v316 i32) (zext v315)) - (let (v317 i32) (const.i32 10)) - (let (v318 i1) (eq v316 v317)) - (let (v319 i32) (zext v318)) - (br (block 49 v321 v292 v327 v319 v334 v337 v294 v340 v350 v362 v366 v299 v373))) - - (block 51 - (br (block 45))) - ) - - (func (export #::write_char) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 u32) (bitcast v0)) - (let (v5 u32) (add.checked v4 4)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr i32)) (inttoptr v5)) - (let (v8 i32) (load v7)) - (let (v9 u32) (bitcast v0)) - (let (v10 u32) (mod.unchecked v9 4)) - (assertz 250 v10) - (let (v11 (ptr i32)) (inttoptr v9)) - (let (v12 i32) (load v11)) - (let (v13 u32) (bitcast v0)) - (let (v14 u32) (add.checked v13 8)) - (let (v15 u32) (mod.unchecked v14 4)) - (assertz 250 v15) - (let (v16 (ptr i32)) (inttoptr v14)) - (let (v17 i32) (load v16)) - (let (v18 u32) (bitcast v17)) - (let (v19 (ptr u8)) (inttoptr v18)) - (let (v20 u8) (load v19)) - (let (v21 i32) (zext v20)) - (let (v22 i1) (eq v21 0)) - (let (v23 i32) (zext v22)) - (let (v24 i1) (neq v23 0)) - (condbr v24 (block 2 v17 v1 v12 v8) (block 3))) - - (block 1 (param v2 i32) - (ret v2)) - - (block 2 - (param v36 i32) - (param v37 i32) - (param v45 i32) - (param v46 i32) - (let (v38 i32) (const.i32 10)) - (let (v39 i1) (eq v37 v38)) - (let (v40 i32) (zext v39)) - (let (v41 u32) (bitcast v40)) - (let (v42 u8) (trunc v41)) - (let (v43 u32) (bitcast v36)) - (let (v44 (ptr u8)) (inttoptr v43)) - (store v44 v42) - (let (v47 u32) (bitcast v46)) - (let (v48 u32) (add.checked v47 16)) - (let (v49 u32) (mod.unchecked v48 4)) - (assertz 250 v49) - (let (v50 (ptr i32)) (inttoptr v48)) - (let (v51 i32) (load v50)) - (br (block 1 v51))) - - (block 3 - (let (v25 i32) (const.i32 1048956)) - (let (v26 i32) (const.i32 4)) - (let (v27 u32) (bitcast v8)) - (let (v28 u32) (add.checked v27 12)) - (let (v29 u32) (mod.unchecked v28 4)) - (assertz 250 v29) - (let (v30 (ptr i32)) (inttoptr v28)) - (let (v31 i32) (load v30)) - (let (v32 i1) (eq v31 0)) - (let (v33 i32) (zext v32)) - (let (v34 i1) (neq v33 0)) - (condbr v34 (block 2 v17 v1 v12 v8) (block 4))) - - (block 4 - (let (v35 i32) (const.i32 1)) - (ret v35)) - ) - - (func (export #core::fmt::builders::DebugStruct::finish) - (param i32) (result i32) - (block 0 (param v0 i32) - (let (v2 i32) (const.i32 0)) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (add.checked v3 4)) - (let (v5 (ptr u8)) (inttoptr v4)) - (let (v6 u8) (load v5)) - (let (v7 i32) (zext v6)) - (let (v8 u32) (bitcast v0)) - (let (v9 u32) (add.checked v8 5)) - (let (v10 (ptr u8)) (inttoptr v9)) - (let (v11 u8) (load v10)) - (let (v12 i32) (zext v11)) - (let (v13 i1) (eq v12 0)) - (let (v14 i32) (zext v13)) - (let (v15 i1) (neq v14 0)) - (condbr v15 (block 2 v7) (block 3))) - - (block 1 (param v1 i32) - (ret v1)) - - (block 2 (param v73 i32) - (let (v74 i32) (const.i32 1)) - (let (v75 i32) (band v73 v74)) - (br (block 1 v75))) - - (block 3 - (let (v16 i32) (const.i32 1)) - (let (v17 i32) (const.i32 1)) - (let (v18 i32) (band v7 v17)) - (let (v19 i1) (neq v18 0)) - (condbr v19 (block 4 v0 v16) (block 5))) - - (block 4 (param v66 i32) (param v67 i32) - (let (v68 u32) (bitcast v67)) - (let (v69 u8) (trunc v68)) - (let (v70 u32) (bitcast v66)) - (let (v71 u32) (add.checked v70 4)) - (let (v72 (ptr u8)) (inttoptr v71)) - (store v72 v69) - (br (block 2 v67))) - - (block 5 - (let (v20 u32) (bitcast v0)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr i32)) (inttoptr v20)) - (let (v23 i32) (load v22)) - (let (v24 u32) (bitcast v23)) - (let (v25 u32) (add.checked v24 28)) - (let (v26 (ptr u8)) (inttoptr v25)) - (let (v27 u8) (load v26)) - (let (v28 i32) (zext v27)) - (let (v29 i32) (const.i32 4)) - (let (v30 i32) (band v28 v29)) - (let (v31 i1) (neq v30 0)) - (condbr v31 (block 6) (block 7))) - - (block 6 - (let (v49 u32) (bitcast v23)) - (let (v50 u32) (add.checked v49 20)) - (let (v51 u32) (mod.unchecked v50 4)) - (assertz 250 v51) - (let (v52 (ptr i32)) (inttoptr v50)) - (let (v53 i32) (load v52)) - (let (v54 i32) (const.i32 1048970)) - (let (v55 i32) (const.i32 1)) - (let (v56 u32) (bitcast v23)) - (let (v57 u32) (add.checked v56 24)) - (let (v58 u32) (mod.unchecked v57 4)) - (assertz 250 v58) - (let (v59 (ptr i32)) (inttoptr v57)) - (let (v60 i32) (load v59)) - (let (v61 u32) (bitcast v60)) - (let (v62 u32) (add.checked v61 12)) - (let (v63 u32) (mod.unchecked v62 4)) - (assertz 250 v63) - (let (v64 (ptr i32)) (inttoptr v62)) - (let (v65 i32) (load v64)) - (br (block 4 v0 v65))) - - (block 7 - (let (v32 u32) (bitcast v23)) - (let (v33 u32) (add.checked v32 20)) - (let (v34 u32) (mod.unchecked v33 4)) - (assertz 250 v34) - (let (v35 (ptr i32)) (inttoptr v33)) - (let (v36 i32) (load v35)) - (let (v37 i32) (const.i32 1048971)) - (let (v38 i32) (const.i32 2)) - (let (v39 u32) (bitcast v23)) - (let (v40 u32) (add.checked v39 24)) - (let (v41 u32) (mod.unchecked v40 4)) - (assertz 250 v41) - (let (v42 (ptr i32)) (inttoptr v40)) - (let (v43 i32) (load v42)) - (let (v44 u32) (bitcast v43)) - (let (v45 u32) (add.checked v44 12)) - (let (v46 u32) (mod.unchecked v45 4)) - (assertz 250 v46) - (let (v47 (ptr i32)) (inttoptr v45)) - (let (v48 i32) (load v47)) - (br (block 4 v0 v48))) - ) - - (func (export #core::fmt::Formatter::pad_integral) - (param i32) (param i32) (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 - (param v0 i32) - (param v1 i32) - (param v2 i32) - (param v3 i32) - (param v4 i32) - (param v5 i32) - (let (v7 i32) (const.i32 0)) - (let (v8 i1) (neq v1 0)) - (condbr v8 (block 3) (block 4))) - - (block 1 (param v6 i32) - (ret v6)) - - (block 2 - (param v29 i32) - (param v34 i32) - (param v41 i32) - (param v134 i32) - (param v140 i32) - (param v161 i32) - (param v180 i32) - (param v186 i32) - (let (v30 i32) (const.i32 4)) - (let (v31 i32) (band v29 v30)) - (let (v32 i1) (neq v31 0)) - (condbr v32 (block 6) (block 7))) - - (block 3 - (let (v17 i32) (const.i32 43)) - (let (v18 i32) (const.i32 1114112)) - (let (v19 u32) (bitcast v0)) - (let (v20 u32) (add.checked v19 28)) - (let (v21 u32) (mod.unchecked v20 4)) - (assertz 250 v21) - (let (v22 (ptr i32)) (inttoptr v20)) - (let (v23 i32) (load v22)) - (let (v24 i32) (const.i32 1)) - (let (v25 i32) (band v23 v24)) - (let (v26 i1) (neq v25 0)) - (let (v27 i32) (select v26 v17 v18)) - (let (v28 i32) (add.wrapping v25 v5)) - (br (block 2 v23 v3 v2 v28 v0 v27 v4 v5))) - - (block 4 - (let (v9 i32) (const.i32 1)) - (let (v10 i32) (add.wrapping v5 v9)) - (let (v11 u32) (bitcast v0)) - (let (v12 u32) (add.checked v11 28)) - (let (v13 u32) (mod.unchecked v12 4)) - (assertz 250 v13) - (let (v14 (ptr i32)) (inttoptr v12)) - (let (v15 i32) (load v14)) - (let (v16 i32) (const.i32 45)) - (br (block 2 v15 v3 v2 v10 v0 v16 v4 v5))) - - (block 5 - (param v139 i32) - (param v160 i32) - (param v166 i32) - (param v169 i32) - (param v179 i32) - (param v185 i32) - (param v201 i32) - (param v222 i32) - (let (v145 u32) (bitcast v139)) - (let (v146 u32) (mod.unchecked v145 4)) - (assertz 250 v146) - (let (v147 (ptr i32)) (inttoptr v145)) - (let (v148 i32) (load v147)) - (let (v149 i1) (neq v148 0)) - (condbr v149 (block 23) (block 24))) - - (block 6 - (let (v35 i32) (const.i32 16)) - (let (v36 u32) (bitcast v34)) - (let (v37 u32) (bitcast v35)) - (let (v38 i1) (lt v36 v37)) - (let (v39 i32) (sext v38)) - (let (v40 i1) (neq v39 0)) - (condbr v40 (block 9) (block 10))) - - (block 7 - (let (v33 i32) (const.i32 0)) - (br (block 5 v140 v161 v33 v34 v180 v186 v134 v29))) - - (block 8 - (param v132 i32) - (param v133 i32) - (param v141 i32) - (param v162 i32) - (param v167 i32) - (param v170 i32) - (param v181 i32) - (param v187 i32) - (param v223 i32) - (let (v138 i32) (add.wrapping v132 v133)) - (br (block 5 v141 v162 v167 v170 v181 v187 v138 v223))) - - (block 9 - (let (v43 i1) (neq v34 0)) - (condbr v43 (block 11) (block 12))) - - (block 10 - (let (v42 i32) (call #core::str::count::do_count_chars v41 v34)) - (br (block 8 v42 v134 v140 v161 v41 v34 v180 v186 v29))) - - (block 11 - (let (v45 i32) (const.i32 3)) - (let (v46 i32) (band v34 v45)) - (let (v47 i32) (const.i32 4)) - (let (v48 u32) (bitcast v34)) - (let (v49 u32) (bitcast v47)) - (let (v50 i1) (gte v48 v49)) - (let (v51 i32) (zext v50)) - (let (v52 i1) (neq v51 0)) - (condbr v52 (block 14) (block 15))) - - (block 12 - (let (v44 i32) (const.i32 0)) - (br (block 8 v44 v134 v140 v161 v41 v34 v180 v186 v29))) - - (block 13 - (param v107 i32) - (param v112 i32) - (param v113 i32) - (param v131 i32) - (param v135 i32) - (param v142 i32) - (param v163 i32) - (param v171 i32) - (param v182 i32) - (param v188 i32) - (param v224 i32) - (let (v109 i1) (eq v107 0)) - (let (v110 i32) (zext v109)) - (let (v111 i1) (neq v110 0)) - (condbr v111 (block 8 v131 v135 v142 v163 v112 v171 v182 v188 v224) (block 19))) - - (block 14 - (let (v55 i32) (const.i32 12)) - (let (v56 i32) (band v34 v55)) - (let (v57 i32) (const.i32 0)) - (let (v58 i32) (const.i32 0)) - (br (block 16 v57 v41 v58 v56 v46 v134 v140 v161 v34 v180 v186 v29))) - - (block 15 - (let (v53 i32) (const.i32 0)) - (let (v54 i32) (const.i32 0)) - (br (block 13 v46 v41 v54 v53 v134 v140 v161 v34 v180 v186 v29))) - - (block 16 - (param v59 i32) - (param v60 i32) - (param v61 i32) - (param v101 i32) - (param v108 i32) - (param v136 i32) - (param v143 i32) - (param v164 i32) - (param v172 i32) - (param v183 i32) - (param v189 i32) - (param v225 i32) - (let (v62 i32) (add.wrapping v60 v61)) - (let (v63 u32) (bitcast v62)) - (let (v64 (ptr i8)) (inttoptr v63)) - (let (v65 i8) (load v64)) - (let (v66 i32) (sext v65)) - (let (v67 i32) (const.i32 -65)) - (let (v68 i1) (gt v66 v67)) - (let (v69 i32) (zext v68)) - (let (v70 i32) (add.wrapping v59 v69)) - (let (v71 i32) (const.i32 1)) - (let (v72 i32) (add.wrapping v62 v71)) - (let (v73 u32) (bitcast v72)) - (let (v74 (ptr i8)) (inttoptr v73)) - (let (v75 i8) (load v74)) - (let (v76 i32) (sext v75)) - (let (v77 i32) (const.i32 -65)) - (let (v78 i1) (gt v76 v77)) - (let (v79 i32) (zext v78)) - (let (v80 i32) (add.wrapping v70 v79)) - (let (v81 i32) (const.i32 2)) - (let (v82 i32) (add.wrapping v62 v81)) - (let (v83 u32) (bitcast v82)) - (let (v84 (ptr i8)) (inttoptr v83)) - (let (v85 i8) (load v84)) - (let (v86 i32) (sext v85)) - (let (v87 i32) (const.i32 -65)) - (let (v88 i1) (gt v86 v87)) - (let (v89 i32) (zext v88)) - (let (v90 i32) (add.wrapping v80 v89)) - (let (v91 i32) (const.i32 3)) - (let (v92 i32) (add.wrapping v62 v91)) - (let (v93 u32) (bitcast v92)) - (let (v94 (ptr i8)) (inttoptr v93)) - (let (v95 i8) (load v94)) - (let (v96 i32) (sext v95)) - (let (v97 i32) (const.i32 -65)) - (let (v98 i1) (gt v96 v97)) - (let (v99 i32) (zext v98)) - (let (v100 i32) (add.wrapping v90 v99)) - (let (v102 i32) (const.i32 4)) - (let (v103 i32) (add.wrapping v61 v102)) - (let (v104 i1) (neq v101 v103)) - (let (v105 i32) (zext v104)) - (let (v106 i1) (neq v105 0)) - (condbr v106 (block 16 v100 v60 v103 v101 v108 v136 v143 v164 v172 v183 v189 v225) (block 18))) - - (block 17 - (br (block 13 v108 v60 v103 v100 v136 v143 v164 v172 v183 v189 v225))) - - (block 18 - (br (block 17))) - - (block 19 - (let (v114 i32) (add.wrapping v112 v113)) - (br (block 20 v131 v114 v107 v135 v142 v163 v112 v171 v182 v188 v224))) - - (block 20 - (param v115 i32) - (param v116 i32) - (param v127 i32) - (param v137 i32) - (param v144 i32) - (param v165 i32) - (param v168 i32) - (param v173 i32) - (param v184 i32) - (param v190 i32) - (param v226 i32) - (let (v117 u32) (bitcast v116)) - (let (v118 (ptr i8)) (inttoptr v117)) - (let (v119 i8) (load v118)) - (let (v120 i32) (sext v119)) - (let (v121 i32) (const.i32 -65)) - (let (v122 i1) (gt v120 v121)) - (let (v123 i32) (zext v122)) - (let (v124 i32) (add.wrapping v115 v123)) - (let (v125 i32) (const.i32 1)) - (let (v126 i32) (add.wrapping v116 v125)) - (let (v128 i32) (const.i32 -1)) - (let (v129 i32) (add.wrapping v127 v128)) - (let (v130 i1) (neq v129 0)) - (condbr v130 (block 20 v124 v126 v129 v137 v144 v165 v168 v173 v184 v190 v226) (block 22))) - - (block 21 - (br (block 8 v124 v137 v144 v165 v168 v173 v184 v190 v226))) - - (block 22 - (br (block 21))) - - (block 23 - (let (v196 u32) (bitcast v139)) - (let (v197 u32) (add.checked v196 4)) - (let (v198 u32) (mod.unchecked v197 4)) - (assertz 250 v198) - (let (v199 (ptr i32)) (inttoptr v197)) - (let (v200 i32) (load v199)) - (let (v202 u32) (bitcast v200)) - (let (v203 u32) (bitcast v201)) - (let (v204 i1) (gt v202 v203)) - (let (v205 i32) (sext v204)) - (let (v206 i1) (neq v205 0)) - (condbr v206 (block 30) (block 31))) - - (block 24 - (let (v150 u32) (bitcast v139)) - (let (v151 u32) (add.checked v150 20)) - (let (v152 u32) (mod.unchecked v151 4)) - (assertz 250 v152) - (let (v153 (ptr i32)) (inttoptr v151)) - (let (v154 i32) (load v153)) - (let (v155 u32) (bitcast v139)) - (let (v156 u32) (add.checked v155 24)) - (let (v157 u32) (mod.unchecked v156 4)) - (assertz 250 v157) - (let (v158 (ptr i32)) (inttoptr v156)) - (let (v159 i32) (load v158)) - (let (v174 i32) (call #core::fmt::Formatter::pad_integral::write_prefix v154 v159 v160 v166 v169)) - (let (v175 i1) (eq v174 0)) - (let (v176 i32) (zext v175)) - (let (v177 i1) (neq v176 0)) - (condbr v177 (block 25) (block 26))) - - (block 25 - (let (v191 u32) (bitcast v159)) - (let (v192 u32) (add.checked v191 12)) - (let (v193 u32) (mod.unchecked v192 4)) - (assertz 250 v193) - (let (v194 (ptr i32)) (inttoptr v192)) - (let (v195 i32) (load v194)) - (ret v195)) - - (block 26 - (let (v178 i32) (const.i32 1)) - (ret v178)) - - (block 27 (param v423 i32) - (br (block 1 v423))) - - (block 28 - (let (v316 i32) (sub.wrapping v200 v201)) - (let (v317 u32) (bitcast v139)) - (let (v318 u32) (add.checked v317 32)) - (let (v319 (ptr u8)) (inttoptr v318)) - (let (v320 u8) (load v319)) - (let (v321 i32) (zext v320)) - (let (v322 u32) (cast v321)) - (switchv322 - (0 . (block 44)) - (1 . (block 44)) - (2 . (block 43)) - (3 . (block 42 v321 v139 v160 v166 v169 v179 v185 v316)) - (_ . (block 42 v321 v139 v160 v166 v169 v179 v185 v316)))) - - (block 29 - (let (v311 u32) (bitcast v216)) - (let (v312 u32) (add.checked v311 12)) - (let (v313 u32) (mod.unchecked v312 4)) - (assertz 250 v313) - (let (v314 (ptr i32)) (inttoptr v312)) - (let (v315 i32) (load v314)) - (br (block 27 v315))) - - (block 30 - (let (v227 i32) (const.i32 8)) - (let (v228 i32) (band v222 v227)) - (let (v229 i1) (eq v228 0)) - (let (v230 i32) (zext v229)) - (let (v231 i1) (neq v230 0)) - (condbr v231 (block 28) (block 33))) - - (block 31 - (let (v207 u32) (bitcast v139)) - (let (v208 u32) (add.checked v207 20)) - (let (v209 u32) (mod.unchecked v208 4)) - (assertz 250 v209) - (let (v210 (ptr i32)) (inttoptr v208)) - (let (v211 i32) (load v210)) - (let (v212 u32) (bitcast v139)) - (let (v213 u32) (add.checked v212 24)) - (let (v214 u32) (mod.unchecked v213 4)) - (assertz 250 v214) - (let (v215 (ptr i32)) (inttoptr v213)) - (let (v216 i32) (load v215)) - (let (v217 i32) (call #core::fmt::Formatter::pad_integral::write_prefix v211 v216 v160 v166 v169)) - (let (v218 i1) (eq v217 0)) - (let (v219 i32) (zext v218)) - (let (v220 i1) (neq v219 0)) - (condbr v220 (block 29) (block 32))) - - (block 32 - (let (v221 i32) (const.i32 1)) - (ret v221)) - - (block 33 - (let (v232 u32) (bitcast v139)) - (let (v233 u32) (add.checked v232 16)) - (let (v234 u32) (mod.unchecked v233 4)) - (assertz 250 v234) - (let (v235 (ptr i32)) (inttoptr v233)) - (let (v236 i32) (load v235)) - (let (v237 i32) (const.i32 48)) - (let (v238 u32) (bitcast v139)) - (let (v239 u32) (add.checked v238 16)) - (let (v240 u32) (mod.unchecked v239 4)) - (assertz 250 v240) - (let (v241 (ptr i32)) (inttoptr v239)) - (store v241 v237) - (let (v242 u32) (bitcast v139)) - (let (v243 u32) (add.checked v242 32)) - (let (v244 (ptr u8)) (inttoptr v243)) - (let (v245 u8) (load v244)) - (let (v246 i32) (zext v245)) - (let (v247 i32) (const.i32 1)) - (let (v248 i32) (const.i32 1)) - (let (v249 u32) (bitcast v248)) - (let (v250 u8) (trunc v249)) - (let (v251 u32) (bitcast v139)) - (let (v252 u32) (add.checked v251 32)) - (let (v253 (ptr u8)) (inttoptr v252)) - (store v253 v250) - (let (v254 u32) (bitcast v139)) - (let (v255 u32) (add.checked v254 20)) - (let (v256 u32) (mod.unchecked v255 4)) - (assertz 250 v256) - (let (v257 (ptr i32)) (inttoptr v255)) - (let (v258 i32) (load v257)) - (let (v259 u32) (bitcast v139)) - (let (v260 u32) (add.checked v259 24)) - (let (v261 u32) (mod.unchecked v260 4)) - (assertz 250 v261) - (let (v262 (ptr i32)) (inttoptr v260)) - (let (v263 i32) (load v262)) - (let (v264 i32) (call #core::fmt::Formatter::pad_integral::write_prefix v258 v263 v160 v166 v169)) - (let (v265 i1) (neq v264 0)) - (condbr v265 (block 27 v247) (block 34))) - - (block 34 - (let (v266 i32) (sub.wrapping v200 v201)) - (let (v267 i32) (const.i32 1)) - (let (v268 i32) (add.wrapping v266 v267)) - (br (block 36 v268 v258 v263 v179 v185 v139 v246 v236))) - - (block 35 - (let (v289 u32) (bitcast v277)) - (let (v290 u32) (add.checked v289 12)) - (let (v291 u32) (mod.unchecked v290 4)) - (assertz 250 v291) - (let (v292 (ptr i32)) (inttoptr v290)) - (let (v293 i32) (load v292)) - (let (v294 i1) (eq v293 0)) - (let (v295 i32) (zext v294)) - (let (v296 i1) (neq v295 0)) - (condbr v296 (block 40) (block 41))) - - (block 36 - (param v269 i32) - (param v275 i32) - (param v277 i32) - (param v287 i32) - (param v288 i32) - (param v298 i32) - (param v299 i32) - (param v305 i32) - (let (v270 i32) (const.i32 -1)) - (let (v271 i32) (add.wrapping v269 v270)) - (let (v272 i1) (eq v271 0)) - (let (v273 i32) (zext v272)) - (let (v274 i1) (neq v273 0)) - (condbr v274 (block 35) (block 38))) - - (block 37 - (let (v286 i32) (const.i32 1)) - (ret v286)) - - (block 38 - (let (v276 i32) (const.i32 48)) - (let (v278 u32) (bitcast v277)) - (let (v279 u32) (add.checked v278 16)) - (let (v280 u32) (mod.unchecked v279 4)) - (assertz 250 v280) - (let (v281 (ptr i32)) (inttoptr v279)) - (let (v282 i32) (load v281)) - (let (v283 i1) (eq v282 0)) - (let (v284 i32) (zext v283)) - (let (v285 i1) (neq v284 0)) - (condbr v285 (block 36 v271 v275 v277 v287 v288 v298 v299 v305) (block 39))) - - (block 39 - (br (block 37))) - - (block 40 - (let (v300 u32) (bitcast v299)) - (let (v301 u8) (trunc v300)) - (let (v302 u32) (bitcast v298)) - (let (v303 u32) (add.checked v302 32)) - (let (v304 (ptr u8)) (inttoptr v303)) - (store v304 v301) - (let (v306 u32) (bitcast v298)) - (let (v307 u32) (add.checked v306 16)) - (let (v308 u32) (mod.unchecked v307 4)) - (assertz 250 v308) - (let (v309 (ptr i32)) (inttoptr v307)) - (store v309 v305) - (let (v310 i32) (const.i32 0)) - (ret v310)) - - (block 41 - (let (v297 i32) (const.i32 1)) - (ret v297)) - - (block 42 - (param v336 i32) - (param v339 i32) - (param v375 i32) - (param v377 i32) - (param v379 i32) - (param v383 i32) - (param v385 i32) - (param v416 i32) - (let (v337 i32) (const.i32 1)) - (let (v338 i32) (add.wrapping v336 v337)) - (let (v340 u32) (bitcast v339)) - (let (v341 u32) (add.checked v340 16)) - (let (v342 u32) (mod.unchecked v341 4)) - (assertz 250 v342) - (let (v343 (ptr i32)) (inttoptr v341)) - (let (v344 i32) (load v343)) - (let (v345 u32) (bitcast v339)) - (let (v346 u32) (add.checked v345 24)) - (let (v347 u32) (mod.unchecked v346 4)) - (assertz 250 v347) - (let (v348 (ptr i32)) (inttoptr v346)) - (let (v349 i32) (load v348)) - (let (v350 u32) (bitcast v339)) - (let (v351 u32) (add.checked v350 20)) - (let (v352 u32) (mod.unchecked v351 4)) - (assertz 250 v352) - (let (v353 (ptr i32)) (inttoptr v351)) - (let (v354 i32) (load v353)) - (br (block 46 v338 v354 v344 v349 v375 v377 v379 v383 v385 v416))) - - (block 43 - (let (v324 i32) (const.i32 1)) - (let (v325 u32) (bitcast v316)) - (let (v326 u32) (bitcast v324)) - (let (v327 u32) (shr.wrapping v325 v326)) - (let (v328 i32) (bitcast v327)) - (let (v329 i32) (const.i32 1)) - (let (v330 i32) (add.wrapping v316 v329)) - (let (v331 i32) (const.i32 1)) - (let (v332 u32) (bitcast v330)) - (let (v333 u32) (bitcast v331)) - (let (v334 u32) (shr.wrapping v332 v333)) - (let (v335 i32) (bitcast v334)) - (br (block 42 v328 v139 v160 v166 v169 v179 v185 v335))) - - (block 44 - (let (v323 i32) (const.i32 0)) - (br (block 42 v316 v139 v160 v166 v169 v179 v185 v323))) - - (block 45 - (let (v373 i32) (const.i32 1)) - (let (v380 i32) (call #core::fmt::Formatter::pad_integral::write_prefix v361 v363 v374 v376 v378)) - (let (v381 i1) (neq v380 0)) - (condbr v381 (block 27 v373) (block 50))) - - (block 46 - (param v355 i32) - (param v361 i32) - (param v362 i32) - (param v363 i32) - (param v374 i32) - (param v376 i32) - (param v378 i32) - (param v382 i32) - (param v384 i32) - (param v415 i32) - (let (v356 i32) (const.i32 -1)) - (let (v357 i32) (add.wrapping v355 v356)) - (let (v358 i1) (eq v357 0)) - (let (v359 i32) (zext v358)) - (let (v360 i1) (neq v359 0)) - (condbr v360 (block 45) (block 48))) - - (block 47 - (let (v372 i32) (const.i32 1)) - (ret v372)) - - (block 48 - (let (v364 u32) (bitcast v363)) - (let (v365 u32) (add.checked v364 16)) - (let (v366 u32) (mod.unchecked v365 4)) - (assertz 250 v366) - (let (v367 (ptr i32)) (inttoptr v365)) - (let (v368 i32) (load v367)) - (let (v369 i1) (eq v368 0)) - (let (v370 i32) (zext v369)) - (let (v371 i1) (neq v370 0)) - (condbr v371 (block 46 v357 v361 v362 v363 v374 v376 v378 v382 v384 v415) (block 49))) - - (block 49 - (br (block 47))) - - (block 50 - (let (v386 u32) (bitcast v363)) - (let (v387 u32) (add.checked v386 12)) - (let (v388 u32) (mod.unchecked v387 4)) - (assertz 250 v388) - (let (v389 (ptr i32)) (inttoptr v387)) - (let (v390 i32) (load v389)) - (let (v391 i1) (neq v390 0)) - (condbr v391 (block 27 v373) (block 51))) - - (block 51 - (let (v392 i32) (const.i32 0)) - (br (block 52 v415 v392 v361 v362 v363))) - - (block 52 - (param v393 i32) - (param v394 i32) - (param v404 i32) - (param v405 i32) - (param v406 i32) - (let (v395 i1) (neq v393 v394)) - (let (v396 i32) (zext v395)) - (let (v397 i1) (neq v396 0)) - (condbr v397 (block 54) (block 55))) - - (block 53 - (let (v417 i32) (const.i32 -1)) - (let (v418 i32) (add.wrapping v403 v417)) - (let (v419 u32) (bitcast v418)) - (let (v420 u32) (bitcast v393)) - (let (v421 i1) (lt v419 v420)) - (let (v422 i32) (sext v421)) - (ret v422)) - - (block 54 - (let (v402 i32) (const.i32 1)) - (let (v403 i32) (add.wrapping v394 v402)) - (let (v407 u32) (bitcast v406)) - (let (v408 u32) (add.checked v407 16)) - (let (v409 u32) (mod.unchecked v408 4)) - (assertz 250 v409) - (let (v410 (ptr i32)) (inttoptr v408)) - (let (v411 i32) (load v410)) - (let (v412 i1) (eq v411 0)) - (let (v413 i32) (zext v412)) - (let (v414 i1) (neq v413 0)) - (condbr v414 (block 52 v393 v403 v404 v405 v406) (block 56))) - - (block 55 - (let (v398 u32) (bitcast v393)) - (let (v399 u32) (bitcast v393)) - (let (v400 i1) (lt v398 v399)) - (let (v401 i32) (sext v400)) - (ret v401)) - - (block 56 - (br (block 53))) - ) - - (func (export #core::fmt::Write::write_fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 1048932)) - (let (v4 i32) (call #core::fmt::write v0 v3 v1)) - (br (block 1 v4))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #core::str::count::do_count_chars) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i32) (const.i32 3)) - (let (v5 i32) (add.wrapping v0 v4)) - (let (v6 i32) (const.i32 -4)) - (let (v7 i32) (band v5 v6)) - (let (v8 i32) (sub.wrapping v7 v0)) - (let (v9 u32) (bitcast v1)) - (let (v10 u32) (bitcast v8)) - (let (v11 i1) (lt v9 v10)) - (let (v12 i32) (sext v11)) - (let (v13 i1) (neq v12 0)) - (condbr v13 (block 3 v1 v0) (block 4))) - - (block 1 (param v2 i32) - (ret v2)) - - (block 2 (param v520 i32) - (br (block 1 v520))) - - (block 3 (param v429 i32) (param v494 i32) - (let (v430 i1) (neq v429 0)) - (condbr v430 (block 34) (block 35))) - - (block 4 - (let (v14 i32) (sub.wrapping v1 v8)) - (let (v15 i32) (const.i32 4)) - (let (v16 u32) (bitcast v14)) - (let (v17 u32) (bitcast v15)) - (let (v18 i1) (lt v16 v17)) - (let (v19 i32) (sext v18)) - (let (v20 i1) (neq v19 0)) - (condbr v20 (block 3 v1 v0) (block 5))) - - (block 5 - (let (v21 i32) (const.i32 3)) - (let (v22 i32) (band v14 v21)) - (let (v23 i32) (const.i32 0)) - (let (v24 i32) (const.i32 0)) - (let (v25 i1) (eq v7 v0)) - (let (v26 i32) (zext v25)) - (let (v27 i1) (neq v26 0)) - (condbr v27 (block 6 v0 v8 v22 v14 v23 v24) (block 7))) - - (block 6 - (param v108 i32) - (param v110 i32) - (param v115 i32) - (param v122 i32) - (param v169 i32) - (param v174 i32) - (let (v114 i32) (add.wrapping v108 v110)) - (let (v119 i1) (eq v115 0)) - (let (v120 i32) (zext v119)) - (let (v121 i1) (neq v120 0)) - (condbr v121 (block 18 v122 v169 v174 v114) (block 19))) - - (block 7 - (let (v28 i32) (const.i32 0)) - (let (v29 i32) (sub.wrapping v0 v7)) - (let (v30 i32) (const.i32 -4)) - (let (v31 u32) (bitcast v29)) - (let (v32 u32) (bitcast v30)) - (let (v33 i1) (lte v31 v32)) - (let (v34 i32) (sext v33)) - (let (v35 i1) (neq v34 0)) - (condbr v35 (block 9) (block 10))) - - (block 8 - (param v83 i32) - (param v86 i32) - (param v87 i32) - (param v105 i32) - (param v106 i32) - (param v111 i32) - (param v116 i32) - (param v123 i32) - (param v170 i32) - (let (v85 i1) (neq v83 0)) - (condbr v85 (block 6 v86 v111 v116 v123 v170 v105) (block 14))) - - (block 9 - (let (v37 i32) (const.i32 0)) - (br (block 11 v28 v0 v37 v26 v29 v8 v22 v14 v23))) - - (block 10 - (let (v36 i32) (const.i32 0)) - (br (block 8 v26 v0 v36 v28 v29 v8 v22 v14 v23))) - - (block 11 - (param v38 i32) - (param v39 i32) - (param v40 i32) - (param v84 i32) - (param v107 i32) - (param v112 i32) - (param v117 i32) - (param v124 i32) - (param v171 i32) - (let (v41 i32) (add.wrapping v39 v40)) - (let (v42 u32) (bitcast v41)) - (let (v43 (ptr i8)) (inttoptr v42)) - (let (v44 i8) (load v43)) - (let (v45 i32) (sext v44)) - (let (v46 i32) (const.i32 -65)) - (let (v47 i1) (gt v45 v46)) - (let (v48 i32) (zext v47)) - (let (v49 i32) (add.wrapping v38 v48)) - (let (v50 i32) (const.i32 1)) - (let (v51 i32) (add.wrapping v41 v50)) - (let (v52 u32) (bitcast v51)) - (let (v53 (ptr i8)) (inttoptr v52)) - (let (v54 i8) (load v53)) - (let (v55 i32) (sext v54)) - (let (v56 i32) (const.i32 -65)) - (let (v57 i1) (gt v55 v56)) - (let (v58 i32) (zext v57)) - (let (v59 i32) (add.wrapping v49 v58)) - (let (v60 i32) (const.i32 2)) - (let (v61 i32) (add.wrapping v41 v60)) - (let (v62 u32) (bitcast v61)) - (let (v63 (ptr i8)) (inttoptr v62)) - (let (v64 i8) (load v63)) - (let (v65 i32) (sext v64)) - (let (v66 i32) (const.i32 -65)) - (let (v67 i1) (gt v65 v66)) - (let (v68 i32) (zext v67)) - (let (v69 i32) (add.wrapping v59 v68)) - (let (v70 i32) (const.i32 3)) - (let (v71 i32) (add.wrapping v41 v70)) - (let (v72 u32) (bitcast v71)) - (let (v73 (ptr i8)) (inttoptr v72)) - (let (v74 i8) (load v73)) - (let (v75 i32) (sext v74)) - (let (v76 i32) (const.i32 -65)) - (let (v77 i1) (gt v75 v76)) - (let (v78 i32) (zext v77)) - (let (v79 i32) (add.wrapping v69 v78)) - (let (v80 i32) (const.i32 4)) - (let (v81 i32) (add.wrapping v40 v80)) - (let (v82 i1) (neq v81 0)) - (condbr v82 (block 11 v79 v39 v81 v84 v107 v112 v117 v124 v171) (block 13))) - - (block 12 - (br (block 8 v84 v39 v81 v79 v107 v112 v117 v124 v171))) - - (block 13 - (br (block 12))) - - (block 14 - (let (v88 i32) (add.wrapping v86 v87)) - (br (block 15 v105 v88 v106 v86 v111 v116 v123 v170))) - - (block 15 - (param v89 i32) - (param v90 i32) - (param v101 i32) - (param v109 i32) - (param v113 i32) - (param v118 i32) - (param v125 i32) - (param v172 i32) - (let (v91 u32) (bitcast v90)) - (let (v92 (ptr i8)) (inttoptr v91)) - (let (v93 i8) (load v92)) - (let (v94 i32) (sext v93)) - (let (v95 i32) (const.i32 -65)) - (let (v96 i1) (gt v94 v95)) - (let (v97 i32) (zext v96)) - (let (v98 i32) (add.wrapping v89 v97)) - (let (v99 i32) (const.i32 1)) - (let (v100 i32) (add.wrapping v90 v99)) - (let (v102 i32) (const.i32 1)) - (let (v103 i32) (add.wrapping v101 v102)) - (let (v104 i1) (neq v103 0)) - (condbr v104 (block 15 v98 v100 v103 v109 v113 v118 v125 v172) (block 17))) - - (block 16 - (br (block 6 v109 v113 v118 v125 v172 v98))) - - (block 17 - (br (block 16))) - - (block 18 - (param v162 i32) - (param v168 i32) - (param v173 i32) - (param v333 i32) - (let (v163 i32) (const.i32 2)) - (let (v164 u32) (bitcast v162)) - (let (v165 u32) (bitcast v163)) - (let (v166 u32) (shr.wrapping v164 v165)) - (let (v167 i32) (bitcast v166)) - (let (v175 i32) (add.wrapping v168 v173)) - (br (block 22 v333 v167 v175))) - - (block 19 - (let (v126 i32) (const.i32 -4)) - (let (v127 i32) (band v122 v126)) - (let (v128 i32) (add.wrapping v114 v127)) - (let (v129 u32) (bitcast v128)) - (let (v130 (ptr i8)) (inttoptr v129)) - (let (v131 i8) (load v130)) - (let (v132 i32) (sext v131)) - (let (v133 i32) (const.i32 -65)) - (let (v134 i1) (gt v132 v133)) - (let (v135 i32) (zext v134)) - (let (v136 i32) (const.i32 1)) - (let (v137 i1) (eq v115 v136)) - (let (v138 i32) (zext v137)) - (let (v139 i1) (neq v138 0)) - (condbr v139 (block 18 v122 v135 v174 v114) (block 20))) - - (block 20 - (let (v140 u32) (bitcast v128)) - (let (v141 u32) (add.checked v140 1)) - (let (v142 (ptr i8)) (inttoptr v141)) - (let (v143 i8) (load v142)) - (let (v144 i32) (sext v143)) - (let (v145 i32) (const.i32 -65)) - (let (v146 i1) (gt v144 v145)) - (let (v147 i32) (zext v146)) - (let (v148 i32) (add.wrapping v135 v147)) - (let (v149 i32) (const.i32 2)) - (let (v150 i1) (eq v115 v149)) - (let (v151 i32) (zext v150)) - (let (v152 i1) (neq v151 0)) - (condbr v152 (block 18 v122 v148 v174 v114) (block 21))) - - (block 21 - (let (v153 u32) (bitcast v128)) - (let (v154 u32) (add.checked v153 2)) - (let (v155 (ptr i8)) (inttoptr v154)) - (let (v156 i8) (load v155)) - (let (v157 i32) (sext v156)) - (let (v158 i32) (const.i32 -65)) - (let (v159 i1) (gt v157 v158)) - (let (v160 i32) (zext v159)) - (let (v161 i32) (add.wrapping v148 v160)) - (br (block 18 v122 v161 v174 v114))) - - (block 22 (param v176 i32) (param v177 i32) (param v325 i32) - (let (v178 i1) (eq v177 0)) - (let (v179 i32) (zext v178)) - (let (v180 i1) (neq v179 0)) - (condbr v180 (block 2 v325) (block 24))) - - (block 23 - (let (v334 i32) (const.i32 252)) - (let (v335 i32) (band v298 v334)) - (let (v336 i32) (const.i32 2)) - (let (v337 u32) (bitcast v336)) - (let (v338 i32) (shl.wrapping v335 v337)) - (let (v339 i32) (add.wrapping v301 v338)) - (let (v340 u32) (bitcast v339)) - (let (v341 u32) (mod.unchecked v340 4)) - (assertz 250 v341) - (let (v342 (ptr i32)) (inttoptr v340)) - (let (v343 i32) (load v342)) - (let (v344 i32) (const.i32 -1)) - (let (v345 i32) (bxor v343 v344)) - (let (v346 i32) (const.i32 7)) - (let (v347 u32) (bitcast v345)) - (let (v348 u32) (bitcast v346)) - (let (v349 u32) (shr.wrapping v347 v348)) - (let (v350 i32) (bitcast v349)) - (let (v351 i32) (const.i32 6)) - (let (v352 u32) (bitcast v343)) - (let (v353 u32) (bitcast v351)) - (let (v354 u32) (shr.wrapping v352 v353)) - (let (v355 i32) (bitcast v354)) - (let (v356 i32) (bor v350 v355)) - (let (v357 i32) (const.i32 16843009)) - (let (v358 i32) (band v356 v357)) - (let (v359 i32) (const.i32 1)) - (let (v360 i1) (eq v328 v359)) - (let (v361 i32) (zext v360)) - (let (v362 i1) (neq v361 0)) - (condbr v362 (block 31 v358 v327) (block 32))) - - (block 24 - (let (v181 i32) (const.i32 192)) - (let (v182 i32) (const.i32 192)) - (let (v183 u32) (bitcast v177)) - (let (v184 u32) (bitcast v182)) - (let (v185 i1) (lt v183 v184)) - (let (v186 i32) (sext v185)) - (let (v187 i1) (neq v186 0)) - (let (v188 i32) (select v187 v177 v181)) - (let (v189 i32) (const.i32 3)) - (let (v190 i32) (band v188 v189)) - (let (v191 i32) (const.i32 2)) - (let (v192 u32) (bitcast v191)) - (let (v193 i32) (shl.wrapping v188 v192)) - (let (v194 i32) (const.i32 0)) - (let (v195 i32) (const.i32 4)) - (let (v196 u32) (bitcast v177)) - (let (v197 u32) (bitcast v195)) - (let (v198 i1) (lt v196 v197)) - (let (v199 i32) (sext v198)) - (let (v200 i1) (neq v199 0)) - (condbr v200 (block 25 v177 v188 v176 v193 v194 v325 v190) (block 26))) - - (block 25 - (param v296 i32) - (param v298 i32) - (param v301 i32) - (param v303 i32) - (param v306 i32) - (param v324 i32) - (param v328 i32) - (let (v300 i32) (sub.wrapping v296 v298)) - (let (v305 i32) (add.wrapping v301 v303)) - (let (v307 i32) (const.i32 8)) - (let (v308 u32) (bitcast v306)) - (let (v309 u32) (bitcast v307)) - (let (v310 u32) (shr.wrapping v308 v309)) - (let (v311 i32) (bitcast v310)) - (let (v312 i32) (const.i32 16711935)) - (let (v313 i32) (band v311 v312)) - (let (v314 i32) (const.i32 16711935)) - (let (v315 i32) (band v306 v314)) - (let (v316 i32) (add.wrapping v313 v315)) - (let (v317 i32) (const.i32 65537)) - (let (v318 i32) (mul.wrapping v316 v317)) - (let (v319 i32) (const.i32 16)) - (let (v320 u32) (bitcast v318)) - (let (v321 u32) (bitcast v319)) - (let (v322 u32) (shr.wrapping v320 v321)) - (let (v323 i32) (bitcast v322)) - (let (v327 i32) (add.wrapping v323 v324)) - (let (v330 i1) (eq v328 0)) - (let (v331 i32) (zext v330)) - (let (v332 i1) (neq v331 0)) - (condbr v332 (block 22 v305 v300 v327) (block 30))) - - (block 26 - (let (v201 i32) (const.i32 1008)) - (let (v202 i32) (band v193 v201)) - (let (v203 i32) (add.wrapping v176 v202)) - (let (v204 i32) (const.i32 0)) - (br (block 27 v176 v204 v203 v177 v188 v176 v193 v325 v190))) - - (block 27 - (param v205 i32) - (param v285 i32) - (param v292 i32) - (param v297 i32) - (param v299 i32) - (param v302 i32) - (param v304 i32) - (param v326 i32) - (param v329 i32) - (let (v206 u32) (bitcast v205)) - (let (v207 u32) (add.checked v206 12)) - (let (v208 u32) (mod.unchecked v207 4)) - (assertz 250 v208) - (let (v209 (ptr i32)) (inttoptr v207)) - (let (v210 i32) (load v209)) - (let (v211 i32) (const.i32 -1)) - (let (v212 i32) (bxor v210 v211)) - (let (v213 i32) (const.i32 7)) - (let (v214 u32) (bitcast v212)) - (let (v215 u32) (bitcast v213)) - (let (v216 u32) (shr.wrapping v214 v215)) - (let (v217 i32) (bitcast v216)) - (let (v218 i32) (const.i32 6)) - (let (v219 u32) (bitcast v210)) - (let (v220 u32) (bitcast v218)) - (let (v221 u32) (shr.wrapping v219 v220)) - (let (v222 i32) (bitcast v221)) - (let (v223 i32) (bor v217 v222)) - (let (v224 i32) (const.i32 16843009)) - (let (v225 i32) (band v223 v224)) - (let (v226 u32) (bitcast v205)) - (let (v227 u32) (add.checked v226 8)) - (let (v228 u32) (mod.unchecked v227 4)) - (assertz 250 v228) - (let (v229 (ptr i32)) (inttoptr v227)) - (let (v230 i32) (load v229)) - (let (v231 i32) (const.i32 -1)) - (let (v232 i32) (bxor v230 v231)) - (let (v233 i32) (const.i32 7)) - (let (v234 u32) (bitcast v232)) - (let (v235 u32) (bitcast v233)) - (let (v236 u32) (shr.wrapping v234 v235)) - (let (v237 i32) (bitcast v236)) - (let (v238 i32) (const.i32 6)) - (let (v239 u32) (bitcast v230)) - (let (v240 u32) (bitcast v238)) - (let (v241 u32) (shr.wrapping v239 v240)) - (let (v242 i32) (bitcast v241)) - (let (v243 i32) (bor v237 v242)) - (let (v244 i32) (const.i32 16843009)) - (let (v245 i32) (band v243 v244)) - (let (v246 u32) (bitcast v205)) - (let (v247 u32) (add.checked v246 4)) - (let (v248 u32) (mod.unchecked v247 4)) - (assertz 250 v248) - (let (v249 (ptr i32)) (inttoptr v247)) - (let (v250 i32) (load v249)) - (let (v251 i32) (const.i32 -1)) - (let (v252 i32) (bxor v250 v251)) - (let (v253 i32) (const.i32 7)) - (let (v254 u32) (bitcast v252)) - (let (v255 u32) (bitcast v253)) - (let (v256 u32) (shr.wrapping v254 v255)) - (let (v257 i32) (bitcast v256)) - (let (v258 i32) (const.i32 6)) - (let (v259 u32) (bitcast v250)) - (let (v260 u32) (bitcast v258)) - (let (v261 u32) (shr.wrapping v259 v260)) - (let (v262 i32) (bitcast v261)) - (let (v263 i32) (bor v257 v262)) - (let (v264 i32) (const.i32 16843009)) - (let (v265 i32) (band v263 v264)) - (let (v266 u32) (bitcast v205)) - (let (v267 u32) (mod.unchecked v266 4)) - (assertz 250 v267) - (let (v268 (ptr i32)) (inttoptr v266)) - (let (v269 i32) (load v268)) - (let (v270 i32) (const.i32 -1)) - (let (v271 i32) (bxor v269 v270)) - (let (v272 i32) (const.i32 7)) - (let (v273 u32) (bitcast v271)) - (let (v274 u32) (bitcast v272)) - (let (v275 u32) (shr.wrapping v273 v274)) - (let (v276 i32) (bitcast v275)) - (let (v277 i32) (const.i32 6)) - (let (v278 u32) (bitcast v269)) - (let (v279 u32) (bitcast v277)) - (let (v280 u32) (shr.wrapping v278 v279)) - (let (v281 i32) (bitcast v280)) - (let (v282 i32) (bor v276 v281)) - (let (v283 i32) (const.i32 16843009)) - (let (v284 i32) (band v282 v283)) - (let (v286 i32) (add.wrapping v284 v285)) - (let (v287 i32) (add.wrapping v265 v286)) - (let (v288 i32) (add.wrapping v245 v287)) - (let (v289 i32) (add.wrapping v225 v288)) - (let (v290 i32) (const.i32 16)) - (let (v291 i32) (add.wrapping v205 v290)) - (let (v293 i1) (neq v291 v292)) - (let (v294 i32) (zext v293)) - (let (v295 i1) (neq v294 0)) - (condbr v295 (block 27 v291 v289 v292 v297 v299 v302 v304 v326 v329) (block 29))) - - (block 28 - (br (block 25 v297 v299 v302 v304 v289 v326 v329))) - - (block 29 - (br (block 28))) - - (block 30 - (br (block 23))) - - (block 31 (param v409 i32) (param v427 i32) - (let (v410 i32) (const.i32 8)) - (let (v411 u32) (bitcast v409)) - (let (v412 u32) (bitcast v410)) - (let (v413 u32) (shr.wrapping v411 v412)) - (let (v414 i32) (bitcast v413)) - (let (v415 i32) (const.i32 459007)) - (let (v416 i32) (band v414 v415)) - (let (v417 i32) (const.i32 16711935)) - (let (v418 i32) (band v409 v417)) - (let (v419 i32) (add.wrapping v416 v418)) - (let (v420 i32) (const.i32 65537)) - (let (v421 i32) (mul.wrapping v419 v420)) - (let (v422 i32) (const.i32 16)) - (let (v423 u32) (bitcast v421)) - (let (v424 u32) (bitcast v422)) - (let (v425 u32) (shr.wrapping v423 v424)) - (let (v426 i32) (bitcast v425)) - (let (v428 i32) (add.wrapping v426 v427)) - (ret v428)) - - (block 32 - (let (v363 u32) (bitcast v339)) - (let (v364 u32) (add.checked v363 4)) - (let (v365 u32) (mod.unchecked v364 4)) - (assertz 250 v365) - (let (v366 (ptr i32)) (inttoptr v364)) - (let (v367 i32) (load v366)) - (let (v368 i32) (const.i32 -1)) - (let (v369 i32) (bxor v367 v368)) - (let (v370 i32) (const.i32 7)) - (let (v371 u32) (bitcast v369)) - (let (v372 u32) (bitcast v370)) - (let (v373 u32) (shr.wrapping v371 v372)) - (let (v374 i32) (bitcast v373)) - (let (v375 i32) (const.i32 6)) - (let (v376 u32) (bitcast v367)) - (let (v377 u32) (bitcast v375)) - (let (v378 u32) (shr.wrapping v376 v377)) - (let (v379 i32) (bitcast v378)) - (let (v380 i32) (bor v374 v379)) - (let (v381 i32) (const.i32 16843009)) - (let (v382 i32) (band v380 v381)) - (let (v383 i32) (add.wrapping v382 v358)) - (let (v384 i32) (const.i32 2)) - (let (v385 i1) (eq v328 v384)) - (let (v386 i32) (zext v385)) - (let (v387 i1) (neq v386 0)) - (condbr v387 (block 31 v383 v327) (block 33))) - - (block 33 - (let (v388 u32) (bitcast v339)) - (let (v389 u32) (add.checked v388 8)) - (let (v390 u32) (mod.unchecked v389 4)) - (assertz 250 v390) - (let (v391 (ptr i32)) (inttoptr v389)) - (let (v392 i32) (load v391)) - (let (v393 i32) (const.i32 -1)) - (let (v394 i32) (bxor v392 v393)) - (let (v395 i32) (const.i32 7)) - (let (v396 u32) (bitcast v394)) - (let (v397 u32) (bitcast v395)) - (let (v398 u32) (shr.wrapping v396 v397)) - (let (v399 i32) (bitcast v398)) - (let (v400 i32) (const.i32 6)) - (let (v401 u32) (bitcast v392)) - (let (v402 u32) (bitcast v400)) - (let (v403 u32) (shr.wrapping v401 v402)) - (let (v404 i32) (bitcast v403)) - (let (v405 i32) (bor v399 v404)) - (let (v406 i32) (const.i32 16843009)) - (let (v407 i32) (band v405 v406)) - (let (v408 i32) (add.wrapping v407 v383)) - (br (block 31 v408 v327))) - - (block 34 - (let (v432 i32) (const.i32 3)) - (let (v433 i32) (band v429 v432)) - (let (v434 i32) (const.i32 4)) - (let (v435 u32) (bitcast v429)) - (let (v436 u32) (bitcast v434)) - (let (v437 i1) (gte v435 v436)) - (let (v438 i32) (zext v437)) - (let (v439 i1) (neq v438 0)) - (condbr v439 (block 37) (block 38))) - - (block 35 - (let (v431 i32) (const.i32 0)) - (ret v431)) - - (block 36 - (param v495 i32) - (param v500 i32) - (param v501 i32) - (param v519 i32) - (let (v497 i1) (eq v495 0)) - (let (v498 i32) (zext v497)) - (let (v499 i1) (neq v498 0)) - (condbr v499 (block 2 v519) (block 42))) - - (block 37 - (let (v442 i32) (const.i32 -4)) - (let (v443 i32) (band v429 v442)) - (let (v444 i32) (const.i32 0)) - (let (v445 i32) (const.i32 0)) - (br (block 39 v444 v494 v445 v443 v433))) - - (block 38 - (let (v440 i32) (const.i32 0)) - (let (v441 i32) (const.i32 0)) - (br (block 36 v433 v494 v441 v440))) - - (block 39 - (param v446 i32) - (param v447 i32) - (param v448 i32) - (param v488 i32) - (param v496 i32) - (let (v449 i32) (add.wrapping v447 v448)) - (let (v450 u32) (bitcast v449)) - (let (v451 (ptr i8)) (inttoptr v450)) - (let (v452 i8) (load v451)) - (let (v453 i32) (sext v452)) - (let (v454 i32) (const.i32 -65)) - (let (v455 i1) (gt v453 v454)) - (let (v456 i32) (zext v455)) - (let (v457 i32) (add.wrapping v446 v456)) - (let (v458 i32) (const.i32 1)) - (let (v459 i32) (add.wrapping v449 v458)) - (let (v460 u32) (bitcast v459)) - (let (v461 (ptr i8)) (inttoptr v460)) - (let (v462 i8) (load v461)) - (let (v463 i32) (sext v462)) - (let (v464 i32) (const.i32 -65)) - (let (v465 i1) (gt v463 v464)) - (let (v466 i32) (zext v465)) - (let (v467 i32) (add.wrapping v457 v466)) - (let (v468 i32) (const.i32 2)) - (let (v469 i32) (add.wrapping v449 v468)) - (let (v470 u32) (bitcast v469)) - (let (v471 (ptr i8)) (inttoptr v470)) - (let (v472 i8) (load v471)) - (let (v473 i32) (sext v472)) - (let (v474 i32) (const.i32 -65)) - (let (v475 i1) (gt v473 v474)) - (let (v476 i32) (zext v475)) - (let (v477 i32) (add.wrapping v467 v476)) - (let (v478 i32) (const.i32 3)) - (let (v479 i32) (add.wrapping v449 v478)) - (let (v480 u32) (bitcast v479)) - (let (v481 (ptr i8)) (inttoptr v480)) - (let (v482 i8) (load v481)) - (let (v483 i32) (sext v482)) - (let (v484 i32) (const.i32 -65)) - (let (v485 i1) (gt v483 v484)) - (let (v486 i32) (zext v485)) - (let (v487 i32) (add.wrapping v477 v486)) - (let (v489 i32) (const.i32 4)) - (let (v490 i32) (add.wrapping v448 v489)) - (let (v491 i1) (neq v488 v490)) - (let (v492 i32) (zext v491)) - (let (v493 i1) (neq v492 0)) - (condbr v493 (block 39 v487 v447 v490 v488 v496) (block 41))) - - (block 40 - (br (block 36 v496 v447 v490 v487))) - - (block 41 - (br (block 40))) - - (block 42 - (let (v502 i32) (add.wrapping v500 v501)) - (br (block 43 v519 v502 v495))) - - (block 43 (param v503 i32) (param v504 i32) (param v515 i32) - (let (v505 u32) (bitcast v504)) - (let (v506 (ptr i8)) (inttoptr v505)) - (let (v507 i8) (load v506)) - (let (v508 i32) (sext v507)) - (let (v509 i32) (const.i32 -65)) - (let (v510 i1) (gt v508 v509)) - (let (v511 i32) (zext v510)) - (let (v512 i32) (add.wrapping v503 v511)) - (let (v513 i32) (const.i32 1)) - (let (v514 i32) (add.wrapping v504 v513)) - (let (v516 i32) (const.i32 -1)) - (let (v517 i32) (add.wrapping v515 v516)) - (let (v518 i1) (neq v517 0)) - (condbr v518 (block 43 v512 v514 v517) (block 45))) - - (block 44 - (br (block 2 v512))) - - (block 45 - (br (block 44))) - ) - - (func (export #core::fmt::Formatter::pad_integral::write_prefix) - (param i32) (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 - (param v0 i32) - (param v1 i32) - (param v2 i32) - (param v3 i32) - (param v4 i32) - (let (v6 i32) (const.i32 1114112)) - (let (v7 i1) (eq v2 v6)) - (let (v8 i32) (zext v7)) - (let (v9 i1) (neq v8 0)) - (condbr v9 (block 2 v3 v0 v4 v1) (block 3))) - - (block 1 (param v5 i32) - (ret v5)) - - (block 2 - (param v19 i32) - (param v22 i32) - (param v23 i32) - (param v24 i32) - (let (v20 i1) (neq v19 0)) - (condbr v20 (block 5) (block 6))) - - (block 3 - (let (v10 u32) (bitcast v1)) - (let (v11 u32) (add.checked v10 16)) - (let (v12 u32) (mod.unchecked v11 4)) - (assertz 250 v12) - (let (v13 (ptr i32)) (inttoptr v11)) - (let (v14 i32) (load v13)) - (let (v15 i1) (eq v14 0)) - (let (v16 i32) (zext v15)) - (let (v17 i1) (neq v16 0)) - (condbr v17 (block 2 v3 v0 v4 v1) (block 4))) - - (block 4 - (let (v18 i32) (const.i32 1)) - (ret v18)) - - (block 5 - (let (v25 u32) (bitcast v24)) - (let (v26 u32) (add.checked v25 12)) - (let (v27 u32) (mod.unchecked v26 4)) - (assertz 250 v27) - (let (v28 (ptr i32)) (inttoptr v26)) - (let (v29 i32) (load v28)) - (br (block 1 v29))) - - (block 6 - (let (v21 i32) (const.i32 0)) - (ret v21)) - ) - - (func (export #core::fmt::Formatter::debug_struct) - (param i32) (param i32) (param i32) (param i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v4 u32) (bitcast v1)) - (let (v5 u32) (add.checked v4 20)) - (let (v6 u32) (mod.unchecked v5 4)) - (assertz 250 v6) - (let (v7 (ptr i32)) (inttoptr v5)) - (let (v8 i32) (load v7)) - (let (v9 u32) (bitcast v1)) - (let (v10 u32) (add.checked v9 24)) - (let (v11 u32) (mod.unchecked v10 4)) - (assertz 250 v11) - (let (v12 (ptr i32)) (inttoptr v10)) - (let (v13 i32) (load v12)) - (let (v14 u32) (bitcast v13)) - (let (v15 u32) (add.checked v14 12)) - (let (v16 u32) (mod.unchecked v15 4)) - (assertz 250 v16) - (let (v17 (ptr i32)) (inttoptr v15)) - (let (v18 i32) (load v17)) - (let (v19 i32) (const.i32 0)) - (let (v20 u32) (bitcast v19)) - (let (v21 u8) (trunc v20)) - (let (v22 u32) (bitcast v0)) - (let (v23 u32) (add.checked v22 5)) - (let (v24 (ptr u8)) (inttoptr v23)) - (store v24 v21) - (let (v25 u32) (bitcast v18)) - (let (v26 u8) (trunc v25)) - (let (v27 u32) (bitcast v0)) - (let (v28 u32) (add.checked v27 4)) - (let (v29 (ptr u8)) (inttoptr v28)) - (store v29 v26) - (let (v30 u32) (bitcast v0)) - (let (v31 u32) (mod.unchecked v30 4)) - (assertz 250 v31) - (let (v32 (ptr i32)) (inttoptr v30)) - (store v32 v1) - (br (block 1))) - - (block 1 - (ret)) - ) - - (func (export #core::fmt::num::imp::::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 u32) (bitcast v0)) - (let (v4 u32) (mod.unchecked v3 8)) - (assertz 250 v4) - (let (v5 (ptr i64)) (inttoptr v3)) - (let (v6 i64) (load v5)) - (let (v7 i32) (const.i32 1)) - (let (v8 i32) (call #core::fmt::num::imp::fmt_u64 v6 v7 v1)) - (br (block 1 v8))) - - (block 1 (param v2 i32) - (ret v2)) - ) - - (func (export #core::fmt::num::imp::fmt_u64) - (param i64) (param i32) (param i32) (result i32) - (block 0 (param v0 i64) (param v1 i32) (param v2 i32) - (let (v4 i32) (const.i32 0)) - (let (v5 i64) (const.i64 0)) - (let (v6 i32) (const.i32 0)) - (let (v7 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v8 i32) (const.i32 48)) - (let (v9 i32) (sub.wrapping v7 v8)) - (let (v10 (ptr i32)) (global.symbol #__stack_pointer)) - (store v10 v9) - (let (v11 i32) (const.i32 39)) - (let (v12 i64) (const.i64 10000)) - (let (v13 u64) (bitcast v0)) - (let (v14 u64) (bitcast v12)) - (let (v15 i1) (gte v13 v14)) - (let (v16 i32) (zext v15)) - (let (v17 i1) (neq v16 0)) - (condbr v17 (block 3) (block 4))) - - (block 1 (param v3 i32) - (ret v3)) - - (block 2 - (param v84 i64) - (param v92 i32) - (param v95 i32) - (param v165 i32) - (param v169 i32) - (let (v85 i64) (const.i64 99)) - (let (v86 u64) (bitcast v84)) - (let (v87 u64) (bitcast v85)) - (let (v88 i1) (gt v86 v87)) - (let (v89 i32) (sext v88)) - (let (v90 i1) (neq v89 0)) - (condbr v90 (block 9) (block 10))) - - (block 3 - (let (v18 i32) (const.i32 39)) - (br (block 5 v9 v18 v0 v2 v1))) - - (block 4 - (br (block 2 v0 v9 v11 v2 v1))) - - (block 5 - (param v19 i32) - (param v22 i32) - (param v26 i64) - (param v166 i32) - (param v170 i32) - (let (v20 i32) (const.i32 9)) - (let (v21 i32) (add.wrapping v19 v20)) - (let (v23 i32) (add.wrapping v21 v22)) - (let (v24 i32) (const.i32 -4)) - (let (v25 i32) (add.wrapping v23 v24)) - (let (v27 i64) (const.i64 10000)) - (let (v28 u64) (bitcast v26)) - (let (v29 u64) (bitcast v27)) - (let (v30 u64) (div.unchecked v28 v29)) - (let (v31 i64) (bitcast v30)) - (let (v32 i64) (const.i64 10000)) - (let (v33 i64) (mul.wrapping v31 v32)) - (let (v34 i64) (sub.wrapping v26 v33)) - (let (v35 i32) (trunc v34)) - (let (v36 i32) (const.i32 65535)) - (let (v37 i32) (band v35 v36)) - (let (v38 i32) (const.i32 100)) - (let (v39 u32) (bitcast v37)) - (let (v40 u32) (bitcast v38)) - (let (v41 u32) (div.unchecked v39 v40)) - (let (v42 i32) (bitcast v41)) - (let (v43 i32) (const.i32 1)) - (let (v44 u32) (bitcast v43)) - (let (v45 i32) (shl.wrapping v42 v44)) - (let (v46 i32) (const.i32 1049010)) - (let (v47 i32) (add.wrapping v45 v46)) - (let (v48 u32) (bitcast v47)) - (let (v49 (ptr u16)) (inttoptr v48)) - (let (v50 u16) (load v49)) - (let (v51 i32) (zext v50)) - (let (v52 u32) (bitcast v51)) - (let (v53 u16) (trunc v52)) - (let (v54 u32) (bitcast v25)) - (let (v55 (ptr u16)) (inttoptr v54)) - (store v55 v53) - (let (v56 i32) (const.i32 -2)) - (let (v57 i32) (add.wrapping v23 v56)) - (let (v58 i32) (const.i32 100)) - (let (v59 i32) (mul.wrapping v42 v58)) - (let (v60 i32) (sub.wrapping v35 v59)) - (let (v61 i32) (const.i32 65535)) - (let (v62 i32) (band v60 v61)) - (let (v63 i32) (const.i32 1)) - (let (v64 u32) (bitcast v63)) - (let (v65 i32) (shl.wrapping v62 v64)) - (let (v66 i32) (const.i32 1049010)) - (let (v67 i32) (add.wrapping v65 v66)) - (let (v68 u32) (bitcast v67)) - (let (v69 (ptr u16)) (inttoptr v68)) - (let (v70 u16) (load v69)) - (let (v71 i32) (zext v70)) - (let (v72 u32) (bitcast v71)) - (let (v73 u16) (trunc v72)) - (let (v74 u32) (bitcast v57)) - (let (v75 (ptr u16)) (inttoptr v74)) - (store v75 v73) - (let (v76 i32) (const.i32 -4)) - (let (v77 i32) (add.wrapping v22 v76)) - (let (v78 i64) (const.i64 99999999)) - (let (v79 u64) (bitcast v26)) - (let (v80 u64) (bitcast v78)) - (let (v81 i1) (gt v79 v80)) - (let (v82 i32) (sext v81)) - (let (v83 i1) (neq v82 0)) - (condbr v83 (block 5 v19 v77 v31 v166 v170) (block 7))) - - (block 6 - (br (block 2 v31 v19 v77 v166 v170))) - - (block 7 - (br (block 6))) - - (block 8 - (param v125 i32) - (param v132 i32) - (param v135 i32) - (param v164 i32) - (param v168 i32) - (let (v126 i32) (const.i32 10)) - (let (v127 u32) (bitcast v125)) - (let (v128 u32) (bitcast v126)) - (let (v129 i1) (lt v127 v128)) - (let (v130 i32) (sext v129)) - (let (v131 i1) (neq v130 0)) - (condbr v131 (block 12) (block 13))) - - (block 9 - (let (v93 i32) (const.i32 9)) - (let (v94 i32) (add.wrapping v92 v93)) - (let (v96 i32) (const.i32 -2)) - (let (v97 i32) (add.wrapping v95 v96)) - (let (v98 i32) (add.wrapping v94 v97)) - (let (v99 i32) (trunc v84)) - (let (v100 i32) (const.i32 65535)) - (let (v101 i32) (band v99 v100)) - (let (v102 i32) (const.i32 100)) - (let (v103 u32) (bitcast v101)) - (let (v104 u32) (bitcast v102)) - (let (v105 u32) (div.unchecked v103 v104)) - (let (v106 i32) (bitcast v105)) - (let (v107 i32) (const.i32 100)) - (let (v108 i32) (mul.wrapping v106 v107)) - (let (v109 i32) (sub.wrapping v99 v108)) - (let (v110 i32) (const.i32 65535)) - (let (v111 i32) (band v109 v110)) - (let (v112 i32) (const.i32 1)) - (let (v113 u32) (bitcast v112)) - (let (v114 i32) (shl.wrapping v111 v113)) - (let (v115 i32) (const.i32 1049010)) - (let (v116 i32) (add.wrapping v114 v115)) - (let (v117 u32) (bitcast v116)) - (let (v118 (ptr u16)) (inttoptr v117)) - (let (v119 u16) (load v118)) - (let (v120 i32) (zext v119)) - (let (v121 u32) (bitcast v120)) - (let (v122 u16) (trunc v121)) - (let (v123 u32) (bitcast v98)) - (let (v124 (ptr u16)) (inttoptr v123)) - (store v124 v122) - (br (block 8 v106 v92 v97 v165 v169))) - - (block 10 - (let (v91 i32) (trunc v84)) - (br (block 8 v91 v92 v95 v165 v169))) - - (block 11 - (param v163 i32) - (param v167 i32) - (param v173 i32) - (param v176 i32) - (let (v171 i32) (const.i32 1)) - (let (v172 i32) (const.i32 0)) - (let (v174 i32) (const.i32 9)) - (let (v175 i32) (add.wrapping v173 v174)) - (let (v177 i32) (add.wrapping v175 v176)) - (let (v178 i32) (const.i32 39)) - (let (v179 i32) (sub.wrapping v178 v176)) - (let (v180 i32) (call #core::fmt::Formatter::pad_integral v163 v167 v171 v172 v177 v179)) - (let (v181 i32) (const.i32 48)) - (let (v182 i32) (add.wrapping v173 v181)) - (let (v183 (ptr i32)) (global.symbol #__stack_pointer)) - (store v183 v182) - (br (block 1 v180))) - - (block 12 - (let (v152 i32) (const.i32 9)) - (let (v153 i32) (add.wrapping v132 v152)) - (let (v154 i32) (const.i32 -1)) - (let (v155 i32) (add.wrapping v135 v154)) - (let (v156 i32) (add.wrapping v153 v155)) - (let (v157 i32) (const.i32 48)) - (let (v158 i32) (bor v125 v157)) - (let (v159 u32) (bitcast v158)) - (let (v160 u8) (trunc v159)) - (let (v161 u32) (bitcast v156)) - (let (v162 (ptr u8)) (inttoptr v161)) - (store v162 v160) - (br (block 11 v164 v168 v132 v155))) - - (block 13 - (let (v133 i32) (const.i32 9)) - (let (v134 i32) (add.wrapping v132 v133)) - (let (v136 i32) (const.i32 -2)) - (let (v137 i32) (add.wrapping v135 v136)) - (let (v138 i32) (add.wrapping v134 v137)) - (let (v139 i32) (const.i32 1)) - (let (v140 u32) (bitcast v139)) - (let (v141 i32) (shl.wrapping v125 v140)) - (let (v142 i32) (const.i32 1049010)) - (let (v143 i32) (add.wrapping v141 v142)) - (let (v144 u32) (bitcast v143)) - (let (v145 (ptr u16)) (inttoptr v144)) - (let (v146 u16) (load v145)) - (let (v147 i32) (zext v146)) - (let (v148 u32) (bitcast v147)) - (let (v149 u16) (trunc v148)) - (let (v150 u32) (bitcast v138)) - (let (v151 (ptr u16)) (inttoptr v150)) - (store v151 v149) - (br (block 11 v164 v168 v132 v137))) - ) - - (func (export #core::fmt::num::::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i64) (const.i64 0)) - (let (v5 i32) (const.i32 0)) - (let (v6 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v7 i32) (const.i32 128)) - (let (v8 i32) (sub.wrapping v6 v7)) - (let (v9 (ptr i32)) (global.symbol #__stack_pointer)) - (store v9 v8) - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (mod.unchecked v10 8)) - (assertz 250 v11) - (let (v12 (ptr i64)) (inttoptr v10)) - (let (v13 i64) (load v12)) - (let (v14 i32) (const.i32 0)) - (br (block 2 v8 v14 v13 v1))) - - (block 1 (param v2 i32) - (ret v2)) - - (block 2 - (param v15 i32) - (param v16 i32) - (param v20 i64) - (param v64 i32) - (let (v17 i32) (add.wrapping v15 v16)) - (let (v18 i32) (const.i32 127)) - (let (v19 i32) (add.wrapping v17 v18)) - (let (v21 i32) (trunc v20)) - (let (v22 i32) (const.i32 15)) - (let (v23 i32) (band v21 v22)) - (let (v24 i32) (const.i32 48)) - (let (v25 i32) (bor v23 v24)) - (let (v26 i32) (const.i32 87)) - (let (v27 i32) (add.wrapping v23 v26)) - (let (v28 i32) (const.i32 10)) - (let (v29 u32) (bitcast v23)) - (let (v30 u32) (bitcast v28)) - (let (v31 i1) (lt v29 v30)) - (let (v32 i32) (sext v31)) - (let (v33 i1) (neq v32 0)) - (let (v34 i32) (select v33 v25 v27)) - (let (v35 u32) (bitcast v34)) - (let (v36 u8) (trunc v35)) - (let (v37 u32) (bitcast v19)) - (let (v38 (ptr u8)) (inttoptr v37)) - (store v38 v36) - (let (v39 i32) (const.i32 -1)) - (let (v40 i32) (add.wrapping v16 v39)) - (let (v41 i64) (const.i64 16)) - (let (v42 u64) (bitcast v20)) - (let (v43 u64) (bitcast v41)) - (let (v44 i1) (lt v42 v43)) - (let (v45 i32) (sext v44)) - (let (v46 i64) (const.i64 4)) - (let (v47 u64) (bitcast v20)) - (let (v48 u32) (cast v46)) - (let (v49 u64) (shr.wrapping v47 v48)) - (let (v50 i64) (bitcast v49)) - (let (v51 i1) (eq v45 0)) - (let (v52 i32) (zext v51)) - (let (v53 i1) (neq v52 0)) - (condbr v53 (block 2 v15 v40 v50 v64) (block 4))) - - (block 3 - (let (v54 i32) (const.i32 128)) - (let (v55 i32) (add.wrapping v40 v54)) - (let (v56 i32) (const.i32 129)) - (let (v57 u32) (bitcast v55)) - (let (v58 u32) (bitcast v56)) - (let (v59 i1) (lt v57 v58)) - (let (v60 i32) (sext v59)) - (let (v61 i1) (neq v60 0)) - (condbr v61 (block 5) (block 6))) - - (block 4 - (br (block 3))) - - (block 5 - (let (v65 i32) (const.i32 1)) - (let (v66 i32) (const.i32 1049008)) - (let (v67 i32) (const.i32 2)) - (let (v68 i32) (add.wrapping v15 v40)) - (let (v69 i32) (const.i32 128)) - (let (v70 i32) (add.wrapping v68 v69)) - (let (v71 i32) (const.i32 0)) - (let (v72 i32) (sub.wrapping v71 v40)) - (let (v73 i32) (call #core::fmt::Formatter::pad_integral v64 v65 v66 v67 v70 v72)) - (let (v74 i32) (const.i32 128)) - (let (v75 i32) (add.wrapping v15 v74)) - (let (v76 (ptr i32)) (global.symbol #__stack_pointer)) - (store v76 v75) - (br (block 1 v73))) - - (block 6 - (let (v62 i32) (const.i32 128)) - (let (v63 i32) (const.i32 1048992)) - (call #core::slice::index::slice_start_index_len_fail v55 v62 v63) - (unreachable)) - ) - - (func (export #core::fmt::num::::fmt) - (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (const.i32 0)) - (let (v4 i64) (const.i64 0)) - (let (v5 i32) (const.i32 0)) - (let (v6 i32) (global.load i32 (global.symbol #__stack_pointer))) - (let (v7 i32) (const.i32 128)) - (let (v8 i32) (sub.wrapping v6 v7)) - (let (v9 (ptr i32)) (global.symbol #__stack_pointer)) - (store v9 v8) - (let (v10 u32) (bitcast v0)) - (let (v11 u32) (mod.unchecked v10 8)) - (assertz 250 v11) - (let (v12 (ptr i64)) (inttoptr v10)) - (let (v13 i64) (load v12)) - (let (v14 i32) (const.i32 0)) - (br (block 2 v8 v14 v13 v1))) - - (block 1 (param v2 i32) - (ret v2)) - - (block 2 - (param v15 i32) - (param v16 i32) - (param v20 i64) - (param v64 i32) - (let (v17 i32) (add.wrapping v15 v16)) - (let (v18 i32) (const.i32 127)) - (let (v19 i32) (add.wrapping v17 v18)) - (let (v21 i32) (trunc v20)) - (let (v22 i32) (const.i32 15)) - (let (v23 i32) (band v21 v22)) - (let (v24 i32) (const.i32 48)) - (let (v25 i32) (bor v23 v24)) - (let (v26 i32) (const.i32 55)) - (let (v27 i32) (add.wrapping v23 v26)) - (let (v28 i32) (const.i32 10)) - (let (v29 u32) (bitcast v23)) - (let (v30 u32) (bitcast v28)) - (let (v31 i1) (lt v29 v30)) - (let (v32 i32) (sext v31)) - (let (v33 i1) (neq v32 0)) - (let (v34 i32) (select v33 v25 v27)) - (let (v35 u32) (bitcast v34)) - (let (v36 u8) (trunc v35)) - (let (v37 u32) (bitcast v19)) - (let (v38 (ptr u8)) (inttoptr v37)) - (store v38 v36) - (let (v39 i32) (const.i32 -1)) - (let (v40 i32) (add.wrapping v16 v39)) - (let (v41 i64) (const.i64 16)) - (let (v42 u64) (bitcast v20)) - (let (v43 u64) (bitcast v41)) - (let (v44 i1) (lt v42 v43)) - (let (v45 i32) (sext v44)) - (let (v46 i64) (const.i64 4)) - (let (v47 u64) (bitcast v20)) - (let (v48 u32) (cast v46)) - (let (v49 u64) (shr.wrapping v47 v48)) - (let (v50 i64) (bitcast v49)) - (let (v51 i1) (eq v45 0)) - (let (v52 i32) (zext v51)) - (let (v53 i1) (neq v52 0)) - (condbr v53 (block 2 v15 v40 v50 v64) (block 4))) - - (block 3 - (let (v54 i32) (const.i32 128)) - (let (v55 i32) (add.wrapping v40 v54)) - (let (v56 i32) (const.i32 129)) - (let (v57 u32) (bitcast v55)) - (let (v58 u32) (bitcast v56)) - (let (v59 i1) (lt v57 v58)) - (let (v60 i32) (sext v59)) - (let (v61 i1) (neq v60 0)) - (condbr v61 (block 5) (block 6))) - - (block 4 - (br (block 3))) - - (block 5 - (let (v65 i32) (const.i32 1)) - (let (v66 i32) (const.i32 1049008)) - (let (v67 i32) (const.i32 2)) - (let (v68 i32) (add.wrapping v15 v40)) - (let (v69 i32) (const.i32 128)) - (let (v70 i32) (add.wrapping v68 v69)) - (let (v71 i32) (const.i32 0)) - (let (v72 i32) (sub.wrapping v71 v40)) - (let (v73 i32) (call #core::fmt::Formatter::pad_integral v64 v65 v66 v67 v70 v72)) - (let (v74 i32) (const.i32 128)) - (let (v75 i32) (add.wrapping v15 v74)) - (let (v76 (ptr i32)) (global.symbol #__stack_pointer)) - (store v76 v75) - (br (block 1 v73))) - - (block 6 - (let (v62 i32) (const.i32 128)) - (let (v63 i32) (const.i32 1048992)) - (call #core::slice::index::slice_start_index_len_fail v55 v62 v63) - (unreachable)) - ) - - (func (export #cabi_realloc) - (param i32) (param i32) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) (param v2 i32) (param v3 i32) - (let (v5 i32) (call #wit_bindgen_rt::cabi_realloc v0 v1 v2 v3)) - (br (block 1 v5))) - - (block 1 (param v4 i32) - (ret v4)) - ) - - ;; Imports - (func (import #miden:base/account@1.0.0 #get-id) (result i64)) - (func (import #miden:base/core-types@1.0.0 #account-id-from-felt) - (param i64) (result i64)) - (func (import #miden:basic-wallet/basic-wallet@1.0.0 #receive-asset) - (param i64) (param i64) (param i64) (param i64)) - (func (import #wit-component:shim #indirect-miden:base/note@1.0.0-get-assets) - (param i32)) - (func (import #wit-component:shim #indirect-miden:base/note@1.0.0-get-inputs) - (param i32)) - ) - - (module #wit-component:fixups - - ) - -) diff --git a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet_p2id_note.wat b/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet_p2id_note.wat deleted file mode 100644 index 2a2c61e42..000000000 --- a/tests/integration/expected/wit_sdk_basic_wallet/basic_wallet_p2id_note.wat +++ /dev/null @@ -1,4035 +0,0 @@ -(component - (type (;0;) - (instance - (type (;0;) (record (field "inner" u64))) - (export (;1;) "felt" (type (eq 0))) - (type (;2;) (record (field "inner" 1))) - (export (;3;) "account-id" (type (eq 2))) - (type (;4;) (tuple 1 1 1 1)) - (export (;5;) "word" (type (eq 4))) - (type (;6;) (record (field "inner" 5))) - (export (;7;) "core-asset" (type (eq 6))) - (type (;8;) (func (param "felt" 1) (result 3))) - (export (;0;) "account-id-from-felt" (func (type 8))) - ) - ) - (import "miden:base/core-types@1.0.0" (instance (;0;) (type 0))) - (alias export 0 "account-id" (type (;1;))) - (type (;2;) - (instance - (alias outer 1 1 (type (;0;))) - (export (;1;) "account-id" (type (eq 0))) - (type (;2;) (func (result 1))) - (export (;0;) "get-id" (func (type 2))) - ) - ) - (import "miden:base/account@1.0.0" (instance (;1;) (type 2))) - (alias export 0 "felt" (type (;3;))) - (alias export 0 "core-asset" (type (;4;))) - (type (;5;) - (instance - (alias outer 1 3 (type (;0;))) - (export (;1;) "felt" (type (eq 0))) - (alias outer 1 4 (type (;2;))) - (export (;3;) "core-asset" (type (eq 2))) - (type (;4;) (list 1)) - (type (;5;) (func (result 4))) - (export (;0;) "get-inputs" (func (type 5))) - (type (;6;) (list 3)) - (type (;7;) (func (result 6))) - (export (;1;) "get-assets" (func (type 7))) - ) - ) - (import "miden:base/note@1.0.0" (instance (;2;) (type 5))) - (alias export 0 "core-asset" (type (;6;))) - (type (;7;) - (instance - (alias outer 1 6 (type (;0;))) - (export (;1;) "core-asset" (type (eq 0))) - (type (;2;) (func (param "core-asset" 1))) - (export (;0;) "receive-asset" (func (type 2))) - ) - ) - (import "miden:basic-wallet/basic-wallet@1.0.0" (instance (;3;) (type 7))) - (core module (;0;) - (type (;0;) (func (param i32 i32 i32) (result i32))) - (type (;1;) (func (param i32 i32) (result i32))) - (type (;2;) (func (param i32))) - (type (;3;) (func (param i64) (result i64))) - (type (;4;) (func (result i64))) - (type (;5;) (func (param i64 i64 i64 i64))) - (type (;6;) (func)) - (type (;7;) (func (param i32 i32 i32))) - (type (;8;) (func (param i32 i32 i32 i32) (result i32))) - (type (;9;) (func (param i32 i32 i32 i32))) - (type (;10;) (func (param i32 i32))) - (type (;11;) (func (param i32 i32 i32 i32 i32) (result i32))) - (type (;12;) (func (param i32 i32 i32 i32 i32 i32 i32))) - (type (;13;) (func (param i32) (result i32))) - (type (;14;) (func (param i32 i32 i32 i32 i32 i32) (result i32))) - (type (;15;) (func (param i64 i32 i32) (result i32))) - (import "miden:base/note@1.0.0" "get-inputs" (func $basic_wallet_p2id_note::bindings::miden::base::note::get_inputs::wit_import (;0;) (type 2))) - (import "miden:base/core-types@1.0.0" "account-id-from-felt" (func $basic_wallet_p2id_note::bindings::miden::base::core_types::account_id_from_felt::wit_import (;1;) (type 3))) - (import "miden:base/account@1.0.0" "get-id" (func $basic_wallet_p2id_note::bindings::miden::base::account::get_id::wit_import (;2;) (type 4))) - (import "miden:base/note@1.0.0" "get-assets" (func $basic_wallet_p2id_note::bindings::miden::base::note::get_assets::wit_import (;3;) (type 2))) - (import "miden:basic-wallet/basic-wallet@1.0.0" "receive-asset" (func $basic_wallet_p2id_note::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import (;4;) (type 5))) - (func $__wasm_call_ctors (;5;) (type 6)) - (func $<&T as core::fmt::Debug>::fmt (;6;) (type 1) (param i32 i32) (result i32) - (local i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 2 - global.set $__stack_pointer - local.get 0 - i32.load - local.set 0 - local.get 2 - i32.const 8 - i32.add - local.get 1 - i32.const 1048621 - i32.const 9 - call $core::fmt::Formatter::debug_struct - local.get 2 - i32.const 8 - i32.add - i32.const 1048616 - i32.const 5 - local.get 0 - i32.const 1048632 - call $core::fmt::builders::DebugStruct::field - call $core::fmt::builders::DebugStruct::finish - local.set 0 - local.get 2 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 0 - ) - (func $core::fmt::num::::fmt (;7;) (type 1) (param i32 i32) (result i32) - (local i32) - block ;; label = @1 - local.get 1 - i32.load offset=28 - local.tee 2 - i32.const 16 - i32.and - br_if 0 (;@1;) - block ;; label = @2 - local.get 2 - i32.const 32 - i32.and - br_if 0 (;@2;) - local.get 0 - local.get 1 - call $core::fmt::num::imp::::fmt - return - end - local.get 0 - local.get 1 - call $core::fmt::num::::fmt - return - end - local.get 0 - local.get 1 - call $core::fmt::num::::fmt - ) - (func $core::panicking::assert_failed (;8;) (type 7) (param i32 i32 i32) - (local i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 3 - global.set $__stack_pointer - local.get 3 - local.get 1 - i32.store offset=12 - local.get 3 - local.get 0 - i32.store offset=8 - i32.const 0 - local.get 3 - i32.const 8 - i32.add - i32.const 1048576 - local.get 3 - i32.const 12 - i32.add - i32.const 1048576 - local.get 2 - i32.const 1048696 - call $core::panicking::assert_failed_inner - unreachable - ) - (func $rust_begin_unwind (;9;) (type 2) (param i32) - loop ;; label = @1 - br 0 (;@1;) - end - ) - (func $::fmt (;10;) (type 1) (param i32 i32) (result i32) - (local i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 2 - global.set $__stack_pointer - local.get 2 - i32.const 8 - i32.add - local.get 1 - i32.const 1048596 - i32.const 4 - call $core::fmt::Formatter::debug_struct - local.get 2 - i32.const 8 - i32.add - i32.const 1048616 - i32.const 5 - local.get 0 - i32.const 1048600 - call $core::fmt::builders::DebugStruct::field - call $core::fmt::builders::DebugStruct::finish - local.set 1 - local.get 2 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 1 - ) - (func $basic_wallet_p2id_note::bindings::__link_custom_section_describing_imports (;11;) (type 6)) - (func $__rust_alloc (;12;) (type 1) (param i32 i32) (result i32) - i32.const 1049280 - local.get 1 - local.get 0 - call $::alloc - ) - (func $__rust_realloc (;13;) (type 8) (param i32 i32 i32 i32) (result i32) - (local i32) - block ;; label = @1 - i32.const 1049280 - local.get 2 - local.get 3 - call $::alloc - local.tee 4 - i32.eqz - br_if 0 (;@1;) - local.get 4 - local.get 0 - local.get 1 - local.get 3 - local.get 1 - local.get 3 - i32.lt_u - select - memory.copy - i32.const 1049280 - local.get 0 - local.get 2 - local.get 1 - call $::dealloc - end - local.get 4 - ) - (func $miden:base/note-script@1.0.0#note-script (;14;) (type 6) - (local i32 i32 i32 i64 i64 i32 i32 i32 i32) - global.get $__stack_pointer - i32.const 48 - i32.sub - local.tee 0 - global.set $__stack_pointer - call $wit_bindgen_rt::run_ctors_once - local.get 0 - i64.const 0 - i64.store offset=24 - local.get 0 - i32.const 24 - i32.add - call $basic_wallet_p2id_note::bindings::miden::base::note::get_inputs::wit_import - block ;; label = @1 - block ;; label = @2 - local.get 0 - i32.load offset=28 - local.tee 1 - i32.eqz - br_if 0 (;@2;) - local.get 0 - local.get 0 - i32.load offset=24 - local.tee 2 - i64.load - call $basic_wallet_p2id_note::bindings::miden::base::core_types::account_id_from_felt::wit_import - local.tee 3 - i64.store offset=8 - local.get 0 - call $basic_wallet_p2id_note::bindings::miden::base::account::get_id::wit_import - local.tee 4 - i64.store offset=16 - local.get 4 - local.get 3 - i64.ne - br_if 1 (;@1;) - local.get 0 - i64.const 0 - i64.store offset=24 - local.get 0 - i32.const 24 - i32.add - call $basic_wallet_p2id_note::bindings::miden::base::note::get_assets::wit_import - block ;; label = @3 - local.get 0 - i32.load offset=28 - local.tee 5 - i32.eqz - br_if 0 (;@3;) - local.get 0 - i32.load offset=24 - local.tee 6 - local.get 5 - i32.const 5 - i32.shl - i32.add - local.set 7 - local.get 6 - local.set 8 - loop ;; label = @4 - local.get 8 - i64.load - local.get 8 - i64.load offset=8 - local.get 8 - i64.load offset=16 - local.get 8 - i64.load offset=24 - call $basic_wallet_p2id_note::bindings::miden::basic_wallet::basic_wallet::receive_asset::wit_import - local.get 8 - i32.const 32 - i32.add - local.tee 8 - local.get 7 - i32.ne - br_if 0 (;@4;) - end - i32.const 1049280 - local.get 6 - i32.const 8 - local.get 5 - i32.const 5 - i32.shl - call $::dealloc - end - i32.const 1049280 - local.get 2 - i32.const 8 - local.get 1 - i32.const 3 - i32.shl - call $::dealloc - local.get 0 - i32.const 48 - i32.add - global.set $__stack_pointer - return - end - i32.const 0 - i32.const 0 - i32.const 1048680 - call $core::panicking::panic_bounds_check - unreachable - end - local.get 0 - i32.const 0 - i32.store offset=24 - local.get 0 - i32.const 16 - i32.add - local.get 0 - i32.const 8 - i32.add - local.get 0 - i32.const 24 - i32.add - call $core::panicking::assert_failed - unreachable - ) - (func $wit_bindgen_rt::cabi_realloc (;15;) (type 8) (param i32 i32 i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 1 - br_if 0 (;@3;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - i32.const 0 - i32.load8_u offset=1049284 - drop - local.get 3 - local.get 2 - call $__rust_alloc - local.set 2 - br 1 (;@2;) - end - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $__rust_realloc - local.set 2 - end - local.get 2 - br_if 0 (;@1;) - unreachable - end - local.get 2 - ) - (func $wit_bindgen_rt::run_ctors_once (;16;) (type 6) - block ;; label = @1 - i32.const 0 - i32.load8_u offset=1049285 - br_if 0 (;@1;) - call $__wasm_call_ctors - i32.const 0 - i32.const 1 - i32.store8 offset=1049285 - end - ) - (func $wee_alloc::alloc_first_fit (;17;) (type 0) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 2 - i32.load - local.tee 3 - br_if 0 (;@1;) - i32.const 0 - return - end - local.get 1 - i32.const -1 - i32.add - local.set 4 - i32.const 0 - local.get 1 - i32.sub - local.set 5 - local.get 0 - i32.const 2 - i32.shl - local.set 6 - loop ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - local.get 3 - i32.const 8 - i32.add - local.set 0 - br 1 (;@2;) - end - loop ;; label = @3 - local.get 3 - local.get 1 - i32.const -2 - i32.and - i32.store offset=8 - block ;; label = @4 - block ;; label = @5 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.tee 0 - br_if 0 (;@5;) - i32.const 0 - local.set 8 - br 1 (;@4;) - end - i32.const 0 - local.get 0 - local.get 0 - i32.load8_u - i32.const 1 - i32.and - select - local.set 8 - end - block ;; label = @4 - local.get 3 - i32.load - local.tee 1 - i32.const -4 - i32.and - local.tee 9 - i32.eqz - br_if 0 (;@4;) - local.get 1 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 9 - local.get 9 - i32.load offset=4 - i32.const 3 - i32.and - local.get 0 - i32.or - i32.store offset=4 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.set 0 - local.get 3 - i32.load - local.set 1 - end - block ;; label = @4 - local.get 0 - i32.eqz - br_if 0 (;@4;) - local.get 0 - local.get 0 - i32.load - i32.const 3 - i32.and - local.get 1 - i32.const -4 - i32.and - i32.or - i32.store - local.get 3 - i32.load offset=4 - local.set 7 - local.get 3 - i32.load - local.set 1 - end - local.get 3 - local.get 7 - i32.const 3 - i32.and - i32.store offset=4 - local.get 3 - local.get 1 - i32.const 3 - i32.and - i32.store - block ;; label = @4 - local.get 1 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - end - local.get 2 - local.get 8 - i32.store - local.get 8 - local.set 3 - local.get 8 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - end - local.get 8 - i32.const 8 - i32.add - local.set 0 - local.get 8 - local.set 3 - end - block ;; label = @2 - local.get 3 - i32.load - i32.const -4 - i32.and - local.tee 8 - local.get 0 - i32.sub - local.get 6 - i32.lt_u - br_if 0 (;@2;) - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.const 72 - i32.add - local.get 8 - local.get 6 - i32.sub - local.get 5 - i32.and - local.tee 8 - i32.le_u - br_if 0 (;@4;) - local.get 4 - local.get 0 - i32.and - br_if 2 (;@2;) - local.get 2 - local.get 1 - i32.const -4 - i32.and - i32.store - local.get 3 - i32.load - local.set 0 - local.get 3 - local.set 1 - br 1 (;@3;) - end - i32.const 0 - local.set 7 - local.get 8 - i32.const 0 - i32.store - local.get 8 - i32.const -8 - i32.add - local.tee 1 - i64.const 0 - i64.store align=4 - local.get 1 - local.get 3 - i32.load - i32.const -4 - i32.and - i32.store - block ;; label = @4 - local.get 3 - i32.load - local.tee 9 - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@4;) - local.get 9 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load offset=4 - i32.const 3 - i32.and - local.get 1 - i32.or - i32.store offset=4 - local.get 1 - i32.load offset=4 - i32.const 3 - i32.and - local.set 7 - end - local.get 1 - local.get 7 - local.get 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 0 - i32.load - i32.const -2 - i32.and - i32.store - local.get 3 - local.get 3 - i32.load - local.tee 0 - i32.const 3 - i32.and - local.get 1 - i32.or - local.tee 8 - i32.store - block ;; label = @4 - local.get 0 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 1 - i32.load - local.set 0 - br 1 (;@3;) - end - local.get 3 - local.get 8 - i32.const -3 - i32.and - i32.store - local.get 1 - i32.load - i32.const 2 - i32.or - local.set 0 - end - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store - local.get 1 - i32.const 8 - i32.add - return - end - local.get 2 - local.get 1 - i32.store - local.get 1 - local.set 3 - local.get 1 - br_if 0 (;@1;) - end - i32.const 0 - ) - (func $::alloc (;18;) (type 0) (param i32 i32 i32) (result i32) - (local i32 i32 i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 3 - global.set $__stack_pointer - block ;; label = @1 - block ;; label = @2 - local.get 2 - br_if 0 (;@2;) - local.get 1 - local.set 2 - br 1 (;@1;) - end - local.get 3 - local.get 0 - i32.load - i32.store offset=12 - block ;; label = @2 - local.get 2 - i32.const 3 - i32.add - local.tee 4 - i32.const 2 - i32.shr_u - local.tee 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.tee 2 - br_if 0 (;@2;) - block ;; label = @3 - local.get 4 - i32.const -4 - i32.and - local.tee 2 - local.get 1 - i32.const 3 - i32.shl - i32.const 512 - i32.add - local.tee 4 - local.get 2 - local.get 4 - i32.gt_u - select - i32.const 65543 - i32.add - local.tee 4 - i32.const 16 - i32.shr_u - memory.grow - local.tee 2 - i32.const -1 - i32.ne - br_if 0 (;@3;) - i32.const 0 - local.set 2 - br 1 (;@2;) - end - local.get 2 - i32.const 16 - i32.shl - local.tee 2 - i32.const 0 - i32.store offset=4 - local.get 2 - local.get 3 - i32.load offset=12 - i32.store offset=8 - local.get 2 - local.get 2 - local.get 4 - i32.const -65536 - i32.and - i32.add - i32.const 2 - i32.or - i32.store - local.get 3 - local.get 2 - i32.store offset=12 - local.get 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.set 2 - end - local.get 0 - local.get 3 - i32.load offset=12 - i32.store - end - local.get 3 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 2 - ) - (func $::dealloc (;19;) (type 9) (param i32 i32 i32 i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 1 - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.eqz - br_if 0 (;@1;) - local.get 0 - i32.load - local.set 4 - local.get 1 - i32.const 0 - i32.store - local.get 1 - i32.const -8 - i32.add - local.tee 3 - local.get 3 - i32.load - local.tee 5 - i32.const -2 - i32.and - local.tee 6 - i32.store - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - local.get 1 - i32.const -4 - i32.add - local.tee 7 - i32.load - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@5;) - local.get 8 - i32.load - local.tee 9 - i32.const 1 - i32.and - br_if 0 (;@5;) - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - local.get 5 - i32.const -4 - i32.and - local.tee 10 - br_if 0 (;@8;) - local.get 8 - local.set 1 - br 1 (;@7;) - end - local.get 8 - local.set 1 - local.get 5 - i32.const 2 - i32.and - br_if 0 (;@7;) - local.get 10 - local.get 10 - i32.load offset=4 - i32.const 3 - i32.and - local.get 8 - i32.or - i32.store offset=4 - local.get 3 - i32.load - local.set 6 - local.get 7 - i32.load - local.tee 5 - i32.const -4 - i32.and - local.tee 1 - i32.eqz - br_if 1 (;@6;) - local.get 1 - i32.load - local.set 9 - end - local.get 1 - local.get 6 - i32.const -4 - i32.and - local.get 9 - i32.const 3 - i32.and - i32.or - i32.store - local.get 7 - i32.load - local.set 5 - local.get 3 - i32.load - local.set 6 - end - local.get 7 - local.get 5 - i32.const 3 - i32.and - i32.store - local.get 3 - local.get 6 - i32.const 3 - i32.and - i32.store - local.get 6 - i32.const 2 - i32.and - i32.eqz - br_if 1 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - br 1 (;@4;) - end - local.get 5 - i32.const 2 - i32.and - br_if 1 (;@3;) - local.get 5 - i32.const -4 - i32.and - local.tee 5 - i32.eqz - br_if 1 (;@3;) - local.get 5 - i32.load8_u - i32.const 1 - i32.and - br_if 1 (;@3;) - local.get 1 - local.get 5 - i32.load offset=8 - i32.const -4 - i32.and - i32.store - local.get 5 - local.get 3 - i32.const 1 - i32.or - i32.store offset=8 - end - local.get 4 - local.set 3 - br 1 (;@2;) - end - local.get 1 - local.get 4 - i32.store - end - local.get 0 - local.get 3 - i32.store - end - ) - (func $core::panicking::panic_fmt (;20;) (type 10) (param i32 i32) - (local i32) - global.get $__stack_pointer - i32.const 32 - i32.sub - local.tee 2 - global.set $__stack_pointer - local.get 2 - i32.const 16 - i32.add - local.get 0 - i32.const 16 - i32.add - i64.load align=4 - i64.store - local.get 2 - i32.const 8 - i32.add - local.get 0 - i32.const 8 - i32.add - i64.load align=4 - i64.store - local.get 2 - i32.const 1 - i32.store16 offset=28 - local.get 2 - local.get 1 - i32.store offset=24 - local.get 2 - local.get 0 - i64.load align=4 - i64.store - local.get 2 - call $rust_begin_unwind - unreachable - ) - (func $core::slice::index::slice_start_index_len_fail (;21;) (type 7) (param i32 i32 i32) - (local i32 i64) - global.get $__stack_pointer - i32.const 48 - i32.sub - local.tee 3 - global.set $__stack_pointer - local.get 3 - local.get 0 - i32.store - local.get 3 - local.get 1 - i32.store offset=4 - local.get 3 - i32.const 2 - i32.store offset=12 - local.get 3 - i32.const 1049264 - i32.store offset=8 - local.get 3 - i64.const 2 - i64.store offset=20 align=4 - local.get 3 - i32.const 6 - i64.extend_i32_u - i64.const 32 - i64.shl - local.tee 4 - local.get 3 - i32.const 4 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=40 - local.get 3 - local.get 4 - local.get 3 - i64.extend_i32_u - i64.or - i64.store offset=32 - local.get 3 - local.get 3 - i32.const 32 - i32.add - i32.store offset=16 - local.get 3 - i32.const 8 - i32.add - local.get 2 - call $core::panicking::panic_fmt - unreachable - ) - (func $core::panicking::panic_bounds_check (;22;) (type 7) (param i32 i32 i32) - (local i32 i64) - global.get $__stack_pointer - i32.const 48 - i32.sub - local.tee 3 - global.set $__stack_pointer - local.get 3 - local.get 1 - i32.store offset=4 - local.get 3 - local.get 0 - i32.store - local.get 3 - i32.const 2 - i32.store offset=12 - local.get 3 - i32.const 1048768 - i32.store offset=8 - local.get 3 - i64.const 2 - i64.store offset=20 align=4 - local.get 3 - i32.const 6 - i64.extend_i32_u - i64.const 32 - i64.shl - local.tee 4 - local.get 3 - i64.extend_i32_u - i64.or - i64.store offset=40 - local.get 3 - local.get 4 - local.get 3 - i32.const 4 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=32 - local.get 3 - local.get 3 - i32.const 32 - i32.add - i32.store offset=16 - local.get 3 - i32.const 8 - i32.add - local.get 2 - call $core::panicking::panic_fmt - unreachable - ) - (func $core::fmt::Formatter::pad (;23;) (type 0) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32) - local.get 0 - i32.load offset=8 - local.set 3 - block ;; label = @1 - block ;; label = @2 - local.get 0 - i32.load - local.tee 4 - br_if 0 (;@2;) - local.get 3 - i32.const 1 - i32.and - i32.eqz - br_if 1 (;@1;) - end - block ;; label = @2 - local.get 3 - i32.const 1 - i32.and - i32.eqz - br_if 0 (;@2;) - local.get 1 - local.get 2 - i32.add - local.set 5 - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.load offset=12 - local.tee 6 - br_if 0 (;@4;) - i32.const 0 - local.set 7 - local.get 1 - local.set 8 - br 1 (;@3;) - end - i32.const 0 - local.set 7 - local.get 1 - local.set 8 - loop ;; label = @4 - local.get 8 - local.tee 3 - local.get 5 - i32.eq - br_if 2 (;@2;) - block ;; label = @5 - block ;; label = @6 - local.get 3 - i32.load8_s - local.tee 8 - i32.const -1 - i32.le_s - br_if 0 (;@6;) - local.get 3 - i32.const 1 - i32.add - local.set 8 - br 1 (;@5;) - end - block ;; label = @6 - local.get 8 - i32.const -32 - i32.ge_u - br_if 0 (;@6;) - local.get 3 - i32.const 2 - i32.add - local.set 8 - br 1 (;@5;) - end - block ;; label = @6 - local.get 8 - i32.const -16 - i32.ge_u - br_if 0 (;@6;) - local.get 3 - i32.const 3 - i32.add - local.set 8 - br 1 (;@5;) - end - local.get 3 - i32.const 4 - i32.add - local.set 8 - end - local.get 8 - local.get 3 - i32.sub - local.get 7 - i32.add - local.set 7 - local.get 6 - i32.const -1 - i32.add - local.tee 6 - br_if 0 (;@4;) - end - end - local.get 8 - local.get 5 - i32.eq - br_if 0 (;@2;) - block ;; label = @3 - local.get 8 - i32.load8_s - local.tee 3 - i32.const -1 - i32.gt_s - br_if 0 (;@3;) - local.get 3 - i32.const -32 - i32.lt_u - drop - end - block ;; label = @3 - block ;; label = @4 - local.get 7 - i32.eqz - br_if 0 (;@4;) - block ;; label = @5 - local.get 7 - local.get 2 - i32.ge_u - br_if 0 (;@5;) - local.get 1 - local.get 7 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - br_if 1 (;@4;) - i32.const 0 - local.set 3 - br 2 (;@3;) - end - local.get 7 - local.get 2 - i32.eq - br_if 0 (;@4;) - i32.const 0 - local.set 3 - br 1 (;@3;) - end - local.get 1 - local.set 3 - end - local.get 7 - local.get 2 - local.get 3 - select - local.set 2 - local.get 3 - local.get 1 - local.get 3 - select - local.set 1 - end - block ;; label = @2 - local.get 4 - br_if 0 (;@2;) - local.get 0 - i32.load offset=20 - local.get 1 - local.get 2 - local.get 0 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - return - end - local.get 0 - i32.load offset=4 - local.set 4 - block ;; label = @2 - block ;; label = @3 - local.get 2 - i32.const 16 - i32.lt_u - br_if 0 (;@3;) - local.get 1 - local.get 2 - call $core::str::count::do_count_chars - local.set 3 - br 1 (;@2;) - end - block ;; label = @3 - local.get 2 - br_if 0 (;@3;) - i32.const 0 - local.set 3 - br 1 (;@2;) - end - local.get 2 - i32.const 3 - i32.and - local.set 6 - block ;; label = @3 - block ;; label = @4 - local.get 2 - i32.const 4 - i32.ge_u - br_if 0 (;@4;) - i32.const 0 - local.set 3 - i32.const 0 - local.set 7 - br 1 (;@3;) - end - local.get 2 - i32.const 12 - i32.and - local.set 5 - i32.const 0 - local.set 3 - i32.const 0 - local.set 7 - loop ;; label = @4 - local.get 3 - local.get 1 - local.get 7 - i32.add - local.tee 8 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 8 - i32.const 1 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 8 - i32.const 2 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 8 - i32.const 3 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 3 - local.get 5 - local.get 7 - i32.const 4 - i32.add - local.tee 7 - i32.ne - br_if 0 (;@4;) - end - end - local.get 6 - i32.eqz - br_if 0 (;@2;) - local.get 1 - local.get 7 - i32.add - local.set 8 - loop ;; label = @3 - local.get 3 - local.get 8 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 3 - local.get 8 - i32.const 1 - i32.add - local.set 8 - local.get 6 - i32.const -1 - i32.add - local.tee 6 - br_if 0 (;@3;) - end - end - block ;; label = @2 - block ;; label = @3 - local.get 4 - local.get 3 - i32.le_u - br_if 0 (;@3;) - local.get 4 - local.get 3 - i32.sub - local.set 5 - i32.const 0 - local.set 3 - block ;; label = @4 - block ;; label = @5 - block ;; label = @6 - local.get 0 - i32.load8_u offset=32 - br_table 2 (;@4;) 0 (;@6;) 1 (;@5;) 2 (;@4;) 2 (;@4;) - end - local.get 5 - local.set 3 - i32.const 0 - local.set 5 - br 1 (;@4;) - end - local.get 5 - i32.const 1 - i32.shr_u - local.set 3 - local.get 5 - i32.const 1 - i32.add - i32.const 1 - i32.shr_u - local.set 5 - end - local.get 3 - i32.const 1 - i32.add - local.set 3 - local.get 0 - i32.load offset=16 - local.set 6 - local.get 0 - i32.load offset=24 - local.set 8 - local.get 0 - i32.load offset=20 - local.set 7 - loop ;; label = @4 - local.get 3 - i32.const -1 - i32.add - local.tee 3 - i32.eqz - br_if 2 (;@2;) - local.get 7 - local.get 6 - local.get 8 - i32.load offset=16 - call_indirect (type 1) - i32.eqz - br_if 0 (;@4;) - end - i32.const 1 - return - end - local.get 0 - i32.load offset=20 - local.get 1 - local.get 2 - local.get 0 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - return - end - block ;; label = @2 - local.get 7 - local.get 1 - local.get 2 - local.get 8 - i32.load offset=12 - call_indirect (type 0) - i32.eqz - br_if 0 (;@2;) - i32.const 1 - return - end - i32.const 0 - local.set 3 - loop ;; label = @2 - block ;; label = @3 - local.get 5 - local.get 3 - i32.ne - br_if 0 (;@3;) - local.get 5 - local.get 5 - i32.lt_u - return - end - local.get 3 - i32.const 1 - i32.add - local.set 3 - local.get 7 - local.get 6 - local.get 8 - i32.load offset=16 - call_indirect (type 1) - i32.eqz - br_if 0 (;@2;) - end - local.get 3 - i32.const -1 - i32.add - local.get 5 - i32.lt_u - return - end - local.get 0 - i32.load offset=20 - local.get 1 - local.get 2 - local.get 0 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - ) - (func $core::fmt::num::imp::::fmt (;24;) (type 1) (param i32 i32) (result i32) - local.get 0 - i64.load32_u - i32.const 1 - local.get 1 - call $core::fmt::num::imp::fmt_u64 - ) - (func $core::fmt::write (;25;) (type 0) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - global.get $__stack_pointer - i32.const 48 - i32.sub - local.tee 3 - global.set $__stack_pointer - local.get 3 - i32.const 3 - i32.store8 offset=44 - local.get 3 - i32.const 32 - i32.store offset=28 - i32.const 0 - local.set 4 - local.get 3 - i32.const 0 - i32.store offset=40 - local.get 3 - local.get 1 - i32.store offset=36 - local.get 3 - local.get 0 - i32.store offset=32 - local.get 3 - i32.const 0 - i32.store offset=20 - local.get 3 - i32.const 0 - i32.store offset=12 - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - local.get 2 - i32.load offset=16 - local.tee 5 - br_if 0 (;@5;) - local.get 2 - i32.load offset=12 - local.tee 0 - i32.eqz - br_if 1 (;@4;) - local.get 2 - i32.load offset=8 - local.set 1 - local.get 0 - i32.const 3 - i32.shl - local.set 6 - local.get 0 - i32.const -1 - i32.add - i32.const 536870911 - i32.and - i32.const 1 - i32.add - local.set 4 - local.get 2 - i32.load - local.set 0 - loop ;; label = @6 - block ;; label = @7 - local.get 0 - i32.const 4 - i32.add - i32.load - local.tee 7 - i32.eqz - br_if 0 (;@7;) - local.get 3 - i32.load offset=32 - local.get 0 - i32.load - local.get 7 - local.get 3 - i32.load offset=36 - i32.load offset=12 - call_indirect (type 0) - br_if 4 (;@3;) - end - local.get 1 - i32.load - local.get 3 - i32.const 12 - i32.add - local.get 1 - i32.load offset=4 - call_indirect (type 1) - br_if 3 (;@3;) - local.get 1 - i32.const 8 - i32.add - local.set 1 - local.get 0 - i32.const 8 - i32.add - local.set 0 - local.get 6 - i32.const -8 - i32.add - local.tee 6 - br_if 0 (;@6;) - br 2 (;@4;) - end - end - local.get 2 - i32.load offset=20 - local.tee 1 - i32.eqz - br_if 0 (;@4;) - local.get 1 - i32.const 5 - i32.shl - local.set 8 - local.get 1 - i32.const -1 - i32.add - i32.const 134217727 - i32.and - i32.const 1 - i32.add - local.set 4 - local.get 2 - i32.load offset=8 - local.set 9 - local.get 2 - i32.load - local.set 0 - i32.const 0 - local.set 6 - loop ;; label = @5 - block ;; label = @6 - local.get 0 - i32.const 4 - i32.add - i32.load - local.tee 1 - i32.eqz - br_if 0 (;@6;) - local.get 3 - i32.load offset=32 - local.get 0 - i32.load - local.get 1 - local.get 3 - i32.load offset=36 - i32.load offset=12 - call_indirect (type 0) - br_if 3 (;@3;) - end - local.get 3 - local.get 5 - local.get 6 - i32.add - local.tee 1 - i32.const 16 - i32.add - i32.load - i32.store offset=28 - local.get 3 - local.get 1 - i32.const 28 - i32.add - i32.load8_u - i32.store8 offset=44 - local.get 3 - local.get 1 - i32.const 24 - i32.add - i32.load - i32.store offset=40 - local.get 1 - i32.const 12 - i32.add - i32.load - local.set 7 - i32.const 0 - local.set 10 - i32.const 0 - local.set 11 - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - local.get 1 - i32.const 8 - i32.add - i32.load - br_table 1 (;@7;) 0 (;@8;) 2 (;@6;) 1 (;@7;) - end - local.get 7 - i32.const 3 - i32.shl - local.set 12 - i32.const 0 - local.set 11 - local.get 9 - local.get 12 - i32.add - local.tee 12 - i32.load offset=4 - br_if 1 (;@6;) - local.get 12 - i32.load - local.set 7 - end - i32.const 1 - local.set 11 - end - local.get 3 - local.get 7 - i32.store offset=16 - local.get 3 - local.get 11 - i32.store offset=12 - local.get 1 - i32.const 4 - i32.add - i32.load - local.set 7 - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - local.get 1 - i32.load - br_table 1 (;@7;) 0 (;@8;) 2 (;@6;) 1 (;@7;) - end - local.get 7 - i32.const 3 - i32.shl - local.set 11 - local.get 9 - local.get 11 - i32.add - local.tee 11 - i32.load offset=4 - br_if 1 (;@6;) - local.get 11 - i32.load - local.set 7 - end - i32.const 1 - local.set 10 - end - local.get 3 - local.get 7 - i32.store offset=24 - local.get 3 - local.get 10 - i32.store offset=20 - local.get 9 - local.get 1 - i32.const 20 - i32.add - i32.load - i32.const 3 - i32.shl - i32.add - local.tee 1 - i32.load - local.get 3 - i32.const 12 - i32.add - local.get 1 - i32.load offset=4 - call_indirect (type 1) - br_if 2 (;@3;) - local.get 0 - i32.const 8 - i32.add - local.set 0 - local.get 8 - local.get 6 - i32.const 32 - i32.add - local.tee 6 - i32.ne - br_if 0 (;@5;) - end - end - local.get 4 - local.get 2 - i32.load offset=4 - i32.ge_u - br_if 1 (;@2;) - local.get 3 - i32.load offset=32 - local.get 2 - i32.load - local.get 4 - i32.const 3 - i32.shl - i32.add - local.tee 1 - i32.load - local.get 1 - i32.load offset=4 - local.get 3 - i32.load offset=36 - i32.load offset=12 - call_indirect (type 0) - i32.eqz - br_if 1 (;@2;) - end - i32.const 1 - local.set 1 - br 1 (;@1;) - end - i32.const 0 - local.set 1 - end - local.get 3 - i32.const 48 - i32.add - global.set $__stack_pointer - local.get 1 - ) - (func $core::fmt::builders::DebugStruct::field (;26;) (type 11) (param i32 i32 i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i64) - global.get $__stack_pointer - i32.const 64 - i32.sub - local.tee 5 - global.set $__stack_pointer - i32.const 1 - local.set 6 - block ;; label = @1 - local.get 0 - i32.load8_u offset=4 - br_if 0 (;@1;) - local.get 0 - i32.load8_u offset=5 - local.set 7 - block ;; label = @2 - local.get 0 - i32.load - local.tee 8 - i32.load offset=28 - local.tee 9 - i32.const 4 - i32.and - br_if 0 (;@2;) - i32.const 1 - local.set 6 - local.get 8 - i32.load offset=20 - i32.const 1048963 - i32.const 1048960 - local.get 7 - i32.const 1 - i32.and - local.tee 7 - select - i32.const 2 - i32.const 3 - local.get 7 - select - local.get 8 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - br_if 1 (;@1;) - local.get 8 - i32.load offset=20 - local.get 1 - local.get 2 - local.get 8 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - br_if 1 (;@1;) - local.get 8 - i32.load offset=20 - i32.const 1048928 - i32.const 2 - local.get 8 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - br_if 1 (;@1;) - local.get 3 - local.get 8 - local.get 4 - i32.load offset=12 - call_indirect (type 1) - local.set 6 - br 1 (;@1;) - end - i32.const 1 - local.set 6 - block ;; label = @2 - local.get 7 - i32.const 1 - i32.and - br_if 0 (;@2;) - local.get 8 - i32.load offset=20 - i32.const 1048965 - i32.const 3 - local.get 8 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - br_if 1 (;@1;) - local.get 8 - i32.load offset=28 - local.set 9 - end - i32.const 1 - local.set 6 - local.get 5 - i32.const 1 - i32.store8 offset=27 - local.get 5 - local.get 8 - i64.load offset=20 align=4 - i64.store offset=12 align=4 - local.get 5 - i32.const 1048932 - i32.store offset=52 - local.get 5 - local.get 5 - i32.const 27 - i32.add - i32.store offset=20 - local.get 5 - local.get 8 - i64.load offset=8 align=4 - i64.store offset=36 align=4 - local.get 8 - i64.load align=4 - local.set 10 - local.get 5 - local.get 9 - i32.store offset=56 - local.get 5 - local.get 8 - i32.load offset=16 - i32.store offset=44 - local.get 5 - local.get 8 - i32.load8_u offset=32 - i32.store8 offset=60 - local.get 5 - local.get 10 - i64.store offset=28 align=4 - local.get 5 - local.get 5 - i32.const 12 - i32.add - i32.store offset=48 - local.get 5 - i32.const 12 - i32.add - local.get 1 - local.get 2 - call $::write_str - br_if 0 (;@1;) - local.get 5 - i32.const 12 - i32.add - i32.const 1048928 - i32.const 2 - call $::write_str - br_if 0 (;@1;) - local.get 3 - local.get 5 - i32.const 28 - i32.add - local.get 4 - i32.load offset=12 - call_indirect (type 1) - br_if 0 (;@1;) - local.get 5 - i32.load offset=48 - i32.const 1048968 - i32.const 2 - local.get 5 - i32.load offset=52 - i32.load offset=12 - call_indirect (type 0) - local.set 6 - end - local.get 0 - i32.const 1 - i32.store8 offset=5 - local.get 0 - local.get 6 - i32.store8 offset=4 - local.get 5 - i32.const 64 - i32.add - global.set $__stack_pointer - local.get 0 - ) - (func $<&T as core::fmt::Display>::fmt (;27;) (type 1) (param i32 i32) (result i32) - local.get 1 - local.get 0 - i32.load - local.get 0 - i32.load offset=4 - call $core::fmt::Formatter::pad - ) - (func $core::panicking::assert_failed_inner (;28;) (type 12) (param i32 i32 i32 i32 i32 i32 i32) - (local i32 i64) - global.get $__stack_pointer - i32.const 112 - i32.sub - local.tee 7 - global.set $__stack_pointer - local.get 7 - local.get 2 - i32.store offset=12 - local.get 7 - local.get 1 - i32.store offset=8 - local.get 7 - local.get 4 - i32.store offset=20 - local.get 7 - local.get 3 - i32.store offset=16 - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.const 255 - i32.and - br_table 0 (;@4;) 1 (;@3;) 2 (;@2;) 0 (;@4;) - end - local.get 7 - i32.const 1048784 - i32.store offset=24 - i32.const 2 - local.set 2 - br 2 (;@1;) - end - local.get 7 - i32.const 1048786 - i32.store offset=24 - i32.const 2 - local.set 2 - br 1 (;@1;) - end - local.get 7 - i32.const 1048788 - i32.store offset=24 - i32.const 7 - local.set 2 - end - local.get 7 - local.get 2 - i32.store offset=28 - block ;; label = @1 - local.get 5 - i32.load - br_if 0 (;@1;) - local.get 7 - i32.const 3 - i32.store offset=92 - local.get 7 - i32.const 1048844 - i32.store offset=88 - local.get 7 - i64.const 3 - i64.store offset=100 align=4 - local.get 7 - i32.const 7 - i64.extend_i32_u - i64.const 32 - i64.shl - local.tee 8 - local.get 7 - i32.const 16 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=72 - local.get 7 - local.get 8 - local.get 7 - i32.const 8 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=64 - local.get 7 - i32.const 8 - i64.extend_i32_u - i64.const 32 - i64.shl - local.get 7 - i32.const 24 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=56 - local.get 7 - local.get 7 - i32.const 56 - i32.add - i32.store offset=96 - local.get 7 - i32.const 88 - i32.add - local.get 6 - call $core::panicking::panic_fmt - unreachable - end - local.get 7 - i32.const 32 - i32.add - i32.const 16 - i32.add - local.get 5 - i32.const 16 - i32.add - i64.load align=4 - i64.store - local.get 7 - i32.const 32 - i32.add - i32.const 8 - i32.add - local.get 5 - i32.const 8 - i32.add - i64.load align=4 - i64.store - local.get 7 - local.get 5 - i64.load align=4 - i64.store offset=32 - local.get 7 - i32.const 4 - i32.store offset=92 - local.get 7 - i32.const 1048896 - i32.store offset=88 - local.get 7 - i64.const 4 - i64.store offset=100 align=4 - local.get 7 - i32.const 7 - i64.extend_i32_u - i64.const 32 - i64.shl - local.tee 8 - local.get 7 - i32.const 16 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=80 - local.get 7 - local.get 8 - local.get 7 - i32.const 8 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=72 - local.get 7 - i32.const 9 - i64.extend_i32_u - i64.const 32 - i64.shl - local.get 7 - i32.const 32 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=64 - local.get 7 - i32.const 8 - i64.extend_i32_u - i64.const 32 - i64.shl - local.get 7 - i32.const 24 - i32.add - i64.extend_i32_u - i64.or - i64.store offset=56 - local.get 7 - local.get 7 - i32.const 56 - i32.add - i32.store offset=96 - local.get 7 - i32.const 88 - i32.add - local.get 6 - call $core::panicking::panic_fmt - unreachable - ) - (func $<&T as core::fmt::Debug>::fmt (;29;) (type 1) (param i32 i32) (result i32) - local.get 0 - i32.load - local.get 1 - local.get 0 - i32.load offset=4 - i32.load offset=12 - call_indirect (type 1) - ) - (func $::fmt (;30;) (type 1) (param i32 i32) (result i32) - local.get 1 - i32.load offset=20 - local.get 1 - i32.load offset=24 - local.get 0 - call $core::fmt::write - ) - (func $::write_str (;31;) (type 0) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) - local.get 1 - i32.const -1 - i32.add - local.set 3 - local.get 0 - i32.load offset=4 - local.set 4 - local.get 0 - i32.load - local.set 5 - local.get 0 - i32.load offset=8 - local.set 6 - i32.const 0 - local.set 7 - i32.const 0 - local.set 8 - i32.const 0 - local.set 9 - i32.const 0 - local.set 10 - block ;; label = @1 - loop ;; label = @2 - local.get 10 - i32.const 1 - i32.and - br_if 1 (;@1;) - block ;; label = @3 - block ;; label = @4 - local.get 9 - local.get 2 - i32.gt_u - br_if 0 (;@4;) - loop ;; label = @5 - local.get 1 - local.get 9 - i32.add - local.set 11 - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - block ;; label = @9 - local.get 2 - local.get 9 - i32.sub - local.tee 12 - i32.const 7 - i32.gt_u - br_if 0 (;@9;) - local.get 2 - local.get 9 - i32.ne - br_if 1 (;@8;) - local.get 2 - local.set 9 - br 5 (;@4;) - end - block ;; label = @9 - block ;; label = @10 - local.get 11 - i32.const 3 - i32.add - i32.const -4 - i32.and - local.tee 13 - local.get 11 - i32.sub - local.tee 14 - i32.eqz - br_if 0 (;@10;) - i32.const 0 - local.set 0 - loop ;; label = @11 - local.get 11 - local.get 0 - i32.add - i32.load8_u - i32.const 10 - i32.eq - br_if 5 (;@6;) - local.get 14 - local.get 0 - i32.const 1 - i32.add - local.tee 0 - i32.ne - br_if 0 (;@11;) - end - local.get 14 - local.get 12 - i32.const -8 - i32.add - local.tee 15 - i32.le_u - br_if 1 (;@9;) - br 3 (;@7;) - end - local.get 12 - i32.const -8 - i32.add - local.set 15 - end - loop ;; label = @9 - i32.const 16843008 - local.get 13 - i32.load - local.tee 0 - i32.const 168430090 - i32.xor - i32.sub - local.get 0 - i32.or - i32.const 16843008 - local.get 13 - i32.const 4 - i32.add - i32.load - local.tee 0 - i32.const 168430090 - i32.xor - i32.sub - local.get 0 - i32.or - i32.and - i32.const -2139062144 - i32.and - i32.const -2139062144 - i32.ne - br_if 2 (;@7;) - local.get 13 - i32.const 8 - i32.add - local.set 13 - local.get 14 - i32.const 8 - i32.add - local.tee 14 - local.get 15 - i32.le_u - br_if 0 (;@9;) - br 2 (;@7;) - end - end - i32.const 0 - local.set 0 - loop ;; label = @8 - local.get 11 - local.get 0 - i32.add - i32.load8_u - i32.const 10 - i32.eq - br_if 2 (;@6;) - local.get 12 - local.get 0 - i32.const 1 - i32.add - local.tee 0 - i32.ne - br_if 0 (;@8;) - end - local.get 2 - local.set 9 - br 3 (;@4;) - end - block ;; label = @7 - local.get 14 - local.get 12 - i32.ne - br_if 0 (;@7;) - local.get 2 - local.set 9 - br 3 (;@4;) - end - loop ;; label = @7 - block ;; label = @8 - local.get 11 - local.get 14 - i32.add - i32.load8_u - i32.const 10 - i32.ne - br_if 0 (;@8;) - local.get 14 - local.set 0 - br 2 (;@6;) - end - local.get 12 - local.get 14 - i32.const 1 - i32.add - local.tee 14 - i32.ne - br_if 0 (;@7;) - end - local.get 2 - local.set 9 - br 2 (;@4;) - end - local.get 0 - local.get 9 - i32.add - local.tee 14 - i32.const 1 - i32.add - local.set 9 - block ;; label = @6 - local.get 14 - local.get 2 - i32.ge_u - br_if 0 (;@6;) - local.get 11 - local.get 0 - i32.add - i32.load8_u - i32.const 10 - i32.ne - br_if 0 (;@6;) - local.get 9 - local.set 11 - local.get 9 - local.set 0 - br 3 (;@3;) - end - local.get 9 - local.get 2 - i32.le_u - br_if 0 (;@5;) - end - end - i32.const 1 - local.set 10 - local.get 8 - local.set 11 - local.get 2 - local.set 0 - local.get 8 - local.get 2 - i32.eq - br_if 2 (;@1;) - end - block ;; label = @3 - block ;; label = @4 - local.get 6 - i32.load8_u - i32.eqz - br_if 0 (;@4;) - local.get 5 - i32.const 1048956 - i32.const 4 - local.get 4 - i32.load offset=12 - call_indirect (type 0) - br_if 1 (;@3;) - end - local.get 0 - local.get 8 - i32.sub - local.set 13 - i32.const 0 - local.set 14 - block ;; label = @4 - local.get 0 - local.get 8 - i32.eq - br_if 0 (;@4;) - local.get 3 - local.get 0 - i32.add - i32.load8_u - i32.const 10 - i32.eq - local.set 14 - end - local.get 1 - local.get 8 - i32.add - local.set 0 - local.get 6 - local.get 14 - i32.store8 - local.get 11 - local.set 8 - local.get 5 - local.get 0 - local.get 13 - local.get 4 - i32.load offset=12 - call_indirect (type 0) - i32.eqz - br_if 1 (;@2;) - end - end - i32.const 1 - local.set 7 - end - local.get 7 - ) - (func $::write_char (;32;) (type 1) (param i32 i32) (result i32) - (local i32 i32) - local.get 0 - i32.load offset=4 - local.set 2 - local.get 0 - i32.load - local.set 3 - block ;; label = @1 - local.get 0 - i32.load offset=8 - local.tee 0 - i32.load8_u - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.const 1048956 - i32.const 4 - local.get 2 - i32.load offset=12 - call_indirect (type 0) - i32.eqz - br_if 0 (;@1;) - i32.const 1 - return - end - local.get 0 - local.get 1 - i32.const 10 - i32.eq - i32.store8 - local.get 3 - local.get 1 - local.get 2 - i32.load offset=16 - call_indirect (type 1) - ) - (func $core::fmt::builders::DebugStruct::finish (;33;) (type 13) (param i32) (result i32) - (local i32 i32) - local.get 0 - i32.load8_u offset=4 - local.tee 1 - local.set 2 - block ;; label = @1 - local.get 0 - i32.load8_u offset=5 - i32.eqz - br_if 0 (;@1;) - i32.const 1 - local.set 2 - block ;; label = @2 - local.get 1 - i32.const 1 - i32.and - br_if 0 (;@2;) - block ;; label = @3 - local.get 0 - i32.load - local.tee 2 - i32.load8_u offset=28 - i32.const 4 - i32.and - br_if 0 (;@3;) - local.get 2 - i32.load offset=20 - i32.const 1048971 - i32.const 2 - local.get 2 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - local.set 2 - br 1 (;@2;) - end - local.get 2 - i32.load offset=20 - i32.const 1048970 - i32.const 1 - local.get 2 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - local.set 2 - end - local.get 0 - local.get 2 - i32.store8 offset=4 - end - local.get 2 - i32.const 1 - i32.and - ) - (func $core::fmt::Formatter::pad_integral (;34;) (type 14) (param i32 i32 i32 i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - block ;; label = @2 - local.get 1 - br_if 0 (;@2;) - local.get 5 - i32.const 1 - i32.add - local.set 6 - local.get 0 - i32.load offset=28 - local.set 7 - i32.const 45 - local.set 8 - br 1 (;@1;) - end - i32.const 43 - i32.const 1114112 - local.get 0 - i32.load offset=28 - local.tee 7 - i32.const 1 - i32.and - local.tee 1 - select - local.set 8 - local.get 1 - local.get 5 - i32.add - local.set 6 - end - block ;; label = @1 - block ;; label = @2 - local.get 7 - i32.const 4 - i32.and - br_if 0 (;@2;) - i32.const 0 - local.set 2 - br 1 (;@1;) - end - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.const 16 - i32.lt_u - br_if 0 (;@3;) - local.get 2 - local.get 3 - call $core::str::count::do_count_chars - local.set 1 - br 1 (;@2;) - end - block ;; label = @3 - local.get 3 - br_if 0 (;@3;) - i32.const 0 - local.set 1 - br 1 (;@2;) - end - local.get 3 - i32.const 3 - i32.and - local.set 9 - block ;; label = @3 - block ;; label = @4 - local.get 3 - i32.const 4 - i32.ge_u - br_if 0 (;@4;) - i32.const 0 - local.set 1 - i32.const 0 - local.set 10 - br 1 (;@3;) - end - local.get 3 - i32.const 12 - i32.and - local.set 11 - i32.const 0 - local.set 1 - i32.const 0 - local.set 10 - loop ;; label = @4 - local.get 1 - local.get 2 - local.get 10 - i32.add - local.tee 12 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 12 - i32.const 1 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 12 - i32.const 2 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 12 - i32.const 3 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 1 - local.get 11 - local.get 10 - i32.const 4 - i32.add - local.tee 10 - i32.ne - br_if 0 (;@4;) - end - end - local.get 9 - i32.eqz - br_if 0 (;@2;) - local.get 2 - local.get 10 - i32.add - local.set 12 - loop ;; label = @3 - local.get 1 - local.get 12 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 1 - local.get 12 - i32.const 1 - i32.add - local.set 12 - local.get 9 - i32.const -1 - i32.add - local.tee 9 - br_if 0 (;@3;) - end - end - local.get 1 - local.get 6 - i32.add - local.set 6 - end - block ;; label = @1 - local.get 0 - i32.load - br_if 0 (;@1;) - block ;; label = @2 - local.get 0 - i32.load offset=20 - local.tee 1 - local.get 0 - i32.load offset=24 - local.tee 12 - local.get 8 - local.get 2 - local.get 3 - call $core::fmt::Formatter::pad_integral::write_prefix - i32.eqz - br_if 0 (;@2;) - i32.const 1 - return - end - local.get 1 - local.get 4 - local.get 5 - local.get 12 - i32.load offset=12 - call_indirect (type 0) - return - end - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.load offset=4 - local.tee 1 - local.get 6 - i32.gt_u - br_if 0 (;@4;) - local.get 0 - i32.load offset=20 - local.tee 1 - local.get 0 - i32.load offset=24 - local.tee 12 - local.get 8 - local.get 2 - local.get 3 - call $core::fmt::Formatter::pad_integral::write_prefix - i32.eqz - br_if 1 (;@3;) - i32.const 1 - return - end - local.get 7 - i32.const 8 - i32.and - i32.eqz - br_if 1 (;@2;) - local.get 0 - i32.load offset=16 - local.set 9 - local.get 0 - i32.const 48 - i32.store offset=16 - local.get 0 - i32.load8_u offset=32 - local.set 7 - i32.const 1 - local.set 11 - local.get 0 - i32.const 1 - i32.store8 offset=32 - local.get 0 - i32.load offset=20 - local.tee 12 - local.get 0 - i32.load offset=24 - local.tee 10 - local.get 8 - local.get 2 - local.get 3 - call $core::fmt::Formatter::pad_integral::write_prefix - br_if 2 (;@1;) - local.get 1 - local.get 6 - i32.sub - i32.const 1 - i32.add - local.set 1 - block ;; label = @4 - loop ;; label = @5 - local.get 1 - i32.const -1 - i32.add - local.tee 1 - i32.eqz - br_if 1 (;@4;) - local.get 12 - i32.const 48 - local.get 10 - i32.load offset=16 - call_indirect (type 1) - i32.eqz - br_if 0 (;@5;) - end - i32.const 1 - return - end - block ;; label = @4 - local.get 12 - local.get 4 - local.get 5 - local.get 10 - i32.load offset=12 - call_indirect (type 0) - i32.eqz - br_if 0 (;@4;) - i32.const 1 - return - end - local.get 0 - local.get 7 - i32.store8 offset=32 - local.get 0 - local.get 9 - i32.store offset=16 - i32.const 0 - return - end - local.get 1 - local.get 4 - local.get 5 - local.get 12 - i32.load offset=12 - call_indirect (type 0) - local.set 11 - br 1 (;@1;) - end - local.get 1 - local.get 6 - i32.sub - local.set 6 - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.load8_u offset=32 - local.tee 1 - br_table 2 (;@2;) 0 (;@4;) 1 (;@3;) 0 (;@4;) 2 (;@2;) - end - local.get 6 - local.set 1 - i32.const 0 - local.set 6 - br 1 (;@2;) - end - local.get 6 - i32.const 1 - i32.shr_u - local.set 1 - local.get 6 - i32.const 1 - i32.add - i32.const 1 - i32.shr_u - local.set 6 - end - local.get 1 - i32.const 1 - i32.add - local.set 1 - local.get 0 - i32.load offset=16 - local.set 9 - local.get 0 - i32.load offset=24 - local.set 12 - local.get 0 - i32.load offset=20 - local.set 10 - block ;; label = @2 - loop ;; label = @3 - local.get 1 - i32.const -1 - i32.add - local.tee 1 - i32.eqz - br_if 1 (;@2;) - local.get 10 - local.get 9 - local.get 12 - i32.load offset=16 - call_indirect (type 1) - i32.eqz - br_if 0 (;@3;) - end - i32.const 1 - return - end - i32.const 1 - local.set 11 - local.get 10 - local.get 12 - local.get 8 - local.get 2 - local.get 3 - call $core::fmt::Formatter::pad_integral::write_prefix - br_if 0 (;@1;) - local.get 10 - local.get 4 - local.get 5 - local.get 12 - i32.load offset=12 - call_indirect (type 0) - br_if 0 (;@1;) - i32.const 0 - local.set 1 - loop ;; label = @2 - block ;; label = @3 - local.get 6 - local.get 1 - i32.ne - br_if 0 (;@3;) - local.get 6 - local.get 6 - i32.lt_u - return - end - local.get 1 - i32.const 1 - i32.add - local.set 1 - local.get 10 - local.get 9 - local.get 12 - i32.load offset=16 - call_indirect (type 1) - i32.eqz - br_if 0 (;@2;) - end - local.get 1 - i32.const -1 - i32.add - local.get 6 - i32.lt_u - return - end - local.get 11 - ) - (func $core::fmt::Write::write_fmt (;35;) (type 1) (param i32 i32) (result i32) - local.get 0 - i32.const 1048932 - local.get 1 - call $core::fmt::write - ) - (func $core::str::count::do_count_chars (;36;) (type 1) (param i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - block ;; label = @2 - local.get 1 - local.get 0 - i32.const 3 - i32.add - i32.const -4 - i32.and - local.tee 2 - local.get 0 - i32.sub - local.tee 3 - i32.lt_u - br_if 0 (;@2;) - local.get 1 - local.get 3 - i32.sub - local.tee 4 - i32.const 4 - i32.lt_u - br_if 0 (;@2;) - local.get 4 - i32.const 3 - i32.and - local.set 5 - i32.const 0 - local.set 6 - i32.const 0 - local.set 1 - block ;; label = @3 - local.get 2 - local.get 0 - i32.eq - local.tee 7 - br_if 0 (;@3;) - i32.const 0 - local.set 1 - block ;; label = @4 - block ;; label = @5 - local.get 0 - local.get 2 - i32.sub - local.tee 8 - i32.const -4 - i32.le_u - br_if 0 (;@5;) - i32.const 0 - local.set 9 - br 1 (;@4;) - end - i32.const 0 - local.set 9 - loop ;; label = @5 - local.get 1 - local.get 0 - local.get 9 - i32.add - local.tee 2 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 2 - i32.const 1 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 2 - i32.const 2 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 2 - i32.const 3 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 1 - local.get 9 - i32.const 4 - i32.add - local.tee 9 - br_if 0 (;@5;) - end - end - local.get 7 - br_if 0 (;@3;) - local.get 0 - local.get 9 - i32.add - local.set 2 - loop ;; label = @4 - local.get 1 - local.get 2 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 1 - local.get 2 - i32.const 1 - i32.add - local.set 2 - local.get 8 - i32.const 1 - i32.add - local.tee 8 - br_if 0 (;@4;) - end - end - local.get 0 - local.get 3 - i32.add - local.set 9 - block ;; label = @3 - local.get 5 - i32.eqz - br_if 0 (;@3;) - local.get 9 - local.get 4 - i32.const -4 - i32.and - i32.add - local.tee 2 - i32.load8_s - i32.const -65 - i32.gt_s - local.set 6 - local.get 5 - i32.const 1 - i32.eq - br_if 0 (;@3;) - local.get 6 - local.get 2 - i32.load8_s offset=1 - i32.const -65 - i32.gt_s - i32.add - local.set 6 - local.get 5 - i32.const 2 - i32.eq - br_if 0 (;@3;) - local.get 6 - local.get 2 - i32.load8_s offset=2 - i32.const -65 - i32.gt_s - i32.add - local.set 6 - end - local.get 4 - i32.const 2 - i32.shr_u - local.set 3 - local.get 6 - local.get 1 - i32.add - local.set 8 - loop ;; label = @3 - local.get 9 - local.set 4 - local.get 3 - i32.eqz - br_if 2 (;@1;) - local.get 3 - i32.const 192 - local.get 3 - i32.const 192 - i32.lt_u - select - local.tee 6 - i32.const 3 - i32.and - local.set 7 - local.get 6 - i32.const 2 - i32.shl - local.set 5 - i32.const 0 - local.set 2 - block ;; label = @4 - local.get 3 - i32.const 4 - i32.lt_u - br_if 0 (;@4;) - local.get 4 - local.get 5 - i32.const 1008 - i32.and - i32.add - local.set 0 - i32.const 0 - local.set 2 - local.get 4 - local.set 1 - loop ;; label = @5 - local.get 1 - i32.load offset=12 - local.tee 9 - i32.const -1 - i32.xor - i32.const 7 - i32.shr_u - local.get 9 - i32.const 6 - i32.shr_u - i32.or - i32.const 16843009 - i32.and - local.get 1 - i32.load offset=8 - local.tee 9 - i32.const -1 - i32.xor - i32.const 7 - i32.shr_u - local.get 9 - i32.const 6 - i32.shr_u - i32.or - i32.const 16843009 - i32.and - local.get 1 - i32.load offset=4 - local.tee 9 - i32.const -1 - i32.xor - i32.const 7 - i32.shr_u - local.get 9 - i32.const 6 - i32.shr_u - i32.or - i32.const 16843009 - i32.and - local.get 1 - i32.load - local.tee 9 - i32.const -1 - i32.xor - i32.const 7 - i32.shr_u - local.get 9 - i32.const 6 - i32.shr_u - i32.or - i32.const 16843009 - i32.and - local.get 2 - i32.add - i32.add - i32.add - i32.add - local.set 2 - local.get 1 - i32.const 16 - i32.add - local.tee 1 - local.get 0 - i32.ne - br_if 0 (;@5;) - end - end - local.get 3 - local.get 6 - i32.sub - local.set 3 - local.get 4 - local.get 5 - i32.add - local.set 9 - local.get 2 - i32.const 8 - i32.shr_u - i32.const 16711935 - i32.and - local.get 2 - i32.const 16711935 - i32.and - i32.add - i32.const 65537 - i32.mul - i32.const 16 - i32.shr_u - local.get 8 - i32.add - local.set 8 - local.get 7 - i32.eqz - br_if 0 (;@3;) - end - local.get 4 - local.get 6 - i32.const 252 - i32.and - i32.const 2 - i32.shl - i32.add - local.tee 2 - i32.load - local.tee 1 - i32.const -1 - i32.xor - i32.const 7 - i32.shr_u - local.get 1 - i32.const 6 - i32.shr_u - i32.or - i32.const 16843009 - i32.and - local.set 1 - block ;; label = @3 - local.get 7 - i32.const 1 - i32.eq - br_if 0 (;@3;) - local.get 2 - i32.load offset=4 - local.tee 9 - i32.const -1 - i32.xor - i32.const 7 - i32.shr_u - local.get 9 - i32.const 6 - i32.shr_u - i32.or - i32.const 16843009 - i32.and - local.get 1 - i32.add - local.set 1 - local.get 7 - i32.const 2 - i32.eq - br_if 0 (;@3;) - local.get 2 - i32.load offset=8 - local.tee 2 - i32.const -1 - i32.xor - i32.const 7 - i32.shr_u - local.get 2 - i32.const 6 - i32.shr_u - i32.or - i32.const 16843009 - i32.and - local.get 1 - i32.add - local.set 1 - end - local.get 1 - i32.const 8 - i32.shr_u - i32.const 459007 - i32.and - local.get 1 - i32.const 16711935 - i32.and - i32.add - i32.const 65537 - i32.mul - i32.const 16 - i32.shr_u - local.get 8 - i32.add - return - end - block ;; label = @2 - local.get 1 - br_if 0 (;@2;) - i32.const 0 - return - end - local.get 1 - i32.const 3 - i32.and - local.set 9 - block ;; label = @2 - block ;; label = @3 - local.get 1 - i32.const 4 - i32.ge_u - br_if 0 (;@3;) - i32.const 0 - local.set 8 - i32.const 0 - local.set 2 - br 1 (;@2;) - end - local.get 1 - i32.const -4 - i32.and - local.set 3 - i32.const 0 - local.set 8 - i32.const 0 - local.set 2 - loop ;; label = @3 - local.get 8 - local.get 0 - local.get 2 - i32.add - local.tee 1 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 1 - i32.const 1 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 1 - i32.const 2 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.get 1 - i32.const 3 - i32.add - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 8 - local.get 3 - local.get 2 - i32.const 4 - i32.add - local.tee 2 - i32.ne - br_if 0 (;@3;) - end - end - local.get 9 - i32.eqz - br_if 0 (;@1;) - local.get 0 - local.get 2 - i32.add - local.set 1 - loop ;; label = @2 - local.get 8 - local.get 1 - i32.load8_s - i32.const -65 - i32.gt_s - i32.add - local.set 8 - local.get 1 - i32.const 1 - i32.add - local.set 1 - local.get 9 - i32.const -1 - i32.add - local.tee 9 - br_if 0 (;@2;) - end - end - local.get 8 - ) - (func $core::fmt::Formatter::pad_integral::write_prefix (;37;) (type 11) (param i32 i32 i32 i32 i32) (result i32) - block ;; label = @1 - local.get 2 - i32.const 1114112 - i32.eq - br_if 0 (;@1;) - local.get 0 - local.get 2 - local.get 1 - i32.load offset=16 - call_indirect (type 1) - i32.eqz - br_if 0 (;@1;) - i32.const 1 - return - end - block ;; label = @1 - local.get 3 - br_if 0 (;@1;) - i32.const 0 - return - end - local.get 0 - local.get 3 - local.get 4 - local.get 1 - i32.load offset=12 - call_indirect (type 0) - ) - (func $core::fmt::Formatter::debug_struct (;38;) (type 9) (param i32 i32 i32 i32) - local.get 1 - i32.load offset=20 - local.get 2 - local.get 3 - local.get 1 - i32.load offset=24 - i32.load offset=12 - call_indirect (type 0) - local.set 3 - local.get 0 - i32.const 0 - i32.store8 offset=5 - local.get 0 - local.get 3 - i32.store8 offset=4 - local.get 0 - local.get 1 - i32.store - ) - (func $core::fmt::num::imp::::fmt (;39;) (type 1) (param i32 i32) (result i32) - local.get 0 - i64.load - i32.const 1 - local.get 1 - call $core::fmt::num::imp::fmt_u64 - ) - (func $core::fmt::num::imp::fmt_u64 (;40;) (type 15) (param i64 i32 i32) (result i32) - (local i32 i32 i64 i32 i32 i32) - global.get $__stack_pointer - i32.const 48 - i32.sub - local.tee 3 - global.set $__stack_pointer - i32.const 39 - local.set 4 - block ;; label = @1 - block ;; label = @2 - local.get 0 - i64.const 10000 - i64.ge_u - br_if 0 (;@2;) - local.get 0 - local.set 5 - br 1 (;@1;) - end - i32.const 39 - local.set 4 - loop ;; label = @2 - local.get 3 - i32.const 9 - i32.add - local.get 4 - i32.add - local.tee 6 - i32.const -4 - i32.add - local.get 0 - local.get 0 - i64.const 10000 - i64.div_u - local.tee 5 - i64.const 10000 - i64.mul - i64.sub - i32.wrap_i64 - local.tee 7 - i32.const 65535 - i32.and - i32.const 100 - i32.div_u - local.tee 8 - i32.const 1 - i32.shl - i32.const 1049010 - i32.add - i32.load16_u align=1 - i32.store16 align=1 - local.get 6 - i32.const -2 - i32.add - local.get 7 - local.get 8 - i32.const 100 - i32.mul - i32.sub - i32.const 65535 - i32.and - i32.const 1 - i32.shl - i32.const 1049010 - i32.add - i32.load16_u align=1 - i32.store16 align=1 - local.get 4 - i32.const -4 - i32.add - local.set 4 - local.get 0 - i64.const 99999999 - i64.gt_u - local.set 6 - local.get 5 - local.set 0 - local.get 6 - br_if 0 (;@2;) - end - end - block ;; label = @1 - block ;; label = @2 - local.get 5 - i64.const 99 - i64.gt_u - br_if 0 (;@2;) - local.get 5 - i32.wrap_i64 - local.set 6 - br 1 (;@1;) - end - local.get 3 - i32.const 9 - i32.add - local.get 4 - i32.const -2 - i32.add - local.tee 4 - i32.add - local.get 5 - i32.wrap_i64 - local.tee 6 - local.get 6 - i32.const 65535 - i32.and - i32.const 100 - i32.div_u - local.tee 6 - i32.const 100 - i32.mul - i32.sub - i32.const 65535 - i32.and - i32.const 1 - i32.shl - i32.const 1049010 - i32.add - i32.load16_u align=1 - i32.store16 align=1 - end - block ;; label = @1 - block ;; label = @2 - local.get 6 - i32.const 10 - i32.lt_u - br_if 0 (;@2;) - local.get 3 - i32.const 9 - i32.add - local.get 4 - i32.const -2 - i32.add - local.tee 4 - i32.add - local.get 6 - i32.const 1 - i32.shl - i32.const 1049010 - i32.add - i32.load16_u align=1 - i32.store16 align=1 - br 1 (;@1;) - end - local.get 3 - i32.const 9 - i32.add - local.get 4 - i32.const -1 - i32.add - local.tee 4 - i32.add - local.get 6 - i32.const 48 - i32.or - i32.store8 - end - local.get 2 - local.get 1 - i32.const 1 - i32.const 0 - local.get 3 - i32.const 9 - i32.add - local.get 4 - i32.add - i32.const 39 - local.get 4 - i32.sub - call $core::fmt::Formatter::pad_integral - local.set 4 - local.get 3 - i32.const 48 - i32.add - global.set $__stack_pointer - local.get 4 - ) - (func $core::fmt::num::::fmt (;41;) (type 1) (param i32 i32) (result i32) - (local i32 i64 i32) - global.get $__stack_pointer - i32.const 128 - i32.sub - local.tee 2 - global.set $__stack_pointer - local.get 0 - i64.load - local.set 3 - i32.const 0 - local.set 0 - loop ;; label = @1 - local.get 2 - local.get 0 - i32.add - i32.const 127 - i32.add - local.get 3 - i32.wrap_i64 - i32.const 15 - i32.and - local.tee 4 - i32.const 48 - i32.or - local.get 4 - i32.const 87 - i32.add - local.get 4 - i32.const 10 - i32.lt_u - select - i32.store8 - local.get 0 - i32.const -1 - i32.add - local.set 0 - local.get 3 - i64.const 16 - i64.lt_u - local.set 4 - local.get 3 - i64.const 4 - i64.shr_u - local.set 3 - local.get 4 - i32.eqz - br_if 0 (;@1;) - end - block ;; label = @1 - local.get 0 - i32.const 128 - i32.add - local.tee 4 - i32.const 129 - i32.lt_u - br_if 0 (;@1;) - local.get 4 - i32.const 128 - i32.const 1048992 - call $core::slice::index::slice_start_index_len_fail - unreachable - end - local.get 1 - i32.const 1 - i32.const 1049008 - i32.const 2 - local.get 2 - local.get 0 - i32.add - i32.const 128 - i32.add - i32.const 0 - local.get 0 - i32.sub - call $core::fmt::Formatter::pad_integral - local.set 0 - local.get 2 - i32.const 128 - i32.add - global.set $__stack_pointer - local.get 0 - ) - (func $core::fmt::num::::fmt (;42;) (type 1) (param i32 i32) (result i32) - (local i32 i64 i32) - global.get $__stack_pointer - i32.const 128 - i32.sub - local.tee 2 - global.set $__stack_pointer - local.get 0 - i64.load - local.set 3 - i32.const 0 - local.set 0 - loop ;; label = @1 - local.get 2 - local.get 0 - i32.add - i32.const 127 - i32.add - local.get 3 - i32.wrap_i64 - i32.const 15 - i32.and - local.tee 4 - i32.const 48 - i32.or - local.get 4 - i32.const 55 - i32.add - local.get 4 - i32.const 10 - i32.lt_u - select - i32.store8 - local.get 0 - i32.const -1 - i32.add - local.set 0 - local.get 3 - i64.const 16 - i64.lt_u - local.set 4 - local.get 3 - i64.const 4 - i64.shr_u - local.set 3 - local.get 4 - i32.eqz - br_if 0 (;@1;) - end - block ;; label = @1 - local.get 0 - i32.const 128 - i32.add - local.tee 4 - i32.const 129 - i32.lt_u - br_if 0 (;@1;) - local.get 4 - i32.const 128 - i32.const 1048992 - call $core::slice::index::slice_start_index_len_fail - unreachable - end - local.get 1 - i32.const 1 - i32.const 1049008 - i32.const 2 - local.get 2 - local.get 0 - i32.add - i32.const 128 - i32.add - i32.const 0 - local.get 0 - i32.sub - call $core::fmt::Formatter::pad_integral - local.set 0 - local.get 2 - i32.const 128 - i32.add - global.set $__stack_pointer - local.get 0 - ) - (func $cabi_realloc (;43;) (type 8) (param i32 i32 i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $wit_bindgen_rt::cabi_realloc - ) - (table (;0;) 13 13 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "miden:base/note-script@1.0.0#note-script" (func $miden:base/note-script@1.0.0#note-script)) - (export "cabi_realloc" (func $cabi_realloc)) - (export "cabi_realloc_wit_bindgen_0_28_0" (func $wit_bindgen_rt::cabi_realloc)) - (elem (;0;) (i32.const 1) func $<&T as core::fmt::Debug>::fmt $basic_wallet_p2id_note::bindings::__link_custom_section_describing_imports $core::fmt::num::::fmt $::fmt $cabi_realloc $core::fmt::num::imp::::fmt $<&T as core::fmt::Debug>::fmt $<&T as core::fmt::Display>::fmt $::fmt $::write_str $::write_char $core::fmt::Write::write_fmt) - (data $.rodata (;0;) (i32.const 1048576) "\00\00\00\00\04\00\00\00\04\00\00\00\01\00\00\00\02\00\00\00Felt\00\00\00\00\08\00\00\00\08\00\00\00\03\00\00\00innerAccountId\00\00\00\00\00\00\08\00\00\00\08\00\00\00\04\00\00\00\02\00\00\00\02\00\00\00\02\00\00\00\02\00\00\00\02\00\00\00src/lib.rs\00\00\5c\00\10\00\0a\00\00\00!\00\00\00,\00\00\00\5c\00\10\00\0a\00\00\00$\00\00\00\09\00\00\00\05\00\00\00index out of bounds: the len is but the index is \00\00\8c\00\10\00 \00\00\00\ac\00\10\00\12\00\00\00==!=matchesassertion `left right` failed\0a left: \0a right: \00\db\00\10\00\10\00\00\00\eb\00\10\00\17\00\00\00\02\01\10\00\09\00\00\00 right` failed: \0a left: \00\00\00\db\00\10\00\10\00\00\00$\01\10\00\10\00\00\004\01\10\00\09\00\00\00\02\01\10\00\09\00\00\00: \00\00\00\00\00\00\0c\00\00\00\04\00\00\00\0a\00\00\00\0b\00\00\00\0c\00\00\00 { , {\0a,\0a} }core/src/fmt/num.rs\8d\01\10\00\13\00\00\00f\00\00\00\17\00\00\000x00010203040506070809101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899range start index out of range for slice of length \00\00z\02\10\00\12\00\00\00\8c\02\10\00\22\00\00\00") - ) - (core module (;1;) - (type (;0;) (func (param i32))) - (func $indirect-miden:base/note@1.0.0-get-inputs (;0;) (type 0) (param i32) - local.get 0 - i32.const 0 - call_indirect (type 0) - ) - (func $indirect-miden:base/note@1.0.0-get-assets (;1;) (type 0) (param i32) - local.get 0 - i32.const 1 - call_indirect (type 0) - ) - (table (;0;) 2 2 funcref) - (export "0" (func $indirect-miden:base/note@1.0.0-get-inputs)) - (export "1" (func $indirect-miden:base/note@1.0.0-get-assets)) - (export "$imports" (table 0)) - ) - (core module (;2;) - (type (;0;) (func (param i32))) - (import "" "0" (func (;0;) (type 0))) - (import "" "1" (func (;1;) (type 0))) - (import "" "$imports" (table (;0;) 2 2 funcref)) - (elem (;0;) (i32.const 0) func 0 1) - ) - (core instance (;0;) (instantiate 1)) - (alias core export 0 "0" (core func (;0;))) - (alias core export 0 "1" (core func (;1;))) - (core instance (;1;) - (export "get-inputs" (func 0)) - (export "get-assets" (func 1)) - ) - (alias export 0 "account-id-from-felt" (func (;0;))) - (core func (;2;) (canon lower (func 0))) - (core instance (;2;) - (export "account-id-from-felt" (func 2)) - ) - (alias export 1 "get-id" (func (;1;))) - (core func (;3;) (canon lower (func 1))) - (core instance (;3;) - (export "get-id" (func 3)) - ) - (alias export 3 "receive-asset" (func (;2;))) - (core func (;4;) (canon lower (func 2))) - (core instance (;4;) - (export "receive-asset" (func 4)) - ) - (core instance (;5;) (instantiate 0 - (with "miden:base/note@1.0.0" (instance 1)) - (with "miden:base/core-types@1.0.0" (instance 2)) - (with "miden:base/account@1.0.0" (instance 3)) - (with "miden:basic-wallet/basic-wallet@1.0.0" (instance 4)) - ) - ) - (alias core export 5 "memory" (core memory (;0;))) - (alias core export 5 "cabi_realloc" (core func (;5;))) - (alias core export 0 "$imports" (core table (;0;))) - (alias export 2 "get-inputs" (func (;3;))) - (core func (;6;) (canon lower (func 3) (memory 0) (realloc 5))) - (alias export 2 "get-assets" (func (;4;))) - (core func (;7;) (canon lower (func 4) (memory 0) (realloc 5))) - (core instance (;6;) - (export "$imports" (table 0)) - (export "0" (func 6)) - (export "1" (func 7)) - ) - (core instance (;7;) (instantiate 2 - (with "" (instance 6)) - ) - ) - (type (;8;) (func)) - (alias core export 5 "miden:base/note-script@1.0.0#note-script" (core func (;8;))) - (func (;5;) (type 8) (canon lift (core func 8))) - (component (;0;) - (type (;0;) (func)) - (import "import-func-note-script" (func (;0;) (type 0))) - (type (;1;) (func)) - (export (;1;) "note-script" (func 0) (func (type 1))) - ) - (instance (;4;) (instantiate 0 - (with "import-func-note-script" (func 5)) - ) - ) - (export (;5;) "miden:base/note-script@1.0.0" (instance 4)) -) \ No newline at end of file diff --git a/tests/integration/expected/wit_sdk_basic_wallet/miden_sdk.wat b/tests/integration/expected/wit_sdk_basic_wallet/miden_sdk.wat deleted file mode 100644 index 1c738a7ab..000000000 --- a/tests/integration/expected/wit_sdk_basic_wallet/miden_sdk.wat +++ /dev/null @@ -1,898 +0,0 @@ -(component - (core module (;0;) - (type (;0;) (func)) - (type (;1;) (func (param i32))) - (type (;2;) (func (param i32 i32) (result i32))) - (type (;3;) (func (param i32 i32 i32 i32) (result i32))) - (type (;4;) (func (param i64) (result i64))) - (type (;5;) (func (param i64 i64 i64 i64) (result i32))) - (type (;6;) (func (param i32 i64 i64 i64 i64) (result i32))) - (type (;7;) (func (param i32 i32 i32) (result i32))) - (type (;8;) (func (param i32 i32 i32 i32))) - (type (;9;) (func (param i32 i32))) - (type (;10;) (func (param i32 i32 i32))) - (func $__wasm_call_ctors (;0;) (type 0)) - (func $rust_begin_unwind (;1;) (type 1) (param i32) - loop ;; label = @1 - br 0 (;@1;) - end - ) - (func $miden_sdk::bindings::__link_custom_section_describing_imports (;2;) (type 0)) - (func $__rust_alloc (;3;) (type 2) (param i32 i32) (result i32) - i32.const 1048652 - local.get 1 - local.get 0 - call $::alloc - ) - (func $__rust_realloc (;4;) (type 3) (param i32 i32 i32 i32) (result i32) - (local i32) - block ;; label = @1 - i32.const 1048652 - local.get 2 - local.get 3 - call $::alloc - local.tee 4 - i32.eqz - br_if 0 (;@1;) - local.get 4 - local.get 0 - local.get 1 - local.get 3 - local.get 1 - local.get 3 - i32.lt_u - select - memory.copy - i32.const 1048652 - local.get 0 - local.get 2 - local.get 1 - call $::dealloc - end - local.get 4 - ) - (func $miden:base/core-types@1.0.0#account-id-from-felt (;5;) (type 4) (param i64) (result i64) - call $wit_bindgen_rt::run_ctors_once - local.get 0 - ) - (func $miden:base/types@1.0.0#from-core-asset (;6;) (type 5) (param i64 i64 i64 i64) (result i32) - call $wit_bindgen_rt::run_ctors_once - i32.const 1048584 - i32.const 19 - i32.const 1048616 - call $core::panicking::panic - unreachable - ) - (func $miden:base/types@1.0.0#to-core-asset (;7;) (type 6) (param i32 i64 i64 i64 i64) (result i32) - call $wit_bindgen_rt::run_ctors_once - i32.const 1048584 - i32.const 19 - i32.const 1048632 - call $core::panicking::panic - unreachable - ) - (func $wit_bindgen_rt::cabi_realloc (;8;) (type 3) (param i32 i32 i32 i32) (result i32) - block ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 1 - br_if 0 (;@3;) - local.get 3 - i32.eqz - br_if 2 (;@1;) - i32.const 0 - i32.load8_u offset=1048656 - drop - local.get 3 - local.get 2 - call $__rust_alloc - local.set 2 - br 1 (;@2;) - end - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $__rust_realloc - local.set 2 - end - local.get 2 - br_if 0 (;@1;) - unreachable - end - local.get 2 - ) - (func $wit_bindgen_rt::run_ctors_once (;9;) (type 0) - block ;; label = @1 - i32.const 0 - i32.load8_u offset=1048657 - br_if 0 (;@1;) - call $__wasm_call_ctors - i32.const 0 - i32.const 1 - i32.store8 offset=1048657 - end - ) - (func $wee_alloc::alloc_first_fit (;10;) (type 7) (param i32 i32 i32) (result i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 2 - i32.load - local.tee 3 - br_if 0 (;@1;) - i32.const 0 - return - end - local.get 1 - i32.const -1 - i32.add - local.set 4 - i32.const 0 - local.get 1 - i32.sub - local.set 5 - local.get 0 - i32.const 2 - i32.shl - local.set 6 - loop ;; label = @1 - block ;; label = @2 - block ;; label = @3 - local.get 3 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - local.get 3 - i32.const 8 - i32.add - local.set 0 - br 1 (;@2;) - end - loop ;; label = @3 - local.get 3 - local.get 1 - i32.const -2 - i32.and - i32.store offset=8 - block ;; label = @4 - block ;; label = @5 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.tee 0 - br_if 0 (;@5;) - i32.const 0 - local.set 8 - br 1 (;@4;) - end - i32.const 0 - local.get 0 - local.get 0 - i32.load8_u - i32.const 1 - i32.and - select - local.set 8 - end - block ;; label = @4 - local.get 3 - i32.load - local.tee 1 - i32.const -4 - i32.and - local.tee 9 - i32.eqz - br_if 0 (;@4;) - local.get 1 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 9 - local.get 9 - i32.load offset=4 - i32.const 3 - i32.and - local.get 0 - i32.or - i32.store offset=4 - local.get 3 - i32.load offset=4 - local.tee 7 - i32.const -4 - i32.and - local.set 0 - local.get 3 - i32.load - local.set 1 - end - block ;; label = @4 - local.get 0 - i32.eqz - br_if 0 (;@4;) - local.get 0 - local.get 0 - i32.load - i32.const 3 - i32.and - local.get 1 - i32.const -4 - i32.and - i32.or - i32.store - local.get 3 - i32.load offset=4 - local.set 7 - local.get 3 - i32.load - local.set 1 - end - local.get 3 - local.get 7 - i32.const 3 - i32.and - i32.store offset=4 - local.get 3 - local.get 1 - i32.const 3 - i32.and - i32.store - block ;; label = @4 - local.get 1 - i32.const 2 - i32.and - i32.eqz - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - end - local.get 2 - local.get 8 - i32.store - local.get 8 - local.set 3 - local.get 8 - i32.load offset=8 - local.tee 1 - i32.const 1 - i32.and - br_if 0 (;@3;) - end - local.get 8 - i32.const 8 - i32.add - local.set 0 - local.get 8 - local.set 3 - end - block ;; label = @2 - local.get 3 - i32.load - i32.const -4 - i32.and - local.tee 8 - local.get 0 - i32.sub - local.get 6 - i32.lt_u - br_if 0 (;@2;) - block ;; label = @3 - block ;; label = @4 - local.get 0 - i32.const 72 - i32.add - local.get 8 - local.get 6 - i32.sub - local.get 5 - i32.and - local.tee 8 - i32.le_u - br_if 0 (;@4;) - local.get 4 - local.get 0 - i32.and - br_if 2 (;@2;) - local.get 2 - local.get 1 - i32.const -4 - i32.and - i32.store - local.get 3 - i32.load - local.set 0 - local.get 3 - local.set 1 - br 1 (;@3;) - end - i32.const 0 - local.set 7 - local.get 8 - i32.const 0 - i32.store - local.get 8 - i32.const -8 - i32.add - local.tee 1 - i64.const 0 - i64.store align=4 - local.get 1 - local.get 3 - i32.load - i32.const -4 - i32.and - i32.store - block ;; label = @4 - local.get 3 - i32.load - local.tee 9 - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@4;) - local.get 9 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 8 - local.get 8 - i32.load offset=4 - i32.const 3 - i32.and - local.get 1 - i32.or - i32.store offset=4 - local.get 1 - i32.load offset=4 - i32.const 3 - i32.and - local.set 7 - end - local.get 1 - local.get 7 - local.get 3 - i32.or - i32.store offset=4 - local.get 0 - local.get 0 - i32.load - i32.const -2 - i32.and - i32.store - local.get 3 - local.get 3 - i32.load - local.tee 0 - i32.const 3 - i32.and - local.get 1 - i32.or - local.tee 8 - i32.store - block ;; label = @4 - local.get 0 - i32.const 2 - i32.and - br_if 0 (;@4;) - local.get 1 - i32.load - local.set 0 - br 1 (;@3;) - end - local.get 3 - local.get 8 - i32.const -3 - i32.and - i32.store - local.get 1 - i32.load - i32.const 2 - i32.or - local.set 0 - end - local.get 1 - local.get 0 - i32.const 1 - i32.or - i32.store - local.get 1 - i32.const 8 - i32.add - return - end - local.get 2 - local.get 1 - i32.store - local.get 1 - local.set 3 - local.get 1 - br_if 0 (;@1;) - end - i32.const 0 - ) - (func $::alloc (;11;) (type 7) (param i32 i32 i32) (result i32) - (local i32 i32 i32) - global.get $__stack_pointer - i32.const 16 - i32.sub - local.tee 3 - global.set $__stack_pointer - block ;; label = @1 - block ;; label = @2 - local.get 2 - br_if 0 (;@2;) - local.get 1 - local.set 2 - br 1 (;@1;) - end - local.get 3 - local.get 0 - i32.load - i32.store offset=12 - block ;; label = @2 - local.get 2 - i32.const 3 - i32.add - local.tee 4 - i32.const 2 - i32.shr_u - local.tee 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.tee 2 - br_if 0 (;@2;) - block ;; label = @3 - local.get 4 - i32.const -4 - i32.and - local.tee 2 - local.get 1 - i32.const 3 - i32.shl - i32.const 512 - i32.add - local.tee 4 - local.get 2 - local.get 4 - i32.gt_u - select - i32.const 65543 - i32.add - local.tee 4 - i32.const 16 - i32.shr_u - memory.grow - local.tee 2 - i32.const -1 - i32.ne - br_if 0 (;@3;) - i32.const 0 - local.set 2 - br 1 (;@2;) - end - local.get 2 - i32.const 16 - i32.shl - local.tee 2 - i32.const 0 - i32.store offset=4 - local.get 2 - local.get 3 - i32.load offset=12 - i32.store offset=8 - local.get 2 - local.get 2 - local.get 4 - i32.const -65536 - i32.and - i32.add - i32.const 2 - i32.or - i32.store - local.get 3 - local.get 2 - i32.store offset=12 - local.get 5 - local.get 1 - local.get 3 - i32.const 12 - i32.add - call $wee_alloc::alloc_first_fit - local.set 2 - end - local.get 0 - local.get 3 - i32.load offset=12 - i32.store - end - local.get 3 - i32.const 16 - i32.add - global.set $__stack_pointer - local.get 2 - ) - (func $::dealloc (;12;) (type 8) (param i32 i32 i32 i32) - (local i32 i32 i32 i32 i32 i32 i32) - block ;; label = @1 - local.get 1 - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.eqz - br_if 0 (;@1;) - local.get 0 - i32.load - local.set 4 - local.get 1 - i32.const 0 - i32.store - local.get 1 - i32.const -8 - i32.add - local.tee 3 - local.get 3 - i32.load - local.tee 5 - i32.const -2 - i32.and - local.tee 6 - i32.store - block ;; label = @2 - block ;; label = @3 - block ;; label = @4 - block ;; label = @5 - local.get 1 - i32.const -4 - i32.add - local.tee 7 - i32.load - i32.const -4 - i32.and - local.tee 8 - i32.eqz - br_if 0 (;@5;) - local.get 8 - i32.load - local.tee 9 - i32.const 1 - i32.and - br_if 0 (;@5;) - block ;; label = @6 - block ;; label = @7 - block ;; label = @8 - local.get 5 - i32.const -4 - i32.and - local.tee 10 - br_if 0 (;@8;) - local.get 8 - local.set 1 - br 1 (;@7;) - end - local.get 8 - local.set 1 - local.get 5 - i32.const 2 - i32.and - br_if 0 (;@7;) - local.get 10 - local.get 10 - i32.load offset=4 - i32.const 3 - i32.and - local.get 8 - i32.or - i32.store offset=4 - local.get 3 - i32.load - local.set 6 - local.get 7 - i32.load - local.tee 5 - i32.const -4 - i32.and - local.tee 1 - i32.eqz - br_if 1 (;@6;) - local.get 1 - i32.load - local.set 9 - end - local.get 1 - local.get 6 - i32.const -4 - i32.and - local.get 9 - i32.const 3 - i32.and - i32.or - i32.store - local.get 7 - i32.load - local.set 5 - local.get 3 - i32.load - local.set 6 - end - local.get 7 - local.get 5 - i32.const 3 - i32.and - i32.store - local.get 3 - local.get 6 - i32.const 3 - i32.and - i32.store - local.get 6 - i32.const 2 - i32.and - i32.eqz - br_if 1 (;@4;) - local.get 8 - local.get 8 - i32.load - i32.const 2 - i32.or - i32.store - br 1 (;@4;) - end - local.get 5 - i32.const 2 - i32.and - br_if 1 (;@3;) - local.get 5 - i32.const -4 - i32.and - local.tee 5 - i32.eqz - br_if 1 (;@3;) - local.get 5 - i32.load8_u - i32.const 1 - i32.and - br_if 1 (;@3;) - local.get 1 - local.get 5 - i32.load offset=8 - i32.const -4 - i32.and - i32.store - local.get 5 - local.get 3 - i32.const 1 - i32.or - i32.store offset=8 - end - local.get 4 - local.set 3 - br 1 (;@2;) - end - local.get 1 - local.get 4 - i32.store - end - local.get 0 - local.get 3 - i32.store - end - ) - (func $core::panicking::panic_fmt (;13;) (type 9) (param i32 i32) - (local i32) - global.get $__stack_pointer - i32.const 32 - i32.sub - local.tee 2 - global.set $__stack_pointer - local.get 2 - i32.const 16 - i32.add - local.get 0 - i32.const 16 - i32.add - i64.load align=4 - i64.store - local.get 2 - i32.const 8 - i32.add - local.get 0 - i32.const 8 - i32.add - i64.load align=4 - i64.store - local.get 2 - i32.const 1 - i32.store16 offset=28 - local.get 2 - local.get 1 - i32.store offset=24 - local.get 2 - local.get 0 - i64.load align=4 - i64.store - local.get 2 - call $rust_begin_unwind - unreachable - ) - (func $core::panicking::panic (;14;) (type 10) (param i32 i32 i32) - (local i32) - global.get $__stack_pointer - i32.const 32 - i32.sub - local.tee 3 - global.set $__stack_pointer - local.get 3 - i32.const 0 - i32.store offset=16 - local.get 3 - i32.const 1 - i32.store offset=4 - local.get 3 - i64.const 4 - i64.store offset=8 align=4 - local.get 3 - local.get 1 - i32.store offset=28 - local.get 3 - local.get 0 - i32.store offset=24 - local.get 3 - local.get 3 - i32.const 24 - i32.add - i32.store - local.get 3 - local.get 2 - call $core::panicking::panic_fmt - unreachable - ) - (func $cabi_realloc (;15;) (type 3) (param i32 i32 i32 i32) (result i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - call $wit_bindgen_rt::cabi_realloc - ) - (table (;0;) 3 3 funcref) - (memory (;0;) 17) - (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) - (export "memory" (memory 0)) - (export "miden:base/core-types@1.0.0#account-id-from-felt" (func $miden:base/core-types@1.0.0#account-id-from-felt)) - (export "miden:base/types@1.0.0#from-core-asset" (func $miden:base/types@1.0.0#from-core-asset)) - (export "miden:base/types@1.0.0#to-core-asset" (func $miden:base/types@1.0.0#to-core-asset)) - (export "cabi_realloc" (func $cabi_realloc)) - (export "cabi_realloc_wit_bindgen_0_28_0" (func $wit_bindgen_rt::cabi_realloc)) - (elem (;0;) (i32.const 1) func $miden_sdk::bindings::__link_custom_section_describing_imports $cabi_realloc) - (data $.rodata (;0;) (i32.const 1048576) "\01\00\00\00\01\00\00\00not yet implementedsrc/lib.rs\00\00\00\1b\00\10\00\0a\00\00\00!\00\00\00\09\00\00\00\1b\00\10\00\0a\00\00\00%\00\00\00\09\00\00\00\02\00\00\00") - ) - (core instance (;0;) (instantiate 0)) - (alias core export 0 "memory" (core memory (;0;))) - (alias core export 0 "cabi_realloc" (core func (;0;))) - (type (;0;) (record (field "inner" u64))) - (type (;1;) (record (field "inner" 0))) - (type (;2;) (func (param "felt" 0) (result 1))) - (alias core export 0 "miden:base/core-types@1.0.0#account-id-from-felt" (core func (;1;))) - (func (;0;) (type 2) (canon lift (core func 1))) - (component (;0;) - (type (;0;) (record (field "inner" u64))) - (import "import-type-felt" (type (;1;) (eq 0))) - (type (;2;) (record (field "inner" 1))) - (import "import-type-account-id" (type (;3;) (eq 2))) - (type (;4;) (func (param "felt" 1) (result 3))) - (import "import-func-account-id-from-felt" (func (;0;) (type 4))) - (type (;5;) (record (field "inner" u64))) - (export (;6;) "felt" (type 5)) - (type (;7;) (tuple 6 6 6 6)) - (export (;8;) "word" (type 7)) - (type (;9;) (record (field "inner" 6))) - (export (;10;) "account-id" (type 9)) - (type (;11;) (record (field "inner" 8))) - (export (;12;) "recipient" (type 11)) - (type (;13;) (record (field "inner" 6))) - (export (;14;) "tag" (type 13)) - (type (;15;) (record (field "inner" 8))) - (export (;16;) "core-asset" (type 15)) - (type (;17;) (record (field "inner" 6))) - (export (;18;) "nonce" (type 17)) - (type (;19;) (record (field "inner" 8))) - (export (;20;) "account-hash" (type 19)) - (type (;21;) (record (field "inner" 8))) - (export (;22;) "block-hash" (type 21)) - (type (;23;) (record (field "inner" 8))) - (export (;24;) "storage-value" (type 23)) - (type (;25;) (record (field "inner" 8))) - (export (;26;) "storage-root" (type 25)) - (type (;27;) (record (field "inner" 8))) - (export (;28;) "account-code-root" (type 27)) - (type (;29;) (record (field "inner" 8))) - (export (;30;) "vault-commitment" (type 29)) - (type (;31;) (record (field "inner" 6))) - (export (;32;) "note-id" (type 31)) - (type (;33;) (func (param "felt" 6) (result 10))) - (export (;1;) "account-id-from-felt" (func 0) (func (type 33))) - ) - (instance (;0;) (instantiate 0 - (with "import-func-account-id-from-felt" (func 0)) - (with "import-type-felt" (type 0)) - (with "import-type-account-id" (type 1)) - ) - ) - (export (;1;) "miden:base/core-types@1.0.0" (instance 0)) - (alias export 1 "core-asset" (type (;3;))) - (alias export 1 "account-id" (type (;4;))) - (type (;5;) (record (field "asset" 4) (field "amount" u64))) - (alias export 1 "word" (type (;6;))) - (type (;7;) (record (field "inner" 6))) - (type (;8;) (variant (case "fungible" 5) (case "non-fungible" 7))) - (type (;9;) (func (param "core-asset" 3) (result 8))) - (alias core export 0 "miden:base/types@1.0.0#from-core-asset" (core func (;2;))) - (func (;1;) (type 9) (canon lift (core func 2) (memory 0))) - (type (;10;) (func (param "asset" 8) (result 3))) - (alias core export 0 "miden:base/types@1.0.0#to-core-asset" (core func (;3;))) - (func (;2;) (type 10) (canon lift (core func 3) (memory 0))) - (alias export 1 "felt" (type (;11;))) - (component (;1;) - (type (;0;) (record (field "inner" u64))) - (import "import-type-felt" (type (;1;) (eq 0))) - (type (;2;) (record (field "inner" 1))) - (import "import-type-account-id" (type (;3;) (eq 2))) - (type (;4;) (tuple 1 1 1 1)) - (import "import-type-word" (type (;5;) (eq 4))) - (type (;6;) (record (field "inner" 5))) - (import "import-type-core-asset" (type (;7;) (eq 6))) - (import "import-type-core-asset0" (type (;8;) (eq 7))) - (import "import-type-account-id0" (type (;9;) (eq 3))) - (type (;10;) (record (field "asset" 9) (field "amount" u64))) - (import "import-type-fungible-asset" (type (;11;) (eq 10))) - (import "import-type-word0" (type (;12;) (eq 5))) - (type (;13;) (record (field "inner" 12))) - (import "import-type-non-fungible-asset" (type (;14;) (eq 13))) - (type (;15;) (variant (case "fungible" 11) (case "non-fungible" 14))) - (import "import-type-asset" (type (;16;) (eq 15))) - (type (;17;) (func (param "core-asset" 8) (result 16))) - (import "import-func-from-core-asset" (func (;0;) (type 17))) - (type (;18;) (func (param "asset" 16) (result 8))) - (import "import-func-to-core-asset" (func (;1;) (type 18))) - (export (;19;) "felt" (type 1)) - (export (;20;) "account-id" (type 3)) - (export (;21;) "word" (type 5)) - (export (;22;) "core-asset" (type 7)) - (type (;23;) (record (field "asset" 20) (field "amount" u64))) - (export (;24;) "fungible-asset" (type 23)) - (type (;25;) (record (field "inner" 21))) - (export (;26;) "non-fungible-asset" (type 25)) - (type (;27;) (variant (case "fungible" 24) (case "non-fungible" 26))) - (export (;28;) "asset" (type 27)) - (type (;29;) (func (param "core-asset" 22) (result 28))) - (export (;2;) "from-core-asset" (func 0) (func (type 29))) - (type (;30;) (func (param "asset" 28) (result 22))) - (export (;3;) "to-core-asset" (func 1) (func (type 30))) - ) - (instance (;2;) (instantiate 1 - (with "import-func-from-core-asset" (func 1)) - (with "import-func-to-core-asset" (func 2)) - (with "import-type-felt" (type 11)) - (with "import-type-account-id" (type 4)) - (with "import-type-word" (type 6)) - (with "import-type-core-asset" (type 3)) - (with "import-type-core-asset0" (type 3)) - (with "import-type-account-id0" (type 4)) - (with "import-type-fungible-asset" (type 5)) - (with "import-type-word0" (type 6)) - (with "import-type-non-fungible-asset" (type 7)) - (with "import-type-asset" (type 8)) - ) - ) - (export (;3;) "miden:base/types@1.0.0" (instance 2)) -) \ No newline at end of file diff --git a/tests/integration/expected/xor_bool.hir b/tests/integration/expected/xor_bool.hir index fe223934e..b43860061 100644 --- a/tests/integration/expected/xor_bool.hir +++ b/tests/integration/expected/xor_bool.hir @@ -1,23 +1,21 @@ -(component - ;; Modules - (module #test_rust_bf1daa4716c09a8fc6f8362a72042139765ebe6efcfdd17e817a70b2a495f6b1 - ;; Constants - (const (id 0) 0x00100000) +builtin.component root_ns:root@1.0.0 { + builtin.module public @test_rust_12478c1c469cdb8e5c0d08223b9ea0b6f7c1cade83ed772fb79969d2af3004cb { + public builtin.function @entrypoint(v0: i32, v1: i32) -> i32 { + ^block6(v0: i32, v1: i32): + v3 = arith.bxor v0, v1 : i32; + builtin.ret v3; + }; - ;; Global Variables - (global (export #__stack_pointer) (id 0) (type i32) (const 0)) - (global (export #gv1) (id 1) (type i32) (const 0)) - (global (export #gv2) (id 2) (type i32) (const 0)) + builtin.global_variable private @#__stack_pointer : i32 { + builtin.ret_imm 1048576; + }; - ;; Functions - (func (export #entrypoint) (param i32) (param i32) (result i32) - (block 0 (param v0 i32) (param v1 i32) - (let (v3 i32) (bxor v0 v1)) - (br (block 1 v3))) + builtin.global_variable public @#gv1 : i32 { + builtin.ret_imm 1048576; + }; - (block 1 (param v2 i32) - (ret v2)) - ) - ) - -) + builtin.global_variable public @#gv2 : i32 { + builtin.ret_imm 1048576; + }; + }; +}; \ No newline at end of file diff --git a/tests/integration/expected/xor_bool.masm b/tests/integration/expected/xor_bool.masm index 746d91b19..1d47abe9e 100644 --- a/tests/integration/expected/xor_bool.masm +++ b/tests/integration/expected/xor_bool.masm @@ -1,7 +1,24 @@ -# mod test_rust_bf1daa4716c09a8fc6f8362a72042139765ebe6efcfdd17e817a70b2a495f6b1 +# mod root_ns:root@1.0.0 -export.entrypoint - swap.1 u32xor +proc.init + push.1179648 + trace.240 + exec.::intrinsics::mem::heap_init + trace.252 + push.1048576 + u32assert + mem_store.278528 + push.1048576 + u32assert + mem_store.278529 + push.1048576 + u32assert + mem_store.278530 end +# mod root_ns:root@1.0.0::test_rust_12478c1c469cdb8e5c0d08223b9ea0b6f7c1cade83ed772fb79969d2af3004cb + +export.entrypoint + u32xor +end diff --git a/tests/integration/expected/xor_bool.wat b/tests/integration/expected/xor_bool.wat index f4e93507b..8b4729b59 100644 --- a/tests/integration/expected/xor_bool.wat +++ b/tests/integration/expected/xor_bool.wat @@ -1,10 +1,5 @@ -(module $test_rust_bf1daa4716c09a8fc6f8362a72042139765ebe6efcfdd17e817a70b2a495f6b1.wasm +(module $test_rust_12478c1c469cdb8e5c0d08223b9ea0b6f7c1cade83ed772fb79969d2af3004cb.wasm (type (;0;) (func (param i32 i32) (result i32))) - (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.xor - ) (memory (;0;) 16) (global $__stack_pointer (;0;) (mut i32) i32.const 1048576) (global (;1;) i32 i32.const 1048576) @@ -13,4 +8,9 @@ (export "entrypoint" (func $entrypoint)) (export "__data_end" (global 1)) (export "__heap_base" (global 2)) -) \ No newline at end of file + (func $entrypoint (;0;) (type 0) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.xor + ) +) diff --git a/tests/integration/src/cargo_proj/mod.rs b/tests/integration/src/cargo_proj/mod.rs index d58feef00..29ad586a7 100644 --- a/tests/integration/src/cargo_proj/mod.rs +++ b/tests/integration/src/cargo_proj/mod.rs @@ -33,12 +33,12 @@ pub fn panic_error(what: &str, err: impl Into) -> ! { pe(what, err); #[track_caller] fn pe(what: &str, err: anyhow::Error) -> ! { - let mut result = format!("{}\nerror: {}", what, err); + let mut result = format!("{what}\nerror: {err}"); for cause in err.chain().skip(1) { let _ = writeln!(result, "\nCaused by:"); - let _ = write!(result, "{}", cause); + let _ = write!(result, "{cause}"); } - panic!("\n{}", result); + panic!("\n{result}"); } } @@ -251,14 +251,23 @@ impl ProjectBuilder { } fn skip_rust_compilation(&self, artifact_name: &str) -> bool { - let computed_artifact_path = self + let computed_artifact_path_wuu = self .root() .join("target") .join("wasm32-unknown-unknown") .join("release") .join(artifact_name) .with_extension("wasm"); - if std::env::var("SKIP_RUST").is_ok() && computed_artifact_path.exists() { + let computed_artifact_path_ww = self + .root() + .join("target") + .join("wasm32-wasip2") + .join("release") + .join(artifact_name) + .with_extension("wasm"); + if std::env::var("SKIP_RUST").is_ok() + && (computed_artifact_path_wuu.exists() || computed_artifact_path_ww.exists()) + { eprintln!("Skipping Rust compilation"); true } else { @@ -352,7 +361,7 @@ impl Project { let dst = self.root().join(dst.as_ref()); { if let Err(e) = os::unix::fs::symlink(&src, &dst) { - panic!("failed to symlink {:?} to {:?}: {:?}", src, dst, e); + panic!("failed to symlink {src:?} to {dst:?}: {e:?}"); } } } @@ -384,12 +393,11 @@ pub fn basic_manifest(name: &str, version: &str) -> String { format!( r#" [package] - name = "{}" - version = "{}" + name = "{name}" + version = "{version}" authors = [] edition = "2021" - "#, - name, version + "# ) } @@ -398,16 +406,15 @@ pub fn basic_bin_manifest(name: &str) -> String { r#" [package] - name = "{}" + name = "{name}" version = "0.5.0" authors = ["wycats@example.com"] edition = "2021" [[bin]] - name = "{}" - "#, - name, name + name = "{name}" + "# ) } @@ -416,16 +423,15 @@ pub fn basic_lib_manifest(name: &str) -> String { r#" [package] - name = "{}" + name = "{name}" version = "0.5.0" authors = ["wycats@example.com"] edition = "2021" [lib] - name = "{}" - "#, - name, name + name = "{name}" + "# ) } diff --git a/tests/integration/src/cargo_proj/paths.rs b/tests/integration/src/cargo_proj/paths.rs index 1a0c9eb6f..efab588e1 100644 --- a/tests/integration/src/cargo_proj/paths.rs +++ b/tests/integration/src/cargo_proj/paths.rs @@ -35,7 +35,7 @@ impl CargoPathExt for Path { if e.kind() == ErrorKind::NotFound { return; } - panic!("failed to remove {:?}, could not read: {:?}", self, e); + panic!("failed to remove {self:?}, could not read: {e:?}"); } }; // There is a race condition between fetching the metadata and @@ -43,10 +43,10 @@ impl CargoPathExt for Path { // for our tests. if meta.is_dir() { if let Err(e) = fs::remove_dir_all(self) { - panic!("failed to remove {:?}: {:?}", self, e) + panic!("failed to remove {self:?}: {e:?}") } } else if let Err(e) = fs::remove_file(self) { - panic!("failed to remove {:?}: {:?}", self, e) + panic!("failed to remove {self:?}: {e:?}") } } @@ -145,7 +145,7 @@ where pub fn get_lib_filename(name: &str, kind: &str) -> String { let prefix = get_lib_prefix(kind); let extension = get_lib_extension(kind); - format!("{}{}.{}", prefix, name, extension) + format!("{prefix}{name}.{extension}") } pub fn get_lib_prefix(kind: &str) -> &str { diff --git a/tests/integration/src/codegen/intrinsics/mem.rs b/tests/integration/src/codegen/intrinsics/mem.rs new file mode 100644 index 000000000..192e1d311 --- /dev/null +++ b/tests/integration/src/codegen/intrinsics/mem.rs @@ -0,0 +1,961 @@ +use std::borrow::Cow; + +use midenc_debug::ToMidenRepr; +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_hir::HirOpBuilder; +use midenc_hir::{ + dialects::builtin::BuiltinOpBuilder, AbiParam, Felt, PointerType, Signature, SourceSpan, Type, + ValueRef, +}; +use proptest::{ + prelude::any, + prop_assert_eq, + test_runner::{TestCaseError, TestError, TestRunner}, +}; + +use crate::testing::*; + +/// Tests the memory load intrinsic for aligned loads of single-word (i.e. 32-bit) values +#[test] +fn load_sw() { + setup::enable_compiler_instrumentation(); + + // Write address to use + let write_to = 17 * 2u32.pow(16); + + // Generate a `test` module with `main` function that invokes `load_sw` when lowered to MASM + let signature = Signature::new( + [AbiParam::new(Type::from(PointerType::new(Type::U32)))], + [AbiParam::new(Type::U32)], + ); + + // Compile once outside the test loop + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + // Get the input pointer, and load the value at that address + let ptr = block.borrow().arguments()[0] as ValueRef; + let loaded = builder.load(ptr, SourceSpan::default()).unwrap(); + // Return the value so we can assert that the output of execution matches + builder.ret(Some(loaded), SourceSpan::default()).unwrap(); + }); + + let config = proptest::test_runner::Config::with_cases(10); + let res = TestRunner::new(config).run(&any::(), move |value| { + // Write `value` to the start of the 17th page (1 page after the 16 pages reserved for the + // Rust stack) + let value_bytes = value.to_ne_bytes(); + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &value_bytes, + }]; + + let args = [Felt::new(write_to as u64)]; + let output = + eval_package::(&package, initializers, &args, context.session(), |trace| { + let stored = trace.read_from_rust_memory::(write_to).ok_or_else(|| { + TestCaseError::fail(format!( + "expected {value} to have been written to byte address {write_to}, but \ + read from that address failed" + )) + })?; + prop_assert_eq!( + stored, + value, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + value, + write_to, + stored + ); + Ok(()) + })?; + + prop_assert_eq!(output, value); + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} + +/// Tests the memory load intrinsic for aligned loads of double-word (i.e. 64-bit) values +#[test] +fn load_dw() { + setup::enable_compiler_instrumentation(); + + // Write address to use + let write_to = 17 * 2u32.pow(16); + + // Generate a `test` module with `main` function that invokes `load_dw` when lowered to MASM + let signature = Signature::new( + [AbiParam::new(Type::from(PointerType::new(Type::U64)))], + [AbiParam::new(Type::U64)], + ); + + // Compile once outside the test loop + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + // Get the input pointer, and load the value at that address + let ptr = block.borrow().arguments()[0] as ValueRef; + let loaded = builder.load(ptr, SourceSpan::default()).unwrap(); + // Return the value so we can assert that the output of execution matches + builder.ret(Some(loaded), SourceSpan::default()).unwrap(); + }); + + let config = proptest::test_runner::Config::with_cases(10); + let res = TestRunner::new(config).run(&any::(), move |value| { + // Write `value` to the start of the 17th page (1 page after the 16 pages reserved for the + // Rust stack) + let value_felts = value.to_felts(); + let initializers = [Initializer::MemoryFelts { + addr: write_to / 4, + felts: Cow::Borrowed(value_felts.as_slice()), + }]; + + let args = [Felt::new(write_to as u64)]; + let output = + eval_package::(&package, initializers, &args, context.session(), |trace| { + let hi = + trace.read_memory_element(write_to / 4).unwrap_or_default().as_int() as u32; + let lo = trace.read_memory_element((write_to / 4) + 1).unwrap_or_default().as_int() + as u32; + log::trace!(target: "executor", "hi = {hi} ({hi:0x})"); + log::trace!(target: "executor", "lo = {lo} ({lo:0x})"); + let stored = trace.read_from_rust_memory::(write_to).ok_or_else(|| { + TestCaseError::fail(format!( + "expected {value} to have been written to byte address {write_to}, but \ + read from that address failed" + )) + })?; + prop_assert_eq!( + stored, + value, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + value, + write_to, + stored + ); + Ok(()) + })?; + + prop_assert_eq!(output, value); + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} + +/// Tests the memory load intrinsic for loads of single-byte (i.e. 8-bit) values +#[test] +fn load_u8() { + setup::enable_compiler_instrumentation(); + + // Write address to use + let write_to = 17 * 2u32.pow(16); + + // Generate a `test` module with `main` function that invokes load for u8 when lowered to MASM + let signature = Signature::new( + [AbiParam::new(Type::from(PointerType::new(Type::U8)))], + [AbiParam::new(Type::U8)], + ); + + // Compile once outside the test loop + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + // Get the input pointer, and load the value at that address + let ptr = block.borrow().arguments()[0] as ValueRef; + let loaded = builder.load(ptr, SourceSpan::default()).unwrap(); + // Return the value so we can assert that the output of execution matches + builder.ret(Some(loaded), SourceSpan::default()).unwrap(); + }); + + let config = proptest::test_runner::Config::with_cases(10); + let res = TestRunner::new(config).run(&any::(), move |value| { + // Write `value` to the start of the 17th page (1 page after the 16 pages reserved for the + // Rust stack) + let value_bytes = [value]; + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &value_bytes, + }]; + + let args = [Felt::new(write_to as u64)]; + let output = + eval_package::(&package, initializers, &args, context.session(), |trace| { + let stored = trace.read_from_rust_memory::(write_to).ok_or_else(|| { + TestCaseError::fail(format!( + "expected {value} to have been written to byte address {write_to}, but \ + read from that address failed" + )) + })?; + prop_assert_eq!( + stored, + value, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + value, + write_to, + stored + ); + Ok(()) + })?; + + prop_assert_eq!(output, value); + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} + +/// Tests the memory load intrinsic for loads of 16-bit (u16) values +#[test] +fn load_u16() { + setup::enable_compiler_instrumentation(); + + // Write address to use + let write_to = 17 * 2u32.pow(16); + + // Generate a `test` module with `main` function that invokes load for u16 when lowered to MASM + let signature = Signature::new( + [AbiParam::new(Type::from(PointerType::new(Type::U16)))], + [AbiParam::new(Type::U16)], + ); + + // Compile once outside the test loop + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + // Get the input pointer, and load the value at that address + let ptr = block.borrow().arguments()[0] as ValueRef; + let loaded = builder.load(ptr, SourceSpan::default()).unwrap(); + // Return the value so we can assert that the output of execution matches + builder.ret(Some(loaded), SourceSpan::default()).unwrap(); + }); + + let config = proptest::test_runner::Config::with_cases(10); + let res = TestRunner::new(config).run(&any::(), move |value| { + // Write `value` to the start of the 17th page (1 page after the 16 pages reserved for the + // Rust stack) + let value_bytes = value.to_ne_bytes(); + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &value_bytes, + }]; + + let args = [Felt::new(write_to as u64)]; + let output = + eval_package::(&package, initializers, &args, context.session(), |trace| { + let stored = trace.read_from_rust_memory::(write_to).ok_or_else(|| { + TestCaseError::fail(format!( + "expected {value} to have been written to byte address {write_to}, but \ + read from that address failed" + )) + })?; + prop_assert_eq!( + stored, + value, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + value, + write_to, + stored + ); + Ok(()) + })?; + + prop_assert_eq!(output, value); + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} + +/// Tests the memory load intrinsic for loads of boolean (i.e. 1-bit) values +#[test] +fn load_bool() { + setup::enable_compiler_instrumentation(); + + // Write address to use + let write_to = 17 * 2u32.pow(16); + + // Generate a `test` module with `main` function that invokes load for bool when lowered to MASM + let signature = Signature::new( + [AbiParam::new(Type::from(PointerType::new(Type::I1)))], + [AbiParam::new(Type::I1)], + ); + + // Compile once outside the test loop + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + // Get the input pointer, and load the value at that address + let ptr = block.borrow().arguments()[0] as ValueRef; + let loaded = builder.load(ptr, SourceSpan::default()).unwrap(); + // Return the value so we can assert that the output of execution matches + builder.ret(Some(loaded), SourceSpan::default()).unwrap(); + }); + + let config = proptest::test_runner::Config::with_cases(10); + let res = TestRunner::new(config).run(&any::(), move |value| { + // Write `value` to the start of the 17th page (1 page after the 16 pages reserved for the + // Rust stack) + let value_bytes = [value as u8]; + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &value_bytes, + }]; + + let args = [Felt::new(write_to as u64)]; + let output = eval_package::( + &package, + initializers, + &args, + context.session(), + |trace| { + let stored = trace.read_from_rust_memory::(write_to).ok_or_else(|| { + TestCaseError::fail(format!( + "expected {value} to have been written to byte address {write_to}, but \ + read from that address failed" + )) + })?; + let stored_bool = stored != 0; + prop_assert_eq!( + stored_bool, + value, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + value, + write_to, + stored_bool + ); + Ok(()) + }, + )?; + + prop_assert_eq!(output, value); + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} + +/// Tests that u16 stores only affect the targeted 2 bytes and don't corrupt surrounding memory +#[test] +fn store_u16() { + setup::enable_compiler_instrumentation(); + + // Use the start of the 17th page (1 page after the 16 pages reserved for the Rust stack) + let write_to = 17 * 2u32.pow(16); + + // Generate a `test` module with `main` function that stores two u16 values + let signature = Signature::new( + [AbiParam::new(Type::U16), AbiParam::new(Type::U16)], + [AbiParam::new(Type::U32)], // Return u32 to satisfy test infrastructure + ); + + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + let (value1, value2) = { + let block_ref = block.borrow(); + let args = block_ref.arguments(); + (args[0] as ValueRef, args[1] as ValueRef) + }; + + // Create pointer to the base address + let base_addr = builder.u32(write_to, SourceSpan::default()); + let ptr_u16 = builder + .inttoptr(base_addr, Type::from(PointerType::new(Type::U16)), SourceSpan::default()) + .unwrap(); + + // Store first u16 at offset 0 + builder.store(ptr_u16, value1, SourceSpan::default()).unwrap(); + + // After first store, load back the u16 value at offset 0 + let loaded1_after_store1 = builder.load(ptr_u16, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded1_after_store1, value1, SourceSpan::default()).unwrap(); + + // Load u16 at offset 2 (should still be unchanged - 0xCCDD) + let addr_plus_2 = builder.u32(write_to + 2, SourceSpan::default()); + let ptr_u16_offset2 = builder + .inttoptr(addr_plus_2, Type::from(PointerType::new(Type::U16)), SourceSpan::default()) + .unwrap(); + let loaded2_before_store2 = builder.load(ptr_u16_offset2, SourceSpan::default()).unwrap(); + let expected_initial_at_2 = builder.u16(0xccdd, SourceSpan::default()); + builder + .assert_eq(loaded2_before_store2, expected_initial_at_2, SourceSpan::default()) + .unwrap(); + + // Now store second u16 at offset 2 + builder.store(ptr_u16_offset2, value2, SourceSpan::default()).unwrap(); + + // After second store, load both u16 values to verify they are correct + // Load u16 at offset 0 (should still be value1) + let loaded1_after_store2 = builder.load(ptr_u16, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded1_after_store2, value1, SourceSpan::default()).unwrap(); + + // Load u16 at offset 2 (should now be value2) + let loaded2_after_store2 = builder.load(ptr_u16_offset2, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded2_after_store2, value2, SourceSpan::default()).unwrap(); + + // Return a constant to satisfy test infrastructure + let result = builder.u32(1, SourceSpan::default()); + builder.ret(Some(result), SourceSpan::default()).unwrap(); + }); + + let config = proptest::test_runner::Config::with_cases(32); + let res = TestRunner::new(config).run( + &(any::(), any::()), + move |(store_value1, store_value2)| { + // Initialize memory with a pattern that's different from what we'll write + // This helps us detect any unintended modifications + // Pattern: [0xFF, 0xEE, 0xDD, 0xCC, 0x11, 0x22, 0x33, 0x44] + let initial_bytes = [0xff, 0xee, 0xdd, 0xcc, 0x11, 0x22, 0x33, 0x44]; + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &initial_bytes, + }]; + + // Note: Arguments are pushed in reverse order on the stack in Miden + let args = [Felt::new(store_value2 as u64), Felt::new(store_value1 as u64)]; + let output = eval_package::( + &package, + initializers, + &args, + context.session(), + |trace| { + // The trace callback runs after execution + // All assertions in the program passed, so we know: + // 1. After first store, bytes 0-1 contain value1, bytes 2-3 are unchanged + // 2. After second store, bytes 2-3 contain value2, bytes 0-1 still contain value1 + + // Read final memory state for verification + // Since trace reader requires 4-byte alignment, read the full word and extract u16 values + let word0 = trace.read_from_rust_memory::(write_to).ok_or_else(|| { + TestCaseError::fail(format!("failed to read from byte address {write_to}")) + })?; + + // Extract u16 values from the 32-bit word (little-endian) + let stored1 = (word0 & 0xffff) as u16; + let stored2 = ((word0 >> 16) & 0xffff) as u16; + + prop_assert_eq!( + stored1, + store_value1, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + store_value1, + write_to, + stored1 + ); + + prop_assert_eq!( + stored2, + store_value2, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + store_value2, + write_to + 2, + stored2 + ); + + Ok(()) + }, + )?; + + prop_assert_eq!(output, 1u32); + + Ok(()) + }, + ); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} + +/// Tests that u8 stores only affect the targeted byte and don't corrupt surrounding memory +#[test] +fn store_u8() { + setup::enable_compiler_instrumentation(); + + // Use the start of the 17th page (1 page after the 16 pages reserved for the Rust stack) + let write_to = 17 * 2u32.pow(16); + + // Generate a `test` module with `main` function that stores four u8 values + let signature = Signature::new( + [ + AbiParam::new(Type::U8), + AbiParam::new(Type::U8), + AbiParam::new(Type::U8), + AbiParam::new(Type::U8), + ], + [AbiParam::new(Type::U32)], // Return u32 to satisfy test infrastructure + ); + + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + let (value0, value1, value2, value3) = { + let block_ref = block.borrow(); + let args = block_ref.arguments(); + ( + args[0] as ValueRef, + args[1] as ValueRef, + args[2] as ValueRef, + args[3] as ValueRef, + ) + }; + + // Create pointer to the base address + let base_addr = builder.u32(write_to, SourceSpan::default()); + let ptr_u8 = builder + .inttoptr(base_addr, Type::from(PointerType::new(Type::U8)), SourceSpan::default()) + .unwrap(); + + // Store first u8 at offset 0 + builder.store(ptr_u8, value0, SourceSpan::default()).unwrap(); + + // After first store, verify byte at offset 0 changed + let loaded0_after_store0 = builder.load(ptr_u8, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded0_after_store0, value0, SourceSpan::default()).unwrap(); + + // Verify other bytes remain unchanged + // Check byte at offset 1 (should still be 0xEE) + let addr_plus_1 = builder.u32(write_to + 1, SourceSpan::default()); + let ptr_u8_offset1 = builder + .inttoptr(addr_plus_1, Type::from(PointerType::new(Type::U8)), SourceSpan::default()) + .unwrap(); + let loaded1_before_store1 = builder.load(ptr_u8_offset1, SourceSpan::default()).unwrap(); + let expected_initial_at_1 = builder.u8(0xee, SourceSpan::default()); + builder + .assert_eq(loaded1_before_store1, expected_initial_at_1, SourceSpan::default()) + .unwrap(); + + // Store second u8 at offset 1 + builder.store(ptr_u8_offset1, value1, SourceSpan::default()).unwrap(); + + // After second store, verify both bytes have correct values + let loaded0_after_store1 = builder.load(ptr_u8, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded0_after_store1, value0, SourceSpan::default()).unwrap(); + + let loaded1_after_store1 = builder.load(ptr_u8_offset1, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded1_after_store1, value1, SourceSpan::default()).unwrap(); + + // Check byte at offset 2 (should still be 0xDD) + let addr_plus_2 = builder.u32(write_to + 2, SourceSpan::default()); + let ptr_u8_offset2 = builder + .inttoptr(addr_plus_2, Type::from(PointerType::new(Type::U8)), SourceSpan::default()) + .unwrap(); + let loaded2_before_store2 = builder.load(ptr_u8_offset2, SourceSpan::default()).unwrap(); + let expected_initial_at_2 = builder.u8(0xdd, SourceSpan::default()); + builder + .assert_eq(loaded2_before_store2, expected_initial_at_2, SourceSpan::default()) + .unwrap(); + + // Store third u8 at offset 2 + builder.store(ptr_u8_offset2, value2, SourceSpan::default()).unwrap(); + + // After third store, verify first three bytes have correct values + let loaded0_after_store2 = builder.load(ptr_u8, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded0_after_store2, value0, SourceSpan::default()).unwrap(); + + let loaded1_after_store2 = builder.load(ptr_u8_offset1, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded1_after_store2, value1, SourceSpan::default()).unwrap(); + + let loaded2_after_store2 = builder.load(ptr_u8_offset2, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded2_after_store2, value2, SourceSpan::default()).unwrap(); + + // Check byte at offset 3 (should still be 0xCC) + let addr_plus_3 = builder.u32(write_to + 3, SourceSpan::default()); + let ptr_u8_offset3 = builder + .inttoptr(addr_plus_3, Type::from(PointerType::new(Type::U8)), SourceSpan::default()) + .unwrap(); + let loaded3_before_store3 = builder.load(ptr_u8_offset3, SourceSpan::default()).unwrap(); + let expected_initial_at_3 = builder.u8(0xcc, SourceSpan::default()); + builder + .assert_eq(loaded3_before_store3, expected_initial_at_3, SourceSpan::default()) + .unwrap(); + + // Store fourth u8 at offset 3 + builder.store(ptr_u8_offset3, value3, SourceSpan::default()).unwrap(); + + // After fourth store, verify all four bytes have correct values + let loaded0_after_store3 = builder.load(ptr_u8, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded0_after_store3, value0, SourceSpan::default()).unwrap(); + + let loaded1_after_store3 = builder.load(ptr_u8_offset1, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded1_after_store3, value1, SourceSpan::default()).unwrap(); + + let loaded2_after_store3 = builder.load(ptr_u8_offset2, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded2_after_store3, value2, SourceSpan::default()).unwrap(); + + let loaded3_after_store3 = builder.load(ptr_u8_offset3, SourceSpan::default()).unwrap(); + builder.assert_eq(loaded3_after_store3, value3, SourceSpan::default()).unwrap(); + + // Return a constant to satisfy test infrastructure + let result = builder.u32(1, SourceSpan::default()); + builder.ret(Some(result), SourceSpan::default()).unwrap(); + }); + + let config = proptest::test_runner::Config::with_cases(32); + let res = TestRunner::new(config).run( + &(any::(), any::(), any::(), any::()), + move |(store_value0, store_value1, store_value2, store_value3)| { + // Initialize memory with a pattern that's different from what we'll write + // This helps us detect any unintended modifications + // Pattern: [0xFF, 0xEE, 0xDD, 0xCC] for the first word only + let initial_bytes = [0xff, 0xee, 0xdd, 0xcc]; + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &initial_bytes, + }]; + + // Note: Arguments are pushed in reverse order on the stack in Miden + let args = [ + Felt::new(store_value3 as u64), + Felt::new(store_value2 as u64), + Felt::new(store_value1 as u64), + Felt::new(store_value0 as u64), + ]; + let output = eval_package::( + &package, + initializers, + &args, + context.session(), + |trace| { + // The trace callback runs after execution + // All assertions in the program passed, so we know each store only affected its target byte + + // Read final memory state for verification + let word0 = trace.read_from_rust_memory::(write_to).ok_or_else(|| { + TestCaseError::fail(format!("failed to read from byte address {write_to}")) + })?; + + // Extract u8 values from the 32-bit word (little-endian) + let stored0 = (word0 & 0xff) as u8; + let stored1 = ((word0 >> 8) & 0xff) as u8; + let stored2 = ((word0 >> 16) & 0xff) as u8; + let stored3 = ((word0 >> 24) & 0xff) as u8; + + prop_assert_eq!( + stored0, + store_value0, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + store_value0, + write_to, + stored0 + ); + + prop_assert_eq!( + stored1, + store_value1, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + store_value1, + write_to + 1, + stored1 + ); + + prop_assert_eq!( + stored2, + store_value2, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + store_value2, + write_to + 2, + stored2 + ); + + prop_assert_eq!( + stored3, + store_value3, + "expected {} to have been written to byte address {}, but found {} there \ + instead", + store_value3, + write_to + 3, + stored3 + ); + + Ok(()) + }, + )?; + + prop_assert_eq!(output, 1u32); + + Ok(()) + }, + ); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} + +#[test] +fn store_unaligned_u32() { + // Use the start of the 17th page (1 page after the 16 pages reserved for the Rust stack) + let write_to = 17 * 2u32.pow(16); + let write_val = 0xddccbbaa_u32; // Little-endian bytes will be [AA BB CC DD]. + + // Generate a `test` module with `main` function that stores to a u32 offset. + let signature = Signature::new( + [AbiParam::new(Type::U32)], + [AbiParam::new(Type::U32)], // Return u32 to satisfy test infrastructure + ); + + // Compile once outside the test loop + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + let idx_val = block.borrow().arguments()[0] as ValueRef; + + // Set base pointer, add argument offset to it. + let base_addr = builder.u32(write_to, SourceSpan::default()); + let write_addr = builder.add(base_addr, idx_val, SourceSpan::default()).unwrap(); + let ptr = builder + .inttoptr(write_addr, Type::from(PointerType::new(Type::U32)), SourceSpan::default()) + .unwrap(); + + // Store test value to pointer. + let write_val = builder.u32(write_val, SourceSpan::default()); + builder.store(ptr, write_val, SourceSpan::default()).unwrap(); + + // Return a constant to satisfy test infrastructure + let result = builder.u32(1, SourceSpan::default()); + builder.ret(Some(result), SourceSpan::default()).unwrap(); + }); + + let run_test = |offs: u32, expected0: u32, expected1: u32| { + // Initialise memory with some known bytes. + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], + }]; + + let output = eval_package::( + &package, + initializers, + &[Felt::new(offs as u64)], + context.session(), + |trace| { + // Get the overwritten words. + let word0 = trace.read_from_rust_memory::(write_to).unwrap(); + let word1 = trace.read_from_rust_memory::(write_to + 4).unwrap(); + + eprintln!("word0: 0x{word0:0>8x}"); + eprintln!("word1: 0x{word1:0>8x}"); + + assert_eq!( + word0, expected0, + "expected 1st overwritten word to be {expected0}, got {word0}, with offset \ + {offs}" + ); + + assert_eq!( + word1, expected1, + "expected 2nd overwritten word to be {expected1}, got {word1}, with offset \ + {offs}" + ); + + Ok(()) + }, + ) + .unwrap(); + + assert_eq!(output, 1); + }; + + // Overwrite 11 22 33 44 55 66 77 88 with bytes aa bb cc dd at offset 1: + // Expect 11 aa bb cc | dd 66 77 88 + // or 0xccbbaa11 and 0x887766dd. + run_test(1, 0xccbbaa11, 0x887766dd); + + // Overwrite 11 22 33 44 55 66 77 88 with bytes aa bb cc dd at offset 2: + // Expect 11 22 aa bb | cc dd 77 88 + // or 0xbbaa2211 and 0x8877ddcc. + run_test(2, 0xbbaa2211, 0x8877ddcc); + + // Overwrite 11 22 33 44 55 66 77 88 with bytes aa bb cc dd at offset 3: + // Expect 11 22 33 aa | bb cc dd 88 + // or 0xaa332211 and 0x88ddccbb. + run_test(3, 0xaa332211, 0x88ddccbb); +} + +#[test] +fn store_unaligned_u64() { + // Use the start of the 17th page (1 page after the 16 pages reserved for the Rust stack) + let write_to = 17 * 2u32.pow(16); + + // STORE_DW writes the high 32bit word to address and low 32bit word to address+1. + // So a .store() of 0xddccbbaa_cdabffee writes 0xddccbbaa to addr and 0xcdabffee to addr+1. + // Which in turn will be little-endian bytes [ AA BB CC DD EE FF AB CD ] at addr. + let write_val = 0xddccbbaa_cdabffee_u64; + + // Generate a `test` module with `main` function that stores to a u32 offset. + let signature = Signature::new( + [AbiParam::new(Type::U32)], + [AbiParam::new(Type::U32)], // Return u32 to satisfy test infrastructure + ); + + // Compile once outside the test loop + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + let idx_val = block.borrow().arguments()[0] as ValueRef; + + // Set base pointer, add argument offset to it. + let base_addr = builder.u32(write_to, SourceSpan::default()); + let write_addr = builder.add(base_addr, idx_val, SourceSpan::default()).unwrap(); + let ptr = builder + .inttoptr(write_addr, Type::from(PointerType::new(Type::U64)), SourceSpan::default()) + .unwrap(); + + // Store test value to pointer. + let write_val = builder.u64(write_val, SourceSpan::default()); + builder.store(ptr, write_val, SourceSpan::default()).unwrap(); + + // Return a constant to satisfy test infrastructure + let result = builder.u32(1, SourceSpan::default()); + builder.ret(Some(result), SourceSpan::default()).unwrap(); + }); + + let run_test = |offs: u32, expected0: u32, expected1: u32, expected2: u32, expected3: u32| { + // Initialise memory with some known bytes. + let initializers = [Initializer::MemoryBytes { + addr: write_to, + bytes: &[ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, + ], + }]; + + let output = eval_package::( + &package, + initializers, + &[Felt::new(offs as u64)], + context.session(), + |trace| { + // Get the overwritten words. + let word0 = trace.read_from_rust_memory::(write_to).unwrap(); + let word1 = trace.read_from_rust_memory::(write_to + 4).unwrap(); + let word2 = trace.read_from_rust_memory::(write_to + 8).unwrap(); + let word3 = trace.read_from_rust_memory::(write_to + 12).unwrap(); + + eprintln!("word0: 0x{word0:0>8x}"); + eprintln!("word1: 0x{word1:0>8x}"); + eprintln!("word2: 0x{word2:0>8x}"); + eprintln!("word3: 0x{word3:0>8x}"); + + assert_eq!( + word0, expected0, + "expected 1st overwritten word to be {expected0}, got {word0}, with offset \ + {offs}" + ); + + assert_eq!( + word1, expected1, + "expected 2nd overwritten word to be {expected1}, got {word1}, with offset \ + {offs}" + ); + + assert_eq!( + word2, expected2, + "expected 3rd overwritten word to be {expected2}, got {word2}, with offset \ + {offs}" + ); + + assert_eq!( + word3, expected3, + "expected 4th overwritten word to be {expected3}, got {word3}, with offset \ + {offs}" + ); + + Ok(()) + }, + ) + .unwrap(); + + assert_eq!(output, 1); + }; + + // Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18 + // with bytes aa bb cc dd ee ff ab cd at offset 1: + // Expect 01 aa bb cc dd ee ff ab cd 12 13 14 15 16 17 18 + // or 0xccbbaa01, 0xabffeedd, 0x141312cd, 0x18171615 + run_test(1, 0xccbbaa01, 0xabffeedd, 0x141312cd, 0x18171615); + + // Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18 + // with bytes aa bb cc dd ee ff ab cd at offset 2: + // Expect 01 02 aa bb cc dd ee ff ab cd 13 14 15 16 17 18 + // or 0xbbaa0201, 0xffeeddcc, 0x1413cdab, 0x18171615 + run_test(2, 0xbbaa0201, 0xffeeddcc, 0x1413cdab, 0x18171615); + + // Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18 + // with bytes aa bb cc dd ee ff ab cd at offset 3: + // Expect 01 02 03 aa bb cc dd ee ff ab cd 14 15 16 17 18 + // or 0xaa030201, 0xeeddccbb, 0x14cdabff, 0x18171615 + run_test(3, 0xaa030201, 0xeeddccbb, 0x14cdabff, 0x18171615); + + // Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18 + // with bytes aa bb cc dd ee ff ab cd at offset 4: + // Expect 01 02 03 04 aa bb cc dd ee ff ab cd 15 16 17 18 + // or 0x04030201, 0xddccbbaa, 0xcdabffee, 0x18171615 + run_test(4, 0x04030201, 0xddccbbaa, 0xcdabffee, 0x18171615); + + // Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18 + // with bytes aa bb cc dd ee ff ab cd at offset 5: + // Expect 01 02 03 04 05 aa bb cc dd ee ff ab cd 16 17 18 + // or 0x04030201, 0xccbbaa05, 0xabffeedd, 0x181716cd + run_test(5, 0x04030201, 0xccbbaa05, 0xabffeedd, 0x181716cd); + + // Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18 + // with bytes aa bb cc dd ee ff ab cd at offset 6: + // Expect 01 02 03 04 05 06 aa bb cc dd ee ff ab cd 17 18 + // or 0x04030201, 0xbbaa0605, 0xffeeddcc, 0x1817cdab + run_test(6, 0x04030201, 0xbbaa0605, 0xffeeddcc, 0x1817cdab); + + // Overwrite 01 02 03 04 05 06 07 08-11 12 13 14 15 16 17 18 + // with bytes aa bb cc dd ee ff ab cd at offset 7: + // Expect 01 02 03 04 05 06 07 aa bb cc dd ee ff ab cd 18 + // or 0x04030201, 0xaa070605, 0xeeddccbb, 0x18cdabff + run_test(7, 0x04030201, 0xaa070605, 0xeeddccbb, 0x18cdabff); +} diff --git a/tests/integration/src/codegen/intrinsics/mod.rs b/tests/integration/src/codegen/intrinsics/mod.rs new file mode 100644 index 000000000..e4e8f41b0 --- /dev/null +++ b/tests/integration/src/codegen/intrinsics/mod.rs @@ -0,0 +1 @@ +mod mem; diff --git a/tests/integration/src/codegen/mod.rs b/tests/integration/src/codegen/mod.rs new file mode 100644 index 000000000..9aededc28 --- /dev/null +++ b/tests/integration/src/codegen/mod.rs @@ -0,0 +1,2 @@ +mod intrinsics; +mod operations; diff --git a/tests/integration/src/codegen/operations.rs b/tests/integration/src/codegen/operations.rs new file mode 100644 index 000000000..cde2d11e0 --- /dev/null +++ b/tests/integration/src/codegen/operations.rs @@ -0,0 +1,185 @@ +use midenc_dialect_arith::ArithOpBuilder; +use midenc_dialect_cf::ControlFlowOpBuilder; +use midenc_hir::{ + dialects::builtin::BuiltinOpBuilder, AbiParam, Felt, Immediate, Signature, SourceSpan, Type, + ValueRef, +}; + +use crate::testing::{compile_test_module, eval_package}; + +fn run_select_test(ty: Type, a: Immediate, a_result: &[u64], b: Immediate, b_result: &[u64]) { + let span = SourceSpan::default(); + + // Wrap 'select' in a function which takes a bool and returns selection from consts. + let signature = Signature::new([AbiParam::new(Type::I1)], [AbiParam::new(ty)]); + + let (package, context) = compile_test_module(signature, |builder| { + let block = builder.current_block(); + let cond_val = block.borrow().arguments()[0] as ValueRef; + + let a_imm = builder.imm(a, span); + let b_imm = builder.imm(b, span); + + let result_val = builder.select(cond_val, a_imm, b_imm, span).unwrap(); + + builder.ret(Some(result_val), span).unwrap(); + }); + + let run_test = |cond_val, expected: &[u64]| { + eval_package::( + &package, + None, + &[Felt::from(cond_val)], + context.session(), + |trace| { + let outputs = trace.outputs().as_int_vec(); + let len = expected.len(); + + // Ensure the expected values are at the top of the stack and the rest are zeroes. + assert_eq!(&outputs.as_slice()[..len], expected); + assert!(outputs[len..].iter().all(|el| *el == 0)); + + Ok(()) + }, + ) + .unwrap(); + }; + + run_test(true, a_result); + run_test(false, b_result); +} + +macro_rules! simple_select { + ($ty: ident, $a: literal, $b: literal) => { + // This is a bit basic, and will break if $a or $b are negative. + run_select_test( + Type::$ty, + Immediate::$ty($a), + &[$a as u64], + Immediate::$ty($b), + &[$b as u64], + ); + }; +} + +#[test] +fn select_u32() { + simple_select!(U32, 11111111, 22222222); +} + +#[test] +fn select_i32() { + simple_select!(I32, 11111111, 22222222); +} + +#[test] +fn select_u16() { + simple_select!(U16, 11111, 22222); +} + +#[test] +fn select_i16() { + simple_select!(I16, 11111, 22222); +} + +#[test] +fn select_u8() { + simple_select!(U8, 111, 222); +} + +#[test] +fn select_i8() { + simple_select!(I8, 11, 22); +} + +#[test] +fn select_i1() { + simple_select!(I1, true, false); +} + +#[test] +fn select_felt() { + run_select_test( + Type::Felt, + Immediate::Felt(Felt::new(1111111111111111)), + &[1111111111111111_u64], + Immediate::Felt(Felt::new(2222222222222222)), + &[2222222222222222_u64], + ); +} + +#[test] +fn select_u64() { + // U64 is split into two 32bit limbs. + run_select_test( + Type::U64, + Immediate::U64(1111111111111111), + &[1111111111111111_u64 >> 32, 1111111111111111_u64 & 0xffffffff], + Immediate::U64(2222222222222222), + &[2222222222222222_u64 >> 32, 2222222222222222_u64 & 0xffffffff], + ); +} + +#[test] +fn select_i64() { + // I64 is split into two 32bit limbs. + run_select_test( + Type::I64, + Immediate::I64(1111111111111111), + &[1111111111111111_u64 >> 32, 1111111111111111_u64 & 0xffffffff], + Immediate::I64(2222222222222222), + &[2222222222222222_u64 >> 32, 2222222222222222_u64 & 0xffffffff], + ); +} + +#[test] +fn select_u128() { + // U128 is split into four 32bit limbs. + // + // It also mixes them up based on the virtual 64bit limbs. + let ones = 1111111111111111111111111111111_u128; + let twos = 2222222222222222222222222222222_u128; + run_select_test( + Type::U128, + Immediate::U128(ones), + &[ + ((ones >> 32) & 0xffffffff) as u64, // lo_mid + (ones & 0xffffffff) as u64, // lo_lo + (ones >> 96) as u64, // hi_hi + ((ones >> 64) & 0xffffffff) as u64, // hi_mid + ], + Immediate::U128(twos), + &[ + ((twos >> 32) & 0xffffffff) as u64, // lo_mid + (twos & 0xffffffff) as u64, // lo_lo + (twos >> 96) as u64, // hi_hi + ((twos >> 64) & 0xffffffff) as u64, // hi_mid + ], + ); +} + +#[test] +fn select_i128() { + // I128 is split into four 32bit limbs. + // + // It also mixes them up based on the virtual 64bit limbs. + let ones = 1111111111111111111111111111111_i128; + let twos = 2222222222222222222222222222222_i128; + run_select_test( + Type::I128, + Immediate::I128(ones), + &[ + ((ones >> 32) & 0xffffffff) as u64, // lo_mid + (ones & 0xffffffff) as u64, // lo_lo + (ones >> 96) as u64, // hi_hi + ((ones >> 64) & 0xffffffff) as u64, // hi_mid + ], + Immediate::I128(twos), + &[ + ((twos >> 32) & 0xffffffff) as u64, // lo_mid + (twos & 0xffffffff) as u64, // lo_lo + (twos >> 96) as u64, // hi_hi + ((twos >> 64) & 0xffffffff) as u64, // hi_mid + ], + ); +} diff --git a/tests/integration/src/compiler_test.rs b/tests/integration/src/compiler_test.rs index 9a4a46182..e94a4bb79 100644 --- a/tests/integration/src/compiler_test.rs +++ b/tests/integration/src/compiler_test.rs @@ -1,11 +1,8 @@ -#![allow(dead_code)] - use core::panic; use std::{ borrow::Cow, ffi::OsStr, fmt, fs, - io::Read, path::{Path, PathBuf}, process::{Command, Stdio}, rc::Rc, @@ -13,54 +10,33 @@ use std::{ }; use miden_assembly::LibraryPath; -use midenc_frontend_wasm::{translate, WasmTranslationConfig}; -use midenc_hir::{demangle, FunctionIdent, Ident, Symbol}; +use midenc_compile::{ + compile_link_output_to_masm_with_pre_assembly_stage, compile_to_unoptimized_hir, +}; +use midenc_frontend_wasm::WasmTranslationConfig; +use midenc_hir::{ + demangle::demangle, dialects::builtin, interner::Symbol, Context, FunctionIdent, Ident, Op, +}; use midenc_session::{InputFile, InputType, Session}; -use crate::cargo_proj::project; +use crate::{ + cargo_proj::project, + testing::{format_report, setup}, +}; type LinkMasmModules = Vec<(LibraryPath, String)>; -#[derive(derive_more::From)] -pub enum HirArtifact { - Program(Box), - Module(Box), - Component(Box), -} - -impl HirArtifact { - pub fn unwrap_module(&self) -> &midenc_hir::Module { - match self { - HirArtifact::Module(module) => module, - _ => panic!("Expected a Module"), - } - } - - pub fn unwrap_program(&self) -> &midenc_hir::Program { - match self { - Self::Program(program) => program, - _ => panic!("attempted to unwrap a program, but had a component"), - } - } - - pub fn unwrap_component(&self) -> &midenc_hir::Component { - match self { - Self::Component(program) => program, - _ => panic!("attempted to unwrap a component, but had a program"), - } - } -} - /// Configuration for tests which use as input, the artifact produced by a Cargo build +#[derive(Debug)] pub struct CargoTest { project_dir: PathBuf, manifest_path: Option>, target_dir: Option, name: Cow<'static, str>, - target: Cow<'static, str>, entrypoint: Option>, build_std: bool, build_alloc: bool, + release: bool, } impl CargoTest { /// Create a new `cargo` test with the given name, and project directory @@ -70,10 +46,10 @@ impl CargoTest { manifest_path: None, target_dir: None, name: name.into(), - target: "wasm32-wasip1".into(), entrypoint: None, build_std: false, build_alloc: false, + release: true, } } @@ -92,13 +68,6 @@ impl CargoTest { self } - /// Specify the target triple to pass to Cargo - #[inline] - pub fn with_target(mut self, target: impl Into>) -> Self { - self.target = target.into(); - self - } - /// Specify the target directory for Cargo #[inline] pub fn with_target_dir(mut self, target_dir: impl Into) -> Self { @@ -119,16 +88,6 @@ impl CargoTest { self.manifest_path = Some(manifest_path.into()); self } - - /// Get a [PathBuf] representing the path to the expected Cargo artifact - pub fn wasm_artifact_path(&self) -> PathBuf { - self.project_dir - .join("target") - .join(self.target.as_ref()) - .join("release") - .join(self.name.as_ref()) - .with_extension("wasm") - } } /// Configuration for tests which use as input, the artifact produced by an invocation of `rustc` @@ -136,6 +95,7 @@ pub struct RustcTest { target_dir: Option, name: Cow<'static, str>, target: Cow<'static, str>, + #[allow(dead_code)] output_name: Option>, source_code: Cow<'static, str>, rustflags: Vec>, @@ -160,20 +120,12 @@ impl RustcTest { /// The various types of input artifacts that can be used to drive compiler tests pub enum CompilerTestInputType { - /// A project that uses `cargo miden build` to produce a Wasm module to use as input + /// A project that uses `cargo miden build` to produce a Wasm component to use as input CargoMiden(CargoTest), - /// A project that uses `cargo component build` to produce a Wasm module to use as input - CargoComponent(CargoTest), - /// A project that uses `cargo build` to produce a core Wasm module to use as input - Cargo(CargoTest), /// A project that uses `rustc` to produce a core Wasm module to use as input Rustc(RustcTest), } -impl From for CompilerTestInputType { - fn from(config: CargoTest) -> Self { - Self::Cargo(config) - } -} + impl From for CompilerTestInputType { fn from(config: RustcTest) -> Self { Self::Rustc(config) @@ -207,15 +159,18 @@ pub struct CompilerTestBuilder { /// The extra MASM modules to link to the compiled MASM program link_masm_modules: LinkMasmModules, /// Extra flags to pass to the midenc driver - midenc_flags: Vec>, + midenc_flags: Vec, /// Extra RUSTFLAGS to set when compiling Rust code rustflags: Vec>, /// The cargo workspace directory of the compiler + #[allow(dead_code)] workspace_dir: String, } impl CompilerTestBuilder { /// Construct a new [CompilerTestBuilder] for the given source type configuration pub fn new(source: impl Into) -> Self { + setup::enable_compiler_instrumentation(); + let workspace_dir = get_workspace_dir(); let mut source = source.into(); let mut rustflags = match source { @@ -223,14 +178,10 @@ impl CompilerTestBuilder { _ => vec![], }; let entrypoint = match source { - CompilerTestInputType::Cargo(ref mut config) => config.entrypoint.take(), - CompilerTestInputType::CargoComponent(ref mut config) => config.entrypoint.take(), CompilerTestInputType::Rustc(_) => Some("__main".into()), CompilerTestInputType::CargoMiden(ref mut config) => config.entrypoint.take(), }; let name = match source { - CompilerTestInputType::Cargo(ref mut config) => config.name.as_ref(), - CompilerTestInputType::CargoComponent(ref mut config) => config.name.as_ref(), CompilerTestInputType::Rustc(ref mut config) => config.name.as_ref(), CompilerTestInputType::CargoMiden(ref mut config) => config.name.as_ref(), }; @@ -241,16 +192,15 @@ impl CompilerTestBuilder { rustflags.extend([ // Enable bulk-memory features (e.g. native memcpy/memset instructions) "-C".into(), - "target-feature=+bulk-memory".into(), + "target-feature=+bulk-memory,+wide-arithmetic".into(), // Remap the compiler workspace to `.` so that build outputs do not embed user- // specific paths, which would cause expect tests to break "--remap-path-prefix".into(), format!("{workspace_dir}=../../").into(), ]); - let mut midenc_flags = vec!["--debug".into(), "--verbose".into()]; + let mut midenc_flags = vec!["--verbose".into()]; if let Some(entrypoint) = entrypoint { - midenc_flags - .extend(["--entrypoint".into(), format!("{}", entrypoint.display()).into()]); + midenc_flags.extend(["--entrypoint".into(), format!("{}", entrypoint.display())]); } Self { config: Default::default(), @@ -293,15 +243,12 @@ impl CompilerTestBuilder { None => (), } self.midenc_flags - .extend(["--entrypoint".into(), format!("{}", entrypoint.display()).into()]); + .extend(["--entrypoint".into(), format!("{}", entrypoint.display())]); self } /// Append additional `midenc` compiler flags - pub fn with_midenc_flags( - &mut self, - flags: impl IntoIterator>, - ) -> &mut Self { + pub fn with_midenc_flags(&mut self, flags: impl IntoIterator) -> &mut Self { self.midenc_flags.extend(flags); self } @@ -315,6 +262,30 @@ impl CompilerTestBuilder { self } + /// Specify if the test fixture should be compiled in release mode + pub fn with_release(&mut self, release: bool) -> &mut Self { + match self.source { + CompilerTestInputType::CargoMiden(ref mut config) => config.release = release, + CompilerTestInputType::Rustc(_) => (), + } + self + } + + /// Override the Cargo target directory to the specified path + pub fn with_target_dir(&mut self, path: impl AsRef) -> &mut Self { + match &mut self.source { + CompilerTestInputType::CargoMiden(CargoTest { + ref mut target_dir, .. + }) + | CompilerTestInputType::Rustc(RustcTest { + ref mut target_dir, .. + }) => { + *target_dir = Some(path.as_ref().to_path_buf()); + } + } + self + } + /// Add additional Miden Assembly module sources, to be linked with the program under test. pub fn link_with_masm_module( &mut self, @@ -330,7 +301,7 @@ impl CompilerTestBuilder { /// Consume the builder, invoke any tools required to obtain the inputs for the test, and if /// successful, return a [CompilerTest], ready for evaluation. - pub fn build(self) -> CompilerTest { + pub fn build(mut self) -> CompilerTest { // Set up the command used to compile the test inputs (typically Rust -> Wasm) let mut command = match self.source { CompilerTestInputType::CargoMiden(_) => { @@ -338,16 +309,6 @@ impl CompilerTestBuilder { cmd.arg("miden").arg("build"); cmd } - CompilerTestInputType::CargoComponent(_) => { - let mut cmd = Command::new("cargo"); - cmd.arg("component").arg("build"); - cmd - } - CompilerTestInputType::Cargo(_) => { - let mut cmd = Command::new("cargo"); - cmd.arg("build"); - cmd - } CompilerTestInputType::Rustc(_) => Command::new("rustc"), }; @@ -355,12 +316,6 @@ impl CompilerTestBuilder { let project_dir = match self.source { CompilerTestInputType::CargoMiden(CargoTest { ref project_dir, .. - }) - | CompilerTestInputType::CargoComponent(CargoTest { - ref project_dir, .. - }) - | CompilerTestInputType::Cargo(CargoTest { - ref project_dir, .. }) => Cow::Borrowed(project_dir.as_path()), CompilerTestInputType::Rustc(RustcTest { ref target_dir, .. }) => target_dir .as_deref() @@ -369,43 +324,12 @@ impl CompilerTestBuilder { }; // Cargo-based source types share a lot of configuration in common - match self.source { - CompilerTestInputType::CargoComponent(_) => { - let manifest_path = project_dir.join("Cargo.toml"); - command.arg("--manifest-path").arg(manifest_path).arg("--release"); - // Render Cargo output as JSON - command.arg("--message-format=json-render-diagnostics"); - } - - CompilerTestInputType::Cargo(ref config) => { - let manifest_path = project_dir.join("Cargo.toml"); - command - .arg("--manifest-path") - .arg(manifest_path) - .arg("--release") - .arg("--target") - .arg(config.target.as_ref()); - - if config.build_std { - // compile std as part of crate graph compilation - // https://doc.rust-lang.org/cargo/reference/unstable.html#build-std - command.arg("-Z").arg("build-std=core,alloc,std,panic_abort"); - - // abort on panic without message formatting (core::fmt uses call_indirect) - command.arg("-Z").arg("build-std-features=panic_immediate_abort"); - } else if config.build_alloc { - // compile libcore and liballoc as part of crate graph compilation - // https://doc.rust-lang.org/cargo/reference/unstable.html#build-std - command.arg("-Z").arg("build-std=core,alloc"); - - // abort on panic without message formatting (core::fmt uses call_indirect) - command.arg("-Z").arg("build-std-features=panic_immediate_abort"); - } - - // Render Cargo output as JSON - command.arg("--message-format=json-render-diagnostics"); + if let CompilerTestInputType::CargoMiden(ref config) = self.source { + let manifest_path = project_dir.join("Cargo.toml"); + command.arg("--manifest-path").arg(manifest_path); + if config.release { + command.arg("--release"); } - _ => (), } // All test source types support custom RUSTFLAGS @@ -427,7 +351,7 @@ impl CompilerTestBuilder { // Build test match self.source { - CompilerTestInputType::CargoMiden(_) => { + CompilerTestInputType::CargoMiden(..) => { let mut args = vec![command.get_program().to_str().unwrap().to_string()]; let cmd_args: Vec = command .get_args() @@ -436,54 +360,24 @@ impl CompilerTestBuilder { .map(|s| s.to_str().unwrap().to_string()) .collect(); args.extend(cmd_args); - let wasm_artifacts = cargo_miden::run(args.into_iter()).unwrap(); - let wasm_comp_path = &wasm_artifacts.first().unwrap(); - let artifact_name = - wasm_comp_path.file_stem().unwrap().to_str().unwrap().to_string(); - let input_file = InputFile::from_path(wasm_comp_path).unwrap(); - let mut inputs = vec![input_file]; - inputs.extend(self.link_masm_modules.into_iter().map(|(path, content)| { - let path = path.to_string(); - InputFile::new( - midenc_session::FileType::Masm, - InputType::Stdin { - name: path.into(), - input: content.into_bytes(), - }, - ) - })); - - CompilerTest { - config: self.config, - session: default_session(inputs, &self.midenc_flags), - artifact_name: artifact_name.into(), - entrypoint: self.entrypoint, - ..Default::default() - } - } - CompilerTestInputType::CargoComponent(_) => { - let mut child = command.spawn().unwrap_or_else(|_| { - panic!( - "Failed to execute command: {}", - command - .get_args() - .map(|arg| format!("'{}'", arg.to_str().unwrap())) - .collect::>() - .join(" ") - ) - }); - - let wasm_artifacts = find_wasm_artifacts(&mut child); - let output = child.wait().expect("Couldn't get cargo's exit status"); - if !output.success() { - report_cargo_error(child); - } - assert!(output.success()); - assert_eq!(wasm_artifacts.len(), 1, "Expected one Wasm artifact"); - let wasm_comp_path = &wasm_artifacts.first().unwrap(); + let build_output = + cargo_miden::run(args.into_iter(), cargo_miden::OutputType::Wasm) + .unwrap() + .expect("'cargo miden build' should return Some(CommandOutput)") + .unwrap_build_output(); // Use the new method + let (wasm_artifact_path, mut extra_midenc_flags) = match build_output { + cargo_miden::BuildOutput::Wasm { + artifact_path, + midenc_flags, + } => (artifact_path, midenc_flags), + other => panic!("Expected Wasm output, got {:?}", other), + }; + // dbg!(&wasm_artifact_path); + // dbg!(&extra_midenc_flags); + self.midenc_flags.append(&mut extra_midenc_flags); let artifact_name = - wasm_comp_path.file_stem().unwrap().to_str().unwrap().to_string(); - let input_file = InputFile::from_path(wasm_comp_path).unwrap(); + wasm_artifact_path.file_stem().unwrap().to_str().unwrap().to_string(); + let input_file = InputFile::from_path(wasm_artifact_path).unwrap(); let mut inputs = vec![input_file]; inputs.extend(self.link_masm_modules.into_iter().map(|(path, content)| { let path = path.to_string(); @@ -495,70 +389,20 @@ impl CompilerTestBuilder { }, ) })); + // dbg!(&inputs); + let context = setup::default_context(inputs, &self.midenc_flags); + let session = context.session_rc(); CompilerTest { config: self.config, - session: default_session(inputs, &self.midenc_flags), + session, + context, artifact_name: artifact_name.into(), entrypoint: self.entrypoint, ..Default::default() } } - CompilerTestInputType::Cargo(config) => { - let expected_wasm_artifact_path = config.wasm_artifact_path(); - let skip_rust_compilation = - std::env::var("SKIP_RUST").is_ok() && expected_wasm_artifact_path.exists(); - let wasm_artifact_path = if !skip_rust_compilation { - let mut child = command.spawn().unwrap_or_else(|_| { - panic!( - "Failed to execute command: {}", - command - .get_args() - .map(|arg| format!("'{}'", arg.to_str().unwrap())) - .collect::>() - .join(" ") - ) - }); - // Find the Wasm artifacts from the cargo build output for debugging purposes - let mut wasm_artifacts = find_wasm_artifacts(&mut child); - let output = child.wait().expect("Couldn't get cargo's exit status"); - if !output.success() { - report_cargo_error(child); - } - assert!(output.success()); - // filter out dependencies - wasm_artifacts.retain(|path| { - let path_str = path.to_str().unwrap(); - !path_str.contains("release/deps") - }); - dbg!(&wasm_artifacts); - assert_eq!(wasm_artifacts.len(), 1, "Expected one Wasm artifact"); - wasm_artifacts.swap_remove(0) - } else { - drop(command); - expected_wasm_artifact_path - }; - let input_file = InputFile::from_path(wasm_artifact_path).unwrap(); - let mut inputs = vec![input_file]; - inputs.extend(self.link_masm_modules.into_iter().map(|(path, content)| { - let path = path.to_string(); - InputFile::new( - midenc_session::FileType::Masm, - InputType::Stdin { - name: path.into(), - input: content.into_bytes(), - }, - ) - })); - CompilerTest { - config: self.config, - session: default_session(inputs, &self.midenc_flags), - artifact_name: config.name, - entrypoint: self.entrypoint, - ..Default::default() - } - } CompilerTestInputType::Rustc(config) => { // Ensure we have a fresh working directory prepared let working_dir = config @@ -580,6 +424,7 @@ impl CompilerTestBuilder { let output = command .args(["-C", "opt-level=z"]) // optimize for size + .args(["-C", "target-feature=+wide-arithmetic"]) .arg("--target") .arg(config.target.as_ref()) .arg("-o") @@ -604,9 +449,13 @@ impl CompilerTestBuilder { }, ) })); + + let context = setup::default_context(inputs, &self.midenc_flags); + let session = context.session_rc(); CompilerTest { config: self.config, - session: default_session(inputs, &self.midenc_flags), + session, + context, artifact_name: config.name, entrypoint: self.entrypoint, ..Default::default() @@ -618,67 +467,25 @@ impl CompilerTestBuilder { /// Convenience builders impl CompilerTestBuilder { - /// Compile the Wasm component from a Rust Cargo project using cargo-component - pub fn rust_source_cargo_component( + /// Compile the Rust project using cargo-miden + pub fn rust_source_cargo_miden( cargo_project_folder: impl AsRef, config: WasmTranslationConfig, + midenc_flags: impl IntoIterator, ) -> Self { let name = cargo_project_folder .as_ref() .file_stem() .map(|name| name.to_string_lossy().into_owned()) .unwrap_or("".to_string()); - let mut builder = CompilerTestBuilder::new(CompilerTestInputType::CargoComponent( + let mut builder = CompilerTestBuilder::new(CompilerTestInputType::CargoMiden( CargoTest::new(name, cargo_project_folder.as_ref().to_path_buf()), )); builder.with_wasm_translation_config(config); - builder - } - - /// Set the Rust source code to compile a library Cargo project to Wasm module - pub fn rust_source_cargo_lib( - cargo_project_folder: impl AsRef, - artifact_name: impl Into>, - is_build_std: bool, - entry_func_name: Option>, - midenc_flags: impl IntoIterator>, - ) -> Self { - let cargo_project_folder = cargo_project_folder.as_ref().to_path_buf(); - let config = - CargoTest::new(artifact_name, cargo_project_folder).with_build_std(is_build_std); - let mut builder = CompilerTestBuilder::new(match entry_func_name { - Some(entry) => config.with_entrypoint(entry), - None => config, - }); builder.with_midenc_flags(midenc_flags); builder } - /// Set the Rust source code to compile using a Cargo project and binary bundle name - pub fn rust_source_cargo( - cargo_project_folder: impl AsRef, - artifact_name: impl Into>, - entrypoint: impl Into>, - ) -> Self { - let temp_dir = std::env::temp_dir(); - let target_dir = temp_dir.join(cargo_project_folder.as_ref()); - let project_dir = Path::new("../rust-apps-wasm") - .join(cargo_project_folder.as_ref()) - .canonicalize() - .unwrap_or_else(|_| { - panic!( - "unknown project folder: ../rust-apps-wasm/{}", - cargo_project_folder.as_ref().display() - ) - }); - let config = CargoTest::new(artifact_name, project_dir) - .with_build_alloc(true) - .with_target_dir(target_dir) - .with_target("wasm32-unknown-unknown") - .with_entrypoint(entrypoint); - CompilerTestBuilder::new(config) - } - /// Set the Rust source code to compile pub fn rust_source_program(rust_source: impl Into>) -> Self { let rust_source = rust_source.into(); @@ -687,9 +494,16 @@ impl CompilerTestBuilder { } /// Set the Rust source code to compile and add a binary operation test - pub fn rust_fn_body( + pub fn rust_fn_body(rust_source: &str, midenc_flags: impl IntoIterator) -> Self { + let name = format!("test_rust_{}", hash_string(rust_source)); + Self::rust_fn_body_with_artifact_name(name, rust_source, midenc_flags) + } + + /// Set the Rust source code to compile and add a binary operation test + pub fn rust_fn_body_with_artifact_name( + name: impl Into>, rust_source: &str, - midenc_flags: impl IntoIterator>, + midenc_flags: impl IntoIterator, ) -> Self { let rust_source = format!( r#" @@ -702,11 +516,10 @@ impl CompilerTestBuilder { }} #[no_mangle] - pub extern "C" fn entrypoint{} - "#, - rust_source + pub extern "C" fn entrypoint{rust_source} + "# ); - let name = format!("test_rust_{}", hash_string(&rust_source)); + let name = name.into(); let module_name = Ident::with_empty_span(Symbol::intern(&name)); let mut builder = CompilerTestBuilder::new(RustcTest::new(name, rust_source)); builder.with_midenc_flags(midenc_flags).with_entrypoint(FunctionIdent { @@ -720,8 +533,8 @@ impl CompilerTestBuilder { pub fn rust_fn_body_with_stdlib_sys( name: impl Into>, source: &str, - is_build_std: bool, - midenc_flags: impl IntoIterator>, + config: WasmTranslationConfig, + midenc_flags: impl IntoIterator, ) -> Self { let name = name.into(); let stdlib_sys_path = stdlib_sys_crate_path(); @@ -731,6 +544,8 @@ impl CompilerTestBuilder { "Cargo.toml", format!( r#" + cargo-features = ["trim-paths"] + [package] name = "{name}" version = "0.0.1" @@ -748,7 +563,8 @@ impl CompilerTestBuilder { panic = "abort" # optimize for size opt-level = "z" - debug = true + debug = false + trim-paths = ["diagnostics", "object"] "#, sdk_alloc_path = sdk_alloc_path.display(), stdlib_sys_path = stdlib_sys_path.display(), @@ -761,6 +577,7 @@ impl CompilerTestBuilder { r#" #![no_std] #![no_main] + #![allow(unused_imports)] #[panic_handler] fn my_panic(_info: &core::panic::PanicInfo) -> ! {{ @@ -772,32 +589,28 @@ impl CompilerTestBuilder { static ALLOC: miden_sdk_alloc::BumpAlloc = miden_sdk_alloc::BumpAlloc::new(); extern crate miden_stdlib_sys; - use miden_stdlib_sys::*; + use miden_stdlib_sys::{{*, intrinsics}}; + + extern crate alloc; #[no_mangle] - pub extern "C" fn entrypoint{} - "#, - source + #[allow(improper_ctypes_definitions)] + pub extern "C" fn entrypoint{source} + "# ) .as_str(), ) .build(); - Self::rust_source_cargo_lib( - proj.root(), - name, - is_build_std, - Some("entrypoint".into()), - midenc_flags, - ) + + Self::rust_source_cargo_miden(proj.root(), config, midenc_flags) } /// Set the Rust source code to compile with `miden-sdk` (sdk + intrinsics) pub fn rust_source_with_sdk( name: impl Into>, source: &str, - is_build_std: bool, - entrypoint: Option>, - midenc_flags: impl IntoIterator>, + config: WasmTranslationConfig, + midenc_flags: impl IntoIterator, ) -> Self { let name = name.into(); let sdk_path = sdk_crate_path(); @@ -806,24 +619,27 @@ impl CompilerTestBuilder { .file( "Cargo.toml", format!( - r#"[package] -name = "{name}" -version = "0.0.1" -edition = "2021" -authors = [] - -[dependencies] -miden-sdk-alloc = {{ path = "{sdk_alloc_path}" }} -miden-sdk = {{ path = "{sdk_path}" }} - -[lib] -crate-type = ["cdylib"] - -[profile.release] -panic = "abort" -# optimize for size -opt-level = "z" -debug = true + r#" + cargo-features = ["trim-paths"] + + [package] + name = "{name}" + version = "0.0.1" + edition = "2021" + authors = [] + + [dependencies] + miden-sdk-alloc = {{ path = "{sdk_alloc_path}" }} + miden = {{ path = "{sdk_path}" }} + + [lib] + crate-type = ["cdylib"] + + [profile.release] + panic = "abort" + # optimize for size + opt-level = "z" + debug = false "#, sdk_path = sdk_path.display(), sdk_alloc_path = sdk_alloc_path.display(), @@ -835,6 +651,7 @@ debug = true format!( r#"#![no_std] #![no_main] +#![allow(unused_imports)] #[panic_handler] fn my_panic(_info: &core::panic::PanicInfo) -> ! {{ @@ -845,21 +662,20 @@ fn my_panic(_info: &core::panic::PanicInfo) -> ! {{ #[global_allocator] static ALLOC: miden_sdk_alloc::BumpAlloc = miden_sdk_alloc::BumpAlloc::new(); -extern crate miden_sdk; -use miden_sdk::*; +extern crate miden; +use miden::*; extern crate alloc; use alloc::vec::Vec; -{} -"#, - source +{source} +"# ) .as_str(), ) .build(); - Self::rust_source_cargo_lib(proj.root(), name, is_build_std, entrypoint, midenc_flags) + Self::rust_source_cargo_miden(proj.root(), config, midenc_flags) } /// Like `rust_source_with_sdk`, but expects the source code to be the body of a function @@ -867,17 +683,11 @@ use alloc::vec::Vec; pub fn rust_fn_body_with_sdk( name: impl Into>, source: &str, - is_build_std: bool, - midenc_flags: impl IntoIterator>, + config: WasmTranslationConfig, + midenc_flags: impl IntoIterator, ) -> Self { let source = format!("#[no_mangle]\npub extern \"C\" fn entrypoint{source}"); - Self::rust_source_with_sdk( - name, - &source, - is_build_std, - Some("entrypoint".into()), - midenc_flags, - ) + Self::rust_source_with_sdk(name, &source, config, midenc_flags) } } @@ -888,18 +698,20 @@ pub struct CompilerTest { pub config: WasmTranslationConfig, /// The compiler session pub session: Rc, + /// The compiler context + pub context: Rc, /// The artifact name from which this test is derived artifact_name: Cow<'static, str>, /// The entrypoint function to use when building the IR entrypoint: Option, /// The compiled IR - hir: Option, + hir: Option, /// The MASM source code masm_src: Option, /// The compiled IR MASM program - ir_masm_program: Option, String>>, + ir_masm_program: Option, String>>, /// The compiled package containing a program executable by the VM - package: Option, String>>, + package: Option, String>>, } impl fmt::Debug for CompilerTest { @@ -911,44 +723,9 @@ impl fmt::Debug for CompilerTest { .field("entrypoint", &self.entrypoint) .field_with("hir", |f| match self.hir.as_ref() { None => f.debug_tuple("None").finish(), - Some(HirArtifact::Module(module)) => f - .debug_struct("Module") - .field("name", &module.name) - .field("entrypoint", &module.entrypoint()) - .field("is_kernel", &module.is_kernel()) - .field_with("functions", |f| { - f.debug_list() - .entries(module.functions().map(|fun| &fun.signature)) - .finish() - }) - .field_with("globals", |f| { - f.debug_list().entries(module.globals().iter()).finish() - }) - .finish_non_exhaustive(), - Some(HirArtifact::Program(program)) => f - .debug_struct("Program") - .field("is_executable", &program.is_executable()) - .field_with("modules", |f| { - f.debug_list().entries(program.modules().iter().map(|m| m.name)).finish() - }) - .field_with("libraries", |f| { - let mut map = f.debug_map(); - for (digest, lib) in program.libraries().iter() { - map.key(digest).value_with(|f| { - f.debug_list() - .entries(lib.exports().map(|proc| proc.to_string())) - .finish() - }); - } - map.finish() - }) - .finish_non_exhaustive(), - Some(HirArtifact::Component(component)) => f - .debug_struct("Component") - .field_with("exports", |f| { - f.debug_map().entries(component.exports().iter()).finish() - }) - .finish_non_exhaustive(), + Some(link_output) => { + f.debug_tuple("Some").field(&link_output.component.borrow().id()).finish() + } }) .finish_non_exhaustive() } @@ -956,9 +733,12 @@ impl fmt::Debug for CompilerTest { impl Default for CompilerTest { fn default() -> Self { + let context = setup::dummy_context(&[]); + let session = context.session_rc(); Self { config: WasmTranslationConfig::default(), - session: dummy_session(&[]), + session, + context, artifact_name: "unknown".into(), entrypoint: None, hir: None, @@ -975,39 +755,18 @@ impl CompilerTest { self.artifact_name.as_ref() } - /// Compile the Wasm component from a Rust Cargo project using cargo-component - pub fn rust_source_cargo_component( - cargo_project_folder: impl AsRef, - config: WasmTranslationConfig, - ) -> Self { - CompilerTestBuilder::rust_source_cargo_component(cargo_project_folder, config).build() + /// Return the entrypoint for this test, if specified + pub fn entrypoint(&self) -> Option { + self.entrypoint } - /// Set the Rust source code to compile a library Cargo project to Wasm module - pub fn rust_source_cargo_lib( + /// Compile the Rust project using cargo-miden + pub fn rust_source_cargo_miden( cargo_project_folder: impl AsRef, - artifact_name: impl Into>, - is_build_std: bool, - entry_func_name: Option>, - midenc_flags: impl IntoIterator>, - ) -> Self { - CompilerTestBuilder::rust_source_cargo_lib( - cargo_project_folder, - artifact_name, - is_build_std, - entry_func_name, - midenc_flags, - ) - .build() - } - - /// Set the Rust source code to compile using a Cargo project and binary bundle name - pub fn rust_source_cargo( - cargo_project_folder: &str, - artifact_name: impl Into>, - entrypoint: impl Into>, + config: WasmTranslationConfig, + midenc_flags: impl IntoIterator, ) -> Self { - CompilerTestBuilder::rust_source_cargo(cargo_project_folder, artifact_name, entrypoint) + CompilerTestBuilder::rust_source_cargo_miden(cargo_project_folder, config, midenc_flags) .build() } @@ -1017,10 +776,7 @@ impl CompilerTest { } /// Set the Rust source code to compile and add a binary operation test - pub fn rust_fn_body( - source: &str, - midenc_flags: impl IntoIterator>, - ) -> Self { + pub fn rust_fn_body(source: &str, midenc_flags: impl IntoIterator) -> Self { CompilerTestBuilder::rust_fn_body(source, midenc_flags).build() } @@ -1028,120 +784,79 @@ impl CompilerTest { pub fn rust_fn_body_with_stdlib_sys( name: impl Into>, source: &str, - is_build_std: bool, - midenc_flags: impl IntoIterator>, + config: WasmTranslationConfig, + midenc_flags: impl IntoIterator, ) -> Self { - CompilerTestBuilder::rust_fn_body_with_stdlib_sys(name, source, is_build_std, midenc_flags) + CompilerTestBuilder::rust_fn_body_with_stdlib_sys(name, source, config, midenc_flags) .build() } - /// Set the Rust source code to compile with `miden-sdk` (sdk + intrinsics) - pub fn rust_source_with_sdk( - name: impl Into>, - source: &str, - is_build_std: bool, - entrypoint: Option>, - midenc_flags: impl IntoIterator>, - ) -> Self { - CompilerTestBuilder::rust_source_with_sdk( - name, - source, - is_build_std, - entrypoint, - midenc_flags, - ) - .build() - } - - /// Like [Self::rust_source_with_sdk], but expects the source code to be a function parameter - /// list and body, rather than arbitrary source code. - /// - /// NOTE: It is valid to append additional sources _after_ the closing brace of the function - /// body. - pub fn rust_fn_body_with_sdk( - name: impl Into>, - source: &str, - is_build_std: bool, - midenc_flags: impl IntoIterator>, - ) -> Self { - CompilerTestBuilder::rust_fn_body_with_sdk(name, source, is_build_std, midenc_flags).build() - } - /// Compare the compiled Wasm against the expected output - pub fn expect_wasm(&self, expected_wat_file: expect_test::ExpectFile) { + pub fn expect_wasm(&self, expected_wat_file: midenc_expect_test::ExpectFile) { let wasm_bytes = self.wasm_bytes(); let wat = demangle(wasm_to_wat(&wasm_bytes)); expected_wat_file.assert_eq(&wat); } - fn wasm_to_ir(&self) -> HirArtifact { - let ir_component = translate(&self.wasm_bytes(), &self.config, &self.session) - .expect("Failed to translate Wasm binary to IR component"); - Box::new(ir_component).into() + /// Get the translated IR component, translating the Wasm if it has not been done yet + pub fn hir(&mut self) -> builtin::ComponentRef { + self.link_output().component } - /// Get the compiled IR, compiling the Wasm if it has not been compiled yet - pub fn hir(&mut self) -> &HirArtifact { + /// Get a reference to the full IR linker output, translating the Wasm if needed. + pub fn link_output(&mut self) -> &midenc_compile::LinkOutput { + use midenc_compile::compile_to_optimized_hir; + if self.hir.is_none() { - self.hir = Some(self.wasm_to_ir()); + let link_output = compile_to_optimized_hir(self.context.clone()) + .map_err(format_report) + .expect("failed to translate wasm to hir component"); + self.hir = Some(link_output); } self.hir.as_ref().unwrap() } - /// Compare the compiled IR against the expected output - pub fn expect_ir(&mut self, expected_hir_file: expect_test::ExpectFile) { - match self.hir() { - HirArtifact::Program(hir_program) => { - // Program does not implement pretty printer yet, use the module containing the - // entrypoint function, or the first module found if no entrypoint is set - let ir_module = hir_program - .entrypoint() - .map(|entry| { - hir_program - .modules() - .find(&entry.module) - .get() - .expect("missing entrypoint module") - }) - .unwrap_or_else(|| { - hir_program.modules().iter().next().expect("expected at least one module") - }) - .to_string(); - let ir_module = demangle(ir_module); - expected_hir_file.assert_eq(&ir_module); - } - HirArtifact::Component(hir_component) => { - let ir_component = demangle(hir_component.to_string()); - expected_hir_file.assert_eq(&ir_component); - } - HirArtifact::Module(hir_module) => { - let ir_module = demangle(hir_module.to_string()); - expected_hir_file.assert_eq(&ir_module); - } - } + /// Compare the compiled(optimized) IR against the expected output + pub fn expect_ir(&mut self, expected_hir_file: midenc_expect_test::ExpectFile) { + use midenc_hir::Op; + + let ir = demangle(self.hir().borrow().as_operation().to_string()); + expected_hir_file.assert_eq(&ir); + } + + /// Compare the compiled(unoptimized) IR against the expected output + pub fn expect_ir_unoptimized(&mut self, expected_hir_file: midenc_expect_test::ExpectFile) { + let component = compile_to_unoptimized_hir(self.context.clone()) + .map_err(format_report) + .expect("failed to translate wasm to hir component") + .component; + + let ir = demangle(component.borrow().as_operation().to_string()); + expected_hir_file.assert_eq(&ir); } /// Compare the compiled MASM against the expected output - pub fn expect_masm(&mut self, expected_masm_file: expect_test::ExpectFile) { + pub fn expect_masm(&mut self, expected_masm_file: midenc_expect_test::ExpectFile) { let program = demangle(self.masm_src().as_str()); + std::println!("{program}"); expected_masm_file.assert_eq(&program); } /// Get the compiled IR MASM program - pub fn ir_masm_program(&mut self) -> Arc { + pub fn ir_masm_program(&mut self) -> Arc { if self.ir_masm_program.is_none() { - self.compile_wasm_to_masm_program(); + self.compile_wasm_to_masm_program().unwrap(); } match self.ir_masm_program.as_ref().unwrap().as_ref() { - Ok(prog) => prog.clone(), + Ok(component) => component.clone(), Err(msg) => panic!("{msg}"), } } - /// Get the compiled [midenc_codegen_masm::Package] - pub fn compiled_package(&mut self) -> Arc { + /// Get the compiled [miden_mast_package::Package] + pub fn compiled_package(&mut self) -> Arc { if self.package.is_none() { - self.compile_wasm_to_masm_program(); + self.compile_wasm_to_masm_program().unwrap(); } match self.package.as_ref().unwrap().as_ref() { Ok(prog) => prog.clone(), @@ -1152,7 +867,9 @@ impl CompilerTest { /// Get the MASM source code pub fn masm_src(&mut self) -> String { if self.masm_src.is_none() { - self.compile_wasm_to_masm_program(); + if let Err(err) = self.compile_wasm_to_masm_program() { + panic!("{err}"); + } } self.masm_src.clone().unwrap() } @@ -1166,55 +883,43 @@ impl CompilerTest { } } - pub(crate) fn compile_wasm_to_masm_program(&mut self) { - use midenc_codegen_masm::MasmArtifact; - use midenc_compile::compile_to_memory_with_pre_assembly_stage; - use midenc_hir::pass::AnalysisManager; + /// Assemble the Wasm input to Miden Assembly + /// + /// If the Wasm has already been translated to the IR, it is just assembled, otherwise the + /// Wasm will be translated to the IR, caching the translation results, and then assembled. + pub(crate) fn compile_wasm_to_masm_program(&mut self) -> Result<(), String> { + use midenc_compile::CodegenOutput; + use midenc_hir::Context; let mut src = None; let mut masm_program = None; - let mut stage = - |artifact: MasmArtifact, _analyses: &mut AnalysisManager, _session: &Session| { - match artifact { - MasmArtifact::Executable(ref program) => { - src = Some(program.to_string()); - masm_program = Some(Arc::from(program.clone())); - } - MasmArtifact::Library(ref lib) => { - src = Some(lib.to_string()); - } - } - Ok(artifact) - }; - let package = - compile_to_memory_with_pre_assembly_stage(self.session.clone(), &mut stage as _) - .map_err(format_report) - .unwrap_or_else(|err| panic!("{err}")) - .unwrap_mast(); + let mut stage = |output: CodegenOutput, _context: Rc| { + src = Some(output.component.to_string()); + if output.component.entrypoint.is_some() { + masm_program = Some(Arc::clone(&output.component)); + } + Ok(output) + }; + + let link_output = self.link_output().clone(); + let package = compile_link_output_to_masm_with_pre_assembly_stage(link_output, &mut stage) + .map_err(format_report)? + .unwrap_mast(); + assert!(src.is_some(), "failed to pretty print masm artifact"); - assert!(masm_program.is_some(), "failed to capture masm artifact"); - assert!( - package.is_program(), - "expected to have produced an executable program, not a library" - ); self.masm_src = src; self.ir_masm_program = masm_program.map(Ok); self.package = Some(Ok(Arc::new(package))); + Ok(()) } } -fn format_report(report: miden_assembly::diagnostics::Report) -> String { - use miden_assembly::diagnostics::reporting::PrintDiagnostic; - - PrintDiagnostic::new(report).to_string() -} - fn stdlib_sys_crate_path() -> PathBuf { let cwd = std::env::current_dir().unwrap(); cwd.parent().unwrap().parent().unwrap().join("sdk").join("stdlib-sys") } -fn sdk_alloc_crate_path() -> PathBuf { +pub fn sdk_alloc_crate_path() -> PathBuf { let cwd = std::env::current_dir().unwrap(); cwd.parent().unwrap().parent().unwrap().join("sdk").join("alloc") } @@ -1237,66 +942,78 @@ fn get_workspace_dir() -> String { compiler_workspace_dir.to_string() } -fn report_cargo_error(child: std::process::Child) { - eprintln!("pwd: {:?}", std::env::current_dir().unwrap()); - let mut stderr = Vec::new(); - child.stderr.unwrap().read_exact(&mut stderr).expect("Failed to read stderr"); - let stderr = String::from_utf8(stderr).expect("Failed to parse stderr"); - eprintln!("stderr: {}", stderr); - panic!("Rust to Wasm compilation failed!"); -} +fn wasm_to_wat(wasm_bytes: &[u8]) -> String { + // Disable printing of the various custom sections, e.g. "producers", either because they + // contain strings which are highly variable (but not important), or because they are debug info + // related. + struct NoCustomSectionsPrinter(T); + impl wasmprinter::Print for NoCustomSectionsPrinter { + fn write_str(&mut self, s: &str) -> std::io::Result<()> { + self.0.write_str(s) + } -fn find_wasm_artifacts(child: &mut std::process::Child) -> Vec { - let mut wasm_artifacts = Vec::new(); - let reader = std::io::BufReader::new(child.stdout.take().unwrap()); - for message in cargo_metadata::Message::parse_stream(reader) { - if let cargo_metadata::Message::CompilerArtifact(artifact) = - message.expect("Failed to parse cargo metadata") - { - // find the Wasm artifact in artifact.filenames - for filename in artifact.filenames { - if filename.as_str().ends_with(".wasm") { - wasm_artifacts.push(filename.into_std_path_buf()); - } + fn newline(&mut self) -> std::io::Result<()> { + self.0.newline() + } + + fn start_line(&mut self, binary_offset: Option) { + self.0.start_line(binary_offset); + } + + fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> std::io::Result<()> { + self.0.write_fmt(args) + } + + fn print_custom_section( + &mut self, + name: &str, + binary_offset: usize, + data: &[u8], + ) -> std::io::Result { + match name { + "producers" | "target_features" => Ok(true), + debug if debug.starts_with(".debug") => Ok(true), + _ => self.0.print_custom_section(name, binary_offset, data), } } - } - wasm_artifacts -} -fn wasm_to_wat(wasm_bytes: &[u8]) -> String { - let mut wasm_printer = wasmprinter::Printer::new(); - // disable printing of the "producers" section because it contains a rustc version - // to not brake tests when rustc is updated - wasm_printer.add_custom_section_printer("producers", |_, _, _| Ok(())); - let wat = wasm_printer.print(wasm_bytes.as_ref()).unwrap(); - wat -} + fn start_literal(&mut self) -> std::io::Result<()> { + self.0.start_literal() + } -fn dummy_session(flags: &[&str]) -> Rc { - let dummy = InputFile::from_path(PathBuf::from("dummy.wasm")).unwrap(); - default_session([dummy], flags) -} + fn start_name(&mut self) -> std::io::Result<()> { + self.0.start_name() + } + + fn start_keyword(&mut self) -> std::io::Result<()> { + self.0.start_keyword() + } + + fn start_type(&mut self) -> std::io::Result<()> { + self.0.start_type() + } -/// Create a default session for testing -pub fn default_session(inputs: I, argv: &[S]) -> Rc -where - I: IntoIterator, - S: AsRef, -{ - use midenc_hir::diagnostics::reporting::{self, ReportHandlerOpts}; - - let result = reporting::set_hook(Box::new(|_| Box::new(ReportHandlerOpts::new().build()))); - if result.is_ok() { - reporting::set_panic_hook(); + fn start_comment(&mut self) -> std::io::Result<()> { + self.0.start_comment() + } + + fn reset_color(&mut self) -> std::io::Result<()> { + self.0.reset_color() + } + + fn supports_async_color(&self) -> bool { + self.0.supports_async_color() + } } - let argv = argv.iter().map(|arg| arg.as_ref()); - let session = midenc_compile::Compiler::new_session(inputs, None, argv); - Rc::new(session) + let mut wat = String::with_capacity(1024); + let config = wasmprinter::Config::new(); + let mut wasm_printer = NoCustomSectionsPrinter(wasmprinter::PrintFmtWrite(&mut wat)); + config.print(wasm_bytes, &mut wasm_printer).unwrap(); + wat } fn hash_string(inputs: &str) -> String { let hash = ::digest(inputs.as_bytes()); - format!("{:x}", hash) + format!("{hash:x}") } diff --git a/tests/integration/src/exec_emulator.rs b/tests/integration/src/exec_emulator.rs deleted file mode 100644 index ee40028f9..000000000 --- a/tests/integration/src/exec_emulator.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::sync::Arc; - -use midenc_codegen_masm::{Emulator, Program}; -use midenc_debug::TestFelt; -use midenc_hir::{Felt, Stack}; - -/// Execute the module using the emulator with the given arguments -/// Arguments are expected to be in the order they are passed to the entrypoint function -pub fn execute_emulator(program: Arc, args: &[Felt]) -> Vec { - let mut emulator = Emulator::default(); - emulator.load_program(program).expect("failed to load program"); - { - let stack = emulator.stack_mut(); - for arg in args.iter().copied().rev() { - stack.push(arg); - } - } - let operand_stack = emulator.start().expect("failed to invoke"); - operand_stack.stack().iter().map(|felt| TestFelt(*felt)).collect() -} diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index bdaf6190d..3a7da5321 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -1,15 +1,19 @@ //! Compilation and semantic tests for the whole compiler pipeline #![feature(iter_array_chunks)] #![feature(debug_closure_helpers)] -//#![deny(warnings)] +#![deny(warnings)] #![deny(missing_docs)] mod cargo_proj; mod compiler_test; -mod exec_emulator; +pub mod testing; -pub use compiler_test::{default_session, CargoTest, CompilerTest, CompilerTestBuilder, RustcTest}; -pub use exec_emulator::execute_emulator; +pub use self::{ + compiler_test::{CargoTest, CompilerTest, CompilerTestBuilder, RustcTest}, + testing::setup::default_session, +}; +#[cfg(test)] +mod codegen; #[cfg(test)] mod rust_masm_tests; diff --git a/tests/integration/src/rust_masm_tests/abi_transform/advice_map.rs b/tests/integration/src/rust_masm_tests/abi_transform/advice_map.rs new file mode 100644 index 000000000..3015874d4 --- /dev/null +++ b/tests/integration/src/rust_masm_tests/abi_transform/advice_map.rs @@ -0,0 +1,120 @@ +use core::panic; +use std::collections::VecDeque; + +use miden_core::{utils::group_slice_elements, FieldElement, StarkField}; +use miden_processor::AdviceInputs; +use midenc_debug::{Executor, FromMidenRepr, TestFelt, ToMidenRepr}; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; +use midenc_hir::Felt; +use midenc_session::Emit; +use proptest::{ + arbitrary::any, + prelude::TestCaseError, + prop_assert_eq, + test_runner::{TestError, TestRunner}, +}; + +use crate::{ + testing::{eval_package, Initializer}, + CompilerTest, +}; + +#[test] +fn test_adv_load_preimage() { + let main_fn = r#" + (k0: Felt, k1: Felt, k2: Felt, k3: Felt) -> alloc::vec::Vec { + let key = Word::from([k0, k1, k2, k3]); + + let num_felts = intrinsics::advice::adv_push_mapvaln(key.clone()); + + let num_felts_u64 = num_felts.as_u64(); + assert_eq(Felt::from_u32((num_felts_u64 % 4) as u32), felt!(0)); + let num_words = Felt::from_u64_unchecked(num_felts_u64 / 4); + + let commitment = key; + let input = adv_load_preimage(num_words, commitment); + assert_eq(input[0], felt!(1)); + assert_eq(input[1], felt!(2)); + assert_eq(input[5], felt!(6)); + assert_eq(input[14], felt!(15)); + input + }"# + .to_string(); + + let config = WasmTranslationConfig::default(); + let mut test = + CompilerTest::rust_fn_body_with_stdlib_sys("adv_load_preimage", &main_fn, config, []); + + // Test expected compilation artifacts + test.expect_wasm(expect_file![format!("../../../expected/adv_load_preimage.wat")]); + test.expect_ir(expect_file![format!("../../../expected/adv_load_preimage.hir")]); + test.expect_masm(expect_file![format!("../../../expected/adv_load_preimage.masm")]); + + let package = test.compiled_package(); + + // Create test data: 4 words (16 felts) + let input: Vec = vec![ + Felt::new(1), + Felt::new(2), + Felt::new(3), + Felt::new(4), + Felt::new(5), + Felt::new(6), + Felt::new(7), + Felt::new(8), + Felt::new(9), + Felt::new(10), + Felt::new(11), + Felt::new(12), + Felt::new(13), + Felt::new(14), + Felt::new(15), + Felt::new(Felt::MODULUS - 1), + ]; + + let commitment = miden_core::crypto::hash::Rpo256::hash_elements(&input); + dbg!(&commitment.to_hex()); + let mut advice_map = std::collections::BTreeMap::new(); + advice_map.insert(commitment, input.clone()); + + let out_addr = 20u32 * 65536; + let args = [ + commitment[3], + commitment[2], + commitment[1], + commitment[0], + Felt::new(out_addr as u64), + ]; + + let mut exec = Executor::for_package(&package, args.to_vec(), &test.session) + .expect("Failed to create executor"); + exec.with_advice_inputs(AdviceInputs::default().with_map(advice_map)); + let trace = exec.execute(&package.unwrap_program(), &test.session); + + let result_ptr = out_addr; + // Read the Vec metadata from memory (capacity, ptr, len, padding) + let vec_metadata: [TestFelt; 4] = + trace.read_from_rust_memory(result_ptr).expect("Failed to read vec metadata"); + + let capacity = vec_metadata[0].0.as_int() as usize; + let data_ptr = vec_metadata[1].0.as_int() as u32; + let vec_len = vec_metadata[2].0.as_int() as usize; + dbg!(capacity, data_ptr, vec_len); + + // Reconstruct the Vec by reading all words from memory + let mut loaded: Vec = Vec::with_capacity(capacity); + for i in 0..(vec_len / 4) { + let word_addr = data_ptr + (i * 4 * 4) as u32; + let w: [TestFelt; 4] = trace + .read_from_rust_memory(word_addr) + .unwrap_or_else(|| panic!("Failed to read word at index {}", i)); + loaded.push(w[0].0); + loaded.push(w[1].0); + loaded.push(w[2].0); + loaded.push(w[3].0); + } + + // Compare the reconstructed Vec exactly with input + assert_eq!(loaded, input, "Vec mismatch"); +} diff --git a/tests/integration/src/rust_masm_tests/abi_transform/mod.rs b/tests/integration/src/rust_masm_tests/abi_transform/mod.rs index c5b743a68..590dd5e12 100644 --- a/tests/integration/src/rust_masm_tests/abi_transform/mod.rs +++ b/tests/integration/src/rust_masm_tests/abi_transform/mod.rs @@ -1,2 +1,3 @@ +mod advice_map; mod stdlib; mod tx_kernel; diff --git a/tests/integration/src/rust_masm_tests/abi_transform/stdlib.rs b/tests/integration/src/rust_masm_tests/abi_transform/stdlib.rs index 8f87df273..2c7c0dac1 100644 --- a/tests/integration/src/rust_masm_tests/abi_transform/stdlib.rs +++ b/tests/integration/src/rust_masm_tests/abi_transform/stdlib.rs @@ -1,10 +1,11 @@ use core::panic; use std::collections::VecDeque; -use expect_test::expect_file; -use miden_core::utils::group_slice_elements; +use miden_core::{utils::group_slice_elements, FieldElement}; use miden_processor::AdviceInputs; -use midenc_debug::{Executor, PopFromStack, PushToStack, TestFelt}; +use midenc_debug::{Executor, TestFelt, ToMidenRepr}; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; use midenc_hir::Felt; use midenc_session::Emit; use proptest::{ @@ -14,17 +15,21 @@ use proptest::{ test_runner::{TestError, TestRunner}, }; -use crate::CompilerTest; +use crate::{ + testing::{eval_package, Initializer}, + CompilerTest, +}; #[test] fn test_blake3_hash() { let main_fn = "(a: [u8; 32]) -> [u8; 32] { miden_stdlib_sys::blake3_hash_1to1(a) }".to_string(); let artifact_name = "abi_transform_stdlib_blake3_hash"; + let config = WasmTranslationConfig::default(); let mut test = CompilerTest::rust_fn_body_with_stdlib_sys( artifact_name, &main_fn, - true, + config, ["--test-harness".into()], ); // Test expected compilation artifacts @@ -35,43 +40,39 @@ fn test_blake3_hash() { let package = test.compiled_package(); // Run the Rust and compiled MASM code against a bunch of random inputs and compare the results - let res = TestRunner::default().run(&any::<[u8; 32]>(), move |ibytes| { + let config = proptest::test_runner::Config::with_cases(10); + let res = TestRunner::new(config).run(&any::<[u8; 32]>(), move |ibytes| { let hash_bytes = blake3::hash(&ibytes); let rs_out = hash_bytes.as_bytes(); + + // Place the hash output at 20 * PAGE_SIZE, and the hash input at 21 * PAGE_SIZE let in_addr = 21u32 * 65536; let out_addr = 20u32 * 65536; - let mut frame = Vec::::default(); - // Convert input bytes to words - let words = midenc_debug::bytes_to_words(&ibytes); - for word in words.into_iter().rev() { - PushToStack::try_push(&word, &mut frame); - } - PushToStack::try_push(&2u32, &mut frame); // num_words - PushToStack::try_push(&(in_addr / 16), &mut frame); // dest_ptr - dbg!(&ibytes, &frame, rs_out); + let initializers = [Initializer::MemoryBytes { + addr: in_addr, + bytes: &ibytes, + }]; + + let owords = rs_out.to_words(); + + dbg!(&ibytes, rs_out); + // Arguments are: [hash_output_ptr, hash_input_ptr] - let mut exec = Executor::for_package( - &package, - // Place the hash output at 20 * PAGE_SIZE, and the hash input at 21 * PAGE_SIZE - vec![Felt::new(in_addr as u64), Felt::new(out_addr as u64)], - &test.session, - ) - .map_err(|err| TestCaseError::fail(err.to_string()))?; - // Reverse the stack contents, so that the correct order is preserved after - // MemAdviceProvider does its own reverse - frame.reverse(); - let advice_inputs = AdviceInputs::default().with_stack(frame); - exec.with_advice_inputs(advice_inputs); - let trace = exec.execute(&package.unwrap_program(), &test.session); - let vm_in: [u8; 32] = trace - .read_from_rust_memory(in_addr) - .expect("expected memory to have been written"); - dbg!(&vm_in); - let vm_out: [u8; 32] = trace - .read_from_rust_memory(out_addr) - .expect("expected memory to have been written"); - dbg!(&vm_out); - prop_assert_eq!(rs_out, &vm_out, "VM output mismatch"); + let args = [Felt::new(in_addr as u64), Felt::new(out_addr as u64)]; + eval_package::(&package, initializers, &args, &test.session, |trace| { + let vm_in: [u8; 32] = trace + .read_from_rust_memory(in_addr) + .expect("expected memory to have been written"); + dbg!(&vm_in); + prop_assert_eq!(&ibytes, &vm_in, "VM input mismatch"); + let vm_out: [u8; 32] = trace + .read_from_rust_memory(out_addr) + .expect("expected memory to have been written"); + dbg!(&vm_out); + prop_assert_eq!(rs_out, &vm_out, "VM output mismatch"); + Ok(()) + })?; + Ok(()) }); @@ -83,3 +84,193 @@ fn test_blake3_hash() { _ => panic!("Unexpected test result: {:?}", res), } } + +#[test] +fn test_hash_elements() { + let main_fn = r#" + (input: alloc::vec::Vec) -> miden_stdlib_sys::Felt { + let res = miden_stdlib_sys::hash_elements(input); + res.inner.inner.0 + }"# + .to_string(); + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_fn_body_with_stdlib_sys( + "hash_elements", + &main_fn, + config, + ["--test-harness".into()], + ); + // Test expected compilation artifacts + test.expect_wasm(expect_file![format!("../../../expected/hash_elements.wat")]); + test.expect_ir(expect_file![format!("../../../expected/hash_elements.hir")]); + test.expect_masm(expect_file![format!("../../../expected/hash_elements.masm")]); + + let package = test.compiled_package(); + + // Run the Rust and compiled MASM code against a bunch of random inputs and compare the results + let config = proptest::test_runner::Config::with_cases(32); + // let res = TestRunner::new(config).run(&any::<[midenc_debug::Felt; 8]>(), move |test_felts| { + let res = TestRunner::new(config).run(&any::>(), move |test_felts| { + let raw_felts: Vec = test_felts.into_iter().map(From::from).collect(); + + dbg!(raw_felts.len()); + let expected_digest = miden_core::crypto::hash::Rpo256::hash_elements(&raw_felts); + let expected_felts: [TestFelt; 4] = [ + TestFelt(expected_digest[0]), + TestFelt(expected_digest[1]), + TestFelt(expected_digest[2]), + TestFelt(expected_digest[3]), + ]; + let wide_ptr_addr = 20u32 * 65536; // 1310720 + + // The order below is exactly the order Rust compiled code is expected to have the data + // layed out in the fat pointer for the entrypoint. + let mut wide_ptr = vec![ + Felt::from(raw_felts.capacity() as u32), + Felt::from(wide_ptr_addr + 16), + Felt::from(raw_felts.len() as u32), + Felt::ZERO, + ]; + wide_ptr.extend_from_slice(&raw_felts); + let initializers = [ + Initializer::MemoryFelts { + addr: wide_ptr_addr / 4, + felts: (&wide_ptr).into(), + }, + // TODO: multiple initializers do not work + // Initializer::MemoryFelts { + // addr: in_addr / 4, + // felts: raw_felts.into(), + // }, + ]; + + let args = [Felt::new(wide_ptr_addr as u64)]; + + eval_package::(&package, initializers, &args, &test.session, |trace| { + let res: Felt = trace.parse_result().unwrap(); + dbg!(res); + dbg!(expected_digest[0]); + prop_assert_eq!(res, expected_digest[0]); + Ok(()) + })?; + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {:?}", value); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {:?}", res), + } +} + +#[test] +fn test_hash_words() { + // Similar to test_hash_elements, but passes Vec and uses hash_words + let main_fn = r#" + (input: alloc::vec::Vec) -> miden_stdlib_sys::Felt { + let res = miden_stdlib_sys::hash_words(&input); + // Return the first limb of the digest for easy comparison + res.inner.inner.0 + }"# + .to_string(); + + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_fn_body_with_stdlib_sys( + "hash_words", + &main_fn, + config, + ["--test-harness".into()], + ); + test.expect_wasm(expect_file![format!("../../../expected/hash_words.wat")]); + test.expect_ir(expect_file![format!("../../../expected/hash_words.hir")]); + test.expect_masm(expect_file![format!("../../../expected/hash_words.masm")]); + + let package = test.compiled_package(); + + let config = proptest::test_runner::Config::with_cases(32); + let res = + TestRunner::new(config).run(&any::>(), move |test_words| { + let raw_words: Vec<[Felt; 4]> = test_words + .into_iter() + .map(|w| [w[0].into(), w[1].into(), w[2].into(), w[3].into()]) + .collect(); + let mut flat_felts: Vec = Vec::with_capacity(raw_words.len() * 4); + for w in &raw_words { + flat_felts.extend_from_slice(w); + } + + let expected_digest = miden_core::crypto::hash::Rpo256::hash_elements(&flat_felts); + + let wide_ptr_addr = 20u32 * 65536; + + let mut wide_ptr: Vec = vec![ + Felt::from(raw_words.capacity() as u32), + Felt::from(wide_ptr_addr + 16), // pointer to first element just past header + Felt::from(raw_words.len() as u32), + Felt::ZERO, + ]; + for w in &raw_words { + wide_ptr.extend_from_slice(w); + } + + let initializers = [Initializer::MemoryFelts { + addr: wide_ptr_addr / 4, + felts: (&wide_ptr).into(), + }]; + + let args = [Felt::new(wide_ptr_addr as u64)]; + + eval_package::(&package, initializers, &args, &test.session, |trace| { + let res: Felt = trace.parse_result().unwrap(); + prop_assert_eq!(res, expected_digest[0]); + Ok(()) + })?; + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {:?}", value); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {:?}", res), + } +} + +#[test] +fn test_vec_alloc_vec() { + // regression test for https://github.com/0xMiden/compiler/issues/595 + let main_fn = r#" + (a: u32) -> Felt { + let input: alloc::vec::Vec = alloc::vec![ + felt!(1), + felt!(2), + felt!(3), + ]; + input[a as usize] + } + "# + .to_string(); + let config = WasmTranslationConfig::default(); + let mut test = + CompilerTest::rust_fn_body_with_stdlib_sys("vec_alloc_vec", &main_fn, config, []); + // Test expected compilation artifacts + test.expect_wasm(expect_file![format!("../../../expected/vec_alloc_vec.wat")]); + test.expect_ir(expect_file![format!("../../../expected/vec_alloc_vec.hir")]); + test.expect_masm(expect_file![format!("../../../expected/vec_alloc_vec.masm")]); + + let package = test.compiled_package(); + + let args = [Felt::from(2u32)]; + + eval_package::(&package, [], &args, &test.session, |trace| { + let res: u32 = trace.parse_result().unwrap(); + assert_eq!(res, 3, "unexpected result (regression test for https://github.com/0xMiden/compiler/issues/595)"); + Ok(()) + }) + .unwrap(); +} diff --git a/tests/integration/src/rust_masm_tests/abi_transform/tx_kernel.rs b/tests/integration/src/rust_masm_tests/abi_transform/tx_kernel.rs index 8443c2a8f..a4c3a14be 100644 --- a/tests/integration/src/rust_masm_tests/abi_transform/tx_kernel.rs +++ b/tests/integration/src/rust_masm_tests/abi_transform/tx_kernel.rs @@ -1,13 +1,14 @@ use std::fmt::Write; -use expect_test::expect_file; use miden_assembly::LibraryPath; use miden_core::{Felt, FieldElement}; use miden_processor::ExecutionError; use midenc_debug::Executor; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; use midenc_session::{diagnostics::Report, Emit}; -use crate::{execute_emulator, CompilerTestBuilder}; +use crate::CompilerTestBuilder; #[allow(unused)] fn setup_log() { @@ -21,10 +22,10 @@ fn setup_log() { #[test] fn test_get_inputs_4() -> Result<(), Report> { - test_get_inputs("4", vec![u32::MAX.into(), Felt::ONE, Felt::ZERO, u32::MAX.into()]) + test_get_inputs("4", vec![u32::MAX, 1, 2, 3]) } -fn test_get_inputs(test_name: &str, expected_inputs: Vec) -> Result<(), Report> { +fn test_get_inputs(test_name: &str, expected_inputs: Vec) -> Result<(), Report> { assert!(expected_inputs.len() == 4, "for now only word-sized inputs are supported"); let masm = format!( " @@ -36,55 +37,51 @@ export.get_inputs push.4 end ", - expect1 = expected_inputs.first().map(|i| i.as_int()).unwrap_or(0), - expect2 = expected_inputs.get(1).map(|i| i.as_int()).unwrap_or(0), - expect3 = expected_inputs.get(2).map(|i| i.as_int()).unwrap_or(0), - expect4 = expected_inputs.get(3).map(|i| i.as_int()).unwrap_or(0), + expect1 = expected_inputs[0], + expect2 = expected_inputs[1], + expect3 = expected_inputs[2], + expect4 = expected_inputs[3], ); - let main_fn = "() -> Vec { get_inputs() }"; - let artifact_name = format!("abi_transform_tx_kernel_get_inputs_{}", test_name); + let main_fn = format!( + r#"() -> () {{ + let v = miden::note::get_inputs(); + assert_eq(v.len().into(), felt!(4)); + assert_eq(v[0], felt!({expect1})); + assert_eq(v[1], felt!({expect2})); + assert_eq(v[2], felt!({expect3})); + assert_eq(v[3], felt!({expect4})); + }}"#, + expect1 = expected_inputs[0], + expect2 = expected_inputs[1], + expect3 = expected_inputs[2], + expect4 = expected_inputs[3], + ); + let artifact_name = format!("abi_transform_tx_kernel_get_inputs_{test_name}"); + let config = WasmTranslationConfig::default(); let mut test_builder = - CompilerTestBuilder::rust_fn_body_with_sdk(artifact_name.clone(), main_fn, true, None); + CompilerTestBuilder::rust_fn_body_with_sdk(artifact_name.clone(), &main_fn, config, []); test_builder.link_with_masm_module("miden::note", masm); let mut test = test_builder.build(); - // Test expected compilation artifacts test.expect_wasm(expect_file![format!("../../../expected/{artifact_name}.wat")]); test.expect_ir(expect_file![format!("../../../expected/{artifact_name}.hir")]); test.expect_masm(expect_file![format!("../../../expected/{artifact_name}.masm")]); - let package = test.compiled_package(); - // Provide a place in memory where the vector returned by `get_inputs` should be stored - let out_addr = 18u32 * 65536; - let exec = Executor::for_package(&package, vec![Felt::new(out_addr as u64)], &test.session)?; - let trace = exec.execute(&package.unwrap_program(), &test.session); - // Verify that the vector contains the expected elements: - // - // Rust lays out the vector struct as follows (lowest addressed bytes first): - // - // [capacity, buf_ptr, len] - // - // 1. Extract the data pointer and length from the vector written to out_addr - let data_ptr = trace.read_memory_element(out_addr / 16, 1).unwrap().as_int() as u32; - assert_ne!(data_ptr, 0, "expected non-null data pointer"); - dbg!(data_ptr); - let len = trace.read_memory_element(out_addr / 16, 2).unwrap().as_int() as usize; - assert_eq!( - len, - expected_inputs.len(), - "expected vector to contain all of the expected inputs" - ); - // 2. Read the vector elements via data_ptr and ensure they match the inputs - dbg!(len); - let word = trace.read_memory_word(data_ptr / 16).unwrap(); - assert_eq!( - word.as_slice(), - expected_inputs.as_slice(), - "expected vector contents to match inputs" - ); - - // let ir_program = test.ir_masm_program(); - // let emul_out = execute_emulator(ir_program.clone(), &[]); + let exec = Executor::for_package(&package, vec![], &test.session)?; + let _ = exec.execute(&package.unwrap_program(), &test.session); Ok(()) } + +#[test] +fn test_get_id() { + let main_fn = "() -> AccountId { miden::account::get_id() }"; + let artifact_name = "abi_transform_tx_kernel_get_id"; + let config = WasmTranslationConfig::default(); + let test_builder = + CompilerTestBuilder::rust_fn_body_with_sdk(artifact_name, main_fn, config, []); + let mut test = test_builder.build(); + // Test expected compilation artifacts + test.expect_wasm(expect_file![format!("../../../expected/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../../expected/{artifact_name}.hir")]); +} diff --git a/tests/integration/src/rust_masm_tests/apps.rs b/tests/integration/src/rust_masm_tests/apps.rs index 98955fe79..162b32cf7 100644 --- a/tests/integration/src/rust_masm_tests/apps.rs +++ b/tests/integration/src/rust_masm_tests/apps.rs @@ -1,40 +1,178 @@ use std::collections::VecDeque; -use expect_test::expect_file; -use midenc_debug::{Executor, PopFromStack, PushToStack}; +use midenc_debug::Executor; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; use midenc_hir::Felt; use proptest::{prelude::*, test_runner::TestRunner}; -use crate::CompilerTest; +use crate::{ + cargo_proj::project, + compiler_test::{sdk_alloc_crate_path, sdk_crate_path}, + CompilerTest, CompilerTestBuilder, +}; + +fn cargo_toml(name: &str) -> String { + let sdk_alloc_path = sdk_alloc_crate_path(); + let sdk_path = sdk_crate_path(); + format!( + r#" + [package] + name = "{name}" + version = "0.0.1" + edition = "2021" + authors = [] + + [lib] + crate-type = ["cdylib"] + + [dependencies] + miden-sdk-alloc = {{ path = "{sdk_alloc_path}" }} + miden = {{ path = "{sdk_path}" }} + + [profile.release] + # optimize the output for size + opt-level = "z" + panic = "abort" + + [profile.dev] + panic = "abort" + opt-level = 1 + debug-assertions = true + overflow-checks = false + debug = false + "#, + sdk_alloc_path = sdk_alloc_path.display(), + sdk_path = sdk_path.display() + ) +} + +#[test] +fn function_call_hir2() { + let name = "function_call_hir2"; + let cargo_proj = project(name) + .file("Cargo.toml", &cargo_toml(name)) + .file( + "src/lib.rs", + r#" + #![no_std] + + // Global allocator to use heap memory in no-std environment + // #[global_allocator] + // static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + + // Required for no-std crates + #[panic_handler] + fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} + } + + // use miden::Felt; + + #[no_mangle] + #[inline(never)] + pub fn add(a: u32, b: u32) -> u32 { + a + b + } + + #[no_mangle] + pub fn entrypoint(a: u32, b: u32) -> u32 { + add(a, b) + } + "#, + ) + .build(); + let mut test = CompilerTestBuilder::rust_source_cargo_miden( + cargo_proj.root(), + WasmTranslationConfig::default(), + [], + ) + .build(); + + let artifact_name = name; + test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); +} + +#[test] +fn mem_intrinsics_heap_base() { + let name = "mem_intrinsics_heap_base"; + let cargo_proj = project(name) + .file("Cargo.toml", &cargo_toml(name)) + .file( + "src/lib.rs", + r#" + #![no_std] + + // Global allocator to use heap memory in no-std environment + #[global_allocator] + static ALLOC: miden_sdk_alloc::BumpAlloc = miden_sdk_alloc::BumpAlloc::new(); + + // Required for no-std crates + #[panic_handler] + fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} + } + + extern crate alloc; + use alloc::{vec, vec::Vec}; + + #[no_mangle] + pub fn entrypoint(a: u32) -> Vec { + vec![a*2] + } + "#, + ) + .build(); + let mut test = CompilerTestBuilder::rust_source_cargo_miden( + cargo_proj.root(), + WasmTranslationConfig::default(), + [], + ) + .build(); + + let artifact_name = name; + test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); +} #[test] -fn fib() { - let mut test = - CompilerTest::rust_source_cargo("fib", "miden_integration_tests_rust_fib_wasm", "fib"); - // Test expected compilation artifacts - test.expect_wasm(expect_file!["../../expected/fib.wat"]); - test.expect_ir(expect_file!["../../expected/fib.hir"]); - test.expect_masm(expect_file!["../../expected/fib.masm"]); - // let ir_masm = test.ir_masm_program(); - let package = test.compiled_package(); - - // Run the Rust and compiled MASM code against a bunch of random inputs and compare the results - TestRunner::default() - .run(&(1u32..30), move |a| { - let rust_out = miden_integration_tests_rust_fib::fib(a); - let mut args = Vec::::default(); - PushToStack::try_push(&a, &mut args); - - let exec = Executor::for_package(&package, args, &test.session) - .map_err(|err| TestCaseError::fail(err.to_string()))?; - let output: u32 = exec.execute_into(&package.unwrap_program(), &test.session); - dbg!(output); - prop_assert_eq!(rust_out, output); - // args.reverse(); - // let emul_out: u32 = - // execute_emulator(ir_masm.clone(), &args).first().unwrap().clone().into(); - // prop_assert_eq!(rust_out, emul_out); - Ok(()) - }) - .unwrap(); +fn felt_intrinsics() { + let name = "felt_intrinsics"; + let cargo_proj = project(name) + .file("Cargo.toml", &cargo_toml(name)) + .file( + "src/lib.rs", + r#" + #![no_std] + + // Required for no-std crates + #[panic_handler] + fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} + } + + // Global allocator to use heap memory in no-std environment + #[global_allocator] + static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + + use miden::*; + + #[no_mangle] + pub fn entrypoint(a: Felt, b: Felt) -> Felt { + a / (a * b - a + b) + } + "#, + ) + .build(); + let mut test = CompilerTestBuilder::rust_source_cargo_miden( + cargo_proj.root(), + WasmTranslationConfig::default(), + [], + ) + .build(); + + let artifact_name = name; + test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); } diff --git a/tests/integration/src/rust_masm_tests/components.rs b/tests/integration/src/rust_masm_tests/components.rs deleted file mode 100644 index e48bedbcd..000000000 --- a/tests/integration/src/rust_masm_tests/components.rs +++ /dev/null @@ -1,232 +0,0 @@ -use expect_test::expect_file; -use miden_core::crypto::hash::RpoDigest; -use midenc_frontend_wasm::{ImportMetadata, WasmTranslationConfig}; -use midenc_hir::{FunctionType, Ident, InterfaceFunctionIdent, InterfaceIdent, Symbol, Type}; - -use crate::{cargo_proj::project, CompilerTest}; - -#[test] -fn wcm_no_imports() { - let config = Default::default(); - - let proj = project("wcm_no_imports") - .file( - "Cargo.toml", - r#" - [package] - name = "add-wasm-component" - version = "0.0.1" - edition = "2021" - authors = [] - - [dependencies] - wit-bindgen-rt = "0.28" - wee_alloc = { version = "0.4.5", default-features = false} - - [lib] - crate-type = ["cdylib"] - - [package.metadata.component] - package = "miden:add" - - [profile.release] - panic = "abort" - "#, - ) - .file( - "src/lib.rs", - r#" - #![no_std] - - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - - #[panic_handler] - fn my_panic(_info: &core::panic::PanicInfo) -> ! { - loop {} - } - - bindings::export!(Component with_types_in bindings); - - mod bindings; - - use crate::bindings::exports::miden::add_package::add_interface::Guest; - - struct Component; - - impl Guest for Component { - fn add(a: u32, b: u32) -> u32 { - a + b - } - } - "#, - ) - .file( - "wit/add.wit", - r#" - package miden:add-package@1.0.0; - - interface add-interface { - add: func(a: u32, b: u32) -> u32; - } - - world add-world { - export add-interface; - } - "#, - ) - .build(); - let mut test = CompilerTest::rust_source_cargo_component(proj.root(), config); - let artifact_name = test.artifact_name(); - test.expect_wasm(expect_file![format!("../../expected/components/{artifact_name}.wat")]); - test.expect_ir(expect_file![format!("../../expected/components/{artifact_name}.hir")]); -} - -#[test] -fn wcm_import() { - // Imports an add component used in the above test - - let interface_function_ident = InterfaceFunctionIdent { - interface: InterfaceIdent::from_full_ident( - "miden:add-package/add-interface@1.0.0".to_string(), - ), - function: Symbol::intern("add"), - }; - let import_metadata = [( - interface_function_ident, - ImportMetadata { - digest: RpoDigest::default(), - }, - )] - .into_iter() - .collect(); - - let config = WasmTranslationConfig { - import_metadata, - ..Default::default() - }; - - // Create the add component that will be imported in the wcm_import project - let _add_proj_dep = project("wcm_import_add") - .file( - "wit/add.wit", - r#" - package miden:add-package@1.0.0; - - interface add-interface { - add: func(a: u32, b: u32) -> u32; - } - - world add-world { - export add-interface; - } - "#, - ) - .no_manifest() - .build(); - - let proj = project("wcm_import") - .file( - "Cargo.toml", - r#" - [package] - name = "inc-wasm-component" - version = "0.0.1" - edition = "2021" - authors = [] - - [dependencies] - wit-bindgen-rt = "0.28" - wee_alloc = { version = "0.4.5", default-features = false} - - [lib] - crate-type = ["cdylib"] - - [package.metadata.component] - package = "miden:inc" - - [package.metadata.component.target.dependencies] - "miden:add" = { path = "../wcm_import_add/wit" } - - [profile.release] - panic = "abort" - "#, - ) - .file( - "src/lib.rs", - r#" - #![no_std] - - #[global_allocator] - static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - - #[panic_handler] - fn my_panic(_info: &core::panic::PanicInfo) -> ! { - loop {} - } - - bindings::export!(Component with_types_in bindings); - - mod bindings; - - use crate::bindings::{miden::add_package::add_interface::add, Guest}; - - struct Component; - - impl Guest for Component { - fn inc(a: u32) -> u32 { - add(a, 1) - } - } - "#, - ) - .file( - "wit/inc.wit", - r#" - package miden:inc-package@1.0.0; - - use miden:add-package/add-interface@1.0.0; - - world inc { - import add-interface; - export inc: func(a: u32) -> u32; - } - "#, - ) - .build(); - - let mut test = CompilerTest::rust_source_cargo_component(proj.root(), config); - dbg!(&test); - let artifact_name = test.artifact_name(); - test.expect_wasm(expect_file![format!("../../expected/components/{artifact_name}.wat")]); - test.expect_ir(expect_file![format!("../../expected/components/{artifact_name}.hir")]); - - let ir_component = test.hir().unwrap_component(); - - assert!(!ir_component.modules().is_empty()); - - let export_name_sym = Symbol::intern("inc"); - let export = ir_component.exports().get(&export_name_sym.into()).unwrap(); - assert_eq!(export.function.function.as_symbol(), export_name_sym); - - let expected_export_func_ty = FunctionType::new_wasm(vec![Type::U32], vec![Type::U32]); - assert_eq!(export.function_ty, expected_export_func_ty); - let module = ir_component.modules().first().unwrap().1; - dbg!(&module.imports()); - let import_info = module.imports(); - let function_id = *import_info - .imported(&Ident::from("miden:add-package/add-interface@1.0.0")) - .unwrap() - .iter() - .collect::>() - .first() - .cloned() - .unwrap(); - let component_import = - ir_component.imports().get(&function_id).unwrap().unwrap_canon_abi_import(); - assert_eq!(component_import.interface_function, interface_function_ident); - assert!(!component_import.function_ty.params.is_empty()); - let expected_import_func_ty = - FunctionType::new_wasm(vec![Type::U32, Type::U32], vec![Type::U32]); - assert_eq!(component_import.function_ty, expected_import_func_ty); -} diff --git a/tests/integration/src/rust_masm_tests/examples.rs b/tests/integration/src/rust_masm_tests/examples.rs new file mode 100644 index 000000000..ab88d370a --- /dev/null +++ b/tests/integration/src/rust_masm_tests/examples.rs @@ -0,0 +1,383 @@ +use std::{collections::VecDeque, sync::Arc}; + +use miden_core::utils::{Deserializable, Serializable}; +use miden_mast_package::Package; +use miden_objects::account::AccountComponentMetadata; +use midenc_debug::{Executor, ToMidenRepr}; +use midenc_expect_test::{expect, expect_file}; +use midenc_frontend_wasm::WasmTranslationConfig; +use midenc_hir::{ + interner::Symbol, Felt, FunctionIdent, Ident, Immediate, Op, SourceSpan, SymbolTable, +}; +use prop::test_runner::{Config, TestRunner}; +use proptest::prelude::*; + +use crate::{cargo_proj::project, CompilerTest, CompilerTestBuilder}; + +#[test] +fn storage_example() { + let config = WasmTranslationConfig::default(); + let mut test = + CompilerTest::rust_source_cargo_miden("../../examples/storage-example", config, []); + + test.expect_wasm(expect_file!["../../expected/examples/storage_example.wat"]); + test.expect_ir(expect_file!["../../expected/examples/storage_example.hir"]); + test.expect_masm(expect_file!["../../expected/examples/storage_example.masm"]); + let package = test.compiled_package(); + let account_component_metadata_bytes = + package.as_ref().account_component_metadata_bytes.clone().unwrap(); + let toml = AccountComponentMetadata::read_from_bytes(&account_component_metadata_bytes) + .unwrap() + .as_toml() + .unwrap(); + expect![[r#" + name = "storage-example" + description = "A simple example of a Miden account storage API" + version = "0.1.0" + supported-types = ["RegularAccountUpdatableCode"] + + [[storage]] + name = "owner_public_key" + description = "test value" + slot = 0 + type = "auth::rpo_falcon512::pub_key" + + [[storage]] + name = "asset_qty_map" + description = "test map" + slot = 1 + values = [] + "#]] + .assert_eq(&toml); +} + +#[test] +fn fibonacci() { + fn expected_fib(n: u32) -> u32 { + let mut a = 0; + let mut b = 1; + for _ in 0..n { + let c = a + b; + a = b; + b = c; + } + a + } + + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden("../../examples/fibonacci", config, []); + test.expect_wasm(expect_file!["../../expected/examples/fib.wat"]); + test.expect_ir(expect_file!["../../expected/examples/fib.hir"]); + test.expect_masm(expect_file!["../../expected/examples/fib.masm"]); + let package = test.compiled_package(); + + // Run the Rust and compiled MASM code against a bunch of random inputs and compare the results + TestRunner::default() + .run(&(1u32..30), move |a| { + let rust_out = expected_fib(a); + let args = a.to_felts(); + let exec = Executor::for_package(&package, args, &test.session) + .map_err(|err| TestCaseError::fail(err.to_string()))?; + let output: u32 = exec.execute_into(&package.unwrap_program(), &test.session); + dbg!(output); + prop_assert_eq!(rust_out, output); + Ok(()) + }) + .unwrap_or_else(|err| panic!("{err}")); +} + +#[test] +fn collatz() { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + fn expected(mut n: u32) -> u32 { + let mut steps = 0; + while n != 1 { + if n.is_multiple_of(2) { + n /= 2; + } else { + n = 3 * n + 1; + } + steps += 1; + } + steps + } + + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden("../../examples/collatz", config, []); + let artifact_name = "collatz"; + test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); + test.expect_masm(expect_file![format!("../../expected/{artifact_name}.masm")]); + let package = test.compiled_package(); + + // Run the Rust and compiled MASM code against a bunch of random inputs and compare the results + TestRunner::new(Config::with_cases(4)) + .run(&(1u32..30), move |a| { + let rust_out = expected(a); + let args = a.to_felts(); + let exec = Executor::for_package(&package, args, &test.session) + .map_err(|err| TestCaseError::fail(err.to_string()))?; + let output: u32 = exec.execute_into(&package.unwrap_program(), &test.session); + dbg!(output); + prop_assert_eq!(rust_out, output); + Ok(()) + }) + .unwrap_or_else(|err| { + panic!("{err}"); + }); +} + +#[test] +fn is_prime() { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); + + fn expected(n: u32) -> bool { + if n <= 1 { + return false; + } + if n <= 3 { + return true; + } + if n.is_multiple_of(2) || n.is_multiple_of(3) { + return false; + } + let mut i = 5; + while i * i <= n { + if n.is_multiple_of(i) || n.is_multiple_of(i + 2) { + return false; + } + i += 6; + } + true + } + + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden("../../examples/is-prime", config, []); + let artifact_name = "is_prime"; + test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); + test.expect_masm(expect_file![format!("../../expected/{artifact_name}.masm")]); + let package = test.compiled_package(); + let hir = test.hir(); + + println!("{}", hir.borrow().as_operation()); + + // Run the Rust and compiled MASM code against a bunch of random inputs and compare the results + TestRunner::new(Config::with_cases(100)) + .run(&(1u32..30), move |a| { + let rust_out = expected(a); + + // Test the IR + let mut evaluator = + midenc_hir_eval::HirEvaluator::new(hir.borrow().as_operation().context_rc()); + let op = hir + .borrow() + .symbol_manager() + .lookup_symbol_ref( + &midenc_hir::SymbolPath::new([ + midenc_hir::SymbolNameComponent::Component("is_prime".into()), + midenc_hir::SymbolNameComponent::Leaf("entrypoint".into()), + ]) + .unwrap(), + ) + .unwrap(); + let result = evaluator + .eval(&op.borrow(), [midenc_hir_eval::Value::Immediate((a as i32).into())]) + .unwrap_or_else(|err| panic!("{err}")); + let midenc_hir_eval::Value::Immediate(Immediate::I32(result)) = result[0] else { + //return Err(TestCaseError::fail(format!( + panic!("expected i32 immediate for input {a}, got {:?}", result[0]); + //))); + }; + prop_assert_eq!(rust_out as i32, result); + + let args = a.to_felts(); + let exec = Executor::for_package(&package, args, &test.session) + .map_err(|err| TestCaseError::fail(err.to_string()))?; + let output: u32 = exec.execute_into(&package.unwrap_program(), &test.session); + dbg!(output); + prop_assert_eq!(rust_out as u32, output); + Ok(()) + }) + .unwrap_or_else(|err| { + panic!("{err}"); + }); +} + +#[test] +fn counter_contract() { + let config = WasmTranslationConfig::default(); + let mut builder_release = CompilerTestBuilder::rust_source_cargo_miden( + "../../examples/counter-contract", + config.clone(), + [], + ); + builder_release.with_release(true); + let mut test_release = builder_release.build(); + test_release.expect_wasm(expect_file!["../../expected/examples/counter.wat"]); + test_release.expect_ir(expect_file!["../../expected/examples/counter.hir"]); + test_release.expect_masm(expect_file!["../../expected/examples/counter.masm"]); + let package = test_release.compiled_package(); + let account_component_metadata_bytes = + package.as_ref().account_component_metadata_bytes.clone().unwrap(); + let toml = AccountComponentMetadata::read_from_bytes(&account_component_metadata_bytes) + .unwrap() + .as_toml() + .unwrap(); + expect![[r#" + name = "counter-contract" + description = "A simple example of a Miden counter contract using the Account Storage API" + version = "0.1.0" + supported-types = ["RegularAccountUpdatableCode"] + + [[storage]] + name = "count_map" + description = "counter contract storage map" + slot = 0 + values = [] + "#]] + .assert_eq(&toml); +} + +#[test] +fn counter_contract_debug_build() { + // This build checks the dev profile build compilation for counter-contract + // see https://github.com/0xMiden/compiler/issues/510 + let config = WasmTranslationConfig::default(); + let mut builder = + CompilerTestBuilder::rust_source_cargo_miden("../../examples/counter-contract", config, []); + builder.with_release(false); + let mut test = builder.build(); + let package = test.compiled_package(); +} + +#[test] +fn counter_note() { + // build and check counter-note + let config = WasmTranslationConfig::default(); + let builder = + CompilerTestBuilder::rust_source_cargo_miden("../../examples/counter-note", config, []); + + let mut test = builder.build(); + + test.expect_wasm(expect_file!["../../expected/examples/counter_note.wat"]); + test.expect_ir(expect_file!["../../expected/examples/counter_note.hir"]); + test.expect_masm(expect_file!["../../expected/examples/counter_note.masm"]); + let package = test.compiled_package(); + assert!(package.is_program(), "expected program"); + + // TODO: uncomment after the testing environment implemented (node, devnet, etc.) + // + // let mut exec = Executor::new(vec![]); + // for dep_path in test.dependencies { + // let account_package = + // Arc::new(Package::read_from_bytes(&std::fs::read(dep_path).unwrap()).unwrap()); + // exec.dependency_resolver_mut() + // .add(account_package.digest(), account_package.into()); + // } + // exec.with_dependencies(&package.manifest.dependencies).unwrap(); + // let trace = exec.execute(&package.unwrap_program(), &test.session); +} + +#[test] +fn basic_wallet_and_p2id() { + let config = WasmTranslationConfig::default(); + let mut test = + CompilerTest::rust_source_cargo_miden("../../examples/basic-wallet", config.clone(), []); + test.expect_wasm(expect_file![format!("../../expected/examples/basic_wallet.wat")]); + test.expect_ir(expect_file![format!("../../expected/examples/basic_wallet.hir")]); + test.expect_masm(expect_file![format!("../../expected/examples/basic_wallet.masm")]); + let account_package = test.compiled_package(); + assert!(account_package.is_library(), "expected library"); + + let mut test = CompilerTest::rust_source_cargo_miden( + "../../examples/basic-wallet-tx-script", + config.clone(), + [], + ); + test.expect_wasm(expect_file![format!("../../expected/examples/basic_wallet_tx_script.wat")]); + test.expect_ir(expect_file![format!("../../expected/examples/basic_wallet_tx_script.hir")]); + test.expect_masm(expect_file![format!("../../expected/examples/basic_wallet_tx_script.masm")]); + let package = test.compiled_package(); + assert!(package.is_program(), "expected program"); + + let mut test = CompilerTest::rust_source_cargo_miden("../../examples/p2id-note", config, []); + test.expect_wasm(expect_file![format!("../../expected/examples/p2id.wat")]); + test.expect_ir(expect_file![format!("../../expected/examples/p2id.hir")]); + test.expect_masm(expect_file![format!("../../expected/examples/p2id.masm")]); + let note_package = test.compiled_package(); + assert!(note_package.is_program(), "expected program"); +} + +#[test] +fn auth_component_no_auth() { + let config = WasmTranslationConfig::default(); + let mut test = + CompilerTest::rust_source_cargo_miden("../../examples/auth-component-no-auth", config, []); + test.expect_wasm(expect_file![format!("../../expected/examples/auth_component_no_auth.wat")]); + test.expect_ir(expect_file![format!("../../expected/examples/auth_component_no_auth.hir")]); + test.expect_masm(expect_file![format!("../../expected/examples/auth_component_no_auth.masm")]); + let auth_comp_package = test.compiled_package(); + let lib = auth_comp_package.unwrap_library(); + let expected_module = "miden:base/authentication-component@1.0.0"; + let expected_function = "auth__procedure"; + let exports = lib + .exports() + .map(|e| format!("{}::{}", e.name.module, e.name.name.as_str())) + .collect::>(); + // dbg!(&exports); + assert!( + lib.exports().any(|export| { + export.name.module.to_string() == expected_module + && export.name.name.as_str() == expected_function + }), + "expected one of the exports to contain module '{expected_module}' and function \ + '{expected_function}'" + ); + + // Test that the package loads + let bytes = auth_comp_package.to_bytes(); + let _loaded_package = miden_mast_package::Package::read_from_bytes(&bytes).unwrap(); +} + +#[test] +fn auth_component_rpo_falcon512() { + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden( + "../../examples/auth-component-rpo-falcon512", + config, + [], + ); + test.expect_wasm(expect_file![format!( + "../../expected/examples/auth_component_rpo_falcon512.wat" + )]); + test.expect_ir(expect_file![format!( + "../../expected/examples/auth_component_rpo_falcon512.hir" + )]); + test.expect_masm(expect_file![format!( + "../../expected/examples/auth_component_rpo_falcon512.masm" + )]); + let auth_comp_package = test.compiled_package(); + let lib = auth_comp_package.unwrap_library(); + let expected_module = "miden:base/authentication-component@1.0.0"; + let expected_function = "auth__procedure"; + assert!( + lib.exports().any(|export| { + export.name.module.to_string() == expected_module + && export.name.name.as_str() == expected_function + }), + "expected one of the exports to contain module '{expected_module}' and function \ + '{expected_function}'" + ); + + // Test that the package loads + let bytes = auth_comp_package.to_bytes(); + let _loaded_package = miden_mast_package::Package::read_from_bytes(&bytes).unwrap(); +} diff --git a/tests/integration/src/rust_masm_tests/instructions.rs b/tests/integration/src/rust_masm_tests/instructions.rs index 561a859b7..30fddce42 100644 --- a/tests/integration/src/rust_masm_tests/instructions.rs +++ b/tests/integration/src/rust_masm_tests/instructions.rs @@ -1,12 +1,18 @@ -use expect_test::expect_file; -use midenc_debug::PushToStack; +use miden_core::{Felt, FieldElement, Word}; +use midenc_debug::ToMidenRepr; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; +use midenc_hir::SmallVec; use proptest::{ prelude::*, test_runner::{TestError, TestRunner}, }; use super::run_masm_vs_rust; -use crate::CompilerTest; +use crate::{ + testing::{eval_package, Initializer}, + CompilerTest, +}; macro_rules! test_bin_op { ($name:ident, $op:tt, $op_ty:ty, $res_ty:ty, $a_range:expr, $b_range:expr) => { @@ -37,8 +43,9 @@ macro_rules! test_bin_op { let rs_out = a $op b; dbg!(&rs_out); let mut args = Vec::::default(); - PushToStack::try_push(&b, &mut args); - PushToStack::try_push(&a, &mut args); + b.push_to_operand_stack(&mut args); + a.push_to_operand_stack(&mut args); + dbg!(&args); run_masm_vs_rust(rs_out, &package, &args, &test.session) }); match res { @@ -47,7 +54,74 @@ macro_rules! test_bin_op { }, Ok(_) => (), _ => panic!("Unexpected test result: {:?}", res), - } + } + } + }); + }; +} + +macro_rules! test_wide_bin_op { + ($name:ident, $op:tt, $op_ty:ty, $res_ty:ty, $a_range:expr, $b_range:expr) => { + test_wide_bin_op!($name, $op, $op_ty, $op_ty, $res_ty, $a_range, $b_range); + }; + + ($name:ident, $op:tt, $a_ty:ty, $b_ty:ty, $res_ty:tt, $a_range:expr, $b_range:expr) => { + concat_idents::concat_idents!(test_name = $name, _, $a_ty { + #[test] + fn test_name() { + let op_str = stringify!($op); + let a_ty_str = stringify!($a_ty); + let b_ty_str = stringify!($b_ty); + let res_ty_str = stringify!($res_ty); + let main_fn = format!("(a: {a_ty_str}, b: {b_ty_str}) -> {res_ty_str} {{ a {op_str} b }}"); + let mut test = CompilerTest::rust_fn_body(&main_fn, None); + // Test expected compilation artifacts + let artifact_name = format!("{}_{}", stringify!($name), stringify!($a_ty)); + test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); + test.expect_masm(expect_file![format!("../../expected/{artifact_name}.masm")]); + let package = test.compiled_package(); + + let res = TestRunner::default().run(&($a_range, $b_range), move |(a, b)| { + dbg!(a, b); + let rs_out = a $op b; + dbg!(&rs_out); + + // Moves the little-endian 32bit limbs [A, B, C, D] to [D, C, B, A]. + let rs_out = ((rs_out >> 32) & 0xffffffff_00000000_00000000) + | ((rs_out & 0xffffffff_00000000_00000000) << 32) + | ((rs_out & 0xffffffff_00000000) >> 32) + | ((rs_out & 0xffffffff) << 32); + let rs_out_bytes = rs_out.to_le_bytes(); + + // Write the operation result to 20 * PAGE_SIZE. + let out_addr = 20u32 * 65536; + + let mut args = Vec::::default(); + b.push_to_operand_stack(&mut args); + a.push_to_operand_stack(&mut args); + out_addr.push_to_operand_stack(&mut args); + dbg!(&args); + + eval_package::(&package, None, &args, &test.session, |trace| { + let vm_out: [u8; 16] = + trace.read_from_rust_memory(out_addr).expect("output was not written"); + dbg!(&vm_out); + dbg!(&rs_out_bytes); + prop_assert_eq!(&rs_out_bytes, &vm_out, "VM output mismatch"); + Ok(()) + })?; + + Ok(()) + }); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {:?}", value); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {:?}", res), + } } }); }; @@ -76,7 +150,7 @@ macro_rules! test_unary_op { let rs_out = $op a; dbg!(&rs_out); let mut args = Vec::::default(); - a.try_push(&mut args); + a.push_to_operand_stack(&mut args); run_masm_vs_rust(rs_out, &package, &args, &test.session) }); match res { @@ -115,8 +189,8 @@ macro_rules! test_func_two_arg { let rust_out = $func(a, b); dbg!(&rust_out); let mut args = Vec::::default(); - b.try_push(&mut args); - a.try_push(&mut args); + b.push_to_operand_stack(&mut args); + a.push_to_operand_stack(&mut args); run_masm_vs_rust(rust_out, &package, &args, &test.session) }); match res { @@ -172,6 +246,18 @@ test_int_op!(add, +, i32, 0..=i32::MAX/2, 0..=i32::MAX/2); test_int_op!(add, +, i16, 0..=i16::MAX/2, 0..=i16::MAX/2); test_int_op!(add, +, i8, 0..=i8::MAX/2, 0..=i8::MAX/2); +// Useful for debugging traces: +// - WK1234 is (1000 << 96) | (2000 << 64) | (3000 << 32) | 4000; +// - WC1234 is (100 << 96) | (200 << 64) | (300 << 32) | 400; +// +// const WK1234: i128 = 79228162551157825753847955460000; +// const WC1234: i128 = 7922816255115782575384795546000; +// +// test_wide_bin_op!(xxx, x, i128, i128, WK1234..=WK1234, WC1234..=WC1234); + +test_wide_bin_op!(add, +, u128, u128, 0..=u128::MAX/2, 0..=u128::MAX/2); +test_wide_bin_op!(add, +, i128, i128, i128::MIN/2..=i128::MAX/2, -1..=i128::MAX/2); + test_int_op!(sub, -, u64, u64::MAX/2..=u64::MAX, 0..=u64::MAX/2); test_int_op!(sub, -, i64, i64::MIN/2..=i64::MAX/2, -1..=i64::MAX/2); test_int_op!(sub, -, u32, u32::MAX/2..=u32::MAX, 0..=u32::MAX/2); @@ -181,6 +267,9 @@ test_int_op!(sub, -, i32, i32::MIN+1..=0, i32::MIN+1..=0); test_int_op!(sub, -, i16, i16::MIN+1..=0, i16::MIN+1..=0); test_int_op!(sub, -, i8, i8::MIN+1..=0, i8::MIN+1..=0); +test_wide_bin_op!(sub, -, u128, u128, u128::MAX/2..=u128::MAX, 0..=u128::MAX/2); +test_wide_bin_op!(sub, -, i128, i128, i128::MIN/2..=i128::MAX/2, -1..=i128::MAX/2); + test_int_op!(mul, *, u64, 0u64..=16656, 0u64..=16656); test_int_op!(mul, *, i64, -65656i64..=65656, -65656i64..=65656); test_int_op!(mul, *, u32, 0u32..=16656, 0u32..=16656); @@ -190,6 +279,13 @@ test_int_op!(mul, *, i32, -16656i32..=16656, -16656i32..=16656); //test_int_op!(mul, *, i16); //test_int_op!(mul, *, i8); +const MAX_U128_64: u128 = u64::MAX as u128; +const MAX_I128_64: i128 = i64::MAX as i128; +const MIN_I128_64: i128 = i64::MIN as i128; + +test_wide_bin_op!(mul, *, u128, u128, 0..=MAX_U128_64, 0..=MAX_U128_64); +test_wide_bin_op!(mul, *, i128, i128, MIN_I128_64..MAX_I128_64, MIN_I128_64..=MAX_I128_64); + // TODO: build with cargo to avoid core::panicking // TODO: separate macro for div and rem tests to filter out division by zero // test_int_op!(div, /, u32); @@ -204,7 +300,7 @@ test_unary_op!(neg, -, i64, (i64::MIN + 1)..=i64::MAX); // Comparison ops -// enable when https://github.com/0xPolygonMiden/compiler/issues/56 is fixed +// enable when https://github.com/0xMiden/compiler/issues/56 is fixed test_func_two_arg!(min, core::cmp::min, i32, i32, i32); test_func_two_arg!(min, core::cmp::min, u32, u32, u32); test_func_two_arg!(min, core::cmp::min, u8, u8, u8); @@ -301,9 +397,9 @@ test_int_op!(shl, <<, i8, 0..i8::MAX, 0u8..8); test_int_op!(shr, >>, i64, i64::MIN..=i64::MAX, 0u64..=63); test_int_op!(shr, >>, u64, 0..=u64::MAX, 0u64..=63); -test_int_op!(shr, >>, u32, 0..u32::MAX, 0..32); -test_int_op!(shr, >>, u16, 0..u16::MAX, 0..16); -test_int_op!(shr, >>, u8, 0..u8::MAX, 0..8); +test_int_op!(shr, >>, u32, 0..u32::MAX, 0u32..32); +test_int_op!(shr, >>, u16, 0..u16::MAX, 0u32..16); +test_int_op!(shr, >>, u8, 0..u8::MAX, 0u32..8); // # The following tests use small signed operands which we don't fully support yet //test_int_op!(shr, >>, i8, i8::MIN..=i8::MAX, 0..=7); //test_int_op!(shr, >>, i16, i16::MIN..=i16::MAX, 0..=15); @@ -322,3 +418,80 @@ test_unary_op_total!(bnot, !, u32); test_unary_op_total!(bnot, !, u16); test_unary_op_total!(bnot, !, u8); test_unary_op_total!(bnot, !, bool); + +#[test] +fn test_hmerge() { + let main_fn = r#" + (f0: miden_stdlib_sys::Felt, f1: miden_stdlib_sys::Felt, f2: miden_stdlib_sys::Felt, f3: miden_stdlib_sys::Felt, f4: miden_stdlib_sys::Felt, f5: miden_stdlib_sys::Felt, f6: miden_stdlib_sys::Felt, f7: miden_stdlib_sys::Felt) -> miden_stdlib_sys::Felt { + let digest1 = miden_stdlib_sys::Digest::new([f0, f1, f2, f3]); + let digest2 = miden_stdlib_sys::Digest::new([f4, f5, f6, f7]); + let digests = [digest1, digest2]; + let res = miden_stdlib_sys::intrinsics::crypto::merge(digests); + res.inner.inner.0 + }"# + .to_string(); + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_fn_body_with_stdlib_sys("hmerge", &main_fn, config, []); + + test.expect_wasm(expect_file![format!("../../expected/hmerge.wat")]); + test.expect_ir(expect_file![format!("../../expected/hmerge.hir")]); + test.expect_masm(expect_file![format!("../../expected/hmerge.masm")]); + + let package = test.compiled_package(); + + // Run the Rust and compiled MASM code against a bunch of random inputs and compare the results + let config = proptest::test_runner::Config::with_cases(16); + let res = TestRunner::new(config).run( + &any::<([midenc_debug::Felt; 4], [midenc_debug::Felt; 4])>(), + move |(felts_in1, felts_in2)| { + let raw_felts_in1: [Felt; 4] = [ + felts_in1[0].into(), + felts_in1[1].into(), + felts_in1[2].into(), + felts_in1[3].into(), + ]; + + let raw_felts_in2: [Felt; 4] = [ + felts_in2[0].into(), + felts_in2[1].into(), + felts_in2[2].into(), + felts_in2[3].into(), + ]; + let digests_in = + [miden_core::Word::from(raw_felts_in1), miden_core::Word::from(raw_felts_in2)]; + let digest_out = miden_core::crypto::hash::Rpo256::merge(&digests_in); + let felts_out: [midenc_debug::Felt; 4] = [ + midenc_debug::Felt(digest_out[0]), + midenc_debug::Felt(digest_out[1]), + midenc_debug::Felt(digest_out[2]), + midenc_debug::Felt(digest_out[3]), + ]; + + let args = [ + raw_felts_in2[3], + raw_felts_in2[2], + raw_felts_in2[1], + raw_felts_in2[0], + raw_felts_in1[3], + raw_felts_in1[2], + raw_felts_in1[1], + raw_felts_in1[0], + ]; + eval_package::(&package, [], &args, &test.session, |trace| { + let res: Felt = trace.parse_result().unwrap(); + prop_assert_eq!(res, digest_out[0]); + Ok(()) + })?; + + Ok(()) + }, + ); + + match res { + Err(TestError::Fail(_, value)) => { + panic!("Found minimal(shrinked) failing case: {value:?}"); + } + Ok(_) => (), + _ => panic!("Unexpected test result: {res:?}"), + } +} diff --git a/tests/integration/src/rust_masm_tests/intrinsics.rs b/tests/integration/src/rust_masm_tests/intrinsics.rs index 149591972..ae3b25073 100644 --- a/tests/integration/src/rust_masm_tests/intrinsics.rs +++ b/tests/integration/src/rust_masm_tests/intrinsics.rs @@ -1,8 +1,9 @@ use core::panic; -use expect_test::expect_file; use miden_core::Felt; -use midenc_debug::{PushToStack, TestFelt}; +use midenc_debug::{TestFelt, ToMidenRepr}; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; use proptest::{ arbitrary::any, test_runner::{TestError, TestRunner}, @@ -21,7 +22,8 @@ macro_rules! test_bin_op { let res_ty_str = stringify!($res_ty); let main_fn = format!("(a: {op_ty_str}, b: {op_ty_str}) -> {res_ty_str} {{ a {op_str} b }}"); let artifact_name = format!("{}_{}", stringify!($name), stringify!($op_ty).to_lowercase()); - let mut test = CompilerTest::rust_fn_body_with_stdlib_sys(artifact_name.clone(), &main_fn, false, None); + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_fn_body_with_stdlib_sys(artifact_name.clone(), &main_fn, config, None); // Test expected compilation artifacts test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); @@ -37,8 +39,8 @@ macro_rules! test_bin_op { let rs_out = a_felt $op b_felt; dbg!(&rs_out); let mut args = Vec::::default(); - PushToStack::try_push(&b, &mut args); - PushToStack::try_push(&a, &mut args); + b.push_to_operand_stack(&mut args); + a.push_to_operand_stack(&mut args); run_masm_vs_rust(rs_out, &package, &args, &test.session) }); match res { @@ -62,7 +64,8 @@ macro_rules! test_compile_comparison_op { let op_str = stringify!($op); let main_fn = format!("(a: Felt, b: Felt) -> bool {{ a {op_str} b }}"); let artifact_name = format!("{}_felt", stringify!($name)); - let mut test = CompilerTest::rust_fn_body_with_stdlib_sys(artifact_name.clone(), &main_fn, false, None); + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_fn_body_with_stdlib_sys(artifact_name.clone(), &main_fn, config, None); // Test expected compilation artifacts test.expect_wasm(expect_file![format!("../../expected/{artifact_name}.wat")]); test.expect_ir(expect_file![format!("../../expected/{artifact_name}.hir")]); @@ -94,7 +97,7 @@ test_bool_op_total!(eq, ==); // TODO: Comparison operators are not defined for Felt, so we cannot compile a Rust equivalent for // the semantic test -// see https://github.com/0xPolygonMiden/compiler/issues/175 +// see https://github.com/0xMiden/compiler/issues/175 // test_bool_op_total!(gt, >); // test_bool_op_total!(lt, <); // test_bool_op_total!(ge, >=); diff --git a/tests/integration/src/rust_masm_tests/misc.rs b/tests/integration/src/rust_masm_tests/misc.rs new file mode 100644 index 000000000..daece0a98 --- /dev/null +++ b/tests/integration/src/rust_masm_tests/misc.rs @@ -0,0 +1,132 @@ +use miden_core::{Felt, FieldElement}; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; + +use crate::{ + testing::{eval_package, setup}, + CompilerTest, +}; + +#[test] +fn test_func_arg_same() { + // This test reproduces the https://github.com/0xMiden/compiler/issues/606 + let main_fn = r#" + (x: &mut Felt, y: &mut Felt) -> i32 { + intrinsic(x, y) + } + + #[no_mangle] + #[inline(never)] + fn intrinsic(a: &mut Felt, b: &mut Felt) -> i32 { + unsafe { (a as *mut Felt) as i32 } + } + + "#; + + setup::enable_compiler_instrumentation(); + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_fn_body_with_stdlib_sys("func_arg_same", main_fn, config, []); + + test.expect_wasm(expect_file!["../../expected/func_arg_same.wat"]); + test.expect_ir(expect_file!["../../expected/func_arg_same.hir"]); + test.expect_masm(expect_file!["../../expected/func_arg_same.masm"]); + + let package = test.compiled_package(); + + let addr1: u32 = 10 * 65536; + let addr2: u32 = 11 * 65536; + + // Test 1: addr1 is passed as x and should be returned + let args1 = [Felt::from(addr2), Felt::from(addr1)]; // Arguments are pushed in reverse order on stack + eval_package::(&package, [], &args1, &test.session, |trace| { + let result: u32 = trace.parse_result().unwrap(); + assert_eq!(result, addr1); + Ok(()) + }) + .unwrap(); + + // Test 1: addr2 is passed as x and should be returned + let args2 = [Felt::from(addr1), Felt::from(addr2)]; // Arguments are pushed in reverse order on stack + eval_package::(&package, [], &args2, &test.session, |trace| { + let result: u32 = trace.parse_result().unwrap(); + assert_eq!(result, addr2); + Ok(()) + }) + .unwrap(); +} + +#[ignore = "too fragile (depends on mem addrs), this bug is also covered by the test_hmerge test"] +#[test] +fn test_func_arg_order() { + // This test reproduces the "swapped and frozen" function arguments issue + // https://github.com/0xMiden/compiler/pull/576 discovered while working on hmerge VM op + // The issue manifests in "intrinsic" function parameters being in the wrong order + // (see assert_eq before the call and inside the function) + // on the stack AND the their order is not changing when the parameters are + // swapped at the call site (see expect_masm with the same file name, i.e. the MASM + // do not change when she parameters are swapped). + fn main_fn_template(digest_ptr_name: &str, result_ptr_name: &str) -> String { + format!( + r#" + (f0: miden_stdlib_sys::Felt, f1: miden_stdlib_sys::Felt, f2: miden_stdlib_sys::Felt, f3: miden_stdlib_sys::Felt, f4: miden_stdlib_sys::Felt, f5: miden_stdlib_sys::Felt, f6: miden_stdlib_sys::Felt, f7: miden_stdlib_sys::Felt) -> miden_stdlib_sys::Felt {{ + let digest1 = miden_stdlib_sys::Digest::new([f0, f1, f2, f3]); + let digest2 = miden_stdlib_sys::Digest::new([f4, f5, f6, f7]); + let digests = [digest1, digest2]; + let res = merge(digests); + res.inner.inner.0 + }} + + #[inline] + pub fn merge(digests: [Digest; 2]) -> Digest {{ + unsafe {{ + let digests_ptr = digests.as_ptr().addr() as u32; + assert_eq(Felt::from_u32(digests_ptr as u32), Felt::from_u32(1048528)); + + let mut ret_area = ::core::mem::MaybeUninit::::uninit(); + let result_ptr = ret_area.as_mut_ptr().addr() as u32; + assert_eq(Felt::from_u32(result_ptr as u32), Felt::from_u32(1048560)); + + intrinsic({digest_ptr_name} as *const Felt, {result_ptr_name} as *mut Felt); + + Digest::from_word(ret_area.assume_init()) + }} + }} + + #[no_mangle] + fn intrinsic(digests_ptr: *const Felt, result_ptr: *mut Felt) {{ + // see assert_eq above, before the call + assert_eq(Felt::from_u32(digests_ptr as u32), Felt::from_u32(1048528)); + // see assert_eq above, before the call + assert_eq(Felt::from_u32(result_ptr as u32), Felt::from_u32(1048560)); + }} + "# + ) + } + + let config = WasmTranslationConfig::default(); + let main_fn_correct = main_fn_template("digests_ptr", "result_ptr"); + let mut test = CompilerTest::rust_fn_body_with_stdlib_sys( + "func_arg_order", + &main_fn_correct, + config.clone(), + [], + ); + + test.expect_wasm(expect_file![format!("../../expected/func_arg_order.wat")]); + test.expect_ir(expect_file![format!("../../expected/func_arg_order.hir")]); + test.expect_masm(expect_file![format!("../../expected/func_arg_order.masm")]); + + let args = [ + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + Felt::ZERO, + ]; + + eval_package::(&test.compiled_package(), [], &args, &test.session, |trace| Ok(())) + .unwrap(); +} diff --git a/tests/integration/src/rust_masm_tests/mod.rs b/tests/integration/src/rust_masm_tests/mod.rs index d7ba7d5ee..7f94c936f 100644 --- a/tests/integration/src/rust_masm_tests/mod.rs +++ b/tests/integration/src/rust_masm_tests/mod.rs @@ -4,36 +4,34 @@ use std::{collections::VecDeque, sync::Arc}; use miden_core::Felt; -use midenc_codegen_masm::Package; -use midenc_debug::{Executor, PopFromStack}; +use midenc_debug::{Executor, FromMidenRepr}; use midenc_session::Session; use proptest::{prop_assert_eq, test_runner::TestCaseError}; -use crate::execute_emulator; - mod abi_transform; mod apps; -mod components; +mod examples; mod instructions; mod intrinsics; +mod misc; mod rust_sdk; mod types; -mod wit_sdk; pub fn run_masm_vs_rust( rust_out: T, - package: &Package, + package: &miden_mast_package::Package, args: &[Felt], session: &Session, ) -> Result<(), TestCaseError> where - T: Clone + PopFromStack + std::cmp::PartialEq + std::fmt::Debug, + T: Clone + FromMidenRepr + PartialEq + std::fmt::Debug, { - let exec = Executor::for_package(package, args.to_vec(), session) + let exec = Executor::for_package(package, args.iter().copied(), session) .map_err(|err| TestCaseError::fail(err.to_string()))?; let output = exec.execute_into(&package.unwrap_program(), session); + std::dbg!(&output); prop_assert_eq!(rust_out.clone(), output, "VM output mismatch"); - // TODO: Uncomment after https://github.com/0xPolygonMiden/compiler/issues/228 is fixed + // TODO: Uncomment after https://github.com/0xMiden/compiler/issues/228 is fixed // let emul_out: T = (*execute_emulator(ir_program.clone(), args).first().unwrap()).into(); // prop_assert_eq!(rust_out, emul_out, "Emulator output mismatch"); Ok(()) diff --git a/tests/integration/src/rust_masm_tests/rust_sdk.rs b/tests/integration/src/rust_masm_tests/rust_sdk.rs deleted file mode 100644 index 2e369a484..000000000 --- a/tests/integration/src/rust_masm_tests/rust_sdk.rs +++ /dev/null @@ -1,56 +0,0 @@ -use std::path::PathBuf; - -use expect_test::expect_file; - -use crate::{cargo_proj::project, compiler_test::sdk_crate_path, CompilerTest}; - -#[test] -fn account() { - let artifact_name = "miden_sdk_account_test"; - let mut test = CompilerTest::rust_source_cargo_lib( - "../rust-apps-wasm/rust-sdk/account-test", - artifact_name, - true, - None, - None, - ); - test.expect_wasm(expect_file![format!( - "../../expected/rust_sdk_account_test/{artifact_name}.wat" - )]); - test.expect_ir(expect_file![format!( - "../../expected/rust_sdk_account_test/{artifact_name}.hir" - )]); - // test.expect_masm(expect_file![format!( - // "../../expected/rust_sdk_account_test/{artifact_name}.masm" - // )]); -} - -#[test] -fn basic_wallet() { - let project_name = "rust_sdk_basic_wallet"; - let source = r#" - -pub struct Account; - -impl Account { - #[no_mangle] - pub fn receive_asset(asset: CoreAsset) { - add_asset(asset); - } - - #[no_mangle] - pub fn send_asset(asset: CoreAsset, tag: Tag, note_type: NoteType, recipient: Recipient) { - let asset = remove_asset(asset); - create_note(asset, tag, note_type, recipient); - } -} -"#; - - let mut test = CompilerTest::rust_source_with_sdk(project_name, source, true, None, None); - let artifact_name = test.artifact_name(); - test.expect_wasm(expect_file![format!("../../expected/{project_name}/{artifact_name}.wat")]); - test.expect_ir(expect_file![format!("../../expected/{project_name}/{artifact_name}.hir")]); - // TODO: fix flaky test, "exec."_ZN19miden_sdk_tx_kernel9add_asset17h6f4cff304c095ffc" is - // changing the suffix on every n-th run test.expect_masm(expect_file![format!("../../ - // expected/{project_name}/{artifact_name}.masm" )]); -} diff --git a/tests/integration/src/rust_masm_tests/rust_sdk/macros.rs b/tests/integration/src/rust_masm_tests/rust_sdk/macros.rs new file mode 100644 index 000000000..299372b41 --- /dev/null +++ b/tests/integration/src/rust_masm_tests/rust_sdk/macros.rs @@ -0,0 +1,44 @@ +use std::fs; + +use super::*; + +#[test] +fn component_macros_account_and_note() { + let config = WasmTranslationConfig::default(); + let mut account = CompilerTest::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/component-macros-account", + config.clone(), + [], + ); + account.expect_wasm(expect_file![format!( + "../../../expected/rust_sdk/component_macros_account.wat" + )]); + account.expect_ir(expect_file![format!( + "../../../expected/rust_sdk/component_macros_account.hir" + )]); + account.expect_masm(expect_file![format!( + "../../../expected/rust_sdk/component_macros_account.masm" + )]); + let account_package = account.compiled_package(); + + let builder = CompilerTestBuilder::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/component-macros-note", + config, + [], + ); + let mut note = builder.build(); + note.expect_wasm(expect_file![format!("../../../expected/rust_sdk/component_macros_note.wat")]); + note.expect_ir(expect_file![format!("../../../expected/rust_sdk/component_macros_note.hir")]); + note.expect_masm(expect_file![format!( + "../../../expected/rust_sdk/component_macros_note.masm" + )]); + let note_package = note.compiled_package(); + let program = note_package.unwrap_program(); + + let mut exec = Executor::new(vec![]); + exec.dependency_resolver_mut() + .add(account_package.digest(), account_package.into()); + let dependencies = note_package.manifest.dependencies(); + exec.with_dependencies(dependencies).unwrap(); + exec.execute(&program, ¬e.session); +} diff --git a/tests/integration/src/rust_masm_tests/rust_sdk/mod.rs b/tests/integration/src/rust_masm_tests/rust_sdk/mod.rs new file mode 100644 index 000000000..58f008cb5 --- /dev/null +++ b/tests/integration/src/rust_masm_tests/rust_sdk/mod.rs @@ -0,0 +1,301 @@ +use std::{collections::BTreeMap, env, path::PathBuf, sync::Arc}; + +use miden_core::{ + utils::{Deserializable, Serializable}, + Felt, FieldElement, Word, +}; +use miden_mast_package::Package; +use miden_objects::account::{AccountComponentMetadata, AccountComponentTemplate, InitStorageData}; +use midenc_debug::Executor; +use midenc_expect_test::expect_file; +use midenc_frontend_wasm::WasmTranslationConfig; +use midenc_hir::{interner::Symbol, FunctionIdent, Ident, SourceSpan}; + +use crate::{ + cargo_proj::project, + compiler_test::{sdk_alloc_crate_path, sdk_crate_path}, + CompilerTest, CompilerTestBuilder, +}; + +mod macros; + +#[test] +#[ignore = "until https://github.com/0xMiden/compiler/issues/439 is fixed"] +fn account() { + let artifact_name = "miden_sdk_account_test"; + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/account-test", + config, + [], + ); + test.expect_wasm(expect_file![format!( + "../../../expected/rust_sdk_account_test/{artifact_name}.wat" + )]); + test.expect_ir(expect_file![format!( + "../../../expected/rust_sdk_account_test/{artifact_name}.hir" + )]); + // test.expect_masm(expect_file![format!( + // "../../../expected/rust_sdk_account_test/{artifact_name}.masm" + // )]); +} + +#[test] +fn rust_sdk_swapp_note_bindings() { + let name = "rust_sdk_swapp_note_bindings"; + let sdk_path = sdk_crate_path(); + let sdk_alloc_path = sdk_alloc_crate_path(); + let component_package = format!("miden:{}", name.replace('_', "-")); + let cargo_toml = format!( + r#" +[package] +name = "{name}" +version = "0.0.1" +edition = "2021" +authors = [] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +miden-sdk-alloc = {{ path = "{sdk_alloc_path}" }} +miden = {{ path = "{sdk_path}" }} + +[package.metadata.component] +package = "{component_package}" + +[package.metadata.miden] +project-kind = "note-script" + +[profile.release] +opt-level = "z" +panic = "abort" +debug = false +"#, + name = name, + sdk_path = sdk_path.display(), + sdk_alloc_path = sdk_alloc_path.display(), + component_package = component_package, + ); + + let lib_rs = r#"#![no_std] + +use miden::*; + +#[note_script] +fn run(_arg: Word) { + let sender = note::get_sender(); + let script_root = note::get_script_root(); + let serial_number = note::get_serial_number(); + let balance = account::get_balance(sender); + + assert_eq!(sender.prefix, sender.prefix); + assert_eq!(sender.suffix, sender.suffix); + assert_eq!(script_root, script_root); + assert_eq!(serial_number, serial_number); + assert_eq!(balance, balance); +} +"#; + + let cargo_proj = + project(name).file("Cargo.toml", &cargo_toml).file("src/lib.rs", lib_rs).build(); + + let mut test = CompilerTestBuilder::rust_source_cargo_miden( + cargo_proj.root(), + WasmTranslationConfig::default(), + [], + ) + .build(); + + test.expect_wasm(expect_file![format!( + "../../../expected/rust_sdk/rust_sdk_swapp_note_bindings.wat" + )]); + test.expect_ir(expect_file![format!( + "../../../expected/rust_sdk/rust_sdk_swapp_note_bindings.hir" + )]); + test.expect_masm(expect_file![format!( + "../../../expected/rust_sdk/rust_sdk_swapp_note_bindings.masm" + )]); + // Ensure the crate compiles all the way to a package, exercising the bindings. + test.compiled_package(); +} + +#[test] +fn rust_sdk_cross_ctx_account_and_note() { + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/cross-ctx-account", + config.clone(), + [], + ); + test.expect_wasm(expect_file![format!("../../../expected/rust_sdk/cross_ctx_account.wat")]); + test.expect_ir(expect_file![format!("../../../expected/rust_sdk/cross_ctx_account.hir")]); + test.expect_masm(expect_file![format!("../../../expected/rust_sdk/cross_ctx_account.masm")]); + let account_package = test.compiled_package(); + let lib = account_package.unwrap_library(); + assert!( + !lib.exports() + .any(|export| { export.name.to_string().starts_with("intrinsics") }), + "expected no intrinsics in the exports" + ); + let expected_module = "miden:cross-ctx-account/foo@1.0.0"; + let expected_function = "process-felt"; + assert!( + lib.exports().any(|export| { + export.name.module.to_string() == expected_module + && export.name.name.as_str() == expected_function + }), + "expected one of the exports to contain module '{expected_module}' and function \ + '{expected_function}" + ); + // Test that the package loads + let bytes = account_package.to_bytes(); + let loaded_package = miden_mast_package::Package::read_from_bytes(&bytes).unwrap(); + + // Build counter note + let builder = CompilerTestBuilder::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/cross-ctx-note", + config, + [], + ); + + let mut test = builder.build(); + test.expect_wasm(expect_file![format!("../../../expected/rust_sdk/cross_ctx_note.wat")]); + test.expect_ir(expect_file![format!("../../../expected/rust_sdk/cross_ctx_note.hir")]); + test.expect_masm(expect_file![format!("../../../expected/rust_sdk/cross_ctx_note.masm")]); + let package = test.compiled_package(); + let program = package.unwrap_program(); + let mut exec = Executor::new(vec![]); + exec.dependency_resolver_mut() + .add(account_package.digest(), account_package.into()); + let dependencies = package.manifest.dependencies(); + exec.with_dependencies(dependencies).unwrap(); + let trace = exec.execute(&program, &test.session); +} + +#[test] +fn rust_sdk_cross_ctx_account_and_note_word() { + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/cross-ctx-account-word", + config.clone(), + [], + ); + test.expect_wasm(expect_file![format!( + "../../../expected/rust_sdk/cross_ctx_account_word.wat" + )]); + test.expect_ir(expect_file![format!("../../../expected/rust_sdk/cross_ctx_account_word.hir")]); + test.expect_masm(expect_file![format!( + "../../../expected/rust_sdk/cross_ctx_account_word.masm" + )]); + let account_package = test.compiled_package(); + let lib = account_package.unwrap_library(); + let expected_module = "miden:cross-ctx-account-word/foo@1.0.0"; + let expected_function = "process-word"; + let exports = lib + .exports() + .filter(|e| !e.name.module.to_string().starts_with("intrinsics")) + .map(|e| format!("{}::{}", e.name.module, e.name.name.as_str())) + .collect::>(); + // dbg!(&exports); + assert!( + lib.exports().any(|export| { + export.name.module.to_string() == expected_module + && export.name.name.as_str() == expected_function + }), + "expected one of the exports to contain module '{expected_module}' and function \ + '{expected_function}" + ); + // Test that the package loads + let bytes = account_package.to_bytes(); + let loaded_package = miden_mast_package::Package::read_from_bytes(&bytes).unwrap(); + + // Build counter note + let builder = CompilerTestBuilder::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/cross-ctx-note-word", + config, + [], + ); + + let mut test = builder.build(); + test.expect_wasm(expect_file![format!("../../../expected/rust_sdk/cross_ctx_note_word.wat")]); + test.expect_ir(expect_file![format!("../../../expected/rust_sdk/cross_ctx_note_word.hir")]); + test.expect_masm(expect_file![format!("../../../expected/rust_sdk/cross_ctx_note_word.masm")]); + let package = test.compiled_package(); + let mut exec = Executor::new(vec![]); + exec.dependency_resolver_mut() + .add(account_package.digest(), account_package.into()); + exec.with_dependencies(package.manifest.dependencies()).unwrap(); + let trace = exec.execute(&package.unwrap_program(), &test.session); +} + +#[test] +fn pure_rust_hir2() { + let _ = env_logger::builder().is_test(true).try_init(); + let config = WasmTranslationConfig::default(); + let mut test = + CompilerTest::rust_source_cargo_miden("../rust-apps-wasm/rust-sdk/add", config, []); + let artifact_name = test.artifact_name().to_string(); + test.expect_wasm(expect_file![format!("../../../expected/rust_sdk/{artifact_name}.wat")]); + test.expect_ir(expect_file![format!("../../../expected/rust_sdk/{artifact_name}.hir")]); +} + +#[test] +fn rust_sdk_cross_ctx_word_arg_account_and_note() { + let config = WasmTranslationConfig::default(); + let mut test = CompilerTest::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg", + config.clone(), + [], + ); + test.expect_wasm(expect_file![format!( + "../../../expected/rust_sdk/cross_ctx_account_word_arg.wat" + )]); + test.expect_ir(expect_file![format!( + "../../../expected/rust_sdk/cross_ctx_account_word_arg.hir" + )]); + test.expect_masm(expect_file![format!( + "../../../expected/rust_sdk/cross_ctx_account_word_arg.masm" + )]); + let account_package = test.compiled_package(); + + let lib = account_package.unwrap_library(); + let expected_module = "miden:cross-ctx-account-word-arg/foo@1.0.0"; + let expected_function = "process-word"; + let exports = lib + .exports() + .filter(|e| !e.name.module.to_string().starts_with("intrinsics")) + .map(|e| format!("{}::{}", e.name.module, e.name.name.as_str())) + .collect::>(); + dbg!(&exports); + assert!( + lib.exports().any(|export| { + export.name.module.to_string() == expected_module + && export.name.name.as_str() == expected_function + }), + "expected one of the exports to contain module '{expected_module}' and function \ + '{expected_function}" + ); + + // Build counter note + let builder = CompilerTestBuilder::rust_source_cargo_miden( + "../rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg", + config, + [], + ); + let mut test = builder.build(); + test.expect_wasm(expect_file![format!( + "../../../expected/rust_sdk/cross_ctx_note_word_arg.wat" + )]); + test.expect_ir(expect_file![format!("../../../expected/rust_sdk/cross_ctx_note_word_arg.hir")]); + test.expect_masm(expect_file![format!( + "../../../expected/rust_sdk/cross_ctx_note_word_arg.masm" + )]); + let package = test.compiled_package(); + assert!(package.is_program()); + let mut exec = Executor::new(vec![]); + exec.dependency_resolver_mut() + .add(account_package.digest(), account_package.into()); + exec.with_dependencies(package.manifest.dependencies()).unwrap(); + let trace = exec.execute(&package.unwrap_program(), &test.session); +} diff --git a/tests/integration/src/rust_masm_tests/types.rs b/tests/integration/src/rust_masm_tests/types.rs index f8b7b340d..01c166c4c 100644 --- a/tests/integration/src/rust_masm_tests/types.rs +++ b/tests/integration/src/rust_masm_tests/types.rs @@ -1,4 +1,4 @@ -use expect_test::expect_file; +use midenc_expect_test::expect_file; use crate::CompilerTest; @@ -7,8 +7,7 @@ fn test_enum() { let mut test = CompilerTest::rust_source_program(include_str!("types_src/enum.rs")); test.expect_wasm(expect_file!["../../expected/types/enum.wat"]); test.expect_ir(expect_file!["../../expected/types/enum.hir"]); - // uncomment when https://github.com/0xPolygonMiden/compiler/issues/281 is fixed - // test.expect_masm(expect_file!["../../expected/types/enum.masm"]); + test.expect_masm(expect_file!["../../expected/types/enum.masm"]); } #[test] @@ -17,17 +16,6 @@ fn test_array() { test.expect_wasm(expect_file!["../../expected/types/array.wat"]); test.expect_ir(expect_file!["../../expected/types/array.hir"]); test.expect_masm(expect_file!["../../expected/types/array.masm"]); - - assert!( - test.hir() - .unwrap_component() - .first_module() - .segments() - .last() - .unwrap() - .is_readonly(), - "data segment should be readonly" - ); } #[test] @@ -36,15 +24,4 @@ fn test_static_mut() { test.expect_wasm(expect_file!["../../expected/types/static_mut.wat"]); test.expect_ir(expect_file!["../../expected/types/static_mut.hir"]); test.expect_masm(expect_file!["../../expected/types/static_mut.masm"]); - assert!( - !test - .hir() - .unwrap_component() - .first_module() - .segments() - .last() - .unwrap() - .is_readonly(), - "data segment should be mutable" - ); } diff --git a/tests/integration/src/rust_masm_tests/wit_sdk.rs b/tests/integration/src/rust_masm_tests/wit_sdk.rs index b2f0781e7..71dc285fc 100644 --- a/tests/integration/src/rust_masm_tests/wit_sdk.rs +++ b/tests/integration/src/rust_masm_tests/wit_sdk.rs @@ -1,9 +1,13 @@ -use std::{collections::BTreeMap, path::PathBuf, str::FromStr}; +use std::{ + collections::{BTreeMap, HashSet}, + path::PathBuf, + str::FromStr, +}; use expect_test::expect_file; use miden_core::crypto::hash::RpoDigest; -use midenc_frontend_wasm::{ImportMetadata, WasmTranslationConfig}; -use midenc_hir::{FunctionExportName, InterfaceFunctionIdent, InterfaceIdent, Symbol}; +use midenc_frontend_wasm::WasmTranslationConfig; +use midenc_hir::{InterfaceFunctionIdent, InterfaceIdent, Symbol}; use crate::CompilerTest; @@ -21,12 +25,12 @@ fn sdk() { #[test] fn sdk_basic_wallet() { - let interface_tx = InterfaceIdent::from_full_ident("miden:base/tx@1.0.0".to_string()); + let interface_tx = InterfaceIdent::from_full_ident("miden:base/tx@1.0.0"); let create_note_ident = InterfaceFunctionIdent { interface: interface_tx, function: Symbol::intern("create-note"), }; - let interface_account = InterfaceIdent::from_full_ident("miden:base/account@1.0.0".to_string()); + let interface_account = InterfaceIdent::from_full_ident("miden:base/account@1.0.0"); let add_asset_ident = InterfaceFunctionIdent { interface: interface_account, function: Symbol::intern("add-asset"), @@ -35,34 +39,9 @@ fn sdk_basic_wallet() { interface: interface_account, function: Symbol::intern("remove-asset"), }; - let import_metadata: BTreeMap = [ - ( - create_note_ident, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), - ( - remove_asset_ident, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), - ( - add_asset_ident, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), - ] - .into_iter() - .collect(); - let expected_exports: Vec = - vec![Symbol::intern("send-asset").into(), Symbol::intern("receive-asset").into()]; - let config = WasmTranslationConfig { - import_metadata: import_metadata.clone(), - ..Default::default() - }; + let expected_imports: HashSet = + [create_note_ident, remove_asset_ident, add_asset_ident].into_iter().collect(); + let config = WasmTranslationConfig::default(); let mut test = CompilerTest::rust_source_cargo_component("../rust-apps-wasm/wit-sdk/basic-wallet", config); let artifact_name = test.artifact_name(); @@ -74,74 +53,41 @@ fn sdk_basic_wallet() { )]); let ir = test.hir().unwrap_component(); for import in ir.imports().values() { - assert!(import_metadata.contains_key(&import.unwrap_canon_abi_import().interface_function)); - } - for name in expected_exports { - assert!(ir.exports().contains_key(&name)); + assert!(expected_imports.contains(&import.unwrap_canon_abi_import().interface_function)); } } #[test] fn sdk_basic_wallet_p2id_note() { - let interface_account = InterfaceIdent::from_full_ident("miden:base/account@1.0.0".to_string()); - let basic_wallet = - InterfaceIdent::from_full_ident("miden:basic-wallet/basic-wallet@1.0.0".to_string()); - let core_types = InterfaceIdent::from_full_ident("miden:base/core-types@1.0.0".to_string()); - let note = InterfaceIdent::from_full_ident("miden:base/note@1.0.0".to_string()); - let import_metadata: BTreeMap = [ - ( - InterfaceFunctionIdent { - interface: interface_account, - function: Symbol::intern("get-id"), - }, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), - ( - InterfaceFunctionIdent { - interface: core_types, - function: Symbol::intern("account-id-from-felt"), - }, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), - ( - InterfaceFunctionIdent { - interface: basic_wallet, - function: Symbol::intern("receive-asset"), - }, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), - ( - InterfaceFunctionIdent { - interface: note, - function: Symbol::intern("get-assets"), - }, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), - ( - InterfaceFunctionIdent { - interface: note, - function: Symbol::intern("get-inputs"), - }, - ImportMetadata { - digest: RpoDigest::default(), - }, - ), + let interface_account = InterfaceIdent::from_full_ident("miden:base/account@1.0.0"); + let basic_wallet = InterfaceIdent::from_full_ident("miden:basic-wallet/basic-wallet@1.0.0"); + let core_types = InterfaceIdent::from_full_ident("miden:base/core-types@1.0.0"); + let note = InterfaceIdent::from_full_ident("miden:base/note@1.0.0"); + let expected_imports: HashSet = [ + InterfaceFunctionIdent { + interface: interface_account, + function: Symbol::intern("get-id"), + }, + InterfaceFunctionIdent { + interface: core_types, + function: Symbol::intern("account-id-from-felt"), + }, + InterfaceFunctionIdent { + interface: basic_wallet, + function: Symbol::intern("receive-asset"), + }, + InterfaceFunctionIdent { + interface: note, + function: Symbol::intern("get-assets"), + }, + InterfaceFunctionIdent { + interface: note, + function: Symbol::intern("get-inputs"), + }, ] .into_iter() .collect(); - let expected_exports: Vec = vec![Symbol::intern("note-script").into()]; - let config = WasmTranslationConfig { - import_metadata: import_metadata.clone(), - ..Default::default() - }; + let config = WasmTranslationConfig::default(); let mut test = CompilerTest::rust_source_cargo_component("../rust-apps-wasm/wit-sdk/p2id-note", config); let artifact_name = test.artifact_name(); @@ -154,14 +100,11 @@ fn sdk_basic_wallet_p2id_note() { let ir = test.hir().unwrap_component(); for import in ir.imports().values() { let canon_abi_import = import.unwrap_canon_abi_import(); - assert!(import_metadata.contains_key(&canon_abi_import.interface_function)); + assert!(expected_imports.contains(&canon_abi_import.interface_function)); if ["get-assets", "get-inputs"] .contains(&canon_abi_import.interface_function.function.as_str()) { assert!(canon_abi_import.options.realloc.is_some()); } } - for name in expected_exports { - assert!(ir.exports().contains_key(&name)); - } } diff --git a/tests/integration/src/testing/eval.rs b/tests/integration/src/testing/eval.rs new file mode 100644 index 000000000..2b405cf56 --- /dev/null +++ b/tests/integration/src/testing/eval.rs @@ -0,0 +1,180 @@ +use miden_core::{Felt, FieldElement}; +use miden_processor::AdviceInputs; +use midenc_compile::LinkOutput; +use midenc_debug::{ExecutionTrace, Executor, FromMidenRepr}; +use midenc_session::Session; +use proptest::test_runner::TestCaseError; + +use super::*; + +/// Evaluates `package` using the debug executor, producing an output of type `T` +/// +/// * `initializers` is an optional set of [Initializer] to run at program start by the compiler- +/// emitted test harness, to set up memory or other global state. +/// * `args` are the set of arguments that will be placed on the operand stack, in order of +/// appearance +/// * `verify_trace` is a callback which gets the [ExecutionTrace], and can be used to assert +/// things about the trace, such as the state of memory at program exit. +pub fn eval_package<'a, T, I, F>( + package: &miden_mast_package::Package, + initializers: I, + args: &[Felt], + session: &Session, + verify_trace: F, +) -> Result +where + T: Clone + FromMidenRepr + PartialEq + core::fmt::Debug, + I: IntoIterator>, + F: Fn(&ExecutionTrace) -> Result<(), TestCaseError>, +{ + // Provide input bytes/felts/words via the advice stack + // + // NOTE: This relies on MasmComponent to emit a test harness via `emit_test_harness` during + // assembly of the package. + // + // First, convert the input to words, zero-padding as needed; and push on to the + // advice stack in reverse. + let mut advice_stack = Vec::::with_capacity(64); + let mut num_initializers = 0u64; + for initializer in initializers { + num_initializers += 1; + let num_words = match &initializer { + Initializer::Value { value, .. } => { + value.push_words_to_advice_stack(&mut advice_stack) as u32 + } + Initializer::MemoryBytes { bytes, .. } => { + let words = midenc_debug::bytes_to_words(bytes); + let num_words = words.len() as u32; + for word in words.into_iter().rev() { + for felt in word.into_iter() { + advice_stack.push(felt); + } + } + num_words + } + Initializer::MemoryFelts { felts, .. } => { + let num_felts = felts.len().next_multiple_of(4); + let num_words = num_felts / 4; + let mut buf = Vec::with_capacity(num_words); + let mut words = felts.iter().copied().array_chunks::<4>(); + for mut word in words.by_ref() { + word.reverse(); + buf.push(word); + } + if let Some(remainder) = words.into_remainder().filter(|r| r.len() > 0) { + if remainder.len() > 0 { + let mut word = [Felt::ZERO; 4]; + for (i, felt) in remainder.enumerate() { + word[i] = felt; + } + word.reverse(); + buf.push(word); + } + } + for word in buf.into_iter().rev() { + for felt in word.into_iter() { + advice_stack.push(felt); + } + } + num_words as u32 + } + Initializer::MemoryWords { words, .. } => { + for word in words.iter().rev() { + for elem in word.iter() { + advice_stack.push(*elem); + } + } + words.len() as u32 + } + }; + + // The test harness invokes std::mem::pipe_words_to_memory, which expects the operand stack + // to look like: `[num_words, write_ptr]`. + // + // Since we're feeding this data in via the advice stack, the test harness code will expect + // these values on the advice stack in the opposite order, as the `adv_push` instruction + // will pop each element off the advice stack, and push on to the operand stack, after which + // these two values will be in the expected order. + advice_stack.push(Felt::new(num_words as u64)); // num_words + advice_stack.push(Felt::new(initializer.element_addr() as u64)); // dest_ptr + } + + // Push the number of initializers on the advice stack + advice_stack.push(Felt::new(num_initializers)); + + let mut exec = Executor::for_package(package, args.to_vec(), session) + .map_err(|err| TestCaseError::fail(format_report(err)))?; + + // Reverse the stack contents, so that the correct order is preserved after MemAdviceProvider + // does its own reverse + advice_stack.reverse(); + + exec.with_advice_inputs(AdviceInputs::default().with_stack(advice_stack)); + + let trace = exec.execute(&package.unwrap_program(), session); + verify_trace(&trace)?; + + dbg!(trace.outputs()); + + let output = trace.parse_result::().expect("expected output was not returned"); + dbg!(&output); + + Ok(output) +} + +/// Helper function to compile a test module with the given signature and build function +pub fn compile_test_module( + signature: midenc_hir::Signature, + build_fn: impl Fn(&mut midenc_hir::dialects::builtin::FunctionBuilder<'_, midenc_hir::OpBuilder>), +) -> (miden_mast_package::Package, std::rc::Rc) { + let context = setup::dummy_context(&["--test-harness", "--entrypoint", "test::main"]); + let link_output = setup::build_empty_component_for_test(context.clone()); + setup::build_entrypoint(link_output.component, &signature, build_fn); + let package = compile_link_output_to_package(link_output).unwrap(); + (package, context) +} + +/// Compiles a LinkOutput to a Package, suitable for execution +pub fn compile_link_output_to_package( + link_output: LinkOutput, +) -> Result { + use midenc_compile::{compile_link_output_to_masm_with_pre_assembly_stage, CodegenOutput}; + + // Compile to Package + let mut pre_assembly_stage = |output: CodegenOutput, _context| { + println!("# Assembled\n{}", &output.component); + Ok(output) + }; + let artifact = + compile_link_output_to_masm_with_pre_assembly_stage(link_output, &mut pre_assembly_stage) + .map_err(|err| TestCaseError::fail(format_report(err)))?; + Ok(artifact.unwrap_mast()) +} + +/// This helper exists to handle the boilerplate of compiling/assembling the output of the link +/// stage of the compiler to a package, and then evaluating that package with [eval_package]. +/// +/// Evaluates the package assembled from `link_output` using the debug executor, producing an output +/// of type `T` +/// +/// * `initializers` is an optional set of [Initializer] to run at program start by the compiler- +/// emitted test harness, to set up memory or other global state. +/// * `args` are the set of arguments that will be placed on the operand stack, in order of +/// appearance +/// * `verify_trace` is a callback which gets the [ExecutionTrace], and can be used to assert +/// things about the trace, such as the state of memory at program exit. +pub fn eval_link_output<'a, T, I, F>( + link_output: LinkOutput, + initializers: I, + args: &[Felt], + session: &Session, + verify_trace: F, +) -> Result +where + T: Clone + FromMidenRepr + PartialEq + core::fmt::Debug, + I: IntoIterator>, + F: Fn(&ExecutionTrace) -> Result<(), TestCaseError>, +{ + let package = compile_link_output_to_package(link_output)?; + eval_package::(&package, initializers, args, session, verify_trace) +} diff --git a/tests/integration/src/testing/initializer.rs b/tests/integration/src/testing/initializer.rs new file mode 100644 index 000000000..12326cd1d --- /dev/null +++ b/tests/integration/src/testing/initializer.rs @@ -0,0 +1,51 @@ +use std::borrow::Cow; + +use miden_core::{Felt, Word}; +use midenc_debug::ToMidenRepr; + +/// An [Initializer] represents a known initialization pattern that is handled by the compiler- +/// emitted test harness, when enabled. +/// +/// These can be used to initialize global program state when the program starts, to set things up +/// for a specific test case. +pub enum Initializer<'a> { + /// Write `value` to memory starting at `addr` + Value { + /// The address (in byte-addressable space) to write `value` to + addr: u32, + /// The value to be written + value: Box, + }, + /// Write `bytes` to memory starting at `addr` + MemoryBytes { + /// The address (in byte-addressable space) to write `bytes` to + addr: u32, + /// The bytes to be written + bytes: &'a [u8], + }, + /// Write `felts` to memory starting at `addr` + MemoryFelts { + /// The address (in element-addressable space) to write `felts` to + addr: u32, + /// The field elements to be written + felts: Cow<'a, [Felt]>, + }, + /// Write `words` to memory starting at `addr` + #[allow(dead_code)] + MemoryWords { + /// The address (in element-addressable space) to write `words` to + addr: u32, + /// The words to be written + words: Cow<'a, [Word]>, + }, +} + +impl Initializer<'_> { + /// Get the address this initializes, in element-addressable space + pub fn element_addr(&self) -> u32 { + match self { + Self::Value { addr, .. } | Self::MemoryBytes { addr, .. } => *addr / 4, + Self::MemoryFelts { addr, .. } | Self::MemoryWords { addr, .. } => *addr, + } + } +} diff --git a/tests/integration/src/testing/mod.rs b/tests/integration/src/testing/mod.rs new file mode 100644 index 000000000..fad919d3a --- /dev/null +++ b/tests/integration/src/testing/mod.rs @@ -0,0 +1,31 @@ +//! This module provides core utilities for constructing tests outside of the primary +//! [crate::CompilerTest] infrastructure. +mod eval; +mod initializer; +pub mod setup; + +pub use self::{ + eval::{compile_link_output_to_package, compile_test_module, eval_link_output, eval_package}, + initializer::Initializer, +}; + +/// Pretty-print `report` to a String +pub fn format_report(report: miden_assembly::diagnostics::Report) -> String { + use core::fmt::Write; + + use miden_assembly::diagnostics::reporting::PrintDiagnostic; + + let mut labels_str = String::new(); + if let Some(labels) = report.labels() { + for label in labels { + if let Some(label) = label.label() { + writeln!(&mut labels_str, "{label}").unwrap(); + } + } + } + + let mut str = PrintDiagnostic::new(report).to_string(); + writeln!(&mut str, "{labels_str}").unwrap(); + + str +} diff --git a/tests/integration/src/testing/setup.rs b/tests/integration/src/testing/setup.rs new file mode 100644 index 000000000..7db6c754d --- /dev/null +++ b/tests/integration/src/testing/setup.rs @@ -0,0 +1,150 @@ +//! This module provides core utilities for setting up the necessary artifacts and state for tests. +use std::{path::PathBuf, rc::Rc}; + +use midenc_compile::LinkOutput; +use midenc_hir::{ + dialects::builtin::{ + self, ComponentBuilder, FunctionBuilder, FunctionRef, ModuleBuilder, WorldBuilder, + }, + version::Version, + BuilderExt, Context, Ident, Op, OpBuilder, Signature, SourceSpan, +}; +use midenc_session::{InputFile, Session}; + +use super::format_report; + +/// Enable compiler-internal tracing and instrumentation during tests +pub fn enable_compiler_instrumentation() { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .format_timestamp(None) + .is_test(true) + .try_init(); +} + +/// Create a valid [Context] representing a compiler session with a "dummy" input that doesn't +/// actually exist. +/// +/// This is used to fulfill the requirement of having a valid [Context], when it won't actually be +/// used for compilation (at least, not via the main compiler entrypoint). +pub fn dummy_context(flags: &[&str]) -> Rc { + let session = dummy_session(flags); + let context = Rc::new(Context::new(session)); + midenc_codegen_masm::register_dialect_hooks(&context); + midenc_hir_eval::register_dialect_hooks(&context); + context +} + +/// Create a valid [Session] with a "dummy" input that doesn't actually exist. +/// +/// This is used when you need to call into some code that requires a valid [Session], but it won't +/// actually be used for compilation (or at least, not via the main compiler entrypoint). +pub fn dummy_session(flags: &[&str]) -> Rc { + let dummy = InputFile::from_path(PathBuf::from("dummy.wasm")).unwrap(); + default_session([dummy], flags) +} + +/// Create a valid [Context] for `inputs` with `argv`, with useful defaults. +pub fn default_context(inputs: I, argv: &[S]) -> Rc +where + I: IntoIterator, + S: AsRef, +{ + let session = default_session(inputs, argv); + let context = Rc::new(Context::new(session)); + midenc_codegen_masm::register_dialect_hooks(&context); + midenc_hir_eval::register_dialect_hooks(&context); + context +} + +/// Create a valid [Session] for compiling `inputs` with `argv`, with useful defaults. +pub fn default_session(inputs: I, argv: &[S]) -> Rc +where + I: IntoIterator, + S: AsRef, +{ + use midenc_session::diagnostics::reporting::{self, ReportHandlerOpts}; + + let result = reporting::set_hook(Box::new(|_| { + let wrapping_width = 300; // avoid wrapped file paths in the backtrace + Box::new(ReportHandlerOpts::new().width(wrapping_width).build()) + })); + if result.is_ok() { + reporting::set_panic_hook(); + } + + let argv = argv.iter().map(|arg| arg.as_ref()); + let session = midenc_compile::Compiler::new_session(inputs, None, argv); + Rc::new(session) +} + +/// Create a [LinkOutput] representing an empty component named `root:root@1.0.0`. +/// +/// Callers may then populate the world/component as they see fit for a particular test. +pub fn build_empty_component_for_test(context: Rc) -> LinkOutput { + let mut builder = OpBuilder::new(context.clone()); + let world = { + let builder = builder.create::(SourceSpan::default()); + builder().unwrap_or_else(|err| panic!("failed to create world:\n{}", format_report(err))) + }; + let mut world_builder = WorldBuilder::new(world); + let name = Ident::with_empty_span("root".into()); + let ns_name = Ident::with_empty_span("root_ns".into()); + let component = world_builder + .define_component(ns_name, name, Version::new(1, 0, 0)) + .unwrap_or_else(|err| panic!("failed to define component:\n{}", format_report(err))); + + let mut link_output = LinkOutput { + world, + component, + masm: Default::default(), + mast: Default::default(), + packages: Default::default(), + account_component_metadata_bytes: None, + }; + link_output + .link_libraries_from(context.session()) + .unwrap_or_else(|err| panic!("{}", format_report(err))); + link_output +} + +/// Defines a module called `test` in `component`, containing a function called `main` with +/// `signature`, designed to be invoked as the entrypoint of a test case. +/// +/// The body of `main` is populated by `build`, which must ensure that it returns from `main` +/// with the results expected by `signature`. +/// +/// A reference to the generated `main` function is returned, should the caller wish to modify it +/// further. +pub fn build_entrypoint( + component: builtin::ComponentRef, + signature: &Signature, + build: F, +) -> FunctionRef +where + F: Fn(&mut FunctionBuilder<'_, OpBuilder>), +{ + let module = { + let mut component_builder = ComponentBuilder::new(component); + component_builder + .define_module(Ident::with_empty_span("test".into())) + .unwrap_or_else(|err| panic!("failed to define module:\n{}", format_report(err))) + }; + let function = { + let mut module_builder = ModuleBuilder::new(module); + module_builder + .define_function(Ident::with_empty_span("main".into()), signature.clone()) + .unwrap_or_else(|err| panic!("failed to define function:\n{}", format_report(err))) + }; + + // Define function body + { + let context = function.borrow().as_operation().context_rc(); + let mut builder = OpBuilder::new(context); + let mut builder = FunctionBuilder::new(function, &mut builder); + build(&mut builder); + } + + println!("# Entrypoint\n{}\n", function.borrow().as_operation()); + + function +} diff --git a/tests/rust-apps-wasm/fib/Cargo.lock b/tests/rust-apps-wasm/fib/Cargo.lock deleted file mode 100644 index bc2f32629..000000000 --- a/tests/rust-apps-wasm/fib/Cargo.lock +++ /dev/null @@ -1,30 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "dlmalloc" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a" -dependencies = [ - "libc", -] - -[[package]] -name = "libc" -version = "0.2.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" - -[[package]] -name = "miden-integration-tests-rust-fib" -version = "0.0.0" - -[[package]] -name = "miden-integration-tests-rust-fib-wasm" -version = "0.0.0" -dependencies = [ - "dlmalloc", - "miden-integration-tests-rust-fib", -] diff --git a/tests/rust-apps-wasm/fib/Cargo.toml b/tests/rust-apps-wasm/fib/Cargo.toml deleted file mode 100644 index b975b3f10..000000000 --- a/tests/rust-apps-wasm/fib/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -name = "miden-integration-tests-rust-fib-wasm" -version = "0.0.0" -edition = "2021" - -[lib] -crate-type = ["cdylib"] - -[dependencies] -dlmalloc = { version = "0.2.4", features = ["global"] } -miden-integration-tests-rust-fib = { path = "../../rust-apps/fib" } - -[profile.release] -opt-level = "z" -debug = true diff --git a/tests/rust-apps-wasm/fib/src/lib.rs b/tests/rust-apps-wasm/fib/src/lib.rs deleted file mode 100644 index 357025f8d..000000000 --- a/tests/rust-apps-wasm/fib/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_std] - -#[global_allocator] -static A: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; - -#[panic_handler] -fn my_panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -pub fn fib(n: u32) -> u32 { - miden_integration_tests_rust_fib::fib(n) -} diff --git a/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.lock index c4b431fa0..62ca746bc 100644 --- a/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.lock +++ b/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,88 +11,76 @@ dependencies = [ "memchr", ] +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "ascii-canvas" -version = "3.0.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ "term", ] [[package]] name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "backtrace" -version = "0.3.73" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "backtrace-ext" -version = "0.2.1" +name = "bech32" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" -dependencies = [ - "backtrace", -] +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "blake3" -version = "1.5.3" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9ec96fe9a81b5e365f9db71fe00edc4fe4ca2cc7dcb7861f0603012a7caa210" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" dependencies = [ "arrayref", "arrayvec", "cc", - "cfg-if 1.0.0", + "cfg-if", "constant_time_eq", ] @@ -121,27 +94,20 @@ dependencies = [ ] [[package]] -name = "byteorder" -version = "1.5.0" +name = "bumpalo" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cc" -version = "1.1.8" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504bdec147f2cc13c8b57ed9401fd8a147cc66b67ad5cb241394244f2c947549" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ - "jobserver", - "libc", + "shlex", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -150,25 +116,19 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.6" @@ -180,47 +140,46 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.10.7" +name = "derive_more" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "block-buffer", - "crypto-common", + "derive_more-impl", ] [[package]] -name = "dirs-next" -version = "2.0.0" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "cfg-if 1.0.0", - "dirs-sys-next", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "dirs-sys-next" -version = "0.1.2" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "libc", - "redox_users", - "winapi", + "block-buffer", + "crypto-common", ] [[package]] name = "dissimilar" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "ena" @@ -233,31 +192,27 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno" -version = "0.3.9" +name = "fixedbitset" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] -name = "fixedbitset" -version = "0.4.2" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -269,9 +224,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -279,33 +234,33 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-sink", @@ -316,11 +271,12 @@ dependencies = [ [[package]] name = "generator" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "979f00864edc7516466d6b3157706e06c032f22715700ddd878228a91d02bc56" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" dependencies = [ - "cfg-if 1.0.0", + "cc", + "cfg-if", "libc", "log", "rustversion", @@ -339,32 +295,44 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", + "js-sys", "libc", + "r-efi", "wasi", + "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.29.0" +name = "glob" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] -name = "glob" -version = "0.3.1" +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "hashbrown" -version = "0.14.5" +name = "id-arena" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" [[package]] name = "indenter" @@ -374,42 +342,38 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "2.3.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", + "serde", ] -[[package]] -name = "is_ci" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" - [[package]] name = "itertools" -version = "0.11.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] -name = "jobserver" -version = "0.1.32" +name = "js-sys" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ - "libc", + "once_cell", + "wasm-bindgen", ] [[package]] @@ -423,9 +387,9 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.20.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" dependencies = [ "ascii-canvas", "bit-set", @@ -434,19 +398,22 @@ dependencies = [ "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", + "sha3", "string_cache", "term", - "tiny-keccak", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.2" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] [[package]] name = "lazy_static" @@ -455,32 +422,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "libm" -version = "0.2.8" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] -name = "libredox" -version = "0.1.3" +name = "libc" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags", - "libc", -] +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] -name = "linux-raw-sys" -version = "0.4.14" +name = "libm" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "lock_api" @@ -494,9 +451,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loom" @@ -504,7 +461,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "generator", "scoped-tls", "tracing", @@ -527,99 +484,172 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "memory_units" -version = "0.4.0" +name = "miden" +version = "0.6.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +checksum = "c2871bc4f392053cd115d4532e4b0fb164791829cc94b35641ed72480547dfd1" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] [[package]] name = "miden-assembly" -version = "0.10.3" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345ae47710bbb4c6956dcc669a537c5cf2081879b6238c0df6da50e84a77ea3f" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3aefae8d99d66c3f8666e917cb3ef976edb39247099311f695e5ba57305616d" +checksum = "6ab186ac7061b47415b923cf2da88df505f25c333f7caace80fb7760cf9c0590" dependencies = [ "aho-corasick", "lalrpop", "lalrpop-util", + "log", "miden-core", - "miden-miette", - "miden-thiserror", - "rustc_version 0.4.0", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", "smallvec", - "tracing", - "unicode-width", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.6.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.6.0" +dependencies = [ + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml", ] [[package]] name = "miden-base-sys" -version = "0.0.6" +version = "0.6.0" dependencies = [ - "miden-assembly", "miden-stdlib-sys", ] [[package]] name = "miden-core" -version = "0.10.3" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e46df4105dc2ec15aa14182ce6de299720991bfb83a9b6aa9293c6ee2b12b18" +checksum = "395ad0b07592486e02de3ff7f3bff1d7fa81b1a7120f7f0b1027d650d810bbab" dependencies = [ - "lock_api", - "loom", - "memchr", "miden-crypto", + "miden-debug-types", "miden-formatting", - "miden-miette", - "miden-thiserror", "num-derive", "num-traits", - "parking_lot", + "thiserror", "winter-math", "winter-utils", ] [[package]] name = "miden-crypto" -version = "0.10.0" +version = "0.15.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6fad06fc3af260ed3c4235821daa2132813d993f96d446856036ae97e9606dd" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" dependencies = [ "blake3", - "cc", "glob", "num", "num-complex", "rand", "rand_core", "sha3", + "thiserror", "winter-crypto", "winter-math", "winter-utils", ] +[[package]] +name = "miden-debug-types" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84e5b15ea6fe0f80688fde2d6e4f5a3b66417d2541388b7a6cf967c6a8a2bc0" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + [[package]] name = "miden-formatting" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9d87c128f467874b272fec318e792385e35b14d200408a10d30909228db864" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", ] [[package]] name = "miden-miette" -version = "7.1.1" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c532250422d933f15b148fb81e4522a5d649c178ab420d0d596c86228da35570" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" dependencies = [ - "backtrace", - "backtrace-ext", - "cfg-if 1.0.0", + "cfg-if", "futures", "indenter", "lazy_static", "miden-miette-derive", - "miden-thiserror", "owo-colors", "regex", "rustc_version 0.2.3", @@ -627,21 +657,18 @@ dependencies = [ "serde_json", "spin", "strip-ansi-escapes", - "supports-color", - "supports-hyperlinks", - "supports-unicode", "syn", - "terminal_size", "textwrap", + "thiserror", "trybuild", - "unicode-width", + "unicode-width 0.1.14", ] [[package]] name = "miden-miette-derive" -version = "7.1.0" +version = "8.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc759f0a2947acae217a2f32f722105cacc57d17d5f93bc16362142943a4edd" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" dependencies = [ "proc-macro2", "quote", @@ -649,57 +676,98 @@ dependencies = [ ] [[package]] -name = "miden-sdk" -version = "0.0.6" +name = "miden-objects" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b513a36886b910d71c39b17f37e48179e251fca49cebcbcf57094bef9941f63" dependencies = [ - "miden-base-sys", - "miden-sdk-alloc", - "miden-stdlib-sys", + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64304339292cc4e50a7b534197326824eeb21f3ffaadd955be63cc57093854ed" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", ] [[package]] name = "miden-sdk-account-test" version = "0.0.0" dependencies = [ - "miden-sdk", - "wee_alloc", + "miden", ] [[package]] name = "miden-sdk-alloc" -version = "0.0.6" +version = "0.6.0" [[package]] name = "miden-stdlib-sys" -version = "0.0.6" +version = "0.6.0" [[package]] -name = "miden-thiserror" -version = "1.0.59" +name = "miden-utils-diagnostics" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "183ff8de338956ecfde3a38573241eb7a6f3d44d73866c210e5629c07fa00253" +checksum = "e9bf9a2c1cf3e3694d0eb347695a291602a57f817dd001ac838f300799576a69" dependencies = [ - "miden-thiserror-impl", + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", ] [[package]] -name = "miden-thiserror-impl" -version = "1.0.59" +name = "miden-utils-sync" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee4176a0f2e7d29d2a8ee7e60b6deb14ce67a20e94c3e2c7275cdb8804e1862" +checksum = "bb026e69ae937b2a83bf69ea86577e0ec2450cb22cce163190b9fd42f2972b63" dependencies = [ - "proc-macro2", - "quote", - "syn", + "lock_api", + "loom", ] [[package]] -name = "miniz_oxide" -version = "0.7.4" +name = "miden-verifier" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "72a305e5e2c68d10bcb8e2ed3dc38e54bc3a538acc9eadc154b713d5bb47af22" dependencies = [ - "adler", + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", ] [[package]] @@ -803,20 +871,11 @@ dependencies = [ "libm", ] -[[package]] -name = "object" -version = "0.36.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "overload" @@ -826,9 +885,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owo-colors" -version = "4.0.0" +version = "4.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f" +checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" [[package]] name = "parking_lot" @@ -846,18 +905,24 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "petgraph" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", "indexmap", @@ -865,18 +930,18 @@ dependencies = [ [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -884,99 +949,80 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] -name = "rand" -version = "0.8.5" +name = "r-efi" +version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "rand" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ - "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -990,13 +1036,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -1007,15 +1053,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "rustc-demangle" -version = "0.1.24" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustc_version" @@ -1028,37 +1068,24 @@ dependencies = [ [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.23", + "semver 1.0.26", ] [[package]] -name = "rustix" -version = "0.38.34" +name = "rustversion" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -1092,9 +1119,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -1104,18 +1134,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.205" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33aedb1a7135da52b7c21791455563facbbcc43d0f0f66165b42c21b3dfb150" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.205" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692d6f5ac90220161d6774db30c662202721e64aed9058d2c394f451261420c1" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -1124,9 +1154,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.122" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -1136,9 +1166,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -1162,17 +1192,23 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "smawk" @@ -1188,12 +1224,11 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", "phf_shared", "precomputed-hash", @@ -1201,54 +1236,37 @@ dependencies = [ [[package]] name = "strip-ansi-escapes" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" dependencies = [ "vte", ] -[[package]] -name = "supports-color" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9829b314621dfc575df4e409e79f9d6a66a3bd707ab73f23cb4aa3a854ac854f" -dependencies = [ - "is_ci", -] - -[[package]] -name = "supports-hyperlinks" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" - -[[package]] -name = "supports-unicode" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" - [[package]] name = "syn" -version = "2.0.72" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + [[package]] name = "term" -version = "0.7.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" dependencies = [ - "dirs-next", - "rustversion", - "winapi", + "windows-sys", ] [[package]] @@ -1260,41 +1278,31 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "terminal_size" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] - [[package]] name = "textwrap" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "smawk", "unicode-linebreak", - "unicode-width", + "unicode-width 0.2.0", ] [[package]] name = "thiserror" -version = "1.0.63" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -1307,25 +1315,17 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "once_cell", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "toml" -version = "0.8.19" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" dependencies = [ + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -1334,31 +1334,38 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -1367,9 +1374,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -1378,9 +1385,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", "valuable", @@ -1399,9 +1406,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -1417,30 +1424,31 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.99" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8" +checksum = "1c9bf9513a2f4aeef5fdac8677d7d349c79fdbcc03b9c86da6e9d254f1e43be2" dependencies = [ "dissimilar", "glob", "serde", "serde_derive", "serde_json", + "target-triple", "termcolor", "toml", ] [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-linebreak" @@ -1450,27 +1458,27 @@ checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "unicode-xid" -version = "0.2.4" +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] -name = "utf8parse" -version = "0.2.2" +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "version_check" @@ -1480,50 +1488,121 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "vte" -version = "0.11.1" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ - "utf8parse", - "vte_generate_state_changes", + "wit-bindgen-rt", ] [[package]] -name = "vte_generate_state_changes" -version = "0.1.2" +name = "wasm-bindgen" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", "proc-macro2", "quote", + "syn", + "wasm-bindgen-shared", ] [[package]] -name = "walkdir" -version = "2.5.0" +name = "wasm-bindgen-macro" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ - "same-file", - "winapi-util", + "quote", + "wasm-bindgen-macro-support", ] [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "wasm-bindgen-macro-support" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] [[package]] -name = "wee_alloc" -version = "0.4.5" +name = "wasm-bindgen-shared" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ - "cfg-if 0.1.10", - "libc", - "memory_units", - "winapi", + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", ] [[package]] @@ -1548,7 +1627,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys", ] [[package]] @@ -1559,32 +1638,55 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.58.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +checksum = "c5ee8f3d025738cb02bad7868bbb5f8a6327501e870bf51f1b455b0a2454a419" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ "windows-core", - "windows-targets 0.52.6", ] [[package]] name = "windows-core" -version = "0.58.0" +version = "0.61.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +checksum = "46ec44dc15085cea82cf9c78f85a9114c463a369786585ad2882d1ff0b0acf40" dependencies = [ "windows-implement", "windows-interface", + "windows-link", "windows-result", "windows-strings", - "windows-targets 0.52.6", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", ] [[package]] name = "windows-implement" -version = "0.58.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", @@ -1593,9 +1695,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.58.0" +version = "0.59.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", @@ -1603,40 +1705,37 @@ dependencies = [ ] [[package]] -name = "windows-result" -version = "0.2.0" +name = "windows-link" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" -dependencies = [ - "windows-targets 0.52.6", -] +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] -name = "windows-strings" -version = "0.1.0" +name = "windows-numerics" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-result", - "windows-targets 0.52.6", + "windows-core", + "windows-link", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-result" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "4b895b5356fc36103d0f64dd1e94dfa7ac5633f1c9dd6e80fe9ec4adef69e09d" dependencies = [ - "windows-targets 0.48.5", + "windows-link", ] [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-strings" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "2a7ab927b2637c19b3dbe0965e75d8f2d30bdd697a1516191cad2ec4df8fb28a" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -1645,22 +1744,7 @@ version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -1669,21 +1753,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-threading" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] [[package]] name = "windows_aarch64_gnullvm" @@ -1691,24 +1778,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1721,48 +1796,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1771,18 +1822,31 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + [[package]] name = "winter-crypto" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00fbb724d2d9fbfd3aa16ea27f5e461d4fe1d74b0c9e0ed1bf79e9e2a955f4d5" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" dependencies = [ "blake3", "sha3", @@ -1790,38 +1854,163 @@ dependencies = [ "winter-utils", ] +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + [[package]] name = "winter-math" -version = "0.9.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004f85bb051ce986ec0b9a2bd90aaf81b83e3c67464becfdf7db31f14c1019ba" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" dependencies = [ "winter-utils", ] +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + [[package]] name = "winter-utils" -version = "0.9.1" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0568612a95bcae3c94fb14da2686f8279ca77723dbdf1e97cf3673798faf6485" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] [[package]] -name = "zerocopy" -version = "0.7.35" +name = "wit-bindgen-rust" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" dependencies = [ - "byteorder", - "zerocopy-derive", + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", ] [[package]] -name = "zerocopy-derive" -version = "0.7.35" +name = "wit-bindgen-rust-macro" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" dependencies = [ + "anyhow", + "prettyplease", "proc-macro2", "quote", "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", ] diff --git a/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.toml index 2b8a9e198..eca3defde 100644 --- a/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.toml +++ b/tests/rust-apps-wasm/rust-sdk/account-test/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["trim-paths"] + [package] name = "miden-sdk-account-test" rust-version = "1.71" @@ -8,11 +10,11 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] -miden-sdk = { path = "../../../../sdk/sdk" } -wee_alloc = { version = "0.4.5", default-features = false } +miden = { path = "../../../../sdk/sdk" } [profile.release] panic = "abort" # optimize for size opt-level = "z" debug = true +trim-paths = ["diagnostics", "object"] diff --git a/tests/rust-apps-wasm/rust-sdk/account-test/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/account-test/src/lib.rs index e0c0602da..5f481f590 100644 --- a/tests/rust-apps-wasm/rust-sdk/account-test/src/lib.rs +++ b/tests/rust-apps-wasm/rust-sdk/account-test/src/lib.rs @@ -1,6 +1,6 @@ extern crate alloc; -use miden_sdk::*; +use miden::*; #[global_allocator] static ALLOC: BumpAlloc = BumpAlloc::new(); @@ -10,15 +10,15 @@ pub struct Account; impl Account { #[no_mangle] pub fn get_wallet_magic_number() -> Felt { - let acc_id = get_id(); + let acc_id = miden::account::get_id(); let magic = felt!(42); magic + acc_id.into() } #[no_mangle] pub fn test_add_asset() -> Felt { - let asset_in = CoreAsset::new([felt!(1), felt!(2), felt!(3), felt!(4)]); - let asset_out = add_asset(asset_in); + let asset_in = Asset::new([felt!(1), felt!(2), felt!(3), felt!(4)]); + let asset_out = miden::account::add_asset(asset_in); asset_out.as_word()[0] } @@ -34,7 +34,7 @@ impl Account { } else if a >= b { b / a } else if a == b { - assert_eq(a, b); + miden::assert_eq(a, b); a + Felt::from_u64_unchecked(d) } else if a != b { -a @@ -54,7 +54,7 @@ impl Note { #[no_mangle] pub fn note_script() -> Felt { let mut sum = Felt::new(0).unwrap(); - for input in get_inputs() { + for input in miden::note::get_inputs() { sum = sum + input; } sum @@ -87,17 +87,17 @@ pub fn test_pipe_double_words_to_memory(num_words: Felt) -> (Word, Vec) { } #[no_mangle] -pub fn test_remove_asset(asset: CoreAsset) -> Felt { - let asset_out = remove_asset(asset); +pub fn test_remove_asset(asset: Asset) -> Felt { + let asset_out = miden::account::remove_asset(asset); asset_out.as_word()[0] } #[no_mangle] pub fn test_create_note( - asset: CoreAsset, + asset: Asset, tag: Tag, note_type: NoteType, recipient: Recipient, ) -> NoteId { - create_note(asset, tag, note_type, recipient) + miden::tx::create_note(asset, tag, note_type, recipient) } diff --git a/tests/rust-apps-wasm/rust-sdk/add/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/add/Cargo.lock new file mode 100644 index 000000000..2b97c0952 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/add/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pure-rust-add" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/add/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/add/Cargo.toml new file mode 100644 index 000000000..5c2bfc6d0 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/add/Cargo.toml @@ -0,0 +1,39 @@ +cargo-features = ["trim-paths"] + +[package] +name = "pure-rust-add" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../../../sdk/sdk" } + + +[profile.release] +# optimize the output for size +opt-level = "z" +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +opt-level = 1 +debug-assertions = true +overflow-checks = false +debug = true +trim-paths = ["diagnostics", "object"] diff --git a/tests/rust-apps-wasm/rust-sdk/add/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/add/src/lib.rs new file mode 100644 index 000000000..dade07487 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/add/src/lib.rs @@ -0,0 +1,19 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +// use miden::Felt; + +#[no_mangle] +pub fn entrypoint(a: u32, b: u32) -> u32 { + a + b +} diff --git a/tests/rust-apps-wasm/rust-sdk/component-macros-account/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/component-macros-account/Cargo.lock new file mode 100644 index 000000000..85d08881e --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/component-macros-account/Cargo.lock @@ -0,0 +1,1979 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "component_macros_account" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2871bc4f392053cd115d4532e4b0fb164791829cc94b35641ed72480547dfd1" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345ae47710bbb4c6956dcc669a537c5cf2081879b6238c0df6da50e84a77ea3f" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab186ac7061b47415b923cf2da88df505f25c333f7caace80fb7760cf9c0590" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.27", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.27", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ad0b07592486e02de3ff7f3bff1d7fa81b1a7120f7f0b1027d650d810bbab" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84e5b15ea6fe0f80688fde2d6e4f5a3b66417d2541388b7a6cf967c6a8a2bc0" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9d87c128f467874b272fec318e792385e35b14d200408a10d30909228db864" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b513a36886b910d71c39b17f37e48179e251fca49cebcbcf57094bef9941f63" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.27", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64304339292cc4e50a7b534197326824eeb21f3ffaadd955be63cc57093854ed" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bf9a2c1cf3e3694d0eb347695a291602a57f817dd001ac838f300799576a69" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb026e69ae937b2a83bf69ea86577e0ec2450cb22cce163190b9fd42f2972b63" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a305e5e2c68d10bcb8e2ed3dc38e54bc3a538acc9eadc154b713d5bb47af22" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.3", + "toml_datetime 0.7.3", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d66678374d835fe847e0dc8348fde2ceb5be4a7ec204437d8367f0d8df266a5" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver 1.0.27", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/component-macros-account/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/component-macros-account/Cargo.toml new file mode 100644 index 000000000..bf327b9b4 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/component-macros-account/Cargo.toml @@ -0,0 +1,25 @@ +cargo-features = ["trim-paths"] + +[package] +name = "component_macros_account" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +miden = { path = "../../../../sdk/sdk" } + +[package.metadata.component] +package = "miden:component-macros-account" + +[package.metadata.miden] +project-kind = "account" +supported-types = ["RegularAccountUpdatableCode"] + +[profile.release] +trim-paths = ["diagnostics", "object"] + +[profile.dev] +trim-paths = ["diagnostics", "object"] diff --git a/tests/rust-apps-wasm/rust-sdk/component-macros-account/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/component-macros-account/src/lib.rs new file mode 100644 index 000000000..ab194adf1 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/component-macros-account/src/lib.rs @@ -0,0 +1,90 @@ +#![no_std] + +use miden::{component, export_type, Asset, Felt, Word}; + +pub mod my_types { + use miden::{export_type, Felt}; + + #[export_type] + pub enum EnumA { + VariantA, + VariantB, + } + + #[export_type] + pub struct StructC { + pub inner1: Felt, + pub inner2: Felt, + } +} + +#[export_type] +pub struct StructA { + pub foo: Word, + pub asset: Asset, +} + +#[export_type] +pub struct StructB { + pub bar: Felt, + pub baz: Felt, +} + +#[export_type] +pub struct StructD { + pub bar: Felt, + pub baz: Felt, +} + +#[export_type] +pub struct ForwardHolder { + pub nested: LaterDefined, +} + +#[export_type] +pub struct LaterDefined { + pub value: Felt, +} + +#[component] +struct MyAccount; + +#[component] +impl MyAccount { + /// Exercises exported user-defined type and SDK type in signatures and return value. + pub fn test_custom_types(&self, a: StructA, asset: Asset) -> StructB { + let foo_val = + Word::from([a.foo.inner.0, asset.inner.inner.0, a.foo.inner.1, a.foo.inner.2]); + + let val_a = StructA { + foo: foo_val, + asset, + }; + let c = self.test_custom_types2(val_a, asset); + StructB { + bar: c.inner1, + baz: c.inner2, + } + } + + /// Exercises user-defined types in a sub-module + pub fn test_custom_types2(&self, a: StructA, asset: Asset) -> my_types::StructC { + let d = self.test_custom_types_private(a, asset); + + let _forward = ForwardHolder { + nested: LaterDefined { value: d.bar }, + }; + + my_types::StructC { + inner1: d.bar, + inner2: d.baz, + } + } + + fn test_custom_types_private(&self, a: StructA, _asset: Asset) -> StructD { + StructD { + bar: a.foo.inner.0, + baz: a.foo.inner.1, + } + } +} diff --git a/tests/rust-apps-wasm/rust-sdk/component-macros-note/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/component-macros-note/Cargo.lock new file mode 100644 index 000000000..b25038dc9 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/component-macros-note/Cargo.lock @@ -0,0 +1,1979 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "component_macros_note" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown 0.16.0", + "serde", + "serde_core", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2871bc4f392053cd115d4532e4b0fb164791829cc94b35641ed72480547dfd1" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "345ae47710bbb4c6956dcc669a537c5cf2081879b6238c0df6da50e84a77ea3f" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ab186ac7061b47415b923cf2da88df505f25c333f7caace80fb7760cf9c0590" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.27", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.27", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "395ad0b07592486e02de3ff7f3bff1d7fa81b1a7120f7f0b1027d650d810bbab" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c84e5b15ea6fe0f80688fde2d6e4f5a3b66417d2541388b7a6cf967c6a8a2bc0" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9d87c128f467874b272fec318e792385e35b14d200408a10d30909228db864" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b513a36886b910d71c39b17f37e48179e251fca49cebcbcf57094bef9941f63" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.27", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64304339292cc4e50a7b534197326824eeb21f3ffaadd955be63cc57093854ed" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9bf9a2c1cf3e3694d0eb347695a291602a57f817dd001ac838f300799576a69" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb026e69ae937b2a83bf69ea86577e0ec2450cb22cce163190b9fd42f2972b63" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a305e5e2c68d10bcb8e2ed3dc38e54bc3a538acc9eadc154b713d5bb47af22" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.27", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.2", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned 1.0.3", + "toml_datetime 0.7.3", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d66678374d835fe847e0dc8348fde2ceb5be4a7ec204437d8367f0d8df266a5" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver 1.0.27", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.27", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/component-macros-note/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/component-macros-note/Cargo.toml new file mode 100644 index 000000000..646ba57c2 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/component-macros-note/Cargo.toml @@ -0,0 +1,24 @@ +cargo-features = ["trim-paths"] + +[package] +name = "component_macros_note" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +miden = { path = "../../../../sdk/sdk" } + +[package.metadata.component] +package = "miden:component-macros-note" + +[package.metadata.miden] +project-kind = "note-script" + +[package.metadata.miden.dependencies] +"miden:component-macros" = { path = "../component-macros-account" } + +[package.metadata.component.target.dependencies] +"miden:basic-wallet" = { path = "../component-macros-account/target/generated-wit/" } diff --git a/tests/rust-apps-wasm/rust-sdk/component-macros-note/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/component-macros-note/src/lib.rs new file mode 100644 index 000000000..ec52d0318 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/component-macros-note/src/lib.rs @@ -0,0 +1,25 @@ +#![no_std] + +use miden::*; + +use crate::bindings::miden::component_macros_account::component_macros_account::{ + test_custom_types, StructA, StructB, +}; + +#[note_script] +fn run(_arg: Word) { + let foo_val = Word::from([felt!(11), felt!(22), felt!(33), felt!(44)]); + let asset = Asset::new([felt!(99), felt!(88), felt!(77), felt!(66)]); + let value = StructA { + foo: foo_val, + asset, + }; + let result = test_custom_types(value, asset); + let expected = StructB { + bar: foo_val.inner.0, + baz: asset.inner.inner.0, + }; + + assert_eq!(result.bar, expected.bar); + assert_eq!(result.baz, expected.baz); +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/Cargo.lock new file mode 100644 index 000000000..ff795bf50 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cross-ctx-account-word-arg" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/Cargo.toml new file mode 100644 index 000000000..aca76a4d8 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/Cargo.toml @@ -0,0 +1,42 @@ +cargo-features = ["trim-paths"] + +[package] +name = "cross-ctx-account-word-arg" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../../../sdk/sdk" } + +[profile.release] +# optimize the output for size +opt-level = "z" +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +opt-level = 1 +debug-assertions = true +overflow-checks = false +debug = true +trim-paths = ["diagnostics", "object"] + +[package.metadata.miden] +project-kind = "account" +supported-types = ["RegularAccountUpdatableCode"] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/src/lib.rs new file mode 100644 index 000000000..1481358ee --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/src/lib.rs @@ -0,0 +1,63 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +use miden::*; + +miden::generate!(); +bindings::export!(MyFoo); + +use bindings::exports::miden::cross_ctx_account_word_arg::*; + +struct MyFoo; + +impl foo::Guest for MyFoo { + fn process_word( + input1: Word, + input2: Word, + input3: Word, + felt1: Felt, + felt2: Felt, + felt3: Felt, + felt4: Felt, + ) -> Felt { + // Use weighted sum to encode the order of elements. Different weights ensure different + // results if elements are reordered during the flattening + let sum1 = input1.inner.0 * felt!(1) + + input1.inner.1 * felt!(2) + + input1.inner.2 * felt!(4) + + input1.inner.3 * felt!(8); + + let sum2 = input2.inner.0 * felt!(16) + + input2.inner.1 * felt!(32) + + input2.inner.2 * felt!(64) + + input2.inner.3 * felt!(128); + + let sum3 = input3.inner.0 * felt!(256) + + input3.inner.1 * felt!(512) + + input3.inner.2 * felt!(1024) + + input3.inner.3 * felt!(2048); + + let felt_sum = felt1 * felt!(4096) + felt2 * felt!(8192) + felt3 * felt!(16384); + + let felt4_sum = felt4; + + sum1 + sum2 + sum3 + felt_sum + felt4_sum + } +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/wit/cross-ctx-account-word.wit b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/wit/cross-ctx-account-word.wit new file mode 100644 index 000000000..92db0c2a5 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word-arg/wit/cross-ctx-account-word.wit @@ -0,0 +1,13 @@ +package miden:cross-ctx-account-word-arg@1.0.0; + +use miden:base/core-types@1.0.0; + +interface foo { + use core-types.{word, felt}; + + process-word: func(input1: word, input2: word, input3: word, felt1: felt, felt2: felt, felt3: felt, felt4: felt) -> felt; +} + +world foo-world { + export foo; +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/Cargo.lock new file mode 100644 index 000000000..52e1b40d6 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cross-ctx-account-word" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/Cargo.toml new file mode 100644 index 000000000..017af0df4 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/Cargo.toml @@ -0,0 +1,43 @@ +cargo-features = ["trim-paths"] + +[package] +name = "cross-ctx-account-word" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../../../sdk/sdk" } + + +[profile.release] +# optimize the output for size +opt-level = "z" +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +opt-level = 1 +debug-assertions = true +overflow-checks = false +debug = true +trim-paths = ["diagnostics", "object"] + +[package.metadata.miden] +project-kind = "account" +supported-types = ["RegularAccountUpdatableCode"] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/src/lib.rs new file mode 100644 index 000000000..396292a1b --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/src/lib.rs @@ -0,0 +1,96 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +use bindings::exports::miden::cross_ctx_account_word::*; + +miden::generate!(); +bindings::export!(MyFoo); + +use foo::{MixedStruct, NestedStruct, Pair, Triple}; +use miden::{felt, Felt, Word}; + +struct MyFoo; + +impl foo::Guest for MyFoo { + fn process_word(input: Word) -> Word { + let result = Word::new([ + input.inner.0 + felt!(1), + input.inner.1 + felt!(2), + input.inner.2 + felt!(3), + input.inner.3 + felt!(4), + ]); + + result + } + + // To test the proper `canon lower` reconstruction on shim + fixup modules bypass + // The same signature, different name and body + fn process_another_word(input: Word) -> Word { + let result = Word::new([ + input.inner.0 + felt!(2), + input.inner.1 + felt!(3), + input.inner.2 + felt!(4), + input.inner.3 + felt!(5), + ]); + + result + } + + fn process_felt(input: Felt) -> Felt { + input + felt!(3) + } + + fn process_pair(input: Pair) -> Pair { + Pair { + first: input.first + felt!(4), + second: input.second + felt!(4), + } + } + + fn process_triple(input: Triple) -> Triple { + Triple { + x: input.x + felt!(5), + y: input.y + felt!(5), + z: input.z + felt!(5), + } + } + + fn process_mixed(input: MixedStruct) -> MixedStruct { + MixedStruct { + f: input.f + 1000, + a: input.a + felt!(6), + b: input.b + 10, + c: input.c + felt!(7), + d: input.d + 11, + e: !input.e, + g: input.g + 9, + } + } + + fn process_nested(input: NestedStruct) -> NestedStruct { + NestedStruct { + inner: Pair { + first: input.inner.first + felt!(8), + second: input.inner.second + felt!(8), + }, + value: input.value + felt!(8), + } + } +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/wit/cross-ctx-account-word.wit b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/wit/cross-ctx-account-word.wit new file mode 100644 index 000000000..dfd7e7ba9 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account-word/wit/cross-ctx-account-word.wit @@ -0,0 +1,47 @@ +package miden:cross-ctx-account-word@1.0.0; + +use miden:base/core-types@1.0.0; + +interface foo { + use core-types.{word, felt}; + + // Test different struct shapes + record pair { + first: felt, + second: felt + } + + record triple { + x: felt, + y: felt, + z: felt + } + + record mixed-struct { + f: u64, + a: felt, + b: u32, + c: felt, + d: u8, + e: bool, + g: u16, + } + + record nested-struct { + inner: pair, + value: felt + } + + process-word: func(input: word) -> word; + process-another-word: func(input: word) -> word; + process-felt: func(input: felt) -> felt; + + process-pair: func(input: pair) -> pair; + process-triple: func(input: triple) -> triple; + process-mixed: func(input: mixed-struct) -> mixed-struct; + process-nested: func(input: nested-struct) -> nested-struct; +} + +world foo-world { + export foo; +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/.gitignore b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/.gitignore @@ -0,0 +1 @@ +/target diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/Cargo.lock new file mode 100644 index 000000000..b017bfb8d --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cross-ctx-account" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/Cargo.toml new file mode 100644 index 000000000..f396ed254 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/Cargo.toml @@ -0,0 +1,42 @@ +cargo-features = ["trim-paths"] + +[package] +name = "cross-ctx-account" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../../../sdk/sdk" } + +[profile.release] +# optimize the output for size +opt-level = "z" +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +opt-level = 1 +debug-assertions = true +overflow-checks = false +debug = true +trim-paths = ["diagnostics", "object"] + +[package.metadata.miden] +project-kind = "account" +supported-types = ["RegularAccountUpdatableCode"] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/src/lib.rs new file mode 100644 index 000000000..2435958aa --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/src/lib.rs @@ -0,0 +1,39 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +use bindings::exports::miden::cross_ctx_account::*; + +miden::generate!(); +bindings::export!(MyFoo); + +use miden::Felt; + +// To test the data segment loading +pub static mut FOO: u32 = 42; + +struct MyFoo; + +impl foo::Guest for MyFoo { + fn process_felt(input: Felt) -> Felt { + let res = input + Felt::from_u32(unsafe { FOO }); + unsafe { FOO = res.as_u64() as u32 }; + res + } +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/wit/cross-ctx-account.wit b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/wit/cross-ctx-account.wit new file mode 100644 index 000000000..cc45a8ded --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-account/wit/cross-ctx-account.wit @@ -0,0 +1,13 @@ +package miden:cross-ctx-account@1.0.0; + +use miden:base/core-types@1.0.0; + +interface foo { + use core-types.{felt}; + + process-felt: func(input: felt) -> felt; +} + +world foo-world { + export foo; +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/Cargo.lock new file mode 100644 index 000000000..ca66bb5e4 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cross-ctx-note-word-arg" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/Cargo.toml new file mode 100644 index 000000000..b711a284c --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/Cargo.toml @@ -0,0 +1,48 @@ +cargo-features = ["trim-paths"] + +[package] +name = "cross-ctx-note-word-arg" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../../../sdk/sdk" } + +[profile.release] +# optimize the output for size +opt-level = "z" +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +opt-level = 1 +debug-assertions = true +overflow-checks = false +debug = true +trim-paths = ["diagnostics", "object"] + +[package.metadata.miden] +project-kind = "note-script" + +# Miden dependencies for cargo-miden build/linking +[package.metadata.miden.dependencies] +"miden:cross-ctx-account-word-arg" = { path = "../cross-ctx-account-word-arg" } + +[package.metadata.component.target.dependencies] +"miden:cross-ctx-account-word-arg" = { path = "../cross-ctx-account-word-arg/wit/cross-ctx-account-word.wit" } diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/src/lib.rs new file mode 100644 index 000000000..1a4f32632 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/src/lib.rs @@ -0,0 +1,58 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +miden::generate!(); +bindings::export!(MyNote); + +use bindings::{ + exports::miden::base::note_script::Guest, miden::cross_ctx_account_word_arg::foo::process_word, +}; +use miden::*; + +struct MyNote; + +impl Guest for MyNote { + fn run(_arg: Word) { + let input1 = Word { + inner: (felt!(1), felt!(2), felt!(3), felt!(4)), + }; + let input2 = Word { + inner: (felt!(5), felt!(6), felt!(7), felt!(8)), + }; + let input3 = Word { + inner: (felt!(9), felt!(10), felt!(11), felt!(12)), + }; + let felt1 = felt!(13); + let felt2 = felt!(14); + let felt3 = felt!(15); + + // Returns "hash" of the inputs + let output = process_word(input1, input2, input3, felt1, felt2, felt3, felt!(7)); + // Expected: + // input1: 1*1 + 2*2 + 3*4 + 4*8 = 1 + 4 + 12 + 32 = 49 + // input2: 5*16 + 6*32 + 7*64 + 8*128 = 80 + 192 + 448 + 1024 = 1744 + // input3: 9*256 + 10*512 + 11*1024 + 12*2048 = 2304 + 5120 + 11264 + 24576 = 43264 + // felt1: 13*4096 = 53248 + // felt2: 14*8192 = 114688 + // felt3: 15*16384 = 245760 + // Total: 49 + 1744 + 43264 + 53248 + 114688 + 245760 = 458753 + 7 = 458760 + assert_eq(output, felt!(458760)); + } +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/wit/cross-ctx-note-word.wit b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/wit/cross-ctx-note-word.wit new file mode 100644 index 000000000..c5554c8ba --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word-arg/wit/cross-ctx-note-word.wit @@ -0,0 +1,6 @@ +package miden:cross-ctx-note-word-arg@1.0.0; + +world cross-ctx-note-word-world { + import miden:cross-ctx-account-word-arg/foo@1.0.0; + export miden:base/note-script@1.0.0; +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/Cargo.lock new file mode 100644 index 000000000..d8a5304c8 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cross-ctx-note-word" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/Cargo.toml new file mode 100644 index 000000000..ebd8e7483 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/Cargo.toml @@ -0,0 +1,48 @@ +cargo-features = ["trim-paths"] + +[package] +name = "cross-ctx-note-word" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../../../sdk/sdk" } + +[profile.release] +# optimize the output for size +opt-level = "z" +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +opt-level = 1 +debug-assertions = true +overflow-checks = false +debug = true +trim-paths = ["diagnostics", "object"] + +[package.metadata.miden] +project-kind = "note-script" + +# Miden dependencies for cargo-miden build/linking +[package.metadata.miden.dependencies] +"miden:cross-ctx-account-word" = { path = "../cross-ctx-account-word" } + +[package.metadata.component.target.dependencies] +"miden:cross-ctx-account-word" = { path = "../cross-ctx-account-word/wit/cross-ctx-account-word.wit" } diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/src/lib.rs new file mode 100644 index 000000000..747827328 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/src/lib.rs @@ -0,0 +1,104 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +miden::generate!(); +bindings::export!(MyNote); + +use bindings::{exports::miden::base::note_script::Guest, miden::cross_ctx_account_word::foo::*}; +use miden::*; + +struct MyNote; + +impl Guest for MyNote { + fn run(_arg: Word) { + let input = Word { + inner: (felt!(2), felt!(3), felt!(4), felt!(5)), + }; + + let output = process_word(input.clone()); + + assert_eq(output.inner.0, felt!(3)); + assert_eq(output.inner.1, felt!(5)); + assert_eq(output.inner.2, felt!(7)); + assert_eq(output.inner.3, felt!(9)); + + let output = process_another_word(input); + + assert_eq(output.inner.0, felt!(4)); + assert_eq(output.inner.1, felt!(6)); + assert_eq(output.inner.2, felt!(8)); + assert_eq(output.inner.3, felt!(10)); + + let felt_input = felt!(9); + let felt_output = process_felt(felt_input); + assert_eq(felt_output, felt!(12)); + + let pair_input = Pair { + first: felt!(10), + second: felt!(20), + }; + let pair_output = process_pair(pair_input); + assert_eq(pair_output.first, felt!(14)); // 10 + 4 + assert_eq(pair_output.second, felt!(24)); // 20 + 4 + + let triple_input = Triple { + x: felt!(100), + y: felt!(200), + z: felt!(300), + }; + let triple_output = process_triple(triple_input); + assert_eq(triple_output.x, felt!(105)); // 100 + 5 + assert_eq(triple_output.y, felt!(205)); // 200 + 5 + assert_eq(triple_output.z, felt!(305)); // 300 + 5 + + let mixed_input = MixedStruct { + f: u64::MAX - 1000, + a: Felt::new(Felt::M - 1 - 6).unwrap(), + b: u32::MAX - 10, + c: felt!(50), + d: 111, + e: false, + g: 3, + }; + let mixed_output = process_mixed(mixed_input); + if mixed_output.f != u64::MAX { + // fail + assert_eq!(0, 1); + } + assert_eq(mixed_output.a, Felt::new(Felt::M - 1).unwrap()); // M - 1 - 6 + 6 + assert_eq(mixed_output.b.into(), Felt::from_u32(u32::MAX)); // u32::MAX - 10 + 10 + assert_eq(mixed_output.c, felt!(57)); // 50 + 7 + assert_eq(mixed_output.d.into(), Felt::from_u32(122)); + assert_eq(Felt::from_u32(mixed_output.e as u32), felt!(1)); + assert_eq(mixed_output.g.into(), Felt::from_u32(12)); + + let nested_input = NestedStruct { + inner: Pair { + first: felt!(30), + second: felt!(40), + }, + value: felt!(50), + }; + let nested_output = process_nested(nested_input); + assert_eq(nested_output.inner.first, felt!(38)); // 30 + 8 + assert_eq(nested_output.inner.second, felt!(48)); // 40 + 8 + assert_eq(nested_output.value, felt!(58)); // 50 + 8 + } +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/wit/cross-ctx-note-word.wit b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/wit/cross-ctx-note-word.wit new file mode 100644 index 000000000..eade40429 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note-word/wit/cross-ctx-note-word.wit @@ -0,0 +1,6 @@ +package miden:cross-ctx-note-word@1.0.0; + +world cross-ctx-note-word-world { + import miden:cross-ctx-account-word/foo@1.0.0; + export miden:base/note-script@1.0.0; +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/.gitignore b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/.gitignore new file mode 100644 index 000000000..ea8c4bf7f --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/.gitignore @@ -0,0 +1 @@ +/target diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.lock b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.lock new file mode 100644 index 000000000..2fde1ad08 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "blake3" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cross-ctx-note" +version = "0.1.0" +dependencies = [ + "miden", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dissimilar" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "ena" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" +dependencies = [ + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generator" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +dependencies = [ + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax 0.8.5", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "miden" +version = "0.7.0" +dependencies = [ + "miden-base", + "miden-base-sys", + "miden-sdk-alloc", + "miden-stdlib-sys", + "wit-bindgen", +] + +[[package]] +name = "miden-air" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db750ce0c58f51ba786c7391f392c4b77e0c83a44c5096672d4d0270d3cc7763" +dependencies = [ + "miden-core", + "thiserror", + "winter-air", + "winter-prover", +] + +[[package]] +name = "miden-assembly" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82575d8479aad3966be3defdc17911264bfdc99f9a7bb185180eec57c6bda9f5" +dependencies = [ + "log", + "miden-assembly-syntax", + "miden-core", + "miden-mast-package", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-assembly-syntax" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b3588ce15920c0bff47e8bf8c6ca9e0a7a539ff93014cb5ec3c665f60bc0f1" +dependencies = [ + "aho-corasick", + "lalrpop", + "lalrpop-util", + "log", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "midenc-hir-type", + "regex", + "rustc_version 0.4.1", + "semver 1.0.26", + "smallvec", + "thiserror", +] + +[[package]] +name = "miden-base" +version = "0.7.0" +dependencies = [ + "miden-base-macros", + "miden-base-sys", + "miden-stdlib-sys", +] + +[[package]] +name = "miden-base-macros" +version = "0.7.0" +dependencies = [ + "heck", + "miden-objects", + "proc-macro2", + "quote", + "semver 1.0.26", + "syn", + "toml 0.8.23", +] + +[[package]] +name = "miden-base-sys" +version = "0.7.0" +dependencies = [ + "miden-stdlib-sys", +] + +[[package]] +name = "miden-core" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "571a943a923e5fb3f1bed534f41a1542c531ad2aec87dc0d5699af1709fbcea6" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-formatting", + "num-derive", + "num-traits", + "thiserror", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-crypto" +version = "0.15.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4329275a11c7d8328b14a7129b21d40183056dcd0d871c3069be6e550d6ca40" +dependencies = [ + "blake3", + "glob", + "num", + "num-complex", + "rand", + "rand_core", + "sha3", + "thiserror", + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "miden-debug-types" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a4b53092da70aa4c9b75acc85e1c7b4d8202ce89487d2271ebdc2defcb08d6" +dependencies = [ + "memchr", + "miden-crypto", + "miden-formatting", + "miden-miette", + "miden-utils-sync", + "paste", + "thiserror", +] + +[[package]] +name = "miden-formatting" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e392e0a8c34b32671012b439de35fa8987bf14f0f8aac279b97f8b8cc6e263b" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-mast-package" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57efbfaea75eeb07d448c04aefce241bf8f23ea11600a669d897280551819992" +dependencies = [ + "derive_more", + "miden-assembly-syntax", + "miden-core", +] + +[[package]] +name = "miden-miette" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eef536978f24a179d94fa2a41e4f92b28e7d8aab14b8d23df28ad2a3d7098b20" +dependencies = [ + "cfg-if", + "futures", + "indenter", + "lazy_static", + "miden-miette-derive", + "owo-colors", + "regex", + "rustc_version 0.2.3", + "rustversion", + "serde_json", + "spin", + "strip-ansi-escapes", + "syn", + "textwrap", + "thiserror", + "trybuild", + "unicode-width 0.1.14", +] + +[[package]] +name = "miden-miette-derive" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86a905f3ea65634dd4d1041a4f0fd0a3e77aa4118341d265af1a94339182222f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "miden-objects" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a16bce60bda18cfeaa49e99e35307c6f45297bfe2f0e18c009fa356edd552e70" +dependencies = [ + "bech32", + "getrandom", + "miden-assembly", + "miden-core", + "miden-crypto", + "miden-processor", + "miden-utils-sync", + "miden-verifier", + "semver 1.0.26", + "thiserror", +] + +[[package]] +name = "miden-processor" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c033f51b575b5a70b763dc0bc05062b6562a8286c6d0c144eaff92da8f214f" +dependencies = [ + "miden-air", + "miden-core", + "miden-debug-types", + "miden-utils-diagnostics", + "thiserror", + "tracing", + "winter-prover", +] + +[[package]] +name = "miden-sdk-alloc" +version = "0.7.0" + +[[package]] +name = "miden-stdlib-sys" +version = "0.7.0" + +[[package]] +name = "miden-utils-diagnostics" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d640d80ce3438275b13d0d400901e5bbf3179737818d91d4e84f747a480fd8b" +dependencies = [ + "miden-crypto", + "miden-debug-types", + "miden-miette", + "paste", + "tracing", +] + +[[package]] +name = "miden-utils-sync" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf49e1fbfefeb58de992767ae7b0b6200885e342f4dd43c510daefce9539b95" +dependencies = [ + "lock_api", + "loom", +] + +[[package]] +name = "miden-verifier" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104dce8b4668639aa97aa748a98aab0ea33c103e06ef5c3fd12445ab3bd2387" +dependencies = [ + "miden-air", + "miden-core", + "thiserror", + "tracing", + "winter-verifier", +] + +[[package]] +name = "midenc-hir-type" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e381ba23e4f57ffa0d6039113f6d6004e5b8c7ae6cb909329b48f2ab525e8680" +dependencies = [ + "miden-formatting", + "smallvec", + "thiserror", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48dd4f4a2c8405440fd0462561f0e5806bd0f77e86f51c761481bdd4018b545e" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + +[[package]] +name = "redox_syscall" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.26", +] + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.141" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-triple" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" + +[[package]] +name = "term" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2111ef44dae28680ae9752bb89409e7310ca33a8c621ebe7b106cf5c928b3ac0" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width 0.2.1", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit", +] + +[[package]] +name = "toml" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0aee96c12fa71097902e0bb061a5e1ebd766a6636bb605ba401c45c1650eac" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 1.0.0", + "toml_datetime 0.7.0", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trybuild" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65af40ad689f2527aebbd37a0a816aea88ff5f774ceabe99de5be02f2f91dae2" +dependencies = [ + "dissimilar", + "glob", + "serde", + "serde_derive", + "serde_json", + "target-triple", + "termcolor", + "toml 0.9.2", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" +dependencies = [ + "bitflags", + "hashbrown", + "indexmap", + "semver 1.0.26", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +dependencies = [ + "memchr", +] + +[[package]] +name = "winter-air" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef01227f23c7c331710f43b877a8333f5f8d539631eea763600f1a74bf018c7c" +dependencies = [ + "libm", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-crypto" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb247bc142438798edb04067ab72a22cf815f57abbd7b78a6fa986fc101db8" +dependencies = [ + "blake3", + "sha3", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-fri" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd592b943f9d65545683868aaf1b601eb66e52bfd67175347362efff09101d3a" +dependencies = [ + "winter-crypto", + "winter-math", + "winter-utils", +] + +[[package]] +name = "winter-math" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aecfb48ee6a8b4746392c8ff31e33e62df8528a3b5628c5af27b92b14aef1ea" +dependencies = [ + "winter-utils", +] + +[[package]] +name = "winter-maybe-async" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d31a19dae58475d019850e25b0170e94b16d382fbf6afee9c0e80fdc935e73e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "winter-prover" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84cc631ed56cd39b78ef932c1ec4060cc6a44d114474291216c32f56655b3048" +dependencies = [ + "tracing", + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-maybe-async", + "winter-utils", +] + +[[package]] +name = "winter-utils" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9951263ef5317740cd0f49e618db00c72fabb70b75756ea26c4d5efe462c04dd" + +[[package]] +name = "winter-verifier" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0425ea81f8f703a1021810216da12003175c7974a584660856224df04b2e2fdb" +dependencies = [ + "winter-air", + "winter-crypto", + "winter-fri", + "winter-math", + "winter-utils", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.239.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver 1.0.26", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.toml b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.toml new file mode 100644 index 000000000..3eadf0ee5 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/Cargo.toml @@ -0,0 +1,48 @@ +cargo-features = ["trim-paths"] + +[package] +name = "cross-ctx-note" +version = "0.1.0" +edition = "2021" + +[lib] +# Build this crate as a self-contained, C-style dynamic library +# This is required to emit the proper Wasm module type +crate-type = ["cdylib"] + +[dependencies] +# Miden SDK consists of a stdlib (intrinsic functions for VM ops, stdlib functions and types) +# and transaction kernel API for the Miden rollup +miden = { path = "../../../../sdk/sdk" } + +[profile.release] +# optimize the output for size +opt-level = "z" +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +trim-paths = ["diagnostics", "object"] + +[profile.dev] +# Explicitly disable panic infrastructure on Wasm, as +# there is no proper support for them anyway, and it +# ensures that panics do not pull in a bunch of standard +# library code unintentionally +panic = "abort" +opt-level = 1 +debug-assertions = true +overflow-checks = false +debug = true +trim-paths = ["diagnostics", "object"] + +[package.metadata.miden] +project-kind = "note-script" + +# Miden dependencies for cargo-miden build/linking +[package.metadata.miden.dependencies] +"miden:cross-ctx-account" = { path = "../cross-ctx-account" } + +[package.metadata.component.target.dependencies] +"miden:cross-ctx-account" = { path = "../cross-ctx-account/wit/cross-ctx-account.wit" } diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/src/lib.rs b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/src/lib.rs new file mode 100644 index 000000000..cc3d536b6 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/src/lib.rs @@ -0,0 +1,43 @@ +// Do not link against libstd (i.e. anything defined in `std::`) +#![no_std] + +// However, we could still use some standard library types while +// remaining no-std compatible, if we uncommented the following lines: +// +// extern crate alloc; +// use alloc::vec::Vec; + +// Global allocator to use heap memory in no-std environment +#[global_allocator] +static ALLOC: miden::BumpAlloc = miden::BumpAlloc::new(); + +// Required for no-std crates +#[cfg(not(test))] +#[panic_handler] +fn my_panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +use miden::*; + +miden::generate!(); +bindings::export!(MyNote); + +use bindings::{ + exports::miden::base::note_script::Guest, miden::cross_ctx_account::foo::process_felt, +}; + +// To test the data segment loading +pub static mut BAR: u32 = 11; + +struct MyNote; + +impl Guest for MyNote { + fn run(_arg: Word) { + let input = Felt::from_u32(unsafe { BAR }); + assert_eq(input, felt!(11)); + let output = process_felt(input); + assert_eq(output, felt!(53)); + unsafe { BAR = output.as_u64() as u32 }; + } +} diff --git a/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/wit/cross-ctx-note.wit b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/wit/cross-ctx-note.wit new file mode 100644 index 000000000..c2eb0aac5 --- /dev/null +++ b/tests/rust-apps-wasm/rust-sdk/cross-ctx-note/wit/cross-ctx-note.wit @@ -0,0 +1,6 @@ +package miden:cross-ctx-note@1.0.0; + +world cross-ctx-note-world { + import miden:cross-ctx-account/foo@1.0.0; + export miden:base/note-script@1.0.0; +} diff --git a/tests/rust-apps-wasm/wit-sdk/basic-wallet/Cargo.lock b/tests/rust-apps-wasm/wit-sdk/basic-wallet/Cargo.lock deleted file mode 100644 index e48ef9474..000000000 --- a/tests/rust-apps-wasm/wit-sdk/basic-wallet/Cargo.lock +++ /dev/null @@ -1,69 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "basic-wallet" -version = "0.0.0" -dependencies = [ - "wee_alloc", - "wit-bindgen-rt", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "wit-bindgen-rt" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a37bd9274cb2d4754b915d624447ec0dce9105d174361841c0826efc79ceb9" diff --git a/tests/rust-apps-wasm/wit-sdk/basic-wallet/Cargo.toml b/tests/rust-apps-wasm/wit-sdk/basic-wallet/Cargo.toml deleted file mode 100644 index 736b46881..000000000 --- a/tests/rust-apps-wasm/wit-sdk/basic-wallet/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "basic-wallet" -version = "0.0.0" -rust-version = "1.71" -authors = ["Miden contributors"] -description = "Basic wallet" -repository = "https://github.com/0xPolygonMiden/miden-ir" -homepage = "https://github.com/0xPolygonMiden/miden-ir" -documentation = "https://github.com/0xPolygonMiden/miden-ir" -license = "MIT" -edition = "2021" -publish = false - -[dependencies] -wit-bindgen-rt = "0.28" -wee_alloc = { version = "0.4.5", default-features = false } - -[lib] -crate-type = ["cdylib"] - -[package.metadata.component] -package = "miden:basic-wallet" - -[package.metadata.component.dependencies] - -[package.metadata.component.target.dependencies] -"miden:base" = { path = "../sdk/wit" } - -[profile.release] -panic = "abort" -debug = true diff --git a/tests/rust-apps-wasm/wit-sdk/basic-wallet/src/bindings.rs b/tests/rust-apps-wasm/wit-sdk/basic-wallet/src/bindings.rs deleted file mode 100644 index c81aa3024..000000000 --- a/tests/rust-apps-wasm/wit-sdk/basic-wallet/src/bindings.rs +++ /dev/null @@ -1,1374 +0,0 @@ -#[allow(dead_code)] -pub mod miden { - #[allow(dead_code)] - pub mod base { - #[allow(dead_code, clippy::all)] - pub mod core_types { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - /// Represents base field element in the field using Montgomery representation. - /// Internal values represent x * R mod M where R = 2^64 mod M and x in [0, M). - /// The backing type is `f64` but the internal values are always integer in the range [0, M). - /// Field modulus M = 2^64 - 2^32 + 1 - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Felt { - /// We plan to use f64 as the backing type for the field element. It has the size that we need and - /// we don't plan to support floating point arithmetic in programs for Miden VM. - /// - /// For now its u64 - pub inner: u64, - } - impl ::core::fmt::Debug for Felt { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Felt").field("inner", &self.inner).finish() - } - } - /// A group of four field elements in the Miden base field. - pub type Word = (Felt, Felt, Felt, Felt); - /// Unique identifier of an account. - /// - /// Account ID consists of 1 field element (~64 bits). This field element uniquely identifies a - /// single account and also specifies the type of the underlying account. Specifically: - /// - The two most significant bits of the ID specify the type of the account: - /// - 00 - regular account with updatable code. - /// - 01 - regular account with immutable code. - /// - 10 - fungible asset faucet with immutable code. - /// - 11 - non-fungible asset faucet with immutable code. - /// - The third most significant bit of the ID specifies whether the account data is stored on-chain: - /// - 0 - full account data is stored on-chain. - /// - 1 - only the account hash is stored on-chain which serves as a commitment to the account state. - /// As such the three most significant bits fully describes the type of the account. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct AccountId { - pub inner: Felt, - } - impl ::core::fmt::Debug for AccountId { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("AccountId").field("inner", &self.inner).finish() - } - } - /// Recipient of the note, i.e., hash(hash(hash(serial_num, [0; 4]), note_script_hash), input_hash) - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Recipient { - pub inner: Word, - } - impl ::core::fmt::Debug for Recipient { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Recipient").field("inner", &self.inner).finish() - } - } - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Tag { - pub inner: Felt, - } - impl ::core::fmt::Debug for Tag { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Tag").field("inner", &self.inner).finish() - } - } - /// A fungible or a non-fungible asset. - /// - /// All assets are encoded using a single word (4 elements) such that it is easy to determine the - /// type of an asset both inside and outside Miden VM. Specifically: - /// Element 1 will be: - /// - ZERO for a fungible asset - /// - non-ZERO for a non-fungible asset - /// The most significant bit will be: - /// - ONE for a fungible asset - /// - ZERO for a non-fungible asset - /// - /// The above properties guarantee that there can never be a collision between a fungible and a - /// non-fungible asset. - /// - /// The methodology for constructing fungible and non-fungible assets is described below. - /// - /// # Fungible assets - /// The most significant element of a fungible asset is set to the ID of the faucet which issued - /// the asset. This guarantees the properties described above (the first bit is ONE). - /// - /// The least significant element is set to the amount of the asset. This amount cannot be greater - /// than 2^63 - 1 and thus requires 63-bits to store. - /// - /// Elements 1 and 2 are set to ZERO. - /// - /// It is impossible to find a collision between two fungible assets issued by different faucets as - /// the faucet_id is included in the description of the asset and this is guaranteed to be different - /// for each faucet as per the faucet creation logic. - /// - /// # Non-fungible assets - /// The 4 elements of non-fungible assets are computed as follows: - /// - First the asset data is hashed. This compresses an asset of an arbitrary length to 4 field - /// elements: [d0, d1, d2, d3]. - /// - d1 is then replaced with the faucet_id which issues the asset: [d0, faucet_id, d2, d3]. - /// - Lastly, the most significant bit of d3 is set to ZERO. - /// - /// It is impossible to find a collision between two non-fungible assets issued by different faucets - /// as the faucet_id is included in the description of the non-fungible asset and this is guaranteed - /// to be different as per the faucet creation logic. Collision resistance for non-fungible assets - /// issued by the same faucet is ~2^95. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct CoreAsset { - pub inner: Word, - } - impl ::core::fmt::Debug for CoreAsset { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("CoreAsset").field("inner", &self.inner).finish() - } - } - /// Account nonce - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Nonce { - pub inner: Felt, - } - impl ::core::fmt::Debug for Nonce { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Nonce").field("inner", &self.inner).finish() - } - } - /// Account hash - #[repr(C)] - #[derive(Clone, Copy)] - pub struct AccountHash { - pub inner: Word, - } - impl ::core::fmt::Debug for AccountHash { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("AccountHash").field("inner", &self.inner).finish() - } - } - /// Block hash - #[repr(C)] - #[derive(Clone, Copy)] - pub struct BlockHash { - pub inner: Word, - } - impl ::core::fmt::Debug for BlockHash { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("BlockHash").field("inner", &self.inner).finish() - } - } - /// Storage value - #[repr(C)] - #[derive(Clone, Copy)] - pub struct StorageValue { - pub inner: Word, - } - impl ::core::fmt::Debug for StorageValue { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("StorageValue").field("inner", &self.inner).finish() - } - } - /// Account storage root - #[repr(C)] - #[derive(Clone, Copy)] - pub struct StorageRoot { - pub inner: Word, - } - impl ::core::fmt::Debug for StorageRoot { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("StorageRoot").field("inner", &self.inner).finish() - } - } - /// Account code root - #[repr(C)] - #[derive(Clone, Copy)] - pub struct AccountCodeRoot { - pub inner: Word, - } - impl ::core::fmt::Debug for AccountCodeRoot { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("AccountCodeRoot") - .field("inner", &self.inner) - .finish() - } - } - /// Commitment to the account vault - #[repr(C)] - #[derive(Clone, Copy)] - pub struct VaultCommitment { - pub inner: Word, - } - impl ::core::fmt::Debug for VaultCommitment { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("VaultCommitment") - .field("inner", &self.inner) - .finish() - } - } - /// An id of the created note - #[repr(C)] - #[derive(Clone, Copy)] - pub struct NoteId { - pub inner: Felt, - } - impl ::core::fmt::Debug for NoteId { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("NoteId").field("inner", &self.inner).finish() - } - } - #[allow(unused_unsafe, clippy::all)] - /// Creates a new account ID from a field element. - pub fn account_id_from_felt(felt: Felt) -> AccountId { - unsafe { - let Felt { inner: inner0 } = felt; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/core-types@1.0.0")] - extern "C" { - #[link_name = "account-id-from-felt"] - fn wit_import(_: i64) -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64) -> i64 { - unreachable!() - } - let ret = wit_import(_rt::as_i64(inner0)); - AccountId { - inner: Felt { inner: ret as u64 }, - } - } - } - } - #[allow(dead_code, clippy::all)] - pub mod account { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - pub type Felt = super::super::super::miden::base::core_types::Felt; - pub type CoreAsset = super::super::super::miden::base::core_types::CoreAsset; - pub type AccountId = super::super::super::miden::base::core_types::AccountId; - pub type Nonce = super::super::super::miden::base::core_types::Nonce; - pub type AccountHash = super::super::super::miden::base::core_types::AccountHash; - pub type StorageValue = super::super::super::miden::base::core_types::StorageValue; - pub type StorageRoot = super::super::super::miden::base::core_types::StorageRoot; - pub type AccountCodeRoot = super::super::super::miden::base::core_types::AccountCodeRoot; - pub type VaultCommitment = super::super::super::miden::base::core_types::VaultCommitment; - #[allow(unused_unsafe, clippy::all)] - /// Get the id of the currently executing account - pub fn get_id() -> AccountId { - unsafe { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-id"] - fn wit_import() -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import() -> i64 { - unreachable!() - } - let ret = wit_import(); - super::super::super::miden::base::core_types::AccountId { - inner: super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - }, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Return the account nonce - pub fn get_nonce() -> Nonce { - unsafe { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-nonce"] - fn wit_import() -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import() -> i64 { - unreachable!() - } - let ret = wit_import(); - super::super::super::miden::base::core_types::Nonce { - inner: super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - }, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the initial hash of the currently executing account - pub fn get_initial_hash() -> AccountHash { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-initial-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::AccountHash { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the current hash of the account data stored in memory - pub fn get_current_hash() -> AccountHash { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-current-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::AccountHash { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Increment the account nonce by the specified value. - /// value can be at most 2^32 - 1 otherwise this procedure panics - pub fn incr_nonce(value: Felt) { - unsafe { - let super::super::super::miden::base::core_types::Felt { - inner: inner0, - } = value; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "incr-nonce"] - fn wit_import(_: i64); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64) { - unreachable!() - } - wit_import(_rt::as_i64(inner0)); - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the value of the specified key in the account storage - pub fn get_item(index: Felt) -> StorageValue { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let super::super::super::miden::base::core_types::Felt { - inner: inner0, - } = index; - let ptr1 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-item"] - fn wit_import(_: i64, _: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: *mut u8) { - unreachable!() - } - wit_import(_rt::as_i64(inner0), ptr1); - let l2 = *ptr1.add(0).cast::(); - let l3 = *ptr1.add(8).cast::(); - let l4 = *ptr1.add(16).cast::(); - let l5 = *ptr1.add(24).cast::(); - super::super::super::miden::base::core_types::StorageValue { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l5 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Set the value of the specified key in the account storage - /// Returns the old value of the key and the new storage root - pub fn set_item( - index: Felt, - value: StorageValue, - ) -> (StorageRoot, StorageValue) { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 64]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 64]); - let super::super::super::miden::base::core_types::Felt { - inner: inner0, - } = index; - let super::super::super::miden::base::core_types::StorageValue { - inner: inner1, - } = value; - let (t2_0, t2_1, t2_2, t2_3) = inner1; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t2_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t2_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t2_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner6, - } = t2_3; - let ptr7 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "set-item"] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: *mut u8, - ); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: i64, _: *mut u8) { - unreachable!() - } - wit_import( - _rt::as_i64(inner0), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - _rt::as_i64(inner6), - ptr7, - ); - let l8 = *ptr7.add(0).cast::(); - let l9 = *ptr7.add(8).cast::(); - let l10 = *ptr7.add(16).cast::(); - let l11 = *ptr7.add(24).cast::(); - let l12 = *ptr7.add(32).cast::(); - let l13 = *ptr7.add(40).cast::(); - let l14 = *ptr7.add(48).cast::(); - let l15 = *ptr7.add(56).cast::(); - ( - super::super::super::miden::base::core_types::StorageRoot { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l8 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l9 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l10 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l11 as u64, - }, - ), - }, - super::super::super::miden::base::core_types::StorageValue { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l12 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l13 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l14 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l15 as u64, - }, - ), - }, - ) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Sets the code of the account the transaction is being executed against. - /// This procedure can only be executed on regular accounts with updatable - /// code. Otherwise, this procedure fails. code is the hash of the code - /// to set. - pub fn set_code(code_root: AccountCodeRoot) { - unsafe { - let super::super::super::miden::base::core_types::AccountCodeRoot { - inner: inner0, - } = code_root; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "set-code"] - fn wit_import(_: i64, _: i64, _: i64, _: i64); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ); - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the balance of a fungible asset associated with a account_id. - /// Panics if the asset is not a fungible asset. account_id is the faucet id - /// of the fungible asset of interest. balance is the vault balance of the - /// fungible asset. - pub fn get_balance(account_id: AccountId) -> Felt { - unsafe { - let super::super::super::miden::base::core_types::AccountId { - inner: inner0, - } = account_id; - let super::super::super::miden::base::core_types::Felt { - inner: inner1, - } = inner0; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-balance"] - fn wit_import(_: i64) -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64) -> i64 { - unreachable!() - } - let ret = wit_import(_rt::as_i64(inner1)); - super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns a boolean indicating whether the non-fungible asset is present - /// in the vault. Panics if the asset is a fungible asset. asset is the - /// non-fungible asset of interest. has_asset is a boolean indicating - /// whether the account vault has the asset of interest. - pub fn has_non_fungible_asset(asset: CoreAsset) -> bool { - unsafe { - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "has-non-fungible-asset"] - fn wit_import(_: i64, _: i64, _: i64, _: i64) -> i32; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64) -> i32 { - unreachable!() - } - let ret = wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ); - _rt::bool_lift(ret as u8) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Add the specified asset to the vault. Panics under various conditions. - /// Returns the final asset in the account vault defined as follows: If asset is - /// a non-fungible asset, then returns the same as asset. If asset is a - /// fungible asset, then returns the total fungible asset in the account - /// vault after asset was added to it. - pub fn add_asset(asset: CoreAsset) -> CoreAsset { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - let ptr6 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "add-asset"] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ptr6, - ); - let l7 = *ptr6.add(0).cast::(); - let l8 = *ptr6.add(8).cast::(); - let l9 = *ptr6.add(16).cast::(); - let l10 = *ptr6.add(24).cast::(); - super::super::super::miden::base::core_types::CoreAsset { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l7 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l8 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l9 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l10 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Remove the specified asset from the vault - pub fn remove_asset(asset: CoreAsset) -> CoreAsset { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - let ptr6 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "remove-asset"] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ptr6, - ); - let l7 = *ptr6.add(0).cast::(); - let l8 = *ptr6.add(8).cast::(); - let l9 = *ptr6.add(16).cast::(); - let l10 = *ptr6.add(24).cast::(); - super::super::super::miden::base::core_types::CoreAsset { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l7 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l8 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l9 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l10 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the commitment to the account vault. - pub fn get_vault_commitment() -> VaultCommitment { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-vault-commitment"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::VaultCommitment { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - } - #[allow(dead_code, clippy::all)] - pub mod tx { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - pub type Felt = super::super::super::miden::base::core_types::Felt; - pub type CoreAsset = super::super::super::miden::base::core_types::CoreAsset; - pub type Tag = super::super::super::miden::base::core_types::Tag; - pub type Recipient = super::super::super::miden::base::core_types::Recipient; - pub type BlockHash = super::super::super::miden::base::core_types::BlockHash; - pub type Word = super::super::super::miden::base::core_types::Word; - pub type NoteId = super::super::super::miden::base::core_types::NoteId; - #[allow(unused_unsafe, clippy::all)] - /// Returns the block number of the last known block at the time of transaction execution. - pub fn get_block_number() -> Felt { - unsafe { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-block-number"] - fn wit_import() -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import() -> i64 { - unreachable!() - } - let ret = wit_import(); - super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the block hash of the last known block at the time of transaction execution. - pub fn get_block_hash() -> BlockHash { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-block-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::BlockHash { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the input notes hash. This is computed as a sequential hash of - /// (nullifier, script_root) tuples over all input notes. - pub fn get_input_notes_hash() -> Word { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-input-notes-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the output notes hash. This is computed as a sequential hash of - /// (note_hash, note_metadata) tuples over all output notes. - pub fn get_output_notes_hash() -> Word { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-output-notes-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Creates a new note. - /// asset is the asset to be included in the note. - /// tag is the tag to be included in the note. - /// recipient is the recipient of the note. - /// Returns the id of the created note. - pub fn create_note( - asset: CoreAsset, - tag: Tag, - recipient: Recipient, - ) -> NoteId { - unsafe { - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - let super::super::super::miden::base::core_types::Tag { - inner: inner6, - } = tag; - let super::super::super::miden::base::core_types::Felt { - inner: inner7, - } = inner6; - let super::super::super::miden::base::core_types::Recipient { - inner: inner8, - } = recipient; - let (t9_0, t9_1, t9_2, t9_3) = inner8; - let super::super::super::miden::base::core_types::Felt { - inner: inner10, - } = t9_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner11, - } = t9_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner12, - } = t9_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner13, - } = t9_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "create-note"] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - ) -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - ) -> i64 { - unreachable!() - } - let ret = wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - _rt::as_i64(inner7), - _rt::as_i64(inner10), - _rt::as_i64(inner11), - _rt::as_i64(inner12), - _rt::as_i64(inner13), - ); - super::super::super::miden::base::core_types::NoteId { - inner: super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - }, - } - } - } - } - } -} -#[allow(dead_code)] -pub mod exports { - #[allow(dead_code)] - pub mod miden { - #[allow(dead_code)] - pub mod basic_wallet { - #[allow(dead_code, clippy::all)] - pub mod basic_wallet { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; - use super::super::super::super::_rt; - pub type CoreAsset = super::super::super::super::miden::base::core_types::CoreAsset; - pub type Tag = super::super::super::super::miden::base::core_types::Tag; - pub type Recipient = super::super::super::super::miden::base::core_types::Recipient; - #[doc(hidden)] - #[allow(non_snake_case)] - pub unsafe fn _export_receive_asset_cabi( - arg0: i64, - arg1: i64, - arg2: i64, - arg3: i64, - ) { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - T::receive_asset(super::super::super::super::miden::base::core_types::CoreAsset { - inner: ( - super::super::super::super::miden::base::core_types::Felt { - inner: arg0 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg1 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg2 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg3 as u64, - }, - ), - }); - } - #[doc(hidden)] - #[allow(non_snake_case)] - pub unsafe fn _export_send_asset_cabi( - arg0: i64, - arg1: i64, - arg2: i64, - arg3: i64, - arg4: i64, - arg5: i64, - arg6: i64, - arg7: i64, - arg8: i64, - ) { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - T::send_asset( - super::super::super::super::miden::base::core_types::CoreAsset { - inner: ( - super::super::super::super::miden::base::core_types::Felt { - inner: arg0 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg1 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg2 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg3 as u64, - }, - ), - }, - super::super::super::super::miden::base::core_types::Tag { - inner: super::super::super::super::miden::base::core_types::Felt { - inner: arg4 as u64, - }, - }, - super::super::super::super::miden::base::core_types::Recipient { - inner: ( - super::super::super::super::miden::base::core_types::Felt { - inner: arg5 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg6 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg7 as u64, - }, - super::super::super::super::miden::base::core_types::Felt { - inner: arg8 as u64, - }, - ), - }, - ); - } - pub trait Guest { - fn receive_asset(core_asset: CoreAsset); - fn send_asset(core_asset: CoreAsset, tag: Tag, recipient: Recipient); - } - #[doc(hidden)] - macro_rules! __export_miden_basic_wallet_basic_wallet_1_0_0_cabi { - ($ty:ident with_types_in $($path_to_types:tt)*) => { - const _ : () = { #[export_name = - "miden:basic-wallet/basic-wallet@1.0.0#receive-asset"] unsafe - extern "C" fn export_receive_asset(arg0 : i64, arg1 : i64, arg2 : - i64, arg3 : i64,) { $($path_to_types)*:: - _export_receive_asset_cabi::<$ty > (arg0, arg1, arg2, arg3) } - #[export_name = - "miden:basic-wallet/basic-wallet@1.0.0#send-asset"] unsafe extern - "C" fn export_send_asset(arg0 : i64, arg1 : i64, arg2 : i64, arg3 - : i64, arg4 : i64, arg5 : i64, arg6 : i64, arg7 : i64, arg8 : - i64,) { $($path_to_types)*:: _export_send_asset_cabi::<$ty > - (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) } }; - }; - } - #[doc(hidden)] - pub(crate) use __export_miden_basic_wallet_basic_wallet_1_0_0_cabi; - } - } - } -} -mod _rt { - pub fn as_i64(t: T) -> i64 { - t.as_i64() - } - pub trait AsI64 { - fn as_i64(self) -> i64; - } - impl<'a, T: Copy + AsI64> AsI64 for &'a T { - fn as_i64(self) -> i64 { - (*self).as_i64() - } - } - impl AsI64 for i64 { - #[inline] - fn as_i64(self) -> i64 { - self as i64 - } - } - impl AsI64 for u64 { - #[inline] - fn as_i64(self) -> i64 { - self as i64 - } - } - pub unsafe fn bool_lift(val: u8) -> bool { - if cfg!(debug_assertions) { - match val { - 0 => false, - 1 => true, - _ => panic!("invalid bool discriminant"), - } - } else { - val != 0 - } - } - #[cfg(target_arch = "wasm32")] - pub fn run_ctors_once() { - wit_bindgen_rt::run_ctors_once(); - } -} -/// Generates `#[no_mangle]` functions to export the specified type as the -/// root implementation of all generated traits. -/// -/// For more information see the documentation of `wit_bindgen::generate!`. -/// -/// ```rust -/// # macro_rules! export{ ($($t:tt)*) => (); } -/// # trait Guest {} -/// struct MyType; -/// -/// impl Guest for MyType { -/// // ... -/// } -/// -/// export!(MyType); -/// ``` -#[allow(unused_macros)] -#[doc(hidden)] -macro_rules! __export_basic_wallet_world_impl { - ($ty:ident) => { - self::export!($ty with_types_in self); - }; - ($ty:ident with_types_in $($path_to_types_root:tt)*) => { - $($path_to_types_root)*:: - exports::miden::basic_wallet::basic_wallet::__export_miden_basic_wallet_basic_wallet_1_0_0_cabi!($ty - with_types_in $($path_to_types_root)*:: - exports::miden::basic_wallet::basic_wallet); - }; -} -#[doc(inline)] -pub(crate) use __export_basic_wallet_world_impl as export; -#[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.30.0:basic-wallet-world:encoded world"] -#[doc(hidden)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 2072] = *b"\ -\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x8f\x0f\x01A\x02\x01\ -A\x16\x01B\x1e\x01r\x01\x05innerw\x04\0\x04felt\x03\0\0\x01o\x04\x01\x01\x01\x01\ -\x04\0\x04word\x03\0\x02\x01r\x01\x05inner\x01\x04\0\x0aaccount-id\x03\0\x04\x01\ -r\x01\x05inner\x03\x04\0\x09recipient\x03\0\x06\x01r\x01\x05inner\x01\x04\0\x03t\ -ag\x03\0\x08\x01r\x01\x05inner\x03\x04\0\x0acore-asset\x03\0\x0a\x01r\x01\x05inn\ -er\x01\x04\0\x05nonce\x03\0\x0c\x01r\x01\x05inner\x03\x04\0\x0caccount-hash\x03\0\ -\x0e\x01r\x01\x05inner\x03\x04\0\x0ablock-hash\x03\0\x10\x01r\x01\x05inner\x03\x04\ -\0\x0dstorage-value\x03\0\x12\x01r\x01\x05inner\x03\x04\0\x0cstorage-root\x03\0\x14\ -\x01r\x01\x05inner\x03\x04\0\x11account-code-root\x03\0\x16\x01r\x01\x05inner\x03\ -\x04\0\x10vault-commitment\x03\0\x18\x01r\x01\x05inner\x01\x04\0\x07note-id\x03\0\ -\x1a\x01@\x01\x04felt\x01\0\x05\x04\0\x14account-id-from-felt\x01\x1c\x03\x01\x1b\ -miden:base/core-types@1.0.0\x05\0\x02\x03\0\0\x04felt\x02\x03\0\0\x0acore-asset\x02\ -\x03\0\0\x03tag\x02\x03\0\0\x09recipient\x02\x03\0\0\x0aaccount-id\x02\x03\0\0\x05\ -nonce\x02\x03\0\0\x0caccount-hash\x02\x03\0\0\x0dstorage-value\x02\x03\0\0\x0cst\ -orage-root\x02\x03\0\0\x11account-code-root\x02\x03\0\0\x10vault-commitment\x01B\ -/\x02\x03\x02\x01\x01\x04\0\x04felt\x03\0\0\x02\x03\x02\x01\x02\x04\0\x0acore-as\ -set\x03\0\x02\x02\x03\x02\x01\x03\x04\0\x03tag\x03\0\x04\x02\x03\x02\x01\x04\x04\ -\0\x09recipient\x03\0\x06\x02\x03\x02\x01\x05\x04\0\x0aaccount-id\x03\0\x08\x02\x03\ -\x02\x01\x06\x04\0\x05nonce\x03\0\x0a\x02\x03\x02\x01\x07\x04\0\x0caccount-hash\x03\ -\0\x0c\x02\x03\x02\x01\x08\x04\0\x0dstorage-value\x03\0\x0e\x02\x03\x02\x01\x09\x04\ -\0\x0cstorage-root\x03\0\x10\x02\x03\x02\x01\x0a\x04\0\x11account-code-root\x03\0\ -\x12\x02\x03\x02\x01\x0b\x04\0\x10vault-commitment\x03\0\x14\x01@\0\0\x09\x04\0\x06\ -get-id\x01\x16\x01@\0\0\x0b\x04\0\x09get-nonce\x01\x17\x01@\0\0\x0d\x04\0\x10get\ --initial-hash\x01\x18\x04\0\x10get-current-hash\x01\x18\x01@\x01\x05value\x01\x01\ -\0\x04\0\x0aincr-nonce\x01\x19\x01@\x01\x05index\x01\0\x0f\x04\0\x08get-item\x01\ -\x1a\x01o\x02\x11\x0f\x01@\x02\x05index\x01\x05value\x0f\0\x1b\x04\0\x08set-item\ -\x01\x1c\x01@\x01\x09code-root\x13\x01\0\x04\0\x08set-code\x01\x1d\x01@\x01\x0aa\ -ccount-id\x09\0\x01\x04\0\x0bget-balance\x01\x1e\x01@\x01\x05asset\x03\0\x7f\x04\ -\0\x16has-non-fungible-asset\x01\x1f\x01@\x01\x05asset\x03\0\x03\x04\0\x09add-as\ -set\x01\x20\x04\0\x0cremove-asset\x01\x20\x01@\0\0\x15\x04\0\x14get-vault-commit\ -ment\x01!\x03\x01\x18miden:base/account@1.0.0\x05\x0c\x02\x03\0\0\x0ablock-hash\x02\ -\x03\0\0\x04word\x02\x03\0\0\x07note-id\x01B%\x02\x03\x02\x01\x01\x04\0\x04felt\x03\ -\0\0\x02\x03\x02\x01\x02\x04\0\x0acore-asset\x03\0\x02\x02\x03\x02\x01\x03\x04\0\ -\x03tag\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x09recipient\x03\0\x06\x02\x03\x02\x01\ -\x05\x04\0\x0aaccount-id\x03\0\x08\x02\x03\x02\x01\x06\x04\0\x05nonce\x03\0\x0a\x02\ -\x03\x02\x01\x07\x04\0\x0caccount-hash\x03\0\x0c\x02\x03\x02\x01\x08\x04\0\x0dst\ -orage-value\x03\0\x0e\x02\x03\x02\x01\x09\x04\0\x0cstorage-root\x03\0\x10\x02\x03\ -\x02\x01\x0a\x04\0\x11account-code-root\x03\0\x12\x02\x03\x02\x01\x0b\x04\0\x10v\ -ault-commitment\x03\0\x14\x02\x03\x02\x01\x0d\x04\0\x0ablock-hash\x03\0\x16\x02\x03\ -\x02\x01\x0e\x04\0\x04word\x03\0\x18\x02\x03\x02\x01\x0f\x04\0\x07note-id\x03\0\x1a\ -\x01@\0\0\x01\x04\0\x10get-block-number\x01\x1c\x01@\0\0\x17\x04\0\x0eget-block-\ -hash\x01\x1d\x01@\0\0\x19\x04\0\x14get-input-notes-hash\x01\x1e\x04\0\x15get-out\ -put-notes-hash\x01\x1e\x01@\x03\x05asset\x03\x03tag\x05\x09recipient\x07\0\x1b\x04\ -\0\x0bcreate-note\x01\x1f\x03\x01\x13miden:base/tx@1.0.0\x05\x10\x01B\x0a\x02\x03\ -\x02\x01\x02\x04\0\x0acore-asset\x03\0\0\x02\x03\x02\x01\x03\x04\0\x03tag\x03\0\x02\ -\x02\x03\x02\x01\x04\x04\0\x09recipient\x03\0\x04\x01@\x01\x0acore-asset\x01\x01\ -\0\x04\0\x0dreceive-asset\x01\x06\x01@\x03\x0acore-asset\x01\x03tag\x03\x09recip\ -ient\x05\x01\0\x04\0\x0asend-asset\x01\x07\x04\x01%miden:basic-wallet/basic-wall\ -et@1.0.0\x05\x11\x04\x01+miden:basic-wallet/basic-wallet-world@1.0.0\x04\0\x0b\x18\ -\x01\0\x12basic-wallet-world\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\x0d\ -wit-component\x070.215.0\x10wit-bindgen-rust\x060.30.0"; -#[inline(never)] -#[doc(hidden)] -pub fn __link_custom_section_describing_imports() { - wit_bindgen_rt::maybe_link_cabi_realloc(); -} diff --git a/tests/rust-apps-wasm/wit-sdk/basic-wallet/src/lib.rs b/tests/rust-apps-wasm/wit-sdk/basic-wallet/src/lib.rs deleted file mode 100644 index 0b83b2c95..000000000 --- a/tests/rust-apps-wasm/wit-sdk/basic-wallet/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![no_std] - -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - -#[panic_handler] -fn my_panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -bindings::export!(Component with_types_in bindings); - -#[allow(dead_code)] -mod bindings; - -use bindings::{ - exports::miden::basic_wallet::basic_wallet::{CoreAsset, Guest, Recipient, Tag}, - miden::base::{ - account::{add_asset, remove_asset}, - tx::create_note, - }, -}; - -struct Component; - -impl Guest for Component { - fn receive_asset(asset: CoreAsset) { - add_asset(asset); - } - - fn send_asset(asset: CoreAsset, tag: Tag, recipient: Recipient) { - let asset = remove_asset(asset); - create_note(asset, tag, recipient); - } -} diff --git a/tests/rust-apps-wasm/wit-sdk/basic-wallet/wit/basic-wallet.wit b/tests/rust-apps-wasm/wit-sdk/basic-wallet/wit/basic-wallet.wit deleted file mode 100644 index f30375a67..000000000 --- a/tests/rust-apps-wasm/wit-sdk/basic-wallet/wit/basic-wallet.wit +++ /dev/null @@ -1,19 +0,0 @@ -package miden:basic-wallet@1.0.0; - -use miden:base/core-types@1.0.0; -use miden:base/types@1.0.0; -use miden:base/tx@1.0.0; -use miden:base/account@1.0.0; - -interface basic-wallet { - use core-types.{core-asset, tag, recipient}; - - receive-asset: func(core-asset: core-asset); - send-asset: func(core-asset: core-asset, tag: tag, recipient: recipient); -} - -world basic-wallet-world { - import account; - import tx; - export basic-wallet; -} diff --git a/tests/rust-apps-wasm/wit-sdk/p2id-note/Cargo.lock b/tests/rust-apps-wasm/wit-sdk/p2id-note/Cargo.lock deleted file mode 100644 index 79bc8a1f1..000000000 --- a/tests/rust-apps-wasm/wit-sdk/p2id-note/Cargo.lock +++ /dev/null @@ -1,69 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "basic-wallet-p2id-note" -version = "0.0.0" -dependencies = [ - "wee_alloc", - "wit-bindgen-rt", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "wit-bindgen-rt" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a37bd9274cb2d4754b915d624447ec0dce9105d174361841c0826efc79ceb9" diff --git a/tests/rust-apps-wasm/wit-sdk/p2id-note/Cargo.toml b/tests/rust-apps-wasm/wit-sdk/p2id-note/Cargo.toml deleted file mode 100644 index f891ee738..000000000 --- a/tests/rust-apps-wasm/wit-sdk/p2id-note/Cargo.toml +++ /dev/null @@ -1,35 +0,0 @@ -[package] -name = "basic-wallet-p2id-note" -version = "0.0.0" -rust-version = "1.71" -authors = ["Miden contributors"] -description = "p2id-note for basic wallet" -repository = "https://github.com/0xPolygonMiden/miden-ir" -homepage = "https://github.com/0xPolygonMiden/miden-ir" -documentation = "https://github.com/0xPolygonMiden/miden-ir" -license = "MIT" -edition = "2021" -publish = false - -[lib] -crate-type = ["cdylib"] - -[dependencies] -wit-bindgen-rt = "0.28" -wee_alloc = { version = "0.4.5", default-features = false } - -[package.metadata.component] -package = "miden:basic-wallet-p2id-note" - -[package.metadata.component.dependencies] - -[package.metadata.component.target.dependencies] -"miden:base" = { path = "../sdk/wit" } -"miden:basic-wallet" = { path = "../basic-wallet/wit" } - -[package.metadata.component.bindings] -derives = ["PartialEq"] - -[profile.release] -panic = "abort" -debug = true diff --git a/tests/rust-apps-wasm/wit-sdk/p2id-note/src/bindings.rs b/tests/rust-apps-wasm/wit-sdk/p2id-note/src/bindings.rs deleted file mode 100644 index 45bff2b7d..000000000 --- a/tests/rust-apps-wasm/wit-sdk/p2id-note/src/bindings.rs +++ /dev/null @@ -1,1516 +0,0 @@ -#[allow(dead_code)] -pub mod miden { - #[allow(dead_code)] - pub mod base { - #[allow(dead_code, clippy::all)] - pub mod core_types { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - /// Represents base field element in the field using Montgomery representation. - /// Internal values represent x * R mod M where R = 2^64 mod M and x in [0, M). - /// The backing type is `f64` but the internal values are always integer in the range [0, M). - /// Field modulus M = 2^64 - 2^32 + 1 - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct Felt { - /// We plan to use f64 as the backing type for the field element. It has the size that we need and - /// we don't plan to support floating point arithmetic in programs for Miden VM. - /// - /// For now its u64 - pub inner: u64, - } - impl ::core::fmt::Debug for Felt { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Felt").field("inner", &self.inner).finish() - } - } - /// A group of four field elements in the Miden base field. - pub type Word = (Felt, Felt, Felt, Felt); - /// Unique identifier of an account. - /// - /// Account ID consists of 1 field element (~64 bits). This field element uniquely identifies a - /// single account and also specifies the type of the underlying account. Specifically: - /// - The two most significant bits of the ID specify the type of the account: - /// - 00 - regular account with updatable code. - /// - 01 - regular account with immutable code. - /// - 10 - fungible asset faucet with immutable code. - /// - 11 - non-fungible asset faucet with immutable code. - /// - The third most significant bit of the ID specifies whether the account data is stored on-chain: - /// - 0 - full account data is stored on-chain. - /// - 1 - only the account hash is stored on-chain which serves as a commitment to the account state. - /// As such the three most significant bits fully describes the type of the account. - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct AccountId { - pub inner: Felt, - } - impl ::core::fmt::Debug for AccountId { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("AccountId").field("inner", &self.inner).finish() - } - } - /// Recipient of the note, i.e., hash(hash(hash(serial_num, [0; 4]), note_script_hash), input_hash) - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct Recipient { - pub inner: Word, - } - impl ::core::fmt::Debug for Recipient { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Recipient").field("inner", &self.inner).finish() - } - } - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct Tag { - pub inner: Felt, - } - impl ::core::fmt::Debug for Tag { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Tag").field("inner", &self.inner).finish() - } - } - /// A fungible or a non-fungible asset. - /// - /// All assets are encoded using a single word (4 elements) such that it is easy to determine the - /// type of an asset both inside and outside Miden VM. Specifically: - /// Element 1 will be: - /// - ZERO for a fungible asset - /// - non-ZERO for a non-fungible asset - /// The most significant bit will be: - /// - ONE for a fungible asset - /// - ZERO for a non-fungible asset - /// - /// The above properties guarantee that there can never be a collision between a fungible and a - /// non-fungible asset. - /// - /// The methodology for constructing fungible and non-fungible assets is described below. - /// - /// # Fungible assets - /// The most significant element of a fungible asset is set to the ID of the faucet which issued - /// the asset. This guarantees the properties described above (the first bit is ONE). - /// - /// The least significant element is set to the amount of the asset. This amount cannot be greater - /// than 2^63 - 1 and thus requires 63-bits to store. - /// - /// Elements 1 and 2 are set to ZERO. - /// - /// It is impossible to find a collision between two fungible assets issued by different faucets as - /// the faucet_id is included in the description of the asset and this is guaranteed to be different - /// for each faucet as per the faucet creation logic. - /// - /// # Non-fungible assets - /// The 4 elements of non-fungible assets are computed as follows: - /// - First the asset data is hashed. This compresses an asset of an arbitrary length to 4 field - /// elements: [d0, d1, d2, d3]. - /// - d1 is then replaced with the faucet_id which issues the asset: [d0, faucet_id, d2, d3]. - /// - Lastly, the most significant bit of d3 is set to ZERO. - /// - /// It is impossible to find a collision between two non-fungible assets issued by different faucets - /// as the faucet_id is included in the description of the non-fungible asset and this is guaranteed - /// to be different as per the faucet creation logic. Collision resistance for non-fungible assets - /// issued by the same faucet is ~2^95. - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct CoreAsset { - pub inner: Word, - } - impl ::core::fmt::Debug for CoreAsset { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("CoreAsset").field("inner", &self.inner).finish() - } - } - /// Account nonce - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct Nonce { - pub inner: Felt, - } - impl ::core::fmt::Debug for Nonce { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Nonce").field("inner", &self.inner).finish() - } - } - /// Account hash - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct AccountHash { - pub inner: Word, - } - impl ::core::fmt::Debug for AccountHash { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("AccountHash").field("inner", &self.inner).finish() - } - } - /// Block hash - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct BlockHash { - pub inner: Word, - } - impl ::core::fmt::Debug for BlockHash { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("BlockHash").field("inner", &self.inner).finish() - } - } - /// Storage value - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct StorageValue { - pub inner: Word, - } - impl ::core::fmt::Debug for StorageValue { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("StorageValue").field("inner", &self.inner).finish() - } - } - /// Account storage root - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct StorageRoot { - pub inner: Word, - } - impl ::core::fmt::Debug for StorageRoot { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("StorageRoot").field("inner", &self.inner).finish() - } - } - /// Account code root - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct AccountCodeRoot { - pub inner: Word, - } - impl ::core::fmt::Debug for AccountCodeRoot { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("AccountCodeRoot") - .field("inner", &self.inner) - .finish() - } - } - /// Commitment to the account vault - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct VaultCommitment { - pub inner: Word, - } - impl ::core::fmt::Debug for VaultCommitment { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("VaultCommitment") - .field("inner", &self.inner) - .finish() - } - } - /// An id of the created note - #[repr(C)] - #[derive(Clone, Copy, PartialEq)] - pub struct NoteId { - pub inner: Felt, - } - impl ::core::fmt::Debug for NoteId { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("NoteId").field("inner", &self.inner).finish() - } - } - #[allow(unused_unsafe, clippy::all)] - /// Creates a new account ID from a field element. - pub fn account_id_from_felt(felt: Felt) -> AccountId { - unsafe { - let Felt { inner: inner0 } = felt; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/core-types@1.0.0")] - extern "C" { - #[link_name = "account-id-from-felt"] - fn wit_import(_: i64) -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64) -> i64 { - unreachable!() - } - let ret = wit_import(_rt::as_i64(inner0)); - AccountId { - inner: Felt { inner: ret as u64 }, - } - } - } - } - #[allow(dead_code, clippy::all)] - pub mod tx { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - pub type Felt = super::super::super::miden::base::core_types::Felt; - pub type CoreAsset = super::super::super::miden::base::core_types::CoreAsset; - pub type Tag = super::super::super::miden::base::core_types::Tag; - pub type Recipient = super::super::super::miden::base::core_types::Recipient; - pub type BlockHash = super::super::super::miden::base::core_types::BlockHash; - pub type Word = super::super::super::miden::base::core_types::Word; - pub type NoteId = super::super::super::miden::base::core_types::NoteId; - #[allow(unused_unsafe, clippy::all)] - /// Returns the block number of the last known block at the time of transaction execution. - pub fn get_block_number() -> Felt { - unsafe { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-block-number"] - fn wit_import() -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import() -> i64 { - unreachable!() - } - let ret = wit_import(); - super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the block hash of the last known block at the time of transaction execution. - pub fn get_block_hash() -> BlockHash { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-block-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::BlockHash { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the input notes hash. This is computed as a sequential hash of - /// (nullifier, script_root) tuples over all input notes. - pub fn get_input_notes_hash() -> Word { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-input-notes-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the output notes hash. This is computed as a sequential hash of - /// (note_hash, note_metadata) tuples over all output notes. - pub fn get_output_notes_hash() -> Word { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "get-output-notes-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Creates a new note. - /// asset is the asset to be included in the note. - /// tag is the tag to be included in the note. - /// recipient is the recipient of the note. - /// Returns the id of the created note. - pub fn create_note( - asset: CoreAsset, - tag: Tag, - recipient: Recipient, - ) -> NoteId { - unsafe { - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - let super::super::super::miden::base::core_types::Tag { - inner: inner6, - } = tag; - let super::super::super::miden::base::core_types::Felt { - inner: inner7, - } = inner6; - let super::super::super::miden::base::core_types::Recipient { - inner: inner8, - } = recipient; - let (t9_0, t9_1, t9_2, t9_3) = inner8; - let super::super::super::miden::base::core_types::Felt { - inner: inner10, - } = t9_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner11, - } = t9_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner12, - } = t9_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner13, - } = t9_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/tx@1.0.0")] - extern "C" { - #[link_name = "create-note"] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - ) -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - ) -> i64 { - unreachable!() - } - let ret = wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - _rt::as_i64(inner7), - _rt::as_i64(inner10), - _rt::as_i64(inner11), - _rt::as_i64(inner12), - _rt::as_i64(inner13), - ); - super::super::super::miden::base::core_types::NoteId { - inner: super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - }, - } - } - } - } - #[allow(dead_code, clippy::all)] - pub mod account { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - pub type Felt = super::super::super::miden::base::core_types::Felt; - pub type CoreAsset = super::super::super::miden::base::core_types::CoreAsset; - pub type AccountId = super::super::super::miden::base::core_types::AccountId; - pub type Nonce = super::super::super::miden::base::core_types::Nonce; - pub type AccountHash = super::super::super::miden::base::core_types::AccountHash; - pub type StorageValue = super::super::super::miden::base::core_types::StorageValue; - pub type StorageRoot = super::super::super::miden::base::core_types::StorageRoot; - pub type AccountCodeRoot = super::super::super::miden::base::core_types::AccountCodeRoot; - pub type VaultCommitment = super::super::super::miden::base::core_types::VaultCommitment; - #[allow(unused_unsafe, clippy::all)] - /// Get the id of the currently executing account - pub fn get_id() -> AccountId { - unsafe { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-id"] - fn wit_import() -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import() -> i64 { - unreachable!() - } - let ret = wit_import(); - super::super::super::miden::base::core_types::AccountId { - inner: super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - }, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Return the account nonce - pub fn get_nonce() -> Nonce { - unsafe { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-nonce"] - fn wit_import() -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import() -> i64 { - unreachable!() - } - let ret = wit_import(); - super::super::super::miden::base::core_types::Nonce { - inner: super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - }, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the initial hash of the currently executing account - pub fn get_initial_hash() -> AccountHash { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-initial-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::AccountHash { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the current hash of the account data stored in memory - pub fn get_current_hash() -> AccountHash { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-current-hash"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::AccountHash { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Increment the account nonce by the specified value. - /// value can be at most 2^32 - 1 otherwise this procedure panics - pub fn incr_nonce(value: Felt) { - unsafe { - let super::super::super::miden::base::core_types::Felt { - inner: inner0, - } = value; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "incr-nonce"] - fn wit_import(_: i64); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64) { - unreachable!() - } - wit_import(_rt::as_i64(inner0)); - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the value of the specified key in the account storage - pub fn get_item(index: Felt) -> StorageValue { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let super::super::super::miden::base::core_types::Felt { - inner: inner0, - } = index; - let ptr1 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-item"] - fn wit_import(_: i64, _: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: *mut u8) { - unreachable!() - } - wit_import(_rt::as_i64(inner0), ptr1); - let l2 = *ptr1.add(0).cast::(); - let l3 = *ptr1.add(8).cast::(); - let l4 = *ptr1.add(16).cast::(); - let l5 = *ptr1.add(24).cast::(); - super::super::super::miden::base::core_types::StorageValue { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l5 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Set the value of the specified key in the account storage - /// Returns the old value of the key and the new storage root - pub fn set_item( - index: Felt, - value: StorageValue, - ) -> (StorageRoot, StorageValue) { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 64]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 64]); - let super::super::super::miden::base::core_types::Felt { - inner: inner0, - } = index; - let super::super::super::miden::base::core_types::StorageValue { - inner: inner1, - } = value; - let (t2_0, t2_1, t2_2, t2_3) = inner1; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t2_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t2_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t2_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner6, - } = t2_3; - let ptr7 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "set-item"] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: *mut u8, - ); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: i64, _: *mut u8) { - unreachable!() - } - wit_import( - _rt::as_i64(inner0), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - _rt::as_i64(inner6), - ptr7, - ); - let l8 = *ptr7.add(0).cast::(); - let l9 = *ptr7.add(8).cast::(); - let l10 = *ptr7.add(16).cast::(); - let l11 = *ptr7.add(24).cast::(); - let l12 = *ptr7.add(32).cast::(); - let l13 = *ptr7.add(40).cast::(); - let l14 = *ptr7.add(48).cast::(); - let l15 = *ptr7.add(56).cast::(); - ( - super::super::super::miden::base::core_types::StorageRoot { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l8 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l9 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l10 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l11 as u64, - }, - ), - }, - super::super::super::miden::base::core_types::StorageValue { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l12 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l13 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l14 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l15 as u64, - }, - ), - }, - ) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Sets the code of the account the transaction is being executed against. - /// This procedure can only be executed on regular accounts with updatable - /// code. Otherwise, this procedure fails. code is the hash of the code - /// to set. - pub fn set_code(code_root: AccountCodeRoot) { - unsafe { - let super::super::super::miden::base::core_types::AccountCodeRoot { - inner: inner0, - } = code_root; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "set-code"] - fn wit_import(_: i64, _: i64, _: i64, _: i64); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ); - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the balance of a fungible asset associated with a account_id. - /// Panics if the asset is not a fungible asset. account_id is the faucet id - /// of the fungible asset of interest. balance is the vault balance of the - /// fungible asset. - pub fn get_balance(account_id: AccountId) -> Felt { - unsafe { - let super::super::super::miden::base::core_types::AccountId { - inner: inner0, - } = account_id; - let super::super::super::miden::base::core_types::Felt { - inner: inner1, - } = inner0; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-balance"] - fn wit_import(_: i64) -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64) -> i64 { - unreachable!() - } - let ret = wit_import(_rt::as_i64(inner1)); - super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns a boolean indicating whether the non-fungible asset is present - /// in the vault. Panics if the asset is a fungible asset. asset is the - /// non-fungible asset of interest. has_asset is a boolean indicating - /// whether the account vault has the asset of interest. - pub fn has_non_fungible_asset(asset: CoreAsset) -> bool { - unsafe { - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "has-non-fungible-asset"] - fn wit_import(_: i64, _: i64, _: i64, _: i64) -> i32; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64) -> i32 { - unreachable!() - } - let ret = wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ); - _rt::bool_lift(ret as u8) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Add the specified asset to the vault. Panics under various conditions. - /// Returns the final asset in the account vault defined as follows: If asset is - /// a non-fungible asset, then returns the same as asset. If asset is a - /// fungible asset, then returns the total fungible asset in the account - /// vault after asset was added to it. - pub fn add_asset(asset: CoreAsset) -> CoreAsset { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - let ptr6 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "add-asset"] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ptr6, - ); - let l7 = *ptr6.add(0).cast::(); - let l8 = *ptr6.add(8).cast::(); - let l9 = *ptr6.add(16).cast::(); - let l10 = *ptr6.add(24).cast::(); - super::super::super::miden::base::core_types::CoreAsset { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l7 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l8 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l9 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l10 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Remove the specified asset from the vault - pub fn remove_asset(asset: CoreAsset) -> CoreAsset { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - let ptr6 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "remove-asset"] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64, _: *mut u8) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ptr6, - ); - let l7 = *ptr6.add(0).cast::(); - let l8 = *ptr6.add(8).cast::(); - let l9 = *ptr6.add(16).cast::(); - let l10 = *ptr6.add(24).cast::(); - super::super::super::miden::base::core_types::CoreAsset { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l7 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l8 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l9 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l10 as u64, - }, - ), - } - } - } - #[allow(unused_unsafe, clippy::all)] - /// Returns the commitment to the account vault. - pub fn get_vault_commitment() -> VaultCommitment { - unsafe { - #[repr(align(8))] - struct RetArea([::core::mem::MaybeUninit; 32]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 32]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/account@1.0.0")] - extern "C" { - #[link_name = "get-vault-commitment"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::(); - let l2 = *ptr0.add(8).cast::(); - let l3 = *ptr0.add(16).cast::(); - let l4 = *ptr0.add(24).cast::(); - super::super::super::miden::base::core_types::VaultCommitment { - inner: ( - super::super::super::miden::base::core_types::Felt { - inner: l1 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l2 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l3 as u64, - }, - super::super::super::miden::base::core_types::Felt { - inner: l4 as u64, - }, - ), - } - } - } - } - #[allow(dead_code, clippy::all)] - pub mod note { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - pub type Felt = super::super::super::miden::base::core_types::Felt; - pub type CoreAsset = super::super::super::miden::base::core_types::CoreAsset; - pub type AccountId = super::super::super::miden::base::core_types::AccountId; - #[allow(unused_unsafe, clippy::all)] - /// Get the inputs of the currently executed note - pub fn get_inputs() -> _rt::Vec { - unsafe { - #[repr(align(4))] - struct RetArea([::core::mem::MaybeUninit; 8]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 8]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/note@1.0.0")] - extern "C" { - #[link_name = "get-inputs"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::<*mut u8>(); - let l2 = *ptr0.add(4).cast::(); - let len3 = l2; - _rt::Vec::from_raw_parts(l1.cast(), len3, len3) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the assets of the currently executing note - pub fn get_assets() -> _rt::Vec { - unsafe { - #[repr(align(4))] - struct RetArea([::core::mem::MaybeUninit; 8]); - let mut ret_area = RetArea([::core::mem::MaybeUninit::uninit(); 8]); - let ptr0 = ret_area.0.as_mut_ptr().cast::(); - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/note@1.0.0")] - extern "C" { - #[link_name = "get-assets"] - fn wit_import(_: *mut u8); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: *mut u8) { - unreachable!() - } - wit_import(ptr0); - let l1 = *ptr0.add(0).cast::<*mut u8>(); - let l2 = *ptr0.add(4).cast::(); - let len3 = l2; - _rt::Vec::from_raw_parts(l1.cast(), len3, len3) - } - } - #[allow(unused_unsafe, clippy::all)] - /// Get the sender of the currently executing note - pub fn get_sender() -> AccountId { - unsafe { - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:base/note@1.0.0")] - extern "C" { - #[link_name = "get-sender"] - fn wit_import() -> i64; - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import() -> i64 { - unreachable!() - } - let ret = wit_import(); - super::super::super::miden::base::core_types::AccountId { - inner: super::super::super::miden::base::core_types::Felt { - inner: ret as u64, - }, - } - } - } - } - } - #[allow(dead_code)] - pub mod basic_wallet { - #[allow(dead_code, clippy::all)] - pub mod basic_wallet { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::__link_custom_section_describing_imports; - use super::super::super::_rt; - pub type CoreAsset = super::super::super::miden::base::core_types::CoreAsset; - pub type Tag = super::super::super::miden::base::core_types::Tag; - pub type Recipient = super::super::super::miden::base::core_types::Recipient; - #[allow(unused_unsafe, clippy::all)] - pub fn receive_asset(core_asset: CoreAsset) { - unsafe { - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = core_asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:basic-wallet/basic-wallet@1.0.0")] - extern "C" { - #[link_name = "receive-asset"] - fn wit_import(_: i64, _: i64, _: i64, _: i64); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import(_: i64, _: i64, _: i64, _: i64) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - ); - } - } - #[allow(unused_unsafe, clippy::all)] - pub fn send_asset(core_asset: CoreAsset, tag: Tag, recipient: Recipient) { - unsafe { - let super::super::super::miden::base::core_types::CoreAsset { - inner: inner0, - } = core_asset; - let (t1_0, t1_1, t1_2, t1_3) = inner0; - let super::super::super::miden::base::core_types::Felt { - inner: inner2, - } = t1_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner3, - } = t1_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner4, - } = t1_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner5, - } = t1_3; - let super::super::super::miden::base::core_types::Tag { - inner: inner6, - } = tag; - let super::super::super::miden::base::core_types::Felt { - inner: inner7, - } = inner6; - let super::super::super::miden::base::core_types::Recipient { - inner: inner8, - } = recipient; - let (t9_0, t9_1, t9_2, t9_3) = inner8; - let super::super::super::miden::base::core_types::Felt { - inner: inner10, - } = t9_0; - let super::super::super::miden::base::core_types::Felt { - inner: inner11, - } = t9_1; - let super::super::super::miden::base::core_types::Felt { - inner: inner12, - } = t9_2; - let super::super::super::miden::base::core_types::Felt { - inner: inner13, - } = t9_3; - #[cfg(target_arch = "wasm32")] - #[link(wasm_import_module = "miden:basic-wallet/basic-wallet@1.0.0")] - extern "C" { - #[link_name = "send-asset"] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - ); - } - #[cfg(not(target_arch = "wasm32"))] - fn wit_import( - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - _: i64, - ) { - unreachable!() - } - wit_import( - _rt::as_i64(inner2), - _rt::as_i64(inner3), - _rt::as_i64(inner4), - _rt::as_i64(inner5), - _rt::as_i64(inner7), - _rt::as_i64(inner10), - _rt::as_i64(inner11), - _rt::as_i64(inner12), - _rt::as_i64(inner13), - ); - } - } - } - } -} -#[allow(dead_code)] -pub mod exports { - #[allow(dead_code)] - pub mod miden { - #[allow(dead_code)] - pub mod base { - #[allow(dead_code, clippy::all)] - pub mod note_script { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; - use super::super::super::super::_rt; - #[doc(hidden)] - #[allow(non_snake_case)] - pub unsafe fn _export_note_script_cabi() { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - T::note_script(); - } - pub trait Guest { - fn note_script(); - } - #[doc(hidden)] - macro_rules! __export_miden_base_note_script_1_0_0_cabi { - ($ty:ident with_types_in $($path_to_types:tt)*) => { - const _ : () = { #[export_name = - "miden:base/note-script@1.0.0#note-script"] unsafe extern "C" fn - export_note_script() { $($path_to_types)*:: - _export_note_script_cabi::<$ty > () } }; - }; - } - #[doc(hidden)] - pub(crate) use __export_miden_base_note_script_1_0_0_cabi; - } - } - } -} -mod _rt { - pub fn as_i64(t: T) -> i64 { - t.as_i64() - } - pub trait AsI64 { - fn as_i64(self) -> i64; - } - impl<'a, T: Copy + AsI64> AsI64 for &'a T { - fn as_i64(self) -> i64 { - (*self).as_i64() - } - } - impl AsI64 for i64 { - #[inline] - fn as_i64(self) -> i64 { - self as i64 - } - } - impl AsI64 for u64 { - #[inline] - fn as_i64(self) -> i64 { - self as i64 - } - } - pub unsafe fn bool_lift(val: u8) -> bool { - if cfg!(debug_assertions) { - match val { - 0 => false, - 1 => true, - _ => panic!("invalid bool discriminant"), - } - } else { - val != 0 - } - } - pub use alloc_crate::vec::Vec; - #[cfg(target_arch = "wasm32")] - pub fn run_ctors_once() { - wit_bindgen_rt::run_ctors_once(); - } - extern crate alloc as alloc_crate; -} -/// Generates `#[no_mangle]` functions to export the specified type as the -/// root implementation of all generated traits. -/// -/// For more information see the documentation of `wit_bindgen::generate!`. -/// -/// ```rust -/// # macro_rules! export{ ($($t:tt)*) => (); } -/// # trait Guest {} -/// struct MyType; -/// -/// impl Guest for MyType { -/// // ... -/// } -/// -/// export!(MyType); -/// ``` -#[allow(unused_macros)] -#[doc(hidden)] -macro_rules! __export_notes_world_impl { - ($ty:ident) => { - self::export!($ty with_types_in self); - }; - ($ty:ident with_types_in $($path_to_types_root:tt)*) => { - $($path_to_types_root)*:: - exports::miden::base::note_script::__export_miden_base_note_script_1_0_0_cabi!($ty - with_types_in $($path_to_types_root)*:: exports::miden::base::note_script); - }; -} -#[doc(inline)] -pub(crate) use __export_notes_world_impl as export; -#[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.30.0:notes-world:encoded world"] -#[doc(hidden)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 2434] = *b"\ -\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x80\x12\x01A\x02\x01\ -A\x1a\x01B\x1e\x01r\x01\x05innerw\x04\0\x04felt\x03\0\0\x01o\x04\x01\x01\x01\x01\ -\x04\0\x04word\x03\0\x02\x01r\x01\x05inner\x01\x04\0\x0aaccount-id\x03\0\x04\x01\ -r\x01\x05inner\x03\x04\0\x09recipient\x03\0\x06\x01r\x01\x05inner\x01\x04\0\x03t\ -ag\x03\0\x08\x01r\x01\x05inner\x03\x04\0\x0acore-asset\x03\0\x0a\x01r\x01\x05inn\ -er\x01\x04\0\x05nonce\x03\0\x0c\x01r\x01\x05inner\x03\x04\0\x0caccount-hash\x03\0\ -\x0e\x01r\x01\x05inner\x03\x04\0\x0ablock-hash\x03\0\x10\x01r\x01\x05inner\x03\x04\ -\0\x0dstorage-value\x03\0\x12\x01r\x01\x05inner\x03\x04\0\x0cstorage-root\x03\0\x14\ -\x01r\x01\x05inner\x03\x04\0\x11account-code-root\x03\0\x16\x01r\x01\x05inner\x03\ -\x04\0\x10vault-commitment\x03\0\x18\x01r\x01\x05inner\x01\x04\0\x07note-id\x03\0\ -\x1a\x01@\x01\x04felt\x01\0\x05\x04\0\x14account-id-from-felt\x01\x1c\x03\x01\x1b\ -miden:base/core-types@1.0.0\x05\0\x02\x03\0\0\x04felt\x02\x03\0\0\x0acore-asset\x02\ -\x03\0\0\x03tag\x02\x03\0\0\x09recipient\x02\x03\0\0\x0aaccount-id\x02\x03\0\0\x05\ -nonce\x02\x03\0\0\x0caccount-hash\x02\x03\0\0\x0dstorage-value\x02\x03\0\0\x0cst\ -orage-root\x02\x03\0\0\x11account-code-root\x02\x03\0\0\x10vault-commitment\x02\x03\ -\0\0\x0ablock-hash\x02\x03\0\0\x04word\x02\x03\0\0\x07note-id\x01B%\x02\x03\x02\x01\ -\x01\x04\0\x04felt\x03\0\0\x02\x03\x02\x01\x02\x04\0\x0acore-asset\x03\0\x02\x02\ -\x03\x02\x01\x03\x04\0\x03tag\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x09recipient\x03\ -\0\x06\x02\x03\x02\x01\x05\x04\0\x0aaccount-id\x03\0\x08\x02\x03\x02\x01\x06\x04\ -\0\x05nonce\x03\0\x0a\x02\x03\x02\x01\x07\x04\0\x0caccount-hash\x03\0\x0c\x02\x03\ -\x02\x01\x08\x04\0\x0dstorage-value\x03\0\x0e\x02\x03\x02\x01\x09\x04\0\x0cstora\ -ge-root\x03\0\x10\x02\x03\x02\x01\x0a\x04\0\x11account-code-root\x03\0\x12\x02\x03\ -\x02\x01\x0b\x04\0\x10vault-commitment\x03\0\x14\x02\x03\x02\x01\x0c\x04\0\x0abl\ -ock-hash\x03\0\x16\x02\x03\x02\x01\x0d\x04\0\x04word\x03\0\x18\x02\x03\x02\x01\x0e\ -\x04\0\x07note-id\x03\0\x1a\x01@\0\0\x01\x04\0\x10get-block-number\x01\x1c\x01@\0\ -\0\x17\x04\0\x0eget-block-hash\x01\x1d\x01@\0\0\x19\x04\0\x14get-input-notes-has\ -h\x01\x1e\x04\0\x15get-output-notes-hash\x01\x1e\x01@\x03\x05asset\x03\x03tag\x05\ -\x09recipient\x07\0\x1b\x04\0\x0bcreate-note\x01\x1f\x03\x01\x13miden:base/tx@1.\ -0.0\x05\x0f\x01B/\x02\x03\x02\x01\x01\x04\0\x04felt\x03\0\0\x02\x03\x02\x01\x02\x04\ -\0\x0acore-asset\x03\0\x02\x02\x03\x02\x01\x03\x04\0\x03tag\x03\0\x04\x02\x03\x02\ -\x01\x04\x04\0\x09recipient\x03\0\x06\x02\x03\x02\x01\x05\x04\0\x0aaccount-id\x03\ -\0\x08\x02\x03\x02\x01\x06\x04\0\x05nonce\x03\0\x0a\x02\x03\x02\x01\x07\x04\0\x0c\ -account-hash\x03\0\x0c\x02\x03\x02\x01\x08\x04\0\x0dstorage-value\x03\0\x0e\x02\x03\ -\x02\x01\x09\x04\0\x0cstorage-root\x03\0\x10\x02\x03\x02\x01\x0a\x04\0\x11accoun\ -t-code-root\x03\0\x12\x02\x03\x02\x01\x0b\x04\0\x10vault-commitment\x03\0\x14\x01\ -@\0\0\x09\x04\0\x06get-id\x01\x16\x01@\0\0\x0b\x04\0\x09get-nonce\x01\x17\x01@\0\ -\0\x0d\x04\0\x10get-initial-hash\x01\x18\x04\0\x10get-current-hash\x01\x18\x01@\x01\ -\x05value\x01\x01\0\x04\0\x0aincr-nonce\x01\x19\x01@\x01\x05index\x01\0\x0f\x04\0\ -\x08get-item\x01\x1a\x01o\x02\x11\x0f\x01@\x02\x05index\x01\x05value\x0f\0\x1b\x04\ -\0\x08set-item\x01\x1c\x01@\x01\x09code-root\x13\x01\0\x04\0\x08set-code\x01\x1d\ -\x01@\x01\x0aaccount-id\x09\0\x01\x04\0\x0bget-balance\x01\x1e\x01@\x01\x05asset\ -\x03\0\x7f\x04\0\x16has-non-fungible-asset\x01\x1f\x01@\x01\x05asset\x03\0\x03\x04\ -\0\x09add-asset\x01\x20\x04\0\x0cremove-asset\x01\x20\x01@\0\0\x15\x04\0\x14get-\ -vault-commitment\x01!\x03\x01\x18miden:base/account@1.0.0\x05\x10\x01B\x1e\x02\x03\ -\x02\x01\x01\x04\0\x04felt\x03\0\0\x02\x03\x02\x01\x02\x04\0\x0acore-asset\x03\0\ -\x02\x02\x03\x02\x01\x03\x04\0\x03tag\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x09rec\ -ipient\x03\0\x06\x02\x03\x02\x01\x05\x04\0\x0aaccount-id\x03\0\x08\x02\x03\x02\x01\ -\x06\x04\0\x05nonce\x03\0\x0a\x02\x03\x02\x01\x07\x04\0\x0caccount-hash\x03\0\x0c\ -\x02\x03\x02\x01\x08\x04\0\x0dstorage-value\x03\0\x0e\x02\x03\x02\x01\x09\x04\0\x0c\ -storage-root\x03\0\x10\x02\x03\x02\x01\x0a\x04\0\x11account-code-root\x03\0\x12\x02\ -\x03\x02\x01\x0b\x04\0\x10vault-commitment\x03\0\x14\x01p\x01\x01@\0\0\x16\x04\0\ -\x0aget-inputs\x01\x17\x01p\x03\x01@\0\0\x18\x04\0\x0aget-assets\x01\x19\x01@\0\0\ -\x09\x04\0\x0aget-sender\x01\x1a\x03\x01\x15miden:base/note@1.0.0\x05\x11\x01B\x0a\ -\x02\x03\x02\x01\x02\x04\0\x0acore-asset\x03\0\0\x02\x03\x02\x01\x03\x04\0\x03ta\ -g\x03\0\x02\x02\x03\x02\x01\x04\x04\0\x09recipient\x03\0\x04\x01@\x01\x0acore-as\ -set\x01\x01\0\x04\0\x0dreceive-asset\x01\x06\x01@\x03\x0acore-asset\x01\x03tag\x03\ -\x09recipient\x05\x01\0\x04\0\x0asend-asset\x01\x07\x03\x01%miden:basic-wallet/b\ -asic-wallet@1.0.0\x05\x12\x01B\x02\x01@\0\x01\0\x04\0\x0bnote-script\x01\0\x04\x01\ -\x1cmiden:base/note-script@1.0.0\x05\x13\x04\x01\x1cmiden:p2id/notes-world@1.0.0\ -\x04\0\x0b\x11\x01\0\x0bnotes-world\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\ -\x0dwit-component\x070.215.0\x10wit-bindgen-rust\x060.30.0"; -#[inline(never)] -#[doc(hidden)] -pub fn __link_custom_section_describing_imports() { - wit_bindgen_rt::maybe_link_cabi_realloc(); -} diff --git a/tests/rust-apps-wasm/wit-sdk/p2id-note/src/lib.rs b/tests/rust-apps-wasm/wit-sdk/p2id-note/src/lib.rs deleted file mode 100644 index 702a5e800..000000000 --- a/tests/rust-apps-wasm/wit-sdk/p2id-note/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -#![no_std] - -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - -#[panic_handler] -fn my_panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -bindings::export!(Component with_types_in bindings); - -#[allow(dead_code)] -mod bindings; - -use bindings::{ - exports::miden::base::note_script::Guest, - miden::{ - base::{ - account::get_id, - core_types::account_id_from_felt, - note::{get_assets, get_inputs}, - }, - basic_wallet::basic_wallet::receive_asset, - }, -}; - -pub struct Component; - -impl Guest for Component { - fn note_script() { - let inputs = get_inputs(); - let target_account_id_felt = inputs[0]; - let target_account_id = account_id_from_felt(target_account_id_felt); - let account_id = get_id(); - assert_eq!(account_id, target_account_id); - let assets = get_assets(); - for asset in assets { - receive_asset(asset); - } - } -} diff --git a/tests/rust-apps-wasm/wit-sdk/p2id-note/wit/p2id-note.wit b/tests/rust-apps-wasm/wit-sdk/p2id-note/wit/p2id-note.wit deleted file mode 100644 index c30a257b2..000000000 --- a/tests/rust-apps-wasm/wit-sdk/p2id-note/wit/p2id-note.wit +++ /dev/null @@ -1,13 +0,0 @@ -package miden:p2id@1.0.0; - - -world notes-world { - import miden:base/core-types@1.0.0; - import miden:base/tx@1.0.0; - import miden:base/account@1.0.0; - import miden:base/note@1.0.0; - - import miden:basic-wallet/basic-wallet@1.0.0; - - export miden:base/note-script@1.0.0; -} diff --git a/tests/rust-apps-wasm/wit-sdk/sdk/Cargo.lock b/tests/rust-apps-wasm/wit-sdk/sdk/Cargo.lock deleted file mode 100644 index 02128e975..000000000 --- a/tests/rust-apps-wasm/wit-sdk/sdk/Cargo.lock +++ /dev/null @@ -1,69 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" - -[[package]] -name = "miden-sdk" -version = "0.0.0" -dependencies = [ - "wee_alloc", - "wit-bindgen-rt", -] - -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "wit-bindgen-rt" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a37bd9274cb2d4754b915d624447ec0dce9105d174361841c0826efc79ceb9" diff --git a/tests/rust-apps-wasm/wit-sdk/sdk/Cargo.toml b/tests/rust-apps-wasm/wit-sdk/sdk/Cargo.toml deleted file mode 100644 index 7c9b5c2a1..000000000 --- a/tests/rust-apps-wasm/wit-sdk/sdk/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "miden-sdk" -version = "0.0.0" -rust-version = "1.71" -authors = ["Miden contributors"] -description = "Miden SDK" -license = "MIT" -edition = "2021" -publish = false - -# To keep it out of the root workspace since it cannot be built for a non-Wasm target -[workspace] - -[dependencies] -wit-bindgen-rt = "0.28" -wee_alloc = { version = "0.4.5", default-features = false } - - -[lib] -crate-type = ["cdylib"] - -[package.metadata.component] -package = "component:miden" - -[package.metadata.component.dependencies] - -[profile.release] -panic = "abort" -debug = true diff --git a/tests/rust-apps-wasm/wit-sdk/sdk/src/bindings.rs b/tests/rust-apps-wasm/wit-sdk/sdk/src/bindings.rs deleted file mode 100644 index fc063acda..000000000 --- a/tests/rust-apps-wasm/wit-sdk/sdk/src/bindings.rs +++ /dev/null @@ -1,468 +0,0 @@ -#[allow(dead_code)] -pub mod exports { - #[allow(dead_code)] - pub mod miden { - #[allow(dead_code)] - pub mod base { - #[allow(dead_code, clippy::all)] - pub mod core_types { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; - use super::super::super::super::_rt; - /// Represents base field element in the field using Montgomery representation. - /// Internal values represent x * R mod M where R = 2^64 mod M and x in [0, M). - /// The backing type is `f64` but the internal values are always integer in the range [0, M). - /// Field modulus M = 2^64 - 2^32 + 1 - #[repr(C)] - #[derive(Clone, Copy)] - pub struct Felt { - /// We plan to use f64 as the backing type for the field element. It has the size that we need and - /// we don't plan to support floating point arithmetic in programs for Miden VM. - /// - /// For now its u64 - pub inner: u64, - } - impl ::core::fmt::Debug for Felt { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("Felt").field("inner", &self.inner).finish() - } - } - /// A group of four field elements in the Miden base field. - pub type Word = (Felt, Felt, Felt, Felt); - /// Unique identifier of an account. - /// - /// Account ID consists of 1 field element (~64 bits). This field element uniquely identifies a - /// single account and also specifies the type of the underlying account. Specifically: - /// - The two most significant bits of the ID specify the type of the account: - /// - 00 - regular account with updatable code. - /// - 01 - regular account with immutable code. - /// - 10 - fungible asset faucet with immutable code. - /// - 11 - non-fungible asset faucet with immutable code. - /// - The third most significant bit of the ID specifies whether the account data is stored on-chain: - /// - 0 - full account data is stored on-chain. - /// - 1 - only the account hash is stored on-chain which serves as a commitment to the account state. - /// As such the three most significant bits fully describes the type of the account. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct AccountId { - pub inner: Felt, - } - impl ::core::fmt::Debug for AccountId { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("AccountId").field("inner", &self.inner).finish() - } - } - /// A fungible or a non-fungible asset. - /// - /// All assets are encoded using a single word (4 elements) such that it is easy to determine the - /// type of an asset both inside and outside Miden VM. Specifically: - /// Element 1 will be: - /// - ZERO for a fungible asset - /// - non-ZERO for a non-fungible asset - /// The most significant bit will be: - /// - ONE for a fungible asset - /// - ZERO for a non-fungible asset - /// - /// The above properties guarantee that there can never be a collision between a fungible and a - /// non-fungible asset. - /// - /// The methodology for constructing fungible and non-fungible assets is described below. - /// - /// # Fungible assets - /// The most significant element of a fungible asset is set to the ID of the faucet which issued - /// the asset. This guarantees the properties described above (the first bit is ONE). - /// - /// The least significant element is set to the amount of the asset. This amount cannot be greater - /// than 2^63 - 1 and thus requires 63-bits to store. - /// - /// Elements 1 and 2 are set to ZERO. - /// - /// It is impossible to find a collision between two fungible assets issued by different faucets as - /// the faucet_id is included in the description of the asset and this is guaranteed to be different - /// for each faucet as per the faucet creation logic. - /// - /// # Non-fungible assets - /// The 4 elements of non-fungible assets are computed as follows: - /// - First the asset data is hashed. This compresses an asset of an arbitrary length to 4 field - /// elements: [d0, d1, d2, d3]. - /// - d1 is then replaced with the faucet_id which issues the asset: [d0, faucet_id, d2, d3]. - /// - Lastly, the most significant bit of d3 is set to ZERO. - /// - /// It is impossible to find a collision between two non-fungible assets issued by different faucets - /// as the faucet_id is included in the description of the non-fungible asset and this is guaranteed - /// to be different as per the faucet creation logic. Collision resistance for non-fungible assets - /// issued by the same faucet is ~2^95. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct CoreAsset { - pub inner: Word, - } - impl ::core::fmt::Debug for CoreAsset { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("CoreAsset").field("inner", &self.inner).finish() - } - } - #[doc(hidden)] - #[allow(non_snake_case)] - pub unsafe fn _export_account_id_from_felt_cabi( - arg0: i64, - ) -> i64 { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - let result0 = T::account_id_from_felt(Felt { inner: arg0 as u64 }); - let AccountId { inner: inner1 } = result0; - let Felt { inner: inner2 } = inner1; - _rt::as_i64(inner2) - } - pub trait Guest { - /// Creates a new account ID from a field element. - fn account_id_from_felt(felt: Felt) -> AccountId; - } - #[doc(hidden)] - macro_rules! __export_miden_base_core_types_1_0_0_cabi { - ($ty:ident with_types_in $($path_to_types:tt)*) => { - const _ : () = { #[export_name = - "miden:base/core-types@1.0.0#account-id-from-felt"] unsafe extern - "C" fn export_account_id_from_felt(arg0 : i64,) -> i64 { - $($path_to_types)*:: _export_account_id_from_felt_cabi::<$ty > - (arg0) } }; - }; - } - #[doc(hidden)] - pub(crate) use __export_miden_base_core_types_1_0_0_cabi; - } - #[allow(dead_code, clippy::all)] - pub mod types { - #[used] - #[doc(hidden)] - static __FORCE_SECTION_REF: fn() = super::super::super::super::__link_custom_section_describing_imports; - use super::super::super::super::_rt; - pub type AccountId = super::super::super::super::exports::miden::base::core_types::AccountId; - pub type Word = super::super::super::super::exports::miden::base::core_types::Word; - pub type CoreAsset = super::super::super::super::exports::miden::base::core_types::CoreAsset; - /// A fungible asset - #[repr(C)] - #[derive(Clone, Copy)] - pub struct FungibleAsset { - /// Faucet ID of the faucet which issued the asset as well as the asset amount. - pub asset: AccountId, - /// Asset amount is guaranteed to be 2^63 - 1 or smaller. - pub amount: u64, - } - impl ::core::fmt::Debug for FungibleAsset { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("FungibleAsset") - .field("asset", &self.asset) - .field("amount", &self.amount) - .finish() - } - } - /// A commitment to a non-fungible asset. - /// - /// A non-fungible asset consists of 4 field elements which are computed by hashing asset data - /// (which can be of arbitrary length) to produce: [d0, d1, d2, d3]. We then replace d1 with the - /// faucet_id that issued the asset: [d0, faucet_id, d2, d3]. We then set the most significant bit - /// of the most significant element to ZERO. - #[repr(C)] - #[derive(Clone, Copy)] - pub struct NonFungibleAsset { - pub inner: Word, - } - impl ::core::fmt::Debug for NonFungibleAsset { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - f.debug_struct("NonFungibleAsset") - .field("inner", &self.inner) - .finish() - } - } - /// A fungible or a non-fungible asset. - #[derive(Clone, Copy)] - pub enum Asset { - Fungible(FungibleAsset), - NonFungible(NonFungibleAsset), - } - impl ::core::fmt::Debug for Asset { - fn fmt( - &self, - f: &mut ::core::fmt::Formatter<'_>, - ) -> ::core::fmt::Result { - match self { - Asset::Fungible(e) => { - f.debug_tuple("Asset::Fungible").field(e).finish() - } - Asset::NonFungible(e) => { - f.debug_tuple("Asset::NonFungible").field(e).finish() - } - } - } - } - #[doc(hidden)] - #[allow(non_snake_case)] - pub unsafe fn _export_from_core_asset_cabi( - arg0: i64, - arg1: i64, - arg2: i64, - arg3: i64, - ) -> *mut u8 { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - let result0 = T::from_core_asset(super::super::super::super::exports::miden::base::core_types::CoreAsset { - inner: ( - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg0 as u64, - }, - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg1 as u64, - }, - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg2 as u64, - }, - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg3 as u64, - }, - ), - }); - let ptr1 = _RET_AREA.0.as_mut_ptr().cast::(); - match result0 { - Asset::Fungible(e) => { - *ptr1.add(0).cast::() = (0i32) as u8; - let FungibleAsset { asset: asset2, amount: amount2 } = e; - let super::super::super::super::exports::miden::base::core_types::AccountId { - inner: inner3, - } = asset2; - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner4, - } = inner3; - *ptr1.add(8).cast::() = _rt::as_i64(inner4); - *ptr1.add(16).cast::() = _rt::as_i64(amount2); - } - Asset::NonFungible(e) => { - *ptr1.add(0).cast::() = (1i32) as u8; - let NonFungibleAsset { inner: inner5 } = e; - let (t6_0, t6_1, t6_2, t6_3) = inner5; - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner7, - } = t6_0; - *ptr1.add(8).cast::() = _rt::as_i64(inner7); - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner8, - } = t6_1; - *ptr1.add(16).cast::() = _rt::as_i64(inner8); - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner9, - } = t6_2; - *ptr1.add(24).cast::() = _rt::as_i64(inner9); - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner10, - } = t6_3; - *ptr1.add(32).cast::() = _rt::as_i64(inner10); - } - } - ptr1 - } - #[doc(hidden)] - #[allow(non_snake_case)] - pub unsafe fn _export_to_core_asset_cabi( - arg0: i32, - arg1: i64, - arg2: i64, - arg3: i64, - arg4: i64, - ) -> *mut u8 { - #[cfg(target_arch = "wasm32")] _rt::run_ctors_once(); - let v0 = match arg0 { - 0 => { - let e0 = FungibleAsset { - asset: super::super::super::super::exports::miden::base::core_types::AccountId { - inner: super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg1 as u64, - }, - }, - amount: arg2 as u64, - }; - Asset::Fungible(e0) - } - n => { - debug_assert_eq!(n, 1, "invalid enum discriminant"); - let e0 = NonFungibleAsset { - inner: ( - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg1 as u64, - }, - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg2 as u64, - }, - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg3 as u64, - }, - super::super::super::super::exports::miden::base::core_types::Felt { - inner: arg4 as u64, - }, - ), - }; - Asset::NonFungible(e0) - } - }; - let result1 = T::to_core_asset(v0); - let ptr2 = _RET_AREA.0.as_mut_ptr().cast::(); - let super::super::super::super::exports::miden::base::core_types::CoreAsset { - inner: inner3, - } = result1; - let (t4_0, t4_1, t4_2, t4_3) = inner3; - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner5, - } = t4_0; - *ptr2.add(0).cast::() = _rt::as_i64(inner5); - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner6, - } = t4_1; - *ptr2.add(8).cast::() = _rt::as_i64(inner6); - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner7, - } = t4_2; - *ptr2.add(16).cast::() = _rt::as_i64(inner7); - let super::super::super::super::exports::miden::base::core_types::Felt { - inner: inner8, - } = t4_3; - *ptr2.add(24).cast::() = _rt::as_i64(inner8); - ptr2 - } - pub trait Guest { - /// Converts a core asset to a an asset representation. - fn from_core_asset(core_asset: CoreAsset) -> Asset; - /// Converts an asset to a core asset representation. - fn to_core_asset(asset: Asset) -> CoreAsset; - } - #[doc(hidden)] - macro_rules! __export_miden_base_types_1_0_0_cabi { - ($ty:ident with_types_in $($path_to_types:tt)*) => { - const _ : () = { #[export_name = - "miden:base/types@1.0.0#from-core-asset"] unsafe extern "C" fn - export_from_core_asset(arg0 : i64, arg1 : i64, arg2 : i64, arg3 : - i64,) -> * mut u8 { $($path_to_types)*:: - _export_from_core_asset_cabi::<$ty > (arg0, arg1, arg2, arg3) } - #[export_name = "miden:base/types@1.0.0#to-core-asset"] unsafe - extern "C" fn export_to_core_asset(arg0 : i32, arg1 : i64, arg2 : - i64, arg3 : i64, arg4 : i64,) -> * mut u8 { $($path_to_types)*:: - _export_to_core_asset_cabi::<$ty > (arg0, arg1, arg2, arg3, arg4) - } }; - }; - } - #[doc(hidden)] - pub(crate) use __export_miden_base_types_1_0_0_cabi; - #[repr(align(8))] - struct _RetArea([::core::mem::MaybeUninit; 40]); - static mut _RET_AREA: _RetArea = _RetArea( - [::core::mem::MaybeUninit::uninit(); 40], - ); - } - } - } -} -mod _rt { - #[cfg(target_arch = "wasm32")] - pub fn run_ctors_once() { - wit_bindgen_rt::run_ctors_once(); - } - pub fn as_i64(t: T) -> i64 { - t.as_i64() - } - pub trait AsI64 { - fn as_i64(self) -> i64; - } - impl<'a, T: Copy + AsI64> AsI64 for &'a T { - fn as_i64(self) -> i64 { - (*self).as_i64() - } - } - impl AsI64 for i64 { - #[inline] - fn as_i64(self) -> i64 { - self as i64 - } - } - impl AsI64 for u64 { - #[inline] - fn as_i64(self) -> i64 { - self as i64 - } - } -} -/// Generates `#[no_mangle]` functions to export the specified type as the -/// root implementation of all generated traits. -/// -/// For more information see the documentation of `wit_bindgen::generate!`. -/// -/// ```rust -/// # macro_rules! export{ ($($t:tt)*) => (); } -/// # trait Guest {} -/// struct MyType; -/// -/// impl Guest for MyType { -/// // ... -/// } -/// -/// export!(MyType); -/// ``` -#[allow(unused_macros)] -#[doc(hidden)] -macro_rules! __export_base_world_impl { - ($ty:ident) => { - self::export!($ty with_types_in self); - }; - ($ty:ident with_types_in $($path_to_types_root:tt)*) => { - $($path_to_types_root)*:: - exports::miden::base::core_types::__export_miden_base_core_types_1_0_0_cabi!($ty - with_types_in $($path_to_types_root)*:: exports::miden::base::core_types); - $($path_to_types_root)*:: - exports::miden::base::types::__export_miden_base_types_1_0_0_cabi!($ty - with_types_in $($path_to_types_root)*:: exports::miden::base::types); - }; -} -#[doc(inline)] -pub(crate) use __export_base_world_impl as export; -#[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.30.0:base-world:encoded world"] -#[doc(hidden)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 922] = *b"\ -\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\x99\x06\x01A\x02\x01\ -A\x08\x01B\x1e\x01r\x01\x05innerw\x04\0\x04felt\x03\0\0\x01o\x04\x01\x01\x01\x01\ -\x04\0\x04word\x03\0\x02\x01r\x01\x05inner\x01\x04\0\x0aaccount-id\x03\0\x04\x01\ -r\x01\x05inner\x03\x04\0\x09recipient\x03\0\x06\x01r\x01\x05inner\x01\x04\0\x03t\ -ag\x03\0\x08\x01r\x01\x05inner\x03\x04\0\x0acore-asset\x03\0\x0a\x01r\x01\x05inn\ -er\x01\x04\0\x05nonce\x03\0\x0c\x01r\x01\x05inner\x03\x04\0\x0caccount-hash\x03\0\ -\x0e\x01r\x01\x05inner\x03\x04\0\x0ablock-hash\x03\0\x10\x01r\x01\x05inner\x03\x04\ -\0\x0dstorage-value\x03\0\x12\x01r\x01\x05inner\x03\x04\0\x0cstorage-root\x03\0\x14\ -\x01r\x01\x05inner\x03\x04\0\x11account-code-root\x03\0\x16\x01r\x01\x05inner\x03\ -\x04\0\x10vault-commitment\x03\0\x18\x01r\x01\x05inner\x01\x04\0\x07note-id\x03\0\ -\x1a\x01@\x01\x04felt\x01\0\x05\x04\0\x14account-id-from-felt\x01\x1c\x04\x01\x1b\ -miden:base/core-types@1.0.0\x05\0\x02\x03\0\0\x04felt\x02\x03\0\0\x0aaccount-id\x02\ -\x03\0\0\x04word\x02\x03\0\0\x0acore-asset\x01B\x12\x02\x03\x02\x01\x01\x04\0\x04\ -felt\x03\0\0\x02\x03\x02\x01\x02\x04\0\x0aaccount-id\x03\0\x02\x02\x03\x02\x01\x03\ -\x04\0\x04word\x03\0\x04\x02\x03\x02\x01\x04\x04\0\x0acore-asset\x03\0\x06\x01r\x02\ -\x05asset\x03\x06amountw\x04\0\x0efungible-asset\x03\0\x08\x01r\x01\x05inner\x05\ -\x04\0\x12non-fungible-asset\x03\0\x0a\x01q\x02\x08fungible\x01\x09\0\x0cnon-fun\ -gible\x01\x0b\0\x04\0\x05asset\x03\0\x0c\x01@\x01\x0acore-asset\x07\0\x0d\x04\0\x0f\ -from-core-asset\x01\x0e\x01@\x01\x05asset\x0d\0\x07\x04\0\x0dto-core-asset\x01\x0f\ -\x04\x01\x16miden:base/types@1.0.0\x05\x05\x04\x01\x1bmiden:base/base-world@1.0.\ -0\x04\0\x0b\x10\x01\0\x0abase-world\x03\0\0\0G\x09producers\x01\x0cprocessed-by\x02\ -\x0dwit-component\x070.215.0\x10wit-bindgen-rust\x060.30.0"; -#[inline(never)] -#[doc(hidden)] -pub fn __link_custom_section_describing_imports() { - wit_bindgen_rt::maybe_link_cabi_realloc(); -} diff --git a/tests/rust-apps-wasm/wit-sdk/sdk/src/lib.rs b/tests/rust-apps-wasm/wit-sdk/sdk/src/lib.rs deleted file mode 100644 index d13032ba0..000000000 --- a/tests/rust-apps-wasm/wit-sdk/sdk/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![no_std] - -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - -#[panic_handler] -fn my_panic(_info: &core::panic::PanicInfo) -> ! { - loop {} -} - -bindings::export!(Component with_types_in bindings); - -mod bindings; - -use bindings::exports::miden::base::{ - core_types, - core_types::{AccountId, CoreAsset, Felt}, - types, - types::Asset, -}; - -pub struct Component; - -impl core_types::Guest for Component { - fn account_id_from_felt(felt: Felt) -> AccountId { - // TODO: assert that felt is a valid account id - AccountId { inner: felt } - } -} - -impl types::Guest for Component { - fn from_core_asset(asset: CoreAsset) -> Asset { - todo!() - } - - fn to_core_asset(asset: Asset) -> CoreAsset { - todo!() - } -} diff --git a/tests/rust-apps-wasm/wit-sdk/sdk/wit/miden.wit b/tests/rust-apps-wasm/wit-sdk/sdk/wit/miden.wit deleted file mode 100644 index dd6546743..000000000 --- a/tests/rust-apps-wasm/wit-sdk/sdk/wit/miden.wit +++ /dev/null @@ -1,307 +0,0 @@ -package miden:base@1.0.0; - -/// Types to be used in tx-kernel interface -interface core-types { - /// Represents base field element in the field using Montgomery representation. - /// Internal values represent x * R mod M where R = 2^64 mod M and x in [0, M). - /// The backing type is `f64` but the internal values are always integer in the range [0, M). - /// Field modulus M = 2^64 - 2^32 + 1 - record felt { - /// We plan to use f64 as the backing type for the field element. It has the size that we need and - /// we don't plan to support floating point arithmetic in programs for Miden VM. - /// - /// For now its u64 - inner: u64 - } - - - /// A group of four field elements in the Miden base field. - type word = tuple; - - /// Unique identifier of an account. - /// - /// Account ID consists of 1 field element (~64 bits). This field element uniquely identifies a - /// single account and also specifies the type of the underlying account. Specifically: - /// - The two most significant bits of the ID specify the type of the account: - /// - 00 - regular account with updatable code. - /// - 01 - regular account with immutable code. - /// - 10 - fungible asset faucet with immutable code. - /// - 11 - non-fungible asset faucet with immutable code. - /// - The third most significant bit of the ID specifies whether the account data is stored on-chain: - /// - 0 - full account data is stored on-chain. - /// - 1 - only the account hash is stored on-chain which serves as a commitment to the account state. - /// As such the three most significant bits fully describes the type of the account. - record account-id { - inner: felt - } - - /// Creates a new account ID from a field element. - account-id-from-felt: func(felt: felt) -> account-id; - - /// Recipient of the note, i.e., hash(hash(hash(serial_num, [0; 4]), note_script_hash), input_hash) - record recipient { - inner: word - } - - record tag { - inner: felt - } - - /// A fungible or a non-fungible asset. - /// - /// All assets are encoded using a single word (4 elements) such that it is easy to determine the - /// type of an asset both inside and outside Miden VM. Specifically: - /// Element 1 will be: - /// - ZERO for a fungible asset - /// - non-ZERO for a non-fungible asset - /// The most significant bit will be: - /// - ONE for a fungible asset - /// - ZERO for a non-fungible asset - /// - /// The above properties guarantee that there can never be a collision between a fungible and a - /// non-fungible asset. - /// - /// The methodology for constructing fungible and non-fungible assets is described below. - /// - /// # Fungible assets - /// The most significant element of a fungible asset is set to the ID of the faucet which issued - /// the asset. This guarantees the properties described above (the first bit is ONE). - /// - /// The least significant element is set to the amount of the asset. This amount cannot be greater - /// than 2^63 - 1 and thus requires 63-bits to store. - /// - /// Elements 1 and 2 are set to ZERO. - /// - /// It is impossible to find a collision between two fungible assets issued by different faucets as - /// the faucet_id is included in the description of the asset and this is guaranteed to be different - /// for each faucet as per the faucet creation logic. - /// - /// # Non-fungible assets - /// The 4 elements of non-fungible assets are computed as follows: - /// - First the asset data is hashed. This compresses an asset of an arbitrary length to 4 field - /// elements: [d0, d1, d2, d3]. - /// - d1 is then replaced with the faucet_id which issues the asset: [d0, faucet_id, d2, d3]. - /// - Lastly, the most significant bit of d3 is set to ZERO. - /// - /// It is impossible to find a collision between two non-fungible assets issued by different faucets - /// as the faucet_id is included in the description of the non-fungible asset and this is guaranteed - /// to be different as per the faucet creation logic. Collision resistance for non-fungible assets - /// issued by the same faucet is ~2^95. - record core-asset { - inner: word - } - - /// Account nonce - record nonce { - inner: felt - } - - /// Account hash - record account-hash { - inner: word - } - - /// Block hash - record block-hash { - inner: word - } - - /// Storage value - record storage-value { - inner: word - } - - /// Account storage root - record storage-root { - inner: word - } - - /// Account code root - record account-code-root { - inner: word - } - - /// Commitment to the account vault - record vault-commitment { - inner: word - } - - /// An id of the created note - record note-id { - inner: felt - } - -} - - -/// Account-related functions -interface account { - use core-types.{felt,core-asset, tag, recipient, account-id, nonce, account-hash, storage-value, storage-root, account-code-root, vault-commitment}; - - /// Get the id of the currently executing account - get-id: func() -> account-id; - /// Return the account nonce - get-nonce: func() -> nonce; - /// Get the initial hash of the currently executing account - get-initial-hash: func() -> account-hash; - /// Get the current hash of the account data stored in memory - get-current-hash: func() -> account-hash; - /// Increment the account nonce by the specified value. - /// value can be at most 2^32 - 1 otherwise this procedure panics - incr-nonce: func(value: felt); - /// Get the value of the specified key in the account storage - get-item: func(index: felt) -> storage-value; - /// Set the value of the specified key in the account storage - /// Returns the old value of the key and the new storage root - set-item: func(index: felt, value: storage-value) -> tuple; - /// Sets the code of the account the transaction is being executed against. - /// This procedure can only be executed on regular accounts with updatable - /// code. Otherwise, this procedure fails. code is the hash of the code - /// to set. - set-code: func(code-root: account-code-root); - /// Returns the balance of a fungible asset associated with a account_id. - /// Panics if the asset is not a fungible asset. account_id is the faucet id - /// of the fungible asset of interest. balance is the vault balance of the - /// fungible asset. - get-balance: func(account-id: account-id) -> felt; - /// Returns a boolean indicating whether the non-fungible asset is present - /// in the vault. Panics if the asset is a fungible asset. asset is the - /// non-fungible asset of interest. has_asset is a boolean indicating - /// whether the account vault has the asset of interest. - has-non-fungible-asset: func(asset: core-asset) -> bool; - /// Add the specified asset to the vault. Panics under various conditions. - /// Returns the final asset in the account vault defined as follows: If asset is - /// a non-fungible asset, then returns the same as asset. If asset is a - /// fungible asset, then returns the total fungible asset in the account - /// vault after asset was added to it. - add-asset: func(asset: core-asset) -> core-asset; - /// Remove the specified asset from the vault - remove-asset: func(asset: core-asset) -> core-asset; - /// Returns the commitment to the account vault. - get-vault-commitment: func() -> vault-commitment; -} - -// Note-related functions -interface note { - use core-types.{felt, core-asset, tag, recipient, account-id, nonce, account-hash, storage-value, storage-root, account-code-root, vault-commitment}; - - /// Get the inputs of the currently executed note - get-inputs: func() -> list; - /// Get the assets of the currently executing note - get-assets: func() -> list; - /// Get the sender of the currently executing note - get-sender: func() -> account-id; - -} - -/// Transaction-related functions -interface tx { - use core-types.{felt, core-asset, tag, recipient, account-id, nonce, account-hash, storage-value, storage-root, account-code-root, vault-commitment, block-hash, word, note-id}; - - /// Returns the block number of the last known block at the time of transaction execution. - get-block-number: func() -> felt; - /// Returns the block hash of the last known block at the time of transaction execution. - get-block-hash: func() -> block-hash; - /// Returns the input notes hash. This is computed as a sequential hash of - /// (nullifier, script_root) tuples over all input notes. - get-input-notes-hash: func() -> word; - /// Returns the output notes hash. This is computed as a sequential hash of - /// (note_hash, note_metadata) tuples over all output notes. - get-output-notes-hash: func() -> word; - /// Creates a new note. - /// asset is the asset to be included in the note. - /// tag is the tag to be included in the note. - /// recipient is the recipient of the note. - /// Returns the id of the created note. - create-note: func(asset: core-asset, tag: tag, recipient: recipient) -> note-id; -} - -/// Asset-related functions. These functions can only be called by faucet accounts. -interface asset { - use core-types.{felt, core-asset, tag, recipient, account-id, nonce, account-hash, storage-value, storage-root, account-code-root, vault-commitment, block-hash, word}; - - /// Builds a fungible asset for the specified fungible faucet and amount. - /// faucet_id is the faucet to create the asset for. - /// amount is the amount of the asset to create. - /// Returns the created asset. - build-fungible-asset: func(faucet-id: account-id, amount: felt) -> core-asset; - /// Creates a fungible asset for the faucet the transaction is being - /// executed against. - /// amount is the amount of the asset to create. - /// Returns the created asset. - create-fungible-asset: func(amount: felt) -> core-asset; - /// Builds a non-fungible asset for the specified non-fungible faucet and - /// data-hash. - /// faucet_id is the faucet to create the asset for. - /// data-hash is the data hash of the non-fungible asset to build. - /// Returns the created asset. - build-non-fungible-asset: func(faucet-id: account-id, data-hash: word) -> core-asset; - /// Creates a non-fungible asset for the faucet the transaction is being executed against. - /// data-hash is the data hash of the non-fungible asset to create. - /// Returns the created asset. - create-non-fungible-asset: func(data-hash: word) -> core-asset; -} - - -/// Faucet-related functions. These functions can only be called by faucet accounts. -interface faucet { - use core-types.{felt, core-asset, tag, recipient, account-id, nonce, account-hash, storage-value, storage-root, account-code-root, vault-commitment, block-hash, word}; - - /// Mint an asset from the faucet transaction is being executed against. - /// Returns the minted asset. - mint: func(asset: core-asset) -> core-asset; - /// Burn an asset from the faucet transaction is being executed against. - /// Returns the burned asset. - burn: func(asset: core-asset) -> core-asset; - /// Returns the total issuance of the fungible faucet the transaction is - /// being executed against. Panics if the transaction is not being executed - /// against a fungible faucet. - get-total-issuance: func() -> felt; - -} - - -/// High-level representation of core types -interface types { - use core-types.{felt, account-id, word, core-asset}; - - /// A fungible asset - record fungible-asset { - /// Faucet ID of the faucet which issued the asset as well as the asset amount. - asset: account-id, - /// Asset amount is guaranteed to be 2^63 - 1 or smaller. - amount: u64 - } - - /// A commitment to a non-fungible asset. - /// - /// A non-fungible asset consists of 4 field elements which are computed by hashing asset data - /// (which can be of arbitrary length) to produce: [d0, d1, d2, d3]. We then replace d1 with the - /// faucet_id that issued the asset: [d0, faucet_id, d2, d3]. We then set the most significant bit - /// of the most significant element to ZERO. - record non-fungible-asset { - inner: word, - } - - /// A fungible or a non-fungible asset. - variant asset { - fungible(fungible-asset), - non-fungible(non-fungible-asset), - } - - /// Converts a core asset to a an asset representation. - from-core-asset: func(core-asset: core-asset) -> asset; - /// Converts an asset to a core asset representation. - to-core-asset: func(asset: asset) -> core-asset; -} - - -/// Note script interface that is expected to be implemented by the note script. -interface note-script { - note-script: func(); -} - -world base-world { - export types; - export core-types; -} diff --git a/tests/rust-apps/fib/Cargo.toml b/tests/rust-apps/fib/Cargo.toml deleted file mode 100644 index 8f815bf3a..000000000 --- a/tests/rust-apps/fib/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "miden-integration-tests-rust-fib" -version = "0.0.0" -edition = "2021" -publish = false - -[profile.release] -debug = true diff --git a/tests/rust-apps/fib/src/lib.rs b/tests/rust-apps/fib/src/lib.rs deleted file mode 100644 index d63aefa74..000000000 --- a/tests/rust-apps/fib/src/lib.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![no_std] - -#[no_mangle] -pub fn fib(n: u32) -> u32 { - let mut a = 0; - let mut b = 1; - for _ in 0..n { - let c = a + b; - a = b; - b = c; - } - a -} diff --git a/tools/cargo-miden/CHANGELOG.md b/tools/cargo-miden/CHANGELOG.md index 948560d16..96b59e6ed 100644 --- a/tools/cargo-miden/CHANGELOG.md +++ b/tools/cargo-miden/CHANGELOG.md @@ -6,6 +6,102 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.4.1](https://github.com/0xMiden/compiler/compare/cargo-miden-v0.4.0...cargo-miden-v0.4.1) - 2025-09-03 + +### Added + +- allow `cargo miden build` in a member of a workspace + +### Fixed + +- disable `debug-assertions` in dev profile +- draft cargo-miden support (disabled for now) for building the crate which is included in the workspace + +### Other + +- update new project templates tag to v0.14.0 +- pass profile options via `--config` flag to `cargo` +- Add 128-bit wide arithmetic support to the compiler. + +## [0.4.0](https://github.com/0xMiden/compiler/compare/cargo-miden-v0.1.5...cargo-miden-v0.4.0) - 2025-08-15 + +### Added + +- add basic-wallet-tx-script to the `cargo miden example` command +- add `--tx-script` option to the `cargo miden new` command +- add `project-kind` with `account`, `note-script` and +- add missing and fix existing tx kernel function bindings +- rename note script rollup target into script +- *(cargo-miden)* `example [name]` for paired projects (basic-wallet +- move existing new project templates to `cargo miden example` + +### Fixed + +- switch to `v0.12.0` tag for new project templates +- new templates path, test `--note` with an account +- remove `add` from the skip list in bindings generator +- improve the detection when to deploy Miden SDK WIT files +- override the binary name for help and error messages to `cargo miden` +- rewrite `ExampleCommand` to fetch from `examples` folder in the + +### Other + +- rename `note-script` and `tx-script` entrypoints to `run` +- use local compiler path in `ExampleCommand` under tests +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) +- print detailed error on `cargo-miden` fail +- `ExampleCommand` implementation, add comments +- use `v0.11.0` tag for new project templates +- add comments regarding Cargo.toml processing for example projects +- print the list of the available examples on `cargo miden example --help` +- merge new project and example test building into one test + +## [0.1.5](https://github.com/0xMiden/compiler/compare/cargo-miden-v0.1.0...cargo-miden-v0.1.5) - 2025-07-01 + +### Added + +- implement Wasm CM indirect lowering shim+fixup module bypass +- *(cargo-miden)* switch rollup projects to `wasm32-wasip2` target + +### Other + +- sketch out generic testnet test infrastructure +- add `counter_contract_debug_build` test to reproduce #510, +- remove unused code (componentization) in + +## [0.0.8](https://github.com/0xMiden/compiler/compare/cargo-miden-v0.0.7...cargo-miden-v0.0.8) - 2025-04-24 + +### Added +- switch cargo-miden to use v0.7.0 tag in rust-templates repo +- add `--program`, `--account`, `--note` flags +- draft wasm/masm output request in cargo-miden +- *(cargo-miden)* support building Wasm component from a Cargo project + +### Fixed +- although we support only `--program` in cargo-miden, +- hide --account and --note options, make --program option default, +- refine `Component` imports and exports to reference module imports +- cast `count` parameter in Wasm `memory.copy` op to U32; + +### Other +- treat warnings as compiler errors, +- update rust toolchain, clean up deps +- switch to the published version `v0.21` of the `cargo-component` +- emit optimized ir, rather than initial translated ir during tests +- switch uses of hir crates to hir2 +- update `cargo-component` (without `env_vars` parameter) +- set `RUSTFLAGS` env var instead of passing it to +- update wit-bindgen and cargo-component to the latest patched versions +- update `cargo miden new` to use `v0.8.0` of the program template +- update to the latest `miden-mast-package` (renamed from +- optimize codegen for `AccountId::as_felt`; +- add note script compilation test; +- [**breaking**] rename `miden-sdk` crate to `miden` [#338](https://github.com/0xMiden/compiler/pull/338) +- remove `miden:core-import/types` custom types +- clean up todos +- skip Miden SDK function generation in bindings.rs +- add check for the proper artifact file extension in the cargo-miden test + ## [0.0.7](https://github.com/0xPolygonMiden/compiler/compare/cargo-miden-v0.0.6...cargo-miden-v0.0.7) - 2024-09-17 ### Fixed diff --git a/tools/cargo-miden/Cargo.toml b/tools/cargo-miden/Cargo.toml index 5dfdac61d..dcced1060 100644 --- a/tools/cargo-miden/Cargo.toml +++ b/tools/cargo-miden/Cargo.toml @@ -28,12 +28,15 @@ env_logger.workspace = true log.workspace = true clap.workspace = true anyhow.workspace = true -cargo-component = "0.16" -cargo-component-core = "0.16" -cargo_metadata = "0.18" -cargo-generate = "0.22" -semver = "1.0.20" -parse_arg = "0.1.4" +cargo_metadata = "0.19" +cargo-generate = "0.23" path-absolutize = "3.1.1" +serde.workspace = true +serde_json = "1.0" +toml_edit = { version = "0.23", features = ["serde"] } +semver.workspace = true +parse_arg = "0.1.6" [dev-dependencies] +# Use local paths for dev-only dependency to avoid relying on crates.io during packaging +miden-mast-package.workspace = true diff --git a/tools/cargo-miden/README.md b/tools/cargo-miden/README.md index 7dd839385..26bd27593 100644 --- a/tools/cargo-miden/README.md +++ b/tools/cargo-miden/README.md @@ -2,4 +2,4 @@ This crate provides a cargo extension that allows you to compile Rust code to Miden VM MASM. -See the [documentation](/docs/usage/cargo-miden.md) for more details. \ No newline at end of file +See the [documentation](https://0xmiden.github.io/compiler/usage/cargo-miden.html) for more details. diff --git a/tools/cargo-miden/src/build.rs b/tools/cargo-miden/src/build.rs deleted file mode 100644 index 5b88a071d..000000000 --- a/tools/cargo-miden/src/build.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - rc::Rc, -}; - -use midenc_compile::Compiler; -use midenc_session::{ - diagnostics::{IntoDiagnostic, Report, WrapErr}, - InputFile, OutputType, -}; - -pub fn build_masm( - wasm_file_path: &Path, - output_folder: &Path, - is_bin: bool, -) -> Result { - if !output_folder.exists() { - return Err(Report::msg(format!( - "MASM output folder '{}' does not exist.", - output_folder.to_str().unwrap() - ))); - } - log::debug!( - "Compiling '{}' Wasm to '{}' directory with midenc ...", - wasm_file_path.to_str().unwrap(), - &output_folder.to_str().unwrap() - ); - let input = InputFile::from_path(wasm_file_path) - .into_diagnostic() - .wrap_err("Invalid input file")?; - let output_file = output_folder - .join(wasm_file_path.file_stem().expect("invalid wasm file path: no file stem")) - .with_extension(OutputType::Masp.extension()); - let project_type = if is_bin { "--exe" } else { "--lib" }; - let args: Vec<&std::ffi::OsStr> = vec![ - "--output-dir".as_ref(), - output_folder.as_os_str(), - "-o".as_ref(), - output_file.as_os_str(), - project_type.as_ref(), - "--verbose".as_ref(), - "--target".as_ref(), - "rollup".as_ref(), - ]; - let session = Rc::new(Compiler::new_session([input], None, args)); - midenc_compile::compile(session.clone())?; - Ok(output_file) -} diff --git a/tools/cargo-miden/src/cli.rs b/tools/cargo-miden/src/cli.rs new file mode 100644 index 000000000..7b36a2126 --- /dev/null +++ b/tools/cargo-miden/src/cli.rs @@ -0,0 +1,28 @@ +use clap::{Parser, Subcommand}; + +use crate::commands::{BuildCommand, ExampleCommand, NewCommand}; + +/// Top-level command-line interface for `cargo-miden`. +#[derive(Debug, Parser)] +#[command( + bin_name = "cargo miden", + version, + propagate_version = true, + arg_required_else_help = true +)] +pub struct CargoMidenCli { + /// The subcommand to execute. + #[command(subcommand)] + pub command: CargoMidenCommand, +} + +/// Subcommands supported by `cargo-miden`. +#[derive(Debug, Subcommand)] +pub enum CargoMidenCommand { + /// Create a new Miden project (default) or a contract from a template (see Options). + New(NewCommand), + /// Compile the current crate to Miden package. + Build(BuildCommand), + /// Scaffold one of the curated example projects. + Example(ExampleCommand), +} diff --git a/tools/cargo-miden/src/commands/build.rs b/tools/cargo-miden/src/commands/build.rs new file mode 100644 index 000000000..89fac3d8e --- /dev/null +++ b/tools/cargo-miden/src/commands/build.rs @@ -0,0 +1,424 @@ +use std::{ + io::{BufRead, BufReader}, + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +use anyhow::{bail, Context, Result}; +use cargo_metadata::{camino, Artifact, Message, Metadata, MetadataCommand, Package}; +use clap::Args; +use midenc_session::TargetEnv; +use path_absolutize::Absolutize; + +use crate::{ + compile_masm, + config::{CargoArguments, CargoPackageSpec}, + dependencies::process_miden_dependencies, + target::{self, install_wasm32_target}, + BuildOutput, CommandOutput, OutputType, +}; + +/// Command-line arguments accepted by `cargo miden build`. +/// +/// We capture all tokens following the `build` subcommand so that the build pipeline can +/// interpret them and forward the appropriate options to Cargo. +#[derive(Clone, Debug, Args)] +#[command(disable_version_flag = true, trailing_var_arg = true)] +pub struct BuildCommand { + /// Additional arguments forwarded to the underlying Cargo invocation. + #[arg(value_name = "CARGO_ARG", allow_hyphen_values = true)] + pub cargo_args: Vec, +} + +impl BuildCommand { + /// Executes `cargo miden build`, returning the resulting command output. + pub fn exec(self, build_output_type: OutputType) -> Result> { + let mut invocation = Vec::with_capacity(self.cargo_args.len() + 1); + invocation.push("build".to_string()); + invocation.extend(self.cargo_args); + + let cargo_args = CargoArguments::parse_from(invocation.clone().into_iter())?; + let metadata = load_metadata(cargo_args.manifest_path.as_deref())?; + + let mut packages = + load_component_metadata(&metadata, cargo_args.packages.iter(), cargo_args.workspace)?; + + if packages.is_empty() { + bail!( + "manifest `{path}` contains no package or the workspace has no members", + path = metadata.workspace_root.join("Cargo.toml") + ); + } + + let cargo_package = determine_cargo_package(&metadata, &cargo_args)?; + + let target_env = target::detect_target_environment(cargo_package)?; + let project_type = target::target_environment_to_project_type(target_env); + + if !packages.iter().any(|p| p.package.id == cargo_package.id) { + packages.push(PackageComponentMetadata::new(cargo_package)?); + } + + let dependency_packages_paths = process_miden_dependencies(cargo_package, &cargo_args)?; + + let mut spawn_args: Vec<_> = invocation.clone(); + spawn_args.extend_from_slice( + &[ + "-Z", + "build-std=std,core,alloc,panic_abort", + "-Z", + "build-std-features=panic_immediate_abort", + ] + .map(|s| s.to_string()), + ); + + let cfg_pairs: Vec<(&str, &str)> = vec![ + ("profile.dev.panic", "\"abort\""), + ("profile.dev.opt-level", "1"), + ("profile.dev.overflow-checks", "false"), + ("profile.dev.debug", "true"), + ("profile.dev.debug-assertions", "false"), + ("profile.release.opt-level", "\"z\""), + ("profile.release.panic", "\"abort\""), + ]; + + for (key, value) in cfg_pairs { + spawn_args.push("--config".to_string()); + spawn_args.push(format!("{key}={value}")); + } + + let extra_rust_flags = String::from( + "-C target-feature=+bulk-memory,+wide-arithmetic -C link-args=--fatal-warnings", + ); + let maybe_old_rustflags = match std::env::var("RUSTFLAGS") { + Ok(current) if !current.is_empty() => { + std::env::set_var("RUSTFLAGS", format!("{current} {extra_rust_flags}")); + Some(current) + } + _ => { + std::env::set_var("RUSTFLAGS", extra_rust_flags); + None + } + }; + + let wasi = match target_env { + TargetEnv::Rollup { .. } => "wasip2", + _ => "wasip1", + }; + + let wasm_outputs = run_cargo(wasi, &cargo_args, &spawn_args)?; + + if let Some(old_rustflags) = maybe_old_rustflags { + std::env::set_var("RUSTFLAGS", old_rustflags); + } else { + std::env::remove_var("RUSTFLAGS"); + } + + assert_eq!(wasm_outputs.len(), 1, "expected only one Wasm artifact"); + let wasm_output = wasm_outputs.first().expect("expected at least one Wasm artifact"); + + let mut midenc_flags = midenc_flags_from_target(target_env, project_type, wasm_output); + + for dep_path in dependency_packages_paths { + midenc_flags.push("--link-library".to_string()); + midenc_flags.push(dep_path.to_string_lossy().to_string()); + } + + match build_output_type { + OutputType::Wasm => Ok(Some(CommandOutput::BuildCommandOutput { + output: BuildOutput::Wasm { + artifact_path: wasm_output.clone(), + midenc_flags, + }, + })), + OutputType::Masm => { + let metadata_out_dir = + metadata.target_directory.join("miden").join(if cargo_args.release { + "release" + } else { + "debug" + }); + if !metadata_out_dir.exists() { + std::fs::create_dir_all(&metadata_out_dir)?; + } + + let output = compile_masm::wasm_to_masm( + wasm_output, + metadata_out_dir.as_std_path(), + midenc_flags, + ) + .map_err(|e| anyhow::anyhow!("{e}"))?; + + Ok(Some(CommandOutput::BuildCommandOutput { + output: BuildOutput::Masm { + artifact_path: output, + }, + })) + } + } + } +} + +fn run_cargo( + wasi: &str, + cargo_args: &CargoArguments, + spawn_args: &[String], +) -> Result> { + let cargo_path = std::env::var("CARGO") + .map(PathBuf::from) + .ok() + .unwrap_or_else(|| PathBuf::from("cargo")); + + let mut cargo = Command::new(&cargo_path); + cargo.args(spawn_args); + + // Handle the target for buildable commands + install_wasm32_target(wasi)?; + + cargo.arg("--target").arg(format!("wasm32-{wasi}")); + + if let Some(format) = &cargo_args.message_format { + if format != "json-render-diagnostics" { + bail!("unsupported cargo message format `{format}`"); + } + } + + // It will output the message as json so we can extract the wasm files + // that will be componentized + cargo.arg("--message-format").arg("json-render-diagnostics"); + cargo.stdout(Stdio::piped()); + + let artifacts = spawn_cargo(cargo, &cargo_path, cargo_args, true)?; + + let outputs: Vec = artifacts + .into_iter() + .filter_map(|a| { + let path: PathBuf = a.filenames.first().unwrap().clone().into(); + if path.to_str().unwrap().contains("wasm32-wasip") { + Some(path) + } else { + None + } + }) + .collect(); + + Ok(outputs) +} + +pub fn spawn_cargo( + mut cmd: Command, + cargo: &Path, + cargo_args: &CargoArguments, + process_messages: bool, +) -> Result> { + log::debug!("spawning command {cmd:?}"); + + let mut child = cmd + .spawn() + .context(format!("failed to spawn `{cargo}`", cargo = cargo.display()))?; + + let mut artifacts = Vec::new(); + if process_messages { + let stdout = child.stdout.take().expect("no stdout"); + let reader = BufReader::new(stdout); + for line in reader.lines() { + let line = line.context("failed to read output from `cargo`")?; + + // If the command line arguments also had `--message-format`, echo the line + if cargo_args.message_format.is_some() { + println!("{line}"); + } + + if line.is_empty() { + continue; + } + + for message in Message::parse_stream(line.as_bytes()) { + if let Message::CompilerArtifact(artifact) = + message.context("unexpected JSON message from cargo")? + { + for path in &artifact.filenames { + match path.extension() { + Some("wasm") => { + artifacts.push(artifact); + break; + } + _ => continue, + } + } + } + } + } + } + + let status = child + .wait() + .context(format!("failed to wait for `{cargo}` to finish", cargo = cargo.display()))?; + + if !status.success() { + std::process::exit(status.code().unwrap_or(1)); + } + + Ok(artifacts) +} + +fn determine_cargo_package<'a>( + metadata: &'a cargo_metadata::Metadata, + cargo_args: &CargoArguments, +) -> Result<&'a cargo_metadata::Package> { + let package = if let Some(manifest_path) = cargo_args.manifest_path.as_deref() { + let mp_utf8 = camino::Utf8Path::from_path(manifest_path).ok_or_else(|| { + anyhow::anyhow!("manifest path is not valid UTF-8: {}", manifest_path.display()) + })?; + let mp_abs = mp_utf8 + .as_std_path() + .absolutize() + .map_err(|e| { + anyhow::anyhow!( + "failed to absolutize manifest path {}: {e}", + manifest_path.display() + ) + })? + .into_owned(); + metadata + .packages + .iter() + .find(|p| p.manifest_path.as_std_path() == mp_abs.as_path()) + .ok_or_else(|| { + anyhow::anyhow!( + "unable to determine package: manifest `{}` does not match any workspace \ + package", + manifest_path.display() + ) + })? + } else { + let cwd = std::env::current_dir()?; + metadata + .packages + .iter() + .find(|p| p.manifest_path.parent().map(|d| d.as_std_path()) == Some(cwd.as_path())) + .ok_or_else(|| { + anyhow::anyhow!( + "unable to determine package; run inside a member directory or pass `-p \ + ` / `--manifest-path `" + ) + })? + }; + Ok(package) +} + +/// Produces the `midenc` CLI flags implied by the detected target environment and project type. +fn midenc_flags_from_target( + target_env: TargetEnv, + project_type: midenc_session::ProjectType, + wasm_output: &Path, +) -> Vec { + let mut midenc_args = Vec::new(); + + match target_env { + TargetEnv::Base | TargetEnv::Emu => match project_type { + midenc_session::ProjectType::Program => { + midenc_args.push("--exe".into()); + let masm_module_name = wasm_output + .file_stem() + .expect("invalid wasm file path: no file stem") + .to_str() + .unwrap(); + let entrypoint_opt = format!("--entrypoint={masm_module_name}::entrypoint"); + midenc_args.push(entrypoint_opt); + } + midenc_session::ProjectType::Library => midenc_args.push("--lib".into()), + }, + TargetEnv::Rollup { target } => { + midenc_args.push("--target".into()); + match target { + midenc_session::RollupTarget::Account => { + midenc_args.push("rollup:account".into()); + midenc_args.push("--lib".into()); + } + midenc_session::RollupTarget::NoteScript => { + midenc_args.push("rollup:note-script".into()); + midenc_args.push("--exe".into()); + midenc_args.push("--entrypoint=miden:base/note-script@1.0.0::run".to_string()) + } + midenc_session::RollupTarget::TransactionScript => { + midenc_args.push("rollup:transaction-script".into()); + midenc_args.push("--exe".into()); + midenc_args + .push("--entrypoint=miden:base/transaction-script@1.0.0::run".to_string()) + } + midenc_session::RollupTarget::AuthComponent => { + midenc_args.push("rollup:authentication-component".into()); + midenc_args.push("--lib".into()); + } + } + } + } + midenc_args +} + +/// Loads the workspace metadata based on the given manifest path. +fn load_metadata(manifest_path: Option<&Path>) -> Result { + let mut command = MetadataCommand::new(); + command.no_deps(); + + if let Some(path) = manifest_path { + log::debug!("loading metadata from manifest `{path}`", path = path.display()); + command.manifest_path(path); + } else { + log::debug!("loading metadata from current directory"); + } + + command.exec().context("failed to load cargo metadata") +} + +/// Loads the component metadata for the given package specs. +/// +/// If `workspace` is true, all workspace packages are loaded. +fn load_component_metadata<'a>( + metadata: &'a Metadata, + specs: impl ExactSizeIterator, + workspace: bool, +) -> Result>> { + let pkgs = if workspace { + metadata.workspace_packages() + } else if specs.len() > 0 { + let mut pkgs = Vec::with_capacity(specs.len()); + for spec in specs { + let pkg = metadata + .packages + .iter() + .find(|p| { + p.name == spec.name + && match spec.version.as_ref() { + Some(v) => &p.version == v, + None => true, + } + }) + .with_context(|| { + format!("package ID specification `{spec}` did not match any packages") + })?; + pkgs.push(pkg); + } + + pkgs + } else { + metadata.workspace_default_packages() + }; + + pkgs.into_iter().map(PackageComponentMetadata::new).collect::>() +} + +/// Represents a cargo package paired with its component metadata. +#[derive(Debug)] +pub struct PackageComponentMetadata<'a> { + /// The cargo package. + pub package: &'a Package, +} + +impl<'a> PackageComponentMetadata<'a> { + /// Creates a new package metadata from the given package. + pub fn new(package: &'a Package) -> Result { + Ok(Self { package }) + } +} diff --git a/tools/cargo-miden/src/commands/example_project.rs b/tools/cargo-miden/src/commands/example_project.rs new file mode 100644 index 000000000..10533bb6c --- /dev/null +++ b/tools/cargo-miden/src/commands/example_project.rs @@ -0,0 +1,361 @@ +use std::path::{Path, PathBuf}; + +use anyhow::Context; +use cargo_generate::{GenerateArgs, TemplatePath}; +use clap::Args; +use toml_edit::{DocumentMut, Item}; + +use crate::utils::compiler_path; + +/// Paired project mappings for examples that create multiple related projects +const PAIRED_PROJECTS: &[(&str, &str)] = &[("counter-contract", "counter-note")]; + +/// Triple project mappings for examples that create three related projects +/// Each tuple contains (tx-script, account, note) project names +/// Any of these names can be used to create all three projects +const TRIPLE_PROJECTS: &[(&str, &str, &str)] = + &[("basic-wallet-tx-script", "basic-wallet", "p2id-note")]; + +/// Create a new Miden example project +#[derive(Debug, Args)] +#[clap(disable_version_flag = true)] +pub struct ExampleCommand { + #[clap(help = r#"The example name to use from the compiler repository +Available examples: +basic-wallet : Basic wallet account +p2id-note : Pay-to-ID note +basic-wallet-tx-script: Transaction script used in basic-wallet and p2id-note +counter-contract : Counter contract +counter-note : Counter note +fibonacci : Fibonacci sequence calculator +collatz : Collatz conjecture calculator +is-prime : Prime number checker +storage-example : Storage operations example"#)] + pub example_name: String, +} + +use std::fs; + +impl ExampleCommand { + pub fn exec(self) -> anyhow::Result { + // Check if this is a triple project - any of the three names can be used + if let Some((tx_script, account, note)) = + TRIPLE_PROJECTS.iter().find(|(tx_script, account, note)| { + *tx_script == self.example_name + || *account == self.example_name + || *note == self.example_name + }) + { + // Always use the tx-script name as the main directory name + self.exec_triple_projects(tx_script, account, note) + } else if let Some((first, second)) = PAIRED_PROJECTS + .iter() + .find(|(first, second)| *first == self.example_name || *second == self.example_name) + { + self.exec_paired_projects(first, second) + } else { + self.exec_single_project() + } + } + + fn exec_single_project(&self) -> anyhow::Result { + // Use example name as project name + let project_name = self.example_name.clone(); + let project_path = PathBuf::from(&project_name); + + // Check if directory already exists + if project_path.exists() { + return Err(anyhow::anyhow!( + "Directory '{}' already exists. Please remove it or choose a different location.", + project_name + )); + } + + let template_path = template_path(&project_name); + + // Generate in current directory + let destination = { + use path_absolutize::Absolutize; + std::env::current_dir()?.absolutize().map(|p| p.to_path_buf())? + }; + + let generate_args = GenerateArgs { + template_path, + destination: Some(destination), + name: Some(project_name.clone()), + // Force the `name` to not be kebab-cased + force: true, + force_git_init: true, + verbose: true, + ..Default::default() + }; + cargo_generate::generate(generate_args) + .context("Failed to scaffold new Miden project from the template")?; + + // Process the Cargo.toml to update dependencies and WIT paths + process_cargo_toml(&project_path).context("Failed to process Cargo.toml")?; + + Ok(project_path) + } + + /// Create a pair (account and note script) projects in a sub-folder + fn exec_paired_projects( + &self, + first_project: &str, + second_project: &str, + ) -> anyhow::Result { + // Create main directory with the requested example name + let main_dir = PathBuf::from(&self.example_name); + + if main_dir.exists() { + return Err(anyhow::anyhow!( + "Directory '{}' already exists. Please remove it or choose a different location.", + self.example_name + )); + } + + // Create the main directory + fs::create_dir_all(&main_dir)?; + + // Generate both projects + let project_names = [first_project, second_project]; + for project_name in &project_names { + let template_path = template_path(project_name); + + let destination = { + use path_absolutize::Absolutize; + main_dir.absolutize().map(|p| p.to_path_buf())? + }; + + let generate_args = GenerateArgs { + template_path, + destination: Some(destination), + name: Some(project_name.to_string()), + force: true, + force_git_init: false, // Don't init git for subdirectories + verbose: true, + ..Default::default() + }; + + cargo_generate::generate(generate_args) + .context(format!("Failed to scaffold {project_name} project"))?; + + let project_path = main_dir.join(project_name); + + // Process the Cargo.toml + process_cargo_toml(&project_path) + .context(format!("Failed to process Cargo.toml for {project_name}"))?; + } + + // Update dependencies for paired projects + if first_project == "counter-contract" && second_project == "counter-note" { + update_project_dependency( + &main_dir, + "counter-note", + "miden:counter-contract", + "counter-contract", + "counter.wit", + )?; + } + + Ok(main_dir) + } + + /// Create a triple (tx-script, account and note script) projects in a sub-folder + fn exec_triple_projects( + &self, + tx_script: &str, + account: &str, + note: &str, + ) -> anyhow::Result { + // Create main directory with the requested example name + let main_dir = PathBuf::from(&self.example_name); + + if main_dir.exists() { + return Err(anyhow::anyhow!( + "Directory '{}' already exists. Please remove it or choose a different location.", + self.example_name + )); + } + + // Create the main directory + fs::create_dir_all(&main_dir)?; + + // Generate all three projects + let project_names = [tx_script, account, note]; + for project_name in &project_names { + let template_path = template_path(project_name); + + let destination = { + use path_absolutize::Absolutize; + main_dir.absolutize().map(|p| p.to_path_buf())? + }; + + let generate_args = GenerateArgs { + template_path, + destination: Some(destination), + name: Some(project_name.to_string()), + force: true, + force_git_init: false, // Don't init git for subdirectories + verbose: true, + ..Default::default() + }; + + cargo_generate::generate(generate_args) + .context(format!("Failed to scaffold {project_name} project"))?; + + let project_path = main_dir.join(project_name); + + // Process the Cargo.toml + process_cargo_toml(&project_path) + .context(format!("Failed to process Cargo.toml for {project_name}"))?; + } + + // Update dependencies for triple projects + update_triple_project_dependencies(&main_dir, tx_script, account, note)?; + + Ok(main_dir) + } +} + +/// Update dependencies for triple projects in a generic way +fn update_triple_project_dependencies( + main_dir: &Path, + tx_script: &str, + account: &str, + note: &str, +) -> anyhow::Result<()> { + // Use the actual WIT file name (keep hyphens) + let account_wit = format!("{account}.wit"); + + // Update note to depend on account + update_project_dependency(main_dir, note, &format!("miden:{account}"), account, &account_wit)?; + + // Update tx script to depend on account + update_project_dependency( + main_dir, + tx_script, + &format!("miden:{account}"), + account, + &account_wit, + )?; + + Ok(()) +} + +/// Process the generated Cargo.toml to update dependencies and WIT paths. +/// The projects in `example` folder set Miden SDK dependencies as local paths. +/// After copying we switch them to a git dependency (Miden SDK crate) and drop +/// the explicit `miden:base` WIT dependency since the SDK prelude is injected +/// automatically by the `miden::generate!` macro. +fn process_cargo_toml(project_path: &Path) -> anyhow::Result<()> { + let cargo_toml_path = project_path.join("Cargo.toml"); + let content = fs::read_to_string(&cargo_toml_path)?; + let mut doc = content.parse::()?; + + // Update miden dependency to use git repository + if let Some(deps) = doc.get_mut("dependencies").and_then(|d| d.as_table_mut()) { + if let Some(miden_dep) = deps.get_mut("miden") { + *miden_dep = Item::Value(toml_edit::Value::InlineTable({ + let mut table = toml_edit::InlineTable::new(); + if cfg!(test) || std::env::var("TEST").is_ok() { + table.insert("path", compiler_path().join("sdk/sdk").to_str().unwrap().into()); + } else { + table.insert("git", "https://github.com/0xMiden/compiler".into()); + } + + table + })); + } + } + + // Write the updated Cargo.toml + fs::write(&cargo_toml_path, doc.to_string())?; + Ok(()) +} + +/// Update a project's dependencies to use another local project +/// This is used when one project in a pair/triple depends on another +fn update_project_dependency( + main_dir: &Path, + note_dir: &str, + dependency_name: &str, + contract_dir: &str, + wit_file_name: &str, +) -> anyhow::Result<()> { + let note_cargo_toml = main_dir.join(note_dir).join("Cargo.toml"); + let content = fs::read_to_string(¬e_cargo_toml)?; + let mut doc = content.parse::()?; + + // Update miden dependency to use local path + if let Some(miden_deps) = doc + .get_mut("package") + .and_then(|p| p.as_table_mut()) + .and_then(|t| t.get_mut("metadata")) + .and_then(|m| m.as_table_mut()) + .and_then(|t| t.get_mut("miden")) + .and_then(|m| m.as_table_mut()) + .and_then(|t| t.get_mut("dependencies")) + .and_then(|d| d.as_table_mut()) + { + if let Some(dep) = miden_deps.get_mut(dependency_name) { + *dep = Item::Value(toml_edit::Value::InlineTable({ + let mut table = toml_edit::InlineTable::new(); + table.insert("path", format!("../{contract_dir}").into()); + table + })); + } + } + + // Update WIT file dependency to use local contract + if let Some(wit_deps) = doc + .get_mut("package") + .and_then(|p| p.as_table_mut()) + .and_then(|t| t.get_mut("metadata")) + .and_then(|m| m.as_table_mut()) + .and_then(|t| t.get_mut("component")) + .and_then(|c| c.as_table_mut()) + .and_then(|t| t.get_mut("target")) + .and_then(|t| t.as_table_mut()) + .and_then(|t| t.get_mut("dependencies")) + .and_then(|d| d.as_table_mut()) + { + if let Some(wit_dep) = wit_deps.get_mut(dependency_name) { + if let Some(table) = wit_dep.as_inline_table_mut() { + if let Some(path_value) = table.get_mut("path") { + let path = path_value.to_string(); + *path_value = if path.contains("target/generated-wit") { + toml_edit::Value::from(format!("../{contract_dir}/target/generated-wit/")) + } else { + toml_edit::Value::from(format!("../{contract_dir}/wit/{wit_file_name}")) + }; + } + } + } + } + + fs::write(¬e_cargo_toml, doc.to_string())?; + Ok(()) +} + +fn template_path(project_name: &str) -> TemplatePath { + if cfg!(test) || std::env::var("TEST").is_ok() { + TemplatePath { + path: Some( + compiler_path() + .join("examples") + .join(project_name) + .to_str() + .unwrap() + .to_string(), + ), + ..Default::default() + } + } else { + TemplatePath { + git: Some("https://github.com/0xMiden/compiler".into()), + auto_path: Some(format!("examples/{project_name}")), + ..Default::default() + } + } +} diff --git a/tools/cargo-miden/src/commands/mod.rs b/tools/cargo-miden/src/commands/mod.rs new file mode 100644 index 000000000..a0cd0ce0e --- /dev/null +++ b/tools/cargo-miden/src/commands/mod.rs @@ -0,0 +1,7 @@ +pub mod build; +pub mod example_project; +pub mod new_project; + +pub use build::BuildCommand; +pub use example_project::ExampleCommand; +pub use new_project::NewCommand; diff --git a/tools/cargo-miden/src/commands/new_project.rs b/tools/cargo-miden/src/commands/new_project.rs new file mode 100644 index 000000000..b09427962 --- /dev/null +++ b/tools/cargo-miden/src/commands/new_project.rs @@ -0,0 +1,259 @@ +use std::{ + fmt, + path::{Path, PathBuf}, +}; + +use anyhow::Context; +use cargo_generate::{GenerateArgs, TemplatePath}; +use clap::Args; + +/// The tag used in checkout of the new project template. +/// +/// Before changing it make sure the new tag exists in the rust-templates repo and points to the +/// desired commit. +const TEMPLATES_REPO_TAG: &str = "v0.22.0"; + +// This should have been an enum but I could not bend `clap` to expose variants as flags +/// Project template +#[derive(Clone, Debug, Args)] +pub struct ProjectTemplate { + /// Rust program + #[clap(long, group = "template", conflicts_with_all(["account", "note", "tx_script", "auth_component"]))] + program: bool, + /// Miden rollup account + #[clap(long, group = "template", conflicts_with_all(["program", "note", "tx_script", "auth_component"]))] + account: bool, + /// Miden rollup note script + #[clap(long, group = "template", conflicts_with_all(["program", "account", "tx_script", "auth_component"]))] + note: bool, + /// Miden rollup transaction script + #[clap(long, group = "template", conflicts_with_all(["program", "account", "note", "auth_component"]))] + tx_script: bool, + /// Miden rollup authentication component + #[clap(long, group = "template", conflicts_with_all(["program", "account", "note", "tx_script"]))] + auth_component: bool, +} + +#[allow(unused)] +impl ProjectTemplate { + pub fn program() -> Self { + Self { + program: true, + account: false, + note: false, + tx_script: false, + auth_component: false, + } + } + + pub fn account() -> Self { + Self { + program: false, + account: true, + note: false, + tx_script: false, + auth_component: false, + } + } + + pub fn note() -> Self { + Self { + program: false, + account: false, + note: true, + tx_script: false, + auth_component: false, + } + } + + pub fn tx_script() -> Self { + Self { + program: false, + account: false, + note: false, + tx_script: true, + auth_component: false, + } + } + + pub fn auth_component() -> Self { + Self { + program: false, + account: false, + note: false, + tx_script: false, + auth_component: true, + } + } +} + +impl Default for ProjectTemplate { + fn default() -> Self { + Self::account() + } +} + +impl fmt::Display for ProjectTemplate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if self.program { + write!(f, "program") + } else if self.account { + write!(f, "account") + } else if self.note { + write!(f, "note") + } else if self.tx_script { + write!(f, "tx-script") + } else if self.auth_component { + write!(f, "auth-component") + } else { + panic!("Invalid project template, at least one variant must be set") + } + } +} + +/// Create a new clean slate Miden project at +#[derive(Debug, Args)] +#[clap(disable_version_flag = true)] +pub struct NewCommand { + /// The pash for the generated project + #[clap()] + pub path: PathBuf, + /// The template name to use to generate the package + #[clap(flatten)] + pub template: Option, + /// The path to the template to use to generate the project + #[clap(long, conflicts_with("template"))] + pub template_path: Option, + /// Use a locally cloned compiler in the generated package + #[clap(long, hide(true), conflicts_with_all(["compiler_rev", "compiler_branch"]))] + pub compiler_path: Option, + /// Use a specific revision of the compiler in the generated package + #[clap(long, hide(true), conflicts_with("compiler_branch"))] + pub compiler_rev: Option, + /// Use a specific branch of the compiler in the generated package + #[clap(long, hide(true))] + pub compiler_branch: Option, +} + +use crate::utils::set_default_test_compiler; + +impl NewCommand { + pub fn exec(self) -> anyhow::Result { + let name = self + .path + .file_name() + .ok_or_else(|| { + anyhow::anyhow!( + "Failed to get the last segment of the provided path for the project name" + ) + })? + .to_str() + .ok_or_else(|| { + anyhow::anyhow!( + "The last segment of the provided path must be valid UTF8 to generate a valid \ + project name" + ) + })? + .to_string(); + + let mut define = vec![]; + if let Some(compiler_path) = self.compiler_path.as_deref() { + define.push(format!("compiler_path={}", compiler_path.display())); + } + if let Some(compiler_rev) = self.compiler_rev.as_deref() { + define.push(format!("compiler_rev={compiler_rev}")); + } + if let Some(compiler_branch) = self.compiler_branch.as_deref() { + define.push(format!("compiler_branch={compiler_branch}")); + } + + // If we're running the test suite, and no specific options have been provided for what + // compiler to use - specify the path to current compiler directory + if cfg!(test) || std::env::var("TEST").is_ok() { + let use_local_compiler = self.compiler_path.is_none() + && self.compiler_rev.is_none() + && self.compiler_branch.is_none(); + if use_local_compiler { + set_default_test_compiler(&mut define); + } + } + + let template_path = match self.template_path.as_ref() { + Some(template_path) => TemplatePath { + path: Some(template_path.display().to_string()), + ..Default::default() + }, + None => match self.template.as_ref() { + Some(project_template) => TemplatePath { + git: Some("https://github.com/0xMiden/rust-templates".into()), + tag: Some(TEMPLATES_REPO_TAG.into()), + auto_path: Some(project_template.to_string()), + ..Default::default() + }, + None => TemplatePath { + git: Some("https://github.com/Keinberger/miden-project-environment".into()), + tag: None, + ..Default::default() + }, + }, + }; + + let destination = self + .path + .parent() + .map(|p| { + use path_absolutize::Absolutize; + p.absolutize().map(|p| p.to_path_buf()) + }) + .transpose() + .context("Failed to convert destination path to an absolute path")?; + // Determine whether we should initialize a new Git repository. + // If the destination directory (where the new project directory will be created) + // is already inside a Git repository, avoid running `git init` to prevent creating + // a nested repo. + let should_git_init = { + // Resolve the directory where the project will be created (destination root). + // Use a concrete PathBuf to avoid lifetime issues. + let dest_root: PathBuf = match &destination { + Some(dest) => dest.clone(), + None => { + // Fall back to current directory; cargo-generate will create a subdir here. + std::env::current_dir()? + } + }; + !is_inside_git_repo(&dest_root) + }; + + let generate_args = GenerateArgs { + template_path, + destination, + name: Some(name), + // Force the `name` to not be kebab-cased + force: true, + force_git_init: should_git_init, + verbose: true, + define, + ..Default::default() + }; + let _project_path = cargo_generate::generate(generate_args) + .context("Failed to scaffold new Miden project from the template")?; + + Ok(self.path) + } +} + +/// Returns true if `path` is inside an existing Git repository. +/// +/// This checks for a `.git` directory or file in `path` or any of its ancestor +/// directories. A `.git` file is used by worktrees/submodules and should be treated +/// as an indicator of a Git repository as well. +fn is_inside_git_repo(path: &Path) -> bool { + // Walk up the directory tree from `path` to the filesystem root. + for ancestor in path.ancestors() { + let git_marker = ancestor.join(".git"); + if git_marker.exists() { + return true; + } + } + false +} diff --git a/tools/cargo-miden/src/compile_masm.rs b/tools/cargo-miden/src/compile_masm.rs new file mode 100644 index 000000000..1709d9d2e --- /dev/null +++ b/tools/cargo-miden/src/compile_masm.rs @@ -0,0 +1,53 @@ +use std::{ + path::{Path, PathBuf}, + rc::Rc, +}; + +use midenc_compile::{Compiler, Context}; +use midenc_session::{ + diagnostics::{IntoDiagnostic, Report, WrapErr}, + InputFile, OutputType, +}; + +pub fn wasm_to_masm( + wasm_file_path: &Path, + output_folder: &Path, + mut midenc_args: Vec, +) -> Result { + if !output_folder.exists() { + return Err(Report::msg(format!( + "MASM output folder '{}' does not exist.", + output_folder.to_str().unwrap() + ))); + } + log::debug!( + "Compiling '{}' Wasm to '{}' directory with midenc ...", + wasm_file_path.to_str().unwrap(), + &output_folder.to_str().unwrap() + ); + let input = InputFile::from_path(wasm_file_path) + .into_diagnostic() + .wrap_err("Invalid input file")?; + let masm_file_name = wasm_file_path + .file_stem() + .expect("invalid wasm file path: no file stem") + .to_str() + .unwrap(); + let output_file = + output_folder.join(masm_file_name).with_extension(OutputType::Masp.extension()); + + let mut args: Vec = vec![ + "--output-dir".to_string(), + output_folder.to_str().unwrap().to_string(), + "-o".to_string(), + output_file.to_str().unwrap().to_string(), + "--verbose".to_string(), + ]; + args.append(&mut midenc_args); + + let session = Rc::new(Compiler::new_session([input], None, args)); + let context = Rc::new(Context::new(session)); + println!("Creating Miden package {}", output_file.display()); + midenc_compile::compile(context.clone())?; + Ok(output_file) +} diff --git a/tools/cargo-miden/src/config.rs b/tools/cargo-miden/src/config.rs index 593416639..5a6030301 100644 --- a/tools/cargo-miden/src/config.rs +++ b/tools/cargo-miden/src/config.rs @@ -1,4 +1,4 @@ -//! Module for cargo-miden configuration. +//! Module for cargo-component configuration. //! //! This implements an argument parser because `clap` is not //! designed for parsing unknown or unsupported arguments. @@ -10,20 +10,22 @@ //! detect certain arguments, but not error out if the arguments //! are otherwise unknown as they will be passed to `cargo` directly. //! -//! This will allow `cargo-miden` to be used as a drop-in +//! This will allow `cargo-component` to be used as a drop-in //! replacement for `cargo` without having to be fully aware of //! the many subcommands and options that `cargo` supports. //! //! What is detected here is the minimal subset of the arguments -//! that `cargo` supports which are necessary for `cargo-miden` +//! that `cargo` supports which are necessary for `cargo-component` //! to function. use std::{collections::BTreeMap, fmt, fmt::Display, path::PathBuf, str::FromStr}; use anyhow::{anyhow, bail, Context, Result}; -use cargo_component_core::terminal::{Color, Terminal}; +use cargo_metadata::Metadata; +use midenc_session::ColorChoice; use parse_arg::{iter_short, match_arg}; use semver::Version; +use toml_edit::DocumentMut; /// Represents a cargo package specifier. /// @@ -61,6 +63,23 @@ impl CargoPackageSpec { }, }) } + + /// Loads Cargo.toml in the current directory, attempts to find the matching package from metadata. + #[allow(unused)] + pub fn find_current_package_spec(metadata: &Metadata) -> Option { + let current_manifest = std::fs::read_to_string("Cargo.toml").ok()?; + let document: DocumentMut = current_manifest.parse().ok()?; + let name = document["package"]["name"].as_str()?; + let version = metadata + .packages + .iter() + .find(|found| found.name == name) + .map(|found| found.version.clone()); + Some(CargoPackageSpec { + name: name.to_string(), + version, + }) + } } impl FromStr for CargoPackageSpec { @@ -340,19 +359,23 @@ impl Args { /// Represents known cargo arguments. /// /// This is a subset of the arguments that cargo supports that -/// are necessary for cargo-miden to function. +/// are necessary for cargo-component to function. #[derive(Default, Debug, Clone, Eq, PartialEq)] pub struct CargoArguments { /// The --color argument. - pub color: Option, + pub color: Option, /// The (count of) --verbose argument. pub verbose: usize, + /// The --help argument. + pub help: bool, /// The --quiet argument. pub quiet: bool, /// The --target argument. pub targets: Vec, /// The --manifest-path argument. pub manifest_path: Option, + /// The `--message-format`` argument. + pub message_format: Option, /// The --frozen argument. pub frozen: bool, /// The --locked argument. @@ -369,16 +392,19 @@ pub struct CargoArguments { impl CargoArguments { /// Determines if network access is allowed based on the configuration. + #[allow(unused)] pub fn network_allowed(&self) -> bool { !self.frozen && !self.offline } /// Determines if an update to the lock file is allowed based on the configuration. + #[allow(unused)] pub fn lock_update_allowed(&self) -> bool { !self.frozen && !self.locked } /// Parses the arguments from the environment. + #[allow(unused)] pub fn parse() -> Result { Self::parse_from(std::env::args().skip(1)) } @@ -391,6 +417,7 @@ impl CargoArguments { let mut args = Args::default() .single("--color", "WHEN", Some('c')) .single("--manifest-path", "PATH", None) + .single("--message-format", "FMT", None) .multiple("--package", "SPEC", Some('p')) .multiple("--target", "TRIPLE", None) .flag("--release", Some('r')) @@ -400,13 +427,14 @@ impl CargoArguments { .flag("--all", None) .flag("--workspace", None) .counting("--verbose", Some('v')) - .flag("--quiet", Some('q')); + .flag("--quiet", Some('q')) + .flag("--help", Some('h')); let mut iter = iter.map(Into::into).peekable(); - // Skip the first argument if it is `miden` + // Skip the first argument if it is `component` if let Some(arg) = iter.peek() { - if arg == "miden" { + if arg == "component" { iter.next().unwrap(); } } @@ -426,12 +454,14 @@ impl CargoArguments { Ok(Self { color: args.get_mut("--color").unwrap().take_single().map(|v| v.parse()).transpose()?, verbose: args.get("--verbose").unwrap().count(), + help: args.get("--help").unwrap().count() > 0, quiet: args.get("--quiet").unwrap().count() > 0, manifest_path: args .get_mut("--manifest-path") .unwrap() .take_single() .map(PathBuf::from), + message_format: args.get_mut("--message-format").unwrap().take_single(), targets: args.get_mut("--target").unwrap().take_multiple(), frozen: args.get("--frozen").unwrap().count() > 0, locked: args.get("--locked").unwrap().count() > 0, @@ -450,27 +480,6 @@ impl CargoArguments { } } -/// Configuration information for cargo-miden. -/// -/// This is used to configure the behavior of cargo-miden. -#[derive(Debug)] -pub struct Config { - /// The terminal to use. - terminal: Terminal, -} - -impl Config { - /// Create a new `Config` with the given terminal. - pub fn new(terminal: Terminal) -> Result { - Ok(Self { terminal }) - } - - /// Gets a reference to the terminal for writing messages. - pub fn terminal(&self) -> &Terminal { - &self.terminal - } -} - #[cfg(test)] mod test { use std::iter::empty; @@ -690,7 +699,7 @@ mod test { fn it_parses_counting_flag() { let mut args = Args::default().counting("--flag", Some('f')); - // Test not the flag + // Test not the the flag args.parse("--not-flag", &mut empty::()).unwrap(); let arg = args.get("--flag").unwrap(); assert_eq!(arg.count(), 0); @@ -717,15 +726,17 @@ mod test { #[test] fn it_parses_cargo_arguments() { let args: CargoArguments = - CargoArguments::parse_from(["miden", "build", "--workspace"].into_iter()).unwrap(); + CargoArguments::parse_from(["component", "build", "--workspace"].into_iter()).unwrap(); assert_eq!( args, CargoArguments { color: None, verbose: 0, + help: false, quiet: false, targets: Vec::new(), manifest_path: None, + message_format: None, release: false, frozen: false, locked: false, @@ -737,12 +748,15 @@ mod test { let args = CargoArguments::parse_from( [ - "miden", + "component", "publish", + "--help", "-vvv", "--color=auto", "--manifest-path", "Cargo.toml", + "--message-format", + "json-render-diagnostics", "--release", "--package", "package1", @@ -763,11 +777,13 @@ mod test { assert_eq!( args, CargoArguments { - color: Some(Color::Auto), + color: Some(ColorChoice::Auto), verbose: 3, + help: true, quiet: true, targets: vec!["foo".to_string(), "bar".to_string()], manifest_path: Some("Cargo.toml".into()), + message_format: Some("json-render-diagnostics".into()), release: true, frozen: true, locked: true, diff --git a/tools/cargo-miden/src/dependencies.rs b/tools/cargo-miden/src/dependencies.rs new file mode 100644 index 000000000..5d987d0ec --- /dev/null +++ b/tools/cargo-miden/src/dependencies.rs @@ -0,0 +1,224 @@ +use std::{ + collections::{HashMap, HashSet}, + fs, + path::PathBuf, +}; + +use anyhow::{anyhow, bail, Context, Result}; +use cargo_metadata::{camino, Package}; +use serde::Deserialize; + +use crate::{config::CargoArguments, BuildOutput, OutputType}; + +/// Defines dependency (the rhs of the dependency `"ns:package" = { path = "..." }` pair) +#[derive(Deserialize, Debug, Clone)] +#[serde(deny_unknown_fields)] +struct MidenDependencyInfo { + /// Local path to the cargo-miden project that produces Miden package or Miden package `.masp` file + path: PathBuf, +} + +/// Representation for [package.metadata.miden] +#[derive(Deserialize, Debug, Default)] +struct MidenMetadata { + #[serde(default)] + dependencies: HashMap, +} + +/// Processes Miden dependencies defined in `[package.metadata.miden.dependencies]` +/// for the given package. +/// +/// This involves finding dependency projects, recursively building them if necessary, +/// and collecting the paths to the resulting `.masp` package artifacts. +pub fn process_miden_dependencies( + package: &Package, + cargo_args: &CargoArguments, +) -> Result> { + let mut dependency_packages_paths: Vec = Vec::new(); + // Avoid redundant builds/checks + let mut processed_dep_paths: HashSet = HashSet::new(); + + log::debug!("Processing Miden dependencies for package '{}'...", package.name); + + // Get the manifest directory from the package + let manifest_path = &package.manifest_path; + let manifest_dir = manifest_path + .parent() + .with_context(|| format!("Failed to get parent directory for manifest: {manifest_path}"))?; + + // Extract Miden metadata using serde_json + let miden_metadata: MidenMetadata = package + .metadata + .get("miden") + .cloned() + .map(serde_json::from_value) + .transpose() + .context("Failed to deserialize [package.metadata.miden]")? + .unwrap_or_default(); + + let dependencies = miden_metadata.dependencies; + + if !dependencies.is_empty() { + log::debug!(" Found dependencies defined in {manifest_path}"); + + for (dep_name, dep_info) in &dependencies { + let relative_path = &dep_info.path; + // Resolve relative to the *dependency declaring* manifest's directory + let utf8_relative_path = match camino::Utf8PathBuf::from_path_buf(relative_path.clone()) + { + Ok(p) => p, + Err(e) => { + bail!( + "Dependency path for '{}' is not valid UTF-8 ({}): {}", + dep_name, + relative_path.display(), + e.to_path_buf().display() + ); + } + }; + let dep_path = manifest_dir.join(&utf8_relative_path); + + let absolute_dep_path = + fs::canonicalize(dep_path.as_std_path()).with_context(|| { + format!("resolving dependency path for '{dep_name}' ({dep_path})") + })?; + + // Skip if we've already processed this exact path + if processed_dep_paths.contains(&absolute_dep_path) { + // Check if the artifact path is already collected, add if not + if dependency_packages_paths.contains(&absolute_dep_path) { + // Already in the list, nothing to do. + } else { + // If it was processed but is a valid .masp file, ensure it's in the final list + if absolute_dep_path.is_file() + && absolute_dep_path.extension().is_some_and(|ext| ext == "masp") + { + dependency_packages_paths.push(absolute_dep_path.clone()); + } + } + continue; + } + + if absolute_dep_path.is_file() { + // Look for a Miden package .masp file + if absolute_dep_path.extension().is_some_and(|ext| ext == "masp") { + log::debug!( + " - Found pre-compiled dependency '{}': {}", + dep_name, + absolute_dep_path.display() + ); + if !dependency_packages_paths.iter().any(|p| p == &absolute_dep_path) { + dependency_packages_paths.push(absolute_dep_path.clone()); + } + // Mark as processed + processed_dep_paths.insert(absolute_dep_path); + } else { + bail!( + "Dependency path for '{}' points to a file, but it's not a .masp file: {}", + dep_name, + absolute_dep_path.display() + ); + } + } else if absolute_dep_path.is_dir() { + // Build a cargo project + let dep_manifest_path = absolute_dep_path.join("Cargo.toml"); + if dep_manifest_path.is_file() { + log::debug!( + " - Building Miden library dependency project '{}' at {}", + dep_name, + absolute_dep_path.display() + ); + + let mut dep_build_args = vec![ + "cargo".to_string(), + "miden".to_string(), + "build".to_string(), + "--manifest-path".to_string(), + dep_manifest_path.to_string_lossy().to_string(), + ]; + // Inherit release/debug profile from parent build + if cargo_args.release { + dep_build_args.push("--release".to_string()); + } + // Dependencies should always be built as libraries + dep_build_args.push("--lib".to_string()); + + // We expect dependencies to *always* produce Masm libraries (.masp) + let command_output = crate::run(dep_build_args.into_iter(), OutputType::Masm) + .with_context(|| { + format!( + "building dependency '{}' at {}", + dep_name, + absolute_dep_path.display() + ) + })? + .ok_or(anyhow!("`cargo miden build` does not produced any output"))?; + + let build_output = command_output.unwrap_build_output(); + + let artifact_path = match build_output { + BuildOutput::Masm { artifact_path } => artifact_path, + // We specifically requested Masm, so Wasm output would be an error. + BuildOutput::Wasm { artifact_path, .. } => { + bail!( + "Dependency build for '{}' unexpectedly produced WASM output at \ + {}. Expected MASM (.masp)", + dep_name, + artifact_path.display() + ); + } + }; + log::debug!( + " - Dependency '{}' built successfully. Output: {}", + dep_name, + artifact_path.display() + ); + // Ensure it's a .masp file and add if unique + if artifact_path.extension().is_some_and(|ext| ext == "masp") { + if !dependency_packages_paths.iter().any(|p| p == &artifact_path) { + dependency_packages_paths.push(artifact_path); + } else { + bail!( + "Dependency build for '{}' produced a duplicate artifact: {}", + dep_name, + artifact_path.display() + ); + } + } else { + bail!( + "Build output for dependency '{}' is not a .masp file: {}.", + dep_name, + artifact_path.display() + ); + } + // Mark the *directory* as processed + processed_dep_paths.insert(absolute_dep_path); + } else { + bail!( + "Dependency path for '{}' points to a directory, but it does not contain \ + a Cargo.toml file: {}", + dep_name, + absolute_dep_path.display() + ); + } + } else { + bail!( + "Dependency path for '{}' does not exist or is not a file/directory: {}", + dep_name, + absolute_dep_path.display() + ); + } + } + } else { + log::debug!(" No Miden dependencies found for package '{}'.", package.name); + } + log::debug!( + "Finished processing Miden dependencies. Packages to link: [{}]", + dependency_packages_paths + .iter() + .map(|p| p.display().to_string()) + .collect::>() + .join(", ") + ); + Ok(dependency_packages_paths) +} diff --git a/tools/cargo-miden/src/lib.rs b/tools/cargo-miden/src/lib.rs index d413e8d5f..70f48e5dc 100644 --- a/tools/cargo-miden/src/lib.rs +++ b/tools/cargo-miden/src/lib.rs @@ -1,128 +1,68 @@ -use std::path::PathBuf; +//! `cargo-miden` as a library -use cargo_component::load_metadata; -use clap::{CommandFactory, Parser}; -use config::CargoArguments; -use midenc_session::diagnostics::Report; -use new_project::NewCommand; +#![deny(warnings)] +#![deny(missing_docs)] -use crate::run_cargo_command::run_cargo_command; +use anyhow::Result; +use clap::Parser; -mod build; -pub mod config; -mod new_project; -mod run_cargo_command; +mod cli; +mod commands; +mod compile_masm; +mod config; +mod dependencies; +mod outputs; mod target; - -fn version() -> &'static str { - option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION")) -} - -/// The list of commands that are built-in to `cargo-miden`. -const BUILTIN_COMMANDS: &[&str] = &[ - "miden", // for indirection via `cargo miden` - "new", -]; - -const AFTER_HELP: &str = "Unrecognized subcommands will be passed to cargo verbatim - and the artifacts will be processed afterwards (e.g. `build` command compiles MASM). - \nSee `cargo help` for more information on available cargo commands."; - -/// Cargo integration for Miden -#[derive(Parser)] -#[clap( - bin_name = "cargo", - version, - propagate_version = true, - arg_required_else_help = true, - after_help = AFTER_HELP -)] -#[command(version = version())] -enum CargoMiden { - /// Cargo integration for Miden - #[clap(subcommand, hide = true, after_help = AFTER_HELP)] - Miden(Command), // indirection via `cargo miden` - #[clap(flatten)] - Command(Command), -} - -#[derive(Parser)] -enum Command { - New(NewCommand), +mod utils; + +pub use outputs::{BuildOutput, CommandOutput}; +pub use target::{ + detect_project_type, detect_target_environment, target_environment_to_project_type, +}; + +/// Requested output type for the `build` command. +pub enum OutputType { + /// Return the Wasm component or core Wasm module emitted by Cargo. + Wasm, + /// Return the compiled Miden package. + Masm, } -fn detect_subcommand(args: I) -> Option +/// Runs the `cargo-miden` entry point. +/// +/// The iterator of arguments is expected to mirror the invocation of `cargo miden …`. +/// The command returns an optional [`CommandOutput`]; commands that only produce side-effects +/// (such as printing help) will return `Ok(None)`. +pub fn run(args: T, build_output_type: OutputType) -> Result> where - I: IntoIterator, - T: Into + Clone, + T: Iterator, { - let mut iter = args.into_iter().map(Into::into).skip(1).peekable(); + let collected: Vec = args.collect(); + let command_tokens = extract_command_tokens(&collected); - // Skip the first argument if it is `miden` (i.e. `cargo miden`) - if let Some(arg) = iter.peek() { - if arg == "miden" { - iter.next().unwrap(); - } - } + let cli = cli::CargoMidenCli::parse_from(command_tokens); - for arg in iter { - // Break out of processing at the first `--` - if arg == "--" { - break; + match cli.command { + cli::CargoMidenCommand::New(cmd) => { + let project_path = cmd.exec()?; + Ok(Some(CommandOutput::NewCommandOutput { project_path })) } - - if !arg.starts_with('-') { - return Some(arg); + cli::CargoMidenCommand::Example(cmd) => { + let project_path = cmd.exec()?; + Ok(Some(CommandOutput::NewCommandOutput { project_path })) } + cli::CargoMidenCommand::Build(cmd) => cmd.exec(build_output_type), } - - None } -pub fn run(args: T) -> Result, Report> -where - T: Iterator, -{ - let args = args.collect::>(); - let subcommand = detect_subcommand(args.clone()); - - let outputs = match subcommand.as_deref() { - // Check for built-in command or no command (shows help) - Some(cmd) if BUILTIN_COMMANDS.contains(&cmd) => { - match CargoMiden::parse_from(args.clone()) { - CargoMiden::Miden(cmd) | CargoMiden::Command(cmd) => match cmd { - Command::New(cmd) => vec![cmd.exec().map_err(Report::msg)?], - }, - } - } - - // If no subcommand was detected, - None => { - // Attempt to parse the supported CLI (expected to fail) - CargoMiden::parse_from(args); - - // If somehow the CLI parsed correctly despite no subcommand, - // print the help instead - CargoMiden::command().print_long_help().map_err(Report::msg)?; - Vec::new() - } - - _ => { - // Not a built-in command, run the cargo command - let cargo_args = - CargoArguments::parse_from(args.clone().into_iter()).map_err(Report::msg)?; - let metadata = - load_metadata(cargo_args.manifest_path.as_deref()).map_err(Report::msg)?; - if metadata.packages.is_empty() { - return Err(Report::msg(format!( - "manifest `{path}` contains no package or the workspace has no members", - path = metadata.workspace_root.join("Cargo.toml") - ))); - } +fn extract_command_tokens(args: &[String]) -> Vec { + if args.is_empty() { + panic!("expected `cargo miden [COMMAND]`, got empty args"); + } - let spawn_args: Vec<_> = args.into_iter().skip(1).collect(); - run_cargo_command(&metadata, subcommand.as_deref(), &cargo_args, &spawn_args)? - } - }; - Ok(outputs) + if let Some(idx) = args.iter().position(|arg| arg == "miden") { + args.iter().skip(idx).cloned().collect() + } else { + panic!("expected `cargo miden [COMMAND]`, got {args:?}"); + } } diff --git a/tools/cargo-miden/src/main.rs b/tools/cargo-miden/src/main.rs index 6129d0672..269b47db8 100644 --- a/tools/cargo-miden/src/main.rs +++ b/tools/cargo-miden/src/main.rs @@ -1,30 +1,16 @@ -use cargo_component_core::terminal::{Terminal, Verbosity}; -use cargo_miden::{config::CargoArguments, run}; +use anyhow::Ok; +use cargo_miden::{run, OutputType}; fn main() -> anyhow::Result<()> { // Initialize logger - let mut builder = env_logger::Builder::from_env("CARGO_MIDEN_TRACE"); + let mut builder = env_logger::Builder::from_env("CARGO_MIDEN_LOG"); builder.format_indent(Some(2)); builder.format_timestamp(None); builder.init(); - let cargo_args = CargoArguments::parse_from(std::env::args())?; - let terminal = Terminal::new( - if cargo_args.quiet { - Verbosity::Quiet - } else { - match cargo_args.verbose { - 0 => Verbosity::Normal, - _ => Verbosity::Verbose, - } - }, - cargo_args.color.unwrap_or_default(), - ); - - if let Err(e) = run(std::env::args()) { - terminal.error(format!("{e:?}"))?; + if let Err(e) = run(std::env::args(), OutputType::Masm) { + eprintln!("{e:?}"); std::process::exit(1); } - Ok(()) } diff --git a/tools/cargo-miden/src/new_project.rs b/tools/cargo-miden/src/new_project.rs deleted file mode 100644 index c4c26faff..000000000 --- a/tools/cargo-miden/src/new_project.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::path::PathBuf; - -use anyhow::Context; -use cargo_generate::{GenerateArgs, TemplatePath}; -use clap::Args; - -/// Create a new Miden project at -#[derive(Args)] -#[clap(disable_version_flag = true)] -pub struct NewCommand { - /// The path for the generated package. - #[clap()] - pub path: PathBuf, - /// The path to the template to use to generate the package - #[clap(long, hide(true))] - pub template_path: Option, - /// Use a locally cloned compiler in the generated package - #[clap(long, conflicts_with_all(["compiler_rev", "compiler_branch"]))] - pub compiler_path: Option, - /// Use a specific revision of the compiler in the generated package - #[clap(long, conflicts_with("compiler_branch"))] - pub compiler_rev: Option, - /// Use a specific branch of the compiler in the generated package - #[clap(long)] - pub compiler_branch: Option, -} - -impl NewCommand { - pub fn exec(self) -> anyhow::Result { - let name = self - .path - .file_name() - .ok_or_else(|| { - anyhow::anyhow!( - "Failed to get the last segment of the provided path for the project name" - ) - })? - .to_str() - .ok_or_else(|| { - anyhow::anyhow!( - "The last segment of the provided path must be valid UTF8 to generate a valid \ - project name" - ) - })? - .to_string(); - - let mut define = vec![]; - if let Some(compiler_path) = self.compiler_path.as_deref() { - define.push(format!("compiler_path={}", compiler_path.display())); - } - if let Some(compiler_rev) = self.compiler_rev.as_deref() { - define.push(format!("compiler_rev={compiler_rev}")); - } - if let Some(compiler_branch) = self.compiler_branch.as_deref() { - define.push(format!("compiler_branch={compiler_branch}")); - } - - // If we're running the test suite, and no specific options have been provided for what - // compiler to use - specify the path to current compiler directory - if cfg!(test) || std::env::var("TEST").is_ok() { - let use_local_compiler = self.compiler_path.is_none() - && self.compiler_rev.is_none() - && self.compiler_branch.is_none(); - if use_local_compiler { - set_default_test_compiler(&mut define); - } - } - - let template_path = match self.template_path.as_ref() { - Some(template_path) => TemplatePath { - path: Some(template_path.display().to_string()), - subfolder: Some("account".into()), - ..Default::default() - }, - None => TemplatePath { - git: Some("https://github.com/0xPolygonMiden/rust-templates".into()), - tag: Some("v0.4.0".into()), - auto_path: Some("account".into()), - ..Default::default() - }, - }; - - let destination = self - .path - .parent() - .map(|p| { - use path_absolutize::Absolutize; - p.absolutize().map(|p| p.to_path_buf()) - }) - .transpose() - .context("Failed to convert destination path to an absolute path")?; - let generate_args = GenerateArgs { - template_path, - destination, - name: Some(name), - // Force the `name` to not be kebab-cased - force: true, - force_git_init: true, - verbose: true, - define, - ..Default::default() - }; - cargo_generate::generate(generate_args) - .context("Failed to scaffold new Miden project from the template")?; - Ok(self.path) - } -} - -fn set_default_test_compiler(define: &mut Vec) { - use std::path::Path; - - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let compiler_path = Path::new(&manifest_dir).parent().unwrap().parent().unwrap(); - define.push(format!("compiler_path={}", compiler_path.display())); -} diff --git a/tools/cargo-miden/src/outputs.rs b/tools/cargo-miden/src/outputs.rs new file mode 100644 index 000000000..3913c7d55 --- /dev/null +++ b/tools/cargo-miden/src/outputs.rs @@ -0,0 +1,69 @@ +use std::path::{Path, PathBuf}; + +/// Represents the structured output of a successful `cargo miden` command. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum CommandOutput { + /// Output from the `new` command. + NewCommandOutput { + /// The path to the newly created project directory. + project_path: PathBuf, + }, + /// Output from the `build` command. + BuildCommandOutput { + /// The type and path of the artifact produced by the build. + output: BuildOutput, + }, + // Add other variants here if other commands need structured output later. +} + +impl CommandOutput { + /// Panics if the output is not `BuildCommandOutput`, otherwise returns the inner `BuildOutput`. + pub fn unwrap_build_output(self) -> BuildOutput { + match self { + CommandOutput::BuildCommandOutput { output } => output, + _ => panic!("called `unwrap_build_output()` on a non-BuildCommandOutput value"), + } + } + + /// Panics if the output is not `NewCommandOutput`, otherwise returns the inner project path. + pub fn unwrap_new_output(self) -> PathBuf { + match self { + CommandOutput::NewCommandOutput { project_path } => project_path, + _ => panic!("called `unwrap_new_output()` on a non-NewCommandOutput value"), + } + } +} + +/// Represents the specific artifact produced by the `build` command. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BuildOutput { + /// Miden Assembly (.masm) output. + Masm { + /// Path to the compiled MASM file or directory containing artifacts. + artifact_path: PathBuf, + // Potentially add other relevant info like package name, component type etc. + }, + /// WebAssembly (.wasm) output. + Wasm { + /// Path to the compiled WASM file. + artifact_path: PathBuf, + /// Additional arguments passed to the Miden compiler. + midenc_flags: Vec, + }, +} + +impl BuildOutput { + /// Get a reference to the filesystem path where the build artifact was placed + pub fn artifact_path(&self) -> &Path { + match self { + Self::Masm { artifact_path } | Self::Wasm { artifact_path, .. } => artifact_path, + } + } + + /// Convert this build output to the underlying filesystem path of the build artifact + pub fn into_artifact_path(self) -> PathBuf { + match self { + Self::Masm { artifact_path } | Self::Wasm { artifact_path, .. } => artifact_path, + } + } +} diff --git a/tools/cargo-miden/src/run_cargo_command.rs b/tools/cargo-miden/src/run_cargo_command.rs deleted file mode 100644 index c35d4c9fc..000000000 --- a/tools/cargo-miden/src/run_cargo_command.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::{path::PathBuf, process::Command}; - -use cargo_metadata::Metadata; -use midenc_session::diagnostics::{IntoDiagnostic, Report}; - -use crate::{ - build::build_masm, - config::CargoArguments, - target::{install_wasm32_wasi, WASM32_WASI_TARGET}, -}; - -fn is_wasm_target(target: &str) -> bool { - target == WASM32_WASI_TARGET -} - -/// Runs the cargo command as specified in the configuration. -/// -/// Returns any relevant output artifacts. -pub fn run_cargo_command( - metadata: &Metadata, - subcommand: Option<&str>, - cargo_args: &CargoArguments, - spawn_args: &[String], -) -> Result, Report> { - let cargo = std::env::var("CARGO") - .map(PathBuf::from) - .ok() - .unwrap_or_else(|| PathBuf::from("cargo")); - - let mut args = spawn_args.iter().peekable(); - if let Some(arg) = args.peek() { - if *arg == "miden" { - args.next().unwrap(); - } - } - - // Spawn the actual cargo command - log::debug!( - "spawning cargo `{cargo}` with arguments `{args:?}`", - cargo = cargo.display(), - args = args.clone().collect::>(), - ); - - let mut cmd = Command::new(&cargo); - cmd.args(args); - - let is_build = matches!(subcommand, Some("b") | Some("build")); - - // Handle the target for build commands - if is_build { - install_wasm32_wasi().map_err(Report::msg)?; - - // Add an implicit wasm32-wasi target if there isn't a wasm target present - if !cargo_args.targets.iter().any(|t| is_wasm_target(t)) { - cmd.arg("--target").arg(WASM32_WASI_TARGET); - } - } - - cmd.arg("-Z") - // compile std as part of crate graph compilation - // https://doc.rust-lang.org/cargo/reference/unstable.html#build-std - // to abort on panic below - .arg("build-std=std,core,alloc,panic_abort") - .arg("-Z") - // abort on panic without message formatting (core::fmt uses call_indirect) - .arg("build-std-features=panic_immediate_abort"); - - match cmd.status() { - Ok(status) => { - if !status.success() { - return Err(Report::msg(format!( - "cargo failed with exit code {}", - status.code().unwrap_or(1) - ))); - } - } - Err(e) => { - return Err(Report::msg(format!( - "failed to spawn `{cargo}`: {e}", - cargo = cargo.display() - ))); - } - } - let mut outputs = Vec::new(); - if is_build { - log::debug!("searching for WebAssembly modules to compile to MASM"); - let targets = cargo_args - .targets - .iter() - .map(String::as_str) - .filter(|t| is_wasm_target(t)) - .chain(cargo_args.targets.is_empty().then_some(WASM32_WASI_TARGET)); - - for target in targets { - let out_dir = metadata.target_directory.join(target).join(if cargo_args.release { - "release" - } else { - "debug" - }); - - let miden_out_dir = - metadata.target_directory.join("miden").join(if cargo_args.release { - "release" - } else { - "debug" - }); - if !miden_out_dir.exists() { - std::fs::create_dir_all(&miden_out_dir).into_diagnostic()?; - } - - for package in &metadata.packages { - let is_bin = package.targets.iter().any(|t| t.is_bin()); - - // First try for .wasm - let path = out_dir.join(&package.name).with_extension("wasm"); - if path.exists() { - let output = - build_masm(path.as_std_path(), miden_out_dir.as_std_path(), is_bin)?; - outputs.push(output); - } else { - let path = out_dir.join(package.name.replace('-', "_")).with_extension("wasm"); - if path.exists() { - let output = - build_masm(path.as_std_path(), miden_out_dir.as_std_path(), is_bin)?; - outputs.push(output); - } else { - log::debug!("no output found for package `{name}`", name = package.name); - return Err(Report::msg("Cargo build failed, no Wasm artifact found")); - } - } - } - } - } - - Ok(outputs) -} diff --git a/tools/cargo-miden/src/target.rs b/tools/cargo-miden/src/target.rs index 51d257d1f..2fe5cec69 100644 --- a/tools/cargo-miden/src/target.rs +++ b/tools/cargo-miden/src/target.rs @@ -5,34 +5,104 @@ use std::{ }; use anyhow::{bail, Result}; +use cargo_metadata::Package; +use midenc_session::{ProjectType, RollupTarget, TargetEnv}; -pub const WASM32_WASI_TARGET: &str = "wasm32-wasip1"; +/// Detects the target environment based on Cargo metadata. +pub fn detect_target_environment(root_pkg: &Package) -> Result { + let Some(meta_obj) = root_pkg.metadata.as_object() else { + return Ok(TargetEnv::Base); + }; + let Some(miden_meta) = meta_obj.get("miden") else { + return Ok(TargetEnv::Base); + }; + let Some(miden_meta_obj) = miden_meta.as_object() else { + return Ok(TargetEnv::Base); + }; -pub fn install_wasm32_wasi() -> Result<()> { - log::info!("Installing {WASM32_WASI_TARGET} target"); + // project-kind field is required + let Some(project_kind) = miden_meta_obj.get("project-kind") else { + bail!( + "Missing required field 'project-kind' in [package.metadata.miden]. Must be one of: \ + 'account', 'note-script', or 'transaction-script'" + ); + }; + + let Some(kind_str) = project_kind.as_str() else { + bail!( + "Field 'project-kind' in [package.metadata.miden] must be a string. Must be one of: \ + 'account', 'note-script', or 'transaction-script'" + ); + }; + + match kind_str { + "account" => Ok(TargetEnv::Rollup { + target: RollupTarget::Account, + }), + "note-script" => Ok(TargetEnv::Rollup { + target: RollupTarget::NoteScript, + }), + "transaction-script" => Ok(TargetEnv::Rollup { + target: RollupTarget::TransactionScript, + }), + "authentication-component" => Ok(TargetEnv::Rollup { + target: RollupTarget::AuthComponent, + }), + _ => bail!( + "Invalid value '{}' for 'project-kind' in [package.metadata.miden]. Must be one of: \ + 'account', 'note-script', or 'transaction-script'", + kind_str + ), + } +} + +/// Determines the project type based on the target environment +pub fn target_environment_to_project_type(target_env: TargetEnv) -> ProjectType { + match target_env { + TargetEnv::Base => ProjectType::Program, + TargetEnv::Rollup { target } => match target { + RollupTarget::Account => ProjectType::Library, + RollupTarget::AuthComponent => ProjectType::Library, + RollupTarget::NoteScript | RollupTarget::TransactionScript => ProjectType::Program, + }, + TargetEnv::Emu => { + panic!("Emulator target environment is not supported for project type detection",) + } + } +} + +/// Detect the project type +pub fn detect_project_type(root_pkg: &Package) -> Result { + let target_env = detect_target_environment(root_pkg)?; + Ok(target_environment_to_project_type(target_env)) +} + +pub fn install_wasm32_target(wasi: &str) -> Result<()> { let sysroot = get_sysroot()?; - if sysroot.join(format!("lib/rustlib/{}", WASM32_WASI_TARGET)).exists() { + if sysroot.join(format!("lib/rustlib/wasm32-{wasi}")).exists() { return Ok(()); } if env::var_os("RUSTUP_TOOLCHAIN").is_none() { bail!( - "failed to find the `{WASM32_WASI_TARGET}` target and `rustup` is not available. If \ - you're using rustup make sure that it's correctly installed; if not, make sure to \ - install the `{WASM32_WASI_TARGET}` target before using this command", + "failed to find the `wasm32-{wasi}` target and `rustup` is not available. If you're \ + using rustup make sure that it's correctly installed; if not, make sure to install \ + the `wasm32-{wasi}` target before using this command" ); } + log::info!("Installing wasm32-{wasi} target"); + let output = Command::new("rustup") .arg("target") .arg("add") - .arg(WASM32_WASI_TARGET) + .arg(format!("wasm32-{wasi}")) .stderr(Stdio::inherit()) .stdout(Stdio::inherit()) .output()?; if !output.status.success() { - bail!("failed to install the `{WASM32_WASI_TARGET}` target"); + bail!("failed to install the `wasm32-{wasi}` target"); } Ok(()) diff --git a/tools/cargo-miden/src/utils.rs b/tools/cargo-miden/src/utils.rs new file mode 100644 index 000000000..9556bbfb6 --- /dev/null +++ b/tools/cargo-miden/src/utils.rs @@ -0,0 +1,12 @@ +use std::path::{Path, PathBuf}; + +pub(crate) fn set_default_test_compiler(define: &mut Vec) { + let compiler_path = compiler_path(); + define.push(format!("compiler_path={}", compiler_path.display())); +} + +pub(crate) fn compiler_path() -> PathBuf { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let compiler_path = Path::new(&manifest_dir).parent().unwrap().parent().unwrap(); + compiler_path.to_path_buf() +} diff --git a/tools/cargo-miden/tests/build.rs b/tools/cargo-miden/tests/build.rs index 12e155e80..931ba09d0 100644 --- a/tools/cargo-miden/tests/build.rs +++ b/tools/cargo-miden/tests/build.rs @@ -1,75 +1,435 @@ -use std::{env, fs, vec}; +#![allow(unused)] -use cargo_miden::run; +use std::{env, fs}; -// NOTE: This test sets the current working directory so don't run it in parallel with tests -// that depend on the current directory +use cargo_miden::{run, OutputType}; +use miden_mast_package::Package; +use midenc_session::miden_assembly::utils::Deserializable; + +fn example_project_args(example_name: &str) -> Vec { + vec![ + "cargo".to_string(), + "miden".to_string(), + "example".to_string(), + example_name.to_string(), + ] +} -fn new_project_args(project_name: &str, template_path: Option<&str>) -> Vec { - let mut args = vec!["cargo", "miden", "new", project_name]; - if let Some(template_path) = template_path { - args.extend(["--template-path", template_path]); +fn new_project_args(project_name: &str, template: &str) -> Vec { + let template = if let Ok(templates_path) = std::env::var("TEST_LOCAL_TEMPLATES_PATH") { + &format!("--template-path={templates_path}/{}", template.strip_prefix("--").unwrap()) + } else { + template }; - args.into_iter().map(|s| s.to_string()).collect() + let args: Vec = ["cargo", "miden", "new", project_name, template] + .into_iter() + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect(); + args } +// NOTE: This test sets the current working directory so don't run it in parallel with tests +// that depend on the current directory + #[test] -fn build_new_project_from_template() { +fn test_all_templates_and_examples() { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .is_test(true) + .format_timestamp(None) + .try_init(); // Signal to `cargo-miden` that we're running in a test harness. // // This is necessary because cfg!(test) does not work for integration tests, so we're forced // to use an out-of-band signal like this instead env::set_var("TEST", "1"); + // Test example templates + + // Test counter-contract example (which also creates counter-note) + let (counter_contract, counter_note) = build_paired_example_projects( + "counter-contract", + "counter-contract", + "counter-note", + "counter_contract", + "counter_note", + ); + assert!(counter_contract.is_library()); + assert_eq!(counter_contract.name, "counter_contract"); + assert!(counter_note.is_program()); + assert_eq!(counter_note.name, "counter_note"); + + // Test fibonacci example + let fibonacci = build_example_project_from_template("fibonacci"); + assert!(fibonacci.is_program()); + assert_eq!(fibonacci.name, "fibonacci"); + + // Test collatz example + let collatz = build_example_project_from_template("collatz"); + assert!(collatz.is_program()); + assert_eq!(collatz.name, "collatz"); + + // Test is-prime example + let is_prime = build_example_project_from_template("is-prime"); + assert!(is_prime.is_program()); + assert_eq!(is_prime.name, "is_prime"); + + // Test storage-example + let storage = build_example_project_from_template("storage-example"); + assert!(storage.is_library()); + assert_eq!(storage.name, "storage_example"); + + // Test basic-wallet-tx-script example using different entry points + // Test 1: Using "basic-wallet-tx-script" as the example name + let (tx_script, wallet, p2id) = build_triple_example_projects( + "basic-wallet-tx-script", + "basic-wallet-tx-script", + "basic-wallet", + "p2id-note", + "basic_wallet_tx_script", + "basic_wallet", + "p2id", + ); + assert!(tx_script.is_program()); + assert_eq!(tx_script.name, "basic_wallet_tx_script"); + assert!(wallet.is_library()); + assert_eq!(wallet.name, "basic_wallet"); + assert!(p2id.is_program()); + assert_eq!(p2id.name, "p2id"); + + // Test 2: Using "basic-wallet" as the example name (should create all three) + let (tx_script2, wallet2, p2id2) = build_triple_example_projects( + "basic-wallet", + "basic-wallet-tx-script", + "basic-wallet", + "p2id-note", + "basic_wallet_tx_script", + "basic_wallet", + "p2id", + ); + assert!(tx_script2.is_program()); + assert_eq!(tx_script2.name, "basic_wallet_tx_script"); + assert!(wallet2.is_library()); + assert_eq!(wallet2.name, "basic_wallet"); + assert!(p2id2.is_program()); + assert_eq!(p2id2.name, "p2id"); + + // Test 3: Using "p2id-note" as the example name (should create all three) + let (tx_script3, wallet3, p2id3) = build_triple_example_projects( + "p2id-note", + "basic-wallet-tx-script", + "basic-wallet", + "p2id-note", + "basic_wallet_tx_script", + "basic_wallet", + "p2id", + ); + assert!(tx_script3.is_program()); + assert_eq!(tx_script3.name, "basic_wallet_tx_script"); + assert!(wallet3.is_library()); + assert_eq!(wallet3.name, "basic_wallet"); + assert!(p2id3.is_program()); + assert_eq!(p2id3.name, "p2id"); + + // Test new project templates + let account = build_new_project_from_template("--account"); + assert!(account.is_library()); + + let note = build_new_project_from_template("--note"); + assert!(note.is_program()); + + let tx_script = build_new_project_from_template("--tx-script"); + assert!(tx_script.is_program()); + + let program = build_new_project_from_template("--program"); + assert!(program.is_program()); + + let auth_comp = build_new_project_from_template("--auth-component"); + assert!(auth_comp.is_library()); + + let expected_function = "auth__procedure"; + let lib = auth_comp.unwrap_library(); + assert!( + lib.exports().any(|export| { export.name.name.as_str() == expected_function }), + "expected one of the authentication component exports to contain function \ + '{expected_function}'" + ); +} + +/// Build paired example projects (e.g., account and note script) and return their packages +/// Creates both projects in a subdirectory and builds them separately +fn build_paired_example_projects( + example_name: &str, + first_dir: &str, + second_dir: &str, + first_expected_name: &str, + second_expected_name: &str, +) -> (Package, Package) { + let restore_dir = env::current_dir().unwrap(); + let temp_dir = env::temp_dir().join(format!( + "test_example_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + )); + fs::create_dir_all(&temp_dir).unwrap(); + env::set_current_dir(&temp_dir).unwrap(); + + // Create the project - it will create both projects + let args = example_project_args(example_name); + + let output = run(args.into_iter(), OutputType::Masm) + .expect("Failed to create new project") + .expect("'cargo miden example' should return Some(CommandOutput)"); + let main_project_path = match output { + cargo_miden::CommandOutput::NewCommandOutput { project_path } => { + project_path.canonicalize().unwrap() + } + other => panic!("Expected NewCommandOutput, got {other:?}"), + }; + assert!(main_project_path.exists()); + + // Build first project + let first_path = main_project_path.join(first_dir); + assert!(first_path.exists()); + env::set_current_dir(&first_path).unwrap(); + + let first_package = build_project_in_current_dir(first_expected_name); + + // Build second project + let second_path = main_project_path.join(second_dir); + assert!(second_path.exists()); + env::set_current_dir(&second_path).unwrap(); + + let second_package = build_project_in_current_dir(second_expected_name); + + env::set_current_dir(restore_dir).unwrap(); + fs::remove_dir_all(temp_dir).unwrap(); + + (first_package, second_package) +} + +/// Build triple example projects (e.g., tx-script, account and note script) and return their packages +/// Creates all three projects in a subdirectory and builds them separately +fn build_triple_example_projects( + example_name: &str, + first_dir: &str, + second_dir: &str, + third_dir: &str, + first_expected_name: &str, + second_expected_name: &str, + third_expected_name: &str, +) -> (Package, Package, Package) { + let restore_dir = env::current_dir().unwrap(); + let temp_dir = env::temp_dir().join(format!( + "test_example_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + )); + fs::create_dir_all(&temp_dir).unwrap(); + env::set_current_dir(&temp_dir).unwrap(); + + // Create the project - it will create all three projects + let args = example_project_args(example_name); + + let output = run(args.into_iter(), OutputType::Masm) + .expect("Failed to create new project") + .expect("'cargo miden example' should return Some(CommandOutput)"); + let main_project_path = match output { + cargo_miden::CommandOutput::NewCommandOutput { project_path } => { + project_path.canonicalize().unwrap() + } + other => panic!("Expected NewCommandOutput, got {other:?}"), + }; + assert!(main_project_path.exists()); + + // Build second project (basic-wallet) + let second_path = main_project_path.join(second_dir); + assert!(second_path.exists()); + env::set_current_dir(&second_path).unwrap(); + let second_package = build_project_in_current_dir(second_expected_name); + + // Build third project (p2id-note) + let third_path = main_project_path.join(third_dir); + assert!(third_path.exists()); + env::set_current_dir(&third_path).unwrap(); + let third_package = build_project_in_current_dir(third_expected_name); + + // Build first project (basic-wallet-tx-script) + let first_path = main_project_path.join(first_dir); + assert!(first_path.exists()); + env::set_current_dir(&first_path).unwrap(); + let first_package = build_project_in_current_dir(first_expected_name); + + env::set_current_dir(restore_dir).unwrap(); + fs::remove_dir_all(temp_dir).unwrap(); + + (first_package, second_package, third_package) +} + +/// Build a project in the current directory and verify it compiles correctly +/// Tests both debug and release builds, returning the package from the release build +fn build_project_in_current_dir(expected_name: &str) -> Package { + // build with the dev profile + let args = ["cargo", "miden", "build"].iter().map(|s| s.to_string()); + let output = run(args, OutputType::Masm) + .expect("Failed to compile with the dev profile") + .expect("'cargo miden build' should return Some(CommandOutput)"); + let expected_masm_path = match output { + cargo_miden::CommandOutput::BuildCommandOutput { output } => match output { + cargo_miden::BuildOutput::Masm { artifact_path } => artifact_path, + other => panic!("Expected Masm output, got {other:?}"), + }, + other => panic!("Expected BuildCommandOutput, got {other:?}"), + }; + assert!(expected_masm_path.exists()); + assert!(expected_masm_path.to_str().unwrap().contains("/debug/")); + assert_eq!(expected_masm_path.extension().unwrap(), "masp"); + assert!(expected_masm_path.metadata().unwrap().len() > 0); + + // build with the release profile + let args = ["cargo", "miden", "build", "--release"].iter().map(|s| s.to_string()); + let output = run(args, OutputType::Masm) + .expect("Failed to compile with the release profile") + .expect("'cargo miden build --release' should return Some(CommandOutput)"); + let expected_masm_path = match output { + cargo_miden::CommandOutput::BuildCommandOutput { output } => match output { + cargo_miden::BuildOutput::Masm { artifact_path } => artifact_path, + other => panic!("Expected Masm output, got {other:?}"), + }, + other => panic!("Expected BuildCommandOutput, got {other:?}"), + }; + assert!(expected_masm_path.exists()); + assert_eq!(expected_masm_path.extension().unwrap(), "masp"); + assert!(expected_masm_path.to_str().unwrap().contains("/release/")); + assert!(expected_masm_path.to_str().unwrap().contains(expected_name)); + assert!(expected_masm_path.metadata().unwrap().len() > 0); + let package_bytes = fs::read(expected_masm_path).unwrap(); + Package::read_from_bytes(&package_bytes).unwrap() +} + +/// Build an example project from the specified template and return its package +/// Creates the project in a temporary directory and builds it +fn build_example_project_from_template(example_name: &str) -> Package { + let restore_dir = env::current_dir().unwrap(); + let temp_dir = env::temp_dir().join(format!( + "test_example_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + )); + fs::create_dir_all(&temp_dir).unwrap(); + env::set_current_dir(&temp_dir).unwrap(); + + // Create the project - it will be named after the example + let args = example_project_args(example_name); + + let output = run(args.into_iter(), OutputType::Masm) + .expect("Failed to create new project") + .expect("'cargo miden example' should return Some(CommandOutput)"); + let new_project_path = match output { + cargo_miden::CommandOutput::NewCommandOutput { project_path } => { + project_path.canonicalize().unwrap() + } + other => panic!("Expected NewCommandOutput, got {other:?}"), + }; + assert!(new_project_path.exists()); + env::set_current_dir(&new_project_path).unwrap(); + + // Convert hyphens to underscores for the expected package name + let expected_name = example_name.replace("-", "_"); + let package = build_project_in_current_dir(&expected_name); + + env::set_current_dir(restore_dir).unwrap(); + fs::remove_dir_all(temp_dir).unwrap(); + package +} + +/// Build a new project from the specified template and return its package +/// Handles special cases like note templates that require a contract dependency +fn build_new_project_from_template(template: &str) -> Package { let restore_dir = env::current_dir().unwrap(); let temp_dir = env::temp_dir(); env::set_current_dir(&temp_dir).unwrap(); + + if template == "--note" || template == "--tx-script" { + // create the counter contract cargo project since the note and tx-script depend on it + let project_name = "add-contract"; + let expected_new_project_dir = &temp_dir.join(project_name); + if expected_new_project_dir.exists() { + fs::remove_dir_all(expected_new_project_dir).unwrap(); + } + let output = run(new_project_args(project_name, "--account").into_iter(), OutputType::Masm) + .expect("Failed to create new add-contract dependency project") + .expect("'cargo miden new' should return Some(CommandOutput)"); + } + let project_name = "test_proj_underscore"; let expected_new_project_dir = &temp_dir.join(project_name); - dbg!(&expected_new_project_dir); if expected_new_project_dir.exists() { fs::remove_dir_all(expected_new_project_dir).unwrap(); } - let args = new_project_args(project_name, None); - // let args = new_project_args( - // project_name, - // Some( - // &(format!( - // "{}/../../../rust-templates/account", - // std::env::var("CARGO_MANIFEST_DIR").unwrap() - // )), - // ), - // ); - - let outputs = run(args.into_iter()).expect("Failed to create new project"); - let new_project_path = outputs.first().unwrap().canonicalize().unwrap(); - dbg!(&new_project_path); + let args = new_project_args(project_name, template); + + let output = run(args.into_iter(), OutputType::Masm) + .expect("Failed to create new project from {template} template") + .expect("'cargo miden new' should return Some(CommandOutput)"); + let new_project_path = match output { + cargo_miden::CommandOutput::NewCommandOutput { project_path } => { + project_path.canonicalize().unwrap() + } + other => panic!("Expected NewCommandOutput, got {other:?}"), + }; assert!(new_project_path.exists()); assert_eq!(new_project_path, expected_new_project_dir.canonicalize().unwrap()); env::set_current_dir(&new_project_path).unwrap(); // build with the dev profile let args = ["cargo", "miden", "build"].iter().map(|s| s.to_string()); - let outputs = run(args).expect("Failed to compile with the dev profile"); - assert_eq!(outputs.len(), 1); - let expected_masm_path = outputs.first().unwrap(); - dbg!(&expected_masm_path); + let output = run(args, OutputType::Masm) + .unwrap_or_else(|e| { + panic!( + "Failed to compile with the dev profile for template: {template} \nwith error: {e}" + ) + }) + .expect("'cargo miden build' should return Some(CommandOutput)"); + let expected_masm_path = match output { + cargo_miden::CommandOutput::BuildCommandOutput { output } => match output { + cargo_miden::BuildOutput::Masm { artifact_path } => artifact_path, + other => panic!("Expected Masm output, got {other:?}"), + }, + other => panic!("Expected BuildCommandOutput, got {other:?}"), + }; assert!(expected_masm_path.exists()); assert!(expected_masm_path.to_str().unwrap().contains("/debug/")); + assert_eq!(expected_masm_path.extension().unwrap(), "masp"); assert!(expected_masm_path.metadata().unwrap().len() > 0); - // assert_eq!(expected_masm_path.metadata().unwrap().len(), 0); // build with the release profile let args = ["cargo", "miden", "build", "--release"].iter().map(|s| s.to_string()); - let outputs = run(args).expect("Failed to compile with the release profile"); - assert_eq!(outputs.len(), 1); - let expected_masm_path = outputs.first().unwrap(); - dbg!(&expected_masm_path); + let output = run(args, OutputType::Masm) + .expect("Failed to compile with the release profile") + .expect("'cargo miden build --release' should return Some(CommandOutput)"); + let expected_masm_path = match output { + cargo_miden::CommandOutput::BuildCommandOutput { output } => match output { + cargo_miden::BuildOutput::Masm { artifact_path } => artifact_path, + other => panic!("Expected Masm output, got {other:?}"), + }, + other => panic!("Expected BuildCommandOutput, got {other:?}"), + }; assert!(expected_masm_path.exists()); + assert_eq!(expected_masm_path.extension().unwrap(), "masp"); assert!(expected_masm_path.to_str().unwrap().contains("/release/")); assert!(expected_masm_path.metadata().unwrap().len() > 0); + let package_bytes = fs::read(expected_masm_path).unwrap(); + let package = Package::read_from_bytes(&package_bytes).unwrap(); env::set_current_dir(restore_dir).unwrap(); fs::remove_dir_all(new_project_path).unwrap(); + package } diff --git a/tools/cargo-miden/tests/mod.rs b/tools/cargo-miden/tests/mod.rs index 3a146708a..985c6ff7a 100755 --- a/tools/cargo-miden/tests/mod.rs +++ b/tools/cargo-miden/tests/mod.rs @@ -1,2 +1,4 @@ mod build; +mod project_type; mod utils; +mod workspace; diff --git a/tools/cargo-miden/tests/project_type.rs b/tools/cargo-miden/tests/project_type.rs new file mode 100644 index 000000000..bc7214f80 --- /dev/null +++ b/tools/cargo-miden/tests/project_type.rs @@ -0,0 +1,105 @@ +use std::path::Path; + +use cargo_metadata::MetadataCommand; +use midenc_session::{ProjectType, RollupTarget, TargetEnv}; + +#[test] +fn test_project_type_detection() { + // Define examples with both expected project type and target environment + let examples = [ + // (example_name, expected_project_type, expected_target_environment) + ("collatz", ProjectType::Program, TargetEnv::Base), + ( + "counter-contract", + ProjectType::Library, + TargetEnv::Rollup { + target: RollupTarget::Account, + }, + ), + ( + "counter-note", + ProjectType::Program, + TargetEnv::Rollup { + target: RollupTarget::NoteScript, + }, + ), + ("fibonacci", ProjectType::Program, TargetEnv::Base), + ("is-prime", ProjectType::Program, TargetEnv::Base), + ( + "storage-example", + ProjectType::Library, + TargetEnv::Rollup { + target: RollupTarget::Account, + }, + ), + ]; + + for (example_name, expected_type, expected_env) in examples { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + // Relative path from tools/cargo-miden/tests/ -> tools/cargo-miden/ -> examples/ + let example_manifest_path = manifest_dir + .join("../../examples") // Go up two levels from crate root + .join(example_name) + .join("Cargo.toml") + .canonicalize() // Resolve path for clearer error messages + .unwrap_or_else(|e| { + panic!("Failed to find manifest path for {example_name}: {e}") + }); + + println!("Testing project type detection for: {}", example_manifest_path.display()); + + let metadata = MetadataCommand::new() + .manifest_path(&example_manifest_path) + .no_deps() // Avoid pulling deps for simple metadata read + .exec() + .unwrap_or_else(|e| { + panic!( + "Failed to load metadata for {}: {}", + example_manifest_path.display(), + e + ) + }); + + // Determine root package and test target environment detection + let root_pkg = metadata.root_package().expect("example metadata missing root package"); + let detected_env = cargo_miden::detect_target_environment(root_pkg).unwrap_or_else(|e| { + panic!( + "Failed to detect target environment for {}: {}", + example_manifest_path.display(), + e + ) + }); + assert_eq!( + detected_env, + expected_env, + "Target environment mismatch for example '{}': expected {:?}, detected {:?} \ + (manifest: {})", + example_name, + expected_env, + detected_env, + example_manifest_path.display() + ); + + // Test project type detection + let detected_type = cargo_miden::detect_project_type(root_pkg).unwrap_or_else(|e| { + panic!("Failed to detect project type for {}: {}", example_manifest_path.display(), e) + }); + assert_eq!( + detected_type, + expected_type, + "Project type mismatch for example '{}': expected {:?}, detected {:?} (manifest: {})", + example_name, + expected_type, + detected_type, + example_manifest_path.display() + ); + + // Verify that project type is correctly derived from target environment + let derived_type = cargo_miden::target_environment_to_project_type(detected_env); + assert_eq!( + derived_type, detected_type, + "Derived project type mismatch for example '{example_name}': expected \ + {detected_type:?}, derived {derived_type:?}" + ); + } +} diff --git a/tools/cargo-miden/tests/workspace.rs b/tools/cargo-miden/tests/workspace.rs new file mode 100644 index 000000000..681482131 --- /dev/null +++ b/tools/cargo-miden/tests/workspace.rs @@ -0,0 +1,136 @@ +use std::{env, fs, path::Path}; + +use cargo_miden::{run, BuildOutput, OutputType}; + +/// Creates a minimal Cargo workspace at `root` with a single member named `member_name`. +fn write_workspace_root(root: &Path, member_name: &str) { + let ws_toml = format!( + r#"[workspace] +resolver = "2" +members = ["{member_name}"] + +[workspace.package] +version = "0.1.0" +edition = "2021" +authors = ["Miden Contributors"] +license = "MIT" +repository = "https://example.com/test" +"# + ); + fs::write(root.join("Cargo.toml"), ws_toml).expect("write workspace Cargo.toml"); +} + +fn new_project_args(project_name: &str, template: &str) -> Vec { + let template = if let Ok(templates_path) = std::env::var("TEST_LOCAL_TEMPLATES_PATH") { + &format!("--template-path={templates_path}/{}", template.trim_start_matches("--")) + } else { + template + }; + ["cargo", "miden", "new", project_name, template] + .into_iter() + .filter(|s| !s.is_empty()) + .map(|s| s.to_string()) + .collect() +} + +#[test] +fn build_workspace_member_account_project() { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .is_test(true) + .format_timestamp(None) + .try_init(); + // signal integration tests to the cargo-miden code path + env::set_var("TEST", "1"); + + // create temp workspace root + let restore_dir = env::current_dir().unwrap(); + let ws_root = env::temp_dir().join(format!( + "cargo_miden_ws_test_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + )); + if ws_root.exists() { + fs::remove_dir_all(&ws_root).unwrap(); + } + fs::create_dir_all(&ws_root).unwrap(); + env::set_current_dir(&ws_root).unwrap(); + + // write workspace manifest + let member_name = "member_account"; + write_workspace_root(&ws_root, member_name); + + // create account project as a workspace member + let output = run(new_project_args(member_name, "--account").into_iter(), OutputType::Masm) + .expect("cargo miden new failed") + .expect("expected NewCommandOutput"); + let project_path = match output { + cargo_miden::CommandOutput::NewCommandOutput { project_path } => project_path, + other => panic!("Expected NewCommandOutput, got {other:?}"), + }; + assert!(project_path.ends_with(member_name)); + + // change into the member directory and try to build using cargo-miden + env::set_current_dir(&project_path).unwrap(); + let output = + run(["cargo", "miden", "build"].into_iter().map(|s| s.to_string()), OutputType::Masm) + .unwrap() + .unwrap() + .unwrap_build_output(); + assert!(matches!(output, BuildOutput::Masm { .. })); + + // cleanup + env::set_current_dir(restore_dir).unwrap(); + fs::remove_dir_all(ws_root).unwrap(); +} + +#[test] +fn build_from_workspace_root_is_rejected() { + let _ = env_logger::Builder::from_env("MIDENC_TRACE") + .is_test(true) + .format_timestamp(None) + .try_init(); + env::set_var("TEST", "1"); + + // create temp workspace root + let restore_dir = env::current_dir().unwrap(); + let ws_root = env::temp_dir().join(format!( + "cargo_miden_ws_root_test_{}", + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis() + )); + if ws_root.exists() { + fs::remove_dir_all(&ws_root).unwrap(); + } + fs::create_dir_all(&ws_root).unwrap(); + env::set_current_dir(&ws_root).unwrap(); + + // write workspace manifest and scaffold a member + let member_name = "member_account"; + write_workspace_root(&ws_root, member_name); + let _ = run( + ["cargo", "miden", "new", member_name, "--account"] + .into_iter() + .map(|s| s.to_string()), + OutputType::Masm, + ) + .expect("cargo miden new failed") + .expect("expected NewCommandOutput"); + + // Run cargo miden build at the workspace root without selecting a package + env::set_current_dir(&ws_root).unwrap(); + let err = run(["cargo", "miden", "build"].into_iter().map(|s| s.to_string()), OutputType::Masm) + .expect_err("expected workspace root build to be rejected"); + let msg = err.to_string(); + assert!( + msg.contains("unable to determine package") && msg.contains("member"), + "unexpected error message: {msg}" + ); + + // cleanup + env::set_current_dir(restore_dir).unwrap(); + fs::remove_dir_all(ws_root).unwrap(); +} diff --git a/tools/expect-test/CHANGELOG.md b/tools/expect-test/CHANGELOG.md new file mode 100644 index 000000000..a19eb2609 --- /dev/null +++ b/tools/expect-test/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.0](https://github.com/0xMiden/compiler/compare/midenc-expect-test-v0.1.5...midenc-expect-test-v0.4.0) - 2025-08-15 + +### Other + +- update Rust toolchain nightly-2025-07-20 (1.90.0-nightly) + +## [0.1.5](https://github.com/0xMiden/compiler/compare/midenc-expect-test-v0.1.0...midenc-expect-test-v0.1.5) - 2025-07-01 + +### Other + +- move `midenc-expect-test` to `tools` folder +- update url +- fix typos ([#243](https://github.com/0xMiden/compiler/pull/243)) +- a few minor improvements +- set up mdbook deploy +- add guides for compiling rust->masm +- add mdbook skeleton +- provide some initial usage instructions +- Initial commit diff --git a/tools/expect-test/Cargo.toml b/tools/expect-test/Cargo.toml new file mode 100644 index 000000000..cd6550126 --- /dev/null +++ b/tools/expect-test/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "midenc-expect-test" +description = "Snapshot testing for Miden compiler" +version.workspace = true +rust-version.workspace = true +authors.workspace = true +repository.workspace = true +categories.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +edition.workspace = true + +[dependencies] +similar = "2" +once_cell = "1" diff --git a/tools/expect-test/src/lib.rs b/tools/expect-test/src/lib.rs new file mode 100644 index 000000000..20a3bf8ea --- /dev/null +++ b/tools/expect-test/src/lib.rs @@ -0,0 +1,983 @@ +//! Minimalistic snapshot testing for Rust. +//! +//! # Introduction +//! +//! `midenc_expect_test` is a small addition over plain `assert_eq!` testing approach, +//! which allows to automatically update tests results. +//! +//! The core of the library is the `expect!` macro. It can be though of as a +//! super-charged string literal, which can update itself. +//! +//! Let's see an example: +//! +//! ```no_run +//! use midenc_expect_test::expect; +//! +//! let actual = 2 + 2; +//! let expected = expect!["5"]; // or expect![["5"]] +//! expected.assert_eq(&actual.to_string()) +//! ``` +//! +//! Running this code will produce a test failure, as `"5"` is indeed not equal +//! to `"4"`. Running the test with `UPDATE_EXPECT=1` env variable however would +//! "magically" update the code to: +//! +//! ```no_run +//! # use midenc_expect_test::expect; +//! let actual = 2 + 2; +//! let expected = expect!["4"]; +//! expected.assert_eq(&actual.to_string()) +//! ``` +//! +//! This becomes very useful when you have a lot of tests with verbose and +//! potentially changing expected output. +//! +//! Under the hood, the `expect!` macro uses `file!`, `line!` and `column!` to +//! record source position at compile time. At runtime, this position is used +//! to patch the file in-place, if `UPDATE_EXPECT` is set. +//! +//! # Guide +//! +//! `expect!` returns an instance of `Expect` struct, which holds position +//! information and a string literal. Use `Expect::assert_eq` for string +//! comparison. Use `Expect::assert_debug_eq` for verbose debug comparison. Note +//! that leading indentation is automatically removed. +//! +//! ``` +//! use midenc_expect_test::expect; +//! +//! #[derive(Debug)] +//! struct Foo { +//! value: i32, +//! } +//! +//! let actual = Foo { value: 92 }; +//! let expected = expect![[" +//! Foo { +//! value: 92, +//! } +//! "]]; +//! expected.assert_debug_eq(&actual); +//! ``` +//! +//! Be careful with `assert_debug_eq` - in general, stability of the debug +//! representation is not guaranteed. However, even if it changes, you can +//! quickly update all the tests by running the test suite with `UPDATE_EXPECT` +//! environmental variable set. +//! +//! If the expected data is too verbose to include inline, you can store it in +//! an external file using the `expect_file!` macro: +//! +//! ```no_run +//! use midenc_expect_test::expect_file; +//! +//! let actual = 42; +//! let expected = expect_file!["./the-answer.txt"]; +//! expected.assert_eq(&actual.to_string()); +//! ``` +//! +//! File path is relative to the current file. +//! +//! # Suggested Workflows +//! +//! I like to use data-driven tests with `midenc_expect_test`. I usually define a +//! single driver function `check` and then call it from individual tests: +//! +//! ``` +//! use midenc_expect_test::{expect, Expect}; +//! +//! fn check(actual: i32, expect: Expect) { +//! let actual = actual.to_string(); +//! expect.assert_eq(&actual); +//! } +//! +//! #[test] +//! fn test_addition() { +//! check(90 + 2, expect![["92"]]); +//! } +//! +//! #[test] +//! fn test_multiplication() { +//! check(46 * 2, expect![["92"]]); +//! } +//! ``` +//! +//! Each test's body is a single call to `check`. All the variation in tests +//! comes from the input data. +//! +//! When writing a new test, I usually copy-paste an old one, leave the `expect` +//! blank and use `UPDATE_EXPECT` to fill the value for me: +//! +//! ``` +//! # use midenc_expect_test::{expect, Expect}; +//! # fn check(_: i32, _: Expect) {} +//! #[test] +//! fn test_division() { +//! check(92 / 2, expect![[""]]) +//! } +//! ``` +//! +//! See +//! +//! for a cool example of snapshot testing in the wild! +//! + +#![allow(clippy::test_attr_in_doctest)] + +use std::{ + collections::HashMap, + convert::TryInto, + env, fmt, fs, mem, + ops::Range, + panic, + path::{Path, PathBuf}, + sync::Mutex, +}; + +use once_cell::sync::{Lazy, OnceCell}; + +const HELP: &str = " +You can update all `expect!` tests by running: + + env UPDATE_EXPECT=1 cargo test + +To update a single test, place the cursor on `expect` token and use `run` feature of rust-analyzer. +"; + +fn update_expect() -> bool { + env::var("UPDATE_EXPECT").is_ok() +} + +/// Creates an instance of `Expect` from string literal: +/// +/// ``` +/// # use midenc_expect_test::expect; +/// expect![[" +/// Foo { value: 92 } +/// "]]; +/// expect![r#"{"Foo": 92}"#]; +/// ``` +/// +/// Leading indentation is stripped. +#[macro_export] +macro_rules! expect { + [$data:literal] => { $crate::expect![[$data]] }; + [[$data:literal]] => {$crate::Expect { + position: $crate::Position { + file: file!(), + line: line!(), + column: column!(), + }, + data: $data, + indent: true, + }}; + [] => { $crate::expect![[""]] }; + [[]] => { $crate::expect![[""]] }; +} + +/// Creates an instance of `ExpectFile` from relative or absolute path: +/// +/// ``` +/// # use midenc_expect_test::expect_file; +/// expect_file!["./test_data/bar.html"]; +/// ``` +#[macro_export] +macro_rules! expect_file { + [$path:expr] => {$crate::ExpectFile { + path: std::path::PathBuf::from($path), + position: file!(), + }}; +} + +/// Self-updating string literal. +#[derive(Debug)] +pub struct Expect { + #[doc(hidden)] + pub position: Position, + #[doc(hidden)] + pub data: &'static str, + #[doc(hidden)] + pub indent: bool, +} + +/// Self-updating file. +#[derive(Debug)] +pub struct ExpectFile { + #[doc(hidden)] + pub path: PathBuf, + #[doc(hidden)] + pub position: &'static str, +} + +/// Position of original `expect!` in the source file. +#[derive(Debug)] +pub struct Position { + #[doc(hidden)] + pub file: &'static str, + #[doc(hidden)] + pub line: u32, + #[doc(hidden)] + pub column: u32, +} + +impl fmt::Display for Position { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}:{}", self.file, self.line, self.column) + } +} + +#[derive(Clone, Copy)] +enum StrLitKind { + Normal, + Raw(usize), +} + +impl StrLitKind { + fn write_start(self, w: &mut impl std::fmt::Write) -> std::fmt::Result { + match self { + Self::Normal => write!(w, "\""), + Self::Raw(n) => { + write!(w, "r")?; + for _ in 0..n { + write!(w, "#")?; + } + write!(w, "\"") + } + } + } + + fn write_end(self, w: &mut impl std::fmt::Write) -> std::fmt::Result { + match self { + Self::Normal => write!(w, "\""), + Self::Raw(n) => { + write!(w, "\"")?; + for _ in 0..n { + write!(w, "#")?; + } + Ok(()) + } + } + } +} + +impl Expect { + /// Checks if this expect is equal to `actual`. + pub fn assert_eq(&self, actual: &str) { + let trimmed = self.trimmed(); + if trimmed == actual { + return; + } + Runtime::fail_expect(self, &trimmed, actual); + } + + /// Checks if this expect is equal to `format!("{:#?}", actual)`. + pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { + let actual = format!("{actual:#?}\n"); + self.assert_eq(&actual) + } + + /// If `true` (default), in-place update will indent the string literal. + pub fn indent(&mut self, yes: bool) { + self.indent = yes; + } + + /// Returns the content of this expect. + pub fn data(&self) -> &str { + self.data + } + + fn trimmed(&self) -> String { + if !self.data.contains('\n') { + return self.data.to_string(); + } + trim_indent(self.data) + } + + fn locate(&self, file: &str) -> Location { + let mut target_line = None; + let mut line_start = 0; + for (i, line) in lines_with_ends(file).enumerate() { + if i == self.position.line as usize - 1 { + // `column` points to the first character of the macro invocation: + // + // expect![[r#""#]] expect![""] + // ^ ^ ^ ^ + // column offset offset + // + // Seek past the exclam, then skip any whitespace and + // the macro delimiter to get to our argument. + #[allow(clippy::skip_while_next)] + let byte_offset = line + .char_indices() + .skip((self.position.column - 1).try_into().unwrap()) + .skip_while(|&(_, c)| c != '!') + .skip(1) // ! + .skip_while(|&(_, c)| c.is_whitespace()) + .skip(1) // [({ + .skip_while(|&(_, c)| c.is_whitespace()) + .next() + .expect("Failed to parse macro invocation") + .0; + + let literal_start = line_start + byte_offset; + let indent = line.chars().take_while(|&it| it == ' ').count(); + target_line = Some((literal_start, indent)); + break; + } + line_start += line.len(); + } + let (literal_start, line_indent) = target_line.unwrap(); + + let lit_to_eof = &file[literal_start..]; + let lit_to_eof_trimmed = lit_to_eof.trim_start(); + + let literal_start = literal_start + (lit_to_eof.len() - lit_to_eof_trimmed.len()); + + let literal_len = + locate_end(lit_to_eof_trimmed).expect("Couldn't find closing delimiter for `expect!`."); + let literal_range = literal_start..literal_start + literal_len; + Location { + line_indent, + literal_range, + } + } +} + +fn locate_end(arg_start_to_eof: &str) -> Option { + match arg_start_to_eof.chars().next()? { + c if c.is_whitespace() => panic!("skip whitespace before calling `locate_end`"), + + // expect![[]] + '[' => { + let str_start_to_eof = arg_start_to_eof[1..].trim_start(); + let str_len = find_str_lit_len(str_start_to_eof)?; + let str_end_to_eof = &str_start_to_eof[str_len..]; + let closing_brace_offset = str_end_to_eof.find(']')?; + Some((arg_start_to_eof.len() - str_end_to_eof.len()) + closing_brace_offset + 1) + } + + // expect![] | expect!{} | expect!() + ']' | '}' | ')' => Some(0), + + // expect!["..."] | expect![r#"..."#] + _ => find_str_lit_len(arg_start_to_eof), + } +} + +/// Parses a string literal, returning the byte index of its last character +/// (either a quote or a hash). +fn find_str_lit_len(str_lit_to_eof: &str) -> Option { + use StrLitKind::*; + + fn try_find_n_hashes( + s: &mut impl Iterator, + desired_hashes: usize, + ) -> Option<(usize, Option)> { + let mut n = 0; + loop { + match s.next()? { + '#' => n += 1, + c => return Some((n, Some(c))), + } + + if n == desired_hashes { + return Some((n, None)); + } + } + } + + let mut s = str_lit_to_eof.chars(); + let kind = match s.next()? { + '"' => Normal, + 'r' => { + let (n, c) = try_find_n_hashes(&mut s, usize::MAX)?; + if c != Some('"') { + return None; + } + Raw(n) + } + _ => return None, + }; + + let mut oldc = None; + loop { + let c = oldc.take().or_else(|| s.next())?; + match (c, kind) { + ('\\', Normal) => { + let _escaped = s.next()?; + } + ('"', Normal) => break, + ('"', Raw(0)) => break, + ('"', Raw(n)) => { + let (seen, c) = try_find_n_hashes(&mut s, n)?; + if seen == n { + break; + } + oldc = c; + } + _ => {} + } + } + + Some(str_lit_to_eof.len() - s.as_str().len()) +} + +impl ExpectFile { + /// Checks if file contents is equal to `actual`. + pub fn assert_eq(&self, actual: &str) { + let expected = self.data(); + if actual == expected { + return; + } + Runtime::fail_file(self, &expected, actual); + } + + /// Checks if file contents is equal to `format!("{:#?}", actual)`. + pub fn assert_debug_eq(&self, actual: &impl fmt::Debug) { + let actual = format!("{actual:#?}\n"); + self.assert_eq(&actual) + } + + /// Returns the content of this expect. + pub fn data(&self) -> String { + fs::read_to_string(self.abs_path()).unwrap_or_default().replace("\r\n", "\n") + } + + fn write(&self, contents: &str) { + fs::write(self.abs_path(), contents).unwrap() + } + + fn abs_path(&self) -> PathBuf { + if self.path.is_absolute() { + self.path.to_owned() + } else { + let dir = Path::new(self.position).parent().unwrap(); + to_abs_ws_path(&dir.join(&self.path)) + } + } +} + +#[derive(Default)] +struct Runtime { + help_printed: bool, + per_file: HashMap<&'static str, FileRuntime>, +} +static RT: Lazy> = Lazy::new(Default::default); + +impl Runtime { + fn fail_expect(expect: &Expect, expected: &str, actual: &str) { + let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + if update_expect() { + println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.position); + rt.per_file + .entry(expect.position.file) + .or_insert_with(|| FileRuntime::new(expect)) + .update(expect, actual); + return; + } + rt.panic(expect.position.to_string(), expected, actual); + } + + fn fail_file(expect: &ExpectFile, expected: &str, actual: &str) { + let mut rt = RT.lock().unwrap_or_else(|poisoned| poisoned.into_inner()); + if update_expect() { + println!("\x1b[1m\x1b[92mupdating\x1b[0m: {}", expect.path.display()); + expect.write(actual); + return; + } + rt.panic(expect.path.display().to_string(), expected, actual); + } + + fn panic(&mut self, position: String, expected: &str, actual: &str) { + let print_help = !mem::replace(&mut self.help_printed, true); + let help = if print_help { HELP } else { "" }; + + let diff = format_unified_diff(expected, actual); + + println!( + "\n +\x1b[1m\x1b[91merror\x1b[97m: expect test failed\x1b[0m + \x1b[1m\x1b[34m-->\x1b[0m {position} +{help} +\x1b[1mExpect\x1b[0m: +---- +{expected} +---- + +\x1b[1mActual\x1b[0m: +---- +{actual} +---- + +\x1b[1mDiff\x1b[0m: +---- +{diff} +---- +" + ); + // Use resume_unwind instead of panic!() to prevent a backtrace, which is unnecessary noise. + panic::resume_unwind(Box::new(())); + } +} + +struct FileRuntime { + path: PathBuf, + original_text: String, + patchwork: Patchwork, +} + +impl FileRuntime { + fn new(expect: &Expect) -> FileRuntime { + let path = to_abs_ws_path(Path::new(expect.position.file)); + let original_text = fs::read_to_string(&path).unwrap(); + let patchwork = Patchwork::new(original_text.clone()); + FileRuntime { + path, + original_text, + patchwork, + } + } + + fn update(&mut self, expect: &Expect, actual: &str) { + let loc = expect.locate(&self.original_text); + let desired_indent = if expect.indent { + Some(loc.line_indent) + } else { + None + }; + let patch = format_patch(desired_indent, actual); + self.patchwork.patch(loc.literal_range, &patch); + fs::write(&self.path, &self.patchwork.text).unwrap() + } +} + +#[derive(Debug)] +struct Location { + line_indent: usize, + + /// The byte range of the argument to `expect!`, including the inner `[]` if it exists. + literal_range: Range, +} + +#[derive(Debug)] +struct Patchwork { + text: String, + indels: Vec<(Range, usize)>, +} + +impl Patchwork { + fn new(text: String) -> Patchwork { + Patchwork { + text, + indels: Vec::new(), + } + } + + fn patch(&mut self, mut range: Range, patch: &str) { + self.indels.push((range.clone(), patch.len())); + self.indels.sort_by_key(|(delete, _insert)| delete.start); + + let (delete, insert) = self + .indels + .iter() + .take_while(|(delete, _)| delete.start < range.start) + .map(|(delete, insert)| (delete.end - delete.start, insert)) + .fold((0usize, 0usize), |(x1, y1), (x2, y2)| (x1 + x2, y1 + y2)); + + for pos in &mut [&mut range.start, &mut range.end] { + **pos -= delete; + **pos += insert; + } + + self.text.replace_range(range, patch); + } +} + +fn lit_kind_for_patch(patch: &str) -> StrLitKind { + let has_dquote = patch.chars().any(|c| c == '"'); + if !has_dquote { + let has_bslash_or_newline = patch.chars().any(|c| matches!(c, '\\' | '\n')); + return if has_bslash_or_newline { + StrLitKind::Raw(1) + } else { + StrLitKind::Normal + }; + } + + // Find the maximum number of hashes that follow a double quote in the string. + // We need to use one more than that to delimit the string. + let leading_hashes = |s: &str| s.chars().take_while(|&c| c == '#').count(); + let max_hashes = patch.split('"').map(leading_hashes).max().unwrap(); + StrLitKind::Raw(max_hashes + 1) +} + +fn format_patch(desired_indent: Option, patch: &str) -> String { + let lit_kind = lit_kind_for_patch(patch); + let indent = desired_indent.map(|it| " ".repeat(it)); + let is_multiline = patch.contains('\n'); + + let mut buf = String::new(); + if matches!(lit_kind, StrLitKind::Raw(_)) { + buf.push('['); + } + lit_kind.write_start(&mut buf).unwrap(); + if is_multiline { + buf.push('\n'); + } + let mut final_newline = false; + for line in lines_with_ends(patch) { + if is_multiline && !line.trim().is_empty() { + if let Some(indent) = &indent { + buf.push_str(indent); + buf.push_str(" "); + } + } + buf.push_str(line); + final_newline = line.ends_with('\n'); + } + if final_newline { + if let Some(indent) = &indent { + buf.push_str(indent); + } + } + lit_kind.write_end(&mut buf).unwrap(); + if matches!(lit_kind, StrLitKind::Raw(_)) { + buf.push(']'); + } + buf +} + +fn to_abs_ws_path(path: &Path) -> PathBuf { + if path.is_absolute() { + return path.to_owned(); + } + + static WORKSPACE_ROOT: OnceCell = OnceCell::new(); + WORKSPACE_ROOT + .get_or_try_init(|| { + // Until https://github.com/rust-lang/cargo/issues/3946 is resolved, this + // is set with a hack like https://github.com/rust-lang/cargo/issues/3946#issuecomment-973132993 + if let Ok(workspace_root) = env::var("CARGO_WORKSPACE_DIR") { + return Ok(workspace_root.into()); + } + + // If a hack isn't used, we use a heuristic to find the "top-level" workspace. + // This fails in some cases, see https://github.com/rust-analyzer/expect-test/issues/33 + let my_manifest = env::var("CARGO_MANIFEST_DIR")?; + let workspace_root = Path::new(&my_manifest) + .ancestors() + .filter(|it| it.join("Cargo.toml").exists()) + .last() + .unwrap() + .to_path_buf(); + + Ok(workspace_root) + }) + .unwrap_or_else(|_: env::VarError| { + panic!("No CARGO_MANIFEST_DIR env var and the path is relative: {}", path.display()) + }) + .join(path) +} + +fn trim_indent(mut text: &str) -> String { + if text.starts_with('\n') { + text = &text[1..]; + } + let indent = text + .lines() + .filter(|it| !it.trim().is_empty()) + .map(|it| it.len() - it.trim_start().len()) + .min() + .unwrap_or(0); + + lines_with_ends(text) + .map(|line| { + if line.len() <= indent { + line.trim_start_matches(' ') + } else { + &line[indent..] + } + }) + .collect() +} + +fn lines_with_ends(text: &str) -> LinesWithEnds<'_> { + LinesWithEnds { text } +} + +struct LinesWithEnds<'a> { + text: &'a str, +} + +impl<'a> Iterator for LinesWithEnds<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option<&'a str> { + if self.text.is_empty() { + return None; + } + let idx = self.text.find('\n').map_or(self.text.len(), |it| it + 1); + let (res, next) = self.text.split_at(idx); + self.text = next; + Some(res) + } +} + +fn format_unified_diff(expected: &str, actual: &str) -> String { + use similar::{ChangeTag, TextDiff}; + + let diff = TextDiff::from_lines(expected, actual); + let mut result = String::new(); + + for (idx, group) in diff.grouped_ops(3).into_iter().enumerate() { + if idx > 0 { + result.push('\n'); + } + for op in group { + for change in diff.iter_changes(&op) { + let (sign, color) = match change.tag() { + ChangeTag::Delete => ("-", "\x1b[31m"), // red + ChangeTag::Insert => ("+", "\x1b[32m"), // green + ChangeTag::Equal => (" ", ""), + }; + + result.push_str(color); + result.push_str(sign); + result.push(' '); + + let line = change.value(); + result.push_str(line); + if !line.ends_with('\n') { + result.push('\n'); + } + + if !color.is_empty() { + result.push_str("\x1b[0m"); + } + } + } + } + + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_trivial_assert() { + expect!["5"].assert_eq("5"); + } + + #[test] + fn test_format_patch() { + let patch = format_patch(None, "hello\nworld\n"); + expect![[r##" + [r#" + hello + world + "#]"##]] + .assert_eq(&patch); + + let patch = format_patch(None, r"hello\tworld"); + expect![[r##"[r#"hello\tworld"#]"##]].assert_eq(&patch); + + let patch = format_patch(None, "{\"foo\": 42}"); + expect![[r##"[r#"{"foo": 42}"#]"##]].assert_eq(&patch); + + let patch = format_patch(Some(0), "hello\nworld\n"); + expect![[r##" + [r#" + hello + world + "#]"##]] + .assert_eq(&patch); + + let patch = format_patch(Some(4), "single line"); + expect![[r#""single line""#]].assert_eq(&patch); + } + + #[test] + fn test_patchwork() { + let mut patchwork = Patchwork::new("one two three".to_string()); + patchwork.patch(4..7, "zwei"); + patchwork.patch(0..3, "один"); + patchwork.patch(8..13, "3"); + expect![[r#" + Patchwork { + text: "один zwei 3", + indels: [ + ( + 0..3, + 8, + ), + ( + 4..7, + 4, + ), + ( + 8..13, + 1, + ), + ], + } + "#]] + .assert_debug_eq(&patchwork); + } + + #[test] + fn test_expect_file() { + expect_file!["./lib.rs"].assert_eq(include_str!("./lib.rs")) + } + + #[test] + fn smoke_test_indent() { + fn check_indented(input: &str, mut expect: Expect) { + expect.indent(true); + expect.assert_eq(input); + } + fn check_not_indented(input: &str, mut expect: Expect) { + expect.indent(false); + expect.assert_eq(input); + } + + check_indented( + "\ +line1 + line2 +", + expect![[r#" + line1 + line2 + "#]], + ); + + check_not_indented( + "\ +line1 + line2 +", + expect![[r#" +line1 + line2 +"#]], + ); + } + + #[test] + fn test_locate() { + macro_rules! check_locate { + ($( [[$s:literal]] ),* $(,)?) => {$({ + let lit = stringify!($s); + let with_trailer = format!("{} \t]]\n", lit); + assert_eq!(locate_end(&with_trailer), Some(lit.len())); + })*}; + } + + // Check that we handle string literals containing "]]" correctly. + check_locate!( + [[r#"{ arr: [[1, 2], [3, 4]], other: "foo" } "#]], + [["]]"]], + [["\"]]"]], + [[r#""]]"#]], + ); + + // Check `expect![[ ]]` as well. + assert_eq!(locate_end("]]"), Some(0)); + } + + #[test] + fn test_find_str_lit_len() { + macro_rules! check_str_lit_len { + ($( $s:literal ),* $(,)?) => {$({ + let lit = stringify!($s); + assert_eq!(find_str_lit_len(lit), Some(lit.len())); + })*} + } + + check_str_lit_len![ + r##"foa\""#"##, + r##" + + asdf][]]""""# + "##, + "", + "\"", + "\"\"", + "#\"#\"#", + ]; + } + + #[test] + fn test_format_unified_diff_insertions() { + // Insertion at the beginning + let result = format_unified_diff("world", "Hello world"); + expect![ + "- world ++ Hello world +" + ] + .assert_eq(&result); + + // Insertion in the middle + let result = format_unified_diff("Hello world", "Hello beautiful world"); + expect![ + "- Hello world ++ Hello beautiful world +" + ] + .assert_eq(&result); + + // Insertion at the end + let result = format_unified_diff("Hello world", "Hello world!"); + expect![ + "- Hello world ++ Hello world! +" + ] + .assert_eq(&result); + } + + #[test] + fn test_format_unified_diff_deletions() { + // Deletion at the beginning + let result = format_unified_diff("Hello world", "world"); + expect![ + "- Hello world ++ world +" + ] + .assert_eq(&result); + + // Deletion in the middle + let result = format_unified_diff("Hello beautiful world", "Hello world"); + expect![ + "- Hello beautiful world ++ Hello world +" + ] + .assert_eq(&result); + + // Deletion at the end + let result = format_unified_diff("Hello world!", "Hello world"); + expect![ + "- Hello world! ++ Hello world +" + ] + .assert_eq(&result); + } + + #[test] + fn test_format_unified_diff_mixed() { + // Mixed insertion and deletion + let result = format_unified_diff("The quick brown fox", "The slow brown fox"); + expect![ + "- The quick brown fox ++ The slow brown fox +" + ] + .assert_eq(&result); + } +}